@phnx-labs/agents-cli 1.22.44 → 1.22.46

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +24 -0
  3. package/dist/bootstrap.js +1 -6
  4. package/dist/cli/command-registry.d.ts +0 -1
  5. package/dist/cli/command-registry.js +1 -3
  6. package/dist/commands/accounts.d.ts +1 -9
  7. package/dist/commands/accounts.js +12 -90
  8. package/dist/commands/auth.d.ts +0 -7
  9. package/dist/commands/auth.js +198 -83
  10. package/dist/commands/insights.js +82 -154
  11. package/dist/commands/view.js +1 -1
  12. package/dist/lib/accounting/usage.d.ts +22 -3
  13. package/dist/lib/accounting/usage.js +94 -12
  14. package/dist/lib/agent-spec/agents.js +6 -1
  15. package/dist/lib/cli-resources.js +17 -15
  16. package/dist/lib/devices/harness-inventory.js +20 -3
  17. package/dist/lib/exec.d.ts +20 -0
  18. package/dist/lib/exec.js +43 -6
  19. package/dist/lib/identity/client.d.ts +53 -0
  20. package/dist/lib/identity/client.js +106 -0
  21. package/dist/lib/identity/index.d.ts +115 -0
  22. package/dist/lib/identity/index.js +82 -0
  23. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  24. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
  25. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  26. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  27. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  28. package/dist/lib/probe.d.ts +8 -0
  29. package/dist/lib/probe.js +105 -0
  30. package/dist/lib/startup/command-registry.d.ts +3 -1
  31. package/dist/lib/startup/command-registry.js +5 -2
  32. package/dist/lib/view-types.d.ts +2 -2
  33. package/package.json +1 -1
  34. package/dist/commands/org.d.ts +0 -11
  35. package/dist/commands/org.js +0 -228
  36. package/dist/lib/entitlement.d.ts +0 -31
  37. package/dist/lib/entitlement.js +0 -137
  38. package/dist/lib/prix-account.d.ts +0 -159
  39. package/dist/lib/prix-account.js +0 -215
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.46
4
+
5
+ - **`agents auth` returns, against Phoenix ID instead of a sibling product's backend (RUSH-2581).** 1.22.45 removed the account layer that authenticated against Rush's `api.prix.dev`. It comes back pointed at **Phoenix ID** (`phnx-labs/phoenix-id`), agents-cli's own account service: `agents auth login` runs a device-code flow whose browser page is Phoenix-branded and Google-only, `agents auth whoami` reports the signed-in account, `agents auth logout` clears **this machine** and nothing else, and the team surface nests as `agents auth space` (`list`/`create`/`members`/`invite`/`role`/`remove`). Everything goes through one new seam, `lib/identity/` — one base URL (`PHOENIX_ID_BASE`), one session file, one HTTP funnel, one error type — replacing the shape that had the backend URL hardcoded in five files and the session token re-read by seven separate functions. agents-cli reads no other product's credentials: there is no `~/.rush/user.yaml` fallback. Source: `apps/cli/src/lib/identity/{client,index}.ts`, `apps/cli/src/commands/auth.ts`.
6
+
7
+ - **`agents auth` points at the deployed Phoenix ID service (RUSH-2581).** The `PHOENIX_ID_BASE` default shipped naming `id.phnx.sh`, a domain that was never registered, so every `agents auth login` would have failed DNS with nothing behind it. It now defaults to the live service (a `workers.dev` URL until a custom hostname is attached). Override it with `PHOENIX_ID_BASE` to point at a local backend. Source: `apps/cli/src/lib/identity/client.ts`.
8
+
9
+ - **The menu-bar prepack gate hard-fails on an unstapled or thin helper on any OS (RUSH-3031).** `verify-menubar-helper.sh`'s notarization check used to silently no-op when `xcrun` was absent — the exact case a Linux attestation-producer box hits — which let 1.22.44 ship a Dev-ID-signed but un-stapled, thin (single-arch) `MenubarHelper.app` that Gatekeeper rejected on every Mac. The gate now hard-fails when the stapled ticket (`Contents/CodeResources`) is missing and `xcrun` is unavailable, and hard-fails when the bundled executable is not a universal (fat) Mach-O binary, checked portably with `od` on any platform. Source: `apps/cli/scripts/verify-menubar-helper.sh`.
10
+ - **The release-attestation producer no longer arms the real-`~/.agents` hermeticity guards it needs to get vitest's extended timeout profile (RUSH-3007).** `CI=true` used to control both the vitest hookTimeout/ignore-pool-error profile AND `tests/setup.ts`'s leak tripwires against the real developer home — so a producer run on a box with a live daemon (e.g. mac-mini) false-failed 129/129 test files on a fully green, 12,559/12,559-test suite. `release-attestation-produce.sh` now sets `AGENTS_ATTEST_PRODUCER=1` (and unsets any ambient `CI`) to opt into the timeout profile without arming the guards; a genuine CI runner's behavior is unchanged. Source: `apps/cli/scripts/release-attestation-produce.sh`, `apps/cli/tests/hermetic-guards.ts`, `apps/cli/tests/setup.ts`, `apps/cli/vitest.config.ts`.
11
+
12
+ - **Rotation remembers a tokens/credits-exhausted account instead of re-picking it every launch (RUSH-3018).**
13
+ A rate limit resets on a clock; running *out of credits / hitting a spend cap*
14
+ does not — yet the two were conflated. A billing refusal (`out of usage credits`,
15
+ `monthly spend limit`) was detected only to trigger one failover and then
16
+ forgotten, so balanced rotation re-picked the dead account on the next launch,
17
+ it refused again, failed over again — burning a launch each time. It now
18
+ persists a clock-less `out_of_credits` marker per account
19
+ (`noteClaudeOutOfCredits`), which the rotation eligibility gate treats as
20
+ blocking until a later **successful** run on that account clears it
21
+ (`clearClaudeAccountRefusal`) — never a timestamp. Session/rate limits are
22
+ unchanged (still recover on their reset). Source:
23
+ `apps/cli/src/lib/accounting/usage.ts`, `apps/cli/src/lib/exec.ts`.
24
+
25
+ - **Harness capability probes can no longer leak grandchild processes (RUSH-3028).** The version probe behind `agents view` (`<cli> --version`) and the async manifest `check:` runner executed third-party binaries whose own forked children could outlive the probe — the GitHub Copilot npm wrapper forks a platform-binary downloader into `~/Library/Caches/copilot`, and under a redirected test HOME that survivor raced teardown `rm` (the dominant residual ENOTEMPTY suite flake after RUSH-3021). Probes now run in their own process group via `probeCapture`, and the whole group is reaped when the probe settles — on clean exit, on timeout, and on parent death (a `process.on('exit')` hook covers the CLI's hard-exit SIGINT path). On win32, where process groups don't apply, the direct child is killed on settle, matching the old `execFile` timeout behavior. Source: `apps/cli/src/lib/probe.ts`, `apps/cli/src/lib/agent-spec/agents.ts`, `apps/cli/src/lib/cli-resources.ts`.
26
+
27
+ ## 1.22.45
28
+
29
+ - **BREAKING: the Prix-coupled account layer is removed — `agents auth`, `agents org`, and the plan-tier gates (RUSH-2581).** 1.22.42 shipped `agents auth login/whoami/logout` and `agents org` against the Rush product's backend (`api.prix.dev`), with `entitlement.ts` reading the Rush billing tier to cap accounts at 3-per-harness on free and to gate the `agents insights` friction sections. agents-cli's identity must not ride a separate product's login and database, so all of it is removed: `auth` and `org` are retired top-level names (they fail loudly, no auto-correct), account registration is uncapped again, `agents accounts` output drops the `dormant` field/suffix, and `agents insights` renders the full report with no `plan`/`notice` JSON fields. The Rush *feature* integrations are untouched (`cloud/rush.ts` dispatch, the opt-in secrets sync driver, the owner-notify transport). The replacement — agents-cli's own account backend with Phoenix branding, built for later Phoenix-ID consolidation — is tracked in RUSH-2581. Source: `apps/cli/src/lib/{prix-account,entitlement}.ts` (deleted), `apps/cli/src/commands/{auth,org}.ts` (deleted), `apps/cli/src/commands/{accounts,insights}.ts`, `apps/cli/src/{bootstrap.ts,cli/command-registry.ts,lib/startup/command-registry.ts}`.
30
+
31
+ - **The menubar prepack gate fails closed off-Mac when the bundle has no stapled notarization ticket (RUSH-3026).** 1.22.44 was packed on a Linux box where the gate's codesign/xcrun checks silently no-op'd, so an un-stapled dev bundle shipped and Gatekeeper rejected it on every Mac ("not notarized/valid; skipping launch") — the menu bar died until a rollback to 1.22.43. `stapler staple` writes the ticket as a plain file (`Contents/CodeResources`), so its absence is provable anywhere: the gate now hard-fails the pack when it is missing and no xcrun is available, turning that failure into a pack error instead of a shipped regression. Source: `apps/cli/scripts/verify-menubar-helper.sh`.
32
+
33
+ - **A Linux box can now produce the pretested release tarball — the attestation producer seeds the already-signed helper apps (RUSH-3026).** `release-attestation-produce.sh` runs in a fresh worktree whose `bin/` is empty (the signed `.app` helpers are untracked), so on any non-Mac box `npm pack` died at the prepack gates and attestation production — and therefore every release — stayed chained to a Mac. Off a macOS signing box the producer now seeds `bin/Agents CLI.app` and `bin/MenubarHelper.app` copy-if-absent from the caller checkout; the prepack gates still verify them (keychain sha pin, menubar presence), so a wrong or tampered seed fails the pack exactly as before. A Darwin producer's freshly signed apps are never overwritten. Source: `apps/cli/scripts/release-attestation-produce.sh`.
34
+
3
35
  ## 1.22.44
4
36
 
5
37
  - **Browser domain-skill discovery is layer-aware (RUSH-2497).** `agents browser start --url <url>` auto-loads a site-specific SKILL.md, but the lookup only ever searched the user layer (`~/.agents/skills/browser/domain-skills`), so a skill shipped in the system layer (`~/.agents/.system/skills/browser/domain-skills`) — or a project's `.agents/` — was silently invisible: the browser just opened without the guide. `resolveDomainSkill` now searches project > user > system > extra repos, first layer with a match wins, mirroring `resolveResource` precedence; `$AGENTS_BROWSER_DOMAIN_SKILLS_DIR` stays a single-root override for tests. The project layer resolves from the calling process's cwd — the shared browser daemon does not yet receive the CLI caller's cwd over IPC, so there the project layer follows the daemon's own cwd until RUSH-2996 threads it through. A miss still never breaks browser start, but now logs the roots searched at debug level (`AGENTS_DEBUG`/`DEBUG`) — the total silence is what hid this. Source: `apps/cli/src/lib/browser/domain-skills.ts`.
package/README.md CHANGED
@@ -81,6 +81,7 @@ Also available as `ag` -- all commands work with both `agents` and `ag`.
81
81
  - [Plugins](#plugins)
82
82
  - [Make it yours](#make-it-yours)
83
83
  - [Browser](#browser)
84
+ - [Sign in](#sign-in)
84
85
  - [Accounts](#accounts)
85
86
  - [Secrets](#secrets)
86
87
  - [Routines](#routines)
@@ -1002,6 +1003,29 @@ agents browser profiles create cloud \
1002
1003
 
1003
1004
  ---
1004
1005
 
1006
+ ## Sign in
1007
+
1008
+ `agents auth` signs this machine in to **Phoenix ID** — the Phoenix Labs account layer that spaces and plan tiers hang off. Sign-in is Google-only and runs a device-code flow: the CLI shows a code, your browser confirms it, and the CLI picks the session up.
1009
+
1010
+ ```bash
1011
+ agents auth login # shows a code, opens a Phoenix-branded page
1012
+ agents auth whoami # who this machine is signed in as (--json)
1013
+ agents auth logout # clears THIS machine; no other device is touched
1014
+
1015
+ agents auth space create "Design Team" # spaces are the team primitive
1016
+ agents auth space list # spaces you belong to
1017
+ agents auth space invite ada@example.com --role member
1018
+ agents auth space members # who is in it
1019
+ agents auth space role ada@example.com admin
1020
+ agents auth space remove ada@example.com # or remove yourself to leave
1021
+ ```
1022
+
1023
+ The session lives in this machine's agents state dir, so `logout` here signs out nothing else. `whoami` and every `space` subcommand take `--json`. `PHOENIX_ID_BASE` points the CLI at a different account service (a local one, for instance); it defaults to the deployed Phoenix ID service.
1024
+
1025
+ Distinct from **Accounts** below: this is *your human identity*; those are the *harness credentials* an agent runs under.
1026
+
1027
+ ---
1028
+
1005
1029
  ## Accounts
1006
1030
 
1007
1031
  Give a provider credential a durable name once, reuse it everywhere -- across harnesses, across machines.
package/dist/bootstrap.js CHANGED
@@ -264,12 +264,7 @@ Observe (read the fleet — no store merge):
264
264
  sync status Sync/drift only (not the live fleet snapshot)
265
265
  devices snapshot One-process inventory + active sessions poll
266
266
 
267
- Identity (Prix account — shared with paid tiers):
268
- auth login Sign in via the device-code flow
269
- auth whoami Show the signed-in Prix user
270
- auth space Create and manage a space (invite collaborators)
271
-
272
- Credentials (harness keys, not your Prix user):
267
+ Credentials (harness keys):
273
268
  harness Custom (host CLI + model + auth) harnesses; replaces former profiles command
274
269
  secrets Keychain-backed env bundles; synced vault: secrets vault unlock|lock
275
270
  accounts Provider credentials + native OAuth logout
@@ -91,7 +91,6 @@ export declare const loadHumans: ModuleLoader;
91
91
  export declare const loadAccounts: ModuleLoader;
92
92
  export declare const loadDaemon: ModuleLoader;
93
93
  export declare const loadAuth: ModuleLoader;
94
- export declare const loadOrg: ModuleLoader;
95
94
  /**
96
95
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
97
96
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -94,7 +94,6 @@ export const loadHumans = async () => (await import('../commands/humans.js')).re
94
94
  export const loadAccounts = async () => (await import('../commands/accounts.js')).registerAccountsCommand;
95
95
  export const loadDaemon = async () => (await import('../commands/daemon.js')).registerDaemonCommand;
96
96
  export const loadAuth = async () => (await import('../commands/auth.js')).registerAuthCommand;
97
- export const loadOrg = async () => (await import('../commands/org.js')).registerOrgCommand;
98
97
  /**
99
98
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
100
99
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -126,8 +125,6 @@ export const LAZY_COMMAND_NAMES = new Set([
126
125
  */
127
126
  export const COMMAND_LOADERS = {
128
127
  accounts: [loadAccounts],
129
- auth: [loadAuth],
130
- org: [loadOrg],
131
128
  view: [loadView],
132
129
  inspect: [loadInspect],
133
130
  feedback: [loadFeedback],
@@ -211,6 +208,7 @@ export const COMMAND_LOADERS = {
211
208
  webhooks: [loadWebhooks],
212
209
  humans: [loadHumans],
213
210
  daemon: [loadDaemon],
211
+ auth: [loadAuth],
214
212
  };
215
213
  /**
216
214
  * Register every module in {@link COMMAND_LOADERS} onto one fresh program and
@@ -30,16 +30,8 @@ export declare function parseBundleKey(raw: string): {
30
30
  bundle: string;
31
31
  key: string;
32
32
  };
33
- /**
34
- * Named accounts that `set-default` / `switch` can pin for this harness —
35
- * capped to the caller's plan tier (RUSH-2424). Downgrading never deletes a
36
- * credential: an over-cap account simply falls out of this list (see
37
- * {@link dormantAccountsForHarness}) and stops being a switch/rotation target
38
- * until the plan is upgraded.
39
- */
33
+ /** Named accounts that `set-default` / `switch` can pin for this harness. */
40
34
  export declare function listSwitchableAccounts(agent: AgentId): Promise<UnifiedAccount[]>;
41
- /** Accounts registered for this harness beyond the plan's cap — kept, never deleted, excluded from switch/rotation, reactivated by an upgrade. */
42
- export declare function dormantAccountsForHarness(agent: AgentId): Promise<UnifiedAccount[]>;
43
35
  /**
44
36
  * Pin the per-harness default. Shared by `accounts set-default` and `accounts switch`.
45
37
  * Provider accounts must authenticate the harness; native accounts must belong to it.
@@ -17,7 +17,6 @@ import { discoverNativeAccounts } from '../lib/account-catalog.js';
17
17
  import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
18
18
  import { getAccountProvider, listAccountProviders } from '../lib/account-provider-registry.js';
19
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';
21
20
  function parseInstallation(raw) {
22
21
  const at = raw.lastIndexOf('@');
23
22
  if (at < 1 || at === raw.length - 1)
@@ -74,8 +73,8 @@ function secretFromBundle(raw) {
74
73
  const { bundle, key } = parseBundleKey(raw);
75
74
  return readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' }).env[key];
76
75
  }
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 };
76
+ function publicAccount(account) {
77
+ return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
79
78
  }
80
79
  async function printAccounts(json, fleet = false) {
81
80
  if (fleet)
@@ -83,16 +82,12 @@ async function printAccounts(json, fleet = false) {
83
82
  const records = Object.values(readAccountRegistry().accounts).sort((a, b) => a.name.localeCompare(b.name));
84
83
  const discovered = await discoverNativeAccounts();
85
84
  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);
90
85
  const native = discovered.map(row => {
91
86
  const saved = savedNative.find(account => account.agent === row.agent && account.identityKey === row.id);
92
- return { ...row, name: saved?.name, id: saved?.id ?? row.id, dormant: !!saved && dormantIds.has(saved.id) };
87
+ return { ...row, name: saved?.name, id: saved?.id ?? row.id };
93
88
  });
94
89
  if (json) {
95
- console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name), dormantIds.has(account.id))), ...native], null, 2));
90
+ console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name))), ...native], null, 2));
96
91
  return;
97
92
  }
98
93
  console.log(chalk.bold('Provider account bundles\n'));
@@ -100,14 +95,13 @@ async function printAccounts(json, fleet = false) {
100
95
  console.log(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
101
96
  for (const account of records) {
102
97
  const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
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
+ console.log(` ${chalk.cyan(account.name)} ${account.provider} ${account.auth} ${present}`);
105
99
  }
106
100
  console.log(chalk.bold('\nNative harness logins\n'));
107
101
  if (!native.length)
108
102
  console.log(chalk.gray(' No signed-in native accounts found.'));
109
103
  for (const account of native)
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
+ console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}`);
111
105
  }
112
106
  function parseAuth(raw) {
113
107
  if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
@@ -131,7 +125,7 @@ function providerAuthenticatesHarness(provider, auth, agent) {
131
125
  throw err;
132
126
  }
133
127
  }
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. */
128
+ /** Every account (native + provider) registered/usable for this harness, oldest-first by name. */
135
129
  function accountsForHarness(agent) {
136
130
  const meta = readMeta();
137
131
  const native = listNativeAccounts(meta).filter(account => account.agent === agent);
@@ -140,52 +134,9 @@ function accountsForHarness(agent) {
140
134
  .map(account => ({ ...account, kind: 'provider' }));
141
135
  return [...native, ...providers].sort((a, b) => a.name.localeCompare(b.name));
142
136
  }
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
- */
137
+ /** Named accounts that `set-default` / `switch` can pin for this harness. */
150
138
  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
- }
139
+ return accountsForHarness(agent);
189
140
  }
190
141
  /**
191
142
  * Pin the per-harness default. Shared by `accounts set-default` and `accounts switch`.
@@ -279,16 +230,10 @@ export function registerAccountsCommand(program) {
279
230
  const provider = getAccountProvider(o.provider);
280
231
  if (!provider.authKinds.includes(auth))
281
232
  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);
287
233
  const secret = o.fromSecrets ? secretFromBundle(o.fromSecrets) : await password({ message: `Enter ${provider.provider} ${auth} for '${name}':` });
288
234
  const account = addAccount(name, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
289
235
  console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
290
236
  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);
292
237
  });
293
238
  accounts.command('set-key <name>')
294
239
  .description('Rotate an account credential without changing its identity')
@@ -306,22 +251,12 @@ export function registerAccountsCommand(program) {
306
251
  const unified = findUnifiedAccount(name, meta);
307
252
  if (!unified)
308
253
  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
- }
319
254
  const account = unified.kind === 'provider'
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) };
255
+ ? { ...publicAccount(inspectAccount(unified.name)), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
256
+ : { ...unified, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
322
257
  if (o.json || command.optsWithGlobals().json)
323
258
  return console.log(JSON.stringify(account, null, 2));
324
- console.log(chalk.bold(account.name) + (account.dormant ? chalk.gray(' — dormant (upgrade to reactivate)') : ''));
259
+ console.log(chalk.bold(account.name));
325
260
  console.log(` kind: ${account.kind}`);
326
261
  console.log(` id: ${account.id}`);
327
262
  console.log(` custody: ${account.custody}`);
@@ -340,12 +275,10 @@ export function registerAccountsCommand(program) {
340
275
  .description('Name a signed-in native installation without copying its OAuth credentials')
341
276
  .action(async (source, name) => {
342
277
  const identity = await nativeIdentityFromSource(source);
343
- const { tier, cap, existing } = await assertAccountCapacityFor([identity.agent]);
344
278
  const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
345
279
  console.log(chalk.green(`Named ${source} as ${account.name}.`));
346
280
  if (account.scope === 'device')
347
281
  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);
349
282
  });
350
283
  accounts.command('attach <account> <target>')
351
284
  .description('Attach a named account to a native installation or custom harness')
@@ -383,17 +316,6 @@ export function registerAccountsCommand(program) {
383
316
  // Provider account: it must be able to authenticate the target's harness.
384
317
  getAccountProvider(account.provider).envFor(targetAgent, account.auth);
385
318
  }
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));
397
319
  bindAccount(name, target);
398
320
  console.log(chalk.green(`Attached ${account.name} to ${target}.`));
399
321
  });
@@ -1,9 +1,2 @@
1
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
2
  export declare function registerAuthCommand(program: Command): void;