@imdigitalashish/zpi 0.1.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 (604) hide show
  1. package/CHANGELOG.md +2801 -0
  2. package/README.md +95 -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 +293 -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 +571 -0
  32. package/dist/core/agent-session.d.ts.map +1 -0
  33. package/dist/core/agent-session.js +2353 -0
  34. package/dist/core/agent-session.js.map +1 -0
  35. package/dist/core/auth-storage.d.ts +129 -0
  36. package/dist/core/auth-storage.d.ts.map +1 -0
  37. package/dist/core/auth-storage.js +394 -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 +971 -0
  84. package/dist/core/export-html/template.html +54 -0
  85. package/dist/core/export-html/template.js +1586 -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 +402 -0
  99. package/dist/core/extensions/loader.js.map +1 -0
  100. package/dist/core/extensions/runner.d.ts +146 -0
  101. package/dist/core/extensions/runner.d.ts.map +1 -0
  102. package/dist/core/extensions/runner.js +626 -0
  103. package/dist/core/extensions/runner.js.map +1 -0
  104. package/dist/core/extensions/types.d.ts +984 -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/memory.d.ts +64 -0
  125. package/dist/core/memory.d.ts.map +1 -0
  126. package/dist/core/memory.js +247 -0
  127. package/dist/core/memory.js.map +1 -0
  128. package/dist/core/messages.d.ts +77 -0
  129. package/dist/core/messages.d.ts.map +1 -0
  130. package/dist/core/messages.js +149 -0
  131. package/dist/core/messages.js.map +1 -0
  132. package/dist/core/model-registry.d.ts +102 -0
  133. package/dist/core/model-registry.d.ts.map +1 -0
  134. package/dist/core/model-registry.js +515 -0
  135. package/dist/core/model-registry.js.map +1 -0
  136. package/dist/core/model-resolver.d.ts +104 -0
  137. package/dist/core/model-resolver.d.ts.map +1 -0
  138. package/dist/core/model-resolver.js +403 -0
  139. package/dist/core/model-resolver.js.map +1 -0
  140. package/dist/core/package-manager.d.ts +151 -0
  141. package/dist/core/package-manager.d.ts.map +1 -0
  142. package/dist/core/package-manager.js +1426 -0
  143. package/dist/core/package-manager.js.map +1 -0
  144. package/dist/core/prompt-templates.d.ts +50 -0
  145. package/dist/core/prompt-templates.d.ts.map +1 -0
  146. package/dist/core/prompt-templates.js +251 -0
  147. package/dist/core/prompt-templates.js.map +1 -0
  148. package/dist/core/resolve-config-value.d.ts +17 -0
  149. package/dist/core/resolve-config-value.d.ts.map +1 -0
  150. package/dist/core/resolve-config-value.js +59 -0
  151. package/dist/core/resolve-config-value.js.map +1 -0
  152. package/dist/core/resource-loader.d.ts +184 -0
  153. package/dist/core/resource-loader.d.ts.map +1 -0
  154. package/dist/core/resource-loader.js +673 -0
  155. package/dist/core/resource-loader.js.map +1 -0
  156. package/dist/core/sdk.d.ts +90 -0
  157. package/dist/core/sdk.d.ts.map +1 -0
  158. package/dist/core/sdk.js +238 -0
  159. package/dist/core/sdk.js.map +1 -0
  160. package/dist/core/session-manager.d.ts +323 -0
  161. package/dist/core/session-manager.d.ts.map +1 -0
  162. package/dist/core/session-manager.js +1091 -0
  163. package/dist/core/session-manager.js.map +1 -0
  164. package/dist/core/settings-manager.d.ts +230 -0
  165. package/dist/core/settings-manager.d.ts.map +1 -0
  166. package/dist/core/settings-manager.js +656 -0
  167. package/dist/core/settings-manager.js.map +1 -0
  168. package/dist/core/skills.d.ts +58 -0
  169. package/dist/core/skills.d.ts.map +1 -0
  170. package/dist/core/skills.js +364 -0
  171. package/dist/core/skills.js.map +1 -0
  172. package/dist/core/slash-commands.d.ts +15 -0
  173. package/dist/core/slash-commands.d.ts.map +1 -0
  174. package/dist/core/slash-commands.js +23 -0
  175. package/dist/core/slash-commands.js.map +1 -0
  176. package/dist/core/system-prompt.d.ts +26 -0
  177. package/dist/core/system-prompt.d.ts.map +1 -0
  178. package/dist/core/system-prompt.js +150 -0
  179. package/dist/core/system-prompt.js.map +1 -0
  180. package/dist/core/timings.d.ts +7 -0
  181. package/dist/core/timings.d.ts.map +1 -0
  182. package/dist/core/timings.js +25 -0
  183. package/dist/core/timings.js.map +1 -0
  184. package/dist/core/tools/bash.d.ts +55 -0
  185. package/dist/core/tools/bash.d.ts.map +1 -0
  186. package/dist/core/tools/bash.js +242 -0
  187. package/dist/core/tools/bash.js.map +1 -0
  188. package/dist/core/tools/edit-diff.d.ts +63 -0
  189. package/dist/core/tools/edit-diff.d.ts.map +1 -0
  190. package/dist/core/tools/edit-diff.js +243 -0
  191. package/dist/core/tools/edit-diff.js.map +1 -0
  192. package/dist/core/tools/edit.d.ts +39 -0
  193. package/dist/core/tools/edit.d.ts.map +1 -0
  194. package/dist/core/tools/edit.js +146 -0
  195. package/dist/core/tools/edit.js.map +1 -0
  196. package/dist/core/tools/find.d.ts +39 -0
  197. package/dist/core/tools/find.d.ts.map +1 -0
  198. package/dist/core/tools/find.js +206 -0
  199. package/dist/core/tools/find.js.map +1 -0
  200. package/dist/core/tools/grep.d.ts +45 -0
  201. package/dist/core/tools/grep.d.ts.map +1 -0
  202. package/dist/core/tools/grep.js +239 -0
  203. package/dist/core/tools/grep.js.map +1 -0
  204. package/dist/core/tools/index.d.ts +102 -0
  205. package/dist/core/tools/index.d.ts.map +1 -0
  206. package/dist/core/tools/index.js +71 -0
  207. package/dist/core/tools/index.js.map +1 -0
  208. package/dist/core/tools/ls.d.ts +40 -0
  209. package/dist/core/tools/ls.d.ts.map +1 -0
  210. package/dist/core/tools/ls.js +118 -0
  211. package/dist/core/tools/ls.js.map +1 -0
  212. package/dist/core/tools/memory.d.ts +45 -0
  213. package/dist/core/tools/memory.d.ts.map +1 -0
  214. package/dist/core/tools/memory.js +346 -0
  215. package/dist/core/tools/memory.js.map +1 -0
  216. package/dist/core/tools/path-utils.d.ts +8 -0
  217. package/dist/core/tools/path-utils.d.ts.map +1 -0
  218. package/dist/core/tools/path-utils.js +81 -0
  219. package/dist/core/tools/path-utils.js.map +1 -0
  220. package/dist/core/tools/read.d.ts +39 -0
  221. package/dist/core/tools/read.d.ts.map +1 -0
  222. package/dist/core/tools/read.js +166 -0
  223. package/dist/core/tools/read.js.map +1 -0
  224. package/dist/core/tools/truncate.d.ts +70 -0
  225. package/dist/core/tools/truncate.d.ts.map +1 -0
  226. package/dist/core/tools/truncate.js +205 -0
  227. package/dist/core/tools/truncate.js.map +1 -0
  228. package/dist/core/tools/write.d.ts +29 -0
  229. package/dist/core/tools/write.d.ts.map +1 -0
  230. package/dist/core/tools/write.js +78 -0
  231. package/dist/core/tools/write.js.map +1 -0
  232. package/dist/index.d.ts +28 -0
  233. package/dist/index.d.ts.map +1 -0
  234. package/dist/index.js +43 -0
  235. package/dist/index.js.map +1 -0
  236. package/dist/main.d.ts +8 -0
  237. package/dist/main.d.ts.map +1 -0
  238. package/dist/main.js +651 -0
  239. package/dist/main.js.map +1 -0
  240. package/dist/migrations.d.ts +33 -0
  241. package/dist/migrations.d.ts.map +1 -0
  242. package/dist/migrations.js +261 -0
  243. package/dist/migrations.js.map +1 -0
  244. package/dist/modes/index.d.ts +9 -0
  245. package/dist/modes/index.d.ts.map +1 -0
  246. package/dist/modes/index.js +8 -0
  247. package/dist/modes/index.js.map +1 -0
  248. package/dist/modes/interactive/components/armin.d.ts +34 -0
  249. package/dist/modes/interactive/components/armin.d.ts.map +1 -0
  250. package/dist/modes/interactive/components/armin.js +333 -0
  251. package/dist/modes/interactive/components/armin.js.map +1 -0
  252. package/dist/modes/interactive/components/assistant-message.d.ts +16 -0
  253. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  254. package/dist/modes/interactive/components/assistant-message.js +96 -0
  255. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  256. package/dist/modes/interactive/components/bash-execution.d.ts +35 -0
  257. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  258. package/dist/modes/interactive/components/bash-execution.js +162 -0
  259. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  260. package/dist/modes/interactive/components/bordered-loader.d.ts +16 -0
  261. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -0
  262. package/dist/modes/interactive/components/bordered-loader.js +51 -0
  263. package/dist/modes/interactive/components/bordered-loader.js.map +1 -0
  264. package/dist/modes/interactive/components/branch-summary-message.d.ts +16 -0
  265. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -0
  266. package/dist/modes/interactive/components/branch-summary-message.js +44 -0
  267. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -0
  268. package/dist/modes/interactive/components/compaction-summary-message.d.ts +16 -0
  269. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -0
  270. package/dist/modes/interactive/components/compaction-summary-message.js +45 -0
  271. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -0
  272. package/dist/modes/interactive/components/config-selector.d.ts +71 -0
  273. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -0
  274. package/dist/modes/interactive/components/config-selector.js +479 -0
  275. package/dist/modes/interactive/components/config-selector.js.map +1 -0
  276. package/dist/modes/interactive/components/countdown-timer.d.ts +14 -0
  277. package/dist/modes/interactive/components/countdown-timer.d.ts.map +1 -0
  278. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  279. package/dist/modes/interactive/components/countdown-timer.js.map +1 -0
  280. package/dist/modes/interactive/components/custom-editor.d.ts +21 -0
  281. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  282. package/dist/modes/interactive/components/custom-editor.js +70 -0
  283. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  284. package/dist/modes/interactive/components/custom-message.d.ts +20 -0
  285. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -0
  286. package/dist/modes/interactive/components/custom-message.js +79 -0
  287. package/dist/modes/interactive/components/custom-message.js.map +1 -0
  288. package/dist/modes/interactive/components/daxnuts.d.ts +23 -0
  289. package/dist/modes/interactive/components/daxnuts.d.ts.map +1 -0
  290. package/dist/modes/interactive/components/daxnuts.js +140 -0
  291. package/dist/modes/interactive/components/daxnuts.js.map +1 -0
  292. package/dist/modes/interactive/components/diff.d.ts +12 -0
  293. package/dist/modes/interactive/components/diff.d.ts.map +1 -0
  294. package/dist/modes/interactive/components/diff.js +133 -0
  295. package/dist/modes/interactive/components/diff.js.map +1 -0
  296. package/dist/modes/interactive/components/dynamic-border.d.ts +15 -0
  297. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  298. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  299. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  300. package/dist/modes/interactive/components/extension-editor.d.ts +17 -0
  301. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -0
  302. package/dist/modes/interactive/components/extension-editor.js +102 -0
  303. package/dist/modes/interactive/components/extension-editor.js.map +1 -0
  304. package/dist/modes/interactive/components/extension-input.d.ts +23 -0
  305. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -0
  306. package/dist/modes/interactive/components/extension-input.js +61 -0
  307. package/dist/modes/interactive/components/extension-input.js.map +1 -0
  308. package/dist/modes/interactive/components/extension-selector.d.ts +24 -0
  309. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -0
  310. package/dist/modes/interactive/components/extension-selector.js +78 -0
  311. package/dist/modes/interactive/components/extension-selector.js.map +1 -0
  312. package/dist/modes/interactive/components/footer.d.ts +26 -0
  313. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  314. package/dist/modes/interactive/components/footer.js +213 -0
  315. package/dist/modes/interactive/components/footer.js.map +1 -0
  316. package/dist/modes/interactive/components/index.d.ts +32 -0
  317. package/dist/modes/interactive/components/index.d.ts.map +1 -0
  318. package/dist/modes/interactive/components/index.js +33 -0
  319. package/dist/modes/interactive/components/index.js.map +1 -0
  320. package/dist/modes/interactive/components/keybinding-hints.d.ts +41 -0
  321. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -0
  322. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  323. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -0
  324. package/dist/modes/interactive/components/login-dialog.d.ts +42 -0
  325. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -0
  326. package/dist/modes/interactive/components/login-dialog.js +145 -0
  327. package/dist/modes/interactive/components/login-dialog.js.map +1 -0
  328. package/dist/modes/interactive/components/model-selector.d.ts +47 -0
  329. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  330. package/dist/modes/interactive/components/model-selector.js +271 -0
  331. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  332. package/dist/modes/interactive/components/oauth-selector.d.ts +19 -0
  333. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  334. package/dist/modes/interactive/components/oauth-selector.js +97 -0
  335. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  336. package/dist/modes/interactive/components/scoped-models-selector.d.ts +49 -0
  337. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -0
  338. package/dist/modes/interactive/components/scoped-models-selector.js +275 -0
  339. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -0
  340. package/dist/modes/interactive/components/session-selector-search.d.ts +23 -0
  341. package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -0
  342. package/dist/modes/interactive/components/session-selector-search.js +155 -0
  343. package/dist/modes/interactive/components/session-selector-search.js.map +1 -0
  344. package/dist/modes/interactive/components/session-selector.d.ts +95 -0
  345. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  346. package/dist/modes/interactive/components/session-selector.js +851 -0
  347. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  348. package/dist/modes/interactive/components/settings-selector.d.ts +58 -0
  349. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -0
  350. package/dist/modes/interactive/components/settings-selector.js +299 -0
  351. package/dist/modes/interactive/components/settings-selector.js.map +1 -0
  352. package/dist/modes/interactive/components/show-images-selector.d.ts +10 -0
  353. package/dist/modes/interactive/components/show-images-selector.d.ts.map +1 -0
  354. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  355. package/dist/modes/interactive/components/show-images-selector.js.map +1 -0
  356. package/dist/modes/interactive/components/skill-invocation-message.d.ts +17 -0
  357. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -0
  358. package/dist/modes/interactive/components/skill-invocation-message.js +47 -0
  359. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -0
  360. package/dist/modes/interactive/components/theme-selector.d.ts +11 -0
  361. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  362. package/dist/modes/interactive/components/theme-selector.js +46 -0
  363. package/dist/modes/interactive/components/theme-selector.js.map +1 -0
  364. package/dist/modes/interactive/components/thinking-selector.d.ts +11 -0
  365. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  366. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  367. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  368. package/dist/modes/interactive/components/tool-execution.d.ts +70 -0
  369. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  370. package/dist/modes/interactive/components/tool-execution.js +636 -0
  371. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  372. package/dist/modes/interactive/components/tree-selector.d.ts +68 -0
  373. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -0
  374. package/dist/modes/interactive/components/tree-selector.js +934 -0
  375. package/dist/modes/interactive/components/tree-selector.js.map +1 -0
  376. package/dist/modes/interactive/components/user-message-selector.d.ts +30 -0
  377. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  378. package/dist/modes/interactive/components/user-message-selector.js +113 -0
  379. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  380. package/dist/modes/interactive/components/user-message.d.ts +8 -0
  381. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  382. package/dist/modes/interactive/components/user-message.js +16 -0
  383. package/dist/modes/interactive/components/user-message.js.map +1 -0
  384. package/dist/modes/interactive/components/visual-truncate.d.ts +24 -0
  385. package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -0
  386. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  387. package/dist/modes/interactive/components/visual-truncate.js.map +1 -0
  388. package/dist/modes/interactive/interactive-mode.d.ts +316 -0
  389. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  390. package/dist/modes/interactive/interactive-mode.js +3848 -0
  391. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  392. package/dist/modes/interactive/theme/dark.json +85 -0
  393. package/dist/modes/interactive/theme/light.json +84 -0
  394. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  395. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  396. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  397. package/dist/modes/interactive/theme/theme.js +944 -0
  398. package/dist/modes/interactive/theme/theme.js.map +1 -0
  399. package/dist/modes/print-mode.d.ts +28 -0
  400. package/dist/modes/print-mode.d.ts.map +1 -0
  401. package/dist/modes/print-mode.js +101 -0
  402. package/dist/modes/print-mode.js.map +1 -0
  403. package/dist/modes/rpc/rpc-client.d.ts +217 -0
  404. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  405. package/dist/modes/rpc/rpc-client.js +405 -0
  406. package/dist/modes/rpc/rpc-client.js.map +1 -0
  407. package/dist/modes/rpc/rpc-mode.d.ts +20 -0
  408. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  409. package/dist/modes/rpc/rpc-mode.js +511 -0
  410. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  411. package/dist/modes/rpc/rpc-types.d.ts +409 -0
  412. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  413. package/dist/modes/rpc/rpc-types.js +8 -0
  414. package/dist/modes/rpc/rpc-types.js.map +1 -0
  415. package/dist/utils/changelog.d.ts +21 -0
  416. package/dist/utils/changelog.d.ts.map +1 -0
  417. package/dist/utils/changelog.js +87 -0
  418. package/dist/utils/changelog.js.map +1 -0
  419. package/dist/utils/clipboard-image.d.ts +11 -0
  420. package/dist/utils/clipboard-image.d.ts.map +1 -0
  421. package/dist/utils/clipboard-image.js +162 -0
  422. package/dist/utils/clipboard-image.js.map +1 -0
  423. package/dist/utils/clipboard-native.d.ts +7 -0
  424. package/dist/utils/clipboard-native.d.ts.map +1 -0
  425. package/dist/utils/clipboard-native.js +14 -0
  426. package/dist/utils/clipboard-native.js.map +1 -0
  427. package/dist/utils/clipboard.d.ts +2 -0
  428. package/dist/utils/clipboard.d.ts.map +1 -0
  429. package/dist/utils/clipboard.js +67 -0
  430. package/dist/utils/clipboard.js.map +1 -0
  431. package/dist/utils/frontmatter.d.ts +8 -0
  432. package/dist/utils/frontmatter.d.ts.map +1 -0
  433. package/dist/utils/frontmatter.js +26 -0
  434. package/dist/utils/frontmatter.js.map +1 -0
  435. package/dist/utils/git.d.ts +26 -0
  436. package/dist/utils/git.d.ts.map +1 -0
  437. package/dist/utils/git.js +163 -0
  438. package/dist/utils/git.js.map +1 -0
  439. package/dist/utils/image-convert.d.ts +9 -0
  440. package/dist/utils/image-convert.d.ts.map +1 -0
  441. package/dist/utils/image-convert.js +35 -0
  442. package/dist/utils/image-convert.js.map +1 -0
  443. package/dist/utils/image-resize.d.ts +36 -0
  444. package/dist/utils/image-resize.d.ts.map +1 -0
  445. package/dist/utils/image-resize.js +181 -0
  446. package/dist/utils/image-resize.js.map +1 -0
  447. package/dist/utils/mime.d.ts +2 -0
  448. package/dist/utils/mime.d.ts.map +1 -0
  449. package/dist/utils/mime.js +26 -0
  450. package/dist/utils/mime.js.map +1 -0
  451. package/dist/utils/photon.d.ts +21 -0
  452. package/dist/utils/photon.d.ts.map +1 -0
  453. package/dist/utils/photon.js +121 -0
  454. package/dist/utils/photon.js.map +1 -0
  455. package/dist/utils/shell.d.ts +26 -0
  456. package/dist/utils/shell.d.ts.map +1 -0
  457. package/dist/utils/shell.js +186 -0
  458. package/dist/utils/shell.js.map +1 -0
  459. package/dist/utils/sleep.d.ts +5 -0
  460. package/dist/utils/sleep.d.ts.map +1 -0
  461. package/dist/utils/sleep.js +17 -0
  462. package/dist/utils/sleep.js.map +1 -0
  463. package/dist/utils/tools-manager.d.ts +3 -0
  464. package/dist/utils/tools-manager.d.ts.map +1 -0
  465. package/dist/utils/tools-manager.js +207 -0
  466. package/dist/utils/tools-manager.js.map +1 -0
  467. package/docs/compaction.md +390 -0
  468. package/docs/custom-provider.md +548 -0
  469. package/docs/development.md +69 -0
  470. package/docs/extensions.md +1935 -0
  471. package/docs/images/doom-extension.png +0 -0
  472. package/docs/images/exy.png +0 -0
  473. package/docs/images/interactive-mode.png +0 -0
  474. package/docs/images/tree-view.png +0 -0
  475. package/docs/json.md +79 -0
  476. package/docs/keybindings.md +174 -0
  477. package/docs/models.md +293 -0
  478. package/docs/packages.md +209 -0
  479. package/docs/prompt-templates.md +67 -0
  480. package/docs/providers.md +186 -0
  481. package/docs/rpc.md +1317 -0
  482. package/docs/sdk.md +968 -0
  483. package/docs/session.md +412 -0
  484. package/docs/settings.md +223 -0
  485. package/docs/shell-aliases.md +13 -0
  486. package/docs/skills.md +231 -0
  487. package/docs/terminal-setup.md +70 -0
  488. package/docs/termux.md +127 -0
  489. package/docs/themes.md +295 -0
  490. package/docs/tree.md +219 -0
  491. package/docs/tui.md +887 -0
  492. package/docs/windows.md +17 -0
  493. package/examples/README.md +25 -0
  494. package/examples/extensions/README.md +203 -0
  495. package/examples/extensions/antigravity-image-gen.ts +413 -0
  496. package/examples/extensions/auto-commit-on-exit.ts +49 -0
  497. package/examples/extensions/bash-spawn-hook.ts +30 -0
  498. package/examples/extensions/bookmark.ts +50 -0
  499. package/examples/extensions/claude-rules.ts +86 -0
  500. package/examples/extensions/commands.ts +72 -0
  501. package/examples/extensions/confirm-destructive.ts +59 -0
  502. package/examples/extensions/custom-compaction.ts +114 -0
  503. package/examples/extensions/custom-footer.ts +64 -0
  504. package/examples/extensions/custom-header.ts +73 -0
  505. package/examples/extensions/custom-provider-anthropic/index.ts +604 -0
  506. package/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  507. package/examples/extensions/custom-provider-anthropic/package.json +19 -0
  508. package/examples/extensions/custom-provider-gitlab-duo/index.ts +349 -0
  509. package/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  510. package/examples/extensions/custom-provider-gitlab-duo/test.ts +82 -0
  511. package/examples/extensions/custom-provider-qwen-cli/index.ts +345 -0
  512. package/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  513. package/examples/extensions/dirty-repo-guard.ts +56 -0
  514. package/examples/extensions/doom-overlay/README.md +46 -0
  515. package/examples/extensions/doom-overlay/doom/build/doom.js +21 -0
  516. package/examples/extensions/doom-overlay/doom/build/doom.wasm +0 -0
  517. package/examples/extensions/doom-overlay/doom/build.sh +152 -0
  518. package/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  519. package/examples/extensions/doom-overlay/doom-component.ts +132 -0
  520. package/examples/extensions/doom-overlay/doom-engine.ts +173 -0
  521. package/examples/extensions/doom-overlay/doom-keys.ts +104 -0
  522. package/examples/extensions/doom-overlay/index.ts +74 -0
  523. package/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  524. package/examples/extensions/dynamic-resources/SKILL.md +8 -0
  525. package/examples/extensions/dynamic-resources/dynamic.json +79 -0
  526. package/examples/extensions/dynamic-resources/dynamic.md +5 -0
  527. package/examples/extensions/dynamic-resources/index.ts +15 -0
  528. package/examples/extensions/event-bus.ts +43 -0
  529. package/examples/extensions/file-trigger.ts +41 -0
  530. package/examples/extensions/git-checkpoint.ts +53 -0
  531. package/examples/extensions/handoff.ts +150 -0
  532. package/examples/extensions/hello.ts +25 -0
  533. package/examples/extensions/inline-bash.ts +94 -0
  534. package/examples/extensions/input-transform.ts +43 -0
  535. package/examples/extensions/interactive-shell.ts +196 -0
  536. package/examples/extensions/mac-system-theme.ts +47 -0
  537. package/examples/extensions/message-renderer.ts +59 -0
  538. package/examples/extensions/minimal-mode.ts +426 -0
  539. package/examples/extensions/modal-editor.ts +85 -0
  540. package/examples/extensions/model-status.ts +31 -0
  541. package/examples/extensions/notify.ts +55 -0
  542. package/examples/extensions/overlay-qa-tests.ts +881 -0
  543. package/examples/extensions/overlay-test.ts +150 -0
  544. package/examples/extensions/permission-gate.ts +34 -0
  545. package/examples/extensions/pirate.ts +47 -0
  546. package/examples/extensions/plan-mode/README.md +65 -0
  547. package/examples/extensions/plan-mode/index.ts +340 -0
  548. package/examples/extensions/plan-mode/utils.ts +168 -0
  549. package/examples/extensions/preset.ts +398 -0
  550. package/examples/extensions/protected-paths.ts +30 -0
  551. package/examples/extensions/qna.ts +119 -0
  552. package/examples/extensions/question.ts +264 -0
  553. package/examples/extensions/questionnaire.ts +427 -0
  554. package/examples/extensions/rainbow-editor.ts +88 -0
  555. package/examples/extensions/reload-runtime.ts +37 -0
  556. package/examples/extensions/rpc-demo.ts +124 -0
  557. package/examples/extensions/sandbox/index.ts +318 -0
  558. package/examples/extensions/sandbox/package-lock.json +92 -0
  559. package/examples/extensions/sandbox/package.json +19 -0
  560. package/examples/extensions/send-user-message.ts +97 -0
  561. package/examples/extensions/session-name.ts +27 -0
  562. package/examples/extensions/shutdown-command.ts +63 -0
  563. package/examples/extensions/snake.ts +343 -0
  564. package/examples/extensions/space-invaders.ts +560 -0
  565. package/examples/extensions/ssh.ts +220 -0
  566. package/examples/extensions/status-line.ts +40 -0
  567. package/examples/extensions/subagent/README.md +172 -0
  568. package/examples/extensions/subagent/agents/planner.md +37 -0
  569. package/examples/extensions/subagent/agents/reviewer.md +35 -0
  570. package/examples/extensions/subagent/agents/scout.md +50 -0
  571. package/examples/extensions/subagent/agents/worker.md +24 -0
  572. package/examples/extensions/subagent/agents.ts +127 -0
  573. package/examples/extensions/subagent/index.ts +964 -0
  574. package/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  575. package/examples/extensions/subagent/prompts/implement.md +10 -0
  576. package/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  577. package/examples/extensions/summarize.ts +195 -0
  578. package/examples/extensions/system-prompt-header.ts +17 -0
  579. package/examples/extensions/timed-confirm.ts +70 -0
  580. package/examples/extensions/titlebar-spinner.ts +58 -0
  581. package/examples/extensions/todo.ts +299 -0
  582. package/examples/extensions/tool-override.ts +143 -0
  583. package/examples/extensions/tools.ts +146 -0
  584. package/examples/extensions/trigger-compact.ts +40 -0
  585. package/examples/extensions/truncated-tool.ts +192 -0
  586. package/examples/extensions/widget-placement.ts +17 -0
  587. package/examples/extensions/with-deps/index.ts +36 -0
  588. package/examples/extensions/with-deps/package-lock.json +31 -0
  589. package/examples/extensions/with-deps/package.json +22 -0
  590. package/examples/rpc-extension-ui.ts +632 -0
  591. package/examples/sdk/01-minimal.ts +22 -0
  592. package/examples/sdk/02-custom-model.ts +49 -0
  593. package/examples/sdk/03-custom-prompt.ts +55 -0
  594. package/examples/sdk/04-skills.ts +46 -0
  595. package/examples/sdk/05-tools.ts +56 -0
  596. package/examples/sdk/06-extensions.ts +88 -0
  597. package/examples/sdk/07-context-files.ts +40 -0
  598. package/examples/sdk/08-prompt-templates.ts +47 -0
  599. package/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  600. package/examples/sdk/10-settings.ts +51 -0
  601. package/examples/sdk/11-sessions.ts +48 -0
  602. package/examples/sdk/12-full-control.ts +82 -0
  603. package/examples/sdk/README.md +144 -0
  604. package/package.json +96 -0
