@ouro.bot/cli 0.1.0-alpha.55 → 0.1.0-alpha.551

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 (386) hide show
  1. package/README.md +133 -19
  2. package/RepairGuide.ouro/agent.json +5 -0
  3. package/RepairGuide.ouro/psyche/IDENTITY.md +19 -0
  4. package/RepairGuide.ouro/psyche/SOUL.md +55 -0
  5. package/RepairGuide.ouro/skills/diagnose-bootstrap-drift.md +54 -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/changelog.json +3561 -0
  14. package/dist/arc/attention-types.js +8 -0
  15. package/dist/arc/cares.js +140 -0
  16. package/dist/arc/episodes.js +117 -0
  17. package/dist/arc/intentions.js +133 -0
  18. package/dist/arc/json-store.js +117 -0
  19. package/dist/arc/obligations.js +237 -0
  20. package/dist/arc/packets.js +193 -0
  21. package/dist/arc/presence.js +185 -0
  22. package/dist/arc/task-lifecycle.js +65 -0
  23. package/dist/heart/active-work.js +837 -26
  24. package/dist/heart/agent-entry.js +58 -3
  25. package/dist/heart/attachments/image-normalize.js +194 -0
  26. package/dist/heart/attachments/materialize.js +97 -0
  27. package/dist/heart/attachments/originals.js +88 -0
  28. package/dist/heart/attachments/render.js +29 -0
  29. package/dist/heart/attachments/sources/adapter.js +2 -0
  30. package/dist/heart/attachments/sources/bluebubbles.js +156 -0
  31. package/dist/heart/attachments/sources/cli-local-file.js +78 -0
  32. package/dist/heart/attachments/sources/index.js +16 -0
  33. package/dist/heart/attachments/store.js +103 -0
  34. package/dist/heart/attachments/types.js +93 -0
  35. package/dist/heart/auth/auth-flow.js +479 -0
  36. package/dist/heart/background-operations.js +281 -0
  37. package/dist/heart/bundle-state.js +168 -0
  38. package/dist/heart/commitments.js +111 -0
  39. package/dist/heart/config-registry.js +304 -0
  40. package/dist/heart/config.js +114 -118
  41. package/dist/heart/core.js +925 -246
  42. package/dist/heart/cross-chat-delivery.js +3 -18
  43. package/dist/heart/daemon/agent-config-check.js +512 -0
  44. package/dist/heart/daemon/agent-discovery.js +102 -3
  45. package/dist/heart/daemon/agent-service.js +522 -0
  46. package/dist/heart/daemon/agentic-repair.js +554 -0
  47. package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
  48. package/dist/heart/daemon/boot-sync-probe.js +197 -0
  49. package/dist/heart/daemon/cadence.js +70 -0
  50. package/dist/heart/daemon/cli-defaults.js +665 -0
  51. package/dist/heart/daemon/cli-exec.js +7565 -0
  52. package/dist/heart/daemon/cli-help.js +498 -0
  53. package/dist/heart/daemon/cli-parse.js +1590 -0
  54. package/dist/heart/daemon/cli-render-doctor.js +57 -0
  55. package/dist/heart/daemon/cli-render.js +775 -0
  56. package/dist/heart/daemon/cli-types.js +8 -0
  57. package/dist/heart/daemon/connect-bay.js +323 -0
  58. package/dist/heart/daemon/daemon-cli.js +29 -1672
  59. package/dist/heart/daemon/daemon-entry.js +417 -2
  60. package/dist/heart/daemon/daemon-health.js +183 -0
  61. package/dist/heart/daemon/daemon-rollup.js +58 -0
  62. package/dist/heart/daemon/daemon-runtime-sync.js +87 -13
  63. package/dist/heart/daemon/daemon-tombstone.js +236 -0
  64. package/dist/heart/daemon/daemon.js +796 -71
  65. package/dist/heart/daemon/dns-workflow.js +394 -0
  66. package/dist/heart/daemon/doctor-types.js +8 -0
  67. package/dist/heart/daemon/doctor.js +844 -0
  68. package/dist/heart/daemon/drift-detection.js +146 -0
  69. package/dist/heart/daemon/health-monitor.js +122 -1
  70. package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
  71. package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
  72. package/dist/heart/daemon/http-health-probe.js +80 -0
  73. package/dist/heart/daemon/human-command-screens.js +234 -0
  74. package/dist/heart/daemon/human-readiness.js +114 -0
  75. package/dist/heart/daemon/inner-status.js +102 -0
  76. package/dist/heart/daemon/interactive-repair.js +394 -0
  77. package/dist/heart/daemon/launchd.js +37 -8
  78. package/dist/heart/daemon/log-tailer.js +82 -12
  79. package/dist/heart/daemon/logs-prune.js +110 -0
  80. package/dist/heart/daemon/mcp-canary.js +297 -0
  81. package/dist/heart/daemon/message-router.js +2 -2
  82. package/dist/heart/daemon/os-cron-deps.js +135 -0
  83. package/dist/heart/daemon/os-cron.js +14 -12
  84. package/dist/heart/daemon/ouro-bot-entry.js +4 -2
  85. package/dist/heart/daemon/ouro-entry.js +3 -1
  86. package/dist/heart/daemon/process-manager.js +375 -33
  87. package/dist/heart/daemon/provider-discovery.js +137 -0
  88. package/dist/heart/daemon/provider-ping-progress.js +83 -0
  89. package/dist/heart/daemon/pulse.js +475 -0
  90. package/dist/heart/daemon/readiness-repair.js +365 -0
  91. package/dist/heart/daemon/run-hooks.js +2 -0
  92. package/dist/heart/daemon/runtime-logging.js +67 -16
  93. package/dist/heart/daemon/runtime-metadata.js +3 -31
  94. package/dist/heart/daemon/safe-mode.js +161 -0
  95. package/dist/heart/daemon/sense-manager.js +353 -38
  96. package/dist/heart/daemon/session-id-resolver.js +131 -0
  97. package/dist/heart/daemon/skill-management-installer.js +94 -0
  98. package/dist/heart/daemon/socket-client.js +158 -11
  99. package/dist/heart/daemon/stale-bundle-prune.js +96 -0
  100. package/dist/heart/daemon/startup-tui.js +330 -0
  101. package/dist/heart/daemon/task-scheduler.js +3 -25
  102. package/dist/heart/daemon/terminal-ui.js +499 -0
  103. package/dist/heart/daemon/thoughts.js +162 -17
  104. package/dist/heart/daemon/up-progress.js +366 -0
  105. package/dist/heart/daemon/vault-items.js +56 -0
  106. package/dist/heart/delegation.js +1 -1
  107. package/dist/heart/habits/habit-migration.js +189 -0
  108. package/dist/heart/habits/habit-parser.js +140 -0
  109. package/dist/heart/habits/habit-runtime-state.js +100 -0
  110. package/dist/heart/habits/habit-scheduler.js +372 -0
  111. package/dist/heart/{daemon → hatch}/hatch-flow.js +52 -117
  112. package/dist/heart/{daemon → hatch}/hatch-specialist.js +6 -8
  113. package/dist/heart/{daemon → hatch}/specialist-prompt.js +12 -9
  114. package/dist/heart/{daemon → hatch}/specialist-tools.js +35 -12
  115. package/dist/heart/identity.js +200 -51
  116. package/dist/heart/kept-notes.js +357 -0
  117. package/dist/heart/kicks.js +1 -1
  118. package/dist/heart/machine-identity.js +161 -0
  119. package/dist/heart/mail-import-discovery.js +353 -0
  120. package/dist/heart/mailbox/mailbox-http-hooks.js +66 -0
  121. package/dist/heart/mailbox/mailbox-http-response.js +7 -0
  122. package/dist/heart/mailbox/mailbox-http-routes.js +246 -0
  123. package/dist/heart/mailbox/mailbox-http-static.js +103 -0
  124. package/dist/heart/mailbox/mailbox-http-transport.js +116 -0
  125. package/dist/heart/mailbox/mailbox-http.js +99 -0
  126. package/dist/heart/mailbox/mailbox-read.js +31 -0
  127. package/dist/heart/mailbox/mailbox-types.js +27 -0
  128. package/dist/heart/mailbox/mailbox-view.js +195 -0
  129. package/dist/heart/mailbox/readers/agent-machine.js +382 -0
  130. package/dist/heart/mailbox/readers/continuity-readers.js +338 -0
  131. package/dist/heart/mailbox/readers/mail.js +362 -0
  132. package/dist/heart/mailbox/readers/runtime-readers.js +651 -0
  133. package/dist/heart/mailbox/readers/sessions.js +232 -0
  134. package/dist/heart/mailbox/readers/shared.js +111 -0
  135. package/dist/heart/mcp/mcp-server.js +683 -0
  136. package/dist/heart/migrate-config.js +100 -0
  137. package/dist/heart/model-capabilities.js +19 -0
  138. package/dist/heart/platform.js +81 -0
  139. package/dist/heart/provider-attempt.js +134 -0
  140. package/dist/heart/provider-binding-resolver.js +255 -0
  141. package/dist/heart/provider-credentials.js +425 -0
  142. package/dist/heart/provider-failover.js +301 -0
  143. package/dist/heart/provider-models.js +81 -0
  144. package/dist/heart/provider-ping.js +262 -0
  145. package/dist/heart/provider-state.js +216 -0
  146. package/dist/heart/provider-visibility.js +188 -0
  147. package/dist/heart/providers/anthropic-token.js +131 -0
  148. package/dist/heart/providers/anthropic.js +139 -52
  149. package/dist/heart/providers/azure.js +97 -13
  150. package/dist/heart/providers/error-classification.js +127 -0
  151. package/dist/heart/providers/github-copilot.js +145 -0
  152. package/dist/heart/providers/minimax-vlm.js +189 -0
  153. package/dist/heart/providers/minimax.js +26 -8
  154. package/dist/heart/providers/openai-codex.js +55 -40
  155. package/dist/heart/runtime-capability-check.js +170 -0
  156. package/dist/heart/runtime-credentials.js +367 -0
  157. package/dist/heart/runtime-cwd.js +87 -0
  158. package/dist/heart/sense-truth.js +11 -4
  159. package/dist/heart/session-activity.js +43 -22
  160. package/dist/heart/session-events.js +1149 -0
  161. package/dist/heart/session-playback-cli-main.js +5 -0
  162. package/dist/heart/session-playback-cli.js +36 -0
  163. package/dist/heart/session-playback.js +231 -0
  164. package/dist/heart/session-stats-cli-main.js +5 -0
  165. package/dist/heart/session-stats.js +182 -0
  166. package/dist/heart/session-transcript.js +243 -0
  167. package/dist/heart/start-of-turn-packet.js +345 -0
  168. package/dist/heart/streaming.js +44 -27
  169. package/dist/heart/sync-classification.js +176 -0
  170. package/dist/heart/sync.js +449 -0
  171. package/dist/heart/target-resolution.js +9 -5
  172. package/dist/heart/tempo.js +93 -0
  173. package/dist/heart/temporal-view.js +41 -0
  174. package/dist/heart/timeouts.js +101 -0
  175. package/dist/heart/tool-activity-callbacks.js +59 -0
  176. package/dist/heart/tool-description.js +139 -0
  177. package/dist/heart/tool-friction.js +55 -0
  178. package/dist/heart/tool-loop.js +200 -0
  179. package/dist/heart/turn-context.js +381 -0
  180. package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +6 -5
  181. package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
  182. package/dist/heart/versioning/ouro-path-installer.js +426 -0
  183. package/dist/heart/versioning/ouro-version-manager.js +295 -0
  184. package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
  185. package/dist/heart/{daemon → versioning}/update-checker.js +6 -1
  186. package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
  187. package/dist/mailbox-ui/assets/index-BPr5vNuM.css +1 -0
  188. package/dist/mailbox-ui/assets/index-Cm51CY9W.js +61 -0
  189. package/dist/mailbox-ui/index.html +15 -0
  190. package/dist/mailroom/attention.js +167 -0
  191. package/dist/mailroom/autonomy.js +209 -0
  192. package/dist/mailroom/blob-store.js +674 -0
  193. package/dist/mailroom/body-cache.js +61 -0
  194. package/dist/mailroom/core.js +720 -0
  195. package/dist/mailroom/entry.js +160 -0
  196. package/dist/mailroom/file-store.js +430 -0
  197. package/dist/mailroom/mbox-import.js +383 -0
  198. package/dist/mailroom/outbound.js +380 -0
  199. package/dist/mailroom/policy.js +263 -0
  200. package/dist/mailroom/reader.js +233 -0
  201. package/dist/mailroom/search-cache.js +256 -0
  202. package/dist/mailroom/search-relevance.js +319 -0
  203. package/dist/mailroom/smtp-ingress.js +176 -0
  204. package/dist/mailroom/source-state.js +176 -0
  205. package/dist/mailroom/thread.js +109 -0
  206. package/dist/mailroom/travel-extract.js +89 -0
  207. package/dist/mind/bundle-manifest.js +7 -1
  208. package/dist/mind/context.js +165 -101
  209. package/dist/mind/diary-integrity.js +60 -0
  210. package/dist/mind/{memory.js → diary.js} +62 -75
  211. package/dist/mind/embedding-provider.js +60 -0
  212. package/dist/mind/file-state.js +179 -0
  213. package/dist/mind/friends/channel.js +30 -0
  214. package/dist/mind/friends/resolver.js +54 -2
  215. package/dist/mind/friends/store-file.js +39 -3
  216. package/dist/mind/friends/types.js +2 -2
  217. package/dist/mind/journal-index.js +161 -0
  218. package/dist/mind/note-search.js +268 -0
  219. package/dist/mind/obligation-steering.js +221 -0
  220. package/dist/mind/pending.js +4 -0
  221. package/dist/mind/prompt-refresh.js +3 -2
  222. package/dist/mind/prompt.js +995 -123
  223. package/dist/mind/provenance-trust.js +26 -0
  224. package/dist/mind/scrutiny.js +173 -0
  225. package/dist/nerves/cli-logging.js +7 -1
  226. package/dist/nerves/coverage/audit-rules.js +15 -6
  227. package/dist/nerves/coverage/audit.js +28 -2
  228. package/dist/nerves/coverage/cli.js +1 -1
  229. package/dist/nerves/coverage/contract.js +5 -5
  230. package/dist/nerves/coverage/file-completeness.js +139 -5
  231. package/dist/nerves/coverage/run-artifacts.js +1 -1
  232. package/dist/nerves/event-buffer.js +111 -0
  233. package/dist/nerves/index.js +224 -4
  234. package/dist/nerves/observation.js +20 -0
  235. package/dist/nerves/redact.js +79 -0
  236. package/dist/nerves/review/cli-main.js +5 -0
  237. package/dist/nerves/review/cli.js +156 -0
  238. package/dist/nerves/review/core.js +152 -0
  239. package/dist/nerves/runtime.js +5 -1
  240. package/dist/repertoire/ado-client.js +15 -56
  241. package/dist/repertoire/ado-semantic.js +11 -10
  242. package/dist/repertoire/api-client.js +97 -0
  243. package/dist/repertoire/bitwarden-store.js +816 -0
  244. package/dist/repertoire/bundle-templates.js +72 -0
  245. package/dist/repertoire/bw-installer.js +180 -0
  246. package/dist/repertoire/coding/codex-jsonl.js +64 -0
  247. package/dist/repertoire/coding/context-pack.js +330 -0
  248. package/dist/repertoire/coding/feedback.js +197 -30
  249. package/dist/repertoire/coding/manager.js +158 -9
  250. package/dist/repertoire/coding/spawner.js +55 -9
  251. package/dist/repertoire/coding/tools.js +170 -7
  252. package/dist/repertoire/commerce-errors.js +109 -0
  253. package/dist/repertoire/commerce-self-test.js +156 -0
  254. package/dist/repertoire/credential-access.js +111 -0
  255. package/dist/repertoire/duffel-client.js +185 -0
  256. package/dist/repertoire/github-client.js +14 -55
  257. package/dist/repertoire/graph-client.js +11 -52
  258. package/dist/repertoire/guardrails.js +396 -0
  259. package/dist/repertoire/mcp-client.js +295 -0
  260. package/dist/repertoire/mcp-manager.js +362 -0
  261. package/dist/repertoire/mcp-tools.js +63 -0
  262. package/dist/repertoire/shell-sessions.js +133 -0
  263. package/dist/repertoire/skills.js +15 -24
  264. package/dist/repertoire/stripe-client.js +131 -0
  265. package/dist/repertoire/tasks/board.js +31 -5
  266. package/dist/repertoire/tasks/fix.js +182 -0
  267. package/dist/repertoire/tasks/index.js +16 -4
  268. package/dist/repertoire/tasks/lifecycle.js +2 -2
  269. package/dist/repertoire/tasks/parser.js +3 -2
  270. package/dist/repertoire/tasks/scanner.js +194 -37
  271. package/dist/repertoire/tasks/transitions.js +16 -78
  272. package/dist/repertoire/tool-results.js +29 -0
  273. package/dist/repertoire/tools-attachments.js +317 -0
  274. package/dist/repertoire/tools-base.js +47 -1075
  275. package/dist/repertoire/tools-bluebubbles.js +1 -0
  276. package/dist/repertoire/tools-bridge.js +142 -0
  277. package/dist/repertoire/tools-bundle.js +984 -0
  278. package/dist/repertoire/tools-config.js +185 -0
  279. package/dist/repertoire/tools-continuity.js +248 -0
  280. package/dist/repertoire/tools-credential.js +381 -0
  281. package/dist/repertoire/tools-files.js +342 -0
  282. package/dist/repertoire/tools-flight.js +224 -0
  283. package/dist/repertoire/tools-flow.js +119 -0
  284. package/dist/repertoire/tools-github.js +1 -7
  285. package/dist/repertoire/tools-mail.js +1857 -0
  286. package/dist/repertoire/tools-notes.js +421 -0
  287. package/dist/repertoire/tools-session.js +750 -0
  288. package/dist/repertoire/tools-shell.js +120 -0
  289. package/dist/repertoire/tools-stripe.js +180 -0
  290. package/dist/repertoire/tools-surface.js +243 -0
  291. package/dist/repertoire/tools-teams.js +9 -39
  292. package/dist/repertoire/tools-travel.js +125 -0
  293. package/dist/repertoire/tools-trip.js +604 -0
  294. package/dist/repertoire/tools-user-profile.js +144 -0
  295. package/dist/repertoire/tools-vault.js +40 -0
  296. package/dist/repertoire/tools.js +108 -100
  297. package/dist/repertoire/travel-api-client.js +360 -0
  298. package/dist/repertoire/user-profile.js +131 -0
  299. package/dist/repertoire/vault-setup.js +246 -0
  300. package/dist/repertoire/vault-unlock.js +561 -0
  301. package/dist/scripts/claude-code-hook.js +41 -0
  302. package/dist/scripts/claude-code-stop-hook.js +47 -0
  303. package/dist/senses/attention-queue.js +116 -0
  304. package/dist/senses/bluebubbles/active-turns.js +216 -0
  305. package/dist/senses/bluebubbles/attachment-cache.js +53 -0
  306. package/dist/senses/bluebubbles/attachment-download.js +137 -0
  307. package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +219 -18
  308. package/dist/senses/bluebubbles/entry.js +77 -0
  309. package/dist/senses/{bluebubbles-inbound-log.js → bluebubbles/inbound-log.js} +20 -3
  310. package/dist/senses/bluebubbles/index.js +2305 -0
  311. package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
  312. package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +33 -12
  313. package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +3 -3
  314. package/dist/senses/bluebubbles/processed-log.js +133 -0
  315. package/dist/senses/bluebubbles/replay.js +137 -0
  316. package/dist/senses/{bluebubbles-runtime-state.js → bluebubbles/runtime-state.js} +30 -2
  317. package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
  318. package/dist/senses/cli/bracketed-paste.js +82 -0
  319. package/dist/senses/cli/image-paste.js +287 -0
  320. package/dist/senses/cli/image-ref-navigation.js +75 -0
  321. package/dist/senses/cli/ink-app.js +156 -0
  322. package/dist/senses/cli/inline-diff.js +64 -0
  323. package/dist/senses/cli/input-keys.js +174 -0
  324. package/dist/senses/cli/kill-ring.js +86 -0
  325. package/dist/senses/cli/message-list.js +51 -0
  326. package/dist/senses/cli/ouro-tui.js +607 -0
  327. package/dist/senses/cli/spinner-imperative.js +135 -0
  328. package/dist/senses/cli/spinner.js +101 -0
  329. package/dist/senses/cli/status-line.js +60 -0
  330. package/dist/senses/cli/streaming-markdown.js +526 -0
  331. package/dist/senses/cli/tool-display.js +85 -0
  332. package/dist/senses/cli/tool-render.js +85 -0
  333. package/dist/senses/cli/tui-store.js +240 -0
  334. package/dist/senses/cli/virtual-list.js +35 -0
  335. package/dist/senses/cli-entry.js +60 -8
  336. package/dist/senses/cli-layout.js +187 -0
  337. package/dist/senses/cli.js +520 -209
  338. package/dist/senses/commands.js +66 -3
  339. package/dist/senses/habit-turn-message.js +108 -0
  340. package/dist/senses/inner-dialog-worker.js +175 -21
  341. package/dist/senses/inner-dialog.js +330 -27
  342. package/dist/senses/mail-entry.js +66 -0
  343. package/dist/senses/mail.js +379 -0
  344. package/dist/senses/pipeline.js +569 -182
  345. package/dist/senses/proactive-content-guard.js +51 -0
  346. package/dist/senses/shared-turn.js +248 -0
  347. package/dist/senses/surface-tool.js +68 -0
  348. package/dist/senses/teams-entry.js +60 -8
  349. package/dist/senses/teams.js +387 -98
  350. package/dist/senses/trust-gate.js +100 -5
  351. package/dist/trips/core.js +138 -0
  352. package/dist/trips/store.js +146 -0
  353. package/package.json +38 -7
  354. package/skills/agent-commerce.md +106 -0
  355. package/skills/browser-navigation.md +117 -0
  356. package/skills/commerce-setup-guide.md +116 -0
  357. package/skills/commerce-setup.md +84 -0
  358. package/skills/configure-dev-tools.md +101 -0
  359. package/skills/travel-planning.md +138 -0
  360. package/dist/heart/daemon/ouro-path-installer.js +0 -178
  361. package/dist/heart/daemon/subagent-installer.js +0 -166
  362. package/dist/heart/session-recall.js +0 -116
  363. package/dist/mind/associative-recall.js +0 -209
  364. package/dist/senses/bluebubbles-entry.js +0 -13
  365. package/dist/senses/bluebubbles.js +0 -1177
  366. package/dist/senses/debug-activity.js +0 -148
  367. package/subagents/README.md +0 -86
  368. package/subagents/work-doer.md +0 -237
  369. package/subagents/work-merger.md +0 -618
  370. package/subagents/work-planner.md +0 -390
  371. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
  372. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
  373. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
  374. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
  375. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
  376. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
  377. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
  378. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
  379. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
  380. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
  381. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
  382. /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
  383. /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
  384. /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
  385. /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
  386. /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
