@phnx-labs/agents-cli 1.22.22 → 1.22.24
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 +402 -0
- package/README.md +13 -6
- package/dist/bin/agents +0 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +32 -7
- package/dist/commands/defaults.js +2 -0
- package/dist/commands/doctor.js +20 -7
- package/dist/commands/exec.js +100 -34
- package/dist/commands/feed.d.ts +18 -0
- package/dist/commands/feed.js +44 -1
- package/dist/commands/focus.d.ts +108 -5
- package/dist/commands/focus.js +455 -32
- package/dist/commands/fork.d.ts +20 -2
- package/dist/commands/fork.js +91 -64
- package/dist/commands/go.d.ts +39 -1
- package/dist/commands/go.js +112 -7
- package/dist/commands/harness-wizard.d.ts +206 -0
- package/dist/commands/harness-wizard.js +403 -0
- package/dist/commands/harness.d.ts +12 -0
- package/dist/commands/harness.js +97 -101
- package/dist/commands/insights.js +6 -2
- package/dist/commands/inspect.js +39 -5
- package/dist/commands/menubar.js +6 -1
- package/dist/commands/models.js +1 -0
- package/dist/commands/modes.d.ts +12 -0
- package/dist/commands/modes.js +147 -0
- package/dist/commands/resume.js +10 -3
- package/dist/commands/secrets-sync.js +11 -13
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +79 -47
- package/dist/commands/sessions-browser.d.ts +35 -0
- package/dist/commands/sessions-browser.js +140 -14
- package/dist/commands/sessions-resume.d.ts +23 -3
- package/dist/commands/sessions-resume.js +73 -19
- package/dist/commands/sessions.d.ts +91 -5
- package/dist/commands/sessions.js +359 -125
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/sync.js +246 -42
- package/dist/commands/view.js +2 -0
- package/dist/commands/watchdog.js +13 -2
- package/dist/index.js +2 -1
- package/dist/lib/agent-modes.d.ts +49 -0
- package/dist/lib/agent-modes.js +70 -0
- package/dist/lib/agents.d.ts +1 -1
- package/dist/lib/agents.js +132 -0
- package/dist/lib/codex-policy.d.ts +17 -0
- package/dist/lib/codex-policy.js +48 -0
- package/dist/lib/crabbox/lease.d.ts +25 -0
- package/dist/lib/crabbox/lease.js +62 -0
- package/dist/lib/daemon.js +70 -0
- package/dist/lib/doctor-diff.d.ts +3 -0
- package/dist/lib/doctor-diff.js +15 -13
- package/dist/lib/event-stream.d.ts +3 -1
- package/dist/lib/event-stream.js +14 -1
- package/dist/lib/exec.d.ts +16 -0
- package/dist/lib/exec.js +175 -62
- package/dist/lib/feed-broadcast.d.ts +1 -20
- package/dist/lib/feed-broadcast.js +31 -1
- package/dist/lib/hooks/cache.js +36 -3
- package/dist/lib/hooks.d.ts +19 -7
- package/dist/lib/hooks.js +100 -40
- package/dist/lib/hosts/session-index.d.ts +4 -0
- package/dist/lib/hosts/session-index.js +7 -0
- package/dist/lib/manifest.d.ts +12 -2
- package/dist/lib/manifest.js +60 -5
- package/dist/lib/mcp.js +44 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +52 -2
- package/dist/lib/menubar/install-menubar.js +128 -6
- package/dist/lib/models.d.ts +0 -5
- package/dist/lib/models.js +48 -0
- package/dist/lib/plugin-marketplace.js +9 -0
- package/dist/lib/pricing/prices.json +119 -92
- package/dist/lib/pricing/table.js +13 -0
- package/dist/lib/refresh.d.ts +5 -0
- package/dist/lib/refresh.js +37 -33
- package/dist/lib/remote-agents-json.d.ts +29 -1
- package/dist/lib/remote-agents-json.js +47 -10
- package/dist/lib/resource-inventory.d.ts +79 -0
- package/dist/lib/resource-inventory.js +122 -0
- package/dist/lib/resources/mcp.js +2 -0
- package/dist/lib/resources/permissions.js +3 -0
- package/dist/lib/resources/types.d.ts +2 -1
- package/dist/lib/resources.js +8 -5
- package/dist/lib/run-defaults.d.ts +2 -0
- package/dist/lib/run-defaults.js +23 -2
- package/dist/lib/runner.js +50 -36
- 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/bundles.js +157 -65
- package/dist/lib/secrets/filestore.d.ts +5 -3
- package/dist/lib/secrets/filestore.js +12 -8
- package/dist/lib/secrets/index.d.ts +15 -1
- package/dist/lib/secrets/index.js +148 -53
- package/dist/lib/secrets/reaper.d.ts +97 -0
- package/dist/lib/secrets/reaper.js +219 -0
- package/dist/lib/secrets/remote.d.ts +29 -0
- package/dist/lib/secrets/remote.js +37 -1
- package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
- package/dist/lib/secrets/sync-passphrase.js +78 -0
- package/dist/lib/session/active.d.ts +36 -1
- package/dist/lib/session/active.js +60 -19
- package/dist/lib/session/actor-sidecar.d.ts +14 -0
- package/dist/lib/session/actor-sidecar.js +67 -4
- package/dist/lib/session/db.d.ts +1 -1
- package/dist/lib/session/db.js +32 -1
- package/dist/lib/session/discover.js +168 -0
- package/dist/lib/session/parse.d.ts +10 -0
- package/dist/lib/session/parse.js +98 -0
- package/dist/lib/session/recovery.d.ts +37 -0
- package/dist/lib/session/recovery.js +95 -0
- package/dist/lib/session/remote-list.d.ts +10 -1
- package/dist/lib/session/remote-list.js +2 -8
- package/dist/lib/session/remote.d.ts +57 -3
- package/dist/lib/session/remote.js +90 -26
- package/dist/lib/session/resume-command.d.ts +6 -0
- package/dist/lib/session/resume-command.js +8 -0
- package/dist/lib/session/session-cache.d.ts +173 -0
- package/dist/lib/session/session-cache.js +399 -0
- package/dist/lib/session/types.d.ts +1 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/session/width.d.ts +1 -1
- package/dist/lib/session/width.js +12 -2
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +62 -8
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +1 -0
- package/dist/lib/startup/command-registry.js +2 -0
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +5 -4
- package/dist/lib/tmux/session.d.ts +4 -2
- package/dist/lib/tmux/session.js +5 -5
- package/dist/lib/types.d.ts +3 -1
- package/dist/lib/usage.d.ts +13 -0
- package/dist/lib/usage.js +215 -0
- package/dist/lib/versions.js +13 -2
- package/package.json +1 -1
|
@@ -66,7 +66,7 @@ function printBackendNotes(backend) {
|
|
|
66
66
|
console.log(chalk.gray('backend: keychain — macOS reads may ask for Touch ID or the device password.'));
|
|
67
67
|
}
|
|
68
68
|
else if (backend === 'file') {
|
|
69
|
-
console.log(chalk.gray('backend: file —
|
|
69
|
+
console.log(chalk.gray('backend: file — encrypted at rest; headless reads need no passphrase by default, via a machine-local key at ~/.agents/.secrets-key/passphrase.'));
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
72
|
console.log(chalk.gray('backend: vault — synced ~/.agents/vault.age storage; unlock it with agents login.'));
|
package/dist/commands/sync.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import * as path from 'path';
|
|
33
33
|
import chalk from 'chalk';
|
|
34
|
+
import { resolveSyncPassphraseFromEnv } from '../lib/secrets/sync-passphrase.js';
|
|
34
35
|
import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
|
|
35
36
|
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildRepoScopedSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/versions.js';
|
|
36
37
|
import { capableAgents } from '../lib/capabilities.js';
|
|
@@ -43,6 +44,10 @@ import { runUmbrellaSync } from '../lib/sync-umbrella.js';
|
|
|
43
44
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
44
45
|
import { syncRepoGit } from '../lib/git.js';
|
|
45
46
|
import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos } from '../lib/state.js';
|
|
47
|
+
/** Emit one JSON object to stdout for `--json` callers / fleet fan-out. */
|
|
48
|
+
function emitJson(payload) {
|
|
49
|
+
console.log(JSON.stringify(payload));
|
|
50
|
+
}
|
|
46
51
|
/** Register the `agents sync` command. */
|
|
47
52
|
export function registerSyncCommand(program) {
|
|
48
53
|
addHostOption(program.command('sync [agentSpec] [repo]'))
|
|
@@ -57,6 +62,7 @@ export function registerSyncCommand(program) {
|
|
|
57
62
|
.option('-y, --yes', 'Skip the interactive preview and auto-sync all detected resources', false)
|
|
58
63
|
.option('--force', 'Re-sync even if no changes are detected since the last sync', false)
|
|
59
64
|
.option('--quiet', 'Suppress all output (exit code indicates success)', false)
|
|
65
|
+
.option('--json', 'Emit machine-readable JSON (also accepted so fleet fan-out via --host all can parse each peer)', false)
|
|
60
66
|
// Umbrella verb (no agent given): make this machine current.
|
|
61
67
|
.option('--repos', 'Umbrella: git-pull ~/.agents + enabled ~/.agents-* extras', false)
|
|
62
68
|
.option('--secrets', 'Umbrella: pull encrypted secret bundles from the remote', false)
|
|
@@ -88,22 +94,47 @@ function resolveRepoGitTarget(repo) {
|
|
|
88
94
|
* tree, else pull --rebase against origin, pushing local commits for user-owned
|
|
89
95
|
* repos. Delegates the git work to `syncRepoGit`.
|
|
90
96
|
*/
|
|
91
|
-
async function runRepoGitSync(repo, quiet, outLog, errLog) {
|
|
97
|
+
async function runRepoGitSync(repo, quiet, outLog, errLog, json = false) {
|
|
92
98
|
const target = resolveRepoGitTarget(repo);
|
|
93
99
|
if (!target) {
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
if (json) {
|
|
101
|
+
emitJson({
|
|
102
|
+
ok: false,
|
|
103
|
+
mode: 'repo-git',
|
|
104
|
+
repo,
|
|
105
|
+
error: `The '${repo}' repo isn't independently git-synced.`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
errLog(chalk.red(`The '${repo}' repo isn't independently git-synced.`));
|
|
110
|
+
errLog(chalk.gray('Syncable repos: system (pull-only), user, and enabled extra-repo aliases.'));
|
|
111
|
+
}
|
|
96
112
|
process.exitCode = 1;
|
|
97
113
|
return;
|
|
98
114
|
}
|
|
99
|
-
if (!quiet)
|
|
115
|
+
if (!quiet && !json)
|
|
100
116
|
outLog(chalk.bold(`Syncing ${repo} repo…`) + chalk.gray(` (${target.dir})`));
|
|
101
117
|
const result = await syncRepoGit(target.dir, { push: target.push });
|
|
102
118
|
if (!result.success) {
|
|
103
|
-
|
|
119
|
+
if (json) {
|
|
120
|
+
emitJson({ ok: false, mode: 'repo-git', repo, error: result.error ?? 'sync failed' });
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
errLog(chalk.red(`sync ${repo} failed: ${result.error}`));
|
|
124
|
+
}
|
|
104
125
|
process.exitCode = 1;
|
|
105
126
|
return;
|
|
106
127
|
}
|
|
128
|
+
if (json) {
|
|
129
|
+
emitJson({
|
|
130
|
+
ok: true,
|
|
131
|
+
mode: 'repo-git',
|
|
132
|
+
repo,
|
|
133
|
+
commit: result.commit,
|
|
134
|
+
pushed: !!result.pushed,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
107
138
|
if (!quiet) {
|
|
108
139
|
const note = result.pushed ? ' · pushed' : ' · pull-only';
|
|
109
140
|
outLog(chalk.green(`✓ ${repo} → ${result.commit}${note}`));
|
|
@@ -201,12 +232,14 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
|
|
|
201
232
|
* arrives with `agents login`) and runs the fetch+reconcile stages, then prints
|
|
202
233
|
* a one-line summary. Stage failures are non-fatal and surfaced as warnings.
|
|
203
234
|
*/
|
|
204
|
-
async function runUmbrella(opts, quiet, outLog, errLog) {
|
|
235
|
+
async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
|
|
205
236
|
// Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
|
|
206
237
|
// the two-checklist picker: which repos to sync from, which agents to sync
|
|
207
|
-
// into. Any explicit flag or --
|
|
238
|
+
// into. Any explicit flag, --yes, or --json keeps the non-interactive path.
|
|
239
|
+
// --json is a machine consumer (and the fleet fan-out injects it), so never
|
|
240
|
+
// open a picker under it.
|
|
208
241
|
const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.cloud || opts.local);
|
|
209
|
-
if (!quiet && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
|
|
242
|
+
if (!quiet && !json && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
|
|
210
243
|
await runInteractiveReconcile(opts, outLog, errLog);
|
|
211
244
|
return;
|
|
212
245
|
}
|
|
@@ -216,17 +249,36 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
|
|
|
216
249
|
cloud: opts.cloud,
|
|
217
250
|
local: opts.local,
|
|
218
251
|
};
|
|
219
|
-
|
|
220
|
-
|
|
252
|
+
// Same chokepoint as `agents secrets push/pull` — prefers AGENTS_SYNC_PASSPHRASE,
|
|
253
|
+
// falls back to the deprecated master-key name with a single warning.
|
|
254
|
+
const passphrase = resolveSyncPassphraseFromEnv().value ?? undefined;
|
|
255
|
+
// Fleet fan-out only injects --json (not --yes). Treat --json as non-interactive
|
|
256
|
+
// so refresh({ skipPrompts }) never tries to prompt over SSH.
|
|
257
|
+
const yes = !!opts.yes || json;
|
|
258
|
+
if (!quiet && !json)
|
|
221
259
|
outLog(chalk.bold('Syncing this machine…'));
|
|
222
260
|
try {
|
|
223
261
|
const result = await runUmbrellaSync({
|
|
224
262
|
flags,
|
|
225
|
-
yes
|
|
263
|
+
yes,
|
|
226
264
|
passphrase,
|
|
227
|
-
|
|
265
|
+
// quiet under --json so refresh() cannot pollute the JSON stdout the fleet parses.
|
|
266
|
+
quiet: quiet || json,
|
|
267
|
+
log: (msg) => { if (!quiet && !json)
|
|
228
268
|
outLog(chalk.gray(` ${msg}`)); },
|
|
229
269
|
});
|
|
270
|
+
if (json) {
|
|
271
|
+
emitJson({
|
|
272
|
+
ok: true,
|
|
273
|
+
mode: 'umbrella',
|
|
274
|
+
plan: result.plan,
|
|
275
|
+
repos: result.repos,
|
|
276
|
+
secrets: result.secrets,
|
|
277
|
+
devices: result.devices,
|
|
278
|
+
reconciled: result.reconciled,
|
|
279
|
+
});
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
230
282
|
if (!quiet) {
|
|
231
283
|
const parts = [];
|
|
232
284
|
if (result.repos) {
|
|
@@ -245,16 +297,30 @@ async function runUmbrella(opts, quiet, outLog, errLog) {
|
|
|
245
297
|
}
|
|
246
298
|
}
|
|
247
299
|
catch (err) {
|
|
248
|
-
|
|
300
|
+
if (json) {
|
|
301
|
+
emitJson({ ok: false, mode: 'umbrella', error: err.message });
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
errLog(chalk.red(`sync failed: ${err.message}`));
|
|
305
|
+
}
|
|
249
306
|
process.exitCode = 1;
|
|
250
307
|
}
|
|
251
308
|
}
|
|
252
309
|
async function runSync(agentSpec, repoArg, opts) {
|
|
253
|
-
const
|
|
310
|
+
const json = !!opts.json;
|
|
311
|
+
// --json is a machine consumer: suppress human stdout/stderr chatter so the
|
|
312
|
+
// single JSON object on stdout stays parseable for fleet fan-out.
|
|
313
|
+
const quiet = !!opts.quiet || json;
|
|
254
314
|
const errLog = (msg) => { if (!quiet)
|
|
255
315
|
console.error(msg); };
|
|
256
316
|
const outLog = (msg) => { if (!quiet)
|
|
257
317
|
console.log(msg); };
|
|
318
|
+
// Failures under --json still need a structured line on stdout so fleet
|
|
319
|
+
// fan-out's safeJsonParse gets a real object (not "unknown option").
|
|
320
|
+
const failJson = (payload) => {
|
|
321
|
+
if (json)
|
|
322
|
+
emitJson({ ok: false, ...payload });
|
|
323
|
+
};
|
|
258
324
|
// ---------- 1. Resolve agent + version ----------
|
|
259
325
|
let agentId;
|
|
260
326
|
let version;
|
|
@@ -271,14 +337,21 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
271
337
|
// below, and it precedes agent-spec parsing because repo names like
|
|
272
338
|
// "system"/"user" would otherwise fail parseAgentSpec.
|
|
273
339
|
if (agentSpec && !opts.agent && !repoArg && listRepoNames().includes(agentSpec)) {
|
|
274
|
-
await runRepoGitSync(agentSpec, quiet, outLog, errLog);
|
|
340
|
+
await runRepoGitSync(agentSpec, quiet, outLog, errLog, json);
|
|
275
341
|
return;
|
|
276
342
|
}
|
|
277
343
|
if (agentSpec) {
|
|
278
344
|
const parsed = parseAgentSpec(agentSpec);
|
|
279
345
|
if (!parsed) {
|
|
280
|
-
|
|
281
|
-
|
|
346
|
+
failJson({
|
|
347
|
+
mode: 'agent',
|
|
348
|
+
error: `Invalid agent spec '${agentSpec}'.`,
|
|
349
|
+
hint: 'Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned, claude@all',
|
|
350
|
+
});
|
|
351
|
+
if (!json) {
|
|
352
|
+
errLog(chalk.red(`Invalid agent spec '${agentSpec}'.`));
|
|
353
|
+
errLog(chalk.gray('Examples: claude, claude@2.1.142, claude@latest, claude@oldest, claude@pinned, claude@all'));
|
|
354
|
+
}
|
|
282
355
|
process.exitCode = 1;
|
|
283
356
|
return;
|
|
284
357
|
}
|
|
@@ -292,8 +365,11 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
292
365
|
if (repoScope !== undefined) {
|
|
293
366
|
const known = listRepoNames();
|
|
294
367
|
if (!known.includes(repoScope)) {
|
|
295
|
-
|
|
296
|
-
|
|
368
|
+
failJson({ mode: 'agent', error: `Unknown repo '${repoScope}'.`, known });
|
|
369
|
+
if (!json) {
|
|
370
|
+
errLog(chalk.red(`Unknown repo '${repoScope}'.`));
|
|
371
|
+
errLog(chalk.gray(`Known repos: ${known.join(', ')}`));
|
|
372
|
+
}
|
|
297
373
|
process.exitCode = 1;
|
|
298
374
|
return;
|
|
299
375
|
}
|
|
@@ -301,14 +377,18 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
301
377
|
if (opts.agent) {
|
|
302
378
|
const resolved = resolveAgentName(opts.agent);
|
|
303
379
|
if (!resolved) {
|
|
304
|
-
|
|
380
|
+
failJson({ mode: 'agent', error: `Unknown agent '${opts.agent}'.` });
|
|
381
|
+
if (!json)
|
|
382
|
+
errLog(chalk.red(`Unknown agent '${opts.agent}'.`));
|
|
305
383
|
process.exitCode = 1;
|
|
306
384
|
return;
|
|
307
385
|
}
|
|
308
386
|
agentId = resolved;
|
|
309
387
|
}
|
|
310
388
|
if (agentId && isAgentHardDeprecated(agentId)) {
|
|
311
|
-
|
|
389
|
+
failJson({ mode: 'agent', error: hardDeprecationError(agentId) });
|
|
390
|
+
if (!json)
|
|
391
|
+
errLog(chalk.red(hardDeprecationError(agentId)));
|
|
312
392
|
process.exitCode = 1;
|
|
313
393
|
return;
|
|
314
394
|
}
|
|
@@ -320,7 +400,8 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
320
400
|
if (!agentId) {
|
|
321
401
|
// No agent specified → the umbrella verb: make this machine current
|
|
322
402
|
// (fetch repos + secrets + sessions, then reconcile all installed agents).
|
|
323
|
-
|
|
403
|
+
// This is the path fleet fan-out (`--host all`) hits with injected --json.
|
|
404
|
+
await runUmbrella(opts, quiet, outLog, errLog, json);
|
|
324
405
|
return;
|
|
325
406
|
}
|
|
326
407
|
const projectDir = opts.projectDir;
|
|
@@ -332,8 +413,11 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
332
413
|
if (selector === 'all') {
|
|
333
414
|
const installed = listInstalledVersions(agentId);
|
|
334
415
|
if (installed.length === 0) {
|
|
335
|
-
|
|
336
|
-
|
|
416
|
+
failJson({ mode: 'agent-all', agent: agentId, error: `No ${agentLabel(agentId)} versions installed.` });
|
|
417
|
+
if (!json) {
|
|
418
|
+
errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
|
|
419
|
+
errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
|
|
420
|
+
}
|
|
337
421
|
process.exitCode = 1;
|
|
338
422
|
return;
|
|
339
423
|
}
|
|
@@ -341,17 +425,43 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
341
425
|
if (repoScope) {
|
|
342
426
|
selection = buildRepoScopedSelection(repoScope, cwd);
|
|
343
427
|
if (Object.keys(selection).length === 0) {
|
|
344
|
-
|
|
428
|
+
if (json)
|
|
429
|
+
emitJson({ ok: true, mode: 'agent-all', agent: agentId, repo: repoScope, versions: [], note: 'nothing to sync' });
|
|
430
|
+
else
|
|
431
|
+
outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync.`));
|
|
345
432
|
return;
|
|
346
433
|
}
|
|
347
434
|
}
|
|
348
435
|
const scopeLabel = repoScope ? chalk.gray(` (repo: ${repoScope})`) : '';
|
|
349
|
-
|
|
436
|
+
if (!json)
|
|
437
|
+
outLog(chalk.cyan(`Syncing ${installed.length} ${agentLabel(agentId)} version(s)${scopeLabel}.`));
|
|
438
|
+
const versions = [];
|
|
350
439
|
for (const v of installed) {
|
|
351
440
|
const result = syncResourcesToVersion(agentId, v, selection, { projectDir, cwd, force });
|
|
352
|
-
|
|
441
|
+
versions.push({ version: v, result });
|
|
442
|
+
if (!quiet && !json)
|
|
353
443
|
printSyncDetail(result, agentId, v, cwd);
|
|
354
444
|
}
|
|
445
|
+
if (json) {
|
|
446
|
+
emitJson({
|
|
447
|
+
ok: true,
|
|
448
|
+
mode: 'agent-all',
|
|
449
|
+
agent: agentId,
|
|
450
|
+
repo: repoScope,
|
|
451
|
+
versions: versions.map(({ version: v, result }) => ({
|
|
452
|
+
version: v,
|
|
453
|
+
commands: !!result.commands,
|
|
454
|
+
skills: !!result.skills,
|
|
455
|
+
hooks: !!result.hooks,
|
|
456
|
+
memory: result.memory,
|
|
457
|
+
mcp: result.mcp,
|
|
458
|
+
permissions: !!result.permissions,
|
|
459
|
+
subagents: result.subagents,
|
|
460
|
+
plugins: result.plugins,
|
|
461
|
+
workflows: result.workflows,
|
|
462
|
+
})),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
355
465
|
return;
|
|
356
466
|
}
|
|
357
467
|
// ---------- 2. Resolve version (project pin → global default → sole installed) ----------
|
|
@@ -370,15 +480,26 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
370
480
|
version = installed[0];
|
|
371
481
|
}
|
|
372
482
|
else if (installed.length === 0) {
|
|
373
|
-
|
|
374
|
-
|
|
483
|
+
failJson({ mode: 'agent', agent: agentId, error: `No ${agentLabel(agentId)} versions installed.` });
|
|
484
|
+
if (!json) {
|
|
485
|
+
errLog(chalk.red(`No ${agentLabel(agentId)} versions installed.`));
|
|
486
|
+
errLog(chalk.gray(`Install one: agents add ${agentId}@latest`));
|
|
487
|
+
}
|
|
375
488
|
process.exitCode = 1;
|
|
376
489
|
return;
|
|
377
490
|
}
|
|
378
491
|
else {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
492
|
+
failJson({
|
|
493
|
+
mode: 'agent',
|
|
494
|
+
agent: agentId,
|
|
495
|
+
error: `No default ${agentLabel(agentId)} version pinned.`,
|
|
496
|
+
installed,
|
|
497
|
+
});
|
|
498
|
+
if (!json) {
|
|
499
|
+
errLog(chalk.red(`No default ${agentLabel(agentId)} version pinned. Specify one:`));
|
|
500
|
+
for (const v of installed) {
|
|
501
|
+
errLog(chalk.gray(` agents sync ${agentId}@${v}`));
|
|
502
|
+
}
|
|
382
503
|
}
|
|
383
504
|
process.exitCode = 1;
|
|
384
505
|
return;
|
|
@@ -386,18 +507,27 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
386
507
|
}
|
|
387
508
|
}
|
|
388
509
|
if (!isVersionInstalled(agentId, version)) {
|
|
389
|
-
errLog(chalk.red(`${agentLabel(agentId)}@${version} is not installed.`));
|
|
390
510
|
const installed = listInstalledVersions(agentId);
|
|
391
|
-
|
|
392
|
-
|
|
511
|
+
failJson({
|
|
512
|
+
mode: 'agent',
|
|
513
|
+
agent: agentId,
|
|
514
|
+
version,
|
|
515
|
+
error: `${agentLabel(agentId)}@${version} is not installed.`,
|
|
516
|
+
installed,
|
|
517
|
+
});
|
|
518
|
+
if (!json) {
|
|
519
|
+
errLog(chalk.red(`${agentLabel(agentId)}@${version} is not installed.`));
|
|
520
|
+
if (installed.length > 0) {
|
|
521
|
+
errLog(chalk.gray(`Installed: ${installed.join(', ')}`));
|
|
522
|
+
}
|
|
523
|
+
errLog(chalk.gray(`Install it: agents add ${agentId}@${version}`));
|
|
393
524
|
}
|
|
394
|
-
errLog(chalk.gray(`Install it: agents add ${agentId}@${version}`));
|
|
395
525
|
process.exitCode = 1;
|
|
396
526
|
return;
|
|
397
527
|
}
|
|
398
528
|
// ---------- 3. --launch mode bypasses everything below ----------
|
|
399
529
|
if (opts.launch) {
|
|
400
|
-
runLaunchMode(agentId, version, cwd, quiet);
|
|
530
|
+
runLaunchMode(agentId, version, cwd, quiet, json);
|
|
401
531
|
return;
|
|
402
532
|
}
|
|
403
533
|
// ---------- 3b. Repo-scoped single-version sync ----------
|
|
@@ -406,16 +536,33 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
406
536
|
if (repoScope) {
|
|
407
537
|
const scoped = buildRepoScopedSelection(repoScope, cwd);
|
|
408
538
|
if (Object.keys(scoped).length === 0) {
|
|
409
|
-
|
|
539
|
+
if (json) {
|
|
540
|
+
emitJson({
|
|
541
|
+
ok: true,
|
|
542
|
+
mode: 'agent',
|
|
543
|
+
agent: agentId,
|
|
544
|
+
version,
|
|
545
|
+
repo: repoScope,
|
|
546
|
+
note: 'nothing to sync',
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
outLog(chalk.gray(`Nothing from repo '${repoScope}' to sync into ${agentLabel(agentId)}@${version}.`));
|
|
551
|
+
}
|
|
410
552
|
return;
|
|
411
553
|
}
|
|
412
554
|
const result = syncResourcesToVersion(agentId, version, scoped, { projectDir, cwd, force });
|
|
413
|
-
if (
|
|
555
|
+
if (json) {
|
|
556
|
+
emitJson(agentSyncJson(agentId, version, result, repoScope));
|
|
557
|
+
}
|
|
558
|
+
else if (!quiet) {
|
|
414
559
|
printSyncDetail(result, agentId, version, cwd);
|
|
560
|
+
}
|
|
415
561
|
return;
|
|
416
562
|
}
|
|
417
563
|
// ---------- 4. Decide selection (interactive preview vs auto) ----------
|
|
418
|
-
|
|
564
|
+
// --json forces non-interactive (machine consumer / fleet fan-out).
|
|
565
|
+
const yes = !!opts.yes || json;
|
|
419
566
|
const interactive = !quiet && !yes && isInteractiveTerminal();
|
|
420
567
|
let selection;
|
|
421
568
|
if (interactive) {
|
|
@@ -468,6 +615,20 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
468
615
|
const projectRoot = path.dirname(projectDir);
|
|
469
616
|
projectCompile = compileRulesForProject(projectRoot);
|
|
470
617
|
}
|
|
618
|
+
if (json) {
|
|
619
|
+
emitJson({
|
|
620
|
+
...agentSyncJson(agentId, version, result),
|
|
621
|
+
projectCompile: projectCompile
|
|
622
|
+
? {
|
|
623
|
+
compiled: !!projectCompile.compiled,
|
|
624
|
+
agentsPath: projectCompile.agentsPath,
|
|
625
|
+
symlinks: projectCompile.symlinks,
|
|
626
|
+
skippedClobber: projectCompile.skippedClobber,
|
|
627
|
+
}
|
|
628
|
+
: null,
|
|
629
|
+
});
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
471
632
|
if (quiet)
|
|
472
633
|
return;
|
|
473
634
|
// ---------- 6. Detailed output ----------
|
|
@@ -482,6 +643,26 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
482
643
|
console.log(chalk.yellow(`Skipped (user-authored, not overwritten): ${projectCompile.skippedClobber.join(', ')}`));
|
|
483
644
|
}
|
|
484
645
|
}
|
|
646
|
+
/** Stable `--json` payload for a single agent@version resource sync. */
|
|
647
|
+
function agentSyncJson(agent, version, result, repo) {
|
|
648
|
+
return {
|
|
649
|
+
ok: true,
|
|
650
|
+
mode: 'agent',
|
|
651
|
+
agent,
|
|
652
|
+
version,
|
|
653
|
+
...(repo !== undefined ? { repo } : {}),
|
|
654
|
+
commands: !!result.commands,
|
|
655
|
+
skills: !!result.skills,
|
|
656
|
+
hooks: !!result.hooks,
|
|
657
|
+
memory: result.memory,
|
|
658
|
+
mcp: result.mcp,
|
|
659
|
+
permissions: !!result.permissions,
|
|
660
|
+
subagents: result.subagents,
|
|
661
|
+
plugins: result.plugins,
|
|
662
|
+
workflows: result.workflows,
|
|
663
|
+
projectSkipped: result.projectSkipped,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
485
666
|
function anyResources(r) {
|
|
486
667
|
return r.commands.length + r.skills.length + r.hooks.length + r.memory.length +
|
|
487
668
|
r.mcp.length + r.permissions.length + r.subagents.length +
|
|
@@ -533,17 +714,40 @@ function printSyncDetail(result, agent, version, cwd) {
|
|
|
533
714
|
if (kept)
|
|
534
715
|
console.log(chalk.gray(kept));
|
|
535
716
|
}
|
|
536
|
-
function runLaunchMode(agent, version, cwd, quiet) {
|
|
717
|
+
function runLaunchMode(agent, version, cwd, quiet, json = false) {
|
|
537
718
|
let result;
|
|
538
719
|
try {
|
|
539
720
|
result = runLaunchSync({ agent, version, cwd });
|
|
540
721
|
}
|
|
541
722
|
catch (err) {
|
|
542
|
-
if (
|
|
723
|
+
if (json) {
|
|
724
|
+
emitJson({
|
|
725
|
+
ok: false,
|
|
726
|
+
mode: 'launch',
|
|
727
|
+
agent,
|
|
728
|
+
version,
|
|
729
|
+
error: err.message,
|
|
730
|
+
});
|
|
731
|
+
process.exitCode = 1;
|
|
732
|
+
}
|
|
733
|
+
else if (!quiet) {
|
|
543
734
|
console.error(chalk.yellow(`agents: launch sync skipped (${err.message})`));
|
|
544
735
|
}
|
|
545
736
|
return;
|
|
546
737
|
}
|
|
738
|
+
if (json) {
|
|
739
|
+
emitJson({
|
|
740
|
+
ok: true,
|
|
741
|
+
mode: 'launch',
|
|
742
|
+
agent,
|
|
743
|
+
version,
|
|
744
|
+
rulesCompiled: !!result.rulesCompiled,
|
|
745
|
+
workspaceLinks: result.workspaceLinks,
|
|
746
|
+
marketplaces: result.marketplaces,
|
|
747
|
+
workspaceSkipped: result.workspaceSkipped,
|
|
748
|
+
});
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
547
751
|
if (quiet)
|
|
548
752
|
return;
|
|
549
753
|
const bits = [];
|
package/dist/commands/view.js
CHANGED
|
@@ -578,6 +578,8 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
|
|
|
578
578
|
const runDefaultBits = [];
|
|
579
579
|
if (runDefaults.mode)
|
|
580
580
|
runDefaultBits.push(`mode:${runDefaults.mode}`);
|
|
581
|
+
if (runDefaults.effort)
|
|
582
|
+
runDefaultBits.push('effort:' + runDefaults.effort);
|
|
581
583
|
if (!hasEmail && !hasUsage && !signedIn) {
|
|
582
584
|
// No per-version credential. That is NOT the same as unusable: Claude
|
|
583
585
|
// Code authenticates from `CLAUDE_CODE_OAUTH_TOKEN` when the
|
|
@@ -24,6 +24,7 @@ import { parseDuration } from '../lib/hooks/cache.js';
|
|
|
24
24
|
import { getRuntimeStateDir } from '../lib/state.js';
|
|
25
25
|
import { readJob, setJobEnabled } from '../lib/routines.js';
|
|
26
26
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
27
|
+
import { loadLocalActiveSessions } from '../lib/session/session-cache.js';
|
|
27
28
|
import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
|
|
28
29
|
import { gcMailbox } from '../lib/mailbox-gc.js';
|
|
29
30
|
import { devicesWithRoutineEnabled, routineEnabledOnThisDevice } from '../lib/routine-activation.js';
|
|
@@ -151,9 +152,19 @@ export function registerWatchdogCommand(program) {
|
|
|
151
152
|
stateDir: stateDir(),
|
|
152
153
|
sessions,
|
|
153
154
|
});
|
|
155
|
+
// RUSH-2062: share the daemon-warmed local active-session snapshot with
|
|
156
|
+
// menubar/CLI/Factory instead of re-running a full gather every tick.
|
|
157
|
+
const loadWatchdogSessions = async () => {
|
|
158
|
+
const loaded = await loadLocalActiveSessions({
|
|
159
|
+
// Force a live gather only when the warm path is unavailable; the
|
|
160
|
+
// cache layer already re-gathers past DEFAULT_ACTIVE_CACHE_MAX_AGE_MS.
|
|
161
|
+
gather: () => getActiveSessions({ localOnly: true }),
|
|
162
|
+
});
|
|
163
|
+
return loaded.sessions;
|
|
164
|
+
};
|
|
154
165
|
if (!opts.watch) {
|
|
155
166
|
const willInject = computeWillInject();
|
|
156
|
-
const sessions = await
|
|
167
|
+
const sessions = await loadWatchdogSessions();
|
|
157
168
|
const result = await tickOnce(willInject, sessions);
|
|
158
169
|
await runMailboxGc(sessions);
|
|
159
170
|
if (opts.json)
|
|
@@ -172,7 +183,7 @@ export function registerWatchdogCommand(program) {
|
|
|
172
183
|
while (true) {
|
|
173
184
|
// Re-evaluated each tick: picks up enable/disable flips mid-run.
|
|
174
185
|
const willInject = computeWillInject();
|
|
175
|
-
const sessions = await
|
|
186
|
+
const sessions = await loadWatchdogSessions();
|
|
176
187
|
const result = await tickOnce(willInject, sessions);
|
|
177
188
|
await runMailboxGc(sessions);
|
|
178
189
|
if (opts.json)
|
package/dist/index.js
CHANGED
|
@@ -94,7 +94,7 @@ if (IS_DEV_BUILD) {
|
|
|
94
94
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
95
95
|
// first byte of output), the registry maps a command name to a thunk that
|
|
96
96
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
97
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
97
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadExport, loadPackages, loadRoutines, loadMonitors, loadProjects, loadRun, loadFork, loadDefaults, loadSet, loadModels, loadModes, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadStatus, loadSnapshot, loadProfiles, loadHarness, loadSecrets, loadLogin, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadFactory, loadUsage, loadCost, loadInsights, loadPerf, loadTrends, loadOutput, loadBudget, loadAlias, loadMine, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadHumans, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadUninstall, loadShare, loadSend, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
98
98
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
99
99
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
100
100
|
import { getCliLaunch } from './lib/cli-entry.js';
|
|
@@ -941,6 +941,7 @@ async function registerAllEagerCommands() {
|
|
|
941
941
|
await reg(loadDefaults);
|
|
942
942
|
await reg(loadSet);
|
|
943
943
|
await reg(loadModels);
|
|
944
|
+
await reg(loadModes);
|
|
944
945
|
await reg(loadPrune);
|
|
945
946
|
await reg(loadTrash);
|
|
946
947
|
await reg(loadRestore);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission-mode catalog for a harness.
|
|
3
|
+
*
|
|
4
|
+
* Source of truth is AGENTS[id].capabilities.modes plus the native CLI flags
|
|
5
|
+
* in AGENT_COMMANDS.modeFlags. This is the modes analog of the model catalog
|
|
6
|
+
* (`agents models`) — what a human or orchestrating agent reads before
|
|
7
|
+
* `agents run <agent> --mode …` / `agents teams add … --mode …`.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentId, Mode } from './types.js';
|
|
10
|
+
/** Human description for each canonical permission mode. */
|
|
11
|
+
export declare const MODE_DESCRIPTIONS: Record<Mode, string>;
|
|
12
|
+
export interface AgentModeEntry {
|
|
13
|
+
mode: Mode;
|
|
14
|
+
/** Native CLI flags agents-cli forwards for this mode (empty = harness default). */
|
|
15
|
+
flags: string[];
|
|
16
|
+
description: string;
|
|
17
|
+
/** First entry in capabilities.modes — the safest native mode. */
|
|
18
|
+
isDefault: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface AgentModesCatalog {
|
|
21
|
+
agent: AgentId;
|
|
22
|
+
/** Modes this harness natively supports, in declaration order. */
|
|
23
|
+
modes: AgentModeEntry[];
|
|
24
|
+
/** capabilities.modes[0] — safest native mode; run may still default elsewhere (e.g. CLI plan, teams edit). */
|
|
25
|
+
defaultMode: Mode;
|
|
26
|
+
/**
|
|
27
|
+
* Configured run.defaults mode for this agent@version, when set.
|
|
28
|
+
* Null means no pin — CLI default (plan for most harnesses) or modes[0] applies.
|
|
29
|
+
*/
|
|
30
|
+
configuredMode: Mode | null;
|
|
31
|
+
configuredModeSource: string | null;
|
|
32
|
+
/**
|
|
33
|
+
* Whether plan works in a headless (`-p` / prompt) run. false means a headless
|
|
34
|
+
* --mode plan degrades (see resolveHeadlessMode). undefined = assumed true.
|
|
35
|
+
*/
|
|
36
|
+
headlessPlan: boolean;
|
|
37
|
+
/** Canonical modes this harness does NOT list in capabilities.modes. */
|
|
38
|
+
unsupported: Mode[];
|
|
39
|
+
/** Notes an orchestrator should read before picking a mode. */
|
|
40
|
+
notes: string[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build the permission-mode catalog for one harness.
|
|
44
|
+
* `version` only affects the configured run.defaults lookup (modes themselves
|
|
45
|
+
* are per-agent today, not version-gated).
|
|
46
|
+
*/
|
|
47
|
+
export declare function getAgentModesCatalog(agent: AgentId, version?: string | null, cwd?: string): AgentModesCatalog;
|
|
48
|
+
/** Format native flags for display, e.g. `--permission-mode plan` or `(harness default)`. */
|
|
49
|
+
export declare function formatModeFlags(flags: string[]): string;
|