@mariozechner/pi-coding-agent 0.14.2 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +415 -1098
  3. package/dist/cli/args.d.ts +30 -0
  4. package/dist/cli/args.d.ts.map +1 -0
  5. package/dist/cli/args.js +179 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/file-processor.d.ts +11 -0
  8. package/dist/cli/file-processor.d.ts.map +1 -0
  9. package/dist/cli/file-processor.js +82 -0
  10. package/dist/cli/file-processor.js.map +1 -0
  11. package/dist/cli/session-picker.d.ts +7 -0
  12. package/dist/cli/session-picker.d.ts.map +1 -0
  13. package/dist/cli/session-picker.js +29 -0
  14. package/dist/cli/session-picker.js.map +1 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +7 -18
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config.d.ts +2 -2
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +15 -9
  21. package/dist/config.js.map +1 -1
  22. package/dist/core/agent-session.d.ts +287 -0
  23. package/dist/core/agent-session.d.ts.map +1 -0
  24. package/dist/core/agent-session.js +735 -0
  25. package/dist/core/agent-session.js.map +1 -0
  26. package/dist/core/bash-executor.d.ts +41 -0
  27. package/dist/core/bash-executor.d.ts.map +1 -0
  28. package/dist/core/bash-executor.js +132 -0
  29. package/dist/core/bash-executor.js.map +1 -0
  30. package/dist/{compaction.d.ts → core/compaction.d.ts} +5 -1
  31. package/dist/core/compaction.d.ts.map +1 -0
  32. package/dist/{compaction.js → core/compaction.js} +23 -1
  33. package/dist/core/compaction.js.map +1 -0
  34. package/dist/core/export-html.d.ts.map +1 -0
  35. package/dist/{export-html.js → core/export-html.js} +1 -1
  36. package/dist/{export-html.d.ts.map → core/export-html.js.map} +1 -1
  37. package/dist/core/index.d.ts +6 -0
  38. package/dist/core/index.d.ts.map +1 -0
  39. package/dist/core/index.js +6 -0
  40. package/dist/core/index.js.map +1 -0
  41. package/dist/core/messages.d.ts.map +1 -0
  42. package/dist/core/messages.js.map +1 -0
  43. package/dist/core/model-config.d.ts.map +1 -0
  44. package/dist/{model-config.js → core/model-config.js} +1 -1
  45. package/dist/core/model-config.js.map +1 -0
  46. package/dist/core/model-resolver.d.ts +48 -0
  47. package/dist/core/model-resolver.d.ts.map +1 -0
  48. package/dist/core/model-resolver.js +244 -0
  49. package/dist/core/model-resolver.js.map +1 -0
  50. package/dist/core/oauth/anthropic.d.ts.map +1 -0
  51. package/dist/core/oauth/anthropic.js.map +1 -0
  52. package/dist/core/oauth/index.d.ts.map +1 -0
  53. package/dist/{oauth/index.d.ts.map → core/oauth/index.js.map} +1 -1
  54. package/dist/core/oauth/storage.d.ts.map +1 -0
  55. package/dist/{oauth → core/oauth}/storage.js +1 -1
  56. package/dist/core/oauth/storage.js.map +1 -0
  57. package/dist/core/session-manager.d.ts.map +1 -0
  58. package/dist/{session-manager.js → core/session-manager.js} +1 -1
  59. package/dist/core/session-manager.js.map +1 -0
  60. package/dist/core/settings-manager.d.ts.map +1 -0
  61. package/dist/{settings-manager.js → core/settings-manager.js} +1 -1
  62. package/dist/core/settings-manager.js.map +1 -0
  63. package/dist/core/slash-commands.d.ts.map +1 -0
  64. package/dist/{slash-commands.js → core/slash-commands.js} +1 -1
  65. package/dist/core/slash-commands.js.map +1 -0
  66. package/dist/core/system-prompt.d.ts +17 -0
  67. package/dist/core/system-prompt.d.ts.map +1 -0
  68. package/dist/core/system-prompt.js +203 -0
  69. package/dist/core/system-prompt.js.map +1 -0
  70. package/dist/core/tools/bash.d.ts.map +1 -0
  71. package/dist/{tools → core/tools}/bash.js +1 -1
  72. package/dist/core/tools/bash.js.map +1 -0
  73. package/dist/core/tools/edit.d.ts.map +1 -0
  74. package/dist/core/tools/edit.js.map +1 -0
  75. package/dist/core/tools/find.d.ts.map +1 -0
  76. package/dist/{tools → core/tools}/find.js +1 -1
  77. package/dist/core/tools/find.js.map +1 -0
  78. package/dist/core/tools/grep.d.ts.map +1 -0
  79. package/dist/{tools → core/tools}/grep.js +1 -1
  80. package/dist/core/tools/grep.js.map +1 -0
  81. package/dist/core/tools/index.d.ts.map +1 -0
  82. package/dist/core/tools/index.js.map +1 -0
  83. package/dist/core/tools/ls.d.ts.map +1 -0
  84. package/dist/core/tools/ls.js.map +1 -0
  85. package/dist/core/tools/read.d.ts.map +1 -0
  86. package/dist/core/tools/read.js.map +1 -0
  87. package/dist/core/tools/truncate.d.ts.map +1 -0
  88. package/dist/core/tools/truncate.js.map +1 -0
  89. package/dist/core/tools/write.d.ts.map +1 -0
  90. package/dist/core/tools/write.js.map +1 -0
  91. package/dist/index.d.ts +2 -2
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +2 -2
  94. package/dist/index.js.map +1 -1
  95. package/dist/main.d.ts +3 -0
  96. package/dist/main.d.ts.map +1 -1
  97. package/dist/main.js +176 -1082
  98. package/dist/main.js.map +1 -1
  99. package/dist/modes/index.d.ts +9 -0
  100. package/dist/modes/index.d.ts.map +1 -0
  101. package/dist/modes/index.js +8 -0
  102. package/dist/modes/index.js.map +1 -0
  103. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -0
  104. package/dist/modes/interactive/components/assistant-message.js.map +1 -0
  105. package/dist/{tui → modes/interactive/components}/bash-execution.d.ts +1 -1
  106. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -0
  107. package/dist/{tui → modes/interactive/components}/bash-execution.js +1 -1
  108. package/dist/modes/interactive/components/bash-execution.js.map +1 -0
  109. package/dist/modes/interactive/components/compaction.d.ts.map +1 -0
  110. package/dist/modes/interactive/components/compaction.js.map +1 -0
  111. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -0
  112. package/dist/modes/interactive/components/custom-editor.js.map +1 -0
  113. package/dist/modes/interactive/components/dynamic-border.d.ts.map +1 -0
  114. package/dist/modes/interactive/components/dynamic-border.js.map +1 -0
  115. package/dist/modes/interactive/components/footer.d.ts.map +1 -0
  116. package/dist/{tui → modes/interactive/components}/footer.js +1 -1
  117. package/dist/modes/interactive/components/footer.js.map +1 -0
  118. package/dist/{tui → modes/interactive/components}/model-selector.d.ts +1 -1
  119. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -0
  120. package/dist/{tui → modes/interactive/components}/model-selector.js +3 -3
  121. package/dist/modes/interactive/components/model-selector.js.map +1 -0
  122. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -0
  123. package/dist/{tui → modes/interactive/components}/oauth-selector.js +2 -2
  124. package/dist/modes/interactive/components/oauth-selector.js.map +1 -0
  125. package/dist/modes/interactive/components/queue-mode-selector.d.ts.map +1 -0
  126. package/dist/modes/interactive/components/queue-mode-selector.js.map +1 -0
  127. package/dist/{tui → modes/interactive/components}/session-selector.d.ts +1 -1
  128. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -0
  129. package/dist/{tui → modes/interactive/components}/session-selector.js +1 -1
  130. package/dist/modes/interactive/components/session-selector.js.map +1 -0
  131. package/dist/modes/interactive/components/theme-selector.d.ts.map +1 -0
  132. package/dist/{tui/theme-selector.d.ts.map → modes/interactive/components/theme-selector.js.map} +1 -1
  133. package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -0
  134. package/dist/modes/interactive/components/thinking-selector.js.map +1 -0
  135. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -0
  136. package/dist/modes/interactive/components/tool-execution.js.map +1 -0
  137. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -0
  138. package/dist/modes/interactive/components/user-message-selector.js.map +1 -0
  139. package/dist/modes/interactive/components/user-message.d.ts.map +1 -0
  140. package/dist/modes/interactive/components/user-message.js.map +1 -0
  141. package/dist/{tui/tui-renderer.d.ts → modes/interactive/interactive-mode.d.ts} +36 -38
  142. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -0
  143. package/dist/modes/interactive/interactive-mode.js +1217 -0
  144. package/dist/modes/interactive/interactive-mode.js.map +1 -0
  145. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  146. package/dist/{theme → modes/interactive/theme}/theme.js +1 -1
  147. package/dist/modes/interactive/theme/theme.js.map +1 -0
  148. package/dist/modes/print-mode.d.ts +21 -0
  149. package/dist/modes/print-mode.d.ts.map +1 -0
  150. package/dist/modes/print-mode.js +53 -0
  151. package/dist/modes/print-mode.js.map +1 -0
  152. package/dist/modes/rpc/rpc-client.d.ts +182 -0
  153. package/dist/modes/rpc/rpc-client.d.ts.map +1 -0
  154. package/dist/modes/rpc/rpc-client.js +362 -0
  155. package/dist/modes/rpc/rpc-client.js.map +1 -0
  156. package/dist/modes/rpc/rpc-mode.d.ts +19 -0
  157. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -0
  158. package/dist/modes/rpc/rpc-mode.js +204 -0
  159. package/dist/modes/rpc/rpc-mode.js.map +1 -0
  160. package/dist/modes/rpc/rpc-types.d.ts +254 -0
  161. package/dist/modes/rpc/rpc-types.d.ts.map +1 -0
  162. package/dist/modes/rpc/rpc-types.js +8 -0
  163. package/dist/modes/rpc/rpc-types.js.map +1 -0
  164. package/dist/{changelog.d.ts → utils/changelog.d.ts} +1 -1
  165. package/dist/{changelog.js.map → utils/changelog.d.ts.map} +1 -1
  166. package/dist/{changelog.js → utils/changelog.js} +1 -1
  167. package/dist/utils/changelog.js.map +1 -0
  168. package/dist/utils/clipboard.d.ts.map +1 -0
  169. package/dist/utils/clipboard.js.map +1 -0
  170. package/dist/utils/fuzzy.d.ts.map +1 -0
  171. package/dist/utils/fuzzy.js.map +1 -0
  172. package/dist/utils/shell.d.ts.map +1 -0
  173. package/dist/{shell.js → utils/shell.js} +1 -1
  174. package/dist/utils/shell.js.map +1 -0
  175. package/dist/utils/tools-manager.d.ts.map +1 -0
  176. package/dist/{tools-manager.js → utils/tools-manager.js} +1 -1
  177. package/dist/utils/tools-manager.js.map +1 -0
  178. package/package.json +6 -6
  179. package/dist/changelog.d.ts.map +0 -1
  180. package/dist/clipboard.d.ts.map +0 -1
  181. package/dist/clipboard.js.map +0 -1
  182. package/dist/compaction.d.ts.map +0 -1
  183. package/dist/compaction.js.map +0 -1
  184. package/dist/export-html.js.map +0 -1
  185. package/dist/fuzzy.d.ts.map +0 -1
  186. package/dist/fuzzy.js.map +0 -1
  187. package/dist/messages.d.ts.map +0 -1
  188. package/dist/messages.js.map +0 -1
  189. package/dist/model-config.d.ts.map +0 -1
  190. package/dist/model-config.js.map +0 -1
  191. package/dist/oauth/anthropic.d.ts.map +0 -1
  192. package/dist/oauth/anthropic.js.map +0 -1
  193. package/dist/oauth/index.js.map +0 -1
  194. package/dist/oauth/storage.d.ts.map +0 -1
  195. package/dist/oauth/storage.js.map +0 -1
  196. package/dist/session-manager.d.ts.map +0 -1
  197. package/dist/session-manager.js.map +0 -1
  198. package/dist/settings-manager.d.ts.map +0 -1
  199. package/dist/settings-manager.js.map +0 -1
  200. package/dist/shell.d.ts.map +0 -1
  201. package/dist/shell.js.map +0 -1
  202. package/dist/slash-commands.d.ts.map +0 -1
  203. package/dist/slash-commands.js.map +0 -1
  204. package/dist/theme/theme.d.ts.map +0 -1
  205. package/dist/theme/theme.js.map +0 -1
  206. package/dist/tools/bash.d.ts.map +0 -1
  207. package/dist/tools/bash.js.map +0 -1
  208. package/dist/tools/edit.d.ts.map +0 -1
  209. package/dist/tools/edit.js.map +0 -1
  210. package/dist/tools/find.d.ts.map +0 -1
  211. package/dist/tools/find.js.map +0 -1
  212. package/dist/tools/grep.d.ts.map +0 -1
  213. package/dist/tools/grep.js.map +0 -1
  214. package/dist/tools/index.d.ts.map +0 -1
  215. package/dist/tools/index.js.map +0 -1
  216. package/dist/tools/ls.d.ts.map +0 -1
  217. package/dist/tools/ls.js.map +0 -1
  218. package/dist/tools/read.d.ts.map +0 -1
  219. package/dist/tools/read.js.map +0 -1
  220. package/dist/tools/truncate.d.ts.map +0 -1
  221. package/dist/tools/truncate.js.map +0 -1
  222. package/dist/tools/write.d.ts.map +0 -1
  223. package/dist/tools/write.js.map +0 -1
  224. package/dist/tools-manager.d.ts.map +0 -1
  225. package/dist/tools-manager.js.map +0 -1
  226. package/dist/tui/assistant-message.d.ts.map +0 -1
  227. package/dist/tui/assistant-message.js.map +0 -1
  228. package/dist/tui/bash-execution.d.ts.map +0 -1
  229. package/dist/tui/bash-execution.js.map +0 -1
  230. package/dist/tui/compaction.d.ts.map +0 -1
  231. package/dist/tui/compaction.js.map +0 -1
  232. package/dist/tui/custom-editor.d.ts.map +0 -1
  233. package/dist/tui/custom-editor.js.map +0 -1
  234. package/dist/tui/dynamic-border.d.ts.map +0 -1
  235. package/dist/tui/dynamic-border.js.map +0 -1
  236. package/dist/tui/footer.d.ts.map +0 -1
  237. package/dist/tui/footer.js.map +0 -1
  238. package/dist/tui/model-selector.d.ts.map +0 -1
  239. package/dist/tui/model-selector.js.map +0 -1
  240. package/dist/tui/oauth-selector.d.ts.map +0 -1
  241. package/dist/tui/oauth-selector.js.map +0 -1
  242. package/dist/tui/queue-mode-selector.d.ts.map +0 -1
  243. package/dist/tui/queue-mode-selector.js.map +0 -1
  244. package/dist/tui/session-selector.d.ts.map +0 -1
  245. package/dist/tui/session-selector.js.map +0 -1
  246. package/dist/tui/theme-selector.js.map +0 -1
  247. package/dist/tui/thinking-selector.d.ts.map +0 -1
  248. package/dist/tui/thinking-selector.js.map +0 -1
  249. package/dist/tui/tool-execution.d.ts.map +0 -1
  250. package/dist/tui/tool-execution.js.map +0 -1
  251. package/dist/tui/tui-renderer.d.ts.map +0 -1
  252. package/dist/tui/tui-renderer.js +0 -1937
  253. package/dist/tui/tui-renderer.js.map +0 -1
  254. package/dist/tui/user-message-selector.d.ts.map +0 -1
  255. package/dist/tui/user-message-selector.js.map +0 -1
  256. package/dist/tui/user-message.d.ts.map +0 -1
  257. package/dist/tui/user-message.js.map +0 -1
  258. /package/dist/{export-html.d.ts → core/export-html.d.ts} +0 -0
  259. /package/dist/{messages.d.ts → core/messages.d.ts} +0 -0
  260. /package/dist/{messages.js → core/messages.js} +0 -0
  261. /package/dist/{model-config.d.ts → core/model-config.d.ts} +0 -0
  262. /package/dist/{oauth → core/oauth}/anthropic.d.ts +0 -0
  263. /package/dist/{oauth → core/oauth}/anthropic.js +0 -0
  264. /package/dist/{oauth → core/oauth}/index.d.ts +0 -0
  265. /package/dist/{oauth → core/oauth}/index.js +0 -0
  266. /package/dist/{oauth → core/oauth}/storage.d.ts +0 -0
  267. /package/dist/{session-manager.d.ts → core/session-manager.d.ts} +0 -0
  268. /package/dist/{settings-manager.d.ts → core/settings-manager.d.ts} +0 -0
  269. /package/dist/{slash-commands.d.ts → core/slash-commands.d.ts} +0 -0
  270. /package/dist/{tools → core/tools}/bash.d.ts +0 -0
  271. /package/dist/{tools → core/tools}/edit.d.ts +0 -0
  272. /package/dist/{tools → core/tools}/edit.js +0 -0
  273. /package/dist/{tools → core/tools}/find.d.ts +0 -0
  274. /package/dist/{tools → core/tools}/grep.d.ts +0 -0
  275. /package/dist/{tools → core/tools}/index.d.ts +0 -0
  276. /package/dist/{tools → core/tools}/index.js +0 -0
  277. /package/dist/{tools → core/tools}/ls.d.ts +0 -0
  278. /package/dist/{tools → core/tools}/ls.js +0 -0
  279. /package/dist/{tools → core/tools}/read.d.ts +0 -0
  280. /package/dist/{tools → core/tools}/read.js +0 -0
  281. /package/dist/{tools → core/tools}/truncate.d.ts +0 -0
  282. /package/dist/{tools → core/tools}/truncate.js +0 -0
  283. /package/dist/{tools → core/tools}/write.d.ts +0 -0
  284. /package/dist/{tools → core/tools}/write.js +0 -0
  285. /package/dist/{tui → modes/interactive/components}/assistant-message.d.ts +0 -0
  286. /package/dist/{tui → modes/interactive/components}/assistant-message.js +0 -0
  287. /package/dist/{tui → modes/interactive/components}/compaction.d.ts +0 -0
  288. /package/dist/{tui → modes/interactive/components}/compaction.js +0 -0
  289. /package/dist/{tui → modes/interactive/components}/custom-editor.d.ts +0 -0
  290. /package/dist/{tui → modes/interactive/components}/custom-editor.js +0 -0
  291. /package/dist/{tui → modes/interactive/components}/dynamic-border.d.ts +0 -0
  292. /package/dist/{tui → modes/interactive/components}/dynamic-border.js +0 -0
  293. /package/dist/{tui → modes/interactive/components}/footer.d.ts +0 -0
  294. /package/dist/{tui → modes/interactive/components}/oauth-selector.d.ts +0 -0
  295. /package/dist/{tui → modes/interactive/components}/queue-mode-selector.d.ts +0 -0
  296. /package/dist/{tui → modes/interactive/components}/queue-mode-selector.js +0 -0
  297. /package/dist/{tui → modes/interactive/components}/theme-selector.d.ts +0 -0
  298. /package/dist/{tui → modes/interactive/components}/theme-selector.js +0 -0
  299. /package/dist/{tui → modes/interactive/components}/thinking-selector.d.ts +0 -0
  300. /package/dist/{tui → modes/interactive/components}/thinking-selector.js +0 -0
  301. /package/dist/{tui → modes/interactive/components}/tool-execution.d.ts +0 -0
  302. /package/dist/{tui → modes/interactive/components}/tool-execution.js +0 -0
  303. /package/dist/{tui → modes/interactive/components}/user-message-selector.d.ts +0 -0
  304. /package/dist/{tui → modes/interactive/components}/user-message-selector.js +0 -0
  305. /package/dist/{tui → modes/interactive/components}/user-message.d.ts +0 -0
  306. /package/dist/{tui → modes/interactive/components}/user-message.js +0 -0
  307. /package/dist/{theme → modes/interactive/theme}/dark.json +0 -0
  308. /package/dist/{theme → modes/interactive/theme}/light.json +0 -0
  309. /package/dist/{theme → modes/interactive/theme}/theme-schema.json +0 -0
  310. /package/dist/{theme → modes/interactive/theme}/theme.d.ts +0 -0
  311. /package/dist/{clipboard.d.ts → utils/clipboard.d.ts} +0 -0
  312. /package/dist/{clipboard.js → utils/clipboard.js} +0 -0
  313. /package/dist/{fuzzy.d.ts → utils/fuzzy.d.ts} +0 -0
  314. /package/dist/{fuzzy.js → utils/fuzzy.js} +0 -0
  315. /package/dist/{shell.d.ts → utils/shell.d.ts} +0 -0
  316. /package/dist/{tools-manager.d.ts → utils/tools-manager.d.ts} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readdirSync, readFileSync } from "fs";
