@phnx-labs/agents-cli 1.22.53 → 1.22.55
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 +208 -0
- package/README.md +59 -9
- package/dist/bootstrap.js +55 -154
- package/dist/cli/command-registry.d.ts +5 -0
- package/dist/cli/command-registry.js +8 -1
- package/dist/commands/accounts.js +219 -173
- package/dist/commands/apply.js +6 -3
- package/dist/commands/auth-mint.d.ts +8 -0
- package/dist/commands/auth-mint.js +96 -0
- package/dist/commands/auth.js +5 -1
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cost.js +8 -2
- package/dist/commands/daemon.js +2 -2
- package/dist/commands/doctor.js +6 -1
- package/dist/commands/exec.js +10 -8
- package/dist/commands/focus.d.ts +1 -0
- package/dist/commands/focus.js +2 -2
- package/dist/commands/go.d.ts +5 -4
- package/dist/commands/go.js +7 -7
- package/dist/commands/insights.js +9 -0
- package/dist/commands/monitors.js +85 -30
- package/dist/commands/output.js +8 -2
- package/dist/commands/repo.js +18 -0
- package/dist/commands/routines.js +31 -2
- package/dist/commands/secrets.js +33 -14
- package/dist/commands/sessions.d.ts +20 -12
- package/dist/commands/sessions.js +64 -20
- package/dist/commands/setup-accounts.d.ts +8 -0
- package/dist/commands/setup-accounts.js +47 -0
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +11 -2
- package/dist/commands/share.d.ts +79 -3
- package/dist/commands/share.js +347 -18
- package/dist/commands/ssh.d.ts +7 -0
- package/dist/commands/ssh.js +18 -2
- package/dist/commands/status.js +14 -0
- package/dist/commands/view.d.ts +11 -1
- package/dist/commands/view.js +35 -7
- package/dist/lib/account-registry.js +15 -3
- package/dist/lib/accounting/rotate.d.ts +20 -6
- package/dist/lib/accounting/rotate.js +38 -7
- package/dist/lib/accounting/usage.d.ts +68 -1
- package/dist/lib/accounting/usage.js +116 -10
- package/dist/lib/agent-spec/agents.d.ts +5 -2
- package/dist/lib/agent-spec/agents.js +25 -7
- package/dist/lib/analytics/mix-commands.js +12 -6
- package/dist/lib/auth-mint.d.ts +150 -0
- package/dist/lib/auth-mint.js +434 -0
- package/dist/lib/browser/cdp.d.ts +1 -1
- package/dist/lib/browser/cdp.js +1 -1
- package/dist/lib/browser/ffmpeg.d.ts +12 -0
- package/dist/lib/browser/ffmpeg.js +184 -0
- package/dist/lib/browser/remote-control.d.ts +9 -7
- package/dist/lib/browser/remote-control.js +9 -7
- package/dist/lib/browser/service.js +119 -25
- package/dist/lib/claude-account-token.d.ts +10 -0
- package/dist/lib/claude-account-token.js +14 -4
- package/dist/lib/config-drift.d.ts +37 -0
- package/dist/lib/config-drift.js +72 -0
- package/dist/lib/daemon/auth-sync-service.d.ts +19 -0
- package/dist/lib/daemon/auth-sync-service.js +34 -0
- package/dist/lib/daemon/browser-task-reap-service.d.ts +14 -0
- package/dist/lib/daemon/browser-task-reap-service.js +26 -0
- package/dist/lib/daemon/daemon.js +87 -176
- package/dist/lib/daemon/heartbeat-service.d.ts +13 -0
- package/dist/lib/daemon/heartbeat-service.js +26 -0
- package/dist/lib/daemon/monitor-engine-service.d.ts +9 -5
- package/dist/lib/daemon/monitor-engine-service.js +15 -7
- package/dist/lib/daemon/runner.d.ts +15 -0
- package/dist/lib/daemon/runner.js +23 -0
- package/dist/lib/daemon/secrets-broker-service.d.ts +5 -4
- package/dist/lib/daemon/secrets-broker-service.js +17 -32
- package/dist/lib/daemon/service.d.ts +2 -2
- package/dist/lib/daemon/service.js +1 -1
- package/dist/lib/daemon/session-state-service.d.ts +21 -0
- package/dist/lib/daemon/session-state-service.js +34 -0
- package/dist/lib/daemon/supervisor.d.ts +17 -7
- package/dist/lib/daemon/supervisor.js +87 -14
- package/dist/lib/daemon/tmux-reap-service.d.ts +11 -0
- package/dist/lib/daemon/tmux-reap-service.js +28 -0
- package/dist/lib/daemon/webhook-receiver-service.d.ts +9 -0
- package/dist/lib/daemon/webhook-receiver-service.js +17 -0
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +25 -0
- package/dist/lib/device-config.d.ts +3 -3
- package/dist/lib/device-config.js +5 -5
- package/dist/lib/devices/connect.d.ts +26 -0
- package/dist/lib/devices/connect.js +48 -1
- package/dist/lib/devices/doctor-findings.d.ts +5 -1
- package/dist/lib/devices/doctor-findings.js +19 -1
- package/dist/lib/devices/harness-inventory.js +5 -2
- package/dist/lib/exec.d.ts +28 -0
- package/dist/lib/exec.js +73 -7
- package/dist/lib/feed/feed.d.ts +1 -1
- package/dist/lib/feed/feed.js +23 -1
- package/dist/lib/feed-broadcast.js +1 -1
- package/dist/lib/fleet/apply.d.ts +11 -0
- package/dist/lib/fleet/apply.js +23 -3
- package/dist/lib/fleet/auth-sync.js +5 -3
- package/dist/lib/help.d.ts +9 -0
- package/dist/lib/help.js +29 -1
- package/dist/lib/hosts/passthrough.d.ts +1 -10
- package/dist/lib/hosts/passthrough.js +1 -13
- package/dist/lib/installations/versions.js +9 -1
- package/dist/lib/linux-userns.d.ts +58 -0
- package/dist/lib/linux-userns.js +116 -0
- package/dist/lib/memory.d.ts +26 -0
- package/dist/lib/memory.js +80 -1
- package/dist/lib/monitors/config.d.ts +11 -0
- package/dist/lib/monitors/config.js +8 -0
- package/dist/lib/monitors/engine.d.ts +5 -1
- package/dist/lib/monitors/engine.js +13 -4
- package/dist/lib/monitors/state.d.ts +37 -1
- package/dist/lib/monitors/state.js +79 -4
- package/dist/lib/permissions-registry.d.ts +2 -0
- package/dist/lib/permissions-registry.js +116 -14
- package/dist/lib/permissions.d.ts +5 -3
- package/dist/lib/permissions.js +25 -27
- package/dist/lib/profiles.d.ts +8 -7
- package/dist/lib/profiles.js +12 -0
- package/dist/lib/project-key.d.ts +9 -0
- package/dist/lib/project-key.js +11 -0
- package/dist/lib/scheduling/routines.d.ts +47 -0
- package/dist/lib/scheduling/routines.js +70 -1
- package/dist/lib/secrets/bundles.d.ts +35 -0
- package/dist/lib/secrets/bundles.js +78 -1
- package/dist/lib/secrets/push.d.ts +3 -8
- package/dist/lib/secrets/push.js +18 -14
- package/dist/lib/secrets/remote.d.ts +9 -18
- package/dist/lib/secrets/remote.js +11 -26
- package/dist/lib/secrets/reserved-sync.d.ts +65 -0
- package/dist/lib/secrets/reserved-sync.js +129 -0
- package/dist/lib/self-heal/checks/hook-manifest.d.ts +2 -0
- package/dist/lib/self-heal/checks/hook-manifest.js +56 -0
- package/dist/lib/self-heal/registry.js +4 -0
- package/dist/lib/self-heal/types.d.ts +1 -1
- package/dist/lib/session/active.js +1 -4
- package/dist/lib/session/db.d.ts +41 -5
- package/dist/lib/session/db.js +132 -30
- package/dist/lib/session/discover.d.ts +32 -4
- package/dist/lib/session/discover.js +119 -25
- package/dist/lib/session/insights.d.ts +14 -0
- package/dist/lib/session/insights.js +25 -2
- package/dist/lib/session/linear.js +1 -1
- package/dist/lib/session/shell-programs.d.ts +17 -0
- package/dist/lib/session/shell-programs.js +21 -0
- package/dist/lib/session/state.js +2 -1
- package/dist/lib/session/stream-render.js +2 -1
- package/dist/lib/session/tool-calls.js +2 -5
- package/dist/lib/session/trajectory-html.js +2 -1
- package/dist/lib/session/trajectory.js +3 -12
- package/dist/lib/session/types.d.ts +8 -0
- package/dist/lib/share/capture.js +11 -2
- package/dist/lib/share/publish.d.ts +56 -5
- package/dist/lib/share/publish.js +126 -18
- package/dist/lib/share/worker-template.d.ts +3 -12
- package/dist/lib/share/worker-template.js +860 -59
- package/dist/lib/startup/root-command.js +2 -1
- package/dist/lib/state.d.ts +16 -0
- package/dist/lib/state.js +178 -46
- package/dist/lib/sync-status.d.ts +4 -0
- package/dist/lib/sync-status.js +3 -0
- package/dist/lib/traces/classify.js +24 -19
- package/dist/lib/usage-refresh.js +2 -1
- package/dist/lib/view-types.d.ts +7 -0
- package/package.json +9 -2
|
@@ -20,9 +20,21 @@ import { discoverNativeAccounts } from '../lib/account-catalog.js';
|
|
|
20
20
|
import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
|
|
21
21
|
import { getAccountProvider, listAccountProviders, providerAuthenticatesHarness } from '../lib/account-provider-registry.js';
|
|
22
22
|
import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, labelNativeAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
|
|
23
|
+
import { registerMintCommand } from './auth-mint.js';
|
|
23
24
|
function cleanCommandError(command, err) {
|
|
24
25
|
command.error(err instanceof Error ? err.message : String(err), { exitCode: 1, code: 'accounts.error' });
|
|
25
26
|
}
|
|
27
|
+
/** Turn a thrown user-facing Error into commander's clean CLI error instead of Node's stack dump. */
|
|
28
|
+
async function runAccountsAction(command, fn) {
|
|
29
|
+
try {
|
|
30
|
+
await fn();
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
if (isPromptCancelled(err))
|
|
34
|
+
process.exit(130);
|
|
35
|
+
cleanCommandError(command, err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
26
38
|
function parseInstallation(raw) {
|
|
27
39
|
const at = raw.lastIndexOf('@');
|
|
28
40
|
if (at < 1 || at === raw.length - 1)
|
|
@@ -106,6 +118,17 @@ function secretFromBundle(raw) {
|
|
|
106
118
|
const { bundle, key } = parseBundleKey(raw);
|
|
107
119
|
return readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' }).env[key];
|
|
108
120
|
}
|
|
121
|
+
/** Interactive secret entry for add/set-key. Ctrl+C must not become accounts.error. */
|
|
122
|
+
async function promptAccountSecret(message) {
|
|
123
|
+
try {
|
|
124
|
+
return await password({ message });
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (isPromptCancelled(err))
|
|
128
|
+
return null;
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
109
132
|
function publicAccount(account) {
|
|
110
133
|
return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
|
|
111
134
|
}
|
|
@@ -380,84 +403,99 @@ export function registerAccountsCommand(program) {
|
|
|
380
403
|
const accounts = program.command('accounts').description('Browse native logins and manage provider account bundles')
|
|
381
404
|
.option('--json', 'Machine-readable account metadata')
|
|
382
405
|
.option('--fleet', 'Show harness-native signed-in identities across reachable devices')
|
|
383
|
-
.action((o) =>
|
|
384
|
-
|
|
406
|
+
.action(async (o, command) => {
|
|
407
|
+
await runAccountsAction(command, () => printAccounts(!!o.json, !!o.fleet));
|
|
408
|
+
});
|
|
409
|
+
accounts.command('list').description('List credential accounts').option('--json', 'Machine-readable account metadata').action(async (o, command) => {
|
|
410
|
+
await runAccountsAction(command, () => printAccounts(!!(o.json || command.optsWithGlobals().json)));
|
|
411
|
+
});
|
|
412
|
+
registerMintCommand(accounts);
|
|
385
413
|
accounts.command('add <name>')
|
|
386
414
|
.description('Add a durable API key, setup token, or bearer token')
|
|
387
415
|
.requiredOption('--provider <provider>', `Credential provider (${listAccountProviders().join(', ')})`)
|
|
388
416
|
.requiredOption('--auth <type>', 'Credential type: api-key | setup-token | bearer-token')
|
|
389
417
|
.option('--base-url <url>', 'Optional endpoint override stored with the account')
|
|
390
418
|
.option('--from-secrets <bundle:key>', 'Import from an existing agents secrets entry')
|
|
391
|
-
.action(async (name, o) => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
419
|
+
.action(async (name, o, command) => {
|
|
420
|
+
await runAccountsAction(command, async () => {
|
|
421
|
+
const auth = parseAuth(o.auth);
|
|
422
|
+
const provider = getAccountProvider(o.provider);
|
|
423
|
+
if (!provider.authKinds.includes(auth))
|
|
424
|
+
throw new Error(`Provider '${provider.provider}' does not support ${auth}. Supported: ${provider.authKinds.join(', ')}.`);
|
|
425
|
+
const secret = o.fromSecrets
|
|
426
|
+
? secretFromBundle(o.fromSecrets)
|
|
427
|
+
: await promptAccountSecret(`Enter ${provider.provider} ${auth} for '${name}':`);
|
|
428
|
+
if (secret === null) {
|
|
429
|
+
process.exit(130);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
const account = addAccount(name, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
|
|
433
|
+
console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
|
|
434
|
+
console.log(chalk.gray(`Secret bundle '${account.name}' is the account and uses policy never, so agent launches never request Touch ID.`));
|
|
435
|
+
});
|
|
400
436
|
});
|
|
401
437
|
accounts.command('set-key <name>')
|
|
402
438
|
.description('Rotate an account credential without changing its identity')
|
|
403
439
|
.option('--from-secrets <bundle:key>', 'Import from an existing agents secrets entry')
|
|
404
|
-
.action(async (name, o) => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
440
|
+
.action(async (name, o, command) => {
|
|
441
|
+
await runAccountsAction(command, async () => {
|
|
442
|
+
const account = findAccount(name);
|
|
443
|
+
if (!account)
|
|
444
|
+
throw new Error(`Unknown provider account '${name}'.`);
|
|
445
|
+
const secret = o.fromSecrets
|
|
446
|
+
? secretFromBundle(o.fromSecrets)
|
|
447
|
+
: await promptAccountSecret(`Enter new ${account.provider} ${account.auth} for '${name}':`);
|
|
448
|
+
if (secret === null) {
|
|
449
|
+
process.exit(130);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
setAccountSecret(name, secret);
|
|
453
|
+
console.log(chalk.green(`Updated credential for account '${name}'.`));
|
|
454
|
+
});
|
|
411
455
|
});
|
|
412
456
|
accounts.command('view <name>').alias('inspect').description('Show safe account metadata, custody, and attachments').option('--json', 'Machine-readable output').action(async (name, o, command) => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
457
|
+
await runAccountsAction(command, async () => {
|
|
458
|
+
const meta = readMeta();
|
|
459
|
+
const unified = findUnifiedAccount(name, meta);
|
|
460
|
+
if (!unified)
|
|
461
|
+
throw new Error(`Unknown account '${name}'.`);
|
|
462
|
+
const account = unified.kind === 'provider'
|
|
463
|
+
? { ...publicAccount(inspectAccount(unified.name)), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
|
|
464
|
+
: { ...unified, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
|
|
465
|
+
if (o.json || command.optsWithGlobals().json)
|
|
466
|
+
return console.log(JSON.stringify(account, null, 2));
|
|
467
|
+
console.log(chalk.bold(account.name));
|
|
468
|
+
console.log(` kind: ${account.kind}`);
|
|
469
|
+
console.log(` id: ${account.id}`);
|
|
470
|
+
console.log(` custody: ${account.custody}`);
|
|
471
|
+
if (account.kind === 'provider') {
|
|
472
|
+
console.log(` provider: ${account.provider}`);
|
|
473
|
+
console.log(` auth: ${account.auth}`);
|
|
474
|
+
console.log(` credential: ${account.secretPresent ? 'present on this device' : 'missing on this device'}`);
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
console.log(` identity: ${account.identityLabel ?? account.identityKey}`);
|
|
478
|
+
console.log(` scope: ${account.scope}`);
|
|
479
|
+
}
|
|
480
|
+
console.log(` attached: ${account.attached.length ? account.attached.join(', ') : 'none'}`);
|
|
481
|
+
});
|
|
436
482
|
});
|
|
437
483
|
accounts.command('name <source> <name>')
|
|
438
484
|
.description('Name a signed-in native installation without copying its OAuth credentials')
|
|
439
485
|
.action(async (source, name, _o, command) => {
|
|
440
|
-
|
|
486
|
+
await runAccountsAction(command, async () => {
|
|
441
487
|
const identity = await nativeIdentityFromSource(source);
|
|
442
488
|
const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
|
|
443
489
|
console.log(chalk.green(`Named ${source} as ${account.name}.`));
|
|
444
490
|
if (account.scope === 'device')
|
|
445
491
|
console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
|
|
446
|
-
}
|
|
447
|
-
catch (err) {
|
|
448
|
-
cleanCommandError(command, err);
|
|
449
|
-
}
|
|
492
|
+
});
|
|
450
493
|
});
|
|
451
494
|
const labelCmd = accounts.command('label <source> [label]')
|
|
452
495
|
.description('Label a native login by harness or <harness>@<version>; the label binds to the account identity, not the version')
|
|
453
496
|
.option('--account <email-or-id>', 'Native identity to label when the harness has multiple logins')
|
|
454
497
|
.action(async (source, label, o, command) => {
|
|
455
|
-
|
|
456
|
-
await runAccountsLabel(source, label, o);
|
|
457
|
-
}
|
|
458
|
-
catch (err) {
|
|
459
|
-
cleanCommandError(command, err);
|
|
460
|
-
}
|
|
498
|
+
await runAccountsAction(command, () => runAccountsLabel(source, label, o));
|
|
461
499
|
});
|
|
462
500
|
setHelpSections(labelCmd, {
|
|
463
501
|
examples: `agents accounts label codex work
|
|
@@ -468,155 +506,161 @@ agents run codex#work`,
|
|
|
468
506
|
});
|
|
469
507
|
accounts.command('attach <account> <target>')
|
|
470
508
|
.description('Attach a named account to a native installation or custom harness')
|
|
471
|
-
.action(async (name, target) => {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (
|
|
491
|
-
|
|
509
|
+
.action(async (name, target, _o, command) => {
|
|
510
|
+
await runAccountsAction(command, async () => {
|
|
511
|
+
const meta = readMeta();
|
|
512
|
+
const account = findUnifiedAccount(name, meta);
|
|
513
|
+
if (!account)
|
|
514
|
+
throw new Error(`Unknown account '${name}'.`);
|
|
515
|
+
if (account.kind === 'native')
|
|
516
|
+
assertNativeAccountNameable(account.agent);
|
|
517
|
+
// Validate the target exists before mutating any binding.
|
|
518
|
+
const t = classifyAttachTarget(target);
|
|
519
|
+
const targetAgent = t.kind === 'profile' ? t.profile.host.agent : t.agent;
|
|
520
|
+
if (account.kind === 'native') {
|
|
521
|
+
// A provider-backed profile injects provider env at spawn, which would run
|
|
522
|
+
// under a different credential than the native identity claims — refuse it.
|
|
523
|
+
if (t.kind === 'profile' && t.profile.provider) {
|
|
524
|
+
throw new Error(`Custom harness '${target}' is provider-backed (${t.profile.provider}); it cannot host the native login account '${account.name}'. Attach a matching provider account instead.`);
|
|
525
|
+
}
|
|
526
|
+
if (targetAgent !== account.agent)
|
|
527
|
+
throw new Error(`Account '${account.name}' is a ${account.agent} login; '${target}' runs ${targetAgent}.`);
|
|
528
|
+
if (account.scope === 'device') {
|
|
529
|
+
if (t.kind !== 'device-agent')
|
|
530
|
+
throw new Error(`${account.agent} authentication is device-scoped. Attach it with 'agents accounts attach ${account.name} ${account.agent}'.`);
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
if (t.kind !== 'installation')
|
|
534
|
+
throw new Error(`${account.agent} authentication is per-version. Attach '${account.name}' to a specific ${account.agent}@<version>.`);
|
|
535
|
+
const identity = await nativeIdentityFromSource(target);
|
|
536
|
+
if (identity.identityKey !== account.identityKey)
|
|
537
|
+
throw new Error(`'${target}' is signed in to a different identity than account '${account.name}'.`);
|
|
538
|
+
}
|
|
492
539
|
}
|
|
493
540
|
else {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const identity = await nativeIdentityFromSource(target);
|
|
497
|
-
if (identity.identityKey !== account.identityKey)
|
|
498
|
-
throw new Error(`'${target}' is signed in to a different identity than account '${account.name}'.`);
|
|
541
|
+
// Provider account: it must be able to authenticate the target's harness.
|
|
542
|
+
getAccountProvider(account.provider).envFor(targetAgent, account.auth);
|
|
499
543
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
bindAccount(name, target);
|
|
506
|
-
writeClaudeInteractiveOauthToken(t, targetAgent);
|
|
507
|
-
console.log(chalk.green(`Attached ${account.name} to ${target}.`));
|
|
544
|
+
bindAccount(name, target);
|
|
545
|
+
writeClaudeInteractiveOauthToken(t, targetAgent);
|
|
546
|
+
console.log(chalk.green(`Attached ${account.name} to ${target}.`));
|
|
547
|
+
});
|
|
508
548
|
});
|
|
509
549
|
accounts.command('detach <account> <target>')
|
|
510
550
|
.description('Remove one account attachment')
|
|
511
|
-
.action((name, target) => {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
551
|
+
.action(async (name, target, _o, command) => {
|
|
552
|
+
await runAccountsAction(command, () => {
|
|
553
|
+
unbindAccount(name, target);
|
|
554
|
+
// With the binding gone, no setup-token resolves for this version home, so this
|
|
555
|
+
// clears any .oauth_token the attach left behind (else interactive runs would keep
|
|
556
|
+
// authenticating as the just-detached account).
|
|
557
|
+
try {
|
|
558
|
+
const t = classifyAttachTarget(target);
|
|
559
|
+
writeClaudeInteractiveOauthToken(t, t.kind === 'profile' ? t.profile.host.agent : t.agent);
|
|
560
|
+
}
|
|
561
|
+
catch { /* an unresolvable target has no version home to clean */ }
|
|
562
|
+
console.log(chalk.green(`Detached ${name} from ${target}.`));
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
accounts.command('rename <old> <new>').description('Rename an account without changing its stable id').action(async (oldName, newName, _o, command) => {
|
|
566
|
+
await runAccountsAction(command, () => { renameAccount(oldName, newName); });
|
|
567
|
+
});
|
|
568
|
+
accounts.command('remove <name>').description('Remove an account and its device-local credential').action(async (name, _o, command) => {
|
|
569
|
+
await runAccountsAction(command, () => { removeAccount(name); });
|
|
522
570
|
});
|
|
523
|
-
accounts.command('rename <old> <new>').description('Rename an account without changing its stable id').action((oldName, newName) => renameAccount(oldName, newName));
|
|
524
|
-
accounts.command('remove <name>').description('Remove an account and its device-local credential').action((name) => removeAccount(name));
|
|
525
571
|
accounts.command('set-default <agent> <name>')
|
|
526
572
|
.description('Use this account for a harness when --account is omitted')
|
|
527
|
-
.action((agentRaw, name, _o, command) => {
|
|
528
|
-
|
|
573
|
+
.action(async (agentRaw, name, _o, command) => {
|
|
574
|
+
await runAccountsAction(command, () => {
|
|
529
575
|
const { agent, account } = setDefaultAccount(agentRaw, name);
|
|
530
576
|
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
531
|
-
}
|
|
532
|
-
catch (err) {
|
|
533
|
-
cleanCommandError(command, err);
|
|
534
|
-
}
|
|
577
|
+
});
|
|
535
578
|
});
|
|
536
579
|
const switchCmd = accounts.command('switch <harness> [account]')
|
|
537
580
|
.description('Pick the default account for a harness')
|
|
538
581
|
.option('--json', 'Machine-readable account list or the resulting default')
|
|
539
582
|
.action(async (harness, account, o, command) => {
|
|
540
|
-
|
|
541
|
-
await runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) });
|
|
542
|
-
}
|
|
543
|
-
catch (err) {
|
|
544
|
-
cleanCommandError(command, err);
|
|
545
|
-
}
|
|
583
|
+
await runAccountsAction(command, () => runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
546
584
|
});
|
|
547
585
|
accounts.command('clear-default <agent>')
|
|
548
586
|
.description('Return a harness to native login or balanced account selection')
|
|
549
|
-
.action((agentRaw) => {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
587
|
+
.action(async (agentRaw, _o, command) => {
|
|
588
|
+
await runAccountsAction(command, () => {
|
|
589
|
+
parseHarness(agentRaw);
|
|
590
|
+
updateMeta(meta => {
|
|
591
|
+
const defaults = { ...meta.accounts?.defaults };
|
|
592
|
+
delete defaults[agentRaw];
|
|
593
|
+
return { ...meta, accounts: { ...meta.accounts, defaults } };
|
|
594
|
+
});
|
|
595
|
+
console.log(chalk.green(`Cleared the default account for ${agentRaw}.`));
|
|
555
596
|
});
|
|
556
|
-
console.log(chalk.green(`Cleared the default account for ${agentRaw}.`));
|
|
557
597
|
});
|
|
558
598
|
accounts.command('logout <target>')
|
|
559
599
|
.description('Sign out a harness-native OAuth login. API-key / setup-token / bearer accounts use `accounts remove` instead.')
|
|
560
|
-
.action(async (target) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
600
|
+
.action(async (target, _o, command) => {
|
|
601
|
+
await runAccountsAction(command, async () => {
|
|
602
|
+
const provider = findAccount(target);
|
|
603
|
+
if (provider) {
|
|
604
|
+
throw new Error(`Account '${provider.name}' uses ${provider.auth}, not OAuth. Remove it with: agents accounts remove ${provider.name}`);
|
|
605
|
+
}
|
|
606
|
+
const agentRaw = target.split('@')[0];
|
|
607
|
+
if (!ALL_AGENT_IDS.includes(agentRaw)) {
|
|
608
|
+
throw new Error(`Unknown target '${target}'. Pass a native harness (claude, codex, …) or a provider account name. Provider API-key accounts use \`agents accounts remove\`.`);
|
|
609
|
+
}
|
|
610
|
+
const agent = agentRaw;
|
|
611
|
+
const { spawnSync } = await import('child_process');
|
|
612
|
+
const { getBinaryPath, getGlobalDefault, getVersionHomePath, listInstalledVersions } = await import('../lib/installations/versions.js');
|
|
613
|
+
const installed = listInstalledVersions(agent);
|
|
614
|
+
const version = getGlobalDefault(agent) ?? installed[installed.length - 1];
|
|
615
|
+
if (!version)
|
|
616
|
+
throw new Error(`No installed version of ${agent}. Install one with: agents add ${agent}`);
|
|
617
|
+
const bin = getBinaryPath(agent, version);
|
|
618
|
+
const home = getVersionHomePath(agent, version);
|
|
619
|
+
const result = spawnSync(bin, ['logout'], {
|
|
620
|
+
env: { ...process.env, HOME: home },
|
|
621
|
+
stdio: 'inherit',
|
|
622
|
+
});
|
|
623
|
+
if (result.error)
|
|
624
|
+
throw result.error;
|
|
625
|
+
if ((result.status ?? 1) !== 0) {
|
|
626
|
+
throw new Error(`${agent} logout exited ${result.status ?? 'null'}. If this harness has no logout verb, sign out from its own UI.`);
|
|
627
|
+
}
|
|
628
|
+
console.log(chalk.green(`Signed out native ${agent} login (${version}).`));
|
|
581
629
|
});
|
|
582
|
-
if (result.error)
|
|
583
|
-
throw result.error;
|
|
584
|
-
if ((result.status ?? 1) !== 0) {
|
|
585
|
-
throw new Error(`${agent} logout exited ${result.status ?? 'null'}. If this harness has no logout verb, sign out from its own UI.`);
|
|
586
|
-
}
|
|
587
|
-
console.log(chalk.green(`Signed out native ${agent} login (${version}).`));
|
|
588
630
|
});
|
|
589
631
|
accounts.command('sync <name> [device]')
|
|
590
632
|
.description('Copy one provider account bundle to a worker device')
|
|
591
633
|
.option('--device <device>', 'Deprecated destination form; use the positional device')
|
|
592
634
|
.option('--force', 'Replace matching keys on the destination')
|
|
593
|
-
.action(async (name, deviceArg, o) => {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
635
|
+
.action(async (name, deviceArg, o, command) => {
|
|
636
|
+
await runAccountsAction(command, async () => {
|
|
637
|
+
const device = deviceArg ?? o.device;
|
|
638
|
+
if (!device)
|
|
639
|
+
throw new Error('Missing destination device. Usage: agents accounts sync <account> <device>.');
|
|
640
|
+
const account = findAccount(name);
|
|
641
|
+
if (!account)
|
|
642
|
+
throw new Error(`Unknown provider account '${name}'.`);
|
|
643
|
+
const sshTarget = await resolveHostSshTarget(device);
|
|
644
|
+
assertCredentialTransportHostPinned(sshTarget);
|
|
645
|
+
const remoteBackend = resolveRemoteOsSync(device) === 'win32' ? 'keychain' : 'file';
|
|
646
|
+
const literalValues = {
|
|
647
|
+
ACCOUNT_ID: account.id,
|
|
648
|
+
PROVIDER: account.provider,
|
|
649
|
+
AUTH_TYPE: account.auth,
|
|
650
|
+
...(account.baseUrl ? { BASE_URL: account.baseUrl } : {}),
|
|
651
|
+
};
|
|
652
|
+
const result = pushBundleToHost(account.name, device, {
|
|
653
|
+
remoteBackend,
|
|
654
|
+
force: o.force,
|
|
655
|
+
operation: 'accounts sync',
|
|
656
|
+
policyNever: true,
|
|
657
|
+
agentOnly: false,
|
|
658
|
+
literalValues,
|
|
659
|
+
});
|
|
660
|
+
if (!result.ok)
|
|
661
|
+
throw new Error(`${result.message}\nRetry: agents accounts sync ${account.name} ${device}${o.force ? ' --force' : ''}`);
|
|
662
|
+
console.log(chalk.green(`${account.name} synced to ${device} (${result.keyCount} keys, ${remoteBackend} backend, policy never).`));
|
|
616
663
|
});
|
|
617
|
-
if (!result.ok)
|
|
618
|
-
throw new Error(`${result.message}\nRetry: agents accounts sync ${account.name} ${device}${o.force ? ' --force' : ''}`);
|
|
619
|
-
console.log(chalk.green(`${account.name} synced to ${device} (${result.keyCount} keys, ${remoteBackend} backend, policy never).`));
|
|
620
664
|
});
|
|
621
665
|
setHelpSections(switchCmd, {
|
|
622
666
|
examples: `agents accounts switch claude
|
|
@@ -626,7 +670,9 @@ agents run claude`,
|
|
|
626
670
|
notes: 'Switch writes the existing per-harness default (same binding as set-default). Rotation already honors it. Pass an account name to skip the picker. Native name/attach is only for harnesses agents-cli can isolate (claude, codex, grok); provider add is unrestricted.',
|
|
627
671
|
});
|
|
628
672
|
setHelpSections(accounts, {
|
|
629
|
-
examples: `agents accounts
|
|
673
|
+
examples: `agents accounts mint claude
|
|
674
|
+
agents accounts mint claude --account work --email ada@example.com --fleet
|
|
675
|
+
agents accounts add work --provider anthropic --auth setup-token
|
|
630
676
|
agents accounts add openrouter-work --provider openrouter --auth api-key --from-secrets openrouter.ai:OPENROUTER_API_KEY
|
|
631
677
|
agents accounts set-key work
|
|
632
678
|
agents accounts name claude@2.1.220 work
|
|
@@ -639,6 +685,6 @@ agents accounts set-default claude work
|
|
|
639
685
|
agents accounts sync openrouter-work yosemite-s0
|
|
640
686
|
agents run claude --account work
|
|
641
687
|
agents accounts logout claude`,
|
|
642
|
-
notes: 'Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. `accounts switch` is the fast picker over the same default `set-default` writes. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Synced vault unlock is `agents secrets vault unlock`.',
|
|
688
|
+
notes: 'Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. `accounts mint claude` drives `claude setup-token` and seeds both the named account and the reserved file-based auth bundle (same command as `agents auth mint`). `accounts switch` is the fast picker over the same default `set-default` writes. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Synced vault unlock is `agents secrets vault unlock`.',
|
|
643
689
|
});
|
|
644
690
|
}
|
package/dist/commands/apply.js
CHANGED
|
@@ -18,7 +18,8 @@ import { isHostPinned, managedKnownHostsPath } from '../lib/devices/known-hosts.
|
|
|
18
18
|
import { ensureDevicesRegistered } from '../lib/devices/sync.js';
|
|
19
19
|
import { readFleetFile, resolveDesired } from '../lib/fleet/manifest.js';
|
|
20
20
|
import { snapshotAuth } from '../lib/fleet/auth-sync.js';
|
|
21
|
-
import {
|
|
21
|
+
import { AUTH_BUNDLE_NAME } from '../lib/secrets/bundles.js';
|
|
22
|
+
import { agentIdOf, diffFleet, probeDevice, runFleetApply, pool, sourceHome, expandAllSpecs, rosterNeedsVersions, fleetSecretsBundles, } from '../lib/fleet/apply.js';
|
|
22
23
|
import { listInstalledVersions } from '../lib/installations/versions.js';
|
|
23
24
|
/** Version of the running agents-cli — the fleet target version. */
|
|
24
25
|
function localCliVersion() {
|
|
@@ -210,7 +211,9 @@ async function runApply(opts) {
|
|
|
210
211
|
// One extra `secrets list --json` per device, and only when it can change the
|
|
211
212
|
// plan: the manifest declares bundles AND provisioning is on. Same cost
|
|
212
213
|
// discipline as `withVersions` — a fleet that uses no bundles never pays it.
|
|
213
|
-
const
|
|
214
|
+
const secretsBundles = fleetSecretsBundles(manifest.secrets?.bundles);
|
|
215
|
+
const withSecrets = (opts.provisionSecrets === true && secretsBundles.length > 0)
|
|
216
|
+
|| secretsBundles.includes(AUTH_BUNDLE_NAME);
|
|
214
217
|
console.log(chalk.gray(`Probing ${desired.length} device(s)…`));
|
|
215
218
|
const probeList = await pool(desired, 6, async (d) => probeDevice(nameToProfile.get(d.device), { withVersions, withSecrets }));
|
|
216
219
|
const probes = new Map(probeList.map((p) => [p.device, p]));
|
|
@@ -218,7 +221,7 @@ async function runApply(opts) {
|
|
|
218
221
|
let plan = diffFleet(desired, probes, {
|
|
219
222
|
targetCliVersion,
|
|
220
223
|
sourceAuth,
|
|
221
|
-
secretsBundles
|
|
224
|
+
secretsBundles,
|
|
222
225
|
provisionSecrets: opts.provisionSecrets === true,
|
|
223
226
|
forceSecrets: opts.force === true,
|
|
224
227
|
// Portable secret values only ever go to a host whose key is already pinned.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared `mint` subcommand registered on both `agents auth` (the ticket
|
|
3
|
+
* surface) and `agents accounts` (the credential noun). Implementation lives
|
|
4
|
+
* in lib/auth-mint.ts so neither command file owns the seed path.
|
|
5
|
+
*/
|
|
6
|
+
import type { Command } from 'commander';
|
|
7
|
+
import { type MintDriveHooks } from '../lib/auth-mint.js';
|
|
8
|
+
export declare function registerMintCommand(parent: Command, testHooks?: MintDriveHooks): Command;
|