tauri-agent-tools 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/tauri-agent-tools/SKILL.md +1 -1
- package/.agents/skills/tauri-bridge-setup/SKILL.md +22 -3
- package/examples/tauri-bridge/Cargo.toml +1 -0
- package/examples/tauri-bridge/src/dev_bridge.rs +108 -36
- package/examples/tauri-bridge/src/main.rs +9 -2
- package/package.json +1 -1
- package/rust-bridge/README.md +26 -5
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tauri-bridge-setup
|
|
3
3
|
description: How to add the tauri-agent-tools Rust dev bridge to a Tauri application
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
tags: [tauri, rust, bridge, setup, integration]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -21,6 +21,7 @@ serde = { version = "1", features = ["derive"] }
|
|
|
21
21
|
serde_json = "1"
|
|
22
22
|
scopeguard = "1"
|
|
23
23
|
rand = "0.8"
|
|
24
|
+
uuid = { version = "1", features = ["v4"] }
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
## Step 2 — Copy the bridge module
|
|
@@ -43,13 +44,21 @@ cp node_modules/tauri-agent-tools/examples/tauri-bridge/src/dev_bridge.rs src-ta
|
|
|
43
44
|
|
|
44
45
|
## Step 3 — Wire up in main.rs
|
|
45
46
|
|
|
46
|
-
Add the module declaration and start the bridge in your `src-tauri/src/main.rs`:
|
|
47
|
+
Add the module declaration, register the bridge command, and start the bridge in your `src-tauri/src/main.rs`:
|
|
47
48
|
|
|
48
49
|
```rust
|
|
49
50
|
mod dev_bridge;
|
|
50
51
|
|
|
51
52
|
fn main() {
|
|
52
|
-
tauri::Builder::default()
|
|
53
|
+
let mut builder = tauri::Builder::default();
|
|
54
|
+
|
|
55
|
+
if cfg!(debug_assertions) {
|
|
56
|
+
builder = builder.invoke_handler(tauri::generate_handler![
|
|
57
|
+
dev_bridge::__dev_bridge_result
|
|
58
|
+
]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
builder
|
|
53
62
|
.setup(|app| {
|
|
54
63
|
if cfg!(debug_assertions) {
|
|
55
64
|
if let Err(e) = dev_bridge::start_bridge(app.handle()) {
|
|
@@ -63,6 +72,16 @@ fn main() {
|
|
|
63
72
|
}
|
|
64
73
|
```
|
|
65
74
|
|
|
75
|
+
If you already have an `.invoke_handler()` with your own commands, merge them into one handler:
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
builder = builder.invoke_handler(tauri::generate_handler![
|
|
79
|
+
your_command_one,
|
|
80
|
+
your_command_two,
|
|
81
|
+
dev_bridge::__dev_bridge_result,
|
|
82
|
+
]);
|
|
83
|
+
```
|
|
84
|
+
|
|
66
85
|
If you already have a `.setup()` call, add the `if cfg!(debug_assertions) { ... }` block inside it.
|
|
67
86
|
|
|
68
87
|
## Step 4 — Verify
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
use rand::Rng;
|
|
2
2
|
use serde::{Deserialize, Serialize};
|
|
3
|
+
use std::collections::HashMap;
|
|
3
4
|
use std::fs;
|
|
4
5
|
use std::io::Write;
|
|
5
|
-
use std::sync::Arc;
|
|
6
|
+
use std::sync::{Arc, Condvar, Mutex};
|
|
6
7
|
use std::thread;
|
|
7
8
|
use tauri::{AppHandle, Manager};
|
|
8
9
|
use tiny_http::{Header, Response, Server};
|
|
@@ -25,10 +26,31 @@ struct TokenFile {
|
|
|
25
26
|
pid: u32,
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
/// Shared state for pending eval results.
|
|
30
|
+
/// The HTTP handler thread waits on the Condvar; the Tauri command inserts
|
|
31
|
+
/// the result and signals.
|
|
32
|
+
pub struct PendingResults {
|
|
33
|
+
results: Mutex<HashMap<String, serde_json::Value>>,
|
|
34
|
+
notify: Condvar,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Tauri command invoked from injected JS to deliver eval results back to Rust.
|
|
38
|
+
#[tauri::command]
|
|
39
|
+
pub fn __dev_bridge_result(
|
|
40
|
+
id: String,
|
|
41
|
+
value: serde_json::Value,
|
|
42
|
+
state: tauri::State<'_, Arc<PendingResults>>,
|
|
43
|
+
) {
|
|
44
|
+
let mut results = state.results.lock().unwrap();
|
|
45
|
+
results.insert(id, value);
|
|
46
|
+
state.notify.notify_all();
|
|
47
|
+
}
|
|
48
|
+
|
|
28
49
|
/// Start the development bridge HTTP server.
|
|
29
50
|
/// Returns the port number on success.
|
|
30
51
|
pub fn start_bridge(app: &AppHandle) -> Result<u16, String> {
|
|
31
|
-
let server =
|
|
52
|
+
let server =
|
|
53
|
+
Server::http("127.0.0.1:0").map_err(|e| format!("Failed to start bridge: {e}"))?;
|
|
32
54
|
let port = server
|
|
33
55
|
.server_addr()
|
|
34
56
|
.to_ip()
|
|
@@ -58,6 +80,13 @@ pub fn start_bridge(app: &AppHandle) -> Result<u16, String> {
|
|
|
58
80
|
let _ = fs::remove_file(&cleanup_path);
|
|
59
81
|
});
|
|
60
82
|
|
|
83
|
+
// Create shared pending-results state and register it with Tauri
|
|
84
|
+
let pending = Arc::new(PendingResults {
|
|
85
|
+
results: Mutex::new(HashMap::new()),
|
|
86
|
+
notify: Condvar::new(),
|
|
87
|
+
});
|
|
88
|
+
app.manage(pending.clone());
|
|
89
|
+
|
|
61
90
|
let app_handle = app.clone();
|
|
62
91
|
let expected_token = token.clone();
|
|
63
92
|
|
|
@@ -96,45 +125,88 @@ pub fn start_bridge(app: &AppHandle) -> Result<u16, String> {
|
|
|
96
125
|
continue;
|
|
97
126
|
}
|
|
98
127
|
|
|
99
|
-
// Evaluate JS in webview
|
|
100
|
-
let
|
|
101
|
-
let js = eval_req.js.clone();
|
|
128
|
+
// Evaluate JS in webview via callback pattern
|
|
129
|
+
let request_id = uuid::Uuid::new_v4().to_string();
|
|
102
130
|
|
|
103
131
|
if let Some(window) = app_handle.get_webview_window("main") {
|
|
104
|
-
|
|
132
|
+
// Build JS that evaluates the expression, then calls back into Rust
|
|
133
|
+
// via __TAURI__.core.invoke() to deliver the result.
|
|
134
|
+
let callback_js = format!(
|
|
105
135
|
r#"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
(async () => {{
|
|
137
|
+
try {{
|
|
138
|
+
let __result = await eval({js});
|
|
139
|
+
if (typeof __result === "undefined") {{
|
|
140
|
+
__result = null;
|
|
141
|
+
}} else if (typeof __result === "object" && __result !== null) {{
|
|
142
|
+
__result = JSON.stringify(__result);
|
|
143
|
+
}} else if (typeof __result !== "string") {{
|
|
144
|
+
__result = String(__result);
|
|
145
|
+
}}
|
|
146
|
+
await window.__TAURI__.core.invoke("__dev_bridge_result", {{
|
|
147
|
+
id: {id},
|
|
148
|
+
value: __result
|
|
149
|
+
}});
|
|
150
|
+
}} catch(e) {{
|
|
151
|
+
await window.__TAURI__.core.invoke("__dev_bridge_result", {{
|
|
152
|
+
id: {id},
|
|
153
|
+
value: "ERROR: " + e.message
|
|
154
|
+
}});
|
|
155
|
+
}}
|
|
156
|
+
}})();
|
|
112
157
|
"#,
|
|
113
|
-
js = serde_json::to_string(&js).unwrap()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
let
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let
|
|
136
|
-
|
|
158
|
+
js = serde_json::to_string(&eval_req.js).unwrap(),
|
|
159
|
+
id = serde_json::to_string(&request_id).unwrap(),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
let _ = window.eval(&callback_js);
|
|
163
|
+
|
|
164
|
+
// Wait for the result with a 5-second timeout
|
|
165
|
+
let mut results = pending.results.lock().unwrap();
|
|
166
|
+
let deadline = std::time::Duration::from_secs(5);
|
|
167
|
+
let start = std::time::Instant::now();
|
|
168
|
+
|
|
169
|
+
loop {
|
|
170
|
+
if let Some(value) = results.remove(&request_id) {
|
|
171
|
+
let resp = EvalResponse { result: value };
|
|
172
|
+
let json = serde_json::to_string(&resp).unwrap();
|
|
173
|
+
let header =
|
|
174
|
+
Header::from_bytes("Content-Type", "application/json").unwrap();
|
|
175
|
+
let _ =
|
|
176
|
+
request.respond(Response::from_string(json).with_header(header));
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let elapsed = start.elapsed();
|
|
181
|
+
if elapsed >= deadline {
|
|
182
|
+
// Timeout — clean up and respond with 504
|
|
183
|
+
results.remove(&request_id);
|
|
184
|
+
let _ = request.respond(
|
|
185
|
+
Response::from_string("Eval timeout").with_status_code(504),
|
|
186
|
+
);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let remaining = deadline - elapsed;
|
|
191
|
+
let (guard, timeout_result) =
|
|
192
|
+
pending.notify.wait_timeout(results, remaining).unwrap();
|
|
193
|
+
results = guard;
|
|
194
|
+
|
|
195
|
+
if timeout_result.timed_out() && !results.contains_key(&request_id) {
|
|
196
|
+
results.remove(&request_id);
|
|
197
|
+
let _ = request.respond(
|
|
198
|
+
Response::from_string("Eval timeout").with_status_code(504),
|
|
199
|
+
);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
137
202
|
}
|
|
203
|
+
} else {
|
|
204
|
+
let resp = EvalResponse {
|
|
205
|
+
result: serde_json::Value::Null,
|
|
206
|
+
};
|
|
207
|
+
let json = serde_json::to_string(&resp).unwrap();
|
|
208
|
+
let header = Header::from_bytes("Content-Type", "application/json").unwrap();
|
|
209
|
+
let _ = request.respond(Response::from_string(json).with_header(header));
|
|
138
210
|
}
|
|
139
211
|
}
|
|
140
212
|
});
|
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
mod dev_bridge;
|
|
2
2
|
|
|
3
3
|
fn main() {
|
|
4
|
-
tauri::Builder::default()
|
|
4
|
+
let mut builder = tauri::Builder::default();
|
|
5
|
+
|
|
6
|
+
if cfg!(debug_assertions) {
|
|
7
|
+
builder = builder.invoke_handler(tauri::generate_handler![
|
|
8
|
+
dev_bridge::__dev_bridge_result
|
|
9
|
+
]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
builder
|
|
5
13
|
.setup(|app| {
|
|
6
|
-
// Only start bridge in development
|
|
7
14
|
if cfg!(debug_assertions) {
|
|
8
15
|
if let Err(e) = dev_bridge::start_bridge(app.handle()) {
|
|
9
16
|
eprintln!("Warning: Failed to start dev bridge: {e}");
|
package/package.json
CHANGED
package/rust-bridge/README.md
CHANGED
|
@@ -13,21 +13,30 @@ serde = { version = "1", features = ["derive"] }
|
|
|
13
13
|
serde_json = "1"
|
|
14
14
|
scopeguard = "1"
|
|
15
15
|
rand = "0.8"
|
|
16
|
+
uuid = { version = "1", features = ["v4"] }
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
### 2. Copy the bridge module
|
|
19
20
|
|
|
20
21
|
Copy `examples/tauri-bridge/src/dev_bridge.rs` into your Tauri project's `src/` directory.
|
|
21
22
|
|
|
22
|
-
### 3.
|
|
23
|
+
### 3. Wire up in main.rs
|
|
23
24
|
|
|
24
|
-
In your `main.rs
|
|
25
|
+
In your `main.rs`, register the bridge's Tauri command and start the bridge during setup:
|
|
25
26
|
|
|
26
27
|
```rust
|
|
27
28
|
mod dev_bridge;
|
|
28
29
|
|
|
29
30
|
fn main() {
|
|
30
|
-
tauri::Builder::default()
|
|
31
|
+
let mut builder = tauri::Builder::default();
|
|
32
|
+
|
|
33
|
+
if cfg!(debug_assertions) {
|
|
34
|
+
builder = builder.invoke_handler(tauri::generate_handler![
|
|
35
|
+
dev_bridge::__dev_bridge_result
|
|
36
|
+
]);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
builder
|
|
31
40
|
.setup(|app| {
|
|
32
41
|
if cfg!(debug_assertions) {
|
|
33
42
|
if let Err(e) = dev_bridge::start_bridge(app.handle()) {
|
|
@@ -41,6 +50,16 @@ fn main() {
|
|
|
41
50
|
}
|
|
42
51
|
```
|
|
43
52
|
|
|
53
|
+
> **Note:** If your app already uses `.invoke_handler()` with its own commands, merge them into one handler:
|
|
54
|
+
> ```rust
|
|
55
|
+
> builder = builder.invoke_handler(tauri::generate_handler![
|
|
56
|
+
> your_command_one,
|
|
57
|
+
> your_command_two,
|
|
58
|
+
> dev_bridge::__dev_bridge_result,
|
|
59
|
+
> ]);
|
|
60
|
+
> ```
|
|
61
|
+
> Tauri only supports a single `invoke_handler` per builder — commands from multiple calls won't merge.
|
|
62
|
+
|
|
44
63
|
### 4. Use tauri-agent-tools
|
|
45
64
|
|
|
46
65
|
The bridge writes a token file to `/tmp/tauri-dev-bridge-<pid>.token` which `tauri-agent-tools` auto-discovers:
|
|
@@ -61,8 +80,10 @@ tauri-agent-tools eval "document.title"
|
|
|
61
80
|
1. Bridge starts an HTTP server on a random localhost port
|
|
62
81
|
2. A token file with `{ port, token, pid }` is written to `/tmp/`
|
|
63
82
|
3. `tauri-agent-tools` discovers the token file and authenticates via the token
|
|
64
|
-
4. Requests are `POST /eval { js, token }` — the bridge
|
|
65
|
-
5. The
|
|
83
|
+
4. Requests are `POST /eval { js, token }` — the bridge injects JS into the webview
|
|
84
|
+
5. The injected JS evaluates the expression, then calls back into Rust via `window.__TAURI__.core.invoke("__dev_bridge_result", { id, value })` to deliver the result
|
|
85
|
+
6. The HTTP handler thread waits for the result (up to 5 seconds) and returns it as JSON
|
|
86
|
+
7. The token file is cleaned up when the app exits
|
|
66
87
|
|
|
67
88
|
## Security
|
|
68
89
|
|