shortcutxl 0.2.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 (423) hide show
  1. package/README.md +59 -0
  2. package/agent-docs/README.md +397 -0
  3. package/agent-docs/docs/compaction.md +390 -0
  4. package/agent-docs/docs/custom-provider.md +580 -0
  5. package/agent-docs/docs/development.md +69 -0
  6. package/agent-docs/docs/extensions.md +1971 -0
  7. package/agent-docs/docs/json.md +79 -0
  8. package/agent-docs/docs/keybindings.md +174 -0
  9. package/agent-docs/docs/models.md +293 -0
  10. package/agent-docs/docs/packages.md +209 -0
  11. package/agent-docs/docs/prompt-templates.md +67 -0
  12. package/agent-docs/docs/providers.md +186 -0
  13. package/agent-docs/docs/rpc.md +1317 -0
  14. package/agent-docs/docs/sdk.md +962 -0
  15. package/agent-docs/docs/session.md +412 -0
  16. package/agent-docs/docs/settings.md +223 -0
  17. package/agent-docs/docs/shell-aliases.md +13 -0
  18. package/agent-docs/docs/skills.md +231 -0
  19. package/agent-docs/docs/terminal-setup.md +70 -0
  20. package/agent-docs/docs/termux.md +127 -0
  21. package/agent-docs/docs/themes.md +295 -0
  22. package/agent-docs/docs/tree.md +219 -0
  23. package/agent-docs/docs/tui.md +887 -0
  24. package/agent-docs/docs/windows.md +17 -0
  25. package/agent-docs/examples/README.md +25 -0
  26. package/agent-docs/examples/extensions/README.md +205 -0
  27. package/agent-docs/examples/extensions/antigravity-image-gen.ts +447 -0
  28. package/agent-docs/examples/extensions/auto-commit-on-exit.ts +49 -0
  29. package/agent-docs/examples/extensions/bash-spawn-hook.ts +30 -0
  30. package/agent-docs/examples/extensions/bookmark.ts +50 -0
  31. package/agent-docs/examples/extensions/built-in-tool-renderer.ts +256 -0
  32. package/agent-docs/examples/extensions/claude-rules.ts +86 -0
  33. package/agent-docs/examples/extensions/commands.ts +75 -0
  34. package/agent-docs/examples/extensions/confirm-destructive.ts +59 -0
  35. package/agent-docs/examples/extensions/custom-compaction.ts +126 -0
  36. package/agent-docs/examples/extensions/custom-footer.ts +63 -0
  37. package/agent-docs/examples/extensions/custom-header.ts +73 -0
  38. package/agent-docs/examples/extensions/custom-provider-anthropic/index.ts +660 -0
  39. package/agent-docs/examples/extensions/custom-provider-anthropic/package-lock.json +24 -0
  40. package/agent-docs/examples/extensions/custom-provider-anthropic/package.json +19 -0
  41. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/index.ts +362 -0
  42. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/package.json +16 -0
  43. package/agent-docs/examples/extensions/custom-provider-gitlab-duo/test.ts +88 -0
  44. package/agent-docs/examples/extensions/custom-provider-qwen-cli/index.ts +349 -0
  45. package/agent-docs/examples/extensions/custom-provider-qwen-cli/package.json +16 -0
  46. package/agent-docs/examples/extensions/dirty-repo-guard.ts +56 -0
  47. package/agent-docs/examples/extensions/doom-overlay/README.md +46 -0
  48. package/agent-docs/examples/extensions/doom-overlay/doom/build.sh +152 -0
  49. package/agent-docs/examples/extensions/doom-overlay/doom/doomgeneric_pi.c +72 -0
  50. package/agent-docs/examples/extensions/doom-overlay/doom-component.ts +133 -0
  51. package/agent-docs/examples/extensions/doom-overlay/doom-engine.ts +186 -0
  52. package/agent-docs/examples/extensions/doom-overlay/doom-keys.ts +108 -0
  53. package/agent-docs/examples/extensions/doom-overlay/index.ts +74 -0
  54. package/agent-docs/examples/extensions/doom-overlay/wad-finder.ts +51 -0
  55. package/agent-docs/examples/extensions/dynamic-resources/SKILL.md +8 -0
  56. package/agent-docs/examples/extensions/dynamic-resources/dynamic.json +79 -0
  57. package/agent-docs/examples/extensions/dynamic-resources/dynamic.md +5 -0
  58. package/agent-docs/examples/extensions/dynamic-resources/index.ts +15 -0
  59. package/agent-docs/examples/extensions/dynamic-tools.ts +77 -0
  60. package/agent-docs/examples/extensions/event-bus.ts +43 -0
  61. package/agent-docs/examples/extensions/file-trigger.ts +41 -0
  62. package/agent-docs/examples/extensions/git-checkpoint.ts +53 -0
  63. package/agent-docs/examples/extensions/handoff.ts +155 -0
  64. package/agent-docs/examples/extensions/hello.ts +25 -0
  65. package/agent-docs/examples/extensions/inline-bash.ts +94 -0
  66. package/agent-docs/examples/extensions/input-transform.ts +43 -0
  67. package/agent-docs/examples/extensions/interactive-shell.ts +209 -0
  68. package/agent-docs/examples/extensions/mac-system-theme.ts +47 -0
  69. package/agent-docs/examples/extensions/message-renderer.ts +59 -0
  70. package/agent-docs/examples/extensions/minimal-mode.ts +430 -0
  71. package/agent-docs/examples/extensions/modal-editor.ts +90 -0
  72. package/agent-docs/examples/extensions/model-status.ts +31 -0
  73. package/agent-docs/examples/extensions/notify.ts +55 -0
  74. package/agent-docs/examples/extensions/overlay-qa-tests.ts +936 -0
  75. package/agent-docs/examples/extensions/overlay-test.ts +159 -0
  76. package/agent-docs/examples/extensions/permission-gate.ts +37 -0
  77. package/agent-docs/examples/extensions/pirate.ts +47 -0
  78. package/agent-docs/examples/extensions/plan-mode/README.md +65 -0
  79. package/agent-docs/examples/extensions/plan-mode/index.ts +363 -0
  80. package/agent-docs/examples/extensions/plan-mode/utils.ts +173 -0
  81. package/agent-docs/examples/extensions/preset.ts +418 -0
  82. package/agent-docs/examples/extensions/protected-paths.ts +30 -0
  83. package/agent-docs/examples/extensions/qna.ts +122 -0
  84. package/agent-docs/examples/extensions/question.ts +278 -0
  85. package/agent-docs/examples/extensions/questionnaire.ts +440 -0
  86. package/agent-docs/examples/extensions/rainbow-editor.ts +90 -0
  87. package/agent-docs/examples/extensions/reload-runtime.ts +37 -0
  88. package/agent-docs/examples/extensions/rpc-demo.ts +124 -0
  89. package/agent-docs/examples/extensions/sandbox/index.ts +324 -0
  90. package/agent-docs/examples/extensions/sandbox/package-lock.json +92 -0
  91. package/agent-docs/examples/extensions/sandbox/package.json +19 -0
  92. package/agent-docs/examples/extensions/send-user-message.ts +97 -0
  93. package/agent-docs/examples/extensions/session-name.ts +27 -0
  94. package/agent-docs/examples/extensions/shutdown-command.ts +69 -0
  95. package/agent-docs/examples/extensions/snake.ts +343 -0
  96. package/agent-docs/examples/extensions/space-invaders.ts +566 -0
  97. package/agent-docs/examples/extensions/ssh.ts +233 -0
  98. package/agent-docs/examples/extensions/status-line.ts +40 -0
  99. package/agent-docs/examples/extensions/subagent/README.md +172 -0
  100. package/agent-docs/examples/extensions/subagent/agents/planner.md +37 -0
  101. package/agent-docs/examples/extensions/subagent/agents/reviewer.md +35 -0
  102. package/agent-docs/examples/extensions/subagent/agents/scout.md +50 -0
  103. package/agent-docs/examples/extensions/subagent/agents/worker.md +24 -0
  104. package/agent-docs/examples/extensions/subagent/agents.ts +130 -0
  105. package/agent-docs/examples/extensions/subagent/index.ts +1068 -0
  106. package/agent-docs/examples/extensions/subagent/prompts/implement-and-review.md +10 -0
  107. package/agent-docs/examples/extensions/subagent/prompts/implement.md +10 -0
  108. package/agent-docs/examples/extensions/subagent/prompts/scout-and-plan.md +9 -0
  109. package/agent-docs/examples/extensions/summarize.ts +206 -0
  110. package/agent-docs/examples/extensions/system-prompt-header.ts +17 -0
  111. package/agent-docs/examples/extensions/timed-confirm.ts +72 -0
  112. package/agent-docs/examples/extensions/titlebar-spinner.ts +58 -0
  113. package/agent-docs/examples/extensions/todo.ts +314 -0
  114. package/agent-docs/examples/extensions/tool-override.ts +146 -0
  115. package/agent-docs/examples/extensions/tools.ts +145 -0
  116. package/agent-docs/examples/extensions/trigger-compact.ts +40 -0
  117. package/agent-docs/examples/extensions/truncated-tool.ts +194 -0
  118. package/agent-docs/examples/extensions/widget-placement.ts +17 -0
  119. package/agent-docs/examples/extensions/with-deps/index.ts +37 -0
  120. package/agent-docs/examples/extensions/with-deps/package-lock.json +31 -0
  121. package/agent-docs/examples/extensions/with-deps/package.json +22 -0
  122. package/agent-docs/examples/rpc-extension-ui.ts +654 -0
  123. package/agent-docs/examples/sdk/01-minimal.ts +22 -0
  124. package/agent-docs/examples/sdk/02-custom-model.ts +48 -0
  125. package/agent-docs/examples/sdk/03-custom-prompt.ts +55 -0
  126. package/agent-docs/examples/sdk/04-skills.ts +53 -0
  127. package/agent-docs/examples/sdk/05-tools.ts +56 -0
  128. package/agent-docs/examples/sdk/06-extensions.ts +88 -0
  129. package/agent-docs/examples/sdk/07-context-files.ts +40 -0
  130. package/agent-docs/examples/sdk/08-prompt-templates.ts +47 -0
  131. package/agent-docs/examples/sdk/09-api-keys-and-oauth.ts +48 -0
  132. package/agent-docs/examples/sdk/10-settings.ts +54 -0
  133. package/agent-docs/examples/sdk/11-sessions.ts +48 -0
  134. package/agent-docs/examples/sdk/12-full-control.ts +82 -0
  135. package/agent-docs/examples/sdk/README.md +144 -0
  136. package/agent-docs/xll-skill.md +61 -0
  137. package/agent-docs/xll-spec.md +110 -0
  138. package/dist/cli/args.js +290 -0
  139. package/dist/cli/config-selector.js +31 -0
  140. package/dist/cli/file-processor.js +79 -0
  141. package/dist/cli/list-models.js +92 -0
  142. package/dist/cli/package-commands.js +210 -0
  143. package/dist/cli/report-settings-errors.js +11 -0
  144. package/dist/cli/session-picker.js +34 -0
  145. package/dist/cli.js +19 -0
  146. package/dist/config.js +288 -0
  147. package/dist/core/abort.js +15 -0
  148. package/dist/core/agent-loop.js +352 -0
  149. package/dist/core/agent-session.js +2019 -0
  150. package/dist/core/agent.js +410 -0
  151. package/dist/core/auth-storage.js +456 -0
  152. package/dist/core/bash-executor.js +222 -0
  153. package/dist/core/compaction/branch-summarization.js +242 -0
  154. package/dist/core/compaction/compaction.js +610 -0
  155. package/dist/core/compaction/index.js +7 -0
  156. package/dist/core/compaction/utils.js +139 -0
  157. package/dist/core/defaults.js +6 -0
  158. package/dist/core/diagnostics.js +2 -0
  159. package/dist/core/event-bus.js +25 -0
  160. package/dist/core/exec.js +71 -0
  161. package/dist/core/export-html/ansi-to-html.js +256 -0
  162. package/dist/core/export-html/index.js +238 -0
  163. package/dist/core/export-html/session-view-model.js +342 -0
  164. package/dist/core/export-html/template.css +1110 -0
  165. package/dist/core/export-html/template.html +76 -0
  166. package/dist/core/export-html/template.js +1990 -0
  167. package/dist/core/export-html/tool-renderer.js +63 -0
  168. package/dist/core/export-html/vendor/highlight.min.js +7725 -0
  169. package/dist/core/export-html/vendor/marked.min.js +1803 -0
  170. package/dist/core/extensions/index.js +9 -0
  171. package/dist/core/extensions/loader.js +422 -0
  172. package/dist/core/extensions/runner.js +651 -0
  173. package/dist/core/extensions/types.js +35 -0
  174. package/dist/core/extensions/wrapper.js +102 -0
  175. package/dist/core/footer-data-provider.js +162 -0
  176. package/dist/core/index.js +9 -0
  177. package/dist/core/keybindings.js +153 -0
  178. package/dist/core/messages.js +133 -0
  179. package/dist/core/model-registry.js +539 -0
  180. package/dist/core/model-resolver.js +370 -0
  181. package/dist/core/package-manager.js +1485 -0
  182. package/dist/core/prompt-templates.js +253 -0
  183. package/dist/core/resolve-config-value.js +59 -0
  184. package/dist/core/resource-loader.js +700 -0
  185. package/dist/core/sdk.js +197 -0
  186. package/dist/core/session-bash.js +99 -0
  187. package/dist/core/session-compaction.js +165 -0
  188. package/dist/core/session-manager.js +1153 -0
  189. package/dist/core/session-models.js +99 -0
  190. package/dist/core/session-retry.js +155 -0
  191. package/dist/core/settings-manager.js +572 -0
  192. package/dist/core/skills.js +382 -0
  193. package/dist/core/slash-commands.js +31 -0
  194. package/dist/core/system-prompt.js +161 -0
  195. package/dist/core/theme.js +770 -0
  196. package/dist/core/timings.js +26 -0
  197. package/dist/core/tools/bash.js +258 -0
  198. package/dist/core/tools/edit-diff.js +245 -0
  199. package/dist/core/tools/edit.js +148 -0
  200. package/dist/core/tools/find.js +208 -0
  201. package/dist/core/tools/grep.js +246 -0
  202. package/dist/core/tools/index.js +67 -0
  203. package/dist/core/tools/ls.js +123 -0
  204. package/dist/core/tools/path-utils.js +81 -0
  205. package/dist/core/tools/read.js +160 -0
  206. package/dist/core/tools/truncate.js +70 -0
  207. package/dist/core/tools/write.js +82 -0
  208. package/dist/custom/agents/action.js +13 -0
  209. package/dist/custom/agents/document-reader.js +70 -0
  210. package/dist/custom/agents/general.js +26 -0
  211. package/dist/custom/agents/index.js +49 -0
  212. package/dist/custom/agents/installation.js +13 -0
  213. package/dist/custom/agents/types.js +7 -0
  214. package/dist/custom/auth/refresh-timer.js +33 -0
  215. package/dist/custom/auth/shortcut-oauth.js +145 -0
  216. package/dist/custom/constants.js +21 -0
  217. package/dist/custom/context/workbook-summary.js +73 -0
  218. package/dist/custom/credits/shortcut-credits.js +29 -0
  219. package/dist/custom/cron/cron-daemon-entry.js +18 -0
  220. package/dist/custom/cron/daemon-ipc.js +131 -0
  221. package/dist/custom/cron/daemon.js +224 -0
  222. package/dist/custom/cron/jobs.js +226 -0
  223. package/dist/custom/cron/run-log.js +51 -0
  224. package/dist/custom/cron/schedule.js +72 -0
  225. package/dist/custom/cron/status-line.js +98 -0
  226. package/dist/custom/cron/store.js +87 -0
  227. package/dist/custom/cron/types.js +8 -0
  228. package/dist/custom/dev/index.js +59 -0
  229. package/dist/custom/dev/trace-export.js +58 -0
  230. package/dist/custom/ensure-excel.js +63 -0
  231. package/dist/custom/excel-config.js +36 -0
  232. package/dist/custom/preflight.js +422 -0
  233. package/dist/custom/prompts/action.js +100 -0
  234. package/dist/custom/prompts/api.js +66 -0
  235. package/dist/custom/prompts/installation.js +124 -0
  236. package/dist/custom/prompts/shared.js +138 -0
  237. package/dist/custom/providers/llm-usage.js +42 -0
  238. package/dist/custom/providers/message-converter.js +74 -0
  239. package/dist/custom/providers/provider-ids.js +9 -0
  240. package/dist/custom/providers/register-openai-codex-provider.js +27 -0
  241. package/dist/custom/providers/register-shortcut-provider.js +52 -0
  242. package/dist/custom/providers/shortcut-invoke.js +117 -0
  243. package/dist/custom/providers/shortcut-stream.js +252 -0
  244. package/dist/custom/providers/sse-protocol.js +38 -0
  245. package/dist/custom/sync-xll.js +130 -0
  246. package/dist/custom/tools/cron.js +413 -0
  247. package/dist/custom/tools/excel-exec.js +167 -0
  248. package/dist/custom/tools/excel-range.js +50 -0
  249. package/dist/custom/tools/llm-analysis.js +265 -0
  250. package/dist/custom/tools/render-helpers.js +38 -0
  251. package/dist/custom/tools/switch-mode.js +94 -0
  252. package/dist/custom/tools/task/agents.js +6 -0
  253. package/dist/custom/tools/task/index.js +8 -0
  254. package/dist/custom/tools/task/render.js +348 -0
  255. package/dist/custom/tools/task/subprocess.js +320 -0
  256. package/dist/custom/tools/task/task.js +205 -0
  257. package/dist/custom/tools/todo-list.js +195 -0
  258. package/dist/custom/tracing/session-upload.js +93 -0
  259. package/dist/index.js +45 -0
  260. package/dist/main.js +613 -0
  261. package/dist/migrations.js +265 -0
  262. package/dist/modes/index.js +8 -0
  263. package/dist/modes/interactive/components/armin.js +337 -0
  264. package/dist/modes/interactive/components/assistant-message.js +94 -0
  265. package/dist/modes/interactive/components/bash-execution.js +171 -0
  266. package/dist/modes/interactive/components/bordered-loader.js +51 -0
  267. package/dist/modes/interactive/components/branch-summary-message.js +45 -0
  268. package/dist/modes/interactive/components/compaction-summary-message.js +46 -0
  269. package/dist/modes/interactive/components/config-selector.js +488 -0
  270. package/dist/modes/interactive/components/countdown-timer.js +33 -0
  271. package/dist/modes/interactive/components/custom-editor.js +93 -0
  272. package/dist/modes/interactive/components/custom-message.js +81 -0
  273. package/dist/modes/interactive/components/daxnuts.js +140 -0
  274. package/dist/modes/interactive/components/diff.js +133 -0
  275. package/dist/modes/interactive/components/dynamic-border.js +21 -0
  276. package/dist/modes/interactive/components/extension-editor.js +105 -0
  277. package/dist/modes/interactive/components/extension-input.js +61 -0
  278. package/dist/modes/interactive/components/extension-selector.js +78 -0
  279. package/dist/modes/interactive/components/footer.js +309 -0
  280. package/dist/modes/interactive/components/index.js +33 -0
  281. package/dist/modes/interactive/components/keybinding-hints.js +61 -0
  282. package/dist/modes/interactive/components/layout.js +64 -0
  283. package/dist/modes/interactive/components/login-dialog.js +148 -0
  284. package/dist/modes/interactive/components/model-selector.js +237 -0
  285. package/dist/modes/interactive/components/oauth-selector.js +111 -0
  286. package/dist/modes/interactive/components/session-selector-search.js +157 -0
  287. package/dist/modes/interactive/components/session-selector.js +860 -0
  288. package/dist/modes/interactive/components/settings-selector.js +123 -0
  289. package/dist/modes/interactive/components/show-images-selector.js +35 -0
  290. package/dist/modes/interactive/components/skill-invocation-message.js +48 -0
  291. package/dist/modes/interactive/components/theme-selector.js +47 -0
  292. package/dist/modes/interactive/components/thinking-selector.js +47 -0
  293. package/dist/modes/interactive/components/tool-execution.js +789 -0
  294. package/dist/modes/interactive/components/tool-group.js +106 -0
  295. package/dist/modes/interactive/components/tree-selector.js +962 -0
  296. package/dist/modes/interactive/components/user-message-selector.js +115 -0
  297. package/dist/modes/interactive/components/user-message.js +48 -0
  298. package/dist/modes/interactive/components/visual-truncate.js +33 -0
  299. package/dist/modes/interactive/file-attachments.js +135 -0
  300. package/dist/modes/interactive/interactive-mode.js +3775 -0
  301. package/dist/modes/interactive/theme/dark.json +85 -0
  302. package/dist/modes/interactive/theme/light.json +85 -0
  303. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  304. package/dist/modes/interactive/theme/theme.js +177 -0
  305. package/dist/modes/print-mode.js +101 -0
  306. package/dist/modes/rpc/rpc-client.js +387 -0
  307. package/dist/modes/rpc/rpc-mode.js +509 -0
  308. package/dist/modes/rpc/rpc-types.js +8 -0
  309. package/dist/subagent-entry.js +145 -0
  310. package/dist/tool-names.js +34 -0
  311. package/dist/tui/autocomplete.js +596 -0
  312. package/dist/tui/components/box.js +104 -0
  313. package/dist/tui/components/cancellable-loader.js +35 -0
  314. package/dist/tui/components/editor.js +1679 -0
  315. package/dist/tui/components/image.js +69 -0
  316. package/dist/tui/components/input.js +433 -0
  317. package/dist/tui/components/loader.js +49 -0
  318. package/dist/tui/components/markdown.js +629 -0
  319. package/dist/tui/components/select-list.js +152 -0
  320. package/dist/tui/components/settings-list.js +185 -0
  321. package/dist/tui/components/spacer.js +23 -0
  322. package/dist/tui/components/text.js +89 -0
  323. package/dist/tui/components/truncated-text.js +51 -0
  324. package/dist/tui/editor-component.js +2 -0
  325. package/dist/tui/fuzzy.js +107 -0
  326. package/dist/tui/get-east-asian-width/index.js +32 -0
  327. package/dist/tui/get-east-asian-width/lookup.js +404 -0
  328. package/dist/tui/index.js +32 -0
  329. package/dist/tui/keybindings.js +114 -0
  330. package/dist/tui/keys.js +959 -0
  331. package/dist/tui/kill-ring.js +44 -0
  332. package/dist/tui/stdin-buffer.js +317 -0
  333. package/dist/tui/terminal-image.js +288 -0
  334. package/dist/tui/terminal.js +249 -0
  335. package/dist/tui/tui/autocomplete.js +596 -0
  336. package/dist/tui/tui/components/box.js +106 -0
  337. package/dist/tui/tui/components/cancellable-loader.js +35 -0
  338. package/dist/tui/tui/components/editor.js +1679 -0
  339. package/dist/tui/tui/components/image.js +69 -0
  340. package/dist/tui/tui/components/input.js +433 -0
  341. package/dist/tui/tui/components/loader.js +49 -0
  342. package/dist/tui/tui/components/markdown.js +629 -0
  343. package/dist/tui/tui/components/select-list.js +152 -0
  344. package/dist/tui/tui/components/settings-list.js +185 -0
  345. package/dist/tui/tui/components/spacer.js +23 -0
  346. package/dist/tui/tui/components/text.js +91 -0
  347. package/dist/tui/tui/components/truncated-text.js +51 -0
  348. package/dist/tui/tui/editor-component.js +2 -0
  349. package/dist/tui/tui/fuzzy.js +107 -0
  350. package/dist/tui/tui/get-east-asian-width/index.js +32 -0
  351. package/dist/tui/tui/get-east-asian-width/lookup.js +404 -0
  352. package/dist/tui/tui/index.js +32 -0
  353. package/dist/tui/tui/keybindings.js +114 -0
  354. package/dist/tui/tui/keys.js +959 -0
  355. package/dist/tui/tui/kill-ring.js +44 -0
  356. package/dist/tui/tui/stdin-buffer.js +317 -0
  357. package/dist/tui/tui/terminal-image.js +288 -0
  358. package/dist/tui/tui/terminal.js +249 -0
  359. package/dist/tui/tui/tui.js +955 -0
  360. package/dist/tui/tui/undo-stack.js +25 -0
  361. package/dist/tui/tui/utils.js +800 -0
  362. package/dist/tui/tui.js +955 -0
  363. package/dist/tui/undo-stack.js +25 -0
  364. package/dist/tui/utils.js +800 -0
  365. package/dist/utils/changelog.js +87 -0
  366. package/dist/utils/clipboard-image.js +164 -0
  367. package/dist/utils/clipboard-native.js +14 -0
  368. package/dist/utils/clipboard.js +67 -0
  369. package/dist/utils/frontmatter.js +26 -0
  370. package/dist/utils/git.js +166 -0
  371. package/dist/utils/image-convert.js +35 -0
  372. package/dist/utils/image-resize.js +183 -0
  373. package/dist/utils/mime.js +26 -0
  374. package/dist/utils/photon.js +121 -0
  375. package/dist/utils/shell.js +217 -0
  376. package/dist/utils/sleep.js +17 -0
  377. package/dist/utils/tools-manager.js +259 -0
  378. package/package.json +78 -0
  379. package/skills/excel-com-api/SKILL.md +74 -0
  380. package/skills/excel-com-api/excel-type-library.py +27767 -0
  381. package/skills/excel-com-api/office-type-library.py +10867 -0
  382. package/skills/integrations/SKILL.md +138 -0
  383. package/skills/integrations/alphasense.md +457 -0
  384. package/skills/integrations/bloomberg.md +803 -0
  385. package/skills/integrations/calcbench.md +315 -0
  386. package/skills/integrations/capiq.md +848 -0
  387. package/skills/integrations/dynamics-365-finance.md +354 -0
  388. package/skills/integrations/earnings_transcripts.md +387 -0
  389. package/skills/integrations/factset.md +758 -0
  390. package/skills/integrations/ice-fixed-income.md +344 -0
  391. package/skills/integrations/moodys-analytics.md +313 -0
  392. package/skills/integrations/morningstar.md +433 -0
  393. package/skills/integrations/nasdaq-data-link.md +249 -0
  394. package/skills/integrations/pitchbook.md +413 -0
  395. package/skills/integrations/preqin.md +422 -0
  396. package/skills/integrations/quickbooks.md +289 -0
  397. package/skills/integrations/quickfs.md +314 -0
  398. package/skills/integrations/refinitiv.md +473 -0
  399. package/skills/integrations/sage-intacct.md +401 -0
  400. package/skills/integrations/visible-alpha.md +320 -0
  401. package/skills/integrations/xero.md +393 -0
  402. package/skills/integrations/ycharts.md +306 -0
  403. package/skills/pdf-creation/SKILL.md +93 -0
  404. package/skills/pdf-extraction/SKILL.md +32 -0
  405. package/skills/powerpoint-creation/SKILL.md +110 -0
  406. package/skills/sec-edgar/SKILL.md +127 -0
  407. package/skills/sec-edgar/sec_to_pdf.py +109 -0
  408. package/xll/ShortcutXL.xll +0 -0
  409. package/xll/modules/debug_render.py +272 -0
  410. package/xll/modules/gameboy.py +241 -0
  411. package/xll/modules/pong.py +188 -0
  412. package/xll/modules/shortcut_xl/__init__.py +18 -0
  413. package/xll/modules/shortcut_xl/_categorize.py +200 -0
  414. package/xll/modules/shortcut_xl/_com.py +108 -0
  415. package/xll/modules/shortcut_xl/_format.py +252 -0
  416. package/xll/modules/shortcut_xl/_log.py +12 -0
  417. package/xll/modules/shortcut_xl/_managed.py +116 -0
  418. package/xll/modules/shortcut_xl/_registry.py +44 -0
  419. package/xll/modules/shortcut_xl/_threading.py +161 -0
  420. package/xll/modules/shortcut_xl/_tracking.py +283 -0
  421. package/xll/modules/stocks.py +100 -0
  422. package/xll/python3.dll +0 -0
  423. package/xll/python312.dll +0 -0
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Central timing instrumentation for startup profiling.
3
+ * Enable with SHORTCUT_TIMING=1 environment variable.
4
+ */
5
+ // SHORTCUT PATCH: renamed from PI_TIMING
6
+ const ENABLED = process.env.SHORTCUT_TIMING === '1';
7
+ const timings = [];
8
+ let lastTime = Date.now();
9
+ export function time(label) {
10
+ if (!ENABLED)
11
+ return;
12
+ const now = Date.now();
13
+ timings.push({ label, ms: now - lastTime });
14
+ lastTime = now;
15
+ }
16
+ export function printTimings() {
17
+ if (!ENABLED || timings.length === 0)
18
+ return;
19
+ console.error('\n--- Startup Timings ---');
20
+ for (const t of timings) {
21
+ console.error(` ${t.label}: ${t.ms}ms`);
22
+ }
23
+ console.error(` TOTAL: ${timings.reduce((a, b) => a + b.ms, 0)}ms`);
24
+ console.error('------------------------\n');
25
+ }
26
+ //# sourceMappingURL=timings.js.map
@@ -0,0 +1,258 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { spawn } from 'child_process';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { createWriteStream, existsSync } from 'node:fs';
5
+ import { tmpdir } from 'node:os';
6
+ import { join } from 'node:path';
7
+ import { BASH } from '../../tool-names.js';
8
+ import { getShellConfig, getShellEnv, killProcessTree } from '../../utils/shell.js';
9
+ import { ABORT_AGENT_MESSAGE } from '../abort.js';
10
+ import { SettingsManager } from '../settings-manager.js';
11
+ import { DEFAULT_MAX_CHARS, truncateHead, truncateOutput } from './truncate.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Constants
14
+ // ---------------------------------------------------------------------------
15
+ const TOOL_NAME = BASH;
16
+ const TOOL_LABEL = BASH;
17
+ const TOOL_DESCRIPTION = `\
18
+ Execute a bash command in the current working directory. Returns stdout and stderr. \
19
+ Output is truncated to ${DEFAULT_MAX_CHARS} characters. If truncated, full output is saved to a temp file. \
20
+ Default timeout is 30 seconds. Override with the timeout parameter for long-running commands.
21
+
22
+ Python 3 and pip are available for data processing, scripting, and package installation.
23
+ - ALWAYS use bash to execute python and other commands instead of through the
24
+
25
+ On Windows, this tool runs Git Bash (MSYS2), not a native shell. Two consequences:
26
+ 1. Forward-slash flags get auto-converted to paths — use double slashes for literal flags (e.g., reg query //v).
27
+ 2. This tool holds stdout/stderr pipes open until all child processes exit. To launch a process without blocking, use Git Bash's built-in \`start\` with the program name (e.g., \`start excel\`).
28
+
29
+ **Conventions:**
30
+ - When creating files, tell users the file path.
31
+ - Stop retrying after 1-2 unexplained failures.`;
32
+ // ---------------------------------------------------------------------------
33
+ /**
34
+ * Generate a unique temp file path for bash output
35
+ */
36
+ function getTempFilePath() {
37
+ const id = randomBytes(8).toString('hex');
38
+ return join(tmpdir(), `pi-bash-${id}.log`);
39
+ }
40
+ // SHORTCUT PATCH: added description field for task agent display
41
+ const bashSchema = Type.Object({
42
+ description: Type.String({
43
+ description: 'A short description of what the command does for non-technical users.'
44
+ }),
45
+ command: Type.String({ description: 'Bash command to execute' }),
46
+ timeout: Type.Optional(Type.Number({ description: 'Timeout in seconds (default: 30). Increase for long-running commands.' }))
47
+ });
48
+ /**
49
+ * Default bash operations using local shell
50
+ */
51
+ const defaultBashOperations = {
52
+ exec: (command, cwd, { onData, signal, timeout, env }) => {
53
+ return new Promise((resolve, reject) => {
54
+ const { shell, args } = getShellConfig(SettingsManager.create().getShellPath());
55
+ if (!existsSync(cwd)) {
56
+ reject(new Error(`Working directory does not exist: ${cwd}\nCannot execute bash commands.`));
57
+ return;
58
+ }
59
+ const child = spawn(shell, [...args, command], {
60
+ cwd,
61
+ // On Unix, detached creates a process group so killProcessTree can use -pid.
62
+ // On Windows, detached creates a new console which breaks stdout pipes for
63
+ // grandchild processes (external executables). Windows uses taskkill /T instead.
64
+ detached: process.platform !== 'win32',
65
+ env: env ?? getShellEnv(),
66
+ stdio: ['ignore', 'pipe', 'pipe']
67
+ });
68
+ let timedOut = false;
69
+ let settled = false;
70
+ // Set timeout if provided
71
+ let timeoutHandle;
72
+ if (timeout !== undefined && timeout > 0) {
73
+ timeoutHandle = setTimeout(() => {
74
+ timedOut = true;
75
+ if (child.pid) {
76
+ killProcessTree(child.pid);
77
+ }
78
+ // Force-close pipes — on Windows, grandchild processes (e.g. spawned
79
+ // by `start`) can hold pipe handles open even after the shell is killed.
80
+ child.stdout?.destroy();
81
+ child.stderr?.destroy();
82
+ }, timeout * 1000);
83
+ }
84
+ // Stream stdout and stderr
85
+ if (child.stdout) {
86
+ child.stdout.on('data', onData);
87
+ }
88
+ if (child.stderr) {
89
+ child.stderr.on('data', onData);
90
+ }
91
+ // Handle shell spawn errors
92
+ child.on('error', (err) => {
93
+ if (timeoutHandle)
94
+ clearTimeout(timeoutHandle);
95
+ if (signal)
96
+ signal.removeEventListener('abort', onAbort);
97
+ if (!settled) {
98
+ settled = true;
99
+ reject(err);
100
+ }
101
+ });
102
+ // Handle abort signal - kill entire process tree and force-close pipes.
103
+ // On Windows, `start`-spawned processes can hold pipe handles open even
104
+ // after taskkill kills the shell, preventing the 'close' event from firing.
105
+ const onAbort = () => {
106
+ if (child.pid) {
107
+ killProcessTree(child.pid);
108
+ }
109
+ child.stdout?.destroy();
110
+ child.stderr?.destroy();
111
+ // Safety net: if 'close' still doesn't fire (orphaned pipe handles),
112
+ // force-settle after 2 seconds so the tool call doesn't hang forever.
113
+ setTimeout(() => {
114
+ if (!settled) {
115
+ settled = true;
116
+ if (timeoutHandle)
117
+ clearTimeout(timeoutHandle);
118
+ if (signal)
119
+ signal.removeEventListener('abort', onAbort);
120
+ reject(new Error('aborted'));
121
+ }
122
+ }, 2000);
123
+ };
124
+ if (signal) {
125
+ if (signal.aborted) {
126
+ onAbort();
127
+ }
128
+ else {
129
+ signal.addEventListener('abort', onAbort, { once: true });
130
+ }
131
+ }
132
+ // Handle process exit
133
+ child.on('close', (code) => {
134
+ if (timeoutHandle)
135
+ clearTimeout(timeoutHandle);
136
+ if (signal)
137
+ signal.removeEventListener('abort', onAbort);
138
+ if (settled)
139
+ return;
140
+ settled = true;
141
+ if (signal?.aborted) {
142
+ reject(new Error('aborted'));
143
+ return;
144
+ }
145
+ if (timedOut) {
146
+ reject(new Error(`timeout:${timeout}`));
147
+ return;
148
+ }
149
+ resolve({ exitCode: code });
150
+ });
151
+ });
152
+ }
153
+ };
154
+ export function createBashTool(cwd, options) {
155
+ const ops = options?.operations ?? defaultBashOperations;
156
+ const commandPrefix = options?.commandPrefix;
157
+ return {
158
+ name: TOOL_NAME,
159
+ label: TOOL_LABEL,
160
+ description: TOOL_DESCRIPTION,
161
+ parameters: bashSchema,
162
+ execute: async (_toolCallId, { command, timeout: explicitTimeout }, signal, onUpdate) => {
163
+ const timeout = explicitTimeout ?? 30;
164
+ const resolvedCommand = commandPrefix ? `${commandPrefix}\n${command}` : command;
165
+ return new Promise((resolve, reject) => {
166
+ // We'll stream to a temp file if output gets large
167
+ let tempFilePath;
168
+ let tempFileStream;
169
+ // Accumulate output chunks — only used for head truncation at the end
170
+ const chunks = [];
171
+ let totalChars = 0;
172
+ const handleData = (data) => {
173
+ const text = data.toString('utf-8');
174
+ totalChars += text.length;
175
+ // Start writing to temp file once we exceed the threshold
176
+ if (totalChars > DEFAULT_MAX_CHARS && !tempFilePath) {
177
+ tempFilePath = getTempFilePath();
178
+ tempFileStream = createWriteStream(tempFilePath);
179
+ for (const chunk of chunks) {
180
+ tempFileStream.write(chunk);
181
+ }
182
+ }
183
+ if (tempFileStream) {
184
+ tempFileStream.write(text);
185
+ }
186
+ chunks.push(text);
187
+ // Stream partial output to callback
188
+ if (onUpdate) {
189
+ const truncation = truncateHead(chunks.join(''));
190
+ onUpdate({
191
+ content: [{ type: 'text', text: truncation.content || '' }],
192
+ details: {
193
+ truncation: truncation.truncated ? truncation : undefined,
194
+ fullOutputPath: tempFilePath
195
+ }
196
+ });
197
+ }
198
+ };
199
+ ops
200
+ .exec(resolvedCommand, cwd, {
201
+ onData: handleData,
202
+ signal,
203
+ timeout
204
+ })
205
+ .then(({ exitCode }) => {
206
+ if (tempFileStream) {
207
+ tempFileStream.end();
208
+ }
209
+ const fullOutput = chunks.join('');
210
+ const truncation = truncateHead(fullOutput);
211
+ let outputText = truncateOutput(fullOutput) || '(no output)';
212
+ let details;
213
+ if (truncation.truncated) {
214
+ details = {
215
+ truncation,
216
+ fullOutputPath: tempFilePath
217
+ };
218
+ if (tempFilePath) {
219
+ outputText += `\nFull output: ${tempFilePath}`;
220
+ }
221
+ }
222
+ if (exitCode !== 0 && exitCode !== null) {
223
+ outputText += `\n\nCommand exited with code ${exitCode}`;
224
+ reject(new Error(outputText));
225
+ }
226
+ else {
227
+ resolve({ content: [{ type: 'text', text: outputText }], details });
228
+ }
229
+ })
230
+ .catch((err) => {
231
+ if (tempFileStream) {
232
+ tempFileStream.end();
233
+ }
234
+ let output = truncateOutput(chunks.join(''));
235
+ if (err.message === 'aborted') {
236
+ if (output)
237
+ output += '\n\n';
238
+ output += ABORT_AGENT_MESSAGE;
239
+ reject(new Error(output));
240
+ }
241
+ else if (err.message.startsWith('timeout:')) {
242
+ const timeoutSecs = err.message.split(':')[1];
243
+ if (output)
244
+ output += '\n\n';
245
+ output += `Command timed out after ${timeoutSecs} seconds`;
246
+ reject(new Error(output));
247
+ }
248
+ else {
249
+ reject(err);
250
+ }
251
+ });
252
+ });
253
+ }
254
+ };
255
+ }
256
+ /** Default bash tool using process.cwd() - for backwards compatibility */
257
+ export const bashTool = createBashTool(process.cwd());
258
+ //# sourceMappingURL=bash.js.map
@@ -0,0 +1,245 @@
1
+ /**
2
+ * Shared diff computation utilities for the edit tool.
3
+ * Used by both edit.ts (for execution) and tool-execution.ts (for preview rendering).
4
+ */
5
+ import * as Diff from 'diff';
6
+ import { constants } from 'fs';
7
+ import { access, readFile } from 'fs/promises';
8
+ import { resolveToCwd } from './path-utils.js';
9
+ export function detectLineEnding(content) {
10
+ const crlfIdx = content.indexOf('\r\n');
11
+ const lfIdx = content.indexOf('\n');
12
+ if (lfIdx === -1)
13
+ return '\n';
14
+ if (crlfIdx === -1)
15
+ return '\n';
16
+ return crlfIdx < lfIdx ? '\r\n' : '\n';
17
+ }
18
+ export function normalizeToLF(text) {
19
+ return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
20
+ }
21
+ export function restoreLineEndings(text, ending) {
22
+ return ending === '\r\n' ? text.replace(/\n/g, '\r\n') : text;
23
+ }
24
+ /**
25
+ * Normalize text for fuzzy matching. Applies progressive transformations:
26
+ * - Strip trailing whitespace from each line
27
+ * - Normalize smart quotes to ASCII equivalents
28
+ * - Normalize Unicode dashes/hyphens to ASCII hyphen
29
+ * - Normalize special Unicode spaces to regular space
30
+ */
31
+ export function normalizeForFuzzyMatch(text) {
32
+ return (text
33
+ // Strip trailing whitespace per line
34
+ .split('\n')
35
+ .map((line) => line.trimEnd())
36
+ .join('\n')
37
+ // Smart single quotes → '
38
+ .replace(/[\u2018\u2019\u201A\u201B]/g, "'")
39
+ // Smart double quotes → "
40
+ .replace(/[\u201C\u201D\u201E\u201F]/g, '"')
41
+ // Various dashes/hyphens → -
42
+ // U+2010 hyphen, U+2011 non-breaking hyphen, U+2012 figure dash,
43
+ // U+2013 en-dash, U+2014 em-dash, U+2015 horizontal bar, U+2212 minus
44
+ .replace(/[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g, '-')
45
+ // Special spaces → regular space
46
+ // U+00A0 NBSP, U+2002-U+200A various spaces, U+202F narrow NBSP,
47
+ // U+205F medium math space, U+3000 ideographic space
48
+ .replace(/[\u00A0\u2002-\u200A\u202F\u205F\u3000]/g, ' '));
49
+ }
50
+ /**
51
+ * Find oldText in content, trying exact match first, then fuzzy match.
52
+ * When fuzzy matching is used, the returned contentForReplacement is the
53
+ * fuzzy-normalized version of the content (trailing whitespace stripped,
54
+ * Unicode quotes/dashes normalized to ASCII).
55
+ */
56
+ export function fuzzyFindText(content, oldText) {
57
+ // Try exact match first
58
+ const exactIndex = content.indexOf(oldText);
59
+ if (exactIndex !== -1) {
60
+ return {
61
+ found: true,
62
+ index: exactIndex,
63
+ matchLength: oldText.length,
64
+ usedFuzzyMatch: false,
65
+ contentForReplacement: content
66
+ };
67
+ }
68
+ // Try fuzzy match - work entirely in normalized space
69
+ const fuzzyContent = normalizeForFuzzyMatch(content);
70
+ const fuzzyOldText = normalizeForFuzzyMatch(oldText);
71
+ const fuzzyIndex = fuzzyContent.indexOf(fuzzyOldText);
72
+ if (fuzzyIndex === -1) {
73
+ return {
74
+ found: false,
75
+ index: -1,
76
+ matchLength: 0,
77
+ usedFuzzyMatch: false,
78
+ contentForReplacement: content
79
+ };
80
+ }
81
+ // When fuzzy matching, we work in the normalized space for replacement.
82
+ // This means the output will have normalized whitespace/quotes/dashes,
83
+ // which is acceptable since we're fixing minor formatting differences anyway.
84
+ return {
85
+ found: true,
86
+ index: fuzzyIndex,
87
+ matchLength: fuzzyOldText.length,
88
+ usedFuzzyMatch: true,
89
+ contentForReplacement: fuzzyContent
90
+ };
91
+ }
92
+ /** Strip UTF-8 BOM if present, return both the BOM (if any) and the text without it */
93
+ export function stripBom(content) {
94
+ return content.startsWith('\uFEFF')
95
+ ? { bom: '\uFEFF', text: content.slice(1) }
96
+ : { bom: '', text: content };
97
+ }
98
+ /**
99
+ * Generate a unified diff string with line numbers and context.
100
+ * Returns both the diff string and the first changed line number (in the new file).
101
+ */
102
+ export function generateDiffString(oldContent, newContent, contextLines = 4) {
103
+ const parts = Diff.diffLines(oldContent, newContent);
104
+ const output = [];
105
+ const oldLines = oldContent.split('\n');
106
+ const newLines = newContent.split('\n');
107
+ const maxLineNum = Math.max(oldLines.length, newLines.length);
108
+ const lineNumWidth = String(maxLineNum).length;
109
+ let oldLineNum = 1;
110
+ let newLineNum = 1;
111
+ let lastWasChange = false;
112
+ let firstChangedLine;
113
+ for (let i = 0; i < parts.length; i++) {
114
+ const part = parts[i];
115
+ const raw = part.value.split('\n');
116
+ if (raw[raw.length - 1] === '') {
117
+ raw.pop();
118
+ }
119
+ if (part.added || part.removed) {
120
+ // Capture the first changed line (in the new file)
121
+ if (firstChangedLine === undefined) {
122
+ firstChangedLine = newLineNum;
123
+ }
124
+ // Show the change
125
+ for (const line of raw) {
126
+ if (part.added) {
127
+ const lineNum = String(newLineNum).padStart(lineNumWidth, ' ');
128
+ output.push(`+${lineNum} ${line}`);
129
+ newLineNum++;
130
+ }
131
+ else {
132
+ // removed
133
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, ' ');
134
+ output.push(`-${lineNum} ${line}`);
135
+ oldLineNum++;
136
+ }
137
+ }
138
+ lastWasChange = true;
139
+ }
140
+ else {
141
+ // Context lines - only show a few before/after changes
142
+ const nextPartIsChange = i < parts.length - 1 && (parts[i + 1].added || parts[i + 1].removed);
143
+ if (lastWasChange || nextPartIsChange) {
144
+ // Show context
145
+ let linesToShow = raw;
146
+ let skipStart = 0;
147
+ let skipEnd = 0;
148
+ if (!lastWasChange) {
149
+ // Show only last N lines as leading context
150
+ skipStart = Math.max(0, raw.length - contextLines);
151
+ linesToShow = raw.slice(skipStart);
152
+ }
153
+ if (!nextPartIsChange && linesToShow.length > contextLines) {
154
+ // Show only first N lines as trailing context
155
+ skipEnd = linesToShow.length - contextLines;
156
+ linesToShow = linesToShow.slice(0, contextLines);
157
+ }
158
+ // Add ellipsis if we skipped lines at start
159
+ if (skipStart > 0) {
160
+ output.push(` ${''.padStart(lineNumWidth, ' ')} ...`);
161
+ // Update line numbers for the skipped leading context
162
+ oldLineNum += skipStart;
163
+ newLineNum += skipStart;
164
+ }
165
+ for (const line of linesToShow) {
166
+ const lineNum = String(oldLineNum).padStart(lineNumWidth, ' ');
167
+ output.push(` ${lineNum} ${line}`);
168
+ oldLineNum++;
169
+ newLineNum++;
170
+ }
171
+ // Add ellipsis if we skipped lines at end
172
+ if (skipEnd > 0) {
173
+ output.push(` ${''.padStart(lineNumWidth, ' ')} ...`);
174
+ // Update line numbers for the skipped trailing context
175
+ oldLineNum += skipEnd;
176
+ newLineNum += skipEnd;
177
+ }
178
+ }
179
+ else {
180
+ // Skip these context lines entirely
181
+ oldLineNum += raw.length;
182
+ newLineNum += raw.length;
183
+ }
184
+ lastWasChange = false;
185
+ }
186
+ }
187
+ return { diff: output.join('\n'), firstChangedLine };
188
+ }
189
+ /**
190
+ * Compute the diff for an edit operation without applying it.
191
+ * Used for preview rendering in the TUI before the tool executes.
192
+ */
193
+ export async function computeEditDiff(path, oldText, newText, cwd) {
194
+ const absolutePath = resolveToCwd(path, cwd);
195
+ try {
196
+ // Check if file exists and is readable
197
+ try {
198
+ await access(absolutePath, constants.R_OK);
199
+ }
200
+ catch {
201
+ return { error: `File not found: ${path}` };
202
+ }
203
+ // Read the file
204
+ const rawContent = await readFile(absolutePath, 'utf-8');
205
+ // Strip BOM before matching (LLM won't include invisible BOM in oldText)
206
+ const { text: content } = stripBom(rawContent);
207
+ const normalizedContent = normalizeToLF(content);
208
+ const normalizedOldText = normalizeToLF(oldText);
209
+ const normalizedNewText = normalizeToLF(newText);
210
+ // Find the old text using fuzzy matching (tries exact match first, then fuzzy)
211
+ const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
212
+ if (!matchResult.found) {
213
+ return {
214
+ error: `Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`
215
+ };
216
+ }
217
+ // Count occurrences using fuzzy-normalized content for consistency
218
+ const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
219
+ const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
220
+ const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
221
+ if (occurrences > 1) {
222
+ return {
223
+ error: `Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`
224
+ };
225
+ }
226
+ // Compute the new content using the matched position
227
+ // When fuzzy matching was used, contentForReplacement is the normalized version
228
+ const baseContent = matchResult.contentForReplacement;
229
+ const newContent = baseContent.substring(0, matchResult.index) +
230
+ normalizedNewText +
231
+ baseContent.substring(matchResult.index + matchResult.matchLength);
232
+ // Check if it would actually change anything
233
+ if (baseContent === newContent) {
234
+ return {
235
+ error: `No changes would be made to ${path}. The replacement produces identical content.`
236
+ };
237
+ }
238
+ // Generate the diff
239
+ return generateDiffString(baseContent, newContent);
240
+ }
241
+ catch (err) {
242
+ return { error: err instanceof Error ? err.message : String(err) };
243
+ }
244
+ }
245
+ //# sourceMappingURL=edit-diff.js.map
@@ -0,0 +1,148 @@
1
+ import { Type } from '@sinclair/typebox';
2
+ import { constants } from 'fs';
3
+ import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from 'fs/promises';
4
+ import { ABORT_AGENT_MESSAGE } from '../abort.js';
5
+ import { EDIT } from '../../tool-names.js';
6
+ import { detectLineEnding, fuzzyFindText, generateDiffString, normalizeForFuzzyMatch, normalizeToLF, restoreLineEndings, stripBom } from './edit-diff.js';
7
+ import { resolveToCwd } from './path-utils.js';
8
+ const editSchema = Type.Object({
9
+ path: Type.String({ description: 'Path to the file to edit (relative or absolute)' }),
10
+ oldText: Type.String({ description: 'Exact text to find and replace (must match exactly)' }),
11
+ newText: Type.String({ description: 'New text to replace the old text with' })
12
+ });
13
+ const defaultEditOperations = {
14
+ readFile: (path) => fsReadFile(path),
15
+ writeFile: (path, content) => fsWriteFile(path, content, 'utf-8'),
16
+ access: (path) => fsAccess(path, constants.R_OK | constants.W_OK)
17
+ };
18
+ export function createEditTool(cwd, options) {
19
+ const ops = options?.operations ?? defaultEditOperations;
20
+ return {
21
+ name: EDIT,
22
+ label: EDIT,
23
+ description: 'Edit a file by replacing exact text. The oldText must match exactly (including whitespace). Use this for precise, surgical edits.',
24
+ parameters: editSchema,
25
+ execute: async (_toolCallId, { path, oldText, newText }, signal) => {
26
+ const absolutePath = resolveToCwd(path, cwd);
27
+ return new Promise((resolve, reject) => {
28
+ // Check if already aborted
29
+ if (signal?.aborted) {
30
+ reject(new Error(ABORT_AGENT_MESSAGE));
31
+ return;
32
+ }
33
+ let aborted = false;
34
+ // Set up abort handler
35
+ const onAbort = () => {
36
+ aborted = true;
37
+ reject(new Error(ABORT_AGENT_MESSAGE));
38
+ };
39
+ if (signal) {
40
+ signal.addEventListener('abort', onAbort, { once: true });
41
+ }
42
+ // Perform the edit operation
43
+ (async () => {
44
+ try {
45
+ // Check if file exists
46
+ try {
47
+ await ops.access(absolutePath);
48
+ }
49
+ catch {
50
+ if (signal) {
51
+ signal.removeEventListener('abort', onAbort);
52
+ }
53
+ reject(new Error(`File not found: ${path}`));
54
+ return;
55
+ }
56
+ // Check if aborted before reading
57
+ if (aborted) {
58
+ return;
59
+ }
60
+ // Read the file
61
+ const buffer = await ops.readFile(absolutePath);
62
+ const rawContent = buffer.toString('utf-8');
63
+ // Check if aborted after reading
64
+ if (aborted) {
65
+ return;
66
+ }
67
+ // Strip BOM before matching (LLM won't include invisible BOM in oldText)
68
+ const { bom, text: content } = stripBom(rawContent);
69
+ const originalEnding = detectLineEnding(content);
70
+ const normalizedContent = normalizeToLF(content);
71
+ const normalizedOldText = normalizeToLF(oldText);
72
+ const normalizedNewText = normalizeToLF(newText);
73
+ // Find the old text using fuzzy matching (tries exact match first, then fuzzy)
74
+ const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
75
+ if (!matchResult.found) {
76
+ if (signal) {
77
+ signal.removeEventListener('abort', onAbort);
78
+ }
79
+ reject(new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`));
80
+ return;
81
+ }
82
+ // Count occurrences using fuzzy-normalized content for consistency
83
+ const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
84
+ const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
85
+ const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
86
+ if (occurrences > 1) {
87
+ if (signal) {
88
+ signal.removeEventListener('abort', onAbort);
89
+ }
90
+ reject(new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`));
91
+ return;
92
+ }
93
+ // Check if aborted before writing
94
+ if (aborted) {
95
+ return;
96
+ }
97
+ // Perform replacement using the matched text position
98
+ // When fuzzy matching was used, contentForReplacement is the normalized version
99
+ const baseContent = matchResult.contentForReplacement;
100
+ const newContent = baseContent.substring(0, matchResult.index) +
101
+ normalizedNewText +
102
+ baseContent.substring(matchResult.index + matchResult.matchLength);
103
+ // Verify the replacement actually changed something
104
+ if (baseContent === newContent) {
105
+ if (signal) {
106
+ signal.removeEventListener('abort', onAbort);
107
+ }
108
+ reject(new Error(`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.`));
109
+ return;
110
+ }
111
+ const finalContent = bom + restoreLineEndings(newContent, originalEnding);
112
+ await ops.writeFile(absolutePath, finalContent);
113
+ // Check if aborted after writing
114
+ if (aborted) {
115
+ return;
116
+ }
117
+ // Clean up abort handler
118
+ if (signal) {
119
+ signal.removeEventListener('abort', onAbort);
120
+ }
121
+ const diffResult = generateDiffString(baseContent, newContent);
122
+ resolve({
123
+ content: [
124
+ {
125
+ type: 'text',
126
+ text: `Successfully replaced text in ${path}.`
127
+ }
128
+ ],
129
+ details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
130
+ });
131
+ }
132
+ catch (error) {
133
+ // Clean up abort handler
134
+ if (signal) {
135
+ signal.removeEventListener('abort', onAbort);
136
+ }
137
+ if (!aborted) {
138
+ reject(error);
139
+ }
140
+ }
141
+ })();
142
+ });
143
+ }
144
+ };
145
+ }
146
+ /** Default edit tool using process.cwd() - for backwards compatibility */
147
+ export const editTool = createEditTool(process.cwd());
148
+ //# sourceMappingURL=edit.js.map