@bastani/atomic 0.8.10 → 0.8.12-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 (514) hide show
  1. package/CHANGELOG.md +29 -2
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/CHANGELOG.md +3 -0
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/subagents/src/agents/agent-serializer.ts +3 -2
  7. package/dist/builtin/subagents/src/agents/agents.ts +1 -1
  8. package/dist/builtin/subagents/src/extension/index.ts +597 -471
  9. package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +16 -8
  10. package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +9 -13
  11. package/dist/builtin/subagents/src/runs/foreground/execution.ts +7 -3
  12. package/dist/builtin/subagents/src/shared/formatters.ts +8 -3
  13. package/dist/builtin/subagents/src/slash/slash-commands.ts +625 -468
  14. package/dist/builtin/subagents/src/tui/render.ts +342 -158
  15. package/dist/builtin/web-access/package.json +1 -1
  16. package/dist/builtin/workflows/package.json +1 -1
  17. package/dist/builtin/workflows/src/runs/foreground/executor.ts +15 -4
  18. package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +75 -33
  19. package/dist/builtin/workflows/src/shared/store-types.ts +3 -4
  20. package/dist/builtin/workflows/src/shared/store.ts +2 -3
  21. package/dist/builtin/workflows/src/tui/graph-view.ts +12 -1
  22. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +1 -2
  23. package/dist/builtin/workflows/src/tui/status-helpers.ts +1 -1
  24. package/dist/bun/cli.d.ts.map +1 -1
  25. package/dist/bun/cli.js.map +1 -1
  26. package/dist/cli/args.d.ts +1 -1
  27. package/dist/cli/args.d.ts.map +1 -1
  28. package/dist/cli/args.js.map +1 -1
  29. package/dist/cli/config-selector.d.ts +2 -2
  30. package/dist/cli/config-selector.d.ts.map +1 -1
  31. package/dist/cli/config-selector.js.map +1 -1
  32. package/dist/cli/file-processor.d.ts.map +1 -1
  33. package/dist/cli/file-processor.js.map +1 -1
  34. package/dist/cli/initial-message.d.ts +1 -1
  35. package/dist/cli/initial-message.d.ts.map +1 -1
  36. package/dist/cli/initial-message.js.map +1 -1
  37. package/dist/cli/list-models.d.ts +1 -1
  38. package/dist/cli/list-models.d.ts.map +1 -1
  39. package/dist/cli/list-models.js.map +1 -1
  40. package/dist/cli/session-picker.d.ts +1 -1
  41. package/dist/cli/session-picker.d.ts.map +1 -1
  42. package/dist/cli/session-picker.js.map +1 -1
  43. package/dist/cli.d.ts.map +1 -1
  44. package/dist/cli.js +2 -6
  45. package/dist/cli.js.map +1 -1
  46. package/dist/config.d.ts.map +1 -1
  47. package/dist/config.js +45 -22
  48. package/dist/config.js.map +1 -1
  49. package/dist/core/agent-session-runtime.d.ts +9 -9
  50. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  51. package/dist/core/agent-session-runtime.js +2 -3
  52. package/dist/core/agent-session-runtime.js.map +1 -1
  53. package/dist/core/agent-session-services.d.ts +7 -7
  54. package/dist/core/agent-session-services.d.ts.map +1 -1
  55. package/dist/core/agent-session-services.js.map +1 -1
  56. package/dist/core/agent-session.d.ts +10 -10
  57. package/dist/core/agent-session.d.ts.map +1 -1
  58. package/dist/core/agent-session.js.map +1 -1
  59. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  60. package/dist/core/atomic-guide-command.js.map +1 -1
  61. package/dist/core/auth-guidance.d.ts.map +1 -1
  62. package/dist/core/auth-guidance.js.map +1 -1
  63. package/dist/core/auth-storage.d.ts +1 -1
  64. package/dist/core/auth-storage.d.ts.map +1 -1
  65. package/dist/core/auth-storage.js +1 -1
  66. package/dist/core/auth-storage.js.map +1 -1
  67. package/dist/core/bash-executor.d.ts +1 -1
  68. package/dist/core/bash-executor.d.ts.map +1 -1
  69. package/dist/core/bash-executor.js.map +1 -1
  70. package/dist/core/builtin-packages.d.ts.map +1 -1
  71. package/dist/core/builtin-packages.js.map +1 -1
  72. package/dist/core/compaction/branch-summarization.d.ts +3 -3
  73. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  74. package/dist/core/compaction/branch-summarization.js.map +1 -1
  75. package/dist/core/compaction/compaction.d.ts +2 -2
  76. package/dist/core/compaction/compaction.d.ts.map +1 -1
  77. package/dist/core/compaction/compaction.js.map +1 -1
  78. package/dist/core/compaction/index.d.ts +3 -3
  79. package/dist/core/compaction/index.d.ts.map +1 -1
  80. package/dist/core/compaction/index.js.map +1 -1
  81. package/dist/core/exec.d.ts.map +1 -1
  82. package/dist/core/exec.js.map +1 -1
  83. package/dist/core/export-html/index.d.ts +1 -1
  84. package/dist/core/export-html/index.d.ts.map +1 -1
  85. package/dist/core/export-html/index.js.map +1 -1
  86. package/dist/core/export-html/tool-renderer.d.ts +2 -2
  87. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  88. package/dist/core/export-html/tool-renderer.js.map +1 -1
  89. package/dist/core/extensions/index.d.ts +8 -8
  90. package/dist/core/extensions/index.d.ts.map +1 -1
  91. package/dist/core/extensions/index.js.map +1 -1
  92. package/dist/core/extensions/loader.d.ts +3 -3
  93. package/dist/core/extensions/loader.d.ts.map +1 -1
  94. package/dist/core/extensions/loader.js.map +1 -1
  95. package/dist/core/extensions/runner.d.ts +6 -6
  96. package/dist/core/extensions/runner.d.ts.map +1 -1
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/extensions/types.d.ts +20 -20
  99. package/dist/core/extensions/types.d.ts.map +1 -1
  100. package/dist/core/extensions/types.js.map +1 -1
  101. package/dist/core/extensions/wrapper.d.ts +2 -2
  102. package/dist/core/extensions/wrapper.d.ts.map +1 -1
  103. package/dist/core/extensions/wrapper.js.map +1 -1
  104. package/dist/core/footer-data-provider.d.ts.map +1 -1
  105. package/dist/core/footer-data-provider.js.map +1 -1
  106. package/dist/core/http-dispatcher.d.ts +32 -0
  107. package/dist/core/http-dispatcher.d.ts.map +1 -0
  108. package/dist/core/http-dispatcher.js +43 -0
  109. package/dist/core/http-dispatcher.js.map +1 -0
  110. package/dist/core/index.d.ts +8 -8
  111. package/dist/core/index.d.ts.map +1 -1
  112. package/dist/core/index.js.map +1 -1
  113. package/dist/core/keybindings.d.ts.map +1 -1
  114. package/dist/core/keybindings.js.map +1 -1
  115. package/dist/core/model-registry.d.ts +4 -4
  116. package/dist/core/model-registry.d.ts.map +1 -1
  117. package/dist/core/model-registry.js +2 -2
  118. package/dist/core/model-registry.js.map +1 -1
  119. package/dist/core/model-resolver.d.ts +1 -1
  120. package/dist/core/model-resolver.d.ts.map +1 -1
  121. package/dist/core/model-resolver.js.map +1 -1
  122. package/dist/core/package-manager.d.ts +1 -1
  123. package/dist/core/package-manager.d.ts.map +1 -1
  124. package/dist/core/package-manager.js +10 -11
  125. package/dist/core/package-manager.js.map +1 -1
  126. package/dist/core/prompt-templates.d.ts +1 -1
  127. package/dist/core/prompt-templates.d.ts.map +1 -1
  128. package/dist/core/prompt-templates.js.map +1 -1
  129. package/dist/core/resolve-config-value.d.ts.map +1 -1
  130. package/dist/core/resolve-config-value.js.map +1 -1
  131. package/dist/core/resource-loader.d.ts +9 -9
  132. package/dist/core/resource-loader.d.ts.map +1 -1
  133. package/dist/core/resource-loader.js.map +1 -1
  134. package/dist/core/sdk.d.ts +13 -13
  135. package/dist/core/sdk.d.ts.map +1 -1
  136. package/dist/core/sdk.js.map +1 -1
  137. package/dist/core/session-manager.d.ts +1 -1
  138. package/dist/core/session-manager.d.ts.map +1 -1
  139. package/dist/core/session-manager.js.map +1 -1
  140. package/dist/core/settings-manager.d.ts +3 -0
  141. package/dist/core/settings-manager.d.ts.map +1 -1
  142. package/dist/core/settings-manager.js +21 -0
  143. package/dist/core/settings-manager.js.map +1 -1
  144. package/dist/core/skills.d.ts +2 -2
  145. package/dist/core/skills.d.ts.map +1 -1
  146. package/dist/core/skills.js.map +1 -1
  147. package/dist/core/slash-commands.d.ts +1 -1
  148. package/dist/core/slash-commands.d.ts.map +1 -1
  149. package/dist/core/slash-commands.js.map +1 -1
  150. package/dist/core/source-info.d.ts +1 -1
  151. package/dist/core/source-info.d.ts.map +1 -1
  152. package/dist/core/source-info.js.map +1 -1
  153. package/dist/core/system-prompt.d.ts +1 -1
  154. package/dist/core/system-prompt.d.ts.map +1 -1
  155. package/dist/core/system-prompt.js +7 -8
  156. package/dist/core/system-prompt.js.map +1 -1
  157. package/dist/core/telemetry.d.ts +1 -1
  158. package/dist/core/telemetry.d.ts.map +1 -1
  159. package/dist/core/telemetry.js.map +1 -1
  160. package/dist/core/timings.d.ts.map +1 -1
  161. package/dist/core/timings.js.map +1 -1
  162. package/dist/core/tools/ask-user-question/ask-user-question.d.ts +4 -4
  163. package/dist/core/tools/ask-user-question/ask-user-question.d.ts.map +1 -1
  164. package/dist/core/tools/ask-user-question/ask-user-question.js.map +1 -1
  165. package/dist/core/tools/ask-user-question/index.d.ts +1 -1
  166. package/dist/core/tools/ask-user-question/index.d.ts.map +1 -1
  167. package/dist/core/tools/ask-user-question/index.js.map +1 -1
  168. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts +5 -5
  169. package/dist/core/tools/ask-user-question/state/build-questionnaire.d.ts.map +1 -1
  170. package/dist/core/tools/ask-user-question/state/build-questionnaire.js.map +1 -1
  171. package/dist/core/tools/ask-user-question/state/key-router.d.ts +2 -2
  172. package/dist/core/tools/ask-user-question/state/key-router.d.ts.map +1 -1
  173. package/dist/core/tools/ask-user-question/state/key-router.js.map +1 -1
  174. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts +3 -3
  175. package/dist/core/tools/ask-user-question/state/questionnaire-session.d.ts.map +1 -1
  176. package/dist/core/tools/ask-user-question/state/questionnaire-session.js.map +1 -1
  177. package/dist/core/tools/ask-user-question/state/row-intent.d.ts +2 -2
  178. package/dist/core/tools/ask-user-question/state/row-intent.d.ts.map +1 -1
  179. package/dist/core/tools/ask-user-question/state/row-intent.js.map +1 -1
  180. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts +6 -6
  181. package/dist/core/tools/ask-user-question/state/selectors/contract.d.ts.map +1 -1
  182. package/dist/core/tools/ask-user-question/state/selectors/contract.js.map +1 -1
  183. package/dist/core/tools/ask-user-question/state/selectors/derivations.d.ts +2 -2
  184. package/dist/core/tools/ask-user-question/state/selectors/derivations.d.ts.map +1 -1
  185. package/dist/core/tools/ask-user-question/state/selectors/derivations.js.map +1 -1
  186. package/dist/core/tools/ask-user-question/state/selectors/focus.d.ts +1 -1
  187. package/dist/core/tools/ask-user-question/state/selectors/focus.d.ts.map +1 -1
  188. package/dist/core/tools/ask-user-question/state/selectors/focus.js.map +1 -1
  189. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts +8 -8
  190. package/dist/core/tools/ask-user-question/state/selectors/projections.d.ts.map +1 -1
  191. package/dist/core/tools/ask-user-question/state/selectors/projections.js.map +1 -1
  192. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts +4 -4
  193. package/dist/core/tools/ask-user-question/state/state-reducer.d.ts.map +1 -1
  194. package/dist/core/tools/ask-user-question/state/state-reducer.js.map +1 -1
  195. package/dist/core/tools/ask-user-question/state/state.d.ts +2 -2
  196. package/dist/core/tools/ask-user-question/state/state.d.ts.map +1 -1
  197. package/dist/core/tools/ask-user-question/state/state.js.map +1 -1
  198. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts +1 -1
  199. package/dist/core/tools/ask-user-question/tool/format-answer.d.ts.map +1 -1
  200. package/dist/core/tools/ask-user-question/tool/format-answer.js.map +1 -1
  201. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts +1 -1
  202. package/dist/core/tools/ask-user-question/tool/response-envelope.d.ts.map +1 -1
  203. package/dist/core/tools/ask-user-question/tool/response-envelope.js.map +1 -1
  204. package/dist/core/tools/ask-user-question/tool/types.d.ts.map +1 -1
  205. package/dist/core/tools/ask-user-question/tool/types.js.map +1 -1
  206. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.d.ts +1 -1
  207. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.d.ts.map +1 -1
  208. package/dist/core/tools/ask-user-question/tool/validate-questionnaire.js.map +1 -1
  209. package/dist/core/tools/ask-user-question/view/body-residual-spacer.d.ts.map +1 -1
  210. package/dist/core/tools/ask-user-question/view/body-residual-spacer.js.map +1 -1
  211. package/dist/core/tools/ask-user-question/view/component-binding.d.ts +4 -4
  212. package/dist/core/tools/ask-user-question/view/component-binding.d.ts.map +1 -1
  213. package/dist/core/tools/ask-user-question/view/component-binding.js.map +1 -1
  214. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts +2 -2
  215. package/dist/core/tools/ask-user-question/view/components/chat-row-view.d.ts.map +1 -1
  216. package/dist/core/tools/ask-user-question/view/components/chat-row-view.js.map +1 -1
  217. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts +4 -4
  218. package/dist/core/tools/ask-user-question/view/components/multi-select-view.d.ts.map +1 -1
  219. package/dist/core/tools/ask-user-question/view/components/multi-select-view.js.map +1 -1
  220. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts +2 -2
  221. package/dist/core/tools/ask-user-question/view/components/option-list-view.d.ts.map +1 -1
  222. package/dist/core/tools/ask-user-question/view/components/option-list-view.js.map +1 -1
  223. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts +2 -2
  224. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.d.ts.map +1 -1
  225. package/dist/core/tools/ask-user-question/view/components/preview/markdown-content-cache.js.map +1 -1
  226. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts +3 -3
  227. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.d.ts.map +1 -1
  228. package/dist/core/tools/ask-user-question/view/components/preview/preview-block-renderer.js.map +1 -1
  229. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.d.ts +2 -2
  230. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.d.ts.map +1 -1
  231. package/dist/core/tools/ask-user-question/view/components/preview/preview-layout-decider.js.map +1 -1
  232. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.d.ts +8 -8
  233. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.d.ts.map +1 -1
  234. package/dist/core/tools/ask-user-question/view/components/preview/preview-pane.js.map +1 -1
  235. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts +3 -3
  236. package/dist/core/tools/ask-user-question/view/components/submit-picker.d.ts.map +1 -1
  237. package/dist/core/tools/ask-user-question/view/components/submit-picker.js.map +1 -1
  238. package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts +3 -3
  239. package/dist/core/tools/ask-user-question/view/components/tab-bar.d.ts.map +1 -1
  240. package/dist/core/tools/ask-user-question/view/components/tab-bar.js.map +1 -1
  241. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts +8 -8
  242. package/dist/core/tools/ask-user-question/view/dialog-builder.d.ts.map +1 -1
  243. package/dist/core/tools/ask-user-question/view/dialog-builder.js.map +1 -1
  244. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts +5 -5
  245. package/dist/core/tools/ask-user-question/view/props-adapter.d.ts.map +1 -1
  246. package/dist/core/tools/ask-user-question/view/props-adapter.js.map +1 -1
  247. package/dist/core/tools/ask-user-question/view/tab-components.d.ts +4 -4
  248. package/dist/core/tools/ask-user-question/view/tab-components.d.ts.map +1 -1
  249. package/dist/core/tools/ask-user-question/view/tab-components.js.map +1 -1
  250. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts +9 -9
  251. package/dist/core/tools/ask-user-question/view/tab-content-strategy.d.ts.map +1 -1
  252. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js +2 -2
  253. package/dist/core/tools/ask-user-question/view/tab-content-strategy.js.map +1 -1
  254. package/dist/core/tools/bash.d.ts +2 -2
  255. package/dist/core/tools/bash.d.ts.map +1 -1
  256. package/dist/core/tools/bash.js +9 -1
  257. package/dist/core/tools/bash.js.map +1 -1
  258. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  259. package/dist/core/tools/edit-diff.js.map +1 -1
  260. package/dist/core/tools/edit.d.ts +2 -2
  261. package/dist/core/tools/edit.d.ts.map +1 -1
  262. package/dist/core/tools/edit.js.map +1 -1
  263. package/dist/core/tools/find.d.ts +2 -2
  264. package/dist/core/tools/find.d.ts.map +1 -1
  265. package/dist/core/tools/find.js.map +1 -1
  266. package/dist/core/tools/grep.d.ts +2 -2
  267. package/dist/core/tools/grep.d.ts.map +1 -1
  268. package/dist/core/tools/grep.js.map +1 -1
  269. package/dist/core/tools/index.d.ts +19 -19
  270. package/dist/core/tools/index.d.ts.map +1 -1
  271. package/dist/core/tools/index.js.map +1 -1
  272. package/dist/core/tools/ls.d.ts +2 -2
  273. package/dist/core/tools/ls.d.ts.map +1 -1
  274. package/dist/core/tools/ls.js.map +1 -1
  275. package/dist/core/tools/output-accumulator.d.ts +1 -1
  276. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  277. package/dist/core/tools/output-accumulator.js.map +1 -1
  278. package/dist/core/tools/read.d.ts +2 -2
  279. package/dist/core/tools/read.d.ts.map +1 -1
  280. package/dist/core/tools/read.js.map +1 -1
  281. package/dist/core/tools/render-utils.d.ts +1 -1
  282. package/dist/core/tools/render-utils.d.ts.map +1 -1
  283. package/dist/core/tools/render-utils.js.map +1 -1
  284. package/dist/core/tools/todos.d.ts +1 -1
  285. package/dist/core/tools/todos.d.ts.map +1 -1
  286. package/dist/core/tools/todos.js.map +1 -1
  287. package/dist/core/tools/tool-definition-wrapper.d.ts +1 -1
  288. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  289. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  290. package/dist/core/tools/write.d.ts +1 -1
  291. package/dist/core/tools/write.d.ts.map +1 -1
  292. package/dist/core/tools/write.js.map +1 -1
  293. package/dist/index.d.ts +30 -29
  294. package/dist/index.d.ts.map +1 -1
  295. package/dist/index.js +1 -0
  296. package/dist/index.js.map +1 -1
  297. package/dist/main.d.ts +1 -1
  298. package/dist/main.d.ts.map +1 -1
  299. package/dist/main.js +2 -0
  300. package/dist/main.js.map +1 -1
  301. package/dist/migrations.d.ts.map +1 -1
  302. package/dist/migrations.js.map +1 -1
  303. package/dist/modes/index.d.ts +5 -5
  304. package/dist/modes/index.d.ts.map +1 -1
  305. package/dist/modes/index.js.map +1 -1
  306. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  307. package/dist/modes/interactive/components/armin.js.map +1 -1
  308. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  309. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  310. package/dist/modes/interactive/components/atomic-banner.d.ts +1 -1
  311. package/dist/modes/interactive/components/atomic-banner.d.ts.map +1 -1
  312. package/dist/modes/interactive/components/atomic-banner.js.map +1 -1
  313. package/dist/modes/interactive/components/bash-execution.d.ts +1 -1
  314. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  315. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  316. package/dist/modes/interactive/components/bordered-loader.d.ts +1 -1
  317. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  318. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  319. package/dist/modes/interactive/components/branch-summary-message.d.ts +1 -1
  320. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  321. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  322. package/dist/modes/interactive/components/chat-message-renderer.d.ts +3 -3
  323. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  324. package/dist/modes/interactive/components/chat-message-renderer.js +1 -1
  325. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  326. package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
  327. package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
  328. package/dist/modes/interactive/components/compaction-summary-message.d.ts +1 -1
  329. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  330. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  331. package/dist/modes/interactive/components/config-selector.d.ts +2 -2
  332. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  333. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  334. package/dist/modes/interactive/components/countdown-timer.d.ts +2 -2
  335. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -1
  336. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  337. package/dist/modes/interactive/components/custom-editor.d.ts +1 -1
  338. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  339. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  340. package/dist/modes/interactive/components/custom-message.d.ts +2 -2
  341. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  342. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  343. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -1
  344. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  345. package/dist/modes/interactive/components/diff.d.ts.map +1 -1
  346. package/dist/modes/interactive/components/diff.js.map +1 -1
  347. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -1
  348. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  349. package/dist/modes/interactive/components/earendil-announcement.d.ts.map +1 -1
  350. package/dist/modes/interactive/components/earendil-announcement.js.map +1 -1
  351. package/dist/modes/interactive/components/extension-editor.d.ts +1 -1
  352. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  353. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  354. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  355. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  356. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  357. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  358. package/dist/modes/interactive/components/footer.d.ts +3 -3
  359. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  360. package/dist/modes/interactive/components/footer.js +1 -1
  361. package/dist/modes/interactive/components/footer.js.map +1 -1
  362. package/dist/modes/interactive/components/index.d.ts +34 -34
  363. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  364. package/dist/modes/interactive/components/index.js.map +1 -1
  365. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  366. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  367. package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  368. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  369. package/dist/modes/interactive/components/login-dialog.js +1 -1
  370. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  371. package/dist/modes/interactive/components/model-selector.d.ts +2 -2
  372. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  373. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  374. package/dist/modes/interactive/components/oauth-selector.d.ts +1 -1
  375. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  376. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  377. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  378. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  379. package/dist/modes/interactive/components/session-selector-search.d.ts +1 -1
  380. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
  381. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  382. package/dist/modes/interactive/components/session-selector.d.ts +3 -3
  383. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  384. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  385. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  386. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  387. package/dist/modes/interactive/components/settings-selector.js +13 -0
  388. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  389. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -1
  390. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  391. package/dist/modes/interactive/components/skill-invocation-message.d.ts +1 -1
  392. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  393. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  394. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -1
  395. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  396. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
  397. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  398. package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
  399. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  400. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  401. package/dist/modes/interactive/components/tree-selector.d.ts +1 -1
  402. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  403. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  404. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  405. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  406. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  407. package/dist/modes/interactive/components/user-message.js.map +1 -1
  408. package/dist/modes/interactive/components/working-status.d.ts.map +1 -1
  409. package/dist/modes/interactive/components/working-status.js.map +1 -1
  410. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  411. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  412. package/dist/modes/interactive/interactive-mode.js +7 -1
  413. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  414. package/dist/modes/interactive/theme/theme.d.ts +1 -1
  415. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  416. package/dist/modes/interactive/theme/theme.js.map +1 -1
  417. package/dist/modes/print-mode.d.ts +1 -1
  418. package/dist/modes/print-mode.d.ts.map +1 -1
  419. package/dist/modes/print-mode.js.map +1 -1
  420. package/dist/modes/rpc/rpc-client.d.ts +5 -5
  421. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  422. package/dist/modes/rpc/rpc-client.js +1 -1
  423. package/dist/modes/rpc/rpc-client.js.map +1 -1
  424. package/dist/modes/rpc/rpc-mode.d.ts +2 -2
  425. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  426. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  427. package/dist/modes/rpc/rpc-types.d.ts +4 -4
  428. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  429. package/dist/modes/rpc/rpc-types.js.map +1 -1
  430. package/dist/package-manager-cli.d.ts.map +1 -1
  431. package/dist/package-manager-cli.js +22 -6
  432. package/dist/package-manager-cli.js.map +1 -1
  433. package/dist/utils/changelog.d.ts +1 -1
  434. package/dist/utils/changelog.d.ts.map +1 -1
  435. package/dist/utils/changelog.js.map +1 -1
  436. package/dist/utils/child-process.d.ts +5 -2
  437. package/dist/utils/child-process.d.ts.map +1 -1
  438. package/dist/utils/child-process.js +9 -7
  439. package/dist/utils/child-process.js.map +1 -1
  440. package/dist/utils/clipboard-image.d.ts.map +1 -1
  441. package/dist/utils/clipboard-image.js.map +1 -1
  442. package/dist/utils/clipboard.d.ts.map +1 -1
  443. package/dist/utils/clipboard.js.map +1 -1
  444. package/dist/utils/exif-orientation.d.ts +1 -1
  445. package/dist/utils/exif-orientation.d.ts.map +1 -1
  446. package/dist/utils/exif-orientation.js.map +1 -1
  447. package/dist/utils/image-convert.d.ts.map +1 -1
  448. package/dist/utils/image-convert.js.map +1 -1
  449. package/dist/utils/image-resize.d.ts.map +1 -1
  450. package/dist/utils/image-resize.js.map +1 -1
  451. package/dist/utils/shell.d.ts.map +1 -1
  452. package/dist/utils/shell.js.map +1 -1
  453. package/dist/utils/syntax-highlight.d.ts.map +1 -1
  454. package/dist/utils/syntax-highlight.js.map +1 -1
  455. package/dist/utils/tools-manager.d.ts.map +1 -1
  456. package/dist/utils/tools-manager.js.map +1 -1
  457. package/dist/utils/version-check.d.ts.map +1 -1
  458. package/dist/utils/version-check.js.map +1 -1
  459. package/dist/utils/windows-self-update.d.ts +3 -0
  460. package/dist/utils/windows-self-update.d.ts.map +1 -0
  461. package/dist/utils/windows-self-update.js +78 -0
  462. package/dist/utils/windows-self-update.js.map +1 -0
  463. package/docs/compaction.md +5 -5
  464. package/docs/custom-provider.md +2 -2
  465. package/docs/development.md +22 -16
  466. package/docs/docs.json +6 -2
  467. package/docs/extensions.md +25 -25
  468. package/docs/index.md +11 -14
  469. package/docs/keybindings.md +5 -5
  470. package/docs/models.md +6 -6
  471. package/docs/packages.md +55 -48
  472. package/docs/prompt-templates.md +5 -5
  473. package/docs/providers.md +10 -10
  474. package/docs/quickstart.md +32 -30
  475. package/docs/rpc.md +9 -9
  476. package/docs/sdk.md +1 -1
  477. package/docs/session-format.md +3 -3
  478. package/docs/sessions.md +11 -11
  479. package/docs/settings.md +18 -15
  480. package/docs/shell-aliases.md +2 -2
  481. package/docs/skills.md +11 -11
  482. package/docs/terminal-setup.md +8 -8
  483. package/docs/termux.md +6 -6
  484. package/docs/themes.md +10 -10
  485. package/docs/tui.md +5 -4
  486. package/docs/usage.md +50 -50
  487. package/docs/windows.md +2 -2
  488. package/docs/workflows.md +695 -0
  489. package/examples/extensions/custom-provider-gitlab-duo/test.ts +1 -1
  490. package/examples/extensions/doom-overlay/doom-component.ts +2 -2
  491. package/examples/extensions/doom-overlay/index.ts +3 -3
  492. package/examples/extensions/overlay-qa-tests.ts +116 -33
  493. package/examples/extensions/overlay-test.ts +9 -3
  494. package/examples/extensions/plan-mode/index.ts +1 -1
  495. package/examples/extensions/subagent/index.ts +1159 -903
  496. package/package.json +6 -4
  497. package/dist/builtin/workflows/skills/workflow/SKILL.md +0 -322
  498. package/dist/builtin/workflows/skills/workflow/references/context-engineering/advanced-evaluation.md +0 -404
  499. package/dist/builtin/workflows/skills/workflow/references/context-engineering/bdi-mental-states.md +0 -313
  500. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-compression.md +0 -274
  501. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-degradation.md +0 -208
  502. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-fundamentals.md +0 -203
  503. package/dist/builtin/workflows/skills/workflow/references/context-engineering/context-optimization.md +0 -197
  504. package/dist/builtin/workflows/skills/workflow/references/context-engineering/evaluation.md +0 -253
  505. package/dist/builtin/workflows/skills/workflow/references/context-engineering/filesystem-context.md +0 -289
  506. package/dist/builtin/workflows/skills/workflow/references/context-engineering/hosted-agents.md +0 -262
  507. package/dist/builtin/workflows/skills/workflow/references/context-engineering/memory-systems.md +0 -221
  508. package/dist/builtin/workflows/skills/workflow/references/context-engineering/multi-agent-patterns.md +0 -259
  509. package/dist/builtin/workflows/skills/workflow/references/context-engineering/project-development.md +0 -293
  510. package/dist/builtin/workflows/skills/workflow/references/context-engineering/tool-design.md +0 -273
  511. package/dist/builtin/workflows/skills/workflow/references/context-engineering.md +0 -23
  512. package/dist/builtin/workflows/skills/workflow/references/design-checklist.md +0 -83
  513. package/dist/builtin/workflows/skills/workflow/references/running-workflows.md +0 -159
  514. package/dist/builtin/workflows/skills/workflow/references/sdk-authoring.md +0 -260
