@phnx-labs/agents-cli 1.22.84 → 1.22.85
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 +57 -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/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 +32 -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 +28 -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 +15 -0
- package/dist/lib/installations/shims.js +69 -0
- 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 +191 -0
- package/dist/lib/secrets-client.js +710 -0
- package/dist/lib/secrets-policy.d.ts +222 -0
- package/dist/lib/secrets-policy.js +483 -0
- package/dist/lib/secrets-types.d.ts +188 -0
- package/dist/lib/secrets-types.js +24 -0
- 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 +4 -29
- 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
|
@@ -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;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as os from 'os';
|
|
3
3
|
import * as path from 'path';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { bundleBackendSync, bundleExistsSync, isSecretsClientError, readAndResolveBundleEnvSync, secretsKeychainItem, storeGetSync, storeHasSync, } from './secrets-client.js';
|
|
5
|
+
import { AUTH_STORE_ALIAS, ReservedBundleWrongBackendError, assertReservedAuthBackend, isReservedBundleBackendError, isReservedStoreName, } from './reserved-stores.js';
|
|
6
|
+
import { ensureSlot, recordSlot } from './accounts/slots.js';
|
|
7
|
+
import { harnessWorkerKinds } from './harness-auth-capabilities.js';
|
|
7
8
|
/**
|
|
8
9
|
* Reserved FILE-BASED secrets bundle holding long-lived, non-rotating Claude
|
|
9
10
|
* setup-tokens. Usage/probe reads authenticate with these instead of Claude
|
|
@@ -12,8 +13,9 @@ import { secretsKeychainItem } from './secrets/index.js';
|
|
|
12
13
|
* bare key, so one account's token can't be misapplied to another in a
|
|
13
14
|
* multi-account fleet.
|
|
14
15
|
*/
|
|
15
|
-
/** Alias of the
|
|
16
|
-
export const AUTH_BUNDLE =
|
|
16
|
+
/** Alias of the reserved-store name so mint/seed shares one source of truth. */
|
|
17
|
+
export const AUTH_BUNDLE = AUTH_STORE_ALIAS;
|
|
18
|
+
export { ReservedBundleWrongBackendError };
|
|
17
19
|
/**
|
|
18
20
|
* A well-formed Claude OAuth setup-token: the `sk-ant-oat01-` prefix followed by
|
|
19
21
|
* token-safe characters only, on a single line. `claude setup-token` mints exactly
|
|
@@ -28,8 +30,49 @@ export const AUTH_BUNDLE = AUTH_BUNDLE_NAME;
|
|
|
28
30
|
* where a corrupt bundle entry must be caught before it reaches the auth header.
|
|
29
31
|
*/
|
|
30
32
|
const SETUP_TOKEN_RE = /^sk-ant-oat01-[A-Za-z0-9_-]+$/;
|
|
31
|
-
/**
|
|
33
|
+
/**
|
|
34
|
+
* Process-local memo of resolved setup-tokens, keyed by the caller's cache key
|
|
35
|
+
* AND the per-account token key — a version home that switches accounts must
|
|
36
|
+
* miss, never be served the previous account's token. Plaintext setup-tokens
|
|
37
|
+
* are never written to another cache. The bundle now
|
|
38
|
+
* lives behind the standalone `secrets` process (one spawn per read), so the
|
|
39
|
+
* memo is what keeps a probe loop over many accounts from re-decrypting the
|
|
40
|
+
* whole `auth` bundle per account; it is bounded by {@link SETUP_TOKEN_MEMO_TTL_MS}
|
|
41
|
+
* so a rotation performed by another process is observed within seconds, and
|
|
42
|
+
* an in-process writer clears it outright ({@link invalidateClaudeSetupTokenCache}).
|
|
43
|
+
*/
|
|
32
44
|
const setupTokenCache = new Map();
|
|
45
|
+
const SETUP_TOKEN_MEMO_TTL_MS = 10_000;
|
|
46
|
+
/** Drop every memoized setup-token; called after this process seeds or rotates one. */
|
|
47
|
+
export function invalidateClaudeSetupTokenCache() {
|
|
48
|
+
setupTokenCache.clear();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read the reserved `auth` bundle through the standalone. Returns null when it
|
|
52
|
+
* does not exist — checked explicitly, because a prompt-free (`agentOnly`) read
|
|
53
|
+
* of a bundle that does not exist reports LOCKED, and a genuinely locked store
|
|
54
|
+
* must stay distinguishable from an absent bundle. A keychain- or vault-backed
|
|
55
|
+
* `auth` fails loud with {@link ReservedBundleWrongBackendError} — whether the
|
|
56
|
+
* standalone refused the resolve (`WRONG_BACKEND`) or the returned metadata
|
|
57
|
+
* names the wrong backend — so usage/probe never silently ignores a seeded
|
|
58
|
+
* token (SEC-GAP-3).
|
|
59
|
+
*/
|
|
60
|
+
function readReservedAuthBundle(caller) {
|
|
61
|
+
if (!bundleExistsSync(AUTH_BUNDLE))
|
|
62
|
+
return null;
|
|
63
|
+
let resolved;
|
|
64
|
+
try {
|
|
65
|
+
resolved = readAndResolveBundleEnvSync(AUTH_BUNDLE, { caller, agentOnly: true });
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
if (isSecretsClientError(err, 'WRONG_BACKEND')) {
|
|
69
|
+
throw new ReservedBundleWrongBackendError(AUTH_BUNDLE, bundleBackendSync(AUTH_BUNDLE));
|
|
70
|
+
}
|
|
71
|
+
throw err;
|
|
72
|
+
}
|
|
73
|
+
assertReservedAuthBackend(resolved.bundle.backend ?? 'keychain');
|
|
74
|
+
return resolved;
|
|
75
|
+
}
|
|
33
76
|
function credentialFingerprint(credentialPath) {
|
|
34
77
|
try {
|
|
35
78
|
const stat = fs.statSync(credentialPath, { bigint: true });
|
|
@@ -117,7 +160,7 @@ function discoverClaudeAccountEmailFromOauthToken(home) {
|
|
|
117
160
|
return email;
|
|
118
161
|
}
|
|
119
162
|
catch (err) {
|
|
120
|
-
if (err
|
|
163
|
+
if (isReservedBundleBackendError(err))
|
|
121
164
|
throw err;
|
|
122
165
|
return null;
|
|
123
166
|
}
|
|
@@ -132,13 +175,10 @@ function discoverEmailUncached(home, tokenPath) {
|
|
|
132
175
|
}
|
|
133
176
|
if (!isValidClaudeSetupToken(token))
|
|
134
177
|
return null;
|
|
135
|
-
|
|
178
|
+
const resolved = readReservedAuthBundle('usage');
|
|
179
|
+
if (!resolved)
|
|
136
180
|
return null;
|
|
137
|
-
|
|
138
|
-
throw new ReservedBundleWrongBackendError(AUTH_BUNDLE, bundleBackend(AUTH_BUNDLE));
|
|
139
|
-
}
|
|
140
|
-
const { env } = readAndResolveBundleEnv(AUTH_BUNDLE, { caller: 'usage', agentOnly: true });
|
|
141
|
-
for (const [key, value] of Object.entries(env)) {
|
|
181
|
+
for (const [key, value] of Object.entries(resolved.env)) {
|
|
142
182
|
if (value.trim() !== token)
|
|
143
183
|
continue;
|
|
144
184
|
const email = emailFromTokenKey(key);
|
|
@@ -175,8 +215,8 @@ function emailFromTokenKey(key) {
|
|
|
175
215
|
}
|
|
176
216
|
/**
|
|
177
217
|
* Resolve a long-lived setup-token for an EXPLICIT account email, independent of
|
|
178
|
-
* any version home's `.claude.json`. This is what lets
|
|
179
|
-
*
|
|
218
|
+
* any version home's `.claude.json`. This is what lets worker-slot provisioning
|
|
219
|
+
* seed a headless worker home that has never had an interactive login: the
|
|
180
220
|
* account's non-rotating setup-token is already fleet-synced in the file-based
|
|
181
221
|
* `auth` bundle, keyed by email ({@link claudeAccountTokenKey}), so we can write
|
|
182
222
|
* the home's `.oauth_token` from it without the circular
|
|
@@ -192,44 +232,23 @@ export function resolveClaudeSetupTokenForEmail(email, cacheKey) {
|
|
|
192
232
|
const trimmed = email.trim();
|
|
193
233
|
if (!trimmed)
|
|
194
234
|
return null;
|
|
195
|
-
if (!bundleExists(AUTH_BUNDLE))
|
|
196
|
-
return null;
|
|
197
|
-
const backend = bundleBackend(AUTH_BUNDLE);
|
|
198
|
-
if (backend !== 'file') {
|
|
199
|
-
// SEC-GAP-3: a keychain/vault-backed `auth` used to return null here, so
|
|
200
|
-
// usage/probe fell through to the interactive login (Touch ID) with no
|
|
201
|
-
// hint that the seeded setup-token was being ignored.
|
|
202
|
-
throw new ReservedBundleWrongBackendError(AUTH_BUNDLE, backend);
|
|
203
|
-
}
|
|
204
235
|
const key = claudeAccountTokenKey(trimmed);
|
|
205
|
-
const ck = cacheKey ?? `email:${trimmed}`;
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const { env } = readAndResolveBundleEnv(AUTH_BUNDLE, { caller: 'usage', agentOnly: true });
|
|
219
|
-
const v = (env[key] ?? '').trim();
|
|
220
|
-
const token = v.length > 0 && isValidClaudeSetupToken(v) ? v : null;
|
|
221
|
-
const after = credentialFingerprint(credentialPath);
|
|
222
|
-
if (before === after) {
|
|
223
|
-
setupTokenCache.set(ck, { credentialPath, fingerprint: after, token });
|
|
224
|
-
return token;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// The credential changed during both decrypt attempts. Fail closed rather
|
|
228
|
-
// than return a token whose current file fingerprint was never observed.
|
|
229
|
-
return null;
|
|
236
|
+
const ck = `${cacheKey ?? `email:${trimmed}`}\0${key}`;
|
|
237
|
+
const cached = setupTokenCache.get(ck);
|
|
238
|
+
if (cached && Date.now() - cached.readAt < SETUP_TOKEN_MEMO_TTL_MS)
|
|
239
|
+
return cached.token;
|
|
240
|
+
// SEC-GAP-3: a keychain/vault-backed `auth` used to return null here, so
|
|
241
|
+
// usage/probe fell through to the interactive login (Touch ID) with no
|
|
242
|
+
// hint that the seeded setup-token was being ignored. readReservedAuthBundle
|
|
243
|
+
// throws ReservedBundleWrongBackendError instead, which propagates.
|
|
244
|
+
const resolved = readReservedAuthBundle('usage');
|
|
245
|
+
const v = (resolved?.env[key] ?? '').trim();
|
|
246
|
+
const token = v.length > 0 && isValidClaudeSetupToken(v) ? v : null;
|
|
247
|
+
setupTokenCache.set(ck, { readAt: Date.now(), token });
|
|
248
|
+
return token;
|
|
230
249
|
}
|
|
231
250
|
catch (err) {
|
|
232
|
-
if (err
|
|
251
|
+
if (isReservedBundleBackendError(err))
|
|
233
252
|
throw err;
|
|
234
253
|
return null;
|
|
235
254
|
}
|
|
@@ -244,6 +263,109 @@ export function resolveClaudeSetupTokenForEmail(email, cacheKey) {
|
|
|
244
263
|
* It never copies a rotating OAuth credential (`.credentials.json`) — the setup-token
|
|
245
264
|
* stays the credential of record.
|
|
246
265
|
*/
|
|
266
|
+
/**
|
|
267
|
+
* Write a Claude worker slot's `.oauth_token` (0600) from a durable setup-token,
|
|
268
|
+
* the way the pre-slot worker home was provisioned. Refuses a malformed token so
|
|
269
|
+
* a corrupt bundle entry can never reach the auth header (see {@link SETUP_TOKEN_RE}).
|
|
270
|
+
* `home` is the slot dir; the claude adapter shim reads `$CLAUDE_CONFIG_DIR/.oauth_token`
|
|
271
|
+
* where `CLAUDE_CONFIG_DIR` is `<home>/.claude`. Returns the written path.
|
|
272
|
+
*/
|
|
273
|
+
export function writeClaudeWorkerOauthToken(home, token) {
|
|
274
|
+
if (!isValidClaudeSetupToken(token)) {
|
|
275
|
+
throw new Error('Refusing to write a malformed Claude setup-token to a worker slot.');
|
|
276
|
+
}
|
|
277
|
+
const tokenPath = path.join(home, '.claude', '.oauth_token');
|
|
278
|
+
fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
|
|
279
|
+
fs.writeFileSync(tokenPath, token, { mode: 0o600 });
|
|
280
|
+
return tokenPath;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Read one non-rotating credential value from a reserved store by its storage
|
|
284
|
+
* key. Returns null when the bundle or key is absent. Used to materialize a
|
|
285
|
+
* worker slot from a synced durable credential (setup-token / API key).
|
|
286
|
+
*
|
|
287
|
+
* A reserved `__<harness>__` store is file-backed by design (headless, fleet-
|
|
288
|
+
* shareable) and its item is read directly, symmetric with how it is written —
|
|
289
|
+
* `readAndResolveBundleEnv`'s name validation does not yet accept a reserved
|
|
290
|
+
* name on the READ path (a secrets-track seam; see the PR body). A non-reserved
|
|
291
|
+
* bundle (the legacy `auth` alias, a provider bundle) goes through the normal
|
|
292
|
+
* resolver so refs/expiry/lease gates still apply.
|
|
293
|
+
*/
|
|
294
|
+
export function readReservedCredential(bundle, key) {
|
|
295
|
+
try {
|
|
296
|
+
if (isReservedStoreName(bundle) && bundle.startsWith('__')) {
|
|
297
|
+
const item = secretsKeychainItem(bundle, key);
|
|
298
|
+
if (!storeHasSync('file', item))
|
|
299
|
+
return null;
|
|
300
|
+
const value = storeGetSync('file', item).trim();
|
|
301
|
+
return value.length > 0 ? value : null;
|
|
302
|
+
}
|
|
303
|
+
const { env } = readAndResolveBundleEnvSync(bundle, {
|
|
304
|
+
keys: [key],
|
|
305
|
+
keyMode: 'storage',
|
|
306
|
+
agentOnly: true,
|
|
307
|
+
caller: 'provision-worker-slot',
|
|
308
|
+
});
|
|
309
|
+
const value = (env[key] ?? '').trim();
|
|
310
|
+
return value.length > 0 ? value : null;
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
if (isReservedBundleBackendError(err))
|
|
314
|
+
throw err;
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Materialize a worker slot for a portable account from its synced durable
|
|
320
|
+
* credential (PHNX-3940 T6 — the generalization of the pre-slot Claude worker-home
|
|
321
|
+
* provisioning). Creates the HOME-shaped slot dir (T1 `ensureSlot`), then, for a
|
|
322
|
+
* durable harness, writes the credential into it the way the Claude worker home is
|
|
323
|
+
* provisioned today: for `claude`, the setup-token → `.oauth_token` (0600) + the
|
|
324
|
+
* seeded identity email (the read-side join in agent-spec then completes the uuids
|
|
325
|
+
* from the registry row). API-key harnesses need no file — the key is injected at
|
|
326
|
+
* spawn from the reserved store (T5) — so their slot is created and recorded
|
|
327
|
+
* `durable` with no write. A per-device harness (`worker: 'none'`) gets a
|
|
328
|
+
* `per-device` slot and no credential — it logs in per box.
|
|
329
|
+
*
|
|
330
|
+
* This is worker-side reconciliation: it runs on the box where the key landed and
|
|
331
|
+
* NEVER transports anything (the SSH push that delivered the key is the daemon's
|
|
332
|
+
* job — invariant 1). Fails loud when a durable claude account has no resolvable
|
|
333
|
+
* token on this device rather than recording a slot that cannot authenticate.
|
|
334
|
+
*/
|
|
335
|
+
export function provisionWorkerSlot(account) {
|
|
336
|
+
const harness = account.agent;
|
|
337
|
+
const durable = harnessWorkerKinds(harness).some((kind) => kind === 'setup-token' || kind.startsWith('api-key'));
|
|
338
|
+
const slot = ensureSlot(harness, account.id);
|
|
339
|
+
const checkedAt = new Date().toISOString();
|
|
340
|
+
if (!durable) {
|
|
341
|
+
const record = { ...slot, authMode: 'per-device', verdict: 'unconfigured', checkedAt };
|
|
342
|
+
recordSlot(account.id, record);
|
|
343
|
+
return record;
|
|
344
|
+
}
|
|
345
|
+
if (harness === 'claude') {
|
|
346
|
+
const cred = account.workerCredential;
|
|
347
|
+
const token = cred
|
|
348
|
+
? readReservedCredential(cred.bundle, cred.key)
|
|
349
|
+
// Claude row predating T1 (no workerCredential): the legacy `auth` bundle
|
|
350
|
+
// keys the token by the account email.
|
|
351
|
+
: account.identityLabel
|
|
352
|
+
? resolveClaudeSetupTokenForEmail(account.identityLabel, slot.slotDir)
|
|
353
|
+
: null;
|
|
354
|
+
if (!token) {
|
|
355
|
+
const where = cred ? `${cred.bundle}:${cred.key}` : `'${AUTH_BUNDLE}' keyed by ${account.identityLabel ?? '(no email)'}`;
|
|
356
|
+
throw new Error(`No durable Claude setup-token for account ${account.id} on this device (${where}). `
|
|
357
|
+
+ `Mint and sync it from the headed device.`);
|
|
358
|
+
}
|
|
359
|
+
writeClaudeWorkerOauthToken(slot.slotDir, token);
|
|
360
|
+
if (account.identityLabel)
|
|
361
|
+
seedClaudeWorkerHomeIdentity(slot.slotDir, account.identityLabel);
|
|
362
|
+
}
|
|
363
|
+
// API-key harnesses: the key rides the reserved store and is injected at spawn
|
|
364
|
+
// (T5); nothing is written into the slot here.
|
|
365
|
+
const record = { ...slot, authMode: 'durable', verdict: 'unverified', checkedAt };
|
|
366
|
+
recordSlot(account.id, record);
|
|
367
|
+
return record;
|
|
368
|
+
}
|
|
247
369
|
export function seedClaudeWorkerHomeIdentity(versionHome, email) {
|
|
248
370
|
const trimmed = email.trim();
|
|
249
371
|
if (!trimmed)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
21
|
*/
|
|
22
22
|
import { resolveDispatchRepos, normalizeProviderStatus } from './types.js';
|
|
23
|
-
import { readAndResolveBundleEnv } from '../secrets
|
|
23
|
+
import { readAndResolveBundleEnv } from '../secrets-client.js';
|
|
24
24
|
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
|
|
25
25
|
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
|
|
26
26
|
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
|
|
@@ -62,7 +62,7 @@ export class AntigravityCloudProvider {
|
|
|
62
62
|
return KEY_NAMES.some((k) => Boolean(process.env[k]));
|
|
63
63
|
}
|
|
64
64
|
/** Resolve the Gemini API key from the configured bundle or the environment. */
|
|
65
|
-
resolveApiKey() {
|
|
65
|
+
async resolveApiKey() {
|
|
66
66
|
if (this.secretsBundle) {
|
|
67
67
|
try {
|
|
68
68
|
// Cloud dispatch resolves the key on its own (no human at a sheet), so the
|
|
@@ -71,7 +71,7 @@ export class AntigravityCloudProvider {
|
|
|
71
71
|
// catch below re-raises verbatim — dispatch genuinely needs the key, so it
|
|
72
72
|
// fails LOUD with the unlock hint rather than swallowing it into a wrong
|
|
73
73
|
// path. A `never`/no-ACL or broker-held bundle resolves silently.
|
|
74
|
-
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity', agentOnly: true });
|
|
74
|
+
const { env } = await readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity', agentOnly: true });
|
|
75
75
|
for (const k of KEY_NAMES) {
|
|
76
76
|
if (env[k])
|
|
77
77
|
return env[k];
|
|
@@ -112,7 +112,7 @@ export class AntigravityCloudProvider {
|
|
|
112
112
|
throw new Error(`Antigravity cloud is a raw sandbox with no GitHub repo → PR flow. Got repo(s): ${repos.join(', ')}. ` +
|
|
113
113
|
`Use --provider rush for repo-backed dispatch.`);
|
|
114
114
|
}
|
|
115
|
-
const apiKey = this.resolveApiKey();
|
|
115
|
+
const apiKey = await this.resolveApiKey();
|
|
116
116
|
const model = options.model ?? this.model;
|
|
117
117
|
const body = buildInteractionBody(options.prompt, model);
|
|
118
118
|
let resp;
|
package/dist/lib/cloud/cursor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Cursor Cloud Agents provider backed by the public v1 REST API. */
|
|
2
2
|
import { normalizeProviderStatus, resolveDispatchRepos } from './types.js';
|
|
3
|
-
import { readAndResolveBundleEnv } from '../secrets
|
|
3
|
+
import { readAndResolveBundleEnv } from '../secrets-client.js';
|
|
4
4
|
export const CURSOR_API_BASE_URL = 'https://api.cursor.com/v1';
|
|
5
5
|
const KEY_NAME = 'CURSOR_API_KEY';
|
|
6
6
|
/** Translate unified dispatch options into Cursor's documented v1 create shape. */
|
|
@@ -111,10 +111,10 @@ export class CursorCloudProvider {
|
|
|
111
111
|
hasKeySource() {
|
|
112
112
|
return Boolean(this.secretsBundle || process.env[KEY_NAME]);
|
|
113
113
|
}
|
|
114
|
-
resolveApiKey() {
|
|
114
|
+
async resolveApiKey() {
|
|
115
115
|
if (this.secretsBundle) {
|
|
116
116
|
try {
|
|
117
|
-
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:cursor', agentOnly: true });
|
|
117
|
+
const { env } = await readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:cursor', agentOnly: true });
|
|
118
118
|
if (env[KEY_NAME])
|
|
119
119
|
return env[KEY_NAME];
|
|
120
120
|
throw new Error(`Secrets bundle '${this.secretsBundle}' has no ${KEY_NAME}. Add one: agents secrets add ${this.secretsBundle} ${KEY_NAME}`);
|
|
@@ -143,7 +143,7 @@ export class CursorCloudProvider {
|
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
async request(path, init) {
|
|
146
|
-
const apiKey = this.resolveApiKey();
|
|
146
|
+
const apiKey = await this.resolveApiKey();
|
|
147
147
|
let response;
|
|
148
148
|
try {
|
|
149
149
|
response = await fetch(`${CURSOR_API_BASE_URL}${path}`, {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* needs a provider token (e.g. HCLOUD_TOKEN) in the environment. We inject it
|
|
12
12
|
* from a secrets bundle when one is configured (see `crabboxEnv`).
|
|
13
13
|
*/
|
|
14
|
-
import {
|
|
14
|
+
import type { SecretsBundle } from '../secrets-types.js';
|
|
15
15
|
/** A crabbox machine as reported by `crabbox list --json`. */
|
|
16
16
|
export interface CrabboxBox {
|
|
17
17
|
/** Provider machine name, e.g. `crabbox-blue-hermit-1039689b`. */
|
package/dist/lib/crabbox/cli.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* from a secrets bundle when one is configured (see `crabboxEnv`).
|
|
13
13
|
*/
|
|
14
14
|
import { spawn, spawnSync } from 'child_process';
|
|
15
|
-
import {
|
|
15
|
+
import { readAndResolveBundleEnvSync, listBundlesSync, bundleExistsSync } from '../secrets-client.js';
|
|
16
16
|
import { readMeta, writeMeta } from '../state.js';
|
|
17
17
|
import { DEFAULT_CRABBOX_PROFILE } from './config.js';
|
|
18
18
|
/** Locate the crabbox binary, or throw an actionable error. */
|
|
@@ -59,7 +59,7 @@ function resolveTailscaleBundleMemo() {
|
|
|
59
59
|
if (!tailscaleBundleMemo) {
|
|
60
60
|
let value;
|
|
61
61
|
try {
|
|
62
|
-
value = pickTailscaleBundleFromList(
|
|
62
|
+
value = pickTailscaleBundleFromList(listBundlesSync());
|
|
63
63
|
}
|
|
64
64
|
catch {
|
|
65
65
|
/* secrets unreadable — no auto-detect */
|
|
@@ -88,7 +88,7 @@ function resolveTailscaleKeyValueMemo(ts) {
|
|
|
88
88
|
if (!tailscaleValueMemo) {
|
|
89
89
|
let value;
|
|
90
90
|
try {
|
|
91
|
-
const { env } =
|
|
91
|
+
const { env } = readAndResolveBundleEnvSync(ts.name, {
|
|
92
92
|
caller: 'agents run --lease (crabbox tailscale)',
|
|
93
93
|
keys: [ts.key],
|
|
94
94
|
agentOnly: true,
|
|
@@ -129,14 +129,14 @@ export function resolveLeaseBundle() {
|
|
|
129
129
|
return { name: env };
|
|
130
130
|
try {
|
|
131
131
|
const configured = readMeta().lease?.secretsBundle;
|
|
132
|
-
if (configured &&
|
|
132
|
+
if (configured && bundleExistsSync(configured))
|
|
133
133
|
return { name: configured };
|
|
134
134
|
}
|
|
135
135
|
catch {
|
|
136
136
|
/* config unreadable — fall through to auto-detect */
|
|
137
137
|
}
|
|
138
138
|
try {
|
|
139
|
-
const bundles =
|
|
139
|
+
const bundles = listBundlesSync();
|
|
140
140
|
const name = pickLeaseBundleFromList(bundles);
|
|
141
141
|
if (name) {
|
|
142
142
|
const b = bundles.find((x) => x.name === name);
|
|
@@ -187,7 +187,7 @@ function resolveLeaseEnvMemo(explicitBundle) {
|
|
|
187
187
|
// privilege; an unrelated bundle can't leak its other secrets into crabbox).
|
|
188
188
|
// An explicitly-named bundle (env/config or `opts.secretsBundle`) injects
|
|
189
189
|
// whole — the user chose it. Same resolver `agents secrets exec` uses.
|
|
190
|
-
const { env } =
|
|
190
|
+
const { env } = readAndResolveBundleEnvSync(resolved.name, {
|
|
191
191
|
caller: 'agents run --lease (crabbox)',
|
|
192
192
|
keys: resolved.keys,
|
|
193
193
|
// --lease is headless by contract and a locked bundle must fail loud with
|
|
@@ -78,9 +78,9 @@ export declare const CLAUDE_TOKEN_REMOTE = ".claude/.credentials.json";
|
|
|
78
78
|
* The RAW wrapped Claude credential payload (`{"claudeAiOauth":{…}}`) to write to
|
|
79
79
|
* the box's `~/.claude/.credentials.json`, or null if no signed-in token is found.
|
|
80
80
|
*
|
|
81
|
-
* On macOS the token is in the login Keychain, read SILENTLY via
|
|
82
|
-
* `
|
|
83
|
-
* no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
81
|
+
* On macOS the token is in the login Keychain, read SILENTLY via the standalone
|
|
82
|
+
* `secrets` client's `getKeychainTokenSync` (the `/usr/bin/security … -w` path —
|
|
83
|
+
* Claude's item trusts it, no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
84
84
|
* service; an agents-cli managed install (where `~/.claude` symlinks into a
|
|
85
85
|
* versioned home) uses a hash-suffixed service, so we try the bare service first,
|
|
86
86
|
* then enumerate installed version homes (preferring the account whose email
|
|
@@ -16,7 +16,7 @@ import * as os from 'os';
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import { getAccountInfo } from '../agents.js';
|
|
19
|
-
import {
|
|
19
|
+
import { getKeychainTokenSync } from '../secrets-client.js';
|
|
20
20
|
import { getClaudeKeychainService } from '../accounting/usage.js';
|
|
21
21
|
import { listInstalledVersions, getVersionHomePath } from '../installations/versions.js';
|
|
22
22
|
export const LEASE_RUNTIMES = [
|
|
@@ -185,9 +185,9 @@ function isClaudeCredentialsBlob(s) {
|
|
|
185
185
|
* The RAW wrapped Claude credential payload (`{"claudeAiOauth":{…}}`) to write to
|
|
186
186
|
* the box's `~/.claude/.credentials.json`, or null if no signed-in token is found.
|
|
187
187
|
*
|
|
188
|
-
* On macOS the token is in the login Keychain, read SILENTLY via
|
|
189
|
-
* `
|
|
190
|
-
* no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
188
|
+
* On macOS the token is in the login Keychain, read SILENTLY via the standalone
|
|
189
|
+
* `secrets` client's `getKeychainTokenSync` (the `/usr/bin/security … -w` path —
|
|
190
|
+
* Claude's item trusts it, no Touch ID). A default native install uses the bare `Claude Code-credentials`
|
|
191
191
|
* service; an agents-cli managed install (where `~/.claude` symlinks into a
|
|
192
192
|
* versioned home) uses a hash-suffixed service, so we try the bare service first,
|
|
193
193
|
* then enumerate installed version homes (preferring the account whose email
|
|
@@ -203,7 +203,7 @@ function isClaudeCredentialsBlob(s) {
|
|
|
203
203
|
* real Keychain.
|
|
204
204
|
*/
|
|
205
205
|
export async function resolveClaudeCredentialsBlob(opts) {
|
|
206
|
-
const readItem = opts?.readItem ??
|
|
206
|
+
const readItem = opts?.readItem ?? getKeychainTokenSync;
|
|
207
207
|
const service = opts?.service ?? getClaudeKeychainService;
|
|
208
208
|
const listVersions = opts?.listVersions ?? (() => listInstalledVersions('claude'));
|
|
209
209
|
const versionHome = opts?.versionHome ?? ((v) => getVersionHomePath('claude', v));
|