@@ -0,0 +1,816 @@
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
+ const node_child_process_1 = require("node:child_process");
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ const runtime_1 = require("../nerves/runtime");
50
+ const bw_installer_1 = require("./bw-installer");
51
+ const MAX_ERROR_DETAIL_LENGTH = 500;
52
+ const LONG_ENCODED_TOKEN_PATTERN = /[A-Za-z0-9+/=]{32,}/g;
53
+ const BW_PASSWORD_ENV = "OURO_BW_MASTER_PASSWORD";
54
+ function uniqueSecrets(secrets) {
55
+ return [...new Set(secrets.filter((value) => typeof value === "string" && value.length >= 4))].sort((left, right) => right.length - left.length);
56
+ }
57
+ function sanitizeCredentialErrorDetail(message, options = {}) {
58
+ const filtered = message
59
+ .split(/\r?\n/)
60
+ .filter((line) => {
61
+ const trimmed = line.trim();
62
+ if (trimmed.startsWith("Command failed:"))
63
+ return false;
64
+ if (trimmed.includes("[input is hidden]"))
65
+ return false;
66
+ return true;
67
+ })
68
+ .join("\n")
69
+ .trim();
70
+ let sanitized = filtered || "command failed";
71
+ for (const secret of uniqueSecrets(options.secrets ?? [])) {
72
+ sanitized = sanitized.split(secret).join("[redacted]");
73
+ }
74
+ sanitized = sanitized.replace(LONG_ENCODED_TOKEN_PATTERN, "[redacted]");
75
+ if (sanitized.replace(/\[redacted\]/g, "").trim().length === 0) {
76
+ return "command failed";
77
+ }
78
+ return sanitized.slice(0, MAX_ERROR_DETAIL_LENGTH);
79
+ }
80
+ // ---------------------------------------------------------------------------
81
+ // bw CLI wrapper
82
+ // ---------------------------------------------------------------------------
83
+ function isBwSessionUnavailableMessage(message) {
84
+ return (/master password/i.test(message) ||
85
+ /vault is locked/i.test(message) ||
86
+ /not logged in/i.test(message) ||
87
+ /session key/i.test(message) ||
88
+ /local bitwarden session/i.test(message));
89
+ }
90
+ function isBwInvalidUnlockSecretMessage(message) {
91
+ return /invalid master password/i.test(message) || /saved vault unlock secret/i.test(message);
92
+ }
93
+ function isBwTimeoutError(err) {
94
+ const timeoutErr = err;
95
+ const message = err.message.toLowerCase();
96
+ return (timeoutErr.code === "ETIMEDOUT" ||
97
+ timeoutErr.killed === true ||
98
+ timeoutErr.signal === "SIGTERM" ||
99
+ message.includes("timed out"));
100
+ }
101
+ function formatBwOperation(args) {
102
+ const [command, target] = args;
103
+ /* v8 ignore next -- defensive: all execBw call sites pass a concrete bw subcommand @preserve */
104
+ if (!command)
105
+ return "bw command";
106
+ return [command, target].filter(Boolean).join(" ");
107
+ }
108
+ function sanitizeBwErrorDetail(message) {
109
+ if (isBwInvalidUnlockSecretMessage(message)) {
110
+ return "bw CLI rejected the saved vault unlock secret for this machine";
111
+ }
112
+ if (isBwSessionUnavailableMessage(message)) {
113
+ return "bw CLI could not use the local Bitwarden session because it is locked, missing, or expired";
114
+ }
115
+ return sanitizeCredentialErrorDetail(message);
116
+ }
117
+ function formatBwCliError(err, stderr = "", args = []) {
118
+ const operation = formatBwOperation(args);
119
+ if (isBwTimeoutError(err)) {
120
+ return new Error(`bw CLI error: ${operation} timed out -- usually resolves on retry. If it persists, check network connectivity to the vault server.`);
121
+ }
122
+ const detail = sanitizeBwErrorDetail(stderr.trim() || err.message);
123
+ if (detail === "command failed") {
124
+ return new Error(`bw CLI error: ${operation} failed without error detail`);
125
+ }
126
+ return new Error(`bw CLI error: ${detail}`);
127
+ }
128
+ function isBwSessionAuthError(err) {
129
+ return isBwSessionUnavailableMessage(err.message) || isBwInvalidUnlockSecretMessage(err.message);
130
+ }
131
+ function isBwConfigLogoutRequired(err) {
132
+ const message = err.message.toLowerCase();
133
+ return message.includes("logout") && message.includes("required");
134
+ }
135
+ function isBwAlreadyLoggedInError(err) {
136
+ return err.message.toLowerCase().includes("already logged in");
137
+ }
138
+ function shouldUseStructuredItemLookup(domain) {
139
+ return domain.includes("/");
140
+ }
141
+ function shouldUseFullListForStructuredLookup(domain, appDataDir) {
142
+ return domain.includes("/") && !appDataDir;
143
+ }
144
+ function isBwItemNotFoundError(error) {
145
+ const message = error.message.toLowerCase();
146
+ return message.includes("not found") || message.includes("no item");
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Cross-process bw CLI lock
150
+ // ---------------------------------------------------------------------------
151
+ // The bw CLI cannot handle concurrent access to the same app data directory.
152
+ // Two processes (e.g. daemon worker + ouro up CLI) hitting the same dir
153
+ // simultaneously corrupt bw's local state, producing empty/garbled output.
154
+ //
155
+ // We use two layers:
156
+ // 1. In-process async mutex: a Map<string, Promise<void>> keyed by appDataDir
157
+ // serializes calls within a single Node.js process.
158
+ // 2. Cross-process file lock: fs.openSync(lockPath, 'wx') with PID stale
159
+ // detection serializes across processes.
160
+ // ---------------------------------------------------------------------------
161
+ const BW_LOCK_FILENAME = ".ouro-bw.lock";
162
+ const BW_LOCK_TIMEOUT_MS = 30_000;
163
+ const BW_LOCK_POLL_MS = 100;
164
+ const BW_DATA_FILENAME = "data.json";
165
+ const BW_SYNC_MARKER_FILENAME = ".ouro-last-sync";
166
+ const BW_SYNC_FRESH_MS = 60_000;
167
+ /** In-process async mutex keyed by appDataDir. */
168
+ const inProcessLocks = new Map();
169
+ function isPidAlive(pid) {
170
+ try {
171
+ process.kill(pid, 0);
172
+ return true;
173
+ }
174
+ catch {
175
+ return false;
176
+ }
177
+ }
178
+ async function acquireFileLock(lockPath) {
179
+ const content = `${process.pid}\n`;
180
+ const deadline = Date.now() + BW_LOCK_TIMEOUT_MS;
181
+ while (true) {
182
+ try {
183
+ const fd = fs.openSync(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL);
184
+ fs.writeSync(fd, content);
185
+ fs.closeSync(fd);
186
+ return;
187
+ }
188
+ catch (err) {
189
+ if (err.code !== "EEXIST") {
190
+ throw err;
191
+ }
192
+ // Lock file exists -- check for stale lock
193
+ try {
194
+ const existing = fs.readFileSync(lockPath, "utf8").trim();
195
+ const pid = parseInt(existing, 10);
196
+ if (!isNaN(pid) && !isPidAlive(pid)) {
197
+ // Stale lock -- remove and retry immediately
198
+ try {
199
+ fs.unlinkSync(lockPath);
200
+ }
201
+ catch { /* race with another cleaner is fine */ }
202
+ continue;
203
+ }
204
+ }
205
+ catch { /* v8 ignore next -- race: lock file disappeared between openSync and readFileSync @preserve */
206
+ continue;
207
+ }
208
+ if (Date.now() >= deadline) {
209
+ throw new Error(`bw CLI lock timeout: could not acquire ${lockPath} within ${BW_LOCK_TIMEOUT_MS}ms`);
210
+ }
211
+ // Yield to the event loop before retrying
212
+ await delay(BW_LOCK_POLL_MS);
213
+ }
214
+ }
215
+ }
216
+ function releaseFileLock(lockPath) {
217
+ try {
218
+ fs.unlinkSync(lockPath);
219
+ }
220
+ catch {
221
+ // Already removed or never created -- safe to ignore
222
+ }
223
+ }
224
+ async function withBwLock(appDataDir, fn) {
225
+ if (!appDataDir) {
226
+ // No appDataDir means the default bw data location. Still need in-process
227
+ // serialization but cannot do cross-process file lock without a dir.
228
+ return fn();
229
+ }
230
+ const lockKey = appDataDir;
231
+ const lockPath = path.join(appDataDir, BW_LOCK_FILENAME);
232
+ // In-process serialization: chain onto the previous promise for this key
233
+ const previous = inProcessLocks.get(lockKey) ?? Promise.resolve();
234
+ let releaseLock;
235
+ const current = new Promise((resolve) => { releaseLock = resolve; });
236
+ inProcessLocks.set(lockKey, current);
237
+ await previous;
238
+ let fileLockAcquired = false;
239
+ try {
240
+ // Cross-process file lock
241
+ await acquireFileLock(lockPath);
242
+ fileLockAcquired = true;
243
+ return await fn();
244
+ }
245
+ finally {
246
+ if (fileLockAcquired) {
247
+ releaseFileLock(lockPath);
248
+ }
249
+ releaseLock();
250
+ // Clean up the map entry if we are the latest
251
+ if (inProcessLocks.get(lockKey) === current) {
252
+ inProcessLocks.delete(lockKey);
253
+ }
254
+ }
255
+ }
256
+ function execBw(args, sessionToken, appDataDir, stdin, bwBinaryPath = "bw", extraEnv = {}) {
257
+ const env = {
258
+ ...process.env,
259
+ ...(sessionToken ? { BW_SESSION: sessionToken } : {}),
260
+ ...(appDataDir ? { BITWARDENCLI_APPDATA_DIR: appDataDir } : {}),
261
+ ...extraEnv,
262
+ };
263
+ const runCommand = () => new Promise((resolve, reject) => {
264
+ const child = (0, node_child_process_1.execFile)(bwBinaryPath, args, { timeout: 30_000, env }, (err, stdout, stderr) => {
265
+ if (err) {
266
+ if (isBwNotInstalled(err)) {
267
+ reject(new Error("bw CLI not found. Install from https://bitwarden.com/help/cli/"));
268
+ return;
269
+ }
270
+ reject(formatBwCliError(err, stderr, args));
271
+ return;
272
+ }
273
+ resolve(stdout);
274
+ });
275
+ if (stdin !== undefined) {
276
+ child?.stdin?.end(stdin);
277
+ }
278
+ });
279
+ return withBwLock(appDataDir, runCommand);
280
+ }
281
+ /** Check if the error indicates the bw CLI binary is not installed. */
282
+ function isBwNotInstalled(err) {
283
+ const msg = err.message.toLowerCase();
284
+ const code = err.code;
285
+ return code === "ENOENT" || /\bspawn\b.*\benoent\b/.test(msg) || msg.includes("command not found");
286
+ }
287
+ /** Check if the error is transient (network/timeout) and worth retrying. */
288
+ function isTransientError(err) {
289
+ const msg = err.message.toLowerCase();
290
+ return (msg.includes("econnrefused") ||
291
+ msg.includes("etimedout") ||
292
+ msg.includes("enotfound") ||
293
+ msg.includes("socket hang up") ||
294
+ msg.includes("503") ||
295
+ msg.includes("server unavailable") ||
296
+ msg.includes("timed out"));
297
+ }
298
+ const MAX_RETRIES = 3;
299
+ const BASE_BACKOFF_MS = 1000;
300
+ const TRANSIENT_MAX_RETRIES = 3;
301
+ const TRANSIENT_RETRY_BASE_MS = 500;
302
+ function delay(ms) {
303
+ return new Promise((resolve) => setTimeout(resolve, ms));
304
+ }
305
+ function isBwLoginItem(value) {
306
+ if (!value || typeof value !== "object" || Array.isArray(value))
307
+ return false;
308
+ const item = value;
309
+ if (typeof item.id !== "string" || item.id.trim().length === 0)
310
+ return false;
311
+ if (typeof item.name !== "string" || item.name.trim().length === 0)
312
+ return false;
313
+ if (item.login !== undefined) {
314
+ if (!item.login || typeof item.login !== "object" || Array.isArray(item.login))
315
+ return false;
316
+ const login = item.login;
317
+ if (login.username !== undefined && typeof login.username !== "string")
318
+ return false;
319
+ if (login.password !== undefined && typeof login.password !== "string")
320
+ return false;
321
+ if (login.uris !== undefined) {
322
+ if (!Array.isArray(login.uris))
323
+ return false;
324
+ for (const uri of login.uris) {
325
+ if (!uri || typeof uri !== "object" || Array.isArray(uri))
326
+ return false;
327
+ const uriRecord = uri;
328
+ if (uriRecord.uri !== undefined && typeof uriRecord.uri !== "string")
329
+ return false;
330
+ }
331
+ }
332
+ }
333
+ if (item.notes !== undefined && item.notes !== null && typeof item.notes !== "string")
334
+ return false;
335
+ if (item.revisionDate !== undefined && typeof item.revisionDate !== "string")
336
+ return false;
337
+ return true;
338
+ }
339
+ function parseBwItems(stdout, context) {
340
+ let parsed;
341
+ try {
342
+ parsed = JSON.parse(stdout);
343
+ if (!Array.isArray(parsed)) {
344
+ throw new Error("expected item array");
345
+ }
346
+ const items = parsed;
347
+ if (!items.every(isBwLoginItem)) {
348
+ throw new Error("expected login items");
349
+ }
350
+ return items;
351
+ }
352
+ catch {
353
+ if (Array.isArray(parsed)) {
354
+ throw new Error(`bw CLI error: invalid item from ${context}`);
355
+ }
356
+ throw new Error(`bw CLI error: invalid JSON from ${context}`);
357
+ }
358
+ }
359
+ function parseBwItem(stdout, context) {
360
+ let parsed;
361
+ try {
362
+ parsed = JSON.parse(stdout);
363
+ if (!isBwLoginItem(parsed)) {
364
+ throw new Error("expected login item");
365
+ }
366
+ return parsed;
367
+ }
368
+ catch {
369
+ if (parsed !== undefined) {
370
+ throw new Error(`bw CLI error: invalid item from ${context}`);
371
+ }
372
+ throw new Error(`bw CLI error: invalid JSON from ${context}`);
373
+ }
374
+ }
375
+ function parseBwItemId(stdout) {
376
+ try {
377
+ const parsed = JSON.parse(stdout);
378
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
379
+ return null;
380
+ const id = parsed.id;
381
+ return typeof id === "string" && id.trim().length > 0 ? id : null;
382
+ }
383
+ catch {
384
+ return null;
385
+ }
386
+ }
387
+ // ---------------------------------------------------------------------------
388
+ // BitwardenCredentialStore
389
+ // ---------------------------------------------------------------------------
390
+ class BitwardenCredentialStore {
391
+ serverUrl;
392
+ email;
393
+ masterPassword;
394
+ appDataDir;
395
+ sessionToken = null;
396
+ bwBinaryPath = "bw";
397
+ structuredItemCache = null;
398
+ constructor(serverUrl, email, masterPassword, options = {}) {
399
+ this.serverUrl = serverUrl;
400
+ this.email = email;
401
+ this.masterPassword = masterPassword;
402
+ this.appDataDir = options.appDataDir;
403
+ }
404
+ isReady() {
405
+ return true;
406
+ }
407
+ execBw(args, sessionToken, stdin) {
408
+ return execBw(args, sessionToken, this.appDataDir, stdin, this.bwBinaryPath);
409
+ }
410
+ execBwWithPasswordEnv(args) {
411
+ return execBw([...args, "--passwordenv", BW_PASSWORD_ENV], undefined, this.appDataDir, undefined, this.bwBinaryPath, { [BW_PASSWORD_ENV]: this.masterPassword });
412
+ }
413
+ /**
414
+ * Ensure the bw CLI is authenticated and unlocked.
415
+ * Handles three states: logged out → login, locked → unlock, already unlocked → no-op.
416
+ * Retries transient failures (network/timeout) up to MAX_RETRIES with exponential backoff.
417
+ */
418
+ async login() {
419
+ // Ensure bw CLI is installed before any bw commands
420
+ this.bwBinaryPath = await (0, bw_installer_1.ensureBwCli)();
421
+ if (this.appDataDir) {
422
+ fs.mkdirSync(this.appDataDir, { recursive: true, mode: 0o700 });
423
+ }
424
+ let lastError;
425
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
426
+ try {
427
+ await this.loginAttempt();
428
+ return;
429
+ }
430
+ catch (err) {
431
+ /* v8 ignore next -- defensive: loginAttempt always throws Error instances @preserve */
432
+ lastError = err instanceof Error ? err : new Error(String(err));
433
+ // Don't retry non-transient errors (auth failures, bw not installed)
434
+ if (!isTransientError(lastError)) {
435
+ throw lastError;
436
+ }
437
+ // Don't retry after final attempt
438
+ if (attempt === MAX_RETRIES - 1)
439
+ break;
440
+ const backoffMs = BASE_BACKOFF_MS * Math.pow(2, attempt);
441
+ (0, runtime_1.emitNervesEvent)({
442
+ event: "repertoire.bw_login_retry",
443
+ component: "repertoire",
444
+ message: `bw login attempt ${attempt + 1} failed, retrying in ${backoffMs}ms`,
445
+ meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
446
+ });
447
+ await delay(backoffMs);
448
+ }
449
+ }
450
+ throw lastError;
451
+ }
452
+ /** Single login attempt — called by login() retry loop. */
453
+ async loginAttempt() {
454
+ // Check current status
455
+ let status = {};
456
+ try {
457
+ const raw = await this.execBw(["status"]);
458
+ status = JSON.parse(raw);
459
+ }
460
+ catch (err) {
461
+ // If bw CLI is not installed or a transient error, propagate it for retry
462
+ if (err instanceof Error && (isBwNotInstalled(err) || isTransientError(err))) {
463
+ throw err;
464
+ }
465
+ // CLI not configured or broken — proceed with full setup
466
+ }
467
+ // Configure server URL if needed (only works when logged out)
468
+ if (status.status === "unauthenticated" || !status.serverUrl) {
469
+ try {
470
+ await this.execBw(["config", "server", this.serverUrl]);
471
+ }
472
+ catch (error) {
473
+ const err = error;
474
+ // "Logout required" means already logged in — that's fine, skip config.
475
+ if (!isBwConfigLogoutRequired(err)) {
476
+ throw err;
477
+ }
478
+ }
479
+ }
480
+ if (status.status === "locked") {
481
+ // Already logged in, just needs unlock
482
+ const unlockOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
483
+ this.sessionToken = unlockOutput.trim();
484
+ }
485
+ else if (status.status === "unauthenticated" || !status.status) {
486
+ // Not logged in — full login
487
+ let loginOutput;
488
+ try {
489
+ loginOutput = await this.execBwWithPasswordEnv(["login", this.email, "--raw"]);
490
+ }
491
+ catch (error) {
492
+ const err = error;
493
+ if (!isBwAlreadyLoggedInError(err)) {
494
+ throw err;
495
+ }
496
+ loginOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
497
+ }
498
+ try {
499
+ const parsed = JSON.parse(loginOutput);
500
+ this.sessionToken = parsed.access_token ?? loginOutput.trim();
501
+ }
502
+ catch {
503
+ this.sessionToken = loginOutput.trim();
504
+ }
505
+ }
506
+ else {
507
+ // Status is "unlocked" — already good, just need the session token
508
+ const unlockOutput = await this.execBwWithPasswordEnv(["unlock", "--raw"]);
509
+ this.sessionToken = unlockOutput.trim();
510
+ }
511
+ if (this.shouldSyncVaultAfterSession(status)) {
512
+ /* v8 ignore next -- defensive: loginAttempt always sets sessionToken before sync @preserve */
513
+ await this.execBw(["sync"], this.sessionToken ?? undefined);
514
+ this.writeSyncMarker();
515
+ }
516
+ else {
517
+ (0, runtime_1.emitNervesEvent)({
518
+ event: "repertoire.bw_sync_skipped",
519
+ component: "repertoire",
520
+ message: "skipping bw sync because local vault cache is still fresh",
521
+ meta: { email: this.email, serverUrl: this.serverUrl, freshnessWindowMs: BW_SYNC_FRESH_MS },
522
+ });
523
+ }
524
+ }
525
+ async ensureSession() {
526
+ if (!this.sessionToken) {
527
+ await this.login();
528
+ }
529
+ /* v8 ignore next -- defensive: login() always sets sessionToken on success @preserve */
530
+ return this.sessionToken ?? undefined;
531
+ }
532
+ async withSessionRetry(operation) {
533
+ let attemptedFreshSession = false;
534
+ while (true) {
535
+ const session = await this.ensureSession();
536
+ try {
537
+ return await operation(session);
538
+ }
539
+ catch (error) {
540
+ const err = error;
541
+ if (attemptedFreshSession || !isBwSessionAuthError(err)) {
542
+ throw err;
543
+ }
544
+ this.sessionToken = null;
545
+ this.structuredItemCache = null;
546
+ attemptedFreshSession = true;
547
+ }
548
+ }
549
+ }
550
+ async withTransientRetry(operation) {
551
+ let lastError;
552
+ for (let attempt = 0; attempt < TRANSIENT_MAX_RETRIES; attempt++) {
553
+ try {
554
+ return await operation();
555
+ }
556
+ catch (err) {
557
+ /* v8 ignore next -- defensive: operation always throws Error instances @preserve */
558
+ lastError = err instanceof Error ? err : new Error(String(err));
559
+ if (!isTransientError(lastError)) {
560
+ throw lastError;
561
+ }
562
+ if (attempt === TRANSIENT_MAX_RETRIES - 1)
563
+ break;
564
+ const backoffMs = TRANSIENT_RETRY_BASE_MS * Math.pow(2, attempt);
565
+ (0, runtime_1.emitNervesEvent)({
566
+ event: "repertoire.bw_transient_retry",
567
+ component: "repertoire",
568
+ message: `transient bw error, retrying in ${backoffMs}ms`,
569
+ meta: { attempt: attempt + 1, backoffMs, reason: lastError.message },
570
+ });
571
+ await delay(backoffMs);
572
+ }
573
+ }
574
+ throw lastError;
575
+ }
576
+ async get(domain) {
577
+ (0, runtime_1.emitNervesEvent)({
578
+ event: "repertoire.bw_credential_get_start",
579
+ component: "repertoire",
580
+ message: `getting credential via bw for ${domain}`,
581
+ meta: { domain, backend: "bitwarden" },
582
+ });
583
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session, { preferExactStructured: true })));
584
+ if (!item) {
585
+ (0, runtime_1.emitNervesEvent)({
586
+ event: "repertoire.bw_credential_get_end",
587
+ component: "repertoire",
588
+ message: `no bw credential for ${domain}`,
589
+ meta: { domain, found: false, backend: "bitwarden" },
590
+ });
591
+ return null;
592
+ }
593
+ (0, runtime_1.emitNervesEvent)({
594
+ event: "repertoire.bw_credential_get_end",
595
+ component: "repertoire",
596
+ message: `bw credential found for ${domain}`,
597
+ meta: { domain, found: true, backend: "bitwarden" },
598
+ });
599
+ return {
600
+ domain: item.name,
601
+ username: item.login?.username,
602
+ notes: item.notes ?? undefined,
603
+ createdAt: item.revisionDate ?? new Date().toISOString(),
604
+ };
605
+ }
606
+ async getRawSecret(domain, field) {
607
+ const item = await this.withTransientRetry(() => this.withSessionRetry((session) => this.findItemByDomain(domain, session, { preferExactStructured: true })));
608
+ if (!item) {
609
+ throw new Error(`no credential found for domain "${domain}"`);
610
+ }
611
+ // Map common field names to bw item structure
612
+ let value;
613
+ if (field === "password") {
614
+ value = item.login?.password;
615
+ }
616
+ else if (field === "username") {
617
+ value = item.login?.username;
618
+ }
619
+ else {
620
+ value = item[field];
621
+ }
622
+ if (value === undefined || value === null) {
623
+ throw new Error(`field "${field}" not found for domain "${domain}"`);
624
+ }
625
+ return String(value);
626
+ }
627
+ async store(domain, data) {
628
+ (0, runtime_1.emitNervesEvent)({
629
+ event: "repertoire.bw_credential_store_start",
630
+ component: "repertoire",
631
+ message: `storing credential via bw for ${domain}`,
632
+ meta: { domain, backend: "bitwarden" },
633
+ });
634
+ await this.withSessionRetry(async (session) => {
635
+ const existing = await this.findItemByDomain(domain, session);
636
+ const item = {
637
+ ...(existing ?? {}),
638
+ type: 1, // Login type
639
+ name: domain,
640
+ login: {
641
+ username: data.username ?? "",
642
+ password: data.password,
643
+ uris: [{ match: null, uri: `https://${domain}` }],
644
+ },
645
+ notes: data.notes ?? null,
646
+ };
647
+ const encoded = Buffer.from(JSON.stringify(item)).toString("base64");
648
+ this.structuredItemCache = null;
649
+ let savedItem;
650
+ if (existing) {
651
+ const stdout = await this.execBw(["edit", "item", existing.id], session, encoded);
652
+ const savedItemId = parseBwItemId(stdout) ?? existing.id;
653
+ savedItem = await this.findItemById(savedItemId, session);
654
+ }
655
+ else {
656
+ const stdout = await this.execBw(["create", "item"], session, encoded);
657
+ const savedItemId = parseBwItemId(stdout);
658
+ savedItem = savedItemId
659
+ ? await this.findItemById(savedItemId, session)
660
+ : await this.findItemByDomain(domain, session);
661
+ }
662
+ this.assertStoredCredentialMatches(domain, data, savedItem);
663
+ });
664
+ this.structuredItemCache = null;
665
+ (0, runtime_1.emitNervesEvent)({
666
+ event: "repertoire.bw_credential_store_end",
667
+ component: "repertoire",
668
+ message: `credential stored via bw for ${domain}`,
669
+ meta: { domain, backend: "bitwarden" },
670
+ });
671
+ }
672
+ async list() {
673
+ (0, runtime_1.emitNervesEvent)({
674
+ event: "repertoire.bw_credential_list_start",
675
+ component: "repertoire",
676
+ message: "listing bw credentials",
677
+ meta: { backend: "bitwarden" },
678
+ });
679
+ const stdout = await this.withTransientRetry(() => this.withSessionRetry((session) => this.execBw(["list", "items"], session)));
680
+ const items = parseBwItems(stdout, "bw list items");
681
+ const results = items.map((item) => ({
682
+ domain: item.name,
683
+ username: item.login?.username,
684
+ notes: item.notes ?? undefined,
685
+ createdAt: item.revisionDate ?? new Date().toISOString(),
686
+ }));
687
+ (0, runtime_1.emitNervesEvent)({
688
+ event: "repertoire.bw_credential_list_end",
689
+ component: "repertoire",
690
+ message: "bw credentials listed",
691
+ meta: { backend: "bitwarden", count: results.length },
692
+ });
693
+ return results;
694
+ }
695
+ async delete(domain) {
696
+ (0, runtime_1.emitNervesEvent)({
697
+ event: "repertoire.bw_credential_delete_start",
698
+ component: "repertoire",
699
+ message: `deleting credential via bw for ${domain}`,
700
+ meta: { domain, backend: "bitwarden" },
701
+ });
702
+ const item = await this.withSessionRetry((session) => this.findItemByDomain(domain, session));
703
+ if (!item) {
704
+ (0, runtime_1.emitNervesEvent)({
705
+ event: "repertoire.bw_credential_delete_end",
706
+ component: "repertoire",
707
+ message: `no bw credential to delete for ${domain}`,
708
+ meta: { domain, deleted: false, backend: "bitwarden" },
709
+ });
710
+ return false;
711
+ }
712
+ await this.withSessionRetry((session) => this.execBw(["delete", "item", item.id], session));
713
+ this.structuredItemCache = null;
714
+ (0, runtime_1.emitNervesEvent)({
715
+ event: "repertoire.bw_credential_delete_end",
716
+ component: "repertoire",
717
+ message: `credential deleted via bw for ${domain}`,
718
+ meta: { domain, deleted: true, backend: "bitwarden" },
719
+ });
720
+ return true;
721
+ }
722
+ // --- Private ---
723
+ async findItemByDomain(domain, session, options = {}) {
724
+ if (options.preferExactStructured && shouldUseStructuredItemLookup(domain)) {
725
+ return this.findStructuredItemByName(domain, session);
726
+ }
727
+ if (shouldUseFullListForStructuredLookup(domain, this.appDataDir)) {
728
+ const items = await this.readStructuredItemCache(session);
729
+ return items.get(domain) ?? null;
730
+ }
731
+ const stdout = await this.execBw(["list", "items", "--search", domain], session);
732
+ const items = parseBwItems(stdout, "bw list items --search");
733
+ // Find exact match by name
734
+ return items.find((item) => item.name === domain) ?? null;
735
+ }
736
+ async findStructuredItemByName(domain, session) {
737
+ try {
738
+ const stdout = await this.execBw(["get", "item", domain], session);
739
+ const item = parseBwItem(stdout, "bw get item");
740
+ return item.name === domain ? item : null;
741
+ }
742
+ catch (error) {
743
+ /* v8 ignore next -- defensive: execBw rejects with Error instances @preserve */
744
+ const err = error instanceof Error ? error : new Error(String(error));
745
+ if (isBwItemNotFoundError(err))
746
+ return null;
747
+ throw err;
748
+ }
749
+ }
750
+ shouldSyncVaultAfterSession(status) {
751
+ if (status.status === "unauthenticated" || !status.status)
752
+ return true;
753
+ if (!this.appDataDir)
754
+ return true;
755
+ const freshnessTimestamp = this.latestLocalSyncTimestamp();
756
+ if (freshnessTimestamp !== null) {
757
+ return Date.now() - freshnessTimestamp > BW_SYNC_FRESH_MS;
758
+ }
759
+ return true;
760
+ }
761
+ latestLocalSyncTimestamp() {
762
+ const files = [BW_SYNC_MARKER_FILENAME, BW_DATA_FILENAME];
763
+ let latest = null;
764
+ for (const name of files) {
765
+ try {
766
+ const mtimeMs = fs.statSync(path.join(this.appDataDir, name)).mtimeMs;
767
+ latest = latest === null ? mtimeMs : Math.max(latest, mtimeMs);
768
+ }
769
+ catch {
770
+ // Missing freshness file is fine; use any other available timestamp.
771
+ }
772
+ }
773
+ return latest;
774
+ }
775
+ writeSyncMarker() {
776
+ if (!this.appDataDir)
777
+ return;
778
+ try {
779
+ fs.writeFileSync(path.join(this.appDataDir, BW_SYNC_MARKER_FILENAME), `${Date.now()}\n`, { mode: 0o600 });
780
+ }
781
+ catch {
782
+ // If the marker cannot be written, fall back to syncing next time.
783
+ }
784
+ }
785
+ async findItemById(id, session) {
786
+ const stdout = await this.execBw(["get", "item", id], session);
787
+ return parseBwItem(stdout, "bw get item");
788
+ }
789
+ async readStructuredItemCache(session) {
790
+ if (this.structuredItemCache)
791
+ return this.structuredItemCache;
792
+ const stdout = await this.execBw(["list", "items"], session);
793
+ const items = parseBwItems(stdout, "bw list items");
794
+ this.structuredItemCache = new Map(items.map((item) => [item.name, item]));
795
+ return this.structuredItemCache;
796
+ }
797
+ assertStoredCredentialMatches(domain, data, item) {
798
+ if (!item) {
799
+ throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item could not be read back after write`);
800
+ }
801
+ const mismatches = [];
802
+ if (item.name !== domain)
803
+ mismatches.push("name");
804
+ if ((item.login?.username ?? "") !== (data.username ?? ""))
805
+ mismatches.push("username");
806
+ if ((item.login?.password ?? "") !== data.password)
807
+ mismatches.push("password");
808
+ if ((item.notes ?? null) !== (data.notes ?? null))
809
+ mismatches.push("notes");
810
+ if (mismatches.length > 0) {
811
+ const label = mismatches.length === 1 ? "field" : "fields";
812
+ throw new Error(`bw CLI error: credential save verification failed for ${domain}: saved item did not match requested ${label} ${mismatches.join(", ")}`);
813
+ }
814
+ }
815
+ }
816
+ exports.BitwardenCredentialStore = BitwardenCredentialStore;