Initial extract: templates + methodology guide from Ideation
Public, version-controlled source of truth for TeamIDE's project templates, coding conventions, and the build-plan → PM scaffolding rules. Mounted read-only by TeamIDE's planner agent at runtime. Extracted from the personal Ideation repo as a precondition for workspace-projects-agent-spec.md §9.4. Personal Ideation stays private; this repo holds only the publishable methodology.
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "tauri-starter"
|
||||
version = "0.1.0"
|
||||
description = "Tauri Starter App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "1.6", features = ["shell-open"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
chrono = "0.4"
|
||||
tiny_http = "0.12" # For automation HTTP server
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol", "automation"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
automation = [] # Enable automation HTTP server on port 9876
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
|
After Width: | Height: | Size: 299 B |
|
After Width: | Height: | Size: 855 B |
|
After Width: | Height: | Size: 104 B |
|
After Width: | Height: | Size: 239 B |
|
After Width: | Height: | Size: 331 B |
|
After Width: | Height: | Size: 349 B |
|
After Width: | Height: | Size: 975 B |
|
After Width: | Height: | Size: 103 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 121 B |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 199 B |
|
After Width: | Height: | Size: 130 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,166 @@
|
||||
# Tauri Automation Plugin
|
||||
|
||||
A lightweight, reusable automation layer for Tauri applications. Enables external tools to interact with your app via HTTP API.
|
||||
|
||||
## Features
|
||||
|
||||
- **HTTP API** on `localhost:9876` for external automation
|
||||
- **Navigation**: Route to pages
|
||||
- **Element interaction**: Click, type, clear, select
|
||||
- **Reading**: Get text, values, attributes, check existence
|
||||
- **Waiting**: Wait for elements with timeout
|
||||
- **Screenshots**: Capture page as PNG
|
||||
- **Debug mode only**: Automatically disabled in release builds
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ HTTP ┌──────────────────┐
|
||||
│ External Tool │ ───────────▶ │ Rust HTTP Server │
|
||||
│ (curl, tests) │ │ (port 9876) │
|
||||
└─────────────────┘ └────────┬─────────┘
|
||||
│
|
||||
│ Tauri Command
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ JavaScript │
|
||||
│ Automation │
|
||||
│ Service │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
│ DOM APIs
|
||||
▼
|
||||
┌──────────────────┐
|
||||
│ WebView │
|
||||
│ (Your App) │
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
## HTTP API
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Path | Description |
|
||||
|--------|------|-------------|
|
||||
| POST | `/automation/execute` | Execute a command |
|
||||
| GET | `/automation/screenshot` | Capture screenshot (PNG) |
|
||||
| GET | `/automation/health` | Health check |
|
||||
|
||||
### Commands
|
||||
|
||||
```bash
|
||||
# Navigate to a route
|
||||
curl -X POST http://localhost:9876/automation/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command": "navigate", "args": {"path": "/profile"}}'
|
||||
|
||||
# Click an element
|
||||
curl -X POST http://localhost:9876/automation/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command": "click", "args": {"selector": "#submit-btn"}}'
|
||||
|
||||
# Type into an input
|
||||
curl -X POST http://localhost:9876/automation/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command": "type", "args": {"selector": "#email", "text": "test@example.com"}}'
|
||||
|
||||
# Get element text
|
||||
curl -X POST http://localhost:9876/automation/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command": "getText", "args": {"selector": ".heading"}}'
|
||||
|
||||
# Wait for element
|
||||
curl -X POST http://localhost:9876/automation/execute \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"command": "waitFor", "args": {"selector": ".loaded", "timeout": 5000}}'
|
||||
|
||||
# Take screenshot
|
||||
curl http://localhost:9876/automation/screenshot --output screenshot.png
|
||||
|
||||
# Health check
|
||||
curl http://localhost:9876/automation/health
|
||||
```
|
||||
|
||||
### Available Commands
|
||||
|
||||
| Command | Args | Returns | Description |
|
||||
|---------|------|---------|-------------|
|
||||
| `navigate` | `{path}` | `null` | Navigate to route |
|
||||
| `click` | `{selector}` | `null` | Click element |
|
||||
| `type` | `{selector, text}` | `null` | Type into input |
|
||||
| `clear` | `{selector}` | `null` | Clear input value |
|
||||
| `getText` | `{selector}` | `string` | Get element text |
|
||||
| `getValue` | `{selector}` | `string` | Get input value |
|
||||
| `getAttribute` | `{selector, attribute}` | `string` | Get attribute |
|
||||
| `exists` | `{selector}` | `boolean` | Check if element exists |
|
||||
| `waitFor` | `{selector, timeout?}` | `boolean` | Wait for element (default 5s) |
|
||||
| `select` | `{selector, value}` | `null` | Select dropdown option |
|
||||
| `check` | `{selector}` | `null` | Check checkbox |
|
||||
| `uncheck` | `{selector}` | `null` | Uncheck checkbox |
|
||||
| `getUrl` | `{}` | `string` | Get current URL |
|
||||
| `getTitle` | `{}` | `string` | Get page title |
|
||||
| `eval` | `{script}` | `any` | Evaluate JavaScript |
|
||||
|
||||
## Installation (Adding to Another Project)
|
||||
|
||||
### 1. Copy Rust files
|
||||
|
||||
Copy `src-tauri/src/automation/` to your project's `src-tauri/src/`.
|
||||
|
||||
### 2. Update Cargo.toml
|
||||
|
||||
Add dependencies:
|
||||
```toml
|
||||
[dependencies]
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
warp = "0.3"
|
||||
serde_json = "1.0"
|
||||
```
|
||||
|
||||
### 3. Update main.rs
|
||||
|
||||
```rust
|
||||
#[cfg(debug_assertions)]
|
||||
mod automation;
|
||||
|
||||
fn main() {
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
builder = builder
|
||||
.plugin(automation::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
automation::commands::execute_automation
|
||||
]);
|
||||
}
|
||||
|
||||
builder
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Copy JS files
|
||||
|
||||
Copy `src/automation/` to your project's `src/`.
|
||||
|
||||
### 5. Initialize in App.vue
|
||||
|
||||
```typescript
|
||||
import { initAutomation } from './automation'
|
||||
|
||||
onMounted(() => {
|
||||
initAutomation()
|
||||
})
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
- **Debug builds only**: The HTTP server only runs in debug builds (`#[cfg(debug_assertions)]`)
|
||||
- **Localhost only**: Server binds to `127.0.0.1`, not accessible externally
|
||||
- **No auth**: Designed for local development/testing only
|
||||
|
||||
## License
|
||||
|
||||
MIT - Use freely in your projects.
|
||||
@@ -0,0 +1,78 @@
|
||||
//! Tauri Automation Plugin
|
||||
//!
|
||||
//! Provides HTTP API for external automation of the application.
|
||||
//! Enabled via the "automation" feature flag.
|
||||
|
||||
pub mod server;
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Manager, Runtime, Window,
|
||||
};
|
||||
|
||||
/// Shared state for automation
|
||||
pub struct AutomationState {
|
||||
pub window: Arc<Mutex<Option<Window<tauri::Wry>>>>,
|
||||
}
|
||||
|
||||
impl Default for AutomationState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
window: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize the automation plugin
|
||||
pub fn init() -> TauriPlugin<tauri::Wry> {
|
||||
Builder::new("automation")
|
||||
.setup(|app| {
|
||||
let state = AutomationState::default();
|
||||
app.manage(state);
|
||||
|
||||
// Start HTTP server in background thread
|
||||
let app_handle = app.clone();
|
||||
std::thread::spawn(move || {
|
||||
server::start_server(app_handle);
|
||||
});
|
||||
|
||||
println!("[Automation] Plugin initialized - HTTP server starting on port 9876");
|
||||
Ok(())
|
||||
})
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Execute an automation command via JavaScript evaluation
|
||||
pub fn execute_command(window: &Window<tauri::Wry>, command: &str, args: &serde_json::Value) -> Result<serde_json::Value, String> {
|
||||
let args_json = serde_json::to_string(args).map_err(|e| e.to_string())?;
|
||||
|
||||
let script = format!(
|
||||
r#"
|
||||
(async function() {{
|
||||
try {{
|
||||
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {{
|
||||
return JSON.stringify({{ error: 'Automation not initialized. Call initAutomation() first.' }});
|
||||
}}
|
||||
const result = await window.__TAURI_AUTOMATION__.execute('{}', {});
|
||||
return JSON.stringify({{ success: true, result: result }});
|
||||
}} catch (e) {{
|
||||
return JSON.stringify({{ error: e.message || String(e) }});
|
||||
}}
|
||||
}})()
|
||||
"#,
|
||||
command,
|
||||
args_json
|
||||
);
|
||||
|
||||
// Execute JavaScript and get result
|
||||
let result = window.eval(&script);
|
||||
|
||||
match result {
|
||||
Ok(_) => {
|
||||
// For Tauri 1.x, eval doesn't return a value directly
|
||||
Ok(serde_json::json!({ "success": true, "note": "Command sent to webview" }))
|
||||
}
|
||||
Err(e) => Err(format!("Failed to execute script: {}", e))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
//! HTTP server for automation commands
|
||||
|
||||
use std::io::Read;
|
||||
use std::sync::Mutex;
|
||||
use tauri::{AppHandle, Manager};
|
||||
use tiny_http::{Server, Response, Header, Method};
|
||||
|
||||
const PORT: u16 = 9876;
|
||||
|
||||
// Global screenshot buffer (simple approach without lazy_static)
|
||||
static SCREENSHOT_DATA: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
pub fn set_screenshot_data(data: String) {
|
||||
if let Ok(mut guard) = SCREENSHOT_DATA.lock() {
|
||||
*guard = Some(data);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_screenshot_data() -> Option<String> {
|
||||
if let Ok(mut guard) = SCREENSHOT_DATA.lock() {
|
||||
guard.take()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the HTTP server
|
||||
pub fn start_server(app_handle: AppHandle<tauri::Wry>) {
|
||||
let addr = format!("127.0.0.1:{}", PORT);
|
||||
|
||||
let server = match Server::http(&addr) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("[Automation] Failed to start server on {}: {}", addr, e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
println!("[Automation] HTTP server listening on http://{}", addr);
|
||||
|
||||
for mut request in server.incoming_requests() {
|
||||
let method = request.method().clone();
|
||||
let url = request.url().to_string();
|
||||
|
||||
println!("[Automation] {} {}", method, url);
|
||||
|
||||
let response = match (&method, url.as_str()) {
|
||||
// Health check
|
||||
(&Method::Get, "/automation/health") => {
|
||||
json_response(serde_json::json!({
|
||||
"status": "ok",
|
||||
"port": PORT,
|
||||
"version": "1.0.0"
|
||||
}))
|
||||
}
|
||||
|
||||
// Execute command
|
||||
(&Method::Post, "/automation/execute") => {
|
||||
handle_execute(&app_handle, &mut request)
|
||||
}
|
||||
|
||||
// Screenshot
|
||||
(&Method::Get, "/automation/screenshot") => {
|
||||
handle_screenshot(&app_handle)
|
||||
}
|
||||
|
||||
// CORS preflight
|
||||
(&Method::Options, _) => {
|
||||
cors_response()
|
||||
}
|
||||
|
||||
// 404
|
||||
_ => {
|
||||
json_response_with_status(
|
||||
serde_json::json!({ "error": "Not found" }),
|
||||
404
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = request.respond(response) {
|
||||
eprintln!("[Automation] Failed to send response: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle execute command request
|
||||
fn handle_execute(
|
||||
app_handle: &AppHandle<tauri::Wry>,
|
||||
request: &mut tiny_http::Request,
|
||||
) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
// Read body
|
||||
let mut body = String::new();
|
||||
if let Err(e) = request.as_reader().read_to_string(&mut body) {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": format!("Failed to read body: {}", e) }),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
let payload: serde_json::Value = match serde_json::from_str(&body) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": format!("Invalid JSON: {}", e) }),
|
||||
400
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let command = match payload.get("command").and_then(|v| v.as_str()) {
|
||||
Some(c) => c.to_string(),
|
||||
None => {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": "Missing 'command' field" }),
|
||||
400
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let args = payload.get("args").cloned().unwrap_or(serde_json::json!({}));
|
||||
|
||||
// Get the main window
|
||||
let window = match app_handle.get_window("main") {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": "Main window not found" }),
|
||||
500
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Build JavaScript to execute
|
||||
let args_json = serde_json::to_string(&args).unwrap_or_else(|_| "{}".to_string());
|
||||
let script = format!(
|
||||
r#"
|
||||
(async function() {{
|
||||
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {{
|
||||
console.error('[Automation] Not initialized');
|
||||
return;
|
||||
}}
|
||||
try {{
|
||||
const result = await window.__TAURI_AUTOMATION__.execute('{}', {});
|
||||
window.__TAURI_AUTOMATION__._lastResult = {{ success: true, result: result }};
|
||||
}} catch (e) {{
|
||||
window.__TAURI_AUTOMATION__._lastResult = {{ success: false, error: e.message || String(e) }};
|
||||
}}
|
||||
}})();
|
||||
"#,
|
||||
command,
|
||||
args_json
|
||||
);
|
||||
|
||||
// Execute the script
|
||||
if let Err(e) = window.eval(&script) {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": format!("Script execution failed: {}", e) }),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
// Wait a bit for async commands to complete
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
|
||||
// Return success - the result is stored in the webview for debugging
|
||||
json_response(serde_json::json!({
|
||||
"success": true,
|
||||
"message": "Command executed",
|
||||
"command": command
|
||||
}))
|
||||
}
|
||||
|
||||
/// Handle screenshot request
|
||||
fn handle_screenshot(app_handle: &AppHandle<tauri::Wry>) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
let window = match app_handle.get_window("main") {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": "Main window not found" }),
|
||||
500
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Request screenshot from JS
|
||||
let script = r#"
|
||||
(async function() {
|
||||
if (typeof window.__TAURI_AUTOMATION__ === 'undefined') {
|
||||
console.error('[Automation] Not initialized');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await window.__TAURI_AUTOMATION__.captureAndSend();
|
||||
} catch (e) {
|
||||
console.error('[Automation] Screenshot failed:', e);
|
||||
}
|
||||
})();
|
||||
"#;
|
||||
|
||||
if let Err(e) = window.eval(script) {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": format!("Screenshot request failed: {}", e) }),
|
||||
500
|
||||
);
|
||||
}
|
||||
|
||||
// Wait for JS to send the screenshot data
|
||||
// html2canvas can take a while, especially on first load
|
||||
std::thread::sleep(std::time::Duration::from_millis(2000));
|
||||
|
||||
// Check if we have screenshot data
|
||||
if let Some(data_url) = take_screenshot_data() {
|
||||
// Parse data URL: data:image/png;base64,....
|
||||
if let Some(base64_data) = data_url.strip_prefix("data:image/png;base64,") {
|
||||
match base64_decode(base64_data) {
|
||||
Ok(bytes) => {
|
||||
return png_response(bytes);
|
||||
}
|
||||
Err(e) => {
|
||||
return json_response_with_status(
|
||||
serde_json::json!({ "error": format!("Base64 decode failed: {}", e) }),
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_response_with_status(
|
||||
serde_json::json!({ "error": "Screenshot not available. Make sure html2canvas is loaded." }),
|
||||
500
|
||||
)
|
||||
}
|
||||
|
||||
/// Simple base64 decoder
|
||||
fn base64_decode(input: &str) -> Result<Vec<u8>, String> {
|
||||
let input = input.trim();
|
||||
let chars: Vec<char> = input.chars().filter(|c| !c.is_whitespace()).collect();
|
||||
|
||||
const ALPHABET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
let mut output = Vec::new();
|
||||
let mut buffer: u32 = 0;
|
||||
let mut bits_collected = 0;
|
||||
|
||||
for c in chars {
|
||||
if c == '=' {
|
||||
break;
|
||||
}
|
||||
|
||||
let value = ALPHABET.iter().position(|&x| x == c as u8)
|
||||
.ok_or_else(|| format!("Invalid base64 character: {}", c))? as u32;
|
||||
|
||||
buffer = (buffer << 6) | value;
|
||||
bits_collected += 6;
|
||||
|
||||
if bits_collected >= 8 {
|
||||
bits_collected -= 8;
|
||||
output.push((buffer >> bits_collected) as u8);
|
||||
buffer &= (1 << bits_collected) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Create a JSON response
|
||||
fn json_response(data: serde_json::Value) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
json_response_with_status(data, 200)
|
||||
}
|
||||
|
||||
/// Create a JSON response with status
|
||||
fn json_response_with_status(data: serde_json::Value, status: u16) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
let body = serde_json::to_vec(&data).unwrap_or_else(|_| b"{}".to_vec());
|
||||
let len = body.len();
|
||||
let cursor = std::io::Cursor::new(body);
|
||||
|
||||
Response::new(
|
||||
tiny_http::StatusCode(status),
|
||||
vec![
|
||||
Header::from_bytes(&b"Content-Type"[..], &b"application/json"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Methods"[..], &b"GET, POST, OPTIONS"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Headers"[..], &b"Content-Type"[..]).unwrap(),
|
||||
],
|
||||
cursor,
|
||||
Some(len),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a PNG response
|
||||
fn png_response(data: Vec<u8>) -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
let len = data.len();
|
||||
let cursor = std::io::Cursor::new(data);
|
||||
|
||||
Response::new(
|
||||
tiny_http::StatusCode(200),
|
||||
vec![
|
||||
Header::from_bytes(&b"Content-Type"[..], &b"image/png"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
|
||||
],
|
||||
cursor,
|
||||
Some(len),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
/// CORS preflight response
|
||||
fn cors_response() -> Response<std::io::Cursor<Vec<u8>>> {
|
||||
let cursor = std::io::Cursor::new(Vec::new());
|
||||
|
||||
Response::new(
|
||||
tiny_http::StatusCode(204),
|
||||
vec![
|
||||
Header::from_bytes(&b"Access-Control-Allow-Origin"[..], &b"*"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Methods"[..], &b"GET, POST, OPTIONS"[..]).unwrap(),
|
||||
Header::from_bytes(&b"Access-Control-Allow-Headers"[..], &b"Content-Type"[..]).unwrap(),
|
||||
],
|
||||
cursor,
|
||||
Some(0),
|
||||
None,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Prevents additional console window on Windows in release
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use chrono::Local;
|
||||
|
||||
// Automation module - enabled via feature flag
|
||||
#[cfg(feature = "automation")]
|
||||
mod automation;
|
||||
|
||||
/// Log frontend console messages to stdout with timestamp
|
||||
#[tauri::command]
|
||||
fn log_to_stdout(level: String, message: String) {
|
||||
let timestamp = Local::now().format("%Y-%m-%d %H:%M:%S%.3f");
|
||||
println!("[{}] [{}] {}", timestamp, level, message);
|
||||
}
|
||||
|
||||
/// Example command - replace with your own
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! Welcome to Tauri.", name)
|
||||
}
|
||||
|
||||
/// Receive screenshot data from JS automation
|
||||
#[cfg(feature = "automation")]
|
||||
#[tauri::command]
|
||||
fn automation_screenshot_data(data: String) {
|
||||
automation::server::set_screenshot_data(data);
|
||||
}
|
||||
|
||||
#[cfg(feature = "automation")]
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.plugin(automation::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
log_to_stdout,
|
||||
greet,
|
||||
automation_screenshot_data
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "automation"))]
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
log_to_stdout,
|
||||
greet
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"devPath": "http://localhost:9000",
|
||||
"distDir": "../dist"
|
||||
},
|
||||
"package": {
|
||||
"productName": "TauriStarter",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"all": false,
|
||||
"shell": {
|
||||
"open": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"identifier": "com.tauristarter.app",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"fullscreen": false,
|
||||
"resizable": true,
|
||||
"title": "Tauri Starter",
|
||||
"width": 1200,
|
||||
"height": 800,
|
||||
"minWidth": 800,
|
||||
"minHeight": 600
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||