@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.
Files changed (291) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/apply.js +1 -1
  9. package/dist/commands/auth-mint.d.ts +7 -3
  10. package/dist/commands/auth-mint.js +21 -9
  11. package/dist/commands/auth.js +2 -3
  12. package/dist/commands/browser.js +10 -10
  13. package/dist/commands/daemon.d.ts +7 -3
  14. package/dist/commands/daemon.js +40 -25
  15. package/dist/commands/doctor.js +28 -2
  16. package/dist/commands/exec.js +87 -39
  17. package/dist/commands/fleet-capture.js +21 -2
  18. package/dist/commands/harness-wizard.js +2 -2
  19. package/dist/commands/lease.js +7 -12
  20. package/dist/commands/profiles.d.ts +2 -2
  21. package/dist/commands/profiles.js +20 -13
  22. package/dist/commands/repo.js +3 -3
  23. package/dist/commands/run-account-picker.d.ts +3 -3
  24. package/dist/commands/run-account-picker.js +29 -30
  25. package/dist/commands/secrets-passthrough.d.ts +20 -0
  26. package/dist/commands/secrets-passthrough.js +43 -0
  27. package/dist/commands/sessions-trace.d.ts +11 -0
  28. package/dist/commands/sessions-trace.js +71 -0
  29. package/dist/commands/setup-accounts.d.ts +3 -2
  30. package/dist/commands/setup-accounts.js +4 -4
  31. package/dist/commands/setup-secrets.d.ts +16 -21
  32. package/dist/commands/setup-secrets.js +43 -224
  33. package/dist/commands/ssh.js +2 -2
  34. package/dist/commands/sync.js +1 -1
  35. package/dist/commands/update.d.ts +10 -0
  36. package/dist/commands/update.js +43 -40
  37. package/dist/commands/versions.d.ts +11 -11
  38. package/dist/commands/versions.js +131 -83
  39. package/dist/commands/view.d.ts +1 -9
  40. package/dist/commands/view.js +41 -55
  41. package/dist/commands/webhook.js +5 -5
  42. package/dist/commands/workflows.js +2 -1
  43. package/dist/index.d.ts +12 -12
  44. package/dist/index.js +13 -42
  45. package/dist/lib/account-capabilities.d.ts +1 -1
  46. package/dist/lib/account-capabilities.js +1 -1
  47. package/dist/lib/account-catalog.d.ts +121 -6
  48. package/dist/lib/account-catalog.js +421 -7
  49. package/dist/lib/account-registry.d.ts +8 -30
  50. package/dist/lib/account-registry.js +36 -79
  51. package/dist/lib/account-schema.d.ts +1 -1
  52. package/dist/lib/account-schema.js +1 -1
  53. package/dist/lib/accounting/account-pool-collect.js +16 -4
  54. package/dist/lib/accounting/rotate.d.ts +20 -7
  55. package/dist/lib/accounting/rotate.js +89 -13
  56. package/dist/lib/accounting/usage.js +11 -16
  57. package/dist/lib/accounts/add.d.ts +138 -0
  58. package/dist/lib/accounts/add.js +651 -0
  59. package/dist/lib/accounts/migrate.d.ts +117 -0
  60. package/dist/lib/accounts/migrate.js +536 -0
  61. package/dist/lib/accounts/slots.d.ts +13 -0
  62. package/dist/lib/accounts/slots.js +100 -0
  63. package/dist/lib/agent-spec/agents.d.ts +16 -0
  64. package/dist/lib/agent-spec/agents.js +32 -4
  65. package/dist/lib/app-bundle-install.js +5 -4
  66. package/dist/lib/auth-health.d.ts +3 -0
  67. package/dist/lib/auth-health.js +11 -0
  68. package/dist/lib/auth-mint.d.ts +52 -16
  69. package/dist/lib/auth-mint.js +117 -39
  70. package/dist/lib/browser/chrome.d.ts +1 -1
  71. package/dist/lib/browser/chrome.js +5 -5
  72. package/dist/lib/byok-usage.js +3 -3
  73. package/dist/lib/claude-account-token.d.ts +47 -3
  74. package/dist/lib/claude-account-token.js +172 -50
  75. package/dist/lib/cloud/antigravity.js +4 -4
  76. package/dist/lib/cloud/cursor.js +4 -4
  77. package/dist/lib/crabbox/cli.d.ts +1 -1
  78. package/dist/lib/crabbox/cli.js +6 -6
  79. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  80. package/dist/lib/crabbox/runtimes.js +5 -5
  81. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  82. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  83. package/dist/lib/daemon/auth-sync-service.js +28 -1
  84. package/dist/lib/daemon/daemon.d.ts +6 -16
  85. package/dist/lib/daemon/daemon.js +52 -97
  86. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  87. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  88. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  89. package/dist/lib/daemon/harness-update-service.js +1 -1
  90. package/dist/lib/daemon/runner.d.ts +15 -3
  91. package/dist/lib/daemon/runner.js +49 -24
  92. package/dist/lib/daemon-health.d.ts +1 -2
  93. package/dist/lib/daemon-health.js +7 -6
  94. package/dist/lib/daemon-services.d.ts +1 -1
  95. package/dist/lib/daemon-services.js +1 -11
  96. package/dist/lib/daemon-ticks.js +23 -2
  97. package/dist/lib/daemon-webhooks.d.ts +8 -7
  98. package/dist/lib/daemon-webhooks.js +10 -9
  99. package/dist/lib/device-config.js +1 -1
  100. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  101. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  102. package/dist/lib/devices/harness-inventory.js +126 -4
  103. package/dist/lib/doctor-diff.js +2 -1
  104. package/dist/lib/exec-account-home.d.ts +38 -0
  105. package/dist/lib/exec-account-home.js +164 -0
  106. package/dist/lib/exec.d.ts +30 -0
  107. package/dist/lib/exec.js +101 -31
  108. package/dist/lib/feed/activity-stream.d.ts +60 -0
  109. package/dist/lib/feed/activity-stream.js +271 -0
  110. package/dist/lib/feed/activity.d.ts +7 -0
  111. package/dist/lib/feed/activity.js +10 -4
  112. package/dist/lib/feed/watch.d.ts +9 -3
  113. package/dist/lib/feed/watch.js +72 -36
  114. package/dist/lib/fleet/apply.d.ts +2 -2
  115. package/dist/lib/fleet/apply.js +4 -4
  116. package/dist/lib/fleet/auth-sync.js +4 -3
  117. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  118. package/dist/lib/fleet-shared-state.d.ts +6 -0
  119. package/dist/lib/harness/adapter.d.ts +7 -7
  120. package/dist/lib/harness/adapter.js +10 -6
  121. package/dist/lib/harness/adapters/grok.js +8 -3
  122. package/dist/lib/harness/adapters/muse.js +1 -1
  123. package/dist/lib/harness/adapters/opencode.js +12 -3
  124. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  125. package/dist/lib/harness-auth-capabilities.js +52 -0
  126. package/dist/lib/helper-versions.d.ts +6 -4
  127. package/dist/lib/helper-versions.js +5 -4
  128. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  129. package/dist/lib/hosts/credential-transport.js +37 -0
  130. package/dist/lib/hosts/dispatch.d.ts +7 -0
  131. package/dist/lib/hosts/dispatch.js +13 -6
  132. package/dist/lib/identity/client.d.ts +3 -3
  133. package/dist/lib/identity/client.js +3 -3
  134. package/dist/lib/installations/index.d.ts +1 -1
  135. package/dist/lib/installations/index.js +1 -1
  136. package/dist/lib/installations/migrate.js +9 -0
  137. package/dist/lib/installations/resolve.js +1 -1
  138. package/dist/lib/installations/shims.d.ts +15 -0
  139. package/dist/lib/installations/shims.js +69 -0
  140. package/dist/lib/installations/store.d.ts +43 -0
  141. package/dist/lib/installations/store.js +75 -7
  142. package/dist/lib/installations/versions.js +1 -1
  143. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  144. package/dist/lib/menubar/install-menubar.js +3 -3
  145. package/dist/lib/native-accounts.d.ts +35 -0
  146. package/dist/lib/native-accounts.js +41 -0
  147. package/dist/lib/net-close.d.ts +11 -0
  148. package/dist/lib/net-close.js +25 -0
  149. package/dist/lib/openclaw-keychain.js +27 -1
  150. package/dist/lib/profiles.js +22 -9
  151. package/dist/lib/project-resources.js +1 -1
  152. package/dist/lib/reserved-stores.d.ts +53 -0
  153. package/dist/lib/reserved-stores.js +120 -0
  154. package/dist/lib/secrets-client.d.ts +191 -0
  155. package/dist/lib/secrets-client.js +710 -0
  156. package/dist/lib/secrets-policy.d.ts +222 -0
  157. package/dist/lib/secrets-policy.js +483 -0
  158. package/dist/lib/secrets-types.d.ts +188 -0
  159. package/dist/lib/secrets-types.js +24 -0
  160. package/dist/lib/service-manifest.js +3 -4
  161. package/dist/lib/session/active.d.ts +38 -4
  162. package/dist/lib/session/active.js +36 -4
  163. package/dist/lib/session/bash-command.js +10 -0
  164. package/dist/lib/session/db.d.ts +65 -2
  165. package/dist/lib/session/db.js +178 -18
  166. package/dist/lib/session/mirror.d.ts +5 -0
  167. package/dist/lib/session/mirror.js +166 -0
  168. package/dist/lib/session/parse.d.ts +49 -0
  169. package/dist/lib/session/parse.js +324 -30
  170. package/dist/lib/session/prompt.d.ts +93 -2
  171. package/dist/lib/session/prompt.js +266 -15
  172. package/dist/lib/session/remote/peer-stream.d.ts +47 -0
  173. package/dist/lib/session/remote/peer-stream.js +142 -0
  174. package/dist/lib/session/remote/watch.d.ts +1 -1
  175. package/dist/lib/session/remote/watch.js +41 -42
  176. package/dist/lib/session/session-cache.d.ts +9 -0
  177. package/dist/lib/session/session-cache.js +40 -2
  178. package/dist/lib/session/sync/config.js +2 -2
  179. package/dist/lib/session/timeline-pass.d.ts +129 -0
  180. package/dist/lib/session/timeline-pass.js +323 -0
  181. package/dist/lib/session/timeline.d.ts +182 -0
  182. package/dist/lib/session/timeline.js +636 -0
  183. package/dist/lib/session/types.d.ts +155 -1
  184. package/dist/lib/sha256-asset.d.ts +11 -16
  185. package/dist/lib/sha256-asset.js +11 -16
  186. package/dist/lib/share/config.js +14 -5
  187. package/dist/lib/signin-badge.d.ts +26 -0
  188. package/dist/lib/signin-badge.js +42 -0
  189. package/dist/lib/staleness/detectors/workflows.js +3 -105
  190. package/dist/lib/staleness/writers/workflows.js +2 -1
  191. package/dist/lib/state.d.ts +7 -4
  192. package/dist/lib/state.js +18 -7
  193. package/dist/lib/summarizer/pass.d.ts +2 -0
  194. package/dist/lib/summarizer/pass.js +14 -1
  195. package/dist/lib/summarizer/summarize.d.ts +7 -0
  196. package/dist/lib/summarizer/summarize.js +3 -0
  197. package/dist/lib/sync-umbrella.js +2 -2
  198. package/dist/lib/teams/agents.js +1 -1
  199. package/dist/lib/types.d.ts +74 -33
  200. package/dist/lib/view-types.d.ts +6 -3
  201. package/dist/lib/workflows-registry.d.ts +71 -0
  202. package/dist/lib/workflows-registry.js +280 -0
  203. package/dist/lib/workflows.d.ts +21 -21
  204. package/dist/lib/workflows.js +12 -327
  205. package/package.json +2 -3
  206. package/scripts/postinstall.js +4 -29
  207. package/dist/commands/secrets-import.d.ts +0 -18
  208. package/dist/commands/secrets-import.js +0 -74
  209. package/dist/commands/secrets-migrate.d.ts +0 -25
  210. package/dist/commands/secrets-migrate.js +0 -334
  211. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  212. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  213. package/dist/commands/secrets-sync.d.ts +0 -11
  214. package/dist/commands/secrets-sync.js +0 -153
  215. package/dist/commands/secrets-vault.d.ts +0 -10
  216. package/dist/commands/secrets-vault.js +0 -130
  217. package/dist/commands/secrets.d.ts +0 -212
  218. package/dist/commands/secrets.js +0 -3030
  219. package/dist/lib/accounts/connect.d.ts +0 -170
  220. package/dist/lib/accounts/connect.js +0 -383
  221. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  222. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  223. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  224. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  225. package/dist/lib/secrets/agent.d.ts +0 -358
  226. package/dist/lib/secrets/agent.js +0 -1291
  227. package/dist/lib/secrets/audit.d.ts +0 -46
  228. package/dist/lib/secrets/audit.js +0 -101
  229. package/dist/lib/secrets/bundles.d.ts +0 -290
  230. package/dist/lib/secrets/bundles.js +0 -1547
  231. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  232. package/dist/lib/secrets/download-keychain.js +0 -70
  233. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  234. package/dist/lib/secrets/drivers/rush.js +0 -90
  235. package/dist/lib/secrets/fallback.d.ts +0 -48
  236. package/dist/lib/secrets/fallback.js +0 -48
  237. package/dist/lib/secrets/filestore.d.ts +0 -222
  238. package/dist/lib/secrets/filestore.js +0 -1099
  239. package/dist/lib/secrets/headless.d.ts +0 -43
  240. package/dist/lib/secrets/headless.js +0 -56
  241. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  242. package/dist/lib/secrets/icloud-import.js +0 -206
  243. package/dist/lib/secrets/index.d.ts +0 -451
  244. package/dist/lib/secrets/index.js +0 -1568
  245. package/dist/lib/secrets/install-helper.d.ts +0 -72
  246. package/dist/lib/secrets/install-helper.js +0 -245
  247. package/dist/lib/secrets/lease.d.ts +0 -25
  248. package/dist/lib/secrets/lease.js +0 -44
  249. package/dist/lib/secrets/linux.d.ts +0 -77
  250. package/dist/lib/secrets/linux.js +0 -393
  251. package/dist/lib/secrets/list-filter.d.ts +0 -109
  252. package/dist/lib/secrets/list-filter.js +0 -261
  253. package/dist/lib/secrets/mcp.d.ts +0 -93
  254. package/dist/lib/secrets/mcp.js +0 -211
  255. package/dist/lib/secrets/profiles.d.ts +0 -10
  256. package/dist/lib/secrets/profiles.js +0 -13
  257. package/dist/lib/secrets/push.d.ts +0 -133
  258. package/dist/lib/secrets/push.js +0 -273
  259. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  260. package/dist/lib/secrets/rc-hygiene.js +0 -143
  261. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  262. package/dist/lib/secrets/read-backoff.js +0 -64
  263. package/dist/lib/secrets/reaper.d.ts +0 -90
  264. package/dist/lib/secrets/reaper.js +0 -243
  265. package/dist/lib/secrets/remote.d.ts +0 -152
  266. package/dist/lib/secrets/remote.js +0 -344
  267. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  268. package/dist/lib/secrets/reserved-sync.js +0 -147
  269. package/dist/lib/secrets/scope.d.ts +0 -26
  270. package/dist/lib/secrets/scope.js +0 -29
  271. package/dist/lib/secrets/session-store.d.ts +0 -107
  272. package/dist/lib/secrets/session-store.js +0 -342
  273. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  274. package/dist/lib/secrets/sync-backend.js +0 -13
  275. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  276. package/dist/lib/secrets/sync-commands.js +0 -21
  277. package/dist/lib/secrets/sync.d.ts +0 -48
  278. package/dist/lib/secrets/sync.js +0 -237
  279. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  280. package/dist/lib/secrets/unlock-hints.js +0 -36
  281. package/dist/lib/secrets/usage-db.d.ts +0 -46
  282. package/dist/lib/secrets/usage-db.js +0 -96
  283. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  284. package/dist/lib/secrets/vault-age-helper.js +0 -34
  285. package/dist/lib/secrets/vault.d.ts +0 -49
  286. package/dist/lib/secrets/vault.js +0 -399
  287. package/dist/lib/secrets/windows.d.ts +0 -81
  288. package/dist/lib/secrets/windows.js +0 -556
  289. package/scripts/install-helper.js +0 -97
  290. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  291. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -3,7 +3,23 @@ import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from './i
