@bastani/atomic 0.8.27 → 0.8.28-alpha.2

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 (397) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +120 -118
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +2 -2
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +22 -0
  8. package/dist/builtin/workflows/README.md +11 -9
  9. package/dist/builtin/workflows/builtin/open-claude-design.ts +150 -13
  10. package/dist/builtin/workflows/package.json +1 -1
  11. package/dist/builtin/workflows/src/authoring.d.ts +5 -2
  12. package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +3 -1
  13. package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +17 -25
  14. package/dist/builtin/workflows/src/extension/index.ts +133 -18
  15. package/dist/builtin/workflows/src/extension/render-result.ts +22 -2
  16. package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -3
  17. package/dist/builtin/workflows/src/runs/foreground/executor.ts +210 -16
  18. package/dist/builtin/workflows/src/sdk-surface.ts +1 -1
  19. package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +42 -5
  20. package/dist/builtin/workflows/src/shared/store-types.ts +8 -2
  21. package/dist/builtin/workflows/src/shared/store.ts +51 -0
  22. package/dist/builtin/workflows/src/shared/types.ts +14 -4
  23. package/dist/builtin/workflows/src/tui/chat-surface.ts +32 -33
  24. package/dist/builtin/workflows/src/tui/graph-view.ts +4 -1
  25. package/dist/builtin/workflows/src/tui/prompt-card.ts +6 -0
  26. package/dist/builtin/workflows/src/tui/run-detail.ts +11 -4
  27. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +11 -1
  28. package/dist/builtin/workflows/src/tui/status-list.ts +32 -2
  29. package/dist/cli/args.d.ts +4 -0
  30. package/dist/cli/args.d.ts.map +1 -1
  31. package/dist/cli/args.js +35 -0
  32. package/dist/cli/args.js.map +1 -1
  33. package/dist/cli/project-trust.d.ts +10 -0
  34. package/dist/cli/project-trust.d.ts.map +1 -0
  35. package/dist/cli/project-trust.js +36 -0
  36. package/dist/cli/project-trust.js.map +1 -0
  37. package/dist/cli/startup-ui.d.ts +7 -0
  38. package/dist/cli/startup-ui.d.ts.map +1 -0
  39. package/dist/cli/startup-ui.js +57 -0
  40. package/dist/cli/startup-ui.js.map +1 -0
  41. package/dist/config.d.ts.map +1 -1
  42. package/dist/config.js +24 -3
  43. package/dist/config.js.map +1 -1
  44. package/dist/core/agent-session-runtime.d.ts +3 -1
  45. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  46. package/dist/core/agent-session-runtime.js +1 -0
  47. package/dist/core/agent-session-runtime.js.map +1 -1
  48. package/dist/core/agent-session-services.d.ts +2 -1
  49. package/dist/core/agent-session-services.d.ts.map +1 -1
  50. package/dist/core/agent-session-services.js +2 -2
  51. package/dist/core/agent-session-services.js.map +1 -1
  52. package/dist/core/agent-session.d.ts +9 -5
  53. package/dist/core/agent-session.d.ts.map +1 -1
  54. package/dist/core/agent-session.js +205 -51
  55. package/dist/core/agent-session.js.map +1 -1
  56. package/dist/core/auth-guidance.d.ts +10 -1
  57. package/dist/core/auth-guidance.d.ts.map +1 -1
  58. package/dist/core/auth-guidance.js +26 -1
  59. package/dist/core/auth-guidance.js.map +1 -1
  60. package/dist/core/auth-storage.d.ts.map +1 -1
  61. package/dist/core/auth-storage.js +4 -3
  62. package/dist/core/auth-storage.js.map +1 -1
  63. package/dist/core/compaction/branch-summarization.d.ts +5 -3
  64. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  65. package/dist/core/compaction/branch-summarization.js +16 -10
  66. package/dist/core/compaction/branch-summarization.js.map +1 -1
  67. package/dist/core/compaction/compaction.d.ts +4 -84
  68. package/dist/core/compaction/compaction.d.ts.map +1 -1
  69. package/dist/core/compaction/compaction.js +20 -502
  70. package/dist/core/compaction/compaction.js.map +1 -1
  71. package/dist/core/compaction/context-compaction.d.ts.map +1 -1
  72. package/dist/core/compaction/context-compaction.js +39 -82
  73. package/dist/core/compaction/context-compaction.js.map +1 -1
  74. package/dist/core/compaction/index.d.ts +1 -1
  75. package/dist/core/compaction/index.d.ts.map +1 -1
  76. package/dist/core/compaction/index.js +1 -1
  77. package/dist/core/compaction/index.js.map +1 -1
  78. package/dist/core/compaction/utils.d.ts +1 -1
  79. package/dist/core/compaction/utils.d.ts.map +1 -1
  80. package/dist/core/compaction/utils.js +1 -1
  81. package/dist/core/compaction/utils.js.map +1 -1
  82. package/dist/core/experimental.d.ts +2 -0
  83. package/dist/core/experimental.d.ts.map +1 -0
  84. package/dist/core/experimental.js +5 -0
  85. package/dist/core/experimental.js.map +1 -0
  86. package/dist/core/export-html/template.js +19 -6
  87. package/dist/core/extensions/index.d.ts +1 -1
  88. package/dist/core/extensions/index.d.ts.map +1 -1
  89. package/dist/core/extensions/index.js.map +1 -1
  90. package/dist/core/extensions/loader.d.ts +1 -1
  91. package/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/dist/core/extensions/loader.js +6 -4
  93. package/dist/core/extensions/loader.js.map +1 -1
  94. package/dist/core/extensions/runner.d.ts +11 -4
  95. package/dist/core/extensions/runner.d.ts.map +1 -1
  96. package/dist/core/extensions/runner.js +53 -3
  97. package/dist/core/extensions/runner.js.map +1 -1
  98. package/dist/core/extensions/types.d.ts +44 -12
  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/footer-data-provider.d.ts +2 -0
  102. package/dist/core/footer-data-provider.d.ts.map +1 -1
  103. package/dist/core/footer-data-provider.js +27 -1
  104. package/dist/core/footer-data-provider.js.map +1 -1
  105. package/dist/core/index.d.ts +2 -1
  106. package/dist/core/index.d.ts.map +1 -1
  107. package/dist/core/index.js +1 -0
  108. package/dist/core/index.js.map +1 -1
  109. package/dist/core/messages.d.ts +1 -11
  110. package/dist/core/messages.d.ts.map +1 -1
  111. package/dist/core/messages.js +10 -25
  112. package/dist/core/messages.js.map +1 -1
  113. package/dist/core/model-registry.d.ts.map +1 -1
  114. package/dist/core/model-registry.js +64 -7
  115. package/dist/core/model-registry.js.map +1 -1
  116. package/dist/core/model-resolver.d.ts.map +1 -1
  117. package/dist/core/model-resolver.js +1 -0
  118. package/dist/core/model-resolver.js.map +1 -1
  119. package/dist/core/output-guard.d.ts +1 -0
  120. package/dist/core/output-guard.d.ts.map +1 -1
  121. package/dist/core/output-guard.js +52 -22
  122. package/dist/core/output-guard.js.map +1 -1
  123. package/dist/core/package-manager.d.ts +1 -0
  124. package/dist/core/package-manager.d.ts.map +1 -1
  125. package/dist/core/package-manager.js +20 -8
  126. package/dist/core/package-manager.js.map +1 -1
  127. package/dist/core/project-trust.d.ts +15 -0
  128. package/dist/core/project-trust.d.ts.map +1 -0
  129. package/dist/core/project-trust.js +58 -0
  130. package/dist/core/project-trust.js.map +1 -0
  131. package/dist/core/prompt-templates.d.ts +5 -4
  132. package/dist/core/prompt-templates.d.ts.map +1 -1
  133. package/dist/core/prompt-templates.js +30 -29
  134. package/dist/core/prompt-templates.js.map +1 -1
  135. package/dist/core/provider-attribution.d.ts +4 -0
  136. package/dist/core/provider-attribution.d.ts.map +1 -0
  137. package/dist/core/provider-attribution.js +73 -0
  138. package/dist/core/provider-attribution.js.map +1 -0
  139. package/dist/core/provider-display-names.d.ts.map +1 -1
  140. package/dist/core/provider-display-names.js +3 -0
  141. package/dist/core/provider-display-names.js.map +1 -1
  142. package/dist/core/resolve-config-value.d.ts +9 -1
  143. package/dist/core/resolve-config-value.d.ts.map +1 -1
  144. package/dist/core/resolve-config-value.js +134 -11
  145. package/dist/core/resolve-config-value.js.map +1 -1
  146. package/dist/core/resource-loader.d.ts +12 -2
  147. package/dist/core/resource-loader.d.ts.map +1 -1
  148. package/dist/core/resource-loader.js +108 -18
  149. package/dist/core/resource-loader.js.map +1 -1
  150. package/dist/core/sdk.d.ts.map +1 -1
  151. package/dist/core/sdk.js +12 -42
  152. package/dist/core/sdk.js.map +1 -1
  153. package/dist/core/session-manager.d.ts +11 -15
  154. package/dist/core/session-manager.d.ts.map +1 -1
  155. package/dist/core/session-manager.js +111 -111
  156. package/dist/core/session-manager.js.map +1 -1
  157. package/dist/core/settings-manager.d.ts +15 -5
  158. package/dist/core/settings-manager.d.ts.map +1 -1
  159. package/dist/core/settings-manager.js +69 -14
  160. package/dist/core/settings-manager.js.map +1 -1
  161. package/dist/core/slash-commands.d.ts.map +1 -1
  162. package/dist/core/slash-commands.js +1 -0
  163. package/dist/core/slash-commands.js.map +1 -1
  164. package/dist/core/system-prompt.d.ts.map +1 -1
  165. package/dist/core/system-prompt.js +0 -3
  166. package/dist/core/system-prompt.js.map +1 -1
  167. package/dist/core/tools/bash.d.ts.map +1 -1
  168. package/dist/core/tools/bash.js +2 -1
  169. package/dist/core/tools/bash.js.map +1 -1
  170. package/dist/core/tools/edit.d.ts.map +1 -1
  171. package/dist/core/tools/edit.js +7 -10
  172. package/dist/core/tools/edit.js.map +1 -1
  173. package/dist/core/tools/find.d.ts.map +1 -1
  174. package/dist/core/tools/find.js +1 -1
  175. package/dist/core/tools/find.js.map +1 -1
  176. package/dist/core/tools/grep.d.ts.map +1 -1
  177. package/dist/core/tools/grep.js +1 -1
  178. package/dist/core/tools/grep.js.map +1 -1
  179. package/dist/core/tools/ls.d.ts.map +1 -1
  180. package/dist/core/tools/ls.js +1 -1
  181. package/dist/core/tools/ls.js.map +1 -1
  182. package/dist/core/tools/oversized-tool-result.d.ts +53 -0
  183. package/dist/core/tools/oversized-tool-result.d.ts.map +1 -0
  184. package/dist/core/tools/oversized-tool-result.js +206 -0
  185. package/dist/core/tools/oversized-tool-result.js.map +1 -0
  186. package/dist/core/tools/read.d.ts +12 -0
  187. package/dist/core/tools/read.d.ts.map +1 -1
  188. package/dist/core/tools/read.js +99 -34
  189. package/dist/core/tools/read.js.map +1 -1
  190. package/dist/core/tools/render-utils.d.ts +6 -0
  191. package/dist/core/tools/render-utils.d.ts.map +1 -1
  192. package/dist/core/tools/render-utils.js +17 -1
  193. package/dist/core/tools/render-utils.js.map +1 -1
  194. package/dist/core/tools/tool-definition-wrapper.d.ts +6 -0
  195. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  196. package/dist/core/tools/tool-definition-wrapper.js +2 -0
  197. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  198. package/dist/core/tools/tool-limits.d.ts +25 -0
  199. package/dist/core/tools/tool-limits.d.ts.map +1 -0
  200. package/dist/core/tools/tool-limits.js +25 -0
  201. package/dist/core/tools/tool-limits.js.map +1 -0
  202. package/dist/core/tools/write.d.ts.map +1 -1
  203. package/dist/core/tools/write.js +1 -1
  204. package/dist/core/tools/write.js.map +1 -1
  205. package/dist/core/trust-manager.d.ts +31 -0
  206. package/dist/core/trust-manager.d.ts.map +1 -0
  207. package/dist/core/trust-manager.js +196 -0
  208. package/dist/core/trust-manager.js.map +1 -0
  209. package/dist/index.d.ts +12 -7
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +8 -4
  212. package/dist/index.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +142 -30
  215. package/dist/main.js.map +1 -1
  216. package/dist/migrations.d.ts +3 -1
  217. package/dist/migrations.d.ts.map +1 -1
  218. package/dist/migrations.js +325 -7
  219. package/dist/migrations.js.map +1 -1
  220. package/dist/modes/index.d.ts +1 -1
  221. package/dist/modes/index.d.ts.map +1 -1
  222. package/dist/modes/index.js.map +1 -1
  223. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  224. package/dist/modes/interactive/components/bash-execution.js +2 -2
  225. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  226. package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -5
  227. package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
  228. package/dist/modes/interactive/components/chat-message-renderer.js +5 -9
  229. package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
  230. package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
  231. package/dist/modes/interactive/components/chat-session-host.js +0 -3
  232. package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
  233. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  234. package/dist/modes/interactive/components/footer.js +6 -0
  235. package/dist/modes/interactive/components/footer.js.map +1 -1
  236. package/dist/modes/interactive/components/index.d.ts +1 -1
  237. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  238. package/dist/modes/interactive/components/index.js +1 -1
  239. package/dist/modes/interactive/components/index.js.map +1 -1
  240. package/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  241. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  242. package/dist/modes/interactive/components/login-dialog.js +9 -16
  243. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  244. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  245. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  246. package/dist/modes/interactive/components/settings-selector.js +20 -0
  247. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  248. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  249. package/dist/modes/interactive/components/tool-execution.js +22 -0
  250. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  251. package/dist/modes/interactive/components/trust-selector.d.ts +23 -0
  252. package/dist/modes/interactive/components/trust-selector.d.ts.map +1 -0
  253. package/dist/modes/interactive/components/trust-selector.js +85 -0
  254. package/dist/modes/interactive/components/trust-selector.js.map +1 -0
  255. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  256. package/dist/modes/interactive/components/user-message.js +1 -1
  257. package/dist/modes/interactive/components/user-message.js.map +1 -1
  258. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  259. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  260. package/dist/modes/interactive/interactive-mode.js +134 -36
  261. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  262. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  263. package/dist/modes/interactive/theme/theme.js +10 -0
  264. package/dist/modes/interactive/theme/theme.js.map +1 -1
  265. package/dist/modes/print-mode.d.ts.map +1 -1
  266. package/dist/modes/print-mode.js +1 -0
  267. package/dist/modes/print-mode.js.map +1 -1
  268. package/dist/modes/rpc/rpc-client.d.ts +4 -1
  269. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  270. package/dist/modes/rpc/rpc-client.js +52 -8
  271. package/dist/modes/rpc/rpc-client.js.map +1 -1
  272. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  273. package/dist/modes/rpc/rpc-mode.js +24 -5
  274. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  275. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  276. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  277. package/dist/modes/rpc/rpc-types.js.map +1 -1
  278. package/dist/package-manager-cli.d.ts +6 -2
  279. package/dist/package-manager-cli.d.ts.map +1 -1
  280. package/dist/package-manager-cli.js +104 -10
  281. package/dist/package-manager-cli.js.map +1 -1
  282. package/dist/utils/changelog.d.ts +1 -0
  283. package/dist/utils/changelog.d.ts.map +1 -1
  284. package/dist/utils/changelog.js +72 -0
  285. package/dist/utils/changelog.js.map +1 -1
  286. package/dist/utils/deprecation.d.ts +4 -0
  287. package/dist/utils/deprecation.d.ts.map +1 -0
  288. package/dist/utils/deprecation.js +13 -0
  289. package/dist/utils/deprecation.js.map +1 -0
  290. package/dist/utils/git.d.ts.map +1 -1
  291. package/dist/utils/git.js +54 -22
  292. package/dist/utils/git.js.map +1 -1
  293. package/dist/utils/json.d.ts +3 -0
  294. package/dist/utils/json.d.ts.map +1 -0
  295. package/dist/utils/json.js +7 -0
  296. package/dist/utils/json.js.map +1 -0
  297. package/dist/utils/open-browser.d.ts +9 -0
  298. package/dist/utils/open-browser.d.ts.map +1 -0
  299. package/dist/utils/open-browser.js +22 -0
  300. package/dist/utils/open-browser.js.map +1 -0
  301. package/docs/compaction.md +210 -181
  302. package/docs/containerization.md +111 -0
  303. package/docs/custom-provider.md +9 -9
  304. package/docs/development.md +1 -1
  305. package/docs/docs.json +2 -0
  306. package/docs/extensions.md +71 -24
  307. package/docs/index.md +2 -0
  308. package/docs/json.md +3 -4
  309. package/docs/models.md +10 -10
  310. package/docs/packages.md +1 -1
  311. package/docs/prompt-templates.md +9 -2
  312. package/docs/providers.md +18 -5
  313. package/docs/quickstart.md +1 -0
  314. package/docs/rpc.md +3 -2
  315. package/docs/sdk.md +5 -0
  316. package/docs/security.md +56 -0
  317. package/docs/session-format.md +14 -23
  318. package/docs/sessions.md +11 -1
  319. package/docs/settings.md +23 -9
  320. package/docs/skills.md +1 -1
  321. package/docs/terminal-setup.md +44 -2
  322. package/docs/themes.md +1 -1
  323. package/docs/tmux.md +4 -2
  324. package/docs/tui.md +14 -5
  325. package/docs/usage.md +17 -3
  326. package/docs/workflows.md +11 -9
  327. package/examples/README.md +1 -1
  328. package/examples/extensions/README.md +9 -6
  329. package/examples/extensions/bash-spawn-hook.ts +1 -1
  330. package/examples/extensions/built-in-tool-renderer.ts +1 -1
  331. package/examples/extensions/claude-rules.ts +1 -1
  332. package/examples/extensions/commands.ts +1 -1
  333. package/examples/extensions/custom-compaction.ts +43 -106
  334. package/examples/extensions/custom-header.ts +1 -1
  335. package/examples/extensions/custom-provider-anthropic/index.ts +3 -3
  336. package/examples/extensions/custom-provider-anthropic/package-lock.json +4 -4
  337. package/examples/extensions/custom-provider-anthropic/package.json +6 -6
  338. package/examples/extensions/custom-provider-gitlab-duo/index.ts +55 -4
  339. package/examples/extensions/custom-provider-gitlab-duo/package.json +3 -3
  340. package/examples/extensions/doom-overlay/README.md +1 -1
  341. package/examples/extensions/doom-overlay/index.ts +2 -2
  342. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  343. package/examples/extensions/gondolin/index.ts +523 -0
  344. package/examples/extensions/gondolin/package-lock.json +185 -0
  345. package/examples/extensions/gondolin/package.json +19 -0
  346. package/examples/extensions/handoff.ts +7 -45
  347. package/examples/extensions/hidden-thinking-label.ts +1 -1
  348. package/examples/extensions/inline-bash.ts +2 -2
  349. package/examples/extensions/input-transform-streaming.ts +39 -0
  350. package/examples/extensions/input-transform.ts +3 -3
  351. package/examples/extensions/interactive-shell.ts +2 -2
  352. package/examples/extensions/mac-system-theme.ts +2 -2
  353. package/examples/extensions/minimal-mode.ts +1 -1
  354. package/examples/extensions/modal-editor.ts +1 -1
  355. package/examples/extensions/model-status.ts +1 -1
  356. package/examples/extensions/overlay-qa-tests.ts +198 -179
  357. package/examples/extensions/overlay-test.ts +1 -1
  358. package/examples/extensions/pirate.ts +1 -1
  359. package/examples/extensions/preset.ts +14 -12
  360. package/examples/extensions/project-trust.ts +64 -0
  361. package/examples/extensions/prompt-customizer.ts +1 -1
  362. package/examples/extensions/qna.ts +1 -1
  363. package/examples/extensions/question.ts +1 -1
  364. package/examples/extensions/questionnaire.ts +1 -1
  365. package/examples/extensions/rainbow-editor.ts +1 -1
  366. package/examples/extensions/sandbox/index.ts +16 -14
  367. package/examples/extensions/sandbox/package-lock.json +90 -90
  368. package/examples/extensions/sandbox/package.json +17 -17
  369. package/examples/extensions/snake.ts +1 -1
  370. package/examples/extensions/space-invaders.ts +1 -1
  371. package/examples/extensions/ssh.ts +2 -2
  372. package/examples/extensions/subagent/README.md +13 -13
  373. package/examples/extensions/subagent/agents.ts +4 -2
  374. package/examples/extensions/subagent/index.ts +6 -6
  375. package/examples/extensions/summarize.ts +1 -1
  376. package/examples/extensions/tic-tac-toe.ts +1 -1
  377. package/examples/extensions/titlebar-spinner.ts +1 -1
  378. package/examples/extensions/todo.ts +1 -1
  379. package/examples/extensions/tool-override.ts +1 -1
  380. package/examples/extensions/tools.ts +6 -1
  381. package/examples/extensions/trigger-compact.ts +5 -4
  382. package/examples/extensions/with-deps/package-lock.json +4 -4
  383. package/examples/extensions/with-deps/package.json +7 -7
  384. package/examples/extensions/working-indicator.ts +4 -4
  385. package/examples/extensions/working-message-test.ts +1 -1
  386. package/examples/sdk/01-minimal.ts +1 -1
  387. package/examples/sdk/03-custom-prompt.ts +1 -1
  388. package/examples/sdk/04-skills.ts +1 -1
  389. package/examples/sdk/06-extensions.ts +2 -2
  390. package/examples/sdk/08-prompt-templates.ts +1 -1
  391. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  392. package/examples/sdk/README.md +2 -2
  393. package/package.json +8 -8
  394. package/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
  395. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +0 -1
  396. package/dist/modes/interactive/components/compaction-summary-message.js +0 -43
  397. package/dist/modes/interactive/components/compaction-summary-message.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ import type { LoadExtensionsResult, ProjectTrustContext } from "./extensions/types.ts";
