@ebowwa/coder 0.7.63 → 0.7.64

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 (341) hide show
  1. package/dist/core/__tests__/permissions.test.d.ts +12 -0
  2. package/dist/core/__tests__/permissions.test.d.ts.map +1 -0
  3. package/dist/core/__tests__/permissions.test.js +851 -0
  4. package/dist/core/agent-loop/__tests__/compaction.test.d.ts +5 -0
  5. package/dist/core/agent-loop/__tests__/compaction.test.d.ts.map +1 -0
  6. package/dist/core/agent-loop/__tests__/compaction.test.js +209 -0
  7. package/dist/core/agent-loop/__tests__/formatters.test.d.ts +5 -0
  8. package/dist/core/agent-loop/__tests__/formatters.test.d.ts.map +1 -0
  9. package/dist/core/agent-loop/__tests__/formatters.test.js +195 -0
  10. package/dist/core/agent-loop/__tests__/index.test.d.ts +5 -0
  11. package/dist/core/agent-loop/__tests__/index.test.d.ts.map +1 -0
  12. package/dist/core/agent-loop/__tests__/index.test.js +121 -0
  13. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts +5 -0
  14. package/dist/core/agent-loop/__tests__/loop-state.test.d.ts.map +1 -0
  15. package/dist/core/agent-loop/__tests__/loop-state.test.js +340 -0
  16. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts +5 -0
  17. package/dist/core/agent-loop/__tests__/message-builder.test.d.ts.map +1 -0
  18. package/dist/core/agent-loop/__tests__/message-builder.test.js +178 -0
  19. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts +5 -0
  20. package/dist/core/agent-loop/__tests__/tool-executor.test.d.ts.map +1 -0
  21. package/dist/core/agent-loop/__tests__/tool-executor.test.js +331 -0
  22. package/dist/core/agent-loop/compaction.d.ts +39 -0
  23. package/dist/core/agent-loop/compaction.d.ts.map +1 -0
  24. package/dist/core/agent-loop/compaction.js +51 -0
  25. package/dist/core/agent-loop/formatters.d.ts +21 -0
  26. package/dist/core/agent-loop/formatters.d.ts.map +1 -0
  27. package/dist/core/agent-loop/formatters.js +42 -0
  28. package/dist/core/agent-loop/index.d.ts +25 -0
  29. package/dist/core/agent-loop/index.d.ts.map +1 -0
  30. package/dist/core/agent-loop/index.js +83 -0
  31. package/dist/core/agent-loop/loop-state.d.ts +74 -0
  32. package/dist/core/agent-loop/loop-state.d.ts.map +1 -0
  33. package/dist/core/agent-loop/loop-state.js +147 -0
  34. package/dist/core/agent-loop/message-builder.d.ts +13 -0
  35. package/dist/core/agent-loop/message-builder.d.ts.map +1 -0
  36. package/dist/core/agent-loop/message-builder.js +49 -0
  37. package/dist/core/agent-loop/tool-executor.d.ts +23 -0
  38. package/dist/core/agent-loop/tool-executor.d.ts.map +1 -0
  39. package/dist/core/agent-loop/tool-executor.js +152 -0
  40. package/dist/core/agent-loop/turn-executor.d.ts +57 -0
  41. package/dist/core/agent-loop/turn-executor.d.ts.map +1 -0
  42. package/dist/core/agent-loop/turn-executor.js +124 -0
  43. package/dist/core/agent-loop/types.d.ts +141 -0
  44. package/dist/core/agent-loop/types.d.ts.map +1 -0
  45. package/dist/core/agent-loop/types.js +4 -0
  46. package/dist/core/agent-loop.d.ts +17 -0
  47. package/dist/core/agent-loop.d.ts.map +1 -0
  48. package/dist/core/agent-loop.js +16 -0
  49. package/dist/core/api-client-impl.d.ts +62 -0
  50. package/dist/core/api-client-impl.d.ts.map +1 -0
  51. package/dist/core/api-client-impl.js +479 -0
  52. package/dist/core/api-client.d.ts +6 -0
  53. package/dist/core/api-client.d.ts.map +1 -0
  54. package/dist/core/api-client.js +5 -0
  55. package/dist/core/checkpoints.d.ts +128 -0
  56. package/dist/core/checkpoints.d.ts.map +1 -0
  57. package/dist/core/checkpoints.js +438 -0
  58. package/dist/core/claude-md.d.ts +71 -0
  59. package/dist/core/claude-md.d.ts.map +1 -0
  60. package/dist/core/claude-md.js +198 -0
  61. package/dist/core/cognitive-security/hooks.d.ts +138 -0
  62. package/dist/core/cognitive-security/hooks.d.ts.map +1 -0
  63. package/dist/core/cognitive-security/hooks.js +389 -0
  64. package/dist/core/cognitive-security/index.d.ts +751 -0
  65. package/dist/core/cognitive-security/index.d.ts.map +1 -0
  66. package/dist/core/cognitive-security/index.js +1123 -0
  67. package/dist/core/cognitive-security/middleware.d.ts +136 -0
  68. package/dist/core/cognitive-security/middleware.d.ts.map +1 -0
  69. package/dist/core/cognitive-security/middleware.js +376 -0
  70. package/dist/core/config-loader.d.ts +127 -0
  71. package/dist/core/config-loader.d.ts.map +1 -0
  72. package/dist/core/config-loader.js +219 -0
  73. package/dist/core/context-compaction.d.ts +87 -0
  74. package/dist/core/context-compaction.d.ts.map +1 -0
  75. package/dist/core/context-compaction.js +428 -0
  76. package/dist/core/git-status.d.ts +25 -0
  77. package/dist/core/git-status.d.ts.map +1 -0
  78. package/dist/core/git-status.js +204 -0
  79. package/dist/core/image.d.ts +69 -0
  80. package/dist/core/image.d.ts.map +1 -0
  81. package/dist/core/image.js +290 -0
  82. package/dist/core/image.test.d.ts +2 -0
  83. package/dist/core/image.test.d.ts.map +1 -0
  84. package/dist/core/image.test.js +149 -0
  85. package/dist/core/models.d.ts +123 -0
  86. package/dist/core/models.d.ts.map +1 -0
  87. package/dist/core/models.js +325 -0
  88. package/dist/core/permissions.d.ts +81 -0
  89. package/dist/core/permissions.d.ts.map +1 -0
  90. package/dist/core/permissions.js +327 -0
  91. package/dist/core/retry.d.ts +25 -0
  92. package/dist/core/retry.d.ts.map +1 -0
  93. package/dist/core/retry.js +121 -0
  94. package/dist/core/session-store.d.ts +9 -0
  95. package/dist/core/session-store.d.ts.map +1 -0
  96. package/dist/core/session-store.js +10 -0
  97. package/dist/core/sessions/export.d.ts +47 -0
  98. package/dist/core/sessions/export.d.ts.map +1 -0
  99. package/dist/core/sessions/export.js +256 -0
  100. package/dist/core/sessions/index.d.ts +132 -0
  101. package/dist/core/sessions/index.d.ts.map +1 -0
  102. package/dist/core/sessions/index.js +442 -0
  103. package/dist/core/sessions/metadata.d.ts +77 -0
  104. package/dist/core/sessions/metadata.d.ts.map +1 -0
  105. package/dist/core/sessions/metadata.js +233 -0
  106. package/dist/core/sessions/persistence.d.ts +72 -0
  107. package/dist/core/sessions/persistence.d.ts.map +1 -0
  108. package/dist/core/sessions/persistence.js +201 -0
  109. package/dist/core/sessions/types.d.ts +110 -0
  110. package/dist/core/sessions/types.d.ts.map +1 -0
  111. package/dist/core/sessions/types.js +4 -0
  112. package/dist/core/stream-highlighter.d.ts +18 -0
  113. package/dist/core/stream-highlighter.d.ts.map +1 -0
  114. package/dist/core/stream-highlighter.js +916 -0
  115. package/dist/core/system-reminders.d.ts +89 -0
  116. package/dist/core/system-reminders.d.ts.map +1 -0
  117. package/dist/core/system-reminders.js +285 -0
  118. package/dist/ecosystem/hooks/__tests__/index.test.d.ts +5 -0
  119. package/dist/ecosystem/hooks/__tests__/index.test.d.ts.map +1 -0
  120. package/dist/ecosystem/hooks/__tests__/index.test.js +458 -0
  121. package/dist/ecosystem/hooks/index.d.ts +59 -0
  122. package/dist/ecosystem/hooks/index.d.ts.map +1 -0
  123. package/dist/ecosystem/hooks/index.js +294 -0
  124. package/dist/ecosystem/hooks/prompt-evaluator.d.ts +32 -0
  125. package/dist/ecosystem/hooks/prompt-evaluator.d.ts.map +1 -0
  126. package/dist/ecosystem/hooks/prompt-evaluator.js +229 -0
  127. package/dist/ecosystem/skills/index.d.ts +55 -0
  128. package/dist/ecosystem/skills/index.d.ts.map +1 -0
  129. package/dist/ecosystem/skills/index.js +258 -0
  130. package/dist/ecosystem/tools/__tests__/index.test.d.ts +7 -0
  131. package/dist/ecosystem/tools/__tests__/index.test.d.ts.map +1 -0
  132. package/dist/ecosystem/tools/__tests__/index.test.js +856 -0
  133. package/dist/ecosystem/tools/index.d.ts +24 -0
  134. package/dist/ecosystem/tools/index.d.ts.map +1 -0
  135. package/dist/ecosystem/tools/index.js +1709 -0
  136. package/dist/index.d.ts +24 -0
  137. package/dist/index.d.ts.map +1 -0
  138. package/dist/index.js +32 -52192
  139. package/dist/interfaces/mcp/client.d.ts +40 -0
  140. package/dist/interfaces/mcp/client.d.ts.map +1 -0
  141. package/dist/interfaces/mcp/client.js +309 -0
  142. package/dist/interfaces/ui/index.d.ts +36 -0
  143. package/dist/interfaces/ui/index.d.ts.map +1 -0
  144. package/dist/interfaces/ui/index.js +61 -0
  145. package/dist/interfaces/ui/spinner.d.ts +140 -0
  146. package/dist/interfaces/ui/spinner.d.ts.map +1 -0
  147. package/dist/interfaces/ui/spinner.js +342 -0
  148. package/dist/interfaces/ui/terminal/cli/index.d.ts +12 -0
  149. package/dist/interfaces/ui/terminal/cli/index.d.ts.map +1 -0
  150. package/dist/interfaces/ui/terminal/cli/index.js +159 -52768
  151. package/dist/interfaces/ui/terminal/shared/args.d.ts +39 -0
  152. package/dist/interfaces/ui/terminal/shared/args.d.ts.map +1 -0
  153. package/dist/interfaces/ui/terminal/shared/args.js +176 -0
  154. package/dist/interfaces/ui/terminal/shared/index.d.ts +11 -0
  155. package/dist/interfaces/ui/terminal/shared/index.d.ts.map +1 -0
  156. package/dist/interfaces/ui/terminal/shared/index.js +16 -0
  157. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts +124 -0
  158. package/dist/interfaces/ui/terminal/shared/loading-state.d.ts.map +1 -0
  159. package/dist/interfaces/ui/terminal/shared/loading-state.js +246 -0
  160. package/dist/interfaces/ui/terminal/shared/query.d.ts +22 -0
  161. package/dist/interfaces/ui/terminal/shared/query.d.ts.map +1 -0
  162. package/dist/interfaces/ui/terminal/shared/query.js +100 -0
  163. package/dist/interfaces/ui/terminal/shared/setup.d.ts +33 -0
  164. package/dist/interfaces/ui/terminal/shared/setup.d.ts.map +1 -0
  165. package/dist/interfaces/ui/terminal/shared/setup.js +226 -0
  166. package/dist/interfaces/ui/terminal/shared/status-line.d.ts +117 -0
  167. package/dist/interfaces/ui/terminal/shared/status-line.d.ts.map +1 -0
  168. package/dist/interfaces/ui/terminal/shared/status-line.js +267 -0
  169. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts +38 -0
  170. package/dist/interfaces/ui/terminal/shared/system-prompt.d.ts.map +1 -0
  171. package/dist/interfaces/ui/terminal/shared/system-prompt.js +102 -0
  172. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts +39 -0
  173. package/dist/interfaces/ui/terminal/tui/HelpPanel.d.ts.map +1 -0
  174. package/dist/interfaces/ui/terminal/tui/HelpPanel.js +215 -0
  175. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts +91 -0
  176. package/dist/interfaces/ui/terminal/tui/InputContext.d.ts.map +1 -0
  177. package/dist/interfaces/ui/terminal/tui/InputContext.js +154 -0
  178. package/dist/interfaces/ui/terminal/tui/InputField.d.ts +18 -0
  179. package/dist/interfaces/ui/terminal/tui/InputField.d.ts.map +1 -0
  180. package/dist/interfaces/ui/terminal/tui/InputField.js +41 -0
  181. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts +16 -0
  182. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.d.ts.map +1 -0
  183. package/dist/interfaces/ui/terminal/tui/InteractiveTUI.js +451 -0
  184. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts +10 -0
  185. package/dist/interfaces/ui/terminal/tui/MessageArea.d.ts.map +1 -0
  186. package/dist/interfaces/ui/terminal/tui/MessageArea.js +91 -0
  187. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts +48 -0
  188. package/dist/interfaces/ui/terminal/tui/MessageStore.d.ts.map +1 -0
  189. package/dist/interfaces/ui/terminal/tui/MessageStore.js +151 -0
  190. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts +9 -0
  191. package/dist/interfaces/ui/terminal/tui/StatusBar.d.ts.map +1 -0
  192. package/dist/interfaces/ui/terminal/tui/StatusBar.js +36 -0
  193. package/dist/interfaces/ui/terminal/tui/commands.d.ts +21 -0
  194. package/dist/interfaces/ui/terminal/tui/commands.d.ts.map +1 -0
  195. package/dist/interfaces/ui/terminal/tui/commands.js +359 -0
  196. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts +115 -0
  197. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.d.ts.map +1 -0
  198. package/dist/interfaces/ui/terminal/tui/components/InteractiveElements.js +306 -0
  199. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts +92 -0
  200. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.d.ts.map +1 -0
  201. package/dist/interfaces/ui/terminal/tui/components/MultilineInput.js +399 -0
  202. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts +59 -0
  203. package/dist/interfaces/ui/terminal/tui/components/PaneManager.d.ts.map +1 -0
  204. package/dist/interfaces/ui/terminal/tui/components/PaneManager.js +139 -0
  205. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts +68 -0
  206. package/dist/interfaces/ui/terminal/tui/components/Sidebar.d.ts.map +1 -0
  207. package/dist/interfaces/ui/terminal/tui/components/Sidebar.js +340 -0
  208. package/dist/interfaces/ui/terminal/tui/components/index.d.ts +23 -0
  209. package/dist/interfaces/ui/terminal/tui/components/index.d.ts.map +1 -0
  210. package/dist/interfaces/ui/terminal/tui/components/index.js +51 -0
  211. package/dist/interfaces/ui/terminal/tui/console.d.ts +20 -0
  212. package/dist/interfaces/ui/terminal/tui/console.d.ts.map +1 -0
  213. package/dist/interfaces/ui/terminal/tui/console.js +46 -0
  214. package/dist/interfaces/ui/terminal/tui/index.d.ts +20 -0
  215. package/dist/interfaces/ui/terminal/tui/index.d.ts.map +1 -0
  216. package/dist/interfaces/ui/terminal/tui/index.js +28 -0
  217. package/dist/interfaces/ui/terminal/tui/run.d.ts +13 -0
  218. package/dist/interfaces/ui/terminal/tui/run.d.ts.map +1 -0
  219. package/dist/interfaces/ui/terminal/tui/run.js +31 -0
  220. package/dist/interfaces/ui/terminal/tui/spinner.d.ts +44 -0
  221. package/dist/interfaces/ui/terminal/tui/spinner.d.ts.map +1 -0
  222. package/dist/interfaces/ui/terminal/tui/spinner.js +59 -0
  223. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts +39 -0
  224. package/dist/interfaces/ui/terminal/tui/tui-app.d.ts.map +1 -0
  225. package/dist/interfaces/ui/terminal/tui/tui-app.js +198 -0
  226. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts +167 -0
  227. package/dist/interfaces/ui/terminal/tui/tui-footer.d.ts.map +1 -0
  228. package/dist/interfaces/ui/terminal/tui/tui-footer.js +330 -0
  229. package/dist/interfaces/ui/terminal/tui/types.d.ts +165 -0
  230. package/dist/interfaces/ui/terminal/tui/types.d.ts.map +1 -0
  231. package/dist/interfaces/ui/terminal/tui/types.js +5 -0
  232. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts +23 -0
  233. package/dist/interfaces/ui/terminal/tui/useInputHandler.d.ts.map +1 -0
  234. package/dist/interfaces/ui/terminal/tui/useInputHandler.js +72 -0
  235. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts +90 -0
  236. package/dist/interfaces/ui/terminal/tui/useNativeInput.d.ts.map +1 -0
  237. package/dist/interfaces/ui/terminal/tui/useNativeInput.js +188 -0
  238. package/dist/native/index.d.ts +480 -0
  239. package/dist/native/index.d.ts.map +1 -0
  240. package/dist/native/index.js +1625 -0
  241. package/dist/teammates/index.d.ts +161 -0
  242. package/dist/teammates/index.d.ts.map +1 -0
  243. package/dist/teammates/index.js +827 -0
  244. package/dist/types/index.d.ts +482 -0
  245. package/dist/types/index.d.ts.map +1 -0
  246. package/dist/types/index.js +52 -0
  247. package/package.json +4 -2
  248. package/packages/src/core/__tests__/permissions.test.ts +1091 -0
  249. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +280 -0
  250. package/packages/src/core/agent-loop/__tests__/formatters.test.ts +234 -0
  251. package/packages/src/core/agent-loop/__tests__/index.test.ts +162 -0
  252. package/packages/src/core/agent-loop/__tests__/loop-state.test.ts +413 -0
  253. package/packages/src/core/agent-loop/__tests__/message-builder.test.ts +229 -0
  254. package/packages/src/core/agent-loop/__tests__/tool-executor.test.ts +457 -0
  255. package/packages/src/core/agent-loop/compaction.ts +88 -0
  256. package/packages/src/core/agent-loop/formatters.ts +50 -0
  257. package/packages/src/core/agent-loop/index.ts +135 -0
  258. package/packages/src/core/agent-loop/loop-state.ts +187 -0
  259. package/packages/src/core/agent-loop/message-builder.ts +62 -0
  260. package/packages/src/core/agent-loop/tool-executor.ts +211 -0
  261. package/packages/src/core/agent-loop/turn-executor.ts +222 -0
  262. package/packages/src/core/agent-loop/types.ts +148 -0
  263. package/packages/src/core/agent-loop.ts +18 -0
  264. package/packages/src/core/api-client-impl.ts +619 -0
  265. package/packages/src/core/api-client.ts +6 -0
  266. package/packages/src/core/checkpoints.ts +606 -0
  267. package/packages/src/core/claude-md.ts +272 -0
  268. package/packages/src/core/cognitive-security/hooks.ts +590 -0
  269. package/packages/src/core/cognitive-security/index.ts +2041 -0
  270. package/packages/src/core/cognitive-security/middleware.ts +536 -0
  271. package/packages/src/core/config-loader.ts +324 -0
  272. package/packages/src/core/context-compaction.ts +578 -0
  273. package/packages/src/core/git-status.ts +262 -0
  274. package/packages/src/core/image.test.ts +180 -0
  275. package/packages/src/core/image.ts +350 -0
  276. package/packages/src/core/lmdb.db +0 -0
  277. package/packages/src/core/lmdb.db-lock +0 -0
  278. package/packages/src/core/models.ts +430 -0
  279. package/packages/src/core/normalizers/todo +4 -0
  280. package/packages/src/core/permissions.ts +431 -0
  281. package/packages/src/core/retry.ts +170 -0
  282. package/packages/src/core/session-store.ts +36 -0
  283. package/packages/src/core/sessions/export.ts +329 -0
  284. package/packages/src/core/sessions/index.ts +587 -0
  285. package/packages/src/core/sessions/metadata.ts +309 -0
  286. package/packages/src/core/sessions/persistence.ts +244 -0
  287. package/packages/src/core/sessions/types.ts +169 -0
  288. package/packages/src/core/stream-highlighter.ts +1123 -0
  289. package/packages/src/core/system-reminders.ts +402 -0
  290. package/packages/src/core/todo +8 -0
  291. package/packages/src/ecosystem/hooks/__tests__/index.test.ts +561 -0
  292. package/packages/src/ecosystem/hooks/index.ts +341 -0
  293. package/packages/src/ecosystem/hooks/prompt-evaluator.ts +300 -0
  294. package/packages/src/ecosystem/skills/index.ts +295 -0
  295. package/packages/src/ecosystem/tools/__tests__/index.test.ts +1335 -0
  296. package/packages/src/ecosystem/tools/index.ts +1877 -0
  297. package/packages/src/index.ts +120 -0
  298. package/packages/src/interfaces/mcp/client.ts +389 -0
  299. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  300. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  301. package/packages/src/interfaces/ui/index.ts +161 -0
  302. package/packages/src/interfaces/ui/lmdb.db +0 -0
  303. package/packages/src/interfaces/ui/lmdb.db-lock +0 -0
  304. package/packages/src/interfaces/ui/spinner.ts +451 -0
  305. package/packages/src/interfaces/ui/terminal/cli/index.ts +228 -0
  306. package/packages/src/interfaces/ui/terminal/lmdb.db +0 -0
  307. package/packages/src/interfaces/ui/terminal/lmdb.db-lock +0 -0
  308. package/packages/src/interfaces/ui/terminal/shared/args.ts +222 -0
  309. package/packages/src/interfaces/ui/terminal/shared/index.ts +71 -0
  310. package/packages/src/interfaces/ui/terminal/shared/loading-state.ts +322 -0
  311. package/packages/src/interfaces/ui/terminal/shared/query.ts +146 -0
  312. package/packages/src/interfaces/ui/terminal/shared/setup.ts +295 -0
  313. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +358 -0
  314. package/packages/src/interfaces/ui/terminal/shared/system-prompt.ts +146 -0
  315. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +262 -0
  316. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +232 -0
  317. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +62 -0
  318. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +537 -0
  319. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +107 -0
  320. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +240 -0
  321. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +54 -0
  322. package/packages/src/interfaces/ui/terminal/tui/commands.ts +438 -0
  323. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +584 -0
  324. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +614 -0
  325. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +333 -0
  326. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +604 -0
  327. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +118 -0
  328. package/packages/src/interfaces/ui/terminal/tui/console.ts +49 -0
  329. package/packages/src/interfaces/ui/terminal/tui/index.ts +90 -0
  330. package/packages/src/interfaces/ui/terminal/tui/run.tsx +42 -0
  331. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +69 -0
  332. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +390 -0
  333. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +422 -0
  334. package/packages/src/interfaces/ui/terminal/tui/types.ts +186 -0
  335. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +104 -0
  336. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +239 -0
  337. package/packages/src/lmdb.db +0 -0
  338. package/packages/src/lmdb.db-lock +0 -0
  339. package/packages/src/native/index.ts +2345 -0
  340. package/packages/src/teammates/index.ts +982 -0
  341. package/packages/src/types/index.ts +722 -0
