@phnx-labs/agents-cli 1.22.84 → 1.22.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/CHANGELOG.md +57 -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/setup-accounts.d.ts +3 -2
  28. package/dist/commands/setup-accounts.js +4 -4
  29. package/dist/commands/setup-secrets.d.ts +16 -21
  30. package/dist/commands/setup-secrets.js +43 -224
  31. package/dist/commands/ssh.js +2 -2
  32. package/dist/commands/sync.js +1 -1
  33. package/dist/commands/update.d.ts +10 -0
  34. package/dist/commands/update.js +43 -40
  35. package/dist/commands/versions.d.ts +11 -11
  36. package/dist/commands/versions.js +131 -83
  37. package/dist/commands/view.d.ts +1 -9
  38. package/dist/commands/view.js +41 -55
  39. package/dist/commands/webhook.js +5 -5
  40. package/dist/commands/workflows.js +2 -1
  41. package/dist/index.d.ts +12 -12
  42. package/dist/index.js +13 -42
  43. package/dist/lib/account-capabilities.d.ts +1 -1
  44. package/dist/lib/account-capabilities.js +1 -1
  45. package/dist/lib/account-catalog.d.ts +121 -6
  46. package/dist/lib/account-catalog.js +421 -7
  47. package/dist/lib/account-registry.d.ts +8 -30
  48. package/dist/lib/account-registry.js +36 -79
  49. package/dist/lib/account-schema.d.ts +1 -1
  50. package/dist/lib/account-schema.js +1 -1
  51. package/dist/lib/accounting/account-pool-collect.js +16 -4
  52. package/dist/lib/accounting/rotate.d.ts +20 -7
  53. package/dist/lib/accounting/rotate.js +89 -13
  54. package/dist/lib/accounting/usage.js +11 -16
  55. package/dist/lib/accounts/add.d.ts +138 -0
  56. package/dist/lib/accounts/add.js +651 -0
  57. package/dist/lib/accounts/migrate.d.ts +117 -0
  58. package/dist/lib/accounts/migrate.js +536 -0
  59. package/dist/lib/accounts/slots.d.ts +13 -0
  60. package/dist/lib/accounts/slots.js +100 -0
  61. package/dist/lib/agent-spec/agents.d.ts +16 -0
  62. package/dist/lib/agent-spec/agents.js +32 -4
  63. package/dist/lib/app-bundle-install.js +5 -4
  64. package/dist/lib/auth-health.d.ts +3 -0
  65. package/dist/lib/auth-health.js +11 -0
  66. package/dist/lib/auth-mint.d.ts +52 -16
  67. package/dist/lib/auth-mint.js +117 -39
  68. package/dist/lib/browser/chrome.d.ts +1 -1
  69. package/dist/lib/browser/chrome.js +5 -5
  70. package/dist/lib/byok-usage.js +3 -3
  71. package/dist/lib/claude-account-token.d.ts +47 -3
  72. package/dist/lib/claude-account-token.js +172 -50
  73. package/dist/lib/cloud/antigravity.js +4 -4
  74. package/dist/lib/cloud/cursor.js +4 -4
  75. package/dist/lib/crabbox/cli.d.ts +1 -1
  76. package/dist/lib/crabbox/cli.js +6 -6
  77. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  78. package/dist/lib/crabbox/runtimes.js +5 -5
  79. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  80. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  81. package/dist/lib/daemon/auth-sync-service.js +28 -1
  82. package/dist/lib/daemon/daemon.d.ts +6 -16
  83. package/dist/lib/daemon/daemon.js +52 -97
  84. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  85. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  86. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  87. package/dist/lib/daemon/harness-update-service.js +1 -1
  88. package/dist/lib/daemon/runner.d.ts +15 -3
  89. package/dist/lib/daemon/runner.js +49 -24
  90. package/dist/lib/daemon-health.d.ts +1 -2
  91. package/dist/lib/daemon-health.js +7 -6
  92. package/dist/lib/daemon-services.d.ts +1 -1
  93. package/dist/lib/daemon-services.js +1 -11
  94. package/dist/lib/daemon-webhooks.d.ts +8 -7
  95. package/dist/lib/daemon-webhooks.js +10 -9
  96. package/dist/lib/device-config.js +1 -1
  97. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  98. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  99. package/dist/lib/devices/harness-inventory.js +126 -4
  100. package/dist/lib/doctor-diff.js +2 -1
  101. package/dist/lib/exec-account-home.d.ts +38 -0
  102. package/dist/lib/exec-account-home.js +164 -0
  103. package/dist/lib/exec.d.ts +30 -0
  104. package/dist/lib/exec.js +101 -31
  105. package/dist/lib/fleet/apply.d.ts +2 -2
  106. package/dist/lib/fleet/apply.js +4 -4
  107. package/dist/lib/fleet/auth-sync.js +4 -3
  108. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  109. package/dist/lib/harness/adapter.d.ts +7 -7
  110. package/dist/lib/harness/adapter.js +10 -6
  111. package/dist/lib/harness/adapters/grok.js +8 -3
  112. package/dist/lib/harness/adapters/muse.js +1 -1
  113. package/dist/lib/harness/adapters/opencode.js +12 -3
  114. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  115. package/dist/lib/harness-auth-capabilities.js +52 -0
  116. package/dist/lib/helper-versions.d.ts +6 -4
  117. package/dist/lib/helper-versions.js +5 -4
  118. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  119. package/dist/lib/hosts/credential-transport.js +37 -0
  120. package/dist/lib/hosts/dispatch.d.ts +7 -0
  121. package/dist/lib/hosts/dispatch.js +13 -6
  122. package/dist/lib/identity/client.d.ts +3 -3
  123. package/dist/lib/identity/client.js +3 -3
  124. package/dist/lib/installations/index.d.ts +1 -1
  125. package/dist/lib/installations/index.js +1 -1
  126. package/dist/lib/installations/migrate.js +9 -0
  127. package/dist/lib/installations/resolve.js +1 -1
  128. package/dist/lib/installations/shims.d.ts +15 -0
  129. package/dist/lib/installations/shims.js +69 -0
  130. package/dist/lib/installations/store.d.ts +43 -0
  131. package/dist/lib/installations/store.js +75 -7
  132. package/dist/lib/installations/versions.js +1 -1
  133. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  134. package/dist/lib/menubar/install-menubar.js +3 -3
  135. package/dist/lib/native-accounts.d.ts +35 -0
  136. package/dist/lib/native-accounts.js +41 -0
  137. package/dist/lib/net-close.d.ts +11 -0
  138. package/dist/lib/net-close.js +25 -0
  139. package/dist/lib/openclaw-keychain.js +27 -1
  140. package/dist/lib/profiles.js +22 -9
  141. package/dist/lib/project-resources.js +1 -1
  142. package/dist/lib/reserved-stores.d.ts +53 -0
  143. package/dist/lib/reserved-stores.js +120 -0
  144. package/dist/lib/secrets-client.d.ts +191 -0
  145. package/dist/lib/secrets-client.js +710 -0
  146. package/dist/lib/secrets-policy.d.ts +222 -0
  147. package/dist/lib/secrets-policy.js +483 -0
  148. package/dist/lib/secrets-types.d.ts +188 -0
  149. package/dist/lib/secrets-types.js +24 -0
  150. package/dist/lib/service-manifest.js +3 -4
  151. package/dist/lib/session/db.d.ts +18 -0
  152. package/dist/lib/session/db.js +47 -14
  153. package/dist/lib/session/sync/config.js +2 -2
  154. package/dist/lib/sha256-asset.d.ts +11 -16
  155. package/dist/lib/sha256-asset.js +11 -16
  156. package/dist/lib/share/config.js +14 -5
  157. package/dist/lib/signin-badge.d.ts +26 -0
  158. package/dist/lib/signin-badge.js +42 -0
  159. package/dist/lib/staleness/detectors/workflows.js +3 -105
  160. package/dist/lib/staleness/writers/workflows.js +2 -1
  161. package/dist/lib/state.d.ts +7 -4
  162. package/dist/lib/state.js +18 -7
  163. package/dist/lib/sync-umbrella.js +2 -2
  164. package/dist/lib/teams/agents.js +1 -1
  165. package/dist/lib/types.d.ts +74 -33
  166. package/dist/lib/view-types.d.ts +6 -3
  167. package/dist/lib/workflows-registry.d.ts +71 -0
  168. package/dist/lib/workflows-registry.js +280 -0
  169. package/dist/lib/workflows.d.ts +21 -21
  170. package/dist/lib/workflows.js +12 -327
  171. package/package.json +2 -3
  172. package/scripts/postinstall.js +4 -29
  173. package/dist/commands/secrets-import.d.ts +0 -18
  174. package/dist/commands/secrets-import.js +0 -74
  175. package/dist/commands/secrets-migrate.d.ts +0 -25
  176. package/dist/commands/secrets-migrate.js +0 -334
  177. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  178. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  179. package/dist/commands/secrets-sync.d.ts +0 -11
  180. package/dist/commands/secrets-sync.js +0 -153
  181. package/dist/commands/secrets-vault.d.ts +0 -10
  182. package/dist/commands/secrets-vault.js +0 -130
  183. package/dist/commands/secrets.d.ts +0 -212
  184. package/dist/commands/secrets.js +0 -3030
  185. package/dist/lib/accounts/connect.d.ts +0 -176
  186. package/dist/lib/accounts/connect.js +0 -453
  187. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  188. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  189. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  190. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  191. package/dist/lib/secrets/agent.d.ts +0 -358
  192. package/dist/lib/secrets/agent.js +0 -1291
  193. package/dist/lib/secrets/audit.d.ts +0 -46
  194. package/dist/lib/secrets/audit.js +0 -101
  195. package/dist/lib/secrets/bundles.d.ts +0 -290
  196. package/dist/lib/secrets/bundles.js +0 -1547
  197. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  198. package/dist/lib/secrets/download-keychain.js +0 -70
  199. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  200. package/dist/lib/secrets/drivers/rush.js +0 -90
  201. package/dist/lib/secrets/fallback.d.ts +0 -48
  202. package/dist/lib/secrets/fallback.js +0 -48
  203. package/dist/lib/secrets/filestore.d.ts +0 -222
  204. package/dist/lib/secrets/filestore.js +0 -1099
  205. package/dist/lib/secrets/headless.d.ts +0 -43
  206. package/dist/lib/secrets/headless.js +0 -56
  207. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  208. package/dist/lib/secrets/icloud-import.js +0 -206
  209. package/dist/lib/secrets/index.d.ts +0 -451
  210. package/dist/lib/secrets/index.js +0 -1568
  211. package/dist/lib/secrets/install-helper.d.ts +0 -72
  212. package/dist/lib/secrets/install-helper.js +0 -245
  213. package/dist/lib/secrets/lease.d.ts +0 -25
  214. package/dist/lib/secrets/lease.js +0 -44
  215. package/dist/lib/secrets/linux.d.ts +0 -77
  216. package/dist/lib/secrets/linux.js +0 -393
  217. package/dist/lib/secrets/list-filter.d.ts +0 -109
  218. package/dist/lib/secrets/list-filter.js +0 -261
  219. package/dist/lib/secrets/mcp.d.ts +0 -93
  220. package/dist/lib/secrets/mcp.js +0 -211
  221. package/dist/lib/secrets/profiles.d.ts +0 -10
  222. package/dist/lib/secrets/profiles.js +0 -13
  223. package/dist/lib/secrets/push.d.ts +0 -133
  224. package/dist/lib/secrets/push.js +0 -273
  225. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  226. package/dist/lib/secrets/rc-hygiene.js +0 -143
  227. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  228. package/dist/lib/secrets/read-backoff.js +0 -64
  229. package/dist/lib/secrets/reaper.d.ts +0 -90
  230. package/dist/lib/secrets/reaper.js +0 -243
  231. package/dist/lib/secrets/remote.d.ts +0 -152
  232. package/dist/lib/secrets/remote.js +0 -344
  233. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  234. package/dist/lib/secrets/reserved-sync.js +0 -147
  235. package/dist/lib/secrets/scope.d.ts +0 -26
  236. package/dist/lib/secrets/scope.js +0 -29
  237. package/dist/lib/secrets/session-store.d.ts +0 -107
  238. package/dist/lib/secrets/session-store.js +0 -342
  239. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  240. package/dist/lib/secrets/sync-backend.js +0 -13
  241. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  242. package/dist/lib/secrets/sync-commands.js +0 -21
  243. package/dist/lib/secrets/sync.d.ts +0 -48
  244. package/dist/lib/secrets/sync.js +0 -237
  245. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  246. package/dist/lib/secrets/unlock-hints.js +0 -36
  247. package/dist/lib/secrets/usage-db.d.ts +0 -46
  248. package/dist/lib/secrets/usage-db.js +0 -96
  249. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  250. package/dist/lib/secrets/vault-age-helper.js +0 -34
  251. package/dist/lib/secrets/vault.d.ts +0 -49
  252. package/dist/lib/secrets/vault.js +0 -399
  253. package/dist/lib/secrets/windows.d.ts +0 -81
  254. package/dist/lib/secrets/windows.js +0 -556
  255. package/scripts/install-helper.js +0 -97
  256. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  257. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Wire-shared types for the standalone `secrets` CLI (PHNX-3989).