2
+ import type { DefaultProjectTrust } from "./settings-manager.ts";
3
+ import { type ProjectTrustStore } from "./trust-manager.ts";
4
+ export type AppMode = "interactive" | "print" | "json" | "rpc";
5
+ export interface ResolveProjectTrustedOptions {
6
+ cwd: string;
7
+ trustStore: ProjectTrustStore;
8
+ trustOverride?: boolean;
9
+ defaultProjectTrust?: DefaultProjectTrust;
10
+ extensionsResult?: LoadExtensionsResult;
11
+ projectTrustContext: ProjectTrustContext;
12
+ onExtensionError?: (message: string) => void;
13
+ }
14
+ export declare function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean>;
15
+ //# sourceMappingURL=project-trust.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-trust.d.ts","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAIN,KAAK,iBAAiB,EACtB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAE/D,MAAM,WAAW,4BAA4B;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,iBAAiB,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,gBAAgB,CAAC,EAAE,oBAAoB,CAAC;IACxC,mBAAmB,EAAE,mBAAmB,CAAC;IACzC,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAwBD,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,4BAA4B,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDnG","sourcesContent":["import { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasProjectTrustInputs,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Atomic to load .atomic settings and resources, install missing project packages, and execute project extensions.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasProjectTrustInputs(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import { emitProjectTrustEvent } from "./extensions/runner.js";
2
+ import { getProjectTrustOptions, hasProjectTrustInputs, } from "./trust-manager.js";
3
+ function formatProjectTrustPrompt(cwd) {
4
+ return `Trust project folder?\n${cwd}\n\nThis allows Atomic to load .atomic settings and resources, install missing project packages, and execute project extensions.`;
5
+ }
6
+ async function selectProjectTrustOption(cwd, ctx) {
7
+ const options = getProjectTrustOptions(cwd, { includeSessionOnly: true });
8
+ const selected = await ctx.ui.select(formatProjectTrustPrompt(cwd), options.map((option) => option.label));
9
+ return options.find((option) => option.label === selected);
10
+ }
11
+ function saveProjectTrustPromptResult(trustStore, result) {
12
+ if (result.updates.length > 0) {
13
+ trustStore.setMany(result.updates);
14
+ }
15
+ }
16
+ export async function resolveProjectTrusted(options) {
17
+ if (options.trustOverride !== undefined) {
18
+ return options.trustOverride;
19
+ }
20
+ if (!hasProjectTrustInputs(options.cwd)) {
21
+ return true;
22
+ }
23
+ if (options.extensionsResult) {
24
+ const { result, errors } = await emitProjectTrustEvent(options.extensionsResult, { type: "project_trust", cwd: options.cwd }, options.projectTrustContext);
25
+ for (const error of errors) {
26
+ options.onExtensionError?.(`Extension "${error.extensionPath}" project_trust error: ${error.error}`);
27
+ }
28
+ if (result) {
29
+ const trusted = result.trusted === "yes";
30
+ if (result.remember === true) {
31
+ options.trustStore.set(options.cwd, trusted);
32
+ }
33
+ return trusted;
34
+ }
35
+ }
36
+ const decision = options.trustStore.get(options.cwd);
37
+ if (decision !== null) {
38
+ return decision;
39
+ }
40
+ switch (options.defaultProjectTrust ?? "ask") {
41
+ case "always":
42
+ return true;
43
+ case "never":
44
+ return false;
45
+ case "ask":
46
+ break;
47
+ }
48
+ if (!options.projectTrustContext.hasUI) {
49
+ return false;
50
+ }
51
+ const selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);
52
+ if (selected !== undefined) {
53
+ saveProjectTrustPromptResult(options.trustStore, selected);
54
+ return selected.trusted;
55
+ }
56
+ return false;
57
+ }
58
+ //# sourceMappingURL=project-trust.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-trust.js","sourceRoot":"","sources":["../../src/core/project-trust.ts"],"names":[],"mappings":"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","sourcesContent":["import { emitProjectTrustEvent } from \"./extensions/runner.ts\";\nimport type { LoadExtensionsResult, ProjectTrustContext } from \"./extensions/types.ts\";\nimport type { DefaultProjectTrust } from \"./settings-manager.ts\";\nimport {\n\tgetProjectTrustOptions,\n\thasProjectTrustInputs,\n\ttype ProjectTrustOption,\n\ttype ProjectTrustStore,\n} from \"./trust-manager.ts\";\n\nexport type AppMode = \"interactive\" | \"print\" | \"json\" | \"rpc\";\n\nexport interface ResolveProjectTrustedOptions {\n\tcwd: string;\n\ttrustStore: ProjectTrustStore;\n\ttrustOverride?: boolean;\n\tdefaultProjectTrust?: DefaultProjectTrust;\n\textensionsResult?: LoadExtensionsResult;\n\tprojectTrustContext: ProjectTrustContext;\n\tonExtensionError?: (message: string) => void;\n}\n\nfunction formatProjectTrustPrompt(cwd: string): string {\n\treturn `Trust project folder?\\n${cwd}\\n\\nThis allows Atomic to load .atomic settings and resources, install missing project packages, and execute project extensions.`;\n}\n\nasync function selectProjectTrustOption(\n\tcwd: string,\n\tctx: ProjectTrustContext,\n): Promise<ProjectTrustOption | undefined> {\n\tconst options = getProjectTrustOptions(cwd, { includeSessionOnly: true });\n\tconst selected = await ctx.ui.select(\n\t\tformatProjectTrustPrompt(cwd),\n\t\toptions.map((option) => option.label),\n\t);\n\treturn options.find((option) => option.label === selected);\n}\n\nfunction saveProjectTrustPromptResult(trustStore: ProjectTrustStore, result: ProjectTrustOption): void {\n\tif (result.updates.length > 0) {\n\t\ttrustStore.setMany(result.updates);\n\t}\n}\n\nexport async function resolveProjectTrusted(options: ResolveProjectTrustedOptions): Promise<boolean> {\n\tif (options.trustOverride !== undefined) {\n\t\treturn options.trustOverride;\n\t}\n\tif (!hasProjectTrustInputs(options.cwd)) {\n\t\treturn true;\n\t}\n\n\tif (options.extensionsResult) {\n\t\tconst { result, errors } = await emitProjectTrustEvent(\n\t\t\toptions.extensionsResult,\n\t\t\t{ type: \"project_trust\", cwd: options.cwd },\n\t\t\toptions.projectTrustContext,\n\t\t);\n\t\tfor (const error of errors) {\n\t\t\toptions.onExtensionError?.(`Extension \"${error.extensionPath}\" project_trust error: ${error.error}`);\n\t\t}\n\t\tif (result) {\n\t\t\tconst trusted = result.trusted === \"yes\";\n\t\t\tif (result.remember === true) {\n\t\t\t\toptions.trustStore.set(options.cwd, trusted);\n\t\t\t}\n\t\t\treturn trusted;\n\t\t}\n\t}\n\n\tconst decision = options.trustStore.get(options.cwd);\n\tif (decision !== null) {\n\t\treturn decision;\n\t}\n\n\tswitch (options.defaultProjectTrust ?? \"ask\") {\n\t\tcase \"always\":\n\t\t\treturn true;\n\t\tcase \"never\":\n\t\t\treturn false;\n\t\tcase \"ask\":\n\t\t\tbreak;\n\t}\n\n\tif (!options.projectTrustContext.hasUI) {\n\t\treturn false;\n\t}\n\n\tconst selected = await selectProjectTrustOption(options.cwd, options.projectTrustContext);\n\tif (selected !== undefined) {\n\t\tsaveProjectTrustPromptResult(options.trustStore, selected);\n\t\treturn selected.trusted;\n\t}\n\treturn false;\n}\n"]}
@@ -22,9 +22,10 @@ export declare function parseCommandArgs(argsString: string): string[];
22
22
  * - $@ and $ARGUMENTS for all args
