@phnx-labs/agents-cli 1.22.23 → 1.22.25
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 +359 -0
- package/README.md +19 -8
- package/dist/bin/agents +0 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +32 -7
- package/dist/commands/cloud.js +9 -5
- package/dist/commands/defaults.js +2 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +105 -9
- package/dist/commands/exec.js +99 -44
- package/dist/commands/feed.d.ts +23 -0
- package/dist/commands/feed.js +65 -2
- package/dist/commands/focus.d.ts +34 -3
- package/dist/commands/focus.js +301 -57
- package/dist/commands/go.d.ts +14 -1
- package/dist/commands/go.js +49 -5
- package/dist/commands/insights.js +6 -2
- package/dist/commands/inspect.js +39 -5
- package/dist/commands/menubar.js +14 -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/routines.d.ts +3 -0
- package/dist/commands/routines.js +70 -47
- package/dist/commands/run-cloud.js +1 -1
- package/dist/commands/secrets-sync.js +11 -13
- package/dist/commands/secrets.d.ts +2 -0
- package/dist/commands/secrets.js +54 -17
- package/dist/commands/sessions-browser.d.ts +36 -1
- package/dist/commands/sessions-browser.js +165 -19
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +28 -12
- package/dist/commands/sessions.d.ts +23 -1
- package/dist/commands/sessions.js +58 -61
- package/dist/commands/setup-secrets.js +1 -1
- package/dist/commands/setup-watchdog.js +5 -10
- package/dist/commands/setup.js +1 -1
- package/dist/commands/sync.js +246 -42
- package/dist/commands/teams.js +3 -2
- package/dist/commands/view.js +2 -0
- package/dist/commands/watchdog.d.ts +3 -4
- package/dist/commands/watchdog.js +26 -66
- package/dist/index.js +38 -2
- package/dist/lib/agent-modes.d.ts +49 -0
- package/dist/lib/agent-modes.js +70 -0
- package/dist/lib/agents.js +126 -21
- package/dist/lib/cloud/cursor.d.ts +79 -0
- package/dist/lib/cloud/cursor.js +228 -0
- package/dist/lib/cloud/registry.js +2 -0
- package/dist/lib/cloud/types.d.ts +7 -2
- package/dist/lib/cloud/types.js +14 -0
- package/dist/lib/crabbox/cli.d.ts +2 -2
- package/dist/lib/crabbox/config.d.ts +7 -8
- package/dist/lib/crabbox/config.js +14 -14
- package/dist/lib/crabbox/lease.d.ts +11 -4
- package/dist/lib/crabbox/lease.js +40 -8
- package/dist/lib/crabbox/setup-copy.d.ts +5 -0
- package/dist/lib/crabbox/setup-copy.js +17 -1
- package/dist/lib/daemon.js +27 -1
- package/dist/lib/device-config.js +7 -0
- package/dist/lib/devices/doctor-findings.d.ts +7 -1
- package/dist/lib/devices/doctor-findings.js +40 -1
- 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/events.d.ts +9 -0
- package/dist/lib/events.js +58 -0
- package/dist/lib/exec.d.ts +15 -3
- package/dist/lib/exec.js +112 -19
- package/dist/lib/feed-outcome.d.ts +3 -0
- package/dist/lib/feed-outcome.js +18 -10
- package/dist/lib/feed.d.ts +4 -0
- package/dist/lib/hooks/cache.js +36 -3
- package/dist/lib/hooks.d.ts +19 -7
- package/dist/lib/hooks.js +88 -38
- package/dist/lib/hosts/passthrough.d.ts +21 -0
- package/dist/lib/hosts/passthrough.js +39 -12
- 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 +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
- package/dist/lib/menubar/install-menubar.d.ts +52 -2
- package/dist/lib/menubar/install-menubar.js +128 -6
- package/dist/lib/menubar/snapshot.d.ts +15 -0
- package/dist/lib/menubar/snapshot.js +40 -0
- package/dist/lib/plugins.js +13 -1
- package/dist/lib/refresh.d.ts +5 -0
- package/dist/lib/refresh.js +37 -33
- package/dist/lib/resource-inventory.d.ts +79 -0
- package/dist/lib/resource-inventory.js +122 -0
- package/dist/lib/resources/mcp.js +3 -0
- package/dist/lib/resources.js +8 -5
- package/dist/lib/routine-process-cleanup.d.ts +9 -0
- package/dist/lib/routine-process-cleanup.js +73 -0
- package/dist/lib/run-defaults.d.ts +2 -0
- package/dist/lib/run-defaults.js +23 -2
- package/dist/lib/runner.js +27 -22
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
- package/dist/lib/secrets/bundles.js +158 -85
- package/dist/lib/secrets/filestore.d.ts +7 -3
- package/dist/lib/secrets/filestore.js +25 -8
- package/dist/lib/secrets/index.js +34 -8
- package/dist/lib/secrets/rc-hygiene.d.ts +14 -0
- package/dist/lib/secrets/rc-hygiene.js +14 -1
- package/dist/lib/secrets/reaper.d.ts +10 -0
- package/dist/lib/secrets/reaper.js +50 -15
- 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 +4 -0
- package/dist/lib/session/db.js +8 -2
- 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 +2 -0
- package/dist/lib/session/remote-list.js +1 -0
- package/dist/lib/session/session-cache.d.ts +4 -4
- package/dist/lib/session/session-cache.js +4 -4
- package/dist/lib/shims.d.ts +2 -2
- package/dist/lib/shims.js +45 -6
- package/dist/lib/signin-badge.js +2 -0
- package/dist/lib/startup/command-registry.d.ts +16 -0
- package/dist/lib/startup/command-registry.js +44 -0
- package/dist/lib/sync-umbrella.d.ts +5 -0
- package/dist/lib/sync-umbrella.js +5 -4
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- 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/versions.js +16 -1
- package/dist/lib/watchdog/service.d.ts +17 -0
- package/dist/lib/watchdog/service.js +39 -0
- package/package.json +1 -1
package/dist/commands/go.js
CHANGED
|
@@ -28,8 +28,9 @@ import { isPromptCancelled } from './utils.js';
|
|
|
28
28
|
import { focusAction } from './focus.js';
|
|
29
29
|
import { machineId } from '../lib/session/sync/config.js';
|
|
30
30
|
import { attachTmux, runTmux } from '../lib/tmux/binary.js';
|
|
31
|
+
import { paneExitStatus } from '../lib/tmux/session.js';
|
|
31
32
|
import { getDefaultSocketPath } from '../lib/tmux/paths.js';
|
|
32
|
-
import { sshStream, assertValidSshTarget, shellQuote } from '../lib/ssh-exec.js';
|
|
33
|
+
import { sshExec, sshStream, assertValidSshTarget, shellQuote } from '../lib/ssh-exec.js';
|
|
33
34
|
import { enumerateGhosttyTabs, assignGhosttyTabs } from '../lib/session/ghostty-tabs.js';
|
|
34
35
|
const execFileAsync = promisify(execFile);
|
|
35
36
|
export function registerGoCommand(program) {
|
|
@@ -208,15 +209,48 @@ export function describeWhere(s, self) {
|
|
|
208
209
|
return { label: `${s.host ?? 'shell'} on ${remote}`, action: `open a shell on ${remote}` };
|
|
209
210
|
return { label: s.host ?? 'unknown terminal', action: 'resume it (no live attach rail)' };
|
|
210
211
|
}
|
|
211
|
-
/**
|
|
212
|
+
/** Probe the tmux process, not just retained provenance. This is deliberately
|
|
213
|
+
* called immediately before an attach so remain-on-exit panes cannot masquerade
|
|
214
|
+
* as living agent sessions. */
|
|
215
|
+
export async function probeAttachRail(s, self) {
|
|
216
|
+
const mux = s.provenance?.mux;
|
|
217
|
+
if (mux?.kind !== 'tmux' || !mux.pane)
|
|
218
|
+
return { state: 'missing' };
|
|
219
|
+
const remote = s.machine && s.machine !== self ? s.machine : undefined;
|
|
220
|
+
if (!remote) {
|
|
221
|
+
const pane = await paneExitStatus(mux.pane, mux.socket ?? getDefaultSocketPath());
|
|
222
|
+
if (!pane.found)
|
|
223
|
+
return { state: 'missing' };
|
|
224
|
+
return pane.dead ? { state: 'dead', exitStatus: pane.status } : { state: 'alive' };
|
|
225
|
+
}
|
|
226
|
+
assertValidSshTarget(remote);
|
|
227
|
+
const sock = mux.socket ? `-S ${shellQuote(mux.socket)} ` : '';
|
|
228
|
+
const pane = shellQuote(mux.pane);
|
|
229
|
+
const command = `v=$(tmux ${sock}display-message -pt ${pane} -p '#{pane_dead} #{pane_dead_status}' 2>/dev/null) || { echo missing; exit 0; }; ` +
|
|
230
|
+
`printf '%s\\n' "$v"`;
|
|
231
|
+
const result = sshExec(remote, command, { timeoutMs: 15_000, multiplex: true });
|
|
232
|
+
if (result.code !== 0)
|
|
233
|
+
return { state: 'missing' };
|
|
234
|
+
const value = result.stdout.trim();
|
|
235
|
+
if (value === 'missing' || !value)
|
|
236
|
+
return { state: 'missing' };
|
|
237
|
+
const [dead, rawStatus] = value.split(/\s+/);
|
|
238
|
+
const exitStatus = Number.parseInt(rawStatus ?? '', 10);
|
|
239
|
+
return dead === '1'
|
|
240
|
+
? { state: 'dead', exitStatus: Number.isFinite(exitStatus) ? exitStatus : undefined }
|
|
241
|
+
: { state: 'alive' };
|
|
242
|
+
}
|
|
243
|
+
/** Strict attach-only fallback: no pane means no attach. Never open a shell or
|
|
244
|
+
* start recovery, because both would violate the caller's no-fork intent. */
|
|
212
245
|
export async function refuseFallback(s, remote) {
|
|
213
246
|
if (remote) {
|
|
214
|
-
console.log(chalk.yellow(
|
|
215
|
-
|
|
216
|
-
|
|
247
|
+
console.log(chalk.yellow(`Can't attach ${shortId(s)} on ${remote} — it has no living tmux pane.`));
|
|
248
|
+
process.exitCode = 1;
|
|
249
|
+
return;
|
|
217
250
|
}
|
|
218
251
|
console.log(chalk.yellow(`Can't jump to ${shortId(s)} — it's in ${s.host ?? 'an unknown terminal'} with no attach rail (not tmux/Ghostty).`) +
|
|
219
252
|
chalk.gray(`\nTry: agents sessions resume ${shortId(s)}`));
|
|
253
|
+
process.exitCode = 1;
|
|
220
254
|
}
|
|
221
255
|
export async function jumpTo(s, self, fallback = refuseFallback) {
|
|
222
256
|
const remote = s.machine && s.machine !== self ? s.machine : undefined;
|
|
@@ -224,6 +258,11 @@ export async function jumpTo(s, self, fallback = refuseFallback) {
|
|
|
224
258
|
// Path C: remote tmux — ssh in and attach, resolving the pane's session on the remote.
|
|
225
259
|
if (remote) {
|
|
226
260
|
if (mux?.kind === 'tmux' && mux.pane) {
|
|
261
|
+
const liveness = await probeAttachRail(s, self);
|
|
262
|
+
if (liveness.state !== 'alive') {
|
|
263
|
+
await fallback(s, remote);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
227
266
|
assertValidSshTarget(remote);
|
|
228
267
|
const sock = mux.socket ? `-S ${shellQuote(mux.socket)} ` : '';
|
|
229
268
|
const p = shellQuote(mux.pane);
|
|
@@ -240,6 +279,11 @@ export async function jumpTo(s, self, fallback = refuseFallback) {
|
|
|
240
279
|
}
|
|
241
280
|
// Path B: local tmux — attach (or switch-client if we're already inside tmux).
|
|
242
281
|
if (mux?.kind === 'tmux' && mux.pane) {
|
|
282
|
+
const liveness = await probeAttachRail(s, self);
|
|
283
|
+
if (liveness.state !== 'alive') {
|
|
284
|
+
await fallback(s, undefined);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
243
287
|
const socket = mux.socket ?? getDefaultSocketPath();
|
|
244
288
|
const { session, window } = await resolveLocalPane(socket, mux.pane);
|
|
245
289
|
if (session && window != null) {
|
|
@@ -340,7 +340,10 @@ async function insightsAction(options) {
|
|
|
340
340
|
console.error(chalk.red('error: --min-messages must be a non-negative integer'));
|
|
341
341
|
process.exit(1);
|
|
342
342
|
}
|
|
343
|
-
|
|
343
|
+
// `--all` is the spelling people reach for; `--since all` is what the window parser
|
|
344
|
+
// speaks. Accept both rather than failing on an unknown option, and let an explicit
|
|
345
|
+
// --since win so `--all --since 7d` is not silently contradictory.
|
|
346
|
+
const since = options.since ?? (options.all ? 'all' : '30d');
|
|
344
347
|
const sinceMs = since === 'all' ? undefined : parseTimeFilter(since);
|
|
345
348
|
// Refresh the index first, exactly as `agents cost` does, so a report never silently
|
|
346
349
|
// describes a stale picture of disk.
|
|
@@ -436,6 +439,7 @@ export function registerInsightsCommand(program) {
|
|
|
436
439
|
.description('How you work — tools, friction, and rhythm, split by the account that did the work')
|
|
437
440
|
.option('--json', 'Output the full report as JSON')
|
|
438
441
|
.option('--since <time>', 'Window: 7d, 4w, 3mo, an ISO date, or "all" (default 30d)')
|
|
442
|
+
.option('--all', 'Every session ever indexed. Alias for --since all')
|
|
439
443
|
.option('--by <dimension>', 'Group by: account (default), agent, project, or day')
|
|
440
444
|
.option('--account <match>', 'Only sessions whose account key, email, or org contains this')
|
|
441
445
|
.option('--agent <id>', 'Only one harness (claude, codex, droid, …)')
|
|
@@ -454,7 +458,7 @@ export function registerInsightsCommand(program) {
|
|
|
454
458
|
agents insights --by project --since 90d
|
|
455
459
|
|
|
456
460
|
# One account only, all of its history
|
|
457
|
-
agents insights --account "Turing Labs" --
|
|
461
|
+
agents insights --account "Turing Labs" --all
|
|
458
462
|
|
|
459
463
|
# Machine-readable, for a dashboard or a slash command
|
|
460
464
|
agents insights --json
|
package/dist/commands/inspect.js
CHANGED
|
@@ -22,12 +22,14 @@ import * as yaml from 'yaml';
|
|
|
22
22
|
import { AGENTS, getCliState, resolveAgentName } from '../lib/agents.js';
|
|
23
23
|
import { supports } from '../lib/capabilities.js';
|
|
24
24
|
import { resolveConfiguredModel } from '../lib/models.js';
|
|
25
|
+
import { getAgentModesCatalog } from '../lib/agent-modes.js';
|
|
25
26
|
import { resolveSingleAgentTarget, AgentSpecError } from '../lib/agent-spec/index.js';
|
|
26
27
|
import { readMeta, getUserAgentsDir, getSystemAgentsDir, getProjectAgentsDir, getEnabledExtraRepos, } from '../lib/state.js';
|
|
27
28
|
import { getVersionHomePath, isVersionIsolated, getIsolatedDefault, } from '../lib/versions.js';
|
|
28
29
|
import { getShimsDir, getVersionedAliasPath } from '../lib/shims.js';
|
|
29
30
|
import { getAgentResources, listResources, } from '../lib/resources.js';
|
|
30
31
|
import { listHookEntriesFromDir } from '../lib/hooks.js';
|
|
32
|
+
import { getResourceInventory } from '../lib/resource-inventory.js';
|
|
31
33
|
import { listMcpServerConfigs, discoverMcpConfigsFromRepo } from '../lib/mcp.js';
|
|
32
34
|
import { discoverPlugins, discoverPluginsInDir, pluginResourceGroups } from '../lib/plugins.js';
|
|
33
35
|
import { PLUGIN_GROUP_COLORS } from './plugins.js';
|
|
@@ -113,7 +115,7 @@ function truncateValueForPrefix(prefix, value) {
|
|
|
113
115
|
// ─── Command registration ────────────────────────────────────────────────────
|
|
114
116
|
export function registerInspectCommand(program) {
|
|
115
117
|
const cmd = addHostOption(program.command('inspect <target>'))
|
|
116
|
-
.description('Inspect one installed agent at one version, or a DotAgents repo
|
|
118
|
+
.description('Inspect one installed agent harness at one version (not a model), or a DotAgents repo — paths, capabilities, resources, and hook capable/on-disk/wired state.')
|
|
117
119
|
.option('--brief', 'header + capabilities only; skip resources/sessions')
|
|
118
120
|
.option('--json', 'machine-readable JSON output');
|
|
119
121
|
for (const kind of DRILLABLE_KINDS) {
|
|
@@ -575,12 +577,14 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
575
577
|
const isIsolatedDefault = isolated && getIsolatedDefault(agent) === version;
|
|
576
578
|
const capabilities = collectCapabilities(agent, version);
|
|
577
579
|
const itemsByKind = options.brief ? null : collectItemsByKind(agent, versionHome);
|
|
580
|
+
const hookInventory = options.brief ? null : getResourceInventory(agent, version, 'hooks');
|
|
578
581
|
const hookByScript = options.brief ? null : hookManifestByScript(loadCentralHookManifest());
|
|
579
582
|
const mcpConfigs = options.brief ? null : new Map(listMcpServerConfigs().map(s => [s.name, s.config]));
|
|
580
583
|
const sessions = options.brief ? null : {
|
|
581
584
|
total: safeCountSessions(agent),
|
|
582
585
|
};
|
|
583
586
|
if (options.json) {
|
|
587
|
+
const modeCat = getAgentModesCatalog(agent, version);
|
|
584
588
|
const json = {
|
|
585
589
|
agent,
|
|
586
590
|
version,
|
|
@@ -594,7 +598,14 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
594
598
|
strategy,
|
|
595
599
|
installedShim: cliState?.installed === true ? cliState.path : null,
|
|
596
600
|
capabilities,
|
|
597
|
-
|
|
601
|
+
modes: {
|
|
602
|
+
supported: modeCat.modes.map((m) => m.mode),
|
|
603
|
+
defaultMode: modeCat.defaultMode,
|
|
604
|
+
configuredMode: modeCat.configuredMode,
|
|
605
|
+
headlessPlan: modeCat.headlessPlan,
|
|
606
|
+
unsupported: modeCat.unsupported,
|
|
607
|
+
},
|
|
608
|
+
resources: itemsByKind ? summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs, hookInventory) : null,
|
|
598
609
|
sessions,
|
|
599
610
|
};
|
|
600
611
|
console.log(JSON.stringify(json, null, 2));
|
|
@@ -623,12 +634,23 @@ async function renderSummary(agent, version, versionHome, options) {
|
|
|
623
634
|
const reason = res.ok ? '' : chalk.gray(`(${res.reason}${res.need ? ' ' + res.need : ''})`);
|
|
624
635
|
console.log(` ${cap.padEnd(10)} ${mark} ${reason}`);
|
|
625
636
|
}
|
|
637
|
+
// Permission modes are a separate axis from the capability booleans above —
|
|
638
|
+
// same data as `agents modes <agent>`, kept next to the rest of the inspect
|
|
639
|
+
// surface so a human/agent does not have to leave this view.
|
|
640
|
+
{
|
|
641
|
+
const modeCat = getAgentModesCatalog(agent, version);
|
|
642
|
+
const modeList = modeCat.modes.map((m) => (m.isDefault ? `${m.mode}*` : m.mode)).join('/');
|
|
643
|
+
const cfg = modeCat.configuredMode ? chalk.gray(` run default: ${modeCat.configuredMode}`) : '';
|
|
644
|
+
console.log(` ${'modes'.padEnd(10)} ${chalk.cyan(modeList)}${cfg}`);
|
|
645
|
+
console.log(chalk.gray(` agents modes ${agent} · agents models ${agent}@${version}`));
|
|
646
|
+
}
|
|
626
647
|
if (itemsByKind) {
|
|
627
648
|
console.log('\n' + chalk.bold('Resources'));
|
|
628
649
|
for (const kind of SIMPLE_KINDS) {
|
|
629
650
|
printSimpleResourceRow(kind, itemsByKind[kind]);
|
|
630
651
|
}
|
|
631
|
-
|
|
652
|
+
console.log(` ${chalk.bold('Hooks')} ${chalk.gray(formatHookInventoryCounts(hookInventory))}`);
|
|
653
|
+
printExpandedSection('Hook files', hookRows(itemsByKind.hooks, hookByScript));
|
|
632
654
|
printExpandedSection('Plugins', pluginRows(itemsByKind.plugins));
|
|
633
655
|
printExpandedSection('MCP', mcpRows(itemsByKind.mcp, mcpConfigs));
|
|
634
656
|
}
|
|
@@ -769,13 +791,20 @@ function printSimpleResourceRow(kind, items) {
|
|
|
769
791
|
* simple kinds add `names`, and the rich kinds add structured `items` (hook
|
|
770
792
|
* events/predicates, mcp transport/url/command, plugin version + group counts).
|
|
771
793
|
*/
|
|
772
|
-
function summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs) {
|
|
794
|
+
function summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs, hookInventory) {
|
|
773
795
|
const out = {};
|
|
774
796
|
for (const kind of DRILLABLE_KINDS) {
|
|
775
797
|
const items = itemsByKind[kind];
|
|
776
798
|
const base = { total: items.length, bySource: countBySource(items.map(i => i.source)) };
|
|
777
799
|
if (kind === 'hooks') {
|
|
778
|
-
out[kind] = { ...base,
|
|
800
|
+
out[kind] = { ...base,
|
|
801
|
+
capable: hookInventory.capable,
|
|
802
|
+
declared: hookInventory.declared,
|
|
803
|
+
onDisk: hookInventory.onDisk,
|
|
804
|
+
wired: hookInventory.wired,
|
|
805
|
+
unmanaged: hookInventory.unmanaged,
|
|
806
|
+
wiringSupported: hookInventory.wiringSupported,
|
|
807
|
+
items: items.map(i => {
|
|
779
808
|
const h = hookByScript.get(i.name);
|
|
780
809
|
return { name: i.name, source: i.source, events: h?.events ?? [], matcher: h?.matcher, matches: h?.matches, cache: h?.cache };
|
|
781
810
|
}) };
|
|
@@ -800,6 +829,11 @@ function summaryResourcesJson(itemsByKind, hookByScript, mcpConfigs) {
|
|
|
800
829
|
}
|
|
801
830
|
return out;
|
|
802
831
|
}
|
|
832
|
+
function formatHookInventoryCounts(inventory) {
|
|
833
|
+
const wired = inventory.wiringSupported ? String(inventory.wired.length) : 'unknown';
|
|
834
|
+
const unmanaged = inventory.unmanaged.length > 0 ? ` · unmanaged ${inventory.unmanaged.length}` : '';
|
|
835
|
+
return `capable ${inventory.capable ? 'yes' : 'no'} · on-disk ${inventory.onDisk.length} · wired ${wired}${unmanaged}`;
|
|
836
|
+
}
|
|
803
837
|
function collectKind(agent, versionHome, kind) {
|
|
804
838
|
switch (kind) {
|
|
805
839
|
case 'commands':
|
package/dist/commands/menubar.js
CHANGED
|
@@ -51,7 +51,12 @@ function printStatus(s, opts = {}) {
|
|
|
51
51
|
console.log(chalk.gray(' End them with `agents menubar setup`.'));
|
|
52
52
|
}
|
|
53
53
|
if (s.stale) {
|
|
54
|
-
|
|
54
|
+
// Not "runs on next startup": the self-heal only reinstalls from the install
|
|
55
|
+
// that owns the helper, or from another one once the takeover cooldown has
|
|
56
|
+
// passed (mayInstallMenubarHelper) — so on a box with several agents-cli
|
|
57
|
+
// copies this can persist for a while. `setup` bypasses the gate and is the
|
|
58
|
+
// immediate fix.
|
|
59
|
+
console.log(chalk.yellow('\n Installed AGI Menu is stale — `agents menubar setup` updates it now.'));
|
|
55
60
|
}
|
|
56
61
|
else if (!s.serviceInstalled && !s.disabledByUser) {
|
|
57
62
|
console.log(chalk.gray('\n Set it up with `agents menubar setup`.'));
|
|
@@ -132,6 +137,14 @@ export function registerMenubarCommands(program) {
|
|
|
132
137
|
again, run \`agents menubar disable\`.
|
|
133
138
|
`,
|
|
134
139
|
});
|
|
140
|
+
menubar
|
|
141
|
+
.command('snapshot', { hidden: true })
|
|
142
|
+
.description('Emit the consolidated AGI Menu polling snapshot.')
|
|
143
|
+
.option('--json', 'Emit machine-readable JSON')
|
|
144
|
+
.action(async () => {
|
|
145
|
+
const { computeMenubarSnapshot } = await import('../lib/menubar/snapshot.js');
|
|
146
|
+
process.stdout.write(`${JSON.stringify(await computeMenubarSnapshot())}\n`);
|
|
147
|
+
});
|
|
135
148
|
menubar
|
|
136
149
|
.command('enable')
|
|
137
150
|
.description('Install and start AGI Menu (launches at login)')
|
package/dist/commands/models.js
CHANGED
|
@@ -72,6 +72,7 @@ export function registerModelsCommand(program) {
|
|
|
72
72
|
if (!agentSpec && !all) {
|
|
73
73
|
console.log(chalk.gray('\n `agents models <agent>` for one harness · `--all` for the full model list'));
|
|
74
74
|
console.log(chalk.gray(' `agents models tier set <agent> <tier> <model>` to override a tier'));
|
|
75
|
+
console.log(chalk.gray(' Permission modes (--mode plan|edit|auto|skip): `agents modes <agent>`'));
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
// Override subcommands. These WRITE agents.yaml so the user never hand-edits it;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents modes` — list the permission modes a harness accepts for
|
|
3
|
+
* `agents run` / `agents teams add`.
|
|
4
|
+
*
|
|
5
|
+
* Mirror of `agents models`: humans and orchestrating agents read this before
|
|
6
|
+
* picking `--mode plan|edit|auto|skip`. Modes are per-agent today (not version-
|
|
7
|
+
* gated); `agent@version` is accepted so the configured run.defaults mode for
|
|
8
|
+
* that version can be shown beside the catalog.
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from 'commander';
|
|
11
|
+
/** Register `agents modes [agent[@version]]`. */
|
|
12
|
+
export declare function registerModesCommand(program: Command): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents modes` — list the permission modes a harness accepts for
|
|
3
|
+
* `agents run` / `agents teams add`.
|
|
4
|
+
*
|
|
5
|
+
* Mirror of `agents models`: humans and orchestrating agents read this before
|
|
6
|
+
* picking `--mode plan|edit|auto|skip`. Modes are per-agent today (not version-
|
|
7
|
+
* gated); `agent@version` is accepted so the configured run.defaults mode for
|
|
8
|
+
* that version can be shown beside the catalog.
|
|
9
|
+
*/
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { AGENTS, ALL_AGENT_IDS, agentLabel, formatAgentError, resolveAgentName, } from '../lib/agents.js';
|
|
12
|
+
import { formatModeFlags, getAgentModesCatalog, } from '../lib/agent-modes.js';
|
|
13
|
+
import { setHelpSections } from '../lib/help.js';
|
|
14
|
+
import { getGlobalDefault, listInstalledVersions, resolveVersion, resolveVersionAlias } from '../lib/versions.js';
|
|
15
|
+
/** Agents that show in the no-arg overview (skip hard-deprecated). */
|
|
16
|
+
const MODE_AGENTS = ALL_AGENT_IDS.filter((id) => !AGENTS[id].deprecated?.hard);
|
|
17
|
+
/** Register `agents modes [agent[@version]]`. */
|
|
18
|
+
export function registerModesCommand(program) {
|
|
19
|
+
const modes = program
|
|
20
|
+
.command('modes [agentSpec]')
|
|
21
|
+
.description('Show which permission modes (--mode plan|edit|auto|skip) a harness supports for agents run / teams add')
|
|
22
|
+
.option('--json', 'Output machine-readable JSON')
|
|
23
|
+
.action((agentSpec, options) => {
|
|
24
|
+
const targets = resolveTargets(agentSpec);
|
|
25
|
+
if (targets.length === 0)
|
|
26
|
+
process.exit(1);
|
|
27
|
+
if (options.json) {
|
|
28
|
+
const out = targets.map(({ agent, version }) => {
|
|
29
|
+
const catalog = getAgentModesCatalog(agent, version);
|
|
30
|
+
return {
|
|
31
|
+
agent,
|
|
32
|
+
version,
|
|
33
|
+
defaultMode: catalog.defaultMode,
|
|
34
|
+
configuredMode: catalog.configuredMode,
|
|
35
|
+
configuredModeSource: catalog.configuredModeSource,
|
|
36
|
+
headlessPlan: catalog.headlessPlan,
|
|
37
|
+
modes: catalog.modes.map((m) => ({
|
|
38
|
+
mode: m.mode,
|
|
39
|
+
flags: m.flags,
|
|
40
|
+
description: m.description,
|
|
41
|
+
isDefault: m.isDefault,
|
|
42
|
+
})),
|
|
43
|
+
unsupported: catalog.unsupported,
|
|
44
|
+
notes: catalog.notes,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
console.log(JSON.stringify(out, null, 2));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let printed = 0;
|
|
51
|
+
for (const { agent, version, isDefault } of targets) {
|
|
52
|
+
if (printed > 0)
|
|
53
|
+
console.log();
|
|
54
|
+
printCatalog(getAgentModesCatalog(agent, version), version, isDefault);
|
|
55
|
+
printed++;
|
|
56
|
+
}
|
|
57
|
+
if (!agentSpec) {
|
|
58
|
+
console.log(chalk.gray('\n `agents modes <agent>` for one harness · `agents modes claude@2.1.219` for a version\'s run default'));
|
|
59
|
+
console.log(chalk.gray(' Pass --mode on `agents run` / `agents teams add`. Models: `agents models <agent>`.'));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
setHelpSections(modes, {
|
|
63
|
+
examples: `
|
|
64
|
+
agents modes
|
|
65
|
+
agents modes claude
|
|
66
|
+
agents modes claude@2.1.219
|
|
67
|
+
agents modes cursor --json
|
|
68
|
+
`,
|
|
69
|
+
notes: `
|
|
70
|
+
Permission modes control how much the agent can do (not which LLM model
|
|
71
|
+
runs — that is --model / agents models). Supported modes come from each
|
|
72
|
+
harness's capabilities table; unsupported requests degrade (auto→edit,
|
|
73
|
+
plan→safest native) or error (skip when missing).
|
|
74
|
+
'full' is a silent alias for skip.
|
|
75
|
+
agent@version is accepted so any configured run.defaults mode for that
|
|
76
|
+
version is shown; the mode list itself is per-agent today.
|
|
77
|
+
`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function resolveTargets(agentSpec) {
|
|
81
|
+
if (!agentSpec) {
|
|
82
|
+
const targets = [];
|
|
83
|
+
for (const agent of MODE_AGENTS) {
|
|
84
|
+
const version = getGlobalDefault(agent) || listInstalledVersions(agent)[0] || null;
|
|
85
|
+
targets.push({ agent, version, isDefault: true });
|
|
86
|
+
}
|
|
87
|
+
return targets;
|
|
88
|
+
}
|
|
89
|
+
const [agentName, versionSpec] = agentSpec.split('@');
|
|
90
|
+
const agent = resolveAgentName(agentName);
|
|
91
|
+
if (!agent) {
|
|
92
|
+
console.error(chalk.red(formatAgentError(agentName, MODE_AGENTS)));
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
if (AGENTS[agent].deprecated?.hard) {
|
|
96
|
+
console.error(chalk.yellow(`${agent} is deprecated and no longer supports run modes.`));
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
if (versionSpec === 'all') {
|
|
100
|
+
const installed = listInstalledVersions(agent);
|
|
101
|
+
if (installed.length === 0) {
|
|
102
|
+
return [{ agent, version: null, isDefault: true }];
|
|
103
|
+
}
|
|
104
|
+
return installed.map((v) => ({
|
|
105
|
+
agent,
|
|
106
|
+
version: v,
|
|
107
|
+
isDefault: v === getGlobalDefault(agent),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
if (versionSpec) {
|
|
111
|
+
// resolveVersionAlias exits process if the concrete version is not installed.
|
|
112
|
+
const version = resolveVersionAlias(agent, versionSpec) ?? null;
|
|
113
|
+
return [{ agent, version, isDefault: version === getGlobalDefault(agent) }];
|
|
114
|
+
}
|
|
115
|
+
const version = resolveVersion(agent, process.cwd()) || getGlobalDefault(agent) || null;
|
|
116
|
+
return [{ agent, version, isDefault: true }];
|
|
117
|
+
}
|
|
118
|
+
function printCatalog(catalog, version, isDefault) {
|
|
119
|
+
const tag = version
|
|
120
|
+
? ` ${chalk.bold(version)}${isDefault ? chalk.gray(' (default)') : ''}`
|
|
121
|
+
: chalk.gray(' (not installed)');
|
|
122
|
+
console.log(`${agentLabel(catalog.agent)}${tag}`);
|
|
123
|
+
console.log(chalk.gray(' modes:'));
|
|
124
|
+
for (const entry of catalog.modes) {
|
|
125
|
+
const star = entry.isDefault ? chalk.cyan('*') : ' ';
|
|
126
|
+
const flags = chalk.gray(formatModeFlags(entry.flags));
|
|
127
|
+
console.log(` ${star} ${chalk.cyan(entry.mode.padEnd(6))} ${chalk.bold(entry.description)} ${flags}`);
|
|
128
|
+
}
|
|
129
|
+
if (catalog.unsupported.length > 0) {
|
|
130
|
+
console.log(chalk.gray(` unsupported: ${catalog.unsupported.join(', ')}`));
|
|
131
|
+
}
|
|
132
|
+
const defaultBits = [`native default ${chalk.white(catalog.defaultMode)}`];
|
|
133
|
+
if (catalog.configuredMode) {
|
|
134
|
+
const src = catalog.configuredModeSource ? chalk.gray(` (${catalog.configuredModeSource})`) : '';
|
|
135
|
+
defaultBits.push(`run default ${chalk.white(catalog.configuredMode)}${src}`);
|
|
136
|
+
}
|
|
137
|
+
console.log(chalk.gray(` ${defaultBits.join(' · ')}`));
|
|
138
|
+
if (!catalog.headlessPlan && catalog.modes.some((m) => m.mode === 'plan')) {
|
|
139
|
+
console.log(chalk.yellow(' headless plan: not supported (interactive plan still works)'));
|
|
140
|
+
}
|
|
141
|
+
for (const note of catalog.notes) {
|
|
142
|
+
// Keep the full-alias note quiet; surface degrades more visibly.
|
|
143
|
+
if (note.startsWith("'full'"))
|
|
144
|
+
continue;
|
|
145
|
+
console.log(chalk.gray(` note: ${note}`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -46,5 +46,8 @@ export declare function groupRoutineJobsByDevice(jobs: JobConfig[], registry: De
|
|
|
46
46
|
*/
|
|
47
47
|
export declare function groupRoutineJobsByProject(jobs: JobConfig[], knownProjectNames: Set<string>): RoutineListGroup[];
|
|
48
48
|
export declare function buildRunsJson(runs: RunMeta[]): Record<string, unknown>[];
|
|
49
|
+
/** Build the exact structured routine rows shared by `routines list --json`
|
|
50
|
+
* and the one-process AGI Menu snapshot. */
|
|
51
|
+
export declare function buildRoutineListJson(): Record<string, unknown>[];
|
|
49
52
|
/** Register the `agents routines` command tree. */
|
|
50
53
|
export declare function registerRoutinesCommands(program: Command): void;
|
|
@@ -405,6 +405,72 @@ function runMetaJson(run) {
|
|
|
405
405
|
export function buildRunsJson(runs) {
|
|
406
406
|
return runs.map(runMetaJson);
|
|
407
407
|
}
|
|
408
|
+
/** Build the exact structured routine rows shared by `routines list --json`
|
|
409
|
+
* and the one-process AGI Menu snapshot. */
|
|
410
|
+
export function buildRoutineListJson() {
|
|
411
|
+
try {
|
|
412
|
+
monitorRunningJobs();
|
|
413
|
+
}
|
|
414
|
+
catch { /* best-effort orphan reap */ }
|
|
415
|
+
const jobs = listAllJobs(process.cwd());
|
|
416
|
+
if (jobs.length === 0)
|
|
417
|
+
return [];
|
|
418
|
+
const scheduler = new JobScheduler(async () => { });
|
|
419
|
+
scheduler.loadAll();
|
|
420
|
+
try {
|
|
421
|
+
const overdueSet = new Set();
|
|
422
|
+
try {
|
|
423
|
+
for (const job of detectOverdueJobs())
|
|
424
|
+
overdueSet.add(job.name);
|
|
425
|
+
}
|
|
426
|
+
catch {
|
|
427
|
+
// Best-effort indicator; never block the list on detection errors.
|
|
428
|
+
}
|
|
429
|
+
const now = new Date();
|
|
430
|
+
const knownProjectNames = new Set(listProjectDefs().map((project) => project.name));
|
|
431
|
+
return jobs.map((job) => {
|
|
432
|
+
const latestRun = localLatestRun(job);
|
|
433
|
+
const enabledDevices = devicesWithRoutineEnabled(job.name);
|
|
434
|
+
return {
|
|
435
|
+
name: job.name,
|
|
436
|
+
agent: job.agent ?? null,
|
|
437
|
+
workflow: job.workflow ?? null,
|
|
438
|
+
command: job.command ?? null,
|
|
439
|
+
repo: job.repo ?? null,
|
|
440
|
+
schedule: job.schedule ?? null,
|
|
441
|
+
scheduleHuman: fireConditionLabel(job),
|
|
442
|
+
trigger: job.trigger ?? null,
|
|
443
|
+
timezone: job.timezone ?? null,
|
|
444
|
+
devices: enabledDevices,
|
|
445
|
+
enabledDevices,
|
|
446
|
+
host: job.host ?? null,
|
|
447
|
+
hostStrategy: resolveHostStrategy(job),
|
|
448
|
+
source: job.source ?? null,
|
|
449
|
+
sourceRepo: job.source?.repo ?? job.repo ?? null,
|
|
450
|
+
sourceBranch: job.source?.branch ?? null,
|
|
451
|
+
runOnce: Boolean(job.runOnce),
|
|
452
|
+
catchup: job.catchup !== false,
|
|
453
|
+
oneShot: isOneShotRoutine(job),
|
|
454
|
+
expired: isPastOneShotRoutine(job, now),
|
|
455
|
+
runsHere: jobRunsOnThisDevice(job),
|
|
456
|
+
enabled: job.enabled,
|
|
457
|
+
overdue: overdueSet.has(job.name),
|
|
458
|
+
nextRun: nextRunForDisplay(job, scheduler)?.toISOString() ?? null,
|
|
459
|
+
nextRunHuman: nextRunLabel(job, scheduler, now),
|
|
460
|
+
lastStatus: latestRun?.status ?? null,
|
|
461
|
+
exitCode: latestRun?.exitCode ?? null,
|
|
462
|
+
failureReason: latestRun?.errorMessage ?? null,
|
|
463
|
+
lastRunStartedAt: latestRun?.startedAt ?? null,
|
|
464
|
+
lastRunCompletedAt: latestRun?.completedAt ?? null,
|
|
465
|
+
projects: job.projects ?? [],
|
|
466
|
+
projectGroup: computeProjectGroup(job.projects, knownProjectNames),
|
|
467
|
+
};
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
finally {
|
|
471
|
+
scheduler.stopAll();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
408
474
|
/** Detect Ctrl+C or premature stream close during an interactive prompt. */
|
|
409
475
|
function isPromptCancelled(err) {
|
|
410
476
|
return (err instanceof Error &&
|
|
@@ -554,6 +620,10 @@ export function registerRoutinesCommands(program) {
|
|
|
554
620
|
console.error(chalk.red(`Unsupported --group-by '${options.groupBy}'. Use: project (default) or device`));
|
|
555
621
|
process.exit(1);
|
|
556
622
|
}
|
|
623
|
+
if (options.json) {
|
|
624
|
+
process.stdout.write(JSON.stringify(buildRoutineListJson()) + '\n');
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
557
627
|
try {
|
|
558
628
|
monitorRunningJobs();
|
|
559
629
|
}
|
|
@@ -579,53 +649,6 @@ export function registerRoutinesCommands(program) {
|
|
|
579
649
|
catch {
|
|
580
650
|
// Best-effort indicator; never block the list on detection errors.
|
|
581
651
|
}
|
|
582
|
-
// Machine-readable path: same data the table renders, but structured.
|
|
583
|
-
// The menu bar helper relies on this so it never reimplements cron math.
|
|
584
|
-
if (options.json) {
|
|
585
|
-
const nowJson = new Date();
|
|
586
|
-
const knownProjectNames = new Set(listProjectDefs().map((p) => p.name));
|
|
587
|
-
const payload = jobs.map((job) => {
|
|
588
|
-
const latestRun = localLatestRun(job);
|
|
589
|
-
const enabledDevices = devicesWithRoutineEnabled(job.name);
|
|
590
|
-
return {
|
|
591
|
-
name: job.name,
|
|
592
|
-
agent: job.agent ?? null,
|
|
593
|
-
workflow: job.workflow ?? null,
|
|
594
|
-
command: job.command ?? null,
|
|
595
|
-
repo: job.repo ?? null,
|
|
596
|
-
schedule: job.schedule ?? null,
|
|
597
|
-
scheduleHuman: fireConditionLabel(job),
|
|
598
|
-
trigger: job.trigger ?? null,
|
|
599
|
-
timezone: job.timezone ?? null,
|
|
600
|
-
devices: enabledDevices,
|
|
601
|
-
enabledDevices,
|
|
602
|
-
host: job.host ?? null,
|
|
603
|
-
hostStrategy: resolveHostStrategy(job),
|
|
604
|
-
source: job.source ?? null,
|
|
605
|
-
sourceRepo: job.source?.repo ?? job.repo ?? null,
|
|
606
|
-
sourceBranch: job.source?.branch ?? null,
|
|
607
|
-
runOnce: Boolean(job.runOnce),
|
|
608
|
-
catchup: job.catchup !== false,
|
|
609
|
-
oneShot: isOneShotRoutine(job),
|
|
610
|
-
expired: isPastOneShotRoutine(job, nowJson),
|
|
611
|
-
runsHere: jobRunsOnThisDevice(job),
|
|
612
|
-
enabled: job.enabled,
|
|
613
|
-
overdue: overdueSet.has(job.name),
|
|
614
|
-
nextRun: nextRunForDisplay(job, scheduler)?.toISOString() ?? null,
|
|
615
|
-
nextRunHuman: nextRunLabel(job, scheduler, nowJson),
|
|
616
|
-
lastStatus: latestRun?.status ?? null,
|
|
617
|
-
exitCode: latestRun?.exitCode ?? null,
|
|
618
|
-
failureReason: latestRun?.errorMessage ?? null,
|
|
619
|
-
lastRunStartedAt: latestRun?.startedAt ?? null,
|
|
620
|
-
lastRunCompletedAt: latestRun?.completedAt ?? null,
|
|
621
|
-
projects: job.projects ?? [],
|
|
622
|
-
projectGroup: computeProjectGroup(job.projects, knownProjectNames),
|
|
623
|
-
};
|
|
624
|
-
});
|
|
625
|
-
scheduler.stopAll();
|
|
626
|
-
process.stdout.write(JSON.stringify(payload) + '\n');
|
|
627
|
-
return;
|
|
628
|
-
}
|
|
629
652
|
console.log(chalk.bold('Scheduled Jobs\n'));
|
|
630
653
|
// OSC 8 hyperlink helper — renders as a clickable link in supporting terminals.
|
|
631
654
|
// Guarded on process.stdout.isTTY so that piped/redirected output never
|
|
@@ -91,7 +91,7 @@ export function resolveRunCloudProvider(agentId, explicitProvider) {
|
|
|
91
91
|
return resolveProvider(explicitProvider);
|
|
92
92
|
if (!nativeProviderForAgent(agentId)) {
|
|
93
93
|
throw new RunCloudError(`${agentId} has no native cloud. Cloud-capable agents: ${cloudCapableAgentIds().join(', ')}. ` +
|
|
94
|
-
`Override with --provider <id> (rush | codex | factory | antigravity | host).`);
|
|
94
|
+
`Override with --provider <id> (rush | codex | cursor | factory | antigravity | host).`);
|
|
95
95
|
}
|
|
96
96
|
return resolveProvider(undefined, agentId);
|
|
97
97
|
}
|
|
@@ -10,9 +10,10 @@ import chalk from 'chalk';
|
|
|
10
10
|
import { listRemoteBundles, MIN_PASSPHRASE_LEN, pullBundle, pushBundle, } from '../lib/secrets/sync.js';
|
|
11
11
|
import { bundleExists, listBundles } from '../lib/secrets/bundles.js';
|
|
12
12
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
13
|
+
import { missingSyncPassphraseMessage, resolveSyncPassphraseFromEnv, warnEnvPassphraseReadableOnce, } from '../lib/secrets/sync-passphrase.js';
|
|
13
14
|
async function promptPassphrase(message, confirm = false) {
|
|
14
15
|
if (!isInteractiveTerminal()) {
|
|
15
|
-
throw new Error(
|
|
16
|
+
throw new Error(missingSyncPassphraseMessage());
|
|
16
17
|
}
|
|
17
18
|
const { password } = await import('@inquirer/prompts');
|
|
18
19
|
const first = await password({ message, mask: true });
|
|
@@ -25,21 +26,18 @@ async function promptPassphrase(message, confirm = false) {
|
|
|
25
26
|
throw new Error('Passphrases do not match.');
|
|
26
27
|
return first;
|
|
27
28
|
}
|
|
28
|
-
// Print the env-var-source warning at most once per process so a `--all` push
|
|
29
|
-
// over many bundles doesn't flood stderr with the same notice.
|
|
30
|
-
let envPassphraseWarned = false;
|
|
31
29
|
function passphraseFromEnvOrPrompt(confirm) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// Env resolution (current name, then the deprecated master-key name) lives in
|
|
31
|
+
// lib/secrets/sync-passphrase.ts so this command and `agents sync` share one
|
|
32
|
+
// chokepoint — otherwise each call site warns separately and the "once per
|
|
33
|
+
// process" promise is a lie.
|
|
34
|
+
const { value } = resolveSyncPassphraseFromEnv();
|
|
35
|
+
if (value) {
|
|
36
|
+
if (value.length < MIN_PASSPHRASE_LEN) {
|
|
35
37
|
return Promise.reject(new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`));
|
|
36
38
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
process.stderr.write(chalk.yellow('warn: using AGENTS_SECRETS_PASSPHRASE. Env vars are readable by other same-user processes ' +
|
|
40
|
-
'(/proc, ps, crash dumps, CI logs) — rotate the passphrase after CI use.\n'));
|
|
41
|
-
}
|
|
42
|
-
return Promise.resolve(fromEnv);
|
|
39
|
+
warnEnvPassphraseReadableOnce();
|
|
40
|
+
return Promise.resolve(value);
|
|
43
41
|
}
|
|
44
42
|
return promptPassphrase('Sync passphrase', confirm);
|
|
45
43
|
}
|
|
@@ -48,9 +48,11 @@ export declare function parseImportSource(opts: {
|
|
|
48
48
|
* defaults). Shared with the command action so the wiring is unit-testable
|
|
49
49
|
* without a live SSH session.
|
|
50
50
|
*/
|
|
51
|
+
export declare function resolveUnlockTtlMs(ttl: string | undefined, until: string | undefined, now?: number): number;
|
|
51
52
|
export declare function buildRemoteUnlockArgs(names: string[], opts: {
|
|
52
53
|
all?: boolean;
|
|
53
54
|
ttl?: string;
|
|
55
|
+
until?: string;
|
|
54
56
|
durable?: boolean;
|
|
55
57
|
}): string[];
|
|
56
58
|
/**
|