@ouro.bot/cli 0.1.0-alpha.92 → 0.1.0-alpha.920

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 (716) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -23
  3. package/RepairGuide.ouro/agent.json +5 -0
  4. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  5. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  6. package/RepairGuide.ouro/skills/diagnose-broken-remote.md +63 -0
  7. package/RepairGuide.ouro/skills/diagnose-stacked-typed-issues.md +35 -0
  8. package/RepairGuide.ouro/skills/diagnose-sync-blocked.md +54 -0
  9. package/RepairGuide.ouro/skills/diagnose-vault-expired.md +60 -0
  10. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +4 -2
  11. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +2 -2
  12. package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +1 -1
  13. package/assets/bluebubbles-host +264 -0
  14. package/assets/sanctuary-host-launcher.sh +16 -0
  15. package/changelog.json +5819 -16
  16. package/deploy/unraid/Dockerfile +58 -0
  17. package/deploy/unraid/README.txt +2997 -0
  18. package/deploy/unraid/audit-container-spec.sh +4 -0
  19. package/deploy/unraid/container-runtime.json +1 -0
  20. package/deploy/unraid/docker-man-template-transaction.mjs +711 -0
  21. package/deploy/unraid/docker-man-template-xml.cjs +105 -0
  22. package/deploy/unraid/migrate-sanctuary-bundle.mjs +60 -0
  23. package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
  24. package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
  25. package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
  26. package/deploy/unraid/ouro-events/install-usenet-guard.sh +435 -0
  27. package/deploy/unraid/ouro-events/usenet-health.sh +176 -0
  28. package/deploy/unraid/sanctuary-acceptance-adapter.sh +43 -0
  29. package/deploy/unraid/sanctuary-acceptance-contract.json +198 -0
  30. package/deploy/unraid/sanctuary-acceptance-harness.sh +23 -0
  31. package/deploy/unraid/sanctuary-authority-installation.json +36 -0
  32. package/deploy/unraid/sanctuary-authority-service.sh +30 -0
  33. package/deploy/unraid/sanctuary-butler-upgrade.mjs +1254 -0
  34. package/deploy/unraid/sanctuary-deployment-target.mjs +710 -0
  35. package/deploy/unraid/sanctuary-replay-gate.mjs +1564 -0
  36. package/deploy/unraid/sanctuary-safe-rename.py +24 -0
  37. package/deploy/unraid/sanctuary-unit16-host-broker.mjs +2086 -0
  38. package/deploy/unraid/sanctuary-unit16-run.sh +560 -0
  39. package/deploy/unraid/sanctuary-unit18-target-audit.sh +27 -0
  40. package/deploy/unraid/sanctuary.ouro/agent.json +66 -0
  41. package/deploy/unraid/sanctuary.ouro/arc/README.md +3 -0
  42. package/deploy/unraid/sanctuary.ouro/awaiting/house-care-sweep.md +17 -0
  43. package/deploy/unraid/sanctuary.ouro/bundle-meta.json +5 -0
  44. package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +8 -0
  45. package/deploy/unraid/sanctuary.ouro/mcp/media-mcp.mjs +2231 -0
  46. package/deploy/unraid/sanctuary.ouro/provider-readiness.json +11 -0
  47. package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +5 -0
  48. package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +5 -0
  49. package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +33 -0
  50. package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +27 -0
  51. package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +22 -0
  52. package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
  53. package/deploy/unraid/sanctuary.ouro/tool-profiles.json +232 -0
  54. package/deploy/unraid/sanctuary.xml +30 -0
  55. package/dist/a2a/card.js +57 -0
  56. package/dist/a2a/client.js +250 -0
  57. package/dist/a2a/config.js +50 -0
  58. package/dist/a2a/delegated-command-grants.js +93 -0
  59. package/dist/a2a/delegated-command.js +197 -0
  60. package/dist/a2a/delegation-stores.js +56 -0
  61. package/dist/a2a/did-resolution.js +93 -0
  62. package/dist/a2a/escalation-grants.js +84 -0
  63. package/dist/a2a/escalation-wiring.js +60 -0
  64. package/dist/a2a/identity.js +258 -0
  65. package/dist/a2a/inbound-share.js +110 -0
  66. package/dist/a2a/mission-result-wire.js +196 -0
  67. package/dist/a2a/onboarding.js +133 -0
  68. package/dist/a2a/operator-trust.js +382 -0
  69. package/dist/a2a/outbox-client.js +57 -0
  70. package/dist/a2a/outbox-store.js +188 -0
  71. package/dist/a2a/outbox-wire.js +95 -0
  72. package/dist/a2a/pin-store.js +132 -0
  73. package/dist/a2a/replay-grant-holders.js +33 -0
  74. package/dist/a2a/replay-harness.js +188 -0
  75. package/dist/a2a/resolution-proof.js +53 -0
  76. package/dist/a2a/sealed-chat.js +113 -0
  77. package/dist/a2a/seen-ledger.js +120 -0
  78. package/dist/a2a/server.js +771 -0
  79. package/dist/a2a/task-store.js +69 -0
  80. package/dist/a2a/transport.js +45 -0
  81. package/dist/a2a/trust-commands.js +203 -0
  82. package/dist/a2a/trusted-files.js +185 -0
  83. package/dist/a2a/types.js +3 -0
  84. package/dist/arc/attention-types.js +8 -0
  85. package/dist/arc/cares.js +360 -0
  86. package/dist/arc/episodes.js +118 -0
  87. package/dist/arc/evolution.js +490 -0
  88. package/dist/arc/flight-recorder.js +1034 -0
  89. package/dist/arc/intentions.js +134 -0
  90. package/dist/arc/json-store.js +117 -0
  91. package/dist/arc/obligations.js +386 -0
  92. package/dist/arc/packets.js +336 -0
  93. package/dist/arc/presence.js +185 -0
  94. package/dist/arc/task-lifecycle.js +57 -0
  95. package/dist/commerce/store.js +755 -0
  96. package/dist/commerce/types.js +3 -0
  97. package/dist/heart/active-work.js +798 -40
  98. package/dist/heart/agent-entry.js +252 -3
  99. package/dist/heart/approval-files.js +113 -0
  100. package/dist/heart/approval-store.js +960 -0
  101. package/dist/heart/attachments/image-normalize.js +194 -0
  102. package/dist/heart/attachments/materialize.js +97 -0
  103. package/dist/heart/attachments/originals.js +88 -0
  104. package/dist/heart/attachments/render.js +29 -0
  105. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  106. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  107. package/dist/heart/attachments/sources/index.js +18 -0
  108. package/dist/heart/attachments/sources/telegram.js +71 -0
  109. package/dist/heart/attachments/store.js +132 -0
  110. package/dist/heart/attachments/types.js +93 -0
  111. package/dist/heart/auth/auth-flow.js +495 -0
  112. package/dist/heart/autonomy-budget.js +478 -0
  113. package/dist/heart/awaiting/a2a-await-delivery.js +109 -0
  114. package/dist/heart/awaiting/await-alert.js +152 -0
  115. package/dist/heart/awaiting/await-expiry.js +143 -0
  116. package/dist/heart/awaiting/await-loader.js +91 -0
  117. package/dist/heart/awaiting/await-parser.js +152 -0
  118. package/dist/heart/awaiting/await-runtime-state.js +111 -0
  119. package/dist/heart/awaiting/await-scheduler.js +391 -0
  120. package/dist/heart/background-operations.js +281 -0
  121. package/dist/heart/bluebubbles-host-account.js +78 -0
  122. package/dist/heart/bridges/manager.js +137 -17
  123. package/dist/heart/bridges/store.js +14 -2
  124. package/dist/heart/bundle-state.js +168 -0
  125. package/dist/heart/commitments.js +44 -18
  126. package/dist/heart/config-registry.js +331 -0
  127. package/dist/heart/config.js +174 -131
  128. package/dist/heart/context-loss-gauntlet.js +387 -0
  129. package/dist/heart/context-loss-sentinel.js +1159 -0
  130. package/dist/heart/core.js +2364 -578
  131. package/dist/heart/cross-chat-delivery.js +9 -22
  132. package/dist/heart/daemon/agent-config-check.js +451 -0
  133. package/dist/heart/daemon/agent-discovery.js +283 -3
  134. package/dist/heart/daemon/agent-home.js +319 -0
  135. package/dist/heart/daemon/agent-service.js +542 -0
  136. package/dist/heart/daemon/agentic-repair.js +547 -0
  137. package/dist/heart/daemon/await-private-wake.js +59 -0
  138. package/dist/heart/daemon/bluebubbles-cli-integration.js +71 -0
  139. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  140. package/dist/heart/daemon/bluebubbles-host-protocol.js +433 -0
  141. package/dist/heart/daemon/bluebubbles-host.js +273 -0
  142. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  143. package/dist/heart/daemon/cadence.js +180 -0
  144. package/dist/heart/daemon/cli-arg-redaction.js +26 -0
  145. package/dist/heart/daemon/cli-defaults.js +876 -0
  146. package/dist/heart/daemon/cli-desk.js +322 -0
  147. package/dist/heart/daemon/cli-exec.js +9199 -0
  148. package/dist/heart/daemon/cli-help.js +735 -0
  149. package/dist/heart/daemon/cli-parse.js +3211 -0
  150. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  151. package/dist/heart/daemon/cli-render.js +819 -0
  152. package/dist/heart/daemon/cli-types.js +8 -0
  153. package/dist/heart/daemon/connect-bay.js +323 -0
  154. package/dist/heart/daemon/container-credential-bootstrap.js +273 -0
  155. package/dist/heart/daemon/container-healthcheck.js +112 -0
  156. package/dist/heart/daemon/container-runtime.js +97 -0
  157. package/dist/heart/daemon/container-spec-auditor-main.js +171 -0
  158. package/dist/heart/daemon/container-spec-auditor.js +235 -0
  159. package/dist/heart/daemon/daemon-bootstrap-startup.js +115 -0
  160. package/dist/heart/daemon/daemon-cli.js +29 -2350
  161. package/dist/heart/daemon/daemon-entry.js +953 -11
  162. package/dist/heart/daemon/daemon-health.js +186 -0
  163. package/dist/heart/daemon/daemon-rollup.js +57 -0
  164. package/dist/heart/daemon/daemon-runtime-sync.js +88 -13
  165. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  166. package/dist/heart/daemon/daemon.js +2084 -105
  167. package/dist/heart/daemon/dns-workflow.js +394 -0
  168. package/dist/heart/daemon/doctor-types.js +8 -0
  169. package/dist/heart/daemon/doctor.js +1766 -0
  170. package/dist/heart/daemon/freshness.js +345 -0
  171. package/dist/heart/daemon/habit-private-wake.js +61 -0
  172. package/dist/heart/daemon/health-monitor.js +154 -11
  173. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  174. package/dist/heart/daemon/hooks/bundle-meta.js +135 -1
  175. package/dist/heart/daemon/http-health-probe.js +80 -0
  176. package/dist/heart/daemon/human-command-screens.js +234 -0
  177. package/dist/heart/daemon/human-readiness.js +114 -0
  178. package/dist/heart/daemon/inner-status.js +78 -0
  179. package/dist/heart/daemon/interactive-repair.js +394 -0
  180. package/dist/heart/daemon/launchd.js +37 -8
  181. package/dist/heart/daemon/log-tailer.js +79 -10
  182. package/dist/heart/daemon/logs-prune.js +132 -0
  183. package/dist/heart/daemon/mcp-canary.js +524 -0
  184. package/dist/heart/daemon/message-router.js +65 -5
  185. package/dist/heart/daemon/migrate-to-desk.js +848 -0
  186. package/dist/heart/daemon/os-cron-deps.js +135 -0
  187. package/dist/heart/daemon/os-cron.js +22 -14
  188. package/dist/heart/daemon/ouro-bot-entry.js +6 -3
  189. package/dist/heart/daemon/ouro-entry.js +5 -2
  190. package/dist/heart/daemon/plugin-cli.js +432 -0
  191. package/dist/heart/daemon/process-manager.js +760 -61
  192. package/dist/heart/daemon/provider-discovery.js +137 -0
  193. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  194. package/dist/heart/daemon/prunable-bundle.js +144 -0
  195. package/dist/heart/daemon/pulse.js +511 -0
  196. package/dist/heart/daemon/readiness-repair.js +365 -0
  197. package/dist/heart/daemon/run-hooks.js +2 -0
  198. package/dist/heart/daemon/runtime-logging.js +47 -14
  199. package/dist/heart/daemon/runtime-metadata.js +2 -30
  200. package/dist/heart/daemon/runtime-mode.js +13 -2
  201. package/dist/heart/daemon/safe-mode.js +161 -0
  202. package/dist/heart/daemon/sanctuary-acceptance-adapter.js +2654 -0
  203. package/dist/heart/daemon/sanctuary-acceptance-harness.js +1830 -0
  204. package/dist/heart/daemon/sanctuary-acceptance-marker.js +267 -0
  205. package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +955 -0
  206. package/dist/heart/daemon/sanctuary-authority-codec.js +86 -0
  207. package/dist/heart/daemon/sanctuary-authority-epoch.js +281 -0
  208. package/dist/heart/daemon/sanctuary-authority-installation.js +161 -0
  209. package/dist/heart/daemon/sanctuary-authority-ledger.js +241 -0
  210. package/dist/heart/daemon/sanctuary-authority-root-lifecycle.js +1267 -0
  211. package/dist/heart/daemon/sanctuary-authority-vault-migration.js +79 -0
  212. package/dist/heart/daemon/sanctuary-bundle-migration.js +730 -0
  213. package/dist/heart/daemon/sanctuary-host-authority.js +1008 -0
  214. package/dist/heart/daemon/sanctuary-host-detached-supervisor.js +493 -0
  215. package/dist/heart/daemon/sanctuary-host-executor.js +670 -0
  216. package/dist/heart/daemon/sanctuary-host-linux-kernel.js +334 -0
  217. package/dist/heart/daemon/sanctuary-host-supervisor-entry.js +207 -0
  218. package/dist/heart/daemon/sanctuary-host-supervisor.js +95 -0
  219. package/dist/heart/daemon/sanctuary-package-management.js +103 -0
  220. package/dist/heart/daemon/sanctuary-scheduler-liveness.js +227 -0
  221. package/dist/heart/daemon/sanctuary-scheduler-origin.js +132 -0
  222. package/dist/heart/daemon/sanctuary-telegram-authority-entry.js +475 -0
  223. package/dist/heart/daemon/sanctuary-telegram-authority-gateway.js +585 -0
  224. package/dist/heart/daemon/sanctuary-telegram-authority-service.js +676 -0
  225. package/dist/heart/daemon/sense-manager.js +733 -48
  226. package/dist/heart/daemon/session-id-resolver.js +131 -0
  227. package/dist/heart/daemon/skill-management-installer.js +1 -1
  228. package/dist/heart/daemon/socket-client.js +180 -12
  229. package/dist/heart/daemon/stale-bundle-prune.js +113 -0
  230. package/dist/heart/daemon/startup-tui.js +339 -0
  231. package/dist/heart/daemon/supercronic-supervisor.js +282 -0
  232. package/dist/heart/daemon/task-scheduler.js +117 -39
  233. package/dist/heart/daemon/terminal-ui.js +503 -0
  234. package/dist/heart/daemon/thoughts.js +138 -54
  235. package/dist/heart/daemon/up-progress.js +366 -0
  236. package/dist/heart/daemon/vault-items.js +56 -0
  237. package/dist/heart/delegation.js +1 -4
  238. package/dist/heart/external-events/router.js +1516 -0
  239. package/dist/heart/failure-reports.js +336 -0
  240. package/dist/heart/file-lock.js +109 -0
  241. package/dist/heart/frontend-approval-runtime.js +506 -0
  242. package/dist/heart/frontend-journal.js +215 -0
  243. package/dist/heart/frontend-session-service.js +237 -0
  244. package/dist/heart/frontend-socket-client.js +164 -0
  245. package/dist/heart/frontend-socket.js +400 -0
  246. package/dist/heart/habits/habit-cancel.js +810 -0
  247. package/dist/heart/habits/habit-lifecycle.js +1327 -0
  248. package/dist/heart/habits/habit-migration.js +194 -0
  249. package/dist/heart/habits/habit-parser.js +278 -0
  250. package/dist/heart/habits/habit-runtime-state.js +93 -0
  251. package/dist/heart/habits/habit-scheduler.js +653 -0
  252. package/dist/heart/habits/habit-session-summary.js +318 -0
  253. package/dist/heart/habits/habit-session.js +636 -0
  254. package/dist/heart/{daemon → hatch}/hatch-flow.js +55 -62
  255. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  256. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  257. package/dist/heart/{daemon → hatch}/specialist-tools.js +53 -18
  258. package/dist/heart/identity.js +230 -59
  259. package/dist/heart/kept-notes.js +289 -0
  260. package/dist/heart/kicks.js +1 -1
  261. package/dist/heart/machine-identity.js +175 -0
  262. package/dist/heart/mail-import-discovery.js +356 -0
  263. package/dist/heart/mailbox/mailbox-http-hooks.js +98 -0
  264. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  265. package/dist/heart/mailbox/mailbox-http-routes.js +398 -0
  266. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  267. package/dist/heart/mailbox/mailbox-http-transport.js +272 -0
  268. package/dist/heart/mailbox/mailbox-http.js +99 -0
  269. package/dist/heart/mailbox/mailbox-read.js +39 -0
  270. package/dist/heart/mailbox/mailbox-types.js +27 -0
  271. package/dist/heart/mailbox/mailbox-view.js +197 -0
  272. package/dist/heart/mailbox/readers/agent-machine.js +428 -0
  273. package/dist/heart/mailbox/readers/continuity-readers.js +329 -0
  274. package/dist/heart/mailbox/readers/mail.js +375 -0
  275. package/dist/heart/mailbox/readers/runtime-readers.js +823 -0
  276. package/dist/heart/mailbox/readers/sessions.js +231 -0
  277. package/dist/heart/mailbox/readers/shared.js +111 -0
  278. package/dist/heart/mcp/mcp-server.js +696 -0
  279. package/dist/heart/migrate-config.js +100 -0
  280. package/dist/heart/model-capabilities.js +83 -16
  281. package/dist/heart/orientation-frame.js +345 -0
  282. package/dist/heart/platform.js +81 -0
  283. package/dist/heart/primitives.js +1 -1
  284. package/dist/heart/private-runtime/decision-renderer.js +158 -0
  285. package/dist/heart/private-runtime/index.js +18 -0
  286. package/dist/heart/private-runtime/ledger.js +221 -0
  287. package/dist/heart/private-runtime/policy.js +260 -0
  288. package/dist/heart/private-runtime/types.js +2 -0
  289. package/dist/heart/progress-story.js +1 -1
  290. package/dist/heart/provider-attempt.js +137 -0
  291. package/dist/heart/provider-binding-resolver.js +273 -0
  292. package/dist/heart/provider-credentials.js +449 -0
  293. package/dist/heart/provider-failover.js +311 -0
  294. package/dist/heart/provider-models.js +96 -0
  295. package/dist/heart/provider-ping.js +265 -0
  296. package/dist/heart/provider-readiness-cache.js +119 -0
  297. package/dist/heart/provider-visibility.js +188 -0
  298. package/dist/heart/providers/anthropic-token.js +131 -0
  299. package/dist/heart/providers/anthropic.js +173 -60
  300. package/dist/heart/providers/azure.js +23 -11
  301. package/dist/heart/providers/error-classification.js +127 -0
  302. package/dist/heart/providers/github-copilot.js +38 -39
  303. package/dist/heart/providers/minimax-vlm.js +189 -0
  304. package/dist/heart/providers/minimax.js +26 -8
  305. package/dist/heart/providers/openai-codex-token.js +349 -0
  306. package/dist/heart/providers/openai-codex.js +55 -40
  307. package/dist/heart/providers/openai-compatible.js +177 -0
  308. package/dist/heart/reply-style.js +52 -0
  309. package/dist/heart/run-ledger.js +247 -0
  310. package/dist/heart/runtime-capability-check.js +170 -0
  311. package/dist/heart/runtime-credentials.js +607 -0
  312. package/dist/heart/runtime-cwd.js +87 -0
  313. package/dist/heart/sense-truth.js +24 -4
  314. package/dist/heart/session-activity.js +92 -24
  315. package/dist/heart/session-events.js +1504 -0
  316. package/dist/heart/session-playback-cli-main.js +5 -0
  317. package/dist/heart/session-playback-cli.js +36 -0
  318. package/dist/heart/session-playback.js +231 -0
  319. package/dist/heart/session-redaction-repair-cli-main.js +31 -0
  320. package/dist/heart/session-redaction-repair.js +552 -0
  321. package/dist/heart/session-stats-cli-main.js +5 -0
  322. package/dist/heart/session-stats.js +183 -0
  323. package/dist/heart/session-transcript.js +133 -0
  324. package/dist/heart/shelf-claims.js +78 -0
  325. package/dist/heart/shepherd-config.js +113 -0
  326. package/dist/heart/source-grounding.js +297 -0
  327. package/dist/heart/start-of-turn-packet.js +372 -0
  328. package/dist/heart/steward-policy.js +656 -0
  329. package/dist/heart/streaming.js +609 -207
  330. package/dist/heart/structured-output.js +196 -0
  331. package/dist/heart/sync-classification.js +176 -0
  332. package/dist/heart/sync-health.js +152 -0
  333. package/dist/heart/sync.js +726 -0
  334. package/dist/heart/target-resolution.js +11 -7
  335. package/dist/heart/tempo.js +93 -0
  336. package/dist/heart/temporal-view.js +41 -0
  337. package/dist/heart/timeouts.js +101 -0
  338. package/dist/heart/tool-activity-callbacks.js +59 -0
  339. package/dist/heart/tool-approval.js +382 -0
  340. package/dist/heart/tool-description.js +155 -0
  341. package/dist/heart/tool-friction.js +55 -0
  342. package/dist/heart/tool-loop.js +206 -0
  343. package/dist/heart/turn-context.js +496 -0
  344. package/dist/heart/turn-execution-lease.js +30 -0
  345. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  346. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +3 -2
  347. package/dist/heart/versioning/ouro-path-installer.js +432 -0
  348. package/dist/heart/versioning/ouro-recovery-launcher.js +76 -0
  349. package/dist/heart/versioning/ouro-version-manager.js +432 -0
  350. package/dist/heart/versioning/release-sources.js +173 -0
  351. package/dist/heart/versioning/self-update-defaults.js +294 -0
  352. package/dist/heart/versioning/self-update.js +196 -0
  353. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  354. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  355. package/dist/heart/versioning/update-hooks.js +154 -0
  356. package/dist/heart/versioning/update-state.js +126 -0
  357. package/dist/heart/versioning/updater-launchd.js +150 -0
  358. package/dist/heart/versioning/version-intent.js +114 -0
  359. package/dist/heart/work-card.js +380 -0
  360. package/dist/mailbox-ui/assets/index-Cyv4mw3Y.js +1 -0
  361. package/dist/mailbox-ui/assets/index-Du_9G9WO.css +1 -0
  362. package/dist/mailbox-ui/assets/vendor-CcN1XpQ9.js +61 -0
  363. package/dist/mailbox-ui/index.html +16 -0
  364. package/dist/mailroom/attention.js +167 -0
  365. package/dist/mailroom/autonomy.js +209 -0
  366. package/dist/mailroom/blob-store.js +889 -0
  367. package/dist/mailroom/body-cache.js +61 -0
  368. package/dist/mailroom/cache-sync-cli.js +58 -0
  369. package/dist/mailroom/core.js +803 -0
  370. package/dist/mailroom/entry.js +160 -0
  371. package/dist/mailroom/file-store.js +568 -0
  372. package/dist/mailroom/hosted-cache-sync.js +384 -0
  373. package/dist/mailroom/mbox-import.js +393 -0
  374. package/dist/mailroom/migration.js +164 -0
  375. package/dist/mailroom/outbound.js +380 -0
  376. package/dist/mailroom/policy.js +263 -0
  377. package/dist/mailroom/reader.js +292 -0
  378. package/dist/mailroom/search-cache.js +513 -0
  379. package/dist/mailroom/search-relevance.js +319 -0
  380. package/dist/mailroom/smtp-ingress.js +176 -0
  381. package/dist/mailroom/source-state.js +176 -0
  382. package/dist/mailroom/thread.js +109 -0
  383. package/dist/mailroom/travel-extract.js +89 -0
  384. package/dist/mind/bundle-manifest.js +24 -2
  385. package/dist/mind/context.js +307 -139
  386. package/dist/mind/desk-section.js +362 -0
  387. package/dist/mind/diary-integrity.js +60 -0
  388. package/dist/mind/{memory.js → diary.js} +68 -77
  389. package/dist/mind/embedding-provider.js +60 -0
  390. package/dist/mind/file-state.js +179 -0
  391. package/dist/mind/friend-link-codes.js +209 -0
  392. package/dist/mind/friend-merge.js +330 -0
  393. package/dist/mind/{associative-recall.js → note-search.js} +47 -58
  394. package/dist/mind/obligation-steering.js +199 -3
  395. package/dist/mind/pending.js +36 -10
  396. package/dist/mind/prompt-budget.js +479 -0
  397. package/dist/mind/prompt-refresh.js +3 -2
  398. package/dist/mind/prompt.js +1085 -198
  399. package/dist/mind/provenance-trust.js +26 -0
  400. package/dist/mind/record-paths.js +312 -0
  401. package/dist/mind/scrutiny.js +173 -0
  402. package/dist/mind/session-transaction.js +564 -0
  403. package/dist/nerves/cli-logging.js +7 -1
  404. package/dist/nerves/coverage/audit-rules.js +15 -6
  405. package/dist/nerves/coverage/audit.js +28 -2
  406. package/dist/nerves/coverage/cli.js +1 -1
  407. package/dist/nerves/coverage/contract.js +5 -5
  408. package/dist/nerves/coverage/file-completeness.js +139 -5
  409. package/dist/nerves/coverage/run-artifacts.js +55 -35
  410. package/dist/nerves/coverage/source-scanner.js +4 -4
  411. package/dist/nerves/event-buffer.js +111 -0
  412. package/dist/nerves/index.js +293 -9
  413. package/dist/nerves/observation.js +20 -0
  414. package/dist/nerves/redact.js +79 -0
  415. package/dist/nerves/review/cli-main.js +5 -0
  416. package/dist/nerves/review/cli.js +156 -0
  417. package/dist/nerves/review/core.js +159 -0
  418. package/dist/nerves/runtime.js +17 -1
  419. package/dist/repertoire/ado-client.js +15 -56
  420. package/dist/repertoire/ado-semantic.js +16 -10
  421. package/dist/repertoire/api-client.js +97 -0
  422. package/dist/repertoire/bitwarden-store.js +1201 -0
  423. package/dist/repertoire/bundle-templates.js +71 -0
  424. package/dist/repertoire/bw-installer.js +231 -0
  425. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  426. package/dist/repertoire/coding/context-pack.js +331 -0
  427. package/dist/repertoire/coding/feedback.js +174 -32
  428. package/dist/repertoire/coding/index.js +15 -3
  429. package/dist/repertoire/coding/manager.js +159 -10
  430. package/dist/repertoire/coding/spawner.js +40 -11
  431. package/dist/repertoire/coding/tools.js +182 -18
  432. package/dist/repertoire/coding/workbench-client.js +272 -0
  433. package/dist/repertoire/coding/workbench-manager.js +346 -0
  434. package/dist/repertoire/commerce-errors.js +109 -0
  435. package/dist/repertoire/commerce-self-test.js +156 -0
  436. package/dist/repertoire/credential-access.js +183 -0
  437. package/dist/repertoire/desk/classifier.js +362 -0
  438. package/dist/repertoire/duffel-client.js +185 -0
  439. package/dist/repertoire/github-client.js +14 -55
  440. package/dist/repertoire/graph-client.js +11 -52
  441. package/dist/repertoire/guardrails.js +968 -20
  442. package/dist/repertoire/house-sweep.js +469 -0
  443. package/dist/repertoire/mcp-client.js +58 -5
  444. package/dist/repertoire/mcp-manager.js +427 -139
  445. package/dist/repertoire/mcp-tools.js +132 -0
  446. package/dist/repertoire/mcp-write-guard.js +46 -0
  447. package/dist/repertoire/plugin-mcp.js +175 -0
  448. package/dist/repertoire/plugins.js +253 -0
  449. package/dist/repertoire/relationship-authorization.js +322 -0
  450. package/dist/repertoire/sanctuary-container-policy.js +46 -0
  451. package/dist/repertoire/shell-sessions.js +134 -0
  452. package/dist/repertoire/skills.js +48 -4
  453. package/dist/repertoire/stripe-client.js +131 -0
  454. package/dist/repertoire/tool-arguments.js +182 -0
  455. package/dist/repertoire/tool-results.js +29 -0
  456. package/dist/repertoire/tools-a2a.js +699 -0
  457. package/dist/repertoire/tools-attachments.js +322 -0
  458. package/dist/repertoire/tools-awaiting.js +872 -0
  459. package/dist/repertoire/tools-base.js +83 -1168
  460. package/dist/repertoire/tools-bluebubbles.js +2 -0
  461. package/dist/repertoire/tools-bridge.js +144 -0
  462. package/dist/repertoire/tools-bundle.js +995 -0
  463. package/dist/repertoire/tools-commerce.js +253 -0
  464. package/dist/repertoire/tools-config.js +186 -0
  465. package/dist/repertoire/tools-continuity.js +575 -0
  466. package/dist/repertoire/tools-credential.js +383 -0
  467. package/dist/repertoire/tools-escalation.js +84 -0
  468. package/dist/repertoire/tools-evolution.js +527 -0
  469. package/dist/repertoire/tools-files.js +344 -0
  470. package/dist/repertoire/tools-flight.js +290 -0
  471. package/dist/repertoire/tools-flow.js +119 -0
  472. package/dist/repertoire/tools-friend-link.js +57 -0
  473. package/dist/repertoire/tools-github.js +3 -8
  474. package/dist/repertoire/tools-habits.js +103 -0
  475. package/dist/repertoire/tools-house-care.js +174 -0
  476. package/dist/repertoire/tools-mail.js +1828 -0
  477. package/dist/repertoire/tools-notes.js +496 -0
  478. package/dist/repertoire/tools-obligations.js +182 -0
  479. package/dist/repertoire/tools-orientation.js +31 -0
  480. package/dist/repertoire/tools-record.js +482 -0
  481. package/dist/repertoire/tools-rsvp.js +139 -0
  482. package/dist/repertoire/tools-runtime.js +238 -0
  483. package/dist/repertoire/tools-sanctuary-host.js +202 -0
  484. package/dist/repertoire/tools-session.js +1133 -0
  485. package/dist/repertoire/tools-shell.js +149 -0
  486. package/dist/repertoire/tools-shepherd.js +262 -0
  487. package/dist/repertoire/tools-steward-policy.js +147 -0
  488. package/dist/repertoire/tools-stripe.js +224 -0
  489. package/dist/repertoire/tools-surface.js +369 -0
  490. package/dist/repertoire/tools-teams.js +12 -39
  491. package/dist/repertoire/tools-telegram-contacts.js +37 -0
  492. package/dist/repertoire/tools-travel.js +125 -0
  493. package/dist/repertoire/tools-trip.js +982 -0
  494. package/dist/repertoire/tools-unraid.js +556 -0
  495. package/dist/repertoire/tools-user-profile.js +146 -0
  496. package/dist/repertoire/tools-vault.js +40 -0
  497. package/dist/repertoire/tools-voice-local.js +198 -0
  498. package/dist/repertoire/tools-voice.js +145 -0
  499. package/dist/repertoire/tools.js +514 -156
  500. package/dist/repertoire/travel-api-client.js +360 -0
  501. package/dist/repertoire/unraid-client.js +155 -0
  502. package/dist/repertoire/unraid-restart.js +339 -0
  503. package/dist/repertoire/user-profile.js +131 -0
  504. package/dist/repertoire/vault-setup.js +246 -0
  505. package/dist/repertoire/vault-unlock.js +686 -0
  506. package/dist/rsvp/aisleplanner-client.js +354 -0
  507. package/dist/rsvp/cli.js +936 -0
  508. package/dist/rsvp/config.js +327 -0
  509. package/dist/rsvp/cutover.js +433 -0
  510. package/dist/rsvp/diagnostics.js +232 -0
  511. package/dist/rsvp/diff-renderer.js +176 -0
  512. package/dist/rsvp/habit-policy.js +110 -0
  513. package/dist/rsvp/habit-stage.js +123 -0
  514. package/dist/rsvp/incident-bundle.js +97 -0
  515. package/dist/rsvp/migration.js +204 -0
  516. package/dist/rsvp/native-habit-runner.js +630 -0
  517. package/dist/rsvp/outbound-state.js +628 -0
  518. package/dist/rsvp/query.js +118 -0
  519. package/dist/rsvp/replay.js +233 -0
  520. package/dist/rsvp/snapshot.js +232 -0
  521. package/dist/rsvp/spend-ledger.js +175 -0
  522. package/dist/scripts/claude-code-hook.js +41 -0
  523. package/dist/scripts/claude-code-stop-hook.js +47 -0
  524. package/dist/senses/a2a-entry.js +132 -0
  525. package/dist/senses/acp-server.js +386 -0
  526. package/dist/senses/attention-queue.js +186 -0
  527. package/dist/senses/await-turn-message.js +65 -0
  528. package/dist/senses/bluebubbles/active-turns.js +216 -0
  529. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  530. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  531. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +379 -41
  532. package/dist/senses/bluebubbles/context-packet.js +207 -0
  533. package/dist/senses/bluebubbles/context-smoke.js +144 -0
  534. package/dist/senses/bluebubbles/entry.js +90 -0
  535. package/dist/senses/bluebubbles/host-account-verifier.js +88 -0
  536. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +21 -3
  537. package/dist/senses/bluebubbles/index.js +3878 -0
  538. package/dist/senses/bluebubbles/latest-turn.js +311 -0
  539. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -71
  540. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +159 -28
  541. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +24 -13
  542. package/dist/senses/bluebubbles/outbound-state.js +308 -0
  543. package/dist/senses/bluebubbles/processed-log.js +133 -0
  544. package/dist/senses/bluebubbles/reaction-policy.js +47 -0
  545. package/dist/senses/bluebubbles/replay.js +284 -0
  546. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  547. package/dist/senses/bluebubbles/semantic-receipts.js +1986 -0
  548. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  549. package/dist/senses/bluebubbles/webhook-registration.js +257 -0
  550. package/dist/senses/bluebubbles-meta-guard.js +39 -0
  551. package/dist/senses/cli/bracketed-paste.js +82 -0
  552. package/dist/senses/cli/image-paste.js +287 -0
  553. package/dist/senses/cli/image-ref-navigation.js +75 -0
  554. package/dist/senses/cli/ink-app.js +156 -0
  555. package/dist/senses/cli/inline-diff.js +64 -0
  556. package/dist/senses/cli/input-keys.js +174 -0
  557. package/dist/senses/cli/kill-ring.js +86 -0
  558. package/dist/senses/cli/message-list.js +51 -0
  559. package/dist/senses/cli/ouro-tui.js +607 -0
  560. package/dist/senses/cli/spinner-imperative.js +135 -0
  561. package/dist/senses/cli/spinner.js +101 -0
  562. package/dist/senses/cli/status-line.js +60 -0
  563. package/dist/senses/cli/streaming-markdown.js +526 -0
  564. package/dist/senses/cli/tool-display.js +85 -0
  565. package/dist/senses/cli/tool-render.js +85 -0
  566. package/dist/senses/cli/tui-store.js +241 -0
  567. package/dist/senses/cli/virtual-list.js +35 -0
  568. package/dist/senses/cli-entry.js +60 -8
  569. package/dist/senses/cli.js +601 -304
  570. package/dist/senses/commands.js +66 -3
  571. package/dist/senses/context-packet-ledger.js +371 -0
  572. package/dist/senses/context-packets.js +154 -0
  573. package/dist/senses/habit-lifecycle-guard.js +24 -0
  574. package/dist/senses/habit-turn-message.js +160 -0
  575. package/dist/senses/ingress-evidence.js +2 -0
  576. package/dist/senses/mail-entry.js +66 -0
  577. package/dist/senses/mail.js +524 -0
  578. package/dist/senses/pipeline.js +1158 -204
  579. package/dist/senses/private-runtime-worker.js +859 -0
  580. package/dist/senses/private-runtime.js +1627 -0
  581. package/dist/senses/proactive-content-guard.js +51 -0
  582. package/dist/senses/root-host-approval-port.js +345 -0
  583. package/dist/senses/root-host-approval-runtime.js +393 -0
  584. package/dist/senses/sanctuary-authority-resident.js +102 -0
  585. package/dist/senses/sanctuary-download-credit-presentation.js +31 -0
  586. package/dist/senses/sanctuary-full-visibility-contract.js +302 -0
  587. package/dist/senses/sanctuary-grounding.js +118 -0
  588. package/dist/senses/sanctuary-health-acceptance-probe-entry.js +12 -0
  589. package/dist/senses/sanctuary-health-acceptance-probe.js +774 -0
  590. package/dist/senses/sanctuary-health-runner.js +104 -0
  591. package/dist/senses/sanctuary-health.js +422 -0
  592. package/dist/senses/sanctuary-install-state-contract.js +169 -0
  593. package/dist/senses/sanctuary-interactive-control.js +410 -0
  594. package/dist/senses/sanctuary-media-catalog-contract.js +259 -0
  595. package/dist/senses/sanctuary-media-optimization.js +514 -0
  596. package/dist/senses/sanctuary-runtime.js +369 -0
  597. package/dist/senses/sanctuary-sab.js +85 -0
  598. package/dist/senses/sanctuary-storage-optimization-contract.js +32 -0
  599. package/dist/senses/shared-turn.js +793 -0
  600. package/dist/senses/shepherd/client.js +196 -0
  601. package/dist/senses/shepherd/cmux.js +344 -0
  602. package/dist/senses/shepherd/herdr.js +315 -0
  603. package/dist/senses/shepherd/host.js +14 -0
  604. package/dist/senses/shepherd/judge.js +137 -0
  605. package/dist/senses/shepherd/owner-notice.js +245 -0
  606. package/dist/senses/shepherd/redact.js +66 -0
  607. package/dist/senses/shepherd/returns.js +79 -0
  608. package/dist/senses/shepherd/sense.js +309 -0
  609. package/dist/senses/shepherd-entry.js +78 -0
  610. package/dist/senses/surface-tool.js +109 -0
  611. package/dist/senses/teams-entry.js +68 -8
  612. package/dist/senses/teams.js +598 -233
  613. package/dist/senses/telegram-admission.js +718 -0
  614. package/dist/senses/telegram-approval-runtime.js +793 -0
  615. package/dist/senses/telegram-attachments.js +186 -0
  616. package/dist/senses/telegram-audit-ledger.js +220 -0
  617. package/dist/senses/telegram-authority-transport.js +231 -0
  618. package/dist/senses/telegram-client.js +1749 -0
  619. package/dist/senses/telegram-effect-adapter.js +900 -0
  620. package/dist/senses/telegram-entry.js +83 -0
  621. package/dist/senses/telegram-handoff.js +80 -0
  622. package/dist/senses/telegram.js +2405 -0
  623. package/dist/senses/trust-gate.js +104 -9
  624. package/dist/senses/voice/audio-playback.js +237 -0
  625. package/dist/senses/voice/audio-routing.js +119 -0
  626. package/dist/senses/voice/call-auth.js +250 -0
  627. package/dist/senses/voice/elevenlabs.js +202 -0
  628. package/dist/senses/voice/floor-control.js +431 -0
  629. package/dist/senses/voice/floor-controller.js +115 -0
  630. package/dist/senses/voice/golden-path.js +116 -0
  631. package/dist/senses/voice/index.js +29 -0
  632. package/dist/senses/voice/local-audio-control.js +344 -0
  633. package/dist/senses/voice/local-audio-devices.js +516 -0
  634. package/dist/senses/voice/local-audio-disclosure.js +174 -0
  635. package/dist/senses/voice/local-audio-identity.js +103 -0
  636. package/dist/senses/voice/local-audio-join.js +325 -0
  637. package/dist/senses/voice/local-audio-mute.js +199 -0
  638. package/dist/senses/voice/local-audio-notify.js +121 -0
  639. package/dist/senses/voice/local-audio-prompts.js +116 -0
  640. package/dist/senses/voice/local-audio-transport.js +427 -0
  641. package/dist/senses/voice/local-audio.js +281 -0
  642. package/dist/senses/voice/local-media-stream.js +145 -0
  643. package/dist/senses/voice/meeting.js +113 -0
  644. package/dist/senses/voice/outbound.js +190 -0
  645. package/dist/senses/voice/phone.js +33 -0
  646. package/dist/senses/voice/playback.js +139 -0
  647. package/dist/senses/voice/process-guards.js +51 -0
  648. package/dist/senses/voice/realtime-eval.js +496 -0
  649. package/dist/senses/voice/realtime-tool-schema.js +195 -0
  650. package/dist/senses/voice/realtime-trace.js +531 -0
  651. package/dist/senses/voice/transcript.js +70 -0
  652. package/dist/senses/voice/turn.js +191 -0
  653. package/dist/senses/voice/twilio-phone-runtime.js +836 -0
  654. package/dist/senses/voice/twilio-phone.js +5454 -0
  655. package/dist/senses/voice/types.js +2 -0
  656. package/dist/senses/voice/whisper.js +161 -0
  657. package/dist/senses/voice-entry.js +83 -0
  658. package/dist/senses/voice-realtime-eval-command.js +99 -0
  659. package/dist/senses/voice-realtime-eval-entry.js +21 -0
  660. package/dist/senses/voice-twilio-entry.js +87 -0
  661. package/dist/trips/core.js +138 -0
  662. package/dist/trips/store.js +265 -0
  663. package/dist/util/frontmatter.js +69 -0
  664. package/npm-shrinkwrap.json +8100 -0
  665. package/package.json +74 -13
  666. package/skills/agent-commerce.md +113 -0
  667. package/skills/browser-navigation.md +117 -0
  668. package/skills/commerce-setup-guide.md +116 -0
  669. package/skills/commerce-setup.md +84 -0
  670. package/skills/configure-dev-tools.md +99 -0
  671. package/skills/travel-planning.md +138 -0
  672. package/dist/heart/daemon/auth-flow.js +0 -430
  673. package/dist/heart/daemon/ouro-path-installer.js +0 -260
  674. package/dist/heart/daemon/ouro-version-manager.js +0 -171
  675. package/dist/heart/daemon/update-hooks.js +0 -138
  676. package/dist/heart/obligations.js +0 -191
  677. package/dist/heart/safe-workspace.js +0 -362
  678. package/dist/heart/session-recall.js +0 -116
  679. package/dist/mind/friends/channel.js +0 -84
  680. package/dist/mind/friends/group-context.js +0 -144
  681. package/dist/mind/friends/resolver.js +0 -84
  682. package/dist/mind/friends/store-file.js +0 -190
  683. package/dist/mind/friends/store.js +0 -4
  684. package/dist/mind/friends/tokens.js +0 -26
  685. package/dist/mind/friends/trust-explanation.js +0 -74
  686. package/dist/mind/friends/types.js +0 -29
  687. package/dist/repertoire/tasks/board.js +0 -134
  688. package/dist/repertoire/tasks/index.js +0 -224
  689. package/dist/repertoire/tasks/lifecycle.js +0 -80
  690. package/dist/repertoire/tasks/middleware.js +0 -65
  691. package/dist/repertoire/tasks/parser.js +0 -173
  692. package/dist/repertoire/tasks/scanner.js +0 -132
  693. package/dist/repertoire/tasks/transitions.js +0 -144
  694. package/dist/senses/bluebubbles-entry.js +0 -13
  695. package/dist/senses/bluebubbles.js +0 -1181
  696. package/dist/senses/debug-activity.js +0 -154
  697. package/dist/senses/inner-dialog-worker.js +0 -90
  698. package/dist/senses/inner-dialog.js +0 -525
  699. package/subagents/README.md +0 -7
  700. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  701. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  702. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  703. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  704. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  705. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  706. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  707. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  708. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  709. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  710. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  711. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  712. /package/dist/{repertoire/tasks/types.js → heart/attachments/sources/adapter.js} +0 -0
  713. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  714. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  715. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  716. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,1201 @@
