@phnx-labs/agents-cli 1.22.84 → 1.22.86
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 +96 -0
- package/README.md +80 -53
- package/dist/bootstrap.d.ts +7 -6
- package/dist/bootstrap.js +20 -27
- package/dist/cli/command-registry.js +5 -1
- package/dist/commands/accounts.d.ts +6 -13
- package/dist/commands/accounts.js +330 -143
- package/dist/commands/alias.js +7 -3
- package/dist/commands/apply.js +1 -1
- package/dist/commands/auth-mint.d.ts +7 -3
- package/dist/commands/auth-mint.js +21 -9
- package/dist/commands/auth.js +2 -3
- package/dist/commands/browser.js +10 -10
- package/dist/commands/daemon.d.ts +7 -3
- package/dist/commands/daemon.js +40 -25
- package/dist/commands/doctor.js +28 -2
- package/dist/commands/exec.js +87 -39
- package/dist/commands/fleet-capture.js +21 -2
- package/dist/commands/harness-wizard.js +2 -2
- package/dist/commands/lease.js +7 -12
- package/dist/commands/profiles.d.ts +2 -2
- package/dist/commands/profiles.js +20 -13
- package/dist/commands/repo.js +3 -3
- package/dist/commands/run-account-picker.d.ts +3 -3
- package/dist/commands/run-account-picker.js +29 -30
- package/dist/commands/secrets-passthrough.d.ts +20 -0
- package/dist/commands/secrets-passthrough.js +43 -0
- package/dist/commands/setup-accounts.d.ts +3 -2
- package/dist/commands/setup-accounts.js +4 -4
- package/dist/commands/setup-secrets.d.ts +16 -21
- package/dist/commands/setup-secrets.js +43 -224
- package/dist/commands/ssh.js +2 -2
- package/dist/commands/sync.js +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.js +43 -40
- package/dist/commands/versions.d.ts +11 -11
- package/dist/commands/versions.js +131 -83
- package/dist/commands/view.d.ts +1 -9
- package/dist/commands/view.js +41 -55
- package/dist/commands/webhook.js +5 -5
- package/dist/commands/workflows.js +2 -1
- package/dist/index.d.ts +12 -12
- package/dist/index.js +13 -42
- package/dist/lib/account-capabilities.d.ts +1 -1
- package/dist/lib/account-capabilities.js +1 -1
- package/dist/lib/account-catalog.d.ts +121 -6
- package/dist/lib/account-catalog.js +421 -7
- package/dist/lib/account-registry.d.ts +8 -30
- package/dist/lib/account-registry.js +36 -79
- package/dist/lib/account-schema.d.ts +1 -1
- package/dist/lib/account-schema.js +1 -1
- package/dist/lib/accounting/account-pool-collect.js +16 -4
- package/dist/lib/accounting/rotate.d.ts +20 -7
- package/dist/lib/accounting/rotate.js +89 -13
- package/dist/lib/accounting/usage.js +11 -16
- package/dist/lib/accounts/add.d.ts +138 -0
- package/dist/lib/accounts/add.js +651 -0
- package/dist/lib/accounts/migrate.d.ts +117 -0
- package/dist/lib/accounts/migrate.js +536 -0
- package/dist/lib/accounts/slots.d.ts +13 -0
- package/dist/lib/accounts/slots.js +100 -0
- package/dist/lib/agent-spec/agents.d.ts +16 -0
- package/dist/lib/agent-spec/agents.js +38 -4
- package/dist/lib/app-bundle-install.js +5 -4
- package/dist/lib/auth-health.d.ts +3 -0
- package/dist/lib/auth-health.js +11 -0
- package/dist/lib/auth-mint.d.ts +52 -16
- package/dist/lib/auth-mint.js +117 -39
- package/dist/lib/browser/chrome.d.ts +1 -1
- package/dist/lib/browser/chrome.js +5 -5
- package/dist/lib/byok-usage.js +3 -3
- package/dist/lib/claude-account-token.d.ts +47 -3
- package/dist/lib/claude-account-token.js +172 -50
- package/dist/lib/cloud/antigravity.js +4 -4
- package/dist/lib/cloud/cursor.js +4 -4
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +6 -6
- package/dist/lib/crabbox/runtimes.d.ts +3 -3
- package/dist/lib/crabbox/runtimes.js +5 -5
- package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
- package/dist/lib/daemon/account-state-daemon-service.js +199 -3
- package/dist/lib/daemon/auth-sync-service.js +32 -1
- package/dist/lib/daemon/daemon.d.ts +6 -16
- package/dist/lib/daemon/daemon.js +52 -97
- package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
- package/dist/lib/daemon/daemon.test-fixture.js +22 -40
- package/dist/lib/daemon/harness-update-service.d.ts +1 -1
- package/dist/lib/daemon/harness-update-service.js +1 -1
- package/dist/lib/daemon/runner.d.ts +15 -3
- package/dist/lib/daemon/runner.js +49 -24
- package/dist/lib/daemon-health.d.ts +1 -2
- package/dist/lib/daemon-health.js +7 -6
- package/dist/lib/daemon-services.d.ts +1 -1
- package/dist/lib/daemon-services.js +1 -11
- package/dist/lib/daemon-webhooks.d.ts +8 -7
- package/dist/lib/daemon-webhooks.js +10 -9
- package/dist/lib/device-config.js +1 -1
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/harness-inventory.d.ts +39 -0
- package/dist/lib/devices/harness-inventory.js +126 -4
- package/dist/lib/doctor-diff.js +2 -1
- package/dist/lib/exec-account-home.d.ts +38 -0
- package/dist/lib/exec-account-home.js +164 -0
- package/dist/lib/exec.d.ts +30 -0
- package/dist/lib/exec.js +101 -31
- package/dist/lib/fleet/apply.d.ts +2 -2
- package/dist/lib/fleet/apply.js +4 -4
- package/dist/lib/fleet/auth-sync.js +4 -3
- package/dist/lib/fleet-shared-repo-sync.js +6 -9
- package/dist/lib/harness/adapter.d.ts +7 -7
- package/dist/lib/harness/adapter.js +10 -6
- package/dist/lib/harness/adapters/grok.js +8 -3
- package/dist/lib/harness/adapters/muse.js +1 -1
- package/dist/lib/harness/adapters/opencode.js +12 -3
- package/dist/lib/harness-auth-capabilities.d.ts +61 -0
- package/dist/lib/harness-auth-capabilities.js +52 -0
- package/dist/lib/helper-versions.d.ts +6 -4
- package/dist/lib/helper-versions.js +5 -4
- package/dist/lib/hosts/credential-transport.d.ts +10 -0
- package/dist/lib/hosts/credential-transport.js +37 -0
- package/dist/lib/hosts/dispatch.d.ts +7 -0
- package/dist/lib/hosts/dispatch.js +13 -6
- package/dist/lib/identity/client.d.ts +3 -3
- package/dist/lib/identity/client.js +3 -3
- package/dist/lib/installations/index.d.ts +1 -1
- package/dist/lib/installations/index.js +1 -1
- package/dist/lib/installations/migrate.js +9 -0
- package/dist/lib/installations/resolve.js +1 -1
- package/dist/lib/installations/shims.d.ts +21 -4
- package/dist/lib/installations/shims.js +86 -5
- package/dist/lib/installations/store.d.ts +43 -0
- package/dist/lib/installations/store.js +75 -7
- package/dist/lib/installations/versions.js +1 -1
- package/dist/lib/menubar/install-menubar.d.ts +3 -3
- package/dist/lib/menubar/install-menubar.js +3 -3
- package/dist/lib/native-accounts.d.ts +35 -0
- package/dist/lib/native-accounts.js +41 -0
- package/dist/lib/net-close.d.ts +11 -0
- package/dist/lib/net-close.js +25 -0
- package/dist/lib/openclaw-keychain.js +27 -1
- package/dist/lib/profiles.js +22 -9
- package/dist/lib/project-resources.js +1 -1
- package/dist/lib/reserved-stores.d.ts +53 -0
- package/dist/lib/reserved-stores.js +120 -0
- package/dist/lib/secrets-client.d.ts +199 -0
- package/dist/lib/secrets-client.js +718 -0
- package/dist/lib/secrets-policy.d.ts +235 -0
- package/dist/lib/secrets-policy.js +505 -0
- package/dist/lib/secrets-types.d.ts +188 -0
- package/dist/lib/secrets-types.js +24 -0
- package/dist/lib/self-heal/checks/shims.js +5 -3
- package/dist/lib/service-manifest.js +3 -4
- package/dist/lib/session/db.d.ts +18 -0
- package/dist/lib/session/db.js +47 -14
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/sha256-asset.d.ts +11 -16
- package/dist/lib/sha256-asset.js +11 -16
- package/dist/lib/share/config.js +14 -5
- package/dist/lib/signin-badge.d.ts +26 -0
- package/dist/lib/signin-badge.js +42 -0
- package/dist/lib/staleness/detectors/workflows.js +3 -105
- package/dist/lib/staleness/writers/workflows.js +2 -1
- package/dist/lib/state.d.ts +7 -4
- package/dist/lib/state.js +18 -7
- package/dist/lib/sync-umbrella.js +2 -2
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/types.d.ts +74 -33
- package/dist/lib/view-types.d.ts +6 -3
- package/dist/lib/workflows-registry.d.ts +71 -0
- package/dist/lib/workflows-registry.js +280 -0
- package/dist/lib/workflows.d.ts +21 -21
- package/dist/lib/workflows.js +12 -327
- package/package.json +2 -3
- package/scripts/postinstall.js +35 -30
- package/dist/commands/secrets-import.d.ts +0 -18
- package/dist/commands/secrets-import.js +0 -74
- package/dist/commands/secrets-migrate.d.ts +0 -25
- package/dist/commands/secrets-migrate.js +0 -334
- package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
- package/dist/commands/secrets-rotate-passphrase.js +0 -96
- package/dist/commands/secrets-sync.d.ts +0 -11
- package/dist/commands/secrets-sync.js +0 -153
- package/dist/commands/secrets-vault.d.ts +0 -10
- package/dist/commands/secrets-vault.js +0 -130
- package/dist/commands/secrets.d.ts +0 -212
- package/dist/commands/secrets.js +0 -3030
- package/dist/lib/accounts/connect.d.ts +0 -176
- package/dist/lib/accounts/connect.js +0 -453
- package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
- package/dist/lib/daemon/keychain-reap-service.js +0 -32
- package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
- package/dist/lib/daemon/secrets-broker-service.js +0 -51
- package/dist/lib/secrets/agent.d.ts +0 -358
- package/dist/lib/secrets/agent.js +0 -1291
- package/dist/lib/secrets/audit.d.ts +0 -46
- package/dist/lib/secrets/audit.js +0 -101
- package/dist/lib/secrets/bundles.d.ts +0 -290
- package/dist/lib/secrets/bundles.js +0 -1547
- package/dist/lib/secrets/download-keychain.d.ts +0 -47
- package/dist/lib/secrets/download-keychain.js +0 -70
- package/dist/lib/secrets/drivers/rush.d.ts +0 -14
- package/dist/lib/secrets/drivers/rush.js +0 -90
- package/dist/lib/secrets/fallback.d.ts +0 -48
- package/dist/lib/secrets/fallback.js +0 -48
- package/dist/lib/secrets/filestore.d.ts +0 -222
- package/dist/lib/secrets/filestore.js +0 -1099
- package/dist/lib/secrets/headless.d.ts +0 -43
- package/dist/lib/secrets/headless.js +0 -56
- package/dist/lib/secrets/icloud-import.d.ts +0 -79
- package/dist/lib/secrets/icloud-import.js +0 -206
- package/dist/lib/secrets/index.d.ts +0 -451
- package/dist/lib/secrets/index.js +0 -1568
- package/dist/lib/secrets/install-helper.d.ts +0 -72
- package/dist/lib/secrets/install-helper.js +0 -245
- package/dist/lib/secrets/lease.d.ts +0 -25
- package/dist/lib/secrets/lease.js +0 -44
- package/dist/lib/secrets/linux.d.ts +0 -77
- package/dist/lib/secrets/linux.js +0 -393
- package/dist/lib/secrets/list-filter.d.ts +0 -109
- package/dist/lib/secrets/list-filter.js +0 -261
- package/dist/lib/secrets/mcp.d.ts +0 -93
- package/dist/lib/secrets/mcp.js +0 -211
- package/dist/lib/secrets/profiles.d.ts +0 -10
- package/dist/lib/secrets/profiles.js +0 -13
- package/dist/lib/secrets/push.d.ts +0 -133
- package/dist/lib/secrets/push.js +0 -273
- package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
- package/dist/lib/secrets/rc-hygiene.js +0 -143
- package/dist/lib/secrets/read-backoff.d.ts +0 -27
- package/dist/lib/secrets/read-backoff.js +0 -64
- package/dist/lib/secrets/reaper.d.ts +0 -90
- package/dist/lib/secrets/reaper.js +0 -243
- package/dist/lib/secrets/remote.d.ts +0 -152
- package/dist/lib/secrets/remote.js +0 -344
- package/dist/lib/secrets/reserved-sync.d.ts +0 -66
- package/dist/lib/secrets/reserved-sync.js +0 -147
- package/dist/lib/secrets/scope.d.ts +0 -26
- package/dist/lib/secrets/scope.js +0 -29
- package/dist/lib/secrets/session-store.d.ts +0 -107
- package/dist/lib/secrets/session-store.js +0 -342
- package/dist/lib/secrets/sync-backend.d.ts +0 -48
- package/dist/lib/secrets/sync-backend.js +0 -13
- package/dist/lib/secrets/sync-commands.d.ts +0 -21
- package/dist/lib/secrets/sync-commands.js +0 -21
- package/dist/lib/secrets/sync.d.ts +0 -48
- package/dist/lib/secrets/sync.js +0 -237
- package/dist/lib/secrets/unlock-hints.d.ts +0 -27
- package/dist/lib/secrets/unlock-hints.js +0 -36
- package/dist/lib/secrets/usage-db.d.ts +0 -46
- package/dist/lib/secrets/usage-db.js +0 -96
- package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
- package/dist/lib/secrets/vault-age-helper.js +0 -34
- package/dist/lib/secrets/vault.d.ts +0 -49
- package/dist/lib/secrets/vault.js +0 -399
- package/dist/lib/secrets/windows.d.ts +0 -81
- package/dist/lib/secrets/windows.js +0 -556
- package/scripts/install-helper.js +0 -97
- /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
- /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
|
@@ -5,6 +5,7 @@ import { password, select } from '@inquirer/prompts';
|
|
|
5
5
|
import { readClaudeAccountEmail, resolveClaudeSetupToken, resolveClaudeSetupTokenForEmail, seedClaudeWorkerHomeIdentity } from '../lib/claude-account-token.js';
|
|
6
6
|
import { setHelpSections } from '../lib/help.js';
|
|
7
7
|
import { readMeta, updateMeta } from '../lib/state.js';
|
|
8
|
+
import { machineId } from '../lib/machine-id.js';
|
|
8
9
|
import { ALL_AGENT_IDS, getAccountInfo, resolveAgentName } from '../lib/agents.js';
|
|
9
10
|
import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
|
|
10
11
|
import { assertNativeAccountNameable, nativeAccountCapability, nativeIdentityKey } from '../lib/account-capabilities.js';
|
|
@@ -12,20 +13,56 @@ import { collectRunCandidates } from '../lib/accounting/rotate.js';
|
|
|
12
13
|
import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
|
|
13
14
|
import { buildSwitchAccountChoices, formatAccountLimits, pickSwitchAccount } from './run-account-picker.js';
|
|
14
15
|
import { profileExists, readProfile } from '../lib/profiles.js';
|
|
15
|
-
import {
|
|
16
|
-
import { assertCredentialTransportHostPinned, resolveHostSshTarget } from '../lib/secrets/remote.js';
|
|
16
|
+
import { assertCredentialTransportHostPinned, resolveHostSshTarget } from '../lib/hosts/credential-transport.js';
|
|
17
17
|
import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
|
|
18
|
-
import {
|
|
19
|
-
import { collectNativeHomeRows, loadAccountCatalog } from '../lib/account-catalog.js';
|
|
20
|
-
import {
|
|
18
|
+
import { renderAccountFleetMatrix } from '../lib/devices/harness-inventory.js';
|
|
19
|
+
import { accountListJson, collectNativeHomeRows, listDevicesWithoutAccountVerdicts, loadAccountCatalog, renderAccountRows, secretsUnavailableNote, } from '../lib/account-catalog.js';
|
|
20
|
+
import { addRefusal, runAdd, runLogin, supportedAddHarnesses } from '../lib/accounts/add.js';
|
|
21
|
+
import { applyAccountMigration, formatMigrationPlan, planAccountMigration } from '../lib/accounts/migrate.js';
|
|
22
|
+
import { isSymlinkAdoptedHarness } from '../lib/installations/shims.js';
|
|
23
|
+
import { ensureAdoptedDefaultRepoint } from '../lib/exec-account-home.js';
|
|
21
24
|
import { acquireAuthOperationLock } from '../lib/accounts/auth-operation-lock.js';
|
|
22
|
-
import { readAndResolveBundleEnv } from '../lib/secrets
|
|
25
|
+
import { isSecretsClientError, pushBundleToHost, readAndResolveBundleEnv, readBundle } from '../lib/secrets-client.js';
|
|
23
26
|
import { getAccountProvider, listAccountProviders, providerAuthenticatesHarness } from '../lib/account-provider-registry.js';
|
|
24
27
|
import { accountBindings, addAccount, addNativeAccount, assertUnambiguousNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, labelNativeAccount, listNativeAccounts, nativeAccountHome, parseAccountSelector, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
|
|
25
28
|
import { registerMintCommand } from './auth-mint.js';
|
|
26
|
-
/** Comma-joined list of harnesses `accounts
|
|
27
|
-
function
|
|
28
|
-
return
|
|
29
|
+
/** Comma-joined list of harnesses `accounts add` can drive today. */
|
|
30
|
+
function addSupportedList() {
|
|
31
|
+
return supportedAddHarnesses().join(', ');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Legacy-verb retirement: the command is created with `{ hidden: true }` (help
|
|
35
|
+
* no longer lists it) and this hook prints the pointer to its replacement when
|
|
36
|
+
* it runs. Verbs keep executing for one release.
|
|
37
|
+
*/
|
|
38
|
+
function retireVerb(cmd, pointer) {
|
|
39
|
+
cmd.hook('preAction', () => {
|
|
40
|
+
console.error(chalk.gray(`'agents accounts ${cmd.name()}' is replaced by '${pointer}' — still works this release.`));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Shared result printer for `accounts add` / `accounts login` / hidden `connect`. */
|
|
44
|
+
function printAddResult(result, json) {
|
|
45
|
+
if (json) {
|
|
46
|
+
console.log(JSON.stringify(result, null, 2));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const who = result.email ?? result.identityKey;
|
|
50
|
+
const verb = result.mode === 'reconnect' ? 'Re-authenticated' : 'Added';
|
|
51
|
+
console.log(chalk.green(`${verb} ${result.agent}#${result.name} (${who}).`));
|
|
52
|
+
const credentialLine = {
|
|
53
|
+
minted: 'worker credential minted for workers',
|
|
54
|
+
stored: 'worker credential stored for workers',
|
|
55
|
+
kept: 'worker credential unchanged',
|
|
56
|
+
skipped: 'no worker credential — runs on this device only',
|
|
57
|
+
'per-device': 'per-device login — each box signs in for itself',
|
|
58
|
+
};
|
|
59
|
+
console.log(chalk.gray(` ${credentialLine[result.workerCredential]} · provisioning: ${result.provisioning}`));
|
|
60
|
+
if (result.becameDefault)
|
|
61
|
+
console.log(chalk.gray(` Default account for ${result.agent} (first one added).`));
|
|
62
|
+
for (const warning of result.warnings)
|
|
63
|
+
console.log(chalk.yellow(` warning: ${warning}`));
|
|
64
|
+
console.log(chalk.gray(` The native OAuth login stays owned by the harness in its slot; nothing was copied.`));
|
|
65
|
+
console.log(chalk.gray(` Run: agents run ${result.agent}#${result.name}`));
|
|
29
66
|
}
|
|
30
67
|
function cleanCommandError(command, err) {
|
|
31
68
|
command.error(err instanceof Error ? err.message : String(err), { exitCode: 1, code: 'accounts.error' });
|
|
@@ -126,9 +163,38 @@ export function parseBundleKey(raw) {
|
|
|
126
163
|
throw new Error(`Expected bundle:key, got '${raw}'.`);
|
|
127
164
|
return { bundle: raw.slice(0, colon), key: raw.slice(colon + 1) };
|
|
128
165
|
}
|
|
129
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Copy one value out of an `agents secrets` bundle for `--from-secrets`. The
|
|
168
|
+
* standalone reports failures only as codes, so the bundle, the key, and the
|
|
169
|
+
* repair are named here — and existence is checked first, because a
|
|
170
|
+
* prompt-free (`agentOnly`) read of a bundle that does not exist reports
|
|
171
|
+
* LOCKED rather than NOT_FOUND.
|
|
172
|
+
*/
|
|
173
|
+
async function secretFromBundle(raw) {
|
|
130
174
|
const { bundle, key } = parseBundleKey(raw);
|
|
131
|
-
|
|
175
|
+
let vars;
|
|
176
|
+
try {
|
|
177
|
+
vars = (await readBundle(bundle)).vars;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (!isSecretsClientError(err, 'NOT_FOUND'))
|
|
181
|
+
throw err;
|
|
182
|
+
throw new Error(`Secrets bundle '${bundle}' not found. List bundles with 'agents secrets list'.`);
|
|
183
|
+
}
|
|
184
|
+
if (!(key in vars)) {
|
|
185
|
+
const available = Object.keys(vars).join(', ') || '(none)';
|
|
186
|
+
throw new Error(`Secrets bundle '${bundle}' has no key '${key}'. Available: ${available}.`);
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const { env } = await readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' });
|
|
190
|
+
return env[key];
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
if (!isSecretsClientError(err, 'LOCKED'))
|
|
194
|
+
throw err;
|
|
195
|
+
throw new Error(`Secrets bundle '${bundle}' is not unlocked for a prompt-free read. ` +
|
|
196
|
+
`Unlock it first with 'agents secrets unlock ${bundle}', then retry.`);
|
|
197
|
+
}
|
|
132
198
|
}
|
|
133
199
|
/** Interactive secret entry for add/set-key. Ctrl+C must not become accounts.error. */
|
|
134
200
|
async function promptAccountSecret(message) {
|
|
@@ -144,73 +210,37 @@ async function promptAccountSecret(message) {
|
|
|
144
210
|
function publicAccount(account) {
|
|
145
211
|
return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
|
|
146
212
|
}
|
|
147
|
-
async function printAccounts(json, fleet = false) {
|
|
148
|
-
|
|
149
|
-
return runDevicesAccounts({ json });
|
|
150
|
-
const records = Object.values(readAccountRegistry().accounts).sort((a, b) => a.name.localeCompare(b.name));
|
|
213
|
+
async function printAccounts(json, fleet = false, harnessRaw) {
|
|
214
|
+
const harness = harnessRaw ? parseHarness(harnessRaw) : undefined;
|
|
151
215
|
const catalog = await loadAccountCatalog();
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
216
|
+
const note = secretsUnavailableNote(catalog);
|
|
217
|
+
if (note)
|
|
218
|
+
console.error(chalk.yellow(note)); // stderr — never corrupts --json stdout
|
|
219
|
+
const native = harness ? catalog.native.filter((row) => row.agent === harness) : catalog.native;
|
|
220
|
+
const providers = harness
|
|
221
|
+
? catalog.provider.filter((row) => row.harnesses.includes(harness))
|
|
222
|
+
: catalog.provider;
|
|
156
223
|
if (json) {
|
|
157
|
-
console.log(JSON.stringify(
|
|
224
|
+
console.log(JSON.stringify(accountListJson(native, providers, harness), null, 2));
|
|
158
225
|
return;
|
|
159
226
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const byHarness = new Map();
|
|
172
|
-
for (const acct of native) {
|
|
173
|
-
const list = byHarness.get(acct.agent) ?? [];
|
|
174
|
-
list.push(acct);
|
|
175
|
-
byHarness.set(acct.agent, list);
|
|
176
|
-
}
|
|
177
|
-
const harnessW = Math.max(6, ...[...byHarness.keys()].map(h => h.length));
|
|
178
|
-
const labelW = Math.max(6, ...native.map(a => (a.name ?? '—').length));
|
|
179
|
-
const idW = Math.max(8, ...native.map(a => a.display.length));
|
|
180
|
-
let sawDefault = false;
|
|
181
|
-
for (const [harness, list] of [...byHarness.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
182
|
-
const defaultVersion = getGlobalDefault(harness);
|
|
183
|
-
list.forEach((acct, i) => {
|
|
184
|
-
const harnessCell = chalk.gray((i === 0 ? harness : '').padEnd(harnessW));
|
|
185
|
-
const rawLabel = acct.name ?? '—';
|
|
186
|
-
const labelCell = acct.name ? chalk.cyan(rawLabel.padEnd(labelW)) : chalk.gray(rawLabel.padEnd(labelW));
|
|
187
|
-
const isDefault = acct.isDefault ?? (defaultVersion ? acct.versions.includes(defaultVersion) : false);
|
|
188
|
-
if (isDefault)
|
|
189
|
-
sawDefault = true;
|
|
190
|
-
const marker = isDefault ? chalk.green('*') : ' ';
|
|
191
|
-
const state = acct.state === 'reconnect-needed' ? 'not connected here' : 'connected';
|
|
192
|
-
out.push(` ${harnessCell} ${labelCell}${marker} ${acct.display.padEnd(idW)} ${chalk.gray(state)}`);
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
if (sawDefault)
|
|
196
|
-
out.push(chalk.gray('\n * default account for that harness (used when you pass neither @version nor #label)'));
|
|
197
|
-
}
|
|
198
|
-
// Provider bundles — the durable API-key/setup-token accounts, selected with `--account <name>`.
|
|
199
|
-
out.push('');
|
|
200
|
-
out.push(chalk.bold('Provider bundles') + chalk.gray(' run <harness> --account <name>'));
|
|
201
|
-
if (!records.length) {
|
|
202
|
-
out.push(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
|
|
203
|
-
}
|
|
204
|
-
else {
|
|
205
|
-
const nameW = Math.max(4, ...records.map(a => a.name.length));
|
|
206
|
-
const provW = Math.max(8, ...records.map(a => a.provider.length));
|
|
207
|
-
const authW = Math.max(10, ...records.map(a => a.auth.length));
|
|
208
|
-
for (const account of records) {
|
|
209
|
-
const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
|
|
210
|
-
out.push(` ${chalk.cyan(account.name.padEnd(nameW))} ${account.provider.padEnd(provW)} ${account.auth.padEnd(authW)} ${present}`);
|
|
211
|
-
}
|
|
227
|
+
if (fleet) {
|
|
228
|
+
const matrixRows = native.map((row) => ({
|
|
229
|
+
harness: row.agent,
|
|
230
|
+
name: row.name,
|
|
231
|
+
identityLabel: row.identityLabel,
|
|
232
|
+
devices: row.devices,
|
|
233
|
+
}));
|
|
234
|
+
const covered = new Set(matrixRows.flatMap((row) => row.devices.map((device) => device.device)));
|
|
235
|
+
const uncoveredDevices = listDevicesWithoutAccountVerdicts().filter((device) => !covered.has(device));
|
|
236
|
+
console.log(renderAccountFleetMatrix(matrixRows, { uncoveredDevices }).join('\n'));
|
|
237
|
+
return;
|
|
212
238
|
}
|
|
213
|
-
|
|
239
|
+
console.log(renderAccountRows(native, { providers, harness, localDevice: machineId() }));
|
|
240
|
+
}
|
|
241
|
+
/** Compatibility export for tests/consumers; the canonical renderer is shared with view. */
|
|
242
|
+
export function renderAccountList(native, providers = [], opts = {}) {
|
|
243
|
+
return renderAccountRows(native, { providers, localDevice: '', ...opts });
|
|
214
244
|
}
|
|
215
245
|
function parseAuth(raw) {
|
|
216
246
|
if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
|
|
@@ -355,12 +385,19 @@ export function setDefaultAccount(agentRaw, name) {
|
|
|
355
385
|
if (account.agent !== agent) {
|
|
356
386
|
throw new Error(`Account '${account.name}' is a ${account.agent} login and cannot be the default for ${agent}.`);
|
|
357
387
|
}
|
|
358
|
-
|
|
388
|
+
// Adopted harnesses isolate by slot + symlink, not a version home, so the
|
|
389
|
+
// nameable-by-version-home gate does not apply to `accounts default`.
|
|
390
|
+
if (!isSymlinkAdoptedHarness(agent))
|
|
391
|
+
assertNativeAccountNameable(account.agent);
|
|
359
392
|
}
|
|
360
393
|
// Reference by NAME, not id: defaults sync fleet-wide with `agents repo push/pull`
|
|
361
394
|
// while account ids are minted per-device, so an id ref breaks on every other
|
|
362
395
|
// machine ("Unknown account '<uuid>'"). Names are the portable handle — the
|
|
363
396
|
// registry resolves both, and existing uuid entries still resolve.
|
|
397
|
+
// Repoint first: a written default whose symlink still points at another
|
|
398
|
+
// account is a wrong home that looks like success.
|
|
399
|
+
if (account.kind === 'native')
|
|
400
|
+
ensureAdoptedDefaultRepoint(agent, account, readMeta());
|
|
364
401
|
updateMeta(meta => ({ ...meta, accounts: { ...meta.accounts, defaults: { ...meta.accounts?.defaults, [agent]: account.name } } }));
|
|
365
402
|
return { agent, account };
|
|
366
403
|
}
|
|
@@ -410,7 +447,7 @@ export async function runAccountsSwitch(harness, accountName, opts) {
|
|
|
410
447
|
return;
|
|
411
448
|
}
|
|
412
449
|
if (!isInteractiveTerminal()) {
|
|
413
|
-
throw new Error(`Selecting a ${agent} account requires an interactive terminal.\nUse: agents accounts
|
|
450
|
+
throw new Error(`Selecting a ${agent} account requires an interactive terminal.\nUse: agents accounts default ${agent} <account>`);
|
|
414
451
|
}
|
|
415
452
|
const selected = await pickSwitchAccount(agent, rows);
|
|
416
453
|
if (!selected)
|
|
@@ -498,69 +535,221 @@ export async function resolveLogoutTarget(target) {
|
|
|
498
535
|
throw new Error(`Unknown target '${target}'. Pass a native harness (claude, codex, …), <harness>@<label>, <harness>#<account>, or a native account name. Provider API-key accounts use \`agents accounts remove\`.`);
|
|
499
536
|
}
|
|
500
537
|
export function registerAccountsCommand(program) {
|
|
501
|
-
const accounts = program.command('accounts').description('Browse
|
|
538
|
+
const accounts = program.command('accounts').description('Browse and manage harness accounts')
|
|
502
539
|
.option('--json', 'Machine-readable account metadata')
|
|
503
|
-
.option('--fleet', 'Show
|
|
540
|
+
.option('--fleet', 'Show accounts as rows and devices as columns')
|
|
504
541
|
.action(async (o, command) => {
|
|
505
542
|
await runAccountsAction(command, () => printAccounts(!!o.json, !!o.fleet));
|
|
506
543
|
});
|
|
507
|
-
accounts.command('list
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
.
|
|
514
|
-
.action(async (harness, name, o, command) => {
|
|
515
|
-
await runAccountsAction(command, async () => {
|
|
516
|
-
const agent = parseHarness(harness);
|
|
517
|
-
const reason = connectRefusal(agent);
|
|
518
|
-
if (reason)
|
|
519
|
-
throw new Error(reason);
|
|
520
|
-
const result = await runConnect(agent, name, {
|
|
521
|
-
meta: readMeta(),
|
|
522
|
-
onProgress: (m) => console.log(chalk.gray(` ${m}`)),
|
|
523
|
-
});
|
|
524
|
-
if (o.json || command.optsWithGlobals().json)
|
|
525
|
-
return console.log(JSON.stringify(result, null, 2));
|
|
526
|
-
const who = result.email ?? result.identityKey;
|
|
527
|
-
const named = result.name ? `'${result.name}' ` : '';
|
|
528
|
-
const release = result.releaseVersion ? ` [${result.releaseVersion}]` : '';
|
|
529
|
-
console.log(chalk.green(`${result.mode === 'reconnect' ? 'Reconnected' : 'Connected'} ${agent} account ${named}(${who}) in ${agent}@${result.label}${release}.`));
|
|
530
|
-
console.log(chalk.gray(' The native OAuth login stays owned by the harness in that home; nothing was copied.'));
|
|
531
|
-
});
|
|
544
|
+
const listCmd = accounts.command('list [harness]')
|
|
545
|
+
.description('List accounts with authentication verdict, device coverage, usage, and exact repair command')
|
|
546
|
+
.option('--json', 'Machine-readable account metadata')
|
|
547
|
+
.option('--fleet', 'Show accounts as rows and devices as columns')
|
|
548
|
+
.action(async (harness, o, command) => {
|
|
549
|
+
const globals = command.optsWithGlobals();
|
|
550
|
+
await runAccountsAction(command, () => printAccounts(!!(o.json || globals.json), !!(o.fleet || globals.fleet), harness));
|
|
532
551
|
});
|
|
533
|
-
setHelpSections(
|
|
534
|
-
examples: `agents accounts
|
|
535
|
-
agents accounts
|
|
536
|
-
agents accounts
|
|
537
|
-
agents
|
|
538
|
-
notes:
|
|
552
|
+
setHelpSections(listCmd, {
|
|
553
|
+
examples: `agents accounts list
|
|
554
|
+
agents accounts list claude
|
|
555
|
+
agents accounts list --json
|
|
556
|
+
agents accounts list --fleet`,
|
|
557
|
+
notes: 'One row per account per harness. STATE is the daemon verdict. WHERE is `this box` when only this device reports (peers on an older release publish no slot verdicts), else live-device coverage. USAGE is the bar + percent. FIX is the exact repair command — empty when there is none. Reserved credential stores are not listed here; `agents secrets` is the place those show.',
|
|
539
558
|
});
|
|
540
|
-
accounts
|
|
541
|
-
|
|
542
|
-
.
|
|
543
|
-
.
|
|
544
|
-
.option('--
|
|
545
|
-
.option('--
|
|
546
|
-
.
|
|
559
|
+
registerMintCommand(accounts, undefined, { hidden: true });
|
|
560
|
+
const addCmd = accounts.command('add <target> [name]')
|
|
561
|
+
.description('Add an account. Harness form: add <harness> [name] runs the native login in a fresh credential slot and provisions workers. Provider form: add <name> --provider <p> --auth <t> stores a durable credential.')
|
|
562
|
+
.option('--api-key <key>', 'Worker credential for api-key harnesses (codex/grok/cursor/opencode); prompted when omitted')
|
|
563
|
+
.option('--per-device', 'codex: ChatGPT-plan account — no API key stored; workers log in per box')
|
|
564
|
+
.option('--no-worker-token', 'Skip minting/collecting the worker credential (this device only)')
|
|
565
|
+
.option('--provider <provider>', `Provider form: credential provider (${listAccountProviders().join(', ')})`)
|
|
566
|
+
.option('--auth <type>', 'Provider form: api-key | setup-token | bearer-token')
|
|
567
|
+
.option('--base-url <url>', 'Provider form: optional endpoint override stored with the account')
|
|
568
|
+
.option('--from-secrets <bundle:key>', 'Provider form: import from an existing agents secrets entry')
|
|
569
|
+
.option('--json', 'Machine-readable result (never includes a credential)')
|
|
570
|
+
.action(async (target, name, o, command) => {
|
|
547
571
|
await runAccountsAction(command, async () => {
|
|
572
|
+
const json = !!(o.json || command.optsWithGlobals().json);
|
|
573
|
+
const agent = resolveAgentName(target);
|
|
574
|
+
const providerFlags = o.provider !== undefined || o.auth !== undefined || o.baseUrl !== undefined || o.fromSecrets !== undefined;
|
|
575
|
+
if (agent) {
|
|
576
|
+
// Harness form: an account is a credential slot, not a bundle.
|
|
577
|
+
if (providerFlags) {
|
|
578
|
+
throw new Error(`'${target}' is a harness id — mixing the harness and provider forms is ambiguous. `
|
|
579
|
+
+ `Harness form: agents accounts add ${agent} [name]. `
|
|
580
|
+
+ `Provider form: agents accounts add <non-harness-name> --provider <p> --auth <t>.`);
|
|
581
|
+
}
|
|
582
|
+
const reason = addRefusal(agent);
|
|
583
|
+
if (reason)
|
|
584
|
+
throw new Error(reason);
|
|
585
|
+
const result = await runAdd(agent, name, {
|
|
586
|
+
meta: readMeta(),
|
|
587
|
+
onProgress: (m) => { if (!json)
|
|
588
|
+
console.log(chalk.gray(` ${m}`)); },
|
|
589
|
+
apiKey: o.apiKey,
|
|
590
|
+
noWorkerToken: o.workerToken === false,
|
|
591
|
+
perDevice: o.perDevice,
|
|
592
|
+
});
|
|
593
|
+
printAddResult(result, json);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
// Provider form (the pre-v2 `accounts add <name> --provider …`).
|
|
597
|
+
if (o.apiKey !== undefined || o.perDevice || o.workerToken === false) {
|
|
598
|
+
throw new Error(`'${target}' is not a harness id — --api-key/--per-device/--no-worker-token belong to the harness form: agents accounts add <harness> [name].`);
|
|
599
|
+
}
|
|
600
|
+
if (!o.provider || !o.auth) {
|
|
601
|
+
throw new Error(`'${target}' is not a harness id. Store a provider credential with: agents accounts add ${target} --provider <provider> --auth <type>. `
|
|
602
|
+
+ `Add a harness login with: agents accounts add <harness> [name].`);
|
|
603
|
+
}
|
|
548
604
|
const auth = parseAuth(o.auth);
|
|
549
605
|
const provider = getAccountProvider(o.provider);
|
|
550
606
|
if (!provider.authKinds.includes(auth))
|
|
551
607
|
throw new Error(`Provider '${provider.provider}' does not support ${auth}. Supported: ${provider.authKinds.join(', ')}.`);
|
|
552
608
|
const secret = o.fromSecrets
|
|
553
|
-
? secretFromBundle(o.fromSecrets)
|
|
554
|
-
: await promptAccountSecret(`Enter ${provider.provider} ${auth} for '${
|
|
609
|
+
? await secretFromBundle(o.fromSecrets)
|
|
610
|
+
: await promptAccountSecret(`Enter ${provider.provider} ${auth} for '${target}':`);
|
|
555
611
|
if (secret === null) {
|
|
556
612
|
process.exit(130);
|
|
557
613
|
return;
|
|
558
614
|
}
|
|
559
|
-
const account = addAccount(
|
|
615
|
+
const account = addAccount(target, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
|
|
560
616
|
console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
|
|
561
617
|
console.log(chalk.gray(`Secret bundle '${account.name}' is the account and uses policy never, so agent launches never request Touch ID.`));
|
|
562
618
|
});
|
|
563
619
|
});
|
|
620
|
+
setHelpSections(addCmd, {
|
|
621
|
+
examples: `agents accounts add claude work
|
|
622
|
+
agents accounts add codex personal --api-key sk-… # or omit --api-key to be prompted
|
|
623
|
+
agents accounts add codex gmail --per-device # ChatGPT-plan seat; workers log in per box
|
|
624
|
+
agents accounts add claude # name derived from the login's email
|
|
625
|
+
agents accounts add work --provider anthropic --auth setup-token # provider form (non-harness name)
|
|
626
|
+
agents run claude#work`,
|
|
627
|
+
notes: `Headed devices only: on a worker, add refuses before any slot, install, or browser — workers are provisioned automatically from the durable credential the add mints (claude: a setup-token; codex/grok/cursor/opencode: an API key via --api-key or a prompt; codex also accepts --per-device for a ChatGPT-plan seat; kimi/antigravity log in per box). An account is a credential SLOT, not a second installation: the harness has one managed install and each account is a HOME-shaped slot with its own native OAuth login (never copied, never fleet-synced). Re-running add for an already-added identity points at 'agents accounts login <harness>#<name>'. Supported: ${addSupportedList()}; other harnesses fail with the reason. An ambient CLAUDE_CODE_OAUTH_TOKEN in your shell refuses the mint (unset it or pass --no-worker-token).`,
|
|
628
|
+
});
|
|
629
|
+
const loginCmd = accounts.command('login <account>')
|
|
630
|
+
.description('Re-authenticate an account into its slot on this device (<harness>#<name>); re-mints and re-syncs the worker credential')
|
|
631
|
+
.option('--api-key <key>', 'Rotate the stored worker API key (api-key harnesses)')
|
|
632
|
+
.option('--no-worker-token', 'Skip re-minting the worker credential')
|
|
633
|
+
.option('--json', 'Machine-readable result (never includes a credential)')
|
|
634
|
+
.action(async (selector, o, command) => {
|
|
635
|
+
await runAccountsAction(command, async () => {
|
|
636
|
+
const json = !!(o.json || command.optsWithGlobals().json);
|
|
637
|
+
const parsed = parseAccountSelector(selector);
|
|
638
|
+
if (!parsed.agent) {
|
|
639
|
+
throw new Error(`Use the harness-qualified form: agents accounts login <harness>#<name> (e.g. claude#work).`);
|
|
640
|
+
}
|
|
641
|
+
const result = await runLogin(parsed.agent, parsed.name, {
|
|
642
|
+
meta: readMeta(),
|
|
643
|
+
onProgress: (m) => { if (!json)
|
|
644
|
+
console.log(chalk.gray(` ${m}`)); },
|
|
645
|
+
apiKey: o.apiKey,
|
|
646
|
+
noWorkerToken: o.workerToken === false,
|
|
647
|
+
});
|
|
648
|
+
printAddResult(result, json);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
setHelpSections(loginCmd, {
|
|
652
|
+
examples: `agents accounts login claude#work
|
|
653
|
+
agents accounts login codex#personal --api-key sk-… # rotate the worker key
|
|
654
|
+
agents accounts login kimi#main # per-device: logs THIS box in`,
|
|
655
|
+
notes: 'Re-runs the native login into the SAME slot (never a new home) and fails closed when the completed login is a different identity than the account. Headed devices only for harnesses with a portable worker credential; a per-device harness (kimi) may log in on any box — that is how that box gets its login. The daemon re-syncs the account row and worker credential after a successful login.',
|
|
656
|
+
});
|
|
657
|
+
const defaultCmd = accounts.command('default <harness> [name]')
|
|
658
|
+
.description('Set the fleet-wide default account for a harness (picker when no name)')
|
|
659
|
+
.option('--json', 'Machine-readable account list or the resulting default')
|
|
660
|
+
.action(async (harness, name, o, command) => {
|
|
661
|
+
await runAccountsAction(command, () => runAccountsSwitch(harness, name, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
662
|
+
});
|
|
663
|
+
setHelpSections(defaultCmd, {
|
|
664
|
+
examples: `agents accounts default claude
|
|
665
|
+
agents accounts default claude work
|
|
666
|
+
agents accounts default claude --json`,
|
|
667
|
+
notes: 'The one write path for the per-harness default (the hidden set-default/switch share it). Pass a name to skip the picker. Rotation already honors the default.',
|
|
668
|
+
});
|
|
669
|
+
const migrateCmd = accounts.command('migrate')
|
|
670
|
+
.description('Fold leftover per-account installations into 1 harness install + N credential slots')
|
|
671
|
+
.option('--dry-run', 'Print the plan and touch nothing (default)')
|
|
672
|
+
.option('--apply', 'Execute the plan: move homes into slots, trash empties/duplicates, re-index sessions')
|
|
673
|
+
.option('--json', 'Machine-readable plan or apply result')
|
|
674
|
+
.option('--device <name>', 'Run the migration on a fleet device over SSH')
|
|
675
|
+
.action(async (o, command) => {
|
|
676
|
+
await runAccountsAction(command, async () => {
|
|
677
|
+
const json = !!(o.json || command.optsWithGlobals().json);
|
|
678
|
+
if (o.dryRun && o.apply) {
|
|
679
|
+
throw new Error('Pass only one of --dry-run or --apply.');
|
|
680
|
+
}
|
|
681
|
+
const apply = !!o.apply;
|
|
682
|
+
if (o.device) {
|
|
683
|
+
const { isSelfHost } = await import('../lib/devices/self-host.js');
|
|
684
|
+
if (!isSelfHost(o.device)) {
|
|
685
|
+
const { resolveHost } = await import('../lib/hosts/registry.js');
|
|
686
|
+
const { streamAgentsOnHost } = await import('../lib/hosts/passthrough.js');
|
|
687
|
+
const host = await resolveHost(o.device);
|
|
688
|
+
if (!host)
|
|
689
|
+
throw new Error(`Unknown device '${o.device}'.`);
|
|
690
|
+
const flags = [apply ? '--apply' : '--dry-run'];
|
|
691
|
+
if (json)
|
|
692
|
+
flags.push('--json');
|
|
693
|
+
const code = streamAgentsOnHost(host, ['accounts', 'migrate', ...flags], { interactive: false });
|
|
694
|
+
process.exit(code);
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!apply) {
|
|
699
|
+
const plan = await planAccountMigration();
|
|
700
|
+
if (json) {
|
|
701
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
console.log(formatMigrationPlan(plan));
|
|
705
|
+
if (plan.totals.slots + plan.totals.trash + plan.totals.deferred > 0) {
|
|
706
|
+
console.log();
|
|
707
|
+
console.log('Dry-run: nothing written. Re-run with --apply to execute.');
|
|
708
|
+
console.log('Restore a trashed install with: agents trash restore <agent>@<label>');
|
|
709
|
+
}
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
const result = await applyAccountMigration();
|
|
713
|
+
if (json) {
|
|
714
|
+
console.log(JSON.stringify({
|
|
715
|
+
manifestPath: result.manifestPath,
|
|
716
|
+
sessionsReindexed: result.sessionsReindexed,
|
|
717
|
+
plan: result.plan,
|
|
718
|
+
manifest: result.manifest,
|
|
719
|
+
}, null, 2));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
console.log(formatMigrationPlan(result.plan));
|
|
723
|
+
console.log();
|
|
724
|
+
console.log(`Wrote manifest: ${result.manifestPath}`);
|
|
725
|
+
console.log(`Re-indexed ${result.sessionsReindexed} session path${result.sessionsReindexed === 1 ? '' : 's'}.`);
|
|
726
|
+
console.log('Restore a trashed install with: agents trash restore <agent>@<label>');
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
setHelpSections(migrateCmd, {
|
|
730
|
+
examples: `agents accounts migrate --dry-run
|
|
731
|
+
agents accounts migrate --apply
|
|
732
|
+
agents accounts migrate --dry-run --device worker-1`,
|
|
733
|
+
notes: 'Folds leftover per-account installations (the old connect homes) into one managed install plus a HOME-shaped slot per identity. Empty logged-out homes and duplicate identities go to trash (agents trash restore reverses). A busy home is deferred, never moved. --apply is required to write; this upgrade only prints the dry-run report. Native OAuth files stay on this device inside the moved home.',
|
|
734
|
+
});
|
|
735
|
+
// Hidden legacy verb: connect is the old spelling of add.
|
|
736
|
+
const connectCmd = accounts.command('connect <harness> [name]', { hidden: true })
|
|
737
|
+
.description('Legacy alias of accounts add')
|
|
738
|
+
.option('--json', 'Machine-readable result')
|
|
739
|
+
.action(async (harness, name, o, command) => {
|
|
740
|
+
await runAccountsAction(command, async () => {
|
|
741
|
+
const agent = parseHarness(harness);
|
|
742
|
+
const reason = addRefusal(agent);
|
|
743
|
+
if (reason)
|
|
744
|
+
throw new Error(reason);
|
|
745
|
+
const result = await runAdd(agent, name, {
|
|
746
|
+
meta: readMeta(),
|
|
747
|
+
onProgress: (m) => console.log(chalk.gray(` ${m}`)),
|
|
748
|
+
});
|
|
749
|
+
printAddResult(result, !!(o.json || command.optsWithGlobals().json));
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
retireVerb(connectCmd, 'agents accounts add <harness> [name]');
|
|
564
753
|
accounts.command('set-key <name>')
|
|
565
754
|
.description('Rotate an account credential without changing its identity')
|
|
566
755
|
.option('--from-secrets <bundle:key>', 'Import from an existing agents secrets entry')
|
|
@@ -570,7 +759,7 @@ agents run claude --account work`,
|
|
|
570
759
|
if (!account)
|
|
571
760
|
throw new Error(`Unknown provider account '${name}'.`);
|
|
572
761
|
const secret = o.fromSecrets
|
|
573
|
-
? secretFromBundle(o.fromSecrets)
|
|
762
|
+
? await secretFromBundle(o.fromSecrets)
|
|
574
763
|
: await promptAccountSecret(`Enter new ${account.provider} ${account.auth} for '${name}':`);
|
|
575
764
|
if (secret === null) {
|
|
576
765
|
process.exit(130);
|
|
@@ -621,7 +810,7 @@ agents run claude --account work`,
|
|
|
621
810
|
agents accounts view codex#icloud`,
|
|
622
811
|
notes: 'Native names are unique per harness. A bare name that exists for several harnesses is refused — pick one with <harness>#<name>.',
|
|
623
812
|
});
|
|
624
|
-
accounts.command('name <source> <name>')
|
|
813
|
+
retireVerb(accounts.command('name <source> <name>', { hidden: true })
|
|
625
814
|
.description('Name a signed-in native installation without copying its OAuth credentials')
|
|
626
815
|
.action(async (source, name, _o, command) => {
|
|
627
816
|
await runAccountsAction(command, async () => {
|
|
@@ -631,13 +820,14 @@ agents accounts view codex#icloud`,
|
|
|
631
820
|
if (account.scope === 'device')
|
|
632
821
|
console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
|
|
633
822
|
});
|
|
634
|
-
});
|
|
635
|
-
const labelCmd = accounts.command('label <source> [label]')
|
|
823
|
+
}), 'agents accounts add <harness> <name> (names are chosen at add time)');
|
|
824
|
+
const labelCmd = accounts.command('label <source> [label]', { hidden: true })
|
|
636
825
|
.description('Label a native login by harness or <harness>@<version>; the label binds to the account identity, not the version')
|
|
637
826
|
.option('--account <email-or-id>', 'Native identity to label when the harness has multiple logins')
|
|
638
827
|
.action(async (source, label, o, command) => {
|
|
639
828
|
await runAccountsAction(command, () => runAccountsLabel(source, label, o));
|
|
640
829
|
});
|
|
830
|
+
retireVerb(labelCmd, 'agents accounts add <harness> <name> (rename: agents accounts rename <harness>#<old> <new>)');
|
|
641
831
|
setHelpSections(labelCmd, {
|
|
642
832
|
examples: `agents accounts label codex work
|
|
643
833
|
agents accounts label codex@0.146.0 personal
|
|
@@ -645,7 +835,7 @@ agents accounts label codex work --account you@example.com
|
|
|
645
835
|
agents run codex#work`,
|
|
646
836
|
notes: 'The label binds to the signed-in account identity, not the version — codex#work keeps selecting that account after it moves to a newer install. Labels live on the central account rows in agents.yaml, which `agents repo push/pull` already syncs fleet-wide, keyed by (agent, identityKey). One signed-in login needs no selector; with several, an interactive terminal opens a picker, while scripts pass --account or <harness>@<version>.',
|
|
647
837
|
});
|
|
648
|
-
accounts.command('attach <account> <target>')
|
|
838
|
+
retireVerb(accounts.command('attach <account> <target>', { hidden: true })
|
|
649
839
|
.description('Attach a named account to a native installation or custom harness')
|
|
650
840
|
.action(async (name, target, _o, command) => {
|
|
651
841
|
await runAccountsAction(command, async () => {
|
|
@@ -712,8 +902,8 @@ agents run codex#work`,
|
|
|
712
902
|
writeClaudeInteractiveOauthToken(t, targetAgent, account.kind === 'native' && account.agent === 'claude' ? account.identityLabel : undefined);
|
|
713
903
|
console.log(chalk.green(`Attached ${account.name} to ${target}.`));
|
|
714
904
|
});
|
|
715
|
-
});
|
|
716
|
-
accounts.command('detach <account> <target>')
|
|
905
|
+
}), 'agents run <harness>#<name> (accounts select by name; installation bindings are legacy)');
|
|
906
|
+
retireVerb(accounts.command('detach <account> <target>', { hidden: true })
|
|
717
907
|
.description('Remove one account attachment')
|
|
718
908
|
.action(async (name, target, _o, command) => {
|
|
719
909
|
await runAccountsAction(command, () => {
|
|
@@ -736,7 +926,7 @@ agents run codex#work`,
|
|
|
736
926
|
catch { /* an unresolvable target has no version home to clean */ }
|
|
737
927
|
console.log(chalk.green(`Detached ${name} from ${target}.`));
|
|
738
928
|
});
|
|
739
|
-
});
|
|
929
|
+
}), 'agents run <harness>#<name> (accounts select by name; installation bindings are legacy)');
|
|
740
930
|
const renameCmd = accounts.command('rename <old> <new>')
|
|
741
931
|
.description('Rename an account without changing its stable id. Target may be <harness>#<name> when the name exists for several harnesses')
|
|
742
932
|
.action(async (oldName, newName, _o, command) => {
|
|
@@ -756,20 +946,21 @@ agents accounts rename codex#icloud cloud`,
|
|
|
756
946
|
examples: `agents accounts remove claude#icloud`,
|
|
757
947
|
notes: 'Native names are unique per harness. A bare name that exists for several harnesses is refused — pick one with <harness>#<name>.',
|
|
758
948
|
});
|
|
759
|
-
accounts.command('set-default <agent> <name>')
|
|
949
|
+
retireVerb(accounts.command('set-default <agent> <name>', { hidden: true })
|
|
760
950
|
.description('Use this account for a harness when --account is omitted')
|
|
761
951
|
.action(async (agentRaw, name, _o, command) => {
|
|
762
952
|
await runAccountsAction(command, () => {
|
|
763
953
|
const { agent, account } = setDefaultAccount(agentRaw, name);
|
|
764
954
|
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
765
955
|
});
|
|
766
|
-
});
|
|
767
|
-
const switchCmd = accounts.command('switch <harness> [account]')
|
|
956
|
+
}), 'agents accounts default <harness> <name>');
|
|
957
|
+
const switchCmd = accounts.command('switch <harness> [account]', { hidden: true })
|
|
768
958
|
.description('Pick the default account for a harness')
|
|
769
959
|
.option('--json', 'Machine-readable account list or the resulting default')
|
|
770
960
|
.action(async (harness, account, o, command) => {
|
|
771
961
|
await runAccountsAction(command, () => runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) }));
|
|
772
962
|
});
|
|
963
|
+
retireVerb(switchCmd, 'agents accounts default <harness> [name]');
|
|
773
964
|
accounts.command('clear-default <agent>')
|
|
774
965
|
.description('Return a harness to native login or balanced account selection')
|
|
775
966
|
.action(async (agentRaw, _o, command) => {
|
|
@@ -844,7 +1035,7 @@ agents accounts rename codex#icloud cloud`,
|
|
|
844
1035
|
AUTH_TYPE: account.auth,
|
|
845
1036
|
...(account.baseUrl ? { BASE_URL: account.baseUrl } : {}),
|
|
846
1037
|
};
|
|
847
|
-
const result = pushBundleToHost(account.name, device, {
|
|
1038
|
+
const result = await pushBundleToHost(account.name, device, {
|
|
848
1039
|
remoteBackend,
|
|
849
1040
|
force: o.force,
|
|
850
1041
|
operation: 'accounts sync',
|
|
@@ -865,24 +1056,20 @@ agents run claude`,
|
|
|
865
1056
|
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.',
|
|
866
1057
|
});
|
|
867
1058
|
setHelpSections(accounts, {
|
|
868
|
-
examples: `agents accounts
|
|
869
|
-
agents accounts
|
|
870
|
-
agents accounts
|
|
871
|
-
agents accounts
|
|
1059
|
+
examples: `agents accounts add claude work
|
|
1060
|
+
agents accounts add codex personal --api-key sk-…
|
|
1061
|
+
agents accounts login claude#work
|
|
1062
|
+
agents accounts default claude work
|
|
872
1063
|
agents accounts add work --provider anthropic --auth setup-token
|
|
873
1064
|
agents accounts add openrouter-work --provider openrouter --auth api-key --from-secrets openrouter.ai:OPENROUTER_API_KEY
|
|
874
1065
|
agents accounts set-key work
|
|
875
|
-
agents accounts name claude@2.1.220 work
|
|
876
|
-
agents accounts label codex@0.146.0 personal
|
|
877
|
-
agents accounts attach work claude@2.1.225
|
|
878
1066
|
agents accounts view work --json
|
|
879
1067
|
agents accounts view codex#icloud
|
|
880
|
-
agents accounts switch claude
|
|
881
|
-
agents accounts switch claude work
|
|
882
|
-
agents accounts set-default claude work
|
|
883
1068
|
agents accounts sync openrouter-work yosemite-s0
|
|
884
|
-
agents run claude
|
|
885
|
-
agents accounts logout claude
|
|
886
|
-
|
|
1069
|
+
agents run claude#work
|
|
1070
|
+
agents accounts logout claude
|
|
1071
|
+
agents accounts migrate --dry-run
|
|
1072
|
+
agents accounts migrate --apply`,
|
|
1073
|
+
notes: 'An account is a credential SLOT, not an installation: `accounts add <harness> [name]` runs the native login in a fresh HOME-shaped slot of the one managed install, registers the fleet-wide row, and mints the durable worker credential (claude: setup-token; codex/grok/cursor/opencode: --api-key or a prompt) in one step. Headed devices only — workers are provisioned automatically. `accounts login <harness>#<name>` re-auths into the same slot and re-mints; `accounts default <harness> [name]` is the one default write path. Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Leftover per-account installations fold with `agents accounts migrate --dry-run|--apply`. Synced vault unlock is `agents secrets vault unlock`. The legacy connect/name/label/mint/attach/detach/switch/set-default verbs still work this release and print their replacement.',
|
|
887
1074
|
});
|
|
888
1075
|
}
|