3
3
  import { readInstallation } from './installations/store.js';
4
4
  import { readMeta } from './state.js';
5
5
  import { listNativeAccounts, readAccountRegistry } from './account-registry.js';
6
+ import { providerAuthenticatesHarness } from './account-provider-registry.js';
7
+ import { readSlots } from './accounts/slots.js';
8
+ import { harnessWorkerIsPerDevice } from './harness-auth-capabilities.js';
9
+ import { hasKeychainTokenSync, isSecretsTransportError } from './secrets-client.js';
6
10
  import { isLaunchableSignedIn as isCredentialLaunchable } from './accounting/rotate.js';
11
+ import { authCacheKey, readAuthHealthCache } from './auth-health.js';
12
+ import { readFleetSharedDeviceStates } from './fleet-shared-state.js';
13
+ import { machineId } from './machine-id.js';
14
+ import { isHeadedDeviceRole, selfConfiguredDeviceRole } from './device-config.js';
15
+ import { applyUsageHonesty, collectLocalHarnessInventory, } from './devices/harness-inventory.js';
16
+ import { fixFor, } from './signin-badge.js';
17
+ import { renderBar } from './accounting/usage.js';
18
+ import { padToWidth, stringWidth } from './text/width.js';
19
+ export { applyUsageHonesty };
20
+ import chalk from 'chalk';
21
+ /** Compact usage gauge in the USAGE column — same length as the pre-T3 inline bar. */
22
+ const LISTING_USAGE_BAR_LEN = 5;
7
23
  /**
8
24
  * Strict "is this home actually connected?" — a live CREDENTIAL in that exact
9
25
  * version home, not just a metadata identity claim (PHNX-3940). A `.claude.json`
@@ -46,6 +62,28 @@ export async function discoverNativeAccounts() {
46
62
  const rows = await collectNativeHomeRows();
47
63
  return groupNativeAccountRows(rows.map(r => ({ agent: r.agent, version: r.label, accountKey: r.accountKey, email: r.email, signedIn: r.signedIn })));
48
64
  }
65
+ /**
66
+ * The provider (durable-credential) accounts read through `secrets`, tolerant of
67
+ * an unreachable standalone. Native OAuth logins never touch `secrets`, so a
68
+ * missing/broken standalone must NOT take down the whole account view — the
69
+ * provider section is simply reported unavailable. Only a TRANSPORT-level
70
+ * failure (missing binary, timeout, non-JSON, spawn error) degrades; a genuine
71
+ * data error still throws so it is never silently swallowed. DIST-1 holds: there
72
+ * is no fallback to an embedded engine, the standalone just could not answer.
73
+ */
74
+ function readProviderRowsTolerant(meta) {
75
+ try {
76
+ const rows = Object.values(readAccountRegistry().accounts)
77
+ .map((account) => toProviderRow(account, meta))
78
+ .sort((a, b) => a.name.localeCompare(b.name));
79
+ return { rows, error: null };
80
+ }
81
+ catch (err) {
82
+ if (isSecretsTransportError(err))
83
+ return { rows: [], error: err };
84
+ throw err;
85
+ }
86
+ }
49
87
  /** Probe every installed home of every inspectable harness for its native identity. */