1
+ "use strict";
2
+ /**
3
+ * Bitwarden CLI credential store — wraps `bw` CLI for the agent's own vault.
4
+ *
5
+ * This store authenticates directly as the agent using its own master password.
6
+ * The agent owns the vault, so no human-in-the-loop is needed.
7
+ *
8
+ * Requires the `bw` CLI to be installed. Session tokens are cached process-local.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.BitwardenCredentialStore = void 0;
45
+ exports.sanitizeCredentialErrorDetail = sanitizeCredentialErrorDetail;
46
+ exports.readBwCliVersion = readBwCliVersion;
47
+ const node_child_process_1 = require("node:child_process");
48
+ const fs = __importStar(require("node:fs"));
49
+ const path = __importStar(require("node:path"));
50
+ const runtime_1 = require("../nerves/runtime");
51
+ const bw_installer_1 = require("./bw-installer");
52
+ const MAX_ERROR_DETAIL_LENGTH = 500;
53
+ const LONG_ENCODED_TOKEN_PATTERN = /[A-Za-z0-9+/=]{32,}/g;
54
+ const BW_PASSWORD_ENV = "OURO_BW_MASTER_PASSWORD";
55
+ function uniqueSecrets(secrets) {
56
+ return [...new Set(secrets.filter((value) => typeof value === "string" && value.length >= 4))].sort((left, right) => right.length - left.length);
57
+ }
58
+ function sanitizeCredentialErrorDetail(message, options = {}) {
59
+ const filtered = message
60
+ .split(/\r?\n/)
61
+ .filter((line) => {
62
+ const trimmed = line.trim();
63
+ if (trimmed.startsWith("Command failed:"))
64
+ return false;
65
+ if (trimmed.includes("[input is hidden]"))
66
+ return false;
67
+ return true;
68
+ })
69
+ .join("\n")
70
+ .trim();
71
+ let sanitized = filtered || "command failed";
72
+ for (const secret of uniqueSecrets(options.secrets ?? [])) {
73
+ sanitized = sanitized.split(secret).join("[redacted]");
74
+ }
75
+ sanitized = sanitized.replace(LONG_ENCODED_TOKEN_PATTERN, "[redacted]");
76
+ if (sanitized.replace(/\[redacted\]/g, "").trim().length === 0) {
77
+ return "command failed";
78
+ }
79
+ return sanitized.slice(0, MAX_ERROR_DETAIL_LENGTH);
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // bw CLI wrapper
83
+ // ---------------------------------------------------------------------------
84
+ function isBwSessionUnavailableMessage(message) {
85
+ return (/master password/i.test(message) ||
86
+ /vault is locked/i.test(message) ||
87
+ /not logged in/i.test(message) ||
88
+ /session key/i.test(message) ||
89
+ /local bitwarden session/i.test(message));
90
+ }
91
+ function isBwInvalidUnlockSecretMessage(message) {
92
+ return (/invalid master password/i.test(message) ||
93
+ /saved vault unlock secret/i.test(message) ||
94
+ /username or password is incorrect/i.test(message));
95
+ }
96
+ function isBwTimeoutError(err) {
97
+ const timeoutErr = err;
98
+ const message = err.message.toLowerCase();
99
+ return (timeoutErr.code === "ETIMEDOUT" ||
100
+ timeoutErr.killed === true ||
101
+ timeoutErr.signal === "SIGTERM" ||
102
+ message.includes("timed out"));
103
+ }
104
+ function formatBwOperation(args) {
105
+ const [command, target] = args;
106
+ /* v8 ignore next -- defensive: all execBw call sites pass a concrete bw subcommand @preserve */
107
+ if (!command)
108
+ return "bw command";
109
+ return [command, target].filter(Boolean).join(" ");
110
+ }
111
+ function sanitizeBwErrorDetail(message) {
112
+ if (isBwInvalidUnlockSecretMessage(message)) {
113
+ return "bw CLI rejected the saved vault unlock secret for this machine";
114
+ }
115
+ if (isBwSessionUnavailableMessage(message)) {
116
+ return "bw CLI could not use the local Bitwarden session because it is locked, missing, or expired";
117
+ }
118
+ return sanitizeCredentialErrorDetail(message);
119
+ }
120
+ function formatBwCliError(err, stderr = "", args = []) {
121
+ const operation = formatBwOperation(args);
122
+ if (isBwTimeoutError(err)) {
123
+ return new Error(`bw CLI error: ${operation} timed out -- usually resolves on retry. If it persists, check network connectivity to the vault server.`);
124
+ }
125
+ const detail = sanitizeBwErrorDetail(stderr.trim() || err.message);
126
+ if (detail === "command failed") {
127
+ return new Error(`bw CLI error: ${operation} failed without error detail`);
128
+ }
129
+ return new Error(`bw CLI error: ${detail}`);
130
+ }
131
+ function isBwSessionAuthError(err) {
132
+ return isBwSessionUnavailableMessage(err.message) || isBwInvalidUnlockSecretMessage(err.message);
133
+ }
134
+ function isBwConfigLogoutRequired(err) {
135
+ const message = err.message.toLowerCase();
136
+ return message.includes("logout") && message.includes("required");
137
+ }
138
+ function isBwAlreadyLoggedInError(err) {
139
+ return err.message.toLowerCase().includes("already logged in");
140
+ }
141
+ function isBwLoggedOutOrUnauthenticatedError(err) {
142
+ const message = err.message.toLowerCase();
143
+ return (message.includes("not logged in") ||
144
+ message.includes("not authenticated") ||
145
+ message.includes("unauthenticated") ||
146
+ message.includes("local bitwarden session because it is locked, missing, or expired"));
147
+ }
148
+ function shouldUseStructuredItemLookup(domain) {
149
+ return domain.includes("/");
150
+ }
151
+ // ---------------------------------------------------------------------------
152
+ // Cross-process bw CLI lock
153
+ // ---------------------------------------------------------------------------
154
+ // The bw CLI cannot handle concurrent access to the same app data directory.
155
+ // Two processes (e.g. daemon worker + ouro up CLI) hitting the same dir
156
+ // simultaneously corrupt bw's local state, producing empty/garbled output.
157
+ //
158
+ // We use two layers:
159
+ // 1. In-process async mutex: a Map<string, Promise<void>> keyed by appDataDir
160
+ // serializes calls within a single Node.js process.
161
+ // 2. Cross-process file lock: fs.openSync(lockPath, 'wx') with PID stale
162
+ // detection serializes across processes.
163
+ // ---------------------------------------------------------------------------
164
+ const BW_LOCK_FILENAME = ".ouro-bw.lock";
165
+ const BW_COMMAND_TIMEOUT_MS = 30_000;
166
+ const BW_LOCK_TIMEOUT_MS = 75_000;
167
+ const BW_LOCK_POLL_MS = 100;
168
+ const BW_LOCK_DEAD_CHILD_GRACE_MS = 2_000;
169
+ const BW_LOCK_STALE_MS = BW_LOCK_TIMEOUT_MS * 2;
170
+ const BW_DATA_FILENAME = "data.json";
171
+ const BW_SYNC_MARKER_FILENAME = ".ouro-last-sync";
172
+ const BW_SYNC_FRESH_MS = 60_000;
173
+ const BW_TERMINAL_LOGIN_ERROR_RETRY_MS = 15 * 60_000;
174
+ /** In-process async mutex keyed by appDataDir. */
175
+ const inProcessLocks = new Map();
176
+ function isPidAlive(pid) {
177
+ try {
178
+ process.kill(pid, 0);
179
+ return true;
180
+ }
181
+ catch {
182
+ return false;
183
+ }
184
+ }
185
+ function parseBwLockPidValue(content) {
186
+ const pid = Number.parseInt(content, 10);
187
+ return Number.isFinite(pid) ? pid : undefined;
188
+ }
189
+ function parseBwLockPid(value) {
190
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
191
+ }
192
+ function serializeBwLockRecord(record) {
193
+ return `${JSON.stringify({ ownerPid: record.ownerPid, childPid: record.childPid })}\n`;
194
+ }
195
+ function parseBwLockRecord(content) {
196
+ try {
197
+ const parsed = JSON.parse(content);
198
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
199
+ const record = parsed;
200
+ return {
201
+ ownerPid: parseBwLockPid(record.ownerPid),
202
+ childPid: parseBwLockPid(record.childPid),
203
+ };
204
+ }
205
+ }
206
+ catch {
207
+ // Plain-PID lock files from older runtime builds remain valid.
208
+ }
209
+ return { ownerPid: parseBwLockPidValue(content), childPid: undefined };
210
+ }
211
+ function getStaleBwLock(lockPath, content) {
212
+ const lock = parseBwLockRecord(content);
213
+ const ageMs = Date.now() - fs.statSync(lockPath).mtimeMs;
214
+ if (lock.childPid !== undefined &&
215
+ ageMs >= BW_LOCK_DEAD_CHILD_GRACE_MS &&
216
+ !isPidAlive(lock.childPid)) {
217
+ return { reason: "dead-child-pid", pid: lock.childPid, ownerPid: lock.ownerPid, ageMs };
218
+ }
219
+ if (lock.ownerPid !== undefined && !isPidAlive(lock.ownerPid)) {
220
+ return { reason: "dead-pid", pid: lock.ownerPid, ownerPid: lock.ownerPid, ageMs };
221
+ }
222
+ if (ageMs >= BW_LOCK_STALE_MS) {
223
+ return { reason: "stale-age", pid: lock.childPid ?? lock.ownerPid, ownerPid: lock.ownerPid, ageMs };
224
+ }
225
+ return undefined;
226
+ }
227
+ function reapStaleBwLock(lockPath, staleLock) {
228
+ try {
229
+ fs.unlinkSync(lockPath);
230
+ }
231
+ catch {
232
+ // Race with another process cleaning the same stale lock.
233
+ /* v8 ignore next -- race: another process may reap the same stale lock between stat and unlink @preserve */
234
+ return;
235
+ }
236
+ (0, runtime_1.emitNervesEvent)({
237
+ level: "warn",
238
+ event: "repertoire.bw_lock_stale_reaped",
239
+ component: "repertoire",
240
+ message: "stale bw CLI lock reaped",
241
+ meta: {
242
+ lockPath,
243
+ reason: staleLock.reason,
244
+ pid: staleLock.pid === undefined ? "unknown" : String(staleLock.pid),
245
+ ownerPid: staleLock.ownerPid === undefined ? "unknown" : String(staleLock.ownerPid),
246
+ ageMs: String(Math.round(staleLock.ageMs)),
247
+ },
248
+ });
249
+ }
250
+ async function acquireFileLock(lockPath) {
251
+ const content = serializeBwLockRecord({ ownerPid: process.pid, childPid: undefined });
252
+ const deadline = Date.now() + BW_LOCK_TIMEOUT_MS;
253
+ while (true) {
254
+ try {
255
+ const fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL);
256
+ fs.writeSync(fd, content);
257
+ fs.closeSync(fd);
258
+ return {
259
+ recordChildPid: (pid) => {
260
+ try {
261
+ fs.writeFileSync(lockPath, serializeBwLockRecord({ ownerPid: process.pid, childPid: pid }));
262
+ }
263
+ catch {
264
+ // If another process has already reaped the lock after the child
265
+ // died, releasing below remains idempotent.
266
+ }
267
+ },
268
+ };
269
+ }
270
+ catch (err) {
271
+ if (err.code !== "EEXIST") {
272
+ throw err;
273
+ }
274
+ // Lock file exists -- check for stale lock
275
+ try {
276
+ const existing = fs.readFileSync(lockPath, "utf8").trim();
277
+ const staleLock = getStaleBwLock(lockPath, existing);
278
+ if (staleLock) {
279
+ reapStaleBwLock(lockPath, staleLock);
280
+ continue;
281
+ }
282
+ }
283
+ catch {
284
+ /* v8 ignore next -- race: lock file disappeared between openSync and readFileSync @preserve */
285
+ continue;
286
+ }
287
+ if (Date.now() >= deadline) {
288
+ throw new Error(`bw CLI lock timeout: could not acquire ${lockPath} within ${BW_LOCK_TIMEOUT_MS}ms`);
289
+ }
290
+ // Yield to the event loop before retrying
291
+ await delay(BW_LOCK_POLL_MS);
292
+ }
293
+ }
294
+ }
295
+ function releaseFileLock(lockPath) {
296
+ try {
297
+ fs.unlinkSync(lockPath);
298
+ }
299
+ catch {
300
+ // Already removed or never created -- safe to ignore
301
+ }
302
+ }
303
+ async function withBwLock(appDataDir, fn) {
304
+ if (!appDataDir) {
305
+ // No appDataDir means the default bw data location. Still need in-process
306
+ // serialization but cannot do cross-process file lock without a dir.
307
+ return fn();
308
+ }
309
+ const lockKey = appDataDir;
310
+ const lockPath = path.join(appDataDir, BW_LOCK_FILENAME);
311
+ // In-process serialization: chain onto the previous promise for this key
312
+ const previous = inProcessLocks.get(lockKey) ?? Promise.resolve();
313
+ let releaseLock;
314
+ const current = new Promise((resolve) => { releaseLock = resolve; });
315
+ inProcessLocks.set(lockKey, current);
316
+ await previous;
317
+ let fileLockAcquired = false;
318
+ let lockControl;
319
+ try {
320
+ // Cross-process file lock
321
+ lockControl = await acquireFileLock(lockPath);
322
+ fileLockAcquired = true;
323
+ return await fn(lockControl);
324
+ }
325
+ finally {
326
+ if (fileLockAcquired) {
327
+ releaseFileLock(lockPath);
328
+ }
329
+ releaseLock();
330
+ // Clean up the map entry if we are the latest
331
+ if (inProcessLocks.get(lockKey) === current) {
332
+ inProcessLocks.delete(lockKey);
333
+ }
334
+ }
335
+ }
336
+ function execBw(args, sessionToken, appDataDir, stdin, bwBinaryPath = "bw", extraEnv = {}) {
337
+ const env = {
338
+ ...process.env,
339
+ ...(sessionToken ? { BW_SESSION: sessionToken } : {}),
340
+ ...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
341
+ ...extraEnv,
342
+ };
343
+ const runCommand = (lock) => new Promise((resolve, reject) => {
344
+ const command = (0, bw_installer_1.bwCommand)(bwBinaryPath, args);
345
+ const child = (0, node_child_process_1.execFile)(command.file, command.args, { timeout: BW_COMMAND_TIMEOUT_MS, env }, (err, stdout, stderr) => {
346
+ if (err) {
347
+ if (isBwNotInstalled(err)) {
348
+ reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
349
+ return;
350
+ }
351
+ reject(formatBwCliError(err, stderr, args));
352
+ return;
353
+ }
354
+ resolve(stdout);
355
+ });
356
+ if (child?.pid !== undefined) {
357
+ lock?.recordChildPid(child.pid);
358
+ }
359
+ if (stdin !== undefined) {
360
+ child?.stdin?.end(stdin);
361
+ }
362
+ });
363
+ return withBwLock(appDataDir, runCommand);
364
+ }
365
+ /** Check if the error indicates the bw CLI binary is not installed. */
366
+ function isBwNotInstalled(err) {
367
+ const msg = err.message.toLowerCase();
368
+ const code = err.code;
369
+ return code === "ENOENT" || /\bspawn\b.*\benoent\b/.test(msg) || msg.includes("command not found");
370
+ }
371
+ /**
372
+ * Read the installed @bitwarden/cli version from the package that owns the
373
+ * resolved `bw` binary. Returns null when the binary is not an npm install.
374
+ */
375
+ function readBwCliVersion(bwBinaryPath) {
376
+ let dir;
377
+ try {
378
+ dir = path.dirname(fs.realpathSync(bwBinaryPath));
379
+ }
380
+ catch {
381
+ return null;
382
+ }
383
+ for (let depth = 0; depth < 4; depth++) {
384
+ try {
385
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
386
+ if (pkg.name === "@bitwarden/cli" && typeof pkg.version === "string")
387
+ return pkg.version;
388
+ }
389
+ catch {
390
+ // Keep walking up toward the package root.
391
+ }
392
+ dir = path.dirname(dir);
393
+ }
394
+ return null;
395
+ }
396
+ /** Another process holds the profile; the profile itself is not at fault. */
397
+ function isBwLockTimeout(err) {
398
+ return err.message.includes("bw CLI lock timeout");
399
+ }
400
+ /**
401
+ * A local bw step (unlock of an already-authenticated profile) failed for a
402
+ * reason that says nothing about the vault server: the profile on disk is
403
+ * unusable. Network, lock and missing-binary failures are not this, and
404
+ * neither is a rejected unlock secret.
405
+ */
406
+ class LocalProfileUnusableError extends Error {
407
+ localError;
408
+ constructor(localError) {
409
+ super(localError.message);
410
+ this.localError = localError;
411
+ }
412
+ }
413
+ function asLocalProfileFailure(err) {
414
+ if (isTransientError(err) || isBwNotInstalled(err) || isBwLockTimeout(err) || isBwInvalidUnlockSecretMessage(err.message)) {
415
+ return err;
416
+ }
417
+ return new LocalProfileUnusableError(err);
418
+ }
419
+ /** A bw version as a safe directory name component. */
420
+ function bwProfileDirName(version) {
421
+ return `bw-${version.replace(/[^A-Za-z0-9._-]/g, "_")}`;
422
+ }
423
+ /** Check if the error is transient (network/timeout) and worth retrying. */
424
+ function isTransientError(err) {
425
+ const msg = err.message.toLowerCase();
426
+ return (msg.includes("econnrefused") ||
427
+ msg.includes("etimedout") ||
428
+ msg.includes("enotfound") ||
429
+ msg.includes("socket hang up") ||
430
+ msg.includes("econnreset") ||
431
+ msg.includes("eai_again") ||
432
+ msg.includes("enetunreach") ||
433
+ msg.includes("ehostunreach") ||
434
+ msg.includes("fetch failed") ||
435
+ msg.includes("429") ||
436
+ msg.includes("too many requests") ||
437
+ msg.includes("502") ||
438
+ msg.includes("503") ||
439
+ msg.includes("504") ||
440
+ msg.includes("bad gateway") ||
441
+ msg.includes("gateway timeout") ||
442
+ msg.includes("server unavailable") ||
443
+ msg.includes("timed out"));
444
+ }
445
+ const MAX_RETRIES = 3;
446
+ const BASE_BACKOFF_MS = 1000;
447
+ const TRANSIENT_MAX_RETRIES = 3;
448
+ const TRANSIENT_RETRY_BASE_MS = 500;
449
+ function delay(ms) {
450
+ return new Promise((resolve) => setTimeout(resolve, ms));
451
+ }
452
+ function isBwLoginItem(value) {
453
+ if (!value || typeof value !== "object" || Array.isArray(value))
454
+ return false;
455
+ const item = value;
456
+ if (typeof item.id !== "string" || item.id.trim().length === 0)
457
+ return false;
458
+ if (typeof item.name !== "string" || item.name.trim().length === 0)
459
+ return false;
460
+ if (item.login !== undefined) {
461
+ if (!item.login || typeof item.login !== "object" || Array.isArray(item.login))
462
+ return false;
463
+ const login = item.login;
464
+ if (login.username !== undefined && typeof login.username !== "string")
465
+ return false;
466
+ if (login.password !== undefined && typeof login.password !== "string")
467
+ return false;
468
+ if (login.uris !== undefined) {
469
+ if (!Array.isArray(login.uris))
470
+ return false;
471
+ for (const uri of login.uris) {
472
+ if (!uri || typeof uri !== "object" || Array.isArray(uri))
473
+ return false;
474
+ const uriRecord = uri;
475
+ if (uriRecord.uri !== undefined && typeof uriRecord.uri !== "string")
476
+ return false;
477
+ }
478
+ }
479
+ }
480
+ if (item.notes !== undefined && item.notes !== null && typeof item.notes !== "string")
481
+ return false;
482
+ if (item.revisionDate !== undefined && typeof item.revisionDate !== "string")
483
+ return false;
484
+ return true;
485
+ }
486
+ function parseBwItemArray(stdout, context) {
487
+ let parsed;
488
+ try {
489
+ parsed = JSON.parse(stdout);
490
+ if (!Array.isArray(parsed)) {
491
+ throw new Error("expected item array");
492
+ }
493
+ }
494
+ catch {
495
+ throw new Error(`bw CLI error: invalid JSON from ${context}`);
496
+ }
497
+ return parsed;
498
+ }
499
+ function parseBwItems(stdout, context) {
500
+ const items = parseBwItemArray(stdout, context);
501
+ if (!items.every(isBwLoginItem)) {
502
+ throw new Error(`bw CLI error: invalid item from ${context}`);
503
+ }
504
+ return items;
505
+ }
506
+ function parseStructuredBwItems(stdout, context) {
507
+ const rawItems = parseBwItemArray(stdout, context);
508
+ const items = new Map();
509
+ const malformedNames = new Set();
510
+ for (const rawItem of rawItems) {
511
+ if (!rawItem || typeof rawItem !== "object" || Array.isArray(rawItem))
512
+ continue;
513
+ const name = rawItem.name;
514
+ if (typeof name !== "string" || name.trim().length === 0)
515
+ continue;
516
+ if (isBwLoginItem(rawItem)) {
517
+ items.set(name, rawItem);
518
+ }
519
+ else {
520
+ malformedNames.add(name);
521
+ }
522
+ }
523
+ return { items, malformedNames };
524
+ }
525
+ function parseBwItem(stdout, context) {
526
+ let parsed;
527
+ try {
528
+ parsed = JSON.parse(stdout);
529
+ if (!isBwLoginItem(parsed)) {
530
+ throw new Error("expected login item");
531
+ }
532
+ return parsed;
533
+ }
534
+ catch {
535
+ if (parsed !== undefined) {
536
+ throw new Error(`bw CLI error: invalid item from ${context}`);
537
+ }
538
+ throw new Error(`bw CLI error: invalid JSON from ${context}`);
539
+ }
540
+ }
541
+ function parseBwItemId(stdout) {
542
+ try {
543
+ const parsed = JSON.parse(stdout);
544
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
545
+ return null;
546
+ const id = parsed.id;
547
+ return typeof id === "string" && id.trim().length > 0 ? id : null;
548
+ }
549
+ catch {
550
+ return null;
551
+ }
552
+ }
553
+ // ---------------------------------------------------------------------------
554
+ // BitwardenCredentialStore
555
+ // ---------------------------------------------------------------------------
556
+ class BitwardenCredentialStore {
557
+ serverUrl;
558
+ email;
559
+ masterPassword;
560
+ /** The agent's bw folder; each bw version keeps its own profile under it. */
561
+ agentDataDir;
562
+ /** The profile directory for the running bw version (BITWARDENCLI_APPDATA_DIR). */
563
+ appDataDir;
564
+ onInvalidUnlockSecret;
565
+ onLoginSuccess;
566
+ onProgress;
567
+ sessionToken = null;
568
+ terminalLoginError = null;
569
+ terminalLoginErrorAt = 0;
570
+ bwBinaryPath = "bw";
571
+ structuredItemCache = null;
572
+ constructor(serverUrl, email, masterPassword, options = {}) {
573
+ this.serverUrl = serverUrl;
574
+ this.email = email;
575
+ this.masterPassword = masterPassword;
576
+ this.agentDataDir = options.appDataDir;
577
+ this.appDataDir = options.appDataDir;
578
+ this.onInvalidUnlockSecret = options.onInvalidUnlockSecret;
579
+ this.onLoginSuccess = options.onLoginSuccess;
580
+ this.onProgress = options.onProgress;
581
+ }
582
+ isReady() {
583
+ return true;
584
+ }
585
+ execBw(args, sessionToken, stdin) {
586
+ return execBw(args, sessionToken, this.appDataDir, stdin, this.bwBinaryPath);
587
+ }
588
+ execBwWithPasswordEnv(args) {
589
+ return execBw([...args, "--passwordenv", BW_PASSWORD_ENV], undefined, this.appDataDir, undefined, this.bwBinaryPath, { [BW_PASSWORD_ENV]: this.masterPassword });
590
+ }
591
+ async notifyInvalidUnlockSecret(error) {
592
+ if (!this.onInvalidUnlockSecret)
593
+ return;
594
+ try {
595
+ await this.onInvalidUnlockSecret(error);
596
+ }
597
+ catch (callbackError) {
598
+ (0, runtime_1.emitNervesEvent)({
599
+ level: "warn",
600
+ event: "repertoire.bw_invalid_unlock_cleanup_failed",
601
+ component: "repertoire",
602
+ message: "failed to clean up rejected local vault unlock material",
603
+ meta: {
604
+ email: this.email,
605
+ serverUrl: this.serverUrl,
606
+ error: callbackError instanceof Error ? callbackError.message : String(callbackError),
607
+ originalError: error.message,
608
+ },
609
+ });
610
+ }
611
+ }
612
+ /**
613
+ * Ensure the bw CLI is authenticated and unlocked.
614
+ * Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
615
+ * Retries transient failures (network/timeout) up to MAX_RETRIES with exponential backoff.
616
+ */
617
+ async login() {
618
+ if (this.terminalLoginError) {
619
+ // A long-lived process retries a terminal failure after a while instead
620
+ // of holding it until restart; rejected secrets are paused separately.
621
+ if (Date.now() - this.terminalLoginErrorAt < BW_TERMINAL_LOGIN_ERROR_RETRY_MS) {
622
+ throw this.terminalLoginError;
623
+ }
624
+ this.terminalLoginError = null;
625
+ }
626
+ // Ensure bw CLI is installed before any bw commands
627
+ this.onProgress?.("finding the Bitwarden CLI");
628
+ this.bwBinaryPath = await (0, bw_installer_1.ensureBwCli)();
629
+ const bwVersion = readBwCliVersion(this.bwBinaryPath);
630
+ this.onProgress?.(`using bw ${bwVersion ?? "(unknown version)"} at ${this.bwBinaryPath}`);
631
+ if (this.agentDataDir) {
632
+ // Each bw version gets its own profile directory, so machines with
633
+ // several bw installs (one per Node version) never migrate or fight
634
+ // over one another's state.
635
+ this.appDataDir = bwVersion ? path.join(this.agentDataDir, bwProfileDirName(bwVersion)) : this.agentDataDir;
636
+ fs.mkdirSync(this.appDataDir, { recursive: true, mode: 0o700 });
637
+ }
638
+ let lastError;
639
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
640
+ try {
641
+ await this.loginAttempt();
642
+ await this.onLoginSuccess?.();
643
+ return;
644
+ }
645
+ catch (err) {
646
+ /* v8 ignore next -- defensive: loginAttempt always throws Error instances @preserve */
647
+ lastError = err instanceof Error ? err : new Error(String(err));
648
+ // Don't retry non-transient errors (auth failures, bw not installed)
649
+ if (!isTransientError(lastError)) {
650
+ this.setTerminalLoginError(lastError);
651
+ if (isBwInvalidUnlockSecretMessage(lastError.message)) {
652
+ await this.notifyInvalidUnlockSecret(lastError);
653
+ }
654
+ throw lastError;
655
+ }
656
+ // Don't retry after final attempt
657
+ if (attempt === MAX_RETRIES - 1)
658
+ break;
659
+ const backoffMs = BASE_BACKOFF_MS * Math.pow(2, attempt);
660
+ (0, runtime_1.emitNervesEvent)({
661
+ event: "repertoire.bw_login_retry",
662
+ component: "repertoire",
663
+ message: `bw login attempt ${attempt + 1} failed, retrying in ${backoffMs}ms`,
664
+ meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
665
+ });
666
+ this.onProgress?.(`sign-in attempt ${attempt + 1} failed (${lastError.message.split("\n")[0].slice(0, 120)}); retrying in ${Math.round(backoffMs / 1000)}s`);
667
+ await delay(backoffMs);
668
+ }
669
+ }
670
+ this.setTerminalLoginError(lastError);
671
+ /* v8 ignore next -- invalid unlock errors are non-transient and exit through the non-retry path above @preserve */
672
+ if (isBwInvalidUnlockSecretMessage(lastError.message)) {
673
+ await this.notifyInvalidUnlockSecret(lastError);
674
+ }
675
+ throw lastError;
676
+ }
677
+ setTerminalLoginError(error) {
678
+ this.terminalLoginError = error;
679
+ this.terminalLoginErrorAt = Date.now();
680
+ }
681
+ /**
682
+ * Single login attempt — called by login() retry loop.
683
+ *
684
+ * The bw profile directory is a disposable cache; the durable secret is the
685
+ * machine's vault unlock material. When a local step fails on an
686
+ * authenticated profile (a corrupt or half-migrated profile, a missing
687
+ * session), the profile is moved aside and rebuilt with one fresh login.
688
+ * Server steps (login, sync), network failures, lock contention and
689
+ * rejected secrets never discard the profile, so a bad secret or a flaky
690
+ * server costs at most one server login per attempt.
691
+ */
692
+ async loginAttempt() {
693
+ try {
694
+ await this.loginWithLocalProfile();
695
+ }
696
+ catch (error) {
697
+ if (!(error instanceof LocalProfileUnusableError))
698
+ throw error;
699
+ await this.discardLocalProfile(error.localError);
700
+ try {
701
+ await this.loginWithLocalProfile();
702
+ }
703
+ catch (retryError) {
704
+ throw retryError instanceof LocalProfileUnusableError ? retryError.localError : retryError;
705
+ }
706
+ }
707
+ }
708
+ /**
709
+ * Move the local bw profile aside so the next bw command starts from an
710
+ * empty profile. The previous data file is kept as one `.discarded` copy
711
+ * for diagnosis and is overwritten by the next discard.
712
+ */
713
+ async discardLocalProfile(cause) {
714
+ const appDataDir = this.appDataDir;
715
+ this.sessionToken = null;
716
+ this.structuredItemCache = null;
717
+ await withBwLock(appDataDir, async () => {
718
+ const dataPath = path.join(appDataDir, BW_DATA_FILENAME);
719
+ try {
720
+ fs.renameSync(dataPath, `${dataPath}.discarded`);
721
+ }
722
+ catch {
723
+ // Already gone: another process discarded it first, which is the same outcome.
724
+ }
725
+ fs.rmSync(path.join(appDataDir, BW_SYNC_MARKER_FILENAME), { force: true });
726
+ });
727
+ (0, runtime_1.emitNervesEvent)({
728
+ level: "warn",
729
+ event: "repertoire.bw_local_profile_discarded",
730
+ component: "repertoire",
731
+ message: "discarded unusable local bw profile; logging in fresh with the saved unlock secret",
732
+ meta: {
733
+ email: this.email,
734
+ serverUrl: this.serverUrl,
735
+ error: sanitizeCredentialErrorDetail(cause.message),
736
+ },
737
+ });
738
+ }
739
+ async loginWithLocalProfile() {
740
+ this.onProgress?.("reading the local bw profile");
741
+ let status = await this.readStatus();
742
+ if (this.shouldRebuildLocalProfile(status)) {
743
+ await this.rebuildLocalProfile("local bw profile did not match requested vault", status);
744
+ status = { status: "unlocked", serverUrl: this.serverUrl, userEmail: this.email };
745
+ }
746
+ // Configure server URL if needed (only works when logged out)
747
+ if (status.status === "unauthenticated" || !status.serverUrl) {
748
+ try {
749
+ this.onProgress?.(`pointing bw at ${this.serverUrl}`);
750
+ await this.execBw(["config", "server", this.serverUrl]);
751
+ }
752
+ catch (error) {
753
+ const err = error;
754
+ if (!isBwConfigLogoutRequired(err))
755
+ throw err;
756
+ // "Logout required" means bw already has local auth state; keep the
757
+ // existing behavior and proceed to login/unlock below.
758
+ }
759
+ }
760
+ if (!this.sessionToken) {
761
+ this.onProgress?.(status.status === "unauthenticated" || !status.status ? `signing in to ${this.serverUrl} as ${this.email}` : `unlocking ${this.email}`);
762
+ if (status.status === "locked") {
763
+ // Already logged in, just needs unlock.
764
+ const unlockOutput = await this.execWithLocalProfileRebuild("unlock rejected saved local unlock material", status, () => this.execBwWithPasswordEnv(["unlock", "--raw"]));
765
+ this.sessionToken = unlockOutput.trim();
766
+ }
767
+ else if (status.status === "unauthenticated" || !status.status) {
768
+ // Not logged in -- full login.
769
+ this.sessionToken = this.sessionTokenFromLoginOutput(await this.loginWithPassword());
770
+ }
771
+ else {
772
+ // Status is "unlocked" -- already good, just need the session token.
773
+ const unlockOutput = await this.execWithLocalProfileRebuild("unlocked bw profile rejected saved local unlock material", status, () => this.execBwWithPasswordEnv(["unlock", "--raw"]));
774
+ this.sessionToken = unlockOutput.trim();
775
+ }
776
+ }
777
+ if (this.shouldSyncVaultAfterSession(status)) {
778
+ this.onProgress?.("syncing vault items");
779
+ /* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
780
+ await this.execBw(["sync"], this.sessionToken ?? undefined);
781
+ this.writeSyncMarker();
782
+ }
783
+ else {
784
+ (0, runtime_1.emitNervesEvent)({
785
+ event: "repertoire.bw_sync_skipped",
786
+ component: "repertoire",
787
+ message: "skipping bw sync because local vault cache is still fresh",
788
+ meta: { email: this.email, serverUrl: this.serverUrl, freshnessWindowMs: BW_SYNC_FRESH_MS },
789
+ });
790
+ }
791
+ this.terminalLoginError = null;
792
+ }
793
+ async readStatus() {
794
+ try {
795
+ const raw = await this.execBw(["status"]);
796
+ const parsed = JSON.parse(raw);
797
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
798
+ return {};
799
+ const record = parsed;
800
+ return {
801
+ ...(typeof record.status === "string" ? { status: record.status } : {}),
802
+ ...(typeof record.serverUrl === "string" ? { serverUrl: record.serverUrl } : {}),
803
+ ...(typeof record.userEmail === "string" ? { userEmail: record.userEmail } : {}),
804
+ };
805
+ }
806
+ catch (err) {
807
+ // If bw CLI is not installed or a transient error, propagate it for retry.
808
+ if (err instanceof Error && (isBwNotInstalled(err) || isTransientError(err))) {
809
+ throw err;
810
+ }
811
+ // CLI not configured or broken -- proceed with full setup.
812
+ return {};
813
+ }
814
+ }
815
+ normalizedServerUrl(value) {
816
+ return value.trim().replace(/\/+$/, "").toLowerCase();
817
+ }
818
+ shouldRebuildLocalProfile(status) {
819
+ if (status.status !== "locked" && status.status !== "unlocked")
820
+ return false;
821
+ if (status.serverUrl && this.normalizedServerUrl(status.serverUrl) !== this.normalizedServerUrl(this.serverUrl))
822
+ return true;
823
+ if (status.userEmail && status.userEmail.trim().toLowerCase() !== this.email.trim().toLowerCase())
824
+ return true;
825
+ return false;
826
+ }
827
+ sessionTokenFromLoginOutput(loginOutput) {
828
+ try {
829
+ const parsed = JSON.parse(loginOutput);
830
+ return typeof parsed.access_token === "string" && parsed.access_token.trim()
831
+ ? parsed.access_token.trim()
832
+ : loginOutput.trim();
833
+ }
834
+ catch {
835
+ return loginOutput.trim();
836
+ }
837
+ }
838
+ async loginWithPassword() {
839
+ try {
840
+ return await this.execBwWithPasswordEnv(["login", this.email, "--raw"]);
841
+ }
842
+ catch (error) {
843
+ const err = error;
844
+ if (!isBwAlreadyLoggedInError(err))
845
+ throw err;
846
+ }
847
+ try {
848
+ return await this.execBwWithPasswordEnv(["unlock", "--raw"]);
849
+ }
850
+ catch (error) {
851
+ throw asLocalProfileFailure(error);
852
+ }
853
+ }
854
+ forgetLocalSyncMarker() {
855
+ if (!this.appDataDir)
856
+ return;
857
+ try {
858
+ fs.rmSync(path.join(this.appDataDir, BW_SYNC_MARKER_FILENAME), { force: true });
859
+ }
860
+ catch {
861
+ // A stale sync marker is only a cache hint; failure to remove it should not block auth repair.
862
+ }
863
+ }
864
+ async logoutLocalProfile(reason, status, cause) {
865
+ this.sessionToken = null;
866
+ this.structuredItemCache = null;
867
+ this.forgetLocalSyncMarker();
868
+ (0, runtime_1.emitNervesEvent)({
869
+ level: "warn",
870
+ event: "repertoire.bw_local_profile_rebuild",
871
+ component: "repertoire",
872
+ message: "rebuilding local bw profile for agent vault",
873
+ meta: {
874
+ email: this.email,
875
+ serverUrl: this.serverUrl,
876
+ reason,
877
+ /* v8 ignore next -- defensive: every profile rebuild path starts from a locked/unlocked bw status @preserve */
878
+ previousStatus: status.status ?? "unknown",
879
+ previousServerUrl: status.serverUrl ?? null,
880
+ previousUserEmail: status.userEmail ?? null,
881
+ /* v8 ignore next -- defensive: internal rebuild callers pass Error or undefined causes @preserve */
882
+ error: cause instanceof Error ? cause.message : cause ? String(cause) : undefined,
883
+ },
884
+ });
885
+ try {
886
+ await this.execBw(["logout"]);
887
+ }
888
+ catch (error) {
889
+ const err = error;
890
+ if (isBwLoggedOutOrUnauthenticatedError(err))
891
+ return;
892
+ (0, runtime_1.emitNervesEvent)({
893
+ level: "warn",
894
+ event: "repertoire.bw_local_profile_logout_failed",
895
+ component: "repertoire",
896
+ message: "failed to logout local bw profile before rebuild",
897
+ meta: { email: this.email, serverUrl: this.serverUrl, error: err.message },
898
+ });
899
+ }
900
+ }
901
+ async rebuildLocalProfile(reason, status, cause) {
902
+ await this.logoutLocalProfile(reason, status, cause);
903
+ await this.execBw(["config", "server", this.serverUrl]);
904
+ this.sessionToken = this.sessionTokenFromLoginOutput(await this.loginWithPassword());
905
+ }
906
+ async execWithLocalProfileRebuild(reason, status, operation) {
907
+ try {
908
+ return await operation();
909
+ }
910
+ catch (error) {
911
+ const err = error;
912
+ if (!isBwInvalidUnlockSecretMessage(err.message))
913
+ throw asLocalProfileFailure(err);
914
+ await this.rebuildLocalProfile(reason, status, err);
915
+ /* v8 ignore next -- defensive: rebuildLocalProfile always stores a string session token on success @preserve */
916
+ return this.sessionToken ?? "";
917
+ }
918
+ }
919
+ async ensureSession() {
920
+ if (!this.sessionToken) {
921
+ await this.login();
922
+ }
923
+ /* v8 ignore next -- defensive: login() always sets sessionToken on success @preserve */
924
+ return this.sessionToken ?? undefined;
925
+ }
926
+ async withSessionRetry(operation) {
927
+ let attemptedFreshSession = false;
928
+ while (true) {
929
+ const session = await this.ensureSession();
930
+ try {
931
+ return await operation(session);
932
+ }
933
+ catch (error) {
934
+ const err = error;
935
+ if (attemptedFreshSession || !isBwSessionAuthError(err)) {
936
+ throw err;
937
+ }
938
+ this.sessionToken = null;
939
+ this.structuredItemCache = null;
940
+ attemptedFreshSession = true;
941
+ }
942
+ }
943
+ }
944
+ async withTransientRetry(operation) {
945
+ let lastError;
946
+ for (let attempt = 0; attempt < TRANSIENT_MAX_RETRIES; attempt++) {
947
+ try {
948
+ return await operation();
949
+ }
950
+ catch (err) {
951
+ /* v8 ignore next -- defensive: operation always throws Error instances @preserve */
952
+ lastError = err instanceof Error ? err : new Error(String(err));
953
+ if (!isTransientError(lastError)) {
954
+ throw lastError;
955
+ }
956
+ if (attempt === TRANSIENT_MAX_RETRIES - 1)
957
+ break;
958
+ const backoffMs = TRANSIENT_RETRY_BASE_MS * Math.pow(2, attempt);
959
+ (0, runtime_1.emitNervesEvent)({
960
+ event: "repertoire.bw_transient_retry",
961
+ component: "repertoire",
962
+ message: `transient bw error, retrying in ${backoffMs}ms`,
963
+ meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
964
+ });
965
+ await delay(backoffMs);
966
+ }
967
+ }
968
+ throw lastError;
969
+ }
970
+ async get(domain) {
971
+ (0, runtime_1.emitNervesEvent)({
972
+ event: "repertoire.bw_credential_get_start",
973
+ component: "repertoire",
974
+ message: `getting credential via bw for ${domain}`,
975
+ meta: { domain, backend: "bitwarden" },
976
+ });
977
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
978
+ if (!item) {
979
+ (0, runtime_1.emitNervesEvent)({
980
+ event: "repertoire.bw_credential_get_end",
981
+ component: "repertoire",
982
+ message: `no bw credential for ${domain}`,
983
+ meta: { domain, found: false, backend: "bitwarden" },
984
+ });
985
+ return null;
986
+ }
987
+ (0, runtime_1.emitNervesEvent)({
988
+ event: "repertoire.bw_credential_get_end",
989
+ component: "repertoire",
990
+ message: `bw credential found for ${domain}`,
991
+ meta: { domain, found: true, backend: "bitwarden" },
992
+ });
993
+ return {
994
+ domain: item.name,
995
+ username: item.login?.username,
996
+ notes: item.notes ?? undefined,
997
+ createdAt: item.revisionDate ?? new Date().toISOString(),
998
+ };
999
+ }
1000
+ async getRawSecret(domain, field) {
1001
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session)));
1002
+ if (!item) {
1003
+ throw new Error(`no credential found for domain "${domain}"`);
1004
+ }
1005
+ // Map common field names to bw item structure
1006
+ let value;
1007
+ if (field === "password") {
1008
+ value = item.login?.password;
1009
+ }
1010
+ else if (field === "username") {
1011
+ value = item.login?.username;
1012
+ }
1013
+ else {
1014
+ value = item[field];
1015
+ }
1016
+ if (value === undefined || value === null) {
1017
+ throw new Error(`field "${field}" not found for domain "${domain}"`);
1018
+ }
1019
+ return String(value);
1020
+ }
1021
+ async store(domain, data) {
1022
+ (0, runtime_1.emitNervesEvent)({
1023
+ event: "repertoire.bw_credential_store_start",
1024
+ component: "repertoire",
1025
+ message: `storing credential via bw for ${domain}`,
1026
+ meta: { domain, backend: "bitwarden" },
1027
+ });
1028
+ await this.withSessionRetry(async (session) => {
1029
+ if (shouldUseStructuredItemLookup(domain)) {
1030
+ this.structuredItemCache = null;
1031
+ }
1032
+ const existing = await this.findItemByDomain(domain, session);
1033
+ const item = {
1034
+ ...(existing ?? {}),
1035
+ type: 1, // Login type
1036
+ name: domain,
1037
+ login: {
1038
+ username: data.username ?? "",
1039
+ password: data.password,
1040
+ uris: [{ uri: `https://${domain}` }],
1041
+ },
1042
+ notes: data.notes ?? null,
1043
+ };
1044
+ const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
1045
+ this.structuredItemCache = null;
1046
+ let savedItem;
1047
+ if (existing) {
1048
+ const stdout = await this.execBw(["edit", "item", existing.id], session, encoded);
1049
+ const savedItemId = parseBwItemId(stdout) ?? existing.id;
1050
+ savedItem = await this.findItemById(savedItemId, session);
1051
+ }
1052
+ else {
1053
+ const stdout = await this.execBw(["create", "item"], session, encoded);
1054
+ const savedItemId = parseBwItemId(stdout);
1055
+ savedItem = savedItemId
1056
+ ? await this.findItemById(savedItemId, session)
1057
+ : await this.findItemByDomain(domain, session);
1058
+ }
1059
+ this.assertStoredCredentialMatches(domain, data, savedItem);
1060
+ });
1061
+ this.structuredItemCache = null;
1062
+ (0, runtime_1.emitNervesEvent)({
1063
+ event: "repertoire.bw_credential_store_end",
1064
+ component: "repertoire",
1065
+ message: `credential stored via bw for ${domain}`,
1066
+ meta: { domain, backend: "bitwarden" },
1067
+ });
1068
+ }
1069
+ async list() {
1070
+ (0, runtime_1.emitNervesEvent)({
1071
+ event: "repertoire.bw_credential_list_start",
1072
+ component: "repertoire",
1073
+ message: "listing bw credentials",
1074
+ meta: { backend: "bitwarden" },
1075
+ });
1076
+ const stdout = await this.withTransientRetry(() => this.withSessionRetry((session) => this.execBw(["list", "items"], session)));
1077
+ const items = parseBwItems(stdout, "bw list items");
1078
+ const results = items.map((item) => ({
1079
+ domain: item.name,
1080
+ username: item.login?.username,
1081
+ notes: item.notes ?? undefined,
1082
+ createdAt: item.revisionDate ?? new Date().toISOString(),
1083
+ }));
1084
+ (0, runtime_1.emitNervesEvent)({
1085
+ event: "repertoire.bw_credential_list_end",
1086
+ component: "repertoire",
1087
+ message: "bw credentials listed",
1088
+ meta: { backend: "bitwarden", count: results.length },
1089
+ });
1090
+ return results;
1091
+ }
1092
+ async delete(domain) {
1093
+ (0, runtime_1.emitNervesEvent)({
1094
+ event: "repertoire.bw_credential_delete_start",
1095
+ component: "repertoire",
1096
+ message: `deleting credential via bw for ${domain}`,
1097
+ meta: { domain, backend: "bitwarden" },
1098
+ });
1099
+ const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
1100
+ if (!item) {
1101
+ (0, runtime_1.emitNervesEvent)({
1102
+ event: "repertoire.bw_credential_delete_end",
1103
+ component: "repertoire",
1104
+ message: `no bw credential to delete for ${domain}`,
1105
+ meta: { domain, deleted: false, backend: "bitwarden" },
1106
+ });
1107
+ return false;
1108
+ }
1109
+ await this.withSessionRetry((session) => this.execBw(["delete", "item", item.id], session));
1110
+ this.structuredItemCache = null;
1111
+ (0, runtime_1.emitNervesEvent)({
1112
+ event: "repertoire.bw_credential_delete_end",
1113
+ component: "repertoire",
1114
+ message: `credential deleted via bw for ${domain}`,
1115
+ meta: { domain, deleted: true, backend: "bitwarden" },
1116
+ });
1117
+ return true;
1118
+ }
1119
+ // --- Private ---
1120
+ async findItemByDomain(domain, session) {
1121
+ if (shouldUseStructuredItemLookup(domain)) {
1122
+ const cache = await this.readStructuredItemCache(session);
1123
+ if (cache.malformedNames.has(domain)) {
1124
+ throw new Error("bw CLI error: invalid item from bw list items");
1125
+ }
1126
+ const item = cache.items.get(domain);
1127
+ if (item)
1128
+ return item;
1129
+ return null;
1130
+ }
1131
+ const stdout = await this.execBw(["list", "items", "--search", domain], session);
1132
+ const items = parseBwItems(stdout, "bw list items --search");
1133
+ // Find exact match by name
1134
+ return items.find((item) => item.name === domain) ?? null;
1135
+ }
1136
+ shouldSyncVaultAfterSession(status) {
1137
+ if (status.status === "unauthenticated" || !status.status)
1138
+ return true;
1139
+ if (!this.appDataDir)
1140
+ return true;
1141
+ const freshnessTimestamp = this.latestLocalSyncTimestamp();
1142
+ if (freshnessTimestamp !== null) {
1143
+ return Date.now() - freshnessTimestamp > BW_SYNC_FRESH_MS;
1144
+ }
1145
+ return true;
1146
+ }
1147
+ latestLocalSyncTimestamp() {
1148
+ const files = [BW_SYNC_MARKER_FILENAME, BW_DATA_FILENAME];
1149
+ let latest = null;
1150
+ for (const name of files) {
1151
+ try {
1152
+ const mtimeMs = fs.statSync(path.join(this.appDataDir, name)).mtimeMs;
1153
+ latest = latest === null ? mtimeMs : Math.max(latest, mtimeMs);
1154
+ }
1155
+ catch {
1156
+ // Missing freshness file is fine; use any other available timestamp.
1157
+ }
1158
+ }
1159
+ return latest;
1160
+ }
1161
+ writeSyncMarker() {
1162
+ if (!this.appDataDir)
1163
+ return;
1164
+ try {
1165
+ fs.writeFileSync(path.join(this.appDataDir, BW_SYNC_MARKER_FILENAME), `${Date.now()}\n`, { mode: 0o600 });
1166
+ }
1167
+ catch {
1168
+ // If the marker cannot be written, fall back to syncing next time.
1169
+ }
1170
+ }
1171
+ async findItemById(id, session) {
1172
+ const stdout = await this.execBw(["get", "item", id], session);
1173
+ return parseBwItem(stdout, "bw get item");
1174
+ }
1175
+ async readStructuredItemCache(session) {
1176
+ if (this.structuredItemCache)
1177
+ return this.structuredItemCache;
1178
+ const stdout = await this.execBw(["list", "items"], session);
1179
+ this.structuredItemCache = parseStructuredBwItems(stdout, "bw list items");
1180
+ return this.structuredItemCache;
1181
+ }
1182
+ assertStoredCredentialMatches(domain, data, item) {
1183
+ if (!item) {
1184
+ throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item could not be read back after write`);
1185
+ }
1186
+ const mismatches = [];
1187
+ if (item.name !== domain)
1188
+ mismatches.push("name");
1189
+ if ((item.login?.username ?? "") !== (data.username ?? ""))
1190
+ mismatches.push("username");
1191
+ if ((item.login?.password ?? "") !== data.password)
1192
+ mismatches.push("password");
1193
+ if ((item.notes ?? null) !== (data.notes ?? null))
1194
+ mismatches.push("notes");
1195
+ if (mismatches.length > 0) {
1196
+ const label = mismatches.length === 1 ? "field" : "fields";
1197
+ throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item did not match requested ${label} ${mismatches.join(", ")}`);
1198
+ }
1199
+ }
1200
+ }
1201
+ exports.BitwardenCredentialStore = BitwardenCredentialStore;