23
23
  * - ${@:N} for args from Nth onwards (bash-style slicing)
24
24
  * - ${@:N:L} for L args starting from Nth
25
+ * - ${N:-default} for positional args with a default value when missing/empty
25
26
  *
26
- * Note: Replacement happens on the template string only. Argument values
27
- * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
27
+ * Note: Replacement happens on the template string only. Argument and default
28
+ * values containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
28
29
  */
29
30
  export declare function substituteArgs(content: string, args: string[]): string;
30
31
  export interface LoadPromptTemplatesOptions {
@@ -33,9 +34,9 @@ export interface LoadPromptTemplatesOptions {
33
34
  /** Agent config directory for global templates. */
34
35
  agentDir: string;
35
36
  /** Explicit prompt template paths (files or directories). */
36
- promptPaths: string[];
37
+ promptPaths?: string[];
37
38
  /** Include default prompt directories. */
38
- includeDefaults: boolean;
39
+ includeDefaults?: boolean;
39
40
  }
40
41
  /**
41
42
  * Load all prompt templates from:
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAkCtE;AA2ED,MAAM,WAAW,0BAA0B;IAC1C,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0CAA0C;IAC1C,eAAe,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,EAAE,CAqEzF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,MAAM,CActF","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { basename, dirname, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\tconst promptPaths = options.promptPaths;\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = join(resolvedAgentDir, \"prompts\");\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: globalPromptsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: projectPromptsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(template.content, args);\n\t}\n\n\treturn text;\n}\n"]}
1
+ {"version":3,"file":"prompt-templates.d.ts","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9E;;GAEG;AACH,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,UAAU,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAmCtE;AA2ED,MAAM,WAAW,0BAA0B;IAC1C,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,cAAc,EAAE,CAqEzF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,GAAG,MAAM,CActF","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { basename, dirname, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n * - ${N:-default} for positional args with a default value when missing/empty\n *\n * Note: Replacement happens on the template string only. Argument and default\n * values containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tconst allArgs = args.join(\" \");\n\n\t// The default-value group is length-bounded to avoid polynomial-time regex\n\t// backtracking (ReDoS) when scanning template content, which is read from\n\t// disk. This is an Atomic hardening divergence from upstream; 1024 characters\n\t// far exceeds any realistic slash-command default value.\n\treturn content.replace(\n\t\t/\\$\\{(\\d+):-([^}]{0,1024})\\}|\\$\\{@:(\\d+)(?::(\\d+))?\\}|\\$(ARGUMENTS|@|\\d+)/g,\n\t\t(_match, defaultNum, defaultValue, sliceStart, sliceLength, simple) => {\n\t\t\tif (defaultNum) {\n\t\t\t\tconst index = parseInt(defaultNum, 10) - 1;\n\t\t\t\tconst value = args[index];\n\t\t\t\treturn value ? value : defaultValue;\n\t\t\t}\n\n\t\t\tif (sliceStart) {\n\t\t\t\tlet start = parseInt(sliceStart, 10) - 1;\n\t\t\t\tif (start < 0) start = 0;\n\n\t\t\t\tif (sliceLength) {\n\t\t\t\t\tconst length = parseInt(sliceLength, 10);\n\t\t\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t\t\t}\n\t\t\t\treturn args.slice(start).join(\" \");\n\t\t\t}\n\n\t\t\tif (simple === \"ARGUMENTS\" || simple === \"@\") {\n\t\t\t\treturn allArgs;\n\t\t\t}\n\n\t\t\tconst index = parseInt(simple, 10) - 1;\n\t\t\treturn args[index] ?? \"\";\n\t\t},\n\t);\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults?: boolean;\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\tconst promptPaths = options.promptPaths ?? [];\n\tconst includeDefaults = options.includeDefaults ?? true;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = join(resolvedAgentDir, \"prompts\");\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: globalPromptsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: projectPromptsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(template.content, args);\n\t}\n\n\treturn text;\n}\n"]}
@@ -47,38 +47,39 @@ export function parseCommandArgs(argsString) {
47
47
  * - $@ and $ARGUMENTS for all args
48
48
  * - ${@:N} for args from Nth onwards (bash-style slicing)
49
49
  * - ${@:N:L} for L args starting from Nth
50
+ * - ${N:-default} for positional args with a default value when missing/empty
50
51
  *
51
- * Note: Replacement happens on the template string only. Argument values
52
- * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
52
+ * Note: Replacement happens on the template string only. Argument and default
53
+ * values containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.
53
54
  */
54
55
  export function substituteArgs(content, args) {
55
- let result = content;
56
- // Replace $1, $2, etc. with positional args FIRST (before wildcards)
57
- // This prevents wildcard replacement values containing $<digit> patterns from being re-substituted
58
- result = result.replace(/\$(\d+)/g, (_, num) => {
59
- const index = parseInt(num, 10) - 1;
60
- return args[index] ?? "";
61
- });
62
- // Replace ${@:start} or ${@:start:length} with sliced args (bash-style)
63
- // Process BEFORE simple $@ to avoid conflicts
64
- result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
65
- let start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)
66
- // Treat 0 as 1 (bash convention: args start at 1)
67
- if (start < 0)
68
- start = 0;
69
- if (lengthStr) {
70
- const length = parseInt(lengthStr, 10);
71
- return args.slice(start, start + length).join(" ");
56
+ const allArgs = args.join(" ");
57
+ // The default-value group is length-bounded to avoid polynomial-time regex
58
+ // backtracking (ReDoS) when scanning template content, which is read from
59
+ // disk. This is an Atomic hardening divergence from upstream; 1024 characters
60
+ // far exceeds any realistic slash-command default value.
61
+ return content.replace(/\$\{(\d+):-([^}]{0,1024})\}|\$\{@:(\d+)(?::(\d+))?\}|\$(ARGUMENTS|@|\d+)/g, (_match, defaultNum, defaultValue, sliceStart, sliceLength, simple) => {
62
+ if (defaultNum) {
63
+ const index = parseInt(defaultNum, 10) - 1;
64
+ const value = args[index];
65
+ return value ? value : defaultValue;
66
+ }
67
+ if (sliceStart) {
68
+ let start = parseInt(sliceStart, 10) - 1;
69
+ if (start < 0)
70
+ start = 0;
71
+ if (sliceLength) {
72
+ const length = parseInt(sliceLength, 10);
73
+ return args.slice(start, start + length).join(" ");
74
+ }
75
+ return args.slice(start).join(" ");
76
+ }
77
+ if (simple === "ARGUMENTS" || simple === "@") {
78
+ return allArgs;
72
79
  }
73
- return args.slice(start).join(" ");
80
+ const index = parseInt(simple, 10) - 1;
81
+ return args[index] ?? "";
74
82
  });
75
- // Pre-compute all args joined (optimization)
76
- const allArgs = args.join(" ");
77
- // Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)
78
- result = result.replace(/\$ARGUMENTS/g, allArgs);
79
- // Replace $@ with all args joined (existing syntax)
80
- result = result.replace(/\$@/g, allArgs);
81
- return result;
82
83
  }
