@beeuni/agent-tunnel 0.1.0 → 0.1.2
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 +74 -14
- package/dist/agent-cli.js +464 -249
- package/package.json +1 -12
package/dist/agent-cli.js
CHANGED
|
@@ -2966,13 +2966,13 @@ if (typeof globalThis.WebSocket === "undefined") {
|
|
|
2966
2966
|
}
|
|
2967
2967
|
|
|
2968
2968
|
// src/agent/cli.ts
|
|
2969
|
-
import { existsSync as existsSync7, readFileSync as readFileSync11, rmSync as
|
|
2969
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
2970
2970
|
import { hostname as hostname4 } from "os";
|
|
2971
2971
|
import { join as join12 } from "path";
|
|
2972
2972
|
|
|
2973
2973
|
// src/agent/agent.ts
|
|
2974
2974
|
import { spawn as spawn3 } from "child_process";
|
|
2975
|
-
import { hostname as hostname2, platform as
|
|
2975
|
+
import { hostname as hostname2, platform as platform3, arch, release } from "os";
|
|
2976
2976
|
|
|
2977
2977
|
// src/agent/config.ts
|
|
2978
2978
|
import { chmodSync, existsSync, lstatSync, readFileSync as readFileSync2 } from "fs";
|
|
@@ -2991,7 +2991,9 @@ var TunnelErrorCode = {
|
|
|
2991
2991
|
AUTH_FAILED: -32007,
|
|
2992
2992
|
ACCESS_PENDING: -32010,
|
|
2993
2993
|
ACCESS_DENIED: -32011,
|
|
2994
|
-
ACCESS_OFF: -32012
|
|
2994
|
+
ACCESS_OFF: -32012,
|
|
2995
|
+
DESKTOP_PERMISSION_MISSING: -32013,
|
|
2996
|
+
ACCESS_UNCONFIRMED: -32014
|
|
2995
2997
|
};
|
|
2996
2998
|
var TunnelMethods = {
|
|
2997
2999
|
"fs.read": "filesystem",
|
|
@@ -3007,47 +3009,12 @@ var TunnelMethods = {
|
|
|
3007
3009
|
"desktop.cua.list_tools": "desktop",
|
|
3008
3010
|
"desktop.cua.describe": "desktop",
|
|
3009
3011
|
"desktop.cua.call": "desktop",
|
|
3010
|
-
"desktop.cua.bring_to_front": "desktop",
|
|
3011
|
-
"desktop.cua.check_for_update": "desktop",
|
|
3012
|
-
"desktop.cua.check_permissions": "desktop",
|
|
3013
|
-
"desktop.cua.click": "desktop",
|
|
3014
|
-
"desktop.cua.double_click": "desktop",
|
|
3015
|
-
"desktop.cua.drag": "desktop",
|
|
3016
|
-
"desktop.cua.end_session": "desktop",
|
|
3017
|
-
"desktop.cua.get_accessibility_tree": "desktop",
|
|
3018
|
-
"desktop.cua.get_agent_cursor_state": "desktop",
|
|
3019
|
-
"desktop.cua.get_config": "desktop",
|
|
3020
|
-
"desktop.cua.get_cursor_position": "desktop",
|
|
3021
|
-
"desktop.cua.get_recording_state": "desktop",
|
|
3022
|
-
"desktop.cua.get_screen_size": "desktop",
|
|
3023
|
-
"desktop.cua.get_window_state": "desktop",
|
|
3024
|
-
"desktop.cua.hotkey": "desktop",
|
|
3025
|
-
"desktop.cua.kill_app": "desktop",
|
|
3026
|
-
"desktop.cua.launch_app": "desktop",
|
|
3027
|
-
"desktop.cua.list_apps": "desktop",
|
|
3028
|
-
"desktop.cua.list_windows": "desktop",
|
|
3029
|
-
"desktop.cua.move_cursor": "desktop",
|
|
3030
|
-
"desktop.cua.page": "desktop",
|
|
3031
|
-
"desktop.cua.press_key": "desktop",
|
|
3032
|
-
"desktop.cua.replay_trajectory": "desktop",
|
|
3033
|
-
"desktop.cua.right_click": "desktop",
|
|
3034
|
-
"desktop.cua.scroll": "desktop",
|
|
3035
|
-
"desktop.cua.set_agent_cursor_enabled": "desktop",
|
|
3036
|
-
"desktop.cua.set_agent_cursor_motion": "desktop",
|
|
3037
|
-
"desktop.cua.set_agent_cursor_style": "desktop",
|
|
3038
|
-
"desktop.cua.set_config": "desktop",
|
|
3039
|
-
"desktop.cua.set_value": "desktop",
|
|
3040
|
-
"desktop.cua.start_recording": "desktop",
|
|
3041
|
-
"desktop.cua.install_ffmpeg": "desktop",
|
|
3042
|
-
"desktop.cua.start_session": "desktop",
|
|
3043
|
-
"desktop.cua.stop_recording": "desktop",
|
|
3044
|
-
"desktop.cua.type_text": "desktop",
|
|
3045
|
-
"desktop.cua.zoom": "desktop",
|
|
3046
3012
|
"tunnel.ping": null,
|
|
3047
3013
|
"tunnel.pong": null,
|
|
3048
3014
|
"tunnel.permission.revoked": null,
|
|
3049
3015
|
"tunnel.permissions.sync": null,
|
|
3050
|
-
"tunnel.token.rotated": null
|
|
3016
|
+
"tunnel.token.rotated": null,
|
|
3017
|
+
"tunnel.access.set": null
|
|
3051
3018
|
};
|
|
3052
3019
|
|
|
3053
3020
|
// src/shared/permissions.ts
|
|
@@ -3063,53 +3030,25 @@ var VALID_DESKTOP_FEATURES = new Set([
|
|
|
3063
3030
|
"accessibility",
|
|
3064
3031
|
"computer_use"
|
|
3065
3032
|
]);
|
|
3066
|
-
var
|
|
3067
|
-
"desktop.cua.ensure": "computer_use",
|
|
3068
|
-
"desktop.cua.start_daemon": "computer_use",
|
|
3069
|
-
"desktop.cua.status": "computer_use",
|
|
3070
|
-
"desktop.cua.version": "computer_use",
|
|
3071
|
-
"desktop.cua.list_tools": "computer_use",
|
|
3072
|
-
"desktop.cua.describe": "computer_use",
|
|
3073
|
-
"desktop.cua.bring_to_front": "windows",
|
|
3074
|
-
"desktop.cua.check_for_update": "computer_use",
|
|
3075
|
-
"desktop.cua.check_permissions": "computer_use",
|
|
3076
|
-
"desktop.cua.click": "mouse",
|
|
3077
|
-
"desktop.cua.double_click": "mouse",
|
|
3078
|
-
"desktop.cua.drag": "mouse",
|
|
3079
|
-
"desktop.cua.end_session": "computer_use",
|
|
3080
|
-
"desktop.cua.get_accessibility_tree": "accessibility",
|
|
3081
|
-
"desktop.cua.get_agent_cursor_state": "mouse",
|
|
3082
|
-
"desktop.cua.get_config": "computer_use",
|
|
3083
|
-
"desktop.cua.get_cursor_position": "mouse",
|
|
3084
|
-
"desktop.cua.get_recording_state": "computer_use",
|
|
3085
|
-
"desktop.cua.get_screen_size": "screenshot",
|
|
3086
|
-
"desktop.cua.get_window_state": "accessibility",
|
|
3087
|
-
"desktop.cua.hotkey": "keyboard",
|
|
3088
|
-
"desktop.cua.kill_app": "apps",
|
|
3089
|
-
"desktop.cua.launch_app": "apps",
|
|
3090
|
-
"desktop.cua.list_apps": "apps",
|
|
3091
|
-
"desktop.cua.list_windows": "windows",
|
|
3092
|
-
"desktop.cua.move_cursor": "mouse",
|
|
3093
|
-
"desktop.cua.page": "accessibility",
|
|
3094
|
-
"desktop.cua.press_key": "keyboard",
|
|
3095
|
-
"desktop.cua.replay_trajectory": "computer_use",
|
|
3096
|
-
"desktop.cua.right_click": "mouse",
|
|
3097
|
-
"desktop.cua.scroll": "keyboard",
|
|
3098
|
-
"desktop.cua.set_agent_cursor_enabled": "mouse",
|
|
3099
|
-
"desktop.cua.set_agent_cursor_motion": "mouse",
|
|
3100
|
-
"desktop.cua.set_agent_cursor_style": "mouse",
|
|
3101
|
-
"desktop.cua.set_config": "computer_use",
|
|
3102
|
-
"desktop.cua.set_value": "accessibility",
|
|
3103
|
-
"desktop.cua.start_recording": "screenshot",
|
|
3104
|
-
"desktop.cua.install_ffmpeg": "computer_use",
|
|
3105
|
-
"desktop.cua.start_session": "computer_use",
|
|
3106
|
-
"desktop.cua.stop_recording": "screenshot",
|
|
3107
|
-
"desktop.cua.type_text": "keyboard",
|
|
3108
|
-
"desktop.cua.zoom": "screenshot"
|
|
3109
|
-
};
|
|
3033
|
+
var CUA_PREFIX = "desktop.cua.";
|
|
3110
3034
|
function capabilityForMethod(method) {
|
|
3111
3035
|
const capability = TunnelMethods[method];
|
|
3112
|
-
|
|
3036
|
+
if (capability !== undefined)
|
|
3037
|
+
return capability;
|
|
3038
|
+
return method.startsWith(CUA_PREFIX) && method.length > CUA_PREFIX.length ? "desktop" : null;
|
|
3039
|
+
}
|
|
3040
|
+
function normalizeDesktopCall(method, params) {
|
|
3041
|
+
if (!method.startsWith(CUA_PREFIX) || method in TunnelMethods)
|
|
3042
|
+
return { method, params };
|
|
3043
|
+
const { permissionId, ...args } = params;
|
|
3044
|
+
return {
|
|
3045
|
+
method: "desktop.cua.call",
|
|
3046
|
+
params: {
|
|
3047
|
+
tool: method.slice(CUA_PREFIX.length),
|
|
3048
|
+
args,
|
|
3049
|
+
...permissionId === undefined ? {} : { permissionId }
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3113
3052
|
}
|
|
3114
3053
|
function isTunnelCapability(value) {
|
|
3115
3054
|
return VALID_CAPABILITIES.has(value);
|
|
@@ -3271,16 +3210,6 @@ function operationForMethod(method) {
|
|
|
3271
3210
|
const separator = method.indexOf(".");
|
|
3272
3211
|
return separator === -1 ? method : method.slice(separator + 1);
|
|
3273
3212
|
}
|
|
3274
|
-
function desktopFeatureForMethod(method, args = {}) {
|
|
3275
|
-
if (method === "desktop.cua.call") {
|
|
3276
|
-
const tool = args.tool;
|
|
3277
|
-
if (typeof tool !== "string" || tool.length === 0)
|
|
3278
|
-
return;
|
|
3279
|
-
const toolMethod = tool.startsWith("desktop.cua.") ? tool : `desktop.cua.${tool}`;
|
|
3280
|
-
return DESKTOP_METHOD_FEATURES[toolMethod];
|
|
3281
|
-
}
|
|
3282
|
-
return DESKTOP_METHOD_FEATURES[method];
|
|
3283
|
-
}
|
|
3284
3213
|
|
|
3285
3214
|
// src/agent/service-paths.ts
|
|
3286
3215
|
import { createHash } from "crypto";
|
|
@@ -3288,7 +3217,6 @@ import { readFileSync, writeFileSync } from "fs";
|
|
|
3288
3217
|
import { homedir } from "os";
|
|
3289
3218
|
import { join, resolve } from "path";
|
|
3290
3219
|
var SERVICE_LABEL = "ai.beeuni.agent-tunnel";
|
|
3291
|
-
var DEFAULT_INSTALL_BACKGROUND_SERVICE = true;
|
|
3292
3220
|
function defaultAgentTunnelHome() {
|
|
3293
3221
|
return join(homedir(), ".agent-tunnel");
|
|
3294
3222
|
}
|
|
@@ -3543,10 +3471,13 @@ function loadConfig(overrides = {}) {
|
|
|
3543
3471
|
import { spawn } from "child_process";
|
|
3544
3472
|
import { randomUUID } from "crypto";
|
|
3545
3473
|
import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync3, renameSync, rmSync, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
3474
|
+
import { platform } from "os";
|
|
3546
3475
|
import { isAbsolute, join as join3 } from "path";
|
|
3547
3476
|
var ACCESS_HOLD_MS = 20000;
|
|
3548
3477
|
var DESKTOP_APP_HEARTBEAT_MS = 30000;
|
|
3549
3478
|
var MAX_GRANT_MS = 24 * 3600000;
|
|
3479
|
+
var DENY_MS = 10 * 60000;
|
|
3480
|
+
var REQUEST_FRESH_MS = 60000;
|
|
3550
3481
|
var MODES = ["ask", "always", "off"];
|
|
3551
3482
|
var COMPAT = { mode: "always", grantedUntil: null, deniedUntil: null, keepAwake: false };
|
|
3552
3483
|
var accessFilePath = (home = agentTunnelHome()) => join3(home, "access.json");
|
|
@@ -3596,6 +3527,99 @@ function decideAccess(state, now = Date.now()) {
|
|
|
3596
3527
|
const granted = state.grantedUntil ? Date.parse(state.grantedUntil) : 0;
|
|
3597
3528
|
return granted > now && granted - now <= MAX_GRANT_MS ? "run" : "ask";
|
|
3598
3529
|
}
|
|
3530
|
+
var keepAwakeSupported = (os) => os === "darwin" || os === "linux";
|
|
3531
|
+
function parseAccessChange(params) {
|
|
3532
|
+
const raw = params && typeof params === "object" ? params : {};
|
|
3533
|
+
const change = {};
|
|
3534
|
+
if (raw.mode !== undefined) {
|
|
3535
|
+
if (!MODES.includes(raw.mode))
|
|
3536
|
+
throw new Error(`mode must be one of ${MODES.join(", ")}`);
|
|
3537
|
+
change.mode = raw.mode;
|
|
3538
|
+
}
|
|
3539
|
+
if (raw.grantMinutes !== undefined) {
|
|
3540
|
+
if (typeof raw.grantMinutes !== "number" || !(raw.grantMinutes > 0))
|
|
3541
|
+
throw new Error("grantMinutes must be a positive number");
|
|
3542
|
+
change.grantMinutes = raw.grantMinutes;
|
|
3543
|
+
}
|
|
3544
|
+
for (const key of ["revoke", "deny", "keepAwake"]) {
|
|
3545
|
+
if (raw[key] === undefined)
|
|
3546
|
+
continue;
|
|
3547
|
+
if (typeof raw[key] !== "boolean")
|
|
3548
|
+
throw new Error(`${key} must be true or false`);
|
|
3549
|
+
change[key] = raw[key];
|
|
3550
|
+
}
|
|
3551
|
+
if (Object.keys(change).length === 0)
|
|
3552
|
+
throw new Error("No access change given");
|
|
3553
|
+
return change;
|
|
3554
|
+
}
|
|
3555
|
+
function accessCommand(change) {
|
|
3556
|
+
const npx = "npx @beeuni/agent-tunnel@latest";
|
|
3557
|
+
if (change.keepAwake !== undefined && Object.keys(change).length === 1)
|
|
3558
|
+
return `${npx} keep-awake ${change.keepAwake ? "on" : "off"}`;
|
|
3559
|
+
const args = [change.mode, change.grantMinutes !== undefined ? `--allow-for ${change.grantMinutes}` : undefined];
|
|
3560
|
+
return [`${npx} access`, ...args.filter(Boolean)].join(" ");
|
|
3561
|
+
}
|
|
3562
|
+
function nextAccess(current, change, now = Date.now()) {
|
|
3563
|
+
const next = { ...current };
|
|
3564
|
+
if (change.mode !== undefined) {
|
|
3565
|
+
if (!MODES.includes(change.mode))
|
|
3566
|
+
throw new Error(`mode must be one of ${MODES.join(", ")}`);
|
|
3567
|
+
if (change.mode !== current.mode) {
|
|
3568
|
+
next.grantedUntil = null;
|
|
3569
|
+
next.deniedUntil = null;
|
|
3570
|
+
}
|
|
3571
|
+
next.mode = change.mode;
|
|
3572
|
+
}
|
|
3573
|
+
if (change.grantMinutes !== undefined) {
|
|
3574
|
+
const minutes = Number(change.grantMinutes);
|
|
3575
|
+
if (!Number.isFinite(minutes) || minutes <= 0)
|
|
3576
|
+
throw new Error("grantMinutes must be a positive number");
|
|
3577
|
+
next.grantedUntil = new Date(now + Math.min(minutes * 60000, MAX_GRANT_MS)).toISOString();
|
|
3578
|
+
next.deniedUntil = null;
|
|
3579
|
+
}
|
|
3580
|
+
if (change.revoke === true)
|
|
3581
|
+
next.grantedUntil = null;
|
|
3582
|
+
if (change.deny === true) {
|
|
3583
|
+
next.grantedUntil = null;
|
|
3584
|
+
next.deniedUntil = new Date(now + DENY_MS).toISOString();
|
|
3585
|
+
}
|
|
3586
|
+
if (change.keepAwake !== undefined)
|
|
3587
|
+
next.keepAwake = change.keepAwake === true;
|
|
3588
|
+
return next;
|
|
3589
|
+
}
|
|
3590
|
+
function accessWidens(current, next, now = Date.now()) {
|
|
3591
|
+
if (next.mode === "always")
|
|
3592
|
+
return current.mode !== "always";
|
|
3593
|
+
if (next.mode !== "ask")
|
|
3594
|
+
return false;
|
|
3595
|
+
const reach = (state) => state.mode === "ask" && decideAccess(state, now) === "run" ? Date.parse(state.grantedUntil) : 0;
|
|
3596
|
+
return reach(next) > reach(current);
|
|
3597
|
+
}
|
|
3598
|
+
function pendingAccessRequest(home = agentTunnelHome(), now = Date.now()) {
|
|
3599
|
+
const request = readAccessRequest(home);
|
|
3600
|
+
return request && now - Date.parse(request.requestedAt) < REQUEST_FRESH_MS ? request : null;
|
|
3601
|
+
}
|
|
3602
|
+
function accessView(home = agentTunnelHome(), os = platform(), now = Date.now()) {
|
|
3603
|
+
const state = readAccess(home);
|
|
3604
|
+
const request = pendingAccessRequest(home, now);
|
|
3605
|
+
return {
|
|
3606
|
+
...state,
|
|
3607
|
+
grantedUntil: state.grantedUntil && Date.parse(state.grantedUntil) > now ? state.grantedUntil : null,
|
|
3608
|
+
deniedUntil: state.deniedUntil && Date.parse(state.deniedUntil) > now ? state.deniedUntil : null,
|
|
3609
|
+
keepAwakeSupported: keepAwakeSupported(os),
|
|
3610
|
+
pendingRequest: request ? { id: request.id, capability: request.capability, requestedAt: request.requestedAt, ...request.change ? { change: request.change } : {} } : null
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
function applyAccess(change, home = agentTunnelHome(), now = Date.now()) {
|
|
3614
|
+
const request = readAccessRequest(home);
|
|
3615
|
+
const declinesChange = change.deny === true && Boolean(request?.change);
|
|
3616
|
+
const own = declinesChange ? { ...change, deny: undefined } : change;
|
|
3617
|
+
writeAccess(nextAccess(readAccess(home), own, now), home);
|
|
3618
|
+
const answers = own.mode !== undefined || own.grantMinutes !== undefined || change.deny === true;
|
|
3619
|
+
if (request && answers && !(Date.parse(request.requestedAt) > now))
|
|
3620
|
+
rmSync(accessRequestPath(home), { force: true });
|
|
3621
|
+
return accessView(home, platform(), now);
|
|
3622
|
+
}
|
|
3599
3623
|
function writeAccessRequest(input, home = agentTunnelHome()) {
|
|
3600
3624
|
const request = { id: randomUUID(), requestedAt: new Date().toISOString(), ...input };
|
|
3601
3625
|
writePrivateJson(accessRequestPath(home), home, request);
|
|
@@ -3653,10 +3677,10 @@ function wakeDesktopApp(home = agentTunnelHome(), now = Date.now()) {
|
|
|
3653
3677
|
return "no-app";
|
|
3654
3678
|
}
|
|
3655
3679
|
}
|
|
3656
|
-
function keepAwakeCommand(
|
|
3657
|
-
if (
|
|
3680
|
+
function keepAwakeCommand(platform2, pid) {
|
|
3681
|
+
if (platform2 === "darwin")
|
|
3658
3682
|
return { command: "caffeinate", args: ["-s", "-w", String(pid)] };
|
|
3659
|
-
if (
|
|
3683
|
+
if (platform2 === "linux") {
|
|
3660
3684
|
return {
|
|
3661
3685
|
command: "systemd-inhibit",
|
|
3662
3686
|
args: ["--what=sleep", "--who=Beeuni", "--why=Keep this computer reachable", "--mode=block", "tail", `--pid=${pid}`, "-f", "/dev/null"]
|
|
@@ -3669,7 +3693,7 @@ function keepAwakeCommand(platform, pid) {
|
|
|
3669
3693
|
import { execFileSync, spawn as spawn2 } from "child_process";
|
|
3670
3694
|
import { createHash as createHash2 } from "crypto";
|
|
3671
3695
|
import { readFileSync as readFileSync4 } from "fs";
|
|
3672
|
-
import { hostname, platform } from "os";
|
|
3696
|
+
import { hostname, platform as platform2 } from "os";
|
|
3673
3697
|
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
3674
3698
|
var TUNNEL_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
3675
3699
|
var SETUP_TOKEN_PATTERN = /^beeuni_tnl_[A-Za-z0-9_-]{32,64}$/;
|
|
@@ -3776,7 +3800,7 @@ function parseDeviceAuthStatus(value) {
|
|
|
3776
3800
|
}
|
|
3777
3801
|
}
|
|
3778
3802
|
function machineDisplayName({
|
|
3779
|
-
os =
|
|
3803
|
+
os = platform2(),
|
|
3780
3804
|
host = hostname(),
|
|
3781
3805
|
env = process.env,
|
|
3782
3806
|
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"] })
|
|
@@ -3790,7 +3814,7 @@ function machineDisplayName({
|
|
|
3790
3814
|
}
|
|
3791
3815
|
}
|
|
3792
3816
|
function machineId({
|
|
3793
|
-
os =
|
|
3817
|
+
os = platform2(),
|
|
3794
3818
|
run = (command, args) => execFileSync(command, args, { encoding: "utf8", timeout: 2000, stdio: ["ignore", "pipe", "ignore"] }),
|
|
3795
3819
|
read = (path) => readFileSync4(path, "utf8")
|
|
3796
3820
|
} = {}) {
|
|
@@ -3859,7 +3883,7 @@ function openBrowser(url) {
|
|
|
3859
3883
|
darwin: ["open", [safeUrl]],
|
|
3860
3884
|
win32: ["rundll32.exe", ["url.dll,FileProtocolHandler", safeUrl]]
|
|
3861
3885
|
};
|
|
3862
|
-
const [command, args] = opener[
|
|
3886
|
+
const [command, args] = opener[platform2()] ?? ["xdg-open", [safeUrl]];
|
|
3863
3887
|
try {
|
|
3864
3888
|
spawn2(command, args, { detached: true, stdio: "ignore" }).unref();
|
|
3865
3889
|
} catch {}
|
|
@@ -4012,6 +4036,10 @@ function verifyMessageSignature(signingKey, payload, nonce, signature) {
|
|
|
4012
4036
|
|
|
4013
4037
|
// src/agent/agent.ts
|
|
4014
4038
|
var AGENT_VERSION = agentTunnelVersion();
|
|
4039
|
+
function openRelaySocket(url) {
|
|
4040
|
+
const RelaySocket = WebSocket;
|
|
4041
|
+
return new RelaySocket(url, { headers: { "User-Agent": `beeuni-agent-tunnel/${AGENT_VERSION}` } });
|
|
4042
|
+
}
|
|
4015
4043
|
var AUTH_REJECTED_CLOSE_CODES = [4001, 4003];
|
|
4016
4044
|
var RELAY_FAULT_4001_REASONS = new Set(["auth timeout", "authentication error", "authentication response failed"]);
|
|
4017
4045
|
function isCredentialRejection(code, reason = "") {
|
|
@@ -4029,6 +4057,7 @@ function log(icon, msg) {
|
|
|
4029
4057
|
}
|
|
4030
4058
|
var LIVENESS_TIMEOUT_MS = 75000;
|
|
4031
4059
|
var WATCHDOG_INTERVAL_MS = 5000;
|
|
4060
|
+
var DESKTOP_PROBE_EVERY_TICKS = 12;
|
|
4032
4061
|
var CLOCK_JUMP_MS = 30000;
|
|
4033
4062
|
var REJECTED_RETRY_MS = 5 * 60000;
|
|
4034
4063
|
var PAIR_AGAIN_COMMAND = "npx @beeuni/agent-tunnel@latest connect --reauth";
|
|
@@ -4077,6 +4106,9 @@ class TunnelAgent {
|
|
|
4077
4106
|
standbyAttempts = 0;
|
|
4078
4107
|
lastRejectedLogAt = 0;
|
|
4079
4108
|
sentAccessKey = null;
|
|
4109
|
+
desktopPermissions = null;
|
|
4110
|
+
probingDesktop = false;
|
|
4111
|
+
watchdogTicks = 0;
|
|
4080
4112
|
keepAwake = null;
|
|
4081
4113
|
keepAwakeFailed = false;
|
|
4082
4114
|
reconnectTimer = null;
|
|
@@ -4109,7 +4141,7 @@ class TunnelAgent {
|
|
|
4109
4141
|
this.connectStartedAt = this.now();
|
|
4110
4142
|
this.setStatus("connecting");
|
|
4111
4143
|
try {
|
|
4112
|
-
this.ws =
|
|
4144
|
+
this.ws = openRelaySocket(new URL(wsUrl).href);
|
|
4113
4145
|
this.setupWsHandlers();
|
|
4114
4146
|
} catch (err) {
|
|
4115
4147
|
log(`${c.red}✗${c.reset}`, `Connection failed`);
|
|
@@ -4168,7 +4200,8 @@ class TunnelAgent {
|
|
|
4168
4200
|
token: trustedCredential(this.config.token, "token"),
|
|
4169
4201
|
capabilities: this.registry.getCapabilityNames(),
|
|
4170
4202
|
agentVersion: AGENT_VERSION,
|
|
4171
|
-
reportsAccess: true
|
|
4203
|
+
reportsAccess: true,
|
|
4204
|
+
controlsAccess: true
|
|
4172
4205
|
});
|
|
4173
4206
|
});
|
|
4174
4207
|
this.ws.addEventListener("message", (event) => {
|
|
@@ -4262,6 +4295,7 @@ class TunnelAgent {
|
|
|
4262
4295
|
this.standbyAttempts = 0;
|
|
4263
4296
|
this.setStatus("online");
|
|
4264
4297
|
this.reportAccess();
|
|
4298
|
+
this.probeDesktopPermissions();
|
|
4265
4299
|
if (this.stableConnectionTimer)
|
|
4266
4300
|
clearTimeout(this.stableConnectionTimer);
|
|
4267
4301
|
this.stableConnectionTimer = setTimeout(() => {
|
|
@@ -4311,6 +4345,10 @@ class TunnelAgent {
|
|
|
4311
4345
|
log(`${c.yellow}!${c.reset}`, `Token rotated — reconnecting with new token`);
|
|
4312
4346
|
return;
|
|
4313
4347
|
}
|
|
4348
|
+
if ("id" in msg && msg.id && msg.method === "tunnel.access.set") {
|
|
4349
|
+
await this.handleAccessChange(msg);
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4314
4352
|
if ("id" in msg && msg.id) {
|
|
4315
4353
|
await this.handleRpcRequest(msg);
|
|
4316
4354
|
return;
|
|
@@ -4337,7 +4375,8 @@ class TunnelAgent {
|
|
|
4337
4375
|
return true;
|
|
4338
4376
|
}
|
|
4339
4377
|
async handleRpcRequest(request) {
|
|
4340
|
-
const {
|
|
4378
|
+
const { method, params } = normalizeDesktopCall(request.method, request.params ?? {});
|
|
4379
|
+
const { id } = request;
|
|
4341
4380
|
const permissionId = params.permissionId;
|
|
4342
4381
|
const permission = this.permissionGuard.getPermissionForMethod(permissionId, method);
|
|
4343
4382
|
if (!permission) {
|
|
@@ -4362,7 +4401,8 @@ class TunnelAgent {
|
|
|
4362
4401
|
this.sendSignedResult(id, result);
|
|
4363
4402
|
} catch (err) {
|
|
4364
4403
|
const message = err instanceof Error ? err.message : String(err);
|
|
4365
|
-
|
|
4404
|
+
const code = err?.code;
|
|
4405
|
+
this.sendSignedError(id, typeof code === "number" && code <= -32000 && code >= -32099 ? code : -32003, message);
|
|
4366
4406
|
}
|
|
4367
4407
|
}
|
|
4368
4408
|
sendSignedResult(id, result) {
|
|
@@ -4415,7 +4455,7 @@ class TunnelAgent {
|
|
|
4415
4455
|
hostname: hostname2(),
|
|
4416
4456
|
displayName: this.displayName ??= machineDisplayName(),
|
|
4417
4457
|
...this.hardwareId ? { machineId: this.hardwareId } : {},
|
|
4418
|
-
platform:
|
|
4458
|
+
platform: platform3(),
|
|
4419
4459
|
arch: arch(),
|
|
4420
4460
|
osVersion: release(),
|
|
4421
4461
|
agentVersion: AGENT_VERSION,
|
|
@@ -4436,10 +4476,11 @@ class TunnelAgent {
|
|
|
4436
4476
|
try {
|
|
4437
4477
|
const pending = readAccessRequest(this.home);
|
|
4438
4478
|
if (pending && Date.now() - Date.parse(pending.requestedAt) < hold) {
|
|
4439
|
-
requestId = pending.id;
|
|
4479
|
+
requestId = pending.change ? null : pending.id;
|
|
4440
4480
|
} else {
|
|
4441
4481
|
requestId = writeAccessRequest({ capability: capabilityForMethod(method) ?? method, method }, this.home).id;
|
|
4442
4482
|
wakeDesktopApp(this.home);
|
|
4483
|
+
this.reportAccess();
|
|
4443
4484
|
}
|
|
4444
4485
|
} catch {}
|
|
4445
4486
|
const deadline = Date.now() + hold;
|
|
@@ -4454,15 +4495,83 @@ class TunnelAgent {
|
|
|
4454
4495
|
}
|
|
4455
4496
|
return ACCESS_ERRORS.pending;
|
|
4456
4497
|
}
|
|
4498
|
+
async handleAccessChange(request) {
|
|
4499
|
+
let change;
|
|
4500
|
+
try {
|
|
4501
|
+
change = parseAccessChange(request.params);
|
|
4502
|
+
} catch (err) {
|
|
4503
|
+
this.sendSignedError(request.id, TunnelErrorCode.LOCAL_ERROR, err instanceof Error ? err.message : String(err));
|
|
4504
|
+
return;
|
|
4505
|
+
}
|
|
4506
|
+
try {
|
|
4507
|
+
const view = await this.changeAccess(change);
|
|
4508
|
+
this.reportAccess();
|
|
4509
|
+
this.sendSignedResult(request.id, view);
|
|
4510
|
+
} catch (err) {
|
|
4511
|
+
this.reportAccess();
|
|
4512
|
+
this.sendSignedError(request.id, TunnelErrorCode.ACCESS_UNCONFIRMED, err instanceof Error ? err.message : String(err));
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
async changeAccess(change) {
|
|
4516
|
+
const current = readAccess(this.home);
|
|
4517
|
+
const target = nextAccess(current, change, this.now());
|
|
4518
|
+
if (!accessWidens(current, target, this.now()))
|
|
4519
|
+
return applyAccess(change, this.home, this.now());
|
|
4520
|
+
const unconfirmed = (why) => new Error(`computer_access_unconfirmed: ${why}`);
|
|
4521
|
+
const asked = writeAccessRequest({ capability: "access", method: "tunnel.access.set", change }, this.home);
|
|
4522
|
+
this.reportAccess();
|
|
4523
|
+
if (wakeDesktopApp(this.home) === "no-app") {
|
|
4524
|
+
clearAccessRequest(asked.id, this.home);
|
|
4525
|
+
throw unconfirmed(`Only the owner can allow more access, on the computer itself. Run ${accessCommand(change)} there.`);
|
|
4526
|
+
}
|
|
4527
|
+
const hold = this.options.accessHoldMs ?? ACCESS_HOLD_MS;
|
|
4528
|
+
const deadline = Date.now() + hold;
|
|
4529
|
+
while (Date.now() < deadline) {
|
|
4530
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(100, hold / 4)));
|
|
4531
|
+
if (!accessWidens(readAccess(this.home), target, this.now())) {
|
|
4532
|
+
clearAccessRequest(asked.id, this.home);
|
|
4533
|
+
return accessView(this.home, platform3(), this.now());
|
|
4534
|
+
}
|
|
4535
|
+
if (readAccessRequest(this.home)?.id !== asked.id)
|
|
4536
|
+
throw unconfirmed("The owner declined on the computer.");
|
|
4537
|
+
}
|
|
4538
|
+
clearAccessRequest(asked.id, this.home);
|
|
4539
|
+
throw unconfirmed(`Nobody confirmed on the computer within ${Math.round(hold / 1000)} seconds.`);
|
|
4540
|
+
}
|
|
4457
4541
|
reportAccess() {
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
const
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4542
|
+
if (this.status !== "online")
|
|
4543
|
+
return;
|
|
4544
|
+
const view = accessView(this.home, platform3(), this.now());
|
|
4545
|
+
const params = {
|
|
4546
|
+
mode: view.mode,
|
|
4547
|
+
grantedUntil: view.grantedUntil,
|
|
4548
|
+
deniedUntil: view.deniedUntil,
|
|
4549
|
+
keepAwake: view.keepAwake,
|
|
4550
|
+
keepAwakeSupported: view.keepAwakeSupported,
|
|
4551
|
+
pendingRequest: view.pendingRequest ? { capability: view.pendingRequest.capability, requestedAt: view.pendingRequest.requestedAt, ...view.pendingRequest.change ? { change: view.pendingRequest.change } : {} } : null,
|
|
4552
|
+
desktopPermissions: this.desktopPermissions
|
|
4553
|
+
};
|
|
4554
|
+
const key = JSON.stringify(params);
|
|
4555
|
+
if (key === this.sentAccessKey)
|
|
4463
4556
|
return;
|
|
4464
4557
|
this.sentAccessKey = key;
|
|
4465
|
-
this.sendSigned({ jsonrpc: "2.0", method: "tunnel.access.state", params
|
|
4558
|
+
this.sendSigned({ jsonrpc: "2.0", method: "tunnel.access.state", params });
|
|
4559
|
+
}
|
|
4560
|
+
async probeDesktopPermissions() {
|
|
4561
|
+
const call = this.registry.getHandler("desktop.cua.call");
|
|
4562
|
+
if (!call || this.probingDesktop)
|
|
4563
|
+
return;
|
|
4564
|
+
this.probingDesktop = true;
|
|
4565
|
+
try {
|
|
4566
|
+
const report = await call({ tool: "check_permissions", args: {}, __permission: { capability: "desktop" } });
|
|
4567
|
+
this.desktopPermissions = {
|
|
4568
|
+
accessibility: report?.accessibility === true,
|
|
4569
|
+
screenRecording: report?.screen_recording === true
|
|
4570
|
+
};
|
|
4571
|
+
} catch {} finally {
|
|
4572
|
+
this.probingDesktop = false;
|
|
4573
|
+
}
|
|
4574
|
+
this.reportAccess();
|
|
4466
4575
|
}
|
|
4467
4576
|
syncKeepAwake(want) {
|
|
4468
4577
|
if (!want) {
|
|
@@ -4473,7 +4582,7 @@ class TunnelAgent {
|
|
|
4473
4582
|
}
|
|
4474
4583
|
if (this.keepAwake || this.keepAwakeFailed)
|
|
4475
4584
|
return;
|
|
4476
|
-
const blocker = keepAwakeCommand(
|
|
4585
|
+
const blocker = keepAwakeCommand(platform3(), process.pid);
|
|
4477
4586
|
if (!blocker)
|
|
4478
4587
|
return;
|
|
4479
4588
|
const child = spawn3(blocker.command, blocker.args, { stdio: "ignore" });
|
|
@@ -4517,6 +4626,8 @@ class TunnelAgent {
|
|
|
4517
4626
|
this.syncKeepAwake(access.keepAwake);
|
|
4518
4627
|
this.reportAccess();
|
|
4519
4628
|
} catch {}
|
|
4629
|
+
if (++this.watchdogTicks % DESKTOP_PROBE_EVERY_TICKS === 0 && this.status === "online")
|
|
4630
|
+
this.probeDesktopPermissions();
|
|
4520
4631
|
}, interval);
|
|
4521
4632
|
this.watchdog.unref?.();
|
|
4522
4633
|
}
|
|
@@ -4582,7 +4693,7 @@ class TunnelAgent {
|
|
|
4582
4693
|
}
|
|
4583
4694
|
|
|
4584
4695
|
// src/agent/banner.ts
|
|
4585
|
-
import { arch as arch2, hostname as hostname3, platform as
|
|
4696
|
+
import { arch as arch2, hostname as hostname3, platform as platform4 } from "os";
|
|
4586
4697
|
var BOX_WIDTH = 60;
|
|
4587
4698
|
var BAR_WIDTH = 50;
|
|
4588
4699
|
var BAR_FRAMES = 14;
|
|
@@ -4632,7 +4743,7 @@ async function printStartupBanner({
|
|
|
4632
4743
|
blank();
|
|
4633
4744
|
row(` ${c.dim}tunnel${c.reset} ${c.white}${truncate(tunnelId, 40)}${c.reset}`);
|
|
4634
4745
|
row(` ${c.dim}relay${c.reset} ${c.white}${truncate(apiUrl, 40)}${c.reset}`);
|
|
4635
|
-
row(` ${c.dim}machine${c.reset} ${c.white}${truncate(hostname3(), 28)}${c.reset} ${c.dim}(${
|
|
4746
|
+
row(` ${c.dim}machine${c.reset} ${c.white}${truncate(hostname3(), 28)}${c.reset} ${c.dim}(${platform4()} ${arch2()})${c.reset}`);
|
|
4636
4747
|
row(` ${c.dim}access${c.reset} ${capabilityRow}`);
|
|
4637
4748
|
blank();
|
|
4638
4749
|
console.log(` ${c.gray}╰${"─".repeat(BOX_WIDTH - brand.length - 3)} ${c.dim}created by ${c.cyan}beeuni${c.reset} ${c.gray}─╯${c.reset}`);
|
|
@@ -4666,8 +4777,8 @@ class CapabilityRegistry {
|
|
|
4666
4777
|
|
|
4667
4778
|
// src/agent/capabilities/desktop/cua-driver.ts
|
|
4668
4779
|
import { spawn as spawn4 } from "child_process";
|
|
4669
|
-
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, statSync as statSync2 } from "fs";
|
|
4670
|
-
import { homedir as homedir3, platform as
|
|
4780
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
4781
|
+
import { homedir as homedir3, platform as platform5, tmpdir } from "os";
|
|
4671
4782
|
import { basename as basename2, dirname as dirname3, join as join5 } from "path";
|
|
4672
4783
|
var MAX_DRIVER_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
4673
4784
|
var DRIVER_ENV_KEYS = [
|
|
@@ -4785,7 +4896,7 @@ function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
|
|
|
4785
4896
|
clearTimeout(timer);
|
|
4786
4897
|
if (code !== 0) {
|
|
4787
4898
|
const detail = stderr.trim() || stdout.trim();
|
|
4788
|
-
reject(new Error(`${cmd} failed (${code})${detail ? `: ${detail}` : ""}`));
|
|
4899
|
+
reject(new Error(`${basename2(cmd)} failed (${code})${detail ? `: ${detail}` : ""}`));
|
|
4789
4900
|
} else {
|
|
4790
4901
|
resolve2({ stdout, stderr });
|
|
4791
4902
|
}
|
|
@@ -4833,7 +4944,8 @@ class CuaDriver {
|
|
|
4833
4944
|
this.binary = options.binary ?? null;
|
|
4834
4945
|
this.embedded = options.embedded;
|
|
4835
4946
|
const uid = typeof process.getuid === "function" ? process.getuid() : "user";
|
|
4836
|
-
|
|
4947
|
+
const home = serviceLabelFor(agentTunnelHome()).slice(SERVICE_LABEL.length);
|
|
4948
|
+
this.socketPath = options.socketPath ?? join5(tmpdir(), `beeuni-cua-${uid}${home}.sock`);
|
|
4837
4949
|
this.hostBundleId = options.hostBundleId;
|
|
4838
4950
|
}
|
|
4839
4951
|
isEmbedded(binary) {
|
|
@@ -4913,7 +5025,9 @@ class CuaDriver {
|
|
|
4913
5025
|
const bin = await this.ensureInstalled();
|
|
4914
5026
|
if (this.isEmbedded(bin)) {
|
|
4915
5027
|
await this.startEmbeddedDaemon(bin);
|
|
4916
|
-
|
|
5028
|
+
this.daemonReady = true;
|
|
5029
|
+
return { ok: true };
|
|
5030
|
+
} else if (platform5() === "darwin") {
|
|
4917
5031
|
const child = spawn4("open", ["-n", "-g", "-a", "CuaDriver", "--args", "serve"], {
|
|
4918
5032
|
detached: true,
|
|
4919
5033
|
stdio: "ignore",
|
|
@@ -4942,6 +5056,12 @@ class CuaDriver {
|
|
|
4942
5056
|
if (this.daemon && this.daemon.exitCode === null)
|
|
4943
5057
|
return;
|
|
4944
5058
|
const { env, socket } = this.mode(bin);
|
|
5059
|
+
if (existsSync3(this.socketPath)) {
|
|
5060
|
+
await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
|
|
5061
|
+
return;
|
|
5062
|
+
});
|
|
5063
|
+
rmSync2(this.socketPath, { force: true });
|
|
5064
|
+
}
|
|
4945
5065
|
const child = spawn4(bin, ["serve", "--embedded", "--no-permissions-gate", ...socket], {
|
|
4946
5066
|
stdio: "ignore",
|
|
4947
5067
|
env
|
|
@@ -4965,12 +5085,32 @@ class CuaDriver {
|
|
|
4965
5085
|
throw new Error("cua-driver did not start (no socket after 10 s)");
|
|
4966
5086
|
}
|
|
4967
5087
|
}
|
|
5088
|
+
async permissionOwner() {
|
|
5089
|
+
return this.isEmbedded(await this.ensureInstalled()) ? "Beeuni" : "CuaDriver";
|
|
5090
|
+
}
|
|
5091
|
+
async missingPermissions() {
|
|
5092
|
+
const report = await this.call("check_permissions", {});
|
|
5093
|
+
const missing = [];
|
|
5094
|
+
if (report?.accessibility === false)
|
|
5095
|
+
missing.push("Accessibility");
|
|
5096
|
+
if (report?.screen_recording === false)
|
|
5097
|
+
missing.push("Screen Recording");
|
|
5098
|
+
return missing;
|
|
5099
|
+
}
|
|
4968
5100
|
stop() {
|
|
4969
5101
|
this.daemon?.kill();
|
|
4970
5102
|
this.daemon = null;
|
|
4971
5103
|
this.daemonReady = false;
|
|
4972
5104
|
}
|
|
4973
5105
|
async ensureDaemonReady() {
|
|
5106
|
+
const bin = await this.ensureInstalled();
|
|
5107
|
+
if (this.isEmbedded(bin)) {
|
|
5108
|
+
if (this.daemon?.exitCode === null && existsSync3(this.socketPath))
|
|
5109
|
+
return;
|
|
5110
|
+
this.stop();
|
|
5111
|
+
await this.startDaemon();
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
4974
5114
|
if (this.daemonReady)
|
|
4975
5115
|
return;
|
|
4976
5116
|
try {
|
|
@@ -4987,44 +5127,6 @@ class CuaDriver {
|
|
|
4987
5127
|
}
|
|
4988
5128
|
|
|
4989
5129
|
// src/agent/capabilities/desktop.ts
|
|
4990
|
-
var CUA_TOOLS = [
|
|
4991
|
-
"bring_to_front",
|
|
4992
|
-
"check_for_update",
|
|
4993
|
-
"check_permissions",
|
|
4994
|
-
"click",
|
|
4995
|
-
"double_click",
|
|
4996
|
-
"drag",
|
|
4997
|
-
"end_session",
|
|
4998
|
-
"get_accessibility_tree",
|
|
4999
|
-
"get_agent_cursor_state",
|
|
5000
|
-
"get_config",
|
|
5001
|
-
"get_cursor_position",
|
|
5002
|
-
"get_recording_state",
|
|
5003
|
-
"get_screen_size",
|
|
5004
|
-
"get_window_state",
|
|
5005
|
-
"hotkey",
|
|
5006
|
-
"kill_app",
|
|
5007
|
-
"launch_app",
|
|
5008
|
-
"list_apps",
|
|
5009
|
-
"list_windows",
|
|
5010
|
-
"move_cursor",
|
|
5011
|
-
"page",
|
|
5012
|
-
"press_key",
|
|
5013
|
-
"replay_trajectory",
|
|
5014
|
-
"right_click",
|
|
5015
|
-
"scroll",
|
|
5016
|
-
"set_agent_cursor_enabled",
|
|
5017
|
-
"set_agent_cursor_motion",
|
|
5018
|
-
"set_agent_cursor_style",
|
|
5019
|
-
"set_config",
|
|
5020
|
-
"set_value",
|
|
5021
|
-
"start_recording",
|
|
5022
|
-
"install_ffmpeg",
|
|
5023
|
-
"start_session",
|
|
5024
|
-
"stop_recording",
|
|
5025
|
-
"type_text",
|
|
5026
|
-
"zoom"
|
|
5027
|
-
];
|
|
5028
5130
|
var LOCAL_ONLY_CUA_TOOLS = new Set(["check_for_update", "install_ffmpeg"]);
|
|
5029
5131
|
function assertRemotelyCallableTool(tool) {
|
|
5030
5132
|
if (typeof tool !== "string" || tool.length === 0) {
|
|
@@ -5034,24 +5136,30 @@ function assertRemotelyCallableTool(tool) {
|
|
|
5034
5136
|
throw new Error(`CUA tool "${tool}" is local-only and cannot run through Agent Tunnel`);
|
|
5035
5137
|
}
|
|
5036
5138
|
}
|
|
5037
|
-
|
|
5139
|
+
|
|
5140
|
+
class DesktopPermissionMissingError extends Error {
|
|
5141
|
+
code = TunnelErrorCode.DESKTOP_PERMISSION_MISSING;
|
|
5142
|
+
}
|
|
5143
|
+
async function explainFailure(cua, err, onMissing) {
|
|
5144
|
+
const missing = await cua.missingPermissions().catch(() => []);
|
|
5145
|
+
if (missing.length > 0) {
|
|
5146
|
+
onMissing();
|
|
5147
|
+
throw new DesktopPermissionMissingError(`computer_desktop_permission_missing: macOS has not given ${await cua.permissionOwner()} ${missing.join(" and ")} on this computer.`);
|
|
5148
|
+
}
|
|
5149
|
+
throw err;
|
|
5150
|
+
}
|
|
5151
|
+
function createDesktopCapability(options = {}) {
|
|
5038
5152
|
const cua = new CuaDriver;
|
|
5039
5153
|
const methods = new Map;
|
|
5040
|
-
const
|
|
5154
|
+
const onMissing = options.onPermissionMissing ?? (() => {});
|
|
5155
|
+
const assertDesktopPermission = (params) => {
|
|
5041
5156
|
const permission = params.__permission;
|
|
5042
5157
|
if (permission?.capability !== "desktop") {
|
|
5043
5158
|
throw new Error("Permission denied: desktop permission required");
|
|
5044
5159
|
}
|
|
5045
|
-
const features = Array.isArray(permission.scope?.features) ? permission.scope.features.filter((value) => typeof value === "string") : [];
|
|
5046
|
-
if (features.length === 0)
|
|
5047
|
-
return;
|
|
5048
|
-
const feature = desktopFeatureForMethod(method, params);
|
|
5049
|
-
if (!feature || !features.includes(feature)) {
|
|
5050
|
-
throw new Error(`Permission denied: desktop feature "${feature ?? "unknown"}" is not allowed`);
|
|
5051
|
-
}
|
|
5052
5160
|
};
|
|
5053
5161
|
methods.set("desktop.cua.ensure", async (params) => {
|
|
5054
|
-
assertDesktopPermission(params
|
|
5162
|
+
assertDesktopPermission(params);
|
|
5055
5163
|
const binary = await cua.ensureInstalled();
|
|
5056
5164
|
const version = await cua.version().catch(() => {
|
|
5057
5165
|
return;
|
|
@@ -5059,40 +5167,36 @@ function createDesktopCapability() {
|
|
|
5059
5167
|
return { ok: true, binary, version };
|
|
5060
5168
|
});
|
|
5061
5169
|
methods.set("desktop.cua.start_daemon", async (params) => {
|
|
5062
|
-
assertDesktopPermission(params
|
|
5170
|
+
assertDesktopPermission(params);
|
|
5063
5171
|
return cua.startDaemon();
|
|
5064
5172
|
});
|
|
5065
5173
|
methods.set("desktop.cua.status", async (params) => {
|
|
5066
|
-
assertDesktopPermission(params
|
|
5174
|
+
assertDesktopPermission(params);
|
|
5067
5175
|
return { status: await cua.status() };
|
|
5068
5176
|
});
|
|
5069
5177
|
methods.set("desktop.cua.version", async (params) => {
|
|
5070
|
-
assertDesktopPermission(params
|
|
5178
|
+
assertDesktopPermission(params);
|
|
5071
5179
|
return { version: await cua.version() };
|
|
5072
5180
|
});
|
|
5073
5181
|
methods.set("desktop.cua.list_tools", async (params) => {
|
|
5074
|
-
assertDesktopPermission(params
|
|
5182
|
+
assertDesktopPermission(params);
|
|
5075
5183
|
return { tools: await cua.listTools() };
|
|
5076
5184
|
});
|
|
5077
5185
|
methods.set("desktop.cua.describe", async (params) => {
|
|
5078
|
-
assertDesktopPermission(params
|
|
5186
|
+
assertDesktopPermission(params);
|
|
5079
5187
|
return { description: await cua.describe(params.tool) };
|
|
5080
5188
|
});
|
|
5081
5189
|
methods.set("desktop.cua.call", async (params) => {
|
|
5082
|
-
assertDesktopPermission(params
|
|
5190
|
+
assertDesktopPermission(params);
|
|
5083
5191
|
const tool = params.tool;
|
|
5084
5192
|
assertRemotelyCallableTool(tool);
|
|
5085
5193
|
const args = params.args || {};
|
|
5086
|
-
|
|
5194
|
+
try {
|
|
5195
|
+
return await cua.call(tool, args);
|
|
5196
|
+
} catch (err) {
|
|
5197
|
+
return explainFailure(cua, err, onMissing);
|
|
5198
|
+
}
|
|
5087
5199
|
});
|
|
5088
|
-
for (const tool of CUA_TOOLS) {
|
|
5089
|
-
if (LOCAL_ONLY_CUA_TOOLS.has(tool))
|
|
5090
|
-
continue;
|
|
5091
|
-
methods.set(`desktop.cua.${tool}`, async (params) => {
|
|
5092
|
-
assertDesktopPermission(params, `desktop.cua.${tool}`);
|
|
5093
|
-
return cua.call(tool, params);
|
|
5094
|
-
});
|
|
5095
|
-
}
|
|
5096
5200
|
return {
|
|
5097
5201
|
name: "desktop",
|
|
5098
5202
|
methods
|
|
@@ -5503,7 +5607,7 @@ function createShellCapability(config) {
|
|
|
5503
5607
|
}
|
|
5504
5608
|
|
|
5505
5609
|
// src/agent/capabilities/enabled-registry.ts
|
|
5506
|
-
function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary) {
|
|
5610
|
+
function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary, desktop = {}) {
|
|
5507
5611
|
const registry = new CapabilityRegistry;
|
|
5508
5612
|
const enabled = new Set(config.enabledCapabilities ?? ["filesystem", "shell", "desktop"]);
|
|
5509
5613
|
if (enabled.has("filesystem"))
|
|
@@ -5511,13 +5615,13 @@ function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriv
|
|
|
5511
5615
|
if (enabled.has("shell"))
|
|
5512
5616
|
registry.register(createShellCapability(config));
|
|
5513
5617
|
if (enabled.has("desktop") && findDesktopDriver()) {
|
|
5514
|
-
registry.register(createDesktopCapability());
|
|
5618
|
+
registry.register(createDesktopCapability(desktop));
|
|
5515
5619
|
}
|
|
5516
5620
|
return registry;
|
|
5517
5621
|
}
|
|
5518
5622
|
|
|
5519
5623
|
// src/agent/credential-store.ts
|
|
5520
|
-
import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as
|
|
5624
|
+
import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5521
5625
|
import { join as join8 } from "path";
|
|
5522
5626
|
var CONFIG_DIR2 = agentTunnelHome();
|
|
5523
5627
|
var CONFIG_FILE2 = join8(CONFIG_DIR2, "config.json");
|
|
@@ -5561,7 +5665,7 @@ function clearSavedCredentials() {
|
|
|
5561
5665
|
try {
|
|
5562
5666
|
existing = JSON.parse(readFileSync7(CONFIG_FILE2, "utf-8"));
|
|
5563
5667
|
} catch {
|
|
5564
|
-
|
|
5668
|
+
rmSync3(CONFIG_FILE2, { force: true });
|
|
5565
5669
|
return true;
|
|
5566
5670
|
}
|
|
5567
5671
|
const hadCredentials = PAIRING_FIELDS.some((key) => (key in existing) && existing[key] != null);
|
|
@@ -5577,17 +5681,17 @@ function probeCredentials(config, options = {}) {
|
|
|
5577
5681
|
return new Promise((resolve3) => {
|
|
5578
5682
|
let socket;
|
|
5579
5683
|
try {
|
|
5580
|
-
socket =
|
|
5684
|
+
socket = openRelaySocket(new URL(buildTunnelWsUrl(config)).href);
|
|
5581
5685
|
} catch {
|
|
5582
5686
|
resolve3("unreachable");
|
|
5583
5687
|
return;
|
|
5584
5688
|
}
|
|
5585
5689
|
const settle = (result) => {
|
|
5586
5690
|
clearTimeout(timer);
|
|
5691
|
+
resolve3(result);
|
|
5587
5692
|
try {
|
|
5588
5693
|
socket.close(1000, "probe complete");
|
|
5589
5694
|
} catch {}
|
|
5590
|
-
resolve3(result);
|
|
5591
5695
|
};
|
|
5592
5696
|
const timer = setTimeout(() => settle("unreachable"), options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS);
|
|
5593
5697
|
socket.addEventListener("open", () => {
|
|
@@ -5642,7 +5746,6 @@ function isShellStartupNoise(line) {
|
|
|
5642
5746
|
}
|
|
5643
5747
|
|
|
5644
5748
|
// src/agent/prompts.ts
|
|
5645
|
-
import { createInterface } from "readline/promises";
|
|
5646
5749
|
function isInteractiveTerminal() {
|
|
5647
5750
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
5648
5751
|
}
|
|
@@ -5652,41 +5755,16 @@ function isTruthyFlag(value) {
|
|
|
5652
5755
|
function anyFlag(flags, names) {
|
|
5653
5756
|
return names.some((name) => isTruthyFlag(flags[name]));
|
|
5654
5757
|
}
|
|
5655
|
-
async function promptYesNo(question, defaultValue) {
|
|
5656
|
-
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
5657
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
5658
|
-
try {
|
|
5659
|
-
for (;; ) {
|
|
5660
|
-
const answer = (await rl.question(`${question}${suffix}`)).trim().toLowerCase();
|
|
5661
|
-
if (!answer)
|
|
5662
|
-
return defaultValue;
|
|
5663
|
-
if (["y", "yes"].includes(answer))
|
|
5664
|
-
return true;
|
|
5665
|
-
if (["n", "no"].includes(answer))
|
|
5666
|
-
return false;
|
|
5667
|
-
console.log(` ${c.yellow}!${c.reset} Please answer yes or no.`);
|
|
5668
|
-
}
|
|
5669
|
-
} catch (error) {
|
|
5670
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
5671
|
-
process.stdout.write(`
|
|
5672
|
-
`);
|
|
5673
|
-
process.exit(130);
|
|
5674
|
-
}
|
|
5675
|
-
throw error;
|
|
5676
|
-
} finally {
|
|
5677
|
-
rl.close();
|
|
5678
|
-
}
|
|
5679
|
-
}
|
|
5680
5758
|
|
|
5681
5759
|
// src/agent/service.ts
|
|
5682
|
-
import { chmodSync as chmodSync4, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync9, realpathSync as realpathSync3, rmSync as
|
|
5683
|
-
import { platform as
|
|
5760
|
+
import { chmodSync as chmodSync4, copyFileSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync9, realpathSync as realpathSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
5761
|
+
import { homedir as homedir5, platform as platform7 } from "os";
|
|
5684
5762
|
import { join as join10 } from "path";
|
|
5685
5763
|
|
|
5686
5764
|
// src/agent/service-drivers.ts
|
|
5687
5765
|
import { spawnSync } from "child_process";
|
|
5688
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync as
|
|
5689
|
-
import { homedir as homedir4, platform as
|
|
5766
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
5767
|
+
import { homedir as homedir4, platform as platform6, userInfo } from "os";
|
|
5690
5768
|
import { dirname as dirname6, join as join9 } from "path";
|
|
5691
5769
|
|
|
5692
5770
|
// src/agent/service-quoting.ts
|
|
@@ -5835,7 +5913,7 @@ var launchd = {
|
|
|
5835
5913
|
const existed = existsSync5(paths.launchdPlist);
|
|
5836
5914
|
const stop = run("launchctl", ["bootout", launchdTarget(), paths.launchdPlist]);
|
|
5837
5915
|
if (existed)
|
|
5838
|
-
|
|
5916
|
+
rmSync4(paths.launchdPlist, { force: true });
|
|
5839
5917
|
return { detail: stop.detail };
|
|
5840
5918
|
},
|
|
5841
5919
|
start(paths, installed) {
|
|
@@ -5888,7 +5966,7 @@ var systemd = {
|
|
|
5888
5966
|
const existed = existsSync5(paths.systemdUnit);
|
|
5889
5967
|
const disable = run("systemctl", ["--user", "disable", "--now", `${paths.label}.service`]);
|
|
5890
5968
|
if (existed)
|
|
5891
|
-
|
|
5969
|
+
rmSync4(paths.systemdUnit, { force: true });
|
|
5892
5970
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
5893
5971
|
return { detail: disable.detail };
|
|
5894
5972
|
},
|
|
@@ -5963,8 +6041,8 @@ var scheduledTask = {
|
|
|
5963
6041
|
const stop = run("schtasks.exe", ["/End", "/TN", paths.label]);
|
|
5964
6042
|
const del = run("schtasks.exe", ["/Delete", "/TN", paths.label, "/F"]);
|
|
5965
6043
|
if (existed)
|
|
5966
|
-
|
|
5967
|
-
|
|
6044
|
+
rmSync4(paths.windowsScript, { force: true });
|
|
6045
|
+
rmSync4(windowsTaskXmlPath(paths), { force: true });
|
|
5968
6046
|
return { detail: joinDetails(stop, del) };
|
|
5969
6047
|
},
|
|
5970
6048
|
start(paths, installed) {
|
|
@@ -6010,7 +6088,7 @@ function serviceDriverFor(os) {
|
|
|
6010
6088
|
return DRIVERS[os];
|
|
6011
6089
|
}
|
|
6012
6090
|
function serviceDriver() {
|
|
6013
|
-
return serviceDriverFor(
|
|
6091
|
+
return serviceDriverFor(platform6());
|
|
6014
6092
|
}
|
|
6015
6093
|
|
|
6016
6094
|
// src/agent/service.ts
|
|
@@ -6046,8 +6124,22 @@ function runnerPartsFor(script, runtime = {
|
|
|
6046
6124
|
}
|
|
6047
6125
|
return { command: runtime.execPath, args: [vendor(script, paths), "run", "--service"], env };
|
|
6048
6126
|
}
|
|
6127
|
+
function installedAppRunner(apps = ["/Applications/Beeuni.app", join10(homedir5(), "Applications", "Beeuni.app")], os = process.platform) {
|
|
6128
|
+
if (os !== "darwin")
|
|
6129
|
+
return null;
|
|
6130
|
+
for (const app of apps) {
|
|
6131
|
+
const execPath = join10(app, "Contents", "MacOS", "Beeuni");
|
|
6132
|
+
const script = join10(app, "Contents", "Resources", "agent-tunnel", "agent-cli.js");
|
|
6133
|
+
if (existsSync6(execPath) && existsSync6(script))
|
|
6134
|
+
return { execPath, script };
|
|
6135
|
+
}
|
|
6136
|
+
return null;
|
|
6137
|
+
}
|
|
6049
6138
|
var plannedRunner = (script, paths) => isEphemeralRunnerPath(script) ? paths.vendoredRunner : script;
|
|
6050
6139
|
function currentRunnerParts(vendor = vendorRunner) {
|
|
6140
|
+
const app = process.versions.electron ? null : installedAppRunner();
|
|
6141
|
+
if (app)
|
|
6142
|
+
return runnerPartsFor(app.script, { execPath: app.execPath, electron: "installed-app" }, getServicePaths(), vendor);
|
|
6051
6143
|
const script = process.argv[1];
|
|
6052
6144
|
if (script && existsSync6(script))
|
|
6053
6145
|
return runnerPartsFor(script, undefined, getServicePaths(), vendor);
|
|
@@ -6062,7 +6154,7 @@ function withDriver(operate, fallback) {
|
|
|
6062
6154
|
const installed = existsSync6(path);
|
|
6063
6155
|
const outcome = operate(driver, paths, installed);
|
|
6064
6156
|
return {
|
|
6065
|
-
platform:
|
|
6157
|
+
platform: platform7(),
|
|
6066
6158
|
installed: outcome.installed ?? installed,
|
|
6067
6159
|
active: outcome.active ?? fallback.active,
|
|
6068
6160
|
...outcome.enabled !== undefined ? { enabled: outcome.enabled } : {},
|
|
@@ -6080,7 +6172,7 @@ function installService() {
|
|
|
6080
6172
|
return withDriver((driver, servicePaths) => driver.install(servicePaths, runner), { installed: false, active: null });
|
|
6081
6173
|
}
|
|
6082
6174
|
function uninstallService() {
|
|
6083
|
-
|
|
6175
|
+
rmSync5(getServicePaths().binDir, { recursive: true, force: true });
|
|
6084
6176
|
return withDriver((driver, paths) => ({ ...driver.uninstall(paths), installed: false, active: false }), { installed: false, active: false });
|
|
6085
6177
|
}
|
|
6086
6178
|
function startService() {
|
|
@@ -6108,7 +6200,7 @@ function restartService() {
|
|
|
6108
6200
|
function getServiceStatus() {
|
|
6109
6201
|
if (!serviceDriver()) {
|
|
6110
6202
|
return {
|
|
6111
|
-
platform:
|
|
6203
|
+
platform: platform7(),
|
|
6112
6204
|
installed: false,
|
|
6113
6205
|
active: null,
|
|
6114
6206
|
detail: SUPPORTED_PLATFORMS_MESSAGE
|
|
@@ -6165,6 +6257,35 @@ function renderServiceAction(action, status) {
|
|
|
6165
6257
|
blankLine();
|
|
6166
6258
|
}
|
|
6167
6259
|
|
|
6260
|
+
// src/agent/self-restart.ts
|
|
6261
|
+
import { statSync as statSync3 } from "fs";
|
|
6262
|
+
function runtimeFingerprint(files) {
|
|
6263
|
+
return files.map((file) => {
|
|
6264
|
+
try {
|
|
6265
|
+
const stat2 = statSync3(file);
|
|
6266
|
+
return `${stat2.ino}:${stat2.mtimeMs}:${stat2.size}`;
|
|
6267
|
+
} catch {
|
|
6268
|
+
return "missing";
|
|
6269
|
+
}
|
|
6270
|
+
}).join("|");
|
|
6271
|
+
}
|
|
6272
|
+
var PERMISSION_RESTART_MIN_UPTIME_S = 5 * 60;
|
|
6273
|
+
function watchForRestart(exit, intervalMs = 60000) {
|
|
6274
|
+
const files = [process.execPath, process.argv[1]].filter((file) => Boolean(file));
|
|
6275
|
+
const started = runtimeFingerprint(files);
|
|
6276
|
+
setInterval(() => {
|
|
6277
|
+
if (runtimeFingerprint(files) !== started)
|
|
6278
|
+
exit("its code was updated");
|
|
6279
|
+
}, intervalMs).unref();
|
|
6280
|
+
return {
|
|
6281
|
+
permissionMissing: () => {
|
|
6282
|
+
if (process.uptime() < PERMISSION_RESTART_MIN_UPTIME_S)
|
|
6283
|
+
return;
|
|
6284
|
+
setTimeout(() => exit("macOS refused a desktop permission"), 1000).unref();
|
|
6285
|
+
}
|
|
6286
|
+
};
|
|
6287
|
+
}
|
|
6288
|
+
|
|
6168
6289
|
// src/agent/state-file.ts
|
|
6169
6290
|
import { chmodSync as chmodSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
6170
6291
|
import { join as join11 } from "path";
|
|
@@ -6191,10 +6312,22 @@ function writeAgentState(status, tunnel, home = agentTunnelHome()) {
|
|
|
6191
6312
|
renameSync3(tmp, stateFilePath(home));
|
|
6192
6313
|
} catch {}
|
|
6193
6314
|
}
|
|
6315
|
+
function isAlive2(pid) {
|
|
6316
|
+
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0)
|
|
6317
|
+
return false;
|
|
6318
|
+
try {
|
|
6319
|
+
process.kill(pid, 0);
|
|
6320
|
+
return true;
|
|
6321
|
+
} catch (error) {
|
|
6322
|
+
return error.code === "EPERM";
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6194
6325
|
function readAgentState(home = agentTunnelHome()) {
|
|
6195
6326
|
try {
|
|
6196
6327
|
const parsed = JSON.parse(readFileSync10(stateFilePath(home), "utf8"));
|
|
6197
|
-
|
|
6328
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.status !== "string")
|
|
6329
|
+
return null;
|
|
6330
|
+
return parsed.status !== "offline" && !isAlive2(parsed.pid) ? { ...parsed, status: "offline" } : parsed;
|
|
6198
6331
|
} catch {
|
|
6199
6332
|
return null;
|
|
6200
6333
|
}
|
|
@@ -6207,13 +6340,18 @@ var FOREGROUND_FLAGS = ["foreground", "no-daemon", "no-service", "no-background"
|
|
|
6207
6340
|
var DEFAULT_LOG_LINES = 60;
|
|
6208
6341
|
function parseArgs(argv) {
|
|
6209
6342
|
const flags = {};
|
|
6343
|
+
const words = [];
|
|
6210
6344
|
for (let i = 3;i < argv.length; i++) {
|
|
6211
6345
|
const arg = argv[i];
|
|
6212
|
-
if (!arg.startsWith("--"))
|
|
6346
|
+
if (!arg.startsWith("--")) {
|
|
6347
|
+
words.push(arg);
|
|
6213
6348
|
continue;
|
|
6349
|
+
}
|
|
6214
6350
|
const next = argv[i + 1];
|
|
6215
6351
|
flags[arg.slice(2)] = next && !next.startsWith("--") ? argv[++i] : "true";
|
|
6216
6352
|
}
|
|
6353
|
+
if (words.length > 0)
|
|
6354
|
+
flags._ = words.join(" ");
|
|
6217
6355
|
return { command: argv[2] || "help", flags };
|
|
6218
6356
|
}
|
|
6219
6357
|
var jsonMode = false;
|
|
@@ -6238,7 +6376,16 @@ function startAgent(config, options = {}) {
|
|
|
6238
6376
|
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
6239
6377
|
options = { ...options, service: true };
|
|
6240
6378
|
}
|
|
6241
|
-
|
|
6379
|
+
let agent = null;
|
|
6380
|
+
const restart = options.service ? watchForRestart((reason) => {
|
|
6381
|
+
process.stdout.write(`[agent-tunnel] restarting: ${reason}
|
|
6382
|
+
`);
|
|
6383
|
+
agent?.disconnect();
|
|
6384
|
+
process.exit(0);
|
|
6385
|
+
}) : null;
|
|
6386
|
+
const registry = createEnabledCapabilityRegistry(config, undefined, {
|
|
6387
|
+
onPermissionMissing: restart?.permissionMissing
|
|
6388
|
+
});
|
|
6242
6389
|
if (config.enabledCapabilities?.includes("desktop") && !registry.has("desktop")) {
|
|
6243
6390
|
console.error("[agent-tunnel] Computer Use is approved but unavailable: install the trusted cua-driver locally, then restart Agent Tunnel.");
|
|
6244
6391
|
}
|
|
@@ -6254,7 +6401,7 @@ function startAgent(config, options = {}) {
|
|
|
6254
6401
|
version: agentTunnelVersion()
|
|
6255
6402
|
});
|
|
6256
6403
|
}
|
|
6257
|
-
|
|
6404
|
+
agent = new TunnelAgent(config, registry, { onStatus: (status, current) => writeAgentState(status, current) }, {
|
|
6258
6405
|
persistent: options.service === true,
|
|
6259
6406
|
reloadConfig: () => loadConfig({ apiUrl: config.apiUrl })
|
|
6260
6407
|
});
|
|
@@ -6263,31 +6410,37 @@ function startAgent(config, options = {}) {
|
|
|
6263
6410
|
if (!options.service)
|
|
6264
6411
|
console.log(`
|
|
6265
6412
|
${c.dim} Shutting down…${c.reset}`);
|
|
6266
|
-
agent
|
|
6413
|
+
agent?.disconnect();
|
|
6267
6414
|
process.exit(0);
|
|
6268
6415
|
};
|
|
6269
6416
|
process.on("SIGTERM", shutdown);
|
|
6270
6417
|
process.on("SIGINT", shutdown);
|
|
6271
6418
|
}
|
|
6272
|
-
|
|
6419
|
+
function chooseBackgroundMode(flags) {
|
|
6273
6420
|
if (anyFlag(flags, BACKGROUND_FLAGS))
|
|
6274
6421
|
return true;
|
|
6275
6422
|
if (anyFlag(flags, FOREGROUND_FLAGS))
|
|
6276
6423
|
return false;
|
|
6277
|
-
|
|
6278
|
-
|
|
6424
|
+
return !jsonMode && isInteractiveTerminal();
|
|
6425
|
+
}
|
|
6426
|
+
function printServiceControls() {
|
|
6427
|
+
const npx = "npx @beeuni/agent-tunnel@latest";
|
|
6428
|
+
console.log(` ${c.dim}Runs in the background: starts at login, restarts after failures, keeps running after this terminal closes.${c.reset}`);
|
|
6429
|
+
if (installedAppRunner()) {
|
|
6430
|
+
console.log(` ${c.dim}It runs on the Beeuni app on this Mac: same connection, same macOS permissions. Control it from Your computer in the app.${c.reset}`);
|
|
6431
|
+
}
|
|
6279
6432
|
blankLine();
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6433
|
+
field("Pause", `${c.white}${npx} stop${c.reset}`);
|
|
6434
|
+
field("Resume", `${c.white}${npx} start${c.reset}`);
|
|
6435
|
+
field("Disconnect", `${c.white}${npx} logout${c.reset}`);
|
|
6283
6436
|
blankLine();
|
|
6284
|
-
return promptYesNo(" Install the background service now?", DEFAULT_INSTALL_BACKGROUND_SERVICE);
|
|
6285
6437
|
}
|
|
6286
6438
|
async function launch(config, flags, lease) {
|
|
6287
|
-
if (
|
|
6439
|
+
if (chooseBackgroundMode(flags)) {
|
|
6288
6440
|
saveCredentials(config.tunnelId, config.token, config.apiUrl);
|
|
6289
6441
|
if (!jsonMode) {
|
|
6290
6442
|
renderServiceAction("install", SERVICE_ACTIONS.install.run());
|
|
6443
|
+
printServiceControls();
|
|
6291
6444
|
return;
|
|
6292
6445
|
}
|
|
6293
6446
|
try {
|
|
@@ -6495,7 +6648,7 @@ function commandStatus(flags) {
|
|
|
6495
6648
|
serviceLabel: getServicePaths().label,
|
|
6496
6649
|
service,
|
|
6497
6650
|
state: readAgentState(),
|
|
6498
|
-
access:
|
|
6651
|
+
access: accessView(),
|
|
6499
6652
|
lastActivity: lastServiceActivity()
|
|
6500
6653
|
}, null, 2));
|
|
6501
6654
|
return;
|
|
@@ -6552,8 +6705,8 @@ async function unpairOnServer() {
|
|
|
6552
6705
|
async function commandLogout(flags) {
|
|
6553
6706
|
const serverUnpaired = await unpairOnServer();
|
|
6554
6707
|
const removed = clearSavedCredentials();
|
|
6555
|
-
|
|
6556
|
-
|
|
6708
|
+
rmSync6(accessFilePath(), { force: true });
|
|
6709
|
+
rmSync6(accessRequestPath(), { force: true });
|
|
6557
6710
|
const keepService = isTruthyFlag(flags["keep-service"]);
|
|
6558
6711
|
if (!keepService)
|
|
6559
6712
|
SERVICE_ACTIONS.uninstall.run();
|
|
@@ -6624,12 +6777,74 @@ function commandServiceAction(action, flags) {
|
|
|
6624
6777
|
}
|
|
6625
6778
|
renderServiceAction(action, SERVICE_ACTIONS[action].run());
|
|
6626
6779
|
}
|
|
6780
|
+
function describeAccess(view) {
|
|
6781
|
+
const clock = (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
6782
|
+
if (view.mode === "always")
|
|
6783
|
+
return `${c.white}Always allowed${c.reset}`;
|
|
6784
|
+
if (view.mode === "off")
|
|
6785
|
+
return `${c.white}Off${c.reset} ${c.dim}(every agent call is refused)${c.reset}`;
|
|
6786
|
+
const now = view.grantedUntil ? `allowed until ${clock(view.grantedUntil)}` : view.deniedUntil ? `denied until ${clock(view.deniedUntil)}` : "asks before the next call";
|
|
6787
|
+
return `${c.white}Ask each time${c.reset} ${c.dim}· ${now}${c.reset}`;
|
|
6788
|
+
}
|
|
6789
|
+
function printAccess(view, flags) {
|
|
6790
|
+
if (isTruthyFlag(flags.json)) {
|
|
6791
|
+
console.log(JSON.stringify(view, null, 2));
|
|
6792
|
+
return;
|
|
6793
|
+
}
|
|
6794
|
+
const npx = "npx @beeuni/agent-tunnel@latest";
|
|
6795
|
+
blankLine();
|
|
6796
|
+
field("access", describeAccess(view));
|
|
6797
|
+
field("keep awake", view.keepAwakeSupported ? view.keepAwake ? `${c.white}on${c.reset}` : `${c.white}off${c.reset}` : `${c.dim}not available on this system${c.reset}`);
|
|
6798
|
+
if (view.pendingRequest) {
|
|
6799
|
+
field("waiting", `${c.white}${view.pendingRequest.change ? "a change asked for through Beeuni" : `an agent call (${view.pendingRequest.capability})`}${c.reset}`);
|
|
6800
|
+
field("", `${c.dim}${npx} access --allow-for 60 or --deny${c.reset}`);
|
|
6801
|
+
}
|
|
6802
|
+
blankLine();
|
|
6803
|
+
console.log(` ${c.dim}Change it:${c.reset} ${c.white}${npx} access ask|always|off${c.reset}${c.dim}, --allow-for <minutes>, --revoke, --deny${c.reset}`);
|
|
6804
|
+
blankLine();
|
|
6805
|
+
}
|
|
6806
|
+
function commandAccess(flags) {
|
|
6807
|
+
const change = {};
|
|
6808
|
+
if (flags._)
|
|
6809
|
+
change.mode = flags._;
|
|
6810
|
+
if (flags["allow-for"] !== undefined) {
|
|
6811
|
+
change.grantMinutes = Number(flags["allow-for"]);
|
|
6812
|
+
if (!(change.grantMinutes > 0))
|
|
6813
|
+
fail("--allow-for takes minutes, at most 1440. Example: agent-tunnel access --allow-for 60");
|
|
6814
|
+
}
|
|
6815
|
+
if (isTruthyFlag(flags.revoke))
|
|
6816
|
+
change.revoke = true;
|
|
6817
|
+
if (isTruthyFlag(flags.deny))
|
|
6818
|
+
change.deny = true;
|
|
6819
|
+
if (Object.keys(change).length === 0)
|
|
6820
|
+
return printAccess(accessView(), flags);
|
|
6821
|
+
if (change.mode !== undefined && !["ask", "always", "off"].includes(change.mode)) {
|
|
6822
|
+
fail("Access is one of: ask, always, off. Example: agent-tunnel access ask");
|
|
6823
|
+
}
|
|
6824
|
+
if (change.grantMinutes !== undefined && change.mode === undefined && readAccess().mode !== "ask")
|
|
6825
|
+
change.mode = "ask";
|
|
6826
|
+
printAccess(applyAccess(change), flags);
|
|
6827
|
+
}
|
|
6828
|
+
function commandKeepAwake(flags) {
|
|
6829
|
+
if (flags._ === undefined)
|
|
6830
|
+
return printAccess(accessView(), flags);
|
|
6831
|
+
if (flags._ !== "on" && flags._ !== "off")
|
|
6832
|
+
fail("Keep awake is on or off. Example: agent-tunnel keep-awake on");
|
|
6833
|
+
if (!accessView().keepAwakeSupported)
|
|
6834
|
+
fail("Keep awake is not available on this system yet.");
|
|
6835
|
+
printAccess(applyAccess({ keepAwake: flags._ === "on" }), flags);
|
|
6836
|
+
}
|
|
6627
6837
|
var COMMANDS = {
|
|
6628
6838
|
connect: {
|
|
6629
6839
|
summary: "Pair this machine, then run it in the background or this terminal",
|
|
6630
6840
|
run: commandConnect
|
|
6631
6841
|
},
|
|
6632
6842
|
status: { summary: "Show pairing, capabilities, and service state (--json)", run: commandStatus },
|
|
6843
|
+
access: {
|
|
6844
|
+
summary: "When Beeuni may use this computer: ask, always, off (--allow-for <min>, --revoke, --deny, --json)",
|
|
6845
|
+
run: commandAccess
|
|
6846
|
+
},
|
|
6847
|
+
"keep-awake": { summary: "Keep this computer awake while connected: on, off (--json)", run: commandKeepAwake },
|
|
6633
6848
|
logs: { summary: "Show recent service logs (--lines <n>, --all, --clear)", run: commandLogs },
|
|
6634
6849
|
start: {
|
|
6635
6850
|
summary: "Start the background service (resumes a paused one)",
|
|
@@ -6668,7 +6883,7 @@ var OPTIONS = [
|
|
|
6668
6883
|
["--reauth", "With connect: discard the saved credential and pair again"],
|
|
6669
6884
|
["--daemon / --foreground", "With connect: skip the prompt and choose the mode"],
|
|
6670
6885
|
["--project-id <uuid>", 'With connect: offer "Also share with <project>" on the approval page; the computer is yours in every project either way'],
|
|
6671
|
-
["--json", "With connect: NDJSON events, no prompts. With status and logout: machine-readable output"],
|
|
6886
|
+
["--json", "With connect: NDJSON events, no prompts. With status, access, keep-awake, and logout: machine-readable output"],
|
|
6672
6887
|
["--keep-service", "With logout: keep the background service installed"]
|
|
6673
6888
|
];
|
|
6674
6889
|
function showHelp() {
|
|
@@ -6700,14 +6915,14 @@ function showHelp() {
|
|
|
6700
6915
|
var { command, flags } = parseArgs(process.argv);
|
|
6701
6916
|
jsonMode = command === "connect" && isTruthyFlag(flags.json);
|
|
6702
6917
|
if (Object.prototype.hasOwnProperty.call(flags, "keep-awake")) {
|
|
6703
|
-
console.error(` ${glyph.bad} --keep-awake is not
|
|
6918
|
+
console.error(` ${glyph.bad} --keep-awake is not a flag. Run: agent-tunnel keep-awake on`);
|
|
6704
6919
|
process.exit(2);
|
|
6705
6920
|
}
|
|
6706
6921
|
var resolved = COMMANDS[command] ?? Object.values(COMMANDS).find((entry) => entry.aliases?.includes(command));
|
|
6707
6922
|
if (!resolved) {
|
|
6708
6923
|
showHelp();
|
|
6709
6924
|
} else {
|
|
6710
|
-
Promise.resolve(resolved.run(flags)).catch((error) => {
|
|
6925
|
+
Promise.resolve().then(() => resolved.run(flags)).catch((error) => {
|
|
6711
6926
|
fail(error instanceof Error ? error.message : String(error));
|
|
6712
6927
|
});
|
|
6713
6928
|
}
|