@@ -1 +1 @@
1
- {"version":3,"file":"atomic-guide-command.d.ts","sourceRoot":"","sources":["../../src/core/atomic-guide-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,eAAO,MAAM,yBAAyB,WAAW,CAAC;AAClD,eAAO,MAAM,gCAAgC,qCAAqC,CAAC;AA0MnF,QAAA,MAAM,cAAc;mBAEZ,UAAU;;oBAET,UAAU;0BACJ,oBAAoB;;;mBAI3B,WAAW;;oBAEV,WAAW;0BACL,iBAAiB;;;mBAIxB,SAAS;;oBAER,SAAS;0BACH,0BAA0B;;;mBAIjC,WAAW;;oBAEV,YAAY;0BACN,sBAAsB;;EASlC,CAAC;AAEJ,KAAK,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1D,KAAK,sBAAsB,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,sBAAsB,CAAC;AAE9D,eAAO,MAAM,yBAAyB,EAAE,SAAS,qBAAqB,EAErE,CAAC;AAcF,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,qBAAqB,CAEvF;AA6BD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAKtE;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAW3F;AAED,iBAAS,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CActD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAEvF","sourcesContent":["import * as path from \"node:path\";\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\nimport { getChangelogPath, parseChangelog } from \"../utils/changelog.js\";\n\nexport const ATOMIC_GUIDE_COMMAND_NAME = \"atomic\";\nexport const ATOMIC_GUIDE_COMMAND_DESCRIPTION = \"Atomic onboarding and help guide\";\n\nconst OVERVIEW = `# Atomic overview\n\nAtomic turns one-off prompts into developer workflows: on-call debugging, repo research that turns into implementation, testing and review loops, and larger multi-stage automation. Start it in a project with \\`atomic\\`, then talk to it normally. Use \\`@file\\` to attach files, \\`!command\\` to run shell output through the model, and \\`!!command\\` to run shell output without adding it to context.\n\n## Core session commands\n\n| Command | Use |\n|---|---|\n| \\`/login\\` | configure auth |\n| \\`/model\\` | switch model |\n| \\`/settings\\` | thinking level, theme, message delivery, transport |\n| \\`/new\\`, \\`/resume\\` | start or resume sessions |\n| \\`/tree\\`, \\`/fork\\`, \\`/clone\\` | branch or navigate session history |\n| \\`/compact\\` | summarize older context |\n| \\`/hotkeys\\`, \\`/changelog\\` | local help and release notes |\n\n## Examples of using Atomic\n\n| Goal | How to use |\n|---|---|\n| On-call / broken behavior | Use \\`/skill:workflow\\` to create a workflow that runs \\`/run debugger \"Reproduce the failure, patch the root cause, and validate it\"\\` for a focused fix loop |\n| Research → spec → implementation | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:research-codebase\\`, then \\`/skill:create-spec\\`, then direct implementation or \\`/workflow ralph ...\\` |\n| Testing / regression hardening | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:tdd\\` for test-first work, then \\`/parallel-review current diff\\`, then land the change |\n| Large repo discovery | Use \\`/skill:workflow\\` to create a workflow that runs \\`/parallel codebase-locator \"map the area\" -> codebase-analyzer \"trace the current flow\" -> codebase-pattern-finder \"find patterns\" --bg\\` |\n| UI / product polish | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:impeccable\\` for interface critique, refinement, and clearer UX decisions |\n\n## Subagents\n\nSubagents are focused child Atomic sessions you can point at one job inside the repo.\n\n| Built-in subagent | Use |\n|---|---|\n| \\`codebase-locator\\` | find relevant files, tests, entrypoints, and configs |\n| \\`codebase-analyzer\\` | explain current behavior with file:line refs |\n| \\`codebase-pattern-finder\\` | find existing code to model after |\n| \\`debugger\\` | reproduce, diagnose, and fix broken behavior |\n\nHow the direct commands map to repo work:\n- \\`/run\\` = one specialist on one job, for example \\`/run codebase-locator \"Map the webhook retry flow\"\\`\n- \\`/parallel\\` = several independent specialists at once, for example \\`/parallel codebase-locator \"map retry files\" -> codebase-pattern-finder \"find existing retry/backoff patterns\" -> codebase-online-researcher \"research current retry guidance\" --bg\\`\n- \\`/chain\\` = ordered handoffs, for example \\`/chain codebase-locator \"find the auth files\" -> codebase-analyzer \"trace the auth flow\" -> debugger \"patch the failing auth edge case\"\\`\n\n## Top skills\n\n| Skill | When to use | How to run |\n|---|---|---|\n| \\`research-codebase\\` | write a grounded research artifact for one subsystem or question | \\`/skill:research-codebase how the rate limiter works in src/middleware/\\` |\n| \\`create-spec\\` | turn research into an implementation-ready plan | \\`/skill:create-spec from research/docs/<date>-<topic>.md\\` |\n| \\`tdd\\` | do test-first feature or bug work | \\`/skill:tdd\\` |\n| \\`prompt-engineer\\` | tighten a vague prompt before a long run | \\`/skill:prompt-engineer Draft a sharper implementation prompt for ...\\` |\n| \\`workflow\\` | author, inspect, or improve workflows; start by describing the desired workflow in natural language | \\`/skill:workflow\\` |\n| \\`subagent\\` | learn delegation patterns and exact \\`/run\\`, \\`/parallel\\`, and \\`/chain\\` usage | \\`/skill:subagent\\` |\n| \\`impeccable\\` | critique or refine frontend and product UI | \\`/skill:impeccable\\` |\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation loops where you want implementation, review, and validation built in | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | UI and design-system work that benefits from generation and refinement loops | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow list\\` to see what is available and \\`/workflow inputs <name>\\` to inspect inputs in your environment.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see the pieces used on a code task\n\\`/atomic workflows\\` — learn when to use workflows`;\n\nconst EXAMPLE = `# Practical example\n\nThis is an example of a spec-driven development process using Atomic workflows. Use it when you are new to a repo or the task has non-trivial scope. Type the examples below into the Atomic TUI chat after starting \\`atomic\\` in your project.\n\n## 1. Research what exists\n\nUse \\`/skill:research-codebase\\` for a scoped area, subsystem, or directory:\n\n\\`/skill:research-codebase how the rate limiter works in src/middleware/\\`\n\nUse \\`deep-research-codebase\\` when the answer spans the whole repo or a cross-cutting implementation path:\n\n\\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\`\n\nIf the research prompt is vague, tighten it first with \\`/skill:prompt-engineer\\`:\n\n\\`/skill:prompt-engineer Draft a sharper repo-research prompt for understanding payment retries end to end, including retries, queues, and failure handling.\\`\n\n## 2. Create a spec when requirements are fuzzy\n\nSkip this if the implementation request is already precise.\n\n\\`/skill:create-spec from research/docs/<date>-<topic>.md\\`\n\n## 3. Implement with review built in\n\nFor ordinary work, ask Atomic directly and require validation:\n\n\\`Implement the approved spec in specs/<date>-<topic>.md. Run focused tests and summarize validation.\\`\n\nFor larger work, use subagents or a workflow:\n\n\\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\`\n\n## 4. Decide and land\n\nIf you used \\`ralph\\`, the workflow already ran parallel reviewers. Use its final result and review feedback to decide whether to ship or iterate again.\n\nIf you implemented directly instead of using \\`ralph\\`, you can still run:\n\n\\`/parallel-review current diff\\`\n\nAtomic will synthesize reviewer feedback and ask before applying fixes.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic workflows\\` — learn when to use workflows\n\\`/atomic overview\\` — quick refresh`;\n\nconst WORKFLOWS = `# Workflows primer\n\nA workflow is a TypeScript-defined pipeline built with \\`defineWorkflow(...).run(...).compile()\\`. It can run tasks, chains, parallel fan-out, human-in-the-loop prompts, background status, and model fallback chains.\n\nStart by defining the workflow in natural language with \\`/skill:workflow\\`, the workflow creator skill. Describe the goal, inputs, stages, which steps are parallel or sequential, handoff/output shape, and any model or thinking-level preferences; let the skill help turn that into a reusable workflow before you hand-edit TypeScript.\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation and review loops | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | frontend and product design work | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow inputs <name>\\` to inspect the exact inputs in your environment.\n\nUse \\`/skill:research-codebase ...\\` when you want research on one subsystem, directory, or focused question. Use \\`/workflow deep-research-codebase ...\\` when the answer needs end-to-end tracing across many parts of the repo.\n\nIf you are drafting research, reviewer, or synthesis prompts for a workflow, use \\`/skill:prompt-engineer\\` first. It is a good fit when a stage prompt feels vague, overloaded, or underspecified.\n\n## What good workflow authoring looks like\n\nA good workflow request is explicit about stage purpose, model choice, handoff, and the decision each step must return.\n\nExample: prompt \\`/skill:workflow\\` with something like this:\n\n~~~text\nCreate a reusable workflow called review-changes.\n\nIt should accept one required text input called target for a diff, PR summary, or review target.\n\nRun two independent review stages in parallel with fresh context:\n- one reviewer focused on correctness, regressions, and missing tests using openai-codex/gpt-5.5 at xhigh thinking\n- one reviewer focused on edge cases, maintainability, and hidden risks using anthropic/claude-opus-4-7 at xhigh thinking\n\nThen add an aggregate stage that consolidates both reviews, deduplicates overlap, keeps only evidence-backed issues, and separates blockers from optional suggestions using openai/gpt-5.5 at high thinking.\n\nFinally add an adjudicate stage using anthropic/claude-sonnet-4 at high thinking that decides what to fix now, what to defer, and what to reject. Return a short action list with rationale.\n\nThe workflow should return structured output with consolidated_review and decision fields.\n~~~\n\nWhy this is good:\n- it names the workflow and required input\n- it specifies which stages are parallel vs sequential\n- each stage has one job\n- it defines the handoff and final outputs\n- it calls out model choice and thinking level where that matters\n\n## Run and inspect\n\n\\`/workflow list\\`\n\n\\`/workflow inputs ralph\\`\n\n\\`/workflow ralph prompt=\"Migrate the database layer to Drizzle\" max_loops=5\\`\n\n\\`/workflow status\\`\n\n\\`/workflow connect <run-id>\\`\n\n\\`/workflow interrupt <run-id>\\`\n\n\\`/workflow resume <run-id>\\`\n\nWorkflows run as background tasks. Use F2 or \\`/workflow connect <run-id>\\` for the graph viewer. Human-in-the-loop prompts appear there, not as chat modals.\n\n## Author your own\n\nUse \\`/skill:workflow\\` to describe your workflow in natural language and build your first workflow. This is the recommended starting point for creation and design questions: say what you want the workflow to accomplish, what inputs it should accept, what stages should run, and what final output or decision it should return. Ask Atomic in chat when you want help refining or implementing one.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see workflows in a normal task flow\n\\`/atomic overview\\` — quick refresh`;\n\nconst GUIDE_SECTIONS = [\n\t{\n\t\tname: \"overview\",\n\t\taliases: [],\n\t\tlabel: \"overview\",\n\t\tdescription: \"30-second overview\",\n\t\trender: () => OVERVIEW,\n\t},\n\t{\n\t\tname: \"workflows\",\n\t\taliases: [\"workflow\"],\n\t\tlabel: \"workflows\",\n\t\tdescription: \"Workflow primer\",\n\t\trender: () => WORKFLOWS,\n\t},\n\t{\n\t\tname: \"example\",\n\t\taliases: [\"examples\"],\n\t\tlabel: \"example\",\n\t\tdescription: \"Practical first workflow\",\n\t\trender: () => EXAMPLE,\n\t},\n\t{\n\t\tname: \"whats-new\",\n\t\taliases: [\"what's new\", \"whats new\", \"news\", \"updates\", \"changelog\"],\n\t\tlabel: \"what's new\",\n\t\tdescription: \"Recent release notes\",\n\t\trender: readLatestStableChangelog,\n\t},\n] as const satisfies readonly {\n\treadonly name: string;\n\treadonly aliases: readonly string[];\n\treadonly label: string;\n\treadonly description: string;\n\treadonly render: (cwd: string) => string;\n}[];\n\ntype AtomicGuideSection = (typeof GUIDE_SECTIONS)[number];\ntype AtomicGuideSectionName = AtomicGuideSection[\"name\"];\n\nexport type AtomicGuideHelpChoice = AtomicGuideSection[\"label\"];\n\nexport type AtomicGuideMode = \"help\" | AtomicGuideSectionName;\n\nexport const ATOMIC_GUIDE_HELP_CHOICES: readonly AtomicGuideHelpChoice[] = GUIDE_SECTIONS.map(\n\t(section) => section.label,\n);\n\nconst GUIDE_SECTIONS_BY_NAME = new Map<AtomicGuideSectionName, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.name, section]),\n);\nconst GUIDE_SECTIONS_BY_LABEL = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.label, section]),\n);\nconst GUIDE_SECTIONS_BY_INPUT = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.flatMap((section) =>\n\t\t[section.name, section.label, ...section.aliases].map((input) => [input, section] as const),\n\t),\n);\n\nexport function isAtomicGuideHelpChoice(choice: string): choice is AtomicGuideHelpChoice {\n\treturn GUIDE_SECTIONS_BY_LABEL.has(choice);\n}\n\nconst ATOMIC_GUIDE_TRAILING_PUNCTUATION = \"?!.,;:\";\n\nfunction stripTrailingAtomicGuidePunctuation(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && ATOMIC_GUIDE_TRAILING_PUNCTUATION.includes(value.charAt(end - 1))) {\n\t\tend--;\n\t}\n\treturn value.slice(0, end);\n}\n\nfunction getGuideSectionForChoice(choice: string): AtomicGuideSection | undefined {\n\treturn GUIDE_SECTIONS_BY_LABEL.get(choice);\n}\n\nfunction getGuideSectionForMode(mode: AtomicGuideSectionName): AtomicGuideSection {\n\tconst section = GUIDE_SECTIONS_BY_NAME.get(mode);\n\tif (!section) throw new Error(`Unknown Atomic guide section: ${mode}`);\n\treturn section;\n}\n\nfunction getAtomicGuideHelpMenu(): string {\n\tconst sectionHelp = GUIDE_SECTIONS.map(\n\t\t(section) => `- \\`${section.label}\\` — run \\`/atomic ${section.label}\\``,\n\t).join(\"\\n\");\n\treturn `# Atomic\\n\\nSelect where to start:\\n\\n${sectionHelp}`;\n}\n\nexport function normalizeAtomicGuideMode(args: string): AtomicGuideMode {\n\tconst normalized = stripTrailingAtomicGuidePunctuation(args.trim().toLowerCase());\n\tif (!normalized) return \"help\";\n\n\treturn GUIDE_SECTIONS_BY_INPUT.get(normalized)?.name ?? \"help\";\n}\n\nexport function getAtomicGuideArgumentCompletions(prefix: string): AutocompleteItem[] | null {\n\tconst query = prefix.trim().toLowerCase();\n\tconst items = GUIDE_SECTIONS.map((section) => ({\n\t\tvalue: section.label,\n\t\tlabel: section.label,\n\t\tdescription: section.description,\n\t}));\n\tconst filtered = query\n\t\t? items.filter((item) => item.value.startsWith(query) || item.label.startsWith(query))\n\t\t: items;\n\treturn filtered.length > 0 ? filtered : null;\n}\n\nfunction readLatestStableChangelog(cwd: string): string {\n\tconst changelogPath = getChangelogPath();\n\tconst stableSections = parseChangelog(changelogPath)\n\t\t.filter((entry) => entry.prerelease === null)\n\t\t.slice(0, 3)\n\t\t.map((entry) => entry.content.trim())\n\t\t.filter(Boolean);\n\n\tif (stableSections.length === 0) {\n\t\treturn `# What's new\\n\\nNo stable release sections were found. Try \\`/changelog\\` for the interactive changelog viewer.\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n\t}\n\n\tconst relativePath = path.relative(cwd, changelogPath) || changelogPath;\n\treturn `# What's new\\n\\n${stableSections.join(\"\\n\\n\")}\\n\\nSource: \\`${relativePath}\\`\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n}\n\nexport function getAtomicGuideMessage(mode: AtomicGuideMode, cwd: string): string {\n\tif (mode === \"help\") return getAtomicGuideHelpMenu();\n\treturn getGuideSectionForMode(mode).render(cwd);\n}\n\nexport function atomicGuideModeForChoice(choice: AtomicGuideHelpChoice): AtomicGuideMode {\n\treturn getGuideSectionForChoice(choice)?.name ?? \"help\";\n}\n\n"]}
1
+ {"version":3,"file":"atomic-guide-command.d.ts","sourceRoot":"","sources":["../../src/core/atomic-guide-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,eAAO,MAAM,yBAAyB,WAAW,CAAC;AAClD,eAAO,MAAM,gCAAgC,qCAAqC,CAAC;AA0MnF,QAAA,MAAM,cAAc;mBAEZ,UAAU;;oBAET,UAAU;0BACJ,oBAAoB;;;mBAI3B,WAAW;;oBAEV,WAAW;0BACL,iBAAiB;;;mBAIxB,SAAS;;oBAER,SAAS;0BACH,0BAA0B;;;mBAIjC,WAAW;;oBAEV,YAAY;0BACN,sBAAsB;;EASlC,CAAC;AAEJ,KAAK,kBAAkB,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAC1D,KAAK,sBAAsB,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,qBAAqB,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAEhE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,sBAAsB,CAAC;AAE9D,eAAO,MAAM,yBAAyB,EAAE,SAAS,qBAAqB,EAErE,CAAC;AAcF,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,qBAAqB,CAEvF;AA6BD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAKtE;AAED,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAW3F;AAED,iBAAS,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CActD;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGhF;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,qBAAqB,GAAG,eAAe,CAEvF","sourcesContent":["import * as path from \"node:path\";\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\nimport { getChangelogPath, parseChangelog } from \"../utils/changelog.ts\";\n\nexport const ATOMIC_GUIDE_COMMAND_NAME = \"atomic\";\nexport const ATOMIC_GUIDE_COMMAND_DESCRIPTION = \"Atomic onboarding and help guide\";\n\nconst OVERVIEW = `# Atomic overview\n\nAtomic turns one-off prompts into developer workflows: on-call debugging, repo research that turns into implementation, testing and review loops, and larger multi-stage automation. Start it in a project with \\`atomic\\`, then talk to it normally. Use \\`@file\\` to attach files, \\`!command\\` to run shell output through the model, and \\`!!command\\` to run shell output without adding it to context.\n\n## Core session commands\n\n| Command | Use |\n|---|---|\n| \\`/login\\` | configure auth |\n| \\`/model\\` | switch model |\n| \\`/settings\\` | thinking level, theme, message delivery, transport |\n| \\`/new\\`, \\`/resume\\` | start or resume sessions |\n| \\`/tree\\`, \\`/fork\\`, \\`/clone\\` | branch or navigate session history |\n| \\`/compact\\` | summarize older context |\n| \\`/hotkeys\\`, \\`/changelog\\` | local help and release notes |\n\n## Examples of using Atomic\n\n| Goal | How to use |\n|---|---|\n| On-call / broken behavior | Use \\`/skill:workflow\\` to create a workflow that runs \\`/run debugger \"Reproduce the failure, patch the root cause, and validate it\"\\` for a focused fix loop |\n| Research → spec → implementation | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:research-codebase\\`, then \\`/skill:create-spec\\`, then direct implementation or \\`/workflow ralph ...\\` |\n| Testing / regression hardening | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:tdd\\` for test-first work, then \\`/parallel-review current diff\\`, then land the change |\n| Large repo discovery | Use \\`/skill:workflow\\` to create a workflow that runs \\`/parallel codebase-locator \"map the area\" -> codebase-analyzer \"trace the current flow\" -> codebase-pattern-finder \"find patterns\" --bg\\` |\n| UI / product polish | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:impeccable\\` for interface critique, refinement, and clearer UX decisions |\n\n## Subagents\n\nSubagents are focused child Atomic sessions you can point at one job inside the repo.\n\n| Built-in subagent | Use |\n|---|---|\n| \\`codebase-locator\\` | find relevant files, tests, entrypoints, and configs |\n| \\`codebase-analyzer\\` | explain current behavior with file:line refs |\n| \\`codebase-pattern-finder\\` | find existing code to model after |\n| \\`debugger\\` | reproduce, diagnose, and fix broken behavior |\n\nHow the direct commands map to repo work:\n- \\`/run\\` = one specialist on one job, for example \\`/run codebase-locator \"Map the webhook retry flow\"\\`\n- \\`/parallel\\` = several independent specialists at once, for example \\`/parallel codebase-locator \"map retry files\" -> codebase-pattern-finder \"find existing retry/backoff patterns\" -> codebase-online-researcher \"research current retry guidance\" --bg\\`\n- \\`/chain\\` = ordered handoffs, for example \\`/chain codebase-locator \"find the auth files\" -> codebase-analyzer \"trace the auth flow\" -> debugger \"patch the failing auth edge case\"\\`\n\n## Top skills\n\n| Skill | When to use | How to run |\n|---|---|---|\n| \\`research-codebase\\` | write a grounded research artifact for one subsystem or question | \\`/skill:research-codebase how the rate limiter works in src/middleware/\\` |\n| \\`create-spec\\` | turn research into an implementation-ready plan | \\`/skill:create-spec from research/docs/<date>-<topic>.md\\` |\n| \\`tdd\\` | do test-first feature or bug work | \\`/skill:tdd\\` |\n| \\`prompt-engineer\\` | tighten a vague prompt before a long run | \\`/skill:prompt-engineer Draft a sharper implementation prompt for ...\\` |\n| \\`workflow\\` | author, inspect, or improve workflows; start by describing the desired workflow in natural language | \\`/skill:workflow\\` |\n| \\`subagent\\` | learn delegation patterns and exact \\`/run\\`, \\`/parallel\\`, and \\`/chain\\` usage | \\`/skill:subagent\\` |\n| \\`impeccable\\` | critique or refine frontend and product UI | \\`/skill:impeccable\\` |\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation loops where you want implementation, review, and validation built in | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | UI and design-system work that benefits from generation and refinement loops | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow list\\` to see what is available and \\`/workflow inputs <name>\\` to inspect inputs in your environment.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see the pieces used on a code task\n\\`/atomic workflows\\` — learn when to use workflows`;\n\nconst EXAMPLE = `# Practical example\n\nThis is an example of a spec-driven development process using Atomic workflows. Use it when you are new to a repo or the task has non-trivial scope. Type the examples below into the Atomic TUI chat after starting \\`atomic\\` in your project.\n\n## 1. Research what exists\n\nUse \\`/skill:research-codebase\\` for a scoped area, subsystem, or directory:\n\n\\`/skill:research-codebase how the rate limiter works in src/middleware/\\`\n\nUse \\`deep-research-codebase\\` when the answer spans the whole repo or a cross-cutting implementation path:\n\n\\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\`\n\nIf the research prompt is vague, tighten it first with \\`/skill:prompt-engineer\\`:\n\n\\`/skill:prompt-engineer Draft a sharper repo-research prompt for understanding payment retries end to end, including retries, queues, and failure handling.\\`\n\n## 2. Create a spec when requirements are fuzzy\n\nSkip this if the implementation request is already precise.\n\n\\`/skill:create-spec from research/docs/<date>-<topic>.md\\`\n\n## 3. Implement with review built in\n\nFor ordinary work, ask Atomic directly and require validation:\n\n\\`Implement the approved spec in specs/<date>-<topic>.md. Run focused tests and summarize validation.\\`\n\nFor larger work, use subagents or a workflow:\n\n\\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\`\n\n## 4. Decide and land\n\nIf you used \\`ralph\\`, the workflow already ran parallel reviewers. Use its final result and review feedback to decide whether to ship or iterate again.\n\nIf you implemented directly instead of using \\`ralph\\`, you can still run:\n\n\\`/parallel-review current diff\\`\n\nAtomic will synthesize reviewer feedback and ask before applying fixes.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic workflows\\` — learn when to use workflows\n\\`/atomic overview\\` — quick refresh`;\n\nconst WORKFLOWS = `# Workflows primer\n\nA workflow is a TypeScript-defined pipeline built with \\`defineWorkflow(...).run(...).compile()\\`. It can run tasks, chains, parallel fan-out, human-in-the-loop prompts, background status, and model fallback chains.\n\nStart by defining the workflow in natural language with \\`/skill:workflow\\`, the workflow creator skill. Describe the goal, inputs, stages, which steps are parallel or sequential, handoff/output shape, and any model or thinking-level preferences; let the skill help turn that into a reusable workflow before you hand-edit TypeScript.\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation and review loops | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | frontend and product design work | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow inputs <name>\\` to inspect the exact inputs in your environment.\n\nUse \\`/skill:research-codebase ...\\` when you want research on one subsystem, directory, or focused question. Use \\`/workflow deep-research-codebase ...\\` when the answer needs end-to-end tracing across many parts of the repo.\n\nIf you are drafting research, reviewer, or synthesis prompts for a workflow, use \\`/skill:prompt-engineer\\` first. It is a good fit when a stage prompt feels vague, overloaded, or underspecified.\n\n## What good workflow authoring looks like\n\nA good workflow request is explicit about stage purpose, model choice, handoff, and the decision each step must return.\n\nExample: prompt \\`/skill:workflow\\` with something like this:\n\n~~~text\nCreate a reusable workflow called review-changes.\n\nIt should accept one required text input called target for a diff, PR summary, or review target.\n\nRun two independent review stages in parallel with fresh context:\n- one reviewer focused on correctness, regressions, and missing tests using openai-codex/gpt-5.5 at xhigh thinking\n- one reviewer focused on edge cases, maintainability, and hidden risks using anthropic/claude-opus-4-7 at xhigh thinking\n\nThen add an aggregate stage that consolidates both reviews, deduplicates overlap, keeps only evidence-backed issues, and separates blockers from optional suggestions using openai/gpt-5.5 at high thinking.\n\nFinally add an adjudicate stage using anthropic/claude-sonnet-4 at high thinking that decides what to fix now, what to defer, and what to reject. Return a short action list with rationale.\n\nThe workflow should return structured output with consolidated_review and decision fields.\n~~~\n\nWhy this is good:\n- it names the workflow and required input\n- it specifies which stages are parallel vs sequential\n- each stage has one job\n- it defines the handoff and final outputs\n- it calls out model choice and thinking level where that matters\n\n## Run and inspect\n\n\\`/workflow list\\`\n\n\\`/workflow inputs ralph\\`\n\n\\`/workflow ralph prompt=\"Migrate the database layer to Drizzle\" max_loops=5\\`\n\n\\`/workflow status\\`\n\n\\`/workflow connect <run-id>\\`\n\n\\`/workflow interrupt <run-id>\\`\n\n\\`/workflow resume <run-id>\\`\n\nWorkflows run as background tasks. Use F2 or \\`/workflow connect <run-id>\\` for the graph viewer. Human-in-the-loop prompts appear there, not as chat modals.\n\n## Author your own\n\nUse \\`/skill:workflow\\` to describe your workflow in natural language and build your first workflow. This is the recommended starting point for creation and design questions: say what you want the workflow to accomplish, what inputs it should accept, what stages should run, and what final output or decision it should return. Ask Atomic in chat when you want help refining or implementing one.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see workflows in a normal task flow\n\\`/atomic overview\\` — quick refresh`;\n\nconst GUIDE_SECTIONS = [\n\t{\n\t\tname: \"overview\",\n\t\taliases: [],\n\t\tlabel: \"overview\",\n\t\tdescription: \"30-second overview\",\n\t\trender: () => OVERVIEW,\n\t},\n\t{\n\t\tname: \"workflows\",\n\t\taliases: [\"workflow\"],\n\t\tlabel: \"workflows\",\n\t\tdescription: \"Workflow primer\",\n\t\trender: () => WORKFLOWS,\n\t},\n\t{\n\t\tname: \"example\",\n\t\taliases: [\"examples\"],\n\t\tlabel: \"example\",\n\t\tdescription: \"Practical first workflow\",\n\t\trender: () => EXAMPLE,\n\t},\n\t{\n\t\tname: \"whats-new\",\n\t\taliases: [\"what's new\", \"whats new\", \"news\", \"updates\", \"changelog\"],\n\t\tlabel: \"what's new\",\n\t\tdescription: \"Recent release notes\",\n\t\trender: readLatestStableChangelog,\n\t},\n] as const satisfies readonly {\n\treadonly name: string;\n\treadonly aliases: readonly string[];\n\treadonly label: string;\n\treadonly description: string;\n\treadonly render: (cwd: string) => string;\n}[];\n\ntype AtomicGuideSection = (typeof GUIDE_SECTIONS)[number];\ntype AtomicGuideSectionName = AtomicGuideSection[\"name\"];\n\nexport type AtomicGuideHelpChoice = AtomicGuideSection[\"label\"];\n\nexport type AtomicGuideMode = \"help\" | AtomicGuideSectionName;\n\nexport const ATOMIC_GUIDE_HELP_CHOICES: readonly AtomicGuideHelpChoice[] = GUIDE_SECTIONS.map(\n\t(section) => section.label,\n);\n\nconst GUIDE_SECTIONS_BY_NAME = new Map<AtomicGuideSectionName, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.name, section]),\n);\nconst GUIDE_SECTIONS_BY_LABEL = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.label, section]),\n);\nconst GUIDE_SECTIONS_BY_INPUT = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.flatMap((section) =>\n\t\t[section.name, section.label, ...section.aliases].map((input) => [input, section] as const),\n\t),\n);\n\nexport function isAtomicGuideHelpChoice(choice: string): choice is AtomicGuideHelpChoice {\n\treturn GUIDE_SECTIONS_BY_LABEL.has(choice);\n}\n\nconst ATOMIC_GUIDE_TRAILING_PUNCTUATION = \"?!.,;:\";\n\nfunction stripTrailingAtomicGuidePunctuation(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && ATOMIC_GUIDE_TRAILING_PUNCTUATION.includes(value.charAt(end - 1))) {\n\t\tend--;\n\t}\n\treturn value.slice(0, end);\n}\n\nfunction getGuideSectionForChoice(choice: string): AtomicGuideSection | undefined {\n\treturn GUIDE_SECTIONS_BY_LABEL.get(choice);\n}\n\nfunction getGuideSectionForMode(mode: AtomicGuideSectionName): AtomicGuideSection {\n\tconst section = GUIDE_SECTIONS_BY_NAME.get(mode);\n\tif (!section) throw new Error(`Unknown Atomic guide section: ${mode}`);\n\treturn section;\n}\n\nfunction getAtomicGuideHelpMenu(): string {\n\tconst sectionHelp = GUIDE_SECTIONS.map(\n\t\t(section) => `- \\`${section.label}\\` — run \\`/atomic ${section.label}\\``,\n\t).join(\"\\n\");\n\treturn `# Atomic\\n\\nSelect where to start:\\n\\n${sectionHelp}`;\n}\n\nexport function normalizeAtomicGuideMode(args: string): AtomicGuideMode {\n\tconst normalized = stripTrailingAtomicGuidePunctuation(args.trim().toLowerCase());\n\tif (!normalized) return \"help\";\n\n\treturn GUIDE_SECTIONS_BY_INPUT.get(normalized)?.name ?? \"help\";\n}\n\nexport function getAtomicGuideArgumentCompletions(prefix: string): AutocompleteItem[] | null {\n\tconst query = prefix.trim().toLowerCase();\n\tconst items = GUIDE_SECTIONS.map((section) => ({\n\t\tvalue: section.label,\n\t\tlabel: section.label,\n\t\tdescription: section.description,\n\t}));\n\tconst filtered = query\n\t\t? items.filter((item) => item.value.startsWith(query) || item.label.startsWith(query))\n\t\t: items;\n\treturn filtered.length > 0 ? filtered : null;\n}\n\nfunction readLatestStableChangelog(cwd: string): string {\n\tconst changelogPath = getChangelogPath();\n\tconst stableSections = parseChangelog(changelogPath)\n\t\t.filter((entry) => entry.prerelease === null)\n\t\t.slice(0, 3)\n\t\t.map((entry) => entry.content.trim())\n\t\t.filter(Boolean);\n\n\tif (stableSections.length === 0) {\n\t\treturn `# What's new\\n\\nNo stable release sections were found. Try \\`/changelog\\` for the interactive changelog viewer.\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n\t}\n\n\tconst relativePath = path.relative(cwd, changelogPath) || changelogPath;\n\treturn `# What's new\\n\\n${stableSections.join(\"\\n\\n\")}\\n\\nSource: \\`${relativePath}\\`\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n}\n\nexport function getAtomicGuideMessage(mode: AtomicGuideMode, cwd: string): string {\n\tif (mode === \"help\") return getAtomicGuideHelpMenu();\n\treturn getGuideSectionForMode(mode).render(cwd);\n}\n\nexport function atomicGuideModeForChoice(choice: AtomicGuideHelpChoice): AtomicGuideMode {\n\treturn getGuideSectionForChoice(choice)?.name ?? \"help\";\n}\n\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"atomic-guide-command.js","sourceRoot":"","sources":["../../src/core/atomic-guide-command.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from \"node:path\";\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\nimport { getChangelogPath, parseChangelog } from \"../utils/changelog.js\";\n\nexport const ATOMIC_GUIDE_COMMAND_NAME = \"atomic\";\nexport const ATOMIC_GUIDE_COMMAND_DESCRIPTION = \"Atomic onboarding and help guide\";\n\nconst OVERVIEW = `# Atomic overview\n\nAtomic turns one-off prompts into developer workflows: on-call debugging, repo research that turns into implementation, testing and review loops, and larger multi-stage automation. Start it in a project with \\`atomic\\`, then talk to it normally. Use \\`@file\\` to attach files, \\`!command\\` to run shell output through the model, and \\`!!command\\` to run shell output without adding it to context.\n\n## Core session commands\n\n| Command | Use |\n|---|---|\n| \\`/login\\` | configure auth |\n| \\`/model\\` | switch model |\n| \\`/settings\\` | thinking level, theme, message delivery, transport |\n| \\`/new\\`, \\`/resume\\` | start or resume sessions |\n| \\`/tree\\`, \\`/fork\\`, \\`/clone\\` | branch or navigate session history |\n| \\`/compact\\` | summarize older context |\n| \\`/hotkeys\\`, \\`/changelog\\` | local help and release notes |\n\n## Examples of using Atomic\n\n| Goal | How to use |\n|---|---|\n| On-call / broken behavior | Use \\`/skill:workflow\\` to create a workflow that runs \\`/run debugger \"Reproduce the failure, patch the root cause, and validate it\"\\` for a focused fix loop |\n| Research → spec → implementation | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:research-codebase\\`, then \\`/skill:create-spec\\`, then direct implementation or \\`/workflow ralph ...\\` |\n| Testing / regression hardening | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:tdd\\` for test-first work, then \\`/parallel-review current diff\\`, then land the change |\n| Large repo discovery | Use \\`/skill:workflow\\` to create a workflow that runs \\`/parallel codebase-locator \"map the area\" -> codebase-analyzer \"trace the current flow\" -> codebase-pattern-finder \"find patterns\" --bg\\` |\n| UI / product polish | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:impeccable\\` for interface critique, refinement, and clearer UX decisions |\n\n## Subagents\n\nSubagents are focused child Atomic sessions you can point at one job inside the repo.\n\n| Built-in subagent | Use |\n|---|---|\n| \\`codebase-locator\\` | find relevant files, tests, entrypoints, and configs |\n| \\`codebase-analyzer\\` | explain current behavior with file:line refs |\n| \\`codebase-pattern-finder\\` | find existing code to model after |\n| \\`debugger\\` | reproduce, diagnose, and fix broken behavior |\n\nHow the direct commands map to repo work:\n- \\`/run\\` = one specialist on one job, for example \\`/run codebase-locator \"Map the webhook retry flow\"\\`\n- \\`/parallel\\` = several independent specialists at once, for example \\`/parallel codebase-locator \"map retry files\" -> codebase-pattern-finder \"find existing retry/backoff patterns\" -> codebase-online-researcher \"research current retry guidance\" --bg\\`\n- \\`/chain\\` = ordered handoffs, for example \\`/chain codebase-locator \"find the auth files\" -> codebase-analyzer \"trace the auth flow\" -> debugger \"patch the failing auth edge case\"\\`\n\n## Top skills\n\n| Skill | When to use | How to run |\n|---|---|---|\n| \\`research-codebase\\` | write a grounded research artifact for one subsystem or question | \\`/skill:research-codebase how the rate limiter works in src/middleware/\\` |\n| \\`create-spec\\` | turn research into an implementation-ready plan | \\`/skill:create-spec from research/docs/<date>-<topic>.md\\` |\n| \\`tdd\\` | do test-first feature or bug work | \\`/skill:tdd\\` |\n| \\`prompt-engineer\\` | tighten a vague prompt before a long run | \\`/skill:prompt-engineer Draft a sharper implementation prompt for ...\\` |\n| \\`workflow\\` | author, inspect, or improve workflows; start by describing the desired workflow in natural language | \\`/skill:workflow\\` |\n| \\`subagent\\` | learn delegation patterns and exact \\`/run\\`, \\`/parallel\\`, and \\`/chain\\` usage | \\`/skill:subagent\\` |\n| \\`impeccable\\` | critique or refine frontend and product UI | \\`/skill:impeccable\\` |\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation loops where you want implementation, review, and validation built in | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | UI and design-system work that benefits from generation and refinement loops | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow list\\` to see what is available and \\`/workflow inputs <name>\\` to inspect inputs in your environment.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see the pieces used on a code task\n\\`/atomic workflows\\` — learn when to use workflows`;\n\nconst EXAMPLE = `# Practical example\n\nThis is an example of a spec-driven development process using Atomic workflows. Use it when you are new to a repo or the task has non-trivial scope. Type the examples below into the Atomic TUI chat after starting \\`atomic\\` in your project.\n\n## 1. Research what exists\n\nUse \\`/skill:research-codebase\\` for a scoped area, subsystem, or directory:\n\n\\`/skill:research-codebase how the rate limiter works in src/middleware/\\`\n\nUse \\`deep-research-codebase\\` when the answer spans the whole repo or a cross-cutting implementation path:\n\n\\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\`\n\nIf the research prompt is vague, tighten it first with \\`/skill:prompt-engineer\\`:\n\n\\`/skill:prompt-engineer Draft a sharper repo-research prompt for understanding payment retries end to end, including retries, queues, and failure handling.\\`\n\n## 2. Create a spec when requirements are fuzzy\n\nSkip this if the implementation request is already precise.\n\n\\`/skill:create-spec from research/docs/<date>-<topic>.md\\`\n\n## 3. Implement with review built in\n\nFor ordinary work, ask Atomic directly and require validation:\n\n\\`Implement the approved spec in specs/<date>-<topic>.md. Run focused tests and summarize validation.\\`\n\nFor larger work, use subagents or a workflow:\n\n\\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\`\n\n## 4. Decide and land\n\nIf you used \\`ralph\\`, the workflow already ran parallel reviewers. Use its final result and review feedback to decide whether to ship or iterate again.\n\nIf you implemented directly instead of using \\`ralph\\`, you can still run:\n\n\\`/parallel-review current diff\\`\n\nAtomic will synthesize reviewer feedback and ask before applying fixes.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic workflows\\` — learn when to use workflows\n\\`/atomic overview\\` — quick refresh`;\n\nconst WORKFLOWS = `# Workflows primer\n\nA workflow is a TypeScript-defined pipeline built with \\`defineWorkflow(...).run(...).compile()\\`. It can run tasks, chains, parallel fan-out, human-in-the-loop prompts, background status, and model fallback chains.\n\nStart by defining the workflow in natural language with \\`/skill:workflow\\`, the workflow creator skill. Describe the goal, inputs, stages, which steps are parallel or sequential, handoff/output shape, and any model or thinking-level preferences; let the skill help turn that into a reusable workflow before you hand-edit TypeScript.\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation and review loops | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | frontend and product design work | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow inputs <name>\\` to inspect the exact inputs in your environment.\n\nUse \\`/skill:research-codebase ...\\` when you want research on one subsystem, directory, or focused question. Use \\`/workflow deep-research-codebase ...\\` when the answer needs end-to-end tracing across many parts of the repo.\n\nIf you are drafting research, reviewer, or synthesis prompts for a workflow, use \\`/skill:prompt-engineer\\` first. It is a good fit when a stage prompt feels vague, overloaded, or underspecified.\n\n## What good workflow authoring looks like\n\nA good workflow request is explicit about stage purpose, model choice, handoff, and the decision each step must return.\n\nExample: prompt \\`/skill:workflow\\` with something like this:\n\n~~~text\nCreate a reusable workflow called review-changes.\n\nIt should accept one required text input called target for a diff, PR summary, or review target.\n\nRun two independent review stages in parallel with fresh context:\n- one reviewer focused on correctness, regressions, and missing tests using openai-codex/gpt-5.5 at xhigh thinking\n- one reviewer focused on edge cases, maintainability, and hidden risks using anthropic/claude-opus-4-7 at xhigh thinking\n\nThen add an aggregate stage that consolidates both reviews, deduplicates overlap, keeps only evidence-backed issues, and separates blockers from optional suggestions using openai/gpt-5.5 at high thinking.\n\nFinally add an adjudicate stage using anthropic/claude-sonnet-4 at high thinking that decides what to fix now, what to defer, and what to reject. Return a short action list with rationale.\n\nThe workflow should return structured output with consolidated_review and decision fields.\n~~~\n\nWhy this is good:\n- it names the workflow and required input\n- it specifies which stages are parallel vs sequential\n- each stage has one job\n- it defines the handoff and final outputs\n- it calls out model choice and thinking level where that matters\n\n## Run and inspect\n\n\\`/workflow list\\`\n\n\\`/workflow inputs ralph\\`\n\n\\`/workflow ralph prompt=\"Migrate the database layer to Drizzle\" max_loops=5\\`\n\n\\`/workflow status\\`\n\n\\`/workflow connect <run-id>\\`\n\n\\`/workflow interrupt <run-id>\\`\n\n\\`/workflow resume <run-id>\\`\n\nWorkflows run as background tasks. Use F2 or \\`/workflow connect <run-id>\\` for the graph viewer. Human-in-the-loop prompts appear there, not as chat modals.\n\n## Author your own\n\nUse \\`/skill:workflow\\` to describe your workflow in natural language and build your first workflow. This is the recommended starting point for creation and design questions: say what you want the workflow to accomplish, what inputs it should accept, what stages should run, and what final output or decision it should return. Ask Atomic in chat when you want help refining or implementing one.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see workflows in a normal task flow\n\\`/atomic overview\\` — quick refresh`;\n\nconst GUIDE_SECTIONS = [\n\t{\n\t\tname: \"overview\",\n\t\taliases: [],\n\t\tlabel: \"overview\",\n\t\tdescription: \"30-second overview\",\n\t\trender: () => OVERVIEW,\n\t},\n\t{\n\t\tname: \"workflows\",\n\t\taliases: [\"workflow\"],\n\t\tlabel: \"workflows\",\n\t\tdescription: \"Workflow primer\",\n\t\trender: () => WORKFLOWS,\n\t},\n\t{\n\t\tname: \"example\",\n\t\taliases: [\"examples\"],\n\t\tlabel: \"example\",\n\t\tdescription: \"Practical first workflow\",\n\t\trender: () => EXAMPLE,\n\t},\n\t{\n\t\tname: \"whats-new\",\n\t\taliases: [\"what's new\", \"whats new\", \"news\", \"updates\", \"changelog\"],\n\t\tlabel: \"what's new\",\n\t\tdescription: \"Recent release notes\",\n\t\trender: readLatestStableChangelog,\n\t},\n] as const satisfies readonly {\n\treadonly name: string;\n\treadonly aliases: readonly string[];\n\treadonly label: string;\n\treadonly description: string;\n\treadonly render: (cwd: string) => string;\n}[];\n\ntype AtomicGuideSection = (typeof GUIDE_SECTIONS)[number];\ntype AtomicGuideSectionName = AtomicGuideSection[\"name\"];\n\nexport type AtomicGuideHelpChoice = AtomicGuideSection[\"label\"];\n\nexport type AtomicGuideMode = \"help\" | AtomicGuideSectionName;\n\nexport const ATOMIC_GUIDE_HELP_CHOICES: readonly AtomicGuideHelpChoice[] = GUIDE_SECTIONS.map(\n\t(section) => section.label,\n);\n\nconst GUIDE_SECTIONS_BY_NAME = new Map<AtomicGuideSectionName, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.name, section]),\n);\nconst GUIDE_SECTIONS_BY_LABEL = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.label, section]),\n);\nconst GUIDE_SECTIONS_BY_INPUT = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.flatMap((section) =>\n\t\t[section.name, section.label, ...section.aliases].map((input) => [input, section] as const),\n\t),\n);\n\nexport function isAtomicGuideHelpChoice(choice: string): choice is AtomicGuideHelpChoice {\n\treturn GUIDE_SECTIONS_BY_LABEL.has(choice);\n}\n\nconst ATOMIC_GUIDE_TRAILING_PUNCTUATION = \"?!.,;:\";\n\nfunction stripTrailingAtomicGuidePunctuation(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && ATOMIC_GUIDE_TRAILING_PUNCTUATION.includes(value.charAt(end - 1))) {\n\t\tend--;\n\t}\n\treturn value.slice(0, end);\n}\n\nfunction getGuideSectionForChoice(choice: string): AtomicGuideSection | undefined {\n\treturn GUIDE_SECTIONS_BY_LABEL.get(choice);\n}\n\nfunction getGuideSectionForMode(mode: AtomicGuideSectionName): AtomicGuideSection {\n\tconst section = GUIDE_SECTIONS_BY_NAME.get(mode);\n\tif (!section) throw new Error(`Unknown Atomic guide section: ${mode}`);\n\treturn section;\n}\n\nfunction getAtomicGuideHelpMenu(): string {\n\tconst sectionHelp = GUIDE_SECTIONS.map(\n\t\t(section) => `- \\`${section.label}\\` — run \\`/atomic ${section.label}\\``,\n\t).join(\"\\n\");\n\treturn `# Atomic\\n\\nSelect where to start:\\n\\n${sectionHelp}`;\n}\n\nexport function normalizeAtomicGuideMode(args: string): AtomicGuideMode {\n\tconst normalized = stripTrailingAtomicGuidePunctuation(args.trim().toLowerCase());\n\tif (!normalized) return \"help\";\n\n\treturn GUIDE_SECTIONS_BY_INPUT.get(normalized)?.name ?? \"help\";\n}\n\nexport function getAtomicGuideArgumentCompletions(prefix: string): AutocompleteItem[] | null {\n\tconst query = prefix.trim().toLowerCase();\n\tconst items = GUIDE_SECTIONS.map((section) => ({\n\t\tvalue: section.label,\n\t\tlabel: section.label,\n\t\tdescription: section.description,\n\t}));\n\tconst filtered = query\n\t\t? items.filter((item) => item.value.startsWith(query) || item.label.startsWith(query))\n\t\t: items;\n\treturn filtered.length > 0 ? filtered : null;\n}\n\nfunction readLatestStableChangelog(cwd: string): string {\n\tconst changelogPath = getChangelogPath();\n\tconst stableSections = parseChangelog(changelogPath)\n\t\t.filter((entry) => entry.prerelease === null)\n\t\t.slice(0, 3)\n\t\t.map((entry) => entry.content.trim())\n\t\t.filter(Boolean);\n\n\tif (stableSections.length === 0) {\n\t\treturn `# What's new\\n\\nNo stable release sections were found. Try \\`/changelog\\` for the interactive changelog viewer.\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n\t}\n\n\tconst relativePath = path.relative(cwd, changelogPath) || changelogPath;\n\treturn `# What's new\\n\\n${stableSections.join(\"\\n\\n\")}\\n\\nSource: \\`${relativePath}\\`\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n}\n\nexport function getAtomicGuideMessage(mode: AtomicGuideMode, cwd: string): string {\n\tif (mode === \"help\") return getAtomicGuideHelpMenu();\n\treturn getGuideSectionForMode(mode).render(cwd);\n}\n\nexport function atomicGuideModeForChoice(choice: AtomicGuideHelpChoice): AtomicGuideMode {\n\treturn getGuideSectionForChoice(choice)?.name ?? \"help\";\n}\n\n"]}
1
+ {"version":3,"file":"atomic-guide-command.js","sourceRoot":"","sources":["../../src/core/atomic-guide-command.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from \"node:path\";\nimport type { AutocompleteItem } from \"@earendil-works/pi-tui\";\nimport { getChangelogPath, parseChangelog } from \"../utils/changelog.ts\";\n\nexport const ATOMIC_GUIDE_COMMAND_NAME = \"atomic\";\nexport const ATOMIC_GUIDE_COMMAND_DESCRIPTION = \"Atomic onboarding and help guide\";\n\nconst OVERVIEW = `# Atomic overview\n\nAtomic turns one-off prompts into developer workflows: on-call debugging, repo research that turns into implementation, testing and review loops, and larger multi-stage automation. Start it in a project with \\`atomic\\`, then talk to it normally. Use \\`@file\\` to attach files, \\`!command\\` to run shell output through the model, and \\`!!command\\` to run shell output without adding it to context.\n\n## Core session commands\n\n| Command | Use |\n|---|---|\n| \\`/login\\` | configure auth |\n| \\`/model\\` | switch model |\n| \\`/settings\\` | thinking level, theme, message delivery, transport |\n| \\`/new\\`, \\`/resume\\` | start or resume sessions |\n| \\`/tree\\`, \\`/fork\\`, \\`/clone\\` | branch or navigate session history |\n| \\`/compact\\` | summarize older context |\n| \\`/hotkeys\\`, \\`/changelog\\` | local help and release notes |\n\n## Examples of using Atomic\n\n| Goal | How to use |\n|---|---|\n| On-call / broken behavior | Use \\`/skill:workflow\\` to create a workflow that runs \\`/run debugger \"Reproduce the failure, patch the root cause, and validate it\"\\` for a focused fix loop |\n| Research → spec → implementation | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:research-codebase\\`, then \\`/skill:create-spec\\`, then direct implementation or \\`/workflow ralph ...\\` |\n| Testing / regression hardening | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:tdd\\` for test-first work, then \\`/parallel-review current diff\\`, then land the change |\n| Large repo discovery | Use \\`/skill:workflow\\` to create a workflow that runs \\`/parallel codebase-locator \"map the area\" -> codebase-analyzer \"trace the current flow\" -> codebase-pattern-finder \"find patterns\" --bg\\` |\n| UI / product polish | Use \\`/skill:workflow\\` to create a workflow that runs \\`/skill:impeccable\\` for interface critique, refinement, and clearer UX decisions |\n\n## Subagents\n\nSubagents are focused child Atomic sessions you can point at one job inside the repo.\n\n| Built-in subagent | Use |\n|---|---|\n| \\`codebase-locator\\` | find relevant files, tests, entrypoints, and configs |\n| \\`codebase-analyzer\\` | explain current behavior with file:line refs |\n| \\`codebase-pattern-finder\\` | find existing code to model after |\n| \\`debugger\\` | reproduce, diagnose, and fix broken behavior |\n\nHow the direct commands map to repo work:\n- \\`/run\\` = one specialist on one job, for example \\`/run codebase-locator \"Map the webhook retry flow\"\\`\n- \\`/parallel\\` = several independent specialists at once, for example \\`/parallel codebase-locator \"map retry files\" -> codebase-pattern-finder \"find existing retry/backoff patterns\" -> codebase-online-researcher \"research current retry guidance\" --bg\\`\n- \\`/chain\\` = ordered handoffs, for example \\`/chain codebase-locator \"find the auth files\" -> codebase-analyzer \"trace the auth flow\" -> debugger \"patch the failing auth edge case\"\\`\n\n## Top skills\n\n| Skill | When to use | How to run |\n|---|---|---|\n| \\`research-codebase\\` | write a grounded research artifact for one subsystem or question | \\`/skill:research-codebase how the rate limiter works in src/middleware/\\` |\n| \\`create-spec\\` | turn research into an implementation-ready plan | \\`/skill:create-spec from research/docs/<date>-<topic>.md\\` |\n| \\`tdd\\` | do test-first feature or bug work | \\`/skill:tdd\\` |\n| \\`prompt-engineer\\` | tighten a vague prompt before a long run | \\`/skill:prompt-engineer Draft a sharper implementation prompt for ...\\` |\n| \\`workflow\\` | author, inspect, or improve workflows; start by describing the desired workflow in natural language | \\`/skill:workflow\\` |\n| \\`subagent\\` | learn delegation patterns and exact \\`/run\\`, \\`/parallel\\`, and \\`/chain\\` usage | \\`/skill:subagent\\` |\n| \\`impeccable\\` | critique or refine frontend and product UI | \\`/skill:impeccable\\` |\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation loops where you want implementation, review, and validation built in | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | UI and design-system work that benefits from generation and refinement loops | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow list\\` to see what is available and \\`/workflow inputs <name>\\` to inspect inputs in your environment.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see the pieces used on a code task\n\\`/atomic workflows\\` — learn when to use workflows`;\n\nconst EXAMPLE = `# Practical example\n\nThis is an example of a spec-driven development process using Atomic workflows. Use it when you are new to a repo or the task has non-trivial scope. Type the examples below into the Atomic TUI chat after starting \\`atomic\\` in your project.\n\n## 1. Research what exists\n\nUse \\`/skill:research-codebase\\` for a scoped area, subsystem, or directory:\n\n\\`/skill:research-codebase how the rate limiter works in src/middleware/\\`\n\nUse \\`deep-research-codebase\\` when the answer spans the whole repo or a cross-cutting implementation path:\n\n\\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\`\n\nIf the research prompt is vague, tighten it first with \\`/skill:prompt-engineer\\`:\n\n\\`/skill:prompt-engineer Draft a sharper repo-research prompt for understanding payment retries end to end, including retries, queues, and failure handling.\\`\n\n## 2. Create a spec when requirements are fuzzy\n\nSkip this if the implementation request is already precise.\n\n\\`/skill:create-spec from research/docs/<date>-<topic>.md\\`\n\n## 3. Implement with review built in\n\nFor ordinary work, ask Atomic directly and require validation:\n\n\\`Implement the approved spec in specs/<date>-<topic>.md. Run focused tests and summarize validation.\\`\n\nFor larger work, use subagents or a workflow:\n\n\\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\`\n\n## 4. Decide and land\n\nIf you used \\`ralph\\`, the workflow already ran parallel reviewers. Use its final result and review feedback to decide whether to ship or iterate again.\n\nIf you implemented directly instead of using \\`ralph\\`, you can still run:\n\n\\`/parallel-review current diff\\`\n\nAtomic will synthesize reviewer feedback and ask before applying fixes.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic workflows\\` — learn when to use workflows\n\\`/atomic overview\\` — quick refresh`;\n\nconst WORKFLOWS = `# Workflows primer\n\nA workflow is a TypeScript-defined pipeline built with \\`defineWorkflow(...).run(...).compile()\\`. It can run tasks, chains, parallel fan-out, human-in-the-loop prompts, background status, and model fallback chains.\n\nStart by defining the workflow in natural language with \\`/skill:workflow\\`, the workflow creator skill. Describe the goal, inputs, stages, which steps are parallel or sequential, handoff/output shape, and any model or thinking-level preferences; let the skill help turn that into a reusable workflow before you hand-edit TypeScript.\n\n## Built-in workflows\n\n| Workflow | When to use | How to run |\n|---|---|---|\n| \\`deep-research-codebase\\` | broad repo or cross-cutting research before you decide what to change (for one area, use \\`/skill:research-codebase\\`; this indexes the whole repo) | \\`/workflow deep-research-codebase prompt=\"How do payment retries work end to end?\"\\` |\n| \\`ralph\\` | larger implementation and review loops | \\`/workflow ralph prompt=\"Implement specs/<date>-<topic>.md and validate the changed behavior\"\\` |\n| \\`open-claude-design\\` | frontend and product design work | \\`/workflow open-claude-design prompt=\"Refresh the settings page hierarchy\"\\` |\n\nUse \\`/workflow inputs <name>\\` to inspect the exact inputs in your environment.\n\nUse \\`/skill:research-codebase ...\\` when you want research on one subsystem, directory, or focused question. Use \\`/workflow deep-research-codebase ...\\` when the answer needs end-to-end tracing across many parts of the repo.\n\nIf you are drafting research, reviewer, or synthesis prompts for a workflow, use \\`/skill:prompt-engineer\\` first. It is a good fit when a stage prompt feels vague, overloaded, or underspecified.\n\n## What good workflow authoring looks like\n\nA good workflow request is explicit about stage purpose, model choice, handoff, and the decision each step must return.\n\nExample: prompt \\`/skill:workflow\\` with something like this:\n\n~~~text\nCreate a reusable workflow called review-changes.\n\nIt should accept one required text input called target for a diff, PR summary, or review target.\n\nRun two independent review stages in parallel with fresh context:\n- one reviewer focused on correctness, regressions, and missing tests using openai-codex/gpt-5.5 at xhigh thinking\n- one reviewer focused on edge cases, maintainability, and hidden risks using anthropic/claude-opus-4-7 at xhigh thinking\n\nThen add an aggregate stage that consolidates both reviews, deduplicates overlap, keeps only evidence-backed issues, and separates blockers from optional suggestions using openai/gpt-5.5 at high thinking.\n\nFinally add an adjudicate stage using anthropic/claude-sonnet-4 at high thinking that decides what to fix now, what to defer, and what to reject. Return a short action list with rationale.\n\nThe workflow should return structured output with consolidated_review and decision fields.\n~~~\n\nWhy this is good:\n- it names the workflow and required input\n- it specifies which stages are parallel vs sequential\n- each stage has one job\n- it defines the handoff and final outputs\n- it calls out model choice and thinking level where that matters\n\n## Run and inspect\n\n\\`/workflow list\\`\n\n\\`/workflow inputs ralph\\`\n\n\\`/workflow ralph prompt=\"Migrate the database layer to Drizzle\" max_loops=5\\`\n\n\\`/workflow status\\`\n\n\\`/workflow connect <run-id>\\`\n\n\\`/workflow interrupt <run-id>\\`\n\n\\`/workflow resume <run-id>\\`\n\nWorkflows run as background tasks. Use F2 or \\`/workflow connect <run-id>\\` for the graph viewer. Human-in-the-loop prompts appear there, not as chat modals.\n\n## Author your own\n\nUse \\`/skill:workflow\\` to describe your workflow in natural language and build your first workflow. This is the recommended starting point for creation and design questions: say what you want the workflow to accomplish, what inputs it should accept, what stages should run, and what final output or decision it should return. Ask Atomic in chat when you want help refining or implementing one.\n\n─────────────────────────────────────────────────────────────────\n\nWhere to next:\n\n\\`/atomic example\\` — see workflows in a normal task flow\n\\`/atomic overview\\` — quick refresh`;\n\nconst GUIDE_SECTIONS = [\n\t{\n\t\tname: \"overview\",\n\t\taliases: [],\n\t\tlabel: \"overview\",\n\t\tdescription: \"30-second overview\",\n\t\trender: () => OVERVIEW,\n\t},\n\t{\n\t\tname: \"workflows\",\n\t\taliases: [\"workflow\"],\n\t\tlabel: \"workflows\",\n\t\tdescription: \"Workflow primer\",\n\t\trender: () => WORKFLOWS,\n\t},\n\t{\n\t\tname: \"example\",\n\t\taliases: [\"examples\"],\n\t\tlabel: \"example\",\n\t\tdescription: \"Practical first workflow\",\n\t\trender: () => EXAMPLE,\n\t},\n\t{\n\t\tname: \"whats-new\",\n\t\taliases: [\"what's new\", \"whats new\", \"news\", \"updates\", \"changelog\"],\n\t\tlabel: \"what's new\",\n\t\tdescription: \"Recent release notes\",\n\t\trender: readLatestStableChangelog,\n\t},\n] as const satisfies readonly {\n\treadonly name: string;\n\treadonly aliases: readonly string[];\n\treadonly label: string;\n\treadonly description: string;\n\treadonly render: (cwd: string) => string;\n}[];\n\ntype AtomicGuideSection = (typeof GUIDE_SECTIONS)[number];\ntype AtomicGuideSectionName = AtomicGuideSection[\"name\"];\n\nexport type AtomicGuideHelpChoice = AtomicGuideSection[\"label\"];\n\nexport type AtomicGuideMode = \"help\" | AtomicGuideSectionName;\n\nexport const ATOMIC_GUIDE_HELP_CHOICES: readonly AtomicGuideHelpChoice[] = GUIDE_SECTIONS.map(\n\t(section) => section.label,\n);\n\nconst GUIDE_SECTIONS_BY_NAME = new Map<AtomicGuideSectionName, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.name, section]),\n);\nconst GUIDE_SECTIONS_BY_LABEL = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.map((section) => [section.label, section]),\n);\nconst GUIDE_SECTIONS_BY_INPUT = new Map<string, AtomicGuideSection>(\n\tGUIDE_SECTIONS.flatMap((section) =>\n\t\t[section.name, section.label, ...section.aliases].map((input) => [input, section] as const),\n\t),\n);\n\nexport function isAtomicGuideHelpChoice(choice: string): choice is AtomicGuideHelpChoice {\n\treturn GUIDE_SECTIONS_BY_LABEL.has(choice);\n}\n\nconst ATOMIC_GUIDE_TRAILING_PUNCTUATION = \"?!.,;:\";\n\nfunction stripTrailingAtomicGuidePunctuation(value: string): string {\n\tlet end = value.length;\n\twhile (end > 0 && ATOMIC_GUIDE_TRAILING_PUNCTUATION.includes(value.charAt(end - 1))) {\n\t\tend--;\n\t}\n\treturn value.slice(0, end);\n}\n\nfunction getGuideSectionForChoice(choice: string): AtomicGuideSection | undefined {\n\treturn GUIDE_SECTIONS_BY_LABEL.get(choice);\n}\n\nfunction getGuideSectionForMode(mode: AtomicGuideSectionName): AtomicGuideSection {\n\tconst section = GUIDE_SECTIONS_BY_NAME.get(mode);\n\tif (!section) throw new Error(`Unknown Atomic guide section: ${mode}`);\n\treturn section;\n}\n\nfunction getAtomicGuideHelpMenu(): string {\n\tconst sectionHelp = GUIDE_SECTIONS.map(\n\t\t(section) => `- \\`${section.label}\\` — run \\`/atomic ${section.label}\\``,\n\t).join(\"\\n\");\n\treturn `# Atomic\\n\\nSelect where to start:\\n\\n${sectionHelp}`;\n}\n\nexport function normalizeAtomicGuideMode(args: string): AtomicGuideMode {\n\tconst normalized = stripTrailingAtomicGuidePunctuation(args.trim().toLowerCase());\n\tif (!normalized) return \"help\";\n\n\treturn GUIDE_SECTIONS_BY_INPUT.get(normalized)?.name ?? \"help\";\n}\n\nexport function getAtomicGuideArgumentCompletions(prefix: string): AutocompleteItem[] | null {\n\tconst query = prefix.trim().toLowerCase();\n\tconst items = GUIDE_SECTIONS.map((section) => ({\n\t\tvalue: section.label,\n\t\tlabel: section.label,\n\t\tdescription: section.description,\n\t}));\n\tconst filtered = query\n\t\t? items.filter((item) => item.value.startsWith(query) || item.label.startsWith(query))\n\t\t: items;\n\treturn filtered.length > 0 ? filtered : null;\n}\n\nfunction readLatestStableChangelog(cwd: string): string {\n\tconst changelogPath = getChangelogPath();\n\tconst stableSections = parseChangelog(changelogPath)\n\t\t.filter((entry) => entry.prerelease === null)\n\t\t.slice(0, 3)\n\t\t.map((entry) => entry.content.trim())\n\t\t.filter(Boolean);\n\n\tif (stableSections.length === 0) {\n\t\treturn `# What's new\\n\\nNo stable release sections were found. Try \\`/changelog\\` for the interactive changelog viewer.\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n\t}\n\n\tconst relativePath = path.relative(cwd, changelogPath) || changelogPath;\n\treturn `# What's new\\n\\n${stableSections.join(\"\\n\\n\")}\\n\\nSource: \\`${relativePath}\\`\\n\\n─────────────────────────────────────────────────────────────────\\n\\nWhere to next:\\n\\n\\`/atomic example\\` — see a practical first workflow\\n\\`/atomic overview\\` — quick refresh`;\n}\n\nexport function getAtomicGuideMessage(mode: AtomicGuideMode, cwd: string): string {\n\tif (mode === \"help\") return getAtomicGuideHelpMenu();\n\treturn getGuideSectionForMode(mode).render(cwd);\n}\n\nexport function atomicGuideModeForChoice(choice: AtomicGuideHelpChoice): AtomicGuideMode {\n\treturn getGuideSectionForChoice(choice)?.name ?? \"help\";\n}\n\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-guidance.d.ts","sourceRoot":"","sources":["../../src/core/auth-guidance.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnE","sourcesContent":["import { join } from \"node:path\";\nimport { getDocsPath } from \"../config.js\";\n\nconst UNKNOWN_PROVIDER = \"unknown\";\n\nexport function getProviderLoginHelp(): string {\n\treturn [\n\t\t\"Use /login to log into a provider via OAuth or API key. See:\",\n\t\t` ${join(getDocsPath(), \"providers.md\")}`,\n\t\t` ${join(getDocsPath(), \"models.md\")}`,\n\t].join(\"\\n\");\n}\n\nexport function formatNoModelsAvailableMessage(): string {\n\treturn `No models available. ${getProviderLoginHelp()}`;\n}\n\nexport function formatNoModelSelectedMessage(): string {\n\treturn `No model selected.\\n\\n${getProviderLoginHelp()}\\n\\nThen use /model to select a model.`;\n}\n\nexport function formatNoApiKeyFoundMessage(provider: string): string {\n\tconst providerDisplay = provider === UNKNOWN_PROVIDER ? \"the selected model\" : provider;\n\treturn `No API key found for ${providerDisplay}.\\n\\n${getProviderLoginHelp()}`;\n}\n"]}
1
+ {"version":3,"file":"auth-guidance.d.ts","sourceRoot":"","sources":["../../src/core/auth-guidance.ts"],"names":[],"mappings":"AAKA,wBAAgB,oBAAoB,IAAI,MAAM,CAM7C;AAED,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,wBAAgB,4BAA4B,IAAI,MAAM,CAErD;AAED,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnE","sourcesContent":["import { join } from \"node:path\";\nimport { getDocsPath } from \"../config.ts\";\n\nconst UNKNOWN_PROVIDER = \"unknown\";\n\nexport function getProviderLoginHelp(): string {\n\treturn [\n\t\t\"Use /login to log into a provider via OAuth or API key. See:\",\n\t\t` ${join(getDocsPath(), \"providers.md\")}`,\n\t\t` ${join(getDocsPath(), \"models.md\")}`,\n\t].join(\"\\n\");\n}\n\nexport function formatNoModelsAvailableMessage(): string {\n\treturn `No models available. ${getProviderLoginHelp()}`;\n}\n\nexport function formatNoModelSelectedMessage(): string {\n\treturn `No model selected.\\n\\n${getProviderLoginHelp()}\\n\\nThen use /model to select a model.`;\n}\n\nexport function formatNoApiKeyFoundMessage(provider: string): string {\n\tconst providerDisplay = provider === UNKNOWN_PROVIDER ? \"the selected model\" : provider;\n\treturn `No API key found for ${providerDisplay}.\\n\\n${getProviderLoginHelp()}`;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"auth-guidance.js","sourceRoot":"","sources":["../../src/core/auth-guidance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAEnC,MAAM,UAAU,oBAAoB;IACnC,OAAO;QACN,8DAA8D;QAC9D,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE;QAC1C,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,EAAE;KACvC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC7C,OAAO,wBAAwB,oBAAoB,EAAE,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,OAAO,yBAAyB,oBAAoB,EAAE,wCAAwC,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IAC1D,MAAM,eAAe,GAAG,QAAQ,KAAK,gBAAgB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxF,OAAO,wBAAwB,eAAe,QAAQ,oBAAoB,EAAE,EAAE,CAAC;AAChF,CAAC","sourcesContent":["import { join } from \"node:path\";\nimport { getDocsPath } from \"../config.js\";\n\nconst UNKNOWN_PROVIDER = \"unknown\";\n\nexport function getProviderLoginHelp(): string {\n\treturn [\n\t\t\"Use /login to log into a provider via OAuth or API key. See:\",\n\t\t` ${join(getDocsPath(), \"providers.md\")}`,\n\t\t` ${join(getDocsPath(), \"models.md\")}`,\n\t].join(\"\\n\");\n}\n\nexport function formatNoModelsAvailableMessage(): string {\n\treturn `No models available. ${getProviderLoginHelp()}`;\n}\n\nexport function formatNoModelSelectedMessage(): string {\n\treturn `No model selected.\\n\\n${getProviderLoginHelp()}\\n\\nThen use /model to select a model.`;\n}\n\nexport function formatNoApiKeyFoundMessage(provider: string): string {\n\tconst providerDisplay = provider === UNKNOWN_PROVIDER ? \"the selected model\" : provider;\n\treturn `No API key found for ${providerDisplay}.\\n\\n${getProviderLoginHelp()}`;\n}\n"]}
1
+ {"version":3,"file":"auth-guidance.js","sourceRoot":"","sources":["../../src/core/auth-guidance.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAEnC,MAAM,UAAU,oBAAoB;IACnC,OAAO;QACN,8DAA8D;QAC9D,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,EAAE;QAC1C,KAAK,IAAI,CAAC,WAAW,EAAE,EAAE,WAAW,CAAC,EAAE;KACvC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,MAAM,UAAU,8BAA8B;IAC7C,OAAO,wBAAwB,oBAAoB,EAAE,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,4BAA4B;IAC3C,OAAO,yBAAyB,oBAAoB,EAAE,wCAAwC,CAAC;AAChG,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,QAAgB;IAC1D,MAAM,eAAe,GAAG,QAAQ,KAAK,gBAAgB,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,QAAQ,CAAC;IACxF,OAAO,wBAAwB,eAAe,QAAQ,oBAAoB,EAAE,EAAE,CAAC;AAChF,CAAC","sourcesContent":["import { join } from \"node:path\";\nimport { getDocsPath } from \"../config.ts\";\n\nconst UNKNOWN_PROVIDER = \"unknown\";\n\nexport function getProviderLoginHelp(): string {\n\treturn [\n\t\t\"Use /login to log into a provider via OAuth or API key. See:\",\n\t\t` ${join(getDocsPath(), \"providers.md\")}`,\n\t\t` ${join(getDocsPath(), \"models.md\")}`,\n\t].join(\"\\n\");\n}\n\nexport function formatNoModelsAvailableMessage(): string {\n\treturn `No models available. ${getProviderLoginHelp()}`;\n}\n\nexport function formatNoModelSelectedMessage(): string {\n\treturn `No model selected.\\n\\n${getProviderLoginHelp()}\\n\\nThen use /model to select a model.`;\n}\n\nexport function formatNoApiKeyFoundMessage(provider: string): string {\n\tconst providerDisplay = provider === UNKNOWN_PROVIDER ? \"the selected model\" : provider;\n\treturn `No API key found for ${providerDisplay}.\\n\\n${getProviderLoginHelp()}`;\n}\n"]}
@@ -48,12 +48,12 @@ export declare class InMemoryAuthStorageBackend implements AuthStorageBackend {
48
48
  * Credential storage backed by a JSON file.
49
49
  */
50
50
  export declare class AuthStorage {
51
- private storage;
52
51
  private data;
53
52
  private runtimeOverrides;
54
53
  private fallbackResolver?;
55
54
  private loadError;
56
55
  private errors;
56
+ private storage;
57
57
  private constructor();
58
58
  static create(authPath?: string): AuthStorage;
59
59
  static fromStorage(storage: AuthStorageBackend): AuthStorage;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@earendil-works/pi-ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentConfigPaths, getAgentDir } from \"../config.js\";\nimport { resolveConfigValue } from \"./resolve-config-value.js\";\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?: \"stored\" | \"runtime\" | \"environment\" | \"fallback\" | \"models_json_key\" | \"models_json_command\";\n\tlabel?: string;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tconstructor(\n\t\tprivate authPath: string = join(getAgentDir(), \"auth.json\"),\n\t\tprivate readPaths: string[] = [authPath],\n\t) {}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\tprivate readMergedAuth(): string | undefined {\n\t\tlet merged: AuthStorageData = {};\n\t\tlet found = false;\n\t\tfor (let i = this.readPaths.length - 1; i >= 0; i--) {\n\t\t\tconst readPath = this.readPaths[i]!;\n\t\t\tif (!existsSync(readPath)) continue;\n\t\t\tconst parsed = JSON.parse(readFileSync(readPath, \"utf-8\")) as AuthStorageData;\n\t\t\tmerged = { ...merged, ...parsed };\n\t\t\tfound = true;\n\t\t}\n\t\treturn found ? JSON.stringify(merged, null, 2) : undefined;\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\tif (existsSync(this.authPath)) {\n\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t}\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\t\tthis.ensureFileExists();\n\t\t\t\t}\n\t\t\t\tif (!release) {\n\t\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t\t}\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\tthis.ensureFileExists();\n\t\t\t}\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tprivate constructor(private storage: AuthStorageBackend) {\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string): AuthStorage {\n\t\treturn new AuthStorage(\n\t\t\tnew FileAuthStorageBackend(\n\t\t\t\tauthPath ?? join(getAgentDir(), \"auth.json\"),\n\t\t\t\tauthPath ? [authPath] : getAgentConfigPaths(\"auth.json\"),\n\t\t\t),\n\t\t);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend): AuthStorage {\n\t\treturn new AuthStorage(storage);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\tif (this.fallbackResolver?.(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\tif (this.data[provider]) {\n\t\t\treturn { configured: true, source: \"stored\" };\n\t\t}\n\n\t\tif (this.runtimeOverrides.has(provider)) {\n\t\t\treturn { configured: false, source: \"runtime\", label: \"--api-key\" };\n\t\t}\n\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tif (envKeys?.[0]) {\n\t\t\treturn { configured: false, source: \"environment\", label: envKeys[0] };\n\t\t}\n\n\t\tif (this.fallbackResolver?.(provider)) {\n\t\t\treturn { configured: false, source: \"fallback\", label: \"custom provider config\" };\n\t\t}\n\n\t\treturn { configured: false };\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst cred = currentData[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. API key from auth.json\n\t * 3. OAuth token from auth.json (auto-refreshed with locking)\n\t * 4. Environment variable\n\t * 5. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\t// Runtime override takes highest priority\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) {\n\t\t\treturn runtimeKey;\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\treturn resolveConfigValue(cred.key);\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\tif (!provider) {\n\t\t\t\t// Unknown OAuth provider, can't get API key\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Check if token needs refresh\n\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\tif (needsRefresh) {\n\t\t\t\t// Use locked refresh to prevent race conditions\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result.apiKey;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t// Refresh failed - re-read file to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t// Another instance refreshed successfully, use those credentials\n\t\t\t\t\t\treturn provider.getApiKey(updatedCred);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Refresh truly failed - return undefined so model discovery skips this provider\n\t\t\t\t\t// User can /login to re-authenticate (credentials preserved for retry)\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Token not expired, use current access token\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to environment variable\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (envKey) return envKey;\n\n\t\t// Fall back to custom resolver (e.g., models.json custom providers)\n\t\tif (options?.includeFallback !== false) {\n\t\t\treturn this.fallbackResolver?.(providerId) ?? undefined;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n}\n"]}
1
+ {"version":3,"file":"auth-storage.d.ts","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@earendil-works/pi-ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentConfigPaths, getAgentDir } from \"../config.ts\";\nimport { resolveConfigValue } from \"./resolve-config-value.ts\";\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?: \"stored\" | \"runtime\" | \"environment\" | \"fallback\" | \"models_json_key\" | \"models_json_command\";\n\tlabel?: string;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tdeclare private authPath: string;\n\tdeclare private readPaths: string[];\n\n\tconstructor(\n\t\tauthPath: string = join(getAgentDir(), \"auth.json\"),\n\t\treadPaths: string[] = [authPath],\n\t) {\n\t\tthis.authPath = authPath;\n\t\tthis.readPaths = readPaths;\n\t}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\tprivate readMergedAuth(): string | undefined {\n\t\tlet merged: AuthStorageData = {};\n\t\tlet found = false;\n\t\tfor (let i = this.readPaths.length - 1; i >= 0; i--) {\n\t\t\tconst readPath = this.readPaths[i]!;\n\t\t\tif (!existsSync(readPath)) continue;\n\t\t\tconst parsed = JSON.parse(readFileSync(readPath, \"utf-8\")) as AuthStorageData;\n\t\t\tmerged = { ...merged, ...parsed };\n\t\t\tfound = true;\n\t\t}\n\t\treturn found ? JSON.stringify(merged, null, 2) : undefined;\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\tif (existsSync(this.authPath)) {\n\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t}\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\t\tthis.ensureFileExists();\n\t\t\t\t}\n\t\t\t\tif (!release) {\n\t\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t\t}\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\tthis.ensureFileExists();\n\t\t\t}\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tdeclare private storage: AuthStorageBackend;\n\nprivate constructor(storage: AuthStorageBackend) {\n\t\tthis.storage = storage;\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string): AuthStorage {\n\t\treturn new AuthStorage(\n\t\t\tnew FileAuthStorageBackend(\n\t\t\t\tauthPath ?? join(getAgentDir(), \"auth.json\"),\n\t\t\t\tauthPath ? [authPath] : getAgentConfigPaths(\"auth.json\"),\n\t\t\t),\n\t\t);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend): AuthStorage {\n\t\treturn new AuthStorage(storage);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\tif (this.fallbackResolver?.(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\tif (this.data[provider]) {\n\t\t\treturn { configured: true, source: \"stored\" };\n\t\t}\n\n\t\tif (this.runtimeOverrides.has(provider)) {\n\t\t\treturn { configured: false, source: \"runtime\", label: \"--api-key\" };\n\t\t}\n\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tif (envKeys?.[0]) {\n\t\t\treturn { configured: false, source: \"environment\", label: envKeys[0] };\n\t\t}\n\n\t\tif (this.fallbackResolver?.(provider)) {\n\t\t\treturn { configured: false, source: \"fallback\", label: \"custom provider config\" };\n\t\t}\n\n\t\treturn { configured: false };\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst cred = currentData[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. API key from auth.json\n\t * 3. OAuth token from auth.json (auto-refreshed with locking)\n\t * 4. Environment variable\n\t * 5. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\t// Runtime override takes highest priority\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) {\n\t\t\treturn runtimeKey;\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\treturn resolveConfigValue(cred.key);\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\tif (!provider) {\n\t\t\t\t// Unknown OAuth provider, can't get API key\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Check if token needs refresh\n\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\tif (needsRefresh) {\n\t\t\t\t// Use locked refresh to prevent race conditions\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result.apiKey;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t// Refresh failed - re-read file to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t// Another instance refreshed successfully, use those credentials\n\t\t\t\t\t\treturn provider.getApiKey(updatedCred);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Refresh truly failed - return undefined so model discovery skips this provider\n\t\t\t\t\t// User can /login to re-authenticate (credentials preserved for retry)\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Token not expired, use current access token\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to environment variable\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (envKey) return envKey;\n\n\t\t// Fall back to custom resolver (e.g., models.json custom providers)\n\t\tif (options?.includeFallback !== false) {\n\t\t\treturn this.fallbackResolver?.(providerId) ?? undefined;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n}\n"]}
@@ -165,11 +165,11 @@ export class InMemoryAuthStorageBackend {
165
165
  */
166
166
  export class AuthStorage {
167
167
  constructor(storage) {
168
- this.storage = storage;
169
168
  this.data = {};
170
169
  this.runtimeOverrides = new Map();
171
170
  this.loadError = null;
172
171
  this.errors = [];
172
+ this.storage = storage;
173
173
  this.reload();
174
174
  }
175
175
  static create(authPath) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth-storage.js","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@earendil-works/pi-ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentConfigPaths, getAgentDir } from \"../config.js\";\nimport { resolveConfigValue } from \"./resolve-config-value.js\";\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?: \"stored\" | \"runtime\" | \"environment\" | \"fallback\" | \"models_json_key\" | \"models_json_command\";\n\tlabel?: string;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tconstructor(\n\t\tprivate authPath: string = join(getAgentDir(), \"auth.json\"),\n\t\tprivate readPaths: string[] = [authPath],\n\t) {}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\tprivate readMergedAuth(): string | undefined {\n\t\tlet merged: AuthStorageData = {};\n\t\tlet found = false;\n\t\tfor (let i = this.readPaths.length - 1; i >= 0; i--) {\n\t\t\tconst readPath = this.readPaths[i]!;\n\t\t\tif (!existsSync(readPath)) continue;\n\t\t\tconst parsed = JSON.parse(readFileSync(readPath, \"utf-8\")) as AuthStorageData;\n\t\t\tmerged = { ...merged, ...parsed };\n\t\t\tfound = true;\n\t\t}\n\t\treturn found ? JSON.stringify(merged, null, 2) : undefined;\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\tif (existsSync(this.authPath)) {\n\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t}\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\t\tthis.ensureFileExists();\n\t\t\t\t}\n\t\t\t\tif (!release) {\n\t\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t\t}\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\tthis.ensureFileExists();\n\t\t\t}\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tprivate constructor(private storage: AuthStorageBackend) {\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string): AuthStorage {\n\t\treturn new AuthStorage(\n\t\t\tnew FileAuthStorageBackend(\n\t\t\t\tauthPath ?? join(getAgentDir(), \"auth.json\"),\n\t\t\t\tauthPath ? [authPath] : getAgentConfigPaths(\"auth.json\"),\n\t\t\t),\n\t\t);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend): AuthStorage {\n\t\treturn new AuthStorage(storage);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\tif (this.fallbackResolver?.(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\tif (this.data[provider]) {\n\t\t\treturn { configured: true, source: \"stored\" };\n\t\t}\n\n\t\tif (this.runtimeOverrides.has(provider)) {\n\t\t\treturn { configured: false, source: \"runtime\", label: \"--api-key\" };\n\t\t}\n\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tif (envKeys?.[0]) {\n\t\t\treturn { configured: false, source: \"environment\", label: envKeys[0] };\n\t\t}\n\n\t\tif (this.fallbackResolver?.(provider)) {\n\t\t\treturn { configured: false, source: \"fallback\", label: \"custom provider config\" };\n\t\t}\n\n\t\treturn { configured: false };\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst cred = currentData[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. API key from auth.json\n\t * 3. OAuth token from auth.json (auto-refreshed with locking)\n\t * 4. Environment variable\n\t * 5. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\t// Runtime override takes highest priority\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) {\n\t\t\treturn runtimeKey;\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\treturn resolveConfigValue(cred.key);\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\tif (!provider) {\n\t\t\t\t// Unknown OAuth provider, can't get API key\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Check if token needs refresh\n\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\tif (needsRefresh) {\n\t\t\t\t// Use locked refresh to prevent race conditions\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result.apiKey;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t// Refresh failed - re-read file to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t// Another instance refreshed successfully, use those credentials\n\t\t\t\t\t\treturn provider.getApiKey(updatedCred);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Refresh truly failed - return undefined so model discovery skips this provider\n\t\t\t\t\t// User can /login to re-authenticate (credentials preserved for retry)\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Token not expired, use current access token\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to environment variable\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (envKey) return envKey;\n\n\t\t// Fall back to custom resolver (e.g., models.json custom providers)\n\t\tif (options?.includeFallback !== false) {\n\t\t\treturn this.fallbackResolver?.(providerId) ?? undefined;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n}\n"]}
1
+ {"version":3,"file":"auth-storage.js","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport {\n\tfindEnvKeys,\n\tgetEnvApiKey,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@earendil-works/pi-ai\";\nimport { getOAuthApiKey, getOAuthProvider, getOAuthProviders } from \"@earendil-works/pi-ai/oauth\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentConfigPaths, getAgentDir } from \"../config.ts\";\nimport { resolveConfigValue } from \"./resolve-config-value.ts\";\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\nexport type AuthStatus = {\n\tconfigured: boolean;\n\tsource?: \"stored\" | \"runtime\" | \"environment\" | \"fallback\" | \"models_json_key\" | \"models_json_command\";\n\tlabel?: string;\n};\n\ntype LockResult<T> = {\n\tresult: T;\n\tnext?: string;\n};\n\nexport interface AuthStorageBackend {\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T;\n\twithLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T>;\n}\n\nexport class FileAuthStorageBackend implements AuthStorageBackend {\n\tdeclare private authPath: string;\n\tdeclare private readPaths: string[];\n\n\tconstructor(\n\t\tauthPath: string = join(getAgentDir(), \"auth.json\"),\n\t\treadPaths: string[] = [authPath],\n\t) {\n\t\tthis.authPath = authPath;\n\t\tthis.readPaths = readPaths;\n\t}\n\n\tprivate ensureParentDir(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t}\n\n\tprivate ensureFileExists(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\t}\n\n\tprivate acquireLockSyncWithRetry(path: string): () => void {\n\t\tconst maxAttempts = 10;\n\t\tconst delayMs = 20;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 1; attempt <= maxAttempts; attempt++) {\n\t\t\ttry {\n\t\t\t\treturn lockfile.lockSync(path, { realpath: false });\n\t\t\t} catch (error) {\n\t\t\t\tconst code =\n\t\t\t\t\ttypeof error === \"object\" && error !== null && \"code\" in error\n\t\t\t\t\t\t? String((error as { code?: unknown }).code)\n\t\t\t\t\t\t: undefined;\n\t\t\t\tif (code !== \"ELOCKED\" || attempt === maxAttempts) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tlastError = error;\n\t\t\t\tconst start = Date.now();\n\t\t\t\twhile (Date.now() - start < delayMs) {\n\t\t\t\t\t// Sleep synchronously to avoid changing callers to async.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthrow (lastError as Error) ?? new Error(\"Failed to acquire auth storage lock\");\n\t}\n\n\tprivate readMergedAuth(): string | undefined {\n\t\tlet merged: AuthStorageData = {};\n\t\tlet found = false;\n\t\tfor (let i = this.readPaths.length - 1; i >= 0; i--) {\n\t\t\tconst readPath = this.readPaths[i]!;\n\t\t\tif (!existsSync(readPath)) continue;\n\t\t\tconst parsed = JSON.parse(readFileSync(readPath, \"utf-8\")) as AuthStorageData;\n\t\t\tmerged = { ...merged, ...parsed };\n\t\t\tfound = true;\n\t\t}\n\t\treturn found ? JSON.stringify(merged, null, 2) : undefined;\n\t}\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => void) | undefined;\n\t\ttry {\n\t\t\tif (existsSync(this.authPath)) {\n\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t}\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = fn(current);\n\t\t\tif (next !== undefined) {\n\t\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\t\tthis.ensureFileExists();\n\t\t\t\t}\n\t\t\t\tif (!release) {\n\t\t\t\t\trelease = this.acquireLockSyncWithRetry(this.authPath);\n\t\t\t\t}\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\trelease();\n\t\t\t}\n\t\t}\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tthis.ensureParentDir();\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\t\tlet lockCompromised = false;\n\t\tlet lockCompromisedError: Error | undefined;\n\t\tconst throwIfCompromised = () => {\n\t\t\tif (lockCompromised) {\n\t\t\t\tthrow lockCompromisedError ?? new Error(\"Auth storage lock was compromised\");\n\t\t\t}\n\t\t};\n\n\t\ttry {\n\t\t\tif (!existsSync(this.authPath)) {\n\t\t\t\tthis.ensureFileExists();\n\t\t\t}\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000,\n\t\t\t\tonCompromised: (err) => {\n\t\t\t\t\tlockCompromised = true;\n\t\t\t\t\tlockCompromisedError = err;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthrowIfCompromised();\n\t\t\tconst current = this.readMergedAuth();\n\t\t\tconst { result, next } = await fn(current);\n\t\t\tthrowIfCompromised();\n\t\t\tif (next !== undefined) {\n\t\t\t\twriteFileSync(this.authPath, next, \"utf-8\");\n\t\t\t\tchmodSync(this.authPath, 0o600);\n\t\t\t}\n\t\t\tthrowIfCompromised();\n\t\t\treturn result;\n\t\t} finally {\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors when lock is compromised.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nexport class InMemoryAuthStorageBackend implements AuthStorageBackend {\n\tprivate value: string | undefined;\n\n\twithLock<T>(fn: (current: string | undefined) => LockResult<T>): T {\n\t\tconst { result, next } = fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync withLockAsync<T>(fn: (current: string | undefined) => Promise<LockResult<T>>): Promise<T> {\n\t\tconst { result, next } = await fn(this.value);\n\t\tif (next !== undefined) {\n\t\t\tthis.value = next;\n\t\t}\n\t\treturn result;\n\t}\n}\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\tprivate loadError: Error | null = null;\n\tprivate errors: Error[] = [];\n\n\tdeclare private storage: AuthStorageBackend;\n\nprivate constructor(storage: AuthStorageBackend) {\n\t\tthis.storage = storage;\n\t\tthis.reload();\n\t}\n\n\tstatic create(authPath?: string): AuthStorage {\n\t\treturn new AuthStorage(\n\t\t\tnew FileAuthStorageBackend(\n\t\t\t\tauthPath ?? join(getAgentDir(), \"auth.json\"),\n\t\t\t\tauthPath ? [authPath] : getAgentConfigPaths(\"auth.json\"),\n\t\t\t),\n\t\t);\n\t}\n\n\tstatic fromStorage(storage: AuthStorageBackend): AuthStorage {\n\t\treturn new AuthStorage(storage);\n\t}\n\n\tstatic inMemory(data: AuthStorageData = {}): AuthStorage {\n\t\tconst storage = new InMemoryAuthStorageBackend();\n\t\tstorage.withLock(() => ({ result: undefined, next: JSON.stringify(data, null, 2) }));\n\t\treturn AuthStorage.fromStorage(storage);\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\tprivate recordError(error: unknown): void {\n\t\tconst normalizedError = error instanceof Error ? error : new Error(String(error));\n\t\tthis.errors.push(normalizedError);\n\t}\n\n\tprivate parseStorageData(content: string | undefined): AuthStorageData {\n\t\tif (!content) {\n\t\t\treturn {};\n\t\t}\n\t\treturn JSON.parse(content) as AuthStorageData;\n\t}\n\n\t/**\n\t * Reload credentials from storage.\n\t */\n\treload(): void {\n\t\tlet content: string | undefined;\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tcontent = current;\n\t\t\t\treturn { result: undefined };\n\t\t\t});\n\t\t\tthis.data = this.parseStorageData(content);\n\t\t\tthis.loadError = null;\n\t\t} catch (error) {\n\t\t\tthis.loadError = error as Error;\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\tprivate persistProviderChange(provider: string, credential: AuthCredential | undefined): void {\n\t\tif (this.loadError) {\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.storage.withLock((current) => {\n\t\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\t\tconst merged: AuthStorageData = { ...currentData };\n\t\t\t\tif (credential) {\n\t\t\t\t\tmerged[provider] = credential;\n\t\t\t\t} else {\n\t\t\t\t\tdelete merged[provider];\n\t\t\t\t}\n\t\t\t\treturn { result: undefined, next: JSON.stringify(merged, null, 2) };\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis.recordError(error);\n\t\t}\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.persistProviderChange(provider, credential);\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.persistProviderChange(provider, undefined);\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\tif (this.fallbackResolver?.(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Return auth status without exposing credential values or refreshing tokens.\n\t */\n\tgetAuthStatus(provider: string): AuthStatus {\n\t\tif (this.data[provider]) {\n\t\t\treturn { configured: true, source: \"stored\" };\n\t\t}\n\n\t\tif (this.runtimeOverrides.has(provider)) {\n\t\t\treturn { configured: false, source: \"runtime\", label: \"--api-key\" };\n\t\t}\n\n\t\tconst envKeys = findEnvKeys(provider);\n\t\tif (envKeys?.[0]) {\n\t\t\treturn { configured: false, source: \"environment\", label: envKeys[0] };\n\t\t}\n\n\t\tif (this.fallbackResolver?.(provider)) {\n\t\t\treturn { configured: false, source: \"fallback\", label: \"custom provider config\" };\n\t\t}\n\n\t\treturn { configured: false };\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\tdrainErrors(): Error[] {\n\t\tconst drained = [...this.errors];\n\t\tthis.errors = [];\n\t\treturn drained;\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with backend locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst result = await this.storage.withLockAsync(async (current) => {\n\t\t\tconst currentData = this.parseStorageData(current);\n\t\t\tthis.data = currentData;\n\t\t\tthis.loadError = null;\n\n\t\t\tconst cred = currentData[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\treturn { result: { apiKey: provider.getApiKey(cred), newCredentials: cred } };\n\t\t\t}\n\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(currentData)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst refreshed = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (!refreshed) {\n\t\t\t\treturn { result: null };\n\t\t\t}\n\n\t\t\tconst merged: AuthStorageData = {\n\t\t\t\t...currentData,\n\t\t\t\t[providerId]: { type: \"oauth\", ...refreshed.newCredentials },\n\t\t\t};\n\t\t\tthis.data = merged;\n\t\t\tthis.loadError = null;\n\t\t\treturn { result: refreshed, next: JSON.stringify(merged, null, 2) };\n\t\t});\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. API key from auth.json\n\t * 3. OAuth token from auth.json (auto-refreshed with locking)\n\t * 4. Environment variable\n\t * 5. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKey(providerId: string, options?: { includeFallback?: boolean }): Promise<string | undefined> {\n\t\t// Runtime override takes highest priority\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) {\n\t\t\treturn runtimeKey;\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\treturn resolveConfigValue(cred.key);\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\tif (!provider) {\n\t\t\t\t// Unknown OAuth provider, can't get API key\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Check if token needs refresh\n\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\tif (needsRefresh) {\n\t\t\t\t// Use locked refresh to prevent race conditions\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result.apiKey;\n\t\t\t\t\t}\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.recordError(error);\n\t\t\t\t\t// Refresh failed - re-read file to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t// Another instance refreshed successfully, use those credentials\n\t\t\t\t\t\treturn provider.getApiKey(updatedCred);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Refresh truly failed - return undefined so model discovery skips this provider\n\t\t\t\t\t// User can /login to re-authenticate (credentials preserved for retry)\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Token not expired, use current access token\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to environment variable\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (envKey) return envKey;\n\n\t\t// Fall back to custom resolver (e.g., models.json custom providers)\n\t\tif (options?.includeFallback !== false) {\n\t\t\treturn this.fallbackResolver?.(providerId) ?? undefined;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n}\n"]}
@@ -5,7 +5,7 @@
5
5
  * - AgentSession.executeBash() for interactive and RPC modes
6
6
  * - Direct calls from modes that need bash execution
7
7
  */
8
- import type { BashOperations } from "./tools/bash.js";
8
+ import type { BashOperations } from "./tools/bash.ts";
9
9
  export interface BashExecutorOptions {
10
10
  /** Callback for streaming output chunks (already sanitized) */
11
11
  onChunk?: (chunk: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAqGrB","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.js\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAqGrB","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.js\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashOperations } from \"./tools/bash.ts\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.ts\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst ensureTempFile = () => {\n\t\tif (tempFilePath) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\tfor (const chunk of outputChunks) {\n\t\t\ttempFileStream.write(chunk);\n\t\t}\n\t};\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES) {\n\t\t\tensureTempFile();\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tif (truncationResult.truncated) {\n\t\t\tensureTempFile();\n\t\t}\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\tif (truncationResult.truncated) {\n\t\t\t\tensureTempFile();\n\t\t\t}\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}