@@ -0,0 +1,242 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { createWriteStream, existsSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Type } from "@sinclair/typebox";
6
+ import { spawn } from "child_process";
7
+ import { getShellConfig, getShellEnv, killProcessTree } from "../../utils/shell.js";
8
+ import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateTail } from "./truncate.js";
9
+ /**
10
+ * Generate a unique temp file path for bash output
11
+ */
12
+ function getTempFilePath() {
13
+ const id = randomBytes(8).toString("hex");
14
+ return join(tmpdir(), `pi-bash-${id}.log`);
15
+ }
16
+ const bashSchema = Type.Object({
17
+ command: Type.String({ description: "Bash command to execute" }),
18
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (optional, no default timeout)" })),
19
+ });
20
+ /**
21
+ * Default bash operations using local shell
22
+ */
23
+ const defaultBashOperations = {
24
+ exec: (command, cwd, { onData, signal, timeout, env }) => {
25
+ return new Promise((resolve, reject) => {
26
+ const { shell, args } = getShellConfig();
27
+ if (!existsSync(cwd)) {
28
+ reject(new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`));
29
+ return;
30
+ }
31
+ const child = spawn(shell, [...args, command], {
32
+ cwd,
33
+ detached: true,
34
+ env: env ?? getShellEnv(),
35
+ stdio: ["ignore", "pipe", "pipe"],
36
+ });
37
+ let timedOut = false;
38
+ // Set timeout if provided
39
+ let timeoutHandle;
40
+ if (timeout !== undefined && timeout > 0) {
41
+ timeoutHandle = setTimeout(() => {
42
+ timedOut = true;
43
+ if (child.pid) {
44
+ killProcessTree(child.pid);
45
+ }
46
+ }, timeout * 1000);
47
+ }
48
+ // Stream stdout and stderr
49
+ if (child.stdout) {
50
+ child.stdout.on("data", onData);
51
+ }
52
+ if (child.stderr) {
53
+ child.stderr.on("data", onData);
54
+ }
55
+ // Handle shell spawn errors
56
+ child.on("error", (err) => {
57
+ if (timeoutHandle)
58
+ clearTimeout(timeoutHandle);
59
+ if (signal)
60
+ signal.removeEventListener("abort", onAbort);
61
+ reject(err);
62
+ });
63
+ // Handle abort signal - kill entire process tree
64
+ const onAbort = () => {
65
+ if (child.pid) {
66
+ killProcessTree(child.pid);
67
+ }
68
+ };
69
+ if (signal) {
70
+ if (signal.aborted) {
71
+ onAbort();
72
+ }
73
+ else {
74
+ signal.addEventListener("abort", onAbort, { once: true });
75
+ }
76
+ }
77
+ // Handle process exit
78
+ child.on("close", (code) => {
79
+ if (timeoutHandle)
80
+ clearTimeout(timeoutHandle);
81
+ if (signal)
82
+ signal.removeEventListener("abort", onAbort);
83
+ if (signal?.aborted) {
84
+ reject(new Error("aborted"));
85
+ return;
86
+ }
87
+ if (timedOut) {
88
+ reject(new Error(`timeout:${timeout}`));
89
+ return;
90
+ }
91
+ resolve({ exitCode: code });
92
+ });
93
+ });
94
+ },
95
+ };
96
+ function resolveSpawnContext(command, cwd, spawnHook) {
97
+ const baseContext = {
98
+ command,
99
+ cwd,
100
+ env: { ...getShellEnv() },
101
+ };
102
+ return spawnHook ? spawnHook(baseContext) : baseContext;
103
+ }
104
+ export function createBashTool(cwd, options) {
105
+ const ops = options?.operations ?? defaultBashOperations;
106
+ const commandPrefix = options?.commandPrefix;
107
+ const spawnHook = options?.spawnHook;
108
+ return {
109
+ name: "bash",
110
+ label: "bash",
111
+ description: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,
112
+ parameters: bashSchema,
113
+ execute: async (_toolCallId, { command, timeout }, signal, onUpdate) => {
114
+ // Apply command prefix if configured (e.g., "shopt -s expand_aliases" for alias support)
115
+ const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
116
+ const spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);
117
+ return new Promise((resolve, reject) => {
118
+ // We'll stream to a temp file if output gets large
119
+ let tempFilePath;
120
+ let tempFileStream;
121
+ let totalBytes = 0;
122
+ // Keep a rolling buffer of the last chunk for tail truncation
123
+ const chunks = [];
124
+ let chunksBytes = 0;
125
+ // Keep more than we need so we have enough for truncation
126
+ const maxChunksBytes = DEFAULT_MAX_BYTES * 2;
127
+ const handleData = (data) => {
128
+ totalBytes += data.length;
129
+ // Start writing to temp file once we exceed the threshold
130
+ if (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {
131
+ tempFilePath = getTempFilePath();
132
+ tempFileStream = createWriteStream(tempFilePath);
133
+ // Write all buffered chunks to the file
134
+ for (const chunk of chunks) {
135
+ tempFileStream.write(chunk);
136
+ }
137
+ }
138
+ // Write to temp file if we have one
139
+ if (tempFileStream) {
140
+ tempFileStream.write(data);
141
+ }
142
+ // Keep rolling buffer of recent data
143
+ chunks.push(data);
144
+ chunksBytes += data.length;
145
+ // Trim old chunks if buffer is too large
146
+ while (chunksBytes > maxChunksBytes && chunks.length > 1) {
147
+ const removed = chunks.shift();
148
+ chunksBytes -= removed.length;
149
+ }
150
+ // Stream partial output to callback (truncated rolling buffer)
151
+ if (onUpdate) {
152
+ const fullBuffer = Buffer.concat(chunks);
153
+ const fullText = fullBuffer.toString("utf-8");
154
+ const truncation = truncateTail(fullText);
155
+ onUpdate({
156
+ content: [{ type: "text", text: truncation.content || "" }],
157
+ details: {
158
+ truncation: truncation.truncated ? truncation : undefined,
159
+ fullOutputPath: tempFilePath,
160
+ },
161
+ });
162
+ }
163
+ };
164
+ ops.exec(spawnContext.command, spawnContext.cwd, {
165
+ onData: handleData,
166
+ signal,
167
+ timeout,
168
+ env: spawnContext.env,
169
+ })
170
+ .then(({ exitCode }) => {
171
+ // Close temp file stream
172
+ if (tempFileStream) {
173
+ tempFileStream.end();
174
+ }
175
+ // Combine all buffered chunks
176
+ const fullBuffer = Buffer.concat(chunks);
177
+ const fullOutput = fullBuffer.toString("utf-8");
178
+ // Apply tail truncation
179
+ const truncation = truncateTail(fullOutput);
180
+ let outputText = truncation.content || "(no output)";
181
+ // Build details with truncation info
182
+ let details;
183
+ if (truncation.truncated) {
184
+ details = {
185
+ truncation,
186
+ fullOutputPath: tempFilePath,
187
+ };
188
+ // Build actionable notice
189
+ const startLine = truncation.totalLines - truncation.outputLines + 1;
190
+ const endLine = truncation.totalLines;
191
+ if (truncation.lastLinePartial) {
192
+ // Edge case: last line alone > 30KB
193
+ const lastLineSize = formatSize(Buffer.byteLength(fullOutput.split("\n").pop() || "", "utf-8"));
194
+ outputText += `\n\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;
195
+ }
196
+ else if (truncation.truncatedBy === "lines") {
197
+ outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;
198
+ }
199
+ else {
200
+ outputText += `\n\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;
201
+ }
202
+ }
203
+ if (exitCode !== 0 && exitCode !== null) {
204
+ outputText += `\n\nCommand exited with code ${exitCode}`;
205
+ reject(new Error(outputText));
206
+ }
207
+ else {
208
+ resolve({ content: [{ type: "text", text: outputText }], details });
209
+ }
210
+ })
211
+ .catch((err) => {
212
+ // Close temp file stream
213
+ if (tempFileStream) {
214
+ tempFileStream.end();
215
+ }
216
+ // Combine all buffered chunks for error output
217
+ const fullBuffer = Buffer.concat(chunks);
218
+ let output = fullBuffer.toString("utf-8");
219
+ if (err.message === "aborted") {
220
+ if (output)
221
+ output += "\n\n";
222
+ output += "Command aborted";
223
+ reject(new Error(output));
224
+ }
225
+ else if (err.message.startsWith("timeout:")) {
226
+ const timeoutSecs = err.message.split(":")[1];
227
+ if (output)
228
+ output += "\n\n";
229
+ output += `Command timed out after ${timeoutSecs} seconds`;
230
+ reject(new Error(output));
231
+ }
232
+ else {
233
+ reject(err);
234
+ }
235
+ });
236
+ });
237
+ },
238
+ };
239
+ }
240
+ /** Default bash tool using process.cwd() - for backwards compatibility */
241
+ export const bashTool = createBashTool(process.cwd());
242
+ //# sourceMappingURL=bash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash.js","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomBytes } from \"node:crypto\";\r\nimport { createWriteStream, existsSync } from \"node:fs\";\r\nimport { tmpdir } from \"node:os\";\r\nimport { join } from \"node:path\";\r\nimport type { AgentTool } from \"@mariozechner/pi-agent-core\";\r\nimport { type Static, Type } from \"@sinclair/typebox\";\r\nimport { spawn } from \"child_process\";\r\nimport { getShellConfig, getShellEnv, killProcessTree } from \"../../utils/shell.js\";\r\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\r\n\r\n/**\r\n * Generate a unique temp file path for bash output\r\n */\r\nfunction getTempFilePath(): string {\r\n\tconst id = randomBytes(8).toString(\"hex\");\r\n\treturn join(tmpdir(), `pi-bash-${id}.log`);\r\n}\r\n\r\nconst bashSchema = Type.Object({\r\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\r\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\r\n});\r\n\r\nexport type BashToolInput = Static<typeof bashSchema>;\r\n\r\nexport interface BashToolDetails {\r\n\ttruncation?: TruncationResult;\r\n\tfullOutputPath?: string;\r\n}\r\n\r\n/**\r\n * Pluggable operations for the bash tool.\r\n * Override these to delegate command execution to remote systems (e.g., SSH).\r\n */\r\nexport interface BashOperations {\r\n\t/**\r\n\t * Execute a command and stream output.\r\n\t * @param command - The command to execute\r\n\t * @param cwd - Working directory\r\n\t * @param options - Execution options\r\n\t * @returns Promise resolving to exit code (null if killed)\r\n\t */\r\n\texec: (\r\n\t\tcommand: string,\r\n\t\tcwd: string,\r\n\t\toptions: {\r\n\t\t\tonData: (data: Buffer) => void;\r\n\t\t\tsignal?: AbortSignal;\r\n\t\t\ttimeout?: number;\r\n\t\t\tenv?: NodeJS.ProcessEnv;\r\n\t\t},\r\n\t) => Promise<{ exitCode: number | null }>;\r\n}\r\n\r\n/**\r\n * Default bash operations using local shell\r\n */\r\nconst defaultBashOperations: BashOperations = {\r\n\texec: (command, cwd, { onData, signal, timeout, env }) => {\r\n\t\treturn new Promise((resolve, reject) => {\r\n\t\t\tconst { shell, args } = getShellConfig();\r\n\r\n\t\t\tif (!existsSync(cwd)) {\r\n\t\t\t\treject(new Error(`Working directory does not exist: ${cwd}\\nCannot execute bash commands.`));\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tconst child = spawn(shell, [...args, command], {\r\n\t\t\t\tcwd,\r\n\t\t\t\tdetached: true,\r\n\t\t\t\tenv: env ?? getShellEnv(),\r\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\r\n\t\t\t});\r\n\r\n\t\t\tlet timedOut = false;\r\n\r\n\t\t\t// Set timeout if provided\r\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\r\n\t\t\tif (timeout !== undefined && timeout > 0) {\r\n\t\t\t\ttimeoutHandle = setTimeout(() => {\r\n\t\t\t\t\ttimedOut = true;\r\n\t\t\t\t\tif (child.pid) {\r\n\t\t\t\t\t\tkillProcessTree(child.pid);\r\n\t\t\t\t\t}\r\n\t\t\t\t}, timeout * 1000);\r\n\t\t\t}\r\n\r\n\t\t\t// Stream stdout and stderr\r\n\t\t\tif (child.stdout) {\r\n\t\t\t\tchild.stdout.on(\"data\", onData);\r\n\t\t\t}\r\n\t\t\tif (child.stderr) {\r\n\t\t\t\tchild.stderr.on(\"data\", onData);\r\n\t\t\t}\r\n\r\n\t\t\t// Handle shell spawn errors\r\n\t\t\tchild.on(\"error\", (err) => {\r\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\r\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\r\n\t\t\t\treject(err);\r\n\t\t\t});\r\n\r\n\t\t\t// Handle abort signal - kill entire process tree\r\n\t\t\tconst onAbort = () => {\r\n\t\t\t\tif (child.pid) {\r\n\t\t\t\t\tkillProcessTree(child.pid);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t\tif (signal) {\r\n\t\t\t\tif (signal.aborted) {\r\n\t\t\t\t\tonAbort();\r\n\t\t\t\t} else {\r\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Handle process exit\r\n\t\t\tchild.on(\"close\", (code) => {\r\n\t\t\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\r\n\t\t\t\tif (signal) signal.removeEventListener(\"abort\", onAbort);\r\n\r\n\t\t\t\tif (signal?.aborted) {\r\n\t\t\t\t\treject(new Error(\"aborted\"));\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (timedOut) {\r\n\t\t\t\t\treject(new Error(`timeout:${timeout}`));\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tresolve({ exitCode: code });\r\n\t\t\t});\r\n\t\t});\r\n\t},\r\n};\r\n\r\nexport interface BashSpawnContext {\r\n\tcommand: string;\r\n\tcwd: string;\r\n\tenv: NodeJS.ProcessEnv;\r\n}\r\n\r\nexport type BashSpawnHook = (context: BashSpawnContext) => BashSpawnContext;\r\n\r\nfunction resolveSpawnContext(command: string, cwd: string, spawnHook?: BashSpawnHook): BashSpawnContext {\r\n\tconst baseContext: BashSpawnContext = {\r\n\t\tcommand,\r\n\t\tcwd,\r\n\t\tenv: { ...getShellEnv() },\r\n\t};\r\n\r\n\treturn spawnHook ? spawnHook(baseContext) : baseContext;\r\n}\r\n\r\nexport interface BashToolOptions {\r\n\t/** Custom operations for command execution. Default: local shell */\r\n\toperations?: BashOperations;\r\n\t/** Command prefix prepended to every command (e.g., \"shopt -s expand_aliases\" for alias support) */\r\n\tcommandPrefix?: string;\r\n\t/** Hook to adjust command, cwd, or env before execution */\r\n\tspawnHook?: BashSpawnHook;\r\n}\r\n\r\nexport function createBashTool(cwd: string, options?: BashToolOptions): AgentTool<typeof bashSchema> {\r\n\tconst ops = options?.operations ?? defaultBashOperations;\r\n\tconst commandPrefix = options?.commandPrefix;\r\n\tconst spawnHook = options?.spawnHook;\r\n\r\n\treturn {\r\n\t\tname: \"bash\",\r\n\t\tlabel: \"bash\",\r\n\t\tdescription: `Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last ${DEFAULT_MAX_LINES} lines or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file. Optionally provide a timeout in seconds.`,\r\n\t\tparameters: bashSchema,\r\n\t\texecute: async (\r\n\t\t\t_toolCallId: string,\r\n\t\t\t{ command, timeout }: { command: string; timeout?: number },\r\n\t\t\tsignal?: AbortSignal,\r\n\t\t\tonUpdate?,\r\n\t\t) => {\r\n\t\t\t// Apply command prefix if configured (e.g., \"shopt -s expand_aliases\" for alias support)\r\n\t\t\tconst resolvedCommand = commandPrefix ? `${commandPrefix}\\n${command}` : command;\r\n\t\t\tconst spawnContext = resolveSpawnContext(resolvedCommand, cwd, spawnHook);\r\n\r\n\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t// We'll stream to a temp file if output gets large\r\n\t\t\t\tlet tempFilePath: string | undefined;\r\n\t\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\r\n\t\t\t\tlet totalBytes = 0;\r\n\r\n\t\t\t\t// Keep a rolling buffer of the last chunk for tail truncation\r\n\t\t\t\tconst chunks: Buffer[] = [];\r\n\t\t\t\tlet chunksBytes = 0;\r\n\t\t\t\t// Keep more than we need so we have enough for truncation\r\n\t\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\r\n\r\n\t\t\t\tconst handleData = (data: Buffer) => {\r\n\t\t\t\t\ttotalBytes += data.length;\r\n\r\n\t\t\t\t\t// Start writing to temp file once we exceed the threshold\r\n\t\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\r\n\t\t\t\t\t\ttempFilePath = getTempFilePath();\r\n\t\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\r\n\t\t\t\t\t\t// Write all buffered chunks to the file\r\n\t\t\t\t\t\tfor (const chunk of chunks) {\r\n\t\t\t\t\t\t\ttempFileStream.write(chunk);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Write to temp file if we have one\r\n\t\t\t\t\tif (tempFileStream) {\r\n\t\t\t\t\t\ttempFileStream.write(data);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Keep rolling buffer of recent data\r\n\t\t\t\t\tchunks.push(data);\r\n\t\t\t\t\tchunksBytes += data.length;\r\n\r\n\t\t\t\t\t// Trim old chunks if buffer is too large\r\n\t\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\r\n\t\t\t\t\t\tconst removed = chunks.shift()!;\r\n\t\t\t\t\t\tchunksBytes -= removed.length;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Stream partial output to callback (truncated rolling buffer)\r\n\t\t\t\t\tif (onUpdate) {\r\n\t\t\t\t\t\tconst fullBuffer = Buffer.concat(chunks);\r\n\t\t\t\t\t\tconst fullText = fullBuffer.toString(\"utf-8\");\r\n\t\t\t\t\t\tconst truncation = truncateTail(fullText);\r\n\t\t\t\t\t\tonUpdate({\r\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: truncation.content || \"\" }],\r\n\t\t\t\t\t\t\tdetails: {\r\n\t\t\t\t\t\t\t\ttruncation: truncation.truncated ? truncation : undefined,\r\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\r\n\t\t\t\tops.exec(spawnContext.command, spawnContext.cwd, {\r\n\t\t\t\t\tonData: handleData,\r\n\t\t\t\t\tsignal,\r\n\t\t\t\t\ttimeout,\r\n\t\t\t\t\tenv: spawnContext.env,\r\n\t\t\t\t})\r\n\t\t\t\t\t.then(({ exitCode }) => {\r\n\t\t\t\t\t\t// Close temp file stream\r\n\t\t\t\t\t\tif (tempFileStream) {\r\n\t\t\t\t\t\t\ttempFileStream.end();\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Combine all buffered chunks\r\n\t\t\t\t\t\tconst fullBuffer = Buffer.concat(chunks);\r\n\t\t\t\t\t\tconst fullOutput = fullBuffer.toString(\"utf-8\");\r\n\r\n\t\t\t\t\t\t// Apply tail truncation\r\n\t\t\t\t\t\tconst truncation = truncateTail(fullOutput);\r\n\t\t\t\t\t\tlet outputText = truncation.content || \"(no output)\";\r\n\r\n\t\t\t\t\t\t// Build details with truncation info\r\n\t\t\t\t\t\tlet details: BashToolDetails | undefined;\r\n\r\n\t\t\t\t\t\tif (truncation.truncated) {\r\n\t\t\t\t\t\t\tdetails = {\r\n\t\t\t\t\t\t\t\ttruncation,\r\n\t\t\t\t\t\t\t\tfullOutputPath: tempFilePath,\r\n\t\t\t\t\t\t\t};\r\n\r\n\t\t\t\t\t\t\t// Build actionable notice\r\n\t\t\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\r\n\t\t\t\t\t\t\tconst endLine = truncation.totalLines;\r\n\r\n\t\t\t\t\t\t\tif (truncation.lastLinePartial) {\r\n\t\t\t\t\t\t\t\t// Edge case: last line alone > 30KB\r\n\t\t\t\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(fullOutput.split(\"\\n\").pop() || \"\", \"utf-8\"));\r\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\r\n\t\t\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\r\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (exitCode !== 0 && exitCode !== null) {\r\n\t\t\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${exitCode}`;\r\n\t\t\t\t\t\t\treject(new Error(outputText));\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.catch((err: Error) => {\r\n\t\t\t\t\t\t// Close temp file stream\r\n\t\t\t\t\t\tif (tempFileStream) {\r\n\t\t\t\t\t\t\ttempFileStream.end();\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Combine all buffered chunks for error output\r\n\t\t\t\t\t\tconst fullBuffer = Buffer.concat(chunks);\r\n\t\t\t\t\t\tlet output = fullBuffer.toString(\"utf-8\");\r\n\r\n\t\t\t\t\t\tif (err.message === \"aborted\") {\r\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\r\n\t\t\t\t\t\t\toutput += \"Command aborted\";\r\n\t\t\t\t\t\t\treject(new Error(output));\r\n\t\t\t\t\t\t} else if (err.message.startsWith(\"timeout:\")) {\r\n\t\t\t\t\t\t\tconst timeoutSecs = err.message.split(\":\")[1];\r\n\t\t\t\t\t\t\tif (output) output += \"\\n\\n\";\r\n\t\t\t\t\t\t\toutput += `Command timed out after ${timeoutSecs} seconds`;\r\n\t\t\t\t\t\t\treject(new Error(output));\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t});\r\n\t\t},\r\n\t};\r\n}\r\n\r\n/** Default bash tool using process.cwd() - for backwards compatibility */\r\nexport const bashTool = createBashTool(process.cwd());\r\n"]}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared diff computation utilities for the edit tool.
3
+ * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).
4
+ */
5
+ export declare function detectLineEnding(content: string): "\r\n" | "\n";
6
+ export declare function normalizeToLF(text: string): string;
7
+ export declare function restoreLineEndings(text: string, ending: "\r\n" | "\n"): string;
8
+ /**
9
+ * Normalize text for fuzzy matching. Applies progressive transformations:
10
+ * - Strip trailing whitespace from each line
11
+ * - Normalize smart quotes to ASCII equivalents
12
+ * - Normalize Unicode dashes/hyphens to ASCII hyphen
13
+ * - Normalize special Unicode spaces to regular space
14
+ */
15
+ export declare function normalizeForFuzzyMatch(text: string): string;
16
+ export interface FuzzyMatchResult {
17
+ /** Whether a match was found */
18
+ found: boolean;
19
+ /** The index where the match starts (in the content that should be used for replacement) */
20
+ index: number;
21
+ /** Length of the matched text */
22
+ matchLength: number;
23
+ /** Whether fuzzy matching was used (false = exact match) */
24
+ usedFuzzyMatch: boolean;
25
+ /**
26
+ * The content to use for replacement operations.
27
+ * When exact match: original content. When fuzzy match: normalized content.
28
+ */
29
+ contentForReplacement: string;
30
+ }
31
+ /**
32
+ * Find oldText in content, trying exact match first, then fuzzy match.
33
+ * When fuzzy matching is used, the returned contentForReplacement is the
34
+ * fuzzy-normalized version of the content (trailing whitespace stripped,
35
+ * Unicode quotes/dashes normalized to ASCII).
36
+ */
37
+ export declare function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult;
38
+ /** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
39
+ export declare function stripBom(content: string): {
40
+ bom: string;
41
+ text: string;
42
+ };
43
+ /**
44
+ * Generate a unified diff string with line numbers and context.
45
+ * Returns both the diff string and the first changed line number (in the new file).
46
+ */
47
+ export declare function generateDiffString(oldContent: string, newContent: string, contextLines?: number): {
48
+ diff: string;
49
+ firstChangedLine: number | undefined;
50
+ };
51
+ export interface EditDiffResult {
52
+ diff: string;
53
+ firstChangedLine: number | undefined;
54
+ }
55
+ export interface EditDiffError {
56
+ error: string;
57
+ }
58
+ /**
59
+ * Compute the diff for an edit operation without applying it.
60
+ * Used for preview rendering in the TUI before the tool executes.
61
+ */
62
+ export declare function computeEditDiff(path: string, oldText: string, newText: string, cwd: string): Promise<EditDiffResult | EditDiffError>;
63
+ //# sourceMappingURL=edit-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit-diff.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit-diff.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9E;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAoB3D;AAED,MAAM,WAAW,gBAAgB;IAChC,gCAAgC;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,4FAA4F;IAC5F,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,cAAc,EAAE,OAAO,CAAC;IACxB;;;OAGG;IACH,qBAAqB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAsChF;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAEvE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,YAAY,SAAI,GACd;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAgGxD;AAED,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACpC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACT,OAAO,CAAC,cAAc,GAAG,aAAa,CAAC,CA6DzC","sourcesContent":["/**\r\n * Shared diff computation utilities for the edit tool.\r\n * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).\r\n */\r\n\r\nimport * as Diff from \"diff\";\r\nimport { constants } from \"fs\";\r\nimport { access, readFile } from \"fs/promises\";\r\nimport { resolveToCwd } from \"./path-utils.js\";\r\n\r\nexport function detectLineEnding(content: string): \"\\r\\n\" | \"\\n\" {\r\n\tconst crlfIdx = content.indexOf(\"\\r\\n\");\r\n\tconst lfIdx = content.indexOf(\"\\n\");\r\n\tif (lfIdx === -1) return \"\\n\";\r\n\tif (crlfIdx === -1) return \"\\n\";\r\n\treturn crlfIdx < lfIdx ? \"\\r\\n\" : \"\\n\";\r\n}\r\n\r\nexport function normalizeToLF(text: string): string {\r\n\treturn text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\r\n}\r\n\r\nexport function restoreLineEndings(text: string, ending: \"\\r\\n\" | \"\\n\"): string {\r\n\treturn ending === \"\\r\\n\" ? text.replace(/\\n/g, \"\\r\\n\") : text;\r\n}\r\n\r\n/**\r\n * Normalize text for fuzzy matching. Applies progressive transformations:\r\n * - Strip trailing whitespace from each line\r\n * - Normalize smart quotes to ASCII equivalents\r\n * - Normalize Unicode dashes/hyphens to ASCII hyphen\r\n * - Normalize special Unicode spaces to regular space\r\n */\r\nexport function normalizeForFuzzyMatch(text: string): string {\r\n\treturn (\r\n\t\ttext\r\n\t\t\t// Strip trailing whitespace per line\r\n\t\t\t.split(\"\\n\")\r\n\t\t\t.map((line) => line.trimEnd())\r\n\t\t\t.join(\"\\n\")\r\n\t\t\t// Smart single quotes → '\r\n\t\t\t.replace(/[\\u2018\\u2019\\u201A\\u201B]/g, \"'\")\r\n\t\t\t// Smart double quotes → \"\r\n\t\t\t.replace(/[\\u201C\\u201D\\u201E\\u201F]/g, '\"')\r\n\t\t\t// Various dashes/hyphens → -\r\n\t\t\t// U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,\r\n\t\t\t// U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus\r\n\t\t\t.replace(/[\\u2010\\u2011\\u2012\\u2013\\u2014\\u2015\\u2212]/g, \"-\")\r\n\t\t\t// Special spaces → regular space\r\n\t\t\t// U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,\r\n\t\t\t// U+205F medium math space, U+3000 ideographic space\r\n\t\t\t.replace(/[\\u00A0\\u2002-\\u200A\\u202F\\u205F\\u3000]/g, \" \")\r\n\t);\r\n}\r\n\r\nexport interface FuzzyMatchResult {\r\n\t/** Whether a match was found */\r\n\tfound: boolean;\r\n\t/** The index where the match starts (in the content that should be used for replacement) */\r\n\tindex: number;\r\n\t/** Length of the matched text */\r\n\tmatchLength: number;\r\n\t/** Whether fuzzy matching was used (false = exact match) */\r\n\tusedFuzzyMatch: boolean;\r\n\t/**\r\n\t * The content to use for replacement operations.\r\n\t * When exact match: original content. When fuzzy match: normalized content.\r\n\t */\r\n\tcontentForReplacement: string;\r\n}\r\n\r\n/**\r\n * Find oldText in content, trying exact match first, then fuzzy match.\r\n * When fuzzy matching is used, the returned contentForReplacement is the\r\n * fuzzy-normalized version of the content (trailing whitespace stripped,\r\n * Unicode quotes/dashes normalized to ASCII).\r\n */\r\nexport function fuzzyFindText(content: string, oldText: string): FuzzyMatchResult {\r\n\t// Try exact match first\r\n\tconst exactIndex = content.indexOf(oldText);\r\n\tif (exactIndex !== -1) {\r\n\t\treturn {\r\n\t\t\tfound: true,\r\n\t\t\tindex: exactIndex,\r\n\t\t\tmatchLength: oldText.length,\r\n\t\t\tusedFuzzyMatch: false,\r\n\t\t\tcontentForReplacement: content,\r\n\t\t};\r\n\t}\r\n\r\n\t// Try fuzzy match - work entirely in normalized space\r\n\tconst fuzzyContent = normalizeForFuzzyMatch(content);\r\n\tconst fuzzyOldText = normalizeForFuzzyMatch(oldText);\r\n\tconst fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);\r\n\r\n\tif (fuzzyIndex === -1) {\r\n\t\treturn {\r\n\t\t\tfound: false,\r\n\t\t\tindex: -1,\r\n\t\t\tmatchLength: 0,\r\n\t\t\tusedFuzzyMatch: false,\r\n\t\t\tcontentForReplacement: content,\r\n\t\t};\r\n\t}\r\n\r\n\t// When fuzzy matching, we work in the normalized space for replacement.\r\n\t// This means the output will have normalized whitespace/quotes/dashes,\r\n\t// which is acceptable since we're fixing minor formatting differences anyway.\r\n\treturn {\r\n\t\tfound: true,\r\n\t\tindex: fuzzyIndex,\r\n\t\tmatchLength: fuzzyOldText.length,\r\n\t\tusedFuzzyMatch: true,\r\n\t\tcontentForReplacement: fuzzyContent,\r\n\t};\r\n}\r\n\r\n/** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */\r\nexport function stripBom(content: string): { bom: string; text: string } {\r\n\treturn content.startsWith(\"\\uFEFF\") ? { bom: \"\\uFEFF\", text: content.slice(1) } : { bom: \"\", text: content };\r\n}\r\n\r\n/**\r\n * Generate a unified diff string with line numbers and context.\r\n * Returns both the diff string and the first changed line number (in the new file).\r\n */\r\nexport function generateDiffString(\r\n\toldContent: string,\r\n\tnewContent: string,\r\n\tcontextLines = 4,\r\n): { diff: string; firstChangedLine: number | undefined } {\r\n\tconst parts = Diff.diffLines(oldContent, newContent);\r\n\tconst output: string[] = [];\r\n\r\n\tconst oldLines = oldContent.split(\"\\n\");\r\n\tconst newLines = newContent.split(\"\\n\");\r\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\r\n\tconst lineNumWidth = String(maxLineNum).length;\r\n\r\n\tlet oldLineNum = 1;\r\n\tlet newLineNum = 1;\r\n\tlet lastWasChange = false;\r\n\tlet firstChangedLine: number | undefined;\r\n\r\n\tfor (let i = 0; i < parts.length; i++) {\r\n\t\tconst part = parts[i];\r\n\t\tconst raw = part.value.split(\"\\n\");\r\n\t\tif (raw[raw.length - 1] === \"\") {\r\n\t\t\traw.pop();\r\n\t\t}\r\n\r\n\t\tif (part.added || part.removed) {\r\n\t\t\t// Capture the first changed line (in the new file)\r\n\t\t\tif (firstChangedLine === undefined) {\r\n\t\t\t\tfirstChangedLine = newLineNum;\r\n\t\t\t}\r\n\r\n\t\t\t// Show the change\r\n\t\t\tfor (const line of raw) {\r\n\t\t\t\tif (part.added) {\r\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\r\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\r\n\t\t\t\t\tnewLineNum++;\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// removed\r\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\r\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\r\n\t\t\t\t\toldLineNum++;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tlastWasChange = true;\r\n\t\t} else {\r\n\t\t\t// Context lines - only show a few before/after changes\r\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\r\n\r\n\t\t\tif (lastWasChange || nextPartIsChange) {\r\n\t\t\t\t// Show context\r\n\t\t\t\tlet linesToShow = raw;\r\n\t\t\t\tlet skipStart = 0;\r\n\t\t\t\tlet skipEnd = 0;\r\n\r\n\t\t\t\tif (!lastWasChange) {\r\n\t\t\t\t\t// Show only last N lines as leading context\r\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\r\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\r\n\t\t\t\t\t// Show only first N lines as trailing context\r\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\r\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Add ellipsis if we skipped lines at start\r\n\t\t\t\tif (skipStart > 0) {\r\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\r\n\t\t\t\t\t// Update line numbers for the skipped leading context\r\n\t\t\t\t\toldLineNum += skipStart;\r\n\t\t\t\t\tnewLineNum += skipStart;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tfor (const line of linesToShow) {\r\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\r\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\r\n\t\t\t\t\toldLineNum++;\r\n\t\t\t\t\tnewLineNum++;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Add ellipsis if we skipped lines at end\r\n\t\t\t\tif (skipEnd > 0) {\r\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\r\n\t\t\t\t\t// Update line numbers for the skipped trailing context\r\n\t\t\t\t\toldLineNum += skipEnd;\r\n\t\t\t\t\tnewLineNum += skipEnd;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// Skip these context lines entirely\r\n\t\t\t\toldLineNum += raw.length;\r\n\t\t\t\tnewLineNum += raw.length;\r\n\t\t\t}\r\n\r\n\t\t\tlastWasChange = false;\r\n\t\t}\r\n\t}\r\n\r\n\treturn { diff: output.join(\"\\n\"), firstChangedLine };\r\n}\r\n\r\nexport interface EditDiffResult {\r\n\tdiff: string;\r\n\tfirstChangedLine: number | undefined;\r\n}\r\n\r\nexport interface EditDiffError {\r\n\terror: string;\r\n}\r\n\r\n/**\r\n * Compute the diff for an edit operation without applying it.\r\n * Used for preview rendering in the TUI before the tool executes.\r\n */\r\nexport async function computeEditDiff(\r\n\tpath: string,\r\n\toldText: string,\r\n\tnewText: string,\r\n\tcwd: string,\r\n): Promise<EditDiffResult | EditDiffError> {\r\n\tconst absolutePath = resolveToCwd(path, cwd);\r\n\r\n\ttry {\r\n\t\t// Check if file exists and is readable\r\n\t\ttry {\r\n\t\t\tawait access(absolutePath, constants.R_OK);\r\n\t\t} catch {\r\n\t\t\treturn { error: `File not found: ${path}` };\r\n\t\t}\r\n\r\n\t\t// Read the file\r\n\t\tconst rawContent = await readFile(absolutePath, \"utf-8\");\r\n\r\n\t\t// Strip BOM before matching (LLM won't include invisible BOM in oldText)\r\n\t\tconst { text: content } = stripBom(rawContent);\r\n\r\n\t\tconst normalizedContent = normalizeToLF(content);\r\n\t\tconst normalizedOldText = normalizeToLF(oldText);\r\n\t\tconst normalizedNewText = normalizeToLF(newText);\r\n\r\n\t\t// Find the old text using fuzzy matching (tries exact match first, then fuzzy)\r\n\t\tconst matchResult = fuzzyFindText(normalizedContent, normalizedOldText);\r\n\r\n\t\tif (!matchResult.found) {\r\n\t\t\treturn {\r\n\t\t\t\terror: `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t// Count occurrences using fuzzy-normalized content for consistency\r\n\t\tconst fuzzyContent = normalizeForFuzzyMatch(normalizedContent);\r\n\t\tconst fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);\r\n\t\tconst occurrences = fuzzyContent.split(fuzzyOldText).length - 1;\r\n\r\n\t\tif (occurrences > 1) {\r\n\t\t\treturn {\r\n\t\t\t\terror: `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t// Compute the new content using the matched position\r\n\t\t// When fuzzy matching was used, contentForReplacement is the normalized version\r\n\t\tconst baseContent = matchResult.contentForReplacement;\r\n\t\tconst newContent =\r\n\t\t\tbaseContent.substring(0, matchResult.index) +\r\n\t\t\tnormalizedNewText +\r\n\t\t\tbaseContent.substring(matchResult.index + matchResult.matchLength);\r\n\r\n\t\t// Check if it would actually change anything\r\n\t\tif (baseContent === newContent) {\r\n\t\t\treturn {\r\n\t\t\t\terror: `No changes would be made to ${path}. The replacement produces identical content.`,\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t// Generate the diff\r\n\t\treturn generateDiffString(baseContent, newContent);\r\n\t} catch (err) {\r\n\t\treturn { error: err instanceof Error ? err.message : String(err) };\r\n\t}\r\n}\r\n"]}
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Shared diff computation utilities for the edit tool.
3
+ * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).
4
+ */
5
+ import * as Diff from "diff";
6
+ import { constants } from "fs";
7
+ import { access, readFile } from "fs/promises";
8
+ import { resolveToCwd } from "./path-utils.js";
9
+ export function detectLineEnding(content) {
10
+ const crlfIdx = content.indexOf("\r\n");
11
+ const lfIdx = content.indexOf("\n");
12
+ if (lfIdx === -1)
13
+ return "\n";
14
+ if (crlfIdx === -1)
15
+ return "\n";
16
+ return crlfIdx < lfIdx ? "\r\n" : "\n";
17
+ }
18
+ export function normalizeToLF(text) {
19
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
20
+ }
21
+ export function restoreLineEndings(text, ending) {
22
+ return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
23
+ }
24
+ /**
25
+ * Normalize text for fuzzy matching. Applies progressive transformations:
26
+ * - Strip trailing whitespace from each line
27
+ * - Normalize smart quotes to ASCII equivalents
28
+ * - Normalize Unicode dashes/hyphens to ASCII hyphen
29
+ * - Normalize special Unicode spaces to regular space
30
+ */
31
+ export function normalizeForFuzzyMatch(text) {
32
+ return (text
33
+ // Strip trailing whitespace per line
34
+ .split("\n")
35
+ .map((line) => line.trimEnd())
36
+ .join("\n")
37
+ // Smart single quotes → '
38
+ .replace(/[\u2018\u2019\u201A\u201B]/g, "'")
39
+ // Smart double quotes → "
40
+ .replace(/[\u201C\u201D\u201E\u201F]/g, '"')
41
+ // Various dashes/hyphens → -
42
+ // U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,
43
+ // U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus
44
+ .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, "-")
45
+ // Special spaces → regular space
46
+ // U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,
47
+ // U+205F medium math space, U+3000 ideographic space
48
+ .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, " "));
49
+ }
50
+ /**
51
+ * Find oldText in content, trying exact match first, then fuzzy match.
52
+ * When fuzzy matching is used, the returned contentForReplacement is the
53
+ * fuzzy-normalized version of the content (trailing whitespace stripped,
54
+ * Unicode quotes/dashes normalized to ASCII).
55
+ */
56
+ export function fuzzyFindText(content, oldText) {
57
+ // Try exact match first
58
+ const exactIndex = content.indexOf(oldText);
59
+ if (exactIndex !== -1) {
60
+ return {
61
+ found: true,
62
+ index: exactIndex,
63
+ matchLength: oldText.length,
64
+ usedFuzzyMatch: false,
65
+ contentForReplacement: content,
66
+ };
67
+ }
68
+ // Try fuzzy match - work entirely in normalized space
69
+ const fuzzyContent = normalizeForFuzzyMatch(content);
70
+ const fuzzyOldText = normalizeForFuzzyMatch(oldText);
71
+ const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);
72
+ if (fuzzyIndex === -1) {
73
+ return {
74
+ found: false,
75
+ index: -1,
76
+ matchLength: 0,
77
+ usedFuzzyMatch: false,
78
+ contentForReplacement: content,
79
+ };
80
+ }
81
+ // When fuzzy matching, we work in the normalized space for replacement.
82
+ // This means the output will have normalized whitespace/quotes/dashes,
83
+ // which is acceptable since we're fixing minor formatting differences anyway.
84
+ return {
85
+ found: true,
86
+ index: fuzzyIndex,
87
+ matchLength: fuzzyOldText.length,
88
+ usedFuzzyMatch: true,
89
+ contentForReplacement: fuzzyContent,
90
+ };
91
+ }
92
+ /** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
93
+ export function stripBom(content) {
94
+ return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
95
+ }
96
+ /**
97
+ * Generate a unified diff string with line numbers and context.
98
+ * Returns both the diff string and the first changed line number (in the new file).
99
+ */
100
+ export function generateDiffString(oldContent, newContent, contextLines = 4) {
101
+ const parts = Diff.diffLines(oldContent, newContent);
102
+ const output = [];
103
+ const oldLines = oldContent.split("\n");
104
+ const newLines = newContent.split("\n");
105
+ const maxLineNum = Math.max(oldLines.length, newLines.length);
106
+ const lineNumWidth = String(maxLineNum).length;
107
+ let oldLineNum = 1;
108
+ let newLineNum = 1;
109
+ let lastWasChange = false;
110
+ let firstChangedLine;
111
+ for (let i = 0; i < parts.length; i++) {
112
+ const part = parts[i];
113
+ const raw = part.value.split("\n");
114
+ if (raw[raw.length - 1] === "") {
115
+ raw.pop();
116
+ }
117
+ if (part.added || part.removed) {
118
+ // Capture the first changed line (in the new file)
119
+ if (firstChangedLine === undefined) {
120
+ firstChangedLine = newLineNum;
121
+ }
122
+ // Show the change
123
+ for (const line of raw) {
124
+ if (part.added) {
125
+ const lineNum = String(newLineNum).padStart(lineNumWidth, " ");
126
+ output.push(`+${lineNum} ${line}`);
127
+ newLineNum++;
128
+ }
129
+ else {
130
+ // removed
131
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
132
+ output.push(`-${lineNum} ${line}`);
133
+ oldLineNum++;
134
+ }
135
+ }
136
+ lastWasChange = true;
137
+ }
138
+ else {
139
+ // Context lines - only show a few before/after changes
140
+ const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
141
+ if (lastWasChange || nextPartIsChange) {
142
+ // Show context
143
+ let linesToShow = raw;
144
+ let skipStart = 0;
145
+ let skipEnd = 0;
146
+ if (!lastWasChange) {
147
+ // Show only last N lines as leading context
148
+ skipStart = Math.max(0, raw.length - contextLines);
149
+ linesToShow = raw.slice(skipStart);
150
+ }
151
+ if (!nextPartIsChange && linesToShow.length > contextLines) {
152
+ // Show only first N lines as trailing context
153
+ skipEnd = linesToShow.length - contextLines;
154
+ linesToShow = linesToShow.slice(0, contextLines);
155
+ }
156
+ // Add ellipsis if we skipped lines at start
157
+ if (skipStart > 0) {
158
+ output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
159
+ // Update line numbers for the skipped leading context
160
+ oldLineNum += skipStart;
161
+ newLineNum += skipStart;
162
+ }
163
+ for (const line of linesToShow) {
164
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, " ");
165
+ output.push(` ${lineNum} ${line}`);
166
+ oldLineNum++;
167
+ newLineNum++;
168
+ }
169
+ // Add ellipsis if we skipped lines at end
170
+ if (skipEnd > 0) {
171
+ output.push(` ${"".padStart(lineNumWidth, " ")} ...`);
172
+ // Update line numbers for the skipped trailing context
173
+ oldLineNum += skipEnd;
174
+ newLineNum += skipEnd;
175
+ }
176
+ }
177
+ else {
178
+ // Skip these context lines entirely
179
+ oldLineNum += raw.length;
180
+ newLineNum += raw.length;
181
+ }
182
+ lastWasChange = false;
183
+ }
184
+ }
185
+ return { diff: output.join("\n"), firstChangedLine };
186
+ }
187
+ /**
188
+ * Compute the diff for an edit operation without applying it.
189
+ * Used for preview rendering in the TUI before the tool executes.
190
+ */
191
+ export async function computeEditDiff(path, oldText, newText, cwd) {
192
+ const absolutePath = resolveToCwd(path, cwd);
193
+ try {
194
+ // Check if file exists and is readable
195
+ try {
196
+ await access(absolutePath, constants.R_OK);
197
+ }
198
+ catch {
199
+ return { error: `File not found: ${path}` };
200
+ }
201
+ // Read the file
202
+ const rawContent = await readFile(absolutePath, "utf-8");
203
+ // Strip BOM before matching (LLM won't include invisible BOM in oldText)
204
+ const { text: content } = stripBom(rawContent);
205
+ const normalizedContent = normalizeToLF(content);
206
+ const normalizedOldText = normalizeToLF(oldText);
207
+ const normalizedNewText = normalizeToLF(newText);
208
+ // Find the old text using fuzzy matching (tries exact match first, then fuzzy)
209
+ const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
210
+ if (!matchResult.found) {
211
+ return {
212
+ error: `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,
213
+ };
214
+ }
215
+ // Count occurrences using fuzzy-normalized content for consistency
216
+ const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
217
+ const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
218
+ const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
219
+ if (occurrences > 1) {
220
+ return {
221
+ error: `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,
222
+ };
223
+ }
224
+ // Compute the new content using the matched position
225
+ // When fuzzy matching was used, contentForReplacement is the normalized version
226
+ const baseContent = matchResult.contentForReplacement;
227
+ const newContent = baseContent.substring(0, matchResult.index) +
228
+ normalizedNewText +
229
+ baseContent.substring(matchResult.index + matchResult.matchLength);
230
+ // Check if it would actually change anything
231
+ if (baseContent === newContent) {
232
+ return {
233
+ error: `No changes would be made to ${path}. The replacement produces identical content.`,
234
+ };
235
+ }
236
+ // Generate the diff
237
+ return generateDiffString(baseContent, newContent);
238
+ }
239
+ catch (err) {
240
+ return { error: err instanceof Error ? err.message : String(err) };
241
+ }
242
+ }
243
+ //# sourceMappingURL=edit-diff.js.map