@phnx-labs/agents-cli 1.22.41 → 1.22.43
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 +86 -0
- package/README.md +10 -4
- package/dist/bin/agents +0 -0
- package/dist/bootstrap.js +25 -7
- package/dist/cli/command-registry.d.ts +2 -3
- package/dist/cli/command-registry.js +4 -8
- package/dist/commands/accounts.d.ts +29 -0
- package/dist/commands/accounts.js +220 -29
- package/dist/commands/alias.d.ts +7 -4
- package/dist/commands/alias.js +34 -9
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +108 -0
- package/dist/commands/browser-sessions-picker.js +42 -62
- package/dist/commands/browser.js +30 -38
- package/dist/commands/computer-actions.d.ts +1 -1
- package/dist/commands/computer-actions.js +1 -1
- package/dist/commands/computer-sessions-picker.js +32 -55
- package/dist/commands/computer.d.ts +1 -1
- package/dist/commands/computer.js +22 -8
- package/dist/commands/daemon.js +1 -1
- package/dist/commands/doctor.d.ts +5 -3
- package/dist/commands/doctor.js +23 -8
- package/dist/commands/exec.js +3 -2
- package/dist/commands/feed.js +2 -31
- package/dist/commands/harness.js +3 -3
- package/dist/commands/hooks.js +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/insights.js +163 -85
- package/dist/commands/inspect.js +2 -2
- package/dist/commands/mine.js +1 -1
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/org.d.ts +6 -0
- package/dist/commands/org.js +175 -0
- package/dist/commands/packages.js +1 -1
- package/dist/commands/profiles.js +2 -2
- package/dist/commands/prune.js +1 -1
- package/dist/commands/routines.js +44 -26
- package/dist/commands/run-account-picker.d.ts +19 -0
- package/dist/commands/run-account-picker.js +81 -0
- package/dist/commands/sessions-picker-factory.d.ts +40 -0
- package/dist/commands/sessions-picker-factory.js +67 -0
- package/dist/commands/sessions-picker.js +124 -122
- package/dist/commands/setup-browser.js +4 -4
- package/dist/commands/setup-mine.js +1 -1
- package/dist/commands/setup-preferences.js +2 -2
- package/dist/commands/setup.js +10 -4
- package/dist/commands/snapshot.d.ts +1 -1
- package/dist/commands/snapshot.js +4 -4
- package/dist/commands/status.d.ts +4 -2
- package/dist/commands/status.js +10 -7
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +30 -3
- package/dist/commands/versions.js +1 -1
- package/dist/commands/view.js +7 -1
- package/dist/commands/webhook.js +1 -1
- package/dist/lib/account-capabilities.d.ts +10 -0
- package/dist/lib/account-capabilities.js +34 -0
- package/dist/lib/accounting/rotate.d.ts +5 -3
- package/dist/lib/accounting/rotate.js +36 -7
- package/dist/lib/accounting/usage.d.ts +18 -4
- package/dist/lib/accounting/usage.js +138 -27
- package/dist/lib/agent-spec/agents.d.ts +584 -0
- package/dist/lib/agent-spec/agents.js +3127 -0
- package/dist/lib/agent-spec/provider.js +1 -1
- package/dist/lib/agents.d.ts +2 -584
- package/dist/lib/agents.js +2 -3127
- package/dist/lib/browser/drivers/local.d.ts +8 -2
- package/dist/lib/browser/drivers/local.js +8 -2
- package/dist/lib/browser/drivers/ssh.d.ts +8 -2
- package/dist/lib/browser/drivers/ssh.js +10 -4
- package/dist/lib/browser/hygiene.d.ts +1 -1
- package/dist/lib/browser/hygiene.js +5 -1
- package/dist/lib/browser/profiles.d.ts +74 -1
- package/dist/lib/browser/profiles.js +114 -5
- package/dist/lib/browser/runtime-state.d.ts +11 -5
- package/dist/lib/browser/runtime-state.js +13 -9
- package/dist/lib/browser/service.d.ts +68 -30
- package/dist/lib/browser/service.js +305 -194
- package/dist/lib/browser/types.d.ts +95 -3
- package/dist/lib/browser/types.js +51 -0
- package/dist/lib/catchup.js +1 -1
- package/dist/lib/cloud/rush.d.ts +0 -15
- package/dist/lib/cloud/rush.js +0 -35
- package/dist/lib/computer/actions.d.ts +1 -1
- package/dist/lib/computer/actions.js +1 -1
- package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
- package/dist/lib/computer/dispatch.d.ts +1 -1
- package/dist/lib/computer/download.d.ts +1 -1
- package/dist/lib/computer/download.js +3 -3
- package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
- package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
- package/dist/lib/crabbox/runtimes.d.ts +6 -2
- package/dist/lib/crabbox/runtimes.js +21 -5
- package/dist/lib/daemon/daemon.js +87 -59
- package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +28 -11
- package/dist/lib/{runner.js → daemon/runner.js} +143 -123
- package/dist/lib/devices/doctor-findings.d.ts +1 -1
- package/dist/lib/devices/doctor-findings.js +4 -4
- package/dist/lib/devices/fleet-inventory.js +1 -1
- package/dist/lib/devices/harness-inventory.js +1 -1
- package/dist/lib/devices/health-report.js +1 -1
- package/dist/lib/devices/registry.js +4 -22
- package/dist/lib/doctor-diff.d.ts +1 -1
- package/dist/lib/doctor-diff.js +1 -1
- package/dist/lib/drift-sync.d.ts +1 -1
- package/dist/lib/drift-sync.js +2 -2
- package/dist/lib/drift.d.ts +1 -1
- package/dist/lib/drift.js +1 -1
- package/dist/lib/entitlement.d.ts +31 -0
- package/dist/lib/entitlement.js +137 -0
- package/dist/lib/exec.js +52 -205
- package/dist/lib/feed/feed.js +2 -8
- package/dist/lib/format.d.ts +9 -0
- package/dist/lib/format.js +16 -0
- package/dist/lib/fs-atomic.d.ts +7 -0
- package/dist/lib/fs-atomic.js +9 -0
- package/dist/lib/github/pr-mergeable.d.ts +49 -0
- package/dist/lib/github/pr-mergeable.js +157 -0
- package/dist/lib/github/pr-verdict.d.ts +67 -0
- package/dist/lib/github/pr-verdict.js +72 -0
- package/dist/lib/harness/adapter.d.ts +161 -0
- package/dist/lib/harness/adapter.js +47 -0
- package/dist/lib/harness/adapters/claude.d.ts +2 -0
- package/dist/lib/harness/adapters/claude.js +110 -0
- package/dist/lib/harness/adapters/codex.d.ts +2 -0
- package/dist/lib/harness/adapters/codex.js +83 -0
- package/dist/lib/harness/adapters/copilot.d.ts +2 -0
- package/dist/lib/harness/adapters/copilot.js +23 -0
- package/dist/lib/harness/adapters/cursor.d.ts +2 -0
- package/dist/lib/harness/adapters/cursor.js +38 -0
- package/dist/lib/harness/adapters/droid.d.ts +2 -0
- package/dist/lib/harness/adapters/droid.js +17 -0
- package/dist/lib/harness/adapters/grok.d.ts +2 -0
- package/dist/lib/harness/adapters/grok.js +14 -0
- package/dist/lib/harness/adapters/kimi.d.ts +2 -0
- package/dist/lib/harness/adapters/kimi.js +49 -0
- package/dist/lib/harness/adapters/muse.d.ts +2 -0
- package/dist/lib/harness/adapters/muse.js +44 -0
- package/dist/lib/harness/adapters/opencode.d.ts +2 -0
- package/dist/lib/harness/adapters/opencode.js +14 -0
- package/dist/lib/harness/exec-config-version.d.ts +8 -0
- package/dist/lib/harness/exec-config-version.js +22 -0
- package/dist/lib/harness/index.d.ts +1 -0
- package/dist/lib/harness/index.js +34 -0
- package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
- package/dist/lib/{hooks.js → hooks/install.js} +11 -11
- package/dist/lib/hosts/passthrough.d.ts +10 -0
- package/dist/lib/hosts/passthrough.js +28 -6
- package/dist/lib/hosts/session-index.js +1 -1
- package/dist/lib/import.js +1 -1
- package/dist/lib/installations/index.d.ts +4 -4
- package/dist/lib/installations/index.js +3 -4
- package/dist/lib/installations/migrate.js +6 -6
- package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
- package/dist/lib/{shims.js → installations/shims.js} +13 -115
- package/dist/lib/installations/store.d.ts +141 -3
- package/dist/lib/installations/store.js +475 -5
- package/dist/lib/installations/versions.d.ts +40 -125
- package/dist/lib/installations/versions.js +57 -462
- package/dist/lib/isolation-boundary-report.d.ts +1 -1
- package/dist/lib/isolation-boundary-report.js +1 -1
- package/dist/lib/mailbox-gc.js +2 -3
- package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.js +19 -8
- package/dist/lib/monitors/config.d.ts +2 -2
- package/dist/lib/monitors/config.js +3 -2
- package/dist/lib/monitors/dispatch.d.ts +1 -1
- package/dist/lib/monitors/dispatch.js +2 -2
- package/dist/lib/monitors/state.d.ts +1 -1
- package/dist/lib/origin-machine.d.ts +18 -0
- package/dist/lib/origin-machine.js +34 -0
- package/dist/lib/perf/db.js +1 -1
- package/dist/lib/perf/spool.js +1 -1
- package/dist/lib/plugins/plugins.js +1 -1
- package/dist/lib/plugins/skills.js +1 -1
- package/dist/lib/prix-account.d.ts +158 -0
- package/dist/lib/prix-account.js +214 -0
- package/dist/lib/profiles.d.ts +7 -0
- package/dist/lib/profiles.js +26 -1
- package/dist/lib/project-key.js +2 -2
- package/dist/lib/project-launch.js +1 -1
- package/dist/lib/refresh.js +2 -2
- package/dist/lib/resource-inventory.d.ts +1 -1
- package/dist/lib/resource-inventory.js +1 -1
- package/dist/lib/rules/rules.js +1 -1
- package/dist/lib/sandbox.d.ts +24 -0
- package/dist/lib/sandbox.js +101 -0
- package/dist/lib/scheduling/routines.d.ts +18 -9
- package/dist/lib/scheduling/routines.js +17 -9
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.js +9 -1
- package/dist/lib/secrets/index.js +1 -1
- package/dist/lib/secrets/list-filter.js +1 -1
- package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
- package/dist/lib/self-heal/checks/path.js +1 -1
- package/dist/lib/self-heal/checks/shadowing.js +1 -1
- package/dist/lib/self-heal/checks/shims.js +1 -1
- package/dist/lib/service-manifest.d.ts +20 -0
- package/dist/lib/service-manifest.js +34 -0
- package/dist/lib/session/cloud.js +1 -1
- package/dist/lib/session/db.js +1 -1
- package/dist/lib/session/discover.d.ts +39 -0
- package/dist/lib/session/discover.js +3 -3
- package/dist/lib/session/fork.js +1 -1
- package/dist/lib/session/live-metadata.js +1 -1
- package/dist/lib/session/origin-machine.d.ts +1 -18
- package/dist/lib/session/origin-machine.js +4 -34
- package/dist/lib/session/parse.js +24 -42
- package/dist/lib/session/recovery.js +1 -1
- package/dist/lib/session/relative-time.d.ts +1 -44
- package/dist/lib/session/relative-time.js +4 -120
- package/dist/lib/session/remote-list.js +1 -1
- package/dist/lib/session/remote.js +2 -2
- package/dist/lib/session/short-id.d.ts +1 -17
- package/dist/lib/session/short-id.js +4 -20
- package/dist/lib/session/width.d.ts +1 -29
- package/dist/lib/session/width.js +4 -101
- package/dist/lib/snapshot.d.ts +2 -2
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/staleness/prune.d.ts +1 -1
- package/dist/lib/staleness/prune.js +1 -1
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +4 -1
- package/dist/lib/startup/command-registry.js +12 -5
- package/dist/lib/teams/agents.js +2 -19
- package/dist/lib/teams/parsers.js +20 -26
- package/dist/lib/teams/placement-probe.js +1 -1
- package/dist/lib/teams/registry.js +2 -21
- package/dist/lib/teams/scheduler.js +1 -1
- package/dist/lib/text/relative-time.d.ts +44 -0
- package/dist/lib/text/relative-time.js +120 -0
- package/dist/lib/text/short-id.d.ts +17 -0
- package/dist/lib/text/short-id.js +20 -0
- package/dist/lib/text/width.d.ts +29 -0
- package/dist/lib/text/width.js +101 -0
- package/dist/lib/triggers/handlers.d.ts +7 -0
- package/dist/lib/triggers/handlers.js +9 -1
- package/dist/lib/triggers/webhook.d.ts +1 -0
- package/dist/lib/triggers/webhook.js +3 -2
- package/dist/lib/uninstall.js +1 -1
- package/dist/lib/version.d.ts +9 -2
- package/dist/lib/version.js +11 -9
- package/dist/lib/view-types.d.ts +4 -0
- package/package.json +1 -1
- package/dist/commands/tickets.d.ts +0 -2
- package/dist/commands/tickets.js +0 -43
- package/dist/lib/observe-aliases.d.ts +0 -30
- package/dist/lib/observe-aliases.js +0 -38
- package/dist/lib/tickets/list.d.ts +0 -53
- package/dist/lib/tickets/list.js +0 -153
- /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
|
@@ -4,7 +4,10 @@ import { setHelpSections } from '../lib/help.js';
|
|
|
4
4
|
import { readMeta, updateMeta } from '../lib/state.js';
|
|
5
5
|
import { ALL_AGENT_IDS, getAccountInfo, resolveAgentName } from '../lib/agents.js';
|
|
6
6
|
import { getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
|
|
7
|
-
import {
|
|
7
|
+
import { assertNativeAccountNameable, nativeAccountCapability, nativeIdentityKey } from '../lib/account-capabilities.js';
|
|
8
|
+
import { collectRunCandidates } from '../lib/accounting/rotate.js';
|
|
9
|
+
import { isInteractiveTerminal } from './utils.js';
|
|
10
|
+
import { buildSwitchAccountChoices, formatAccountLimits, pickSwitchAccount } from './run-account-picker.js';
|
|
8
11
|
import { profileExists, readProfile } from '../lib/profiles.js';
|
|
9
12
|
import { pushBundleToHost } from '../lib/secrets/push.js';
|
|
10
13
|
import { assertCredentialTransportHostPinned, resolveHostSshTarget } from '../lib/secrets/remote.js';
|
|
@@ -14,6 +17,7 @@ import { discoverNativeAccounts } from '../lib/account-catalog.js';
|
|
|
14
17
|
import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
|
|
15
18
|
import { getAccountProvider, listAccountProviders } from '../lib/account-provider-registry.js';
|
|
16
19
|
import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
|
|
20
|
+
import { accountCapForTier, getTier } from '../lib/entitlement.js';
|
|
17
21
|
function parseInstallation(raw) {
|
|
18
22
|
const at = raw.lastIndexOf('@');
|
|
19
23
|
if (at < 1 || at === raw.length - 1)
|
|
@@ -23,12 +27,10 @@ function parseInstallation(raw) {
|
|
|
23
27
|
throw new Error(`Unknown agent '${raw.slice(0, at)}'.`);
|
|
24
28
|
return { agent, version: raw.slice(at + 1) };
|
|
25
29
|
}
|
|
26
|
-
async function nativeIdentityFromSource(raw) {
|
|
30
|
+
export async function nativeIdentityFromSource(raw) {
|
|
27
31
|
const parsed = parseInstallation(raw);
|
|
28
32
|
const capability = nativeAccountCapability(parsed.agent);
|
|
29
|
-
|
|
30
|
-
throw new Error(`${parsed.agent} native accounts are ${capability.status}; agents-cli cannot safely name or attach this login.`);
|
|
31
|
-
}
|
|
33
|
+
assertNativeAccountNameable(parsed.agent);
|
|
32
34
|
if (!listInstalledVersions(parsed.agent).includes(parsed.version))
|
|
33
35
|
throw new Error(`${raw} is not installed.`);
|
|
34
36
|
const info = await getAccountInfo(parsed.agent, getVersionHomePath(parsed.agent, parsed.version));
|
|
@@ -72,8 +74,8 @@ function secretFromBundle(raw) {
|
|
|
72
74
|
const { bundle, key } = parseBundleKey(raw);
|
|
73
75
|
return readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' }).env[key];
|
|
74
76
|
}
|
|
75
|
-
function publicAccount(account) {
|
|
76
|
-
return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
|
|
77
|
+
function publicAccount(account, dormant) {
|
|
78
|
+
return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent, dormant };
|
|
77
79
|
}
|
|
78
80
|
async function printAccounts(json, fleet = false) {
|
|
79
81
|
if (fleet)
|
|
@@ -81,12 +83,16 @@ async function printAccounts(json, fleet = false) {
|
|
|
81
83
|
const records = Object.values(readAccountRegistry().accounts).sort((a, b) => a.name.localeCompare(b.name));
|
|
82
84
|
const discovered = await discoverNativeAccounts();
|
|
83
85
|
const savedNative = listNativeAccounts(readMeta());
|
|
86
|
+
const dormantIds = new Set();
|
|
87
|
+
for (const agent of ALL_AGENT_IDS)
|
|
88
|
+
for (const account of await dormantAccountsForHarness(agent))
|
|
89
|
+
dormantIds.add(account.id);
|
|
84
90
|
const native = discovered.map(row => {
|
|
85
91
|
const saved = savedNative.find(account => account.agent === row.agent && account.identityKey === row.id);
|
|
86
|
-
return { ...row, name: saved?.name, id: saved?.id ?? row.id };
|
|
92
|
+
return { ...row, name: saved?.name, id: saved?.id ?? row.id, dormant: !!saved && dormantIds.has(saved.id) };
|
|
87
93
|
});
|
|
88
94
|
if (json) {
|
|
89
|
-
console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name))), ...native], null, 2));
|
|
95
|
+
console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name), dormantIds.has(account.id))), ...native], null, 2));
|
|
90
96
|
return;
|
|
91
97
|
}
|
|
92
98
|
console.log(chalk.bold('Provider account bundles\n'));
|
|
@@ -94,19 +100,168 @@ async function printAccounts(json, fleet = false) {
|
|
|
94
100
|
console.log(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
|
|
95
101
|
for (const account of records) {
|
|
96
102
|
const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
|
|
97
|
-
|
|
103
|
+
const dormantSuffix = dormantIds.has(account.id) ? chalk.gray(' — dormant (upgrade to reactivate)') : '';
|
|
104
|
+
console.log(` ${chalk.cyan(account.name)} ${account.provider} ${account.auth} ${present}${dormantSuffix}`);
|
|
98
105
|
}
|
|
99
106
|
console.log(chalk.bold('\nNative harness logins\n'));
|
|
100
107
|
if (!native.length)
|
|
101
108
|
console.log(chalk.gray(' No signed-in native accounts found.'));
|
|
102
109
|
for (const account of native)
|
|
103
|
-
console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}`);
|
|
110
|
+
console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}${account.dormant ? chalk.gray(' — dormant (upgrade to reactivate)') : ''}`);
|
|
104
111
|
}
|
|
105
112
|
function parseAuth(raw) {
|
|
106
113
|
if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
|
|
107
114
|
return raw;
|
|
108
115
|
throw new Error(`Unsupported auth type '${raw}'. Use api-key, setup-token, or bearer-token.`);
|
|
109
116
|
}
|
|
117
|
+
function parseHarness(agentRaw) {
|
|
118
|
+
if (!ALL_AGENT_IDS.includes(agentRaw))
|
|
119
|
+
throw new Error(`Unknown agent '${agentRaw}'.`);
|
|
120
|
+
return agentRaw;
|
|
121
|
+
}
|
|
122
|
+
function providerAuthenticatesHarness(provider, auth, agent) {
|
|
123
|
+
try {
|
|
124
|
+
getAccountProvider(provider).envFor(agent, auth);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
129
|
+
if (message.includes('cannot authenticate'))
|
|
130
|
+
return false;
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Every account (native + provider) registered/usable for this harness, oldest-first by name — the full set a plan-tier cap slices into active vs dormant. */
|
|
135
|
+
function accountsForHarness(agent) {
|
|
136
|
+
const meta = readMeta();
|
|
137
|
+
const native = listNativeAccounts(meta).filter(account => account.agent === agent);
|
|
138
|
+
const providers = Object.values(readAccountRegistry().accounts)
|
|
139
|
+
.filter(account => providerAuthenticatesHarness(account.provider, account.auth, agent))
|
|
140
|
+
.map(account => ({ ...account, kind: 'provider' }));
|
|
141
|
+
return [...native, ...providers].sort((a, b) => a.name.localeCompare(b.name));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Named accounts that `set-default` / `switch` can pin for this harness —
|
|
145
|
+
* capped to the caller's plan tier (RUSH-2424). Downgrading never deletes a
|
|
146
|
+
* credential: an over-cap account simply falls out of this list (see
|
|
147
|
+
* {@link dormantAccountsForHarness}) and stops being a switch/rotation target
|
|
148
|
+
* until the plan is upgraded.
|
|
149
|
+
*/
|
|
150
|
+
export async function listSwitchableAccounts(agent) {
|
|
151
|
+
const cap = accountCapForTier(await getTier());
|
|
152
|
+
return accountsForHarness(agent).slice(0, cap);
|
|
153
|
+
}
|
|
154
|
+
/** Accounts registered for this harness beyond the plan's cap — kept, never deleted, excluded from switch/rotation, reactivated by an upgrade. */
|
|
155
|
+
export async function dormantAccountsForHarness(agent) {
|
|
156
|
+
const cap = accountCapForTier(await getTier());
|
|
157
|
+
return accountsForHarness(agent).slice(cap);
|
|
158
|
+
}
|
|
159
|
+
function accountCapRefusalMessage(agent, tier, cap) {
|
|
160
|
+
if (!tier.isPaid)
|
|
161
|
+
return `free plan is capped at 3 ${agent} accounts (3/3). agents upgrade — up to 10 per harness.`;
|
|
162
|
+
return `${tier.tierName} plan is capped at ${cap} ${agent} accounts (${cap}/${cap}).`;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Refuse BEFORE any write when adding a new account would push one of
|
|
166
|
+
* `harnesses` over the caller's plan cap. Returns the tier/cap/pre-add counts
|
|
167
|
+
* so the caller can print the exactly-at-cap notice after a successful write.
|
|
168
|
+
*/
|
|
169
|
+
async function assertAccountCapacityFor(harnesses) {
|
|
170
|
+
const tier = await getTier();
|
|
171
|
+
const cap = accountCapForTier(tier);
|
|
172
|
+
const existing = new Map();
|
|
173
|
+
for (const harness of harnesses)
|
|
174
|
+
existing.set(harness, accountsForHarness(harness).length);
|
|
175
|
+
const capped = harnesses.find(harness => (existing.get(harness) ?? 0) >= cap);
|
|
176
|
+
if (capped)
|
|
177
|
+
throw new Error(accountCapRefusalMessage(capped, tier, cap));
|
|
178
|
+
return { tier, cap, existing };
|
|
179
|
+
}
|
|
180
|
+
/** One-line, non-blocking notice for every harness a free-tier add just brought to exactly its cap. */
|
|
181
|
+
function printCapNoticesFor(harnesses, tier, cap, existing) {
|
|
182
|
+
if (tier.isPaid)
|
|
183
|
+
return;
|
|
184
|
+
for (const harness of harnesses) {
|
|
185
|
+
if ((existing.get(harness) ?? 0) + 1 === cap) {
|
|
186
|
+
console.log(chalk.yellow(`${cap}/${cap} ${harness} accounts on the free plan. agents upgrade — up to 10 per harness.`));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Pin the per-harness default. Shared by `accounts set-default` and `accounts switch`.
|
|
192
|
+
* Provider accounts must authenticate the harness; native accounts must belong to it.
|
|
193
|
+
*/
|
|
194
|
+
export function setDefaultAccount(agentRaw, name) {
|
|
195
|
+
const agent = parseHarness(agentRaw);
|
|
196
|
+
const account = findUnifiedAccount(name, readMeta());
|
|
197
|
+
if (!account)
|
|
198
|
+
throw new Error(`Unknown account '${name}'.`);
|
|
199
|
+
if (account.kind === 'provider') {
|
|
200
|
+
getAccountProvider(account.provider).envFor(agent, account.auth);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
if (account.agent !== agent) {
|
|
204
|
+
throw new Error(`Account '${account.name}' is a ${account.agent} login and cannot be the default for ${agent}.`);
|
|
205
|
+
}
|
|
206
|
+
assertNativeAccountNameable(account.agent);
|
|
207
|
+
}
|
|
208
|
+
updateMeta(meta => ({ ...meta, accounts: { ...meta.accounts, defaults: { ...meta.accounts?.defaults, [agent]: account.id } } }));
|
|
209
|
+
return { agent, account };
|
|
210
|
+
}
|
|
211
|
+
async function switchAccountRows(agent) {
|
|
212
|
+
const accounts = await listSwitchableAccounts(agent);
|
|
213
|
+
const candidates = await collectRunCandidates(agent);
|
|
214
|
+
const defaultId = readMeta().accounts?.defaults?.[agent];
|
|
215
|
+
return accounts.map(account => {
|
|
216
|
+
const candidate = account.kind === 'native'
|
|
217
|
+
? candidates.find(row => row.accountKey === account.identityKey) ?? null
|
|
218
|
+
: null;
|
|
219
|
+
return {
|
|
220
|
+
accountName: account.name,
|
|
221
|
+
kind: account.kind,
|
|
222
|
+
detail: account.kind === 'provider' ? account.provider : (account.identityLabel ?? account.identityKey),
|
|
223
|
+
current: account.id === defaultId,
|
|
224
|
+
candidate,
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
export async function runAccountsSwitch(harness, accountName, opts) {
|
|
229
|
+
const agent = parseHarness(harness);
|
|
230
|
+
const rows = await switchAccountRows(agent);
|
|
231
|
+
if (accountName) {
|
|
232
|
+
const { account } = setDefaultAccount(agent, accountName);
|
|
233
|
+
if (opts.json) {
|
|
234
|
+
console.log(JSON.stringify({ harness: agent, account: account.name, id: account.id }, null, 2));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (opts.json) {
|
|
241
|
+
const choices = buildSwitchAccountChoices(rows);
|
|
242
|
+
console.log(JSON.stringify({
|
|
243
|
+
harness: agent,
|
|
244
|
+
default: rows.find(row => row.current)?.accountName ?? null,
|
|
245
|
+
accounts: rows.map((row, i) => ({
|
|
246
|
+
name: row.accountName,
|
|
247
|
+
kind: row.kind,
|
|
248
|
+
detail: row.detail,
|
|
249
|
+
current: row.current,
|
|
250
|
+
ready: choices[i].ready,
|
|
251
|
+
limits: row.candidate ? formatAccountLimits(row.candidate) : 'limits unavailable',
|
|
252
|
+
})),
|
|
253
|
+
}, null, 2));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (!isInteractiveTerminal()) {
|
|
257
|
+
throw new Error(`Selecting a ${agent} account requires an interactive terminal.\nUse: agents accounts switch ${agent} <account>\nOr: agents accounts set-default ${agent} <account>`);
|
|
258
|
+
}
|
|
259
|
+
const selected = await pickSwitchAccount(agent, rows);
|
|
260
|
+
if (!selected)
|
|
261
|
+
return;
|
|
262
|
+
const { account } = setDefaultAccount(agent, selected);
|
|
263
|
+
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
264
|
+
}
|
|
110
265
|
export function registerAccountsCommand(program) {
|
|
111
266
|
const accounts = program.command('accounts').description('Browse native logins and manage provider account bundles')
|
|
112
267
|
.option('--json', 'Machine-readable account metadata')
|
|
@@ -124,10 +279,16 @@ export function registerAccountsCommand(program) {
|
|
|
124
279
|
const provider = getAccountProvider(o.provider);
|
|
125
280
|
if (!provider.authKinds.includes(auth))
|
|
126
281
|
throw new Error(`Provider '${provider.provider}' does not support ${auth}. Supported: ${provider.authKinds.join(', ')}.`);
|
|
282
|
+
// A provider account may authenticate more than one harness (e.g. openrouter);
|
|
283
|
+
// it counts toward every harness it can authenticate, so refuse before any
|
|
284
|
+
// write if adding it would push ANY of those harnesses over the plan cap.
|
|
285
|
+
const harnesses = ALL_AGENT_IDS.filter(h => providerAuthenticatesHarness(provider.provider, auth, h));
|
|
286
|
+
const { tier, cap, existing } = await assertAccountCapacityFor(harnesses);
|
|
127
287
|
const secret = o.fromSecrets ? secretFromBundle(o.fromSecrets) : await password({ message: `Enter ${provider.provider} ${auth} for '${name}':` });
|
|
128
288
|
const account = addAccount(name, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
|
|
129
289
|
console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
|
|
130
290
|
console.log(chalk.gray(`Secret bundle '${account.name}' is the account and uses policy never, so agent launches never request Touch ID.`));
|
|
291
|
+
printCapNoticesFor(harnesses, tier, cap, existing);
|
|
131
292
|
});
|
|
132
293
|
accounts.command('set-key <name>')
|
|
133
294
|
.description('Rotate an account credential without changing its identity')
|
|
@@ -140,17 +301,27 @@ export function registerAccountsCommand(program) {
|
|
|
140
301
|
setAccountSecret(name, secret);
|
|
141
302
|
console.log(chalk.green(`Updated credential for account '${name}'.`));
|
|
142
303
|
});
|
|
143
|
-
accounts.command('view <name>').alias('inspect').description('Show safe account metadata, custody, and attachments').option('--json', 'Machine-readable output').action((name, o, command) => {
|
|
304
|
+
accounts.command('view <name>').alias('inspect').description('Show safe account metadata, custody, and attachments').option('--json', 'Machine-readable output').action(async (name, o, command) => {
|
|
144
305
|
const meta = readMeta();
|
|
145
306
|
const unified = findUnifiedAccount(name, meta);
|
|
146
307
|
if (!unified)
|
|
147
308
|
throw new Error(`Unknown account '${name}'.`);
|
|
309
|
+
const harnesses = unified.kind === 'provider'
|
|
310
|
+
? ALL_AGENT_IDS.filter(h => providerAuthenticatesHarness(unified.provider, unified.auth, h))
|
|
311
|
+
: [unified.agent];
|
|
312
|
+
let dormant = false;
|
|
313
|
+
for (const harness of harnesses) {
|
|
314
|
+
if ((await dormantAccountsForHarness(harness)).some(a => a.id === unified.id)) {
|
|
315
|
+
dormant = true;
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
148
319
|
const account = unified.kind === 'provider'
|
|
149
|
-
? { ...publicAccount(inspectAccount(unified.name)), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
|
|
150
|
-
: { ...unified, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
|
|
320
|
+
? { ...publicAccount(inspectAccount(unified.name), dormant), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
|
|
321
|
+
: { ...unified, dormant, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
|
|
151
322
|
if (o.json || command.optsWithGlobals().json)
|
|
152
323
|
return console.log(JSON.stringify(account, null, 2));
|
|
153
|
-
console.log(chalk.bold(account.name));
|
|
324
|
+
console.log(chalk.bold(account.name) + (account.dormant ? chalk.gray(' — dormant (upgrade to reactivate)') : ''));
|
|
154
325
|
console.log(` kind: ${account.kind}`);
|
|
155
326
|
console.log(` id: ${account.id}`);
|
|
156
327
|
console.log(` custody: ${account.custody}`);
|
|
@@ -169,10 +340,12 @@ export function registerAccountsCommand(program) {
|
|
|
169
340
|
.description('Name a signed-in native installation without copying its OAuth credentials')
|
|
170
341
|
.action(async (source, name) => {
|
|
171
342
|
const identity = await nativeIdentityFromSource(source);
|
|
343
|
+
const { tier, cap, existing } = await assertAccountCapacityFor([identity.agent]);
|
|
172
344
|
const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
|
|
173
345
|
console.log(chalk.green(`Named ${source} as ${account.name}.`));
|
|
174
346
|
if (account.scope === 'device')
|
|
175
347
|
console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
|
|
348
|
+
printCapNoticesFor([identity.agent], tier, cap, existing);
|
|
176
349
|
});
|
|
177
350
|
accounts.command('attach <account> <target>')
|
|
178
351
|
.description('Attach a named account to a native installation or custom harness')
|
|
@@ -181,6 +354,8 @@ export function registerAccountsCommand(program) {
|
|
|
181
354
|
const account = findUnifiedAccount(name, meta);
|
|
182
355
|
if (!account)
|
|
183
356
|
throw new Error(`Unknown account '${name}'.`);
|
|
357
|
+
if (account.kind === 'native')
|
|
358
|
+
assertNativeAccountNameable(account.agent);
|
|
184
359
|
// Validate the target exists before mutating any binding.
|
|
185
360
|
const t = classifyAttachTarget(target);
|
|
186
361
|
const targetAgent = t.kind === 'profile' ? t.profile.host.agent : t.agent;
|
|
@@ -208,6 +383,17 @@ export function registerAccountsCommand(program) {
|
|
|
208
383
|
// Provider account: it must be able to authenticate the target's harness.
|
|
209
384
|
getAccountProvider(account.provider).envFor(targetAgent, account.auth);
|
|
210
385
|
}
|
|
386
|
+
// Defense-in-depth cap check: by the point we get here `account` already
|
|
387
|
+
// authenticates/belongs to `targetAgent` (validated above), so it is
|
|
388
|
+
// already counted in accountsForHarness(targetAgent) from its `add`/`name`
|
|
389
|
+
// time — this can only refuse if that invariant is ever violated, never a
|
|
390
|
+
// normal re-attach of an account the harness is already at cap with.
|
|
391
|
+
const tier = await getTier();
|
|
392
|
+
const cap = accountCapForTier(tier);
|
|
393
|
+
const alreadyCounted = accountsForHarness(targetAgent).some(existing => existing.id === account.id);
|
|
394
|
+
const otherCount = accountsForHarness(targetAgent).length - (alreadyCounted ? 1 : 0);
|
|
395
|
+
if (otherCount >= cap)
|
|
396
|
+
throw new Error(accountCapRefusalMessage(targetAgent, tier, cap));
|
|
211
397
|
bindAccount(name, target);
|
|
212
398
|
console.log(chalk.green(`Attached ${account.name} to ${target}.`));
|
|
213
399
|
});
|
|
@@ -220,25 +406,21 @@ export function registerAccountsCommand(program) {
|
|
|
220
406
|
accounts.command('rename <old> <new>').description('Rename an account without changing its stable id').action((oldName, newName) => renameAccount(oldName, newName));
|
|
221
407
|
accounts.command('remove <name>').description('Remove an account and its device-local credential').action((name) => removeAccount(name));
|
|
222
408
|
accounts.command('set-default <agent> <name>')
|
|
223
|
-
.description('Use
|
|
409
|
+
.description('Use this account for a harness when --account is omitted')
|
|
224
410
|
.action((agentRaw, name) => {
|
|
225
|
-
|
|
226
|
-
throw new Error(`Unknown agent '${agentRaw}'.`);
|
|
227
|
-
const agent = agentRaw;
|
|
228
|
-
const account = findAccount(name);
|
|
229
|
-
if (!account)
|
|
230
|
-
throw new Error(`Unknown provider account '${name}'.`);
|
|
231
|
-
getAccountProvider(account.provider).envFor(agent, account.auth);
|
|
232
|
-
// Defaults follow the stable account id, so renaming the bundle cannot
|
|
233
|
-
// strand bare runs on a deleted label. findAccount accepts ids and names.
|
|
234
|
-
updateMeta(meta => ({ ...meta, accounts: { ...meta.accounts, defaults: { ...meta.accounts?.defaults, [agent]: account.id } } }));
|
|
411
|
+
const { agent, account } = setDefaultAccount(agentRaw, name);
|
|
235
412
|
console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
|
|
236
413
|
});
|
|
414
|
+
const switchCmd = accounts.command('switch <harness> [account]')
|
|
415
|
+
.description('Pick the default account for a harness')
|
|
416
|
+
.option('--json', 'Machine-readable account list or the resulting default')
|
|
417
|
+
.action(async (harness, account, o, command) => {
|
|
418
|
+
await runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) });
|
|
419
|
+
});
|
|
237
420
|
accounts.command('clear-default <agent>')
|
|
238
421
|
.description('Return a harness to native login or balanced account selection')
|
|
239
422
|
.action((agentRaw) => {
|
|
240
|
-
|
|
241
|
-
throw new Error(`Unknown agent '${agentRaw}'.`);
|
|
423
|
+
parseHarness(agentRaw);
|
|
242
424
|
updateMeta(meta => {
|
|
243
425
|
const defaults = { ...meta.accounts?.defaults };
|
|
244
426
|
delete defaults[agentRaw];
|
|
@@ -309,6 +491,13 @@ export function registerAccountsCommand(program) {
|
|
|
309
491
|
throw new Error(`${result.message}\nRetry: agents accounts sync ${account.name} ${device}${o.force ? ' --force' : ''}`);
|
|
310
492
|
console.log(chalk.green(`${account.name} synced to ${device} (${result.keyCount} keys, ${remoteBackend} backend, policy never).`));
|
|
311
493
|
});
|
|
494
|
+
setHelpSections(switchCmd, {
|
|
495
|
+
examples: `agents accounts switch claude
|
|
496
|
+
agents accounts switch claude work
|
|
497
|
+
agents accounts switch claude --json
|
|
498
|
+
agents run claude`,
|
|
499
|
+
notes: 'Switch writes the existing per-harness default (same binding as set-default). Rotation already honors it. Pass an account name to skip the picker. Native name/attach is only for harnesses agents-cli can isolate (claude, codex, grok); provider add is unrestricted.',
|
|
500
|
+
});
|
|
312
501
|
setHelpSections(accounts, {
|
|
313
502
|
examples: `agents accounts add work --provider anthropic --auth setup-token
|
|
314
503
|
agents accounts add openrouter-work --provider openrouter --auth api-key --from-secrets openrouter.ai:OPENROUTER_API_KEY
|
|
@@ -316,10 +505,12 @@ agents accounts set-key work
|
|
|
316
505
|
agents accounts name claude@2.1.220 work
|
|
317
506
|
agents accounts attach work claude@2.1.225
|
|
318
507
|
agents accounts view work --json
|
|
508
|
+
agents accounts switch claude
|
|
509
|
+
agents accounts switch claude work
|
|
319
510
|
agents accounts set-default claude work
|
|
320
511
|
agents accounts sync openrouter-work yosemite-s0
|
|
321
512
|
agents run claude --account work
|
|
322
513
|
agents accounts logout claude`,
|
|
323
|
-
notes: 'Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Synced vault unlock is `agents secrets vault unlock`.',
|
|
514
|
+
notes: 'Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. `accounts switch` is the fast picker over the same default `set-default` writes. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Synced vault unlock is `agents secrets vault unlock`.',
|
|
324
515
|
});
|
|
325
516
|
}
|
package/dist/commands/alias.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* `agents setup alias` -- create top-level shim binaries that expand to
|
|
3
|
+
* `agents <subcommand>`. Nested under setup because the user is configuring
|
|
4
|
+
* this machine's PATH shortcuts, not operating a separate noun (RUSH-2965).
|
|
3
5
|
*
|
|
4
|
-
* Generates executable scripts in ~/.agents/shims/ (already on PATH from
|
|
5
|
-
* standard agents-cli install). Running `teams` becomes equivalent to
|
|
6
|
+
* Generates executable scripts in ~/.agents/.cache/shims/ (already on PATH from
|
|
7
|
+
* the standard agents-cli install). Running `teams` becomes equivalent to
|
|
6
8
|
* `agents teams`. Aliases are tracked in ~/.agents/aliases.json so we can
|
|
7
9
|
* list, regenerate, or remove them without scanning the shims directory
|
|
8
10
|
* (which also holds agent CLI shims like claude/codex/gemini).
|
|
9
11
|
*/
|
|
10
12
|
import type { Command } from 'commander';
|
|
11
|
-
|
|
13
|
+
/** Register `agents setup alias` under the parent `setup` command. */
|
|
14
|
+
export declare function registerAliasCommand(setupCmd: Command): void;
|
package/dist/commands/alias.js
CHANGED
|
@@ -2,9 +2,12 @@ import chalk from 'chalk';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { getUserAgentsDir } from '../lib/state.js';
|
|
5
|
-
import { getShimsDir } from '../lib/shims.js';
|
|
5
|
+
import { getShimsDir } from '../lib/installations/shims.js';
|
|
6
6
|
import { ALL_AGENT_IDS, AGENTS } from '../lib/agents.js';
|
|
7
|
-
|
|
7
|
+
import { setHelpSections } from '../lib/help.js';
|
|
8
|
+
function aliasesFile() {
|
|
9
|
+
return path.join(getUserAgentsDir(), 'aliases.json');
|
|
10
|
+
}
|
|
8
11
|
/** Names that would clobber an agent CLI shim or the `agents` binary itself. */
|
|
9
12
|
function reservedNames() {
|
|
10
13
|
const reserved = new Set(['agents']);
|
|
@@ -13,10 +16,11 @@ function reservedNames() {
|
|
|
13
16
|
return reserved;
|
|
14
17
|
}
|
|
15
18
|
function readAliases() {
|
|
16
|
-
|
|
19
|
+
const file = aliasesFile();
|
|
20
|
+
if (!fs.existsSync(file))
|
|
17
21
|
return {};
|
|
18
22
|
try {
|
|
19
|
-
const parsed = JSON.parse(fs.readFileSync(
|
|
23
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
20
24
|
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
21
25
|
}
|
|
22
26
|
catch {
|
|
@@ -24,8 +28,9 @@ function readAliases() {
|
|
|
24
28
|
}
|
|
25
29
|
}
|
|
26
30
|
function writeAliases(map) {
|
|
27
|
-
|
|
28
|
-
fs.
|
|
31
|
+
const file = aliasesFile();
|
|
32
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
33
|
+
fs.writeFileSync(file, JSON.stringify(map, null, 2) + '\n', 'utf-8');
|
|
29
34
|
}
|
|
30
35
|
function shimPath(name) {
|
|
31
36
|
return path.join(getShimsDir(), name);
|
|
@@ -46,10 +51,30 @@ function isAliasShim(filePath) {
|
|
|
46
51
|
return false;
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
/** Register `agents setup alias` under the parent `setup` command. */
|
|
55
|
+
export function registerAliasCommand(setupCmd) {
|
|
56
|
+
const cmd = setupCmd
|
|
51
57
|
.command('alias')
|
|
52
58
|
.description('Create shorthand binaries that expand to `agents <subcommand>`');
|
|
59
|
+
setHelpSections(cmd, {
|
|
60
|
+
examples: `
|
|
61
|
+
# Make \`teams\` run \`agents teams\`
|
|
62
|
+
agents setup alias add teams
|
|
63
|
+
|
|
64
|
+
# Shorthand for a longer expansion
|
|
65
|
+
agents setup alias add sess sessions preview
|
|
66
|
+
|
|
67
|
+
agents setup alias list
|
|
68
|
+
agents setup alias remove teams
|
|
69
|
+
`,
|
|
70
|
+
notes: `
|
|
71
|
+
Writes an executable shim into ~/.agents/.cache/shims/ (already on PATH after
|
|
72
|
+
\`agents setup\`) and records it in ~/.agents/aliases.json.
|
|
73
|
+
|
|
74
|
+
Names that collide with the agents binary or an agent CLI (claude, codex,
|
|
75
|
+
grok, …) are refused. Top-level \`agents alias\` is gone — use this group.
|
|
76
|
+
`,
|
|
77
|
+
});
|
|
53
78
|
cmd
|
|
54
79
|
.command('add <name> [expansion...]')
|
|
55
80
|
.description('Create an alias shim. If expansion is omitted, defaults to <name>.')
|
|
@@ -106,7 +131,7 @@ export function registerAliasCommand(program) {
|
|
|
106
131
|
const map = readAliases();
|
|
107
132
|
const entries = Object.entries(map);
|
|
108
133
|
if (entries.length === 0) {
|
|
109
|
-
console.log(chalk.gray('No aliases. Try: agents alias add teams'));
|
|
134
|
+
console.log(chalk.gray('No aliases. Try: agents setup alias add teams'));
|
|
110
135
|
return;
|
|
111
136
|
}
|
|
112
137
|
const width = entries.reduce((max, [n]) => Math.max(max, n.length), 0);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* `agents auth login` — RFC 8628 device-code flow against the already-shipped
|
|
4
|
+
* `/api/v1/auth/device/*` routes. If the live API rejects the request shape
|
|
5
|
+
* (a route this spec missed, or the endpoint moved), this fails loud with the
|
|
6
|
+
* `rush login` fallback rather than silently stubbing a fake session.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runAuthLogin(): Promise<void>;
|
|
9
|
+
export declare function registerAuthCommand(program: Command): void;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { setHelpSections } from '../lib/help.js';
|
|
3
|
+
import { runOrDie } from '../lib/format.js';
|
|
4
|
+
import { clearPrixSession, fetchWhoAmI, pollDeviceToken, PrixApiError, readPrixSession, resolvePrixToken, startDeviceAuthorization, writePrixSession, } from '../lib/prix-account.js';
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* `agents auth login` — RFC 8628 device-code flow against the already-shipped
|
|
10
|
+
* `/api/v1/auth/device/*` routes. If the live API rejects the request shape
|
|
11
|
+
* (a route this spec missed, or the endpoint moved), this fails loud with the
|
|
12
|
+
* `rush login` fallback rather than silently stubbing a fake session.
|
|
13
|
+
*/
|
|
14
|
+
export async function runAuthLogin() {
|
|
15
|
+
let authorization;
|
|
16
|
+
try {
|
|
17
|
+
authorization = await startDeviceAuthorization();
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
21
|
+
const existing = resolvePrixToken();
|
|
22
|
+
if (existing?.source === 'rush') {
|
|
23
|
+
console.log(chalk.yellow(`Device login is unavailable right now (${message}), but you're already signed in via 'rush login'.`));
|
|
24
|
+
console.log("Run 'agents auth whoami' to confirm.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Device login is unavailable right now (${message}). Run 'rush login' instead, then 'agents auth whoami' to confirm.`);
|
|
28
|
+
}
|
|
29
|
+
console.log(`To sign in, open:\n\n ${chalk.cyan(authorization.verification_uri_complete)}\n`);
|
|
30
|
+
console.log(chalk.gray(`(code: ${authorization.user_code})`));
|
|
31
|
+
const deadline = Date.now() + authorization.expires_in * 1000;
|
|
32
|
+
let interval = Math.max(authorization.interval, 1) * 1000;
|
|
33
|
+
while (Date.now() < deadline) {
|
|
34
|
+
await sleep(interval);
|
|
35
|
+
const poll = await pollDeviceToken(authorization.device_code);
|
|
36
|
+
if (poll.status === 'pending')
|
|
37
|
+
continue;
|
|
38
|
+
if (poll.status === 'slow_down') {
|
|
39
|
+
interval += 1000;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (poll.status === 'expired')
|
|
43
|
+
throw new Error('Login code expired. Run `agents auth login` again.');
|
|
44
|
+
if (poll.status === 'denied')
|
|
45
|
+
throw new Error('Login was denied.');
|
|
46
|
+
writePrixSession({
|
|
47
|
+
access_token: poll.access_token,
|
|
48
|
+
refresh_token: poll.refresh_token,
|
|
49
|
+
expires_at: poll.expires_in ? Date.now() + poll.expires_in * 1000 : undefined,
|
|
50
|
+
email: poll.user.email,
|
|
51
|
+
userId: poll.user.id,
|
|
52
|
+
});
|
|
53
|
+
console.log(chalk.green(`Signed in as ${poll.user.email}.`));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
throw new Error('Login code expired. Run `agents auth login` again.');
|
|
57
|
+
}
|
|
58
|
+
async function runAuthWhoami(json) {
|
|
59
|
+
const resolved = resolvePrixToken();
|
|
60
|
+
if (!resolved)
|
|
61
|
+
throw new Error("Not signed in. Run 'agents auth login' first.");
|
|
62
|
+
let who;
|
|
63
|
+
try {
|
|
64
|
+
who = await fetchWhoAmI(resolved.token);
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
if (err instanceof PrixApiError && err.status === 401) {
|
|
68
|
+
throw new Error(`Signed-in token is no longer valid (${resolved.source === 'agents' ? "run 'agents auth login'" : "run 'rush login'"} again).`);
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
if (json) {
|
|
73
|
+
console.log(JSON.stringify({ ...who, source: resolved.source }, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
console.log(`${chalk.bold(who.email)} ${chalk.gray(who.userId)}`);
|
|
77
|
+
console.log(chalk.gray(`signed in via ${resolved.source === 'agents' ? "'agents auth login'" : "'rush login' (shared session)"}`));
|
|
78
|
+
}
|
|
79
|
+
function runAuthLogout() {
|
|
80
|
+
const cleared = clearPrixSession();
|
|
81
|
+
const rushRemains = resolvePrixToken()?.source === 'rush';
|
|
82
|
+
if (!cleared && !readPrixSession()) {
|
|
83
|
+
console.log(chalk.gray('Not signed in via `agents auth login` — nothing to clear.'));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(chalk.green('Signed out.'));
|
|
87
|
+
}
|
|
88
|
+
if (rushRemains) {
|
|
89
|
+
console.log(chalk.gray("You're still signed in via `rush login` — `agents auth whoami` will use that session. Run `rush logout` to clear it too."));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export function registerAuthCommand(program) {
|
|
93
|
+
const auth = program.command('auth').description('Sign in to your Rush account (shared by `agents org` and paid tiers)');
|
|
94
|
+
auth.command('login').description('Sign in via the device-code flow').action(() => runOrDie(() => runAuthLogin()));
|
|
95
|
+
auth.command('whoami').description('Show the signed-in account').option('--json', 'Machine-readable output')
|
|
96
|
+
.action((o, command) => {
|
|
97
|
+
const json = !!(o.json || command.optsWithGlobals().json);
|
|
98
|
+
return runOrDie(() => runAuthWhoami(json), { json });
|
|
99
|
+
});
|
|
100
|
+
auth.command('logout').description('Clear the local `agents auth login` session').action(() => runOrDie(() => runAuthLogout()));
|
|
101
|
+
setHelpSections(auth, {
|
|
102
|
+
examples: `agents auth login
|
|
103
|
+
agents auth whoami
|
|
104
|
+
agents auth whoami --json
|
|
105
|
+
agents auth logout`,
|
|
106
|
+
notes: "`agents auth login` writes its own session, separate from \`rush login\`'s \`~/.rush/user.yaml\` — \`agents auth logout\` never signs you out of \`rush\`. If you're already signed in via \`rush login\`, \`agents auth whoami\`/\`agents org\` use that session automatically; no separate login is required.",
|
|
107
|
+
});
|
|
108
|
+
}
|