@chances-ai/engine 24.0.0

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 (389) hide show
  1. package/dist/agents/discover.d.ts +30 -0
  2. package/dist/agents/discover.d.ts.map +1 -0
  3. package/dist/agents/discover.js +183 -0
  4. package/dist/agents/discover.js.map +1 -0
  5. package/dist/agents/index.d.ts +20 -0
  6. package/dist/agents/index.d.ts.map +1 -0
  7. package/dist/agents/index.js +52 -0
  8. package/dist/agents/index.js.map +1 -0
  9. package/dist/agents/parse.d.ts +61 -0
  10. package/dist/agents/parse.d.ts.map +1 -0
  11. package/dist/agents/parse.js +527 -0
  12. package/dist/agents/parse.js.map +1 -0
  13. package/dist/agents/types.d.ts +52 -0
  14. package/dist/agents/types.d.ts.map +1 -0
  15. package/dist/agents/types.js +8 -0
  16. package/dist/agents/types.js.map +1 -0
  17. package/dist/ai/adapters/ai-sdk-stream.d.ts +19 -0
  18. package/dist/ai/adapters/ai-sdk-stream.d.ts.map +1 -0
  19. package/dist/ai/adapters/ai-sdk-stream.js +125 -0
  20. package/dist/ai/adapters/ai-sdk-stream.js.map +1 -0
  21. package/dist/ai/adapters/ai-sdk.d.ts +56 -0
  22. package/dist/ai/adapters/ai-sdk.d.ts.map +1 -0
  23. package/dist/ai/adapters/ai-sdk.js +112 -0
  24. package/dist/ai/adapters/ai-sdk.js.map +1 -0
  25. package/dist/ai/adapters/mock.d.ts +13 -0
  26. package/dist/ai/adapters/mock.d.ts.map +1 -0
  27. package/dist/ai/adapters/mock.js +54 -0
  28. package/dist/ai/adapters/mock.js.map +1 -0
  29. package/dist/ai/adapters/openai-compatible.d.ts +23 -0
  30. package/dist/ai/adapters/openai-compatible.d.ts.map +1 -0
  31. package/dist/ai/adapters/openai-compatible.js +45 -0
  32. package/dist/ai/adapters/openai-compatible.js.map +1 -0
  33. package/dist/ai/cost.d.ts +3 -0
  34. package/dist/ai/cost.d.ts.map +1 -0
  35. package/dist/ai/cost.js +5 -0
  36. package/dist/ai/cost.js.map +1 -0
  37. package/dist/ai/index.d.ts +12 -0
  38. package/dist/ai/index.d.ts.map +1 -0
  39. package/dist/ai/index.js +11 -0
  40. package/dist/ai/index.js.map +1 -0
  41. package/dist/ai/known-models.d.ts +20 -0
  42. package/dist/ai/known-models.d.ts.map +1 -0
  43. package/dist/ai/known-models.js +129 -0
  44. package/dist/ai/known-models.js.map +1 -0
  45. package/dist/ai/registry.d.ts +12 -0
  46. package/dist/ai/registry.d.ts.map +1 -0
  47. package/dist/ai/registry.js +24 -0
  48. package/dist/ai/registry.js.map +1 -0
  49. package/dist/ai/retry.d.ts +11 -0
  50. package/dist/ai/retry.d.ts.map +1 -0
  51. package/dist/ai/retry.js +14 -0
  52. package/dist/ai/retry.js.map +1 -0
  53. package/dist/ai/router.d.ts +25 -0
  54. package/dist/ai/router.d.ts.map +1 -0
  55. package/dist/ai/router.js +36 -0
  56. package/dist/ai/router.js.map +1 -0
  57. package/dist/ai/setup.d.ts +23 -0
  58. package/dist/ai/setup.d.ts.map +1 -0
  59. package/dist/ai/setup.js +47 -0
  60. package/dist/ai/setup.js.map +1 -0
  61. package/dist/ai/summarizer.d.ts +24 -0
  62. package/dist/ai/summarizer.d.ts.map +1 -0
  63. package/dist/ai/summarizer.js +56 -0
  64. package/dist/ai/summarizer.js.map +1 -0
  65. package/dist/ai/types.d.ts +83 -0
  66. package/dist/ai/types.d.ts.map +1 -0
  67. package/dist/ai/types.js +2 -0
  68. package/dist/ai/types.js.map +1 -0
  69. package/dist/core/compaction/circuit-breaker.d.ts +32 -0
  70. package/dist/core/compaction/circuit-breaker.d.ts.map +1 -0
  71. package/dist/core/compaction/circuit-breaker.js +42 -0
  72. package/dist/core/compaction/circuit-breaker.js.map +1 -0
  73. package/dist/core/compaction/compactor.d.ts +75 -0
  74. package/dist/core/compaction/compactor.d.ts.map +1 -0
  75. package/dist/core/compaction/compactor.js +261 -0
  76. package/dist/core/compaction/compactor.js.map +1 -0
  77. package/dist/core/compaction/estimate.d.ts +39 -0
  78. package/dist/core/compaction/estimate.d.ts.map +1 -0
  79. package/dist/core/compaction/estimate.js +74 -0
  80. package/dist/core/compaction/estimate.js.map +1 -0
  81. package/dist/core/compaction/index.d.ts +5 -0
  82. package/dist/core/compaction/index.d.ts.map +1 -0
  83. package/dist/core/compaction/index.js +5 -0
  84. package/dist/core/compaction/index.js.map +1 -0
  85. package/dist/core/compaction/prune.d.ts +43 -0
  86. package/dist/core/compaction/prune.d.ts.map +1 -0
  87. package/dist/core/compaction/prune.js +51 -0
  88. package/dist/core/compaction/prune.js.map +1 -0
  89. package/dist/core/engine.d.ts +268 -0
  90. package/dist/core/engine.d.ts.map +1 -0
  91. package/dist/core/engine.js +767 -0
  92. package/dist/core/engine.js.map +1 -0
  93. package/dist/core/index.d.ts +6 -0
  94. package/dist/core/index.d.ts.map +1 -0
  95. package/dist/core/index.js +6 -0
  96. package/dist/core/index.js.map +1 -0
  97. package/dist/core/task-tool.d.ts +175 -0
  98. package/dist/core/task-tool.d.ts.map +1 -0
  99. package/dist/core/task-tool.js +901 -0
  100. package/dist/core/task-tool.js.map +1 -0
  101. package/dist/core/workspace-query.d.ts +83 -0
  102. package/dist/core/workspace-query.d.ts.map +1 -0
  103. package/dist/core/workspace-query.js +217 -0
  104. package/dist/core/workspace-query.js.map +1 -0
  105. package/dist/core/worktree/active-marker.d.ts +31 -0
  106. package/dist/core/worktree/active-marker.d.ts.map +1 -0
  107. package/dist/core/worktree/active-marker.js +109 -0
  108. package/dist/core/worktree/active-marker.js.map +1 -0
  109. package/dist/core/worktree/create.d.ts +40 -0
  110. package/dist/core/worktree/create.d.ts.map +1 -0
  111. package/dist/core/worktree/create.js +121 -0
  112. package/dist/core/worktree/create.js.map +1 -0
  113. package/dist/core/worktree/errors.d.ts +7 -0
  114. package/dist/core/worktree/errors.d.ts.map +1 -0
  115. package/dist/core/worktree/errors.js +11 -0
  116. package/dist/core/worktree/errors.js.map +1 -0
  117. package/dist/core/worktree/gc.d.ts +39 -0
  118. package/dist/core/worktree/gc.d.ts.map +1 -0
  119. package/dist/core/worktree/gc.js +146 -0
  120. package/dist/core/worktree/gc.js.map +1 -0
  121. package/dist/core/worktree/git.d.ts +53 -0
  122. package/dist/core/worktree/git.d.ts.map +1 -0
  123. package/dist/core/worktree/git.js +166 -0
  124. package/dist/core/worktree/git.js.map +1 -0
  125. package/dist/core/worktree/index.d.ts +8 -0
  126. package/dist/core/worktree/index.d.ts.map +1 -0
  127. package/dist/core/worktree/index.js +8 -0
  128. package/dist/core/worktree/index.js.map +1 -0
  129. package/dist/core/worktree/paths.d.ts +26 -0
  130. package/dist/core/worktree/paths.d.ts.map +1 -0
  131. package/dist/core/worktree/paths.js +57 -0
  132. package/dist/core/worktree/paths.js.map +1 -0
  133. package/dist/core/worktree/slug.d.ts +6 -0
  134. package/dist/core/worktree/slug.d.ts.map +1 -0
  135. package/dist/core/worktree/slug.js +21 -0
  136. package/dist/core/worktree/slug.js.map +1 -0
  137. package/dist/local-vault/file-store.d.ts +64 -0
  138. package/dist/local-vault/file-store.d.ts.map +1 -0
  139. package/dist/local-vault/file-store.js +225 -0
  140. package/dist/local-vault/file-store.js.map +1 -0
  141. package/dist/local-vault/index.d.ts +57 -0
  142. package/dist/local-vault/index.d.ts.map +1 -0
  143. package/dist/local-vault/index.js +68 -0
  144. package/dist/local-vault/index.js.map +1 -0
  145. package/dist/local-vault/keychain.d.ts +58 -0
  146. package/dist/local-vault/keychain.d.ts.map +1 -0
  147. package/dist/local-vault/keychain.js +200 -0
  148. package/dist/local-vault/keychain.js.map +1 -0
  149. package/dist/local-vault/mutex.d.ts +20 -0
  150. package/dist/local-vault/mutex.d.ts.map +1 -0
  151. package/dist/local-vault/mutex.js +44 -0
  152. package/dist/local-vault/mutex.js.map +1 -0
  153. package/dist/local-vault/passphrase.d.ts +30 -0
  154. package/dist/local-vault/passphrase.d.ts.map +1 -0
  155. package/dist/local-vault/passphrase.js +72 -0
  156. package/dist/local-vault/passphrase.js.map +1 -0
  157. package/dist/lsp/config.d.ts +34 -0
  158. package/dist/lsp/config.d.ts.map +1 -0
  159. package/dist/lsp/config.js +68 -0
  160. package/dist/lsp/config.js.map +1 -0
  161. package/dist/lsp/detect.d.ts +7 -0
  162. package/dist/lsp/detect.d.ts.map +1 -0
  163. package/dist/lsp/detect.js +78 -0
  164. package/dist/lsp/detect.js.map +1 -0
  165. package/dist/lsp/errors.d.ts +11 -0
  166. package/dist/lsp/errors.d.ts.map +1 -0
  167. package/dist/lsp/errors.js +11 -0
  168. package/dist/lsp/errors.js.map +1 -0
  169. package/dist/lsp/formatters.d.ts +147 -0
  170. package/dist/lsp/formatters.d.ts.map +1 -0
  171. package/dist/lsp/formatters.js +259 -0
  172. package/dist/lsp/formatters.js.map +1 -0
  173. package/dist/lsp/index.d.ts +31 -0
  174. package/dist/lsp/index.d.ts.map +1 -0
  175. package/dist/lsp/index.js +31 -0
  176. package/dist/lsp/index.js.map +1 -0
  177. package/dist/lsp/instance.d.ts +72 -0
  178. package/dist/lsp/instance.d.ts.map +1 -0
  179. package/dist/lsp/instance.js +489 -0
  180. package/dist/lsp/instance.js.map +1 -0
  181. package/dist/lsp/lazy-load.d.ts +27 -0
  182. package/dist/lsp/lazy-load.d.ts.map +1 -0
  183. package/dist/lsp/lazy-load.js +57 -0
  184. package/dist/lsp/lazy-load.js.map +1 -0
  185. package/dist/lsp/manager.d.ts +59 -0
  186. package/dist/lsp/manager.d.ts.map +1 -0
  187. package/dist/lsp/manager.js +242 -0
  188. package/dist/lsp/manager.js.map +1 -0
  189. package/dist/lsp/ops.d.ts +13 -0
  190. package/dist/lsp/ops.d.ts.map +1 -0
  191. package/dist/lsp/ops.js +225 -0
  192. package/dist/lsp/ops.js.map +1 -0
  193. package/dist/lsp/rpc.d.ts +47 -0
  194. package/dist/lsp/rpc.d.ts.map +1 -0
  195. package/dist/lsp/rpc.js +134 -0
  196. package/dist/lsp/rpc.js.map +1 -0
  197. package/dist/lsp/safe-uri.d.ts +18 -0
  198. package/dist/lsp/safe-uri.d.ts.map +1 -0
  199. package/dist/lsp/safe-uri.js +96 -0
  200. package/dist/lsp/safe-uri.js.map +1 -0
  201. package/dist/lsp/types.d.ts +70 -0
  202. package/dist/lsp/types.d.ts.map +1 -0
  203. package/dist/lsp/types.js +16 -0
  204. package/dist/lsp/types.js.map +1 -0
  205. package/dist/mcp/bridge.d.ts +57 -0
  206. package/dist/mcp/bridge.d.ts.map +1 -0
  207. package/dist/mcp/bridge.js +98 -0
  208. package/dist/mcp/bridge.js.map +1 -0
  209. package/dist/mcp/category.d.ts +22 -0
  210. package/dist/mcp/category.d.ts.map +1 -0
  211. package/dist/mcp/category.js +11 -0
  212. package/dist/mcp/category.js.map +1 -0
  213. package/dist/mcp/client.d.ts +228 -0
  214. package/dist/mcp/client.d.ts.map +1 -0
  215. package/dist/mcp/client.js +352 -0
  216. package/dist/mcp/client.js.map +1 -0
  217. package/dist/mcp/content.d.ts +86 -0
  218. package/dist/mcp/content.d.ts.map +1 -0
  219. package/dist/mcp/content.js +147 -0
  220. package/dist/mcp/content.js.map +1 -0
  221. package/dist/mcp/env.d.ts +19 -0
  222. package/dist/mcp/env.d.ts.map +1 -0
  223. package/dist/mcp/env.js +50 -0
  224. package/dist/mcp/env.js.map +1 -0
  225. package/dist/mcp/host.d.ts +199 -0
  226. package/dist/mcp/host.d.ts.map +1 -0
  227. package/dist/mcp/host.js +530 -0
  228. package/dist/mcp/host.js.map +1 -0
  229. package/dist/mcp/index.d.ts +18 -0
  230. package/dist/mcp/index.d.ts.map +1 -0
  231. package/dist/mcp/index.js +17 -0
  232. package/dist/mcp/index.js.map +1 -0
  233. package/dist/mcp/load-mcp-host.d.ts +32 -0
  234. package/dist/mcp/load-mcp-host.d.ts.map +1 -0
  235. package/dist/mcp/load-mcp-host.js +49 -0
  236. package/dist/mcp/load-mcp-host.js.map +1 -0
  237. package/dist/mcp/oauth/callback-server.d.ts +73 -0
  238. package/dist/mcp/oauth/callback-server.d.ts.map +1 -0
  239. package/dist/mcp/oauth/callback-server.js +280 -0
  240. package/dist/mcp/oauth/callback-server.js.map +1 -0
  241. package/dist/mcp/oauth/config-hash.d.ts +24 -0
  242. package/dist/mcp/oauth/config-hash.d.ts.map +1 -0
  243. package/dist/mcp/oauth/config-hash.js +55 -0
  244. package/dist/mcp/oauth/config-hash.js.map +1 -0
  245. package/dist/mcp/oauth/error-normalize.d.ts +39 -0
  246. package/dist/mcp/oauth/error-normalize.d.ts.map +1 -0
  247. package/dist/mcp/oauth/error-normalize.js +91 -0
  248. package/dist/mcp/oauth/error-normalize.js.map +1 -0
  249. package/dist/mcp/oauth/provider.d.ts +190 -0
  250. package/dist/mcp/oauth/provider.d.ts.map +1 -0
  251. package/dist/mcp/oauth/provider.js +305 -0
  252. package/dist/mcp/oauth/provider.js.map +1 -0
  253. package/dist/mcp/oauth/refresh-coalescer.d.ts +46 -0
  254. package/dist/mcp/oauth/refresh-coalescer.d.ts.map +1 -0
  255. package/dist/mcp/oauth/refresh-coalescer.js +77 -0
  256. package/dist/mcp/oauth/refresh-coalescer.js.map +1 -0
  257. package/dist/mcp/oauth/sdk-shapes.d.ts +77 -0
  258. package/dist/mcp/oauth/sdk-shapes.d.ts.map +1 -0
  259. package/dist/mcp/oauth/sdk-shapes.js +21 -0
  260. package/dist/mcp/oauth/sdk-shapes.js.map +1 -0
  261. package/dist/mcp/parse.d.ts +28 -0
  262. package/dist/mcp/parse.d.ts.map +1 -0
  263. package/dist/mcp/parse.js +209 -0
  264. package/dist/mcp/parse.js.map +1 -0
  265. package/dist/mcp/prompts.d.ts +31 -0
  266. package/dist/mcp/prompts.d.ts.map +1 -0
  267. package/dist/mcp/prompts.js +71 -0
  268. package/dist/mcp/prompts.js.map +1 -0
  269. package/dist/mcp/redact.d.ts +62 -0
  270. package/dist/mcp/redact.d.ts.map +1 -0
  271. package/dist/mcp/redact.js +87 -0
  272. package/dist/mcp/redact.js.map +1 -0
  273. package/dist/mcp/resources.d.ts +70 -0
  274. package/dist/mcp/resources.d.ts.map +1 -0
  275. package/dist/mcp/resources.js +170 -0
  276. package/dist/mcp/resources.js.map +1 -0
  277. package/dist/mcp/types.d.ts +123 -0
  278. package/dist/mcp/types.d.ts.map +1 -0
  279. package/dist/mcp/types.js +2 -0
  280. package/dist/mcp/types.js.map +1 -0
  281. package/dist/memory/frontmatter.d.ts +18 -0
  282. package/dist/memory/frontmatter.d.ts.map +1 -0
  283. package/dist/memory/frontmatter.js +81 -0
  284. package/dist/memory/frontmatter.js.map +1 -0
  285. package/dist/memory/index.d.ts +5 -0
  286. package/dist/memory/index.d.ts.map +1 -0
  287. package/dist/memory/index.js +5 -0
  288. package/dist/memory/index.js.map +1 -0
  289. package/dist/memory/store.d.ts +44 -0
  290. package/dist/memory/store.d.ts.map +1 -0
  291. package/dist/memory/store.js +237 -0
  292. package/dist/memory/store.js.map +1 -0
  293. package/dist/memory/tools.d.ts +11 -0
  294. package/dist/memory/tools.d.ts.map +1 -0
  295. package/dist/memory/tools.js +159 -0
  296. package/dist/memory/tools.js.map +1 -0
  297. package/dist/memory/types.d.ts +32 -0
  298. package/dist/memory/types.d.ts.map +1 -0
  299. package/dist/memory/types.js +20 -0
  300. package/dist/memory/types.js.map +1 -0
  301. package/dist/plugin-api/index.d.ts +167 -0
  302. package/dist/plugin-api/index.d.ts.map +1 -0
  303. package/dist/plugin-api/index.js +151 -0
  304. package/dist/plugin-api/index.js.map +1 -0
  305. package/dist/plugin-logger/index.d.ts +21 -0
  306. package/dist/plugin-logger/index.d.ts.map +1 -0
  307. package/dist/plugin-logger/index.js +59 -0
  308. package/dist/plugin-logger/index.js.map +1 -0
  309. package/dist/session/index.d.ts +125 -0
  310. package/dist/session/index.d.ts.map +1 -0
  311. package/dist/session/index.js +202 -0
  312. package/dist/session/index.js.map +1 -0
  313. package/dist/tools/approval.d.ts +33 -0
  314. package/dist/tools/approval.d.ts.map +1 -0
  315. package/dist/tools/approval.js +53 -0
  316. package/dist/tools/approval.js.map +1 -0
  317. package/dist/tools/builtins/_shared.d.ts +94 -0
  318. package/dist/tools/builtins/_shared.d.ts.map +1 -0
  319. package/dist/tools/builtins/_shared.js +246 -0
  320. package/dist/tools/builtins/_shared.js.map +1 -0
  321. package/dist/tools/builtins/ask-user-question.d.ts +27 -0
  322. package/dist/tools/builtins/ask-user-question.d.ts.map +1 -0
  323. package/dist/tools/builtins/ask-user-question.js +191 -0
  324. package/dist/tools/builtins/ask-user-question.js.map +1 -0
  325. package/dist/tools/builtins/bash.d.ts +3 -0
  326. package/dist/tools/builtins/bash.d.ts.map +1 -0
  327. package/dist/tools/builtins/bash.js +158 -0
  328. package/dist/tools/builtins/bash.js.map +1 -0
  329. package/dist/tools/builtins/diff.d.ts +3 -0
  330. package/dist/tools/builtins/diff.d.ts.map +1 -0
  331. package/dist/tools/builtins/diff.js +83 -0
  332. package/dist/tools/builtins/diff.js.map +1 -0
  333. package/dist/tools/builtins/edit.d.ts +3 -0
  334. package/dist/tools/builtins/edit.d.ts.map +1 -0
  335. package/dist/tools/builtins/edit.js +40 -0
  336. package/dist/tools/builtins/edit.js.map +1 -0
  337. package/dist/tools/builtins/glob.d.ts +3 -0
  338. package/dist/tools/builtins/glob.d.ts.map +1 -0
  339. package/dist/tools/builtins/glob.js +37 -0
  340. package/dist/tools/builtins/glob.js.map +1 -0
  341. package/dist/tools/builtins/grep.d.ts +3 -0
  342. package/dist/tools/builtins/grep.d.ts.map +1 -0
  343. package/dist/tools/builtins/grep.js +81 -0
  344. package/dist/tools/builtins/grep.js.map +1 -0
  345. package/dist/tools/builtins/lsp.d.ts +3 -0
  346. package/dist/tools/builtins/lsp.d.ts.map +1 -0
  347. package/dist/tools/builtins/lsp.js +102 -0
  348. package/dist/tools/builtins/lsp.js.map +1 -0
  349. package/dist/tools/builtins/pty.d.ts +64 -0
  350. package/dist/tools/builtins/pty.d.ts.map +1 -0
  351. package/dist/tools/builtins/pty.js +536 -0
  352. package/dist/tools/builtins/pty.js.map +1 -0
  353. package/dist/tools/builtins/read.d.ts +3 -0
  354. package/dist/tools/builtins/read.d.ts.map +1 -0
  355. package/dist/tools/builtins/read.js +18 -0
  356. package/dist/tools/builtins/read.js.map +1 -0
  357. package/dist/tools/builtins/web-fetch.d.ts +4 -0
  358. package/dist/tools/builtins/web-fetch.d.ts.map +1 -0
  359. package/dist/tools/builtins/web-fetch.js +353 -0
  360. package/dist/tools/builtins/web-fetch.js.map +1 -0
  361. package/dist/tools/builtins/write.d.ts +3 -0
  362. package/dist/tools/builtins/write.d.ts.map +1 -0
  363. package/dist/tools/builtins/write.js +48 -0
  364. package/dist/tools/builtins/write.js.map +1 -0
  365. package/dist/tools/builtins.d.ts +9 -0
  366. package/dist/tools/builtins.d.ts.map +1 -0
  367. package/dist/tools/builtins.js +29 -0
  368. package/dist/tools/builtins.js.map +1 -0
  369. package/dist/tools/diff.d.ts +18 -0
  370. package/dist/tools/diff.d.ts.map +1 -0
  371. package/dist/tools/diff.js +57 -0
  372. package/dist/tools/diff.js.map +1 -0
  373. package/dist/tools/index.d.ts +10 -0
  374. package/dist/tools/index.d.ts.map +1 -0
  375. package/dist/tools/index.js +9 -0
  376. package/dist/tools/index.js.map +1 -0
  377. package/dist/tools/permission.d.ts +120 -0
  378. package/dist/tools/permission.d.ts.map +1 -0
  379. package/dist/tools/permission.js +208 -0
  380. package/dist/tools/permission.js.map +1 -0
  381. package/dist/tools/registry.d.ts +12 -0
  382. package/dist/tools/registry.d.ts.map +1 -0
  383. package/dist/tools/registry.js +19 -0
  384. package/dist/tools/registry.js.map +1 -0
  385. package/dist/tools/types.d.ts +244 -0
  386. package/dist/tools/types.d.ts.map +1 -0
  387. package/dist/tools/types.js +15 -0
  388. package/dist/tools/types.js.map +1 -0
  389. package/package.json +109 -0
