@kortix/agent-tunnel 0.1.3 → 0.12.7

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.
Files changed (67) hide show
  1. package/README.md +65 -0
  2. package/dist/agent-cli.js +4676 -3174
  3. package/dist/client-cli.js +203 -494
  4. package/package.json +24 -29
  5. package/src/agent/agent.ts +67 -17
  6. package/src/agent/capabilities/desktop/cua-driver.ts +284 -0
  7. package/src/agent/capabilities/desktop.ts +100 -167
  8. package/src/agent/capabilities/enabled-registry.ts +24 -0
  9. package/src/agent/capabilities/filesystem.ts +136 -32
  10. package/src/agent/capabilities/index.ts +1 -1
  11. package/src/agent/capabilities/security.test.ts +204 -0
  12. package/src/agent/capabilities/shell.ts +37 -3
  13. package/src/agent/cli-device-auth.test.ts +179 -0
  14. package/src/agent/cli-help.test.ts +25 -0
  15. package/src/agent/cli.ts +475 -38
  16. package/src/agent/config.test.ts +53 -0
  17. package/src/agent/config.ts +169 -7
  18. package/src/agent/index.ts +1 -0
  19. package/src/agent/security/command-validator.ts +4 -2
  20. package/src/agent/security/path-validator.ts +73 -18
  21. package/src/agent/security/permission-guard.test.ts +52 -0
  22. package/src/agent/security/permission-guard.ts +35 -8
  23. package/src/agent/service.test.ts +63 -0
  24. package/src/agent/service.ts +410 -0
  25. package/src/client/cli.test.ts +150 -547
  26. package/src/client/cli.ts +116 -537
  27. package/src/client/index.ts +1 -1
  28. package/src/client/tools.ts +95 -356
  29. package/src/client/tunnel-client.ts +50 -80
  30. package/src/index.ts +7 -1
  31. package/src/node-ws-polyfill.test.ts +18 -0
  32. package/src/node-ws-polyfill.ts +5 -3
  33. package/src/server/heartbeat.ts +13 -6
  34. package/src/server/relay.test.ts +72 -0
  35. package/src/server/relay.ts +50 -9
  36. package/src/server/server.test.ts +33 -0
  37. package/src/server/server.ts +26 -6
  38. package/src/server/ws-handler.test.ts +158 -0
  39. package/src/server/ws-handler.ts +94 -36
  40. package/src/shared/crypto.ts +2 -3
  41. package/src/shared/index.ts +8 -0
  42. package/src/shared/permissions.ts +292 -0
  43. package/src/shared/types.ts +70 -41
  44. package/dist/agent/index.d.ts +0 -140
  45. package/dist/agent/index.js +0 -21
  46. package/dist/agent/index.js.map +0 -1
  47. package/dist/chunk-7N7GSU6K.js +0 -34
  48. package/dist/client/index.d.ts +0 -183
  49. package/dist/client/index.js +0 -8
  50. package/dist/client/index.js.map +0 -1
  51. package/dist/index.d.ts +0 -7
  52. package/dist/index.js +0 -55
  53. package/dist/index.js.map +0 -1
  54. package/dist/server/index.d.ts +0 -89
  55. package/dist/server/index.js +0 -14
  56. package/dist/server/index.js.map +0 -1
  57. package/dist/shared/index.d.ts +0 -10
  58. package/dist/shared/index.js +0 -20
  59. package/dist/shared/index.js.map +0 -1
  60. package/dist/types-Dpwrd8Ai.d.ts +0 -194
  61. package/src/agent/capabilities/desktop/atspi-helper.ts +0 -345
  62. package/src/agent/capabilities/desktop/csharp-helper.ts +0 -914
  63. package/src/agent/capabilities/desktop/linux-driver.ts +0 -368
  64. package/src/agent/capabilities/desktop/macos-driver.ts +0 -601
  65. package/src/agent/capabilities/desktop/swift-helper.ts +0 -736
  66. package/src/agent/capabilities/desktop/types.ts +0 -201
  67. package/src/agent/capabilities/desktop/windows-driver.ts +0 -220
@@ -1,14 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-7N7GSU6K.js";
3
2
 
4
3
  // src/client/cli.ts
5
- import { writeFileSync, mkdirSync, readFileSync } from "fs";
6
- import { join } from "path";
7
- import { tmpdir } from "os";
8
- import { randomBytes } from "crypto";
4
+ import { readFileSync } from "fs";
9
5
 
10
6
  // src/client/tunnel-client.ts
