@phnx-labs/agents-cli 1.20.50 → 1.20.52
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 +40 -0
- package/dist/commands/browser-picker.js +1 -18
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.js +7 -25
- package/dist/commands/computer.d.ts +1 -0
- package/dist/commands/computer.js +129 -8
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +66 -8
- package/dist/commands/factory.js +9 -4
- package/dist/commands/feed.d.ts +9 -0
- package/dist/commands/feed.js +69 -0
- package/dist/commands/inspect.js +1 -11
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +9 -6
- package/dist/commands/message.js +1 -4
- package/dist/commands/profiles.js +1 -18
- package/dist/commands/repo.js +33 -14
- package/dist/commands/resource-view.d.ts +1 -0
- package/dist/commands/resource-view.js +5 -17
- package/dist/commands/secrets.d.ts +23 -0
- package/dist/commands/secrets.js +174 -70
- package/dist/commands/sessions-picker.js +1 -18
- package/dist/commands/sessions.js +6 -8
- package/dist/commands/teams-picker.js +1 -32
- package/dist/commands/teams.js +5 -27
- package/dist/commands/tmux.js +1 -3
- package/dist/commands/view.js +1 -9
- package/dist/commands/worktree.js +1 -4
- package/dist/index.js +6 -2
- package/dist/lib/agents.d.ts +0 -4
- package/dist/lib/agents.js +20 -33
- package/dist/lib/auto-dispatch-linear.d.ts +18 -0
- package/dist/lib/auto-dispatch-linear.js +107 -0
- package/dist/lib/auto-dispatch-provider.d.ts +10 -0
- package/dist/lib/auto-dispatch-provider.js +25 -0
- package/dist/lib/auto-dispatch.d.ts +87 -0
- package/dist/lib/auto-dispatch.js +142 -0
- package/dist/lib/browser/cdp.js +11 -2
- package/dist/lib/browser/drivers/ssh.d.ts +28 -10
- package/dist/lib/browser/drivers/ssh.js +57 -18
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/refs.js +1 -5
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cli-resources.d.ts +0 -2
- package/dist/lib/cli-resources.js +30 -13
- package/dist/lib/cloud/rush.d.ts +15 -24
- package/dist/lib/cloud/rush.js +7 -32
- package/dist/lib/crabbox/cli.js +4 -1
- package/dist/lib/crabbox/lease.d.ts +6 -0
- package/dist/lib/crabbox/lease.js +40 -10
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +73 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +288 -101
- package/dist/lib/exec.js +32 -14
- package/dist/lib/feed.d.ts +56 -0
- package/dist/lib/feed.js +251 -0
- package/dist/lib/format.d.ts +38 -0
- package/dist/lib/format.js +108 -0
- package/dist/lib/git.d.ts +21 -0
- package/dist/lib/git.js +92 -0
- package/dist/lib/hooks/cache.d.ts +9 -2
- package/dist/lib/hooks/cache.js +220 -8
- package/dist/lib/hooks.js +24 -10
- package/dist/lib/hosts/passthrough.js +1 -0
- package/dist/lib/platform/exec.d.ts +4 -1
- package/dist/lib/platform/exec.js +8 -2
- package/dist/lib/resources.d.ts +0 -8
- package/dist/lib/resources.js +0 -10
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.js +10 -2
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +38 -8
- package/dist/lib/secrets/icloud-import.d.ts +70 -0
- package/dist/lib/secrets/icloud-import.js +173 -0
- package/dist/lib/secrets/index.d.ts +36 -0
- package/dist/lib/secrets/index.js +99 -9
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +11 -1
- package/dist/lib/session/active.js +3 -0
- package/dist/lib/session/db.d.ts +1 -4
- package/dist/lib/session/db.js +20 -25
- package/dist/lib/session/discover.d.ts +2 -2
- package/dist/lib/session/discover.js +60 -48
- package/dist/lib/session/parse.js +35 -34
- package/dist/lib/session/render.d.ts +7 -3
- package/dist/lib/session/render.js +15 -9
- package/dist/lib/session/state.d.ts +55 -0
- package/dist/lib/session/state.js +100 -11
- package/dist/lib/session/types.d.ts +9 -0
- package/dist/lib/shims.d.ts +9 -3
- package/dist/lib/shims.js +44 -8
- package/dist/lib/ssh-tunnel.d.ts +33 -2
- package/dist/lib/ssh-tunnel.js +94 -7
- package/dist/lib/staleness/types.d.ts +0 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/types.d.ts +24 -1
- package/dist/lib/versions.d.ts +0 -26
- package/dist/lib/versions.js +2 -145
- package/dist/lib/warn-unpushed.d.ts +40 -0
- package/dist/lib/warn-unpushed.js +128 -0
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +3 -1
- package/dist/lib/resources/index.d.ts +0 -53
- package/dist/lib/resources/index.js +0 -76
|
@@ -4,7 +4,9 @@ import * as os from 'os';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
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
|
-
import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, } from '../lib/ssh-tunnel.js';
|
|
7
|
+
import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/ssh-tunnel.js';
|
|
8
|
+
import { sshExec } from '../lib/ssh-exec.js';
|
|
9
|
+
import { encodePowershell } from '../lib/hosts/remote-cmd.js';
|
|
8
10
|
import { registerActionCommands, withClient, unwrap, pickTarget } from './computer-actions.js';
|
|
9
11
|
import { runComputerLoop } from '../lib/computer/loop.js';
|
|
10
12
|
import { makeVerbDispatcher } from '../lib/computer/dispatch.js';
|
|
@@ -18,9 +20,10 @@ const COMPUTER_HELP_GROUPS = [
|
|
|
18
20
|
{ title: 'Interact', names: ['launch', 'raise', 'click', 'right-click', 'type', 'type-text', 'key', 'drag', 'scroll', 'ax-action', 'focus', 'wait'] },
|
|
19
21
|
];
|
|
20
22
|
// Subcommands that manage the `--host` remote path themselves (provisioning /
|
|
21
|
-
// tunnel lifecycle
|
|
22
|
-
//
|
|
23
|
-
|
|
23
|
+
// tunnel lifecycle, or daemon-state reporting that must degrade gracefully
|
|
24
|
+
// when no tunnel is recorded). Every other `--host`-bearing subcommand is a
|
|
25
|
+
// plain verb that just needs the TCP endpoint hydrated before it runs.
|
|
26
|
+
const REMOTE_LIFECYCLE = new Set(['setup', 'start', 'stop', 'status', 'reload']);
|
|
24
27
|
/**
|
|
25
28
|
* Pure platform gate. The computer subsystem is macOS-only for LOCAL driving
|
|
26
29
|
* (Accessibility / launchctl). It is NOT blocked off macOS when a remote daemon
|
|
@@ -111,8 +114,13 @@ export function registerComputerSubcommands(program) {
|
|
|
111
114
|
function registerStatusCommand(program) {
|
|
112
115
|
program
|
|
113
116
|
.command('status')
|
|
114
|
-
.description('Report install state, daemon state, and Accessibility trust')
|
|
115
|
-
.
|
|
117
|
+
.description('Report install state, daemon state, and Accessibility trust — or a remote Windows daemon with --host')
|
|
118
|
+
.option('--host <device>', 'Report the remote Windows daemon (tunnel + liveness) instead of the local helper')
|
|
119
|
+
.action(async (opts) => {
|
|
120
|
+
if (opts.host) {
|
|
121
|
+
await reportRemoteStatus(opts.host);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
116
124
|
const socketPath = resolveSocketPath();
|
|
117
125
|
const installed = fs.existsSync(HELPER_APP_DEST);
|
|
118
126
|
const socketUp = fs.existsSync(socketPath);
|
|
@@ -159,6 +167,108 @@ function registerStatusCommand(program) {
|
|
|
159
167
|
}
|
|
160
168
|
});
|
|
161
169
|
}
|
|
170
|
+
// status --host: the local checks (app install, launchd socket, policy files)
|
|
171
|
+
// are macOS concepts — a remote Windows daemon is reported from what actually
|
|
172
|
+
// exists for it: the recorded tunnel and a live trust_status probe through it.
|
|
173
|
+
async function reportRemoteStatus(host) {
|
|
174
|
+
console.log(`host: ${host}`);
|
|
175
|
+
const state = readRemoteState(host);
|
|
176
|
+
if (!state) {
|
|
177
|
+
console.log('tunnel: none');
|
|
178
|
+
console.log('daemon: unknown (no tunnel to probe through)');
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(`Run: agents computer start --host ${host}`);
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
console.log(`tunnel: 127.0.0.1:${state.localPort} -> ${state.target} (127.0.0.1:${state.remotePort})`);
|
|
184
|
+
hydrateRemoteEnvFromState(host);
|
|
185
|
+
try {
|
|
186
|
+
const client = openComputerClient();
|
|
187
|
+
try {
|
|
188
|
+
const r = await client.call('trust_status');
|
|
189
|
+
if (r.error) {
|
|
190
|
+
console.error(`error: ${r.error.code}: ${r.error.message}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
console.log('daemon: running');
|
|
194
|
+
console.log(`trust: ${r.result?.trusted ? 'granted' : 'denied'} (Windows UIAutomation needs no per-app grant)`);
|
|
195
|
+
if (typeof r.result?.pid === 'number')
|
|
196
|
+
console.log(`pid: ${r.result.pid}`);
|
|
197
|
+
if (typeof r.result?.path === 'string' && r.result.path)
|
|
198
|
+
console.log(`exe: ${r.result.path}`);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
await client.close();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
console.log('daemon: unreachable');
|
|
206
|
+
console.log(` ${err.message}`);
|
|
207
|
+
console.log('');
|
|
208
|
+
console.log(`Run: agents computer start --host ${host}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// PowerShell to bounce the remote daemon: kill the running exe (tolerating
|
|
213
|
+
// "not running"), then start the LOGON scheduled task that owns its
|
|
214
|
+
// lifecycle. Pure so tests can assert the exact script (mirrors the
|
|
215
|
+
// ssh-tunnel script builders).
|
|
216
|
+
export function buildRestartTaskScript(taskName, exeName) {
|
|
217
|
+
const procName = exeName.replace(/\.exe$/i, '');
|
|
218
|
+
return [
|
|
219
|
+
`$ErrorActionPreference = 'Stop'`,
|
|
220
|
+
`Stop-Process -Name '${procName}' -Force -ErrorAction SilentlyContinue`,
|
|
221
|
+
`Start-ScheduledTask -TaskName '${taskName}'`,
|
|
222
|
+
`Write-Output 'restarted'`,
|
|
223
|
+
].join('; ');
|
|
224
|
+
}
|
|
225
|
+
// reload --host: the Windows daemon has no policy file to re-read (it
|
|
226
|
+
// enforces no allow-list — see TrustStatus in native/computer-win/Rpc.cs), so
|
|
227
|
+
// reload means bounce the daemon via its scheduled task — the way to pick up
|
|
228
|
+
// a freshly pushed exe — then prove it answers through the recorded tunnel.
|
|
229
|
+
async function reloadRemoteHelper(host) {
|
|
230
|
+
const { target } = await resolveRemoteDevice(host);
|
|
231
|
+
const script = buildRestartTaskScript(REMOTE_TASK_NAME, WIN_HELPER_EXE);
|
|
232
|
+
const res = sshExec(target, `powershell -NoProfile -NonInteractive -EncodedCommand ${encodePowershell(script)}`, { timeoutMs: 60_000 });
|
|
233
|
+
if (res.code !== 0) {
|
|
234
|
+
const msg = (res.stderr || res.stdout || '').trim();
|
|
235
|
+
console.error(`restart failed on ${target}${res.timedOut ? ' (timed out)' : ''}${msg ? `: ${msg}` : ''}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
console.log(`task: restarted "${REMOTE_TASK_NAME}" on ${target}`);
|
|
239
|
+
const state = readRemoteState(host);
|
|
240
|
+
if (!state) {
|
|
241
|
+
console.log(`(no tunnel recorded — run \`agents computer start --host ${host}\` to drive it)`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
hydrateRemoteEnvFromState(host);
|
|
245
|
+
// The relaunched daemon needs a beat to rebind its port; poll through the
|
|
246
|
+
// tunnel until it answers.
|
|
247
|
+
const deadline = Date.now() + 15_000;
|
|
248
|
+
let lastErr = '';
|
|
249
|
+
while (Date.now() < deadline) {
|
|
250
|
+
try {
|
|
251
|
+
const client = openComputerClient();
|
|
252
|
+
try {
|
|
253
|
+
const r = await client.call('trust_status');
|
|
254
|
+
if (!r.error && r.result) {
|
|
255
|
+
console.log(`reloaded: daemon answering (pid ${r.result.pid ?? '?'})`);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
lastErr = r.error ? `${r.error.code}: ${r.error.message}` : 'empty result';
|
|
259
|
+
}
|
|
260
|
+
finally {
|
|
261
|
+
await client.close();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
lastErr = err.message;
|
|
266
|
+
}
|
|
267
|
+
await sleep(500);
|
|
268
|
+
}
|
|
269
|
+
console.error(`daemon did not answer within 15s after restart${lastErr ? ` (${lastErr})` : ''}`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
162
272
|
// run — the embedded observe -> act -> verify agent loop. A reasoning model
|
|
163
273
|
// (Claude API by default, or any Anthropic-shaped endpoint via --base-url for
|
|
164
274
|
// Ollama / vLLM / LiteLLM) drives the EXISTING computer verbs as tools over the
|
|
@@ -560,8 +670,19 @@ function registerStartCommand(program) {
|
|
|
560
670
|
function registerReloadCommand(program) {
|
|
561
671
|
program
|
|
562
672
|
.command('reload')
|
|
563
|
-
.description('Reload the allow-list policy
|
|
564
|
-
.
|
|
673
|
+
.description('Reload the allow-list policy (SIGHUP the local daemon) — or restart a remote Windows daemon with --host')
|
|
674
|
+
.option('--host <device>', 'Restart the remote Windows daemon (its scheduled task) instead of SIGHUPing the local one')
|
|
675
|
+
.action(async (opts) => {
|
|
676
|
+
if (opts.host) {
|
|
677
|
+
try {
|
|
678
|
+
await reloadRemoteHelper(opts.host);
|
|
679
|
+
}
|
|
680
|
+
catch (err) {
|
|
681
|
+
console.error(`error: ${err.message}`);
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
565
686
|
const socketPath = resolveSocketPath();
|
|
566
687
|
if (!fs.existsSync(socketPath)) {
|
|
567
688
|
console.error(`daemon not running (no socket at ${socketPath})`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the structured audit/event log.
|
|
3
3
|
*
|
|
4
|
-
* The event log (`~/.agents
|
|
4
|
+
* The event log (`~/.agents/events.jsonl`) records every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
6
|
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
7
|
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
package/dist/commands/events.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `agents events` — read the structured audit/event log.
|
|
3
3
|
*
|
|
4
|
-
* The event log (`~/.agents
|
|
4
|
+
* The event log (`~/.agents/events.jsonl`) records every
|
|
5
5
|
* `agents <module> <cmd>` invocation plus richer typed events (secrets access,
|
|
6
6
|
* version installs, ...), each stamped with who ran it and from where (OS user,
|
|
7
7
|
* local vs SSH, remote client IP). This command reads it back — the audit trail
|
|
@@ -125,8 +125,7 @@ function collect(value, previous) {
|
|
|
125
125
|
}
|
|
126
126
|
/** Tail today's event file, printing new lines as they land. */
|
|
127
127
|
async function followLog() {
|
|
128
|
-
const
|
|
129
|
-
const file = `${getLogsPath()}/events-${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}.jsonl`;
|
|
128
|
+
const file = getLogsPath();
|
|
130
129
|
let offset = 0;
|
|
131
130
|
try {
|
|
132
131
|
offset = fs.statSync(file).size;
|
package/dist/commands/exec.js
CHANGED
|
@@ -11,6 +11,7 @@ import { setHelpSections } from '../lib/help.js';
|
|
|
11
11
|
import { parseLoopInterval } from '../lib/loop.js';
|
|
12
12
|
import { AGENTS } from '../lib/agents.js';
|
|
13
13
|
import { recordDispatchedRun } from '../lib/audit/log.js';
|
|
14
|
+
import { warnUnpushedWork, shouldWarnUnpushed } from '../lib/warn-unpushed.js';
|
|
14
15
|
import * as fs from 'fs';
|
|
15
16
|
import * as path from 'path';
|
|
16
17
|
import * as os from 'os';
|
|
@@ -211,7 +212,7 @@ export function registerRunCommand(program) {
|
|
|
211
212
|
.option('--secrets-keys <keys>', 'Inject only this comma-separated subset of keys from --secrets bundles (e.g. KEY1,KEY2). Missing keys are an error. Applies to all --secrets bundles on this run.')
|
|
212
213
|
.option('--allow-expired', 'Inject secrets even if their expiry date has passed (overrides the pre-run expiry abort).')
|
|
213
214
|
.option('--cwd <dir>', 'Working directory for the agent (defaults to current directory)')
|
|
214
|
-
.option('--add-dir <dir>', 'Grant access to an additional directory outside the project (Claude
|
|
215
|
+
.option('--add-dir <dir>', 'Grant access to an additional directory outside the project (Claude and Codex, repeatable)', (val, prev) => [...prev, val], [])
|
|
215
216
|
.option('--json', 'Stream events as JSON lines (for parsing by other tools)')
|
|
216
217
|
.option('--quiet', 'Suppress preamble (rotation banner, "Running:" line). Useful when piping JSON events to a parser.', false)
|
|
217
218
|
.option('--headless', 'Force headless mode. Auto-enabled when a prompt is provided; pass explicitly to stay headless with no prompt (reads the prompt from stdin).', false)
|
|
@@ -245,6 +246,12 @@ export function registerRunCommand(program) {
|
|
|
245
246
|
// `--on` and `--computer` are hidden aliases of `--host` — same behavior.
|
|
246
247
|
runCmd.addOption(new Option('--on <name>', 'Alias of --host.').hideHelp());
|
|
247
248
|
runCmd.addOption(new Option('--computer <name>', 'Alias of --host.').hideHelp());
|
|
249
|
+
// Required for the documented `agents run <agent> [prompt] -- <native flags>`
|
|
250
|
+
// passthrough: commander >=13 rejects excess operands by default, so any
|
|
251
|
+
// post-`--` token died with "too many arguments" before the action ran. The
|
|
252
|
+
// action re-derives the `--` boundary from rawArgs and still errors on excess
|
|
253
|
+
// operands that are NOT behind `--`.
|
|
254
|
+
runCmd.allowExcessArguments(true);
|
|
248
255
|
setHelpSections(runCmd, {
|
|
249
256
|
examples: `
|
|
250
257
|
# Headless, read-only: investigate or summarize without writing files
|
|
@@ -293,10 +300,25 @@ export function registerRunCommand(program) {
|
|
|
293
300
|
`,
|
|
294
301
|
});
|
|
295
302
|
runCmd.action(async (agentSpec, prompt, options, command) => {
|
|
296
|
-
// Capture everything after -- as passthrough args forwarded verbatim to the
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
303
|
+
// Capture everything after -- as passthrough args forwarded verbatim to the
|
|
304
|
+
// underlying CLI. Commander strips the literal `--` and folds what follows
|
|
305
|
+
// into the positional operands (so `agents run codex -- --yolo` would parse
|
|
306
|
+
// `--yolo` as the PROMPT) — recover the boundary from rawArgs instead of
|
|
307
|
+
// operand counts. Excess operands not behind `--` are still an error (an
|
|
308
|
+
// unquoted prompt must not silently become agent flags).
|
|
309
|
+
const rawArgs = process.argv;
|
|
310
|
+
const separatorIdx = rawArgs.indexOf('--');
|
|
311
|
+
const passthroughArgs = separatorIdx === -1 ? [] : rawArgs.slice(separatorIdx + 1);
|
|
312
|
+
const operandsBeforeSeparator = command.args.length - passthroughArgs.length;
|
|
313
|
+
if (operandsBeforeSeparator > 2) {
|
|
314
|
+
console.error(chalk.red(`Too many arguments for 'run'. Quote the prompt ("fix the bug"), and put agent-native flags after -- (agents run codex -- --yolo).`));
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
if (prompt !== undefined && operandsBeforeSeparator < 2) {
|
|
318
|
+
// The token commander assigned to [prompt] came from behind `--` — it is
|
|
319
|
+
// a native flag, not a prompt. Run interactively.
|
|
320
|
+
prompt = undefined;
|
|
321
|
+
}
|
|
300
322
|
// --lease: invent a disposable cloud box for this run (via crabbox), run
|
|
301
323
|
// the agent there, then tear it down. Unlike --host, nothing is registered.
|
|
302
324
|
if (options.lease) {
|
|
@@ -305,7 +327,7 @@ export function registerRunCommand(program) {
|
|
|
305
327
|
process.exit(1);
|
|
306
328
|
}
|
|
307
329
|
const backend = typeof options.lease === 'string' ? options.lease : undefined;
|
|
308
|
-
const { detectSignedInRuntimes, pickRuntimes } = await import('../lib/crabbox/runtimes.js');
|
|
330
|
+
const { detectSignedInRuntimes, pickRuntimes, resolveClaudeCredentialsBlob } = await import('../lib/crabbox/runtimes.js');
|
|
309
331
|
const { leaseAndRun } = await import('../lib/crabbox/lease.js');
|
|
310
332
|
const { confirm } = await import('@inquirer/prompts');
|
|
311
333
|
const detected = await detectSignedInRuntimes();
|
|
@@ -322,14 +344,28 @@ export function registerRunCommand(program) {
|
|
|
322
344
|
return `${d?.label ?? id}${d?.email ? ` (${d.email})` : ''}`;
|
|
323
345
|
})
|
|
324
346
|
.join(', ');
|
|
347
|
+
// Claude ships its OAuth token (not just the .claude.json config) — name it
|
|
348
|
+
// explicitly so the consent covers the actual credential transferred.
|
|
349
|
+
const whatShips = runtimes.includes('claude') ? 'credentials + Claude OAuth token' : 'credentials';
|
|
325
350
|
const ok = await confirm({
|
|
326
|
-
message: `Copy
|
|
351
|
+
message: `Copy ${whatShips} for ${names} to a disposable cloud box, run there, then destroy it?`,
|
|
327
352
|
default: false,
|
|
328
353
|
});
|
|
329
354
|
if (!ok) {
|
|
330
355
|
console.error(chalk.yellow('Aborted — no credentials pushed, no box leased.'));
|
|
331
356
|
process.exit(1);
|
|
332
357
|
}
|
|
358
|
+
// Read the Claude OAuth token from the local Keychain (silent) so it can be
|
|
359
|
+
// written to ~/.claude/.credentials.json on the box — otherwise Claude boots
|
|
360
|
+
// "Not logged in". Consent above already covered this transfer.
|
|
361
|
+
let claudeCredentialsJson = null;
|
|
362
|
+
if (runtimes.includes('claude')) {
|
|
363
|
+
const claudeEmail = detected.find((d) => d.id === 'claude')?.email ?? null;
|
|
364
|
+
claudeCredentialsJson = await resolveClaudeCredentialsBlob({ preferEmail: claudeEmail });
|
|
365
|
+
if (!claudeCredentialsJson) {
|
|
366
|
+
console.error(chalk.yellow('Warning: could not read the local Claude OAuth token — the box may come up "Not logged in".'));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
333
369
|
try {
|
|
334
370
|
const { exitCode, box, toreDown } = await leaseAndRun({
|
|
335
371
|
agent: agentSpec.split('@')[0],
|
|
@@ -339,10 +375,11 @@ export function registerRunCommand(program) {
|
|
|
339
375
|
backend,
|
|
340
376
|
runtimes,
|
|
341
377
|
detected,
|
|
378
|
+
claudeCredentialsJson,
|
|
342
379
|
secretsBundle: process.env.AGENTS_LEASE_SECRETS_BUNDLE,
|
|
343
380
|
keep: options.keepBox,
|
|
344
381
|
});
|
|
345
|
-
console.error(chalk.gray(toreDown ? `Box ${box.slug} destroyed.` : `Box ${box.slug} kept${box.ip ? ` (${box.ip})` : ''}. Stop it: crabbox stop
|
|
382
|
+
console.error(chalk.gray(toreDown ? `Box ${box.slug} destroyed.` : `Box ${box.slug} kept${box.ip ? ` (${box.ip})` : ''}. Stop it: crabbox stop ${box.slug}`));
|
|
346
383
|
process.exit(exitCode === null ? 1 : exitCode);
|
|
347
384
|
}
|
|
348
385
|
catch (err) {
|
|
@@ -568,6 +605,9 @@ export function registerRunCommand(program) {
|
|
|
568
605
|
cwd: resumeExec.cwd ?? process.cwd(),
|
|
569
606
|
exitCode: resumeExit,
|
|
570
607
|
});
|
|
608
|
+
// A resumed loop is always headless; surface any commits it left unpushed.
|
|
609
|
+
if (shouldWarnUnpushed(resumeExec.mode ?? 'auto', false))
|
|
610
|
+
await warnUnpushedWork(resumeExec.cwd ?? process.cwd());
|
|
571
611
|
process.exit(resumeExit);
|
|
572
612
|
}
|
|
573
613
|
const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, headlessPlanStallCommand, nativeResume, resolveInteractive }, { ALL_AGENT_IDS }, { profileExists, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports },] = await Promise.all([
|
|
@@ -1279,6 +1319,9 @@ export function registerRunCommand(program) {
|
|
|
1279
1319
|
// Governance chokepoint (#347): the --acp path exits here, bypassing
|
|
1280
1320
|
// the normal finalize below — record its one audit entry.
|
|
1281
1321
|
recordDispatchedRun({ agent, version: defaultVersion ?? 'unknown', mode, cwd, exitCode });
|
|
1322
|
+
// ACP headless run always has a prompt; surface any unpushed commits.
|
|
1323
|
+
if (shouldWarnUnpushed(mode, false))
|
|
1324
|
+
await warnUnpushedWork(cwd);
|
|
1282
1325
|
process.exit(exitCode);
|
|
1283
1326
|
}
|
|
1284
1327
|
catch (err) {
|
|
@@ -1435,6 +1478,9 @@ export function registerRunCommand(program) {
|
|
|
1435
1478
|
// the normal finalize below — record its one audit entry.
|
|
1436
1479
|
const loopExit = loopExitCode(result.stoppedBy);
|
|
1437
1480
|
recordDispatchedRun({ agent, version: defaultVersion ?? 'unknown', mode, cwd, exitCode: loopExit });
|
|
1481
|
+
// A loop is always headless; surface any commits it left unpushed.
|
|
1482
|
+
if (shouldWarnUnpushed(mode, false))
|
|
1483
|
+
await warnUnpushedWork(cwd);
|
|
1438
1484
|
process.exit(loopExit);
|
|
1439
1485
|
}
|
|
1440
1486
|
catch (err) {
|
|
@@ -1462,6 +1508,13 @@ export function registerRunCommand(program) {
|
|
|
1462
1508
|
}
|
|
1463
1509
|
cleanupWorkflowMcpConfig();
|
|
1464
1510
|
cleanupWorkflowSubagents();
|
|
1511
|
+
// Surface committed-but-unpushed work a headless writable run left
|
|
1512
|
+
// behind, so it isn't silently stranded in a worktree. Advisory only,
|
|
1513
|
+
// never throws; skipped for interactive runs (the human sees the shell)
|
|
1514
|
+
// and read-only plan mode (can't commit).
|
|
1515
|
+
if (shouldWarnUnpushed(mode, resolveInteractive(execOptions))) {
|
|
1516
|
+
await warnUnpushedWork(cwd);
|
|
1517
|
+
}
|
|
1465
1518
|
// Governance chokepoint (#347): every dispatched run finalizes here.
|
|
1466
1519
|
// ONE tamper-evident audit record per run — non-fatal by contract.
|
|
1467
1520
|
recordDispatchedRun({ agent: ranAgent, version: ranVersion ?? 'unknown', mode, cwd, exitCode });
|
|
@@ -1470,6 +1523,11 @@ export function registerRunCommand(program) {
|
|
|
1470
1523
|
catch (err) {
|
|
1471
1524
|
cleanupWorkflowMcpConfig();
|
|
1472
1525
|
cleanupWorkflowSubagents();
|
|
1526
|
+
// An agent that committed then crashed is the case where stranded work
|
|
1527
|
+
// matters most — warn before exiting the error path too.
|
|
1528
|
+
if (shouldWarnUnpushed(mode, resolveInteractive(execOptions))) {
|
|
1529
|
+
await warnUnpushedWork(cwd);
|
|
1530
|
+
}
|
|
1473
1531
|
console.error(chalk.red(`Failed to execute ${agent}: ${err.message}`));
|
|
1474
1532
|
process.exit(1);
|
|
1475
1533
|
}
|
package/dist/commands/factory.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { die } from '../lib/format.js';
|
|
2
3
|
import * as fs from 'fs';
|
|
3
4
|
import * as path from 'path';
|
|
4
5
|
import { homedir } from 'os';
|
|
5
6
|
import { betaEnableHint, isBetaEnabled } from '../lib/beta.js';
|
|
6
7
|
import { insertTask } from '../lib/cloud/store.js';
|
|
7
|
-
|
|
8
|
-
console.error(chalk.red(msg));
|
|
9
|
-
process.exit(code);
|
|
10
|
-
}
|
|
8
|
+
import { emit } from '../lib/events.js';
|
|
11
9
|
function requireFactoryUrl() {
|
|
12
10
|
const url = process.env.FACTORY_FLOOR_URL;
|
|
13
11
|
if (!url) {
|
|
@@ -82,6 +80,13 @@ Examples:
|
|
|
82
80
|
createdAt: now,
|
|
83
81
|
updatedAt: now,
|
|
84
82
|
});
|
|
83
|
+
emit('cloud.dispatch', {
|
|
84
|
+
module: 'factory',
|
|
85
|
+
taskId: result.cloud_execution_id,
|
|
86
|
+
ref,
|
|
87
|
+
linear_identifier: result.linear_identifier,
|
|
88
|
+
status: 'queued',
|
|
89
|
+
});
|
|
85
90
|
console.log(chalk.green(`Submitted ${result.linear_identifier} (${result.label})`));
|
|
86
91
|
console.log(` ticket ${result.ticket_id}`);
|
|
87
92
|
console.log(` execution ${result.cloud_execution_id}`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents feed` -- list open blocks (decisions agents are waiting on).
|
|
3
|
+
*
|
|
4
|
+
* Aggregates block records from the local feed store and, with --host, from
|
|
5
|
+
* reachable remote hosts via SSH passthrough. Each block carries enough
|
|
6
|
+
* identity for `agents message` to route a reply back to the right agent.
|
|
7
|
+
*/
|
|
8
|
+
import type { Command } from 'commander';
|
|
9
|
+
export declare function registerFeedCommand(program: Command): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { ensureFeedPublishHook, listBlocks } from '../lib/feed.js';
|
|
3
|
+
import { machineId } from '../lib/machine-id.js';
|
|
4
|
+
import { relTime } from '../lib/format.js';
|
|
5
|
+
function renderBlock(b, localHost) {
|
|
6
|
+
const host = b.host !== localHost ? chalk.yellow(` [${b.host}]`) : '';
|
|
7
|
+
const runtime = chalk.gray(b.runtime);
|
|
8
|
+
const age = chalk.gray(relTime(b.ts));
|
|
9
|
+
console.log(`${chalk.cyan(b.mailboxId)}${host} ${runtime} ${age}`);
|
|
10
|
+
for (const question of b.questions) {
|
|
11
|
+
const header = question.header ? chalk.gray(`[${question.header}] `) : '';
|
|
12
|
+
console.log(` ${header}${question.text}`);
|
|
13
|
+
if (question.options?.length) {
|
|
14
|
+
for (let i = 0; i < question.options.length; i++) {
|
|
15
|
+
const o = question.options[i];
|
|
16
|
+
const desc = o.description ? chalk.gray(` -- ${o.description}`) : '';
|
|
17
|
+
console.log(` ${chalk.dim(`${i + 1}.`)} ${o.label}${desc}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (b.ticket || b.pr) {
|
|
22
|
+
const meta = [b.ticket, b.pr].filter(Boolean).join(' ');
|
|
23
|
+
console.log(` ${chalk.gray(meta)}`);
|
|
24
|
+
}
|
|
25
|
+
console.log(` ${chalk.dim('reply:')} agents message ${b.mailboxId} "<answer>"`);
|
|
26
|
+
console.log();
|
|
27
|
+
}
|
|
28
|
+
export function registerFeedCommand(program) {
|
|
29
|
+
program
|
|
30
|
+
.command('feed')
|
|
31
|
+
.description('List open blocks -- decisions agents are waiting on')
|
|
32
|
+
.option('--json', 'Output as JSON')
|
|
33
|
+
.action(async (opts) => {
|
|
34
|
+
const setupWarnings = [];
|
|
35
|
+
const hookInstall = ensureFeedPublishHook();
|
|
36
|
+
if (hookInstall.error) {
|
|
37
|
+
setupWarnings.push(hookInstall.error);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
|
|
41
|
+
import('../lib/hooks.js'),
|
|
42
|
+
import('../lib/versions.js'),
|
|
43
|
+
]);
|
|
44
|
+
const manifest = parseHookManifest({ warn: false });
|
|
45
|
+
for (const { agent, version } of iterHooksCapableVersions({ agent: 'claude' })) {
|
|
46
|
+
const result = registerHooksToSettings(agent, getVersionHomePath(agent, version), manifest);
|
|
47
|
+
if (result.errors.length > 0) {
|
|
48
|
+
setupWarnings.push(`${agent}@${version}: ${result.errors.join('; ')}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const blocks = listBlocks();
|
|
53
|
+
for (const warning of setupWarnings) {
|
|
54
|
+
console.error(chalk.yellow(`Feed hook setup warning: ${warning}`));
|
|
55
|
+
}
|
|
56
|
+
if (opts.json) {
|
|
57
|
+
console.log(JSON.stringify(blocks, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (blocks.length === 0) {
|
|
61
|
+
console.log(chalk.gray('No open blocks.'));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const local = machineId();
|
|
65
|
+
console.log(chalk.bold(`${blocks.length} open block${blocks.length === 1 ? '' : 's'}:\n`));
|
|
66
|
+
for (const b of blocks)
|
|
67
|
+
renderBlock(b, local);
|
|
68
|
+
});
|
|
69
|
+
}
|
package/dist/commands/inspect.js
CHANGED
|
@@ -17,6 +17,7 @@ import * as fs from 'fs';
|
|
|
17
17
|
import * as os from 'os';
|
|
18
18
|
import * as path from 'path';
|
|
19
19
|
import chalk from 'chalk';
|
|
20
|
+
import { truncate, termLink } from '../lib/format.js';
|
|
20
21
|
import * as yaml from 'yaml';
|
|
21
22
|
import { AGENTS, getCliState, resolveAgentName } from '../lib/agents.js';
|
|
22
23
|
import { supports } from '../lib/capabilities.js';
|
|
@@ -1216,12 +1217,6 @@ function readFirstProseLine(p) {
|
|
|
1216
1217
|
return '';
|
|
1217
1218
|
}
|
|
1218
1219
|
// ─── OSC-8 + path helpers ────────────────────────────────────────────────────
|
|
1219
|
-
function termLink(text, filePath) {
|
|
1220
|
-
if (!filePath || !process.stdout.isTTY)
|
|
1221
|
-
return text;
|
|
1222
|
-
const url = `file://${filePath}`;
|
|
1223
|
-
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
1224
|
-
}
|
|
1225
1220
|
function linkTarget(p) {
|
|
1226
1221
|
if (!p)
|
|
1227
1222
|
return '';
|
|
@@ -1271,8 +1266,3 @@ function safeCountSessions(agent) {
|
|
|
1271
1266
|
return 0;
|
|
1272
1267
|
}
|
|
1273
1268
|
}
|
|
1274
|
-
function truncate(s, n) {
|
|
1275
|
-
if (s.length <= n)
|
|
1276
|
-
return s;
|
|
1277
|
-
return s.slice(0, n - 1) + '…';
|
|
1278
|
-
}
|
package/dist/commands/logs.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `agents logs` — unified, discoverable run-log viewer.
|
|
2
|
+
* `agents logs` — unified, discoverable run-log viewer + audit trail.
|
|
3
3
|
*
|
|
4
4
|
* Resolves a run across two substrates and shows (or `-f` follows) its log:
|
|
5
5
|
* - host-dispatch tasks (`agents run --host`) → combined-stdout log, offset-tailed
|
|
6
6
|
* - sessions (the local index) → transcript, tailed via the sessions tailer
|
|
7
7
|
*
|
|
8
|
+
* Subcommands:
|
|
9
|
+
* - `agents logs audit` — read the structured audit/event log
|
|
10
|
+
* - `agents logs stats` — show aggregate audit statistics
|
|
11
|
+
*
|
|
8
12
|
* Concise by default: a bare `agents logs <id>` prints the same summary digest as
|
|
9
13
|
* `agents sessions <id>` — cheap for an agent to glance at. The token-heavy full
|
|
10
14
|
* transcript / raw stdout is opt-in behind `--full` (alias `-m/--markdown`).
|