@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
package/dist/lib/auth-mint.js
CHANGED
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { resolveAgentName } from './agents.js';
|
|
2
|
-
import { AUTH_BUNDLE, claudeAccountTokenKey, isValidClaudeSetupToken, readClaudeAccountEmail, } from './claude-account-token.js';
|
|
2
|
+
import { AUTH_BUNDLE, claudeAccountTokenKey, isValidClaudeSetupToken, readClaudeAccountEmail, invalidateClaudeSetupTokenCache, } from './claude-account-token.js';
|
|
3
3
|
import { addAccount, findAccount, readAccountRegistry, setAccountSecret } from './account-registry.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { bundleBackendSync, bundleExists, bundleExistsSync, keychainRef, pushBundleToHost, readBundleSync, rotateBundleSecretSync, secretsKeychainItem, storeSetSync, writeBundleWithItemsSync, } from './secrets-client.js';
|
|
5
|
+
import { assertStorableCredentialKind, reservedStoreName, } from './secrets-policy.js';
|
|
6
6
|
import { getBinaryPath, getGlobalDefault, getVersionHomePath, listInstalledVersions } from './installations/versions.js';
|
|
7
7
|
import { shellQuote } from './ssh-exec.js';
|
|
8
8
|
import { defaultPtyDriver, } from './fleet/remote-login.js';
|
|
9
9
|
import { showUrl } from './open-url.js';
|
|
10
10
|
import { loadDevices } from './devices/registry.js';
|
|
11
11
|
import { isSelfHost } from './devices/self-host.js';
|
|
12
|
-
import {
|
|
13
|
-
import { assertCredentialTransportHostPinned, resolveHostSshTarget } from './secrets/remote.js';
|
|
12
|
+
import { assertCredentialTransportHostPinned, resolveHostSshTarget } from './hosts/credential-transport.js';
|
|
14
13
|
import { resolveRemoteOsSync } from './hosts/remote-os.js';
|
|
15
14
|
/** Well-formed Claude setup-token as it appears inside a TTY blob. */
|
|
16
15
|
export const CLAUDE_SETUP_TOKEN_CAPTURE_RE = /sk-ant-oat01-[A-Za-z0-9_-]+/;
|
|
17
16
|
const ACCOUNT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
18
17
|
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Durable-credential flows per harness. Only Claude exposes an interactive
|
|
20
|
+
* setup-token MINT; every other harness's durable credential is a provider API
|
|
21
|
+
* key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
|
|
22
|
+
* from OAuth). The api-key entries describe collection only — they have no
|
|
23
|
+
* mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
|
|
24
|
+
* Native device-code login (kimi/antigravity) stays on `agents fleet login`.
|
|
25
|
+
* The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
|
|
26
|
+
* kinds by auth-mint.test.ts — keep them in lockstep.
|
|
24
27
|
*/
|
|
25
28
|
export const MINT_FLOWS = {
|
|
26
29
|
claude: {
|
|
@@ -32,9 +35,15 @@ export const MINT_FLOWS = {
|
|
|
32
35
|
verificationUrlRegex: /(https:\/\/[^\s"'<>]+)/i,
|
|
33
36
|
tokenCapture: CLAUDE_SETUP_TOKEN_CAPTURE_RE,
|
|
34
37
|
},
|
|
38
|
+
codex: { harness: 'codex', provider: 'openai', auth: 'api-key', mintArgs: null, apiKeyEnv: 'OPENAI_API_KEY' },
|
|
39
|
+
grok: { harness: 'grok', provider: 'xai', auth: 'api-key', mintArgs: null, apiKeyEnv: 'XAI_API_KEY' },
|
|
40
|
+
opencode: { harness: 'opencode', provider: 'opencode', auth: 'api-key', mintArgs: null, apiKeyEnv: 'OPENCODE_API_KEY' },
|
|
41
|
+
cursor: { harness: 'cursor', provider: 'cursor', auth: 'api-key', mintArgs: null, apiKeyEnv: 'CURSOR_API_KEY' },
|
|
42
|
+
droid: { harness: 'droid', provider: 'factory', auth: 'api-key', mintArgs: null, apiKeyEnv: 'FACTORY_API_KEY' },
|
|
35
43
|
};
|
|
44
|
+
/** Harnesses with an interactive setup-token mint (api-key flows are collection, not mint). */
|
|
36
45
|
export function listMintableHarnesses() {
|
|
37
|
-
return Object.
|
|
46
|
+
return Object.values(MINT_FLOWS).filter((f) => f.auth === 'setup-token').map((f) => f.harness);
|
|
38
47
|
}
|
|
39
48
|
export function getMintFlow(harnessRaw) {
|
|
40
49
|
const harness = resolveAgentName(harnessRaw);
|
|
@@ -42,8 +51,12 @@ export function getMintFlow(harnessRaw) {
|
|
|
42
51
|
throw new Error(`Unknown harness '${harnessRaw}'. Interactive setup-token mint is implemented for: ${listMintableHarnesses().join(', ')}.`);
|
|
43
52
|
}
|
|
44
53
|
const flow = MINT_FLOWS[harness];
|
|
45
|
-
if (flow)
|
|
54
|
+
if (flow?.auth === 'setup-token')
|
|
46
55
|
return flow;
|
|
56
|
+
if (flow) {
|
|
57
|
+
throw new Error(`'${harness}' has no derivable token — its durable credential is an API key (${flow.apiKeyEnv}). `
|
|
58
|
+
+ `Collect it with: agents accounts add ${harness} <name> --api-key <key>.`);
|
|
59
|
+
}
|
|
47
60
|
throw new Error(unmintableMessage(harness));
|
|
48
61
|
}
|
|
49
62
|
export function unmintableMessage(harness) {
|
|
@@ -76,6 +89,8 @@ export function extractClaudeSetupToken(screen) {
|
|
|
76
89
|
}
|
|
77
90
|
/** First https URL on the screen, trailing punctuation stripped. */
|
|
78
91
|
export function extractMintUrl(screen, flow) {
|
|
92
|
+
if (!flow.verificationUrlRegex)
|
|
93
|
+
return undefined;
|
|
79
94
|
const text = stripAnsi(screen);
|
|
80
95
|
const m = text.match(flow.verificationUrlRegex);
|
|
81
96
|
if (!m)
|
|
@@ -154,14 +169,15 @@ export function resolveMintIdentity(input) {
|
|
|
154
169
|
export function seedReservedAuthToken(email, token) {
|
|
155
170
|
const cleaned = assertValidSetupToken(token);
|
|
156
171
|
const key = claudeAccountTokenKey(email);
|
|
157
|
-
if (
|
|
158
|
-
const backend =
|
|
172
|
+
if (bundleExistsSync(AUTH_BUNDLE)) {
|
|
173
|
+
const backend = bundleBackendSync(AUTH_BUNDLE);
|
|
159
174
|
if (backend !== 'file') {
|
|
160
175
|
throw new Error(`Reserved bundle '${AUTH_BUNDLE}' exists with backend '${backend}', but usage/probe only reads a FILE-backed auth bundle. Recreate it with: agents secrets create ${AUTH_BUNDLE} --backend file --policy never --i-understand --force`);
|
|
161
176
|
}
|
|
162
|
-
const bundle =
|
|
177
|
+
const bundle = readBundleSync(AUTH_BUNDLE);
|
|
163
178
|
if (key in bundle.vars) {
|
|
164
|
-
|
|
179
|
+
rotateBundleSecretSync(bundle, key, { newValue: cleaned, meta: { type: 'token' } });
|
|
180
|
+
invalidateClaudeSetupTokenCache();
|
|
165
181
|
return { key };
|
|
166
182
|
}
|
|
167
183
|
const item = secretsKeychainItem(AUTH_BUNDLE, key);
|
|
@@ -169,7 +185,8 @@ export function seedReservedAuthToken(email, token) {
|
|
|
169
185
|
if (!bundle.meta)
|
|
170
186
|
bundle.meta = {};
|
|
171
187
|
bundle.meta[key] = { type: 'token' };
|
|
172
|
-
|
|
188
|
+
writeBundleWithItemsSync(bundle, new Map([[item, cleaned]]));
|
|
189
|
+
invalidateClaudeSetupTokenCache();
|
|
173
190
|
return { key };
|
|
174
191
|
}
|
|
175
192
|
const item = secretsKeychainItem(AUTH_BUNDLE, key);
|
|
@@ -181,9 +198,59 @@ export function seedReservedAuthToken(email, token) {
|
|
|
181
198
|
vars: { [key]: keychainRef(key) },
|
|
182
199
|
meta: { [key]: { type: 'token' } },
|
|
183
200
|
};
|
|
184
|
-
|
|
201
|
+
writeBundleWithItemsSync(bundle, new Map([[item, cleaned]]));
|
|
202
|
+
invalidateClaudeSetupTokenCache();
|
|
185
203
|
return { key };
|
|
186
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* The env var a harness's durable worker credential injects as on a worker
|
|
207
|
+
* (claude's setup-token rides CLAUDE_CODE_OAUTH_TOKEN; api-key harnesses ride
|
|
208
|
+
* their MINT_FLOWS apiKeyEnv). Throws for a harness with no portable worker
|
|
209
|
+
* credential — kimi/antigravity log in per box.
|
|
210
|
+
*/
|
|
211
|
+
export function workerCredentialEnv(harness) {
|
|
212
|
+
if (harness === 'claude')
|
|
213
|
+
return 'CLAUDE_CODE_OAUTH_TOKEN';
|
|
214
|
+
const flow = MINT_FLOWS[harness];
|
|
215
|
+
if (flow?.auth === 'api-key' && flow.apiKeyEnv)
|
|
216
|
+
return flow.apiKeyEnv;
|
|
217
|
+
throw new Error(`No portable worker credential env for '${harness}' — it logs in per box.`);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* The reserved-store key for one account's worker credential:
|
|
221
|
+
* `<ENV>_<accountId>` — keyed by account id, never by name or email, so a
|
|
222
|
+
* rename or an email change never breaks the key. Hyphens are stripped because
|
|
223
|
+
* the bundle key grammar (`BUNDLE_KEY_PATTERN`) is env-var-shaped.
|
|
224
|
+
*/
|
|
225
|
+
export function workerCredentialStoreKey(harness, accountId) {
|
|
226
|
+
const slug = accountId.replace(/-/g, '');
|
|
227
|
+
if (!/^[A-Za-z0-9_]+$/.test(slug) || !slug) {
|
|
228
|
+
throw new Error(`Invalid account id '${accountId}' for a worker credential key.`);
|
|
229
|
+
}
|
|
230
|
+
return `${workerCredentialEnv(harness)}_${slug}`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Write (or rotate) one account's worker credential in the reserved
|
|
234
|
+
* `__<harness>__` store (PHNX-3940). Only non-rotating kinds may enter
|
|
235
|
+
* (`assertStorableCredentialKind` refuses a rotating OAuth/session credential at
|
|
236
|
+
* this boundary — RUSH-1958). FILE-backed, policy `never`, same contract as the
|
|
237
|
+
* legacy `auth` bundle: a keychain- or vault-backed store of this name fails
|
|
238
|
+
* loud, since worker provisioning reads the file backend.
|
|
239
|
+
*/
|
|
240
|
+
export function seedReservedStoreKey(harness, kind, key, value) {
|
|
241
|
+
assertStorableCredentialKind(kind, harness);
|
|
242
|
+
const name = reservedStoreName(harness);
|
|
243
|
+
const cleaned = value.trim();
|
|
244
|
+
if (!cleaned)
|
|
245
|
+
throw new Error(`Empty ${kind} for reserved store '${name}' key ${key}.`);
|
|
246
|
+
// A `__<harness>__` reserved store is written and read as raw FILE-backed items,
|
|
247
|
+
// never as a standalone bundle: the standalone's bundle-name validation rejects
|
|
248
|
+
// the `__`-wrapped reserved name, so the worker credential rides the raw item
|
|
249
|
+
// directly, symmetric with how `readReservedCredential` reads it. A first write
|
|
250
|
+
// and a rotation are the same operation — `store.set` overwrites the item value.
|
|
251
|
+
storeSetSync('file', secretsKeychainItem(name, key), cleaned);
|
|
252
|
+
return { bundle: name, key };
|
|
253
|
+
}
|
|
187
254
|
/**
|
|
188
255
|
* Create or rotate the named provider account that `agents run --account` and
|
|
189
256
|
* `agents accounts sync` consume. Existing account of a different kind fails
|
|
@@ -211,6 +278,9 @@ function emitMintProgress(line, json) {
|
|
|
211
278
|
* Tears the session down on the way out (success, timeout, or throw).
|
|
212
279
|
*/
|
|
213
280
|
export async function driveSetupTokenMint(command, flow, opts = {}) {
|
|
281
|
+
if (flow.auth !== 'setup-token' || !flow.tokenCapture) {
|
|
282
|
+
throw new Error(`Harness '${flow.harness}' has no interactive mint command.`);
|
|
283
|
+
}
|
|
214
284
|
const driver = opts.driver ?? defaultPtyDriver();
|
|
215
285
|
const initialDelayMs = opts.drive?.initialDelayMs ?? 1500;
|
|
216
286
|
const pollMs = opts.drive?.pollMs ?? 500;
|
|
@@ -274,12 +344,16 @@ export async function driveSetupTokenMint(command, flow, opts = {}) {
|
|
|
274
344
|
await driver.stop(id).catch(() => { });
|
|
275
345
|
}
|
|
276
346
|
}
|
|
277
|
-
export function buildMintCommand(flow, bin, home) {
|
|
278
|
-
if (!flow.mintArgs) {
|
|
347
|
+
export function buildMintCommand(flow, bin, home, env = {}) {
|
|
348
|
+
if (flow.auth !== 'setup-token' || !flow.mintArgs) {
|
|
279
349
|
throw new Error(`Harness '${flow.harness}' has no interactive mint command.`);
|
|
280
350
|
}
|
|
281
351
|
const args = flow.mintArgs.map(shellQuote).join(' ');
|
|
282
|
-
|
|
352
|
+
const envPrefix = [
|
|
353
|
+
`HOME=${shellQuote(home)}`,
|
|
354
|
+
...Object.entries(env).map(([k, v]) => `${k}=${shellQuote(v)}`),
|
|
355
|
+
].join(' ');
|
|
356
|
+
return `${envPrefix} ${shellQuote(bin)} ${args}`;
|
|
283
357
|
}
|
|
284
358
|
export function resolveMintInstallation(harness) {
|
|
285
359
|
const installed = listInstalledVersions(harness);
|
|
@@ -382,7 +456,7 @@ async function syncMintedBundles(accountName, device) {
|
|
|
382
456
|
const account = findAccount(accountName);
|
|
383
457
|
if (!account)
|
|
384
458
|
throw new Error(`Unknown provider account '${accountName}'.`);
|
|
385
|
-
const accountPush = pushBundleToHost(accountName, device, {
|
|
459
|
+
const accountPush = await pushBundleToHost(accountName, device, {
|
|
386
460
|
remoteBackend,
|
|
387
461
|
force: true,
|
|
388
462
|
operation: 'accounts mint --fleet',
|
|
@@ -397,8 +471,8 @@ async function syncMintedBundles(accountName, device) {
|
|
|
397
471
|
});
|
|
398
472
|
if (!accountPush.ok)
|
|
399
473
|
throw new Error(accountPush.message);
|
|
400
|
-
if (bundleExists(AUTH_BUNDLE)) {
|
|
401
|
-
const authPush = pushBundleToHost(AUTH_BUNDLE, device, {
|
|
474
|
+
if (await bundleExists(AUTH_BUNDLE)) {
|
|
475
|
+
const authPush = await pushBundleToHost(AUTH_BUNDLE, device, {
|
|
402
476
|
remoteBackend: 'file',
|
|
403
477
|
force: true,
|
|
404
478
|
operation: 'accounts mint --fleet',
|
|
@@ -417,32 +491,36 @@ async function syncMintedBundles(accountName, device) {
|
|
|
417
491
|
/**
|
|
418
492
|
* True when a Claude setup-token is already seeded on this box (setup status).
|
|
419
493
|
*
|
|
420
|
-
* Read-only status probe (`agents setup`, `agents doctor`) — the
|
|
421
|
-
* auth-bundle check
|
|
422
|
-
*
|
|
423
|
-
* unavailable — PHNX-3385
|
|
424
|
-
*
|
|
425
|
-
* guards, not a diagnostic. An unreachable
|
|
426
|
-
*
|
|
494
|
+
* Read-only status probe (`agents setup`, `agents doctor`) — neither the
|
|
495
|
+
* account-registry read nor the reserved auth-bundle check MUST crash the whole
|
|
496
|
+
* command because the secret store could not be reached (the macOS Keychain
|
|
497
|
+
* helper source unavailable — PHNX-3385 — or the standalone `secrets` CLI
|
|
498
|
+
* missing / unspawnable); the client's calls fail loud by contract, but that
|
|
499
|
+
* contract is for destructive-write guards, not a diagnostic. An unreachable
|
|
500
|
+
* store here just means "cannot confirm," reported honestly rather than
|
|
501
|
+
* propagated.
|
|
427
502
|
*/
|
|
428
503
|
export function hasMintedSetupToken() {
|
|
429
|
-
const records = Object.values(readAccountRegistry().accounts);
|
|
430
|
-
const setup = records.filter((a) => a.auth === 'setup-token' && a.provider === 'anthropic');
|
|
431
|
-
if (setup.length) {
|
|
432
|
-
return { ready: true, detail: `${setup.length} Claude setup-token account${setup.length === 1 ? '' : 's'}` };
|
|
433
|
-
}
|
|
434
504
|
try {
|
|
435
|
-
|
|
436
|
-
|
|
505
|
+
// The registry is the account bundles behind the standalone `secrets` CLI, so
|
|
506
|
+
// this read is as reachable — or as unreachable — as the auth-bundle read
|
|
507
|
+
// below and sits inside the same guard.
|
|
508
|
+
const records = Object.values(readAccountRegistry().accounts);
|
|
509
|
+
const setup = records.filter((a) => a.auth === 'setup-token' && a.provider === 'anthropic');
|
|
510
|
+
if (setup.length) {
|
|
511
|
+
return { ready: true, detail: `${setup.length} Claude setup-token account${setup.length === 1 ? '' : 's'}` };
|
|
512
|
+
}
|
|
513
|
+
if (bundleExistsSync(AUTH_BUNDLE) && bundleBackendSync(AUTH_BUNDLE) === 'file') {
|
|
514
|
+
const bundle = readBundleSync(AUTH_BUNDLE);
|
|
437
515
|
const keys = Object.keys(bundle.vars).filter((k) => k.startsWith('CLAUDE_CODE_OAUTH_TOKEN_'));
|
|
438
516
|
if (keys.length)
|
|
439
517
|
return { ready: true, detail: `reserved auth bundle (${keys.length} account key${keys.length === 1 ? '' : 's'})` };
|
|
440
518
|
}
|
|
441
519
|
}
|
|
442
520
|
catch (err) {
|
|
443
|
-
return { ready: false, detail: `could not check the
|
|
521
|
+
return { ready: false, detail: `could not check the secret store: ${err.message}` };
|
|
444
522
|
}
|
|
445
|
-
return { ready: false, detail: 'no Claude setup-token minted — agents accounts
|
|
523
|
+
return { ready: false, detail: 'no Claude setup-token minted — agents accounts add claude [name]' };
|
|
446
524
|
}
|
|
447
525
|
function sleep(ms) {
|
|
448
526
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -49,7 +49,7 @@ export interface LaunchResult {
|
|
|
49
49
|
* needs them can `agents secrets unlock <bundle>`). Exported so the no-prompt
|
|
50
50
|
* behavior is testable on the real keychain path without spawning a browser.
|
|
51
51
|
*/
|
|
52
|
-
export declare function resolveProfileSecretsEnv(secrets?: string): Record<string, string
|
|
52
|
+
export declare function resolveProfileSecretsEnv(secrets?: string): Promise<Record<string, string>>;
|
|
53
53
|
export declare function launchBrowser(profileName: string, browserType: BrowserType, port: number, options?: ChromeOptions, secrets?: string, customBinary?: string, isElectron?: boolean): Promise<LaunchResult>;
|
|
54
54
|
export declare function attachToChrome(port: number): Promise<string>;
|
|
55
55
|
export declare function killChrome(pid: number): void;
|
|
@@ -4,7 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import { getProfileRuntimeDir } from './profiles.js';
|
|
6
6
|
import { discoverBrowserWsUrl, registerPipeTransport } from './cdp.js';
|
|
7
|
-
import { readAndResolveBundleEnv, bundleExists } from '../secrets
|
|
7
|
+
import { readAndResolveBundleEnv, bundleExists } from '../secrets-client.js';
|
|
8
8
|
import { writeProfileRuntime, readProfileRuntime } from './runtime-state.js';
|
|
9
9
|
// Windows install roots. Resolve from the environment (fall back to the usual
|
|
10
10
|
// defaults) so per-user installs under %LOCALAPPDATA% and 64-bit Program Files
|
|
@@ -247,11 +247,11 @@ export function listInstalledBrowsers(platform = os.platform()) {
|
|
|
247
247
|
* needs them can `agents secrets unlock <bundle>`). Exported so the no-prompt
|
|
248
248
|
* behavior is testable on the real keychain path without spawning a browser.
|
|
249
249
|
*/
|
|
250
|
-
export function resolveProfileSecretsEnv(secrets) {
|
|
251
|
-
if (!secrets || !bundleExists(secrets))
|
|
250
|
+
export async function resolveProfileSecretsEnv(secrets) {
|
|
251
|
+
if (!secrets || !(await bundleExists(secrets)))
|
|
252
252
|
return {};
|
|
253
253
|
try {
|
|
254
|
-
const { env } = readAndResolveBundleEnv(secrets, { caller: 'browser profile', agentOnly: true });
|
|
254
|
+
const { env } = await readAndResolveBundleEnv(secrets, { caller: 'browser profile', agentOnly: true });
|
|
255
255
|
return env;
|
|
256
256
|
}
|
|
257
257
|
catch {
|
|
@@ -321,7 +321,7 @@ isElectron = false) {
|
|
|
321
321
|
];
|
|
322
322
|
// Profile secrets: agentOnly (SEC-13) — a locked bundle resolves to an empty map
|
|
323
323
|
// (launch proceeds without them), never a Touch ID prompt. See resolveProfileSecretsEnv.
|
|
324
|
-
const env = { ...process.env, ...resolveProfileSecretsEnv(secrets) };
|
|
324
|
+
const env = { ...process.env, ...(await resolveProfileSecretsEnv(secrets)) };
|
|
325
325
|
const child = spawn(browserPath, args, {
|
|
326
326
|
detached: true,
|
|
327
327
|
stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
|
package/dist/lib/byok-usage.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* same bar style the native usage system uses, just sourced from the provider
|
|
8
8
|
* API rather than the CLI's own usage tracking.
|
|
9
9
|
*/
|
|
10
|
-
import {
|
|
10
|
+
import { getKeychainTokenSync, hasKeychainTokenSync } from './secrets-client.js';
|
|
11
11
|
import { renderBar, getUsageColor } from './accounting/usage.js';
|
|
12
12
|
import * as fs from 'node:fs';
|
|
13
13
|
import * as path from 'node:path';
|
|
@@ -74,9 +74,9 @@ function resolveByokToken(profile) {
|
|
|
74
74
|
if (!keychainItem)
|
|
75
75
|
return null;
|
|
76
76
|
try {
|
|
77
|
-
if (!
|
|
77
|
+
if (!hasKeychainTokenSync(keychainItem))
|
|
78
78
|
return null;
|
|
79
|
-
return
|
|
79
|
+
return getKeychainTokenSync(keychainItem);
|
|
80
80
|
}
|
|
81
81
|
catch {
|
|
82
82
|
return null;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ReservedBundleWrongBackendError } from './reserved-stores.js';
|
|
2
|
+
import type { DeviceAccountSlot, NativeAccountRecord } from './types.js';
|
|
1
3
|
/**
|
|
2
4
|
* Reserved FILE-BASED secrets bundle holding long-lived, non-rotating Claude
|
|
3
5
|
* setup-tokens. Usage/probe reads authenticate with these instead of Claude
|
|
@@ -6,8 +8,11 @@
|
|
|
6
8
|
* bare key, so one account's token can't be misapplied to another in a
|
|
7
9
|
* multi-account fleet.
|
|
8
10
|
*/
|
|
9
|
-
/** Alias of the
|
|
11
|
+
/** Alias of the reserved-store name so mint/seed shares one source of truth. */
|
|
10
12
|
export declare const AUTH_BUNDLE = "auth";
|
|
13
|
+
export { ReservedBundleWrongBackendError };
|
|
14
|
+
/** Drop every memoized setup-token; called after this process seeds or rotates one. */
|
|
15
|
+
export declare function invalidateClaudeSetupTokenCache(): void;
|
|
11
16
|
/** True only for a clean, single-line `sk-ant-oat01-…` token — see {@link SETUP_TOKEN_RE}. */
|
|
12
17
|
export declare function isValidClaudeSetupToken(value: string): boolean;
|
|
13
18
|
/** The per-account key an email maps to inside the `auth` bundle. */
|
|
@@ -24,8 +29,8 @@ export declare function readClaudeAccountEmail(home?: string): string | null;
|
|
|
24
29
|
export declare function resolveClaudeSetupToken(home?: string): string | null;
|
|
25
30
|
/**
|
|
26
31
|
* Resolve a long-lived setup-token for an EXPLICIT account email, independent of
|
|
27
|
-
* any version home's `.claude.json`. This is what lets
|
|
28
|
-
*
|
|
32
|
+
* any version home's `.claude.json`. This is what lets worker-slot provisioning
|
|
33
|
+
* seed a headless worker home that has never had an interactive login: the
|
|
29
34
|
* account's non-rotating setup-token is already fleet-synced in the file-based
|
|
30
35
|
* `auth` bundle, keyed by email ({@link claudeAccountTokenKey}), so we can write
|
|
31
36
|
* the home's `.oauth_token` from it without the circular
|
|
@@ -47,4 +52,43 @@ export declare function resolveClaudeSetupTokenForEmail(email: string, cacheKey?
|
|
|
47
52
|
* It never copies a rotating OAuth credential (`.credentials.json`) — the setup-token
|
|
48
53
|
* stays the credential of record.
|
|
49
54
|
*/
|
|
55
|
+
/**
|
|
56
|
+
* Write a Claude worker slot's `.oauth_token` (0600) from a durable setup-token,
|
|
57
|
+
* the way the pre-slot worker home was provisioned. Refuses a malformed token so
|
|
58
|
+
* a corrupt bundle entry can never reach the auth header (see {@link SETUP_TOKEN_RE}).
|
|
59
|
+
* `home` is the slot dir; the claude adapter shim reads `$CLAUDE_CONFIG_DIR/.oauth_token`
|
|
60
|
+
* where `CLAUDE_CONFIG_DIR` is `<home>/.claude`. Returns the written path.
|
|
61
|
+
*/
|
|
62
|
+
export declare function writeClaudeWorkerOauthToken(home: string, token: string): string;
|
|
63
|
+
/**
|
|
64
|
+
* Read one non-rotating credential value from a reserved store by its storage
|
|
65
|
+
* key. Returns null when the bundle or key is absent. Used to materialize a
|
|
66
|
+
* worker slot from a synced durable credential (setup-token / API key).
|
|
67
|
+
*
|
|
68
|
+
* A reserved `__<harness>__` store is file-backed by design (headless, fleet-
|
|
69
|
+
* shareable) and its item is read directly, symmetric with how it is written —
|
|
70
|
+
* `readAndResolveBundleEnv`'s name validation does not yet accept a reserved
|
|
71
|
+
* name on the READ path (a secrets-track seam; see the PR body). A non-reserved
|
|
72
|
+
* bundle (the legacy `auth` alias, a provider bundle) goes through the normal
|
|
73
|
+
* resolver so refs/expiry/lease gates still apply.
|
|
74
|
+
*/
|
|
75
|
+
export declare function readReservedCredential(bundle: string, key: string): string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Materialize a worker slot for a portable account from its synced durable
|
|
78
|
+
* credential (PHNX-3940 T6 — the generalization of the pre-slot Claude worker-home
|
|
79
|
+
* provisioning). Creates the HOME-shaped slot dir (T1 `ensureSlot`), then, for a
|
|
80
|
+
* durable harness, writes the credential into it the way the Claude worker home is
|
|
81
|
+
* provisioned today: for `claude`, the setup-token → `.oauth_token` (0600) + the
|
|
82
|
+
* seeded identity email (the read-side join in agent-spec then completes the uuids
|
|
83
|
+
* from the registry row). API-key harnesses need no file — the key is injected at
|
|
84
|
+
* spawn from the reserved store (T5) — so their slot is created and recorded
|
|
85
|
+
* `durable` with no write. A per-device harness (`worker: 'none'`) gets a
|
|
86
|
+
* `per-device` slot and no credential — it logs in per box.
|
|
87
|
+
*
|
|
88
|
+
* This is worker-side reconciliation: it runs on the box where the key landed and
|
|
89
|
+
* NEVER transports anything (the SSH push that delivered the key is the daemon's
|
|
90
|
+
* job — invariant 1). Fails loud when a durable claude account has no resolvable
|
|
91
|
+
* token on this device rather than recording a slot that cannot authenticate.
|
|
92
|
+
*/
|
|
93
|
+
export declare function provisionWorkerSlot(account: NativeAccountRecord): DeviceAccountSlot;
|
|
50
94
|
export declare function seedClaudeWorkerHomeIdentity(versionHome: string, email: string): void;
|