@vandeepunk/pi-coding-agent 0.0.1

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 (595) hide show
  1. package/CHANGELOG.md +2564 -0
  2. package/README.md +555 -0
  3. package/dist/cli/args.d.ts +47 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +286 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/config-selector.d.ts +14 -0
  8. package/dist/cli/config-selector.d.ts.map +1 -0
  9. package/dist/cli/config-selector.js +31 -0
  10. package/dist/cli/config-selector.js.map +1 -0
  11. package/dist/cli/file-processor.d.ts +15 -0
  12. package/dist/cli/file-processor.d.ts.map +1 -0
  13. package/dist/cli/file-processor.js +79 -0
  14. package/dist/cli/file-processor.js.map +1 -0
  15. package/dist/cli/list-models.d.ts +9 -0
  16. package/dist/cli/list-models.d.ts.map +1 -0
  17. package/dist/cli/list-models.js +92 -0
  18. package/dist/cli/list-models.js.map +1 -0
  19. package/dist/cli/session-picker.d.ts +9 -0
  20. package/dist/cli/session-picker.d.ts.map +1 -0
  21. package/dist/cli/session-picker.js +34 -0
  22. package/dist/cli/session-picker.js.map +1 -0
  23. package/dist/cli.d.ts +3 -0
  24. package/dist/cli.d.ts.map +1 -0
  25. package/dist/cli.js +11 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/config.d.ts +68 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +203 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/core/agent-session.d.ts +574 -0
  32. package/dist/core/agent-session.d.ts.map +1 -0
  33. package/dist/core/agent-session.js +2260 -0
  34. package/dist/core/agent-session.js.map +1 -0
  35. package/dist/core/auth-storage.d.ts +102 -0
  36. package/dist/core/auth-storage.d.ts.map +1 -0
  37. package/dist/core/auth-storage.js +282 -0
  38. package/dist/core/auth-storage.js.map +1 -0
  39. package/dist/core/bash-executor.d.ts +47 -0
  40. package/dist/core/bash-executor.d.ts.map +1 -0
  41. package/dist/core/bash-executor.js +212 -0
  42. package/dist/core/bash-executor.js.map +1 -0
  43. package/dist/core/compaction/branch-summarization.d.ts +86 -0
  44. package/dist/core/compaction/branch-summarization.d.ts.map +1 -0
  45. package/dist/core/compaction/branch-summarization.js +242 -0
  46. package/dist/core/compaction/branch-summarization.js.map +1 -0
  47. package/dist/core/compaction/compaction.d.ts +121 -0
  48. package/dist/core/compaction/compaction.d.ts.map +1 -0
  49. package/dist/core/compaction/compaction.js +607 -0
  50. package/dist/core/compaction/compaction.js.map +1 -0
  51. package/dist/core/compaction/index.d.ts +7 -0
  52. package/dist/core/compaction/index.d.ts.map +1 -0
  53. package/dist/core/compaction/index.js +7 -0
  54. package/dist/core/compaction/index.js.map +1 -0
  55. package/dist/core/compaction/utils.d.ts +35 -0
  56. package/dist/core/compaction/utils.d.ts.map +1 -0
  57. package/dist/core/compaction/utils.js +138 -0
  58. package/dist/core/compaction/utils.js.map +1 -0
  59. package/dist/core/defaults.d.ts +3 -0
  60. package/dist/core/defaults.d.ts.map +1 -0
  61. package/dist/core/defaults.js +2 -0
  62. package/dist/core/defaults.js.map +1 -0
  63. package/dist/core/diagnostics.d.ts +15 -0
  64. package/dist/core/diagnostics.d.ts.map +1 -0
  65. package/dist/core/diagnostics.js +2 -0
  66. package/dist/core/diagnostics.js.map +1 -0
  67. package/dist/core/event-bus.d.ts +9 -0
  68. package/dist/core/event-bus.d.ts.map +1 -0
  69. package/dist/core/event-bus.js +25 -0
  70. package/dist/core/event-bus.js.map +1 -0
  71. package/dist/core/exec.d.ts +29 -0
  72. package/dist/core/exec.d.ts.map +1 -0
  73. package/dist/core/exec.js +71 -0
  74. package/dist/core/exec.js.map +1 -0
  75. package/dist/core/export-html/ansi-to-html.d.ts +22 -0
  76. package/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
  77. package/dist/core/export-html/ansi-to-html.js +249 -0
  78. package/dist/core/export-html/ansi-to-html.js.map +1 -0
  79. package/dist/core/export-html/index.d.ts +34 -0
  80. package/dist/core/export-html/index.d.ts.map +1 -0
  81. package/dist/core/export-html/index.js +222 -0
  82. package/dist/core/export-html/index.js.map +1 -0
  83. package/dist/core/export-html/template.css +909 -0
  84. package/dist/core/export-html/template.html +54 -0
  85. package/dist/core/export-html/template.js +1549 -0
  86. package/dist/core/export-html/tool-renderer.d.ts +35 -0
  87. package/dist/core/export-html/tool-renderer.d.ts.map +1 -0
  88. package/dist/core/export-html/tool-renderer.js +57 -0
  89. package/dist/core/export-html/tool-renderer.js.map +1 -0
  90. package/dist/core/export-html/vendor/highlight.min.js +1213 -0
  91. package/dist/core/export-html/vendor/marked.min.js +6 -0
  92. package/dist/core/extensions/index.d.ts +11 -0
  93. package/dist/core/extensions/index.d.ts.map +1 -0
  94. package/dist/core/extensions/index.js +9 -0
  95. package/dist/core/extensions/index.js.map +1 -0
  96. package/dist/core/extensions/loader.d.ts +25 -0
  97. package/dist/core/extensions/loader.d.ts.map +1 -0
  98. package/dist/core/extensions/loader.js +400 -0
  99. package/dist/core/extensions/loader.js.map +1 -0
  100. package/dist/core/extensions/runner.d.ts +129 -0
  101. package/dist/core/extensions/runner.d.ts.map +1 -0
  102. package/dist/core/extensions/runner.js +576 -0
  103. package/dist/core/extensions/runner.js.map +1 -0
  104. package/dist/core/extensions/types.d.ts +928 -0
  105. package/dist/core/extensions/types.d.ts.map +1 -0
  106. package/dist/core/extensions/types.js +35 -0
  107. package/dist/core/extensions/types.js.map +1 -0
  108. package/dist/core/extensions/wrapper.d.ts +27 -0
  109. package/dist/core/extensions/wrapper.d.ts.map +1 -0
  110. package/dist/core/extensions/wrapper.js +102 -0
  111. package/dist/core/extensions/wrapper.js.map +1 -0
  112. package/dist/core/footer-data-provider.d.ts +32 -0
  113. package/dist/core/footer-data-provider.d.ts.map +1 -0
  114. package/dist/core/footer-data-provider.js +134 -0
  115. package/dist/core/footer-data-provider.js.map +1 -0
  116. package/dist/core/index.d.ts +9 -0
  117. package/dist/core/index.d.ts.map +1 -0
  118. package/dist/core/index.js +9 -0
  119. package/dist/core/index.js.map +1 -0
  120. package/dist/core/keybindings.d.ts +55 -0
  121. package/dist/core/keybindings.d.ts.map +1 -0
  122. package/dist/core/keybindings.js +153 -0
  123. package/dist/core/keybindings.js.map +1 -0
  124. package/dist/core/messages.d.ts +77 -0
  125. package/dist/core/messages.d.ts.map +1 -0
  126. package/dist/core/messages.js +123 -0
  127. package/dist/core/messages.js.map +1 -0
  128. package/dist/core/model-registry.d.ts +100 -0
  129. package/dist/core/model-registry.d.ts.map +1 -0
  130. package/dist/core/model-registry.js +419 -0
  131. package/dist/core/model-registry.js.map +1 -0
  132. package/dist/core/model-resolver.d.ts +76 -0
  133. package/dist/core/model-resolver.d.ts.map +1 -0
  134. package/dist/core/model-resolver.js +313 -0
  135. package/dist/core/model-resolver.js.map +1 -0
  136. package/dist/core/package-manager.d.ts +131 -0
  137. package/dist/core/package-manager.d.ts.map +1 -0
  138. package/dist/core/package-manager.js +1290 -0
  139. package/dist/core/package-manager.js.map +1 -0
  140. package/dist/core/prompt-templates.d.ts +50 -0
  141. package/dist/core/prompt-templates.d.ts.map +1 -0
  142. package/dist/core/prompt-templates.js +251 -0
  143. package/dist/core/prompt-templates.js.map +1 -0
  144. package/dist/core/resolve-config-value.d.ts +17 -0
  145. package/dist/core/resolve-config-value.d.ts.map +1 -0
  146. package/dist/core/resolve-config-value.js +59 -0
  147. package/dist/core/resolve-config-value.js.map +1 -0
  148. package/dist/core/resource-loader.d.ts +184 -0
  149. package/dist/core/resource-loader.d.ts.map +1 -0
  150. package/dist/core/resource-loader.js +673 -0
  151. package/dist/core/resource-loader.js.map +1 -0
  152. package/dist/core/sdk.d.ts +90 -0
  153. package/dist/core/sdk.d.ts.map +1 -0
  154. package/dist/core/sdk.js +234 -0
  155. package/dist/core/sdk.js.map +1 -0
  156. package/dist/core/session-manager.d.ts +323 -0
  157. package/dist/core/session-manager.d.ts.map +1 -0
  158. package/dist/core/session-manager.js +1091 -0
  159. package/dist/core/session-manager.js.map +1 -0
  160. package/dist/core/settings-manager.d.ts +187 -0
  161. package/dist/core/settings-manager.d.ts.map +1 -0
  162. package/dist/core/settings-manager.js +552 -0
  163. package/dist/core/settings-manager.js.map +1 -0
  164. package/dist/core/skills.d.ts +58 -0
  165. package/dist/core/skills.d.ts.map +1 -0
  166. package/dist/core/skills.js +310 -0
  167. package/dist/core/skills.js.map +1 -0
  168. package/dist/core/slash-commands.d.ts +15 -0
  169. package/dist/core/slash-commands.d.ts.map +1 -0
  170. package/dist/core/slash-commands.js +21 -0
  171. package/dist/core/slash-commands.js.map +1 -0
  172. package/dist/core/system-prompt.d.ts +24 -0
  173. package/dist/core/system-prompt.d.ts.map +1 -0
  174. package/dist/core/system-prompt.js +137 -0
  175. package/dist/core/system-prompt.js.map +1 -0
  176. package/dist/core/timings.d.ts +7 -0
  177. package/dist/core/timings.d.ts.map +1 -0
  178. package/dist/core/timings.js +25 -0
  179. package/dist/core/timings.js.map +1 -0
  180. package/dist/core/tools/bash.d.ts +55 -0
  181. package/dist/core/tools/bash.d.ts.map +1 -0
  182. package/dist/core/tools/bash.js +242 -0
  183. package/dist/core/tools/bash.js.map +1 -0
  184. package/dist/core/tools/edit-diff.d.ts +63 -0
  185. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  186. package/dist/core/tools/edit-diff.js +243 -0
  187. package/dist/core/tools/edit-diff.js.map +1 -0
  188. package/dist/core/tools/edit.d.ts +39 -0
  189. package/dist/core/tools/edit.d.ts.map +1 -0
  190. package/dist/core/tools/edit.js +146 -0
  191. package/dist/core/tools/edit.js.map +1 -0
  192. package/dist/core/tools/find.d.ts +39 -0
  193. package/dist/core/tools/find.d.ts.map +1 -0
  194. package/dist/core/tools/find.js +206 -0
  195. package/dist/core/tools/find.js.map +1 -0
  196. package/dist/core/tools/grep.d.ts +45 -0
  197. package/dist/core/tools/grep.d.ts.map +1 -0
  198. package/dist/core/tools/grep.js +239 -0
  199. package/dist/core/tools/grep.js.map +1 -0
  200. package/dist/core/tools/index.d.ts +73 -0
  201. package/dist/core/tools/index.d.ts.map +1 -0
  202. package/dist/core/tools/index.js +61 -0
  203. package/dist/core/tools/index.js.map +1 -0
  204. package/dist/core/tools/ls.d.ts +40 -0
  205. package/dist/core/tools/ls.d.ts.map +1 -0
  206. package/dist/core/tools/ls.js +118 -0
  207. package/dist/core/tools/ls.js.map +1 -0
  208. package/dist/core/tools/path-utils.d.ts +8 -0
  209. package/dist/core/tools/path-utils.d.ts.map +1 -0
  210. package/dist/core/tools/path-utils.js +81 -0
  211. package/dist/core/tools/path-utils.js.map +1 -0
  212. package/dist/core/tools/read.d.ts +39 -0
  213. package/dist/core/tools/read.d.ts.map +1 -0
  214. package/dist/core/tools/read.js +166 -0
  215. package/dist/core/tools/read.js.map +1 -0
  216. package/dist/core/tools/truncate.d.ts +70 -0
  217. package/dist/core/tools/truncate.d.ts.map +1 -0
  218. package/dist/core/tools/truncate.js +205 -0
  219. package/dist/core/tools/truncate.js.map +1 -0
  220. package/dist/core/tools/write.d.ts +29 -0
  221. package/dist/core/tools/write.d.ts.map +1 -0
  222. package/dist/core/tools/write.js +78 -0
  223. package/dist/core/tools/write.js.map +1 -0
  224. package/dist/index.d.ts +27 -0
  225. package/dist/index.d.ts.map +1 -0
  226. package/dist/index.js +42 -0
  227. package/dist/index.js.map +1 -0
  228. package/dist/main.d.ts +8 -0
  229. package/dist/main.d.ts.map +1 -0
  230. package/dist/main.js +623 -0
  231. package/dist/main.js.map +1 -0
  232. package/dist/migrations.d.ts +33 -0
  233. package/dist/migrations.d.ts.map +1 -0
  234. package/dist/migrations.js +261 -0
  235. package/dist/migrations.js.map +1 -0
  236. package/dist/modes/index.d.ts +9 -0
  237. package/dist/modes/index.d.ts.map +1 -0
  238. package/dist/modes/index.js +8 -0
  239. package/dist/modes/index.js.map +1 -0
  240. package/dist/modes/interactive/components/armin.d.ts +34 -0
  241. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  242. package/dist/modes/interactive/components/armin.js +333 -0
  243. package/dist/modes/interactive/components/armin.js.map +1 -0
  244. package/dist/modes/interactive/components/assistant-message.d.ts +16 -0
  245. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  246. package/dist/modes/interactive/components/assistant-message.js +91 -0
  247. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  248. package/dist/modes/interactive/components/bash-execution.d.ts +35 -0
  249. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  250. package/dist/modes/interactive/components/bash-execution.js +162 -0
  251. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  252. package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  253. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  254. package/dist/modes/interactive/components/bordered-loader.js +51 -0
  255. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  256. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  257. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  258. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  259. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  260. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  261. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  262. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  263. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  264. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  265. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/config-selector.js +479 -0
  267. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  268. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  269. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  270. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  271. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  272. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  273. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  274. package/dist/modes/interactive/components/custom-editor.js +70 -0
  275. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  276. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  277. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  278. package/dist/modes/interactive/components/custom-message.js +79 -0
  279. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  280. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  281. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  282. package/dist/modes/interactive/components/daxnuts.js +140 -0
  283. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  284. package/dist/modes/interactive/components/diff.d.ts +12 -0
  285. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  286. package/dist/modes/interactive/components/diff.js +133 -0
  287. package/dist/modes/interactive/components/diff.js.map +1 -0
  288. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  289. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  290. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  291. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  292. package/dist/modes/interactive/components/extension-editor.d.ts +17 -0
  293. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  294. package/dist/modes/interactive/components/extension-editor.js +102 -0
  295. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  296. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  297. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  298. package/dist/modes/interactive/components/extension-input.js +61 -0
  299. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  300. package/dist/modes/interactive/components/extension-selector.d.ts +24 -0
  301. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  302. package/dist/modes/interactive/components/extension-selector.js +78 -0
  303. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  304. package/dist/modes/interactive/components/footer.d.ts +26 -0
  305. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  306. package/dist/modes/interactive/components/footer.js +220 -0
  307. package/dist/modes/interactive/components/footer.js.map +1 -0
  308. package/dist/modes/interactive/components/index.d.ts +32 -0
  309. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  310. package/dist/modes/interactive/components/index.js +33 -0
  311. package/dist/modes/interactive/components/index.js.map +1 -0
  312. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  313. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  314. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  315. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  316. package/dist/modes/interactive/components/login-dialog.d.ts +42 -0
  317. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  318. package/dist/modes/interactive/components/login-dialog.js +145 -0
  319. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  320. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  321. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  322. package/dist/modes/interactive/components/model-selector.js +266 -0
  323. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  324. package/dist/modes/interactive/components/oauth-selector.d.ts +19 -0
  325. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  326. package/dist/modes/interactive/components/oauth-selector.js +97 -0
  327. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  328. package/dist/modes/interactive/components/scoped-models-selector.d.ts +49 -0
  329. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  330. package/dist/modes/interactive/components/scoped-models-selector.js +270 -0
  331. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  332. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  333. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  334. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  335. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  336. package/dist/modes/interactive/components/session-selector.d.ts +95 -0
  337. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  338. package/dist/modes/interactive/components/session-selector.js +851 -0
  339. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  340. package/dist/modes/interactive/components/settings-selector.d.ts +53 -0
  341. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  342. package/dist/modes/interactive/components/settings-selector.js +277 -0
  343. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  344. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  345. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  346. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  347. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  348. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  349. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  350. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  351. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  352. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  353. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  354. package/dist/modes/interactive/components/theme-selector.js +46 -0
  355. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  356. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  357. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  358. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  359. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  360. package/dist/modes/interactive/components/tool-execution.d.ts +70 -0
  361. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  362. package/dist/modes/interactive/components/tool-execution.js +621 -0
  363. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  364. package/dist/modes/interactive/components/tree-selector.d.ts +68 -0
  365. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  366. package/dist/modes/interactive/components/tree-selector.js +934 -0
  367. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  368. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  369. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  370. package/dist/modes/interactive/components/user-message-selector.js +113 -0
  371. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  372. package/dist/modes/interactive/components/user-message.d.ts +8 -0
  373. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  374. package/dist/modes/interactive/components/user-message.js +16 -0
  375. package/dist/modes/interactive/components/user-message.js.map +1 -0
  376. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  377. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  378. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  379. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  380. package/dist/modes/interactive/interactive-mode.d.ts +313 -0
  381. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  382. package/dist/modes/interactive/interactive-mode.js +3664 -0
  383. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  384. package/dist/modes/interactive/theme/dark.json +85 -0
  385. package/dist/modes/interactive/theme/light.json +84 -0
  386. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  387. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  388. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  389. package/dist/modes/interactive/theme/theme.js +944 -0
  390. package/dist/modes/interactive/theme/theme.js.map +1 -0
  391. package/dist/modes/print-mode.d.ts +28 -0
  392. package/dist/modes/print-mode.d.ts.map +1 -0
  393. package/dist/modes/print-mode.js +98 -0
  394. package/dist/modes/print-mode.js.map +1 -0
  395. package/dist/modes/rpc/rpc-client.d.ts +217 -0
  396. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  397. package/dist/modes/rpc/rpc-client.js +405 -0
  398. package/dist/modes/rpc/rpc-client.js.map +1 -0
  399. package/dist/modes/rpc/rpc-mode.d.ts +20 -0
  400. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  401. package/dist/modes/rpc/rpc-mode.js +500 -0
  402. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  403. package/dist/modes/rpc/rpc-types.d.ts +409 -0
  404. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  405. package/dist/modes/rpc/rpc-types.js +8 -0
  406. package/dist/modes/rpc/rpc-types.js.map +1 -0
  407. package/dist/utils/changelog.d.ts +21 -0
  408. package/dist/utils/changelog.d.ts.map +1 -0
  409. package/dist/utils/changelog.js +87 -0
  410. package/dist/utils/changelog.js.map +1 -0
  411. package/dist/utils/clipboard-image.d.ts +11 -0
  412. package/dist/utils/clipboard-image.d.ts.map +1 -0
  413. package/dist/utils/clipboard-image.js +162 -0
  414. package/dist/utils/clipboard-image.js.map +1 -0
  415. package/dist/utils/clipboard-native.d.ts +7 -0
  416. package/dist/utils/clipboard-native.d.ts.map +1 -0
  417. package/dist/utils/clipboard-native.js +14 -0
  418. package/dist/utils/clipboard-native.js.map +1 -0
  419. package/dist/utils/clipboard.d.ts +2 -0
  420. package/dist/utils/clipboard.d.ts.map +1 -0
  421. package/dist/utils/clipboard.js +67 -0
  422. package/dist/utils/clipboard.js.map +1 -0
  423. package/dist/utils/frontmatter.d.ts +8 -0
  424. package/dist/utils/frontmatter.d.ts.map +1 -0
  425. package/dist/utils/frontmatter.js +26 -0
  426. package/dist/utils/frontmatter.js.map +1 -0
  427. package/dist/utils/git.d.ts +2 -0
  428. package/dist/utils/git.d.ts.map +1 -0
  429. package/dist/utils/git.js +6 -0
  430. package/dist/utils/git.js.map +1 -0
  431. package/dist/utils/image-convert.d.ts +9 -0
  432. package/dist/utils/image-convert.d.ts.map +1 -0
  433. package/dist/utils/image-convert.js +35 -0
  434. package/dist/utils/image-convert.js.map +1 -0
  435. package/dist/utils/image-resize.d.ts +36 -0
  436. package/dist/utils/image-resize.d.ts.map +1 -0
  437. package/dist/utils/image-resize.js +181 -0
  438. package/dist/utils/image-resize.js.map +1 -0
  439. package/dist/utils/mime.d.ts +2 -0
  440. package/dist/utils/mime.d.ts.map +1 -0
  441. package/dist/utils/mime.js +26 -0
  442. package/dist/utils/mime.js.map +1 -0
  443. package/dist/utils/photon.d.ts +21 -0
  444. package/dist/utils/photon.d.ts.map +1 -0
  445. package/dist/utils/photon.js +121 -0
  446. package/dist/utils/photon.js.map +1 -0
  447. package/dist/utils/shell.d.ts +26 -0
  448. package/dist/utils/shell.d.ts.map +1 -0
  449. package/dist/utils/shell.js +186 -0
  450. package/dist/utils/shell.js.map +1 -0
  451. package/dist/utils/sleep.d.ts +5 -0
  452. package/dist/utils/sleep.d.ts.map +1 -0
  453. package/dist/utils/sleep.js +17 -0
  454. package/dist/utils/sleep.js.map +1 -0
  455. package/dist/utils/tools-manager.d.ts +3 -0
  456. package/dist/utils/tools-manager.d.ts.map +1 -0
  457. package/dist/utils/tools-manager.js +201 -0
  458. package/dist/utils/tools-manager.js.map +1 -0
  459. package/docs/compaction.md +390 -0
  460. package/docs/custom-provider.md +539 -0
  461. package/docs/development.md +69 -0
  462. package/docs/extensions.md +1827 -0
  463. package/docs/images/doom-extension.png +0 -0
  464. package/docs/images/exy.png +0 -0
  465. package/docs/images/interactive-mode.png +0 -0
  466. package/docs/images/tree-view.png +0 -0
  467. package/docs/json.md +79 -0
  468. package/docs/keybindings.md +174 -0
  469. package/docs/models.md +254 -0
  470. package/docs/packages.md +191 -0
  471. package/docs/prompt-templates.md +67 -0
  472. package/docs/providers.md +168 -0
  473. package/docs/rpc.md +1311 -0
  474. package/docs/sdk.md +957 -0
  475. package/docs/session.md +412 -0
  476. package/docs/settings.md +221 -0
  477. package/docs/shell-aliases.md +13 -0
  478. package/docs/skills.md +227 -0
  479. package/docs/terminal-setup.md +70 -0
  480. package/docs/termux.md +127 -0
  481. package/docs/themes.md +295 -0
  482. package/docs/tree.md +219 -0
  483. package/docs/tui.md +887 -0
  484. package/docs/windows.md +17 -0
  485. package/examples/README.md +25 -0
  486. package/examples/extensions/README.md +202 -0
  487. package/examples/extensions/antigravity-image-gen.ts +413 -0
  488. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  489. package/examples/extensions/bash-spawn-hook.ts +30 -0
  490. package/examples/extensions/bookmark.ts +50 -0
  491. package/examples/extensions/claude-rules.ts +86 -0
  492. package/examples/extensions/commands.ts +72 -0
  493. package/examples/extensions/confirm-destructive.ts +59 -0
  494. package/examples/extensions/custom-compaction.ts +114 -0
  495. package/examples/extensions/custom-footer.ts +64 -0
  496. package/examples/extensions/custom-header.ts +73 -0
  497. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  498. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  499. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  500. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  501. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  502. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  503. package/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  504. package/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  505. package/examples/extensions/dirty-repo-guard.ts +56 -0
  506. package/examples/extensions/doom-overlay/README.md +46 -0
  507. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  508. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  509. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  510. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  511. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  512. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  513. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  514. package/examples/extensions/doom-overlay/index.ts +74 -0
  515. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  516. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  517. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  518. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  519. package/examples/extensions/dynamic-resources/index.ts +15 -0
  520. package/examples/extensions/event-bus.ts +43 -0
  521. package/examples/extensions/file-trigger.ts +41 -0
  522. package/examples/extensions/git-checkpoint.ts +53 -0
  523. package/examples/extensions/handoff.ts +150 -0
  524. package/examples/extensions/hello.ts +25 -0
  525. package/examples/extensions/inline-bash.ts +94 -0
  526. package/examples/extensions/input-transform.ts +43 -0
  527. package/examples/extensions/interactive-shell.ts +196 -0
  528. package/examples/extensions/mac-system-theme.ts +47 -0
  529. package/examples/extensions/message-renderer.ts +59 -0
  530. package/examples/extensions/minimal-mode.ts +426 -0
  531. package/examples/extensions/modal-editor.ts +85 -0
  532. package/examples/extensions/model-status.ts +31 -0
  533. package/examples/extensions/notify.ts +55 -0
  534. package/examples/extensions/overlay-qa-tests.ts +881 -0
  535. package/examples/extensions/overlay-test.ts +150 -0
  536. package/examples/extensions/permission-gate.ts +34 -0
  537. package/examples/extensions/pirate.ts +47 -0
  538. package/examples/extensions/plan-mode/README.md +65 -0
  539. package/examples/extensions/plan-mode/index.ts +340 -0
  540. package/examples/extensions/plan-mode/utils.ts +168 -0
  541. package/examples/extensions/preset.ts +398 -0
  542. package/examples/extensions/protected-paths.ts +30 -0
  543. package/examples/extensions/qna.ts +119 -0
  544. package/examples/extensions/question.ts +264 -0
  545. package/examples/extensions/questionnaire.ts +427 -0
  546. package/examples/extensions/rainbow-editor.ts +88 -0
  547. package/examples/extensions/rpc-demo.ts +124 -0
  548. package/examples/extensions/sandbox/index.ts +318 -0
  549. package/examples/extensions/sandbox/package-lock.json +92 -0
  550. package/examples/extensions/sandbox/package.json +19 -0
  551. package/examples/extensions/send-user-message.ts +97 -0
  552. package/examples/extensions/session-name.ts +27 -0
  553. package/examples/extensions/shutdown-command.ts +63 -0
  554. package/examples/extensions/snake.ts +343 -0
  555. package/examples/extensions/space-invaders.ts +560 -0
  556. package/examples/extensions/ssh.ts +220 -0
  557. package/examples/extensions/status-line.ts +40 -0
  558. package/examples/extensions/subagent/README.md +172 -0
  559. package/examples/extensions/subagent/agents/planner.md +37 -0
  560. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  561. package/examples/extensions/subagent/agents/scout.md +50 -0
  562. package/examples/extensions/subagent/agents/worker.md +24 -0
  563. package/examples/extensions/subagent/agents.ts +127 -0
  564. package/examples/extensions/subagent/index.ts +963 -0
  565. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  566. package/examples/extensions/subagent/prompts/implement.md +10 -0
  567. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  568. package/examples/extensions/summarize.ts +195 -0
  569. package/examples/extensions/system-prompt-header.ts +17 -0
  570. package/examples/extensions/timed-confirm.ts +70 -0
  571. package/examples/extensions/titlebar-spinner.ts +58 -0
  572. package/examples/extensions/todo.ts +299 -0
  573. package/examples/extensions/tool-override.ts +143 -0
  574. package/examples/extensions/tools.ts +146 -0
  575. package/examples/extensions/trigger-compact.ts +40 -0
  576. package/examples/extensions/truncated-tool.ts +192 -0
  577. package/examples/extensions/widget-placement.ts +17 -0
  578. package/examples/extensions/with-deps/index.ts +36 -0
  579. package/examples/extensions/with-deps/package-lock.json +31 -0
  580. package/examples/extensions/with-deps/package.json +22 -0
  581. package/examples/rpc-extension-ui.ts +632 -0
  582. package/examples/sdk/01-minimal.ts +22 -0
  583. package/examples/sdk/02-custom-model.ts +49 -0
  584. package/examples/sdk/03-custom-prompt.ts +55 -0
  585. package/examples/sdk/04-skills.ts +46 -0
  586. package/examples/sdk/05-tools.ts +56 -0
  587. package/examples/sdk/06-extensions.ts +88 -0
  588. package/examples/sdk/07-context-files.ts +40 -0
  589. package/examples/sdk/08-prompt-templates.ts +47 -0
  590. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  591. package/examples/sdk/10-settings.ts +38 -0
  592. package/examples/sdk/11-sessions.ts +48 -0
  593. package/examples/sdk/12-full-control.ts +82 -0
  594. package/examples/sdk/README.md +144 -0
  595. package/package.json +97 -0
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Credential storage for API keys and OAuth tokens.
3
+ * Handles loading, saving, and refreshing credentials from auth.json.
4
+ *
5
+ * Uses file locking to prevent race conditions when multiple pi instances
6
+ * try to refresh tokens simultaneously.
7
+ */
8
+ import { getEnvApiKey, getOAuthApiKey, getOAuthProvider, getOAuthProviders, } from "@mariozechner/pi-ai";
9
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
10
+ import { dirname, join } from "path";
11
+ import lockfile from "proper-lockfile";
12
+ import { getAgentDir } from "../config.js";
13
+ import { resolveConfigValue } from "./resolve-config-value.js";
14
+ /**
15
+ * Credential storage backed by a JSON file.
16
+ */
17
+ export class AuthStorage {
18
+ authPath;
19
+ data = {};
20
+ runtimeOverrides = new Map();
21
+ fallbackResolver;
22
+ constructor(authPath = join(getAgentDir(), "auth.json")) {
23
+ this.authPath = authPath;
24
+ this.reload();
25
+ }
26
+ /**
27
+ * Set a runtime API key override (not persisted to disk).
28
+ * Used for CLI --api-key flag.
29
+ */
30
+ setRuntimeApiKey(provider, apiKey) {
31
+ this.runtimeOverrides.set(provider, apiKey);
32
+ }
33
+ /**
34
+ * Remove a runtime API key override.
35
+ */
36
+ removeRuntimeApiKey(provider) {
37
+ this.runtimeOverrides.delete(provider);
38
+ }
39
+ /**
40
+ * Set a fallback resolver for API keys not found in auth.json or env vars.
41
+ * Used for custom provider keys from models.json.
42
+ */
43
+ setFallbackResolver(resolver) {
44
+ this.fallbackResolver = resolver;
45
+ }
46
+ /**
47
+ * Reload credentials from disk.
48
+ */
49
+ reload() {
50
+ if (!existsSync(this.authPath)) {
51
+ this.data = {};
52
+ return;
53
+ }
54
+ try {
55
+ this.data = JSON.parse(readFileSync(this.authPath, "utf-8"));
56
+ }
57
+ catch {
58
+ this.data = {};
59
+ }
60
+ }
61
+ /**
62
+ * Save credentials to disk.
63
+ */
64
+ save() {
65
+ const dir = dirname(this.authPath);
66
+ if (!existsSync(dir)) {
67
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
68
+ }
69
+ writeFileSync(this.authPath, JSON.stringify(this.data, null, 2), "utf-8");
70
+ chmodSync(this.authPath, 0o600);
71
+ }
72
+ /**
73
+ * Get credential for a provider.
74
+ */
75
+ get(provider) {
76
+ return this.data[provider] ?? undefined;
77
+ }
78
+ /**
79
+ * Set credential for a provider.
80
+ */
81
+ set(provider, credential) {
82
+ this.data[provider] = credential;
83
+ this.save();
84
+ }
85
+ /**
86
+ * Remove credential for a provider.
87
+ */
88
+ remove(provider) {
89
+ delete this.data[provider];
90
+ this.save();
91
+ }
92
+ /**
93
+ * List all providers with credentials.
94
+ */
95
+ list() {
96
+ return Object.keys(this.data);
97
+ }
98
+ /**
99
+ * Check if credentials exist for a provider in auth.json.
100
+ */
101
+ has(provider) {
102
+ return provider in this.data;
103
+ }
104
+ /**
105
+ * Check if any form of auth is configured for a provider.
106
+ * Unlike getApiKey(), this doesn't refresh OAuth tokens.
107
+ */
108
+ hasAuth(provider) {
109
+ if (this.runtimeOverrides.has(provider))
110
+ return true;
111
+ if (this.data[provider])
112
+ return true;
113
+ if (getEnvApiKey(provider))
114
+ return true;
115
+ if (this.fallbackResolver?.(provider))
116
+ return true;
117
+ return false;
118
+ }
119
+ /**
120
+ * Get all credentials (for passing to getOAuthApiKey).
121
+ */
122
+ getAll() {
123
+ return { ...this.data };
124
+ }
125
+ /**
126
+ * Login to an OAuth provider.
127
+ */
128
+ async login(providerId, callbacks) {
129
+ const provider = getOAuthProvider(providerId);
130
+ if (!provider) {
131
+ throw new Error(`Unknown OAuth provider: ${providerId}`);
132
+ }
133
+ const credentials = await provider.login(callbacks);
134
+ this.set(providerId, { type: "oauth", ...credentials });
135
+ }
136
+ /**
137
+ * Logout from a provider.
138
+ */
139
+ logout(provider) {
140
+ this.remove(provider);
141
+ }
142
+ /**
143
+ * Refresh OAuth token with file locking to prevent race conditions.
144
+ * Multiple pi instances may try to refresh simultaneously when tokens expire.
145
+ * This ensures only one instance refreshes while others wait and use the result.
146
+ */
147
+ async refreshOAuthTokenWithLock(providerId) {
148
+ const provider = getOAuthProvider(providerId);
149
+ if (!provider) {
150
+ return null;
151
+ }
152
+ // Ensure auth file exists for locking
153
+ if (!existsSync(this.authPath)) {
154
+ const dir = dirname(this.authPath);
155
+ if (!existsSync(dir)) {
156
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
157
+ }
158
+ writeFileSync(this.authPath, "{}", "utf-8");
159
+ chmodSync(this.authPath, 0o600);
160
+ }
161
+ let release;
162
+ try {
163
+ // Acquire exclusive lock with retry and timeout
164
+ // Use generous retry window to handle slow token endpoints
165
+ release = await lockfile.lock(this.authPath, {
166
+ retries: {
167
+ retries: 10,
168
+ factor: 2,
169
+ minTimeout: 100,
170
+ maxTimeout: 10000,
171
+ randomize: true,
172
+ },
173
+ stale: 30000, // Consider lock stale after 30 seconds
174
+ });
175
+ // Re-read file after acquiring lock - another instance may have refreshed
176
+ this.reload();
177
+ const cred = this.data[providerId];
178
+ if (cred?.type !== "oauth") {
179
+ return null;
180
+ }
181
+ // Check if token is still expired after re-reading
182
+ // (another instance may have already refreshed it)
183
+ if (Date.now() < cred.expires) {
184
+ // Token is now valid - another instance refreshed it
185
+ const apiKey = provider.getApiKey(cred);
186
+ return { apiKey, newCredentials: cred };
187
+ }
188
+ // Token still expired, we need to refresh
189
+ const oauthCreds = {};
190
+ for (const [key, value] of Object.entries(this.data)) {
191
+ if (value.type === "oauth") {
192
+ oauthCreds[key] = value;
193
+ }
194
+ }
195
+ const result = await getOAuthApiKey(providerId, oauthCreds);
196
+ if (result) {
197
+ this.data[providerId] = { type: "oauth", ...result.newCredentials };
198
+ this.save();
199
+ return result;
200
+ }
201
+ return null;
202
+ }
203
+ finally {
204
+ // Always release the lock
205
+ if (release) {
206
+ try {
207
+ await release();
208
+ }
209
+ catch {
210
+ // Ignore unlock errors (lock may have been compromised)
211
+ }
212
+ }
213
+ }
214
+ }
215
+ /**
216
+ * Get API key for a provider.
217
+ * Priority:
218
+ * 1. Runtime override (CLI --api-key)
219
+ * 2. API key from auth.json
220
+ * 3. OAuth token from auth.json (auto-refreshed with locking)
221
+ * 4. Environment variable
222
+ * 5. Fallback resolver (models.json custom providers)
223
+ */
224
+ async getApiKey(providerId) {
225
+ // Runtime override takes highest priority
226
+ const runtimeKey = this.runtimeOverrides.get(providerId);
227
+ if (runtimeKey) {
228
+ return runtimeKey;
229
+ }
230
+ const cred = this.data[providerId];
231
+ if (cred?.type === "api_key") {
232
+ return resolveConfigValue(cred.key);
233
+ }
234
+ if (cred?.type === "oauth") {
235
+ const provider = getOAuthProvider(providerId);
236
+ if (!provider) {
237
+ // Unknown OAuth provider, can't get API key
238
+ return undefined;
239
+ }
240
+ // Check if token needs refresh
241
+ const needsRefresh = Date.now() >= cred.expires;
242
+ if (needsRefresh) {
243
+ // Use locked refresh to prevent race conditions
244
+ try {
245
+ const result = await this.refreshOAuthTokenWithLock(providerId);
246
+ if (result) {
247
+ return result.apiKey;
248
+ }
249
+ }
250
+ catch {
251
+ // Refresh failed - re-read file to check if another instance succeeded
252
+ this.reload();
253
+ const updatedCred = this.data[providerId];
254
+ if (updatedCred?.type === "oauth" && Date.now() < updatedCred.expires) {
255
+ // Another instance refreshed successfully, use those credentials
256
+ return provider.getApiKey(updatedCred);
257
+ }
258
+ // Refresh truly failed - return undefined so model discovery skips this provider
259
+ // User can /login to re-authenticate (credentials preserved for retry)
260
+ return undefined;
261
+ }
262
+ }
263
+ else {
264
+ // Token not expired, use current access token
265
+ return provider.getApiKey(cred);
266
+ }
267
+ }
268
+ // Fall back to environment variable
269
+ const envKey = getEnvApiKey(providerId);
270
+ if (envKey)
271
+ return envKey;
272
+ // Fall back to custom resolver (e.g., models.json custom providers)
273
+ return this.fallbackResolver?.(providerId) ?? undefined;
274
+ }
275
+ /**
276
+ * Get all registered OAuth providers
277
+ */
278
+ getOAuthProviders() {
279
+ return getOAuthProviders();
280
+ }
281
+ }
282
+ //# sourceMappingURL=auth-storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-storage.js","sourceRoot":"","sources":["../../src/core/auth-storage.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Credential storage for API keys and OAuth tokens.\n * Handles loading, saving, and refreshing credentials from auth.json.\n *\n * Uses file locking to prevent race conditions when multiple pi instances\n * try to refresh tokens simultaneously.\n */\n\nimport {\n\tgetEnvApiKey,\n\tgetOAuthApiKey,\n\tgetOAuthProvider,\n\tgetOAuthProviders,\n\ttype OAuthCredentials,\n\ttype OAuthLoginCallbacks,\n\ttype OAuthProviderId,\n} from \"@mariozechner/pi-ai\";\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { dirname, join } from \"path\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentDir } from \"../config.js\";\nimport { resolveConfigValue } from \"./resolve-config-value.js\";\n\nexport type ApiKeyCredential = {\n\ttype: \"api_key\";\n\tkey: string;\n};\n\nexport type OAuthCredential = {\n\ttype: \"oauth\";\n} & OAuthCredentials;\n\nexport type AuthCredential = ApiKeyCredential | OAuthCredential;\n\nexport type AuthStorageData = Record<string, AuthCredential>;\n\n/**\n * Credential storage backed by a JSON file.\n */\nexport class AuthStorage {\n\tprivate data: AuthStorageData = {};\n\tprivate runtimeOverrides: Map<string, string> = new Map();\n\tprivate fallbackResolver?: (provider: string) => string | undefined;\n\n\tconstructor(private authPath: string = join(getAgentDir(), \"auth.json\")) {\n\t\tthis.reload();\n\t}\n\n\t/**\n\t * Set a runtime API key override (not persisted to disk).\n\t * Used for CLI --api-key flag.\n\t */\n\tsetRuntimeApiKey(provider: string, apiKey: string): void {\n\t\tthis.runtimeOverrides.set(provider, apiKey);\n\t}\n\n\t/**\n\t * Remove a runtime API key override.\n\t */\n\tremoveRuntimeApiKey(provider: string): void {\n\t\tthis.runtimeOverrides.delete(provider);\n\t}\n\n\t/**\n\t * Set a fallback resolver for API keys not found in auth.json or env vars.\n\t * Used for custom provider keys from models.json.\n\t */\n\tsetFallbackResolver(resolver: (provider: string) => string | undefined): void {\n\t\tthis.fallbackResolver = resolver;\n\t}\n\n\t/**\n\t * Reload credentials from disk.\n\t */\n\treload(): void {\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tthis.data = {};\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tthis.data = JSON.parse(readFileSync(this.authPath, \"utf-8\"));\n\t\t} catch {\n\t\t\tthis.data = {};\n\t\t}\n\t}\n\n\t/**\n\t * Save credentials to disk.\n\t */\n\tprivate save(): void {\n\t\tconst dir = dirname(this.authPath);\n\t\tif (!existsSync(dir)) {\n\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t}\n\t\twriteFileSync(this.authPath, JSON.stringify(this.data, null, 2), \"utf-8\");\n\t\tchmodSync(this.authPath, 0o600);\n\t}\n\n\t/**\n\t * Get credential for a provider.\n\t */\n\tget(provider: string): AuthCredential | undefined {\n\t\treturn this.data[provider] ?? undefined;\n\t}\n\n\t/**\n\t * Set credential for a provider.\n\t */\n\tset(provider: string, credential: AuthCredential): void {\n\t\tthis.data[provider] = credential;\n\t\tthis.save();\n\t}\n\n\t/**\n\t * Remove credential for a provider.\n\t */\n\tremove(provider: string): void {\n\t\tdelete this.data[provider];\n\t\tthis.save();\n\t}\n\n\t/**\n\t * List all providers with credentials.\n\t */\n\tlist(): string[] {\n\t\treturn Object.keys(this.data);\n\t}\n\n\t/**\n\t * Check if credentials exist for a provider in auth.json.\n\t */\n\thas(provider: string): boolean {\n\t\treturn provider in this.data;\n\t}\n\n\t/**\n\t * Check if any form of auth is configured for a provider.\n\t * Unlike getApiKey(), this doesn't refresh OAuth tokens.\n\t */\n\thasAuth(provider: string): boolean {\n\t\tif (this.runtimeOverrides.has(provider)) return true;\n\t\tif (this.data[provider]) return true;\n\t\tif (getEnvApiKey(provider)) return true;\n\t\tif (this.fallbackResolver?.(provider)) return true;\n\t\treturn false;\n\t}\n\n\t/**\n\t * Get all credentials (for passing to getOAuthApiKey).\n\t */\n\tgetAll(): AuthStorageData {\n\t\treturn { ...this.data };\n\t}\n\n\t/**\n\t * Login to an OAuth provider.\n\t */\n\tasync login(providerId: OAuthProviderId, callbacks: OAuthLoginCallbacks): Promise<void> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t\t}\n\n\t\tconst credentials = await provider.login(callbacks);\n\t\tthis.set(providerId, { type: \"oauth\", ...credentials });\n\t}\n\n\t/**\n\t * Logout from a provider.\n\t */\n\tlogout(provider: string): void {\n\t\tthis.remove(provider);\n\t}\n\n\t/**\n\t * Refresh OAuth token with file locking to prevent race conditions.\n\t * Multiple pi instances may try to refresh simultaneously when tokens expire.\n\t * This ensures only one instance refreshes while others wait and use the result.\n\t */\n\tprivate async refreshOAuthTokenWithLock(\n\t\tproviderId: OAuthProviderId,\n\t): Promise<{ apiKey: string; newCredentials: OAuthCredentials } | null> {\n\t\tconst provider = getOAuthProvider(providerId);\n\t\tif (!provider) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Ensure auth file exists for locking\n\t\tif (!existsSync(this.authPath)) {\n\t\t\tconst dir = dirname(this.authPath);\n\t\t\tif (!existsSync(dir)) {\n\t\t\t\tmkdirSync(dir, { recursive: true, mode: 0o700 });\n\t\t\t}\n\t\t\twriteFileSync(this.authPath, \"{}\", \"utf-8\");\n\t\t\tchmodSync(this.authPath, 0o600);\n\t\t}\n\n\t\tlet release: (() => Promise<void>) | undefined;\n\n\t\ttry {\n\t\t\t// Acquire exclusive lock with retry and timeout\n\t\t\t// Use generous retry window to handle slow token endpoints\n\t\t\trelease = await lockfile.lock(this.authPath, {\n\t\t\t\tretries: {\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tfactor: 2,\n\t\t\t\t\tminTimeout: 100,\n\t\t\t\t\tmaxTimeout: 10000,\n\t\t\t\t\trandomize: true,\n\t\t\t\t},\n\t\t\t\tstale: 30000, // Consider lock stale after 30 seconds\n\t\t\t});\n\n\t\t\t// Re-read file after acquiring lock - another instance may have refreshed\n\t\t\tthis.reload();\n\n\t\t\tconst cred = this.data[providerId];\n\t\t\tif (cred?.type !== \"oauth\") {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Check if token is still expired after re-reading\n\t\t\t// (another instance may have already refreshed it)\n\t\t\tif (Date.now() < cred.expires) {\n\t\t\t\t// Token is now valid - another instance refreshed it\n\t\t\t\tconst apiKey = provider.getApiKey(cred);\n\t\t\t\treturn { apiKey, newCredentials: cred };\n\t\t\t}\n\n\t\t\t// Token still expired, we need to refresh\n\t\t\tconst oauthCreds: Record<string, OAuthCredentials> = {};\n\t\t\tfor (const [key, value] of Object.entries(this.data)) {\n\t\t\t\tif (value.type === \"oauth\") {\n\t\t\t\t\toauthCreds[key] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst result = await getOAuthApiKey(providerId, oauthCreds);\n\t\t\tif (result) {\n\t\t\t\tthis.data[providerId] = { type: \"oauth\", ...result.newCredentials };\n\t\t\t\tthis.save();\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\treturn null;\n\t\t} finally {\n\t\t\t// Always release the lock\n\t\t\tif (release) {\n\t\t\t\ttry {\n\t\t\t\t\tawait release();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore unlock errors (lock may have been compromised)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Get API key for a provider.\n\t * Priority:\n\t * 1. Runtime override (CLI --api-key)\n\t * 2. API key from auth.json\n\t * 3. OAuth token from auth.json (auto-refreshed with locking)\n\t * 4. Environment variable\n\t * 5. Fallback resolver (models.json custom providers)\n\t */\n\tasync getApiKey(providerId: string): Promise<string | undefined> {\n\t\t// Runtime override takes highest priority\n\t\tconst runtimeKey = this.runtimeOverrides.get(providerId);\n\t\tif (runtimeKey) {\n\t\t\treturn runtimeKey;\n\t\t}\n\n\t\tconst cred = this.data[providerId];\n\n\t\tif (cred?.type === \"api_key\") {\n\t\t\treturn resolveConfigValue(cred.key);\n\t\t}\n\n\t\tif (cred?.type === \"oauth\") {\n\t\t\tconst provider = getOAuthProvider(providerId);\n\t\t\tif (!provider) {\n\t\t\t\t// Unknown OAuth provider, can't get API key\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// Check if token needs refresh\n\t\t\tconst needsRefresh = Date.now() >= cred.expires;\n\n\t\t\tif (needsRefresh) {\n\t\t\t\t// Use locked refresh to prevent race conditions\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.refreshOAuthTokenWithLock(providerId);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\treturn result.apiKey;\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Refresh failed - re-read file to check if another instance succeeded\n\t\t\t\t\tthis.reload();\n\t\t\t\t\tconst updatedCred = this.data[providerId];\n\n\t\t\t\t\tif (updatedCred?.type === \"oauth\" && Date.now() < updatedCred.expires) {\n\t\t\t\t\t\t// Another instance refreshed successfully, use those credentials\n\t\t\t\t\t\treturn provider.getApiKey(updatedCred);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Refresh truly failed - return undefined so model discovery skips this provider\n\t\t\t\t\t// User can /login to re-authenticate (credentials preserved for retry)\n\t\t\t\t\treturn undefined;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Token not expired, use current access token\n\t\t\t\treturn provider.getApiKey(cred);\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to environment variable\n\t\tconst envKey = getEnvApiKey(providerId);\n\t\tif (envKey) return envKey;\n\n\t\t// Fall back to custom resolver (e.g., models.json custom providers)\n\t\treturn this.fallbackResolver?.(providerId) ?? undefined;\n\t}\n\n\t/**\n\t * Get all registered OAuth providers\n\t */\n\tgetOAuthProviders() {\n\t\treturn getOAuthProviders();\n\t}\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Bash command execution with streaming support and cancellation.
3
+ *
4
+ * This module provides a unified bash execution implementation used by:
5
+ * - AgentSession.executeBash() for interactive and RPC modes
6
+ * - Direct calls from modes that need bash execution
7
+ */
8
+ import type { BashOperations } from "./tools/bash.js";
9
+ export interface BashExecutorOptions {
10
+ /** Callback for streaming output chunks (already sanitized) */
11
+ onChunk?: (chunk: string) => void;
12
+ /** AbortSignal for cancellation */
13
+ signal?: AbortSignal;
14
+ }
15
+ export interface BashResult {
16
+ /** Combined stdout + stderr output (sanitized, possibly truncated) */
17
+ output: string;
18
+ /** Process exit code (undefined if killed/cancelled) */
19
+ exitCode: number | undefined;
20
+ /** Whether the command was cancelled via signal */
21
+ cancelled: boolean;
22
+ /** Whether the output was truncated */
23
+ truncated: boolean;
24
+ /** Path to temp file containing full output (if output exceeded truncation threshold) */
25
+ fullOutputPath?: string;
26
+ }
27
+ /**
28
+ * Execute a bash command with optional streaming and cancellation support.
29
+ *
30
+ * Features:
31
+ * - Streams sanitized output via onChunk callback
32
+ * - Writes large output to temp file for later retrieval
33
+ * - Supports cancellation via AbortSignal
34
+ * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)
35
+ * - Truncates output if it exceeds the default max bytes
36
+ *
37
+ * @param command - The bash command to execute
38
+ * @param options - Optional streaming callback and abort signal
39
+ * @returns Promise resolving to execution result
40
+ */
41
+ export declare function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult>;
42
+ /**
43
+ * Execute a bash command using custom BashOperations.
44
+ * Used for remote execution (SSH, containers, etc.).
45
+ */
46
+ export declare function executeBashWithOperations(command: string, cwd: string, operations: BashOperations, options?: BashExecutorOptions): Promise<BashResult>;
47
+ //# sourceMappingURL=bash-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-executor.d.ts","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOtD,MAAM,WAAW,mBAAmB;IACnC,+DAA+D;IAC/D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IAC1B,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,mDAAmD;IACnD,SAAS,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,SAAS,EAAE,OAAO,CAAC;IACnB,yFAAyF;IACzF,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,UAAU,CAAC,CAwH/F;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC9C,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,cAAc,EAC1B,OAAO,CAAC,EAAE,mBAAmB,GAC3B,OAAO,CAAC,UAAU,CAAC,CAsFrB","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command with optional streaming and cancellation support.\n *\n * Features:\n * - Streams sanitized output via onChunk callback\n * - Writes large output to temp file for later retrieval\n * - Supports cancellation via AbortSignal\n * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)\n * - Truncates output if it exceeds the default max bytes\n *\n * @param command - The bash command to execute\n * @param options - Optional streaming callback and abort signal\n * @returns Promise resolving to execution result\n */\nexport function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst child: ChildProcess = spawn(shell, [...args, command], {\n\t\t\tdetached: true,\n\t\t\tenv: getShellEnv(),\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Track sanitized output for truncation\n\t\tconst outputChunks: string[] = [];\n\t\tlet outputBytes = 0;\n\t\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t// Temp file for large output\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: WriteStream | undefined;\n\t\tlet totalBytes = 0;\n\n\t\t// Handle abort signal\n\t\tconst abortHandler = () => {\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\t// Already aborted, don't even start\n\t\t\t\tchild.kill();\n\t\t\t\tresolve({\n\t\t\t\t\toutput: \"\",\n\t\t\t\t\texitCode: undefined,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: false,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toptions.signal.addEventListener(\"abort\", abortHandler, { once: true });\n\t\t}\n\n\t\tconst decoder = new TextDecoder();\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\ttotalBytes += data.length;\n\n\t\t\t// Sanitize once at the source: strip ANSI, replace binary garbage, normalize newlines\n\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t\t// Start writing to temp file if exceeds threshold\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t// Write already-buffered chunks to temp file\n\t\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(text);\n\t\t\t}\n\n\t\t\t// Keep rolling buffer of sanitized text\n\t\t\toutputChunks.push(text);\n\t\t\toutputBytes += text.length;\n\t\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\t\tconst removed = outputChunks.shift()!;\n\t\t\t\toutputBytes -= removed.length;\n\t\t\t}\n\n\t\t\t// Stream to callback if provided\n\t\t\tif (options?.onChunk) {\n\t\t\t\toptions.onChunk(text);\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Combine buffered chunks for truncation (already sanitized)\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\n\t\t\t// code === null means killed (cancelled)\n\t\t\tconst cancelled = code === null;\n\n\t\t\tresolve({\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: cancelled ? undefined : code,\n\t\t\t\tcancelled,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\treject(err);\n\t\t});\n\t});\n}\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Bash command execution with streaming support and cancellation.
3
+ *
4
+ * This module provides a unified bash execution implementation used by:
5
+ * - AgentSession.executeBash() for interactive and RPC modes
6
+ * - Direct calls from modes that need bash execution
7
+ */
8
+ import { randomBytes } from "node:crypto";
9
+ import { createWriteStream } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { spawn } from "child_process";
13
+ import stripAnsi from "strip-ansi";
14
+ import { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from "../utils/shell.js";
15
+ import { DEFAULT_MAX_BYTES, truncateTail } from "./tools/truncate.js";
16
+ // ============================================================================
17
+ // Implementation
18
+ // ============================================================================
19
+ /**
20
+ * Execute a bash command with optional streaming and cancellation support.
21
+ *
22
+ * Features:
23
+ * - Streams sanitized output via onChunk callback
24
+ * - Writes large output to temp file for later retrieval
25
+ * - Supports cancellation via AbortSignal
26
+ * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)
27
+ * - Truncates output if it exceeds the default max bytes
28
+ *
29
+ * @param command - The bash command to execute
30
+ * @param options - Optional streaming callback and abort signal
31
+ * @returns Promise resolving to execution result
32
+ */
33
+ export function executeBash(command, options) {
34
+ return new Promise((resolve, reject) => {
35
+ const { shell, args } = getShellConfig();
36
+ const child = spawn(shell, [...args, command], {
37
+ detached: true,
38
+ env: getShellEnv(),
39
+ stdio: ["ignore", "pipe", "pipe"],
40
+ });
41
+ // Track sanitized output for truncation
42
+ const outputChunks = [];
43
+ let outputBytes = 0;
44
+ const maxOutputBytes = DEFAULT_MAX_BYTES * 2;
45
+ // Temp file for large output
46
+ let tempFilePath;
47
+ let tempFileStream;
48
+ let totalBytes = 0;
49
+ // Handle abort signal
50
+ const abortHandler = () => {
51
+ if (child.pid) {
52
+ killProcessTree(child.pid);
53
+ }
54
+ };
55
+ if (options?.signal) {
56
+ if (options.signal.aborted) {
57
+ // Already aborted, don't even start
58
+ child.kill();
59
+ resolve({
60
+ output: "",
61
+ exitCode: undefined,
62
+ cancelled: true,
63
+ truncated: false,
64
+ });
65
+ return;
66
+ }
67
+ options.signal.addEventListener("abort", abortHandler, { once: true });
68
+ }
69
+ const decoder = new TextDecoder();
70
+ const handleData = (data) => {
71
+ totalBytes += data.length;
72
+ // Sanitize once at the source: strip ANSI, replace binary garbage, normalize newlines
73
+ const text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\r/g, "");
74
+ // Start writing to temp file if exceeds threshold
75
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
76
+ const id = randomBytes(8).toString("hex");
77
+ tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
78
+ tempFileStream = createWriteStream(tempFilePath);
79
+ // Write already-buffered chunks to temp file
80
+ for (const chunk of outputChunks) {
81
+ tempFileStream.write(chunk);
82
+ }
83
+ }
84
+ if (tempFileStream) {
85
+ tempFileStream.write(text);
86
+ }
87
+ // Keep rolling buffer of sanitized text
88
+ outputChunks.push(text);
89
+ outputBytes += text.length;
90
+ while (outputBytes > maxOutputBytes && outputChunks.length > 1) {
91
+ const removed = outputChunks.shift();
92
+ outputBytes -= removed.length;
93
+ }
94
+ // Stream to callback if provided
95
+ if (options?.onChunk) {
96
+ options.onChunk(text);
97
+ }
98
+ };
99
+ child.stdout?.on("data", handleData);
100
+ child.stderr?.on("data", handleData);
101
+ child.on("close", (code) => {
102
+ // Clean up abort listener
103
+ if (options?.signal) {
104
+ options.signal.removeEventListener("abort", abortHandler);
105
+ }
106
+ if (tempFileStream) {
107
+ tempFileStream.end();
108
+ }
109
+ // Combine buffered chunks for truncation (already sanitized)
110
+ const fullOutput = outputChunks.join("");
111
+ const truncationResult = truncateTail(fullOutput);
112
+ // code === null means killed (cancelled)
113
+ const cancelled = code === null;
114
+ resolve({
115
+ output: truncationResult.truncated ? truncationResult.content : fullOutput,
116
+ exitCode: cancelled ? undefined : code,
117
+ cancelled,
118
+ truncated: truncationResult.truncated,
119
+ fullOutputPath: tempFilePath,
120
+ });
121
+ });
122
+ child.on("error", (err) => {
123
+ // Clean up abort listener
124
+ if (options?.signal) {
125
+ options.signal.removeEventListener("abort", abortHandler);
126
+ }
127
+ if (tempFileStream) {
128
+ tempFileStream.end();
129
+ }
130
+ reject(err);
131
+ });
132
+ });
133
+ }
134
+ /**
135
+ * Execute a bash command using custom BashOperations.
136
+ * Used for remote execution (SSH, containers, etc.).
137
+ */
138
+ export async function executeBashWithOperations(command, cwd, operations, options) {
139
+ const outputChunks = [];
140
+ let outputBytes = 0;
141
+ const maxOutputBytes = DEFAULT_MAX_BYTES * 2;
142
+ let tempFilePath;
143
+ let tempFileStream;
144
+ let totalBytes = 0;
145
+ const decoder = new TextDecoder();
146
+ const onData = (data) => {
147
+ totalBytes += data.length;
148
+ // Sanitize: strip ANSI, replace binary garbage, normalize newlines
149
+ const text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\r/g, "");
150
+ // Start writing to temp file if exceeds threshold
151
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
152
+ const id = randomBytes(8).toString("hex");
153
+ tempFilePath = join(tmpdir(), `pi-bash-${id}.log`);
154
+ tempFileStream = createWriteStream(tempFilePath);
155
+ for (const chunk of outputChunks) {
156
+ tempFileStream.write(chunk);
157
+ }
158
+ }
159
+ if (tempFileStream) {
160
+ tempFileStream.write(text);
161
+ }
162
+ // Keep rolling buffer
163
+ outputChunks.push(text);
164
+ outputBytes += text.length;
165
+ while (outputBytes > maxOutputBytes && outputChunks.length > 1) {
166
+ const removed = outputChunks.shift();
167
+ outputBytes -= removed.length;
168
+ }
169
+ // Stream to callback
170
+ if (options?.onChunk) {
171
+ options.onChunk(text);
172
+ }
173
+ };
174
+ try {
175
+ const result = await operations.exec(command, cwd, {
176
+ onData,
177
+ signal: options?.signal,
178
+ });
179
+ if (tempFileStream) {
180
+ tempFileStream.end();
181
+ }
182
+ const fullOutput = outputChunks.join("");
183
+ const truncationResult = truncateTail(fullOutput);
184
+ const cancelled = options?.signal?.aborted ?? false;
185
+ return {
186
+ output: truncationResult.truncated ? truncationResult.content : fullOutput,
187
+ exitCode: cancelled ? undefined : (result.exitCode ?? undefined),
188
+ cancelled,
189
+ truncated: truncationResult.truncated,
190
+ fullOutputPath: tempFilePath,
191
+ };
192
+ }
193
+ catch (err) {
194
+ if (tempFileStream) {
195
+ tempFileStream.end();
196
+ }
197
+ // Check if it was an abort
198
+ if (options?.signal?.aborted) {
199
+ const fullOutput = outputChunks.join("");
200
+ const truncationResult = truncateTail(fullOutput);
201
+ return {
202
+ output: truncationResult.truncated ? truncationResult.content : fullOutput,
203
+ exitCode: undefined,
204
+ cancelled: true,
205
+ truncated: truncationResult.truncated,
206
+ fullOutputPath: tempFilePath,
207
+ };
208
+ }
209
+ throw err;
210
+ }
211
+ }
212
+ //# sourceMappingURL=bash-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-executor.js","sourceRoot":"","sources":["../../src/core/bash-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Bash command execution with streaming support and cancellation.\n *\n * This module provides a unified bash execution implementation used by:\n * - AgentSession.executeBash() for interactive and RPC modes\n * - Direct calls from modes that need bash execution\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { createWriteStream, type WriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { type ChildProcess, spawn } from \"child_process\";\nimport stripAnsi from \"strip-ansi\";\nimport { getShellConfig, getShellEnv, killProcessTree, sanitizeBinaryOutput } from \"../utils/shell.js\";\nimport type { BashOperations } from \"./tools/bash.js\";\nimport { DEFAULT_MAX_BYTES, truncateTail } from \"./tools/truncate.js\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface BashExecutorOptions {\n\t/** Callback for streaming output chunks (already sanitized) */\n\tonChunk?: (chunk: string) => void;\n\t/** AbortSignal for cancellation */\n\tsignal?: AbortSignal;\n}\n\nexport interface BashResult {\n\t/** Combined stdout + stderr output (sanitized, possibly truncated) */\n\toutput: string;\n\t/** Process exit code (undefined if killed/cancelled) */\n\texitCode: number | undefined;\n\t/** Whether the command was cancelled via signal */\n\tcancelled: boolean;\n\t/** Whether the output was truncated */\n\ttruncated: boolean;\n\t/** Path to temp file containing full output (if output exceeded truncation threshold) */\n\tfullOutputPath?: string;\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\n/**\n * Execute a bash command with optional streaming and cancellation support.\n *\n * Features:\n * - Streams sanitized output via onChunk callback\n * - Writes large output to temp file for later retrieval\n * - Supports cancellation via AbortSignal\n * - Sanitizes output (strips ANSI, removes binary garbage, normalizes newlines)\n * - Truncates output if it exceeds the default max bytes\n *\n * @param command - The bash command to execute\n * @param options - Optional streaming callback and abort signal\n * @returns Promise resolving to execution result\n */\nexport function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst { shell, args } = getShellConfig();\n\t\tconst child: ChildProcess = spawn(shell, [...args, command], {\n\t\t\tdetached: true,\n\t\t\tenv: getShellEnv(),\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\t// Track sanitized output for truncation\n\t\tconst outputChunks: string[] = [];\n\t\tlet outputBytes = 0;\n\t\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t// Temp file for large output\n\t\tlet tempFilePath: string | undefined;\n\t\tlet tempFileStream: WriteStream | undefined;\n\t\tlet totalBytes = 0;\n\n\t\t// Handle abort signal\n\t\tconst abortHandler = () => {\n\t\t\tif (child.pid) {\n\t\t\t\tkillProcessTree(child.pid);\n\t\t\t}\n\t\t};\n\n\t\tif (options?.signal) {\n\t\t\tif (options.signal.aborted) {\n\t\t\t\t// Already aborted, don't even start\n\t\t\t\tchild.kill();\n\t\t\t\tresolve({\n\t\t\t\t\toutput: \"\",\n\t\t\t\t\texitCode: undefined,\n\t\t\t\t\tcancelled: true,\n\t\t\t\t\ttruncated: false,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\toptions.signal.addEventListener(\"abort\", abortHandler, { once: true });\n\t\t}\n\n\t\tconst decoder = new TextDecoder();\n\n\t\tconst handleData = (data: Buffer) => {\n\t\t\ttotalBytes += data.length;\n\n\t\t\t// Sanitize once at the source: strip ANSI, replace binary garbage, normalize newlines\n\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t\t// Start writing to temp file if exceeds threshold\n\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t// Write already-buffered chunks to temp file\n\t\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.write(text);\n\t\t\t}\n\n\t\t\t// Keep rolling buffer of sanitized text\n\t\t\toutputChunks.push(text);\n\t\t\toutputBytes += text.length;\n\t\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\t\tconst removed = outputChunks.shift()!;\n\t\t\t\toutputBytes -= removed.length;\n\t\t\t}\n\n\t\t\t// Stream to callback if provided\n\t\t\tif (options?.onChunk) {\n\t\t\t\toptions.onChunk(text);\n\t\t\t}\n\t\t};\n\n\t\tchild.stdout?.on(\"data\", handleData);\n\t\tchild.stderr?.on(\"data\", handleData);\n\n\t\tchild.on(\"close\", (code) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\t// Combine buffered chunks for truncation (already sanitized)\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\n\t\t\t// code === null means killed (cancelled)\n\t\t\tconst cancelled = code === null;\n\n\t\t\tresolve({\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: cancelled ? undefined : code,\n\t\t\t\tcancelled,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t});\n\t\t});\n\n\t\tchild.on(\"error\", (err) => {\n\t\t\t// Clean up abort listener\n\t\t\tif (options?.signal) {\n\t\t\t\toptions.signal.removeEventListener(\"abort\", abortHandler);\n\t\t\t}\n\n\t\t\tif (tempFileStream) {\n\t\t\t\ttempFileStream.end();\n\t\t\t}\n\n\t\t\treject(err);\n\t\t});\n\t});\n}\n\n/**\n * Execute a bash command using custom BashOperations.\n * Used for remote execution (SSH, containers, etc.).\n */\nexport async function executeBashWithOperations(\n\tcommand: string,\n\tcwd: string,\n\toperations: BashOperations,\n\toptions?: BashExecutorOptions,\n): Promise<BashResult> {\n\tconst outputChunks: string[] = [];\n\tlet outputBytes = 0;\n\tconst maxOutputBytes = DEFAULT_MAX_BYTES * 2;\n\n\tlet tempFilePath: string | undefined;\n\tlet tempFileStream: WriteStream | undefined;\n\tlet totalBytes = 0;\n\n\tconst decoder = new TextDecoder();\n\n\tconst onData = (data: Buffer) => {\n\t\ttotalBytes += data.length;\n\n\t\t// Sanitize: strip ANSI, replace binary garbage, normalize newlines\n\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\n\t\t// Start writing to temp file if exceeds threshold\n\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\tconst id = randomBytes(8).toString(\"hex\");\n\t\t\ttempFilePath = join(tmpdir(), `pi-bash-${id}.log`);\n\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\tfor (const chunk of outputChunks) {\n\t\t\t\ttempFileStream.write(chunk);\n\t\t\t}\n\t\t}\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.write(text);\n\t\t}\n\n\t\t// Keep rolling buffer\n\t\toutputChunks.push(text);\n\t\toutputBytes += text.length;\n\t\twhile (outputBytes > maxOutputBytes && outputChunks.length > 1) {\n\t\t\tconst removed = outputChunks.shift()!;\n\t\t\toutputBytes -= removed.length;\n\t\t}\n\n\t\t// Stream to callback\n\t\tif (options?.onChunk) {\n\t\t\toptions.onChunk(text);\n\t\t}\n\t};\n\n\ttry {\n\t\tconst result = await operations.exec(command, cwd, {\n\t\t\tonData,\n\t\t\tsignal: options?.signal,\n\t\t});\n\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\tconst fullOutput = outputChunks.join(\"\");\n\t\tconst truncationResult = truncateTail(fullOutput);\n\t\tconst cancelled = options?.signal?.aborted ?? false;\n\n\t\treturn {\n\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\texitCode: cancelled ? undefined : (result.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: truncationResult.truncated,\n\t\t\tfullOutputPath: tempFilePath,\n\t\t};\n\t} catch (err) {\n\t\tif (tempFileStream) {\n\t\t\ttempFileStream.end();\n\t\t}\n\n\t\t// Check if it was an abort\n\t\tif (options?.signal?.aborted) {\n\t\t\tconst fullOutput = outputChunks.join(\"\");\n\t\t\tconst truncationResult = truncateTail(fullOutput);\n\t\t\treturn {\n\t\t\t\toutput: truncationResult.truncated ? truncationResult.content : fullOutput,\n\t\t\t\texitCode: undefined,\n\t\t\t\tcancelled: true,\n\t\t\t\ttruncated: truncationResult.truncated,\n\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t};\n\t\t}\n\n\t\tthrow err;\n\t}\n}\n"]}