@phnx-labs/agents-cli 1.22.83 → 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 +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/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/sessions-trace.d.ts +11 -0
- package/dist/commands/sessions-trace.js +71 -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-ticks.js +23 -2
- 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/feed/activity-stream.d.ts +60 -0
- package/dist/lib/feed/activity-stream.js +271 -0
- package/dist/lib/feed/activity.d.ts +7 -0
- package/dist/lib/feed/activity.js +10 -4
- package/dist/lib/feed/watch.d.ts +9 -3
- package/dist/lib/feed/watch.js +72 -36
- 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/fleet-shared-state.d.ts +6 -0
- 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/active.d.ts +38 -4
- package/dist/lib/session/active.js +36 -4
- package/dist/lib/session/bash-command.js +10 -0
- package/dist/lib/session/db.d.ts +65 -2
- package/dist/lib/session/db.js +178 -18
- package/dist/lib/session/mirror.d.ts +5 -0
- package/dist/lib/session/mirror.js +166 -0
- package/dist/lib/session/parse.d.ts +49 -0
- package/dist/lib/session/parse.js +324 -30
- package/dist/lib/session/prompt.d.ts +93 -2
- package/dist/lib/session/prompt.js +266 -15
- package/dist/lib/session/remote/peer-stream.d.ts +47 -0
- package/dist/lib/session/remote/peer-stream.js +142 -0
- package/dist/lib/session/remote/watch.d.ts +1 -1
- package/dist/lib/session/remote/watch.js +41 -42
- package/dist/lib/session/session-cache.d.ts +9 -0
- package/dist/lib/session/session-cache.js +40 -2
- package/dist/lib/session/sync/config.js +2 -2
- package/dist/lib/session/timeline-pass.d.ts +129 -0
- package/dist/lib/session/timeline-pass.js +323 -0
- package/dist/lib/session/timeline.d.ts +182 -0
- package/dist/lib/session/timeline.js +636 -0
- package/dist/lib/session/types.d.ts +155 -1
- 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/summarizer/pass.d.ts +2 -0
- package/dist/lib/summarizer/pass.js +14 -1
- package/dist/lib/summarizer/summarize.d.ts +7 -0
- package/dist/lib/summarizer/summarize.js +3 -0
- 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 -170
- package/dist/lib/accounts/connect.js +0 -383
- 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
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* per-harness branch in
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Config-dir env keys a harness pins to its slot / version home. Every
|
|
3
|
+
* per-harness branch in buildExecEnv deletes the ones it does NOT set, so a
|
|
4
|
+
* slot never inherits another account's dir (PHNX-3940 T5). GROK_HOME,
|
|
5
|
+
* OPENCODE_CONFIG_DIR, and the XDG pair were previously omitted, which let a
|
|
6
|
+
* parent agent's pin leak into a sibling slot.
|
|
7
7
|
*/
|
|
8
8
|
export const CONFIG_DIR_ENV_KEYS = [
|
|
9
9
|
'CLAUDE_CONFIG_DIR',
|
|
10
10
|
'CODEX_HOME',
|
|
11
11
|
'COPILOT_HOME',
|
|
12
12
|
'KIMI_CODE_HOME',
|
|
13
|
+
'GROK_HOME',
|
|
14
|
+
'OPENCODE_CONFIG_DIR',
|
|
15
|
+
'XDG_CONFIG_HOME',
|
|
16
|
+
'XDG_DATA_HOME',
|
|
13
17
|
];
|
|
14
18
|
/**
|
|
15
19
|
* Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
|
|
16
|
-
* (the default / no-config-dir harness) deletes all
|
|
20
|
+
* (the default / no-config-dir harness) deletes all of them — the old `else` arm.
|
|
17
21
|
*/
|
|
18
22
|
export function stripForeignConfigDir(result, keep = []) {
|
|
19
23
|
for (const key of CONFIG_DIR_ENV_KEYS) {
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
// whole config tree via GROK_HOME in the generated shim.
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { stripForeignConfigDir } from '../adapter.js';
|
|
4
3
|
export const grokAdapter = {
|
|
5
4
|
id: 'grok',
|
|
5
|
+
applyExecConfigEnv(result, ctx) {
|
|
6
|
+
if (ctx.versionHome) {
|
|
7
|
+
result.GROK_HOME = path.join(ctx.versionHome, '.grok');
|
|
8
|
+
}
|
|
9
|
+
stripForeignConfigDir(result, ['GROK_HOME']);
|
|
10
|
+
},
|
|
6
11
|
shimConfigEnvBash() {
|
|
7
12
|
return `
|
|
8
13
|
# Grok Build uses GROK_HOME to isolate its entire configuration tree
|
|
@@ -13,7 +13,7 @@ export const museAdapter = {
|
|
|
13
13
|
result.XDG_CONFIG_HOME = path.join(ctx.versionHome, '.config');
|
|
14
14
|
result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
|
|
15
15
|
}
|
|
16
|
-
stripForeignConfigDir(result);
|
|
16
|
+
stripForeignConfigDir(result, ['XDG_CONFIG_HOME', 'XDG_DATA_HOME']);
|
|
17
17
|
},
|
|
18
18
|
shimConfigEnvBash() {
|
|
19
19
|
return `
|
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { stripForeignConfigDir } from '../adapter.js';
|
|
3
|
+
// OpenCode reads plugins/agents/commands from OPENCODE_CONFIG_DIR and auth from
|
|
4
|
+
// $XDG_DATA_HOME/opencode (HARNESS_AUTH.slotEnv). Pin both at the slot / version
|
|
5
|
+
// home so two accounts in one install never share a credential (PHNX-3940 T5).
|
|
4
6
|
export const opencodeAdapter = {
|
|
5
7
|
id: 'opencode',
|
|
8
|
+
applyExecConfigEnv(result, ctx) {
|
|
9
|
+
if (ctx.versionHome) {
|
|
10
|
+
result.OPENCODE_CONFIG_DIR = path.join(ctx.versionHome, '.config', 'opencode');
|
|
11
|
+
result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
|
|
12
|
+
}
|
|
13
|
+
stripForeignConfigDir(result, ['OPENCODE_CONFIG_DIR', 'XDG_DATA_HOME']);
|
|
14
|
+
},
|
|
6
15
|
shimConfigEnvBash() {
|
|
7
16
|
return `
|
|
8
17
|
# OpenCode reads plugins, agents, commands, and other config-directory
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-harness auth capability table (PHNX-3940).
|
|
3
|
+
*
|
|
4
|
+
* One row per `ALL_AGENT_IDS` entry. Governs `accounts add` / connect login
|
|
5
|
+
* argv, identity strength, the durable worker credential (if any), and the
|
|
6
|
+
* config-dir env that pins a slot. Completeness is pinned by the test against
|
|
7
|
+
* `ALL_AGENT_IDS` — a new harness without a row is a type error and a failing
|
|
8
|
+
* test, never a silent skip.
|
|
9
|
+
*
|
|
10
|
+
* Values are taken from evidence-harness-auth.md (2026-09-06) and the adapters
|
|
11
|
+
* under `lib/harness/adapters/`. `LOGIN_INVOCATIONS` is the connect-era subset
|
|
12
|
+
* (claude, codex) derived from this table so existing connect callers keep
|
|
13
|
+
* working until T4 folds connect into `accounts add`.
|
|
14
|
+
*/
|
|
15
|
+
import type { AgentId } from './types.js';
|
|
16
|
+
export type HarnessIdentityKind = 'strong' | 'email' | 'opaque';
|
|
17
|
+
/**
|
|
18
|
+
* Durable worker credential, or `none` when the harness must log in per box.
|
|
19
|
+
* Codex is both: an API key (portable, bills the API) OR a per-device
|
|
20
|
+
* ChatGPT-plan device-auth login (the plan seat; never stored in the reserved
|
|
21
|
+
* store because it is a rotating session).
|
|
22
|
+
*/
|
|
23
|
+
export type HarnessWorkerKind = 'setup-token' | 'none' | `api-key:${string}` | `per-device${'' | `:${string}`}`;
|
|
24
|
+
export type HarnessWorker = HarnessWorkerKind | HarnessWorkerKind[];
|
|
25
|
+
export interface HarnessAuthCapability {
|
|
26
|
+
/** argv after the harness binary to start native login, or null when there is no finite login command. */
|
|
27
|
+
login: string[] | null;
|
|
28
|
+
/** argv to probe login status, or null when the CLI has no status command. */
|
|
29
|
+
status: string[] | null;
|
|
30
|
+
identity: HarnessIdentityKind;
|
|
31
|
+
worker: HarnessWorker;
|
|
32
|
+
/** Config-dir env that pins a slot, or null when isolation is HOME-swap only. */
|
|
33
|
+
slotEnv: string | null;
|
|
34
|
+
}
|
|
35
|
+
export declare const HARNESS_AUTH: Record<AgentId, HarnessAuthCapability>;
|
|
36
|
+
export declare function harnessAuth(agent: AgentId): HarnessAuthCapability;
|
|
37
|
+
/** Worker kinds as a flat list (codex's dual path is two entries). */
|
|
38
|
+
export declare function harnessWorkerKinds(agent: AgentId): HarnessWorkerKind[];
|
|
39
|
+
/**
|
|
40
|
+
* True when this harness has no portable worker credential and must log in
|
|
41
|
+
* per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
|
|
42
|
+
* kind both count; a dual path like Codex (API key OR device-auth) does not.
|
|
43
|
+
*/
|
|
44
|
+
export declare function harnessWorkerIsPerDevice(agent: AgentId): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Native-login invocation per harness. Only harnesses with a REAL, finite login
|
|
47
|
+
* COMMAND that connect currently drives — connect fails clearly for anything
|
|
48
|
+
* else rather than faking a flow that never signs the user in. Verified against
|
|
49
|
+
* the installed CLIs (PHNX-3940): `claude auth login --help` → "Sign in to your
|
|
50
|
+
* Anthropic account" with `--email`; `codex login` drives the OAuth flow.
|
|
51
|
+
* Args are the `HARNESS_AUTH.login` values for the same ids.
|
|
52
|
+
*/
|
|
53
|
+
export interface LoginInvocation {
|
|
54
|
+
/** argv passed to the installed binary to start the native login. */
|
|
55
|
+
args: string[];
|
|
56
|
+
/** Flag that pre-fills the login email (appended as `[emailFlag, email]`), when supported. */
|
|
57
|
+
emailFlag?: string;
|
|
58
|
+
/** One-line hint shown before the login flow takes over. */
|
|
59
|
+
hint?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare const LOGIN_INVOCATIONS: Partial<Record<AgentId, LoginInvocation>>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export const HARNESS_AUTH = {
|
|
2
|
+
claude: { login: ['auth', 'login'], status: ['auth', 'status'], identity: 'strong', worker: 'setup-token', slotEnv: 'CLAUDE_CONFIG_DIR' },
|
|
3
|
+
codex: { login: ['login'], status: ['login', 'status'], identity: 'strong', worker: ['api-key:OPENAI_API_KEY', 'per-device:device-auth'], slotEnv: 'CODEX_HOME' },
|
|
4
|
+
grok: { login: ['login'], status: null, identity: 'strong', worker: 'api-key:XAI_API_KEY', slotEnv: 'GROK_HOME' },
|
|
5
|
+
// auth.json has no email claim; identity is the sorted provider-id join
|
|
6
|
+
// (`resolveOpenCodeAccountId`). NATIVE_ACCOUNT_CAPABILITIES.opencode.inspection
|
|
7
|
+
// is already 'opaque'.
|
|
8
|
+
opencode: { login: ['auth', 'login'], status: ['auth', 'list'], identity: 'opaque', worker: 'api-key:provider', slotEnv: 'XDG_DATA_HOME' },
|
|
9
|
+
cursor: { login: ['login'], status: ['status'], identity: 'strong', worker: 'api-key:CURSOR_API_KEY', slotEnv: null },
|
|
10
|
+
// No finite login argv — launch bare `kimi`, then `/login` in the TUI
|
|
11
|
+
// (`loginHint('kimi') === 'kimi'`, fleet/auth-sync `loginCommand: 'kimi'`).
|
|
12
|
+
kimi: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'KIMI_CODE_HOME' },
|
|
13
|
+
antigravity: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
14
|
+
droid: { login: null, status: null, identity: 'opaque', worker: 'api-key:FACTORY_API_KEY', slotEnv: null },
|
|
15
|
+
gemini: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: null },
|
|
16
|
+
copilot: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'COPILOT_HOME' },
|
|
17
|
+
openclaw: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
18
|
+
amp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
19
|
+
goose: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
20
|
+
hermes: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
21
|
+
// Auth lives under $XDG_CONFIG_HOME/muse (adapter pins XDG_CONFIG_HOME +
|
|
22
|
+
// XDG_DATA_HOME). slotEnv names the config pin; T5 must not HOME-swap.
|
|
23
|
+
muse: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: 'XDG_CONFIG_HOME' },
|
|
24
|
+
warp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
|
|
25
|
+
};
|
|
26
|
+
export function harnessAuth(agent) {
|
|
27
|
+
const cap = HARNESS_AUTH[agent];
|
|
28
|
+
if (!cap)
|
|
29
|
+
throw new Error(`No harness-auth capability for '${agent}'.`);
|
|
30
|
+
return cap;
|
|
31
|
+
}
|
|
32
|
+
/** Worker kinds as a flat list (codex's dual path is two entries). */
|
|
33
|
+
export function harnessWorkerKinds(agent) {
|
|
34
|
+
const worker = harnessAuth(agent).worker;
|
|
35
|
+
return Array.isArray(worker) ? worker : [worker];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* True when this harness has no portable worker credential and must log in
|
|
39
|
+
* per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
|
|
40
|
+
* kind both count; a dual path like Codex (API key OR device-auth) does not.
|
|
41
|
+
*/
|
|
42
|
+
export function harnessWorkerIsPerDevice(agent) {
|
|
43
|
+
return harnessWorkerKinds(agent).every((kind) => kind === 'none' || kind.startsWith('per-device'));
|
|
44
|
+
}
|
|
45
|
+
export const LOGIN_INVOCATIONS = {
|
|
46
|
+
claude: {
|
|
47
|
+
args: HARNESS_AUTH.claude.login,
|
|
48
|
+
emailFlag: '--email',
|
|
49
|
+
hint: 'Complete the Claude sign-in in your browser.',
|
|
50
|
+
},
|
|
51
|
+
codex: { args: HARNESS_AUTH.codex.login },
|
|
52
|
+
};
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* never be derived from `getCliVersion()`.
|
|
28
28
|
*/
|
|
29
29
|
/** The helpers that have their own release train. */
|
|
30
|
-
export type HelperName = 'menubar' | '
|
|
30
|
+
export type HelperName = 'menubar' | 'computer-mac' | 'computer-win';
|
|
31
31
|
/** One helper's release identity. */
|
|
32
32
|
export interface HelperRelease {
|
|
33
33
|
/** Tag prefix — the release is `<tagPrefix>/v<version>`. */
|
|
@@ -41,9 +41,11 @@ export interface HelperRelease {
|
|
|
41
41
|
/**
|
|
42
42
|
* Floors, by helper.
|
|
43
43
|
*
|
|
44
|
-
* `menubar`
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* `menubar` starts at 1.0.0 — the first build published under its own tag
|
|
45
|
+
* rather than the CLI's. It is not "version 1 of the helper"; it is version 1
|
|
46
|
+
* of its independent release train. The keychain helper moved with the
|
|
47
|
+
* standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
|
|
48
|
+
* helper release now, off this table entirely.
|
|
47
49
|
*/
|
|
48
50
|
export declare const HELPER_RELEASES: Readonly<Record<HelperName, HelperRelease>>;
|
|
49
51
|
/** The release tag for one helper at one version, e.g. `menubar/v1.0.0`. */
|
|
@@ -29,13 +29,14 @@
|
|
|
29
29
|
/**
|
|
30
30
|
* Floors, by helper.
|
|
31
31
|
*
|
|
32
|
-
* `menubar`
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* `menubar` starts at 1.0.0 — the first build published under its own tag
|
|
33
|
+
* rather than the CLI's. It is not "version 1 of the helper"; it is version 1
|
|
34
|
+
* of its independent release train. The keychain helper moved with the
|
|
35
|
+
* standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
|
|
36
|
+
* helper release now, off this table entirely.
|
|
35
37
|
*/
|
|
36
38
|
export const HELPER_RELEASES = {
|
|
37
39
|
menubar: { tagPrefix: 'menubar', floor: '1.0.0' },
|
|
38
|
-
keychain: { tagPrefix: 'keychain', floor: '1.0.0' },
|
|
39
40
|
'computer-mac': { tagPrefix: 'computer-mac', floor: '1.0.0' },
|
|
40
41
|
// The Windows helper is a bare .exe, not an .app bundle, so it does not share
|
|
41
42
|
// helper-download.ts's zip/codesign/notarize machinery -- but it has the same
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
|
|
2
|
+
export declare function hostKeyLookupName(target: string): string;
|
|
3
|
+
/** Refuse durable credential transfer until the destination SSH key is pinned. */
|
|
4
|
+
export declare function assertCredentialTransportHostPinned(target: string, pinned?: boolean): void;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve a `--device` value to an ssh target string for a credential-carrying
|
|
7
|
+
* remote operation. Delegates to the same resolver `run --device` uses; on a
|
|
8
|
+
* miss, treats the value as a raw ssh target and validates it.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveHostSshTarget(nameOrAlias: string): Promise<string>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential transport policy for a fleet host (agents-owned, PHNX-3989).
|
|
3
|
+
*
|
|
4
|
+
* Whether a durable credential may leave this device for a given host is
|
|
5
|
+
* agents fleet policy, not a secrets-engine concern: it hangs off the managed
|
|
6
|
+
* `known_hosts` pin and the hosts/devices registry. The standalone `secrets`
|
|
7
|
+
* CLI only carries the bundle once agents-cli has decided the destination is
|
|
8
|
+
* trustworthy, so these guards live here and are passed INTO the transport,
|
|
9
|
+
* never re-implemented on the engine side.
|
|
10
|
+
*/
|
|
11
|
+
import { assertValidSshTarget } from '../ssh-exec.js';
|
|
12
|
+
import { resolveHost } from './registry.js';
|
|
13
|
+
import { sshTargetFor } from './types.js';
|
|
14
|
+
import { isHostPinned } from '../devices/known-hosts.js';
|
|
15
|
+
/** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
|
|
16
|
+
export function hostKeyLookupName(target) {
|
|
17
|
+
return target.split('@').pop() ?? target;
|
|
18
|
+
}
|
|
19
|
+
/** Refuse durable credential transfer until the destination SSH key is pinned. */
|
|
20
|
+
export function assertCredentialTransportHostPinned(target, pinned = isHostPinned(hostKeyLookupName(target))) {
|
|
21
|
+
if (pinned)
|
|
22
|
+
return;
|
|
23
|
+
throw new Error(`Refusing to transfer provider credentials to '${target}' before its SSH host key is pinned. ` +
|
|
24
|
+
`Connect once with 'agents ssh ${target}' and verify the host, then retry.`);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a `--device` value to an ssh target string for a credential-carrying
|
|
28
|
+
* remote operation. Delegates to the same resolver `run --device` uses; on a
|
|
29
|
+
* miss, treats the value as a raw ssh target and validates it.
|
|
30
|
+
*/
|
|
31
|
+
export async function resolveHostSshTarget(nameOrAlias) {
|
|
32
|
+
const host = await resolveHost(nameOrAlias);
|
|
33
|
+
if (host)
|
|
34
|
+
return sshTargetFor(host);
|
|
35
|
+
assertValidSshTarget(nameOrAlias);
|
|
36
|
+
return nameOrAlias;
|
|
37
|
+
}
|
|
@@ -170,6 +170,13 @@ export interface DispatchOptions {
|
|
|
170
170
|
* (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
|
|
171
171
|
* the table side.
|
|
172
172
|
*/
|
|
173
|
+
/** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
|
|
174
|
+
export declare function runAgentSpecArg(opts: {
|
|
175
|
+
agent: string;
|
|
176
|
+
version?: string;
|
|
177
|
+
account?: string;
|
|
178
|
+
accountPicker?: boolean;
|
|
179
|
+
}): string;
|
|
173
180
|
export declare function buildRunForwardedArgs(opts: DispatchOptions): string[];
|
|
174
181
|
export interface InteractiveDispatchOptions {
|
|
175
182
|
agent: string;
|
|
@@ -359,8 +359,19 @@ async function launchDetached(host, target, opts) {
|
|
|
359
359
|
* (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
|
|
360
360
|
* the table side.
|
|
361
361
|
*/
|
|
362
|
+
/** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
|
|
363
|
+
export function runAgentSpecArg(opts) {
|
|
364
|
+
if (opts.accountPicker)
|
|
365
|
+
return `${opts.agent}@`;
|
|
366
|
+
let spec = opts.agent;
|
|
367
|
+
if (opts.version)
|
|
368
|
+
spec += `@${opts.version}`;
|
|
369
|
+
if (opts.account)
|
|
370
|
+
spec += `#${opts.account}`;
|
|
371
|
+
return spec;
|
|
372
|
+
}
|
|
362
373
|
export function buildRunForwardedArgs(opts) {
|
|
363
|
-
const agentArg = opts
|
|
374
|
+
const agentArg = runAgentSpecArg(opts);
|
|
364
375
|
const args = ['run', agentArg, opts.prompt, '--quiet'];
|
|
365
376
|
if (opts.mode)
|
|
366
377
|
args.push('--mode', opts.mode);
|
|
@@ -427,11 +438,7 @@ export function buildInteractiveRunForwardedArgs(opts) {
|
|
|
427
438
|
if (opts.version && opts.accountPicker) {
|
|
428
439
|
throw new Error('Interactive host dispatch cannot combine an account picker with a version pin');
|
|
429
440
|
}
|
|
430
|
-
const agentArg = opts
|
|
431
|
-
? `${opts.agent}@`
|
|
432
|
-
: opts.version
|
|
433
|
-
? `${opts.agent}@${opts.version}`
|
|
434
|
-
: opts.agent;
|
|
441
|
+
const agentArg = runAgentSpecArg(opts);
|
|
435
442
|
const args = ['run', agentArg];
|
|
436
443
|
if (opts.prompt && opts.forceInteractive)
|
|
437
444
|
args.push(opts.prompt);
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* here and nothing else.
|
|
11
11
|
*
|
|
12
12
|
* The shape mirrors the seams this repo already proved elsewhere —
|
|
13
|
-
*
|
|
14
|
-
* (`lib/cloud/types.ts`) — so a second identity backend, if
|
|
15
|
-
* needed, is a swap here rather than a sweep across commands.
|
|
13
|
+
* the bounded secrets process client (`lib/secrets-client.ts`) and
|
|
14
|
+
* `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
|
|
15
|
+
* one is ever needed, is a swap here rather than a sweep across commands.
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
18
|
* Where the account backend lives. Config, never a literal at a call site.
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
* here and nothing else.
|
|
11
11
|
*
|
|
12
12
|
* The shape mirrors the seams this repo already proved elsewhere —
|
|
13
|
-
*
|
|
14
|
-
* (`lib/cloud/types.ts`) — so a second identity backend, if
|
|
15
|
-
* needed, is a swap here rather than a sweep across commands.
|
|
13
|
+
* the bounded secrets process client (`lib/secrets-client.ts`) and
|
|
14
|
+
* `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
|
|
15
|
+
* one is ever needed, is a swap here rather than a sweep across commands.
|
|
16
16
|
*/
|
|
17
17
|
import * as fs from 'fs';
|
|
18
18
|
import * as path from 'path';
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* per-installation isolation track consumes exactly this).
|
|
9
9
|
*/
|
|
10
10
|
export { type Installation, type InstallationRelease, type UpdateOutcome, type UpdateStrategyId, type UpdatePolicy, } from './types.js';
|
|
11
|
-
export { createInstallation, listInstallations, recordRelease, } from './store.js';
|
|
11
|
+
export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, type EnsureHarnessInstallationResult, } from './store.js';
|
|
12
12
|
export { describeInstallation, resolveInstallation, type ResolveInstallationOptions, } from './resolve.js';
|
|
13
13
|
export { selectUpdateStrategy, supportsPinnedUpdate, type UpdateStrategy, } from './strategies.js';
|
|
14
14
|
export { updateInstallation, type UpdateInstallationOptions } from './update.js';
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* it, so the internal split can change without breaking callers (the Cursor
|
|
8
8
|
* per-installation isolation track consumes exactly this).
|
|
9
9
|
*/
|
|
10
|
-
export { createInstallation, listInstallations, recordRelease, } from './store.js';
|
|
10
|
+
export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, } from './store.js';
|
|
11
11
|
export { describeInstallation, resolveInstallation, } from './resolve.js';
|
|
12
12
|
export { selectUpdateStrategy, supportsPinnedUpdate, } from './strategies.js';
|
|
13
13
|
export { updateInstallation } from './update.js';
|
|
@@ -2366,4 +2366,13 @@ export async function runMigration() {
|
|
|
2366
2366
|
catch {
|
|
2367
2367
|
// best-effort — a migration must never fail because tmux wasn't reachable
|
|
2368
2368
|
}
|
|
2369
|
+
// PHNX-3940 T7: report leftover per-account installations. Dry-run only —
|
|
2370
|
+
// moving homes requires an explicit `agents accounts migrate --apply`.
|
|
2371
|
+
try {
|
|
2372
|
+
const { reportAccountSlotMigrationOnUpgrade } = await import('../accounts/migrate.js');
|
|
2373
|
+
await reportAccountSlotMigrationOnUpgrade();
|
|
2374
|
+
}
|
|
2375
|
+
catch {
|
|
2376
|
+
// best-effort — a corrupt install must not block the rest of upgrade
|
|
2377
|
+
}
|
|
2369
2378
|
}
|
|
@@ -21,7 +21,7 @@ export class InstallationNotFoundError extends Error {
|
|
|
21
21
|
// Nothing installed is a different problem from "your selector missed", and
|
|
22
22
|
// the remedy differs — say which one it is rather than printing an empty list.
|
|
23
23
|
super(available.length === 0
|
|
24
|
-
? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}
|
|
24
|
+
? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}`
|
|
25
25
|
: `No ${AGENTS[agent].name} installation matches '${selector}'. Installed: ${available.map((i) => describeInstallation(i)).join(', ')}`);
|
|
26
26
|
this.agent = agent;
|
|
27
27
|
this.selector = selector;
|
|
@@ -201,6 +201,21 @@ export declare const CONFIG_ENV_ISOLATED_AGENTS: readonly AgentId[];
|
|
|
201
201
|
* separate from the user's real `~/.<agent>`. See {@link CONFIG_ENV_ISOLATED_AGENTS}.
|
|
202
202
|
*/
|
|
203
203
|
export declare function supportsIsolatedInstall(agent: AgentId): boolean;
|
|
204
|
+
/**
|
|
205
|
+
* Harnesses that isolate by symlink-adopting `~/.<config>` rather than a
|
|
206
|
+
* config-dir env (PHNX-3940 T5). One active slot per device; `accounts default`
|
|
207
|
+
* repoints the adopted symlink under the auth-op lock.
|
|
208
|
+
*/
|
|
209
|
+
export declare function isSymlinkAdoptedHarness(agent: AgentId): boolean;
|
|
210
|
+
/**
|
|
211
|
+
* Repoint this harness's adopted `~/.<config>` symlink at `home`'s config dir.
|
|
212
|
+
* No-op for env-isolated harnesses. Fails loud when the adopted path is a real
|
|
213
|
+
* directory rather than a symlink — adopting that is `agents use`, not default.
|
|
214
|
+
*/
|
|
215
|
+
export declare function repointAdoptedConfigToHome(agent: AgentId, home: string): {
|
|
216
|
+
success: boolean;
|
|
217
|
+
error?: string;
|
|
218
|
+
};
|
|
204
219
|
/**
|
|
205
220
|
* Generate a versioned alias script that directly execs a specific version.
|
|
206
221
|
* e.g., claude@2.0.65 -> directly runs that version's binary
|
|
@@ -10,6 +10,7 @@ import { IS_WINDOWS, prependToWindowsUserPath } from '../platform/index.js';
|
|
|
10
10
|
import { getShimsDir, getVersionsDir, getBackupsDir, getHistoryDir, ensureAgentsDir } from '../state.js';
|
|
11
11
|
export { getShimsDir };
|
|
12
12
|
import { AGENTS, agentConfigDirName, readAuthAccountIdentity } from '../agents.js';
|
|
13
|
+
import { acquireAuthOperationLock } from '../accounts/auth-operation-lock.js';
|
|
13
14
|
import { codexHomeShimBash } from '../codex-home.js';
|
|
14
15
|
import { resolveHarnessAdapter } from '../harness/index.js';
|
|
15
16
|
import { randomUUID } from 'node:crypto';
|
|
@@ -1130,6 +1131,74 @@ export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'cursor
|
|
|
1130
1131
|
export function supportsIsolatedInstall(agent) {
|
|
1131
1132
|
return CONFIG_ENV_ISOLATED_AGENTS.includes(agent);
|
|
1132
1133
|
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Harnesses that isolate by symlink-adopting `~/.<config>` rather than a
|
|
1136
|
+
* config-dir env (PHNX-3940 T5). One active slot per device; `accounts default`
|
|
1137
|
+
* repoints the adopted symlink under the auth-op lock.
|
|
1138
|
+
*/
|
|
1139
|
+
export function isSymlinkAdoptedHarness(agent) {
|
|
1140
|
+
return !CONFIG_ENV_ISOLATED_AGENTS.includes(agent);
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Repoint this harness's adopted `~/.<config>` symlink at `home`'s config dir.
|
|
1144
|
+
* No-op for env-isolated harnesses. Fails loud when the adopted path is a real
|
|
1145
|
+
* directory rather than a symlink — adopting that is `agents use`, not default.
|
|
1146
|
+
*/
|
|
1147
|
+
export function repointAdoptedConfigToHome(agent, home) {
|
|
1148
|
+
if (!isSymlinkAdoptedHarness(agent))
|
|
1149
|
+
return { success: true };
|
|
1150
|
+
const lock = acquireAuthOperationLock(agent);
|
|
1151
|
+
try {
|
|
1152
|
+
lock.assertHeld();
|
|
1153
|
+
const configPath = getAgentConfigPath(agent);
|
|
1154
|
+
const configDirName = path.relative(os.homedir(), AGENTS[agent].configDir);
|
|
1155
|
+
const target = path.join(home, configDirName);
|
|
1156
|
+
fs.mkdirSync(target, { recursive: true });
|
|
1157
|
+
try {
|
|
1158
|
+
const stat = fs.lstatSync(configPath);
|
|
1159
|
+
if (stat.isSymbolicLink()) {
|
|
1160
|
+
const current = path.resolve(path.dirname(configPath), fs.readlinkSync(configPath));
|
|
1161
|
+
if (current === path.resolve(target))
|
|
1162
|
+
return { success: true };
|
|
1163
|
+
}
|
|
1164
|
+
else {
|
|
1165
|
+
return {
|
|
1166
|
+
success: false,
|
|
1167
|
+
error: `${configPath} is not a symlink; refusing to replace a real config directory. `
|
|
1168
|
+
+ `Run \`agents use ${agent}\` once to adopt it, then \`agents accounts default ${agent}\`.`,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
catch (err) {
|
|
1173
|
+
if (err.code !== 'ENOENT')
|
|
1174
|
+
throw err;
|
|
1175
|
+
}
|
|
1176
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
1177
|
+
// Atomic retarget: create temp symlink, rename over existing (same pattern
|
|
1178
|
+
// as switchHomeFileSymlinks). A window of "path missing" would let a
|
|
1179
|
+
// harness process observe neither the old nor the new target.
|
|
1180
|
+
const tmpPath = `${configPath}.agents-tmp-${process.pid}`;
|
|
1181
|
+
try {
|
|
1182
|
+
fs.unlinkSync(tmpPath);
|
|
1183
|
+
}
|
|
1184
|
+
catch { /* leftover from a killed prior swap */ }
|
|
1185
|
+
try {
|
|
1186
|
+
fs.symlinkSync(target, tmpPath, process.platform === 'win32' ? 'junction' : undefined);
|
|
1187
|
+
fs.renameSync(tmpPath, configPath);
|
|
1188
|
+
}
|
|
1189
|
+
catch (err) {
|
|
1190
|
+
try {
|
|
1191
|
+
fs.unlinkSync(tmpPath);
|
|
1192
|
+
}
|
|
1193
|
+
catch { /* rename may have already consumed it */ }
|
|
1194
|
+
throw err;
|
|
1195
|
+
}
|
|
1196
|
+
return { success: true };
|
|
1197
|
+
}
|
|
1198
|
+
finally {
|
|
1199
|
+
lock.release();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1133
1202
|
/**
|
|
1134
1203
|
* Generate a versioned alias script that directly execs a specific version.
|
|
1135
1204
|
* e.g., claude@2.0.65 -> directly runs that version's binary
|
|
@@ -60,6 +60,42 @@ export declare function listInstallationLabels(agent: AgentId): string[];
|
|
|
60
60
|
* that disappears mid-scan is skipped rather than failing the whole listing.
|
|
61
61
|
*/
|
|
62
62
|
export declare function listInstallations(agent: AgentId): Installation[];
|
|
63
|
+
/**
|
|
64
|
+
* The label of the single managed installation every harness gets under the
|
|
65
|
+
* account model v2 (PHNX-3940). One managed install per harness per device;
|
|
66
|
+
* accounts live in credential slots beside it, and a second home is an expert
|
|
67
|
+
* `--isolated` copy, never another managed install.
|
|
68
|
+
*/
|
|
69
|
+
export declare const MANAGED_INSTALLATION_LABEL = "main";
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the ONE managed installation of a harness, or null when the harness
|
|
72
|
+
* has no managed install yet. The winner is deterministic: the `main` label
|
|
73
|
+
* first, then the recorded global default (the install the user actually
|
|
74
|
+
* launches), then the first remaining non-isolated installation by label.
|
|
75
|
+
* Isolated copies (`agents add --isolated`) never count — they are expert
|
|
76
|
+
* second homes, not the managed install.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveManagedInstallation(agent: AgentId): Installation | null;
|
|
79
|
+
export interface EnsureHarnessInstallationResult {
|
|
80
|
+
installation: Installation;
|
|
81
|
+
/** True when this call installed the harness just now; false when the managed installation already existed and was reused unchanged. */
|
|
82
|
+
installed: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The taught entry point for "make this harness runnable" (PHNX-3940): return
|
|
86
|
+
* the harness's single managed installation, installing the current release
|
|
87
|
+
* into the `main` label when the harness is not installed yet. `release` pins
|
|
88
|
+
* the fresh install to a concrete release (recorded as a pinned update policy
|
|
89
|
+
* by `installVersion`); it has no effect when an installation already exists —
|
|
90
|
+
* moving an existing install is `agents update <agent> --to <release>`'s job.
|
|
91
|
+
*
|
|
92
|
+
* Throws (fail loud) when the install fails; never returns a half-installed
|
|
93
|
+
* record.
|
|
94
|
+
*/
|
|
95
|
+
export declare function ensureHarnessInstallation(agent: AgentId, opts?: {
|
|
96
|
+
release?: string;
|
|
97
|
+
onProgress?: (message: string) => void;
|
|
98
|
+
}): Promise<EnsureHarnessInstallationResult>;
|
|
63
99
|
/**
|
|
64
100
|
* Get the directory where a specific version is installed.
|
|
65
101
|
*/
|
|
@@ -154,6 +190,13 @@ export declare function pickCanonicalGlobalBinaryVersion(agent: AgentId, version
|
|
|
154
190
|
* For a self-updating global-binary agent (droid) every version dir resolves to
|
|
155
191
|
* the SAME binary, so this collapses them to a single canonical entry — one
|
|
156
192
|
* install, one row in `agents view`, never the phantom set of semver dir names.
|
|
193
|
+
*
|
|
194
|
+
* A directory is an installation only when it has a working launch binary.
|
|
195
|
+
* Account credential slots (PHNX-3940, `~/.agents/.history/accounts/**`) are
|
|
196
|
+
* HOME-shaped dirs with no binary and no `installation.json`, so they can
|
|
197
|
+
* never appear here. Record-less pre-frozen installs are reported as-is; their
|
|
198
|
+
* migration belongs to the record-based paths (`listInstallations`), never to
|
|
199
|
+
* this read surface.
|
|
157
200
|
*/
|
|
158
201
|
export declare function listInstalledVersions(agent: AgentId): string[];
|
|
159
202
|
/**
|