@@ -0,0 +1,1709 @@
1
+ /**
2
+ * Built-in Tools
3
+ */
4
+ import { glob as globAsync } from "glob";
5
+ import { spawn } from "child_process";
6
+ import { apply_multi_edits, validate_multi_edits, preview_multi_edits } from "../../native/index.js";
7
+ import { isImageExtension, isBinaryExclusion, readImageFile, toImageBlock, formatImageResult, } from "../../core/image.js";
8
+ import { MODEL_ALIASES, resolveModelAlias } from "../../core/models.js";
9
+ import * as path from "path";
10
+ // ============================================
11
+ // READ TOOL
12
+ // ============================================
13
+ export const ReadTool = {
14
+ name: "Read",
15
+ description: "Reads a file from the local filesystem. You can access any file directly by using this tool.\n\nAssume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid.\n\nThis tool allows Coder to read images (PNG, JPG, JPEG, GIF, WEBP) and PDF files.\n\nUsage:\n- The file_path parameter must be an absolute path, not a relative path\n- By default, reads up to 2000 lines starting from the beginning of the file\n- You can optionally specify line offset and limit (especially handy for long files)\n- Any lines longer than 2000 characters will be truncated\n- Results are returned using cat -n format, with line numbers starting at 1\n\nThis tool can read images (PNG, JPG, JPEG, GIF, WEBP). When reading images, the tool displays them visually.\n\nFor PDF files:\n- Get the pages parameter to read specific page ranges (e.g., pages: \"1-5\")\n- Maximum 20 pages per request\n- For large PDFs (more than 10 pages), you MUST provide the pages parameter to read specific page ranges.\n\nTry to read the whole file by default, but for particularly large files, you should consider reading the file in chunks.\n\nIf you read a file that exists but has empty contents you will receive a system reminder warning in place of file contents.",
16
+ input_schema: {
17
+ type: "object",
18
+ properties: {
19
+ file_path: {
20
+ type: "string",
21
+ description: "The absolute path to the file to read",
22
+ },
23
+ offset: {
24
+ type: "number",
25
+ description: "The line number to start reading from (1-based)",
26
+ },
27
+ limit: {
28
+ type: "number",
29
+ description: "The number of lines to read",
30
+ },
31
+ pages: {
32
+ type: "string",
33
+ description: "Page range for PDF files (e.g., \"1-5\")",
34
+ },
35
+ },
36
+ required: ["file_path"],
37
+ },
38
+ handler: async (args, context) => {
39
+ const filePath = args.file_path;
40
+ const offset = args.offset || 1;
41
+ const limit = args.limit || 2000;
42
+ // Validate required parameters
43
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
44
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
45
+ }
46
+ try {
47
+ // Get file extension to check for image files
48
+ const ext = path.extname(filePath).toLowerCase().slice(1);
49
+ // Check if this is an image file (GI8 set in binary)
50
+ if (isImageExtension(ext)) {
51
+ return await handleImageRead(filePath, context.abortSignal);
52
+ }
53
+ // Check for binary exclusions (CI8 set in binary)
54
+ if (isBinaryExclusion(ext)) {
55
+ return {
56
+ content: `Binary file detected: ${filePath}\nThis file type (${ext}) is not supported for text reading.`,
57
+ is_error: true,
58
+ };
59
+ }
60
+ // Default text file reading
61
+ const file = Bun.file(filePath);
62
+ // Check if file exists
63
+ if (!(await file.exists())) {
64
+ return { content: `Error: File not found: ${filePath}`, is_error: true };
65
+ }
66
+ const text = await file.text();
67
+ const lines = text.split("\n");
68
+ // Apply offset and limit (1-based offset)
69
+ const startLine = Math.max(0, offset - 1);
70
+ const endLine = Math.min(lines.length, startLine + limit);
71
+ const selectedLines = lines.slice(startLine, endLine);
72
+ // Check for truncation
73
+ const wasTruncated = endLine < lines.length;
74
+ // Format with line numbers
75
+ const formatted = selectedLines
76
+ .map((line, i) => `${startLine + i + 1}\t${line}`)
77
+ .join("\n");
78
+ // Add truncation notice if applicable
79
+ let result = formatted;
80
+ if (wasTruncated) {
81
+ result += `\n\n> WARNING: ${filePath} is ${lines.length} lines (limit: ${limit}). Only the first ${limit} lines were loaded.`;
82
+ }
83
+ return { content: result };
84
+ }
85
+ catch (error) {
86
+ const errorMessage = error instanceof Error ? error.message : String(error);
87
+ return { content: `Error reading file: ${errorMessage}`, is_error: true };
88
+ }
89
+ },
90
+ };
91
+ /**
92
+ * Handle reading image files (bwA function in binary)
93
+ */
94
+ async function handleImageRead(filePath, signal) {
95
+ try {
96
+ const result = await readImageFile(filePath, 25000, signal);
97
+ const imageBlock = toImageBlock(result);
98
+ // Return as ContentBlock array for proper image handling
99
+ // TODO: Return image block when using native Anthropic API (proxies like Z.AI don't support images in tool_result)
100
+ // For now, return text description only
101
+ // When proxy support is detected, return: [{ type: "text", text: formatImageResult(result) }, imageBlock]
102
+ return {
103
+ content: `${formatImageResult(result)}
104
+
105
+ Note: Image content was read but cannot be displayed through this API proxy. When using native Anthropic API, the image would be included for visual analysis.`,
106
+ };
107
+ }
108
+ catch (error) {
109
+ const errorMessage = error instanceof Error ? error.message : String(error);
110
+ return { content: `Error reading image: ${errorMessage}`, is_error: true };
111
+ }
112
+ }
113
+ // ============================================
114
+ // WRITE TOOL
115
+ // ============================================
116
+ export const WriteTool = {
117
+ name: "Write",
118
+ description: "Writes a file to the local filesystem. This tool will overwrite the existing file if there is one at the provided path.",
119
+ input_schema: {
120
+ type: "object",
121
+ properties: {
122
+ file_path: {
123
+ type: "string",
124
+ description: "The absolute path to the file to write",
125
+ },
126
+ content: {
127
+ type: "string",
128
+ description: "The content to write to the file",
129
+ },
130
+ },
131
+ required: ["file_path", "content"],
132
+ },
133
+ handler: async (args, context) => {
134
+ const filePath = args.file_path;
135
+ const content = args.content;
136
+ // Validate required parameters
137
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
138
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
139
+ }
140
+ if (content === undefined || content === null) {
141
+ return { content: "Error: content parameter is required", is_error: true };
142
+ }
143
+ try {
144
+ await Bun.write(filePath, content);
145
+ return { content: `Successfully wrote to ${filePath}` };
146
+ }
147
+ catch (error) {
148
+ const errorMessage = error instanceof Error ? error.message : String(error);
149
+ return { content: `Error writing file: ${errorMessage}`, is_error: true };
150
+ }
151
+ },
152
+ };
153
+ // ============================================
154
+ // EDIT TOOL
155
+ // ============================================
156
+ export const EditTool = {
157
+ name: "Edit",
158
+ description: "Performs exact string replacements in files. Use this tool to modify existing files.",
159
+ input_schema: {
160
+ type: "object",
161
+ properties: {
162
+ file_path: {
163
+ type: "string",
164
+ description: "The absolute path to the file to modify",
165
+ },
166
+ old_string: {
167
+ type: "string",
168
+ description: "The text to replace",
169
+ },
170
+ new_string: {
171
+ type: "string",
172
+ description: "The text to replace it with",
173
+ },
174
+ replace_all: {
175
+ type: "boolean",
176
+ description: "Replace all occurrences (default false)",
177
+ },
178
+ },
179
+ required: ["file_path", "old_string", "new_string"],
180
+ },
181
+ handler: async (args, context) => {
182
+ const filePath = args.file_path;
183
+ const oldString = args.old_string;
184
+ const newString = args.new_string;
185
+ const replaceAll = args.replace_all || false;
186
+ // Validate required parameters
187
+ if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
188
+ return { content: "Error: file_path parameter is required and must be a non-empty string", is_error: true };
189
+ }
190
+ if (oldString === undefined || oldString === null) {
191
+ return { content: "Error: old_string parameter is required", is_error: true };
192
+ }
193
+ if (newString === undefined || newString === null) {
194
+ return { content: "Error: new_string parameter is required", is_error: true };
195
+ }
196
+ try {
197
+ const file = Bun.file(filePath);
198
+ let content = await file.text();
199
+ if (replaceAll) {
200
+ const originalContent = content;
201
+ content = content.split(oldString).join(newString);
202
+ const count = (originalContent.match(new RegExp(escapeRegex(oldString), "g")) || []).length;
203
+ if (count === 0) {
204
+ return { content: `Error: String not found in file`, is_error: true };
205
+ }
206
+ await Bun.write(filePath, content);
207
+ return { content: `Successfully replaced ${count} occurrences` };
208
+ }
209
+ else {
210
+ const index = content.indexOf(oldString);
211
+ if (index === -1) {
212
+ return { content: `Error: String not found in file`, is_error: true };
213
+ }
214
+ // Check for uniqueness
215
+ const secondIndex = content.indexOf(oldString, index + 1);
216
+ if (secondIndex !== -1) {
217
+ return {
218
+ content: `Error: String appears multiple times in file. Use replace_all or provide more context.`,
219
+ is_error: true,
220
+ };
221
+ }
222
+ content = content.replace(oldString, newString);
223
+ await Bun.write(filePath, content);
224
+ return { content: `Successfully edited ${filePath}` };
225
+ }
226
+ }
227
+ catch (error) {
228
+ const errorMessage = error instanceof Error ? error.message : String(error);
229
+ return { content: `Error editing file: ${errorMessage}`, is_error: true };
230
+ }
231
+ },
232
+ };
233
+ function escapeRegex(string) {
234
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
235
+ }
236
+ // ============================================
237
+ // BASH TOOL
238
+ // ============================================
239
+ export const BashTool = {
240
+ name: "Bash",
241
+ description: "Executes a given bash command with optional timeout. Working directory persists between commands.",
242
+ input_schema: {
243
+ type: "object",
244
+ properties: {
245
+ command: {
246
+ type: "string",
247
+ description: "The command to execute",
248
+ },
249
+ timeout: {
250
+ type: "number",
251
+ description: "Optional timeout in milliseconds (max 600000)",
252
+ },
253
+ description: {
254
+ type: "string",
255
+ description: "Clear, concise description of what this command does",
256
+ },
257
+ },
258
+ required: ["command"],
259
+ },
260
+ handler: async (args, context) => {
261
+ const command = args.command;
262
+ const timeout = args.timeout || 120000;
263
+ // Validate required parameters
264
+ if (!command || typeof command !== 'string' || command.trim() === '') {
265
+ return { content: "Error: command parameter is required and must be a non-empty string", is_error: true };
266
+ }
267
+ try {
268
+ const result = Bun.spawnSync(["sh", "-c", command], {
269
+ cwd: context.workingDirectory,
270
+ timeout,
271
+ maxBuffer: 1024 * 1024 * 30, // 30MB
272
+ });
273
+ const stdout = result.stdout?.toString() || "";
274
+ const stderr = result.stderr?.toString() || "";
275
+ if (result.exitCode !== 0) {
276
+ return {
277
+ content: `Exit code: ${result.exitCode}\n${stdout}\n${stderr}`.trim(),
278
+ is_error: true,
279
+ };
280
+ }
281
+ return { content: stdout || "(no output)" };
282
+ }
283
+ catch (error) {
284
+ const errorMessage = error instanceof Error ? error.message : String(error);
285
+ return { content: `Error executing command: ${errorMessage}`, is_error: true };
286
+ }
287
+ },
288
+ };
289
+ // ============================================
290
+ // GLOB TOOL
291
+ // ============================================
292
+ export const GlobTool = {
293
+ name: "Glob",
294
+ description: "Fast file pattern matching tool that works with any codebase size. Supports glob patterns.",
295
+ input_schema: {
296
+ type: "object",
297
+ properties: {
298
+ pattern: {
299
+ type: "string",
300
+ description: "The glob pattern to match files",
301
+ },
302
+ path: {
303
+ type: "string",
304
+ description: "The directory to search (default: current directory)",
305
+ },
306
+ },
307
+ required: ["pattern"],
308
+ },
309
+ handler: async (args, context) => {
310
+ const pattern = args.pattern;
311
+ const searchPath = args.path || context.workingDirectory;
312
+ // Validate required parameters
313
+ if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
314
+ return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
315
+ }
316
+ try {
317
+ const files = await globAsync(pattern, {
318
+ cwd: searchPath,
319
+ absolute: true,
320
+ nodir: true,
321
+ });
322
+ if (files.length === 0) {
323
+ return { content: "No files found matching pattern" };
324
+ }
325
+ return { content: files.sort().join("\n") };
326
+ }
327
+ catch (error) {
328
+ const errorMessage = error instanceof Error ? error.message : String(error);
329
+ return { content: `Error searching files: ${errorMessage}`, is_error: true };
330
+ }
331
+ },
332
+ };
333
+ // ============================================
334
+ // GREP TOOL
335
+ // ============================================
336
+ export const GrepTool = {
337
+ name: "Grep",
338
+ description: "A powerful search tool built on ripgrep. Supports full regex syntax.",
339
+ input_schema: {
340
+ type: "object",
341
+ properties: {
342
+ pattern: {
343
+ type: "string",
344
+ description: "The regular expression pattern to search for",
345
+ },
346
+ path: {
347
+ type: "string",
348
+ description: "File or directory to search",
349
+ },
350
+ glob: {
351
+ type: "string",
352
+ description: "Glob pattern to filter files",
353
+ },
354
+ output_mode: {
355
+ type: "string",
356
+ enum: ["content", "files_with_matches", "count"],
357
+ description: "Output mode (default: content)",
358
+ },
359
+ "-i:": {
360
+ type: "boolean",
361
+ description: "Case insensitive search",
362
+ },
363
+ "-C:": {
364
+ type: "number",
365
+ description: "Context lines around match",
366
+ },
367
+ head_limit: {
368
+ type: "number",
369
+ description: "Maximum number of results",
370
+ },
371
+ },
372
+ required: ["pattern"],
373
+ },
374
+ handler: async (args, context) => {
375
+ const pattern = args.pattern;
376
+ const searchPath = args.path || context.workingDirectory;
377
+ const glob = args.glob;
378
+ const outputMode = args.output_mode || "content";
379
+ const caseInsensitive = args["-i:"];
380
+ const contextLines = args["-C:"];
381
+ const headLimit = args.head_limit;
382
+ // Validate required parameters
383
+ if (!pattern || typeof pattern !== 'string' || pattern.trim() === '') {
384
+ return { content: "Error: pattern parameter is required and must be a non-empty string", is_error: true };
385
+ }
386
+ try {
387
+ // Build ripgrep arguments
388
+ const rgArgs = ["--json"];
389
+ if (caseInsensitive)
390
+ rgArgs.push("-i");
391
+ if (contextLines)
392
+ rgArgs.push("-C", String(contextLines));
393
+ if (glob)
394
+ rgArgs.push("--glob", glob);
395
+ if (outputMode === "files_with_matches")
396
+ rgArgs.push("--files-with-matches");
397
+ if (outputMode === "count")
398
+ rgArgs.push("--count");
399
+ rgArgs.push(pattern, searchPath);
400
+ const result = Bun.spawnSync(["rg", ...rgArgs], {
401
+ cwd: searchPath,
402
+ maxBuffer: 1024 * 1024 * 10, // 10MB
403
+ });
404
+ const stdout = result.stdout?.toString() || "";
405
+ if (!stdout.trim()) {
406
+ return { content: "No matches found" };
407
+ }
408
+ // Parse JSON output for content mode
409
+ if (outputMode === "content") {
410
+ const lines = stdout.trim().split("\n");
411
+ const matches = [];
412
+ for (const line of lines.slice(0, headLimit || 100)) {
413
+ try {
414
+ const parsed = JSON.parse(line);
415
+ if (parsed.type === "match") {
416
+ const filePath = parsed.data?.path?.text || "";
417
+ const lineNum = parsed.data?.line_number || 0;
418
+ const text = parsed.data?.lines?.text || "";
419
+ matches.push(`${filePath}:${lineNum}:${text.trim()}`);
420
+ }
421
+ }
422
+ catch {
423
+ // Not JSON, use raw line
424
+ matches.push(line);
425
+ }
426
+ }
427
+ return { content: matches.join("\n") || "No matches found" };
428
+ }
429
+ return { content: stdout.trim() };
430
+ }
431
+ catch (error) {
432
+ const errorMessage = error instanceof Error ? error.message : String(error);
433
+ return { content: `Error searching: ${errorMessage}`, is_error: true };
434
+ }
435
+ },
436
+ };
437
+ // ============================================
438
+ // TASK TOOL (Subagents)
439
+ // ============================================
440
+ export const TaskTool = {
441
+ name: "Task",
442
+ description: `Launch a new agent to handle complex, multi-step tasks autonomously.
443
+
444
+ The Task tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
445
+
446
+ Available agent types and their tools:
447
+ - Bash: Command execution specialist for running bash commands. Use for git operations, command execution, and other terminal tasks.
448
+ - general-purpose: General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks.
449
+ - Explore: Fast agent specialized for exploring codebases. Use to quickly find files by patterns, search code for keywords, or answer questions about the codebase.
450
+ - Plan: Software architect agent for designing implementation plans. Returns step-by-step plans, identifies critical files and considers architectural trade-offs.
451
+
452
+ When using the Task tool, you must specify a subagent_type parameter to select the agent type.
453
+
454
+ Usage notes:
455
+ - Always include a short description (3-5 words) summarizing what the agent will do
456
+ - Launch multiple agents concurrently whenever possible to maximize performance
457
+ - Agents can be resumed using the "resume" parameter by passing the agent ID from a previous invocation.`,
458
+ input_schema: {
459
+ type: "object",
460
+ properties: {
461
+ subagent_type: {
462
+ type: "string",
463
+ enum: ["Bash", "general-purpose", "Explore", "Plan"],
464
+ description: "The agent type to launch",
465
+ },
466
+ description: {
467
+ type: "string",
468
+ description: "A short (3-5 word) description of what the agent will do",
469
+ },
470
+ prompt: {
471
+ type: "string",
472
+ description: "The task for the agent to perform",
473
+ },
474
+ resume: {
475
+ type: "string",
476
+ description: "Resume a previous agent by its ID",
477
+ },
478
+ model: {
479
+ type: "string",
480
+ enum: ["sonnet", "opus", "haiku"],
481
+ description: "Model for the subagent (default: haiku for quick tasks)",
482
+ },
483
+ run_in_background: {
484
+ type: "boolean",
485
+ description: "Run the agent in the background",
486
+ },
487
+ },
488
+ required: ["subagent_type", "prompt"],
489
+ },
490
+ handler: async (args, context) => {
491
+ const subagentType = args.subagent_type;
492
+ const prompt = args.prompt;
493
+ const description = args.description;
494
+ const model = args.model || "haiku";
495
+ const resume = args.resume;
496
+ const runInBackground = args.run_in_background;
497
+ // Generate a unique agent ID
498
+ const agentId = resume || `${subagentType.toLowerCase()}-${Date.now().toString(36)}`;
499
+ try {
500
+ // Get API key from environment (check multiple env var names)
501
+ const apiKey = process.env.ANTHROPIC_API_KEY ||
502
+ process.env.CLAUDE_API_KEY ||
503
+ process.env.ANTHROPIC_AUTH_TOKEN ||
504
+ process.env.Z_AI_API_KEY || "";
505
+ if (!apiKey) {
506
+ return { content: "Error: No API key available for subagent. Set ANTHROPIC_API_KEY, CLAUDE_API_KEY, ANTHROPIC_AUTH_TOKEN, or Z_AI_API_KEY environment variable.", is_error: true };
507
+ }
508
+ // Map model names using centralized aliases
509
+ const fullModel = resolveModelAlias(model) || MODEL_ALIASES.haiku;
510
+ // Find the CLI - check multiple locations
511
+ const possiblePaths = [
512
+ import.meta.dir + "/../../dist/cli.js", // Built CLI
513
+ import.meta.dir + "/../interfaces/ui/terminal/cli/index.ts", // Source CLI
514
+ process.cwd() + "/dist/cli.js", // Built CLI in cwd
515
+ process.cwd() + "/src/interfaces/ui/terminal/cli/index.ts", // Source CLI in cwd
516
+ ];
517
+ let cliPath = null;
518
+ for (const path of possiblePaths) {
519
+ try {
520
+ const file = Bun.file(path);
521
+ if (await file.exists()) {
522
+ cliPath = path;
523
+ break;
524
+ }
525
+ }
526
+ catch {
527
+ // Continue to next path
528
+ }
529
+ }
530
+ if (!cliPath) {
531
+ return {
532
+ content: `Error: Could not find CLI. Tried:\n${possiblePaths.join("\n")}`,
533
+ is_error: true,
534
+ };
535
+ }
536
+ // Build command arguments
537
+ const cmdArgs = [
538
+ "run",
539
+ cliPath,
540
+ "-m", fullModel,
541
+ "-p", context.permissionMode,
542
+ "-q", prompt,
543
+ ];
544
+ if (runInBackground) {
545
+ // Spawn in background with proper env
546
+ const child = Bun.spawn(["bun", ...cmdArgs], {
547
+ cwd: context.workingDirectory,
548
+ detached: true,
549
+ stdio: ["ignore", "pipe", "pipe"],
550
+ env: {
551
+ ...process.env,
552
+ ANTHROPIC_API_KEY: apiKey,
553
+ },
554
+ });
555
+ // Don't wait for background tasks
556
+ child.unref();
557
+ return {
558
+ content: JSON.stringify({
559
+ agentId,
560
+ status: "running",
561
+ message: `Agent started in background. Use TaskOutput tool with task_id: "${agentId}" to check results.`,
562
+ description: description || "Background task",
563
+ }),
564
+ };
565
+ }
566
+ // Run synchronously with timeout
567
+ const result = Bun.spawnSync(["bun", ...cmdArgs], {
568
+ cwd: context.workingDirectory,
569
+ timeout: 300000, // 5 minutes max
570
+ maxBuffer: 1024 * 1024 * 10, // 10MB
571
+ env: {
572
+ ...process.env,
573
+ ANTHROPIC_API_KEY: apiKey,
574
+ },
575
+ });
576
+ const stdout = result.stdout?.toString() || "";
577
+ const stderr = result.stderr?.toString() || "";
578
+ if (result.exitCode !== 0) {
579
+ return {
580
+ content: `Agent failed with exit code ${result.exitCode}\n${stderr}\n${stdout}`.trim(),
581
+ is_error: true,
582
+ };
583
+ }
584
+ return {
585
+ content: JSON.stringify({
586
+ agentId,
587
+ status: "completed",
588
+ output: stdout,
589
+ description: description || "Task completed",
590
+ }),
591
+ };
592
+ }
593
+ catch (error) {
594
+ const errorMessage = error instanceof Error ? error.message : String(error);
595
+ return { content: `Error running subagent: ${errorMessage}`, is_error: true };
596
+ }
597
+ },
598
+ };
599
+ // ============================================
600
+ // TASK OUTPUT TOOL
601
+ // ============================================
602
+ export const TaskOutputTool = {
603
+ name: "TaskOutput",
604
+ description: `Retrieves output from a running or completed task (background shell, agent, or remote session).
605
+
606
+ Takes a task_id parameter identifying the task.
607
+ Returns the task output along with status information.
608
+ Use block=true (default) to wait for task completion.
609
+ Use block=false for non-blocking check of current status.
610
+
611
+ Task IDs can be found using the /tasks command
612
+ Works with all task types: background shells, async agents, and remote sessions`,
613
+ input_schema: {
614
+ type: "object",
615
+ properties: {
616
+ task_id: {
617
+ type: "string",
618
+ description: "The task ID to get output from",
619
+ },
620
+ block: {
621
+ type: "boolean",
622
+ description: "Whether to wait for completion (default: true)",
623
+ default: true,
624
+ },
625
+ timeout: {
626
+ type: "number",
627
+ description: "Max wait time in ms (default: 30000, max: 600000)",
628
+ default: 30000,
629
+ minimum: 0,
630
+ maximum: 600000,
631
+ },
632
+ },
633
+ required: ["task_id"],
634
+ },
635
+ handler: async (args, context) => {
636
+ const taskId = args.task_id;
637
+ const block = args.block ?? true;
638
+ const timeout = args.timeout ?? 30000;
639
+ try {
640
+ // In a real implementation, this would check a task registry
641
+ // For now, we'll check for background task output files
642
+ const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
643
+ const file = Bun.file(taskFile);
644
+ if (!(await file.exists())) {
645
+ return {
646
+ content: `Task not found: ${taskId}. Use /tasks to list available tasks.`,
647
+ is_error: true,
648
+ };
649
+ }
650
+ const taskData = await file.json();
651
+ if (block && taskData.status === "running") {
652
+ // Wait for task completion with timeout
653
+ const startTime = Date.now();
654
+ while (Date.now() - startTime < timeout) {
655
+ await new Promise((resolve) => setTimeout(resolve, 1000));
656
+ const updatedFile = Bun.file(taskFile);
657
+ if (await updatedFile.exists()) {
658
+ const updatedData = await updatedFile.json();
659
+ if (updatedData.status !== "running") {
660
+ return {
661
+ content: JSON.stringify({
662
+ task_id: taskId,
663
+ status: updatedData.status,
664
+ output: updatedData.output,
665
+ error: updatedData.error,
666
+ duration: updatedData.endTime
667
+ ? updatedData.endTime - updatedData.startTime
668
+ : null,
669
+ }, null, 2),
670
+ };
671
+ }
672
+ }
673
+ }
674
+ return {
675
+ content: JSON.stringify({
676
+ task_id: taskId,
677
+ status: "timeout",
678
+ message: `Task still running after ${timeout}ms`,
679
+ }, null, 2),
680
+ };
681
+ }
682
+ return {
683
+ content: JSON.stringify({
684
+ task_id: taskId,
685
+ status: taskData.status,
686
+ output: taskData.output,
687
+ error: taskData.error,
688
+ duration: taskData.endTime
689
+ ? taskData.endTime - taskData.startTime
690
+ : null,
691
+ }, null, 2),
692
+ };
693
+ }
694
+ catch (error) {
695
+ const errorMessage = error instanceof Error ? error.message : String(error);
696
+ return { content: `Error getting task output: ${errorMessage}`, is_error: true };
697
+ }
698
+ },
699
+ };
700
+ // ============================================
701
+ // ASK USER QUESTION TOOL
702
+ // ============================================
703
+ export const AskUserQuestionTool = {
704
+ name: "AskUserQuestion",
705
+ description: `Use this tool when you need to ask the user questions during execution.
706
+
707
+ This allows you to:
708
+ 1. Gather user preferences or requirements
709
+ 2. Clarify ambiguous instructions
710
+ 3. Get decisions on implementation choices
711
+ 4. Offer choices to the user about what direction to take
712
+
713
+ Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
714
+
715
+ The options array should have 2-4 options. Each option should be a distinct, mutually exclusive choice.
716
+ The preview feature allows showing markdown content in a side-by-side layout.
717
+
718
+ User can always select "Other" to provide custom text input.`,
719
+ input_schema: {
720
+ type: "object",
721
+ properties: {
722
+ questions: {
723
+ type: "array",
724
+ description: "Questions to ask the user (1-4 questions)",
725
+ items: {
726
+ type: "object",
727
+ properties: {
728
+ question: {
729
+ type: "string",
730
+ description: "The complete question to ask the user",
731
+ },
732
+ header: {
733
+ type: "string",
734
+ description: "Very short label displayed as a chip/tag (max 12 chars)",
735
+ },
736
+ options: {
737
+ type: "array",
738
+ description: "The available choices (2-4 options)",
739
+ items: {
740
+ type: "object",
741
+ properties: {
742
+ label: {
743
+ type: "string",
744
+ description: "The display text for this option (5 words max)",
745
+ },
746
+ description: {
747
+ type: "string",
748
+ description: "Explanation of what this option means",
749
+ },
750
+ markdown: {
751
+ type: "string",
752
+ description: "Optional preview content shown in a monospace box",
753
+ },
754
+ },
755
+ required: ["label", "description"],
756
+ },
757
+ minItems: 2,
758
+ maxItems: 4,
759
+ },
760
+ multiSelect: {
761
+ type: "boolean",
762
+ description: "Allow selecting multiple options (default: false)",
763
+ default: false,
764
+ },
765
+ },
766
+ required: ["question", "header", "options"],
767
+ },
768
+ minItems: 1,
769
+ maxItems: 4,
770
+ },
771
+ },
772
+ required: ["questions"],
773
+ },
774
+ handler: async (args, context) => {
775
+ const questions = args.questions;
776
+ try {
777
+ // Format questions for display
778
+ const formattedQuestions = questions.map((q, i) => {
779
+ const options = q.options
780
+ .map((opt, j) => {
781
+ let optionText = ` ${j + 1}. ${opt.label}`;
782
+ if (opt.description) {
783
+ optionText += ` - ${opt.description}`;
784
+ }
785
+ return optionText;
786
+ })
787
+ .join("\n");
788
+ return `## Question ${i + 1}: [${q.header}]\n${q.question}\n\nOptions:\n${options}${q.multiSelect ? "\n(multi-select enabled)" : ""}`;
789
+ }).join("\n\n---\n\n");
790
+ // In interactive mode, this would prompt the user
791
+ // For now, return the formatted questions
792
+ return {
793
+ content: JSON.stringify({
794
+ type: "user_question",
795
+ questions: questions,
796
+ formatted: formattedQuestions,
797
+ message: "Questions prepared for user response",
798
+ }, null, 2),
799
+ };
800
+ }
801
+ catch (error) {
802
+ const errorMessage = error instanceof Error ? error.message : String(error);
803
+ return { content: `Error preparing questions: ${errorMessage}`, is_error: true };
804
+ }
805
+ },
806
+ };
807
+ // ============================================
808
+ // ENTER PLAN MODE TOOL
809
+ // ============================================
810
+ export const EnterPlanModeTool = {
811
+ name: "EnterPlanMode",
812
+ description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
813
+
814
+ How This Tool Works:
815
+ - You should have already written your plan to the plan file specified in the plan mode system message
816
+ - This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote
817
+ - This tool simply signals that you're done planning and ready for the user to review and approve
818
+
819
+ When to Use This Tool:
820
+ IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code. For research tasks where you're gathering information, searching files, reading files or in general trying to understand the codebase - do NOT use this tool.
821
+
822
+ Plan mode note: In plan mode, use this tool to clarify requirements or choose between approaches BEFORE finalizing your plan. Do NOT use this tool if your plan is ready - that's what ExitPlanMode is for.
823
+
824
+ Examples:
825
+ - "Search for and understand the implementation of vim mode" - Do NOT use this tool
826
+ - "Help me implement yank mode for vim" - Use EnterPlanMode
827
+
828
+ Important notes:
829
+ - NEVER run additional commands to read or explore code, besides git bash commands
830
+ - NEVER use the TodoWrite or Task tools
831
+ - DO NOT commit files that likely contain secrets (.env, credentials.json, etc.)
832
+ - If you discover unexpected state like unfamiliar files, branches, or configuration, investigate before deleting or overwriting`,
833
+ input_schema: {
834
+ type: "object",
835
+ properties: {
836
+ allowedPrompts: {
837
+ type: "array",
838
+ description: "Prompt-based permissions needed to implement the plan",
839
+ items: {
840
+ type: "object",
841
+ properties: {
842
+ tool: {
843
+ type: "string",
844
+ description: "The tool this prompt applies to (e.g., 'Bash')",
845
+ },
846
+ prompt: {
847
+ type: "string",
848
+ description: "Semantic description of the action (e.g., 'run tests', 'install dependencies')",
849
+ },
850
+ },
851
+ required: ["tool", "prompt"],
852
+ },
853
+ },
854
+ },
855
+ required: [],
856
+ },
857
+ handler: async (args, context) => {
858
+ const allowedPrompts = args.allowedPrompts;
859
+ try {
860
+ // Read the plan file
861
+ const planFile = `${context.workingDirectory}/.claude/plan.md`;
862
+ const file = Bun.file(planFile);
863
+ if (!(await file.exists())) {
864
+ return {
865
+ content: "Error: No plan file found. Please write your plan to .claude/plan.md first.",
866
+ is_error: true,
867
+ };
868
+ }
869
+ const planContent = await file.text();
870
+ return {
871
+ content: JSON.stringify({
872
+ type: "plan_ready",
873
+ planFile: planFile,
874
+ planLength: planContent.length,
875
+ allowedPrompts: allowedPrompts || [],
876
+ message: "Plan is ready for user review. ExitPlanMode will request user approval.",
877
+ }, null, 2),
878
+ };
879
+ }
880
+ catch (error) {
881
+ const errorMessage = error instanceof Error ? error.message : String(error);
882
+ return { content: `Error entering plan mode: ${errorMessage}`, is_error: true };
883
+ }
884
+ },
885
+ };
886
+ // ============================================
887
+ // EXIT PLAN MODE TOOL
888
+ // ============================================
889
+ export const ExitPlanModeTool = {
890
+ name: "ExitPlanMode",
891
+ description: `Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.
892
+
893
+ This tool does NOT take the plan content as a parameter - it will read the plan from the file you wrote.
894
+ This tool simply signals that you're done planning and ready for the user to review and approve.
895
+
896
+ IMPORTANT: Only use this tool when the task requires planning the implementation steps of a task that requires writing code.
897
+ ExitPlanMode inherently requests user approval of the plan.`,
898
+ input_schema: {
899
+ type: "object",
900
+ properties: {
901
+ allowedPrompts: {
902
+ type: "array",
903
+ description: "Prompt-based permissions needed to implement the plan",
904
+ items: {
905
+ type: "object",
906
+ properties: {
907
+ tool: {
908
+ type: "string",
909
+ description: "The tool this prompt applies to",
910
+ },
911
+ prompt: {
912
+ type: "string",
913
+ description: "Semantic description of the action",
914
+ },
915
+ },
916
+ required: ["tool", "prompt"],
917
+ },
918
+ },
919
+ },
920
+ required: [],
921
+ },
922
+ handler: async (args, context) => {
923
+ const allowedPrompts = args.allowedPrompts;
924
+ try {
925
+ // Read the plan file
926
+ const planFile = `${context.workingDirectory}/.claude/plan.md`;
927
+ const file = Bun.file(planFile);
928
+ if (!(await file.exists())) {
929
+ return {
930
+ content: "Error: No plan file found at .claude/plan.md",
931
+ is_error: true,
932
+ };
933
+ }
934
+ const planContent = await file.text();
935
+ return {
936
+ content: JSON.stringify({
937
+ type: "exit_plan_mode",
938
+ status: "awaiting_approval",
939
+ planFile: planFile,
940
+ planPreview: planContent.slice(0, 500) + (planContent.length > 500 ? "..." : ""),
941
+ allowedPrompts: allowedPrompts || [],
942
+ message: "Plan submitted for user approval.",
943
+ }, null, 2),
944
+ };
945
+ }
946
+ catch (error) {
947
+ const errorMessage = error instanceof Error ? error.message : String(error);
948
+ return { content: `Error exiting plan mode: ${errorMessage}`, is_error: true };
949
+ }
950
+ },
951
+ };
952
+ // ============================================
953
+ // SKILL TOOL
954
+ // ============================================
955
+ export const SkillTool = {
956
+ name: "Skill",
957
+ description: `Execute a skill within the main conversation.
958
+
959
+ When users ask you to perform tasks, check if any of the available skills match. Skills provide specialized capabilities and domain knowledge.
960
+
961
+ When users reference a "slash command" or "/<something>" (e.g., "/commit", "/review-pr"), they are referring to a skill. Use this tool to invoke it.
962
+
963
+ How to invoke:
964
+ - Use this tool with the skill name and optional arguments
965
+ - Examples:
966
+ - skill: "commit" - invoke the commit skill
967
+ - skill: "review-pr", args: "123" - invoke with arguments
968
+ - Use fully qualified name for namespaced skills: skill: "ms-office-suite:pdf"
969
+
970
+ Available skills are listed in system-reminder messages in the conversation.
971
+ When a skill matches the user's request, this is a BLOCKING REQUIREMENT: invoke the relevant Skill tool BEFORE generating any other response about the task.
972
+
973
+ Important:
974
+ - NEVER mention a skill without actually calling this tool
975
+ - Do not invoke a skill that is already running
976
+ - Do not use this tool for built-in CLI commands (like /help, /clear)`,
977
+ input_schema: {
978
+ type: "object",
979
+ properties: {
980
+ skill: {
981
+ type: "string",
982
+ description: "The skill name (e.g., 'commit', 'review-pr', or fully qualified 'namespace:skill')",
983
+ },
984
+ args: {
985
+ type: "string",
986
+ description: "Optional arguments for the skill",
987
+ },
988
+ },
989
+ required: ["skill"],
990
+ },
991
+ handler: async (args, context) => {
992
+ const skillName = args.skill;
993
+ const skillArgs = args.args;
994
+ try {
995
+ // Load available skills
996
+ const skillsDir = `${context.workingDirectory}/.claude/skills`;
997
+ const globalSkillsDir = `${process.env.HOME || ""}/.claude/skills`;
998
+ // Check for skill file in multiple locations
999
+ const possiblePaths = [
1000
+ `${skillsDir}/${skillName}.md`,
1001
+ `${skillsDir}/${skillName}/skill.md`,
1002
+ `${globalSkillsDir}/${skillName}.md`,
1003
+ `${globalSkillsDir}/${skillName}/skill.md`,
1004
+ ];
1005
+ let skillFile = null;
1006
+ for (const path of possiblePaths) {
1007
+ const file = Bun.file(path);
1008
+ if (await file.exists()) {
1009
+ skillFile = path;
1010
+ break;
1011
+ }
1012
+ }
1013
+ if (!skillFile) {
1014
+ return {
1015
+ content: `Skill not found: ${skillName}. Available skills can be listed with /help.`,
1016
+ is_error: true,
1017
+ };
1018
+ }
1019
+ // Read and return the skill content
1020
+ const file = Bun.file(skillFile);
1021
+ const skillContent = await file.text();
1022
+ return {
1023
+ content: JSON.stringify({
1024
+ type: "skill_invocation",
1025
+ skill: skillName,
1026
+ args: skillArgs,
1027
+ skillFile: skillFile,
1028
+ content: skillContent,
1029
+ message: `Skill "${skillName}" loaded. Follow the instructions in the skill content.`,
1030
+ }, null, 2),
1031
+ };
1032
+ }
1033
+ catch (error) {
1034
+ const errorMessage = error instanceof Error ? error.message : String(error);
1035
+ return { content: `Error invoking skill: ${errorMessage}`, is_error: true };
1036
+ }
1037
+ },
1038
+ };
1039
+ // ============================================
1040
+ // TASK STOP TOOL
1041
+ // ============================================
1042
+ export const TaskStopTool = {
1043
+ name: "TaskStop",
1044
+ description: `Stops a running background task by its ID.
1045
+ Takes a task_id parameter identifying the task to stop.
1046
+ Returns a success or failure status.
1047
+ Use this tool to terminate a long-running task.`,
1048
+ input_schema: {
1049
+ type: "object",
1050
+ properties: {
1051
+ task_id: {
1052
+ type: "string",
1053
+ description: "The ID of the background task to stop",
1054
+ },
1055
+ shell_id: {
1056
+ type: "string",
1057
+ description: "Deprecated: use task_id instead",
1058
+ },
1059
+ },
1060
+ required: ["task_id"],
1061
+ },
1062
+ handler: async (args, context) => {
1063
+ const taskId = args.task_id;
1064
+ try {
1065
+ const taskFile = `${context.workingDirectory}/.claude/tasks/${taskId}.json`;
1066
+ const file = Bun.file(taskFile);
1067
+ if (!(await file.exists())) {
1068
+ return {
1069
+ content: `Task not found: ${taskId}`,
1070
+ is_error: true,
1071
+ };
1072
+ }
1073
+ const taskData = await file.json();
1074
+ if (taskData.status !== "running") {
1075
+ return {
1076
+ content: JSON.stringify({
1077
+ task_id: taskId,
1078
+ status: taskData.status,
1079
+ message: `Task is already ${taskData.status}`,
1080
+ }, null, 2),
1081
+ };
1082
+ }
1083
+ // In a real implementation, this would kill the process
1084
+ // For now, just update the status
1085
+ taskData.status = "stopped";
1086
+ await Bun.write(taskFile, JSON.stringify(taskData, null, 2));
1087
+ return {
1088
+ content: JSON.stringify({
1089
+ task_id: taskId,
1090
+ status: "stopped",
1091
+ message: "Task stopped successfully",
1092
+ }, null, 2),
1093
+ };
1094
+ }
1095
+ catch (error) {
1096
+ const errorMessage = error instanceof Error ? error.message : String(error);
1097
+ return { content: `Error stopping task: ${errorMessage}`, is_error: true };
1098
+ }
1099
+ },
1100
+ };
1101
+ // ============================================
1102
+ // MULTI-EDIT TOOL (Atomic Multi-File Editing)
1103
+ // ============================================
1104
+ export const MultiEditTool = {
1105
+ name: "MultiEdit",
1106
+ description: `Performs atomic multi-file editing with rollback on failure.
1107
+
1108
+ This tool allows you to edit multiple files simultaneously in a single atomic operation.
1109
+ If any edit fails, all changes are automatically rolled back to maintain consistency.
1110
+
1111
+ Key features:
1112
+ - Validates all edits before applying (files exist, strings found)
1113
+ - Creates automatic backups before editing
1114
+ - Applies all edits atomically (all-or-nothing)
1115
+ - Rolls back on any failure
1116
+
1117
+ Use this when you need to make coordinated changes across multiple files and want
1118
+ to ensure either all changes succeed or none are applied.
1119
+
1120
+ IMPORTANT: You MUST read the files first before using this tool. Only edit files you have already read.`,
1121
+ input_schema: {
1122
+ type: "object",
1123
+ properties: {
1124
+ edits: {
1125
+ type: "array",
1126
+ description: "Array of edit operations to apply atomically",
1127
+ items: {
1128
+ type: "object",
1129
+ properties: {
1130
+ file_path: {
1131
+ type: "string",
1132
+ description: "The absolute path to the file to edit",
1133
+ },
1134
+ old_string: {
1135
+ type: "string",
1136
+ description: "The text to find and replace",
1137
+ },
1138
+ new_string: {
1139
+ type: "string",
1140
+ description: "The text to replace it with",
1141
+ },
1142
+ replace_all: {
1143
+ type: "boolean",
1144
+ description: "Replace all occurrences (default: false)",
1145
+ },
1146
+ },
1147
+ required: ["file_path", "old_string", "new_string"],
1148
+ },
1149
+ },
1150
+ dry_run: {
1151
+ type: "boolean",
1152
+ description: "Preview changes without applying them (default: false)",
1153
+ },
1154
+ },
1155
+ required: ["edits"],
1156
+ },
1157
+ handler: async (args, context) => {
1158
+ const rawEdits = args.edits;
1159
+ const dryRun = args.dry_run || false;
1160
+ try {
1161
+ // Validate inputs
1162
+ if (!Array.isArray(rawEdits) || rawEdits.length === 0) {
1163
+ return { content: "Error: edits must be a non-empty array", is_error: true };
1164
+ }
1165
+ // Validate each edit has required fields and convert to native format
1166
+ const edits = [];
1167
+ for (let i = 0; i < rawEdits.length; i++) {
1168
+ const rawEdit = rawEdits[i];
1169
+ if (!rawEdit || !rawEdit.file_path || !rawEdit.old_string || rawEdit.new_string === undefined) {
1170
+ return {
1171
+ content: `Error: Edit at index ${i} is missing required fields (file_path, old_string, new_string)`,
1172
+ is_error: true,
1173
+ };
1174
+ }
1175
+ edits.push({
1176
+ filePath: rawEdit.file_path,
1177
+ oldString: rawEdit.old_string,
1178
+ newString: rawEdit.new_string,
1179
+ replaceAll: rawEdit.replace_all || false,
1180
+ });
1181
+ }
1182
+ // If dry run, just preview
1183
+ if (dryRun) {
1184
+ const preview = preview_multi_edits(edits);
1185
+ const errors = validate_multi_edits(edits);
1186
+ if (errors.length > 0) {
1187
+ return {
1188
+ content: JSON.stringify({
1189
+ valid: false,
1190
+ errors,
1191
+ preview: preview,
1192
+ }, null, 2),
1193
+ is_error: true,
1194
+ };
1195
+ }
1196
+ return {
1197
+ content: JSON.stringify({
1198
+ valid: true,
1199
+ preview: preview,
1200
+ total_files: preview.length,
1201
+ total_replacements: preview.reduce((sum, p) => sum + p.replacementCount, 0),
1202
+ message: "Dry run successful - no changes applied",
1203
+ }, null, 2),
1204
+ };
1205
+ }
1206
+ // Validate all edits first
1207
+ const errors = validate_multi_edits(edits);
1208
+ if (errors.length > 0) {
1209
+ return {
1210
+ content: `Validation failed:\n${errors.join("\n")}`,
1211
+ is_error: true,
1212
+ };
1213
+ }
1214
+ // Apply edits atomically
1215
+ const result = apply_multi_edits(edits);
1216
+ if (result.success) {
1217
+ return {
1218
+ content: JSON.stringify({
1219
+ success: true,
1220
+ files_modified: result.filesModified,
1221
+ total_replacements: result.totalReplacements,
1222
+ message: `Successfully applied ${result.totalReplacements} replacement(s) across ${result.filesModified.length} file(s)`,
1223
+ }, null, 2),
1224
+ };
1225
+ }
1226
+ else {
1227
+ return {
1228
+ content: JSON.stringify({
1229
+ success: false,
1230
+ error: result.error,
1231
+ rolled_back: result.rolledBack,
1232
+ files_modified: result.filesModified,
1233
+ }, null, 2),
1234
+ is_error: true,
1235
+ };
1236
+ }
1237
+ }
1238
+ catch (error) {
1239
+ const errorMessage = error instanceof Error ? error.message : String(error);
1240
+ return { content: `Error applying multi-edit: ${errorMessage}`, is_error: true };
1241
+ }
1242
+ },
1243
+ };
1244
+ // ============================================
1245
+ // NOTEBOOK EDIT TOOL
1246
+ // ============================================
1247
+ export const NotebookEditTool = {
1248
+ name: "NotebookEdit",
1249
+ description: `Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file) with new source.
1250
+
1251
+ Jupyter notebooks are interactive documents that combine code, text, and visualizations. The notebook_path parameter must be an absolute path, not a relative path. The cell_number is 0-indexed. Use edit_mode=insert to add a new cell at the index specified by cell_number. Use edit_mode=delete to delete the cell at the index specified by cell_number.`,
1252
+ input_schema: {
1253
+ type: "object",
1254
+ properties: {
1255
+ notebook_path: {
1256
+ type: "string",
1257
+ description: "The absolute path to the Jupyter notebook file to edit",
1258
+ },
1259
+ cell_id: {
1260
+ type: "string",
1261
+ description: "The ID of the cell to edit (optional, alternative to cell_number)",
1262
+ },
1263
+ cell_number: {
1264
+ type: "number",
1265
+ description: "The index of the cell to edit (0-indexed)",
1266
+ },
1267
+ new_source: {
1268
+ type: "string",
1269
+ description: "The new source for the cell",
1270
+ },
1271
+ cell_type: {
1272
+ type: "string",
1273
+ enum: ["code", "markdown"],
1274
+ description: "The type of the cell (code or markdown). Defaults to code.",
1275
+ },
1276
+ edit_mode: {
1277
+ type: "string",
1278
+ enum: ["replace", "insert", "delete"],
1279
+ description: "The type of edit to perform (replace, insert, delete)",
1280
+ },
1281
+ },
1282
+ required: ["notebook_path"],
1283
+ },
1284
+ handler: async (args, context) => {
1285
+ const notebookPath = args.notebook_path;
1286
+ const cellId = args.cell_id;
1287
+ const cellNumber = args.cell_number;
1288
+ const newSource = args.new_source;
1289
+ const cellType = args.cell_type || "code";
1290
+ const editMode = args.edit_mode || "replace";
1291
+ try {
1292
+ // Read the notebook
1293
+ const file = Bun.file(notebookPath);
1294
+ if (!await file.exists()) {
1295
+ return { content: `Error: Notebook not found: ${notebookPath}`, is_error: true };
1296
+ }
1297
+ const notebook = await file.json();
1298
+ // Validate notebook structure
1299
+ if (!notebook.cells || !Array.isArray(notebook.cells)) {
1300
+ return { content: "Error: Invalid notebook format - no cells array", is_error: true };
1301
+ }
1302
+ // Find the cell to edit
1303
+ let targetIndex;
1304
+ if (cellId) {
1305
+ // Find by cell ID
1306
+ targetIndex = notebook.cells.findIndex(c => c.id === cellId);
1307
+ if (targetIndex === -1) {
1308
+ return { content: `Error: Cell with ID "${cellId}" not found`, is_error: true };
1309
+ }
1310
+ }
1311
+ else if (cellNumber !== undefined) {
1312
+ targetIndex = cellNumber;
1313
+ if (targetIndex < 0 || targetIndex >= notebook.cells.length) {
1314
+ if (editMode === "insert") {
1315
+ // Allow inserting at the end
1316
+ targetIndex = notebook.cells.length;
1317
+ }
1318
+ else {
1319
+ return { content: `Error: Cell number ${targetIndex} out of range (0-${notebook.cells.length - 1})`, is_error: true };
1320
+ }
1321
+ }
1322
+ }
1323
+ else if (editMode !== "insert") {
1324
+ return { content: "Error: Must specify either cell_id or cell_number", is_error: true };
1325
+ }
1326
+ else {
1327
+ targetIndex = notebook.cells.length;
1328
+ }
1329
+ // Perform the edit
1330
+ switch (editMode) {
1331
+ case "delete": {
1332
+ notebook.cells.splice(targetIndex, 1);
1333
+ break;
1334
+ }
1335
+ case "insert": {
1336
+ const newCell = {
1337
+ id: `cell-${Date.now()}`,
1338
+ cell_type: cellType,
1339
+ source: newSource || "",
1340
+ metadata: {},
1341
+ ...(cellType === "code" ? { outputs: [], execution_count: null } : {}),
1342
+ };
1343
+ notebook.cells.splice(targetIndex, 0, newCell);
1344
+ break;
1345
+ }
1346
+ case "replace":
1347
+ default: {
1348
+ if (newSource === undefined) {
1349
+ return { content: "Error: new_source is required for replace mode", is_error: true };
1350
+ }
1351
+ const existingCell = notebook.cells[targetIndex];
1352
+ if (!existingCell) {
1353
+ return { content: `Error: Cell at index ${targetIndex} not found`, is_error: true };
1354
+ }
1355
+ notebook.cells[targetIndex] = {
1356
+ ...existingCell,
1357
+ source: newSource,
1358
+ cell_type: cellType,
1359
+ ...(cellType === "code" ? { execution_count: null } : {}),
1360
+ };
1361
+ break;
1362
+ }
1363
+ }
1364
+ // Write the notebook back
1365
+ await Bun.write(notebookPath, JSON.stringify(notebook, null, 1));
1366
+ return {
1367
+ content: JSON.stringify({
1368
+ success: true,
1369
+ message: `Successfully ${editMode}d cell in ${notebookPath}`,
1370
+ cellCount: notebook.cells.length,
1371
+ }),
1372
+ };
1373
+ }
1374
+ catch (error) {
1375
+ const errorMessage = error instanceof Error ? error.message : String(error);
1376
+ return { content: `Error editing notebook: ${errorMessage}`, is_error: true };
1377
+ }
1378
+ },
1379
+ };
1380
+ // ============================================
1381
+ // ALL BUILT-IN TOOLS
1382
+ // ============================================
1383
+ // ============================================
1384
+ // TEMPGlmVision TOOL (Simple Vision via GLM)
1385
+ // ============================================
1386
+ export const TempGlmVisionTool = {
1387
+ name: "tempglmvision",
1388
+ description: `Analyze images using GLM-4.6V vision model. Use this tool when you need to analyze, describe, or extract information from images. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
1389
+ input_schema: {
1390
+ type: "object",
1391
+ properties: {
1392
+ imageSource: {
1393
+ type: "string",
1394
+ description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
1395
+ },
1396
+ prompt: {
1397
+ type: "string",
1398
+ description: "Detailed text prompt describing what to analyze, extract, or understand from the image.",
1399
+ },
1400
+ },
1401
+ required: ["imageSource", "prompt"],
1402
+ },
1403
+ handler: async (args, context) => {
1404
+ const imageSource = args.imageSource;
1405
+ const prompt = args.prompt;
1406
+ try {
1407
+ // Get API credentials from environment
1408
+ const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
1409
+ if (!apiKey) {
1410
+ return {
1411
+ content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
1412
+ is_error: true,
1413
+ };
1414
+ }
1415
+ const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
1416
+ // Read and encode the image
1417
+ let imageData;
1418
+ let mediaType;
1419
+ if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
1420
+ // Fetch remote image
1421
+ const response = await fetch(imageSource);
1422
+ if (!response.ok) {
1423
+ return {
1424
+ content: `Error fetching image: ${response.status} ${response.statusText}`,
1425
+ is_error: true,
1426
+ };
1427
+ }
1428
+ const buffer = Buffer.from(await response.arrayBuffer());
1429
+ imageData = buffer.toString("base64");
1430
+ // Detect media type from content-type header or extension
1431
+ const contentType = response.headers.get("content-type");
1432
+ if (contentType?.includes("image/png")) {
1433
+ mediaType = "image/png";
1434
+ }
1435
+ else if (contentType?.includes("image/gif")) {
1436
+ mediaType = "image/gif";
1437
+ }
1438
+ else if (contentType?.includes("image/webp")) {
1439
+ mediaType = "image/webp";
1440
+ }
1441
+ else {
1442
+ mediaType = "image/jpeg"; // Default to JPEG
1443
+ }
1444
+ }
1445
+ else {
1446
+ // Local file path
1447
+ const file = Bun.file(imageSource);
1448
+ if (!(await file.exists())) {
1449
+ return {
1450
+ content: `Error: Image file not found: ${imageSource}`,
1451
+ is_error: true,
1452
+ };
1453
+ }
1454
+ const buffer = Buffer.from(await file.arrayBuffer());
1455
+ imageData = buffer.toString("base64");
1456
+ // Detect media type from extension
1457
+ const ext = path.extname(imageSource).toLowerCase();
1458
+ if (ext === ".png") {
1459
+ mediaType = "image/png";
1460
+ }
1461
+ else if (ext === ".gif") {
1462
+ mediaType = "image/gif";
1463
+ }
1464
+ else if (ext === ".webp") {
1465
+ mediaType = "image/webp";
1466
+ }
1467
+ else {
1468
+ mediaType = "image/jpeg";
1469
+ }
1470
+ }
1471
+ // Build request for vision model
1472
+ // Use GLM-5 for vision (supportsVision: true in models.ts)
1473
+ const request = {
1474
+ model: "glm-5",
1475
+ max_tokens: 4096,
1476
+ messages: [
1477
+ {
1478
+ role: "user",
1479
+ content: [
1480
+ {
1481
+ type: "image",
1482
+ source: {
1483
+ type: "base64",
1484
+ media_type: mediaType,
1485
+ data: imageData,
1486
+ },
1487
+ },
1488
+ {
1489
+ type: "text",
1490
+ text: prompt,
1491
+ },
1492
+ ],
1493
+ },
1494
+ ],
1495
+ };
1496
+ // Make API request
1497
+ const response = await fetch(`${baseUrl}/v1/messages`, {
1498
+ method: "POST",
1499
+ headers: {
1500
+ "Content-Type": "application/json",
1501
+ "x-api-key": apiKey,
1502
+ "anthropic-version": "2023-06-01",
1503
+ },
1504
+ body: JSON.stringify(request),
1505
+ signal: context.abortSignal,
1506
+ });
1507
+ if (!response.ok) {
1508
+ const errorText = await response.text();
1509
+ return {
1510
+ content: `Vision API error: ${response.status} - ${errorText}`,
1511
+ is_error: true,
1512
+ };
1513
+ }
1514
+ const result = (await response.json());
1515
+ if (result.error) {
1516
+ return {
1517
+ content: `Vision API error: ${result.error.message}`,
1518
+ is_error: true,
1519
+ };
1520
+ }
1521
+ // Extract text from response
1522
+ const textContent = result.content
1523
+ ?.filter((block) => block.type === "text")
1524
+ .map((block) => block.text)
1525
+ .join("\n");
1526
+ return {
1527
+ content: textContent || "No text content in vision response",
1528
+ };
1529
+ }
1530
+ catch (error) {
1531
+ const errorMessage = error instanceof Error ? error.message : String(error);
1532
+ return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
1533
+ }
1534
+ },
1535
+ };
1536
+ // ============================================
1537
+ // ANALYZE IMAGE TOOL (Vision via GLM)
1538
+ // ============================================
1539
+ export const AnalyzeImageTool = {
1540
+ name: "mcp__4_5v_mcp__analyze_image",
1541
+ description: `Analyze an image using advanced AI vision models with comprehensive understanding capabilities. Supports PNG, JPG, JPEG, GIF, and WEBP formats. Accepts both local file paths and remote URLs.`,
1542
+ input_schema: {
1543
+ type: "object",
1544
+ properties: {
1545
+ imageSource: {
1546
+ type: "string",
1547
+ description: "Local file path or remote URL to the image (supports PNG, JPG, JPEG, GIF, WEBP)",
1548
+ },
1549
+ prompt: {
1550
+ type: "string",
1551
+ description: "Detailed text prompt describing what to analyze, extract, or understand from the image. For front-end code replication, describe layout structure, color style, main components, and interactive elements.",
1552
+ },
1553
+ },
1554
+ required: ["imageSource", "prompt"],
1555
+ },
1556
+ handler: async (args, context) => {
1557
+ const imageSource = args.imageSource;
1558
+ const prompt = args.prompt;
1559
+ try {
1560
+ // Get API credentials from environment
1561
+ const apiKey = process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN;
1562
+ if (!apiKey) {
1563
+ return {
1564
+ content: "Error: No API key found. Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.",
1565
+ is_error: true,
1566
+ };
1567
+ }
1568
+ const baseUrl = process.env.ANTHROPIC_BASE_URL || "https://api.anthropic.com";
1569
+ // Read and encode the image
1570
+ let imageData;
1571
+ let mediaType;
1572
+ if (imageSource.startsWith("http://") || imageSource.startsWith("https://")) {
1573
+ // Fetch remote image
1574
+ const response = await fetch(imageSource);
1575
+ if (!response.ok) {
1576
+ return {
1577
+ content: `Error fetching image: ${response.status} ${response.statusText}`,
1578
+ is_error: true,
1579
+ };
1580
+ }
1581
+ const buffer = Buffer.from(await response.arrayBuffer());
1582
+ imageData = buffer.toString("base64");
1583
+ // Detect media type from content-type header or extension
1584
+ const contentType = response.headers.get("content-type");
1585
+ if (contentType?.includes("image/png")) {
1586
+ mediaType = "image/png";
1587
+ }
1588
+ else if (contentType?.includes("image/gif")) {
1589
+ mediaType = "image/gif";
1590
+ }
1591
+ else if (contentType?.includes("image/webp")) {
1592
+ mediaType = "image/webp";
1593
+ }
1594
+ else {
1595
+ mediaType = "image/jpeg"; // Default to JPEG
1596
+ }
1597
+ }
1598
+ else {
1599
+ // Local file path
1600
+ const file = Bun.file(imageSource);
1601
+ if (!(await file.exists())) {
1602
+ return {
1603
+ content: `Error: Image file not found: ${imageSource}`,
1604
+ is_error: true,
1605
+ };
1606
+ }
1607
+ const buffer = Buffer.from(await file.arrayBuffer());
1608
+ imageData = buffer.toString("base64");
1609
+ // Detect media type from extension
1610
+ const ext = path.extname(imageSource).toLowerCase();
1611
+ if (ext === ".png") {
1612
+ mediaType = "image/png";
1613
+ }
1614
+ else if (ext === ".gif") {
1615
+ mediaType = "image/gif";
1616
+ }
1617
+ else if (ext === ".webp") {
1618
+ mediaType = "image/webp";
1619
+ }
1620
+ else {
1621
+ mediaType = "image/jpeg";
1622
+ }
1623
+ }
1624
+ // Build request for vision model
1625
+ const request = {
1626
+ model: "glm-5", // Vision-capable model
1627
+ max_tokens: 4096,
1628
+ messages: [
1629
+ {
1630
+ role: "user",
1631
+ content: [
1632
+ {
1633
+ type: "image",
1634
+ source: {
1635
+ type: "base64",
1636
+ media_type: mediaType,
1637
+ data: imageData,
1638
+ },
1639
+ },
1640
+ {
1641
+ type: "text",
1642
+ text: prompt,
1643
+ },
1644
+ ],
1645
+ },
1646
+ ],
1647
+ };
1648
+ // Make API request
1649
+ const response = await fetch(`${baseUrl}/v1/messages`, {
1650
+ method: "POST",
1651
+ headers: {
1652
+ "Content-Type": "application/json",
1653
+ "x-api-key": apiKey,
1654
+ "anthropic-version": "2023-06-01",
1655
+ },
1656
+ body: JSON.stringify(request),
1657
+ signal: context.abortSignal,
1658
+ });
1659
+ if (!response.ok) {
1660
+ const errorText = await response.text();
1661
+ return {
1662
+ content: `Vision API error: ${response.status} - ${errorText}`,
1663
+ is_error: true,
1664
+ };
1665
+ }
1666
+ const result = (await response.json());
1667
+ if (result.error) {
1668
+ return {
1669
+ content: `Vision API error: ${result.error.message}`,
1670
+ is_error: true,
1671
+ };
1672
+ }
1673
+ // Extract text from response
1674
+ const textContent = result.content
1675
+ ?.filter((block) => block.type === "text")
1676
+ .map((block) => block.text)
1677
+ .join("\n");
1678
+ return {
1679
+ content: textContent || "No text content in vision response",
1680
+ };
1681
+ }
1682
+ catch (error) {
1683
+ const errorMessage = error instanceof Error ? error.message : String(error);
1684
+ return { content: `Error analyzing image: ${errorMessage}`, is_error: true };
1685
+ }
1686
+ },
1687
+ };
1688
+ export const builtInTools = [
1689
+ ReadTool,
1690
+ WriteTool,
1691
+ EditTool,
1692
+ MultiEditTool,
1693
+ BashTool,
1694
+ GlobTool,
1695
+ GrepTool,
1696
+ TaskTool,
1697
+ TaskOutputTool,
1698
+ TaskStopTool,
1699
+ AskUserQuestionTool,
1700
+ EnterPlanModeTool,
1701
+ ExitPlanModeTool,
1702
+ SkillTool,
1703
+ NotebookEditTool,
1704
+ AnalyzeImageTool,
1705
+ TempGlmVisionTool,
1706
+ ];
1707
+ export function getToolByName(name) {
1708
+ return builtInTools.find((t) => t.name === name);
1709
+ }