3
+ *
4
+ * The standalone engine (`@phnx-labs/secrets-cli`, extracted from what used to
5
+ * be `cli/src/lib/secrets/**`) owns every one of these shapes; this module only
6
+ * re-declares them so `secrets-client.ts` and its consumers keep exact static
7
+ * types across the process-client boundary with no runtime dependency on the
8
+ * (now deleted) in-repo engine. Keep byte-for-byte in sync with
9
+ * `secrets-cli/src/schema.ts` / `src/core/bundles.ts` — this is the one seam
10
+ * both sides legitimately re-declare rather than share a package for (see the
11
+ * `secrets-client.ts` docblock).
12
+ */
13
+ // --- bundle document ---------------------------------------------------
14
+ export const SECRET_TYPES = [
15
+ 'api-key',
16
+ 'token',
17
+ 'password',
18
+ 'url',
19
+ 'database-url',
20
+ 'ssh-key',
21
+ 'certificate',
22
+ 'webhook',
23
+ 'note',
24
+ ];
@@ -27,10 +27,9 @@
27
27
  * module is the single place both rules live, so a new manifest has one obvious
28
28
  * thing to call.
29
29
  *
30
- * It is not yet the only consumer of the identifier rule: `lib/secrets/agent.ts`
31
- * keeps a literal `SERVICE_LABEL` for its `bootout`. That path is retire-only —
32
- * it detects and removes a plist an older version installed and generates none
33
- * of its own — so it has no HOME to bake and was deliberately left alone.
30
+ * A third consumer that used to keep its own literal `SERVICE_LABEL` for a
31
+ * retire-only `bootout` — the in-repo secrets broker's launchd cleanup — moved
32
+ * out of this repo entirely with the standalone `secrets` engine (PHNX-3989).
34
33
  */