11
- var TunnelClientError = class extends Error {
7
+ function trimTrailingSlashes(value) {
8
+ let end = value.length;
9
+ while (end > 0 && value.charCodeAt(end - 1) === 47)
10
+ end -= 1;
11
+ return value.slice(0, end);
12
+ }
13
+
14
+ class TunnelClientError extends Error {
15
+ code;
16
+ requestId;
17
+ isPermissionRequest;
12
18
  constructor(code, message, requestId, isPermissionRequest = false) {
13
19
  super(message);
14
20
  this.code = code;
@@ -16,8 +22,9 @@ var TunnelClientError = class extends Error {
16
22
  this.isPermissionRequest = isPermissionRequest;
17
23
  this.name = "TunnelClientError";
18
24
  }
19
- };
20
- var TunnelClient = class {
25
+ }
26
+
27
+ class TunnelClient {
21
28
  apiUrl;
22
29
  token;
23
30
  explicitTunnelId;
@@ -26,15 +33,20 @@ var TunnelClient = class {
26
33
  cacheTtlMs;
27
34
  fs;
28
35
  shell;
29
- desktop;
36
+ cua;
30
37
  constructor(config) {
31
- this.apiUrl = config.apiUrl.replace(/\/+$/, "");
38
+ const apiUrl = new URL(config.apiUrl);
39
+ const loopback = apiUrl.hostname === "localhost" || apiUrl.hostname === "127.0.0.1" || apiUrl.hostname === "[::1]" || apiUrl.hostname === "::1";
40
+ if (apiUrl.protocol !== "https:" && !(apiUrl.protocol === "http:" && loopback)) {
41
+ throw new Error("Remote tunnel API URLs must use https");
42
+ }
43
+ this.apiUrl = trimTrailingSlashes(apiUrl.toString());
32
44
  this.token = config.token;
33
45
  this.explicitTunnelId = config.tunnelId;
34
46
  this.cacheTtlMs = config.cacheTtlMs ?? 1e4;
35
47
  this.fs = new FsNamespace(this);
36
48
  this.shell = new ShellNamespace(this);
37
- this.desktop = new DesktopNamespace(this);
49
+ this.cua = new CuaNamespace(this);
38
50
  }
39
51
  async rpc(method, params = {}) {
40
52
  const tunnelId = await this.resolveTunnelId();
@@ -48,13 +60,9 @@ var TunnelClient = class {
48
60
  });
49
61
  const data = await res.json();
50
62
  if (!res.ok) {
51
- if (res.status === 404) this.cachedTunnelId = null;
52
- throw new TunnelClientError(
53
- data.code ?? -1,
54
- data.error ?? `HTTP ${res.status}`,
55
- data.requestId,
56
- res.status === 403 && !!data.requestId
57
- );
63
+ if (res.status === 404)
64
+ this.cachedTunnelId = null;
65
+ throw new TunnelClientError(data.code ?? -1, data.error ?? `HTTP ${res.status}`, data.requestId, res.status === 403 && !!data.requestId);
58
66
  }
59
67
  return data.result;
60
68
  }
@@ -80,7 +88,8 @@ var TunnelClient = class {
80
88
  return await res.json();
81
89
  }
82
90
  async resolveTunnelId() {
83
- if (this.explicitTunnelId) return this.explicitTunnelId;
91
+ if (this.explicitTunnelId)
92
+ return this.explicitTunnelId;
84
93
  if (this.cachedTunnelId && Date.now() - this.cacheTimestamp < this.cacheTtlMs) {
85
94
  return this.cachedTunnelId;
86
95
  }
@@ -92,27 +101,24 @@ var TunnelClient = class {
92
101
  if (res.ok) {
93
102
  const connections = await res.json();
94
103
  const online = connections.find((c) => c.isLive);
95
- if (online) {
96
- this.cachedTunnelId = online.tunnelId;
97
- this.cacheTimestamp = Date.now();
98
- return online.tunnelId;
99
- }
100
- if (connections.length > 0) {
101
- this.cachedTunnelId = connections[0].tunnelId;
104
+ const chosen = online ?? connections[0];
105
+ if (chosen) {
106
+ this.cachedTunnelId = chosen.tunnelId;
102
107
  this.cacheTimestamp = Date.now();
103
- return connections[0].tunnelId;
108
+ return chosen.tunnelId;
104
109
  }
105
110
  }
106
111
  this.cachedTunnelId = null;
107
- throw new TunnelClientError(
108
- -1,
109
- "No tunnel connection found. The user needs to set up Agent Tunnel first:\n1. Create a tunnel connection\n2. Run `npx @kortix/agent-tunnel connect` on their local machine"
110
- );
112
+ throw new TunnelClientError(-1, `No tunnel connection found. The user needs to set up Agent Tunnel first:
113
+ ` + `1. Create a tunnel connection
114
+ ` + "2. Connect the local machine from the Kortix desktop app or run the tunnel connect command");
111
115
  }
112
- };
113
- var FsNamespace = class {
114
- constructor(client2) {
115
- this.client = client2;
116
+ }
117
+
118
+ class FsNamespace {
119
+ client;
120
+ constructor(client) {
121
+ this.client = client;
116
122
  }
117
123
  async read(path, encoding = "utf-8") {
118
124
  return await this.client.rpc("fs.read", { path, encoding });
@@ -129,101 +135,104 @@ var FsNamespace = class {
129
135
  async delete(path) {
130
136
  return await this.client.rpc("fs.delete", { path });
131
137
  }
132
- };
133
- var ShellNamespace = class {
134
- constructor(client2) {
135
- this.client = client2;
138
+ }
139
+
140
+ class ShellNamespace {
141
+ client;
142
+ constructor(client) {
143
+ this.client = client;
136
144
  }
137
- async exec(command, args2 = [], options) {
145
+ async exec(command, args = [], options) {
138
146
  return await this.client.rpc("shell.exec", {
139
147
  command,
140
- args: args2,
148
+ args,
141
149
  cwd: options?.cwd,
142
150
  timeout: options?.timeout
143
151
  });
144
152
  }
145
- };
146
- var DesktopNamespace = class {
147
- constructor(client2) {
148
- this.client = client2;
149
- }
150
- async screenshot(params) {
151
- return await this.client.rpc("desktop.screenshot", params ?? {});
152
- }
153
- async click(params) {
154
- return this.client.rpc("desktop.mouse.click", params);
155
- }
156
- async type(text, delay) {
157
- return this.client.rpc("desktop.keyboard.type", { text, delay });
158
- }
159
- async key(keys) {
160
- return this.client.rpc("desktop.keyboard.key", { keys });
161
- }
162
- async mouseMove(x, y) {
163
- return this.client.rpc("desktop.mouse.move", { x, y });
164
- }
165
- async mouseDrag(fromX, fromY, toX, toY, button) {
166
- return this.client.rpc("desktop.mouse.drag", { fromX, fromY, toX, toY, button });
167
- }
168
- async mouseScroll(x, y, deltaX, deltaY) {
169
- return this.client.rpc("desktop.mouse.scroll", { x, y, deltaX, deltaY });
153
+ }
154
+
155
+ class CuaNamespace {
156
+ client;
157
+ constructor(client) {
158
+ this.client = client;
170
159
  }
171
- async windowList() {
172
- return await this.client.rpc("desktop.window.list", {});
160
+ async ensure() {
161
+ return await this.client.rpc("desktop.cua.ensure", {});
173
162
  }
174
- async windowFocus(windowId) {
175
- return this.client.rpc("desktop.window.focus", { windowId });
163
+ async startDaemon() {
164
+ return await this.client.rpc("desktop.cua.start_daemon", {});
176
165
  }
177
- async appLaunch(app) {
178
- return this.client.rpc("desktop.app.launch", { app });
166
+ async status() {
167
+ return await this.client.rpc("desktop.cua.status", {});
179
168
  }
180
- async appQuit(app) {
181
- return this.client.rpc("desktop.app.quit", { app });
169
+ async version() {
170
+ return await this.client.rpc("desktop.cua.version", {});
182
171
  }
183
- async clipboardRead() {
184
- return await this.client.rpc("desktop.clipboard.read", {});
172
+ async listTools() {
173
+ return await this.client.rpc("desktop.cua.list_tools", {});
185
174
  }
186
- async clipboardWrite(text) {
187
- return this.client.rpc("desktop.clipboard.write", { text });
175
+ async describe(tool) {
176
+ return await this.client.rpc("desktop.cua.describe", { tool });
188
177
  }
189
- async screenInfo() {
190
- return await this.client.rpc("desktop.screen.info", {});
178
+ async call(tool, args = {}) {
179
+ return this.client.rpc("desktop.cua.call", { tool, args });
191
180
  }
192
- async cursorImage(radius) {
193
- return await this.client.rpc("desktop.cursor.image", { radius });
181
+ async listApps() {
182
+ return this.client.rpc("desktop.cua.list_apps", {});
194
183
  }
195
- async axTree(params) {
196
- return await this.client.rpc("desktop.ax.tree", params ?? {});
184
+ async listWindows(params) {
185
+ return this.client.rpc("desktop.cua.list_windows", params ?? {});
197
186
  }
198
- async axAction(elementId, action, pid) {
199
- return await this.client.rpc("desktop.ax.action", { elementId, action, pid });
187
+ async getWindowState(params) {
188
+ return this.client.rpc("desktop.cua.get_window_state", params);
200
189
  }
201
- async axSetValue(elementId, value, pid) {
202
- return await this.client.rpc("desktop.ax.set_value", { elementId, value, pid });
190
+ async click(params) {
191
+ return this.client.rpc("desktop.cua.click", params);
203
192
  }
204
- async axFocus(elementId, pid) {
205
- return await this.client.rpc("desktop.ax.focus", { elementId, pid });
193
+ async typeText(params) {
194
+ return this.client.rpc("desktop.cua.type_text", params);
206
195
  }
207
- async axSearch(query, params) {
208
- return await this.client.rpc("desktop.ax.search", { query, ...params });
196
+ async hotkey(params) {
197
+ return this.client.rpc("desktop.cua.hotkey", params);
209
198
  }
210
- };
199
+ }
211
200
 
212
201
  // src/client/cli.ts
213
202
  var S6_ENV_DIR = process.env.S6_ENV_DIR || "/run/s6/container_environment";
203
+ var FALLBACK_API_URL = "http://localhost:8008";
204
+ var ALL_COMMANDS = [
205
+ "status",
206
+ "fs_read",
207
+ "fs_write",
208
+ "fs_list",
209
+ "shell",
210
+ "cua_ensure",
211
+ "cua_start_daemon",
212
+ "cua_status",
213
+ "cua_version",
214
+ "cua_list_tools",
215
+ "cua_describe",
216
+ "cua_call",
217
+ "cua_list_apps",
218
+ "cua_list_windows",
219
+ "cua_get_window_state",
220
+ "cua_click",
221
+ "cua_type_text",
222
+ "cua_hotkey"
223
+ ];
214
224
  function getEnv(key) {
215
225
  try {
216
- const val = readFileSync(`${S6_ENV_DIR}/${key}`, "utf-8").trim();
217
- if (val) return val;
218
- } catch {
219
- }
226
+ const value = readFileSync(`${S6_ENV_DIR}/${key}`, "utf-8").trim();
227
+ if (value)
228
+ return value;
229
+ } catch {}
220
230
  return process.env[key];
221
231
  }
222
- var FALLBACK_API_URL = "http://localhost:8008";
223
232
  function getApiBase() {
224
233
  const raw = getEnv("TUNNEL_API_URL") || FALLBACK_API_URL;
225
234
  const url = raw.startsWith("http") ? raw : FALLBACK_API_URL;
226
- return url.replace(/\/+$/, "");
235
+ return url.replace(/\/+$/, "").replace(/\/v1\/router\/?$/, "");
227
236
  }
228
237
  var client = new TunnelClient({
229
238
  apiUrl: `${getApiBase()}/v1/tunnel`,
@@ -233,31 +242,9 @@ var client = new TunnelClient({
233
242
  function out(data) {
234
243
  console.log(JSON.stringify(data, null, 2));
235
244
  }
236
- function saveImage(base64, format) {
237
- const ext = format === "jpeg" || format === "jpg" ? "jpg" : "png";
238
- const dir = join(tmpdir(), "tunnel-screenshots");
239
- mkdirSync(dir, { recursive: true });
240
- const path = join(
241
- dir,
242
- `screenshot-${randomBytes(4).toString("hex")}.${ext}`
243
- );
244
- writeFileSync(path, Buffer.from(base64, "base64"));
245
- return path;
246
- }
247
- function formatAXTree(el, indent = 0) {
248
- const pad = " ".repeat(indent);
249
- const parts = [];
250
- const label = el.title || el.value || el.description || "(unnamed)";
251
- const flags = [];
252
- if (!el.enabled) flags.push("disabled");
253
- if (el.focused) flags.push("focused");
254
- if (el.actions.length > 0) flags.push(`actions: ${el.actions.join(",")}`);
255
- const flagStr = flags.length > 0 ? ` [${flags.join(", ")}]` : "";
256
- parts.push(`${pad}[${el.role}] ${label} (id: ${el.id})${flagStr}`);
257
- for (const child of el.children) {
258
- parts.push(formatAXTree(child, indent + 1));
259
- }
260
- return parts.join("\n");
245
+ function fail(message) {
246
+ out({ success: false, error: message });
247
+ process.exitCode = 1;
261
248
  }
262
249
  async function rpcSafe(method, params = {}) {
263
250
  try {
@@ -269,24 +256,24 @@ async function rpcSafe(method, params = {}) {
269
256
  result: null,
270
257
  permissionRequired: true,
271
258
  requestId: err.requestId || "unknown",
272
- message: `Permission required. A permission request (${err.requestId}) has been sent to the user for approval. The user needs to approve this request before you can access their local machine. Please inform the user and try again after they approve.`
259
+ message: `Permission required. A permission request (${err.requestId}) has been sent to the user for approval.`
273
260
  };
274
261
  }
275
262
  throw err;
276
263
  }
277
264
  }
278
265
  async function call(method, params = {}) {
279
- const r = await rpcSafe(method, params);
280
- if (r.permissionRequired) {
266
+ const response = await rpcSafe(method, params);
267
+ if (response.permissionRequired) {
281
268
  out({
282
269
  success: false,
283
270
  permissionRequired: true,
284
- requestId: r.requestId,
285
- message: r.message
271
+ requestId: response.requestId,
272
+ message: response.message
286
273
  });
287
274
  return null;
288
275
  }
289
- return r.result;
276
+ return response.result;
290
277
  }
291
278
  async function status() {
292
279
  const connections = await client.getConnections();
@@ -299,7 +286,8 @@ async function status() {
299
286
  }
300
287
  let hasOnline = false;
301
288
  const mapped = connections.map((data) => {
302
- if (data.isLive) hasOnline = true;
289
+ if (data.isLive)
290
+ hasOnline = true;
303
291
  return {
304
292
  name: data.name || "Unnamed",
305
293
  tunnelId: data.tunnelId,
@@ -312,55 +300,49 @@ async function status() {
312
300
  success: true,
313
301
  connections: mapped,
314
302
  hasOnline,
315
- message: hasOnline ? void 0 : "No tunnel is currently online. Ask the user to run `npx @kortix/agent-tunnel connect` on their local machine."
303
+ message: hasOnline ? undefined : "No tunnel is currently online. Ask the user to run `npx --yes @kortix/agent-tunnel@latest connect` on their local machine."
316
304
  });
317
305
  }
318
- async function fsRead(args2) {
306
+ async function fsRead(args) {
319
307
  const result = await call("fs.read", {
320
- path: args2.path,
321
- encoding: args2.encoding || "utf-8"
308
+ path: args.path,
309
+ encoding: args.encoding || "utf-8"
322
310
  });
323
- if (result === null) return;
311
+ if (result === null)
312
+ return;
324
313
  const data = result;
325
- out({
326
- success: true,
327
- path: data.path || args2.path,
328
- size: data.size,
329
- content: data.content
330
- });
314
+ out({ success: true, path: data.path || args.path, size: data.size, content: data.content });
331
315
  }
332
- async function fsWrite(args2) {
316
+ async function fsWrite(args) {
333
317
  const result = await call("fs.write", {
334
- path: args2.path,
335
- content: args2.content,
336
- encoding: args2.encoding || "utf-8"
318
+ path: args.path,
319
+ content: args.content,
320
+ encoding: args.encoding || "utf-8"
337
321
  });
338
- if (result === null) return;
322
+ if (result === null)
323
+ return;
339
324
  const data = result;
340
325
  out({ success: true, path: data.path, size: data.size });
341
326
  }
342
- async function fsList(args2) {
327
+ async function fsList(args) {
343
328
  const result = await call("fs.list", {
344
- path: args2.path,
345
- recursive: args2.recursive || false
329
+ path: args.path,
330
+ recursive: args.recursive || false
346
331
  });
347
- if (result === null) return;
332
+ if (result === null)
333
+ return;
348
334
  const data = result;
349
- out({
350
- success: true,
351
- path: args2.path,
352
- count: data.count,
353
- entries: data.entries
354
- });
335
+ out({ success: true, path: args.path, count: data.count, entries: data.entries });
355
336
  }
356
- async function shell(args2) {
337
+ async function shell(args) {
357
338
  const result = await call("shell.exec", {
358
- command: args2.command,
359
- args: args2.args || [],
360
- cwd: args2.cwd,
361
- timeout: args2.timeout
339
+ command: args.command,
340
+ args: args.args || [],
341
+ cwd: args.cwd,
342
+ timeout: args.timeout
362
343
  });
363
- if (result === null) return;
344
+ if (result === null)
345
+ return;
364
346
  const data = result;
365
347
  out({
366
348
  success: data.exitCode === 0,
@@ -372,294 +354,44 @@ async function shell(args2) {
372
354
  stderrTruncated: data.stderrTruncated
373
355
  });
374
356
  }
375
- async function screenshot(args2) {
376
- const params = {};
377
- if (args2.x !== void 0 && args2.y !== void 0 && args2.width !== void 0 && args2.height !== void 0) {
378
- params.region = {
379
- x: args2.x,
380
- y: args2.y,
381
- width: args2.width,
382
- height: args2.height
383
- };
384
- }
385
- if (args2.windowId !== void 0) params.windowId = args2.windowId;
386
- const result = await call("desktop.screenshot", params);
387
- if (result === null) return;
388
- const data = result;
389
- const format = data.format || "png";
390
- const sizeKB = Math.round(data.image.length * 0.75 / 1024);
391
- const path = saveImage(data.image, format);
392
- out({
393
- success: true,
394
- path,
395
- width: data.width,
396
- height: data.height,
397
- format,
398
- sizeKB,
399
- message: `Screenshot saved: ${path} (${data.width}x${data.height} ${format.toUpperCase()}, ${sizeKB}KB). Use the Read tool to view.`
400
- });
401
- }
402
- async function click(args2) {
403
- const result = await call("desktop.mouse.click", {
404
- x: args2.x,
405
- y: args2.y,
406
- button: args2.button,
407
- clicks: args2.clicks,
408
- modifiers: args2.modifiers
409
- });
410
- if (result === null) return;
411
- out({
412
- success: true,
413
- x: args2.x,
414
- y: args2.y,
415
- button: args2.button || "left",
416
- clicks: args2.clicks || 1
417
- });
418
- }
419
- async function mouseMove(args2) {
420
- const result = await call("desktop.mouse.move", { x: args2.x, y: args2.y });
421
- if (result === null) return;
422
- out({ success: true, x: args2.x, y: args2.y });
423
- }
424
- async function mouseDrag(args2) {
425
- const result = await call("desktop.mouse.drag", {
426
- fromX: args2.fromX,
427
- fromY: args2.fromY,
428
- toX: args2.toX,
429
- toY: args2.toY,
430
- button: args2.button
431
- });
432
- if (result === null) return;
433
- out({
434
- success: true,
435
- fromX: args2.fromX,
436
- fromY: args2.fromY,
437
- toX: args2.toX,
438
- toY: args2.toY
439
- });
440
- }
441
- async function mouseScroll(args2) {
442
- const result = await call("desktop.mouse.scroll", {
443
- x: args2.x,
444
- y: args2.y,
445
- deltaX: args2.deltaX,
446
- deltaY: args2.deltaY
447
- });
448
- if (result === null) return;
449
- out({
450
- success: true,
451
- x: args2.x,
452
- y: args2.y,
453
- deltaX: args2.deltaX || 0,
454
- deltaY: args2.deltaY || 0
455
- });
456
- }
457
- async function typeText(args2) {
458
- const result = await call("desktop.keyboard.type", {
459
- text: args2.text,
460
- delay: args2.delay
357
+ async function cuaRpc(method, args = {}) {
358
+ const result = await call(method, args);
359
+ if (result === null)
360
+ return;
361
+ out({ success: true, result });
362
+ }
363
+ async function cuaCall(args) {
364
+ const tool = args.tool;
365
+ if (!tool)
366
+ return fail("tool is required");
367
+ const result = await call("desktop.cua.call", {
368
+ tool,
369
+ args: args.args || {}
461
370
  });
462
- if (result === null) return;
463
- out({ success: true, chars: args2.text.length });
371
+ if (result === null)
372
+ return;
373
+ out({ success: true, tool, result });
464
374
  }
465
- async function pressKey(args2) {
466
- const result = await call("desktop.keyboard.key", { keys: args2.keys });
467
- if (result === null) return;
468
- out({ success: true, keys: args2.keys });
375
+ async function cuaDescribe(args) {
376
+ const tool = args.tool;
377
+ if (!tool)
378
+ return fail("tool is required");
379
+ await cuaRpc("desktop.cua.describe", { tool });
469
380
  }
470
- async function windowList() {
471
- const result = await call("desktop.window.list", {});
472
- if (result === null) return;
473
- const data = result;
474
- out({ success: true, windows: data.windows });
475
- }
476
- async function windowFocus(args2) {
477
- const result = await call("desktop.window.focus", {
478
- windowId: args2.windowId
479
- });
480
- if (result === null) return;
481
- out({ success: true, windowId: args2.windowId });
482
- }
483
- async function appLaunch(args2) {
484
- const result = await call("desktop.app.launch", { app: args2.app });
485
- if (result === null) return;
486
- out({ success: true, app: args2.app });
487
- }
488
- async function appQuit(args2) {
489
- const result = await call("desktop.app.quit", { app: args2.app });
490
- if (result === null) return;
491
- out({ success: true, app: args2.app });
492
- }
493
- async function clipboardRead() {
494
- const result = await call("desktop.clipboard.read", {});
495
- if (result === null) return;
496
- const data = result;
497
- out({ success: true, text: data.text || "" });
498
- }
499
- async function clipboardWrite(args2) {
500
- const result = await call("desktop.clipboard.write", { text: args2.text });
501
- if (result === null) return;
502
- out({ success: true, chars: args2.text.length });
503
- }
504
- async function screenInfo() {
505
- const result = await call("desktop.screen.info", {});
506
- if (result === null) return;
507
- const data = result;
508
- out({ success: true, ...data });
509
- }
510
- async function cursorImage(args2) {
511
- const result = await call("desktop.cursor.image", { radius: args2.radius });
512
- if (result === null) return;
513
- const data = result;
514
- const format = data.format || "png";
515
- const sizeKB = Math.round(data.image.length * 0.75 / 1024);
516
- const path = saveImage(data.image, format);
517
- out({
518
- success: true,
519
- path,
520
- width: data.width,
521
- height: data.height,
522
- format,
523
- sizeKB,
524
- message: `Cursor area saved: ${path} (${data.width}x${data.height}). Use the Read tool to view.`
525
- });
526
- }
527
- async function axTree(args2) {
528
- const params = {};
529
- if (args2.pid !== void 0) params.pid = args2.pid;
530
- if (args2.maxDepth !== void 0) params.maxDepth = args2.maxDepth;
531
- if (args2.roles !== void 0) params.roles = args2.roles;
532
- const result = await call("desktop.ax.tree", params);
533
- if (result === null) return;
534
- const data = result;
535
- if (!data.root)
536
- return out({
537
- success: true,
538
- tree: null,
539
- message: "No accessibility tree available"
540
- });
541
- out({
542
- success: true,
543
- elementCount: data.elementCount,
544
- tree: formatAXTree(data.root)
545
- });
546
- }
547
- async function axAction(args2) {
548
- const result = await call("desktop.ax.action", {
549
- elementId: args2.elementId,
550
- action: args2.action,
551
- pid: args2.pid
552
- });
553
- if (result === null) return;
554
- const data = result;
555
- out({
556
- success: data.ok,
557
- action: data.action,
558
- elementId: data.elementId,
559
- role: data.role,
560
- title: data.title,
561
- stateChanged: data.stateChanged,
562
- before: data.before,
563
- after: data.after
564
- });
565
- }
566
- async function axSetValue(args2) {
567
- const result = await call("desktop.ax.set_value", {
568
- elementId: args2.elementId,
569
- value: args2.value,
570
- pid: args2.pid
571
- });
572
- if (result === null) return;
573
- const data = result;
574
- out({
575
- success: data.ok,
576
- elementId: data.elementId,
577
- requestedValue: data.requestedValue,
578
- actualValue: data.actualValue,
579
- error: data.error
580
- });
581
- }
582
- async function axFocus(args2) {
583
- const result = await call("desktop.ax.focus", {
584
- elementId: args2.elementId,
585
- pid: args2.pid
586
- });
587
- if (result === null) return;
588
- const data = result;
589
- out({
590
- success: data.ok,
591
- elementId: data.elementId,
592
- role: data.role,
593
- title: data.title,
594
- before: data.before,
595
- after: data.after,
596
- error: data.error
597
- });
598
- }
599
- async function axSearch(args2) {
600
- const params = { query: args2.query };
601
- if (args2.role !== void 0) params.role = args2.role;
602
- if (args2.pid !== void 0) params.pid = args2.pid;
603
- if (args2.maxResults !== void 0) params.maxResults = args2.maxResults;
604
- const result = await call("desktop.ax.search", params);
605
- if (result === null) return;
606
- const data = result;
607
- if (!data.elements || data.elements.length === 0) {
608
- return out({
609
- success: true,
610
- query: args2.query,
611
- elements: [],
612
- message: `No elements found matching "${args2.query}"`
613
- });
614
- }
615
- const elements = data.elements.map((el) => ({
616
- id: el.id,
617
- role: el.role,
618
- title: el.title || el.value || el.description || "(unnamed)",
619
- bounds: el.bounds,
620
- enabled: el.enabled,
621
- focused: el.focused,
622
- actions: el.actions
623
- }));
624
- out({ success: true, query: args2.query, count: elements.length, elements });
625
- }
626
- var ALL_COMMANDS = [
627
- "status",
628
- "fs_read",
629
- "fs_write",
630
- "fs_list",
631
- "shell",
632
- "screenshot",
633
- "click",
634
- "mouse_move",
635
- "mouse_drag",
636
- "mouse_scroll",
637
- "type",
638
- "key",
639
- "window_list",
640
- "window_focus",
641
- "app_launch",
642
- "app_quit",
643
- "clipboard_read",
644
- "clipboard_write",
645
- "screen_info",
646
- "cursor_image",
647
- "ax_tree",
648
- "ax_action",
649
- "ax_set_value",
650
- "ax_focus",
651
- "ax_search"
652
- ];
653
381
  var [cmd, rawArgs] = process.argv.slice(2);
654
382
  if (!cmd) {
655
- console.error(
656
- `Usage: agent-tunnel-cli <command> [args as JSON]
383
+ console.error(`Usage: agent-tunnel-cli <command> [args as JSON]
657
384
 
658
- Available: ${ALL_COMMANDS.join(" | ")}`
659
- );
385
+ Available: ${ALL_COMMANDS.join(" | ")}`);
386
+ process.exit(1);
387
+ }
388
+ var args = {};
389
+ try {
390
+ args = rawArgs ? JSON.parse(rawArgs) : {};
391
+ } catch (err) {
392
+ fail(err instanceof Error ? err.message : String(err));
660
393
  process.exit(1);
661
394
  }
662
- var args = rawArgs ? JSON.parse(rawArgs) : {};
663
395
  try {
664
396
  switch (cmd) {
665
397
  case "status":
@@ -677,72 +409,49 @@ try {
677
409
  case "shell":
678
410
  await shell(args);
679
411
  break;
680
- case "screenshot":
681
- await screenshot(args);
682
- break;
683
- case "click":
684
- await click(args);
685
- break;
686
- case "mouse_move":
687
- await mouseMove(args);
688
- break;
689
- case "mouse_drag":
690
- await mouseDrag(args);
691
- break;
692
- case "mouse_scroll":
693
- await mouseScroll(args);
694
- break;
695
- case "type":
696
- await typeText(args);
697
- break;
698
- case "key":
699
- await pressKey(args);
700
- break;
701
- case "window_list":
702
- await windowList();
412
+ case "cua_ensure":
413
+ await cuaRpc("desktop.cua.ensure");
703
414
  break;
704
- case "window_focus":
705
- await windowFocus(args);
415
+ case "cua_start_daemon":
416
+ await cuaRpc("desktop.cua.start_daemon");
706
417
  break;
707
- case "app_launch":
708
- await appLaunch(args);
418
+ case "cua_status":
419
+ await cuaRpc("desktop.cua.status");
709
420
  break;
710
- case "app_quit":
711
- await appQuit(args);
421
+ case "cua_version":
422
+ await cuaRpc("desktop.cua.version");
712
423
  break;
713
- case "clipboard_read":
714
- await clipboardRead();
424
+ case "cua_list_tools":
425
+ await cuaRpc("desktop.cua.list_tools");
715
426
  break;
716
- case "clipboard_write":
717
- await clipboardWrite(args);
427
+ case "cua_describe":
428
+ await cuaDescribe(args);
718
429
  break;
719
- case "screen_info":
720
- await screenInfo();
430
+ case "cua_call":
431
+ await cuaCall(args);
721
432
  break;
722
- case "cursor_image":
723
- await cursorImage(args);
433
+ case "cua_list_apps":
434
+ await cuaRpc("desktop.cua.list_apps", args);
724
435
  break;
725
- case "ax_tree":
726
- await axTree(args);
436
+ case "cua_list_windows":
437
+ await cuaRpc("desktop.cua.list_windows", args);
727
438
  break;
728
- case "ax_action":
729
- await axAction(args);
439
+ case "cua_get_window_state":
440
+ await cuaRpc("desktop.cua.get_window_state", args);
730
441
  break;
731
- case "ax_set_value":
732
- await axSetValue(args);
442
+ case "cua_click":
443
+ await cuaRpc("desktop.cua.click", args);
733
444
  break;
734
- case "ax_focus":
735
- await axFocus(args);
445
+ case "cua_type_text":
446
+ await cuaRpc("desktop.cua.type_text", args);
736
447
  break;
737
- case "ax_search":
738
- await axSearch(args);
448
+ case "cua_hotkey":
449
+ await cuaRpc("desktop.cua.hotkey", args);
739
450
  break;
740
451
  default:
741
- console.error(
742
- `Unknown command: ${cmd}
452
+ console.error(`Unknown command: ${cmd}
743
453
 
744
- Available: ${ALL_COMMANDS.join(" | ")}`
745
- );
454
+ Available: ${ALL_COMMANDS.join(" | ")}`);
746
455
  process.exit(1);
747
456
  }
748
457
  } catch (err) {