@beeuni/agent-tunnel 0.1.0 → 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 +74 -14
- package/dist/agent-cli.js +457 -246
- 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 {}
|
|
@@ -4029,6 +4053,7 @@ function log(icon, msg) {
|
|
|
4029
4053
|
}
|
|
4030
4054
|
var LIVENESS_TIMEOUT_MS = 75000;
|
|
4031
4055
|
var WATCHDOG_INTERVAL_MS = 5000;
|
|
4056
|
+
var DESKTOP_PROBE_EVERY_TICKS = 12;
|
|
4032
4057
|
var CLOCK_JUMP_MS = 30000;
|
|
4033
4058
|
var REJECTED_RETRY_MS = 5 * 60000;
|
|
4034
4059
|
var PAIR_AGAIN_COMMAND = "npx @beeuni/agent-tunnel@latest connect --reauth";
|
|
@@ -4077,6 +4102,9 @@ class TunnelAgent {
|
|
|
4077
4102
|
standbyAttempts = 0;
|
|
4078
4103
|
lastRejectedLogAt = 0;
|
|
4079
4104
|
sentAccessKey = null;
|
|
4105
|
+
desktopPermissions = null;
|
|
4106
|
+
probingDesktop = false;
|
|
4107
|
+
watchdogTicks = 0;
|
|
4080
4108
|
keepAwake = null;
|
|
4081
4109
|
keepAwakeFailed = false;
|
|
4082
4110
|
reconnectTimer = null;
|
|
@@ -4168,7 +4196,8 @@ class TunnelAgent {
|
|
|
4168
4196
|
token: trustedCredential(this.config.token, "token"),
|
|
4169
4197
|
capabilities: this.registry.getCapabilityNames(),
|
|
4170
4198
|
agentVersion: AGENT_VERSION,
|
|
4171
|
-
reportsAccess: true
|
|
4199
|
+
reportsAccess: true,
|
|
4200
|
+
controlsAccess: true
|
|
4172
4201
|
});
|
|
4173
4202
|
});
|
|
4174
4203
|
this.ws.addEventListener("message", (event) => {
|
|
@@ -4262,6 +4291,7 @@ class TunnelAgent {
|
|
|
4262
4291
|
this.standbyAttempts = 0;
|
|
4263
4292
|
this.setStatus("online");
|
|
4264
4293
|
this.reportAccess();
|
|
4294
|
+
this.probeDesktopPermissions();
|
|
4265
4295
|
if (this.stableConnectionTimer)
|
|
4266
4296
|
clearTimeout(this.stableConnectionTimer);
|
|
4267
4297
|
this.stableConnectionTimer = setTimeout(() => {
|
|
@@ -4311,6 +4341,10 @@ class TunnelAgent {
|
|
|
4311
4341
|
log(`${c.yellow}!${c.reset}`, `Token rotated — reconnecting with new token`);
|
|
4312
4342
|
return;
|
|
4313
4343
|
}
|
|
4344
|
+
if ("id" in msg && msg.id && msg.method === "tunnel.access.set") {
|
|
4345
|
+
await this.handleAccessChange(msg);
|
|
4346
|
+
return;
|
|
4347
|
+
}
|
|
4314
4348
|
if ("id" in msg && msg.id) {
|
|
4315
4349
|
await this.handleRpcRequest(msg);
|
|
4316
4350
|
return;
|
|
@@ -4337,7 +4371,8 @@ class TunnelAgent {
|
|
|
4337
4371
|
return true;
|
|
4338
4372
|
}
|
|
4339
4373
|
async handleRpcRequest(request) {
|
|
4340
|
-
const {
|
|
4374
|
+
const { method, params } = normalizeDesktopCall(request.method, request.params ?? {});
|
|
4375
|
+
const { id } = request;
|
|
4341
4376
|
const permissionId = params.permissionId;
|
|
4342
4377
|
const permission = this.permissionGuard.getPermissionForMethod(permissionId, method);
|
|
4343
4378
|
if (!permission) {
|
|
@@ -4362,7 +4397,8 @@ class TunnelAgent {
|
|
|
4362
4397
|
this.sendSignedResult(id, result);
|
|
4363
4398
|
} catch (err) {
|
|
4364
4399
|
const message = err instanceof Error ? err.message : String(err);
|
|
4365
|
-
|
|
4400
|
+
const code = err?.code;
|
|
4401
|
+
this.sendSignedError(id, typeof code === "number" && code <= -32000 && code >= -32099 ? code : -32003, message);
|
|
4366
4402
|
}
|
|
4367
4403
|
}
|
|
4368
4404
|
sendSignedResult(id, result) {
|
|
@@ -4415,7 +4451,7 @@ class TunnelAgent {
|
|
|
4415
4451
|
hostname: hostname2(),
|
|
4416
4452
|
displayName: this.displayName ??= machineDisplayName(),
|
|
4417
4453
|
...this.hardwareId ? { machineId: this.hardwareId } : {},
|
|
4418
|
-
platform:
|
|
4454
|
+
platform: platform3(),
|
|
4419
4455
|
arch: arch(),
|
|
4420
4456
|
osVersion: release(),
|
|
4421
4457
|
agentVersion: AGENT_VERSION,
|
|
@@ -4436,10 +4472,11 @@ class TunnelAgent {
|
|
|
4436
4472
|
try {
|
|
4437
4473
|
const pending = readAccessRequest(this.home);
|
|
4438
4474
|
if (pending && Date.now() - Date.parse(pending.requestedAt) < hold) {
|
|
4439
|
-
requestId = pending.id;
|
|
4475
|
+
requestId = pending.change ? null : pending.id;
|
|
4440
4476
|
} else {
|
|
4441
4477
|
requestId = writeAccessRequest({ capability: capabilityForMethod(method) ?? method, method }, this.home).id;
|
|
4442
4478
|
wakeDesktopApp(this.home);
|
|
4479
|
+
this.reportAccess();
|
|
4443
4480
|
}
|
|
4444
4481
|
} catch {}
|
|
4445
4482
|
const deadline = Date.now() + hold;
|
|
@@ -4454,15 +4491,83 @@ class TunnelAgent {
|
|
|
4454
4491
|
}
|
|
4455
4492
|
return ACCESS_ERRORS.pending;
|
|
4456
4493
|
}
|
|
4494
|
+
async handleAccessChange(request) {
|
|
4495
|
+
let change;
|
|
4496
|
+
try {
|
|
4497
|
+
change = parseAccessChange(request.params);
|
|
4498
|
+
} catch (err) {
|
|
4499
|
+
this.sendSignedError(request.id, TunnelErrorCode.LOCAL_ERROR, err instanceof Error ? err.message : String(err));
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
try {
|
|
4503
|
+
const view = await this.changeAccess(change);
|
|
4504
|
+
this.reportAccess();
|
|
4505
|
+
this.sendSignedResult(request.id, view);
|
|
4506
|
+
} catch (err) {
|
|
4507
|
+
this.reportAccess();
|
|
4508
|
+
this.sendSignedError(request.id, TunnelErrorCode.ACCESS_UNCONFIRMED, err instanceof Error ? err.message : String(err));
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
async changeAccess(change) {
|
|
4512
|
+
const current = readAccess(this.home);
|
|
4513
|
+
const target = nextAccess(current, change, this.now());
|
|
4514
|
+
if (!accessWidens(current, target, this.now()))
|
|
4515
|
+
return applyAccess(change, this.home, this.now());
|
|
4516
|
+
const unconfirmed = (why) => new Error(`computer_access_unconfirmed: ${why}`);
|
|
4517
|
+
const asked = writeAccessRequest({ capability: "access", method: "tunnel.access.set", change }, this.home);
|
|
4518
|
+
this.reportAccess();
|
|
4519
|
+
if (wakeDesktopApp(this.home) === "no-app") {
|
|
4520
|
+
clearAccessRequest(asked.id, this.home);
|
|
4521
|
+
throw unconfirmed(`Only the owner can allow more access, on the computer itself. Run ${accessCommand(change)} there.`);
|
|
4522
|
+
}
|
|
4523
|
+
const hold = this.options.accessHoldMs ?? ACCESS_HOLD_MS;
|
|
4524
|
+
const deadline = Date.now() + hold;
|
|
4525
|
+
while (Date.now() < deadline) {
|
|
4526
|
+
await new Promise((resolve2) => setTimeout(resolve2, Math.min(100, hold / 4)));
|
|
4527
|
+
if (!accessWidens(readAccess(this.home), target, this.now())) {
|
|
4528
|
+
clearAccessRequest(asked.id, this.home);
|
|
4529
|
+
return accessView(this.home, platform3(), this.now());
|
|
4530
|
+
}
|
|
4531
|
+
if (readAccessRequest(this.home)?.id !== asked.id)
|
|
4532
|
+
throw unconfirmed("The owner declined on the computer.");
|
|
4533
|
+
}
|
|
4534
|
+
clearAccessRequest(asked.id, this.home);
|
|
4535
|
+
throw unconfirmed(`Nobody confirmed on the computer within ${Math.round(hold / 1000)} seconds.`);
|
|
4536
|
+
}
|
|
4457
4537
|
reportAccess() {
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
const
|
|
4461
|
-
const
|
|
4462
|
-
|
|
4538
|
+
if (this.status !== "online")
|
|
4539
|
+
return;
|
|
4540
|
+
const view = accessView(this.home, platform3(), this.now());
|
|
4541
|
+
const params = {
|
|
4542
|
+
mode: view.mode,
|
|
4543
|
+
grantedUntil: view.grantedUntil,
|
|
4544
|
+
deniedUntil: view.deniedUntil,
|
|
4545
|
+
keepAwake: view.keepAwake,
|
|
4546
|
+
keepAwakeSupported: view.keepAwakeSupported,
|
|
4547
|
+
pendingRequest: view.pendingRequest ? { capability: view.pendingRequest.capability, requestedAt: view.pendingRequest.requestedAt, ...view.pendingRequest.change ? { change: view.pendingRequest.change } : {} } : null,
|
|
4548
|
+
desktopPermissions: this.desktopPermissions
|
|
4549
|
+
};
|
|
4550
|
+
const key = JSON.stringify(params);
|
|
4551
|
+
if (key === this.sentAccessKey)
|
|
4463
4552
|
return;
|
|
4464
4553
|
this.sentAccessKey = key;
|
|
4465
|
-
this.sendSigned({ jsonrpc: "2.0", method: "tunnel.access.state", params
|
|
4554
|
+
this.sendSigned({ jsonrpc: "2.0", method: "tunnel.access.state", params });
|
|
4555
|
+
}
|
|
4556
|
+
async probeDesktopPermissions() {
|
|
4557
|
+
const call = this.registry.getHandler("desktop.cua.call");
|
|
4558
|
+
if (!call || this.probingDesktop)
|
|
4559
|
+
return;
|
|
4560
|
+
this.probingDesktop = true;
|
|
4561
|
+
try {
|
|
4562
|
+
const report = await call({ tool: "check_permissions", args: {}, __permission: { capability: "desktop" } });
|
|
4563
|
+
this.desktopPermissions = {
|
|
4564
|
+
accessibility: report?.accessibility === true,
|
|
4565
|
+
screenRecording: report?.screen_recording === true
|
|
4566
|
+
};
|
|
4567
|
+
} catch {} finally {
|
|
4568
|
+
this.probingDesktop = false;
|
|
4569
|
+
}
|
|
4570
|
+
this.reportAccess();
|
|
4466
4571
|
}
|
|
4467
4572
|
syncKeepAwake(want) {
|
|
4468
4573
|
if (!want) {
|
|
@@ -4473,7 +4578,7 @@ class TunnelAgent {
|
|
|
4473
4578
|
}
|
|
4474
4579
|
if (this.keepAwake || this.keepAwakeFailed)
|
|
4475
4580
|
return;
|
|
4476
|
-
const blocker = keepAwakeCommand(
|
|
4581
|
+
const blocker = keepAwakeCommand(platform3(), process.pid);
|
|
4477
4582
|
if (!blocker)
|
|
4478
4583
|
return;
|
|
4479
4584
|
const child = spawn3(blocker.command, blocker.args, { stdio: "ignore" });
|
|
@@ -4517,6 +4622,8 @@ class TunnelAgent {
|
|
|
4517
4622
|
this.syncKeepAwake(access.keepAwake);
|
|
4518
4623
|
this.reportAccess();
|
|
4519
4624
|
} catch {}
|
|
4625
|
+
if (++this.watchdogTicks % DESKTOP_PROBE_EVERY_TICKS === 0 && this.status === "online")
|
|
4626
|
+
this.probeDesktopPermissions();
|
|
4520
4627
|
}, interval);
|
|
4521
4628
|
this.watchdog.unref?.();
|
|
4522
4629
|
}
|
|
@@ -4582,7 +4689,7 @@ class TunnelAgent {
|
|
|
4582
4689
|
}
|
|
4583
4690
|
|
|
4584
4691
|
// src/agent/banner.ts
|
|
4585
|
-
import { arch as arch2, hostname as hostname3, platform as
|
|
4692
|
+
import { arch as arch2, hostname as hostname3, platform as platform4 } from "os";
|
|
4586
4693
|
var BOX_WIDTH = 60;
|
|
4587
4694
|
var BAR_WIDTH = 50;
|
|
4588
4695
|
var BAR_FRAMES = 14;
|
|
@@ -4632,7 +4739,7 @@ async function printStartupBanner({
|
|
|
4632
4739
|
blank();
|
|
4633
4740
|
row(` ${c.dim}tunnel${c.reset} ${c.white}${truncate(tunnelId, 40)}${c.reset}`);
|
|
4634
4741
|
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}(${
|
|
4742
|
+
row(` ${c.dim}machine${c.reset} ${c.white}${truncate(hostname3(), 28)}${c.reset} ${c.dim}(${platform4()} ${arch2()})${c.reset}`);
|
|
4636
4743
|
row(` ${c.dim}access${c.reset} ${capabilityRow}`);
|
|
4637
4744
|
blank();
|
|
4638
4745
|
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 +4773,8 @@ class CapabilityRegistry {
|
|
|
4666
4773
|
|
|
4667
4774
|
// src/agent/capabilities/desktop/cua-driver.ts
|
|
4668
4775
|
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
|
|
4776
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, realpathSync, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
4777
|
+
import { homedir as homedir3, platform as platform5, tmpdir } from "os";
|
|
4671
4778
|
import { basename as basename2, dirname as dirname3, join as join5 } from "path";
|
|
4672
4779
|
var MAX_DRIVER_OUTPUT_BYTES = 5 * 1024 * 1024;
|
|
4673
4780
|
var DRIVER_ENV_KEYS = [
|
|
@@ -4785,7 +4892,7 @@ function execFile(cmd, args, timeoutMs = 30000, env = driverEnvironment()) {
|
|
|
4785
4892
|
clearTimeout(timer);
|
|
4786
4893
|
if (code !== 0) {
|
|
4787
4894
|
const detail = stderr.trim() || stdout.trim();
|
|
4788
|
-
reject(new Error(`${cmd} failed (${code})${detail ? `: ${detail}` : ""}`));
|
|
4895
|
+
reject(new Error(`${basename2(cmd)} failed (${code})${detail ? `: ${detail}` : ""}`));
|
|
4789
4896
|
} else {
|
|
4790
4897
|
resolve2({ stdout, stderr });
|
|
4791
4898
|
}
|
|
@@ -4833,7 +4940,8 @@ class CuaDriver {
|
|
|
4833
4940
|
this.binary = options.binary ?? null;
|
|
4834
4941
|
this.embedded = options.embedded;
|
|
4835
4942
|
const uid = typeof process.getuid === "function" ? process.getuid() : "user";
|
|
4836
|
-
|
|
4943
|
+
const home = serviceLabelFor(agentTunnelHome()).slice(SERVICE_LABEL.length);
|
|
4944
|
+
this.socketPath = options.socketPath ?? join5(tmpdir(), `beeuni-cua-${uid}${home}.sock`);
|
|
4837
4945
|
this.hostBundleId = options.hostBundleId;
|
|
4838
4946
|
}
|
|
4839
4947
|
isEmbedded(binary) {
|
|
@@ -4913,7 +5021,9 @@ class CuaDriver {
|
|
|
4913
5021
|
const bin = await this.ensureInstalled();
|
|
4914
5022
|
if (this.isEmbedded(bin)) {
|
|
4915
5023
|
await this.startEmbeddedDaemon(bin);
|
|
4916
|
-
|
|
5024
|
+
this.daemonReady = true;
|
|
5025
|
+
return { ok: true };
|
|
5026
|
+
} else if (platform5() === "darwin") {
|
|
4917
5027
|
const child = spawn4("open", ["-n", "-g", "-a", "CuaDriver", "--args", "serve"], {
|
|
4918
5028
|
detached: true,
|
|
4919
5029
|
stdio: "ignore",
|
|
@@ -4942,6 +5052,12 @@ class CuaDriver {
|
|
|
4942
5052
|
if (this.daemon && this.daemon.exitCode === null)
|
|
4943
5053
|
return;
|
|
4944
5054
|
const { env, socket } = this.mode(bin);
|
|
5055
|
+
if (existsSync3(this.socketPath)) {
|
|
5056
|
+
await execFile(bin, ["stop", ...socket], 1e4, env).catch(() => {
|
|
5057
|
+
return;
|
|
5058
|
+
});
|
|
5059
|
+
rmSync2(this.socketPath, { force: true });
|
|
5060
|
+
}
|
|
4945
5061
|
const child = spawn4(bin, ["serve", "--embedded", "--no-permissions-gate", ...socket], {
|
|
4946
5062
|
stdio: "ignore",
|
|
4947
5063
|
env
|
|
@@ -4965,12 +5081,32 @@ class CuaDriver {
|
|
|
4965
5081
|
throw new Error("cua-driver did not start (no socket after 10 s)");
|
|
4966
5082
|
}
|
|
4967
5083
|
}
|
|
5084
|
+
async permissionOwner() {
|
|
5085
|
+
return this.isEmbedded(await this.ensureInstalled()) ? "Beeuni" : "CuaDriver";
|
|
5086
|
+
}
|
|
5087
|
+
async missingPermissions() {
|
|
5088
|
+
const report = await this.call("check_permissions", {});
|
|
5089
|
+
const missing = [];
|
|
5090
|
+
if (report?.accessibility === false)
|
|
5091
|
+
missing.push("Accessibility");
|
|
5092
|
+
if (report?.screen_recording === false)
|
|
5093
|
+
missing.push("Screen Recording");
|
|
5094
|
+
return missing;
|
|
5095
|
+
}
|
|
4968
5096
|
stop() {
|
|
4969
5097
|
this.daemon?.kill();
|
|
4970
5098
|
this.daemon = null;
|
|
4971
5099
|
this.daemonReady = false;
|
|
4972
5100
|
}
|
|
4973
5101
|
async ensureDaemonReady() {
|
|
5102
|
+
const bin = await this.ensureInstalled();
|
|
5103
|
+
if (this.isEmbedded(bin)) {
|
|
5104
|
+
if (this.daemon?.exitCode === null && existsSync3(this.socketPath))
|
|
5105
|
+
return;
|
|
5106
|
+
this.stop();
|
|
5107
|
+
await this.startDaemon();
|
|
5108
|
+
return;
|
|
5109
|
+
}
|
|
4974
5110
|
if (this.daemonReady)
|
|
4975
5111
|
return;
|
|
4976
5112
|
try {
|
|
@@ -4987,44 +5123,6 @@ class CuaDriver {
|
|
|
4987
5123
|
}
|
|
4988
5124
|
|
|
4989
5125
|
// 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
5126
|
var LOCAL_ONLY_CUA_TOOLS = new Set(["check_for_update", "install_ffmpeg"]);
|
|
5029
5127
|
function assertRemotelyCallableTool(tool) {
|
|
5030
5128
|
if (typeof tool !== "string" || tool.length === 0) {
|
|
@@ -5034,24 +5132,30 @@ function assertRemotelyCallableTool(tool) {
|
|
|
5034
5132
|
throw new Error(`CUA tool "${tool}" is local-only and cannot run through Agent Tunnel`);
|
|
5035
5133
|
}
|
|
5036
5134
|
}
|
|
5037
|
-
|
|
5135
|
+
|
|
5136
|
+
class DesktopPermissionMissingError extends Error {
|
|
5137
|
+
code = TunnelErrorCode.DESKTOP_PERMISSION_MISSING;
|
|
5138
|
+
}
|
|
5139
|
+
async function explainFailure(cua, err, onMissing) {
|
|
5140
|
+
const missing = await cua.missingPermissions().catch(() => []);
|
|
5141
|
+
if (missing.length > 0) {
|
|
5142
|
+
onMissing();
|
|
5143
|
+
throw new DesktopPermissionMissingError(`computer_desktop_permission_missing: macOS has not given ${await cua.permissionOwner()} ${missing.join(" and ")} on this computer.`);
|
|
5144
|
+
}
|
|
5145
|
+
throw err;
|
|
5146
|
+
}
|
|
5147
|
+
function createDesktopCapability(options = {}) {
|
|
5038
5148
|
const cua = new CuaDriver;
|
|
5039
5149
|
const methods = new Map;
|
|
5040
|
-
const
|
|
5150
|
+
const onMissing = options.onPermissionMissing ?? (() => {});
|
|
5151
|
+
const assertDesktopPermission = (params) => {
|
|
5041
5152
|
const permission = params.__permission;
|
|
5042
5153
|
if (permission?.capability !== "desktop") {
|
|
5043
5154
|
throw new Error("Permission denied: desktop permission required");
|
|
5044
5155
|
}
|
|
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
5156
|
};
|
|
5053
5157
|
methods.set("desktop.cua.ensure", async (params) => {
|
|
5054
|
-
assertDesktopPermission(params
|
|
5158
|
+
assertDesktopPermission(params);
|
|
5055
5159
|
const binary = await cua.ensureInstalled();
|
|
5056
5160
|
const version = await cua.version().catch(() => {
|
|
5057
5161
|
return;
|
|
@@ -5059,40 +5163,36 @@ function createDesktopCapability() {
|
|
|
5059
5163
|
return { ok: true, binary, version };
|
|
5060
5164
|
});
|
|
5061
5165
|
methods.set("desktop.cua.start_daemon", async (params) => {
|
|
5062
|
-
assertDesktopPermission(params
|
|
5166
|
+
assertDesktopPermission(params);
|
|
5063
5167
|
return cua.startDaemon();
|
|
5064
5168
|
});
|
|
5065
5169
|
methods.set("desktop.cua.status", async (params) => {
|
|
5066
|
-
assertDesktopPermission(params
|
|
5170
|
+
assertDesktopPermission(params);
|
|
5067
5171
|
return { status: await cua.status() };
|
|
5068
5172
|
});
|
|
5069
5173
|
methods.set("desktop.cua.version", async (params) => {
|
|
5070
|
-
assertDesktopPermission(params
|
|
5174
|
+
assertDesktopPermission(params);
|
|
5071
5175
|
return { version: await cua.version() };
|
|
5072
5176
|
});
|
|
5073
5177
|
methods.set("desktop.cua.list_tools", async (params) => {
|
|
5074
|
-
assertDesktopPermission(params
|
|
5178
|
+
assertDesktopPermission(params);
|
|
5075
5179
|
return { tools: await cua.listTools() };
|
|
5076
5180
|
});
|
|
5077
5181
|
methods.set("desktop.cua.describe", async (params) => {
|
|
5078
|
-
assertDesktopPermission(params
|
|
5182
|
+
assertDesktopPermission(params);
|
|
5079
5183
|
return { description: await cua.describe(params.tool) };
|
|
5080
5184
|
});
|
|
5081
5185
|
methods.set("desktop.cua.call", async (params) => {
|
|
5082
|
-
assertDesktopPermission(params
|
|
5186
|
+
assertDesktopPermission(params);
|
|
5083
5187
|
const tool = params.tool;
|
|
5084
5188
|
assertRemotelyCallableTool(tool);
|
|
5085
5189
|
const args = params.args || {};
|
|
5086
|
-
|
|
5190
|
+
try {
|
|
5191
|
+
return await cua.call(tool, args);
|
|
5192
|
+
} catch (err) {
|
|
5193
|
+
return explainFailure(cua, err, onMissing);
|
|
5194
|
+
}
|
|
5087
5195
|
});
|
|
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
5196
|
return {
|
|
5097
5197
|
name: "desktop",
|
|
5098
5198
|
methods
|
|
@@ -5503,7 +5603,7 @@ function createShellCapability(config) {
|
|
|
5503
5603
|
}
|
|
5504
5604
|
|
|
5505
5605
|
// src/agent/capabilities/enabled-registry.ts
|
|
5506
|
-
function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary) {
|
|
5606
|
+
function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriverBinary, desktop = {}) {
|
|
5507
5607
|
const registry = new CapabilityRegistry;
|
|
5508
5608
|
const enabled = new Set(config.enabledCapabilities ?? ["filesystem", "shell", "desktop"]);
|
|
5509
5609
|
if (enabled.has("filesystem"))
|
|
@@ -5511,13 +5611,13 @@ function createEnabledCapabilityRegistry(config, findDesktopDriver = findCuaDriv
|
|
|
5511
5611
|
if (enabled.has("shell"))
|
|
5512
5612
|
registry.register(createShellCapability(config));
|
|
5513
5613
|
if (enabled.has("desktop") && findDesktopDriver()) {
|
|
5514
|
-
registry.register(createDesktopCapability());
|
|
5614
|
+
registry.register(createDesktopCapability(desktop));
|
|
5515
5615
|
}
|
|
5516
5616
|
return registry;
|
|
5517
5617
|
}
|
|
5518
5618
|
|
|
5519
5619
|
// src/agent/credential-store.ts
|
|
5520
|
-
import { chmodSync as chmodSync3, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync7, renameSync as renameSync2, rmSync as
|
|
5620
|
+
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
5621
|
import { join as join8 } from "path";
|
|
5522
5622
|
var CONFIG_DIR2 = agentTunnelHome();
|
|
5523
5623
|
var CONFIG_FILE2 = join8(CONFIG_DIR2, "config.json");
|
|
@@ -5561,7 +5661,7 @@ function clearSavedCredentials() {
|
|
|
5561
5661
|
try {
|
|
5562
5662
|
existing = JSON.parse(readFileSync7(CONFIG_FILE2, "utf-8"));
|
|
5563
5663
|
} catch {
|
|
5564
|
-
|
|
5664
|
+
rmSync3(CONFIG_FILE2, { force: true });
|
|
5565
5665
|
return true;
|
|
5566
5666
|
}
|
|
5567
5667
|
const hadCredentials = PAIRING_FIELDS.some((key) => (key in existing) && existing[key] != null);
|
|
@@ -5642,7 +5742,6 @@ function isShellStartupNoise(line) {
|
|
|
5642
5742
|
}
|
|
5643
5743
|
|
|
5644
5744
|
// src/agent/prompts.ts
|
|
5645
|
-
import { createInterface } from "readline/promises";
|
|
5646
5745
|
function isInteractiveTerminal() {
|
|
5647
5746
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
5648
5747
|
}
|
|
@@ -5652,41 +5751,16 @@ function isTruthyFlag(value) {
|
|
|
5652
5751
|
function anyFlag(flags, names) {
|
|
5653
5752
|
return names.some((name) => isTruthyFlag(flags[name]));
|
|
5654
5753
|
}
|
|
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
5754
|
|
|
5681
5755
|
// 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
|
|
5756
|
+
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";
|
|
5757
|
+
import { homedir as homedir5, platform as platform7 } from "os";
|
|
5684
5758
|
import { join as join10 } from "path";
|
|
5685
5759
|
|
|
5686
5760
|
// src/agent/service-drivers.ts
|
|
5687
5761
|
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
|
|
5762
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
5763
|
+
import { homedir as homedir4, platform as platform6, userInfo } from "os";
|
|
5690
5764
|
import { dirname as dirname6, join as join9 } from "path";
|
|
5691
5765
|
|
|
5692
5766
|
// src/agent/service-quoting.ts
|
|
@@ -5835,7 +5909,7 @@ var launchd = {
|
|
|
5835
5909
|
const existed = existsSync5(paths.launchdPlist);
|
|
5836
5910
|
const stop = run("launchctl", ["bootout", launchdTarget(), paths.launchdPlist]);
|
|
5837
5911
|
if (existed)
|
|
5838
|
-
|
|
5912
|
+
rmSync4(paths.launchdPlist, { force: true });
|
|
5839
5913
|
return { detail: stop.detail };
|
|
5840
5914
|
},
|
|
5841
5915
|
start(paths, installed) {
|
|
@@ -5888,7 +5962,7 @@ var systemd = {
|
|
|
5888
5962
|
const existed = existsSync5(paths.systemdUnit);
|
|
5889
5963
|
const disable = run("systemctl", ["--user", "disable", "--now", `${paths.label}.service`]);
|
|
5890
5964
|
if (existed)
|
|
5891
|
-
|
|
5965
|
+
rmSync4(paths.systemdUnit, { force: true });
|
|
5892
5966
|
run("systemctl", ["--user", "daemon-reload"]);
|
|
5893
5967
|
return { detail: disable.detail };
|
|
5894
5968
|
},
|
|
@@ -5963,8 +6037,8 @@ var scheduledTask = {
|
|
|
5963
6037
|
const stop = run("schtasks.exe", ["/End", "/TN", paths.label]);
|
|
5964
6038
|
const del = run("schtasks.exe", ["/Delete", "/TN", paths.label, "/F"]);
|
|
5965
6039
|
if (existed)
|
|
5966
|
-
|
|
5967
|
-
|
|
6040
|
+
rmSync4(paths.windowsScript, { force: true });
|
|
6041
|
+
rmSync4(windowsTaskXmlPath(paths), { force: true });
|
|
5968
6042
|
return { detail: joinDetails(stop, del) };
|
|
5969
6043
|
},
|
|
5970
6044
|
start(paths, installed) {
|
|
@@ -6010,7 +6084,7 @@ function serviceDriverFor(os) {
|
|
|
6010
6084
|
return DRIVERS[os];
|
|
6011
6085
|
}
|
|
6012
6086
|
function serviceDriver() {
|
|
6013
|
-
return serviceDriverFor(
|
|
6087
|
+
return serviceDriverFor(platform6());
|
|
6014
6088
|
}
|
|
6015
6089
|
|
|
6016
6090
|
// src/agent/service.ts
|
|
@@ -6046,8 +6120,22 @@ function runnerPartsFor(script, runtime = {
|
|
|
6046
6120
|
}
|
|
6047
6121
|
return { command: runtime.execPath, args: [vendor(script, paths), "run", "--service"], env };
|
|
6048
6122
|
}
|
|
6123
|
+
function installedAppRunner(apps = ["/Applications/Beeuni.app", join10(homedir5(), "Applications", "Beeuni.app")], os = process.platform) {
|
|
6124
|
+
if (os !== "darwin")
|
|
6125
|
+
return null;
|
|
6126
|
+
for (const app of apps) {
|
|
6127
|
+
const execPath = join10(app, "Contents", "MacOS", "Beeuni");
|
|
6128
|
+
const script = join10(app, "Contents", "Resources", "agent-tunnel", "agent-cli.js");
|
|
6129
|
+
if (existsSync6(execPath) && existsSync6(script))
|
|
6130
|
+
return { execPath, script };
|
|
6131
|
+
}
|
|
6132
|
+
return null;
|
|
6133
|
+
}
|
|
6049
6134
|
var plannedRunner = (script, paths) => isEphemeralRunnerPath(script) ? paths.vendoredRunner : script;
|
|
6050
6135
|
function currentRunnerParts(vendor = vendorRunner) {
|
|
6136
|
+
const app = process.versions.electron ? null : installedAppRunner();
|
|
6137
|
+
if (app)
|
|
6138
|
+
return runnerPartsFor(app.script, { execPath: app.execPath, electron: "installed-app" }, getServicePaths(), vendor);
|
|
6051
6139
|
const script = process.argv[1];
|
|
6052
6140
|
if (script && existsSync6(script))
|
|
6053
6141
|
return runnerPartsFor(script, undefined, getServicePaths(), vendor);
|
|
@@ -6062,7 +6150,7 @@ function withDriver(operate, fallback) {
|
|
|
6062
6150
|
const installed = existsSync6(path);
|
|
6063
6151
|
const outcome = operate(driver, paths, installed);
|
|
6064
6152
|
return {
|
|
6065
|
-
platform:
|
|
6153
|
+
platform: platform7(),
|
|
6066
6154
|
installed: outcome.installed ?? installed,
|
|
6067
6155
|
active: outcome.active ?? fallback.active,
|
|
6068
6156
|
...outcome.enabled !== undefined ? { enabled: outcome.enabled } : {},
|
|
@@ -6080,7 +6168,7 @@ function installService() {
|
|
|
6080
6168
|
return withDriver((driver, servicePaths) => driver.install(servicePaths, runner), { installed: false, active: null });
|
|
6081
6169
|
}
|
|
6082
6170
|
function uninstallService() {
|
|
6083
|
-
|
|
6171
|
+
rmSync5(getServicePaths().binDir, { recursive: true, force: true });
|
|
6084
6172
|
return withDriver((driver, paths) => ({ ...driver.uninstall(paths), installed: false, active: false }), { installed: false, active: false });
|
|
6085
6173
|
}
|
|
6086
6174
|
function startService() {
|
|
@@ -6108,7 +6196,7 @@ function restartService() {
|
|
|
6108
6196
|
function getServiceStatus() {
|
|
6109
6197
|
if (!serviceDriver()) {
|
|
6110
6198
|
return {
|
|
6111
|
-
platform:
|
|
6199
|
+
platform: platform7(),
|
|
6112
6200
|
installed: false,
|
|
6113
6201
|
active: null,
|
|
6114
6202
|
detail: SUPPORTED_PLATFORMS_MESSAGE
|
|
@@ -6165,6 +6253,35 @@ function renderServiceAction(action, status) {
|
|
|
6165
6253
|
blankLine();
|
|
6166
6254
|
}
|
|
6167
6255
|
|
|
6256
|
+
// src/agent/self-restart.ts
|
|
6257
|
+
import { statSync as statSync3 } from "fs";
|
|
6258
|
+
function runtimeFingerprint(files) {
|
|
6259
|
+
return files.map((file) => {
|
|
6260
|
+
try {
|
|
6261
|
+
const stat2 = statSync3(file);
|
|
6262
|
+
return `${stat2.ino}:${stat2.mtimeMs}:${stat2.size}`;
|
|
6263
|
+
} catch {
|
|
6264
|
+
return "missing";
|
|
6265
|
+
}
|
|
6266
|
+
}).join("|");
|
|
6267
|
+
}
|
|
6268
|
+
var PERMISSION_RESTART_MIN_UPTIME_S = 5 * 60;
|
|
6269
|
+
function watchForRestart(exit, intervalMs = 60000) {
|
|
6270
|
+
const files = [process.execPath, process.argv[1]].filter((file) => Boolean(file));
|
|
6271
|
+
const started = runtimeFingerprint(files);
|
|
6272
|
+
setInterval(() => {
|
|
6273
|
+
if (runtimeFingerprint(files) !== started)
|
|
6274
|
+
exit("its code was updated");
|
|
6275
|
+
}, intervalMs).unref();
|
|
6276
|
+
return {
|
|
6277
|
+
permissionMissing: () => {
|
|
6278
|
+
if (process.uptime() < PERMISSION_RESTART_MIN_UPTIME_S)
|
|
6279
|
+
return;
|
|
6280
|
+
setTimeout(() => exit("macOS refused a desktop permission"), 1000).unref();
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
6283
|
+
}
|
|
6284
|
+
|
|
6168
6285
|
// src/agent/state-file.ts
|
|
6169
6286
|
import { chmodSync as chmodSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
6170
6287
|
import { join as join11 } from "path";
|
|
@@ -6191,10 +6308,22 @@ function writeAgentState(status, tunnel, home = agentTunnelHome()) {
|
|
|
6191
6308
|
renameSync3(tmp, stateFilePath(home));
|
|
6192
6309
|
} catch {}
|
|
6193
6310
|
}
|
|
6311
|
+
function isAlive2(pid) {
|
|
6312
|
+
if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0)
|
|
6313
|
+
return false;
|
|
6314
|
+
try {
|
|
6315
|
+
process.kill(pid, 0);
|
|
6316
|
+
return true;
|
|
6317
|
+
} catch (error) {
|
|
6318
|
+
return error.code === "EPERM";
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6194
6321
|
function readAgentState(home = agentTunnelHome()) {
|
|
6195
6322
|
try {
|
|
6196
6323
|
const parsed = JSON.parse(readFileSync10(stateFilePath(home), "utf8"));
|
|
6197
|
-
|
|
6324
|
+
if (!parsed || typeof parsed !== "object" || typeof parsed.status !== "string")
|
|
6325
|
+
return null;
|
|
6326
|
+
return parsed.status !== "offline" && !isAlive2(parsed.pid) ? { ...parsed, status: "offline" } : parsed;
|
|
6198
6327
|
} catch {
|
|
6199
6328
|
return null;
|
|
6200
6329
|
}
|
|
@@ -6207,13 +6336,18 @@ var FOREGROUND_FLAGS = ["foreground", "no-daemon", "no-service", "no-background"
|
|
|
6207
6336
|
var DEFAULT_LOG_LINES = 60;
|
|
6208
6337
|
function parseArgs(argv) {
|
|
6209
6338
|
const flags = {};
|
|
6339
|
+
const words = [];
|
|
6210
6340
|
for (let i = 3;i < argv.length; i++) {
|
|
6211
6341
|
const arg = argv[i];
|
|
6212
|
-
if (!arg.startsWith("--"))
|
|
6342
|
+
if (!arg.startsWith("--")) {
|
|
6343
|
+
words.push(arg);
|
|
6213
6344
|
continue;
|
|
6345
|
+
}
|
|
6214
6346
|
const next = argv[i + 1];
|
|
6215
6347
|
flags[arg.slice(2)] = next && !next.startsWith("--") ? argv[++i] : "true";
|
|
6216
6348
|
}
|
|
6349
|
+
if (words.length > 0)
|
|
6350
|
+
flags._ = words.join(" ");
|
|
6217
6351
|
return { command: argv[2] || "help", flags };
|
|
6218
6352
|
}
|
|
6219
6353
|
var jsonMode = false;
|
|
@@ -6238,7 +6372,16 @@ function startAgent(config, options = {}) {
|
|
|
6238
6372
|
process.stdout.write = process.stderr.write.bind(process.stderr);
|
|
6239
6373
|
options = { ...options, service: true };
|
|
6240
6374
|
}
|
|
6241
|
-
|
|
6375
|
+
let agent = null;
|
|
6376
|
+
const restart = options.service ? watchForRestart((reason) => {
|
|
6377
|
+
process.stdout.write(`[agent-tunnel] restarting: ${reason}
|
|
6378
|
+
`);
|
|
6379
|
+
agent?.disconnect();
|
|
6380
|
+
process.exit(0);
|
|
6381
|
+
}) : null;
|
|
6382
|
+
const registry = createEnabledCapabilityRegistry(config, undefined, {
|
|
6383
|
+
onPermissionMissing: restart?.permissionMissing
|
|
6384
|
+
});
|
|
6242
6385
|
if (config.enabledCapabilities?.includes("desktop") && !registry.has("desktop")) {
|
|
6243
6386
|
console.error("[agent-tunnel] Computer Use is approved but unavailable: install the trusted cua-driver locally, then restart Agent Tunnel.");
|
|
6244
6387
|
}
|
|
@@ -6254,7 +6397,7 @@ function startAgent(config, options = {}) {
|
|
|
6254
6397
|
version: agentTunnelVersion()
|
|
6255
6398
|
});
|
|
6256
6399
|
}
|
|
6257
|
-
|
|
6400
|
+
agent = new TunnelAgent(config, registry, { onStatus: (status, current) => writeAgentState(status, current) }, {
|
|
6258
6401
|
persistent: options.service === true,
|
|
6259
6402
|
reloadConfig: () => loadConfig({ apiUrl: config.apiUrl })
|
|
6260
6403
|
});
|
|
@@ -6263,31 +6406,37 @@ function startAgent(config, options = {}) {
|
|
|
6263
6406
|
if (!options.service)
|
|
6264
6407
|
console.log(`
|
|
6265
6408
|
${c.dim} Shutting down…${c.reset}`);
|
|
6266
|
-
agent
|
|
6409
|
+
agent?.disconnect();
|
|
6267
6410
|
process.exit(0);
|
|
6268
6411
|
};
|
|
6269
6412
|
process.on("SIGTERM", shutdown);
|
|
6270
6413
|
process.on("SIGINT", shutdown);
|
|
6271
6414
|
}
|
|
6272
|
-
|
|
6415
|
+
function chooseBackgroundMode(flags) {
|
|
6273
6416
|
if (anyFlag(flags, BACKGROUND_FLAGS))
|
|
6274
6417
|
return true;
|
|
6275
6418
|
if (anyFlag(flags, FOREGROUND_FLAGS))
|
|
6276
6419
|
return false;
|
|
6277
|
-
|
|
6278
|
-
|
|
6420
|
+
return !jsonMode && isInteractiveTerminal();
|
|
6421
|
+
}
|
|
6422
|
+
function printServiceControls() {
|
|
6423
|
+
const npx = "npx @beeuni/agent-tunnel@latest";
|
|
6424
|
+
console.log(` ${c.dim}Runs in the background: starts at login, restarts after failures, keeps running after this terminal closes.${c.reset}`);
|
|
6425
|
+
if (installedAppRunner()) {
|
|
6426
|
+
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}`);
|
|
6427
|
+
}
|
|
6279
6428
|
blankLine();
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6429
|
+
field("Pause", `${c.white}${npx} stop${c.reset}`);
|
|
6430
|
+
field("Resume", `${c.white}${npx} start${c.reset}`);
|
|
6431
|
+
field("Disconnect", `${c.white}${npx} logout${c.reset}`);
|
|
6283
6432
|
blankLine();
|
|
6284
|
-
return promptYesNo(" Install the background service now?", DEFAULT_INSTALL_BACKGROUND_SERVICE);
|
|
6285
6433
|
}
|
|
6286
6434
|
async function launch(config, flags, lease) {
|
|
6287
|
-
if (
|
|
6435
|
+
if (chooseBackgroundMode(flags)) {
|
|
6288
6436
|
saveCredentials(config.tunnelId, config.token, config.apiUrl);
|
|
6289
6437
|
if (!jsonMode) {
|
|
6290
6438
|
renderServiceAction("install", SERVICE_ACTIONS.install.run());
|
|
6439
|
+
printServiceControls();
|
|
6291
6440
|
return;
|
|
6292
6441
|
}
|
|
6293
6442
|
try {
|
|
@@ -6495,7 +6644,7 @@ function commandStatus(flags) {
|
|
|
6495
6644
|
serviceLabel: getServicePaths().label,
|
|
6496
6645
|
service,
|
|
6497
6646
|
state: readAgentState(),
|
|
6498
|
-
access:
|
|
6647
|
+
access: accessView(),
|
|
6499
6648
|
lastActivity: lastServiceActivity()
|
|
6500
6649
|
}, null, 2));
|
|
6501
6650
|
return;
|
|
@@ -6552,8 +6701,8 @@ async function unpairOnServer() {
|
|
|
6552
6701
|
async function commandLogout(flags) {
|
|
6553
6702
|
const serverUnpaired = await unpairOnServer();
|
|
6554
6703
|
const removed = clearSavedCredentials();
|
|
6555
|
-
|
|
6556
|
-
|
|
6704
|
+
rmSync6(accessFilePath(), { force: true });
|
|
6705
|
+
rmSync6(accessRequestPath(), { force: true });
|
|
6557
6706
|
const keepService = isTruthyFlag(flags["keep-service"]);
|
|
6558
6707
|
if (!keepService)
|
|
6559
6708
|
SERVICE_ACTIONS.uninstall.run();
|
|
@@ -6624,12 +6773,74 @@ function commandServiceAction(action, flags) {
|
|
|
6624
6773
|
}
|
|
6625
6774
|
renderServiceAction(action, SERVICE_ACTIONS[action].run());
|
|
6626
6775
|
}
|
|
6776
|
+
function describeAccess(view) {
|
|
6777
|
+
const clock = (iso) => new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
6778
|
+
if (view.mode === "always")
|
|
6779
|
+
return `${c.white}Always allowed${c.reset}`;
|
|
6780
|
+
if (view.mode === "off")
|
|
6781
|
+
return `${c.white}Off${c.reset} ${c.dim}(every agent call is refused)${c.reset}`;
|
|
6782
|
+
const now = view.grantedUntil ? `allowed until ${clock(view.grantedUntil)}` : view.deniedUntil ? `denied until ${clock(view.deniedUntil)}` : "asks before the next call";
|
|
6783
|
+
return `${c.white}Ask each time${c.reset} ${c.dim}· ${now}${c.reset}`;
|
|
6784
|
+
}
|
|
6785
|
+
function printAccess(view, flags) {
|
|
6786
|
+
if (isTruthyFlag(flags.json)) {
|
|
6787
|
+
console.log(JSON.stringify(view, null, 2));
|
|
6788
|
+
return;
|
|
6789
|
+
}
|
|
6790
|
+
const npx = "npx @beeuni/agent-tunnel@latest";
|
|
6791
|
+
blankLine();
|
|
6792
|
+
field("access", describeAccess(view));
|
|
6793
|
+
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}`);
|
|
6794
|
+
if (view.pendingRequest) {
|
|
6795
|
+
field("waiting", `${c.white}${view.pendingRequest.change ? "a change asked for through Beeuni" : `an agent call (${view.pendingRequest.capability})`}${c.reset}`);
|
|
6796
|
+
field("", `${c.dim}${npx} access --allow-for 60 or --deny${c.reset}`);
|
|
6797
|
+
}
|
|
6798
|
+
blankLine();
|
|
6799
|
+
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}`);
|
|
6800
|
+
blankLine();
|
|
6801
|
+
}
|
|
6802
|
+
function commandAccess(flags) {
|
|
6803
|
+
const change = {};
|
|
6804
|
+
if (flags._)
|
|
6805
|
+
change.mode = flags._;
|
|
6806
|
+
if (flags["allow-for"] !== undefined) {
|
|
6807
|
+
change.grantMinutes = Number(flags["allow-for"]);
|
|
6808
|
+
if (!(change.grantMinutes > 0))
|
|
6809
|
+
fail("--allow-for takes minutes, at most 1440. Example: agent-tunnel access --allow-for 60");
|
|
6810
|
+
}
|
|
6811
|
+
if (isTruthyFlag(flags.revoke))
|
|
6812
|
+
change.revoke = true;
|
|
6813
|
+
if (isTruthyFlag(flags.deny))
|
|
6814
|
+
change.deny = true;
|
|
6815
|
+
if (Object.keys(change).length === 0)
|
|
6816
|
+
return printAccess(accessView(), flags);
|
|
6817
|
+
if (change.mode !== undefined && !["ask", "always", "off"].includes(change.mode)) {
|
|
6818
|
+
fail("Access is one of: ask, always, off. Example: agent-tunnel access ask");
|
|
6819
|
+
}
|
|
6820
|
+
if (change.grantMinutes !== undefined && change.mode === undefined && readAccess().mode !== "ask")
|
|
6821
|
+
change.mode = "ask";
|
|
6822
|
+
printAccess(applyAccess(change), flags);
|
|
6823
|
+
}
|
|
6824
|
+
function commandKeepAwake(flags) {
|
|
6825
|
+
if (flags._ === undefined)
|
|
6826
|
+
return printAccess(accessView(), flags);
|
|
6827
|
+
if (flags._ !== "on" && flags._ !== "off")
|
|
6828
|
+
fail("Keep awake is on or off. Example: agent-tunnel keep-awake on");
|
|
6829
|
+
if (!accessView().keepAwakeSupported)
|
|
6830
|
+
fail("Keep awake is not available on this system yet.");
|
|
6831
|
+
printAccess(applyAccess({ keepAwake: flags._ === "on" }), flags);
|
|
6832
|
+
}
|
|
6627
6833
|
var COMMANDS = {
|
|
6628
6834
|
connect: {
|
|
6629
6835
|
summary: "Pair this machine, then run it in the background or this terminal",
|
|
6630
6836
|
run: commandConnect
|
|
6631
6837
|
},
|
|
6632
6838
|
status: { summary: "Show pairing, capabilities, and service state (--json)", run: commandStatus },
|
|
6839
|
+
access: {
|
|
6840
|
+
summary: "When Beeuni may use this computer: ask, always, off (--allow-for <min>, --revoke, --deny, --json)",
|
|
6841
|
+
run: commandAccess
|
|
6842
|
+
},
|
|
6843
|
+
"keep-awake": { summary: "Keep this computer awake while connected: on, off (--json)", run: commandKeepAwake },
|
|
6633
6844
|
logs: { summary: "Show recent service logs (--lines <n>, --all, --clear)", run: commandLogs },
|
|
6634
6845
|
start: {
|
|
6635
6846
|
summary: "Start the background service (resumes a paused one)",
|
|
@@ -6668,7 +6879,7 @@ var OPTIONS = [
|
|
|
6668
6879
|
["--reauth", "With connect: discard the saved credential and pair again"],
|
|
6669
6880
|
["--daemon / --foreground", "With connect: skip the prompt and choose the mode"],
|
|
6670
6881
|
["--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"],
|
|
6882
|
+
["--json", "With connect: NDJSON events, no prompts. With status, access, keep-awake, and logout: machine-readable output"],
|
|
6672
6883
|
["--keep-service", "With logout: keep the background service installed"]
|
|
6673
6884
|
];
|
|
6674
6885
|
function showHelp() {
|
|
@@ -6700,14 +6911,14 @@ function showHelp() {
|
|
|
6700
6911
|
var { command, flags } = parseArgs(process.argv);
|
|
6701
6912
|
jsonMode = command === "connect" && isTruthyFlag(flags.json);
|
|
6702
6913
|
if (Object.prototype.hasOwnProperty.call(flags, "keep-awake")) {
|
|
6703
|
-
console.error(` ${glyph.bad} --keep-awake is not
|
|
6914
|
+
console.error(` ${glyph.bad} --keep-awake is not a flag. Run: agent-tunnel keep-awake on`);
|
|
6704
6915
|
process.exit(2);
|
|
6705
6916
|
}
|
|
6706
6917
|
var resolved = COMMANDS[command] ?? Object.values(COMMANDS).find((entry) => entry.aliases?.includes(command));
|
|
6707
6918
|
if (!resolved) {
|
|
6708
6919
|
showHelp();
|
|
6709
6920
|
} else {
|
|
6710
|
-
Promise.resolve(resolved.run(flags)).catch((error) => {
|
|
6921
|
+
Promise.resolve().then(() => resolved.run(flags)).catch((error) => {
|
|
6711
6922
|
fail(error instanceof Error ? error.message : String(error));
|
|
6712
6923
|
});
|
|
6713
6924
|
}
|