2
2
  import { join, resolve } from "path";
3
- import { CONFIG_DIR_NAME, getCommandsDir } from "./config.js";
3
+ import { CONFIG_DIR_NAME, getCommandsDir } from "../config.js";
4
4
  /**
5
5
  * Parse YAML frontmatter from markdown content
6
6
  * Returns { frontmatter, content } where content has frontmatter stripped
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slash-commands.js","sourceRoot":"","sources":["../../src/core/slash-commands.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readdirSync, readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { CONFIG_DIR_NAME, getCommandsDir } from \"../config.js\";\n\n/**\n * Represents a custom slash command loaded from a file\n */\nexport interface FileSlashCommand {\n\tname: string;\n\tdescription: string;\n\tcontent: string;\n\tsource: string; // e.g., \"(user)\", \"(project)\", \"(project:frontend)\"\n}\n\n/**\n * Parse YAML frontmatter from markdown content\n * Returns { frontmatter, content } where content has frontmatter stripped\n */\nfunction parseFrontmatter(content: string): { frontmatter: Record<string, string>; content: string } {\n\tconst frontmatter: Record<string, string> = {};\n\n\tif (!content.startsWith(\"---\")) {\n\t\treturn { frontmatter, content };\n\t}\n\n\tconst endIndex = content.indexOf(\"\\n---\", 3);\n\tif (endIndex === -1) {\n\t\treturn { frontmatter, content };\n\t}\n\n\tconst frontmatterBlock = content.slice(4, endIndex);\n\tconst remainingContent = content.slice(endIndex + 4).trim();\n\n\t// Simple YAML parsing - just key: value pairs\n\tfor (const line of frontmatterBlock.split(\"\\n\")) {\n\t\tconst match = line.match(/^(\\w+):\\s*(.*)$/);\n\t\tif (match) {\n\t\t\tfrontmatter[match[1]] = match[2].trim();\n\t\t}\n\t}\n\n\treturn { frontmatter, content: remainingContent };\n}\n\n/**\n * Parse command arguments respecting quoted strings (bash-style)\n * Returns array of arguments\n */\nexport function parseCommandArgs(argsString: string): string[] {\n\tconst args: string[] = [];\n\tlet current = \"\";\n\tlet inQuote: string | null = null;\n\n\tfor (let i = 0; i < argsString.length; i++) {\n\t\tconst char = argsString[i];\n\n\t\tif (inQuote) {\n\t\t\tif (char === inQuote) {\n\t\t\t\tinQuote = null;\n\t\t\t} else {\n\t\t\t\tcurrent += char;\n\t\t\t}\n\t\t} else if (char === '\"' || char === \"'\") {\n\t\t\tinQuote = char;\n\t\t} else if (char === \" \" || char === \"\\t\") {\n\t\t\tif (current) {\n\t\t\t\targs.push(current);\n\t\t\t\tcurrent = \"\";\n\t\t\t}\n\t\t} else {\n\t\t\tcurrent += char;\n\t\t}\n\t}\n\n\tif (current) {\n\t\targs.push(current);\n\t}\n\n\treturn args;\n}\n\n/**\n * Substitute argument placeholders in command content\n * Supports $1, $2, ... for positional args and $@ for all args\n */\nexport function substituteArgs(content: string, args: string[]): string {\n\tlet result = content;\n\n\t// Replace $@ with all args joined\n\tresult = result.replace(/\\$@/g, args.join(\" \"));\n\n\t// Replace $1, $2, etc. with positional args\n\tresult = result.replace(/\\$(\\d+)/g, (_, num) => {\n\t\tconst index = parseInt(num, 10) - 1;\n\t\treturn args[index] ?? \"\";\n\t});\n\n\treturn result;\n}\n\n/**\n * Recursively scan a directory for .md files and load them as slash commands\n */\nfunction loadCommandsFromDir(dir: string, source: \"user\" | \"project\", subdir: string = \"\"): FileSlashCommand[] {\n\tconst commands: FileSlashCommand[] = [];\n\n\tif (!existsSync(dir)) {\n\t\treturn commands;\n\t}\n\n\ttry {\n\t\tconst entries = readdirSync(dir, { withFileTypes: true });\n\n\t\tfor (const entry of entries) {\n\t\t\tconst fullPath = join(dir, entry.name);\n\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\t// Recurse into subdirectory\n\t\t\t\tconst newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;\n\t\t\t\tcommands.push(...loadCommandsFromDir(fullPath, source, newSubdir));\n\t\t\t} else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n\t\t\t\ttry {\n\t\t\t\t\tconst rawContent = readFileSync(fullPath, \"utf-8\");\n\t\t\t\t\tconst { frontmatter, content } = parseFrontmatter(rawContent);\n\n\t\t\t\t\tconst name = entry.name.slice(0, -3); // Remove .md extension\n\n\t\t\t\t\t// Build source string\n\t\t\t\t\tlet sourceStr: string;\n\t\t\t\t\tif (source === \"user\") {\n\t\t\t\t\t\tsourceStr = subdir ? `(user:${subdir})` : \"(user)\";\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsourceStr = subdir ? `(project:${subdir})` : \"(project)\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Get description from frontmatter or first non-empty line\n\t\t\t\t\tlet description = frontmatter.description || \"\";\n\t\t\t\t\tif (!description) {\n\t\t\t\t\t\tconst firstLine = content.split(\"\\n\").find((line) => line.trim());\n\t\t\t\t\t\tif (firstLine) {\n\t\t\t\t\t\t\t// Truncate if too long\n\t\t\t\t\t\t\tdescription = firstLine.slice(0, 60);\n\t\t\t\t\t\t\tif (firstLine.length > 60) description += \"...\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Append source to description\n\t\t\t\t\tdescription = description ? `${description} ${sourceStr}` : sourceStr;\n\n\t\t\t\t\tcommands.push({\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tdescription,\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\tsource: sourceStr,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Silently skip files that can't be read\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\t// Silently skip directories that can't be read\n\t}\n\n\treturn commands;\n}\n\n/**\n * Load all custom slash commands from:\n * 1. Global: ~/{CONFIG_DIR_NAME}/agent/commands/\n * 2. Project: ./{CONFIG_DIR_NAME}/commands/\n */\nexport function loadSlashCommands(): FileSlashCommand[] {\n\tconst commands: FileSlashCommand[] = [];\n\n\t// 1. Load global commands from ~/{CONFIG_DIR_NAME}/agent/commands/\n\tconst globalCommandsDir = getCommandsDir();\n\tcommands.push(...loadCommandsFromDir(globalCommandsDir, \"user\"));\n\n\t// 2. Load project commands from ./{CONFIG_DIR_NAME}/commands/\n\tconst projectCommandsDir = resolve(process.cwd(), CONFIG_DIR_NAME, \"commands\");\n\tcommands.push(...loadCommandsFromDir(projectCommandsDir, \"project\"));\n\n\treturn commands;\n}\n\n/**\n * Expand a slash command if it matches a file-based command.\n * Returns the expanded content or the original text if not a slash command.\n */\nexport function expandSlashCommand(text: string, fileCommands: FileSlashCommand[]): string {\n\tif (!text.startsWith(\"/\")) return text;\n\n\tconst spaceIndex = text.indexOf(\" \");\n\tconst commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);\n\tconst argsString = spaceIndex === -1 ? \"\" : text.slice(spaceIndex + 1);\n\n\tconst fileCommand = fileCommands.find((cmd) => cmd.name === commandName);\n\tif (fileCommand) {\n\t\tconst args = parseCommandArgs(argsString);\n\t\treturn substituteArgs(fileCommand.content, args);\n\t}\n\n\treturn text;\n}\n"]}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * System prompt construction and project context loading
3
+ */
4
+ import type { ToolName } from "./tools/index.js";
5
+ /**
6
+ * Load all project context files in order:
7
+ * 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md
8
+ * 2. Parent directories (top-most first) down to cwd
9
+ * Each returns {path, content} for separate messages
10
+ */
11
+ export declare function loadProjectContextFiles(): Array<{
12
+ path: string;
13
+ content: string;
14
+ }>;
15
+ /** Build the system prompt with tools, guidelines, and context */
16
+ export declare function buildSystemPrompt(customPrompt?: string, selectedTools?: ToolName[], appendSystemPrompt?: string): string;
17
+ //# sourceMappingURL=system-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAkDjD;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAqClF;AAED,kEAAkE;AAClE,wBAAgB,iBAAiB,CAChC,YAAY,CAAC,EAAE,MAAM,EACrB,aAAa,CAAC,EAAE,QAAQ,EAAE,EAC1B,kBAAkB,CAAC,EAAE,MAAM,GACzB,MAAM,CA0IR","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir, getReadmePath } from \"../config.js\";\nimport type { ToolName } from \"./tools/index.js\";\n\n/** Tool descriptions for system prompt */\nconst toolDescriptions: Record<ToolName, string> = {\n\tread: \"Read file contents\",\n\tbash: \"Execute bash commands (ls, grep, find, etc.)\",\n\tedit: \"Make surgical edits to files (find exact text and replace)\",\n\twrite: \"Create or overwrite files\",\n\tgrep: \"Search file contents for patterns (respects .gitignore)\",\n\tfind: \"Find files by glob pattern (respects .gitignore)\",\n\tls: \"List directory contents\",\n};\n\n/** Resolve input as file path or literal string */\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\n/** Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md) */\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.md\", \"CLAUDE.md\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load all project context files in order:\n * 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md\n * 2. Parent directories (top-most first) down to cwd\n * Each returns {path, content} for separate messages\n */\nexport function loadProjectContextFiles(): Array<{ path: string; content: string }> {\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\n\t// 1. Load global context from ~/{CONFIG_DIR_NAME}/agent/\n\tconst globalContextDir = getAgentDir();\n\tconst globalContext = loadContextFileFromDir(globalContextDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t}\n\n\t// 2. Walk up from cwd to root, collecting all context files\n\tconst cwd = process.cwd();\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tlet currentDir = cwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile) {\n\t\t\t// Add to beginning so we get top-most parent first\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t}\n\n\t\t// Stop if we've reached root\n\t\tif (currentDir === root) break;\n\n\t\t// Move up one directory\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break; // Safety check\n\t\tcurrentDir = parentDir;\n\t}\n\n\t// Add ancestor files in order (top-most → cwd)\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(\n\tcustomPrompt?: string,\n\tselectedTools?: ToolName[],\n\tappendSystemPrompt?: string,\n): string {\n\tconst resolvedCustomPrompt = resolvePromptInput(customPrompt, \"system prompt\");\n\tconst resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, \"append system prompt\");\n\n\tconst now = new Date();\n\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\tweekday: \"long\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"long\",\n\t\tday: \"numeric\",\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t\ttimeZoneName: \"short\",\n\t});\n\n\tconst appendSection = resolvedAppendPrompt ? `\\n\\n${resolvedAppendPrompt}` : \"\";\n\n\tif (resolvedCustomPrompt) {\n\t\tlet prompt = resolvedCustomPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Add date/time and working directory last\n\t\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\t\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute path to README.md\n\tconst readmePath = getReadmePath();\n\n\t// Build tools list based on selected tools\n\tconst tools = selectedTools || ([\"read\", \"bash\", \"edit\", \"write\"] as ToolName[]);\n\tconst toolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join(\"\\n\");\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasEdit = tools.includes(\"edit\");\n\tconst hasWrite = tools.includes(\"write\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// Read-only mode notice (no bash, edit, or write)\n\tif (!hasBash && !hasEdit && !hasWrite) {\n\t\tguidelinesList.push(\"You are in READ-ONLY mode - you cannot modify files or execute arbitrary commands\");\n\t}\n\n\t// Bash without edit/write = read-only bash mode\n\tif (hasBash && !hasEdit && !hasWrite) {\n\t\tguidelinesList.push(\n\t\t\t\"Use bash ONLY for read-only operations (git log, gh issue view, curl, etc.) - do NOT modify any files\",\n\t\t);\n\t}\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\tguidelinesList.push(\"Use bash for file operations like ls, grep, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\tguidelinesList.push(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\t// Read before edit guideline\n\tif (hasRead && hasEdit) {\n\t\tguidelinesList.push(\"Use read to examine files before editing\");\n\t}\n\n\t// Edit guideline\n\tif (hasEdit) {\n\t\tguidelinesList.push(\"Use edit for precise changes (old text must match exactly)\");\n\t}\n\n\t// Write guideline\n\tif (hasWrite) {\n\t\tguidelinesList.push(\"Use write only for new files or complete rewrites\");\n\t}\n\n\t// Output guideline (only when actually writing/executing)\n\tif (hasEdit || hasWrite) {\n\t\tguidelinesList.push(\n\t\t\t\"When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did\",\n\t\t);\n\t}\n\n\t// Always include these\n\tguidelinesList.push(\"Be concise in your responses\");\n\tguidelinesList.push(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nGuidelines:\n${guidelines}\n\nDocumentation:\n- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}\n- Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tconst contextFiles = loadProjectContextFiles();\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Add date/time and working directory last\n\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\treturn prompt;\n}\n"]}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * System prompt construction and project context loading
3
+ */
4
+ import chalk from "chalk";
5
+ import { existsSync, readFileSync } from "fs";
6
+ import { join, resolve } from "path";
7
+ import { getAgentDir, getReadmePath } from "../config.js";
8
+ /** Tool descriptions for system prompt */
9
+ const toolDescriptions = {
10
+ read: "Read file contents",
11
+ bash: "Execute bash commands (ls, grep, find, etc.)",
12
+ edit: "Make surgical edits to files (find exact text and replace)",
13
+ write: "Create or overwrite files",
14
+ grep: "Search file contents for patterns (respects .gitignore)",
15
+ find: "Find files by glob pattern (respects .gitignore)",
16
+ ls: "List directory contents",
17
+ };
18
+ /** Resolve input as file path or literal string */
19
+ function resolvePromptInput(input, description) {
20
+ if (!input) {
21
+ return undefined;
22
+ }
23
+ if (existsSync(input)) {
24
+ try {
25
+ return readFileSync(input, "utf-8");
26
+ }
27
+ catch (error) {
28
+ console.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));
29
+ return input;
30
+ }
31
+ }
32
+ return input;
33
+ }
34
+ /** Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md) */
35
+ function loadContextFileFromDir(dir) {
36
+ const candidates = ["AGENTS.md", "CLAUDE.md"];
37
+ for (const filename of candidates) {
38
+ const filePath = join(dir, filename);
39
+ if (existsSync(filePath)) {
40
+ try {
41
+ return {
42
+ path: filePath,
43
+ content: readFileSync(filePath, "utf-8"),
44
+ };
45
+ }
46
+ catch (error) {
47
+ console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
48
+ }
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ /**
54
+ * Load all project context files in order:
55
+ * 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md
56
+ * 2. Parent directories (top-most first) down to cwd
57
+ * Each returns {path, content} for separate messages
58
+ */
59
+ export function loadProjectContextFiles() {
60
+ const contextFiles = [];
61
+ // 1. Load global context from ~/{CONFIG_DIR_NAME}/agent/
62
+ const globalContextDir = getAgentDir();
63
+ const globalContext = loadContextFileFromDir(globalContextDir);
64
+ if (globalContext) {
65
+ contextFiles.push(globalContext);
66
+ }
67
+ // 2. Walk up from cwd to root, collecting all context files
68
+ const cwd = process.cwd();
69
+ const ancestorContextFiles = [];
70
+ let currentDir = cwd;
71
+ const root = resolve("/");
72
+ while (true) {
73
+ const contextFile = loadContextFileFromDir(currentDir);
74
+ if (contextFile) {
75
+ // Add to beginning so we get top-most parent first
76
+ ancestorContextFiles.unshift(contextFile);
77
+ }
78
+ // Stop if we've reached root
79
+ if (currentDir === root)
80
+ break;
81
+ // Move up one directory
82
+ const parentDir = resolve(currentDir, "..");
83
+ if (parentDir === currentDir)
84
+ break; // Safety check
85
+ currentDir = parentDir;
86
+ }
87
+ // Add ancestor files in order (top-most → cwd)
88
+ contextFiles.push(...ancestorContextFiles);
89
+ return contextFiles;
90
+ }
91
+ /** Build the system prompt with tools, guidelines, and context */
92
+ export function buildSystemPrompt(customPrompt, selectedTools, appendSystemPrompt) {
93
+ const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
94
+ const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
95
+ const now = new Date();
96
+ const dateTime = now.toLocaleString("en-US", {
97
+ weekday: "long",
98
+ year: "numeric",
99
+ month: "long",
100
+ day: "numeric",
101
+ hour: "2-digit",
102
+ minute: "2-digit",
103
+ second: "2-digit",
104
+ timeZoneName: "short",
105
+ });
106
+ const appendSection = resolvedAppendPrompt ? `\n\n${resolvedAppendPrompt}` : "";
107
+ if (resolvedCustomPrompt) {
108
+ let prompt = resolvedCustomPrompt;
109
+ if (appendSection) {
110
+ prompt += appendSection;
111
+ }
112
+ // Append project context files
113
+ const contextFiles = loadProjectContextFiles();
114
+ if (contextFiles.length > 0) {
115
+ prompt += "\n\n# Project Context\n\n";
116
+ prompt += "The following project context files have been loaded:\n\n";
117
+ for (const { path: filePath, content } of contextFiles) {
118
+ prompt += `## ${filePath}\n\n${content}\n\n`;
119
+ }
120
+ }
121
+ // Add date/time and working directory last
122
+ prompt += `\nCurrent date and time: ${dateTime}`;
123
+ prompt += `\nCurrent working directory: ${process.cwd()}`;
124
+ return prompt;
125
+ }
126
+ // Get absolute path to README.md
127
+ const readmePath = getReadmePath();
128
+ // Build tools list based on selected tools
129
+ const tools = selectedTools || ["read", "bash", "edit", "write"];
130
+ const toolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join("\n");
131
+ // Build guidelines based on which tools are actually available
132
+ const guidelinesList = [];
133
+ const hasBash = tools.includes("bash");
134
+ const hasEdit = tools.includes("edit");
135
+ const hasWrite = tools.includes("write");
136
+ const hasGrep = tools.includes("grep");
137
+ const hasFind = tools.includes("find");
138
+ const hasLs = tools.includes("ls");
139
+ const hasRead = tools.includes("read");
140
+ // Read-only mode notice (no bash, edit, or write)
141
+ if (!hasBash && !hasEdit && !hasWrite) {
142
+ guidelinesList.push("You are in READ-ONLY mode - you cannot modify files or execute arbitrary commands");
143
+ }
144
+ // Bash without edit/write = read-only bash mode
145
+ if (hasBash && !hasEdit && !hasWrite) {
146
+ guidelinesList.push("Use bash ONLY for read-only operations (git log, gh issue view, curl, etc.) - do NOT modify any files");
147
+ }
148
+ // File exploration guidelines
149
+ if (hasBash && !hasGrep && !hasFind && !hasLs) {
150
+ guidelinesList.push("Use bash for file operations like ls, grep, find");
151
+ }
152
+ else if (hasBash && (hasGrep || hasFind || hasLs)) {
153
+ guidelinesList.push("Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)");
154
+ }
155
+ // Read before edit guideline
156
+ if (hasRead && hasEdit) {
157
+ guidelinesList.push("Use read to examine files before editing");
158
+ }
159
+ // Edit guideline
160
+ if (hasEdit) {
161
+ guidelinesList.push("Use edit for precise changes (old text must match exactly)");
162
+ }
163
+ // Write guideline
164
+ if (hasWrite) {
165
+ guidelinesList.push("Use write only for new files or complete rewrites");
166
+ }
167
+ // Output guideline (only when actually writing/executing)
168
+ if (hasEdit || hasWrite) {
169
+ guidelinesList.push("When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did");
170
+ }
171
+ // Always include these
172
+ guidelinesList.push("Be concise in your responses");
173
+ guidelinesList.push("Show file paths clearly when working with files");
174
+ const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
175
+ let prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.
176
+
177
+ Available tools:
178
+ ${toolsList}
179
+
180
+ Guidelines:
181
+ ${guidelines}
182
+
183
+ Documentation:
184
+ - Your own documentation (including custom model setup and theme creation) is at: ${readmePath}
185
+ - Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;
186
+ if (appendSection) {
187
+ prompt += appendSection;
188
+ }
189
+ // Append project context files
190
+ const contextFiles = loadProjectContextFiles();
191
+ if (contextFiles.length > 0) {
192
+ prompt += "\n\n# Project Context\n\n";
193
+ prompt += "The following project context files have been loaded:\n\n";
194
+ for (const { path: filePath, content } of contextFiles) {
195
+ prompt += `## ${filePath}\n\n${content}\n\n`;
196
+ }
197
+ }
198
+ // Add date/time and working directory last
199
+ prompt += `\nCurrent date and time: ${dateTime}`;
200
+ prompt += `\nCurrent working directory: ${process.cwd()}`;
201
+ return prompt;
202
+ }
203
+ //# sourceMappingURL=system-prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system-prompt.js","sourceRoot":"","sources":["../../src/core/system-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * System prompt construction and project context loading\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join, resolve } from \"path\";\nimport { getAgentDir, getReadmePath } from \"../config.js\";\nimport type { ToolName } from \"./tools/index.js\";\n\n/** Tool descriptions for system prompt */\nconst toolDescriptions: Record<ToolName, string> = {\n\tread: \"Read file contents\",\n\tbash: \"Execute bash commands (ls, grep, find, etc.)\",\n\tedit: \"Make surgical edits to files (find exact text and replace)\",\n\twrite: \"Create or overwrite files\",\n\tgrep: \"Search file contents for patterns (respects .gitignore)\",\n\tfind: \"Find files by glob pattern (respects .gitignore)\",\n\tls: \"List directory contents\",\n};\n\n/** Resolve input as file path or literal string */\nfunction resolvePromptInput(input: string | undefined, description: string): string | undefined {\n\tif (!input) {\n\t\treturn undefined;\n\t}\n\n\tif (existsSync(input)) {\n\t\ttry {\n\t\t\treturn readFileSync(input, \"utf-8\");\n\t\t} catch (error) {\n\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${description} file ${input}: ${error}`));\n\t\t\treturn input;\n\t\t}\n\t}\n\n\treturn input;\n}\n\n/** Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md) */\nfunction loadContextFileFromDir(dir: string): { path: string; content: string } | null {\n\tconst candidates = [\"AGENTS.md\", \"CLAUDE.md\"];\n\tfor (const filename of candidates) {\n\t\tconst filePath = join(dir, filename);\n\t\tif (existsSync(filePath)) {\n\t\t\ttry {\n\t\t\t\treturn {\n\t\t\t\t\tpath: filePath,\n\t\t\t\t\tcontent: readFileSync(filePath, \"utf-8\"),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Load all project context files in order:\n * 1. Global: ~/{CONFIG_DIR_NAME}/agent/AGENTS.md or CLAUDE.md\n * 2. Parent directories (top-most first) down to cwd\n * Each returns {path, content} for separate messages\n */\nexport function loadProjectContextFiles(): Array<{ path: string; content: string }> {\n\tconst contextFiles: Array<{ path: string; content: string }> = [];\n\n\t// 1. Load global context from ~/{CONFIG_DIR_NAME}/agent/\n\tconst globalContextDir = getAgentDir();\n\tconst globalContext = loadContextFileFromDir(globalContextDir);\n\tif (globalContext) {\n\t\tcontextFiles.push(globalContext);\n\t}\n\n\t// 2. Walk up from cwd to root, collecting all context files\n\tconst cwd = process.cwd();\n\tconst ancestorContextFiles: Array<{ path: string; content: string }> = [];\n\n\tlet currentDir = cwd;\n\tconst root = resolve(\"/\");\n\n\twhile (true) {\n\t\tconst contextFile = loadContextFileFromDir(currentDir);\n\t\tif (contextFile) {\n\t\t\t// Add to beginning so we get top-most parent first\n\t\t\tancestorContextFiles.unshift(contextFile);\n\t\t}\n\n\t\t// Stop if we've reached root\n\t\tif (currentDir === root) break;\n\n\t\t// Move up one directory\n\t\tconst parentDir = resolve(currentDir, \"..\");\n\t\tif (parentDir === currentDir) break; // Safety check\n\t\tcurrentDir = parentDir;\n\t}\n\n\t// Add ancestor files in order (top-most → cwd)\n\tcontextFiles.push(...ancestorContextFiles);\n\n\treturn contextFiles;\n}\n\n/** Build the system prompt with tools, guidelines, and context */\nexport function buildSystemPrompt(\n\tcustomPrompt?: string,\n\tselectedTools?: ToolName[],\n\tappendSystemPrompt?: string,\n): string {\n\tconst resolvedCustomPrompt = resolvePromptInput(customPrompt, \"system prompt\");\n\tconst resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, \"append system prompt\");\n\n\tconst now = new Date();\n\tconst dateTime = now.toLocaleString(\"en-US\", {\n\t\tweekday: \"long\",\n\t\tyear: \"numeric\",\n\t\tmonth: \"long\",\n\t\tday: \"numeric\",\n\t\thour: \"2-digit\",\n\t\tminute: \"2-digit\",\n\t\tsecond: \"2-digit\",\n\t\ttimeZoneName: \"short\",\n\t});\n\n\tconst appendSection = resolvedAppendPrompt ? `\\n\\n${resolvedAppendPrompt}` : \"\";\n\n\tif (resolvedCustomPrompt) {\n\t\tlet prompt = resolvedCustomPrompt;\n\n\t\tif (appendSection) {\n\t\t\tprompt += appendSection;\n\t\t}\n\n\t\t// Append project context files\n\t\tconst contextFiles = loadProjectContextFiles();\n\t\tif (contextFiles.length > 0) {\n\t\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t\t}\n\t\t}\n\n\t\t// Add date/time and working directory last\n\t\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\t\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\t\treturn prompt;\n\t}\n\n\t// Get absolute path to README.md\n\tconst readmePath = getReadmePath();\n\n\t// Build tools list based on selected tools\n\tconst tools = selectedTools || ([\"read\", \"bash\", \"edit\", \"write\"] as ToolName[]);\n\tconst toolsList = tools.map((t) => `- ${t}: ${toolDescriptions[t]}`).join(\"\\n\");\n\n\t// Build guidelines based on which tools are actually available\n\tconst guidelinesList: string[] = [];\n\n\tconst hasBash = tools.includes(\"bash\");\n\tconst hasEdit = tools.includes(\"edit\");\n\tconst hasWrite = tools.includes(\"write\");\n\tconst hasGrep = tools.includes(\"grep\");\n\tconst hasFind = tools.includes(\"find\");\n\tconst hasLs = tools.includes(\"ls\");\n\tconst hasRead = tools.includes(\"read\");\n\n\t// Read-only mode notice (no bash, edit, or write)\n\tif (!hasBash && !hasEdit && !hasWrite) {\n\t\tguidelinesList.push(\"You are in READ-ONLY mode - you cannot modify files or execute arbitrary commands\");\n\t}\n\n\t// Bash without edit/write = read-only bash mode\n\tif (hasBash && !hasEdit && !hasWrite) {\n\t\tguidelinesList.push(\n\t\t\t\"Use bash ONLY for read-only operations (git log, gh issue view, curl, etc.) - do NOT modify any files\",\n\t\t);\n\t}\n\n\t// File exploration guidelines\n\tif (hasBash && !hasGrep && !hasFind && !hasLs) {\n\t\tguidelinesList.push(\"Use bash for file operations like ls, grep, find\");\n\t} else if (hasBash && (hasGrep || hasFind || hasLs)) {\n\t\tguidelinesList.push(\"Prefer grep/find/ls tools over bash for file exploration (faster, respects .gitignore)\");\n\t}\n\n\t// Read before edit guideline\n\tif (hasRead && hasEdit) {\n\t\tguidelinesList.push(\"Use read to examine files before editing\");\n\t}\n\n\t// Edit guideline\n\tif (hasEdit) {\n\t\tguidelinesList.push(\"Use edit for precise changes (old text must match exactly)\");\n\t}\n\n\t// Write guideline\n\tif (hasWrite) {\n\t\tguidelinesList.push(\"Use write only for new files or complete rewrites\");\n\t}\n\n\t// Output guideline (only when actually writing/executing)\n\tif (hasEdit || hasWrite) {\n\t\tguidelinesList.push(\n\t\t\t\"When summarizing your actions, output plain text directly - do NOT use cat or bash to display what you did\",\n\t\t);\n\t}\n\n\t// Always include these\n\tguidelinesList.push(\"Be concise in your responses\");\n\tguidelinesList.push(\"Show file paths clearly when working with files\");\n\n\tconst guidelines = guidelinesList.map((g) => `- ${g}`).join(\"\\n\");\n\n\tlet prompt = `You are an expert coding assistant. You help users with coding tasks by reading files, executing commands, editing code, and writing new files.\n\nAvailable tools:\n${toolsList}\n\nGuidelines:\n${guidelines}\n\nDocumentation:\n- Your own documentation (including custom model setup and theme creation) is at: ${readmePath}\n- Read it when users ask about features, configuration, or setup, and especially if the user asks you to add a custom model or provider, or create a custom theme.`;\n\n\tif (appendSection) {\n\t\tprompt += appendSection;\n\t}\n\n\t// Append project context files\n\tconst contextFiles = loadProjectContextFiles();\n\tif (contextFiles.length > 0) {\n\t\tprompt += \"\\n\\n# Project Context\\n\\n\";\n\t\tprompt += \"The following project context files have been loaded:\\n\\n\";\n\t\tfor (const { path: filePath, content } of contextFiles) {\n\t\t\tprompt += `## ${filePath}\\n\\n${content}\\n\\n`;\n\t\t}\n\t}\n\n\t// Add date/time and working directory last\n\tprompt += `\\nCurrent date and time: ${dateTime}`;\n\tprompt += `\\nCurrent working directory: ${process.cwd()}`;\n\n\treturn prompt;\n}\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash.d.ts","sourceRoot":"","sources":["../../../src/core/tools/bash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAcrD,QAAA,MAAM,UAAU;;;EAGd,CAAC;AAOH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAkKjD,CAAC","sourcesContent":["import { randomBytes } from \"node:crypto\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { getShellConfig, killProcessTree } from \"../../utils/shell.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pi-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\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.`,\n\tparameters: bashSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst { shell, args } = getShellConfig();\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\t// We'll stream to a temp file if output gets large\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\t\tlet totalBytes = 0;\n\n\t\t\t// Keep a rolling buffer of the last chunk for tail truncation\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\t// Keep more than we need so we have enough for truncation\n\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tonAbort();\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\ttotalBytes += data.length;\n\n\t\t\t\t// Start writing to temp file once we exceed the threshold\n\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t\t// Write all buffered chunks to the file\n\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Write to temp file if we have one\n\t\t\t\tif (tempFileStream) {\n\t\t\t\t\ttempFileStream.write(data);\n\t\t\t\t}\n\n\t\t\t\t// Keep rolling buffer of recent data\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\n\t\t\t\t// Trim old chunks if buffer is too large\n\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\t\tconst removed = chunks.shift()!;\n\t\t\t\t\tchunksBytes -= removed.length;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Collect stdout and stderr together\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", handleData);\n\t\t\t}\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", handleData);\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) {\n\t\t\t\t\tclearTimeout(timeoutHandle);\n\t\t\t\t}\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\t// Close temp file stream\n\t\t\t\tif (tempFileStream) {\n\t\t\t\t\ttempFileStream.end();\n\t\t\t\t}\n\n\t\t\t\t// Combine all buffered chunks\n\t\t\t\tconst fullBuffer = Buffer.concat(chunks);\n\t\t\t\tconst fullOutput = fullBuffer.toString(\"utf-8\");\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = fullOutput;\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\treject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = fullOutput;\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += `Command timed out after ${timeout} seconds`;\n\t\t\t\t\treject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Apply tail truncation\n\t\t\t\tconst truncation = truncateTail(fullOutput);\n\t\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t\t// Build details with truncation info\n\t\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t};\n\n\t\t\t\t\t// Build actionable notice\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\t// Edge case: last line alone > 30KB\n\t\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(fullOutput.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${code}`;\n\t\t\t\t\treject(new Error(outputText));\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { Type } from "@sinclair/typebox";
6
6
  import { spawn } from "child_process";
7
- import { getShellConfig, killProcessTree } from "../shell.js";
7
+ import { getShellConfig, killProcessTree } from "../../utils/shell.js";
8
8
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateTail } from "./truncate.js";
9
9
  /**
10
10
  * Generate a unique temp file path for bash output
@@ -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\";\nimport { createWriteStream } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { getShellConfig, killProcessTree } from \"../../utils/shell.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, type TruncationResult, truncateTail } from \"./truncate.js\";\n\n/**\n * Generate a unique temp file path for bash output\n */\nfunction getTempFilePath(): string {\n\tconst id = randomBytes(8).toString(\"hex\");\n\treturn join(tmpdir(), `pi-bash-${id}.log`);\n}\n\nconst bashSchema = Type.Object({\n\tcommand: Type.String({ description: \"Bash command to execute\" }),\n\ttimeout: Type.Optional(Type.Number({ description: \"Timeout in seconds (optional, no default timeout)\" })),\n});\n\ninterface BashToolDetails {\n\ttruncation?: TruncationResult;\n\tfullOutputPath?: string;\n}\n\nexport const bashTool: AgentTool<typeof bashSchema> = {\n\tname: \"bash\",\n\tlabel: \"bash\",\n\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.`,\n\tparameters: bashSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ command, timeout }: { command: string; timeout?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst { shell, args } = getShellConfig();\n\t\t\tconst child = spawn(shell, [...args, command], {\n\t\t\t\tdetached: true,\n\t\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\t});\n\n\t\t\t// We'll stream to a temp file if output gets large\n\t\t\tlet tempFilePath: string | undefined;\n\t\t\tlet tempFileStream: ReturnType<typeof createWriteStream> | undefined;\n\t\t\tlet totalBytes = 0;\n\n\t\t\t// Keep a rolling buffer of the last chunk for tail truncation\n\t\t\tconst chunks: Buffer[] = [];\n\t\t\tlet chunksBytes = 0;\n\t\t\t// Keep more than we need so we have enough for truncation\n\t\t\tconst maxChunksBytes = DEFAULT_MAX_BYTES * 2;\n\n\t\t\tlet timedOut = false;\n\n\t\t\t// Set timeout if provided\n\t\t\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\t\t\tif (timeout !== undefined && timeout > 0) {\n\t\t\t\ttimeoutHandle = setTimeout(() => {\n\t\t\t\t\ttimedOut = true;\n\t\t\t\t\tonAbort();\n\t\t\t\t}, timeout * 1000);\n\t\t\t}\n\n\t\t\tconst handleData = (data: Buffer) => {\n\t\t\t\ttotalBytes += data.length;\n\n\t\t\t\t// Start writing to temp file once we exceed the threshold\n\t\t\t\tif (totalBytes > DEFAULT_MAX_BYTES && !tempFilePath) {\n\t\t\t\t\ttempFilePath = getTempFilePath();\n\t\t\t\t\ttempFileStream = createWriteStream(tempFilePath);\n\t\t\t\t\t// Write all buffered chunks to the file\n\t\t\t\t\tfor (const chunk of chunks) {\n\t\t\t\t\t\ttempFileStream.write(chunk);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Write to temp file if we have one\n\t\t\t\tif (tempFileStream) {\n\t\t\t\t\ttempFileStream.write(data);\n\t\t\t\t}\n\n\t\t\t\t// Keep rolling buffer of recent data\n\t\t\t\tchunks.push(data);\n\t\t\t\tchunksBytes += data.length;\n\n\t\t\t\t// Trim old chunks if buffer is too large\n\t\t\t\twhile (chunksBytes > maxChunksBytes && chunks.length > 1) {\n\t\t\t\t\tconst removed = chunks.shift()!;\n\t\t\t\t\tchunksBytes -= removed.length;\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t// Collect stdout and stderr together\n\t\t\tif (child.stdout) {\n\t\t\t\tchild.stdout.on(\"data\", handleData);\n\t\t\t}\n\t\t\tif (child.stderr) {\n\t\t\t\tchild.stderr.on(\"data\", handleData);\n\t\t\t}\n\n\t\t\t// Handle process exit\n\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\tif (timeoutHandle) {\n\t\t\t\t\tclearTimeout(timeoutHandle);\n\t\t\t\t}\n\t\t\t\tif (signal) {\n\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t}\n\n\t\t\t\t// Close temp file stream\n\t\t\t\tif (tempFileStream) {\n\t\t\t\t\ttempFileStream.end();\n\t\t\t\t}\n\n\t\t\t\t// Combine all buffered chunks\n\t\t\t\tconst fullBuffer = Buffer.concat(chunks);\n\t\t\t\tconst fullOutput = fullBuffer.toString(\"utf-8\");\n\n\t\t\t\tif (signal?.aborted) {\n\t\t\t\t\tlet output = fullOutput;\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += \"Command aborted\";\n\t\t\t\t\treject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (timedOut) {\n\t\t\t\t\tlet output = fullOutput;\n\t\t\t\t\tif (output) output += \"\\n\\n\";\n\t\t\t\t\toutput += `Command timed out after ${timeout} seconds`;\n\t\t\t\t\treject(new Error(output));\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Apply tail truncation\n\t\t\t\tconst truncation = truncateTail(fullOutput);\n\t\t\t\tlet outputText = truncation.content || \"(no output)\";\n\n\t\t\t\t// Build details with truncation info\n\t\t\t\tlet details: BashToolDetails | undefined;\n\n\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\tdetails = {\n\t\t\t\t\t\ttruncation,\n\t\t\t\t\t\tfullOutputPath: tempFilePath,\n\t\t\t\t\t};\n\n\t\t\t\t\t// Build actionable notice\n\t\t\t\t\tconst startLine = truncation.totalLines - truncation.outputLines + 1;\n\t\t\t\t\tconst endLine = truncation.totalLines;\n\n\t\t\t\t\tif (truncation.lastLinePartial) {\n\t\t\t\t\t\t// Edge case: last line alone > 30KB\n\t\t\t\t\t\tconst lastLineSize = formatSize(Buffer.byteLength(fullOutput.split(\"\\n\").pop() || \"\", \"utf-8\"));\n\t\t\t\t\t\toutputText += `\\n\\n[Showing last ${formatSize(truncation.outputBytes)} of line ${endLine} (line is ${lastLineSize}). Full output: ${tempFilePath}]`;\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}. Full output: ${tempFilePath}]`;\n\t\t\t\t\t} else {\n\t\t\t\t\t\toutputText += `\\n\\n[Showing lines ${startLine}-${endLine} of ${truncation.totalLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Full output: ${tempFilePath}]`;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (code !== 0 && code !== null) {\n\t\t\t\t\toutputText += `\\n\\nCommand exited with code ${code}`;\n\t\t\t\t\treject(new Error(outputText));\n\t\t\t\t} else {\n\t\t\t\t\tresolve({ content: [{ type: \"text\", text: outputText }], details });\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle abort signal - kill entire process tree\n\t\t\tconst onAbort = () => {\n\t\t\t\tif (child.pid) {\n\t\t\t\t\tkillProcessTree(child.pid);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tif (signal.aborted) {\n\t\t\t\t\tonAbort();\n\t\t\t\t} else {\n\t\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.d.ts","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAmHrD,QAAA,MAAM,UAAU;;;;EAId,CAAC;AAEH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAmJjD,CAAC","sourcesContent":["import * as os from \"node:os\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { resolve as resolvePath } from \"path\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn os.homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn os.homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\t// Update line numbers for the skipped leading context\n\t\t\t\t\toldLineNum += skipStart;\n\t\t\t\t\tnewLineNum += skipStart;\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\t// Update line numbers for the skipped trailing context\n\t\t\t\t\toldLineNum += skipEnd;\n\t\t\t\t\tnewLineNum += skipEnd;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport const editTool: AgentTool<typeof editSchema> = {\n\tname: \"edit\",\n\tlabel: \"edit\",\n\tdescription:\n\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\tparameters: editSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ path, oldText, newText }: { path: string; oldText: string; newText: string },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\tconst absolutePath = resolvePath(expandPath(path));\n\n\t\treturn new Promise<{\n\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\tdetails: { diff: string } | undefined;\n\t\t}>((resolve, reject) => {\n\t\t\t// Check if already aborted\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet aborted = false;\n\n\t\t\t// Set up abort handler\n\t\t\tconst onAbort = () => {\n\t\t\t\taborted = true;\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t}\n\n\t\t\t// Perform the edit operation\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Check if file exists\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait access(absolutePath, constants.R_OK | constants.W_OK);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(new Error(`File not found: ${path}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if aborted before reading\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Read the file\n\t\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\n\t\t\t\t\t// Check if aborted after reading\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if old text exists\n\t\t\t\t\tif (!content.includes(oldText)) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Count occurrences\n\t\t\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if aborted before writing\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform replacement using indexOf + substring (raw string replace, no special character interpretation)\n\t\t\t\t\t// String.replace() interprets $ in the replacement string, so we do manual replacement\n\t\t\t\t\tconst index = content.indexOf(oldText);\n\t\t\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\t\t\t// Verify the replacement actually changed something\n\t\t\t\t\tif (content === newContent) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tawait writeFile(absolutePath, newContent, \"utf-8\");\n\n\t\t\t\t\t// Check if aborted after writing\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Clean up abort handler\n\t\t\t\t\tif (signal) {\n\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t\t\t});\n\t\t\t\t} catch (error: any) {\n\t\t\t\t\t// Clean up abort handler\n\t\t\t\t\tif (signal) {\n\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit.js","sourceRoot":"","sources":["../../../src/core/tools/edit.ts"],"names":[],"mappings":"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","sourcesContent":["import * as os from \"node:os\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport * as Diff from \"diff\";\nimport { constants } from \"fs\";\nimport { access, readFile, writeFile } from \"fs/promises\";\nimport { resolve as resolvePath } from \"path\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn os.homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn os.homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\n/**\n * Generate a unified diff string with line numbers and context\n */\nfunction generateDiffString(oldContent: string, newContent: string, contextLines = 4): string {\n\tconst parts = Diff.diffLines(oldContent, newContent);\n\tconst output: string[] = [];\n\n\tconst oldLines = oldContent.split(\"\\n\");\n\tconst newLines = newContent.split(\"\\n\");\n\tconst maxLineNum = Math.max(oldLines.length, newLines.length);\n\tconst lineNumWidth = String(maxLineNum).length;\n\n\tlet oldLineNum = 1;\n\tlet newLineNum = 1;\n\tlet lastWasChange = false;\n\n\tfor (let i = 0; i < parts.length; i++) {\n\t\tconst part = parts[i];\n\t\tconst raw = part.value.split(\"\\n\");\n\t\tif (raw[raw.length - 1] === \"\") {\n\t\t\traw.pop();\n\t\t}\n\n\t\tif (part.added || part.removed) {\n\t\t\t// Show the change\n\t\t\tfor (const line of raw) {\n\t\t\t\tif (part.added) {\n\t\t\t\t\tconst lineNum = String(newLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`+${lineNum} ${line}`);\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t} else {\n\t\t\t\t\t// removed\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(`-${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlastWasChange = true;\n\t\t} else {\n\t\t\t// Context lines - only show a few before/after changes\n\t\t\tconst nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);\n\n\t\t\tif (lastWasChange || nextPartIsChange) {\n\t\t\t\t// Show context\n\t\t\t\tlet linesToShow = raw;\n\t\t\t\tlet skipStart = 0;\n\t\t\t\tlet skipEnd = 0;\n\n\t\t\t\tif (!lastWasChange) {\n\t\t\t\t\t// Show only last N lines as leading context\n\t\t\t\t\tskipStart = Math.max(0, raw.length - contextLines);\n\t\t\t\t\tlinesToShow = raw.slice(skipStart);\n\t\t\t\t}\n\n\t\t\t\tif (!nextPartIsChange && linesToShow.length > contextLines) {\n\t\t\t\t\t// Show only first N lines as trailing context\n\t\t\t\t\tskipEnd = linesToShow.length - contextLines;\n\t\t\t\t\tlinesToShow = linesToShow.slice(0, contextLines);\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at start\n\t\t\t\tif (skipStart > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\t// Update line numbers for the skipped leading context\n\t\t\t\t\toldLineNum += skipStart;\n\t\t\t\t\tnewLineNum += skipStart;\n\t\t\t\t}\n\n\t\t\t\tfor (const line of linesToShow) {\n\t\t\t\t\tconst lineNum = String(oldLineNum).padStart(lineNumWidth, \" \");\n\t\t\t\t\toutput.push(` ${lineNum} ${line}`);\n\t\t\t\t\toldLineNum++;\n\t\t\t\t\tnewLineNum++;\n\t\t\t\t}\n\n\t\t\t\t// Add ellipsis if we skipped lines at end\n\t\t\t\tif (skipEnd > 0) {\n\t\t\t\t\toutput.push(` ${\"\".padStart(lineNumWidth, \" \")} ...`);\n\t\t\t\t\t// Update line numbers for the skipped trailing context\n\t\t\t\t\toldLineNum += skipEnd;\n\t\t\t\t\tnewLineNum += skipEnd;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Skip these context lines entirely\n\t\t\t\toldLineNum += raw.length;\n\t\t\t\tnewLineNum += raw.length;\n\t\t\t}\n\n\t\t\tlastWasChange = false;\n\t\t}\n\t}\n\n\treturn output.join(\"\\n\");\n}\n\nconst editSchema = Type.Object({\n\tpath: Type.String({ description: \"Path to the file to edit (relative or absolute)\" }),\n\toldText: Type.String({ description: \"Exact text to find and replace (must match exactly)\" }),\n\tnewText: Type.String({ description: \"New text to replace the old text with\" }),\n});\n\nexport const editTool: AgentTool<typeof editSchema> = {\n\tname: \"edit\",\n\tlabel: \"edit\",\n\tdescription:\n\t\t\"Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.\",\n\tparameters: editSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ path, oldText, newText }: { path: string; oldText: string; newText: string },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\tconst absolutePath = resolvePath(expandPath(path));\n\n\t\treturn new Promise<{\n\t\t\tcontent: Array<{ type: \"text\"; text: string }>;\n\t\t\tdetails: { diff: string } | undefined;\n\t\t}>((resolve, reject) => {\n\t\t\t// Check if already aborted\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet aborted = false;\n\n\t\t\t// Set up abort handler\n\t\t\tconst onAbort = () => {\n\t\t\t\taborted = true;\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t};\n\n\t\t\tif (signal) {\n\t\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\t}\n\n\t\t\t// Perform the edit operation\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Check if file exists\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait access(absolutePath, constants.R_OK | constants.W_OK);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(new Error(`File not found: ${path}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if aborted before reading\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Read the file\n\t\t\t\t\tconst content = await readFile(absolutePath, \"utf-8\");\n\n\t\t\t\t\t// Check if aborted after reading\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if old text exists\n\t\t\t\t\tif (!content.includes(oldText)) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Count occurrences\n\t\t\t\t\tconst occurrences = content.split(oldText).length - 1;\n\n\t\t\t\t\tif (occurrences > 1) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if aborted before writing\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Perform replacement using indexOf + substring (raw string replace, no special character interpretation)\n\t\t\t\t\t// String.replace() interprets $ in the replacement string, so we do manual replacement\n\t\t\t\t\tconst index = content.indexOf(oldText);\n\t\t\t\t\tconst newContent = content.substring(0, index) + newText + content.substring(index + oldText.length);\n\n\t\t\t\t\t// Verify the replacement actually changed something\n\t\t\t\t\tif (content === newContent) {\n\t\t\t\t\t\tif (signal) {\n\t\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treject(\n\t\t\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\t\t`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tawait writeFile(absolutePath, newContent, \"utf-8\");\n\n\t\t\t\t\t// Check if aborted after writing\n\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Clean up abort handler\n\t\t\t\t\tif (signal) {\n\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Successfully replaced text in ${path}. Changed ${oldText.length} characters to ${newText.length} characters.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { diff: generateDiffString(content, newContent) },\n\t\t\t\t\t});\n\t\t\t\t} catch (error: any) {\n\t\t\t\t\t// Clean up abort handler\n\t\t\t\t\tif (signal) {\n\t\t\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!aborted) {\n\t\t\t\t\t\treject(error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.d.ts","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAuBrD,QAAA,MAAM,UAAU;;;;EAMd,CAAC;AASH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CAoKjD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawnSync } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport { globSync } from \"glob\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from \"./truncate.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst DEFAULT_LIMIT = 1000;\n\ninterface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n}\n\nexport const findTool: AgentTool<typeof findSchema> = {\n\tname: \"find\",\n\tlabel: \"find\",\n\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\tparameters: findSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ pattern, path: searchDir, limit }: { pattern: string; path?: string; limit?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => reject(new Error(\"Operation aborted\"));\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Ensure fd is available\n\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\treject(new Error(\"fd is not available and could not be downloaded\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\n\t\t\t\t\t// Build fd arguments\n\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\"--glob\", // Use glob pattern\n\t\t\t\t\t\t\"--color=never\", // No ANSI colors\n\t\t\t\t\t\t\"--hidden\", // Search hidden files (but still respect .gitignore)\n\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t];\n\n\t\t\t\t\t// Include .gitignore files (root + nested) so fd respects them even outside git repos\n\t\t\t\t\tconst gitignoreFiles = new Set<string>();\n\t\t\t\t\tconst rootGitignore = path.join(searchPath, \".gitignore\");\n\t\t\t\t\tif (existsSync(rootGitignore)) {\n\t\t\t\t\t\tgitignoreFiles.add(rootGitignore);\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst nestedGitignores = globSync(\"**/.gitignore\", {\n\t\t\t\t\t\t\tcwd: searchPath,\n\t\t\t\t\t\t\tdot: true,\n\t\t\t\t\t\t\tabsolute: true,\n\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor (const file of nestedGitignores) {\n\t\t\t\t\t\t\tgitignoreFiles.add(file);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Ignore glob errors\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const gitignorePath of gitignoreFiles) {\n\t\t\t\t\t\targs.push(\"--ignore-file\", gitignorePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Pattern and path\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\t// Run fd\n\t\t\t\t\tconst result = spawnSync(fdPath, args, {\n\t\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t\t\tmaxBuffer: 10 * 1024 * 1024, // 10MB\n\t\t\t\t\t});\n\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\treject(new Error(`Failed to run fd: ${result.error.message}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst output = result.stdout?.trim() || \"\";\n\n\t\t\t\t\tif (result.status !== 0) {\n\t\t\t\t\t\tconst errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`;\n\t\t\t\t\t\t// fd returns non-zero for some errors but may still have partial output\n\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\treject(new Error(errorMsg));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!output) {\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No files found matching pattern\" }],\n\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst relativized: string[] = [];\n\n\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\tif (!line) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1); // +1 for the /\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\t\t\trelativePath += \"/\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trelativized.push(relativePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if we hit the result limit\n\t\t\t\t\tconst resultLimitReached = relativized.length >= effectiveLimit;\n\n\t\t\t\t\t// Apply byte truncation (no line limit since we already have result limit)\n\t\t\t\t\tconst rawOutput = relativized.join(\"\\n\");\n\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\n\t\t\t\t\tlet resultOutput = truncation.content;\n\t\t\t\t\tconst details: FindToolDetails = {};\n\n\t\t\t\t\t// Build notices\n\t\t\t\t\tconst notices: string[] = [];\n\n\t\t\t\t\tif (resultLimitReached) {\n\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tdetails.resultLimitReached = effectiveLimit;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: resultOutput }],\n\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t});\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\treject(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -4,7 +4,7 @@ import { existsSync } from "fs";
4
4
  import { globSync } from "glob";
5
5
  import { homedir } from "os";
6
6
  import path from "path";
7
- import { ensureTool } from "../tools-manager.js";
7
+ import { ensureTool } from "../../utils/tools-manager.js";
8
8
  import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
9
9
  /**
10
10
  * Expand ~ to home directory
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find.js","sourceRoot":"","sources":["../../../src/core/tools/find.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAyB,YAAY,EAAE,MAAM,eAAe,CAAC;AAEnG;;GAEG;AACH,SAAS,UAAU,CAAC,QAAgB,EAAU;IAC7C,IAAI,QAAQ,KAAK,GAAG,EAAE,CAAC;QACtB,OAAO,OAAO,EAAE,CAAC;IAClB,CAAC;IACD,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/B,OAAO,OAAO,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB;AAED,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC;IAC9B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,WAAW,EAAE,8EAA8E;KAC3F,CAAC;IACF,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,qDAAqD,EAAE,CAAC,CAAC;IACxG,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,2CAA2C,EAAE,CAAC,CAAC;CAC/F,CAAC,CAAC;AAEH,MAAM,aAAa,GAAG,IAAI,CAAC;AAO3B,MAAM,CAAC,MAAM,QAAQ,GAAiC;IACrD,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,MAAM;IACb,WAAW,EAAE,+IAA+I,aAAa,eAAe,iBAAiB,GAAG,IAAI,8BAA8B;IAC9O,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,KAAK,EACb,WAAmB,EACnB,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAsD,EACvF,MAAoB,EACnB,EAAE,CAAC;QACJ,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YACvC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACvC,OAAO;YACR,CAAC;YAED,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;YAC7D,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAE3D,CAAC,KAAK,IAAI,EAAE,CAAC;gBACZ,IAAI,CAAC;oBACJ,yBAAyB;oBACzB,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;oBAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,MAAM,CAAC,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;wBACrE,OAAO;oBACR,CAAC;oBAED,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC;oBAC9D,MAAM,cAAc,GAAG,KAAK,IAAI,aAAa,CAAC;oBAE9C,qBAAqB;oBACrB,MAAM,IAAI,GAAa;wBACtB,QAAQ,EAAE,mBAAmB;wBAC7B,eAAe,EAAE,iBAAiB;wBAClC,UAAU,EAAE,qDAAqD;wBACjE,eAAe;wBACf,MAAM,CAAC,cAAc,CAAC;qBACtB,CAAC;oBAEF,sFAAsF;oBACtF,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;oBACzC,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;oBAC1D,IAAI,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;wBAC/B,cAAc,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;oBACnC,CAAC;oBAED,IAAI,CAAC;wBACJ,MAAM,gBAAgB,GAAG,QAAQ,CAAC,eAAe,EAAE;4BAClD,GAAG,EAAE,UAAU;4BACf,GAAG,EAAE,IAAI;4BACT,QAAQ,EAAE,IAAI;4BACd,MAAM,EAAE,CAAC,oBAAoB,EAAE,YAAY,CAAC;yBAC5C,CAAC,CAAC;wBACH,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;4BACrC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;wBAC1B,CAAC;oBACF,CAAC;oBAAC,MAAM,CAAC;wBACR,qBAAqB;oBACtB,CAAC;oBAED,KAAK,MAAM,aAAa,IAAI,cAAc,EAAE,CAAC;wBAC5C,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;oBAC3C,CAAC;oBAED,mBAAmB;oBACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;oBAE/B,SAAS;oBACT,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE;wBACtC,QAAQ,EAAE,OAAO;wBACjB,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;qBACpC,CAAC,CAAC;oBAEH,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAE9C,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;wBAClB,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;wBAC/D,OAAO;oBACR,CAAC;oBAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;oBAE3C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBACzB,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,uBAAuB,MAAM,CAAC,MAAM,EAAE,CAAC;wBACjF,wEAAwE;wBACxE,IAAI,CAAC,MAAM,EAAE,CAAC;4BACb,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;4BAC5B,OAAO;wBACR,CAAC;oBACF,CAAC;oBAED,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,OAAO,CAAC;4BACP,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iCAAiC,EAAE,CAAC;4BACpE,OAAO,EAAE,SAAS;yBAClB,CAAC,CAAC;wBACH,OAAO;oBACR,CAAC;oBAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACjC,MAAM,WAAW,GAAa,EAAE,CAAC;oBAEjC,KAAK,MAAM,OAAO,IAAI,KAAK,EAAE,CAAC;wBAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;wBAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;4BACX,SAAS;wBACV,CAAC;wBAED,MAAM,gBAAgB,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;wBACnE,IAAI,YAAY,GAAG,IAAI,CAAC;wBACxB,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;4BACjC,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe;wBAClE,CAAC;6BAAM,CAAC;4BACP,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;wBAChD,CAAC;wBAED,IAAI,gBAAgB,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;4BACrD,YAAY,IAAI,GAAG,CAAC;wBACrB,CAAC;wBAED,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;oBAChC,CAAC;oBAED,mCAAmC;oBACnC,MAAM,kBAAkB,GAAG,WAAW,CAAC,MAAM,IAAI,cAAc,CAAC;oBAEhE,2EAA2E;oBAC3E,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACzC,MAAM,UAAU,GAAG,YAAY,CAAC,SAAS,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,gBAAgB,EAAE,CAAC,CAAC;oBAElF,IAAI,YAAY,GAAG,UAAU,CAAC,OAAO,CAAC;oBACtC,MAAM,OAAO,GAAoB,EAAE,CAAC;oBAEpC,gBAAgB;oBAChB,MAAM,OAAO,GAAa,EAAE,CAAC;oBAE7B,IAAI,kBAAkB,EAAE,CAAC;wBACxB,OAAO,CAAC,IAAI,CACX,GAAG,cAAc,qCAAqC,cAAc,GAAG,CAAC,8BAA8B,CACtG,CAAC;wBACF,OAAO,CAAC,kBAAkB,GAAG,cAAc,CAAC;oBAC7C,CAAC;oBAED,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;wBAC1B,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,CAAC;wBAC/D,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;oBACjC,CAAC;oBAED,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACxB,YAAY,IAAI,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;oBAC/C,CAAC;oBAED,OAAO,CAAC;wBACP,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;wBAC/C,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;qBAC9D,CAAC,CAAC;gBACJ,CAAC;gBAAC,OAAO,CAAM,EAAE,CAAC;oBACjB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBAC9C,MAAM,CAAC,CAAC,CAAC,CAAC;gBACX,CAAC;YAAA,CACD,CAAC,EAAE,CAAC;QAAA,CACL,CAAC,CAAC;IAAA,CACH;CACD,CAAC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawnSync } from \"child_process\";\nimport { existsSync } from \"fs\";\nimport { globSync } from \"glob\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from \"./truncate.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst findSchema = Type.Object({\n\tpattern: Type.String({\n\t\tdescription: \"Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'\",\n\t}),\n\tpath: Type.Optional(Type.String({ description: \"Directory to search in (default: current directory)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of results (default: 1000)\" })),\n});\n\nconst DEFAULT_LIMIT = 1000;\n\ninterface FindToolDetails {\n\ttruncation?: TruncationResult;\n\tresultLimitReached?: number;\n}\n\nexport const findTool: AgentTool<typeof findSchema> = {\n\tname: \"find\",\n\tlabel: \"find\",\n\tdescription: `Search for files by glob pattern. Returns matching file paths relative to the search directory. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} results or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first).`,\n\tparameters: findSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{ pattern, path: searchDir, limit }: { pattern: string; path?: string; limit?: number },\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst onAbort = () => reject(new Error(\"Operation aborted\"));\n\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\t// Ensure fd is available\n\t\t\t\t\tconst fdPath = await ensureTool(\"fd\", true);\n\t\t\t\t\tif (!fdPath) {\n\t\t\t\t\t\treject(new Error(\"fd is not available and could not be downloaded\"));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tconst effectiveLimit = limit ?? DEFAULT_LIMIT;\n\n\t\t\t\t\t// Build fd arguments\n\t\t\t\t\tconst args: string[] = [\n\t\t\t\t\t\t\"--glob\", // Use glob pattern\n\t\t\t\t\t\t\"--color=never\", // No ANSI colors\n\t\t\t\t\t\t\"--hidden\", // Search hidden files (but still respect .gitignore)\n\t\t\t\t\t\t\"--max-results\",\n\t\t\t\t\t\tString(effectiveLimit),\n\t\t\t\t\t];\n\n\t\t\t\t\t// Include .gitignore files (root + nested) so fd respects them even outside git repos\n\t\t\t\t\tconst gitignoreFiles = new Set<string>();\n\t\t\t\t\tconst rootGitignore = path.join(searchPath, \".gitignore\");\n\t\t\t\t\tif (existsSync(rootGitignore)) {\n\t\t\t\t\t\tgitignoreFiles.add(rootGitignore);\n\t\t\t\t\t}\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst nestedGitignores = globSync(\"**/.gitignore\", {\n\t\t\t\t\t\t\tcwd: searchPath,\n\t\t\t\t\t\t\tdot: true,\n\t\t\t\t\t\t\tabsolute: true,\n\t\t\t\t\t\t\tignore: [\"**/node_modules/**\", \"**/.git/**\"],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfor (const file of nestedGitignores) {\n\t\t\t\t\t\t\tgitignoreFiles.add(file);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Ignore glob errors\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const gitignorePath of gitignoreFiles) {\n\t\t\t\t\t\targs.push(\"--ignore-file\", gitignorePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Pattern and path\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\t// Run fd\n\t\t\t\t\tconst result = spawnSync(fdPath, args, {\n\t\t\t\t\t\tencoding: \"utf-8\",\n\t\t\t\t\t\tmaxBuffer: 10 * 1024 * 1024, // 10MB\n\t\t\t\t\t});\n\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\n\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\treject(new Error(`Failed to run fd: ${result.error.message}`));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst output = result.stdout?.trim() || \"\";\n\n\t\t\t\t\tif (result.status !== 0) {\n\t\t\t\t\t\tconst errorMsg = result.stderr?.trim() || `fd exited with code ${result.status}`;\n\t\t\t\t\t\t// fd returns non-zero for some errors but may still have partial output\n\t\t\t\t\t\tif (!output) {\n\t\t\t\t\t\t\treject(new Error(errorMsg));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!output) {\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No files found matching pattern\" }],\n\t\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst relativized: string[] = [];\n\n\t\t\t\t\tfor (const rawLine of lines) {\n\t\t\t\t\t\tconst line = rawLine.replace(/\\r$/, \"\").trim();\n\t\t\t\t\t\tif (!line) {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst hadTrailingSlash = line.endsWith(\"/\") || line.endsWith(\"\\\\\");\n\t\t\t\t\t\tlet relativePath = line;\n\t\t\t\t\t\tif (line.startsWith(searchPath)) {\n\t\t\t\t\t\t\trelativePath = line.slice(searchPath.length + 1); // +1 for the /\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trelativePath = path.relative(searchPath, line);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (hadTrailingSlash && !relativePath.endsWith(\"/\")) {\n\t\t\t\t\t\t\trelativePath += \"/\";\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\trelativized.push(relativePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check if we hit the result limit\n\t\t\t\t\tconst resultLimitReached = relativized.length >= effectiveLimit;\n\n\t\t\t\t\t// Apply byte truncation (no line limit since we already have result limit)\n\t\t\t\t\tconst rawOutput = relativized.join(\"\\n\");\n\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\n\t\t\t\t\tlet resultOutput = truncation.content;\n\t\t\t\t\tconst details: FindToolDetails = {};\n\n\t\t\t\t\t// Build notices\n\t\t\t\t\tconst notices: string[] = [];\n\n\t\t\t\t\tif (resultLimitReached) {\n\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t`${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tdetails.resultLimitReached = effectiveLimit;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\tresultOutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t}\n\n\t\t\t\t\tresolve({\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: resultOutput }],\n\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t});\n\t\t\t\t} catch (e: any) {\n\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\treject(e);\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep.d.ts","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AA6BrD,QAAA,MAAM,UAAU;;;;;;;;EAYd,CAAC;AAUH,eAAO,MAAM,QAAQ,EAAE,SAAS,CAAC,OAAO,UAAU,CA2QjD,CAAC","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, type Stats, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst DEFAULT_LIMIT = 100;\n\ninterface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\nexport const grepTool: AgentTool<typeof grepSchema> = {\n\tname: \"grep\",\n\tlabel: \"grep\",\n\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\tparameters: grepSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{\n\t\t\tpattern,\n\t\t\tpath: searchDir,\n\t\t\tglob,\n\t\t\tignoreCase,\n\t\t\tliteral,\n\t\t\tcontext,\n\t\t\tlimit,\n\t\t}: {\n\t\t\tpattern: string;\n\t\t\tpath?: string;\n\t\t\tglob?: string;\n\t\t\tignoreCase?: boolean;\n\t\t\tliteral?: boolean;\n\t\t\tcontext?: number;\n\t\t\tlimit?: number;\n\t\t},\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tlet searchStat: Stats;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsearchStat = statSync(searchPath);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isDirectory = searchStat.isDirectory();\n\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\n\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\tconst getFileLines = (filePath: string): string[] => {\n\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn lines;\n\t\t\t\t\t};\n\n\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\n\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (literal) {\n\t\t\t\t\t\targs.push(\"--fixed-strings\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (glob) {\n\t\t\t\t\t\targs.push(\"--glob\", glob);\n\t\t\t\t\t}\n\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\trl.close();\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst stopChild = (dueToLimit: boolean = false) => {\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\tstopChild();\n\t\t\t\t\t};\n\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t});\n\n\t\t\t\t\tconst formatBlock = (filePath: string, lineNumber: number): string[] => {\n\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\tconst lines = getFileLines(filePath);\n\t\t\t\t\t\tif (!lines.length) {\n\t\t\t\t\t\t\treturn [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\n\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\n\t\t\t\t\t\t\t// Truncate long lines\n\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\tif (wasTruncated) {\n\t\t\t\t\t\t\t\tlinesTruncated = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (isMatchLine) {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn block;\n\t\t\t\t\t};\n\n\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\n\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\") {\n\t\t\t\t\t\t\t\toutputLines.push(...formatBlock(filePath, lineNumber));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\tcleanup();\n\n\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Apply byte truncation (no line limit since we already have match limit)\n\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\n\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\n\t\t\t\t\t\t// Build notices\n\t\t\t\t\t\tconst notices: string[] = [];\n\n\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\t\toutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -4,7 +4,7 @@ import { spawn } from "child_process";
4
4
  import { readFileSync, statSync } from "fs";
5
5
  import { homedir } from "os";
6
6
  import path from "path";
7
- import { ensureTool } from "../tools-manager.js";
7
+ import { ensureTool } from "../../utils/tools-manager.js";
8
8
  import { DEFAULT_MAX_BYTES, formatSize, GREP_MAX_LINE_LENGTH, truncateHead, truncateLine, } from "./truncate.js";
9
9
  /**
10
10
  * Expand ~ to home directory
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grep.js","sourceRoot":"","sources":["../../../src/core/tools/grep.ts"],"names":[],"mappings":"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","sourcesContent":["import { createInterface } from \"node:readline\";\nimport type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { spawn } from \"child_process\";\nimport { readFileSync, type Stats, statSync } from \"fs\";\nimport { homedir } from \"os\";\nimport path from \"path\";\nimport { ensureTool } from \"../../utils/tools-manager.js\";\nimport {\n\tDEFAULT_MAX_BYTES,\n\tformatSize,\n\tGREP_MAX_LINE_LENGTH,\n\ttype TruncationResult,\n\ttruncateHead,\n\ttruncateLine,\n} from \"./truncate.js\";\n\n/**\n * Expand ~ to home directory\n */\nfunction expandPath(filePath: string): string {\n\tif (filePath === \"~\") {\n\t\treturn homedir();\n\t}\n\tif (filePath.startsWith(\"~/\")) {\n\t\treturn homedir() + filePath.slice(1);\n\t}\n\treturn filePath;\n}\n\nconst grepSchema = Type.Object({\n\tpattern: Type.String({ description: \"Search pattern (regex or literal string)\" }),\n\tpath: Type.Optional(Type.String({ description: \"Directory or file to search (default: current directory)\" })),\n\tglob: Type.Optional(Type.String({ description: \"Filter files by glob pattern, e.g. '*.ts' or '**/*.spec.ts'\" })),\n\tignoreCase: Type.Optional(Type.Boolean({ description: \"Case-insensitive search (default: false)\" })),\n\tliteral: Type.Optional(\n\t\tType.Boolean({ description: \"Treat pattern as literal string instead of regex (default: false)\" }),\n\t),\n\tcontext: Type.Optional(\n\t\tType.Number({ description: \"Number of lines to show before and after each match (default: 0)\" }),\n\t),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of matches to return (default: 100)\" })),\n});\n\nconst DEFAULT_LIMIT = 100;\n\ninterface GrepToolDetails {\n\ttruncation?: TruncationResult;\n\tmatchLimitReached?: number;\n\tlinesTruncated?: boolean;\n}\n\nexport const grepTool: AgentTool<typeof grepSchema> = {\n\tname: \"grep\",\n\tlabel: \"grep\",\n\tdescription: `Search file contents for a pattern. Returns matching lines with file paths and line numbers. Respects .gitignore. Output is truncated to ${DEFAULT_LIMIT} matches or ${DEFAULT_MAX_BYTES / 1024}KB (whichever is hit first). Long lines are truncated to ${GREP_MAX_LINE_LENGTH} chars.`,\n\tparameters: grepSchema,\n\texecute: async (\n\t\t_toolCallId: string,\n\t\t{\n\t\t\tpattern,\n\t\t\tpath: searchDir,\n\t\t\tglob,\n\t\t\tignoreCase,\n\t\t\tliteral,\n\t\t\tcontext,\n\t\t\tlimit,\n\t\t}: {\n\t\t\tpattern: string;\n\t\t\tpath?: string;\n\t\t\tglob?: string;\n\t\t\tignoreCase?: boolean;\n\t\t\tliteral?: boolean;\n\t\t\tcontext?: number;\n\t\t\tlimit?: number;\n\t\t},\n\t\tsignal?: AbortSignal,\n\t) => {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (signal?.aborted) {\n\t\t\t\treject(new Error(\"Operation aborted\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet settled = false;\n\t\t\tconst settle = (fn: () => void) => {\n\t\t\t\tif (!settled) {\n\t\t\t\t\tsettled = true;\n\t\t\t\t\tfn();\n\t\t\t\t}\n\t\t\t};\n\n\t\t\t(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst rgPath = await ensureTool(\"rg\", true);\n\t\t\t\t\tif (!rgPath) {\n\t\t\t\t\t\tsettle(() => reject(new Error(\"ripgrep (rg) is not available and could not be downloaded\")));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst searchPath = path.resolve(expandPath(searchDir || \".\"));\n\t\t\t\t\tlet searchStat: Stats;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tsearchStat = statSync(searchPath);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tsettle(() => reject(new Error(`Path not found: ${searchPath}`)));\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst isDirectory = searchStat.isDirectory();\n\t\t\t\t\tconst contextValue = context && context > 0 ? context : 0;\n\t\t\t\t\tconst effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT);\n\n\t\t\t\t\tconst formatPath = (filePath: string): string => {\n\t\t\t\t\t\tif (isDirectory) {\n\t\t\t\t\t\t\tconst relative = path.relative(searchPath, filePath);\n\t\t\t\t\t\t\tif (relative && !relative.startsWith(\"..\")) {\n\t\t\t\t\t\t\t\treturn relative.replace(/\\\\/g, \"/\");\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn path.basename(filePath);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst fileCache = new Map<string, string[]>();\n\t\t\t\t\tconst getFileLines = (filePath: string): string[] => {\n\t\t\t\t\t\tlet lines = fileCache.get(filePath);\n\t\t\t\t\t\tif (!lines) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst content = readFileSync(filePath, \"utf-8\");\n\t\t\t\t\t\t\t\tlines = content.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\").split(\"\\n\");\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\tlines = [];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfileCache.set(filePath, lines);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn lines;\n\t\t\t\t\t};\n\n\t\t\t\t\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\n\t\t\t\t\tif (ignoreCase) {\n\t\t\t\t\t\targs.push(\"--ignore-case\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (literal) {\n\t\t\t\t\t\targs.push(\"--fixed-strings\");\n\t\t\t\t\t}\n\n\t\t\t\t\tif (glob) {\n\t\t\t\t\t\targs.push(\"--glob\", glob);\n\t\t\t\t\t}\n\n\t\t\t\t\targs.push(pattern, searchPath);\n\n\t\t\t\t\tconst child = spawn(rgPath, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\t\t\t\tconst rl = createInterface({ input: child.stdout });\n\t\t\t\t\tlet stderr = \"\";\n\t\t\t\t\tlet matchCount = 0;\n\t\t\t\t\tlet matchLimitReached = false;\n\t\t\t\t\tlet linesTruncated = false;\n\t\t\t\t\tlet aborted = false;\n\t\t\t\t\tlet killedDueToLimit = false;\n\t\t\t\t\tconst outputLines: string[] = [];\n\n\t\t\t\t\tconst cleanup = () => {\n\t\t\t\t\t\trl.close();\n\t\t\t\t\t\tsignal?.removeEventListener(\"abort\", onAbort);\n\t\t\t\t\t};\n\n\t\t\t\t\tconst stopChild = (dueToLimit: boolean = false) => {\n\t\t\t\t\t\tif (!child.killed) {\n\t\t\t\t\t\t\tkilledDueToLimit = dueToLimit;\n\t\t\t\t\t\t\tchild.kill();\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tconst onAbort = () => {\n\t\t\t\t\t\taborted = true;\n\t\t\t\t\t\tstopChild();\n\t\t\t\t\t};\n\n\t\t\t\t\tsignal?.addEventListener(\"abort\", onAbort, { once: true });\n\n\t\t\t\t\tchild.stderr?.on(\"data\", (chunk) => {\n\t\t\t\t\t\tstderr += chunk.toString();\n\t\t\t\t\t});\n\n\t\t\t\t\tconst formatBlock = (filePath: string, lineNumber: number): string[] => {\n\t\t\t\t\t\tconst relativePath = formatPath(filePath);\n\t\t\t\t\t\tconst lines = getFileLines(filePath);\n\t\t\t\t\t\tif (!lines.length) {\n\t\t\t\t\t\t\treturn [`${relativePath}:${lineNumber}: (unable to read file)`];\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst block: string[] = [];\n\t\t\t\t\t\tconst start = contextValue > 0 ? Math.max(1, lineNumber - contextValue) : lineNumber;\n\t\t\t\t\t\tconst end = contextValue > 0 ? Math.min(lines.length, lineNumber + contextValue) : lineNumber;\n\n\t\t\t\t\t\tfor (let current = start; current <= end; current++) {\n\t\t\t\t\t\t\tconst lineText = lines[current - 1] ?? \"\";\n\t\t\t\t\t\t\tconst sanitized = lineText.replace(/\\r/g, \"\");\n\t\t\t\t\t\t\tconst isMatchLine = current === lineNumber;\n\n\t\t\t\t\t\t\t// Truncate long lines\n\t\t\t\t\t\t\tconst { text: truncatedText, wasTruncated } = truncateLine(sanitized);\n\t\t\t\t\t\t\tif (wasTruncated) {\n\t\t\t\t\t\t\t\tlinesTruncated = true;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (isMatchLine) {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}:${current}: ${truncatedText}`);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tblock.push(`${relativePath}-${current}- ${truncatedText}`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn block;\n\t\t\t\t\t};\n\n\t\t\t\t\trl.on(\"line\", (line) => {\n\t\t\t\t\t\tif (!line.trim() || matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tlet event: any;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tevent = JSON.parse(line);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (event.type === \"match\") {\n\t\t\t\t\t\t\tmatchCount++;\n\t\t\t\t\t\t\tconst filePath = event.data?.path?.text;\n\t\t\t\t\t\t\tconst lineNumber = event.data?.line_number;\n\n\t\t\t\t\t\t\tif (filePath && typeof lineNumber === \"number\") {\n\t\t\t\t\t\t\t\toutputLines.push(...formatBlock(filePath, lineNumber));\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (matchCount >= effectiveLimit) {\n\t\t\t\t\t\t\t\tmatchLimitReached = true;\n\t\t\t\t\t\t\t\tstopChild(true);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"error\", (error) => {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t\tsettle(() => reject(new Error(`Failed to run ripgrep: ${error.message}`)));\n\t\t\t\t\t});\n\n\t\t\t\t\tchild.on(\"close\", (code) => {\n\t\t\t\t\t\tcleanup();\n\n\t\t\t\t\t\tif (aborted) {\n\t\t\t\t\t\t\tsettle(() => reject(new Error(\"Operation aborted\")));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!killedDueToLimit && code !== 0 && code !== 1) {\n\t\t\t\t\t\t\tconst errorMsg = stderr.trim() || `ripgrep exited with code ${code}`;\n\t\t\t\t\t\t\tsettle(() => reject(new Error(errorMsg)));\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (matchCount === 0) {\n\t\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\t\tresolve({ content: [{ type: \"text\", text: \"No matches found\" }], details: undefined }),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Apply byte truncation (no line limit since we already have match limit)\n\t\t\t\t\t\tconst rawOutput = outputLines.join(\"\\n\");\n\t\t\t\t\t\tconst truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });\n\n\t\t\t\t\t\tlet output = truncation.content;\n\t\t\t\t\t\tconst details: GrepToolDetails = {};\n\n\t\t\t\t\t\t// Build notices\n\t\t\t\t\t\tconst notices: string[] = [];\n\n\t\t\t\t\t\tif (matchLimitReached) {\n\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t`${effectiveLimit} matches limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tdetails.matchLimitReached = effectiveLimit;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (truncation.truncated) {\n\t\t\t\t\t\t\tnotices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);\n\t\t\t\t\t\t\tdetails.truncation = truncation;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\t\tnotices.push(\n\t\t\t\t\t\t\t\t`Some lines truncated to ${GREP_MAX_LINE_LENGTH} chars. Use read tool to see full lines`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tdetails.linesTruncated = true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (notices.length > 0) {\n\t\t\t\t\t\t\toutput += `\\n\\n[${notices.join(\". \")}]`;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tsettle(() =>\n\t\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: output }],\n\t\t\t\t\t\t\t\tdetails: Object.keys(details).length > 0 ? details : undefined,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t);\n\t\t\t\t\t});\n\t\t\t\t} catch (err) {\n\t\t\t\t\tsettle(() => reject(err as Error));\n\t\t\t\t}\n\t\t\t})();\n\t\t});\n\t},\n};\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAWvC,eAAO,MAAM,WAAW;;;;;;;;;;;;;;WAA4C,CAAC;AAGrE,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAQpB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,MAAM,OAAO,QAAQ,CAAC","sourcesContent":["export { bashTool } from \"./bash.js\";\nexport { editTool } from \"./edit.js\";\nexport { findTool } from \"./find.js\";\nexport { grepTool } from \"./grep.js\";\nexport { lsTool } from \"./ls.js\";\nexport { readTool } from \"./read.js\";\nexport { writeTool } from \"./write.js\";\n\nimport { bashTool } from \"./bash.js\";\nimport { editTool } from \"./edit.js\";\nimport { findTool } from \"./find.js\";\nimport { grepTool } from \"./grep.js\";\nimport { lsTool } from \"./ls.js\";\nimport { readTool } from \"./read.js\";\nimport { writeTool } from \"./write.js\";\n\n// Default tools for full access mode\nexport const codingTools = [readTool, bashTool, editTool, writeTool];\n\n// All available tools (including read-only exploration tools)\nexport const allTools = {\n\tread: readTool,\n\tbash: bashTool,\n\tedit: editTool,\n\twrite: writeTool,\n\tgrep: grepTool,\n\tfind: findTool,\n\tls: lsTool,\n};\n\nexport type ToolName = keyof typeof allTools;\n"]}