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