@phnx-labs/agents-cli 1.20.34 → 1.20.36
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/CHANGELOG.md +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -6,7 +6,88 @@
|
|
|
6
6
|
// The daemon already implements every method; this file is the thin, typed
|
|
7
7
|
// CLI skin over it plus a shared target resolver so callers stay in bundle-id
|
|
8
8
|
// space and never hand-manage pids.
|
|
9
|
-
import
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { openComputerClient, describeTransport, resolvePolicyPath, } from '../lib/computer-rpc.js';
|
|
12
|
+
import { COMPUTER_INPUT_GATED_VERBS, formatComputerPermissionGrantHint, } from '../lib/permissions.js';
|
|
13
|
+
function isComputerInputVerb(verb) {
|
|
14
|
+
return typeof verb === 'string' && COMPUTER_INPUT_GATED_VERBS.includes(verb);
|
|
15
|
+
}
|
|
16
|
+
function computerSessionId(env = process.env) {
|
|
17
|
+
return env.CODEX_THREAD_ID
|
|
18
|
+
|| env.CLAUDE_CODE_SESSION_ID
|
|
19
|
+
|| env.CLAUDE_SESSION_ID
|
|
20
|
+
|| env.AGENTS_SESSION_ID
|
|
21
|
+
|| env.AGENTS_RUN_ID
|
|
22
|
+
|| null;
|
|
23
|
+
}
|
|
24
|
+
function admissionCachePath(env = process.env) {
|
|
25
|
+
if (env.AGENTS_COMPUTER_ADMISSION_CACHE)
|
|
26
|
+
return env.AGENTS_COMPUTER_ADMISSION_CACHE;
|
|
27
|
+
return path.join(path.dirname(resolvePolicyPath()), 'computer-target-admissions.json');
|
|
28
|
+
}
|
|
29
|
+
function targetSelector(opts) {
|
|
30
|
+
return opts.bundle ? `bundle:${opts.bundle}` : 'frontmost';
|
|
31
|
+
}
|
|
32
|
+
function readAdmissionCache(filePath) {
|
|
33
|
+
try {
|
|
34
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
35
|
+
return Array.isArray(parsed.admissions) ? parsed.admissions.filter(isAdmission) : [];
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isAdmission(v) {
|
|
42
|
+
if (!v || typeof v !== 'object')
|
|
43
|
+
return false;
|
|
44
|
+
const r = v;
|
|
45
|
+
return r.sessionId !== undefined
|
|
46
|
+
&& typeof r.sessionId === 'string'
|
|
47
|
+
&& typeof r.selector === 'string'
|
|
48
|
+
&& r.gateClass === 'input'
|
|
49
|
+
&& typeof r.pid === 'number'
|
|
50
|
+
&& typeof r.bundle_id === 'string'
|
|
51
|
+
&& typeof r.name === 'string'
|
|
52
|
+
&& typeof r.admittedAtMs === 'number'
|
|
53
|
+
&& isComputerInputVerb(r.admittedByVerb);
|
|
54
|
+
}
|
|
55
|
+
function rememberAdmission(app, opts) {
|
|
56
|
+
const sessionId = computerSessionId(opts.env);
|
|
57
|
+
if (!sessionId)
|
|
58
|
+
return;
|
|
59
|
+
const filePath = admissionCachePath(opts.env);
|
|
60
|
+
const admissions = readAdmissionCache(filePath);
|
|
61
|
+
const next = {
|
|
62
|
+
sessionId,
|
|
63
|
+
selector: opts.selector,
|
|
64
|
+
gateClass: 'input',
|
|
65
|
+
pid: app.pid,
|
|
66
|
+
bundle_id: app.bundle_id,
|
|
67
|
+
name: app.name,
|
|
68
|
+
admittedAtMs: opts.nowMs ?? Date.now(),
|
|
69
|
+
admittedByVerb: opts.verb,
|
|
70
|
+
};
|
|
71
|
+
const filtered = admissions.filter((a) => !(a.sessionId === sessionId && a.selector === opts.selector && a.gateClass === 'input'));
|
|
72
|
+
filtered.push(next);
|
|
73
|
+
try {
|
|
74
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
75
|
+
fs.writeFileSync(filePath, JSON.stringify({ admissions: filtered }, null, 2), { mode: 0o600 });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Cache persistence is best-effort; the daemon remains the final gate.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function findAdmission(opts) {
|
|
82
|
+
const sessionId = computerSessionId(opts.env);
|
|
83
|
+
if (!sessionId)
|
|
84
|
+
return null;
|
|
85
|
+
const admissions = readAdmissionCache(admissionCachePath(opts.env));
|
|
86
|
+
const matches = admissions
|
|
87
|
+
.filter((a) => a.sessionId === sessionId && a.selector === opts.selector && a.gateClass === 'input')
|
|
88
|
+
.sort((a, b) => b.admittedAtMs - a.admittedAtMs);
|
|
89
|
+
return matches[0] ?? null;
|
|
90
|
+
}
|
|
10
91
|
// Pure target picker — exercised by unit tests. Precedence: explicit --pid,
|
|
11
92
|
// then --bundle, then the frontmost active allow-listed app (the same default
|
|
12
93
|
// `screenshot` uses). Kept side-effect-free so the resolution rules are
|
|
@@ -24,7 +105,7 @@ export function pickTarget(list, opts) {
|
|
|
24
105
|
if (!app) {
|
|
25
106
|
return {
|
|
26
107
|
ok: false,
|
|
27
|
-
error: `bundle not in allow list (or not running): ${opts.bundle}\
|
|
108
|
+
error: `bundle not in allow list (or not running): ${opts.bundle}\n${formatComputerPermissionGrantHint(opts.bundle)}`,
|
|
28
109
|
};
|
|
29
110
|
}
|
|
30
111
|
return { ok: true, app };
|
|
@@ -33,7 +114,7 @@ export function pickTarget(list, opts) {
|
|
|
33
114
|
if (!active) {
|
|
34
115
|
return {
|
|
35
116
|
ok: false,
|
|
36
|
-
error:
|
|
117
|
+
error: `no active app found in allow list\n${formatComputerPermissionGrantHint()}`,
|
|
37
118
|
};
|
|
38
119
|
}
|
|
39
120
|
return { ok: true, app: active };
|
|
@@ -141,20 +222,37 @@ export function unwrap(r) {
|
|
|
141
222
|
}
|
|
142
223
|
return r.result ?? {};
|
|
143
224
|
}
|
|
144
|
-
|
|
145
|
-
// and exiting when no target matches.
|
|
146
|
-
async function resolveTargetPid(client, opts) {
|
|
225
|
+
export async function resolveTargetPidDecision(client, opts, gate) {
|
|
147
226
|
// A directly-supplied pid skips the list_apps roundtrip — the daemon gates.
|
|
148
227
|
if (opts.pid != null)
|
|
149
|
-
return opts.pid;
|
|
228
|
+
return { ok: true, pid: opts.pid, source: 'pid' };
|
|
150
229
|
const apps = unwrap(await client.call('list_apps'));
|
|
151
230
|
const list = apps.apps || [];
|
|
152
231
|
const picked = pickTarget(list, opts);
|
|
232
|
+
const verb = gate?.verb;
|
|
233
|
+
const selector = targetSelector(opts);
|
|
234
|
+
if (picked.ok && isComputerInputVerb(verb)) {
|
|
235
|
+
rememberAdmission(picked.app, { selector, verb, env: gate?.env, nowMs: gate?.nowMs });
|
|
236
|
+
}
|
|
153
237
|
if (!picked.ok) {
|
|
154
|
-
|
|
238
|
+
if (isComputerInputVerb(verb)) {
|
|
239
|
+
const admitted = findAdmission({ selector, env: gate?.env });
|
|
240
|
+
if (admitted)
|
|
241
|
+
return { ok: true, pid: admitted.pid, source: 'session_admission' };
|
|
242
|
+
}
|
|
243
|
+
return { ok: false, error: picked.error };
|
|
244
|
+
}
|
|
245
|
+
return { ok: true, pid: picked.app.pid, source: 'list_apps' };
|
|
246
|
+
}
|
|
247
|
+
// Resolve the target pid via list_apps + pickTarget, printing a precise error
|
|
248
|
+
// and exiting when no target matches.
|
|
249
|
+
async function resolveTargetPid(client, opts, gate) {
|
|
250
|
+
const resolved = await resolveTargetPidDecision(client, opts, gate);
|
|
251
|
+
if (!resolved.ok) {
|
|
252
|
+
console.error(resolved.error);
|
|
155
253
|
process.exit(1);
|
|
156
254
|
}
|
|
157
|
-
return
|
|
255
|
+
return resolved.pid;
|
|
158
256
|
}
|
|
159
257
|
// --raise flag: app-level focus_window before the main action so coordinate
|
|
160
258
|
// clicks and keystrokes land on a visible, key window.
|
|
@@ -210,7 +308,7 @@ export function registerActionCommands(program) {
|
|
|
210
308
|
.option('--depth <n>', 'Max tree depth', (v) => parseInt(v, 10))
|
|
211
309
|
.option('--json', 'Emit compact JSON (default: pretty)')).action(async (opts) => {
|
|
212
310
|
await withClient(async (client) => {
|
|
213
|
-
const pid = await resolveTargetPid(client, opts);
|
|
311
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'describe' });
|
|
214
312
|
const params = { pid };
|
|
215
313
|
if (opts.depth != null)
|
|
216
314
|
params.max_depth = opts.depth;
|
|
@@ -228,7 +326,7 @@ export function registerActionCommands(program) {
|
|
|
228
326
|
.option('--raise', 'Bring the target app to the front first')
|
|
229
327
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
230
328
|
await withClient(async (client) => {
|
|
231
|
-
const pid = await resolveTargetPid(client, opts);
|
|
329
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'click' });
|
|
232
330
|
const spec = buildElementOrCoords(opts);
|
|
233
331
|
if (!spec.ok) {
|
|
234
332
|
console.error(spec.error);
|
|
@@ -250,7 +348,7 @@ export function registerActionCommands(program) {
|
|
|
250
348
|
.description('Right-click (context menu) an element or coordinate')
|
|
251
349
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
252
350
|
await withClient(async (client) => {
|
|
253
|
-
const pid = await resolveTargetPid(client, opts);
|
|
351
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'right-click' });
|
|
254
352
|
const spec = buildElementOrCoords(opts);
|
|
255
353
|
if (!spec.ok) {
|
|
256
354
|
console.error(spec.error);
|
|
@@ -269,7 +367,7 @@ export function registerActionCommands(program) {
|
|
|
269
367
|
.option('--allow-secure-field', 'Permit typing into a password field')
|
|
270
368
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
271
369
|
await withClient(async (client) => {
|
|
272
|
-
const pid = await resolveTargetPid(client, opts);
|
|
370
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'type' });
|
|
273
371
|
const spec = buildElementOrCoords(opts);
|
|
274
372
|
if (!spec.ok) {
|
|
275
373
|
console.error(spec.error);
|
|
@@ -295,7 +393,7 @@ export function registerActionCommands(program) {
|
|
|
295
393
|
.option('--char-delay <ms>', 'Inter-character delay in ms (default 4; raise for lossy keyboard relays like VM guests, e.g. 25). Clamped to [1, 250].', (v) => parseInt(v, 10))
|
|
296
394
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
297
395
|
await withClient(async (client) => {
|
|
298
|
-
const pid = await resolveTargetPid(client, opts);
|
|
396
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'type-text' });
|
|
299
397
|
await raiseIfRequested(client, pid, opts.raise);
|
|
300
398
|
const params = { pid, text: opts.text };
|
|
301
399
|
if (opts.commit)
|
|
@@ -319,7 +417,7 @@ export function registerActionCommands(program) {
|
|
|
319
417
|
.option('--require-frontmost', 'Fail (not warn) if the target is not the frontmost app')
|
|
320
418
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
321
419
|
await withClient(async (client) => {
|
|
322
|
-
const pid = await resolveTargetPid(client, opts);
|
|
420
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'key' });
|
|
323
421
|
await raiseIfRequested(client, pid, opts.raise);
|
|
324
422
|
const params = { pid, keys: opts.keys };
|
|
325
423
|
if (opts.requireFrontmost)
|
|
@@ -350,7 +448,7 @@ export function registerActionCommands(program) {
|
|
|
350
448
|
process.exit(1);
|
|
351
449
|
}
|
|
352
450
|
await withClient(async (client) => {
|
|
353
|
-
const pid = await resolveTargetPid(client, opts);
|
|
451
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'drag' });
|
|
354
452
|
await raiseIfRequested(client, pid, opts.raise);
|
|
355
453
|
const params = {
|
|
356
454
|
pid,
|
|
@@ -373,7 +471,7 @@ export function registerActionCommands(program) {
|
|
|
373
471
|
.option('--raise', 'Bring the target app to the front first')
|
|
374
472
|
.option('--json', 'Emit JSON'))).action(async (opts) => {
|
|
375
473
|
await withClient(async (client) => {
|
|
376
|
-
const pid = await resolveTargetPid(client, opts);
|
|
474
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'scroll' });
|
|
377
475
|
await raiseIfRequested(client, pid, opts.raise);
|
|
378
476
|
const params = { pid };
|
|
379
477
|
if (opts.id)
|
|
@@ -398,7 +496,7 @@ export function registerActionCommands(program) {
|
|
|
398
496
|
.requiredOption('--action <name>', 'AX action name')
|
|
399
497
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
400
498
|
await withClient(async (client) => {
|
|
401
|
-
const pid = await resolveTargetPid(client, opts);
|
|
499
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'ax-action' });
|
|
402
500
|
const res = unwrap(await client.call('ax_action', { pid, element_id: opts.id, action: opts.action }));
|
|
403
501
|
emit(res, Boolean(opts.json), () => `performed ${opts.action}`);
|
|
404
502
|
});
|
|
@@ -410,7 +508,7 @@ export function registerActionCommands(program) {
|
|
|
410
508
|
.requiredOption('--id <@eN>', 'Element id from `describe`')
|
|
411
509
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
412
510
|
await withClient(async (client) => {
|
|
413
|
-
const pid = await resolveTargetPid(client, opts);
|
|
511
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'focus' });
|
|
414
512
|
const res = unwrap(await client.call('set_focus', { pid, element_id: opts.id }));
|
|
415
513
|
emit(res, Boolean(opts.json), () => `focused ${opts.id}`);
|
|
416
514
|
});
|
|
@@ -426,7 +524,7 @@ export function registerActionCommands(program) {
|
|
|
426
524
|
.option('--title <s>', 'Raise the window whose title contains this string')
|
|
427
525
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
428
526
|
await withClient(async (client) => {
|
|
429
|
-
const pid = await resolveTargetPid(client, opts);
|
|
527
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'raise' });
|
|
430
528
|
const res = unwrap(await client.call('focus_window', { pid, ...buildRaiseParams(opts) }));
|
|
431
529
|
emit(res, Boolean(opts.json), () => {
|
|
432
530
|
const scope = res.raised_window ? `window ${res.title ?? res.window_id ?? ''}`.trim() : 'app';
|
|
@@ -455,7 +553,7 @@ export function registerActionCommands(program) {
|
|
|
455
553
|
const params = { ...spec.params };
|
|
456
554
|
// duration-only waits don't need a target pid
|
|
457
555
|
if (params.duration_ms == null)
|
|
458
|
-
params.pid = await resolveTargetPid(client, opts);
|
|
556
|
+
params.pid = await resolveTargetPid(client, opts, { verb: 'wait' });
|
|
459
557
|
const res = unwrap(await client.call('wait', params));
|
|
460
558
|
emit(res, Boolean(opts.json), () => res.satisfied ? `satisfied (${res.waited_ms}ms)` : `timed out (${res.waited_ms}ms)`);
|
|
461
559
|
});
|
|
@@ -468,7 +566,7 @@ export function registerActionCommands(program) {
|
|
|
468
566
|
.option('--max-chars <n>', 'Cap the extracted text length', (v) => parseInt(v, 10))
|
|
469
567
|
.option('--json', 'Emit JSON')).action(async (opts) => {
|
|
470
568
|
await withClient(async (client) => {
|
|
471
|
-
const pid = await resolveTargetPid(client, opts);
|
|
569
|
+
const pid = await resolveTargetPid(client, opts, { verb: 'get-text' });
|
|
472
570
|
const params = { pid };
|
|
473
571
|
if (opts.id)
|
|
474
572
|
params.element_id = opts.id;
|
|
@@ -6,10 +6,14 @@ import { registerCommandGroups } from '../lib/help.js';
|
|
|
6
6
|
import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
|
|
7
7
|
import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, } from '../lib/ssh-tunnel.js';
|
|
8
8
|
import { registerActionCommands, withClient, unwrap, pickTarget } from './computer-actions.js';
|
|
9
|
+
import { runComputerLoop } from '../lib/computer/loop.js';
|
|
10
|
+
import { makeVerbDispatcher } from '../lib/computer/dispatch.js';
|
|
11
|
+
import { makeClaudeResponder, resolveApiKey, DEFAULT_CLAUDE_MODEL, DEFAULT_CLAUDE_BASE_URL } from '../lib/computer/model.js';
|
|
9
12
|
// Help groups — mirror `agents browser` so the mental model carries over.
|
|
10
13
|
const COMPUTER_HELP_GROUPS = [
|
|
11
14
|
{ title: 'Installation', names: ['setup'] },
|
|
12
15
|
{ title: 'Daemon lifecycle', names: ['start', 'stop', 'reload', 'status'] },
|
|
16
|
+
{ title: 'Autonomous', names: ['run'] },
|
|
13
17
|
{ title: 'Observe', names: ['apps', 'describe', 'screenshot', 'get-text'] },
|
|
14
18
|
{ title: 'Interact', names: ['launch', 'raise', 'click', 'right-click', 'type', 'type-text', 'key', 'drag', 'scroll', 'ax-action', 'focus', 'wait'] },
|
|
15
19
|
];
|
|
@@ -99,6 +103,7 @@ export function registerComputerSubcommands(program) {
|
|
|
99
103
|
registerStopCommand(program);
|
|
100
104
|
registerReloadCommand(program);
|
|
101
105
|
registerStatusCommand(program);
|
|
106
|
+
registerRunCommand(program);
|
|
102
107
|
registerScreenshotCommand(program);
|
|
103
108
|
registerActionCommands(program);
|
|
104
109
|
registerCommandGroups(program, COMPUTER_HELP_GROUPS);
|
|
@@ -154,6 +159,82 @@ function registerStatusCommand(program) {
|
|
|
154
159
|
}
|
|
155
160
|
});
|
|
156
161
|
}
|
|
162
|
+
// run — the embedded observe -> act -> verify agent loop. A reasoning model
|
|
163
|
+
// (Claude API by default, or any Anthropic-shaped endpoint via --base-url for
|
|
164
|
+
// Ollama / vLLM / LiteLLM) drives the EXISTING computer verbs as tools over the
|
|
165
|
+
// daemon socket. New subcommand: the explicit verb interface external agents
|
|
166
|
+
// use is unchanged. The loop auto-switches to the screenshot/coordinate path
|
|
167
|
+
// when an app's AX tree comes back opaque (WebView / canvas).
|
|
168
|
+
function registerRunCommand(program) {
|
|
169
|
+
program
|
|
170
|
+
.command('run')
|
|
171
|
+
.description('Autonomously drive an app from a natural-language task (embedded model loop over the computer verbs)')
|
|
172
|
+
.requiredOption('--task <s>', 'Natural-language task, e.g. "open Notes and write a haiku"')
|
|
173
|
+
.option('--bundle <id>', 'Bundle id to focus the loop on (default: frontmost allow-listed app)')
|
|
174
|
+
.option('--base-url <url>', `Reasoning model base URL — Anthropic wire shape (default: ${DEFAULT_CLAUDE_BASE_URL}; set to a local Ollama/vLLM/LiteLLM endpoint for offline parity)`)
|
|
175
|
+
.option('--model <id>', `Model id (default: ${DEFAULT_CLAUDE_MODEL})`)
|
|
176
|
+
.option('--max-steps <n>', 'Max model turns before giving up', (v) => parseInt(v, 10), 12)
|
|
177
|
+
.option('--max-tokens <n>', 'Max tokens per model turn', (v) => parseInt(v, 10), 1024)
|
|
178
|
+
.option('--host <device>', 'Drive a remote Windows device (requires `agents computer start --host <device>` first)')
|
|
179
|
+
.option('--json', 'Emit the final loop result as JSON')
|
|
180
|
+
.action(async (opts) => {
|
|
181
|
+
const apiKey = resolveApiKey({ apiKey: undefined, baseUrl: opts.baseUrl });
|
|
182
|
+
// The Claude API needs a key; a local/offline endpoint (non-default base
|
|
183
|
+
// URL) usually ignores it, so only hard-fail on the default endpoint.
|
|
184
|
+
if (!apiKey && !opts.baseUrl) {
|
|
185
|
+
console.error('no API key. Set ANTHROPIC_API_KEY (or AGENTS_COMPUTER_API_KEY), or point --base-url at a local endpoint.');
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
const responder = makeClaudeResponder({
|
|
189
|
+
baseUrl: opts.baseUrl,
|
|
190
|
+
model: opts.model,
|
|
191
|
+
maxTokens: opts.maxTokens,
|
|
192
|
+
});
|
|
193
|
+
await withClient(async (client) => {
|
|
194
|
+
const dispatch = makeVerbDispatcher(client);
|
|
195
|
+
const targetInput = opts.bundle ? { bundle: opts.bundle } : {};
|
|
196
|
+
const result = await runComputerLoop({
|
|
197
|
+
task: opts.task,
|
|
198
|
+
responder: (state) => responder({ ...state, task: describeTaskWithTarget(opts.task, opts.bundle) }),
|
|
199
|
+
dispatch: (call) => dispatch({ ...call, input: { ...targetInput, ...call.input } }),
|
|
200
|
+
maxSteps: opts.maxSteps,
|
|
201
|
+
onEvent: opts.json ? undefined : (e) => printLoopEvent(e),
|
|
202
|
+
});
|
|
203
|
+
if (opts.json) {
|
|
204
|
+
console.log(JSON.stringify(result, null, 2));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
console.log('');
|
|
208
|
+
if (result.status === 'done') {
|
|
209
|
+
console.log(`done (${result.turns} turn${result.turns === 1 ? '' : 's'}): ${result.finalText ?? ''}`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
console.log(`stopped: hit max-steps (${result.turns} turns) without a completion signal`);
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Fold the target bundle into the task text so the model biases toward it.
|
|
218
|
+
function describeTaskWithTarget(task, bundle) {
|
|
219
|
+
return bundle ? `${task}\n(Target app bundle id: ${bundle})` : task;
|
|
220
|
+
}
|
|
221
|
+
// Human progress line per loop event — verb + a one-line result digest.
|
|
222
|
+
function printLoopEvent(e) {
|
|
223
|
+
if (e.kind === 'turn') {
|
|
224
|
+
console.log(`--- turn ${e.index + 1} ---`);
|
|
225
|
+
}
|
|
226
|
+
else if (e.kind === 'dispatch') {
|
|
227
|
+
const tag = e.visionFallback ? ' [ax opaque]' : '';
|
|
228
|
+
const status = e.result.ok ? 'ok' : `error: ${e.result.error ?? 'unknown'}`;
|
|
229
|
+
console.log(` ${e.call.name}${tag} -> ${status}`);
|
|
230
|
+
}
|
|
231
|
+
else if (e.kind === 'vision_switch') {
|
|
232
|
+
console.log(' (switching to vision path: screenshot + coordinate clicks)');
|
|
233
|
+
}
|
|
234
|
+
else if (e.kind === 'done') {
|
|
235
|
+
console.log(` model: ${e.text}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
157
238
|
function registerScreenshotCommand(program) {
|
|
158
239
|
program
|
|
159
240
|
.command('screenshot')
|
package/dist/commands/cost.js
CHANGED
|
@@ -4,6 +4,7 @@ import { discoverSessions, parseTimeFilter } from '../lib/session/discover.js';
|
|
|
4
4
|
import { queryUsageRollup, topSessionsByCost, } from '../lib/session/db.js';
|
|
5
5
|
import { formatUsd, PRICING_VERSION } from '../lib/pricing/index.js';
|
|
6
6
|
import { formatDuration } from '../lib/session/render.js';
|
|
7
|
+
import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
|
|
7
8
|
export function registerCostCommand(program) {
|
|
8
9
|
addHostOption(program.command('cost'))
|
|
9
10
|
.description('Roll up $ cost and duration across local agent sessions')
|
|
@@ -84,27 +85,36 @@ async function costAction(options) {
|
|
|
84
85
|
// Top sessions by cost.
|
|
85
86
|
if (top.length > 0) {
|
|
86
87
|
out.push(chalk.bold('Top sessions by cost'));
|
|
88
|
+
const cols = terminalWidth();
|
|
89
|
+
const showProject = cols >= 100;
|
|
87
90
|
const costW = Math.max(...top.map(t => formatUsd(t.costUsd).length), 4);
|
|
88
91
|
for (const t of top) {
|
|
89
92
|
const cost = formatUsd(t.costUsd).padStart(costW);
|
|
90
93
|
const dur = t.durationMs > 0 ? formatDuration(t.durationMs) : '—';
|
|
91
|
-
const label = t.meta.label || t.meta.topic || '(untitled)';
|
|
92
|
-
const proj = t.meta.project ? chalk.gray(` ${t.meta.project}`) : '';
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
const label = (t.meta.label || t.meta.topic || '(untitled)').replace(/\s+/g, ' ').trim();
|
|
95
|
+
const proj = showProject && t.meta.project ? chalk.gray(` ${t.meta.project}`) : '';
|
|
96
|
+
const prefix = ` ${chalk.green(cost)} ${chalk.gray(t.meta.shortId)} ${chalk.cyan(t.meta.agent.padEnd(7))} `;
|
|
97
|
+
const suffix = proj + chalk.gray(` ${dur}`);
|
|
98
|
+
const topicW = Math.max(12, cols - stringWidth(prefix) - stringWidth(suffix));
|
|
99
|
+
out.push(prefix + truncateToWidth(label, topicW) + suffix);
|
|
96
100
|
}
|
|
97
101
|
out.push('');
|
|
98
102
|
}
|
|
99
103
|
// Per-agent / per-project / per-day breakdown.
|
|
100
104
|
const groupLabel = groupBy === 'agent' ? 'agent' : groupBy === 'project' ? 'project' : 'day';
|
|
101
105
|
out.push(chalk.bold(`By ${groupLabel}`));
|
|
102
|
-
const
|
|
106
|
+
const cols = terminalWidth();
|
|
103
107
|
const costW2 = Math.max(...breakdown.map(r => formatUsd(r.costUsd).length), 4);
|
|
108
|
+
const countW = Math.max(...breakdown.map(r => String(r.sessionCount).length), 1);
|
|
109
|
+
const sessionW = Math.max(...breakdown.map(r => `${String(r.sessionCount).padStart(countW)} session${r.sessionCount !== 1 ? 's' : ''}`.length));
|
|
110
|
+
const durationW = Math.max(...breakdown.map(r => r.durationMs > 0 ? stringWidth(formatDuration(r.durationMs)) : 1), 1);
|
|
111
|
+
const fixedW = 2 + 2 + costW2 + 2 + sessionW + 2 + durationW;
|
|
112
|
+
const keyW = Math.max(8, Math.min(Math.max(...breakdown.map(r => stringWidth(r.key)), groupLabel.length), cols - fixedW));
|
|
104
113
|
for (const r of breakdown) {
|
|
105
114
|
const cost = formatUsd(r.costUsd).padStart(costW2);
|
|
106
115
|
const dur = r.durationMs > 0 ? formatDuration(r.durationMs) : '—';
|
|
107
|
-
|
|
116
|
+
const sessions = `${String(r.sessionCount).padStart(countW)} session${r.sessionCount !== 1 ? 's' : ''}`;
|
|
117
|
+
out.push(` ${padToWidth(truncateToWidth(r.key, keyW), keyW)} ${chalk.green(cost)} ${chalk.gray(padToWidth(sessions, sessionW))} ${chalk.gray(padToWidth(dur, durationW))}`);
|
|
108
118
|
}
|
|
109
119
|
console.log(out.join('\n'));
|
|
110
120
|
}
|
|
@@ -132,8 +142,3 @@ function renderDailyHistogram(daily) {
|
|
|
132
142
|
}
|
|
133
143
|
return lines.join('\n');
|
|
134
144
|
}
|
|
135
|
-
/** Truncate a string to n chars with an ellipsis. */
|
|
136
|
-
function truncate(s, n) {
|
|
137
|
-
const oneLine = s.replace(/\s+/g, ' ').trim();
|
|
138
|
-
return oneLine.length > n ? oneLine.slice(0, n - 1) + '…' : oneLine;
|
|
139
|
-
}
|
package/dist/commands/daemon.js
CHANGED
|
@@ -40,9 +40,12 @@ you never need to start it manually.
|
|
|
40
40
|
if (result.method === 'already-running') {
|
|
41
41
|
console.log(chalk.yellow(`Daemon already running (PID: ${result.pid})`));
|
|
42
42
|
}
|
|
43
|
-
else {
|
|
43
|
+
else if (result.pid) {
|
|
44
44
|
console.log(chalk.green(`Daemon started (PID: ${result.pid}, method: ${result.method})`));
|
|
45
45
|
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(chalk.yellow('Daemon start dispatched but no PID surfaced. Check `agents routines status`.'));
|
|
48
|
+
}
|
|
46
49
|
});
|
|
47
50
|
daemonCmd
|
|
48
51
|
.command('stop')
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* apply pending sync.
|
|
23
23
|
*/
|
|
24
24
|
import type { Command } from 'commander';
|
|
25
|
+
export declare function wrapLine(prefix: string, text: string, width?: number): string[];
|
|
25
26
|
/**
|
|
26
27
|
* Windows-only advisory lines. When the effective PowerShell execution policy
|
|
27
28
|
* blocks unsigned local `.ps1` scripts (`Restricted`/`AllSigned`), the generated
|