50
88
  export async function collectNativeHomeRows() {
51
89
  const rows = [];
@@ -140,6 +178,19 @@ export function buildNativeCatalog(rows, meta, globalDefault = getGlobalDefault)
140
178
  installations: homes,
141
179
  isDefault,
142
180
  state: signedIn ? 'connected' : 'reconnect-needed',
181
+ identityLabel: email ?? account?.name ?? group.identityKey,
182
+ provisioning: provisioningFor(group.agent),
183
+ verdict: signedIn ? 'unverified' : 'missing',
184
+ checkedAt: null,
185
+ devices: [],
186
+ usage: null,
187
+ fix: fixFor({
188
+ agent: group.agent,
189
+ verdict: signedIn ? 'unverified' : 'missing',
190
+ name: account?.name,
191
+ version: home,
192
+ provisioning: provisioningFor(group.agent),
193
+ }),
143
194
  });
144
195
  }
145
196
  // Named accounts first, then default, then a stable display order.
@@ -148,19 +199,382 @@ export function buildNativeCatalog(rows, meta, globalDefault = getGlobalDefault)
148
199
  || Number(b.isDefault) - Number(a.isDefault)
149
200
  || a.display.localeCompare(b.display));
150
201
  }
151
- function toProviderRow(account) {
152
- return { kind: 'provider', name: account.name, id: account.id, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl };
202
+ function provisioningFor(agent) {
203
+ return harnessWorkerIsPerDevice(agent) ? 'per-device' : 'portable';
204
+ }
205
+ export function toProviderRow(account, meta) {
206
+ const harnesses = ALL_AGENT_IDS.filter((agent) => providerAuthenticatesHarness(account.provider, account.auth, agent));
207
+ const defaults = meta.accounts?.defaults ?? {};
208
+ const defaultFor = ALL_AGENT_IDS.filter((agent) => {
209
+ const ref = defaults[agent];
210
+ return ref === account.name || ref === account.id;
211
+ });
212
+ const secretPresent = hasKeychainTokenSync(account.secretRef);
213
+ return {
214
+ kind: 'provider',
215
+ name: account.name,
216
+ id: account.id,
217
+ provider: account.provider,
218
+ auth: account.auth,
219
+ ...(account.baseUrl ? { baseUrl: account.baseUrl } : {}),
220
+ harnesses,
221
+ defaultFor,
222
+ identityLabel: account.provider,
223
+ verdict: secretPresent ? 'ready' : 'missing',
224
+ fix: secretPresent ? null : `agents accounts set-key ${account.name}`,
225
+ };
153
226
  }
154
227
  /**
155
228
  * Wire the real collectors to the pure builder. This is the canonical
156
229
  * account-first read model `view.ts` renders: native identities (account +
157
- * connection first, release/home secondary) plus the durable provider bundles.
230
+ * connection first, release/home secondary) plus durable provider credentials.
158
231
  */
159
232
  export async function loadAccountCatalog() {
160
233
  const meta = readMeta();
161
234
  const native = buildNativeCatalog(await collectNativeHomeRows(), meta);
162
- const provider = Object.values(readAccountRegistry().accounts)
163
- .map(toProviderRow)
164
- .sort((a, b) => a.name.localeCompare(b.name));
165
- return { native, provider };
235
+ const host = machineId();
236
+ const auth = readAuthHealthCache();
237
+ const inventory = await collectLocalHarnessInventory();
238
+ const inventoryByHome = new Map(inventory.map((row) => [`${row.agent}:${row.version}`, row]));
239
+ const shared = readSharedAccountVerdicts();
240
+ const headed = isHeadedDeviceRole(selfConfiguredDeviceRole());
241
+ const localSlots = readSlots(meta);
242
+ for (const row of native) {
243
+ const registered = row.id ? listNativeAccounts(meta).find((account) => account.id === row.id) : undefined;
244
+ row.provisioning = registered?.provisioning ?? provisioningFor(row.agent);
245
+ const localHome = row.installations.find((home) => home.label === row.home)
246
+ ?? row.installations.find((home) => home.signedIn)
247
+ ?? row.installations[0];
248
+ const cached = localHome ? auth[authCacheKey(host, row.agent, localHome.label)] : undefined;
249
+ const slot = row.id ? localSlots[row.id] : undefined;
250
+ const observation = resolveLocalAccountObservation(slot, cached, localHome?.signedIn === true);
251
+ const local = {
252
+ device: host,
253
+ authMode: observation.authMode
254
+ ?? (row.provisioning === 'per-device' ? 'per-device' : (headed ? 'native' : 'durable')),
255
+ verdict: observation.verdict,
256
+ ...(observation.checkedAt ? { checkedAt: observation.checkedAt } : {}),
257
+ };
258
+ // Registered accounts join fleet state on their stable id ONLY — two
259
+ // accounts may share one email label, so a label join would cross-attribute
260
+ // one account's revoked verdict to the other. The label index exists solely
261
+ // for unnamed legacy logins, which have no registry id to key on.
262
+ const fromFleet = row.id
263
+ ? (shared.get(`${row.agent}:${row.id}`) ?? [])
264
+ : (shared.get(`label:${row.agent}:${row.identityLabel}`) ?? []);
265
+ row.devices = mergeDeviceVerdicts(fromFleet, local);
266
+ row.verdict = aggregateAccountVerdict(row.provisioning, row.devices);
267
+ row.checkedAt = newestCheckedAt(row.devices);
268
+ const honest = applyUsageHonesty(row.verdict, localHome ? (inventoryByHome.get(`${row.agent}:${localHome.label}`)?.quota ?? null) : null);
269
+ row.verdict = honest.verdict;
270
+ row.usage = honest.usage;
271
+ row.fix = fixFor({
272
+ agent: row.agent,
273
+ verdict: row.verdict,
274
+ name: row.name,
275
+ version: localHome?.label,
276
+ provisioning: row.provisioning,
277
+ hasSlot: slot != null,
278
+ });
279
+ }
280
+ const { rows: provider, error } = readProviderRowsTolerant(meta);
281
+ return error
282
+ ? { native, provider, secretsUnavailable: { code: error.code, message: error.message } }
283
+ : { native, provider };
284
+ }
285
+ /**
286
+ * The one clear line a read-only surface prints when the provider section could
287
+ * not be read (PHNX-3989) — so a missing/broken standalone is stated plainly
288
+ * while the native rows and the rest of the output still render. Null when the
289
+ * catalog is complete. Printed to stderr by the command so it never corrupts
290
+ * `--json` stdout.
291
+ */
292
+ export function secretsUnavailableNote(catalog) {
293
+ const err = catalog.secretsUnavailable;
294
+ if (!err)
295
+ return null;
296
+ return `secrets unavailable (${err.code}) — provider accounts not shown; native logins and the rest are current.`;
297
+ }
298
+ function providerListEntry(row, harness) {
299
+ return {
300
+ kind: 'provider',
301
+ id: row.id,
302
+ harness,
303
+ name: row.name,
304
+ identityLabel: row.identityLabel,
305
+ isDefault: harness ? row.defaultFor.includes(harness) : false,
306
+ provisioning: 'portable',
307
+ verdict: row.verdict,
308
+ checkedAt: null,
309
+ devices: [],
310
+ usage: null,
311
+ fix: row.fix,
312
+ };
313
+ }
314
+ /**
315
+ * Unfiltered JSON emits one entry per harness the credential authenticates
316
+ * (same `id`, `kind: 'provider'`, each with its own `harness`), plus one
317
+ * `harness: null` entry for an orphan. A harness filter emits only that
318
+ * harness's entry. The v2 field set is otherwise unchanged.
319
+ */
320
+ function providerJsonEntries(row, harness) {
321
+ if (harness) {
322
+ return row.harnesses.includes(harness) ? [providerListEntry(row, harness)] : [];
323
+ }
324
+ if (row.harnesses.length === 0)
325
+ return [providerListEntry(row, null)];
326
+ return row.harnesses.map((agent) => providerListEntry(row, agent));
327
+ }
328
+ export function accountListJson(native, providers = [], harness) {
329
+ return {
330
+ version: 2,
331
+ accounts: [
332
+ ...native.map((row) => ({
333
+ kind: 'native',
334
+ id: row.id ?? row.identityKey,
335
+ harness: row.agent,
336
+ name: row.name,
337
+ identityLabel: row.identityLabel,
338
+ isDefault: row.isDefault,
339
+ provisioning: row.provisioning,
340
+ verdict: row.verdict,
341
+ checkedAt: row.checkedAt,
342
+ devices: row.devices.map(({ device, authMode, verdict }) => ({ device, authMode, verdict })),
343
+ usage: row.usage,
344
+ fix: row.fix,
345
+ })),
346
+ ...providers.flatMap((row) => providerJsonEntries(row, harness)),
347
+ ],
348
+ };
349
+ }
350
+ function verdictText(verdict) {
351
+ if (verdict === 'rate_limited')
352
+ return 'LIMITED';
353
+ return verdict.toUpperCase();
354
+ }
355
+ function whereText(row, localDevice) {
356
+ const names = [...new Set(row.devices.map((device) => device.device))];
357
+ // Peers on an older release do not publish slot verdicts, so the only
358
+ // observation is this box — say so rather than a coverage count of +1.
359
+ if (names.length === 1 && names[0] === localDevice)
360
+ return 'this box';
361
+ const live = row.devices.filter((device) => device.verdict === 'live').length;
362
+ if (live > 0)
363
+ return `+${live}`;
364
+ if (row.provisioning === 'per-device') {
365
+ const present = row.devices
366
+ .filter((device) => device.verdict !== 'missing')
367
+ .map((device) => device.device);
368
+ return present.join(', ') || 'this box';
369
+ }
370
+ return '—';
371
+ }
372
+ function usageText(row) {
373
+ if (row.usage?.status === 'rate_limited' && (row.usage.usedPercent === null || row.usage.usedPercent === undefined)) {
374
+ return 'limited';
375
+ }
376
+ if (row.usage?.status === 'out_of_credits')
377
+ return 'no credits';
378
+ if (row.usage?.usedPercent === null || row.usage?.usedPercent === undefined)
379
+ return '';
380
+ const percent = `${row.usage.usedPercent}%${row.usage.stale ? '*' : ''}`;
381
+ return `${renderBar(row.usage.usedPercent, LISTING_USAGE_BAR_LEN)} ${percent}`;
382
+ }
383
+ function nativeLine(row, localDevice) {
384
+ return {
385
+ name: row.name ?? 'unnamed',
386
+ identityLabel: row.identityLabel,
387
+ verdict: row.verdict,
388
+ where: whereText(row, localDevice),
389
+ fix: row.fix,
390
+ usage: usageText(row),
391
+ isDefault: row.isDefault,
392
+ };
393
+ }
394
+ function providerLine(row, harness) {
395
+ return {
396
+ name: row.name,
397
+ identityLabel: row.identityLabel,
398
+ verdict: row.verdict,
399
+ where: '—',
400
+ fix: row.fix,
401
+ usage: '',
402
+ isDefault: harness ? row.defaultFor.includes(harness) : false,
403
+ };
404
+ }
405
+ function formatListingLine(line, nameW, identityW, stateW, whereW, usageW) {
406
+ const marker = line.isDefault ? '*' : ' ';
407
+ const fix = line.fix ? `fix: ${line.fix}` : '';
408
+ return (` ${chalk.green(marker)} ${chalk.cyan(line.name.padEnd(nameW))} `
409
+ + `${line.identityLabel.padEnd(identityW)} `
410
+ + `${verdictText(line.verdict).padEnd(stateW)} `
411
+ + `${line.where.padEnd(whereW)} `
412
+ + `${padToWidth(line.usage, usageW)} `
413
+ + `${fix ? chalk.gray(fix) : ''}`).trimEnd();
414
+ }
415
+ function pushGroup(out, title, lines, widths, harnessHeadings) {
416
+ if (lines.length === 0)
417
+ return;
418
+ if (harnessHeadings)
419
+ out.push(chalk.bold(title));
420
+ out.push(chalk.gray(` ${'ACCOUNT'.padEnd(widths.nameW + 2)}${'IDENTITY'.padEnd(widths.identityW + 2)}${'STATE'.padEnd(widths.stateW + 2)}${'WHERE'.padEnd(widths.whereW + 2)}${'USAGE'.padEnd(widths.usageW + 2)}FIX`));
421
+ for (const line of lines) {
422
+ out.push(formatListingLine(line, widths.nameW, widths.identityW, widths.stateW, widths.whereW, widths.usageW));
423
+ }
424
+ out.push('');
425
+ }
426
+ /** Shared text renderer used by both `accounts list` and account-first `view`. */
427
+ export function renderAccountRows(rows, opts) {
428
+ const heading = opts.heading !== false;
429
+ const footer = opts.footer !== false;
430
+ const harnessHeadings = opts.harnessHeadings !== false;
431
+ const providers = opts.providers ?? [];
432
+ const harnessFilter = opts.harness;
433
+ const localDevice = opts.localDevice;
434
+ const out = [];
435
+ if (heading)
436
+ out.push(`${chalk.bold('Accounts')} ${chalk.gray('run: agents run <h>#<name>')}`, '');
437
+ const visibleProviders = harnessFilter
438
+ ? providers.filter((row) => row.harnesses.includes(harnessFilter))
439
+ : providers;
440
+ const visibleNative = harnessFilter
441
+ ? rows.filter((row) => row.agent === harnessFilter)
442
+ : rows;
443
+ if (visibleNative.length === 0 && visibleProviders.length === 0) {
444
+ out.push(chalk.gray('No accounts found. Add one: agents accounts add <harness> [name]'));
445
+ }
446
+ else {
447
+ const harnesses = new Set();
448
+ for (const row of visibleNative)
449
+ harnesses.add(row.agent);
450
+ for (const row of visibleProviders) {
451
+ for (const agent of row.harnesses) {
452
+ if (!harnessFilter || agent === harnessFilter)
453
+ harnesses.add(agent);
454
+ }
455
+ }
456
+ const grouped = new Map();
457
+ for (const harness of [...harnesses].sort((a, b) => a.localeCompare(b))) {
458
+ const lines = [
459
+ ...visibleNative.filter((row) => row.agent === harness).map((row) => nativeLine(row, localDevice)),
460
+ ...visibleProviders.filter((row) => row.harnesses.includes(harness)).map((row) => providerLine(row, harness)),
461
+ ];
462
+ if (lines.length === 0)
463
+ continue;
464
+ grouped.set(harness, lines);
465
+ }
466
+ const orphans = harnessFilter
467
+ ? []
468
+ : visibleProviders.filter((row) => row.harnesses.length === 0).map((row) => providerLine(row));
469
+ const allLines = [...grouped.values()].flat().concat(orphans);
470
+ const widths = {
471
+ nameW: Math.max(7, ...allLines.map((line) => line.name.length)),
472
+ identityW: Math.max(8, ...allLines.map((line) => line.identityLabel.length)),
473
+ stateW: Math.max(5, ...allLines.map((line) => verdictText(line.verdict).length)),
474
+ whereW: Math.max(5, ...allLines.map((line) => line.where.length)),
475
+ usageW: Math.max(5, ...allLines.map((line) => stringWidth(line.usage))),
476
+ };
477
+ for (const [harness, lines] of grouped) {
478
+ pushGroup(out, harness, lines, widths, harnessHeadings);
479
+ }
480
+ if (orphans.length > 0)
481
+ pushGroup(out, 'Other accounts', orphans, widths, true);
482
+ }
483
+ if (footer) {
484
+ // "Need you" is an actionable repair, not an unread usage probe. Unverified
485
+ // (a worker whose token lacks the usage scope) has no fix and must not
486
+ // inflate the count.
487
+ const count = visibleNative.filter((row) => !!row.fix).length
488
+ + visibleProviders.filter((row) => !!row.fix).length;
489
+ out.push(chalk.gray(`${count} accounts need you · add: agents accounts add <harness>`));
490
+ }
491
+ return out.join('\n').trimEnd();
492
+ }
493
+ export function readSharedAccountVerdicts(userAgentsDir) {
494
+ const out = new Map();
495
+ for (const state of readFleetSharedDeviceStates(userAgentsDir).states) {
496
+ for (const row of state.accounts?.rows ?? []) {
497
+ if (!ALL_AGENT_IDS.includes(row.harness))
498
+ continue;
499
+ if (!['native', 'durable', 'per-device'].includes(row.authMode))
500
+ continue;
501
+ if (!['live', 'expired', 'revoked', 'rate_limited', 'unverified', 'missing'].includes(row.verdict))
502
+ continue;
503
+ const key = `${row.harness}:${row.accountId}`;
504
+ const values = out.get(key) ?? [];
505
+ values.push({
506
+ device: state.device,
507
+ authMode: row.authMode,
508
+ verdict: row.verdict,
509
+ ...(row.checkedAt ? { checkedAt: row.checkedAt } : {}),
510
+ });
511
+ out.set(key, values);
512
+ if (row.identityLabel)
513
+ out.set(`label:${row.harness}:${row.identityLabel}`, values);
514
+ }
515
+ }
516
+ return out;
517
+ }
518
+ /**
519
+ * Devices whose daemon-state file exists but carries no account verdicts —
520
+ * typically an older release that has not started publishing the T3 envelope.
521
+ * The fleet matrix lists them in a coverage note rather than as blank columns.
522
+ */
523
+ export function listDevicesWithoutAccountVerdicts(userAgentsDir) {
524
+ const out = [];
525
+ for (const state of readFleetSharedDeviceStates(userAgentsDir).states) {
526
+ if (!state.accounts?.rows?.length)
527
+ out.push(state.device);
528
+ }
529
+ return out.sort((a, b) => a.localeCompare(b));
530
+ }
531
+ function normalizeAuthVerdict(verdict, signedIn) {
532
+ if (verdict === 'unconfigured')
533
+ return 'missing';
534
+ if (verdict === 'error')
535
+ return signedIn ? 'unverified' : 'missing';
536
+ return verdict ?? (signedIn ? 'unverified' : 'missing');
537
+ }
538
+ /**
539
+ * Merge the two writers of the same local truth — the T1 slot store and the
540
+ * daemon's auth-health cache — by NEWEST observation. A naive `??` chain lets
541
+ * an older slot `live` permanently mask a newer daemon `revoked`, so a revoked
542
+ * account would keep rendering LIVE on its own device. Pure.
543
+ */
544
+ export function resolveLocalAccountObservation(slot, cached, signedIn) {
545
+ const slotAt = slot?.checkedAt ? Date.parse(slot.checkedAt) : null;
546
+ const cachedAt = cached?.checkedAt ?? null;
547
+ const preferSlot = !!slot && (!cached || (slotAt !== null && (cachedAt === null || slotAt >= cachedAt)));
548
+ return {
549
+ verdict: normalizeAuthVerdict((preferSlot ? slot : cached)?.verdict, signedIn),
550
+ ...(preferSlot && slot?.authMode ? { authMode: slot.authMode } : {}),
551
+ ...(preferSlot && slot?.checkedAt
552
+ ? { checkedAt: slot.checkedAt }
553
+ : !preferSlot && cached ? { checkedAt: new Date(cached.checkedAt).toISOString() } : {}),
554
+ };
555
+ }
556
+ function mergeDeviceVerdicts(fleet, local) {
557
+ const byDevice = new Map(fleet.map((row) => [row.device, row]));
558
+ byDevice.set(local.device, local);
559
+ return [...byDevice.values()].sort((a, b) => a.device.localeCompare(b.device));
560
+ }
561
+ function aggregateAccountVerdict(provisioning, devices) {
562
+ const verdicts = devices.map((row) => row.verdict);
563
+ if (verdicts.includes('revoked'))
564
+ return 'revoked';
565
+ if (verdicts.includes('expired'))
566
+ return 'expired';
567
+ if (verdicts.includes('rate_limited'))
568
+ return 'rate_limited';
569
+ if (verdicts.includes('live'))
570
+ return 'live';
571
+ if (verdicts.includes('unverified'))
572
+ return 'unverified';
573
+ return provisioning === 'per-device' ? 'per-device' : 'missing';
574
+ }
575
+ function newestCheckedAt(devices) {
576
+ return devices
577
+ .map((row) => row.checkedAt)
578
+ .filter((value) => !!value)
579
+ .sort((a, b) => Date.parse(b) - Date.parse(a))[0] ?? null;
166
580
  }
@@ -1,4 +1,4 @@
1
- import { type AgentId, type Meta } from './types.js';
1
+ import { type AgentId, type Meta, type NativeAccountRecord } from './types.js';
2
2
  import { type AccountAuthKind } from './account-provider-registry.js';
3
3
  export interface CredentialAccount {
4
4
  id: string;
@@ -19,15 +19,11 @@ export interface ResolvedCredentialAccount {
19
19
  auth: AccountAuthKind;
20
20
  env: Record<string, string>;
21
21
  }
22
- export interface NativeAccount {
23
- id: string;
24
- name: string;
22
+ export interface NativeAccount extends NativeAccountRecord {
25
23
  kind: 'native';
26
- agent: AgentId;
27
- identityKey: string;
28
- identityLabel?: string;
29
- scope: 'version' | 'device';
30
24
  }
25
+ export { recordSlot, readSlots } from './accounts/slots.js';
26
+ export { registeredNativeAccountForEmail, parseNativeIdentityKey } from './native-accounts.js';
31
27
  export type UnifiedAccount = (CredentialAccount & {
32
28
  kind: 'provider';
33
29
  }) | NativeAccount;
@@ -86,7 +82,7 @@ export declare function parseAccountSelector(input: string): {
86
82
  export declare function assertUnambiguousNativeAccount(meta: Pick<Meta, 'accounts' | 'deviceAccounts'>, name: string, agent?: AgentId): void;
87
83
  /**
88
84
  * Validate a native account NAME (charset + per-harness uniqueness) WITHOUT a
89
- * known identity — the pre-flight `agents accounts connect` runs before it
85
+ * known identity — the pre-flight `agents accounts add` runs before it
90
86
  * installs a home and drives a login, so a bad/colliding name fails before any
91
87
  * side effect instead of orphaning a freshly-minted home (PHNX-3940).
92
88
  */
@@ -95,29 +91,11 @@ export declare function addNativeAccount(name: string, agent: AgentId, identityK
95
91
  /** Create or replace the version-independent label for one native identity. */
96
92
  export declare function labelNativeAccount(agent: AgentId, identityKey: string, identityLabel: string | undefined, label: string | undefined, scope: 'version' | 'device'): NativeAccount;
97
93
  /**
98
- * Record THIS box's connect home for an account (PHNX-3940). Device-scoped: the
99
- * home a box minted for an account is not assumed to exist on any other box, so
100
- * it lives in the device doc keyed by the stable account id, never on the
101
- * fleet-synced central identity row. Idempotent. A reconnect reads it back via
102
- * {@link nativeAccountHome} to reuse the exact home even when the credential has
103
- * expired and local signed-in discovery can no longer find it.
94
+ * This box's recorded home LABEL for an account, or null.
95
+ * Reads the leftover device-scoped `homes` map so legacy `acct-*` installation
96
+ * labels still resolve (T5/T7); spawn-time HOME is {@link readSlots}[id].slotDir.
104
97
  */
105
- export declare function setNativeAccountHome(accountId: string, installationLabel: string): void;
106
- /** This box's recorded connect home for an account, or null. */
107
98
  export declare function nativeAccountHome(accountId: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
108
- /**
109
- * Every installation label THIS box has recorded as SOME account's connect home
110
- * (PHNX-3940). These are identity-bearing and MUST NEVER be re-minted for a new
111
- * account — the safe-allocation invariant that stops a new connect from
112
- * overwriting another account's login.
113
- */
114
- export declare function ownedConnectHomeLabels(meta: Pick<Meta, 'deviceAccounts'>): Set<string>;
115
- /** This box's in-flight connect slot for `(agent, name)`, or null. */
116
- export declare function pendingConnectSlot(agent: AgentId, name: string, meta: Pick<Meta, 'deviceAccounts'>): string | null;
117
- /** Record an in-flight connect slot for `(agent, name)` (device-scoped). */
118
- export declare function setPendingConnectSlot(agent: AgentId, name: string, slot: string): void;
119
- /** Clear the in-flight connect slot for `(agent, name)` once the account lands. */
120
- export declare function clearPendingConnectSlot(agent: AgentId, name: string): void;
121
99
  /**
122
100
  * Set the per-harness default account by NAME only when none is configured
123
101
  * (PHNX-3940). Never overrides an existing choice — a first connect selecting a