@@ -0,0 +1,26 @@
1
+ export declare const WORKTREE_DIR_NAME = "worktrees";
2
+ export declare const CHANCES_DIR_NAME = ".chances";
3
+ export declare const ACTIVE_MARKER_FILE = "chances-active.json";
4
+ /** `<canonicalRoot>/.chances/worktrees`. Parent directory for every agent
5
+ * worktree under this repo. Created lazily on first `createAgentWorktree`. */
6
+ export declare function worktreesDir(canonicalRoot: string): string;
7
+ /** `<canonicalRoot>/.chances/worktrees/<slug>`. The exact path `git worktree
8
+ * add` is invoked against. */
9
+ export declare function worktreePathFor(canonicalRoot: string, slug: string): string;
10
+ /**
11
+ * Liveness marker for an active agent worktree. Stored INSIDE the worktree's
12
+ * git-admin directory (`<repo>/.git/worktrees/<slug>/chances-active.json`),
13
+ * not inside the worktree's working tree — so `git status` never sees it as
14
+ * an untracked file (which would otherwise mark the worktree dirty and
15
+ * defeat the GC). The admin dir is created by `git worktree add` and lives
16
+ * for the worktree's lifetime; cleanup is handled by `git worktree remove`.
17
+ *
18
+ * For a linked worktree, `<worktreePath>/.git` is a pointer file whose first
19
+ * line reads `gitdir: <admin-dir>`. We resolve it on demand.
20
+ */
21
+ export declare function activeMarkerPathFor(worktreePath: string): Promise<string>;
22
+ /** Resolves the worktree's git-admin directory. For linked worktrees this is
23
+ * `<repo>/.git/worktrees/<slug>/`; for the main worktree (rarely the case
24
+ * for agent worktrees, but supported) it's `<repo>/.git`. */
25
+ export declare function resolveWorktreeGitDir(worktreePath: string): Promise<string>;
26
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../../src/core/worktree/paths.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,cAAc,CAAC;AAC7C,eAAO,MAAM,gBAAgB,aAAa,CAAC;AAC3C,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD;8EAC8E;AAC9E,wBAAgB,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;8BAC8B;AAC9B,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/E;AAED;;6DAE6D;AAC7D,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBjF"}
@@ -0,0 +1,57 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, isAbsolute, join, resolve } from "node:path";
3
+ export const WORKTREE_DIR_NAME = "worktrees";
4
+ export const CHANCES_DIR_NAME = ".chances";
5
+ export const ACTIVE_MARKER_FILE = "chances-active.json";
6
+ /** `<canonicalRoot>/.chances/worktrees`. Parent directory for every agent
7
+ * worktree under this repo. Created lazily on first `createAgentWorktree`. */
8
+ export function worktreesDir(canonicalRoot) {
9
+ return join(canonicalRoot, CHANCES_DIR_NAME, WORKTREE_DIR_NAME);
10
+ }
11
+ /** `<canonicalRoot>/.chances/worktrees/<slug>`. The exact path `git worktree
12
+ * add` is invoked against. */
13
+ export function worktreePathFor(canonicalRoot, slug) {
14
+ return join(worktreesDir(canonicalRoot), slug);
15
+ }
16
+ /**
17
+ * Liveness marker for an active agent worktree. Stored INSIDE the worktree's
18
+ * git-admin directory (`<repo>/.git/worktrees/<slug>/chances-active.json`),
19
+ * not inside the worktree's working tree — so `git status` never sees it as
20
+ * an untracked file (which would otherwise mark the worktree dirty and
21
+ * defeat the GC). The admin dir is created by `git worktree add` and lives
22
+ * for the worktree's lifetime; cleanup is handled by `git worktree remove`.
23
+ *
24
+ * For a linked worktree, `<worktreePath>/.git` is a pointer file whose first
25
+ * line reads `gitdir: <admin-dir>`. We resolve it on demand.
26
+ */
27
+ export async function activeMarkerPathFor(worktreePath) {
28
+ const adminDir = await resolveWorktreeGitDir(worktreePath);
29
+ return join(adminDir, ACTIVE_MARKER_FILE);
30
+ }
31
+ /** Resolves the worktree's git-admin directory. For linked worktrees this is
32
+ * `<repo>/.git/worktrees/<slug>/`; for the main worktree (rarely the case
33
+ * for agent worktrees, but supported) it's `<repo>/.git`. */
34
+ export async function resolveWorktreeGitDir(worktreePath) {
35
+ const dotGit = join(worktreePath, ".git");
36
+ let body;
37
+ try {
38
+ body = await readFile(dotGit, "utf8");
39
+ }
40
+ catch (e) {
41
+ // `.git` may be a directory in the (unusual) main-worktree case; fall
42
+ // back to using that directory as the admin dir.
43
+ if (e.code === "EISDIR")
44
+ return dotGit;
45
+ throw e;
46
+ }
47
+ const m = body.match(/^gitdir:\s*(.+?)\s*$/m);
48
+ if (!m)
49
+ return dotGit;
50
+ // (Round-2 SHOULD-FIX #6) Older Git or special configs can write a
51
+ // RELATIVE `gitdir:` path; the standard resolution rule is "relative
52
+ // to the directory containing the `.git` pointer file" (i.e. the
53
+ // worktree dir).
54
+ const target = m[1];
55
+ return isAbsolute(target) ? target : resolve(dirname(dotGit), target);
56
+ }
57
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../src/core/worktree/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE/D,MAAM,CAAC,MAAM,iBAAiB,GAAG,WAAW,CAAC;AAC7C,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;AAC3C,MAAM,CAAC,MAAM,kBAAkB,GAAG,qBAAqB,CAAC;AAExD;8EAC8E;AAC9E,MAAM,UAAU,YAAY,CAAC,aAAqB;IAChD,OAAO,IAAI,CAAC,aAAa,EAAE,gBAAgB,EAAE,iBAAiB,CAAC,CAAC;AAClE,CAAC;AAED;8BAC8B;AAC9B,MAAM,UAAU,eAAe,CAAC,aAAqB,EAAE,IAAY;IACjE,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,YAAoB;IAC5D,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,YAAY,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;AAC5C,CAAC;AAED;;6DAE6D;AAC7D,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,YAAoB;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,sEAAsE;QACtE,iDAAiD;QACjD,IAAK,CAA2B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,MAAM,CAAC;QAClE,MAAM,CAAC,CAAC;IACV,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;IAC9C,IAAI,CAAC,CAAC;QAAE,OAAO,MAAM,CAAC;IACtB,mEAAmE;IACnE,qEAAqE;IACrE,iEAAiE;IACjE,iBAAiB;IACjB,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;IACrB,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;AACxE,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function newAgentSlug(): string;
2
+ export declare function isAgentSlug(s: string): boolean;
3
+ /** Slug → branch name. The `chances/` namespace is ours; users can `git
4
+ * branch -D chances/*` to reclaim. */
5
+ export declare function branchNameFor(slug: string): string;
6
+ //# sourceMappingURL=slug.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.d.ts","sourceRoot":"","sources":["../../../src/core/worktree/slug.ts"],"names":[],"mappings":"AAQA,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;sCACsC;AACtC,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlD"}
@@ -0,0 +1,21 @@
1
+ import { randomBytes } from "node:crypto";
2
+ /** Slug format: `agent-` followed by exactly 8 lowercase hex chars. Matches
3
+ * claude-code's per-subagent slug shape. Branch-name-safe (no `/`), short
4
+ * enough to read, ~4 billion-space entropy so birthday collisions are
5
+ * negligible at any realistic concurrent-subagent count. */
6
+ const SLUG_RE = /^agent-[0-9a-f]{8}$/;
7
+ export function newAgentSlug() {
8
+ return `agent-${randomBytes(4).toString("hex")}`;
9
+ }
10
+ export function isAgentSlug(s) {
11
+ return SLUG_RE.test(s);
12
+ }
13
+ /** Slug → branch name. The `chances/` namespace is ours; users can `git
14
+ * branch -D chances/*` to reclaim. */
15
+ export function branchNameFor(slug) {
16
+ if (!isAgentSlug(slug)) {
17
+ throw new Error(`invalid slug for branch name: ${slug}`);
18
+ }
19
+ return `chances/worktree-${slug}`;
20
+ }
21
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.js","sourceRoot":"","sources":["../../../src/core/worktree/slug.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;4DAG4D;AAC5D,MAAM,OAAO,GAAG,qBAAqB,CAAC;AAEtC,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAS;IACnC,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACzB,CAAC;AAED;sCACsC;AACtC,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,oBAAoB,IAAI,EAAE,CAAC;AACpC,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * (3.7) AES-256-GCM encrypted JSON file backend with scrypt KDF.
3
+ *
4
+ * On-disk format (versioned for forward-compat):
5
+ * { v: 1, salt: base64, iv: base64, ciphertext: base64, tag: base64 }
6
+ *
7
+ * - Salt: 16 random bytes generated on first write; persisted unchanged
8
+ * thereafter so the derived key stays stable across writes.
9
+ * - IV: 12 random bytes ROTATED on every write (GCM requires unique
10
+ * IV per key/plaintext pair).
11
+ * - KDF: scryptSync (N=16384, r=8, p=1). Matches claude-code's LocalVault
12
+ * parameters. Cached per-passphrase+salt to amortise the ~80–150 ms
13
+ * derivation cost across writes within one process.
14
+ *
15
+ * **(codex Round-1 MUST-FIX #1)** All mutations go through `mergeMutate`
16
+ * which serialises per-key via the KeyedMutex and preserves unrelated
17
+ * fields. Atomic-rename alone is insufficient — concurrent partial
18
+ * updates would clobber each other.
19
+ */
20
+ export interface FileStoreOptions {
21
+ filePath: string;
22
+ passphrase: string;
23
+ /** Test seam — defaults to `randomBytes`. */
24
+ generateBytes?: (n: number) => Buffer;
25
+ }
26
+ export declare class FileStore {
27
+ private readonly mutex;
28
+ private readonly opts;
29
+ private cachedKey?;
30
+ constructor(opts: FileStoreOptions);
31
+ /** Returns the JSON value at `key`, or `undefined` when absent. */
32
+ read<T = unknown>(key: string): Promise<T | undefined>;
33
+ /** Returns every top-level key. */
34
+ list(): Promise<readonly string[]>;
35
+ /** Removes a key. No-op when absent. */
36
+ remove(key: string): Promise<void>;
37
+ /**
38
+ * The cornerstone API. Serialises per-key R-W-CAS:
39
+ * 1. Decrypt the whole on-disk blob.
40
+ * 2. Call `mutator(currentValueAtKey)`.
41
+ * 3. If mutator returns a value, replace; if it returns undefined AND
42
+ * `allowDelete` is true, delete the entry.
43
+ * 4. Encrypt + atomic-write.
44
+ *
45
+ * **(3.7 codex Round-2 MUST-FIX #1)** Uses a SINGLE file-wide mutex
46
+ * (constant `FILE_LOCK_KEY`), not a per-key mutex. The on-disk format
47
+ * is one encrypted JSON blob — read-modify-write of `key=A` and
48
+ * `key=B` both decrypt the SAME ciphertext, mutate different slots,
49
+ * and the last writer's `encryptAndWrite()` clobbers the other's
50
+ * mutation. Per-key locking only protects same-key writes; cross-key
51
+ * concurrency loses data. Serialising every file write behind one
52
+ * lock costs concurrency that file-vault setups don't have (write
53
+ * volume is "every few minutes when a token refreshes") and buys
54
+ * correctness on the only invariant that matters.
55
+ */
56
+ mergeMutate<T>(key: string, mutator: (current: T | undefined) => T | undefined | Promise<T | undefined>, allowDelete?: boolean): Promise<void>;
57
+ /** Decrypts the whole file. Returns `{}` when the file doesn't exist. */
58
+ private decryptAll;
59
+ /** Encrypts a payload and atomically writes the file. */
60
+ private encryptAndWrite;
61
+ /** Cached scrypt derivation. */
62
+ private deriveKey;
63
+ }
64
+ //# sourceMappingURL=file-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.d.ts","sourceRoot":"","sources":["../../src/local-vault/file-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAOH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACvC;AAyBD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAC1C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAmB;IACxC,OAAO,CAAC,SAAS,CAAC,CAAgC;gBAEtC,IAAI,EAAE,gBAAgB;IAIlC,mEAAmE;IAC7D,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAU5D,mCAAmC;IAC7B,IAAI,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAYxC,wCAAwC;IAClC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC;;;;;;;;;;;;;;;;;;OAkBG;IACG,WAAW,CAAC,CAAC,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EAC3E,WAAW,UAAQ,GAClB,OAAO,CAAC,IAAI,CAAC;IAiChB,yEAAyE;IACzE,OAAO,CAAC,UAAU;IAsClB,yDAAyD;IACzD,OAAO,CAAC,eAAe;IA0CvB,gCAAgC;IAChC,OAAO,CAAC,SAAS;CAYlB"}
@@ -0,0 +1,225 @@
1
+ /**
2
+ * (3.7) AES-256-GCM encrypted JSON file backend with scrypt KDF.
3
+ *
4
+ * On-disk format (versioned for forward-compat):
5
+ * { v: 1, salt: base64, iv: base64, ciphertext: base64, tag: base64 }
6
+ *
7
+ * - Salt: 16 random bytes generated on first write; persisted unchanged
8
+ * thereafter so the derived key stays stable across writes.
9
+ * - IV: 12 random bytes ROTATED on every write (GCM requires unique
10
+ * IV per key/plaintext pair).
11
+ * - KDF: scryptSync (N=16384, r=8, p=1). Matches claude-code's LocalVault
12
+ * parameters. Cached per-passphrase+salt to amortise the ~80–150 ms
13
+ * derivation cost across writes within one process.
14
+ *
15
+ * **(codex Round-1 MUST-FIX #1)** All mutations go through `mergeMutate`
16
+ * which serialises per-key via the KeyedMutex and preserves unrelated
17
+ * fields. Atomic-rename alone is insufficient — concurrent partial
18
+ * updates would clobber each other.
19
+ */
20
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
21
+ import { existsSync, readFileSync, renameSync, statSync, writeFileSync, chmodSync, mkdirSync } from "node:fs";
22
+ import { dirname } from "node:path";
23
+ import { KeyedMutex } from "./mutex.js";
24
+ const SALT_LEN = 16;
25
+ const IV_LEN = 12;
26
+ const KEY_LEN = 32;
27
+ const SCRYPT_N = 16384;
28
+ const SCRYPT_R = 8;
29
+ const SCRYPT_P = 1;
30
+ /** (codex Round-2 MUST-FIX #1) Single file-wide lock key — every
31
+ * `mergeMutate` rewrites the whole encrypted blob, so cross-key
32
+ * concurrency is unsafe with per-key granularity. See class docstring. */
33
+ const FILE_LOCK_KEY = "__file_lock__";
34
+ export class FileStore {
35
+ mutex = new KeyedMutex();
36
+ opts;
37
+ cachedKey;
38
+ constructor(opts) {
39
+ this.opts = opts;
40
+ }
41
+ /** Returns the JSON value at `key`, or `undefined` when absent. */
42
+ async read(key) {
43
+ const release = await this.mutex.acquire(key);
44
+ try {
45
+ const payload = this.decryptAll();
46
+ return payload[key];
47
+ }
48
+ finally {
49
+ release();
50
+ }
51
+ }
52
+ /** Returns every top-level key. */
53
+ async list() {
54
+ // Global lock — we read every key. Mutate ops do per-key locking; this
55
+ // is the lone exception that competes with EVERY pending write.
56
+ const release = await this.mutex.acquire("__list__");
57
+ try {
58
+ const payload = this.decryptAll();
59
+ return Object.keys(payload);
60
+ }
61
+ finally {
62
+ release();
63
+ }
64
+ }
65
+ /** Removes a key. No-op when absent. */
66
+ async remove(key) {
67
+ await this.mergeMutate(key, (current) => {
68
+ if (current === undefined)
69
+ return undefined;
70
+ return undefined; // Marker for delete (vs no-change)
71
+ }, /*allowDelete=*/ true);
72
+ }
73
+ /**
74
+ * The cornerstone API. Serialises per-key R-W-CAS:
75
+ * 1. Decrypt the whole on-disk blob.
76
+ * 2. Call `mutator(currentValueAtKey)`.
77
+ * 3. If mutator returns a value, replace; if it returns undefined AND
78
+ * `allowDelete` is true, delete the entry.
79
+ * 4. Encrypt + atomic-write.
80
+ *
81
+ * **(3.7 codex Round-2 MUST-FIX #1)** Uses a SINGLE file-wide mutex
82
+ * (constant `FILE_LOCK_KEY`), not a per-key mutex. The on-disk format
83
+ * is one encrypted JSON blob — read-modify-write of `key=A` and
84
+ * `key=B` both decrypt the SAME ciphertext, mutate different slots,
85
+ * and the last writer's `encryptAndWrite()` clobbers the other's
86
+ * mutation. Per-key locking only protects same-key writes; cross-key
87
+ * concurrency loses data. Serialising every file write behind one
88
+ * lock costs concurrency that file-vault setups don't have (write
89
+ * volume is "every few minutes when a token refreshes") and buys
90
+ * correctness on the only invariant that matters.
91
+ */
92
+ async mergeMutate(key, mutator, allowDelete = false) {
93
+ const release = await this.mutex.acquire(FILE_LOCK_KEY);
94
+ try {
95
+ // Re-read inside the lock so we always see the latest state, even if
96
+ // another tab/process raced us between the mutex-acquire and our read.
97
+ // (Cross-process is still out of scope — see § 1 of the design doc —
98
+ // but in-process correctness is locked down here.)
99
+ const payload = this.decryptAll();
100
+ const previous = payload[key];
101
+ const next = await mutator(previous);
102
+ if (next === undefined) {
103
+ if (!allowDelete && previous === undefined) {
104
+ // Mutator declined to set, and nothing was there — write a no-op.
105
+ // Returning here skips the disk write to avoid pointless IV churn.
106
+ return;
107
+ }
108
+ if (allowDelete) {
109
+ delete payload[key];
110
+ }
111
+ else if (previous !== undefined) {
112
+ // Mutator returned undefined but allowDelete is false — interpret
113
+ // as "no change," not delete. Defensive — keeps the API
114
+ // explicit about the destructive intent of `remove`.
115
+ return;
116
+ }
117
+ }
118
+ else {
119
+ payload[key] = next;
120
+ }
121
+ this.encryptAndWrite(payload);
122
+ }
123
+ finally {
124
+ release();
125
+ }
126
+ }
127
+ /** Decrypts the whole file. Returns `{}` when the file doesn't exist. */
128
+ decryptAll() {
129
+ if (!existsSync(this.opts.filePath))
130
+ return {};
131
+ const raw = readFileSync(this.opts.filePath, "utf8");
132
+ let parsed;
133
+ try {
134
+ parsed = JSON.parse(raw);
135
+ }
136
+ catch (e) {
137
+ throw new Error(`local-vault: failed to parse ${this.opts.filePath}: ${e.message}. ` +
138
+ `The file may be corrupted; back it up and delete to start fresh.`);
139
+ }
140
+ if (parsed.v !== 1) {
141
+ throw new Error(`local-vault: unsupported on-disk version ${parsed.v} (this build supports v1)`);
142
+ }
143
+ const salt = Buffer.from(parsed.salt, "base64");
144
+ const iv = Buffer.from(parsed.iv, "base64");
145
+ const ciphertext = Buffer.from(parsed.ciphertext, "base64");
146
+ const tag = Buffer.from(parsed.tag, "base64");
147
+ const key = this.deriveKey(salt);
148
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
149
+ decipher.setAuthTag(tag);
150
+ let plaintext;
151
+ try {
152
+ plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
153
+ }
154
+ catch (e) {
155
+ throw new Error(`local-vault: decryption failed — wrong passphrase OR corrupted file. ` +
156
+ `Underlying: ${e.message}`);
157
+ }
158
+ try {
159
+ return JSON.parse(plaintext.toString("utf8"));
160
+ }
161
+ catch (e) {
162
+ throw new Error(`local-vault: decrypted JSON is malformed: ${e.message}`);
163
+ }
164
+ }
165
+ /** Encrypts a payload and atomically writes the file. */
166
+ encryptAndWrite(payload) {
167
+ // Reuse the existing salt when present, else mint a new one.
168
+ let salt;
169
+ if (this.cachedKey) {
170
+ salt = this.cachedKey.salt;
171
+ }
172
+ else if (existsSync(this.opts.filePath)) {
173
+ // Edge: cache empty but file exists. Read the salt from disk.
174
+ const existing = JSON.parse(readFileSync(this.opts.filePath, "utf8"));
175
+ salt = Buffer.from(existing.salt, "base64");
176
+ }
177
+ else {
178
+ salt = (this.opts.generateBytes ?? randomBytes)(SALT_LEN);
179
+ }
180
+ const key = this.deriveKey(salt);
181
+ const iv = (this.opts.generateBytes ?? randomBytes)(IV_LEN);
182
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
183
+ const plaintext = Buffer.from(JSON.stringify(payload), "utf8");
184
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
185
+ const tag = cipher.getAuthTag();
186
+ const wire = {
187
+ v: 1,
188
+ salt: salt.toString("base64"),
189
+ iv: iv.toString("base64"),
190
+ ciphertext: ciphertext.toString("base64"),
191
+ tag: tag.toString("base64"),
192
+ };
193
+ // Ensure parent dir exists. Atomic write via tmp + rename.
194
+ mkdirSync(dirname(this.opts.filePath), { recursive: true });
195
+ const tmp = `${this.opts.filePath}.tmp.${process.pid}.${Date.now()}`;
196
+ writeFileSync(tmp, JSON.stringify(wire), { mode: 0o600 });
197
+ renameSync(tmp, this.opts.filePath);
198
+ if (process.platform !== "win32") {
199
+ // Re-assert 0600 — atomic rename preserves source mode, but a tmp
200
+ // file created with umask interference could land at 0644.
201
+ try {
202
+ const stat = statSync(this.opts.filePath);
203
+ if ((stat.mode & 0o777) !== 0o600)
204
+ chmodSync(this.opts.filePath, 0o600);
205
+ }
206
+ catch {
207
+ /* race; fall through */
208
+ }
209
+ }
210
+ }
211
+ /** Cached scrypt derivation. */
212
+ deriveKey(salt) {
213
+ if (this.cachedKey && this.cachedKey.salt.equals(salt)) {
214
+ return this.cachedKey.key;
215
+ }
216
+ const key = scryptSync(this.opts.passphrase, salt, KEY_LEN, {
217
+ N: SCRYPT_N,
218
+ r: SCRYPT_R,
219
+ p: SCRYPT_P,
220
+ });
221
+ this.cachedKey = { salt, key };
222
+ return key;
223
+ }
224
+ }
225
+ //# sourceMappingURL=file-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-store.js","sourceRoot":"","sources":["../../src/local-vault/file-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * (3.7) `@chances-ai/local-vault` — encrypted credential store.
3
+ *
4
+ * Public surface: `createLocalVault(opts?) -> Promise<LocalVault>`.
5
+ *
6
+ * Backend selection at boot:
7
+ * 1. Probe `@napi-rs/keyring` — if it loads AND a set/get round-trip
8
+ * against a probe key succeeds, backend = keychain.
9
+ * 2. Otherwise, backend = AES-256-GCM file at
10
+ * `<rootDir>/local-vault.enc.json`, with passphrase resolved per
11
+ * `passphrase.ts` (env > sibling file > generated).
12
+ *
13
+ * Both backends share the same `LocalVault` surface: read/list/remove/
14
+ * mergeMutate/diagnostics. The mergeMutate primitive (per-key serialised
15
+ * read-modify-write) is the codex Round-1 MUST-FIX #1 fix — atomic
16
+ * rename alone doesn't prevent partial-update clobber.
17
+ */
18
+ import { type KeyringLoader } from "./keychain.js";
19
+ export interface LocalVault {
20
+ read<T = unknown>(key: string): Promise<T | undefined>;
21
+ list(): Promise<readonly string[]>;
22
+ remove(key: string): Promise<void>;
23
+ /** Per-key serialised read-modify-write. Mutator receives the current
24
+ * value (or undefined). Returning a value writes it. Returning
25
+ * undefined is a no-op UNLESS `allowDelete=true`. */
26
+ mergeMutate<T>(key: string, mutator: (current: T | undefined) => T | undefined | Promise<T | undefined>, allowDelete?: boolean): Promise<void>;
27
+ diagnostics(): VaultDiagnostics;
28
+ }
29
+ export interface VaultDiagnostics {
30
+ backend: "keychain" | "file";
31
+ /** Human-readable reasons explaining why the keychain backend was
32
+ * bypassed, when applicable. */
33
+ warnings: readonly string[];
34
+ }
35
+ export interface CreateLocalVaultOptions {
36
+ /** Where vault data lives. Defaults to `~/.chances/`. */
37
+ rootDir?: string;
38
+ /** Where the passphrase file lives. Defaults to
39
+ * `<dirname(homedir)>/.chances-vault-passphrase` — OUTSIDE the
40
+ * vault tree so a backup of `rootDir` alone doesn't leak the key
41
+ * (codex Round-1 SHOULD-FIX). */
42
+ passphraseFile?: string;
43
+ /** Test seam — swap the keyring loader. Defaults to the real loader. */
44
+ keyringLoader?: KeyringLoader;
45
+ /** Force the file backend even when keychain is available. Used for
46
+ * smoke tests of the fallback path; users shouldn't set this. */
47
+ forceFileBackend?: boolean;
48
+ /** Test seam — override the passphrase resolution entirely. */
49
+ fixedPassphrase?: string;
50
+ }
51
+ export declare const VAULT_FILE_NAME = "local-vault.enc.json";
52
+ export declare function createLocalVault(opts?: CreateLocalVaultOptions): Promise<LocalVault>;
53
+ export { FileStore } from "./file-store.js";
54
+ export { KeychainStore, type KeyringLoader, type KeyringModule } from "./keychain.js";
55
+ export { resolvePassphrase, ENV_VAR as VAULT_ENV_VAR } from "./passphrase.js";
56
+ export { KeyedMutex } from "./mutex.js";
57
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/local-vault/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGlE,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACvD,IAAI,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IACnC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC;;0DAEsD;IACtD,WAAW,CAAC,CAAC,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EAC3E,WAAW,CAAC,EAAE,OAAO,GACpB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,WAAW,IAAI,gBAAgB,CAAC;CACjC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,UAAU,GAAG,MAAM,CAAC;IAC7B;qCACiC;IACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;sCAGkC;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wEAAwE;IACxE,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;sEACkE;IAClE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,+DAA+D;IAC/D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,eAAe,yBAAyB,CAAC;AAEtD,wBAAsB,gBAAgB,CAAC,IAAI,GAAE,uBAA4B,GAAG,OAAO,CAAC,UAAU,CAAC,CAqB9F;AAuBD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,iBAAiB,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * (3.7) `@chances-ai/local-vault` — encrypted credential store.
3
+ *
4
+ * Public surface: `createLocalVault(opts?) -> Promise<LocalVault>`.
5
+ *
6
+ * Backend selection at boot:
7
+ * 1. Probe `@napi-rs/keyring` — if it loads AND a set/get round-trip
8
+ * against a probe key succeeds, backend = keychain.
9
+ * 2. Otherwise, backend = AES-256-GCM file at
10
+ * `<rootDir>/local-vault.enc.json`, with passphrase resolved per
11
+ * `passphrase.ts` (env > sibling file > generated).
12
+ *
13
+ * Both backends share the same `LocalVault` surface: read/list/remove/
14
+ * mergeMutate/diagnostics. The mergeMutate primitive (per-key serialised
15
+ * read-modify-write) is the codex Round-1 MUST-FIX #1 fix — atomic
16
+ * rename alone doesn't prevent partial-update clobber.
17
+ */
18
+ import { homedir } from "node:os";
19
+ import { join } from "node:path";
20
+ import { FileStore } from "./file-store.js";
21
+ import { KeychainStore } from "./keychain.js";
22
+ import { resolvePassphrase } from "./passphrase.js";
23
+ export const VAULT_FILE_NAME = "local-vault.enc.json";
24
+ export async function createLocalVault(opts = {}) {
25
+ const rootDir = opts.rootDir ?? join(homedir(), ".chances");
26
+ const passphraseFile = opts.passphraseFile ?? join(homedir(), ".chances-vault-passphrase");
27
+ const warnings = [];
28
+ // 1. Try keychain first, unless explicitly forced to file.
29
+ if (!opts.forceFileBackend) {
30
+ const probe = await KeychainStore.tryCreate(opts.keyringLoader);
31
+ if (probe.store) {
32
+ return wrapKeychain(probe.store);
33
+ }
34
+ warnings.push(probe.reason);
35
+ }
36
+ else {
37
+ warnings.push("forceFileBackend=true (test/diagnostic mode)");
38
+ }
39
+ // 2. Fall back to file backend.
40
+ const filePath = join(rootDir, VAULT_FILE_NAME);
41
+ const passphrase = opts.fixedPassphrase ?? resolvePassphrase({ passphraseFile }).passphrase;
42
+ const file = new FileStore({ filePath, passphrase });
43
+ return wrapFile(file, warnings);
44
+ }
45
+ function wrapKeychain(store) {
46
+ return {
47
+ read: (k) => store.read(k),
48
+ list: () => store.list(),
49
+ remove: (k) => store.remove(k),
50
+ mergeMutate: (k, m, allowDelete) => store.mergeMutate(k, m, allowDelete),
51
+ diagnostics: () => ({ backend: "keychain", warnings: [] }),
52
+ };
53
+ }
54
+ function wrapFile(store, warnings) {
55
+ const frozen = Object.freeze([...warnings]);
56
+ return {
57
+ read: (k) => store.read(k),
58
+ list: () => store.list(),
59
+ remove: (k) => store.remove(k),
60
+ mergeMutate: (k, m, allowDelete) => store.mergeMutate(k, m, allowDelete),
61
+ diagnostics: () => ({ backend: "file", warnings: frozen }),
62
+ };
63
+ }
64
+ export { FileStore } from "./file-store.js";
65
+ export { KeychainStore } from "./keychain.js";
66
+ export { resolvePassphrase, ENV_VAR as VAULT_ENV_VAR } from "./passphrase.js";
67
+ export { KeyedMutex } from "./mutex.js";
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/local-vault/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * (3.7) OS keychain adapter wrapping `@napi-rs/keyring`.
3
+ *
4
+ * Loaded via a computed-specifier dynamic import (same `bun build
5
+ * --compile` reason as 3.6 OTel) so the binary still compiles when the
6
+ * optional peer is absent. A consumer that didn't install the keyring
7
+ * package falls back to the file backend (`./file-store.ts`) — same
8
+ * graceful degradation as before, no opaque "module not found" crash
9
+ * at boot.
10
+ *
11
+ * Layout: service name `chances-cli` (fixed), account name = vault key.
12
+ * Values are JSON-stringified before storage; parsed on read.
13
+ *
14
+ * **(claude-code anti-pattern noted)** Don't store full RFC 8414 metadata
15
+ * here — Windows Credential Manager has a 4096-byte limit on stored
16
+ * values. § 4.2 of the design doc enforces "URLs only" at the higher
17
+ * (provider) layer; this adapter doesn't try to enforce shape.
18
+ */
19
+ export interface KeyringEntry {
20
+ setPassword(password: string): void | Promise<void>;
21
+ getPassword(): string | null | Promise<string | null>;
22
+ deletePassword(): boolean | Promise<boolean>;
23
+ }
24
+ export interface KeyringModule {
25
+ Entry: new (service: string, account: string) => KeyringEntry;
26
+ }
27
+ export type KeyringLoader = () => Promise<KeyringModule | null>;
28
+ export declare class KeychainStore {
29
+ private readonly mod;
30
+ private readonly mutex;
31
+ private constructor();
32
+ /**
33
+ * Loads the keyring module + runs an access probe. Returns:
34
+ * - the store, when the module loaded AND the probe succeeded;
35
+ * - `null` with a reason string, otherwise.
36
+ *
37
+ * The probe is critical on macOS — the module loads fine, but the FIRST
38
+ * keychain op can throw with a user-denied dialog. We catch that here so
39
+ * the fallback path runs without hanging the consumer.
40
+ */
41
+ static tryCreate(loader?: KeyringLoader): Promise<{
42
+ store: KeychainStore;
43
+ } | {
44
+ store: null;
45
+ reason: string;
46
+ }>;
47
+ read<T = unknown>(key: string): Promise<T | undefined>;
48
+ list(): Promise<readonly string[]>;
49
+ remove(key: string): Promise<void>;
50
+ mergeMutate<T>(key: string, mutator: (current: T | undefined) => T | undefined | Promise<T | undefined>, allowDelete?: boolean): Promise<void>;
51
+ /** (codex Round-2 NICE #3) Update the index under its OWN mutex so
52
+ * concurrent `mergeMutate("a")` / `mergeMutate("b")` can't race on
53
+ * the shared `__chances_keys__` entry. Each holds its per-key lock
54
+ * for the value write, then queues behind the index mutex for the
55
+ * list update. `list()` could otherwise drop entries. */
56
+ private recordKey;
57
+ }
58
+ //# sourceMappingURL=keychain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../../src/local-vault/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAUH,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,WAAW,IAAI,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACtD,cAAc,IAAI,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,YAAY,CAAC;CAC/D;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;AAehE,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAgB;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAoB;IAE1C,OAAO;IAIP;;;;;;;;OAQG;WACU,SAAS,CACpB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC;QAAE,KAAK,EAAE,aAAa,CAAA;KAAE,GAAG;QAAE,KAAK,EAAE,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAwBhE,IAAI,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAgBtD,IAAI,IAAI,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAWlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgClC,WAAW,CAAC,CAAC,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,EAC3E,WAAW,UAAQ,GAClB,OAAO,CAAC,IAAI,CAAC;IA6BhB;;;;8DAI0D;YAC5C,SAAS;CAsBxB"}