83
84
  function loadTemplateFromFile(filePath, sourceInfo) {
84
85
  try {
@@ -155,8 +156,8 @@ function loadTemplatesFromDir(dir, getSourceInfo) {
155
156
  export function loadPromptTemplates(options) {
156
157
  const resolvedCwd = resolvePath(options.cwd);
157
158
  const resolvedAgentDir = resolvePath(options.agentDir);
158
- const promptPaths = options.promptPaths;
159
- const includeDefaults = options.includeDefaults;
159
+ const promptPaths = options.promptPaths ?? [];
160
+ const includeDefaults = options.includeDefaults ?? true;
160
161
  const templates = [];
161
162
  const globalPromptsDir = join(resolvedAgentDir, "prompts");
162
163
  const projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "prompts");
@@ -1 +1 @@
1
- {"version":3,"file":"prompt-templates.js","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { basename, dirname, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n *\n * Note: Replacement happens on the template string only. Argument values\n * containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $1, $2, etc. with positional args FIRST (before wildcards)\n\t// This prevents wildcard replacement values containing $<digit> patterns from being re-substituted\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\t// Replace ${@:start} or ${@:start:length} with sliced args (bash-style)\n\t// Process BEFORE simple $@ to avoid conflicts\n\tresult = result.replace(/\\$\\{@:(\\d+)(?::(\\d+))?\\}/g, (_, startStr, lengthStr) => {\n\t\tlet start = parseInt(startStr, 10) - 1; // Convert to 0-indexed (user provides 1-indexed)\n\t\t// Treat 0 as 1 (bash convention: args start at 1)\n\t\tif (start < 0) start = 0;\n\n\t\tif (lengthStr) {\n\t\t\tconst length = parseInt(lengthStr, 10);\n\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t}\n\t\treturn args.slice(start).join(\" \");\n\t});\n\n\t// Pre-compute all args joined (optimization)\n\tconst allArgs = args.join(\" \");\n\n\t// Replace $ARGUMENTS with all args joined (new syntax, aligns with Claude, Codex, OpenCode)\n\tresult = result.replace(/\\$ARGUMENTS/g, allArgs);\n\n\t// Replace $@ with all args joined (existing syntax)\n\tresult = result.replace(/\\$@/g, allArgs);\n\n\treturn result;\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults: boolean;\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\tconst promptPaths = options.promptPaths;\n\tconst includeDefaults = options.includeDefaults;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = join(resolvedAgentDir, \"prompts\");\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: globalPromptsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: projectPromptsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(template.content, args);\n\t}\n\n\treturn text;\n}\n"]}
1
+ {"version":3,"file":"prompt-templates.js","sourceRoot":"","sources":["../../src/core/prompt-templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync, statSync } from \"fs\";\nimport { basename, dirname, join, resolve, sep } from \"path\";\nimport { CONFIG_DIR_NAME } from \"../config.ts\";\nimport { parseFrontmatter } from \"../utils/frontmatter.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { createSyntheticSourceInfo, type SourceInfo } from \"./source-info.ts\";\n\n/**\n * Represents a prompt template loaded from a markdown file\n */\nexport interface PromptTemplate {\n\tname: string;\n\tdescription: string;\n\targumentHint?: string;\n\tcontent: string;\n\tsourceInfo: SourceInfo;\n\tfilePath: string; // Absolute path to the template file\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in template content\n * Supports:\n * - $1, $2, ... for positional args\n * - $@ and $ARGUMENTS for all args\n * - ${@:N} for args from Nth onwards (bash-style slicing)\n * - ${@:N:L} for L args starting from Nth\n * - ${N:-default} for positional args with a default value when missing/empty\n *\n * Note: Replacement happens on the template string only. Argument and default\n * values containing patterns like $1, $@, or $ARGUMENTS are NOT recursively substituted.\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tconst allArgs = args.join(\" \");\n\n\t// The default-value group is length-bounded to avoid polynomial-time regex\n\t// backtracking (ReDoS) when scanning template content, which is read from\n\t// disk. This is an Atomic hardening divergence from upstream; 1024 characters\n\t// far exceeds any realistic slash-command default value.\n\treturn content.replace(\n\t\t/\\$\\{(\\d+):-([^}]{0,1024})\\}|\\$\\{@:(\\d+)(?::(\\d+))?\\}|\\$(ARGUMENTS|@|\\d+)/g,\n\t\t(_match, defaultNum, defaultValue, sliceStart, sliceLength, simple) => {\n\t\t\tif (defaultNum) {\n\t\t\t\tconst index = parseInt(defaultNum, 10) - 1;\n\t\t\t\tconst value = args[index];\n\t\t\t\treturn value ? value : defaultValue;\n\t\t\t}\n\n\t\t\tif (sliceStart) {\n\t\t\t\tlet start = parseInt(sliceStart, 10) - 1;\n\t\t\t\tif (start < 0) start = 0;\n\n\t\t\t\tif (sliceLength) {\n\t\t\t\t\tconst length = parseInt(sliceLength, 10);\n\t\t\t\t\treturn args.slice(start, start + length).join(\" \");\n\t\t\t\t}\n\t\t\t\treturn args.slice(start).join(\" \");\n\t\t\t}\n\n\t\t\tif (simple === \"ARGUMENTS\" || simple === \"@\") {\n\t\t\t\treturn allArgs;\n\t\t\t}\n\n\t\t\tconst index = parseInt(simple, 10) - 1;\n\t\t\treturn args[index] ?? \"\";\n\t\t},\n\t);\n}\n\nfunction loadTemplateFromFile(filePath: string, sourceInfo: SourceInfo): PromptTemplate | null {\n\ttry {\n\t\tconst rawContent = readFileSync(filePath, \"utf-8\");\n\t\tconst { frontmatter, body } = parseFrontmatter<Record<string, string>>(rawContent);\n\n\t\tconst name = basename(filePath).replace(/\\.md$/, \"\");\n\n\t\t// Get description from frontmatter or first non-empty line\n\t\tlet description = frontmatter.description || \"\";\n\t\tif (!description) {\n\t\t\tconst firstLine = body.split(\"\\n\").find((line) => line.trim());\n\t\t\tif (firstLine) {\n\t\t\t\t// Truncate if too long\n\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tname,\n\t\t\tdescription,\n\t\t\t...(frontmatter[\"argument-hint\"] && { argumentHint: frontmatter[\"argument-hint\"] }),\n\t\t\tcontent: body,\n\t\t\tsourceInfo,\n\t\t\tfilePath,\n\t\t};\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Scan a directory for .md files (non-recursive) and load them as prompt templates.\n */\nfunction loadTemplatesFromDir(dir: string, getSourceInfo: (filePath: string) => SourceInfo): PromptTemplate[] {\n\tconst templates: PromptTemplate[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn templates;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\t// For symlinks, check if they point to a file\n\t\t\tlet isFile = entry.isFile();\n\t\t\tif (entry.isSymbolicLink()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst stats = statSync(fullPath);\n\t\t\t\t\tisFile = stats.isFile();\n\t\t\t\t} catch {\n\t\t\t\t\t// Broken symlink, skip it\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isFile && entry.name.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(fullPath, getSourceInfo(fullPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch {\n\t\treturn templates;\n\t}\n\n\treturn templates;\n}\n\nexport interface LoadPromptTemplatesOptions {\n\t/** Working directory for project-local templates. */\n\tcwd: string;\n\t/** Agent config directory for global templates. */\n\tagentDir: string;\n\t/** Explicit prompt template paths (files or directories). */\n\tpromptPaths?: string[];\n\t/** Include default prompt directories. */\n\tincludeDefaults?: boolean;\n}\n\n/**\n * Load all prompt templates from:\n * 1. Global: agentDir/prompts/\n * 2. Project: cwd/{CONFIG_DIR_NAME}/prompts/\n * 3. Explicit prompt paths\n */\nexport function loadPromptTemplates(options: LoadPromptTemplatesOptions): PromptTemplate[] {\n\tconst resolvedCwd = resolvePath(options.cwd);\n\tconst resolvedAgentDir = resolvePath(options.agentDir);\n\tconst promptPaths = options.promptPaths ?? [];\n\tconst includeDefaults = options.includeDefaults ?? true;\n\n\tconst templates: PromptTemplate[] = [];\n\n\tconst globalPromptsDir = join(resolvedAgentDir, \"prompts\");\n\tconst projectPromptsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, \"prompts\");\n\n\tconst isUnderPath = (target: string, root: string): boolean => {\n\t\tconst normalizedRoot = resolve(root);\n\t\tif (target === normalizedRoot) {\n\t\t\treturn true;\n\t\t}\n\t\tconst prefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;\n\t\treturn target.startsWith(prefix);\n\t};\n\n\tconst getSourceInfo = (resolvedPath: string): SourceInfo => {\n\t\tif (isUnderPath(resolvedPath, globalPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"user\",\n\t\t\t\tbaseDir: globalPromptsDir,\n\t\t\t});\n\t\t}\n\t\tif (isUnderPath(resolvedPath, projectPromptsDir)) {\n\t\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\t\tsource: \"local\",\n\t\t\t\tscope: \"project\",\n\t\t\t\tbaseDir: projectPromptsDir,\n\t\t\t});\n\t\t}\n\t\treturn createSyntheticSourceInfo(resolvedPath, {\n\t\t\tsource: \"local\",\n\t\t\tbaseDir: statSync(resolvedPath).isDirectory() ? resolvedPath : dirname(resolvedPath),\n\t\t});\n\t};\n\n\tif (includeDefaults) {\n\t\ttemplates.push(...loadTemplatesFromDir(globalPromptsDir, getSourceInfo));\n\t\ttemplates.push(...loadTemplatesFromDir(projectPromptsDir, getSourceInfo));\n\t}\n\n\t// 3. Load explicit prompt paths\n\tfor (const rawPath of promptPaths) {\n\t\tconst resolvedPath = resolvePath(rawPath, resolvedCwd, { trim: true });\n\t\tif (!existsSync(resolvedPath)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst stats = statSync(resolvedPath);\n\t\t\tif (stats.isDirectory()) {\n\t\t\t\ttemplates.push(...loadTemplatesFromDir(resolvedPath, getSourceInfo));\n\t\t\t} else if (stats.isFile() && resolvedPath.endsWith(\".md\")) {\n\t\t\t\tconst template = loadTemplateFromFile(resolvedPath, getSourceInfo(resolvedPath));\n\t\t\t\tif (template) {\n\t\t\t\t\ttemplates.push(template);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read failures\n\t\t}\n\t}\n\n\treturn templates;\n}\n\n/**\n * Expand a prompt template if it matches a template name.\n * Returns the expanded content or the original text if not a template.\n */\nexport function expandPromptTemplate(text: string, templates: PromptTemplate[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst templateName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst template = templates.find((t) => t.name === templateName);\n\tif (template) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(template.content, args);\n\t}\n\n\treturn text;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { SettingsManager } from "./settings-manager.ts";
3
+ export declare function mergeProviderAttributionHeaders(model: Model<Api>, settingsManager: SettingsManager, sessionId: string | undefined, ...headerSources: Array<Record<string, string> | undefined>): Record<string, string> | undefined;
4
+ //# sourceMappingURL=provider-attribution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-attribution.d.ts","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA6E7D,wBAAgB,+BAA+B,CAC9C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EACjB,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,GAAG,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,GACzD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAapC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged = {\n\t\t...getSessionHeaders(model, sessionId),\n\t\t...getDefaultAttributionHeaders(model, settingsManager),\n\t};\n\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
@@ -0,0 +1,73 @@
1
+ import { APP_NAME } from "../config.js";
2
+ import { isInstallTelemetryEnabled } from "./telemetry.js";
3
+ const OPENROUTER_HOST = "openrouter.ai";
4
+ const NVIDIA_NIM_HOST = "integrate.api.nvidia.com";
5
+ const CLOUDFLARE_API_HOST = "api.cloudflare.com";
6
+ const CLOUDFLARE_AI_GATEWAY_HOST = "gateway.ai.cloudflare.com";
7
+ const OPENCODE_HOST = "opencode.ai";
8
+ function matchesHost(baseUrl, expectedHost) {
9
+ try {
10
+ return new URL(baseUrl).hostname === expectedHost;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }
16
+ function isOpenRouterModel(model) {
17
+ return model.provider === "openrouter" || matchesHost(model.baseUrl, OPENROUTER_HOST);
18
+ }
19
+ function isNvidiaNimModel(model) {
20
+ return model.provider === "nvidia" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);
21
+ }
22
+ function isCloudflareModel(model) {
23
+ return (model.provider === "cloudflare-workers-ai" ||
24
+ model.provider === "cloudflare-ai-gateway" ||
25
+ matchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||
26
+ matchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST));
27
+ }
28
+ function getDefaultAttributionHeaders(model, settingsManager) {
29
+ if (!isInstallTelemetryEnabled(settingsManager)) {
30
+ return undefined;
31
+ }
32
+ if (isOpenRouterModel(model)) {
33
+ return {
34
+ "HTTP-Referer": "https://atomic.sh",
35
+ "X-OpenRouter-Title": APP_NAME,
36
+ "X-OpenRouter-Categories": "cli-agent",
37
+ };
38
+ }
39
+ if (isNvidiaNimModel(model)) {
40
+ return {
41
+ "X-BILLING-INVOKE-ORIGIN": "Atomic",
42
+ };
43
+ }
44
+ if (isCloudflareModel(model)) {
45
+ return {
46
+ "User-Agent": APP_NAME,
47
+ };
48
+ }
49
+ return undefined;
50
+ }
51
+ function getSessionHeaders(model, sessionId) {
52
+ if (!sessionId)
53
+ return undefined;
54
+ if (model.provider !== "opencode" &&
55
+ model.provider !== "opencode-go" &&
56
+ !matchesHost(model.baseUrl, OPENCODE_HOST)) {
57
+ return undefined;
58
+ }
59
+ return { "x-opencode-session": sessionId, "x-opencode-client": APP_NAME };
60
+ }
61
+ export function mergeProviderAttributionHeaders(model, settingsManager, sessionId, ...headerSources) {
62
+ const merged = {
63
+ ...getSessionHeaders(model, sessionId),
64
+ ...getDefaultAttributionHeaders(model, settingsManager),
65
+ };
66
+ for (const headers of headerSources) {
67
+ if (headers) {
68
+ Object.assign(merged, headers);
69
+ }
70
+ }
71
+ return Object.keys(merged).length > 0 ? merged : undefined;
72
+ }
73
+ //# sourceMappingURL=provider-attribution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-attribution.js","sourceRoot":"","sources":["../../src/core/provider-attribution.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAE3D,MAAM,eAAe,GAAG,eAAe,CAAC;AACxC,MAAM,eAAe,GAAG,0BAA0B,CAAC;AACnD,MAAM,mBAAmB,GAAG,oBAAoB,CAAC;AACjD,MAAM,0BAA0B,GAAG,2BAA2B,CAAC;AAC/D,MAAM,aAAa,GAAG,aAAa,CAAC;AAEpC,SAAS,WAAW,CAAC,OAAe,EAAE,YAAoB;IACzD,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,KAAK,YAAY,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiB;IAC3C,OAAO,KAAK,CAAC,QAAQ,KAAK,YAAY,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACvF,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC;AACnF,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiB;IAC3C,OAAO,CACN,KAAK,CAAC,QAAQ,KAAK,uBAAuB;QAC1C,KAAK,CAAC,QAAQ,KAAK,uBAAuB;QAC1C,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,mBAAmB,CAAC;QAC/C,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,0BAA0B,CAAC,CACtD,CAAC;AACH,CAAC;AAED,SAAS,4BAA4B,CACpC,KAAiB,EACjB,eAAgC;IAEhC,IAAI,CAAC,yBAAyB,CAAC,eAAe,CAAC,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO;YACN,cAAc,EAAE,mBAAmB;YACnC,oBAAoB,EAAE,QAAQ;YAC9B,yBAAyB,EAAE,WAAW;SACtC,CAAC;IACH,CAAC;IAED,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;YACN,yBAAyB,EAAE,QAAQ;SACnC,CAAC;IACH,CAAC;IAED,IAAI,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO;YACN,YAAY,EAAE,QAAQ;SACtB,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAiB,EAAE,SAA6B;IAC1E,IAAI,CAAC,SAAS;QAAE,OAAO,SAAS,CAAC;IACjC,IACC,KAAK,CAAC,QAAQ,KAAK,UAAU;QAC7B,KAAK,CAAC,QAAQ,KAAK,aAAa;QAChC,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,aAAa,CAAC,EACzC,CAAC;QACF,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,OAAO,EAAE,oBAAoB,EAAE,SAAS,EAAE,mBAAmB,EAAE,QAAQ,EAAE,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC9C,KAAiB,EACjB,eAAgC,EAChC,SAA6B,EAC7B,GAAG,aAAwD;IAE3D,MAAM,MAAM,GAAG;QACd,GAAG,iBAAiB,CAAC,KAAK,EAAE,SAAS,CAAC;QACtC,GAAG,4BAA4B,CAAC,KAAK,EAAE,eAAe,CAAC;KACvD,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,aAAa,EAAE,CAAC;QACrC,IAAI,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AAC5D,CAAC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { APP_NAME } from \"../config.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\nimport { isInstallTelemetryEnabled } from \"./telemetry.ts\";\n\nconst OPENROUTER_HOST = \"openrouter.ai\";\nconst NVIDIA_NIM_HOST = \"integrate.api.nvidia.com\";\nconst CLOUDFLARE_API_HOST = \"api.cloudflare.com\";\nconst CLOUDFLARE_AI_GATEWAY_HOST = \"gateway.ai.cloudflare.com\";\nconst OPENCODE_HOST = \"opencode.ai\";\n\nfunction matchesHost(baseUrl: string, expectedHost: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl).hostname === expectedHost;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction isOpenRouterModel(model: Model<Api>): boolean {\n\treturn model.provider === \"openrouter\" || matchesHost(model.baseUrl, OPENROUTER_HOST);\n}\n\nfunction isNvidiaNimModel(model: Model<Api>): boolean {\n\treturn model.provider === \"nvidia\" || matchesHost(model.baseUrl, NVIDIA_NIM_HOST);\n}\n\nfunction isCloudflareModel(model: Model<Api>): boolean {\n\treturn (\n\t\tmodel.provider === \"cloudflare-workers-ai\" ||\n\t\tmodel.provider === \"cloudflare-ai-gateway\" ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_API_HOST) ||\n\t\tmatchesHost(model.baseUrl, CLOUDFLARE_AI_GATEWAY_HOST)\n\t);\n}\n\nfunction getDefaultAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n): Record<string, string> | undefined {\n\tif (!isInstallTelemetryEnabled(settingsManager)) {\n\t\treturn undefined;\n\t}\n\n\tif (isOpenRouterModel(model)) {\n\t\treturn {\n\t\t\t\"HTTP-Referer\": \"https://atomic.sh\",\n\t\t\t\"X-OpenRouter-Title\": APP_NAME,\n\t\t\t\"X-OpenRouter-Categories\": \"cli-agent\",\n\t\t};\n\t}\n\n\tif (isNvidiaNimModel(model)) {\n\t\treturn {\n\t\t\t\"X-BILLING-INVOKE-ORIGIN\": \"Atomic\",\n\t\t};\n\t}\n\n\tif (isCloudflareModel(model)) {\n\t\treturn {\n\t\t\t\"User-Agent\": APP_NAME,\n\t\t};\n\t}\n\n\treturn undefined;\n}\n\nfunction getSessionHeaders(model: Model<Api>, sessionId: string | undefined): Record<string, string> | undefined {\n\tif (!sessionId) return undefined;\n\tif (\n\t\tmodel.provider !== \"opencode\" &&\n\t\tmodel.provider !== \"opencode-go\" &&\n\t\t!matchesHost(model.baseUrl, OPENCODE_HOST)\n\t) {\n\t\treturn undefined;\n\t}\n\treturn { \"x-opencode-session\": sessionId, \"x-opencode-client\": APP_NAME };\n}\n\nexport function mergeProviderAttributionHeaders(\n\tmodel: Model<Api>,\n\tsettingsManager: SettingsManager,\n\tsessionId: string | undefined,\n\t...headerSources: Array<Record<string, string> | undefined>\n): Record<string, string> | undefined {\n\tconst merged = {\n\t\t...getSessionHeaders(model, sessionId),\n\t\t...getDefaultAttributionHeaders(model, settingsManager),\n\t};\n\n\tfor (const headers of headerSources) {\n\t\tif (headers) {\n\t\t\tObject.assign(merged, headers);\n\t\t}\n\t}\n\n\treturn Object.keys(merged).length > 0 ? merged : undefined;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"provider-display-names.d.ts","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA+BlE,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
1
+ {"version":3,"file":"provider-display-names.d.ts","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,+BAA+B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAkClE,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"ant-ling\": \"Ant Ling\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\tnvidia: \"NVIDIA NIM\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\t\"zai-coding-cn\": \"ZAI Coding Plan (China)\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
@@ -1,6 +1,7 @@
1
1
  export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
2
2
  anthropic: "Anthropic",
3
3
  "amazon-bedrock": "Amazon Bedrock",
4
+ "ant-ling": "Ant Ling",
4
5
  "azure-openai-responses": "Azure OpenAI Responses",
5
6
  cerebras: "Cerebras",
6
7
  "cloudflare-ai-gateway": "Cloudflare AI Gateway",
@@ -17,6 +18,7 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
17
18
  "minimax-cn": "MiniMax (China)",
18
19
  moonshotai: "Moonshot AI",
19
20
  "moonshotai-cn": "Moonshot AI (China)",
21
+ nvidia: "NVIDIA NIM",
20
22
  opencode: "OpenCode Zen",
21
23
  "opencode-go": "OpenCode Go",
22
24
  openai: "OpenAI",
@@ -25,6 +27,7 @@ export const BUILT_IN_PROVIDER_DISPLAY_NAMES = {
25
27
  "vercel-ai-gateway": "Vercel AI Gateway",
26
28
  xai: "xAI",
27
29
  zai: "ZAI",
30
+ "zai-coding-cn": "ZAI Coding Plan (China)",
28
31
  xiaomi: "Xiaomi MiMo",
29
32
  "xiaomi-token-plan-cn": "Xiaomi MiMo Token Plan (China)",
30
33
  "xiaomi-token-plan-ams": "Xiaomi MiMo Token Plan (Amsterdam)",
@@ -1 +1 @@
1
- {"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;CAC7D,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
1
+ {"version":3,"file":"provider-display-names.js","sourceRoot":"","sources":["../../src/core/provider-display-names.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,+BAA+B,GAA2B;IACtE,SAAS,EAAE,WAAW;IACtB,gBAAgB,EAAE,gBAAgB;IAClC,UAAU,EAAE,UAAU;IACtB,wBAAwB,EAAE,wBAAwB;IAClD,QAAQ,EAAE,UAAU;IACpB,uBAAuB,EAAE,uBAAuB;IAChD,uBAAuB,EAAE,uBAAuB;IAChD,QAAQ,EAAE,UAAU;IACpB,SAAS,EAAE,WAAW;IACtB,MAAM,EAAE,eAAe;IACvB,eAAe,EAAE,kBAAkB;IACnC,IAAI,EAAE,MAAM;IACZ,WAAW,EAAE,cAAc;IAC3B,aAAa,EAAE,iBAAiB;IAChC,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,iBAAiB;IAC/B,UAAU,EAAE,aAAa;IACzB,eAAe,EAAE,qBAAqB;IACtC,MAAM,EAAE,YAAY;IACpB,QAAQ,EAAE,cAAc;IACxB,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,YAAY;IACxB,QAAQ,EAAE,aAAa;IACvB,mBAAmB,EAAE,mBAAmB;IACxC,GAAG,EAAE,KAAK;IACV,GAAG,EAAE,KAAK;IACV,eAAe,EAAE,yBAAyB;IAC1C,MAAM,EAAE,aAAa;IACrB,sBAAsB,EAAE,gCAAgC;IACxD,uBAAuB,EAAE,oCAAoC;IAC7D,uBAAuB,EAAE,oCAAoC;CAC7D,CAAC","sourcesContent":["export const BUILT_IN_PROVIDER_DISPLAY_NAMES: Record<string, string> = {\n\tanthropic: \"Anthropic\",\n\t\"amazon-bedrock\": \"Amazon Bedrock\",\n\t\"ant-ling\": \"Ant Ling\",\n\t\"azure-openai-responses\": \"Azure OpenAI Responses\",\n\tcerebras: \"Cerebras\",\n\t\"cloudflare-ai-gateway\": \"Cloudflare AI Gateway\",\n\t\"cloudflare-workers-ai\": \"Cloudflare Workers AI\",\n\tdeepseek: \"DeepSeek\",\n\tfireworks: \"Fireworks\",\n\tgoogle: \"Google Gemini\",\n\t\"google-vertex\": \"Google Vertex AI\",\n\tgroq: \"Groq\",\n\thuggingface: \"Hugging Face\",\n\t\"kimi-coding\": \"Kimi For Coding\",\n\tmistral: \"Mistral\",\n\tminimax: \"MiniMax\",\n\t\"minimax-cn\": \"MiniMax (China)\",\n\tmoonshotai: \"Moonshot AI\",\n\t\"moonshotai-cn\": \"Moonshot AI (China)\",\n\tnvidia: \"NVIDIA NIM\",\n\topencode: \"OpenCode Zen\",\n\t\"opencode-go\": \"OpenCode Go\",\n\topenai: \"OpenAI\",\n\topenrouter: \"OpenRouter\",\n\ttogether: \"Together AI\",\n\t\"vercel-ai-gateway\": \"Vercel AI Gateway\",\n\txai: \"xAI\",\n\tzai: \"ZAI\",\n\t\"zai-coding-cn\": \"ZAI Coding Plan (China)\",\n\txiaomi: \"Xiaomi MiMo\",\n\t\"xiaomi-token-plan-cn\": \"Xiaomi MiMo Token Plan (China)\",\n\t\"xiaomi-token-plan-ams\": \"Xiaomi MiMo Token Plan (Amsterdam)\",\n\t\"xiaomi-token-plan-sgp\": \"Xiaomi MiMo Token Plan (Singapore)\",\n};\n"]}
@@ -2,10 +2,18 @@
2
2
  * Resolve configuration values that may be shell commands, environment variables, or literals.
3
3
  * Used by auth-storage.ts and model-registry.ts.
4
4
  */
5
+ export declare function getConfigValueEnvVarName(config: string): string | undefined;
6
+ export declare function getConfigValueEnvVarNames(config: string): string[];
7
+ export declare function getMissingConfigValueEnvVarNames(config: string): string[];
8
+ export declare function isCommandConfigValue(config: string): boolean;
9
+ export declare function isConfigValueConfigured(config: string): boolean;
10
+ export declare function isLegacyEnvVarNameConfigValue(config: string): boolean;
5
11
  /**
6
12
  * Resolve a config value (API key, header value, etc.) to an actual value.
7
13
  * - If starts with "!", executes the rest as a shell command and uses stdout (cached)
8
- * - Otherwise checks environment variable first, then treats as literal (not cached)
14
+ * - Interpolates "$ENV_VAR" or "${ENV_VAR}" references with the named environment variable
15
+ * - In non-command values, "$$" escapes a literal "$" and "$!" escapes a literal "!"
16
+ * - Otherwise treats the value as a literal
9
17
  */
10
18
  export declare function resolveConfigValue(config: string): string | undefined;
11
19
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"resolve-config-value.d.ts","sourceRoot":"","sources":["../../src/core/resolve-config-value.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAiED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAWrF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAU9G;AAED,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,WAAW,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAOpC;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C","sourcesContent":["/**\n * Resolve configuration values that may be shell commands, environment variables, or literals.\n * Used by auth-storage.ts and model-registry.ts.\n */\n\nimport { execSync, spawnSync } from \"child_process\";\nimport { getShellConfig } from \"../utils/shell.ts\";\n\n// Cache for shell command results (persists for process lifetime)\nconst commandResultCache = new Map<string, string | undefined>();\n\n/**\n * Resolve a config value (API key, header value, etc.) to an actual value.\n * - If starts with \"!\", executes the rest as a shell command and uses stdout (cached)\n * - Otherwise checks environment variable first, then treats as literal (not cached)\n */\nexport function resolveConfigValue(config: string): string | undefined {\n\tif (config.startsWith(\"!\")) {\n\t\treturn executeCommand(config);\n\t}\n\tconst envValue = process.env[config];\n\treturn envValue || config;\n}\n\nfunction executeWithConfiguredShell(command: string): { executed: boolean; value: string | undefined } {\n\ttry {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst result = spawnSync(shell, [...args, command], {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 10000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tshell: false,\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tconst error = result.error as NodeJS.ErrnoException;\n\t\t\tif (error.code === \"ENOENT\") {\n\t\t\t\treturn { executed: false, value: undefined };\n\t\t\t}\n\t\t\treturn { executed: true, value: undefined };\n\t\t}\n\n\t\tif (result.status !== 0) {\n\t\t\treturn { executed: true, value: undefined };\n\t\t}\n\n\t\tconst value = (result.stdout ?? \"\").trim();\n\t\treturn { executed: true, value: value || undefined };\n\t} catch {\n\t\treturn { executed: false, value: undefined };\n\t}\n}\n\nfunction executeWithDefaultShell(command: string): string | undefined {\n\ttry {\n\t\tconst output = execSync(command, {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 10000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t});\n\t\treturn output.trim() || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction executeCommandUncached(commandConfig: string): string | undefined {\n\tconst command = commandConfig.slice(1);\n\treturn process.platform === \"win32\"\n\t\t? (() => {\n\t\t\t\tconst configuredResult = executeWithConfiguredShell(command);\n\t\t\t\treturn configuredResult.executed ? configuredResult.value : executeWithDefaultShell(command);\n\t\t\t})()\n\t\t: executeWithDefaultShell(command);\n}\n\nfunction executeCommand(commandConfig: string): string | undefined {\n\tif (commandResultCache.has(commandConfig)) {\n\t\treturn commandResultCache.get(commandConfig);\n\t}\n\n\tconst result = executeCommandUncached(commandConfig);\n\tcommandResultCache.set(commandConfig, result);\n\treturn result;\n}\n\n/**\n * Resolve all header values using the same resolution logic as API keys.\n */\nexport function resolveConfigValueUncached(config: string): string | undefined {\n\tif (config.startsWith(\"!\")) {\n\t\treturn executeCommandUncached(config);\n\t}\n\tconst envValue = process.env[config];\n\treturn envValue || config;\n}\n\nexport function resolveConfigValueOrThrow(config: string, description: string): string {\n\tconst resolvedValue = resolveConfigValueUncached(config);\n\tif (resolvedValue !== undefined) {\n\t\treturn resolvedValue;\n\t}\n\n\tif (config.startsWith(\"!\")) {\n\t\tthrow new Error(`Failed to resolve ${description} from shell command: ${config.slice(1)}`);\n\t}\n\n\tthrow new Error(`Failed to resolve ${description}`);\n}\n\n/**\n * Resolve all header values using the same resolution logic as API keys.\n */\nexport function resolveHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {\n\tif (!headers) return undefined;\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tconst resolvedValue = resolveConfigValue(value);\n\t\tif (resolvedValue) {\n\t\t\tresolved[key] = resolvedValue;\n\t\t}\n\t}\n\treturn Object.keys(resolved).length > 0 ? resolved : undefined;\n}\n\nexport function resolveHeadersOrThrow(\n\theaders: Record<string, string> | undefined,\n\tdescription: string,\n): Record<string, string> | undefined {\n\tif (!headers) return undefined;\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tresolved[key] = resolveConfigValueOrThrow(value, `${description} header \"${key}\"`);\n\t}\n\treturn Object.keys(resolved).length > 0 ? resolved : undefined;\n}\n\n/** Clear the config value command cache. Exported for testing. */\nexport function clearConfigValueCache(): void {\n\tcommandResultCache.clear();\n}\n"]}
1
+ {"version":3,"file":"resolve-config-value.d.ts","sourceRoot":"","sources":["../../src/core/resolve-config-value.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgHH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI3E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAGlE;AAED,wBAAgB,gCAAgC,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrE;AAiED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAM7E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAsBrF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAU9G;AAED,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,EAC3C,WAAW,EAAE,MAAM,GACjB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAOpC;AAED,kEAAkE;AAClE,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C","sourcesContent":["/**\n * Resolve configuration values that may be shell commands, environment variables, or literals.\n * Used by auth-storage.ts and model-registry.ts.\n */\n\nimport { execSync, spawnSync } from \"child_process\";\nimport { getShellConfig } from \"../utils/shell.ts\";\n\n// Cache for shell command results (persists for process lifetime)\nconst commandResultCache = new Map<string, string | undefined>();\nconst ENV_VAR_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\nconst ENV_VAR_NAME_PREFIX_RE = /^[A-Za-z_][A-Za-z0-9_]*/;\nconst LEGACY_ENV_VAR_NAME_RE = /^[A-Z_][A-Z0-9_]*$/;\n\ntype TemplatePart = { type: \"literal\"; value: string } | { type: \"env\"; name: string };\n\ntype ConfigValueReference = { type: \"command\"; config: string } | { type: \"template\"; parts: TemplatePart[] };\n\nfunction appendLiteral(parts: TemplatePart[], value: string): void {\n\tif (!value) return;\n\tconst previousPart = parts[parts.length - 1];\n\tif (previousPart?.type === \"literal\") {\n\t\tpreviousPart.value += value;\n\t\treturn;\n\t}\n\tparts.push({ type: \"literal\", value });\n}\n\nfunction parseConfigValueTemplate(config: string): TemplatePart[] {\n\tconst parts: TemplatePart[] = [];\n\tlet index = 0;\n\n\twhile (index < config.length) {\n\t\tconst dollarIndex = config.indexOf(\"$\", index);\n\t\tif (dollarIndex < 0) {\n\t\t\tappendLiteral(parts, config.slice(index));\n\t\t\tbreak;\n\t\t}\n\n\t\tappendLiteral(parts, config.slice(index, dollarIndex));\n\t\tconst nextChar = config[dollarIndex + 1];\n\n\t\tif (nextChar === \"$\" || nextChar === \"!\") {\n\t\t\tappendLiteral(parts, nextChar);\n\t\t\tindex = dollarIndex + 2;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (nextChar === \"{\") {\n\t\t\tconst endIndex = config.indexOf(\"}\", dollarIndex + 2);\n\t\t\tif (endIndex < 0) {\n\t\t\t\tappendLiteral(parts, \"$\");\n\t\t\t\tindex = dollarIndex + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst name = config.slice(dollarIndex + 2, endIndex);\n\t\t\tif (ENV_VAR_NAME_RE.test(name)) {\n\t\t\t\tparts.push({ type: \"env\", name });\n\t\t\t} else {\n\t\t\t\tappendLiteral(parts, config.slice(dollarIndex, endIndex + 1));\n\t\t\t}\n\t\t\tindex = endIndex + 1;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = config.slice(dollarIndex + 1).match(ENV_VAR_NAME_PREFIX_RE);\n\t\tif (match) {\n\t\t\tparts.push({ type: \"env\", name: match[0] });\n\t\t\tindex = dollarIndex + 1 + match[0].length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tappendLiteral(parts, \"$\");\n\t\tindex = dollarIndex + 1;\n\t}\n\n\treturn parts;\n}\n\nfunction parseConfigValueReference(config: string): ConfigValueReference {\n\tif (config.startsWith(\"!\")) {\n\t\treturn { type: \"command\", config };\n\t}\n\n\treturn { type: \"template\", parts: parseConfigValueTemplate(config) };\n}\n\nfunction resolveEnvConfigValue(name: string): string | undefined {\n\treturn process.env[name] || undefined;\n}\n\nfunction getTemplateEnvVarNames(parts: TemplatePart[]): string[] {\n\tconst names: string[] = [];\n\tfor (const part of parts) {\n\t\tif (part.type !== \"env\" || names.includes(part.name)) continue;\n\t\tnames.push(part.name);\n\t}\n\treturn names;\n}\n\nfunction resolveTemplate(parts: TemplatePart[]): string | undefined {\n\tlet resolved = \"\";\n\tfor (const part of parts) {\n\t\tif (part.type === \"literal\") {\n\t\t\tresolved += part.value;\n\t\t\tcontinue;\n\t\t}\n\t\tconst envValue = resolveEnvConfigValue(part.name);\n\t\tif (envValue === undefined) return undefined;\n\t\tresolved += envValue;\n\t}\n\treturn resolved;\n}\n\nexport function getConfigValueEnvVarName(config: string): string | undefined {\n\tconst reference = parseConfigValueReference(config);\n\tif (reference.type !== \"template\") return undefined;\n\treturn reference.parts.length === 1 && reference.parts[0]?.type === \"env\" ? reference.parts[0].name : undefined;\n}\n\nexport function getConfigValueEnvVarNames(config: string): string[] {\n\tconst reference = parseConfigValueReference(config);\n\treturn reference.type === \"template\" ? getTemplateEnvVarNames(reference.parts) : [];\n}\n\nexport function getMissingConfigValueEnvVarNames(config: string): string[] {\n\treturn getConfigValueEnvVarNames(config).filter((name) => resolveEnvConfigValue(name) === undefined);\n}\n\nexport function isCommandConfigValue(config: string): boolean {\n\treturn parseConfigValueReference(config).type === \"command\";\n}\n\nexport function isConfigValueConfigured(config: string): boolean {\n\treturn getMissingConfigValueEnvVarNames(config).length === 0;\n}\n\nexport function isLegacyEnvVarNameConfigValue(config: string): boolean {\n\treturn LEGACY_ENV_VAR_NAME_RE.test(config);\n}\n\n/**\n * Resolve a config value (API key, header value, etc.) to an actual value.\n * - If starts with \"!\", executes the rest as a shell command and uses stdout (cached)\n * - Interpolates \"$ENV_VAR\" or \"${ENV_VAR}\" references with the named environment variable\n * - In non-command values, \"$$\" escapes a literal \"$\" and \"$!\" escapes a literal \"!\"\n * - Otherwise treats the value as a literal\n */\nexport function resolveConfigValue(config: string): string | undefined {\n\tconst reference = parseConfigValueReference(config);\n\tif (reference.type === \"command\") {\n\t\treturn executeCommand(reference.config);\n\t}\n\treturn resolveTemplate(reference.parts);\n}\n\nfunction executeWithConfiguredShell(command: string): { executed: boolean; value: string | undefined } {\n\ttry {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst result = spawnSync(shell, [...args, command], {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 10000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tshell: false,\n\t\t\twindowsHide: true,\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tconst error = result.error as NodeJS.ErrnoException;\n\t\t\tif (error.code === \"ENOENT\") {\n\t\t\t\treturn { executed: false, value: undefined };\n\t\t\t}\n\t\t\treturn { executed: true, value: undefined };\n\t\t}\n\n\t\tif (result.status !== 0) {\n\t\t\treturn { executed: true, value: undefined };\n\t\t}\n\n\t\tconst value = (result.stdout ?? \"\").trim();\n\t\treturn { executed: true, value: value || undefined };\n\t} catch {\n\t\treturn { executed: false, value: undefined };\n\t}\n}\n\nfunction executeWithDefaultShell(command: string): string | undefined {\n\ttry {\n\t\tconst output = execSync(command, {\n\t\t\tencoding: \"utf-8\",\n\t\t\ttimeout: 10000,\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t});\n\t\treturn output.trim() || undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction executeCommandUncached(commandConfig: string): string | undefined {\n\tconst command = commandConfig.slice(1);\n\treturn process.platform === \"win32\"\n\t\t? (() => {\n\t\t\t\tconst configuredResult = executeWithConfiguredShell(command);\n\t\t\t\treturn configuredResult.executed ? configuredResult.value : executeWithDefaultShell(command);\n\t\t\t})()\n\t\t: executeWithDefaultShell(command);\n}\n\nfunction executeCommand(commandConfig: string): string | undefined {\n\tif (commandResultCache.has(commandConfig)) {\n\t\treturn commandResultCache.get(commandConfig);\n\t}\n\n\tconst result = executeCommandUncached(commandConfig);\n\tcommandResultCache.set(commandConfig, result);\n\treturn result;\n}\n\n/**\n * Resolve all header values using the same resolution logic as API keys.\n */\nexport function resolveConfigValueUncached(config: string): string | undefined {\n\tconst reference = parseConfigValueReference(config);\n\tif (reference.type === \"command\") {\n\t\treturn executeCommandUncached(reference.config);\n\t}\n\treturn resolveTemplate(reference.parts);\n}\n\nexport function resolveConfigValueOrThrow(config: string, description: string): string {\n\tconst resolvedValue = resolveConfigValueUncached(config);\n\tif (resolvedValue !== undefined) {\n\t\treturn resolvedValue;\n\t}\n\n\tconst reference = parseConfigValueReference(config);\n\tif (reference.type === \"command\") {\n\t\tthrow new Error(`Failed to resolve ${description} from shell command: ${reference.config.slice(1)}`);\n\t}\n\n\tif (reference.type === \"template\") {\n\t\tconst missingEnvVars = getMissingConfigValueEnvVarNames(config);\n\t\tif (missingEnvVars.length === 1) {\n\t\t\tthrow new Error(`Failed to resolve ${description} from environment variable: ${missingEnvVars[0]}`);\n\t\t}\n\t\tif (missingEnvVars.length > 1) {\n\t\t\tthrow new Error(`Failed to resolve ${description} from environment variables: ${missingEnvVars.join(\", \")}`);\n\t\t}\n\t}\n\n\tthrow new Error(`Failed to resolve ${description}`);\n}\n\n/**\n * Resolve all header values using the same resolution logic as API keys.\n */\nexport function resolveHeaders(headers: Record<string, string> | undefined): Record<string, string> | undefined {\n\tif (!headers) return undefined;\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tconst resolvedValue = resolveConfigValue(value);\n\t\tif (resolvedValue) {\n\t\t\tresolved[key] = resolvedValue;\n\t\t}\n\t}\n\treturn Object.keys(resolved).length > 0 ? resolved : undefined;\n}\n\nexport function resolveHeadersOrThrow(\n\theaders: Record<string, string> | undefined,\n\tdescription: string,\n): Record<string, string> | undefined {\n\tif (!headers) return undefined;\n\tconst resolved: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tresolved[key] = resolveConfigValueOrThrow(value, `${description} header \"${key}\"`);\n\t}\n\treturn Object.keys(resolved).length > 0 ? resolved : undefined;\n}\n\n/** Clear the config value command cache. Exported for testing. */\nexport function clearConfigValueCache(): void {\n\tcommandResultCache.clear();\n}\n"]}