35
34
  import * as crypto from 'crypto';
36
35
  import * as os from 'os';
@@ -817,6 +817,22 @@ export declare function buildFtsQuery(input: string): {
817
817
  * so "higher = better" is consistent across all tiers.
818
818
  */
819
819
  export declare function ftsSearch(input: string, limit?: number): FtsHit[];
820
+ /**
821
+ * Rewrite file_path for sessions whose path starts with any of `moves.from`,
822
+ * replacing that prefix with `moves.to` and clearing the matching scan_ledger
823
+ * entries so the next scan re-indexes from the new location.
824
+ *
825
+ * One transaction for the whole batch (PHNX-3940 T7): a migration that moves
826
+ * several homes into slots and several version dirs into trash must not leave
827
+ * the index half-rewritten. Longest `from` wins when prefixes nest, and each
828
+ * session row is rewritten at most once.
829
+ *
830
+ * Returns the number of session rows updated.
831
+ */
832
+ export declare function reindexMovedSessionPaths(moves: ReadonlyArray<{
833
+ from: string;
834
+ to: string;
835
+ }>): number;
820
836
  /**
821
837
  * Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
822
838
  * it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
@@ -827,6 +843,8 @@ export declare function ftsSearch(input: string, limit?: number): FtsHit[];
827
843
  * Returns the number of session rows updated.
828
844
  */
829
845
  export declare function updateSessionFilePaths(oldPrefix: string, newPrefix: string): number;
846
+ /** Count indexed sessions whose transcript path sits under `prefix`. */
847
+ export declare function countSessionsWithFilePrefix(prefix: string): number;
830
848
  /** Per-kind capture tallies for a browser task. Counts only -- never the bytes. */
831
849
  interface BrowserCaptureCounts {
832
850
  screenshot: number;
@@ -4317,6 +4317,44 @@ export function ftsSearch(input, limit = 200) {
4317
4317
  hits.sort((a, b) => b.score - a.score);
4318
4318
  return hits.slice(0, limit);
4319
4319
  }
4320
+ /**
4321
+ * Rewrite file_path for sessions whose path starts with any of `moves.from`,
4322
+ * replacing that prefix with `moves.to` and clearing the matching scan_ledger
4323
+ * entries so the next scan re-indexes from the new location.
4324
+ *
4325
+ * One transaction for the whole batch (PHNX-3940 T7): a migration that moves
4326
+ * several homes into slots and several version dirs into trash must not leave
4327
+ * the index half-rewritten. Longest `from` wins when prefixes nest, and each
4328
+ * session row is rewritten at most once.
4329
+ *
4330
+ * Returns the number of session rows updated.
4331
+ */
4332
+ export function reindexMovedSessionPaths(moves) {
4333
+ const usable = moves.filter((m) => m.from && m.from !== m.to);
4334
+ if (usable.length === 0)
4335
+ return 0;
4336
+ const db = getDB();
4337
+ const sorted = [...usable].sort((a, b) => b.from.length - a.from.length);
4338
+ return db.transaction(() => {
4339
+ let n = 0;
4340
+ const seen = new Set();
4341
+ const update = db.prepare(`UPDATE sessions SET file_path = ? WHERE id = ?`);
4342
+ const dropLedger = db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`);
4343
+ const select = db.prepare(`SELECT id, file_path FROM sessions WHERE file_path LIKE ?`);
4344
+ for (const { from, to } of sorted) {
4345
+ const rows = select.all(from + '%');
4346
+ for (const { id, file_path } of rows) {
4347
+ if (seen.has(id))
4348
+ continue;
4349
+ seen.add(id);
4350
+ update.run(to + file_path.slice(from.length), id);
4351
+ dropLedger.run(canonicalLedgerKey(file_path));
4352
+ n++;
4353
+ }
4354
+ }
4355
+ return n;
4356
+ })();
4357
+ }
4320
4358
  /**
4321
4359
  * Rewrite file_path for all sessions whose path starts with oldPrefix, replacing
4322
4360
  * it with newPrefix + the unchanged suffix. Also clears the matching scan_ledger
@@ -4327,21 +4365,16 @@ export function ftsSearch(input, limit = 200) {
4327
4365
  * Returns the number of session rows updated.
4328
4366
  */
4329
4367
  export function updateSessionFilePaths(oldPrefix, newPrefix) {
4330
- const db = getDB();
4331
- const rows = db
4332
- .prepare(`SELECT id, file_path FROM sessions WHERE file_path LIKE ?`)
4333
- .all(oldPrefix + '%');
4334
- if (rows.length === 0)
4368
+ return reindexMovedSessionPaths([{ from: oldPrefix, to: newPrefix }]);
4369
+ }
4370
+ /** Count indexed sessions whose transcript path sits under `prefix`. */
4371
+ export function countSessionsWithFilePrefix(prefix) {
4372
+ if (!prefix)
4335
4373
  return 0;
4336
- const txn = db.transaction(() => {
4337
- for (const { id, file_path } of rows) {
4338
- const newPath = newPrefix + file_path.slice(oldPrefix.length);
4339
- db.prepare(`UPDATE sessions SET file_path = ? WHERE id = ?`).run(newPath, id);
4340
- db.prepare(`DELETE FROM scan_ledger WHERE file_path = ?`).run(canonicalLedgerKey(file_path));
4341
- }
4342
- });
4343
- txn();
4344
- return rows.length;
4374
+ const row = getDB()
4375
+ .prepare(`SELECT COUNT(*) AS c FROM sessions WHERE file_path LIKE ?`)
4376
+ .get(prefix + '%');
4377
+ return row.c;
4345
4378
  }
4346
4379
  /**
4347
4380
  * Upsert one browser task's durable metadata.
@@ -11,7 +11,7 @@
11
11
  * background sync. Credentials come from the `r2.backups` secrets bundle (OS
12
12
  * keychain on macOS, libsecret on Linux) — never from env or disk.
13
13
  */
14
- import { readAndResolveBundleEnv } from '../../secrets/bundles.js';
14
+ import { readAndResolveBundleEnvSync } from '../../secrets-client.js';
15
15
  /** Secrets bundle holding the R2 credentials. */
16
16
  export const SYNC_BUNDLE = 'r2.backups';
17
17
  /**
@@ -30,7 +30,7 @@ function resolveR2Config() {
30
30
  // and degrades to no-transport (sync disabled) with no prompt and no crash —
31
31
  // unlock once (`agents secrets unlock r2.backups`) or set it no-ACL
32
32
  // (`agents secrets policy r2.backups never`) for silent zero-friction sync.
33
- const { env } = readAndResolveBundleEnv(SYNC_BUNDLE, { caller: 'session-transport', agentOnly: true });
33
+ const { env } = readAndResolveBundleEnvSync(SYNC_BUNDLE, { caller: 'session-transport', agentOnly: true });
34
34
  const accountId = env.R2_ACCOUNT_ID?.trim();
35
35
  const bucket = env.R2_BUCKET_NAME?.trim();
36
36
  const accessKeyId = env.R2_ACCESS_KEY_ID?.trim();
@@ -3,22 +3,17 @@
3
3
  *
4
4
  * These live in their own LEAF module — importing only `node:crypto` and
5
5
  * `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
6
- * own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
- * `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
8
- * pure functions, and importing them from there closed a module-initialization
9
- * cycle:
10
- *
11
- * helper-download.ts
12
- * -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
13
- * -> browser/drivers/ssh.ts -> browser/chrome.ts
14
- * -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
15
- * -> secrets/download-keychain.ts
16
- * -> helper-download.ts (still evaluating; const not yet bound)
17
- *
18
- * which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
19
- * initialization` at `secrets/download-keychain.ts:45` for any entry point that
20
- * reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
21
- * a local import here can reintroduce that cycle.
6
+ * own import graph reached `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
+ * the in-repo secrets engine's own keychain-helper downloader, which imported
8
+ * back into `helper-download.ts` while it was still evaluating — before
9
+ * `EXPECTED_TEAM_ID` was bound, throwing `ReferenceError: Cannot access
10
+ * 'EXPECTED_TEAM_ID' before initialization` for any entry point that reached
11
+ * `helper-download.ts` first (RUSH-3113). The secrets engine that closed that
12
+ * cycle is gone now (PHNX-3989 — the standalone `secrets` CLI downloads its own
13
+ * helper), but the discipline that fixed it still holds: keep this module a
14
+ * leaf, since `helper-download.ts` needs nothing beyond these two pure
15
+ * functions and adding a local import here can reintroduce a cycle with
16
+ * whatever imports it next.
22
17
  */
23
18
  /** Pull the digest out of a `<sha256> <filename>` release asset. */
24
19
  export declare function parseSha256Asset(text: string): string;
@@ -3,22 +3,17 @@
3
3
  *
4
4
  * These live in their own LEAF module — importing only `node:crypto` and
5
5
  * `node:fs` — on purpose. They used to sit in `computer/ssh-tunnel.ts`, whose
6
- * own import graph reaches `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
- * `secrets/*`. `helper-download.ts` needs nothing from that graph but these two
8
- * pure functions, and importing them from there closed a module-initialization
9
- * cycle:
10
- *
11
- * helper-download.ts
12
- * -> computer/ssh-tunnel.ts (evaluated BEFORE `EXPECTED_TEAM_ID`)
13
- * -> browser/drivers/ssh.ts -> browser/chrome.ts
14
- * -> secrets/bundles.ts -> secrets/index.ts -> secrets/install-helper.ts
15
- * -> secrets/download-keychain.ts
16
- * -> helper-download.ts (still evaluating; const not yet bound)
17
- *
18
- * which threw `ReferenceError: Cannot access 'EXPECTED_TEAM_ID' before
19
- * initialization` at `secrets/download-keychain.ts:45` for any entry point that
20
- * reached `helper-download.ts` first (RUSH-3113). Keep this module a leaf: adding
21
- * a local import here can reintroduce that cycle.
6
+ * own import graph reached `browser/drivers/ssh.ts` -> `browser/chrome.ts` ->
7
+ * the in-repo secrets engine's own keychain-helper downloader, which imported
8
+ * back into `helper-download.ts` while it was still evaluating — before
9
+ * `EXPECTED_TEAM_ID` was bound, throwing `ReferenceError: Cannot access
10
+ * 'EXPECTED_TEAM_ID' before initialization` for any entry point that reached
11
+ * `helper-download.ts` first (RUSH-3113). The secrets engine that closed that
12
+ * cycle is gone now (PHNX-3989 — the standalone `secrets` CLI downloads its own
13
+ * helper), but the discipline that fixed it still holds: keep this module a
14
+ * leaf, since `helper-download.ts` needs nothing beyond these two pure
15
+ * functions and adding a local import here can reintroduce a cycle with
16
+ * whatever imports it next.
22
17
  */
23
18
  import { createHash } from 'node:crypto';
24
19
  import * as fs from 'node:fs';
@@ -9,8 +9,7 @@
9
9
  // `cloudflare` bundle.
10
10
  import { randomBytes } from 'node:crypto';
11
11
  import { readMeta, updateMeta } from '../state.js';
12
- import { bundleExists, bundleItemStore, bundlePolicy, keychainRef, readAndResolveBundleEnv, readBundle, writeBundle, } from '../secrets/bundles.js';
13
- import { secretsKeychainItem } from '../secrets/index.js';
12
+ import { bundleExistsSync as bundleExists, keychainRef, readAndResolveBundleEnvSync as readAndResolveBundleEnv, readBundleSync as readBundle, secretsKeychainItem, writeBundleWithItemsSync as writeBundleWithItems, } from '../secrets-client.js';
14
13
  export const SHARE_BUNDLE = 'share';
15
14
  export const SHARE_TOKEN_KEY = 'WRITE_TOKEN';
16
15
  export const SHARE_TOKEN_ENV_KEY = 'SHARE_WRITE_TOKEN';
@@ -113,10 +112,12 @@ export function storeWriteToken(token) {
113
112
  vars: {},
114
113
  };
115
114
  }
116
- const store = bundleItemStore(bundle.backend, { noAcl: bundlePolicy(bundle) === 'never' });
117
- store.set(secretsKeychainItem(bundle.name, SHARE_TOKEN_KEY), token);
115
+ // writeBundleWithItems decides the ACL (keychain backend) purely from
116
+ // `bundle.policy === 'never'` — the fresh-bundle case above sets it
117
+ // explicitly, and an existing bundle already carries its own explicit tier.
118
118
  bundle.vars[SHARE_TOKEN_KEY] = keychainRef(SHARE_TOKEN_KEY);
119
- writeBundle(bundle);
119
+ const item = secretsKeychainItem(bundle.name, SHARE_TOKEN_KEY);
120
+ writeBundleWithItems(bundle, new Map([[item, token]]));
120
121
  }
121
122
  /** Read the raw write token from the `share` secrets bundle. */
122
123
  export function readWriteTokenFromBundle() {
@@ -178,6 +179,14 @@ export function readCloudflareCreds(bundle = DEFAULT_CF_BUNDLE, override) {
178
179
  if (override?.apiToken) {
179
180
  return { apiToken: override.apiToken, accountId: override.accountId ?? '' };
180
181
  }
182
+ // Check existence first (same guard as readShareToken above): resolving a
183
+ // missing bundle through the process client surfaces an opaque transport code
184
+ // (e.g. LOCKED), never naming the bundle the user typed — so name it here.
185
+ if (!bundleExists(bundle)) {
186
+ throw new Error(`The '${bundle}' bundle does not exist. ` +
187
+ `Pass credentials directly with --token <t> [--account <id>], or create it: ` +
188
+ `agents secrets add ${bundle} CLOUDFLARE_API_TOKEN`);
189
+ }
181
190
  const { env } = readAndResolveBundleEnv(bundle, {
182
191
  caller: 'share',
183
192
  // Setup is still a read; only `agents secrets unlock` may authenticate.
@@ -1,5 +1,8 @@
1
1
  import type { AccountInfo } from './agents.js';
2
+ import type { AuthVerdict } from './auth-health.js';
2
3
  import type { AgentId } from './types.js';
4
+ export type AccountVerdict = Exclude<AuthVerdict, 'unconfigured' | 'error'> | 'missing' | 'per-device' | 'ready';
5
+ export type AccountProvisioning = 'portable' | 'per-device';
3
6
  /**
4
7
  * The exact command that logs a given agent in — for warn banners and nudges.
5
8
  * Driven off the registry `cliCommand` with the per-agent subcommand overrides
@@ -8,6 +11,29 @@ import type { AgentId } from './types.js';
8
11
  * remaining agents (kimi, gemini, …) start their device/oauth flow on launch.
9
12
  */
10
13
  export declare function loginHint(agentId: AgentId): string;
14
+ /**
15
+ * Exact action shown beside a non-live account. Every emitted command exists
16
+ * today — never a planned surface and never a hidden verb:
17
+ * - Per-device harnesses repair per box via `agents devices login`.
18
+ * - Named accounts re-auth through `agents accounts login <harness>#<name>`.
19
+ * A known account with no slot on a headed device is onboarded with
20
+ * `agents accounts add <harness> <name>`. A worker never runs an
21
+ * interactive login — the hint is `add.ts`'s worker refusal (add on the
22
+ * personal device; this box is provisioned from the durable credential).
23
+ * - Unnamed legacy homes use the same version-targeted command shape as
24
+ * doctor, so the hint never logs a different/default home in by accident.
25
+ * - `unverified` emits nothing: the probe could not confirm state (e.g. a
26
+ * worker whose token lacks the usage scope), so there is nothing to repair.
27
+ */
28
+ export declare function fixFor(input: {
29
+ agent: AgentId;
30
+ verdict: AccountVerdict;
31
+ name?: string | null;
32
+ version?: string | null;
33
+ provisioning?: AccountProvisioning;
34
+ /** When false, this named account has no slot on this device yet. */
35
+ hasSlot?: boolean;
36
+ }): string | null;
11
37
  /**
12
38
  * Whether `agents run` should probe login state before launching. True only for
13
39
  * a launch that actually opens the interactive TUI — where discovering a logged-out
@@ -9,7 +9,9 @@
9
9
  * on it (the run preflight) WARN and continue; they never block.
10
10
  */
11
11
  import chalk from 'chalk';
12
+ import { addWorkerRefusal } from './accounts/add.js';
12
13
  import { AGENTS } from './agents.js';
14
+ import { CONFIG_ENV_ISOLATED_AGENTS } from './installations/shims.js';
13
15
  /**
14
16
  * The exact command that logs a given agent in — for warn banners and nudges.
15
17
  * Driven off the registry `cliCommand` with the per-agent subcommand overrides
@@ -34,6 +36,46 @@ export function loginHint(agentId) {
34
36
  return cli;
35
37
  }
36
38
  }
39
+ /**
40
+ * Exact action shown beside a non-live account. Every emitted command exists
41
+ * today — never a planned surface and never a hidden verb:
42
+ * - Per-device harnesses repair per box via `agents devices login`.
43
+ * - Named accounts re-auth through `agents accounts login <harness>#<name>`.
44
+ * A known account with no slot on a headed device is onboarded with
45
+ * `agents accounts add <harness> <name>`. A worker never runs an
46
+ * interactive login — the hint is `add.ts`'s worker refusal (add on the
47
+ * personal device; this box is provisioned from the durable credential).
48
+ * - Unnamed legacy homes use the same version-targeted command shape as
49
+ * doctor, so the hint never logs a different/default home in by accident.
50
+ * - `unverified` emits nothing: the probe could not confirm state (e.g. a
51
+ * worker whose token lacks the usage scope), so there is nothing to repair.
52
+ */
53
+ export function fixFor(input) {
54
+ const { agent, verdict } = input;
55
+ if (verdict === 'live' || verdict === 'rate_limited' || verdict === 'unverified' || verdict === 'ready')
56
+ return null;
57
+ if (input.provisioning === 'per-device' || verdict === 'per-device') {
58
+ return `agents devices login --agents ${agent}`;
59
+ }
60
+ if (input.name) {
61
+ const worker = addWorkerRefusal(agent, input.name);
62
+ if (worker)
63
+ return worker;
64
+ if (input.hasSlot === false)
65
+ return `agents accounts add ${agent} ${input.name}`;
66
+ return `agents accounts login ${agent}#${input.name}`;
67
+ }
68
+ const version = input.version ?? null;
69
+ if (!version || !CONFIG_ENV_ISOLATED_AGENTS.includes(agent))
70
+ return loginHint(agent);
71
+ if (agent === 'claude')
72
+ return `agents run ${agent}@${version}, then /login`;
73
+ if (agent === 'codex' || agent === 'grok')
74
+ return `agents run ${agent}@${version} -- login`;
75
+ if (agent === 'opencode')
76
+ return `agents run ${agent}@${version} -- auth login`;
77
+ return `agents run ${agent}@${version}`;
78
+ }
37
79
  /**
38
80
  * Whether `agents run` should probe login state before launching. True only for
39
81
  * a launch that actually opens the interactive TUI — where discovering a logged-out
@@ -1,115 +1,13 @@
1
- /**
2
- * Workflows detector — scans the agent-specific on-disk layout for synced
3
- * workflow names (Claude WORKFLOW.md trees, Kimi flow skills, Antigravity
4
- * global_workflows, Goose recipes, OpenClaw Lobster, Grok Rhai).
5
- */
6
- import * as fs from 'fs';
7
- import * as os from 'os';
8
- import * as path from 'path';
9
- import * as yaml from 'yaml';
10
1
  import { capableAgents } from '../../capabilities.js';
11
- import { grokWorkflowMarker } from '../../workflows.js';
2
+ import { workflowTarget } from '../../workflows-registry.js';
12
3
  import { lazyAgentMap } from '../writers/lazy-map.js';
13
4
  function buildWorkflowsDetector(agent) {
14
5
  return {
15
6
  kind: 'workflows',
16
7
  agent,
17
8
  list({ versionHome }) {
18
- if (agent === 'kimi') {
19
- const skillsDir = path.join(versionHome, '.kimi-code', 'skills');
20
- if (!fs.existsSync(skillsDir))
21
- return [];
22
- return fs.readdirSync(skillsDir, { withFileTypes: true })
23
- .filter(d => d.isDirectory() && fs.existsSync(path.join(skillsDir, d.name, 'SKILL.md')))
24
- .filter(d => {
25
- try {
26
- const skill = fs.readFileSync(path.join(skillsDir, d.name, 'SKILL.md'), 'utf-8');
27
- const lines = skill.split('\n');
28
- if (lines[0] !== '---')
29
- return false;
30
- const endIndex = lines.slice(1).findIndex(l => l === '---');
31
- if (endIndex < 0)
32
- return false;
33
- const parsed = yaml.parse(lines.slice(1, endIndex + 1).join('\n'));
34
- return parsed?.type === 'flow' && parsed.agents_workflow === d.name;
35
- }
36
- catch {
37
- return false;
38
- }
39
- })
40
- .map(d => d.name);
41
- }
42
- if (agent === 'antigravity') {
43
- // Antigravity user workflows are HOME-global and shared across versions,
44
- // not version-isolated — see workflows.ts:antigravityWorkflowsDir(). agy
45
- // scans the real ~/.gemini/config/global_workflows/, so the detector reads
46
- // the same shared dir the writer targets (versionHome is intentionally unused).
47
- const dir = path.join(process.env.HOME ?? os.homedir(), '.gemini', 'config', 'global_workflows');
48
- if (!fs.existsSync(dir))
49
- return [];
50
- return fs.readdirSync(dir, { withFileTypes: true })
51
- .filter(d => d.isFile() && d.name.endsWith('.md') && !d.name.startsWith('.'))
52
- .filter(d => {
53
- try {
54
- const content = fs.readFileSync(path.join(dir, d.name), 'utf-8');
55
- const lines = content.split('\n');
56
- if (lines[0] !== '---')
57
- return false;
58
- const endIndex = lines.slice(1).findIndex(l => l === '---');
59
- if (endIndex < 0)
60
- return false;
61
- const parsed = yaml.parse(lines.slice(1, endIndex + 1).join('\n'));
62
- return parsed?.agents_workflow === d.name.slice(0, -'.md'.length);
63
- }
64
- catch {
65
- return false;
66
- }
67
- })
68
- .map(d => d.name.slice(0, -'.md'.length));
69
- }
70
- if (agent === 'goose') {
71
- const recipesDir = path.join(versionHome, '.config', 'goose', 'recipes');
72
- if (!fs.existsSync(recipesDir))
73
- return [];
74
- return fs.readdirSync(recipesDir, { withFileTypes: true })
75
- .filter(d => d.isFile() && d.name.endsWith('.yaml') && !d.name.startsWith('.'))
76
- .map(d => d.name.slice(0, -'.yaml'.length));
77
- }
78
- if (agent === 'openclaw') {
79
- const dir = path.join(versionHome, '.openclaw', 'workflows');
80
- if (!fs.existsSync(dir))
81
- return [];
82
- return fs.readdirSync(dir, { withFileTypes: true })
83
- .filter(d => d.isFile() && d.name.endsWith('.lobster') && !d.name.startsWith('.'))
84
- .filter(d => {
85
- try {
86
- const parsed = yaml.parse(fs.readFileSync(path.join(dir, d.name), 'utf-8'));
87
- const env = parsed && typeof parsed === 'object' && parsed.env && typeof parsed.env === 'object' && !Array.isArray(parsed.env)
88
- ? parsed.env
89
- : {};
90
- return env.AGENTS_CLI_WORKFLOW === d.name.slice(0, -'.lobster'.length);
91
- }
92
- catch {
93
- return false;
94
- }
95
- })
96
- .map(d => d.name.slice(0, -'.lobster'.length));
97
- }
98
- if (agent === 'grok') {
99
- const workflowsDir = path.join(versionHome, '.grok', 'workflows');
100
- if (!fs.existsSync(workflowsDir))
101
- return [];
102
- return fs.readdirSync(workflowsDir, { withFileTypes: true })
103
- .filter(d => d.isFile() && d.name.endsWith('.rhai') && !d.name.startsWith('.'))
104
- .filter(d => grokWorkflowMarker(path.join(workflowsDir, d.name)) === d.name.slice(0, -'.rhai'.length))
105
- .map(d => d.name.slice(0, -'.rhai'.length));
106
- }
107
- const workflowsDir = path.join(versionHome, 'workflows');
108
- if (!fs.existsSync(workflowsDir))
109
- return [];
110
- return fs.readdirSync(workflowsDir, { withFileTypes: true })
111
- .filter(d => d.isDirectory() && fs.existsSync(path.join(workflowsDir, d.name, 'WORKFLOW.md')))
112
- .map(d => d.name);
9
+ const target = workflowTarget(agent);
10
+ return target.names(target.dir(versionHome));
113
11
  },
114
12
  };
115
13
  }
@@ -1,5 +1,6 @@
1
1
  import { capableAgents } from '../../capabilities.js';
2
- import { listInstalledWorkflows, syncWorkflowToVersion } from '../../workflows.js';
2
+ import { listInstalledWorkflows } from '../../workflows.js';
3
+ import { syncWorkflowToVersion } from '../../workflows-registry.js';
3
4
  import { lazyAgentMap } from './lazy-map.js';
4
5
  function buildWorkflowsWriter(agent) {
5
6
  return {
@@ -123,10 +123,13 @@ export declare function getUserSecretsDir(): string;
123
123
  * AGENTS_SECRETS_DB without racing the module-load capture of USER_SECRETS_DIR —
124
124
  * mirrors the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR escape hatches. Holds only
125
125
  * value-free usage telemetry (which bundle was created/imported/exported/viewed/
126
- * accessed/unlocked, when, by whom), never a secret value. It is a derived index
127
- * fed FROM the emitSecretAudit chokepoint alongside the append-only
128
- * ~/.agents/.history/events/YYYY-MM-DD audit log — the same way sessions.db indexes session
129
- * metadata off the real session flow — not a second write path.
126
+ * accessed/unlocked, when, by whom), never a secret value. It used to be a
127
+ * derived index fed FROM the in-repo secrets engine's own emitSecretAudit
128
+ * chokepoint — not a second write path — the same way sessions.db indexes
129
+ * session metadata off the real session flow. That engine (and its audit
130
+ * emission) moved out of this repo entirely with the standalone `secrets`
131
+ * engine (PHNX-3989), so nothing writes this DB from agents-cli today; the
132
+ * read-side queries in `analytics/usage-db.ts` have no current caller.
130
133
  */
131
134
  export declare function getSecretsDbPath(): string;
132
135
  /**
package/dist/lib/state.js CHANGED
@@ -396,10 +396,13 @@ export function getUserSecretsDir() { return USER_SECRETS_DIR; }
396
396
  * AGENTS_SECRETS_DB without racing the module-load capture of USER_SECRETS_DIR —
397
397
  * mirrors the AGENTS_EVENTS_PATH / AGENTS_DEVICES_DIR escape hatches. Holds only
398
398
  * value-free usage telemetry (which bundle was created/imported/exported/viewed/
399
- * accessed/unlocked, when, by whom), never a secret value. It is a derived index
400
- * fed FROM the emitSecretAudit chokepoint alongside the append-only
401
- * ~/.agents/.history/events/YYYY-MM-DD audit log — the same way sessions.db indexes session
402
- * metadata off the real session flow — not a second write path.
399
+ * accessed/unlocked, when, by whom), never a secret value. It used to be a
400
+ * derived index fed FROM the in-repo secrets engine's own emitSecretAudit
401
+ * chokepoint — not a second write path — the same way sessions.db indexes
402
+ * session metadata off the real session flow. That engine (and its audit
403
+ * emission) moved out of this repo entirely with the standalone `secrets`
404
+ * engine (PHNX-3989), so nothing writes this DB from agents-cli today; the
405
+ * read-side queries in `analytics/usage-db.ts` have no current caller.
403
406
  */
404
407
  export function getSecretsDbPath() {
405
408
  return process.env.AGENTS_SECRETS_DB ?? path.join(USER_SECRETS_DIR, 'secrets.db');
@@ -1332,7 +1335,9 @@ export function writeMetaUnlocked(meta) {
1332
1335
  // In-flight connect attempts (PHNX-3940), device-scoped like homes.
1333
1336
  const accountsPending = deviceAccounts?.pendingConnects && Object.keys(deviceAccounts.pendingConnects).length > 0
1334
1337
  ? deviceAccounts.pendingConnects : undefined;
1335
- if (accountsNative || accountsBindings || accountsHomes || accountsPending) {
1338
+ const accountsSlots = deviceAccounts?.slots && Object.keys(deviceAccounts.slots).length > 0
1339
+ ? deviceAccounts.slots : undefined;
1340
+ if (accountsNative || accountsBindings || accountsHomes || accountsPending || accountsSlots) {
1336
1341
  const da = {};
1337
1342
  if (accountsNative)
1338
1343
  da.native = accountsNative;
@@ -1342,6 +1347,8 @@ export function writeMetaUnlocked(meta) {
1342
1347
  da.homes = accountsHomes;
1343
1348
  if (accountsPending)
1344
1349
  da.pendingConnects = accountsPending;
1350
+ if (accountsSlots)
1351
+ da.slots = accountsSlots;
1345
1352
  doc.accounts = da;
1346
1353
  }
1347
1354
  else if (writesDeviceAccounts)
@@ -1487,12 +1494,16 @@ function overlayMachineLocal(meta) {
1487
1494
  if (acc.pendingConnects !== undefined && !isMap(acc.pendingConnects)) {
1488
1495
  throw new Error(`Device config corrupted at ${devicePath}: accounts.pendingConnects must be a map.`);
1489
1496
  }
1497
+ if (acc.slots !== undefined && !isMap(acc.slots)) {
1498
+ throw new Error(`Device config corrupted at ${devicePath}: accounts.slots must be a map.`);
1499
+ }
1490
1500
  const native = acc.native;
1491
1501
  const bindings = acc.bindings;
1492
1502
  const homes = acc.homes;
1493
1503
  const pendingConnects = acc.pendingConnects;
1494
- if (native || bindings || homes || pendingConnects)
1495
- meta.deviceAccounts = { ...(native ? { native } : {}), ...(bindings ? { bindings } : {}), ...(homes ? { homes } : {}), ...(pendingConnects ? { pendingConnects } : {}) };
1504
+ const slots = acc.slots;
1505
+ if (native || bindings || homes || pendingConnects || slots)
1506
+ meta.deviceAccounts = { ...(native ? { native } : {}), ...(bindings ? { bindings } : {}), ...(homes ? { homes } : {}), ...(pendingConnects ? { pendingConnects } : {}), ...(slots ? { slots } : {}) };
1496
1507
  }
1497
1508
  if (Object.prototype.hasOwnProperty.call(dm, 'routines')) {
1498
1509
  if (!Array.isArray(dm.routines) || dm.routines.some((name) => typeof name !== 'string')) {
@@ -17,8 +17,8 @@
17
17
  */
18
18
  import { pullRepo, adoptUserRepoIfNeeded } from './git.js';
19
19
  import { getUserAgentsDir, getEnabledExtraRepos } from './state.js';
20
- import { listRemoteBundles, pullBundle } from './secrets/sync.js';
21
- import { SYNC_PASSPHRASE_ENV } from './secrets/sync-passphrase.js';
20
+ import { listRemoteBundles, pullBundle } from './secrets-client.js';
21
+ import { SYNC_PASSPHRASE_ENV } from './sync-passphrase.js';
22
22
  /**
23
23
  * Decide which stages run. Pure — no I/O. Semantics:
24
24
  * bare (no flags) fetch repos, then reconcile
@@ -21,7 +21,7 @@ import { debug } from './debug.js';
21
21
  import { getAgentsDir as getSystemAgentsDir, getShimsDir } from '../state.js';
22
22
  import { AGENTS, getAccountInfo } from '../agents.js';
23
23
  import { resolveVersion, isVersionInstalled, verifyInstalledBinaryLaunches } from '../installations/versions.js';
24
- import { sanitizeProcessEnv } from '../secrets/bundles.js';
24
+ import { sanitizeProcessEnv } from '../secrets-client.js';
25
25
  import { resolveActor, actorEnv } from '../actor.js';
26
26
  import { recordRunName } from '../session/run-names.js';
27
27
  import { sshExec, shellQuote } from '../ssh-exec.js';