@beeuni/agent-tunnel 0.0.0-stage → 0.1.1
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 +267 -2
- package/dist/agent-cli.js +6924 -0
- package/dist/client-cli.js +511 -0
- package/package.json +30 -4
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/client/cli.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { open } from "node:fs/promises";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
// src/client/tunnel-client.ts
|
|
9
|
+
function trimTrailingSlashes(value) {
|
|
10
|
+
let end = value.length;
|
|
11
|
+
while (end > 0 && value.charCodeAt(end - 1) === 47)
|
|
12
|
+
end -= 1;
|
|
13
|
+
return value.slice(0, end);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class TunnelClientError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
requestId;
|
|
19
|
+
isPermissionRequest;
|
|
20
|
+
constructor(code, message, requestId, isPermissionRequest = false) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.requestId = requestId;
|
|
24
|
+
this.isPermissionRequest = isPermissionRequest;
|
|
25
|
+
this.name = "TunnelClientError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class TunnelClient {
|
|
30
|
+
apiUrl;
|
|
31
|
+
token;
|
|
32
|
+
explicitTunnelId;
|
|
33
|
+
cachedTunnelId = null;
|
|
34
|
+
cacheTimestamp = 0;
|
|
35
|
+
cacheTtlMs;
|
|
36
|
+
fs;
|
|
37
|
+
shell;
|
|
38
|
+
cua;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
const apiUrl = new URL(config.apiUrl);
|
|
41
|
+
const loopback = apiUrl.hostname === "localhost" || apiUrl.hostname === "127.0.0.1" || apiUrl.hostname === "[::1]" || apiUrl.hostname === "::1";
|
|
42
|
+
if (apiUrl.protocol !== "https:" && !(apiUrl.protocol === "http:" && loopback)) {
|
|
43
|
+
throw new Error("Remote tunnel API URLs must use https");
|
|
44
|
+
}
|
|
45
|
+
this.apiUrl = trimTrailingSlashes(apiUrl.toString());
|
|
46
|
+
this.token = config.token;
|
|
47
|
+
this.explicitTunnelId = config.tunnelId;
|
|
48
|
+
this.cacheTtlMs = config.cacheTtlMs ?? 1e4;
|
|
49
|
+
this.fs = new FsNamespace(this);
|
|
50
|
+
this.shell = new ShellNamespace(this);
|
|
51
|
+
this.cua = new CuaNamespace(this);
|
|
52
|
+
}
|
|
53
|
+
async rpc(method, params = {}) {
|
|
54
|
+
const tunnelId = await this.resolveTunnelId();
|
|
55
|
+
const res = await fetch(`${this.apiUrl}/rpc/${tunnelId}`, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
...this.token ? { Authorization: `Bearer ${this.token}` } : {}
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({ method, params })
|
|
62
|
+
});
|
|
63
|
+
let data;
|
|
64
|
+
try {
|
|
65
|
+
data = await res.json();
|
|
66
|
+
} catch {
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new TunnelClientError(-1, `HTTP ${res.status} ${res.statusText}`, undefined, false);
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`Failed to parse response for ${method}: unexpected non-JSON body`);
|
|
71
|
+
}
|
|
72
|
+
if (!res.ok) {
|
|
73
|
+
if (res.status === 404)
|
|
74
|
+
this.cachedTunnelId = null;
|
|
75
|
+
throw new TunnelClientError(data.code ?? -1, data.error ?? `HTTP ${res.status}`, data.requestId, res.status === 403 && !!data.requestId);
|
|
76
|
+
}
|
|
77
|
+
return data.result;
|
|
78
|
+
}
|
|
79
|
+
async rpcWithPermissionFlow(method, params = {}) {
|
|
80
|
+
try {
|
|
81
|
+
return await this.rpc(method, params);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
if (err instanceof TunnelClientError && err.isPermissionRequest) {
|
|
84
|
+
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.`;
|
|
85
|
+
}
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async getConnections() {
|
|
90
|
+
const res = await fetch(`${this.apiUrl}/connections`, {
|
|
91
|
+
headers: {
|
|
92
|
+
...this.token ? { Authorization: `Bearer ${this.token}` } : {}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new TunnelClientError(-1, `Failed to list connections: HTTP ${res.status}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return await res.json();
|
|
100
|
+
} catch {
|
|
101
|
+
throw new Error(`Failed to parse connections response: unexpected non-JSON body`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async resolveTunnelId() {
|
|
105
|
+
if (this.explicitTunnelId)
|
|
106
|
+
return this.explicitTunnelId;
|
|
107
|
+
if (this.cachedTunnelId && Date.now() - this.cacheTimestamp < this.cacheTtlMs) {
|
|
108
|
+
return this.cachedTunnelId;
|
|
109
|
+
}
|
|
110
|
+
const res = await fetch(`${this.apiUrl}/connections`, {
|
|
111
|
+
headers: {
|
|
112
|
+
...this.token ? { Authorization: `Bearer ${this.token}` } : {}
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
if (res.ok) {
|
|
116
|
+
const connections = await res.json();
|
|
117
|
+
const online = connections.find((c) => c.isLive);
|
|
118
|
+
const chosen = online ?? connections[0];
|
|
119
|
+
if (chosen) {
|
|
120
|
+
this.cachedTunnelId = chosen.tunnelId;
|
|
121
|
+
this.cacheTimestamp = Date.now();
|
|
122
|
+
return chosen.tunnelId;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
this.cachedTunnelId = null;
|
|
126
|
+
throw new TunnelClientError(-1, `No tunnel connection found. The user needs to set up Agent Tunnel first:
|
|
127
|
+
` + `1. Create a tunnel connection
|
|
128
|
+
` + "2. Connect the local machine from the Beeuni desktop app or run the tunnel connect command");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class FsNamespace {
|
|
133
|
+
client;
|
|
134
|
+
constructor(client) {
|
|
135
|
+
this.client = client;
|
|
136
|
+
}
|
|
137
|
+
async read(path, encoding = "utf-8") {
|
|
138
|
+
return await this.client.rpc("fs.read", { path, encoding });
|
|
139
|
+
}
|
|
140
|
+
async write(path, content, encoding = "utf-8") {
|
|
141
|
+
return await this.client.rpc("fs.write", { path, content, encoding });
|
|
142
|
+
}
|
|
143
|
+
async list(path, recursive = false) {
|
|
144
|
+
return await this.client.rpc("fs.list", { path, recursive });
|
|
145
|
+
}
|
|
146
|
+
async stat(path) {
|
|
147
|
+
return await this.client.rpc("fs.stat", { path });
|
|
148
|
+
}
|
|
149
|
+
async delete(path) {
|
|
150
|
+
return await this.client.rpc("fs.delete", { path });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
class ShellNamespace {
|
|
155
|
+
client;
|
|
156
|
+
constructor(client) {
|
|
157
|
+
this.client = client;
|
|
158
|
+
}
|
|
159
|
+
async exec(command, args = [], options) {
|
|
160
|
+
return await this.client.rpc("shell.exec", {
|
|
161
|
+
command,
|
|
162
|
+
args,
|
|
163
|
+
cwd: options?.cwd,
|
|
164
|
+
timeout: options?.timeout
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class CuaNamespace {
|
|
170
|
+
client;
|
|
171
|
+
constructor(client) {
|
|
172
|
+
this.client = client;
|
|
173
|
+
}
|
|
174
|
+
async ensure() {
|
|
175
|
+
return await this.client.rpc("desktop.cua.ensure", {});
|
|
176
|
+
}
|
|
177
|
+
async startDaemon() {
|
|
178
|
+
return await this.client.rpc("desktop.cua.start_daemon", {});
|
|
179
|
+
}
|
|
180
|
+
async status() {
|
|
181
|
+
return await this.client.rpc("desktop.cua.status", {});
|
|
182
|
+
}
|
|
183
|
+
async version() {
|
|
184
|
+
return await this.client.rpc("desktop.cua.version", {});
|
|
185
|
+
}
|
|
186
|
+
async listTools() {
|
|
187
|
+
return await this.client.rpc("desktop.cua.list_tools", {});
|
|
188
|
+
}
|
|
189
|
+
async describe(tool) {
|
|
190
|
+
return await this.client.rpc("desktop.cua.describe", { tool });
|
|
191
|
+
}
|
|
192
|
+
async call(tool, args = {}) {
|
|
193
|
+
return this.client.rpc("desktop.cua.call", { tool, args });
|
|
194
|
+
}
|
|
195
|
+
async listApps() {
|
|
196
|
+
return this.client.rpc("desktop.cua.list_apps", {});
|
|
197
|
+
}
|
|
198
|
+
async listWindows(params) {
|
|
199
|
+
return this.client.rpc("desktop.cua.list_windows", params ?? {});
|
|
200
|
+
}
|
|
201
|
+
async getWindowState(params) {
|
|
202
|
+
return this.client.rpc("desktop.cua.get_window_state", params);
|
|
203
|
+
}
|
|
204
|
+
async click(params) {
|
|
205
|
+
return this.client.rpc("desktop.cua.click", params);
|
|
206
|
+
}
|
|
207
|
+
async typeText(params) {
|
|
208
|
+
return this.client.rpc("desktop.cua.type_text", params);
|
|
209
|
+
}
|
|
210
|
+
async hotkey(params) {
|
|
211
|
+
return this.client.rpc("desktop.cua.hotkey", params);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// src/client/cli.ts
|
|
216
|
+
var S6_ENV_DIR = process.env.S6_ENV_DIR || "/run/s6/container_environment";
|
|
217
|
+
var FALLBACK_API_URL = "http://localhost:8008";
|
|
218
|
+
var ALL_COMMANDS = [
|
|
219
|
+
"status",
|
|
220
|
+
"fs_read",
|
|
221
|
+
"fs_write",
|
|
222
|
+
"fs_upload",
|
|
223
|
+
"fs_list",
|
|
224
|
+
"shell",
|
|
225
|
+
"cua_ensure",
|
|
226
|
+
"cua_start_daemon",
|
|
227
|
+
"cua_status",
|
|
228
|
+
"cua_version",
|
|
229
|
+
"cua_list_tools",
|
|
230
|
+
"cua_describe",
|
|
231
|
+
"cua_call",
|
|
232
|
+
"cua_list_apps",
|
|
233
|
+
"cua_list_windows",
|
|
234
|
+
"cua_get_window_state",
|
|
235
|
+
"cua_click",
|
|
236
|
+
"cua_type_text",
|
|
237
|
+
"cua_hotkey"
|
|
238
|
+
];
|
|
239
|
+
function getEnv(key) {
|
|
240
|
+
try {
|
|
241
|
+
const value = readFileSync(`${S6_ENV_DIR}/${key}`, "utf-8").trim();
|
|
242
|
+
if (value)
|
|
243
|
+
return value;
|
|
244
|
+
} catch {}
|
|
245
|
+
return process.env[key];
|
|
246
|
+
}
|
|
247
|
+
function getApiBase() {
|
|
248
|
+
const raw = getEnv("TUNNEL_API_URL") || FALLBACK_API_URL;
|
|
249
|
+
const url = raw.startsWith("http") ? raw : FALLBACK_API_URL;
|
|
250
|
+
return url.replace(/\/+$/, "").replace(/\/v1\/router\/?$/, "");
|
|
251
|
+
}
|
|
252
|
+
var client = new TunnelClient({
|
|
253
|
+
apiUrl: `${getApiBase()}/v1/tunnel`,
|
|
254
|
+
token: getEnv("TUNNEL_TOKEN") || "",
|
|
255
|
+
tunnelId: getEnv("TUNNEL_ID")
|
|
256
|
+
});
|
|
257
|
+
function out(data) {
|
|
258
|
+
console.log(JSON.stringify(data, null, 2));
|
|
259
|
+
}
|
|
260
|
+
function fail(message) {
|
|
261
|
+
out({ success: false, error: message });
|
|
262
|
+
process.exitCode = 1;
|
|
263
|
+
}
|
|
264
|
+
async function rpcSafe(method, params = {}) {
|
|
265
|
+
try {
|
|
266
|
+
const result = await client.rpc(method, params);
|
|
267
|
+
return { result, permissionRequired: false };
|
|
268
|
+
} catch (err) {
|
|
269
|
+
if (err instanceof TunnelClientError && err.isPermissionRequest) {
|
|
270
|
+
return {
|
|
271
|
+
result: null,
|
|
272
|
+
permissionRequired: true,
|
|
273
|
+
requestId: err.requestId || "unknown",
|
|
274
|
+
message: `Permission required. A permission request (${err.requestId}) has been sent to the user for approval.`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
throw err;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function call(method, params = {}) {
|
|
281
|
+
const response = await rpcSafe(method, params);
|
|
282
|
+
if (response.permissionRequired) {
|
|
283
|
+
out({
|
|
284
|
+
success: false,
|
|
285
|
+
permissionRequired: true,
|
|
286
|
+
requestId: response.requestId,
|
|
287
|
+
message: response.message
|
|
288
|
+
});
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
return response.result;
|
|
292
|
+
}
|
|
293
|
+
async function status() {
|
|
294
|
+
const connections = await client.getConnections();
|
|
295
|
+
if (connections.length === 0) {
|
|
296
|
+
return out({
|
|
297
|
+
success: true,
|
|
298
|
+
connections: [],
|
|
299
|
+
message: "No tunnel connections found. The user needs to set up Agent Tunnel first."
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
let hasOnline = false;
|
|
303
|
+
const mapped = connections.map((data) => {
|
|
304
|
+
if (data.isLive)
|
|
305
|
+
hasOnline = true;
|
|
306
|
+
return {
|
|
307
|
+
name: data.name || "Unnamed",
|
|
308
|
+
tunnelId: data.tunnelId,
|
|
309
|
+
status: data.isLive ? "ONLINE" : "OFFLINE",
|
|
310
|
+
capabilities: data.capabilities || [],
|
|
311
|
+
machineInfo: data.machineInfo || {}
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
out({
|
|
315
|
+
success: true,
|
|
316
|
+
connections: mapped,
|
|
317
|
+
hasOnline,
|
|
318
|
+
message: hasOnline ? undefined : "No tunnel is currently online. Ask the user to run `npx --yes @beeuni/agent-tunnel@latest connect` on their local machine."
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function fsRead(args) {
|
|
322
|
+
const result = await call("fs.read", {
|
|
323
|
+
path: args.path,
|
|
324
|
+
encoding: args.encoding || "utf-8"
|
|
325
|
+
});
|
|
326
|
+
if (result === null)
|
|
327
|
+
return;
|
|
328
|
+
const data = result;
|
|
329
|
+
out({ success: true, path: data.path || args.path, size: data.size, content: data.content });
|
|
330
|
+
}
|
|
331
|
+
async function fsWrite(args) {
|
|
332
|
+
const result = await call("fs.write", {
|
|
333
|
+
path: args.path,
|
|
334
|
+
content: args.content,
|
|
335
|
+
sha256: args.sha256,
|
|
336
|
+
encoding: args.encoding || "utf-8"
|
|
337
|
+
});
|
|
338
|
+
if (result === null)
|
|
339
|
+
return;
|
|
340
|
+
const data = result;
|
|
341
|
+
out({ success: true, path: data.path, size: data.size, sha256: data.sha256 });
|
|
342
|
+
}
|
|
343
|
+
async function fsUpload(args) {
|
|
344
|
+
if (typeof args.source !== "string" || !args.source)
|
|
345
|
+
return fail("source is required");
|
|
346
|
+
if (typeof args.path !== "string" || !args.path)
|
|
347
|
+
return fail("path is required");
|
|
348
|
+
const handle = await open(args.source, "r");
|
|
349
|
+
let bytes;
|
|
350
|
+
try {
|
|
351
|
+
const stats = await handle.stat();
|
|
352
|
+
if (!stats.isFile())
|
|
353
|
+
return fail("source must be a regular file");
|
|
354
|
+
if (stats.size > 3 * 1024 * 1024)
|
|
355
|
+
return fail("source exceeds the 3 MiB upload limit");
|
|
356
|
+
bytes = await handle.readFile();
|
|
357
|
+
if (bytes.length > 3 * 1024 * 1024)
|
|
358
|
+
return fail("source exceeds the 3 MiB upload limit");
|
|
359
|
+
} finally {
|
|
360
|
+
await handle.close();
|
|
361
|
+
}
|
|
362
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
363
|
+
const result = await call("fs.write", { path: args.path, content: bytes.toString("base64"), encoding: "base64", sha256 });
|
|
364
|
+
if (result === null) {
|
|
365
|
+
process.exitCode = 1;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const data = result;
|
|
369
|
+
if (data.sha256 !== sha256 || data.size !== bytes.length) {
|
|
370
|
+
return fail("Destination verification failed. Update the connected agent if its response has no sha256. The file may have been written.");
|
|
371
|
+
}
|
|
372
|
+
out({ success: true, path: data.path, size: data.size, sha256 });
|
|
373
|
+
}
|
|
374
|
+
async function fsList(args) {
|
|
375
|
+
const result = await call("fs.list", {
|
|
376
|
+
path: args.path,
|
|
377
|
+
recursive: args.recursive || false
|
|
378
|
+
});
|
|
379
|
+
if (result === null)
|
|
380
|
+
return;
|
|
381
|
+
const data = result;
|
|
382
|
+
out({ success: true, path: args.path, count: data.count, entries: data.entries });
|
|
383
|
+
}
|
|
384
|
+
async function shell(args) {
|
|
385
|
+
const result = await call("shell.exec", {
|
|
386
|
+
command: args.command,
|
|
387
|
+
args: args.args || [],
|
|
388
|
+
cwd: args.cwd,
|
|
389
|
+
timeout: args.timeout
|
|
390
|
+
});
|
|
391
|
+
if (result === null)
|
|
392
|
+
return;
|
|
393
|
+
const data = result;
|
|
394
|
+
out({
|
|
395
|
+
success: data.exitCode === 0,
|
|
396
|
+
exitCode: data.exitCode,
|
|
397
|
+
signal: data.signal,
|
|
398
|
+
stdout: data.stdout,
|
|
399
|
+
stderr: data.stderr,
|
|
400
|
+
stdoutTruncated: data.stdoutTruncated,
|
|
401
|
+
stderrTruncated: data.stderrTruncated
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
async function cuaRpc(method, args = {}) {
|
|
405
|
+
const result = await call(method, args);
|
|
406
|
+
if (result === null)
|
|
407
|
+
return;
|
|
408
|
+
out({ success: true, result });
|
|
409
|
+
}
|
|
410
|
+
async function cuaCall(args) {
|
|
411
|
+
const tool = args.tool;
|
|
412
|
+
if (!tool)
|
|
413
|
+
return fail("tool is required");
|
|
414
|
+
const result = await call("desktop.cua.call", {
|
|
415
|
+
tool,
|
|
416
|
+
args: args.args || {}
|
|
417
|
+
});
|
|
418
|
+
if (result === null)
|
|
419
|
+
return;
|
|
420
|
+
out({ success: true, tool, result });
|
|
421
|
+
}
|
|
422
|
+
async function cuaDescribe(args) {
|
|
423
|
+
const tool = args.tool;
|
|
424
|
+
if (!tool)
|
|
425
|
+
return fail("tool is required");
|
|
426
|
+
await cuaRpc("desktop.cua.describe", { tool });
|
|
427
|
+
}
|
|
428
|
+
var [cmd, rawArgs] = process.argv.slice(2);
|
|
429
|
+
if (!cmd) {
|
|
430
|
+
console.error(`Usage: agent-tunnel-cli <command> [args as JSON]
|
|
431
|
+
|
|
432
|
+
Available: ${ALL_COMMANDS.join(" | ")}`);
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
435
|
+
var args = {};
|
|
436
|
+
try {
|
|
437
|
+
args = rawArgs ? JSON.parse(rawArgs) : {};
|
|
438
|
+
} catch (err) {
|
|
439
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
try {
|
|
443
|
+
switch (cmd) {
|
|
444
|
+
case "status":
|
|
445
|
+
await status();
|
|
446
|
+
break;
|
|
447
|
+
case "fs_read":
|
|
448
|
+
await fsRead(args);
|
|
449
|
+
break;
|
|
450
|
+
case "fs_write":
|
|
451
|
+
await fsWrite(args);
|
|
452
|
+
break;
|
|
453
|
+
case "fs_upload":
|
|
454
|
+
await fsUpload(args);
|
|
455
|
+
break;
|
|
456
|
+
case "fs_list":
|
|
457
|
+
await fsList(args);
|
|
458
|
+
break;
|
|
459
|
+
case "shell":
|
|
460
|
+
await shell(args);
|
|
461
|
+
break;
|
|
462
|
+
case "cua_ensure":
|
|
463
|
+
await cuaRpc("desktop.cua.ensure");
|
|
464
|
+
break;
|
|
465
|
+
case "cua_start_daemon":
|
|
466
|
+
await cuaRpc("desktop.cua.start_daemon");
|
|
467
|
+
break;
|
|
468
|
+
case "cua_status":
|
|
469
|
+
await cuaRpc("desktop.cua.status");
|
|
470
|
+
break;
|
|
471
|
+
case "cua_version":
|
|
472
|
+
await cuaRpc("desktop.cua.version");
|
|
473
|
+
break;
|
|
474
|
+
case "cua_list_tools":
|
|
475
|
+
await cuaRpc("desktop.cua.list_tools");
|
|
476
|
+
break;
|
|
477
|
+
case "cua_describe":
|
|
478
|
+
await cuaDescribe(args);
|
|
479
|
+
break;
|
|
480
|
+
case "cua_call":
|
|
481
|
+
await cuaCall(args);
|
|
482
|
+
break;
|
|
483
|
+
case "cua_list_apps":
|
|
484
|
+
await cuaRpc("desktop.cua.list_apps", args);
|
|
485
|
+
break;
|
|
486
|
+
case "cua_list_windows":
|
|
487
|
+
await cuaRpc("desktop.cua.list_windows", args);
|
|
488
|
+
break;
|
|
489
|
+
case "cua_get_window_state":
|
|
490
|
+
await cuaRpc("desktop.cua.get_window_state", args);
|
|
491
|
+
break;
|
|
492
|
+
case "cua_click":
|
|
493
|
+
await cuaRpc("desktop.cua.click", args);
|
|
494
|
+
break;
|
|
495
|
+
case "cua_type_text":
|
|
496
|
+
await cuaRpc("desktop.cua.type_text", args);
|
|
497
|
+
break;
|
|
498
|
+
case "cua_hotkey":
|
|
499
|
+
await cuaRpc("desktop.cua.hotkey", args);
|
|
500
|
+
break;
|
|
501
|
+
default:
|
|
502
|
+
console.error(`Unknown command: ${cmd}
|
|
503
|
+
|
|
504
|
+
Available: ${ALL_COMMANDS.join(" | ")}`);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
} catch (err) {
|
|
508
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
509
|
+
out({ success: false, error: message });
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beeuni/agent-tunnel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Tunnel relay between cloud AI agents and local machines — server relay, local agent, client SDK, JSON-RPC, HMAC signing",
|
|
7
|
+
"bin": {
|
|
8
|
+
"agent-tunnel": "dist/agent-cli.js",
|
|
9
|
+
"agent-tunnel-cli": "dist/client-cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"hono": "^4.13.7",
|
|
17
|
+
"ws": "^8.18.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tunnel",
|
|
21
|
+
"relay",
|
|
22
|
+
"websocket",
|
|
23
|
+
"json-rpc",
|
|
24
|
+
"hmac",
|
|
25
|
+
"agent",
|
|
26
|
+
"sdk"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
}
|
|
32
|
+
}
|