@kortix/agent-tunnel 0.1.2 → 0.1.4

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.
@@ -0,0 +1,752 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-7N7GSU6K.js";
3
+
4
+ // 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";
9
+
10
+ // src/client/tunnel-client.ts
11
+ var TunnelClientError = class extends Error {
12
+ constructor(code, message, requestId, isPermissionRequest = false) {
13
+ super(message);
14
+ this.code = code;
15
+ this.requestId = requestId;
16
+ this.isPermissionRequest = isPermissionRequest;
17
+ this.name = "TunnelClientError";
18
+ }
19
+ };
20
+ var TunnelClient = class {
21
+ apiUrl;
22
+ token;
23
+ explicitTunnelId;
24
+ cachedTunnelId = null;
25
+ cacheTimestamp = 0;
26
+ cacheTtlMs;
27
+ fs;
28
+ shell;
29
+ desktop;
30
+ constructor(config) {
31
+ this.apiUrl = config.apiUrl.replace(/\/+$/, "");
32
+ this.token = config.token;
33
+ this.explicitTunnelId = config.tunnelId;
34
+ this.cacheTtlMs = config.cacheTtlMs ?? 1e4;
35
+ this.fs = new FsNamespace(this);
36
+ this.shell = new ShellNamespace(this);
37
+ this.desktop = new DesktopNamespace(this);
38
+ }
39
+ async rpc(method, params = {}) {
40
+ const tunnelId = await this.resolveTunnelId();
41
+ const res = await fetch(`${this.apiUrl}/rpc/${tunnelId}`, {
42
+ method: "POST",
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {}
46
+ },
47
+ body: JSON.stringify({ method, params })
48
+ });
49
+ const data = await res.json();
50
+ 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
+ );
58
+ }
59
+ return data.result;
60
+ }
61
+ async rpcWithPermissionFlow(method, params = {}) {
62
+ try {
63
+ return await this.rpc(method, params);
64
+ } catch (err) {
65
+ if (err instanceof TunnelClientError && err.isPermissionRequest) {
66
+ return `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.`;
67
+ }
68
+ throw err;
69
+ }
70
+ }
71
+ async getConnections() {
72
+ const res = await fetch(`${this.apiUrl}/connections`, {
73
+ headers: {
74
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {}
75
+ }
76
+ });
77
+ if (!res.ok) {
78
+ throw new TunnelClientError(-1, `Failed to list connections: HTTP ${res.status}`);
79
+ }
80
+ return await res.json();
81
+ }
82
+ async resolveTunnelId() {
83
+ if (this.explicitTunnelId) return this.explicitTunnelId;
84
+ if (this.cachedTunnelId && Date.now() - this.cacheTimestamp < this.cacheTtlMs) {
85
+ return this.cachedTunnelId;
86
+ }
87
+ const res = await fetch(`${this.apiUrl}/connections`, {
88
+ headers: {
89
+ ...this.token ? { Authorization: `Bearer ${this.token}` } : {}
90
+ }
91
+ });
92
+ if (res.ok) {
93
+ const connections = await res.json();
94
+ 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;
102
+ this.cacheTimestamp = Date.now();
103
+ return connections[0].tunnelId;
104
+ }
105
+ }
106
+ 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
+ );
111
+ }
112
+ };
113
+ var FsNamespace = class {
114
+ constructor(client2) {
115
+ this.client = client2;
116
+ }
117
+ async read(path, encoding = "utf-8") {
118
+ return await this.client.rpc("fs.read", { path, encoding });
119
+ }
120
+ async write(path, content, encoding = "utf-8") {
121
+ return await this.client.rpc("fs.write", { path, content, encoding });
122
+ }
123
+ async list(path, recursive = false) {
124
+ return await this.client.rpc("fs.list", { path, recursive });
125
+ }
126
+ async stat(path) {
127
+ return await this.client.rpc("fs.stat", { path });
128
+ }
129
+ async delete(path) {
130
+ return await this.client.rpc("fs.delete", { path });
131
+ }
132
+ };
133
+ var ShellNamespace = class {
134
+ constructor(client2) {
135
+ this.client = client2;
136
+ }
137
+ async exec(command, args2 = [], options) {
138
+ return await this.client.rpc("shell.exec", {
139
+ command,
140
+ args: args2,
141
+ cwd: options?.cwd,
142
+ timeout: options?.timeout
143
+ });
144
+ }
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 });
170
+ }
171
+ async windowList() {
172
+ return await this.client.rpc("desktop.window.list", {});
173
+ }
174
+ async windowFocus(windowId) {
175
+ return this.client.rpc("desktop.window.focus", { windowId });
176
+ }
177
+ async appLaunch(app) {
178
+ return this.client.rpc("desktop.app.launch", { app });
179
+ }
180
+ async appQuit(app) {
181
+ return this.client.rpc("desktop.app.quit", { app });
182
+ }
183
+ async clipboardRead() {
184
+ return await this.client.rpc("desktop.clipboard.read", {});
185
+ }
186
+ async clipboardWrite(text) {
187
+ return this.client.rpc("desktop.clipboard.write", { text });
188
+ }
189
+ async screenInfo() {
190
+ return await this.client.rpc("desktop.screen.info", {});
191
+ }
192
+ async cursorImage(radius) {
193
+ return await this.client.rpc("desktop.cursor.image", { radius });
194
+ }
195
+ async axTree(params) {
196
+ return await this.client.rpc("desktop.ax.tree", params ?? {});
197
+ }
198
+ async axAction(elementId, action, pid) {
199
+ return await this.client.rpc("desktop.ax.action", { elementId, action, pid });
200
+ }
201
+ async axSetValue(elementId, value, pid) {
202
+ return await this.client.rpc("desktop.ax.set_value", { elementId, value, pid });
203
+ }
204
+ async axFocus(elementId, pid) {
205
+ return await this.client.rpc("desktop.ax.focus", { elementId, pid });
206
+ }
207
+ async axSearch(query, params) {
208
+ return await this.client.rpc("desktop.ax.search", { query, ...params });
209
+ }
210
+ };
211
+
212
+ // src/client/cli.ts
213
+ var S6_ENV_DIR = process.env.S6_ENV_DIR || "/run/s6/container_environment";
214
+ function getEnv(key) {
215
+ try {
216
+ const val = readFileSync(`${S6_ENV_DIR}/${key}`, "utf-8").trim();
217
+ if (val) return val;
218
+ } catch {
219
+ }
220
+ return process.env[key];
221
+ }
222
+ var FALLBACK_API_URL = "http://localhost:8008";
223
+ function getApiBase() {
224
+ const raw = getEnv("TUNNEL_API_URL") || FALLBACK_API_URL;
225
+ const url = raw.startsWith("http") ? raw : FALLBACK_API_URL;
226
+ return url.replace(/\/+$/, "").replace(/\/v1\/router\/?$/, "");
227
+ }
228
+ var client = new TunnelClient({
229
+ apiUrl: `${getApiBase()}/v1/tunnel`,
230
+ token: getEnv("TUNNEL_TOKEN") || "",
231
+ tunnelId: getEnv("TUNNEL_ID")
232
+ });
233
+ function out(data) {
234
+ console.log(JSON.stringify(data, null, 2));
235
+ }
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");
261
+ }
262
+ async function rpcSafe(method, params = {}) {
263
+ try {
264
+ const result = await client.rpc(method, params);
265
+ return { result, permissionRequired: false };
266
+ } catch (err) {
267
+ if (err instanceof TunnelClientError && err.isPermissionRequest) {
268
+ return {
269
+ result: null,
270
+ permissionRequired: true,
271
+ 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.`
273
+ };
274
+ }
275
+ throw err;
276
+ }
277
+ }
278
+ async function call(method, params = {}) {
279
+ const r = await rpcSafe(method, params);
280
+ if (r.permissionRequired) {
281
+ out({
282
+ success: false,
283
+ permissionRequired: true,
284
+ requestId: r.requestId,
285
+ message: r.message
286
+ });
287
+ return null;
288
+ }
289
+ return r.result;
290
+ }
291
+ async function status() {
292
+ const connections = await client.getConnections();
293
+ if (connections.length === 0) {
294
+ return out({
295
+ success: true,
296
+ connections: [],
297
+ message: "No tunnel connections found. The user needs to set up Agent Tunnel first."
298
+ });
299
+ }
300
+ let hasOnline = false;
301
+ const mapped = connections.map((data) => {
302
+ if (data.isLive) hasOnline = true;
303
+ return {
304
+ name: data.name || "Unnamed",
305
+ tunnelId: data.tunnelId,
306
+ status: data.isLive ? "ONLINE" : "OFFLINE",
307
+ capabilities: data.capabilities || [],
308
+ machineInfo: data.machineInfo || {}
309
+ };
310
+ });
311
+ out({
312
+ success: true,
313
+ connections: mapped,
314
+ 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."
316
+ });
317
+ }
318
+ async function fsRead(args2) {
319
+ const result = await call("fs.read", {
320
+ path: args2.path,
321
+ encoding: args2.encoding || "utf-8"
322
+ });
323
+ if (result === null) return;
324
+ const data = result;
325
+ out({
326
+ success: true,
327
+ path: data.path || args2.path,
328
+ size: data.size,
329
+ content: data.content
330
+ });
331
+ }
332
+ async function fsWrite(args2) {
333
+ const result = await call("fs.write", {
334
+ path: args2.path,
335
+ content: args2.content,
336
+ encoding: args2.encoding || "utf-8"
337
+ });
338
+ if (result === null) return;
339
+ const data = result;
340
+ out({ success: true, path: data.path, size: data.size });
341
+ }
342
+ async function fsList(args2) {
343
+ const result = await call("fs.list", {
344
+ path: args2.path,
345
+ recursive: args2.recursive || false
346
+ });
347
+ if (result === null) return;
348
+ const data = result;
349
+ out({
350
+ success: true,
351
+ path: args2.path,
352
+ count: data.count,
353
+ entries: data.entries
354
+ });
355
+ }
356
+ async function shell(args2) {
357
+ const result = await call("shell.exec", {
358
+ command: args2.command,
359
+ args: args2.args || [],
360
+ cwd: args2.cwd,
361
+ timeout: args2.timeout
362
+ });
363
+ if (result === null) return;
364
+ const data = result;
365
+ out({
366
+ success: data.exitCode === 0,
367
+ exitCode: data.exitCode,
368
+ signal: data.signal,
369
+ stdout: data.stdout,
370
+ stderr: data.stderr,
371
+ stdoutTruncated: data.stdoutTruncated,
372
+ stderrTruncated: data.stderrTruncated
373
+ });
374
+ }
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
461
+ });
462
+ if (result === null) return;
463
+ out({ success: true, chars: args2.text.length });
464
+ }
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 });
469
+ }
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
+ var [cmd, rawArgs] = process.argv.slice(2);
654
+ if (!cmd) {
655
+ console.error(
656
+ `Usage: agent-tunnel-cli <command> [args as JSON]
657
+
658
+ Available: ${ALL_COMMANDS.join(" | ")}`
659
+ );
660
+ process.exit(1);
661
+ }
662
+ var args = rawArgs ? JSON.parse(rawArgs) : {};
663
+ try {
664
+ switch (cmd) {
665
+ case "status":
666
+ await status();
667
+ break;
668
+ case "fs_read":
669
+ await fsRead(args);
670
+ break;
671
+ case "fs_write":
672
+ await fsWrite(args);
673
+ break;
674
+ case "fs_list":
675
+ await fsList(args);
676
+ break;
677
+ case "shell":
678
+ await shell(args);
679
+ 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();
703
+ break;
704
+ case "window_focus":
705
+ await windowFocus(args);
706
+ break;
707
+ case "app_launch":
708
+ await appLaunch(args);
709
+ break;
710
+ case "app_quit":
711
+ await appQuit(args);
712
+ break;
713
+ case "clipboard_read":
714
+ await clipboardRead();
715
+ break;
716
+ case "clipboard_write":
717
+ await clipboardWrite(args);
718
+ break;
719
+ case "screen_info":
720
+ await screenInfo();
721
+ break;
722
+ case "cursor_image":
723
+ await cursorImage(args);
724
+ break;
725
+ case "ax_tree":
726
+ await axTree(args);
727
+ break;
728
+ case "ax_action":
729
+ await axAction(args);
730
+ break;
731
+ case "ax_set_value":
732
+ await axSetValue(args);
733
+ break;
734
+ case "ax_focus":
735
+ await axFocus(args);
736
+ break;
737
+ case "ax_search":
738
+ await axSearch(args);
739
+ break;
740
+ default:
741
+ console.error(
742
+ `Unknown command: ${cmd}
743
+
744
+ Available: ${ALL_COMMANDS.join(" | ")}`
745
+ );
746
+ process.exit(1);
747
+ }
748
+ } catch (err) {
749
+ const message = err instanceof Error ? err.message : String(err);
750
+ out({ success: false, error: message });
751
+ process.exit(1);
752
+ }