@f5-sales-demo/xcsh 19.51.2

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 (899) hide show
  1. package/CHANGELOG.md +7073 -0
  2. package/README.md +14 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +551 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +145 -0
  16. package/examples/extensions/with-deps/index.ts +36 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +115 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +149 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +118 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +41 -0
  39. package/examples/sdk/08-slash-commands.ts +46 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +172 -0
  43. package/package.json +539 -0
  44. package/scripts/build-info/resolvers.ts +51 -0
  45. package/scripts/capture-ax-fixture.ts +84 -0
  46. package/scripts/capture-login-fixture.ts +30 -0
  47. package/scripts/check-workflow-field-coverage.ts +101 -0
  48. package/scripts/extension-uat-harness.ts +340 -0
  49. package/scripts/format-prompts.ts +68 -0
  50. package/scripts/generate-api-spec-index.ts +580 -0
  51. package/scripts/generate-branding-index.ts +81 -0
  52. package/scripts/generate-build-info.ts +106 -0
  53. package/scripts/generate-console-catalog.ts +88 -0
  54. package/scripts/generate-console-field-metadata.ts +74 -0
  55. package/scripts/generate-docs-index.ts +40 -0
  56. package/scripts/generate-template.ts +32 -0
  57. package/scripts/generate-terraform-index.ts +81 -0
  58. package/scripts/mermaid-gallery.ts +132 -0
  59. package/scripts/uat-matrix-modalities.ts +451 -0
  60. package/scripts/uat-matrix-report.ts +175 -0
  61. package/scripts/uat-matrix.ts +353 -0
  62. package/src/async/index.ts +2 -0
  63. package/src/async/job-manager.ts +416 -0
  64. package/src/async/support.ts +5 -0
  65. package/src/autoresearch/apply-contract-to-state.ts +1 -0
  66. package/src/autoresearch/command-resume.md +17 -0
  67. package/src/autoresearch/contract.ts +205 -0
  68. package/src/autoresearch/dashboard.ts +374 -0
  69. package/src/autoresearch/git.ts +191 -0
  70. package/src/autoresearch/helpers.ts +328 -0
  71. package/src/autoresearch/index.ts +402 -0
  72. package/src/autoresearch/prompt.md +236 -0
  73. package/src/autoresearch/resume-message.md +16 -0
  74. package/src/autoresearch/state.ts +303 -0
  75. package/src/autoresearch/tools/init-experiment.ts +400 -0
  76. package/src/autoresearch/tools/log-experiment.ts +793 -0
  77. package/src/autoresearch/tools/run-experiment.ts +679 -0
  78. package/src/autoresearch/types.ts +114 -0
  79. package/src/browser/acquire.ts +249 -0
  80. package/src/browser/actions.ts +126 -0
  81. package/src/browser/auth.ts +176 -0
  82. package/src/browser/ax.ts +134 -0
  83. package/src/browser/cdp-core.ts +45 -0
  84. package/src/browser/cdp-page-actions.ts +51 -0
  85. package/src/browser/chrome-locate.ts +66 -0
  86. package/src/browser/dom-context.ts +89 -0
  87. package/src/browser/dt-context.ts +100 -0
  88. package/src/browser/extension-bridge.ts +182 -0
  89. package/src/browser/extension-page-actions.ts +397 -0
  90. package/src/browser/extension-provider.ts +373 -0
  91. package/src/browser/index.ts +17 -0
  92. package/src/browser/input-commit.ts +74 -0
  93. package/src/browser/native-messaging.ts +20 -0
  94. package/src/browser/page-actions.ts +18 -0
  95. package/src/browser/provider.ts +155 -0
  96. package/src/browser/resolver.ts +126 -0
  97. package/src/browser/selector.ts +40 -0
  98. package/src/bun-imports.d.ts +22 -0
  99. package/src/capability/context-file.ts +43 -0
  100. package/src/capability/extension-module.ts +34 -0
  101. package/src/capability/extension.ts +47 -0
  102. package/src/capability/fs.ts +107 -0
  103. package/src/capability/hook.ts +40 -0
  104. package/src/capability/index.ts +438 -0
  105. package/src/capability/instruction.ts +37 -0
  106. package/src/capability/mcp.ts +74 -0
  107. package/src/capability/prompt.ts +35 -0
  108. package/src/capability/rule.ts +224 -0
  109. package/src/capability/settings.ts +34 -0
  110. package/src/capability/skill.ts +50 -0
  111. package/src/capability/slash-command.ts +40 -0
  112. package/src/capability/ssh.ts +41 -0
  113. package/src/capability/system-prompt.ts +34 -0
  114. package/src/capability/tool.ts +38 -0
  115. package/src/capability/types.ts +168 -0
  116. package/src/cli/agents-cli.ts +138 -0
  117. package/src/cli/args.ts +286 -0
  118. package/src/cli/chrome-cli.ts +88 -0
  119. package/src/cli/classify-install-target.ts +50 -0
  120. package/src/cli/commands/init-xdg.ts +27 -0
  121. package/src/cli/config-cli.ts +418 -0
  122. package/src/cli/file-processor.ts +121 -0
  123. package/src/cli/grep-cli.ts +160 -0
  124. package/src/cli/grievances-cli.ts +78 -0
  125. package/src/cli/initial-message.ts +58 -0
  126. package/src/cli/jupyter-cli.ts +106 -0
  127. package/src/cli/list-models.ts +128 -0
  128. package/src/cli/plugin-cli.ts +964 -0
  129. package/src/cli/read-cli.ts +68 -0
  130. package/src/cli/session-picker.ts +52 -0
  131. package/src/cli/setup-cli.ts +479 -0
  132. package/src/cli/shell-cli.ts +174 -0
  133. package/src/cli/ssh-cli.ts +179 -0
  134. package/src/cli/stats-cli.ts +182 -0
  135. package/src/cli/update-cli.ts +446 -0
  136. package/src/cli/web-search-cli.ts +203 -0
  137. package/src/cli.ts +123 -0
  138. package/src/commands/agents.ts +57 -0
  139. package/src/commands/chrome-host.ts +71 -0
  140. package/src/commands/chrome.ts +28 -0
  141. package/src/commands/commit.ts +36 -0
  142. package/src/commands/config.ts +51 -0
  143. package/src/commands/grep.ts +48 -0
  144. package/src/commands/grievances.ts +20 -0
  145. package/src/commands/jupyter.ts +32 -0
  146. package/src/commands/launch.ts +144 -0
  147. package/src/commands/plugin.ts +78 -0
  148. package/src/commands/read.ts +33 -0
  149. package/src/commands/setup.ts +42 -0
  150. package/src/commands/shell.ts +29 -0
  151. package/src/commands/ssh.ts +60 -0
  152. package/src/commands/stats.ts +29 -0
  153. package/src/commands/update.ts +21 -0
  154. package/src/commands/web-search.ts +46 -0
  155. package/src/commit/agentic/agent.ts +314 -0
  156. package/src/commit/agentic/fallback.ts +96 -0
  157. package/src/commit/agentic/index.ts +354 -0
  158. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  159. package/src/commit/agentic/prompts/session-user.md +25 -0
  160. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  161. package/src/commit/agentic/prompts/system.md +38 -0
  162. package/src/commit/agentic/state.ts +60 -0
  163. package/src/commit/agentic/tools/analyze-file.ts +127 -0
  164. package/src/commit/agentic/tools/git-file-diff.ts +191 -0
  165. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  166. package/src/commit/agentic/tools/git-overview.ts +81 -0
  167. package/src/commit/agentic/tools/index.ts +54 -0
  168. package/src/commit/agentic/tools/propose-changelog.ts +139 -0
  169. package/src/commit/agentic/tools/propose-commit.ts +122 -0
  170. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  171. package/src/commit/agentic/tools/schemas.ts +31 -0
  172. package/src/commit/agentic/tools/split-commit.ts +251 -0
  173. package/src/commit/agentic/topo-sort.ts +44 -0
  174. package/src/commit/agentic/trivial.ts +51 -0
  175. package/src/commit/agentic/validation.ts +183 -0
  176. package/src/commit/analysis/conventional.ts +122 -0
  177. package/src/commit/analysis/index.ts +4 -0
  178. package/src/commit/analysis/scope.ts +242 -0
  179. package/src/commit/analysis/summary.ts +105 -0
  180. package/src/commit/analysis/validation.ts +66 -0
  181. package/src/commit/changelog/detect.ts +40 -0
  182. package/src/commit/changelog/generate.ts +101 -0
  183. package/src/commit/changelog/index.ts +234 -0
  184. package/src/commit/changelog/parse.ts +44 -0
  185. package/src/commit/cli.ts +85 -0
  186. package/src/commit/git/diff.ts +148 -0
  187. package/src/commit/index.ts +5 -0
  188. package/src/commit/map-reduce/index.ts +69 -0
  189. package/src/commit/map-reduce/map-phase.ts +193 -0
  190. package/src/commit/map-reduce/reduce-phase.ts +108 -0
  191. package/src/commit/map-reduce/utils.ts +9 -0
  192. package/src/commit/message.ts +11 -0
  193. package/src/commit/model-selection.ts +66 -0
  194. package/src/commit/pipeline.ts +242 -0
  195. package/src/commit/prompts/analysis-system.md +148 -0
  196. package/src/commit/prompts/analysis-user.md +38 -0
  197. package/src/commit/prompts/changelog-system.md +50 -0
  198. package/src/commit/prompts/changelog-user.md +18 -0
  199. package/src/commit/prompts/file-observer-system.md +24 -0
  200. package/src/commit/prompts/file-observer-user.md +8 -0
  201. package/src/commit/prompts/reduce-system.md +50 -0
  202. package/src/commit/prompts/reduce-user.md +17 -0
  203. package/src/commit/prompts/summary-retry.md +3 -0
  204. package/src/commit/prompts/summary-system.md +38 -0
  205. package/src/commit/prompts/summary-user.md +13 -0
  206. package/src/commit/prompts/types-description.md +2 -0
  207. package/src/commit/types.ts +118 -0
  208. package/src/commit/utils/exclusions.ts +42 -0
  209. package/src/commit/utils.ts +44 -0
  210. package/src/config/auto-config.ts +698 -0
  211. package/src/config/context-schema.json +115 -0
  212. package/src/config/file-lock.ts +121 -0
  213. package/src/config/keybindings.ts +493 -0
  214. package/src/config/mcp-schema.json +230 -0
  215. package/src/config/model-equivalence.ts +675 -0
  216. package/src/config/model-registry.ts +2265 -0
  217. package/src/config/model-resolver.ts +1289 -0
  218. package/src/config/prompt-templates.ts +271 -0
  219. package/src/config/resolve-config-value.ts +110 -0
  220. package/src/config/settings-schema.ts +2155 -0
  221. package/src/config/settings.ts +730 -0
  222. package/src/config.ts +418 -0
  223. package/src/cursor.ts +323 -0
  224. package/src/dap/client.ts +674 -0
  225. package/src/dap/config.ts +150 -0
  226. package/src/dap/defaults.json +211 -0
  227. package/src/dap/index.ts +4 -0
  228. package/src/dap/session.ts +1255 -0
  229. package/src/dap/types.ts +600 -0
  230. package/src/debug/index.ts +440 -0
  231. package/src/debug/log-formatting.ts +58 -0
  232. package/src/debug/log-viewer.ts +908 -0
  233. package/src/debug/profiler.ts +158 -0
  234. package/src/debug/report-bundle.ts +365 -0
  235. package/src/debug/system-info.ts +107 -0
  236. package/src/discovery/agents-md.ts +67 -0
  237. package/src/discovery/agents.ts +219 -0
  238. package/src/discovery/builtin.ts +851 -0
  239. package/src/discovery/claude-plugins.ts +297 -0
  240. package/src/discovery/claude.ts +543 -0
  241. package/src/discovery/cline.ts +83 -0
  242. package/src/discovery/codex.ts +535 -0
  243. package/src/discovery/cursor.ts +220 -0
  244. package/src/discovery/gemini.ts +396 -0
  245. package/src/discovery/github.ts +118 -0
  246. package/src/discovery/helpers.ts +934 -0
  247. package/src/discovery/index.ts +79 -0
  248. package/src/discovery/language.ts +46 -0
  249. package/src/discovery/mcp-json.ts +171 -0
  250. package/src/discovery/opencode.ts +393 -0
  251. package/src/discovery/plugin-dir-roots.ts +28 -0
  252. package/src/discovery/ssh.ts +153 -0
  253. package/src/discovery/substitute-plugin-root.ts +29 -0
  254. package/src/discovery/vscode.ts +105 -0
  255. package/src/discovery/windsurf.ts +147 -0
  256. package/src/edit/diff.ts +818 -0
  257. package/src/edit/index.ts +481 -0
  258. package/src/edit/line-hash.ts +67 -0
  259. package/src/edit/modes/chunk.ts +762 -0
  260. package/src/edit/modes/hashline.ts +1366 -0
  261. package/src/edit/modes/patch.ts +1791 -0
  262. package/src/edit/modes/replace.ts +1115 -0
  263. package/src/edit/normalize.ts +375 -0
  264. package/src/edit/renderer.ts +627 -0
  265. package/src/exa/factory.ts +61 -0
  266. package/src/exa/index.ts +27 -0
  267. package/src/exa/mcp-client.ts +293 -0
  268. package/src/exa/render.ts +232 -0
  269. package/src/exa/researcher.ts +47 -0
  270. package/src/exa/search.ts +86 -0
  271. package/src/exa/types.ts +166 -0
  272. package/src/exa/websets.ts +247 -0
  273. package/src/exec/bash-executor.ts +332 -0
  274. package/src/exec/exec.ts +53 -0
  275. package/src/exec/idle-timeout-watchdog.ts +126 -0
  276. package/src/exec/non-interactive-env.ts +48 -0
  277. package/src/export/custom-share.ts +65 -0
  278. package/src/export/html/index.ts +161 -0
  279. package/src/export/html/template.css +1001 -0
  280. package/src/export/html/template.generated.ts +2 -0
  281. package/src/export/html/template.html +46 -0
  282. package/src/export/html/template.js +1950 -0
  283. package/src/export/html/template.macro.ts +24 -0
  284. package/src/export/html/vendor/highlight.min.js +1213 -0
  285. package/src/export/html/vendor/marked.min.js +6 -0
  286. package/src/export/ttsr.ts +434 -0
  287. package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
  288. package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
  289. package/src/extensibility/custom-commands/index.ts +2 -0
  290. package/src/extensibility/custom-commands/loader.ts +236 -0
  291. package/src/extensibility/custom-commands/types.ts +111 -0
  292. package/src/extensibility/custom-tools/index.ts +7 -0
  293. package/src/extensibility/custom-tools/loader.ts +238 -0
  294. package/src/extensibility/custom-tools/types.ts +251 -0
  295. package/src/extensibility/custom-tools/wrapper.ts +47 -0
  296. package/src/extensibility/extensions/index.ts +15 -0
  297. package/src/extensibility/extensions/loader.ts +572 -0
  298. package/src/extensibility/extensions/runner.ts +848 -0
  299. package/src/extensibility/extensions/types.ts +1394 -0
  300. package/src/extensibility/extensions/wrapper.ts +191 -0
  301. package/src/extensibility/hooks/index.ts +5 -0
  302. package/src/extensibility/hooks/loader.ts +255 -0
  303. package/src/extensibility/hooks/runner.ts +425 -0
  304. package/src/extensibility/hooks/tool-wrapper.ts +108 -0
  305. package/src/extensibility/hooks/types.ts +826 -0
  306. package/src/extensibility/plugins/doctor.ts +66 -0
  307. package/src/extensibility/plugins/git-url.ts +281 -0
  308. package/src/extensibility/plugins/index.ts +9 -0
  309. package/src/extensibility/plugins/installer.ts +192 -0
  310. package/src/extensibility/plugins/loader.ts +267 -0
  311. package/src/extensibility/plugins/manager.ts +731 -0
  312. package/src/extensibility/plugins/marketplace/cache.ts +136 -0
  313. package/src/extensibility/plugins/marketplace/fetcher.ts +318 -0
  314. package/src/extensibility/plugins/marketplace/index.ts +6 -0
  315. package/src/extensibility/plugins/marketplace/manager.ts +781 -0
  316. package/src/extensibility/plugins/marketplace/prerequisites.ts +255 -0
  317. package/src/extensibility/plugins/marketplace/registry.ts +196 -0
  318. package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
  319. package/src/extensibility/plugins/marketplace/types.ts +201 -0
  320. package/src/extensibility/plugins/parser.ts +105 -0
  321. package/src/extensibility/plugins/types.ts +194 -0
  322. package/src/extensibility/skills.ts +269 -0
  323. package/src/extensibility/slash-commands.ts +246 -0
  324. package/src/extensibility/tool-proxy.ts +25 -0
  325. package/src/extensibility/utils.ts +38 -0
  326. package/src/index.ts +61 -0
  327. package/src/internal-urls/agent-protocol.ts +136 -0
  328. package/src/internal-urls/api-catalog-resolve.ts +495 -0
  329. package/src/internal-urls/api-catalog-types.ts +94 -0
  330. package/src/internal-urls/api-spec-resolve.ts +1036 -0
  331. package/src/internal-urls/api-spec-types.ts +239 -0
  332. package/src/internal-urls/artifact-protocol.ts +97 -0
  333. package/src/internal-urls/branding-index.generated.ts +88 -0
  334. package/src/internal-urls/build-info-runtime.ts +302 -0
  335. package/src/internal-urls/build-info.generated.ts +33 -0
  336. package/src/internal-urls/computer-profile.ts +692 -0
  337. package/src/internal-urls/console-catalog-types.ts +21 -0
  338. package/src/internal-urls/console-catalog.generated.ts +532 -0
  339. package/src/internal-urls/console-field-metadata-types.ts +59 -0
  340. package/src/internal-urls/console-field-metadata.generated.ts +4841 -0
  341. package/src/internal-urls/console-resolve.ts +178 -0
  342. package/src/internal-urls/docs-index.generated.ts +698 -0
  343. package/src/internal-urls/extension-api.md +147 -0
  344. package/src/internal-urls/index.ts +46 -0
  345. package/src/internal-urls/jobs-protocol.ts +119 -0
  346. package/src/internal-urls/json-query.ts +126 -0
  347. package/src/internal-urls/local-protocol.ts +206 -0
  348. package/src/internal-urls/mcp-protocol.ts +156 -0
  349. package/src/internal-urls/memory-protocol.ts +133 -0
  350. package/src/internal-urls/parse.ts +72 -0
  351. package/src/internal-urls/profile-collectors.ts +106 -0
  352. package/src/internal-urls/router.ts +55 -0
  353. package/src/internal-urls/rule-protocol.ts +55 -0
  354. package/src/internal-urls/skill-protocol.ts +111 -0
  355. package/src/internal-urls/terraform-index.generated.ts +1852 -0
  356. package/src/internal-urls/terraform-resolve.ts +184 -0
  357. package/src/internal-urls/terraform-types.ts +45 -0
  358. package/src/internal-urls/types.ts +52 -0
  359. package/src/internal-urls/user-profile.ts +363 -0
  360. package/src/internal-urls/xcsh-protocol.ts +644 -0
  361. package/src/ipy/cancellation.ts +28 -0
  362. package/src/ipy/executor.ts +1155 -0
  363. package/src/ipy/gateway-coordinator.ts +424 -0
  364. package/src/ipy/kernel.ts +1091 -0
  365. package/src/ipy/modules.ts +144 -0
  366. package/src/ipy/prelude.py +849 -0
  367. package/src/ipy/prelude.ts +3 -0
  368. package/src/ipy/runtime.ts +221 -0
  369. package/src/locales/ar.json +512 -0
  370. package/src/locales/de.json +512 -0
  371. package/src/locales/en.json +549 -0
  372. package/src/locales/es.json +512 -0
  373. package/src/locales/fr.json +512 -0
  374. package/src/locales/hi.json +512 -0
  375. package/src/locales/index.ts +29 -0
  376. package/src/locales/it.json +512 -0
  377. package/src/locales/ja.json +512 -0
  378. package/src/locales/ko.json +512 -0
  379. package/src/locales/pt-br.json +512 -0
  380. package/src/locales/th.json +512 -0
  381. package/src/locales/zh-cn.json +512 -0
  382. package/src/locales/zh-tw.json +512 -0
  383. package/src/lsp/client.ts +941 -0
  384. package/src/lsp/clients/biome-client.ts +202 -0
  385. package/src/lsp/clients/index.ts +50 -0
  386. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  387. package/src/lsp/clients/swiftlint-client.ts +120 -0
  388. package/src/lsp/config.ts +418 -0
  389. package/src/lsp/defaults.json +999 -0
  390. package/src/lsp/edits.ts +109 -0
  391. package/src/lsp/index.ts +1746 -0
  392. package/src/lsp/lspmux.ts +233 -0
  393. package/src/lsp/render.ts +692 -0
  394. package/src/lsp/startup-events.ts +13 -0
  395. package/src/lsp/types.ts +444 -0
  396. package/src/lsp/utils.ts +691 -0
  397. package/src/main.ts +944 -0
  398. package/src/mcp/client.ts +482 -0
  399. package/src/mcp/config-writer.ts +225 -0
  400. package/src/mcp/config.ts +365 -0
  401. package/src/mcp/discoverable-tool-metadata.ts +202 -0
  402. package/src/mcp/index.ts +29 -0
  403. package/src/mcp/json-rpc.ts +84 -0
  404. package/src/mcp/loader.ts +124 -0
  405. package/src/mcp/manager.ts +1152 -0
  406. package/src/mcp/oauth-discovery.ts +349 -0
  407. package/src/mcp/oauth-flow.ts +387 -0
  408. package/src/mcp/render.ts +157 -0
  409. package/src/mcp/smithery-auth.ts +104 -0
  410. package/src/mcp/smithery-connect.ts +145 -0
  411. package/src/mcp/smithery-registry.ts +477 -0
  412. package/src/mcp/tool-bridge.ts +417 -0
  413. package/src/mcp/tool-cache.ts +117 -0
  414. package/src/mcp/transports/http.ts +475 -0
  415. package/src/mcp/transports/index.ts +6 -0
  416. package/src/mcp/transports/stdio.ts +325 -0
  417. package/src/mcp/types.ts +423 -0
  418. package/src/memories/index.ts +1115 -0
  419. package/src/memories/storage.ts +577 -0
  420. package/src/modes/acp/acp-agent.ts +1361 -0
  421. package/src/modes/acp/acp-event-mapper.ts +538 -0
  422. package/src/modes/acp/acp-mode.ts +15 -0
  423. package/src/modes/acp/index.ts +2 -0
  424. package/src/modes/components/agent-dashboard.ts +1124 -0
  425. package/src/modes/components/assistant-message.ts +213 -0
  426. package/src/modes/components/bash-execution.ts +345 -0
  427. package/src/modes/components/bordered-loader.ts +41 -0
  428. package/src/modes/components/branch-summary-message.ts +45 -0
  429. package/src/modes/components/btw-panel.ts +103 -0
  430. package/src/modes/components/compaction-summary-message.ts +51 -0
  431. package/src/modes/components/context-add-wizard.ts +534 -0
  432. package/src/modes/components/countdown-timer.ts +75 -0
  433. package/src/modes/components/custom-editor.ts +238 -0
  434. package/src/modes/components/custom-message.ts +91 -0
  435. package/src/modes/components/diff.ts +210 -0
  436. package/src/modes/components/dynamic-border.ts +25 -0
  437. package/src/modes/components/extensions/extension-dashboard.ts +326 -0
  438. package/src/modes/components/extensions/extension-list.ts +492 -0
  439. package/src/modes/components/extensions/index.ts +9 -0
  440. package/src/modes/components/extensions/inspector-panel.ts +317 -0
  441. package/src/modes/components/extensions/state-manager.ts +587 -0
  442. package/src/modes/components/extensions/types.ts +191 -0
  443. package/src/modes/components/footer.ts +262 -0
  444. package/src/modes/components/gutter-block.ts +315 -0
  445. package/src/modes/components/history-search.ts +158 -0
  446. package/src/modes/components/hook-editor.ts +151 -0
  447. package/src/modes/components/hook-input.ts +81 -0
  448. package/src/modes/components/hook-message.ts +100 -0
  449. package/src/modes/components/hook-selector.ts +192 -0
  450. package/src/modes/components/index.ts +36 -0
  451. package/src/modes/components/keybinding-hints.ts +65 -0
  452. package/src/modes/components/login-dialog.ts +164 -0
  453. package/src/modes/components/mcp-add-wizard.ts +1314 -0
  454. package/src/modes/components/model-selector.ts +946 -0
  455. package/src/modes/components/oauth-selector.ts +211 -0
  456. package/src/modes/components/plugin-selector.ts +95 -0
  457. package/src/modes/components/plugin-settings.ts +477 -0
  458. package/src/modes/components/plugins/index.ts +5 -0
  459. package/src/modes/components/plugins/plugin-dashboard.ts +523 -0
  460. package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
  461. package/src/modes/components/plugins/plugin-list-pane.ts +109 -0
  462. package/src/modes/components/plugins/state-manager.ts +229 -0
  463. package/src/modes/components/plugins/types.ts +51 -0
  464. package/src/modes/components/plugins/utils.ts +6 -0
  465. package/src/modes/components/python-execution.ts +244 -0
  466. package/src/modes/components/queue-mode-selector.ts +56 -0
  467. package/src/modes/components/read-tool-group.ts +137 -0
  468. package/src/modes/components/session-observer-overlay.ts +483 -0
  469. package/src/modes/components/session-selector.ts +343 -0
  470. package/src/modes/components/settings-defs.ts +568 -0
  471. package/src/modes/components/settings-selector.ts +641 -0
  472. package/src/modes/components/show-images-selector.ts +45 -0
  473. package/src/modes/components/skill-message.ts +90 -0
  474. package/src/modes/components/status-line/context-gradient.ts +64 -0
  475. package/src/modes/components/status-line/git-utils.ts +42 -0
  476. package/src/modes/components/status-line/hex-ansi.ts +29 -0
  477. package/src/modes/components/status-line/index.ts +4 -0
  478. package/src/modes/components/status-line/presets.ts +174 -0
  479. package/src/modes/components/status-line/segments.ts +494 -0
  480. package/src/modes/components/status-line/separators.ts +55 -0
  481. package/src/modes/components/status-line/token-rate.ts +66 -0
  482. package/src/modes/components/status-line/types.ts +96 -0
  483. package/src/modes/components/status-line-segment-editor.ts +361 -0
  484. package/src/modes/components/status-line.ts +729 -0
  485. package/src/modes/components/theme-selector.ts +63 -0
  486. package/src/modes/components/thinking-selector.ts +52 -0
  487. package/src/modes/components/todo-reminder.ts +54 -0
  488. package/src/modes/components/tool-execution.ts +781 -0
  489. package/src/modes/components/tree-selector.ts +908 -0
  490. package/src/modes/components/ttsr-notification.ts +80 -0
  491. package/src/modes/components/user-message-selector.ts +141 -0
  492. package/src/modes/components/user-message.ts +100 -0
  493. package/src/modes/components/visual-truncate.ts +63 -0
  494. package/src/modes/components/welcome-checks.ts +313 -0
  495. package/src/modes/components/welcome.ts +270 -0
  496. package/src/modes/controllers/btw-controller.ts +193 -0
  497. package/src/modes/controllers/chord-routing.ts +37 -0
  498. package/src/modes/controllers/command-controller.ts +1293 -0
  499. package/src/modes/controllers/context-command-controller.ts +61 -0
  500. package/src/modes/controllers/event-controller.ts +743 -0
  501. package/src/modes/controllers/extension-ui-controller.ts +983 -0
  502. package/src/modes/controllers/input-controller.ts +877 -0
  503. package/src/modes/controllers/mcp-command-controller.ts +1940 -0
  504. package/src/modes/controllers/selector-controller.ts +1292 -0
  505. package/src/modes/controllers/ssh-command-controller.ts +421 -0
  506. package/src/modes/index.ts +34 -0
  507. package/src/modes/interactive-mode.ts +1781 -0
  508. package/src/modes/oauth-manual-input.ts +42 -0
  509. package/src/modes/print-mode.ts +196 -0
  510. package/src/modes/prompt-action-autocomplete.ts +231 -0
  511. package/src/modes/rpc/host-tools.ts +186 -0
  512. package/src/modes/rpc/rpc-client.ts +762 -0
  513. package/src/modes/rpc/rpc-mode.ts +877 -0
  514. package/src/modes/rpc/rpc-types.ts +322 -0
  515. package/src/modes/session-observer-registry.ts +146 -0
  516. package/src/modes/shared.ts +30 -0
  517. package/src/modes/theme/defaults/index.ts +7 -0
  518. package/src/modes/theme/defaults/xcsh-dark.json +113 -0
  519. package/src/modes/theme/defaults/xcsh-light.json +115 -0
  520. package/src/modes/theme/mermaid-cache.ts +151 -0
  521. package/src/modes/theme/mermaid-palette.ts +39 -0
  522. package/src/modes/theme/theme-schema.json +553 -0
  523. package/src/modes/theme/theme.ts +2563 -0
  524. package/src/modes/types.ts +273 -0
  525. package/src/modes/utils/hotkeys-markdown.ts +60 -0
  526. package/src/modes/utils/keybinding-matchers.ts +30 -0
  527. package/src/modes/utils/read-group-outcome-aggregator.ts +55 -0
  528. package/src/modes/utils/sanitize-error-message.ts +60 -0
  529. package/src/modes/utils/tools-markdown.ts +28 -0
  530. package/src/modes/utils/ui-helpers.ts +734 -0
  531. package/src/plan-mode/approved-plan.ts +55 -0
  532. package/src/plan-mode/state.ts +6 -0
  533. package/src/priority.json +37 -0
  534. package/src/prompts/agents/designer.md +67 -0
  535. package/src/prompts/agents/explore.md +61 -0
  536. package/src/prompts/agents/frontmatter.md +10 -0
  537. package/src/prompts/agents/init.md +36 -0
  538. package/src/prompts/agents/librarian.md +121 -0
  539. package/src/prompts/agents/plan.md +49 -0
  540. package/src/prompts/agents/reviewer.md +127 -0
  541. package/src/prompts/agents/task.md +16 -0
  542. package/src/prompts/ci-green-request.md +35 -0
  543. package/src/prompts/compaction/branch-summary-context.md +5 -0
  544. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  545. package/src/prompts/compaction/branch-summary.md +30 -0
  546. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  547. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  548. package/src/prompts/compaction/compaction-summary.md +38 -0
  549. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  550. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  551. package/src/prompts/memories/consolidation.md +30 -0
  552. package/src/prompts/memories/read-path.md +11 -0
  553. package/src/prompts/memories/stage_one_input.md +6 -0
  554. package/src/prompts/memories/stage_one_system.md +21 -0
  555. package/src/prompts/review-request.md +70 -0
  556. package/src/prompts/system/agent-creation-architect.md +65 -0
  557. package/src/prompts/system/agent-creation-user.md +6 -0
  558. package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
  559. package/src/prompts/system/btw-user.md +8 -0
  560. package/src/prompts/system/commit-message-system.md +2 -0
  561. package/src/prompts/system/custom-system-prompt.md +82 -0
  562. package/src/prompts/system/eager-todo.md +13 -0
  563. package/src/prompts/system/file-operations.md +10 -0
  564. package/src/prompts/system/handoff-document.md +48 -0
  565. package/src/prompts/system/plan-mode-active.md +114 -0
  566. package/src/prompts/system/plan-mode-approved.md +23 -0
  567. package/src/prompts/system/plan-mode-reference.md +14 -0
  568. package/src/prompts/system/plan-mode-subagent.md +34 -0
  569. package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
  570. package/src/prompts/system/subagent-submit-reminder.md +7 -0
  571. package/src/prompts/system/subagent-system-prompt.md +36 -0
  572. package/src/prompts/system/subagent-user-prompt.md +12 -0
  573. package/src/prompts/system/summarization-system.md +3 -0
  574. package/src/prompts/system/system-prompt.md +698 -0
  575. package/src/prompts/system/title-system.md +2 -0
  576. package/src/prompts/system/ttsr-interrupt.md +7 -0
  577. package/src/prompts/system/web-search.md +28 -0
  578. package/src/prompts/tools/ask.md +28 -0
  579. package/src/prompts/tools/ast-edit.md +48 -0
  580. package/src/prompts/tools/ast-grep.md +54 -0
  581. package/src/prompts/tools/async-result.md +5 -0
  582. package/src/prompts/tools/bash.md +67 -0
  583. package/src/prompts/tools/browser.md +26 -0
  584. package/src/prompts/tools/calculator.md +10 -0
  585. package/src/prompts/tools/cancel-job.md +5 -0
  586. package/src/prompts/tools/catalog-workflow-runner.md +13 -0
  587. package/src/prompts/tools/checkpoint.md +16 -0
  588. package/src/prompts/tools/chunk-edit.md +279 -0
  589. package/src/prompts/tools/debug.md +43 -0
  590. package/src/prompts/tools/display-image.md +23 -0
  591. package/src/prompts/tools/exit-plan-mode.md +41 -0
  592. package/src/prompts/tools/find.md +26 -0
  593. package/src/prompts/tools/gemini-image.md +7 -0
  594. package/src/prompts/tools/grep.md +29 -0
  595. package/src/prompts/tools/hashline.md +131 -0
  596. package/src/prompts/tools/inspect-image-system.md +20 -0
  597. package/src/prompts/tools/inspect-image.md +32 -0
  598. package/src/prompts/tools/lsp.md +33 -0
  599. package/src/prompts/tools/patch.md +77 -0
  600. package/src/prompts/tools/poll.md +5 -0
  601. package/src/prompts/tools/python.md +60 -0
  602. package/src/prompts/tools/read-chunk.md +35 -0
  603. package/src/prompts/tools/read.md +63 -0
  604. package/src/prompts/tools/render-mermaid.md +9 -0
  605. package/src/prompts/tools/replace.md +36 -0
  606. package/src/prompts/tools/resolve.md +8 -0
  607. package/src/prompts/tools/rewind.md +13 -0
  608. package/src/prompts/tools/search-tool-bm25.md +34 -0
  609. package/src/prompts/tools/ssh.md +44 -0
  610. package/src/prompts/tools/task-summary.md +28 -0
  611. package/src/prompts/tools/task.md +131 -0
  612. package/src/prompts/tools/todo-write.md +89 -0
  613. package/src/prompts/tools/vim.md +98 -0
  614. package/src/prompts/tools/web-search.md +10 -0
  615. package/src/prompts/tools/write.md +14 -0
  616. package/src/prompts/tools/xcsh-api.md +283 -0
  617. package/src/resource-management/index.ts +42 -0
  618. package/src/sdk.ts +2123 -0
  619. package/src/secrets/index.ts +146 -0
  620. package/src/secrets/obfuscator.ts +290 -0
  621. package/src/secrets/regex.ts +21 -0
  622. package/src/services/context-env.ts +102 -0
  623. package/src/services/xcsh-api-client.ts +230 -0
  624. package/src/services/xcsh-context-command.ts +803 -0
  625. package/src/services/xcsh-context-display.ts +49 -0
  626. package/src/services/xcsh-context-indicators.ts +17 -0
  627. package/src/services/xcsh-context-segment.ts +39 -0
  628. package/src/services/xcsh-context.ts +1410 -0
  629. package/src/services/xcsh-env.ts +107 -0
  630. package/src/services/xcsh-knowledge.ts +177 -0
  631. package/src/services/xcsh-table.ts +164 -0
  632. package/src/session/agent-session.ts +6690 -0
  633. package/src/session/agent-storage.ts +451 -0
  634. package/src/session/artifacts.ts +132 -0
  635. package/src/session/auth-storage.ts +17 -0
  636. package/src/session/blob-store.ts +135 -0
  637. package/src/session/compaction/branch-summarization.ts +322 -0
  638. package/src/session/compaction/compaction.ts +1388 -0
  639. package/src/session/compaction/index.ts +7 -0
  640. package/src/session/compaction/pruning.ts +91 -0
  641. package/src/session/compaction/utils.ts +184 -0
  642. package/src/session/history-storage.ts +253 -0
  643. package/src/session/messages.ts +597 -0
  644. package/src/session/session-dump-format.ts +203 -0
  645. package/src/session/session-manager.ts +2816 -0
  646. package/src/session/session-storage.ts +370 -0
  647. package/src/session/streaming-output.ts +770 -0
  648. package/src/session/tool-choice-queue.ts +213 -0
  649. package/src/slash-commands/builtin-registry.ts +1614 -0
  650. package/src/slash-commands/export-command.ts +178 -0
  651. package/src/slash-commands/marketplace-install-parser.ts +99 -0
  652. package/src/slash-commands/resource-commands.ts +250 -0
  653. package/src/ssh/config-writer.ts +183 -0
  654. package/src/ssh/connection-manager.ts +445 -0
  655. package/src/ssh/ssh-executor.ts +131 -0
  656. package/src/ssh/sshfs-mount.ts +137 -0
  657. package/src/ssh/utils.ts +8 -0
  658. package/src/stt/downloader.ts +71 -0
  659. package/src/stt/index.ts +3 -0
  660. package/src/stt/recorder.ts +351 -0
  661. package/src/stt/setup.ts +52 -0
  662. package/src/stt/stt-controller.ts +160 -0
  663. package/src/stt/transcribe.py +70 -0
  664. package/src/stt/transcriber.ts +91 -0
  665. package/src/system-prompt.ts +688 -0
  666. package/src/task/agents.ts +166 -0
  667. package/src/task/commands.ts +131 -0
  668. package/src/task/discovery.ts +126 -0
  669. package/src/task/executor.ts +1324 -0
  670. package/src/task/index.ts +1197 -0
  671. package/src/task/isolation-backend.ts +72 -0
  672. package/src/task/name-generator.ts +1577 -0
  673. package/src/task/output-manager.ts +107 -0
  674. package/src/task/parallel.ts +116 -0
  675. package/src/task/render.ts +1035 -0
  676. package/src/task/subprocess-tool-registry.ts +88 -0
  677. package/src/task/template.ts +33 -0
  678. package/src/task/types.ts +230 -0
  679. package/src/task/worktree.ts +580 -0
  680. package/src/task/xcsh-command.ts +26 -0
  681. package/src/thinking.ts +87 -0
  682. package/src/tools/action-renderer.ts +198 -0
  683. package/src/tools/archive-reader.ts +315 -0
  684. package/src/tools/ask.ts +791 -0
  685. package/src/tools/ast-edit.ts +492 -0
  686. package/src/tools/ast-grep.ts +453 -0
  687. package/src/tools/auto-generated-guard.ts +305 -0
  688. package/src/tools/bash-interactive.ts +383 -0
  689. package/src/tools/bash-interceptor.ts +67 -0
  690. package/src/tools/bash-normalize.ts +107 -0
  691. package/src/tools/bash-skill-urls.ts +246 -0
  692. package/src/tools/bash.ts +878 -0
  693. package/src/tools/browser-renderer.ts +129 -0
  694. package/src/tools/browser.ts +1366 -0
  695. package/src/tools/calculator.ts +543 -0
  696. package/src/tools/cancel-job.ts +100 -0
  697. package/src/tools/catalog-workflow-runner.ts +718 -0
  698. package/src/tools/checkpoint.ts +133 -0
  699. package/src/tools/context.ts +39 -0
  700. package/src/tools/debug.ts +1004 -0
  701. package/src/tools/display-image-renderer.ts +96 -0
  702. package/src/tools/display-image.ts +109 -0
  703. package/src/tools/exit-plan-mode.ts +101 -0
  704. package/src/tools/fetch.ts +1462 -0
  705. package/src/tools/find.ts +437 -0
  706. package/src/tools/fs-cache-invalidation.ts +33 -0
  707. package/src/tools/gemini-image.ts +1004 -0
  708. package/src/tools/grep.ts +705 -0
  709. package/src/tools/index.ts +442 -0
  710. package/src/tools/inspect-image-renderer.ts +101 -0
  711. package/src/tools/inspect-image.ts +176 -0
  712. package/src/tools/json-tree.ts +266 -0
  713. package/src/tools/jtd-to-json-schema.ts +199 -0
  714. package/src/tools/jtd-to-typescript.ts +136 -0
  715. package/src/tools/jtd-utils.ts +102 -0
  716. package/src/tools/list-limit.ts +40 -0
  717. package/src/tools/mermaid-renderer.ts +127 -0
  718. package/src/tools/notebook.ts +288 -0
  719. package/src/tools/output-meta.ts +588 -0
  720. package/src/tools/path-utils.ts +560 -0
  721. package/src/tools/plan-mode-guard.ts +41 -0
  722. package/src/tools/poll-tool.ts +177 -0
  723. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  724. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  725. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  726. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  727. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  728. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  729. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  730. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  731. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  732. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  733. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  734. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  735. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  736. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  737. package/src/tools/python.ts +1184 -0
  738. package/src/tools/read.ts +1591 -0
  739. package/src/tools/render-mermaid.ts +93 -0
  740. package/src/tools/render-utils.ts +677 -0
  741. package/src/tools/renderers.ts +82 -0
  742. package/src/tools/report-tool-issue.ts +80 -0
  743. package/src/tools/resolve.ts +196 -0
  744. package/src/tools/review.ts +238 -0
  745. package/src/tools/search-tool-bm25.ts +288 -0
  746. package/src/tools/sqlite-reader.ts +623 -0
  747. package/src/tools/ssh.ts +322 -0
  748. package/src/tools/submit-result.ts +229 -0
  749. package/src/tools/todo-render.ts +33 -0
  750. package/src/tools/todo-write.ts +440 -0
  751. package/src/tools/tool-errors.ts +62 -0
  752. package/src/tools/tool-result.ts +93 -0
  753. package/src/tools/tool-timeouts.ts +30 -0
  754. package/src/tools/vim.ts +981 -0
  755. package/src/tools/write.ts +671 -0
  756. package/src/tools/xcsh-api-renderer.ts +402 -0
  757. package/src/tools/xcsh-api.ts +887 -0
  758. package/src/tui/code-cell.ts +101 -0
  759. package/src/tui/file-list.ts +47 -0
  760. package/src/tui/index.ts +11 -0
  761. package/src/tui/output-block.ts +175 -0
  762. package/src/tui/status-line.ts +39 -0
  763. package/src/tui/tree-list.ts +84 -0
  764. package/src/tui/types.ts +15 -0
  765. package/src/tui/utils.ts +103 -0
  766. package/src/utils/changelog.ts +98 -0
  767. package/src/utils/clipboard.ts +80 -0
  768. package/src/utils/command-args.ts +76 -0
  769. package/src/utils/commit-message-generator.ts +137 -0
  770. package/src/utils/edit-mode.ts +50 -0
  771. package/src/utils/event-bus.ts +33 -0
  772. package/src/utils/external-editor.ts +65 -0
  773. package/src/utils/file-display-mode.ts +37 -0
  774. package/src/utils/file-mentions.ts +376 -0
  775. package/src/utils/fuzzy.ts +108 -0
  776. package/src/utils/git.ts +1495 -0
  777. package/src/utils/gitstatus.ts +140 -0
  778. package/src/utils/image-convert.ts +27 -0
  779. package/src/utils/image-loading.ts +98 -0
  780. package/src/utils/image-passthrough.ts +118 -0
  781. package/src/utils/image-resize.ts +272 -0
  782. package/src/utils/image-viewer.ts +17 -0
  783. package/src/utils/lang-from-path.ts +239 -0
  784. package/src/utils/markit.ts +81 -0
  785. package/src/utils/open.ts +20 -0
  786. package/src/utils/session-color.ts +55 -0
  787. package/src/utils/shell-snapshot.ts +188 -0
  788. package/src/utils/title-generator.ts +193 -0
  789. package/src/utils/tool-choice.ts +28 -0
  790. package/src/utils/tools-manager.ts +356 -0
  791. package/src/vim/buffer.ts +309 -0
  792. package/src/vim/commands.ts +382 -0
  793. package/src/vim/engine.ts +2426 -0
  794. package/src/vim/parser.ts +151 -0
  795. package/src/vim/render.ts +252 -0
  796. package/src/vim/types.ts +197 -0
  797. package/src/web/kagi.ts +174 -0
  798. package/src/web/parallel.ts +346 -0
  799. package/src/web/scrapers/artifacthub.ts +207 -0
  800. package/src/web/scrapers/arxiv.ts +83 -0
  801. package/src/web/scrapers/aur.ts +162 -0
  802. package/src/web/scrapers/biorxiv.ts +133 -0
  803. package/src/web/scrapers/bluesky.ts +262 -0
  804. package/src/web/scrapers/brew.ts +172 -0
  805. package/src/web/scrapers/cheatsh.ts +68 -0
  806. package/src/web/scrapers/chocolatey.ts +196 -0
  807. package/src/web/scrapers/choosealicense.ts +95 -0
  808. package/src/web/scrapers/cisa-kev.ts +87 -0
  809. package/src/web/scrapers/clojars.ts +154 -0
  810. package/src/web/scrapers/coingecko.ts +177 -0
  811. package/src/web/scrapers/crates-io.ts +110 -0
  812. package/src/web/scrapers/crossref.ts +136 -0
  813. package/src/web/scrapers/devto.ts +147 -0
  814. package/src/web/scrapers/discogs.ts +306 -0
  815. package/src/web/scrapers/discourse.ts +197 -0
  816. package/src/web/scrapers/dockerhub.ts +138 -0
  817. package/src/web/scrapers/docs-rs.ts +653 -0
  818. package/src/web/scrapers/fdroid.ts +134 -0
  819. package/src/web/scrapers/firefox-addons.ts +191 -0
  820. package/src/web/scrapers/flathub.ts +223 -0
  821. package/src/web/scrapers/github-gist.ts +58 -0
  822. package/src/web/scrapers/github.ts +452 -0
  823. package/src/web/scrapers/gitlab.ts +401 -0
  824. package/src/web/scrapers/go-pkg.ts +266 -0
  825. package/src/web/scrapers/hackage.ts +140 -0
  826. package/src/web/scrapers/hackernews.ts +189 -0
  827. package/src/web/scrapers/hex.ts +105 -0
  828. package/src/web/scrapers/huggingface.ts +321 -0
  829. package/src/web/scrapers/iacr.ts +89 -0
  830. package/src/web/scrapers/index.ts +252 -0
  831. package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
  832. package/src/web/scrapers/lemmy.ts +203 -0
  833. package/src/web/scrapers/lobsters.ts +175 -0
  834. package/src/web/scrapers/mastodon.ts +292 -0
  835. package/src/web/scrapers/maven.ts +138 -0
  836. package/src/web/scrapers/mdn.ts +169 -0
  837. package/src/web/scrapers/metacpan.ts +222 -0
  838. package/src/web/scrapers/musicbrainz.ts +250 -0
  839. package/src/web/scrapers/npm.ts +98 -0
  840. package/src/web/scrapers/nuget.ts +183 -0
  841. package/src/web/scrapers/nvd.ts +222 -0
  842. package/src/web/scrapers/ollama.ts +239 -0
  843. package/src/web/scrapers/open-vsx.ts +106 -0
  844. package/src/web/scrapers/opencorporates.ts +292 -0
  845. package/src/web/scrapers/openlibrary.ts +336 -0
  846. package/src/web/scrapers/orcid.ts +286 -0
  847. package/src/web/scrapers/osv.ts +176 -0
  848. package/src/web/scrapers/packagist.ts +160 -0
  849. package/src/web/scrapers/pub-dev.ts +143 -0
  850. package/src/web/scrapers/pubmed.ts +211 -0
  851. package/src/web/scrapers/pypi.ts +112 -0
  852. package/src/web/scrapers/rawg.ts +110 -0
  853. package/src/web/scrapers/readthedocs.ts +120 -0
  854. package/src/web/scrapers/reddit.ts +95 -0
  855. package/src/web/scrapers/repology.ts +251 -0
  856. package/src/web/scrapers/rfc.ts +201 -0
  857. package/src/web/scrapers/rubygems.ts +103 -0
  858. package/src/web/scrapers/searchcode.ts +190 -0
  859. package/src/web/scrapers/sec-edgar.ts +261 -0
  860. package/src/web/scrapers/semantic-scholar.ts +171 -0
  861. package/src/web/scrapers/snapcraft.ts +187 -0
  862. package/src/web/scrapers/sourcegraph.ts +336 -0
  863. package/src/web/scrapers/spdx.ts +108 -0
  864. package/src/web/scrapers/spotify.ts +198 -0
  865. package/src/web/scrapers/stackoverflow.ts +120 -0
  866. package/src/web/scrapers/terraform.ts +277 -0
  867. package/src/web/scrapers/tldr.ts +47 -0
  868. package/src/web/scrapers/twitter.ts +93 -0
  869. package/src/web/scrapers/types.ts +267 -0
  870. package/src/web/scrapers/utils.ts +109 -0
  871. package/src/web/scrapers/vimeo.ts +133 -0
  872. package/src/web/scrapers/vscode-marketplace.ts +187 -0
  873. package/src/web/scrapers/w3c.ts +156 -0
  874. package/src/web/scrapers/wikidata.ts +344 -0
  875. package/src/web/scrapers/wikipedia.ts +84 -0
  876. package/src/web/scrapers/youtube.ts +313 -0
  877. package/src/web/search/errors.ts +120 -0
  878. package/src/web/search/index.ts +346 -0
  879. package/src/web/search/params.ts +129 -0
  880. package/src/web/search/provider.ts +97 -0
  881. package/src/web/search/providers/anthropic.ts +339 -0
  882. package/src/web/search/providers/base.ts +35 -0
  883. package/src/web/search/providers/brave.ts +150 -0
  884. package/src/web/search/providers/codex.ts +402 -0
  885. package/src/web/search/providers/exa.ts +264 -0
  886. package/src/web/search/providers/firecrawl.ts +115 -0
  887. package/src/web/search/providers/gemini.ts +566 -0
  888. package/src/web/search/providers/jina.ts +99 -0
  889. package/src/web/search/providers/kagi.ts +70 -0
  890. package/src/web/search/providers/kimi.ts +157 -0
  891. package/src/web/search/providers/parallel.ts +63 -0
  892. package/src/web/search/providers/perplexity.ts +546 -0
  893. package/src/web/search/providers/synthetic.ts +113 -0
  894. package/src/web/search/providers/tavily.ts +162 -0
  895. package/src/web/search/providers/utils.ts +36 -0
  896. package/src/web/search/providers/zai.ts +311 -0
  897. package/src/web/search/render.ts +348 -0
  898. package/src/web/search/types.ts +438 -0
  899. package/src/web/search/utils.ts +17 -0
@@ -0,0 +1,1462 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import type { AgentToolResult } from "@f5xc-salesdemos/pi-agent-core";
4
+ import type { ImageContent, TextContent } from "@f5xc-salesdemos/pi-ai";
5
+ import { htmlToMarkdown } from "@f5xc-salesdemos/pi-natives";
6
+ import { type Component, Text } from "@f5xc-salesdemos/pi-tui";
7
+ import { $which, ptree, truncate } from "@f5xc-salesdemos/pi-utils";
8
+ import { parseHTML } from "linkedom";
9
+ import type { Settings } from "../config/settings";
10
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
11
+ import { type Theme, theme } from "../modes/theme/theme";
12
+ import type { ToolSession } from "../sdk";
13
+ import { DEFAULT_MAX_BYTES, truncateHead } from "../session/streaming-output";
14
+ import { renderStatusLine } from "../tui";
15
+ import { CachedOutputBlock } from "../tui/output-block";
16
+ import { formatDimensionNote, resizeImage } from "../utils/image-resize";
17
+ import { ensureTool } from "../utils/tools-manager";
18
+ import { extractWithParallel, findParallelApiKey, getParallelExtractContent } from "../web/parallel";
19
+ import { specialHandlers } from "../web/scrapers";
20
+ import type { RenderResult } from "../web/scrapers/types";
21
+ import { finalizeOutput, loadPage, MAX_OUTPUT_CHARS } from "../web/scrapers/types";
22
+ import { convertWithMarkit, fetchBinary } from "../web/scrapers/utils";
23
+ import { applyListLimit } from "./list-limit";
24
+ import { formatStyledArtifactReference, type OutputMeta } from "./output-meta";
25
+ import { formatExpandHint, getDomain } from "./render-utils";
26
+ import { ToolAbortError, ToolError } from "./tool-errors";
27
+ import { toolResult } from "./tool-result";
28
+ import { clampTimeout } from "./tool-timeouts";
29
+
30
+ // =============================================================================
31
+ // Types and Constants
32
+ // =============================================================================
33
+
34
+ const FETCH_DEFAULT_MAX_LINES = 300;
35
+ // Convertible document types handled by markit.
36
+ const CONVERTIBLE_MIMES = new Set([
37
+ "application/pdf",
38
+ "application/msword",
39
+ "application/vnd.ms-powerpoint",
40
+ "application/vnd.ms-excel",
41
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
42
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
43
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
44
+ "application/rtf",
45
+ "application/epub+zip",
46
+ "application/x-ipynb+json",
47
+ "application/zip",
48
+ "image/png",
49
+ "image/jpeg",
50
+ "image/gif",
51
+ "image/webp",
52
+ "audio/mpeg",
53
+ "audio/wav",
54
+ "audio/ogg",
55
+ ]);
56
+
57
+ const CONVERTIBLE_EXTENSIONS = new Set([
58
+ ".pdf",
59
+ ".doc",
60
+ ".docx",
61
+ ".ppt",
62
+ ".pptx",
63
+ ".xls",
64
+ ".xlsx",
65
+ ".rtf",
66
+ ".epub",
67
+ ".ipynb",
68
+ ".png",
69
+ ".jpg",
70
+ ".jpeg",
71
+ ".gif",
72
+ ".webp",
73
+ ".mp3",
74
+ ".wav",
75
+ ".ogg",
76
+ ]);
77
+
78
+ const IMAGE_MIME_BY_EXTENSION = new Map<string, string>([
79
+ [".png", "image/png"],
80
+ [".jpg", "image/jpeg"],
81
+ [".jpeg", "image/jpeg"],
82
+ [".gif", "image/gif"],
83
+ [".webp", "image/webp"],
84
+ ]);
85
+ const SUPPORTED_INLINE_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
86
+ const MAX_INLINE_IMAGE_SOURCE_BYTES = 20 * 1024 * 1024;
87
+ const MAX_INLINE_IMAGE_OUTPUT_BYTES = 300 * 1024;
88
+
89
+ // =============================================================================
90
+ // Utilities
91
+ // =============================================================================
92
+
93
+ /**
94
+ * Check if a command exists (cross-platform)
95
+ */
96
+ function hasCommand(cmd: string): boolean {
97
+ return Boolean($which(cmd));
98
+ }
99
+
100
+ /**
101
+ * Build llms.txt candidates scoped to the requested URL
102
+ */
103
+ function buildLlmEndpointCandidates(url: string): string[] {
104
+ try {
105
+ const parsed = new URL(url);
106
+ if (parsed.pathname === "/") {
107
+ return [`${parsed.origin}/.well-known/llms.txt`, `${parsed.origin}/llms.txt`, `${parsed.origin}/llms.md`];
108
+ }
109
+
110
+ const trimmedPath = parsed.pathname.replace(/\/+$/, "");
111
+ const segments = trimmedPath.split("/").filter(Boolean);
112
+ const scopeDepth = parsed.pathname.endsWith("/") ? segments.length : Math.max(segments.length - 1, 1);
113
+ const endpoints: string[] = [];
114
+
115
+ for (let depth = scopeDepth; depth >= 1; depth--) {
116
+ const scope = `/${segments.slice(0, depth).join("/")}/`;
117
+ endpoints.push(`${parsed.origin}${scope}llms.txt`, `${parsed.origin}${scope}llms.md`);
118
+ }
119
+
120
+ return endpoints;
121
+ } catch {
122
+ return [];
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Normalize URL (add scheme if missing)
128
+ */
129
+ function normalizeUrl(url: string): string {
130
+ if (!url.match(/^https?:\/\//i)) {
131
+ return `https://${url}`;
132
+ }
133
+ return url;
134
+ }
135
+
136
+ export function isReadableUrlPath(value: string): boolean {
137
+ return /^https?:\/\//i.test(value) || /^www\./i.test(value);
138
+ }
139
+
140
+ const URL_LINE_RANGE_RE = /^L(\d+)(?:-L?(\d+))?$/i;
141
+
142
+ export interface ParsedReadUrlTarget {
143
+ path: string;
144
+ raw: boolean;
145
+ offset?: number;
146
+ limit?: number;
147
+ }
148
+
149
+ export function parseReadUrlTarget(readPath: string, sel?: string): ParsedReadUrlTarget | null {
150
+ const embedded = sel ? undefined : tryExtractEmbeddedUrlSelector(readPath);
151
+ const urlPath = embedded?.path ?? readPath;
152
+ if (!isReadableUrlPath(urlPath)) {
153
+ return null;
154
+ }
155
+
156
+ const selector = sel ?? embedded?.sel;
157
+ const raw = selector === "raw";
158
+ const lineMatch = selector ? URL_LINE_RANGE_RE.exec(selector) : null;
159
+ if (lineMatch) {
160
+ const startLine = Number.parseInt(lineMatch[1]!, 10);
161
+ if (startLine < 1) {
162
+ throw new ToolError("L0 is invalid; lines are 1-indexed. Use sel=L1.");
163
+ }
164
+ const endLine = lineMatch[2] ? Number.parseInt(lineMatch[2], 10) : undefined;
165
+ if (endLine !== undefined && endLine < startLine) {
166
+ throw new ToolError(`Invalid range L${startLine}-L${endLine}: end must be >= start.`);
167
+ }
168
+ return {
169
+ path: urlPath,
170
+ raw: false,
171
+ offset: startLine,
172
+ limit: endLine !== undefined ? endLine - startLine + 1 : undefined,
173
+ };
174
+ }
175
+
176
+ return { path: urlPath, raw };
177
+ }
178
+
179
+ function tryExtractEmbeddedUrlSelector(readPath: string): { path: string; sel?: string } | null {
180
+ const lastColonIndex = readPath.lastIndexOf(":");
181
+ if (lastColonIndex <= 0) {
182
+ return null;
183
+ }
184
+
185
+ const candidateSelector = readPath.slice(lastColonIndex + 1);
186
+ const isEmbeddedSelector = candidateSelector === "raw" || URL_LINE_RANGE_RE.test(candidateSelector);
187
+ if (!isEmbeddedSelector) {
188
+ return null;
189
+ }
190
+
191
+ const basePath = readPath.slice(0, lastColonIndex);
192
+ if (!isReadableUrlPath(basePath)) {
193
+ return null;
194
+ }
195
+
196
+ try {
197
+ new URL(basePath.startsWith("http://") || basePath.startsWith("https://") ? basePath : `https://${basePath}`);
198
+ return { path: basePath, sel: candidateSelector };
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Normalize MIME type (lowercase, strip charset/params)
206
+ */
207
+ function normalizeMime(contentType: string): string {
208
+ return contentType.split(";")[0].trim().toLowerCase();
209
+ }
210
+
211
+ /**
212
+ * Get extension from URL or Content-Disposition
213
+ */
214
+ function getExtensionHint(url: string, contentDisposition?: string): string {
215
+ // Try Content-Disposition filename first
216
+ if (contentDisposition) {
217
+ const match = contentDisposition.match(/filename[*]?=["']?([^"';\n]+)/i);
218
+ if (match) {
219
+ const ext = path.extname(match[1]).toLowerCase();
220
+ if (ext) return ext;
221
+ }
222
+ }
223
+
224
+ // Fall back to URL path
225
+ try {
226
+ const pathname = new URL(url).pathname;
227
+ const ext = path.extname(pathname).toLowerCase();
228
+ if (ext) return ext;
229
+ } catch {}
230
+
231
+ return "";
232
+ }
233
+
234
+ /**
235
+ * Check if content type is convertible via markit.
236
+ */
237
+ function isConvertible(mime: string, extensionHint: string): boolean {
238
+ if (CONVERTIBLE_MIMES.has(mime)) return true;
239
+ if (mime === "application/octet-stream" && CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
240
+ if (CONVERTIBLE_EXTENSIONS.has(extensionHint)) return true;
241
+ return false;
242
+ }
243
+
244
+ function resolveImageMimeType(mime: string, extensionHint: string): string | null {
245
+ if (mime.startsWith("image/")) return mime;
246
+ const shouldUseExtensionHint =
247
+ mime.length === 0 || mime === "application/octet-stream" || mime === "binary/octet-stream" || mime === "unknown";
248
+ if (!shouldUseExtensionHint) return null;
249
+ return IMAGE_MIME_BY_EXTENSION.get(extensionHint) ?? null;
250
+ }
251
+
252
+ function isInlineImageMimeTypeSupported(mimeType: string): boolean {
253
+ return SUPPORTED_INLINE_IMAGE_MIME_TYPES.has(mimeType);
254
+ }
255
+
256
+ /**
257
+ * Check if content looks like HTML
258
+ */
259
+ function looksLikeHtml(content: string): boolean {
260
+ const trimmed = content.trim().toLowerCase();
261
+ return (
262
+ trimmed.startsWith("<!doctype") ||
263
+ trimmed.startsWith("<html") ||
264
+ trimmed.startsWith("<head") ||
265
+ trimmed.startsWith("<body")
266
+ );
267
+ }
268
+
269
+ /**
270
+ * Try fetching URL with .md appended (llms.txt convention)
271
+ */
272
+ async function tryMdSuffix(url: string, timeout: number, signal?: AbortSignal): Promise<string | null> {
273
+ const candidates: string[] = [];
274
+
275
+ try {
276
+ const parsed = new URL(url);
277
+ const pathname = parsed.pathname;
278
+
279
+ if (pathname.endsWith("/")) {
280
+ // /foo/bar/ -> /foo/bar/index.html.md
281
+ candidates.push(`${parsed.origin}${pathname}index.html.md`);
282
+ } else if (pathname.includes(".")) {
283
+ // /foo/bar.html -> /foo/bar.html.md
284
+ candidates.push(`${parsed.origin}${pathname}.md`);
285
+ } else {
286
+ // /foo/bar -> /foo/bar.md
287
+ candidates.push(`${parsed.origin}${pathname}.md`);
288
+ }
289
+ } catch {
290
+ return null;
291
+ }
292
+
293
+ if (signal?.aborted) {
294
+ return null;
295
+ }
296
+
297
+ for (const candidate of candidates) {
298
+ if (signal?.aborted) {
299
+ return null;
300
+ }
301
+ const result = await loadPage(candidate, { timeout, signal });
302
+ if (result.ok && result.content.trim().length > 100 && !looksLikeHtml(result.content)) {
303
+ return result.content;
304
+ }
305
+ }
306
+
307
+ return null;
308
+ }
309
+
310
+ /**
311
+ * Try to fetch LLM-friendly endpoints
312
+ */
313
+ async function tryLlmEndpoints(
314
+ url: string,
315
+ timeout: number,
316
+ signal?: AbortSignal,
317
+ ): Promise<{ content: string; endpoint: string } | null> {
318
+ const endpoints = buildLlmEndpointCandidates(url);
319
+
320
+ if (signal?.aborted || endpoints.length === 0) {
321
+ return null;
322
+ }
323
+
324
+ for (const endpoint of endpoints) {
325
+ if (signal?.aborted) {
326
+ return null;
327
+ }
328
+ const result = await loadPage(endpoint, { timeout: Math.min(timeout, 5), signal });
329
+ if (result.ok && result.content.trim().length > 100 && !looksLikeHtml(result.content)) {
330
+ return { content: result.content, endpoint };
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+
336
+ /**
337
+ * Try content negotiation for markdown/plain
338
+ */
339
+ async function tryContentNegotiation(
340
+ url: string,
341
+ timeout: number,
342
+ signal?: AbortSignal,
343
+ ): Promise<{ content: string; type: string } | null> {
344
+ if (signal?.aborted) {
345
+ return null;
346
+ }
347
+
348
+ const result = await loadPage(url, {
349
+ timeout,
350
+ headers: { Accept: "text/markdown, text/plain;q=0.9, text/html;q=0.8" },
351
+ signal,
352
+ });
353
+
354
+ if (!result.ok) return null;
355
+
356
+ const mime = normalizeMime(result.contentType);
357
+ if ((mime.includes("markdown") || mime === "text/plain") && !looksLikeHtml(result.content)) {
358
+ return { content: result.content, type: result.contentType };
359
+ }
360
+
361
+ return null;
362
+ }
363
+
364
+ /**
365
+ * Read a single HTML attribute from a tag string
366
+ */
367
+ function getHtmlAttribute(tag: string, attribute: string): string | null {
368
+ const pattern = new RegExp(`\\b${attribute}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`, "i");
369
+ const match = tag.match(pattern);
370
+ if (!match) return null;
371
+ return (match[1] ?? match[2] ?? match[3] ?? "").trim();
372
+ }
373
+
374
+ /**
375
+ * Extract bounded <head> markup to avoid expensive whole-page parsing
376
+ */
377
+ function extractHeadHtml(html: string): string {
378
+ const lower = html.toLowerCase();
379
+ const headStart = lower.indexOf("<head");
380
+ if (headStart === -1) {
381
+ return html.slice(0, 32 * 1024);
382
+ }
383
+
384
+ const headTagEnd = html.indexOf(">", headStart);
385
+ if (headTagEnd === -1) {
386
+ return html.slice(headStart, headStart + 32 * 1024);
387
+ }
388
+
389
+ const headEnd = lower.indexOf("</head>", headTagEnd + 1);
390
+ const fallbackEnd = Math.min(html.length, headTagEnd + 1 + 32 * 1024);
391
+ return html.slice(headStart, headEnd === -1 ? fallbackEnd : headEnd + 7);
392
+ }
393
+
394
+ /**
395
+ * Parse alternate links from HTML head
396
+ */
397
+ function parseAlternateLinks(html: string, pageUrl: string): string[] {
398
+ const links: string[] = [];
399
+
400
+ try {
401
+ const pagePath = new URL(pageUrl).pathname;
402
+ const headHtml = extractHeadHtml(html);
403
+ const linkTags = headHtml.match(/<link\b[^>]*>/gi) ?? [];
404
+
405
+ for (const tag of linkTags) {
406
+ const rel = getHtmlAttribute(tag, "rel")?.toLowerCase() ?? "";
407
+ const relTokens = rel.split(/\s+/).filter(Boolean);
408
+ if (!relTokens.includes("alternate")) continue;
409
+
410
+ const href = getHtmlAttribute(tag, "href");
411
+ const type = getHtmlAttribute(tag, "type")?.toLowerCase() ?? "";
412
+ if (!href) continue;
413
+
414
+ // Skip site-wide feeds
415
+ if (
416
+ href.includes("RecentChanges") ||
417
+ href.includes("Special:") ||
418
+ href.includes("/feed/") ||
419
+ href.includes("action=feed")
420
+ ) {
421
+ continue;
422
+ }
423
+
424
+ if (type.includes("markdown")) {
425
+ links.push(href);
426
+ } else if (
427
+ (type.includes("rss") || type.includes("atom") || type.includes("feed")) &&
428
+ (href.includes(pagePath) || href.includes("comments"))
429
+ ) {
430
+ links.push(href);
431
+ }
432
+ }
433
+ } catch {}
434
+
435
+ return links;
436
+ }
437
+
438
+ /**
439
+ * Extract document links from HTML (for PDF/DOCX wrapper pages)
440
+ */
441
+ function extractDocumentLinks(html: string, baseUrl: string): string[] {
442
+ const links: string[] = [];
443
+ const seen = new Set<string>();
444
+
445
+ try {
446
+ const anchorTags = html.slice(0, 512 * 1024).match(/<a\b[^>]*>/gi) ?? [];
447
+ for (const tag of anchorTags) {
448
+ const href = getHtmlAttribute(tag, "href");
449
+ if (!href) continue;
450
+
451
+ const ext = path.extname(href).toLowerCase();
452
+ if (!CONVERTIBLE_EXTENSIONS.has(ext)) continue;
453
+
454
+ const resolved = href.startsWith("http") ? href : new URL(href, baseUrl).href;
455
+ if (seen.has(resolved)) continue;
456
+ seen.add(resolved);
457
+ links.push(resolved);
458
+ if (links.length >= 20) break;
459
+ }
460
+ } catch {}
461
+
462
+ return links;
463
+ }
464
+
465
+ /**
466
+ * Strip CDATA wrapper and clean text
467
+ */
468
+ function cleanFeedText(text: string): string {
469
+ return text
470
+ .replace(/<!\[CDATA\[/g, "")
471
+ .replace(/\]\]>/g, "")
472
+ .replace(/&lt;/g, "<")
473
+ .replace(/&gt;/g, ">")
474
+ .replace(/&amp;/g, "&")
475
+ .replace(/&quot;/g, '"')
476
+ .replace(/<[^>]+>/g, "") // Strip HTML tags
477
+ .trim();
478
+ }
479
+
480
+ /**
481
+ * Parse RSS/Atom feed to markdown
482
+ */
483
+ function parseFeedToMarkdown(content: string, maxItems = 10): string {
484
+ try {
485
+ const doc = parseHTML(content).document;
486
+
487
+ // Try RSS
488
+ const channel = doc.querySelector("channel");
489
+ if (channel) {
490
+ const title = cleanFeedText(channel.querySelector("title")?.text || "RSS Feed");
491
+ const items = channel.querySelectorAll("item").slice(0, maxItems);
492
+
493
+ let md = `# ${title}\n\n`;
494
+ for (const item of items) {
495
+ const itemTitle = cleanFeedText(item.querySelector("title")?.text || "Untitled");
496
+ const link = cleanFeedText(item.querySelector("link")?.text || "");
497
+ const pubDate = cleanFeedText(item.querySelector("pubDate")?.text || "");
498
+ const desc = cleanFeedText(item.querySelector("description")?.text || "");
499
+
500
+ md += `## ${itemTitle}\n`;
501
+ if (pubDate) md += `*${pubDate}*\n\n`;
502
+ if (desc) md += `${desc.slice(0, 500)}${desc.length > 500 ? "..." : ""}\n\n`;
503
+ if (link) md += `[Read more](${link})\n\n`;
504
+ md += "---\n\n";
505
+ }
506
+ return md;
507
+ }
508
+
509
+ // Try Atom
510
+ const feed = doc.querySelector("feed");
511
+ if (feed) {
512
+ const title = cleanFeedText(feed.querySelector("title")?.text || "Atom Feed");
513
+ const entries = feed.querySelectorAll("entry").slice(0, maxItems);
514
+
515
+ let md = `# ${title}\n\n`;
516
+ for (const entry of entries) {
517
+ const entryTitle = cleanFeedText(entry.querySelector("title")?.text || "Untitled");
518
+ const link = entry.querySelector("link")?.getAttribute("href") || "";
519
+ const updated = cleanFeedText(entry.querySelector("updated")?.text || "");
520
+ const summary = cleanFeedText(
521
+ entry.querySelector("summary")?.text || entry.querySelector("content")?.text || "",
522
+ );
523
+
524
+ md += `## ${entryTitle}\n`;
525
+ if (updated) md += `*${updated}*\n\n`;
526
+ if (summary) md += `${summary.slice(0, 500)}${summary.length > 500 ? "..." : ""}\n\n`;
527
+ if (link) md += `[Read more](${link})\n\n`;
528
+ md += "---\n\n";
529
+ }
530
+ return md;
531
+ }
532
+ } catch {}
533
+
534
+ return content; // Fall back to raw content
535
+ }
536
+
537
+ /**
538
+ * Render HTML to markdown using Parallel, jina, trafilatura, lynx (in order of preference)
539
+ */
540
+ async function renderHtmlToText(
541
+ url: string,
542
+ html: string,
543
+ timeout: number,
544
+ settings: Settings,
545
+ userSignal?: AbortSignal,
546
+ ): Promise<{ content: string; ok: boolean; method: string }> {
547
+ const signal = ptree.combineSignals(userSignal, timeout * 1000);
548
+ const execOptions = {
549
+ mode: "group" as const,
550
+ allowNonZero: true,
551
+ allowAbort: true,
552
+ stderr: "full" as const,
553
+ signal,
554
+ };
555
+
556
+ // Try Parallel extract first when credentials are configured
557
+ if (settings.get("providers.parallelFetch") && (await findParallelApiKey())) {
558
+ try {
559
+ const parallelResult = await extractWithParallel([url], {
560
+ objective: "Extract the main content",
561
+ excerpts: true,
562
+ fullContent: false,
563
+ signal,
564
+ });
565
+ const firstDocument = parallelResult.results[0];
566
+ if (firstDocument) {
567
+ const content = getParallelExtractContent(firstDocument);
568
+ if (content.trim().length > 100 && !isLowQualityOutput(content)) {
569
+ return { content, ok: true, method: "parallel" };
570
+ }
571
+ }
572
+ } catch {
573
+ // Parallel extract failed, continue to next method
574
+ signal?.throwIfAborted();
575
+ }
576
+ }
577
+
578
+ // Try jina first (reader API)
579
+ try {
580
+ const jinaUrl = `https://r.jina.ai/${url}`;
581
+ const response = await fetch(jinaUrl, {
582
+ headers: { Accept: "text/markdown" },
583
+ signal,
584
+ });
585
+ if (response.ok) {
586
+ const content = await response.text();
587
+ if (content.trim().length > 100 && !isLowQualityOutput(content)) {
588
+ return { content, ok: true, method: "jina" };
589
+ }
590
+ }
591
+ } catch {
592
+ // Jina failed, continue to next method
593
+ signal?.throwIfAborted();
594
+ }
595
+
596
+ // Try trafilatura (auto-install via uv/pip)
597
+ const trafilatura = await ensureTool("trafilatura", { signal, silent: true });
598
+ if (trafilatura) {
599
+ const result = await ptree.exec([trafilatura, "-u", url, "--output-format", "markdown"], execOptions);
600
+ if (result.ok && result.stdout.trim().length > 100) {
601
+ return { content: result.stdout, ok: true, method: "trafilatura" };
602
+ }
603
+ }
604
+
605
+ // Try lynx (can't auto-install, system package)
606
+ const lynx = hasCommand("lynx");
607
+ if (lynx) {
608
+ const result = await ptree.exec(["lynx", "-dump", "-nolist", "-width", "250", url], execOptions);
609
+ if (result.ok) {
610
+ return { content: result.stdout, ok: true, method: "lynx" };
611
+ }
612
+ }
613
+
614
+ // Fall back to native converter (fastest, no network/subprocess)
615
+ try {
616
+ const content = await htmlToMarkdown(html, { cleanContent: true });
617
+ if (content.trim().length > 100 && !isLowQualityOutput(content)) {
618
+ return { content, ok: true, method: "native" };
619
+ }
620
+ } catch {
621
+ // Native converter failed, continue to next method
622
+ signal?.throwIfAborted();
623
+ }
624
+ return { content: "", ok: false, method: "none" };
625
+ }
626
+
627
+ /**
628
+ * Check if lynx output looks JS-gated or mostly navigation
629
+ */
630
+ function isLowQualityOutput(content: string): boolean {
631
+ const lower = content.toLowerCase();
632
+
633
+ // JS-gated indicators
634
+ const jsGated = [
635
+ "enable javascript",
636
+ "javascript required",
637
+ "turn on javascript",
638
+ "please enable javascript",
639
+ "browser not supported",
640
+ ];
641
+ if (content.length < 1024 && jsGated.some(t => lower.includes(t))) {
642
+ return true;
643
+ }
644
+
645
+ // Mostly navigation (high link/menu density)
646
+ const lines = content.split("\n").filter(l => l.trim());
647
+ const shortLines = lines.filter(l => l.trim().length < 40);
648
+ if (lines.length > 10 && shortLines.length / lines.length > 0.7) {
649
+ return true;
650
+ }
651
+
652
+ return false;
653
+ }
654
+
655
+ /**
656
+ * Format JSON
657
+ */
658
+ function formatJson(content: string): string {
659
+ try {
660
+ return JSON.stringify(JSON.parse(content), null, 2);
661
+ } catch {
662
+ return content;
663
+ }
664
+ }
665
+
666
+ interface FetchImagePayload {
667
+ data: string;
668
+ mimeType: string;
669
+ }
670
+
671
+ type FetchRenderResult = RenderResult & {
672
+ image?: FetchImagePayload;
673
+ };
674
+
675
+ // =============================================================================
676
+ // Unified Special Handler Dispatch
677
+ // =============================================================================
678
+
679
+ /**
680
+ * Try all special handlers
681
+ */
682
+ async function handleSpecialUrls(
683
+ url: string,
684
+ timeout: number,
685
+ signal?: AbortSignal,
686
+ ): Promise<FetchRenderResult | null> {
687
+ for (const handler of specialHandlers) {
688
+ if (signal?.aborted) {
689
+ throw new ToolAbortError();
690
+ }
691
+ const result = await handler(url, timeout, signal);
692
+ if (result) return result;
693
+ }
694
+ return null;
695
+ }
696
+
697
+ // =============================================================================
698
+ // Main Render Function
699
+ // =============================================================================
700
+
701
+ /**
702
+ * Main render function implementing the full pipeline
703
+ */
704
+ async function renderUrl(
705
+ url: string,
706
+ timeout: number,
707
+ raw: boolean,
708
+ settings: Settings,
709
+ signal?: AbortSignal,
710
+ ): Promise<FetchRenderResult> {
711
+ const notes: string[] = [];
712
+ const fetchedAt = new Date().toISOString();
713
+ if (signal?.aborted) {
714
+ throw new ToolAbortError();
715
+ }
716
+
717
+ // Handle internal protocol URLs (e.g., pi-internal://) - return empty
718
+ if (url.startsWith("pi-internal://")) {
719
+ return {
720
+ url,
721
+ finalUrl: url,
722
+ contentType: "text/plain",
723
+ method: "internal",
724
+ content: "",
725
+ fetchedAt,
726
+ truncated: false,
727
+ notes: ["Internal protocol URL - no external content"],
728
+ };
729
+ }
730
+
731
+ // Step 0: Normalize URL (ensure scheme for special handlers)
732
+ url = normalizeUrl(url);
733
+
734
+ // Step 1: Try special handlers for known sites (unless raw mode)
735
+ if (!raw) {
736
+ const specialResult = await handleSpecialUrls(url, timeout, signal);
737
+ if (specialResult) return specialResult;
738
+ }
739
+
740
+ // Step 2: Fetch page
741
+ const response = await loadPage(url, { timeout, signal });
742
+ if (signal?.aborted) {
743
+ throw new ToolAbortError();
744
+ }
745
+ if (!response.ok) {
746
+ return {
747
+ url,
748
+ finalUrl: response.finalUrl || url,
749
+ contentType: response.contentType || "unknown",
750
+ method: "failed",
751
+ content: "",
752
+ fetchedAt,
753
+ truncated: false,
754
+ notes: [response.status ? `Failed to fetch URL (HTTP ${response.status})` : "Failed to fetch URL"],
755
+ };
756
+ }
757
+
758
+ const { finalUrl, content: rawContent } = response;
759
+ const mime = normalizeMime(response.contentType);
760
+ const extHint = getExtensionHint(finalUrl);
761
+
762
+ const imageMimeType = resolveImageMimeType(mime, extHint);
763
+ let skipConvertibleBinaryRetry = false;
764
+ if (imageMimeType) {
765
+ if (!isInlineImageMimeTypeSupported(imageMimeType)) {
766
+ notes.push(
767
+ `Image MIME type ${imageMimeType} is unsupported for inline model serialization; returning text metadata only`,
768
+ );
769
+ const shouldTryConvertibleFallback = isConvertible(mime, extHint);
770
+ if (shouldTryConvertibleFallback) {
771
+ notes.push("Attempting binary conversion fallback for unsupported image MIME type");
772
+ } else {
773
+ notes.push("Falling back to textual rendering from initial response");
774
+ }
775
+ skipConvertibleBinaryRetry = !shouldTryConvertibleFallback;
776
+ } else {
777
+ const binary = await fetchBinary(finalUrl, timeout, signal);
778
+ if (binary.ok) {
779
+ notes.push("Fetched image binary");
780
+ const conversionExtension = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
781
+ let convertedText: string | null = null;
782
+ const converted = await convertWithMarkit(binary.buffer, conversionExtension, timeout, signal);
783
+ if (converted.ok) {
784
+ if (converted.content.trim().length > 50) {
785
+ notes.push("Converted with markit");
786
+ convertedText = converted.content;
787
+ } else {
788
+ notes.push("markit conversion produced no usable output");
789
+ }
790
+ } else if (converted.error) {
791
+ notes.push(`markit conversion failed: ${converted.error}`);
792
+ } else {
793
+ notes.push("markit conversion failed");
794
+ }
795
+
796
+ if (binary.buffer.byteLength > MAX_INLINE_IMAGE_SOURCE_BYTES) {
797
+ notes.push(
798
+ `Image exceeds inline source limit (${binary.buffer.byteLength} bytes > ${MAX_INLINE_IMAGE_SOURCE_BYTES} bytes)`,
799
+ );
800
+ const output = finalizeOutput(
801
+ convertedText ?? `Fetched image content (${imageMimeType}), but it is too large to inline render.`,
802
+ );
803
+ return {
804
+ url,
805
+ finalUrl,
806
+ contentType: imageMimeType,
807
+ method: convertedText ? "markit" : "image-too-large",
808
+ content: output.content,
809
+ fetchedAt,
810
+ truncated: output.truncated,
811
+ notes,
812
+ };
813
+ }
814
+
815
+ const resized = await resizeImage(
816
+ { type: "image", data: Buffer.from(binary.buffer).toBase64(), mimeType: imageMimeType },
817
+ { maxBytes: MAX_INLINE_IMAGE_OUTPUT_BYTES },
818
+ );
819
+ const isDecodedImage =
820
+ resized.originalWidth > 0 && resized.originalHeight > 0 && resized.width > 0 && resized.height > 0;
821
+ if (!isDecodedImage) {
822
+ notes.push(`Fetched payload could not be decoded as ${imageMimeType}; returning text metadata only`);
823
+ const output = finalizeOutput(
824
+ convertedText ??
825
+ rawContent ??
826
+ `Fetched payload was labeled ${imageMimeType}, but bytes were not a valid image.`,
827
+ );
828
+ return {
829
+ url,
830
+ finalUrl,
831
+ contentType: imageMimeType,
832
+ method: convertedText ? "markit" : "image-invalid",
833
+ content: output.content,
834
+ fetchedAt,
835
+ truncated: output.truncated,
836
+ notes,
837
+ };
838
+ }
839
+ if (resized.buffer.length > MAX_INLINE_IMAGE_OUTPUT_BYTES) {
840
+ notes.push(
841
+ `Image exceeds inline output limit after resize (${resized.buffer.length} bytes > ${MAX_INLINE_IMAGE_OUTPUT_BYTES} bytes)`,
842
+ );
843
+ const output = finalizeOutput(
844
+ convertedText ?? `Fetched image content (${imageMimeType}), but it is too large to inline render.`,
845
+ );
846
+ return {
847
+ url,
848
+ finalUrl,
849
+ contentType: imageMimeType,
850
+ method: convertedText ? "markit" : "image-too-large",
851
+ content: output.content,
852
+ fetchedAt,
853
+ truncated: output.truncated,
854
+ notes,
855
+ };
856
+ }
857
+
858
+ const dimensionNote = formatDimensionNote(resized);
859
+ let imageSummary = convertedText ?? `Fetched image content (${resized.mimeType}).`;
860
+ if (dimensionNote) {
861
+ imageSummary += `\n${dimensionNote}`;
862
+ }
863
+ const output = finalizeOutput(imageSummary);
864
+ return {
865
+ url,
866
+ finalUrl,
867
+ contentType: resized.mimeType,
868
+ method: "image",
869
+ content: output.content,
870
+ fetchedAt,
871
+ truncated: output.truncated,
872
+ notes,
873
+ image: {
874
+ data: resized.data,
875
+ mimeType: resized.mimeType,
876
+ },
877
+ };
878
+ }
879
+ notes.push(binary.error ? `Binary fetch failed: ${binary.error}` : "Binary fetch failed");
880
+ notes.push("Falling back to textual rendering from initial response");
881
+ skipConvertibleBinaryRetry = true;
882
+ }
883
+ }
884
+
885
+ // Step 3: Handle convertible binary files (PDF, DOCX, etc.)
886
+ if (!skipConvertibleBinaryRetry && isConvertible(mime, extHint)) {
887
+ const binary = await fetchBinary(finalUrl, timeout, signal);
888
+ if (binary.ok) {
889
+ const ext = getExtensionHint(finalUrl, binary.contentDisposition) || extHint;
890
+ const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
891
+ if (converted.ok) {
892
+ if (converted.content.trim().length > 50) {
893
+ notes.push("Converted with markit");
894
+ const output = finalizeOutput(converted.content);
895
+ return {
896
+ url,
897
+ finalUrl,
898
+ contentType: mime,
899
+ method: "markit",
900
+ content: output.content,
901
+ fetchedAt,
902
+ truncated: output.truncated,
903
+ notes,
904
+ };
905
+ }
906
+ notes.push("markit conversion produced no usable output");
907
+ } else if (converted.error) {
908
+ notes.push(`markit conversion failed: ${converted.error}`);
909
+ } else {
910
+ notes.push("markit conversion failed");
911
+ }
912
+ } else if (binary.error) {
913
+ notes.push(`Binary fetch failed: ${binary.error}`);
914
+ } else {
915
+ notes.push("Binary fetch failed");
916
+ }
917
+ }
918
+
919
+ // Step 4: Handle non-HTML text content
920
+ const isHtml = mime.includes("html") || mime.includes("xhtml");
921
+ const isJson = mime.includes("json");
922
+ const isXml = mime.includes("xml") && !isHtml;
923
+ const isText = mime.includes("text/plain") || mime.includes("text/markdown");
924
+ const isFeed = mime.includes("rss") || mime.includes("atom") || mime.includes("feed");
925
+
926
+ if (isJson) {
927
+ const output = finalizeOutput(formatJson(rawContent));
928
+ return {
929
+ url,
930
+ finalUrl,
931
+ contentType: mime,
932
+ method: "json",
933
+ content: output.content,
934
+ fetchedAt,
935
+ truncated: output.truncated,
936
+ notes,
937
+ };
938
+ }
939
+
940
+ if (isFeed || (isXml && (rawContent.includes("<rss") || rawContent.includes("<feed")))) {
941
+ const parsed = parseFeedToMarkdown(rawContent);
942
+ const output = finalizeOutput(parsed);
943
+ return {
944
+ url,
945
+ finalUrl,
946
+ contentType: mime,
947
+ method: "feed",
948
+ content: output.content,
949
+ fetchedAt,
950
+ truncated: output.truncated,
951
+ notes,
952
+ };
953
+ }
954
+
955
+ if (isText && !looksLikeHtml(rawContent)) {
956
+ const output = finalizeOutput(rawContent);
957
+ return {
958
+ url,
959
+ finalUrl,
960
+ contentType: mime,
961
+ method: "text",
962
+ content: output.content,
963
+ fetchedAt,
964
+ truncated: output.truncated,
965
+ notes,
966
+ };
967
+ }
968
+
969
+ // Step 5: For HTML, try digestible formats first (unless raw mode)
970
+ if (isHtml && !raw) {
971
+ // 5A: Check for page-specific markdown alternate
972
+ const alternates = parseAlternateLinks(rawContent, finalUrl);
973
+ const markdownAlt = alternates.find(alt => alt.endsWith(".md") || alt.includes("markdown"));
974
+ if (markdownAlt) {
975
+ const resolved = markdownAlt.startsWith("http") ? markdownAlt : new URL(markdownAlt, finalUrl).href;
976
+ const altResult = await loadPage(resolved, { timeout, signal });
977
+ if (altResult.ok && altResult.content.trim().length > 100 && !looksLikeHtml(altResult.content)) {
978
+ notes.push(`Used markdown alternate: ${resolved}`);
979
+ const output = finalizeOutput(altResult.content);
980
+ return {
981
+ url,
982
+ finalUrl,
983
+ contentType: "text/markdown",
984
+ method: "alternate-markdown",
985
+ content: output.content,
986
+ fetchedAt,
987
+ truncated: output.truncated,
988
+ notes,
989
+ };
990
+ }
991
+ }
992
+
993
+ // 5B: Try URL.md suffix (llms.txt convention)
994
+ const mdSuffix = await tryMdSuffix(finalUrl, timeout, signal);
995
+ if (mdSuffix) {
996
+ notes.push("Found .md suffix version");
997
+ const output = finalizeOutput(mdSuffix);
998
+ return {
999
+ url,
1000
+ finalUrl,
1001
+ contentType: "text/markdown",
1002
+ method: "md-suffix",
1003
+ content: output.content,
1004
+ fetchedAt,
1005
+ truncated: output.truncated,
1006
+ notes,
1007
+ };
1008
+ }
1009
+
1010
+ // 5C: Content negotiation
1011
+ const negotiated = await tryContentNegotiation(url, timeout, signal);
1012
+ if (negotiated) {
1013
+ notes.push(`Content negotiation returned ${negotiated.type}`);
1014
+ const output = finalizeOutput(negotiated.content);
1015
+ return {
1016
+ url,
1017
+ finalUrl,
1018
+ contentType: normalizeMime(negotiated.type),
1019
+ method: "content-negotiation",
1020
+ content: output.content,
1021
+ fetchedAt,
1022
+ truncated: output.truncated,
1023
+ notes,
1024
+ };
1025
+ }
1026
+
1027
+ // 5D: Check for feed alternates
1028
+ const feedAlternates = alternates.filter(alt => !alt.endsWith(".md") && !alt.includes("markdown"));
1029
+ for (const altUrl of feedAlternates.slice(0, 2)) {
1030
+ const resolved = altUrl.startsWith("http") ? altUrl : new URL(altUrl, finalUrl).href;
1031
+ const altResult = await loadPage(resolved, { timeout, signal });
1032
+ if (altResult.ok && altResult.content.trim().length > 200) {
1033
+ notes.push(`Used feed alternate: ${resolved}`);
1034
+ const parsed = parseFeedToMarkdown(altResult.content);
1035
+ const output = finalizeOutput(parsed);
1036
+ return {
1037
+ url,
1038
+ finalUrl,
1039
+ contentType: "application/feed",
1040
+ method: "alternate-feed",
1041
+ content: output.content,
1042
+ fetchedAt,
1043
+ truncated: output.truncated,
1044
+ notes,
1045
+ };
1046
+ }
1047
+ }
1048
+
1049
+ if (signal?.aborted) {
1050
+ throw new ToolAbortError();
1051
+ }
1052
+
1053
+ // 5E: Render HTML with lynx or html2text
1054
+ const htmlResult = await renderHtmlToText(finalUrl, rawContent, timeout, settings, signal);
1055
+ if (!htmlResult.ok) {
1056
+ notes.push("html rendering failed (lynx/html2text unavailable)");
1057
+ const output = finalizeOutput(rawContent);
1058
+ return {
1059
+ url,
1060
+ finalUrl,
1061
+ contentType: mime,
1062
+ method: "raw-html",
1063
+ content: output.content,
1064
+ fetchedAt,
1065
+ truncated: output.truncated,
1066
+ notes,
1067
+ };
1068
+ }
1069
+
1070
+ // Step 6: If rendered output is low quality, try more targeted fallbacks
1071
+ if (isLowQualityOutput(htmlResult.content)) {
1072
+ const docLinks = extractDocumentLinks(rawContent, finalUrl);
1073
+ if (docLinks.length > 0) {
1074
+ const docUrl = docLinks[0];
1075
+ const binary = await fetchBinary(docUrl, timeout, signal);
1076
+ if (binary.ok) {
1077
+ const ext = getExtensionHint(docUrl, binary.contentDisposition);
1078
+ const converted = await convertWithMarkit(binary.buffer, ext, timeout, signal);
1079
+ if (converted.ok && converted.content.trim().length > htmlResult.content.length) {
1080
+ notes.push(`Extracted and converted document: ${docUrl}`);
1081
+ const output = finalizeOutput(converted.content);
1082
+ return {
1083
+ url,
1084
+ finalUrl,
1085
+ contentType: "application/document",
1086
+ method: "extracted-document",
1087
+ content: output.content,
1088
+ fetchedAt,
1089
+ truncated: output.truncated,
1090
+ notes,
1091
+ };
1092
+ }
1093
+ if (!converted.ok && converted.error) {
1094
+ notes.push(`markit conversion failed: ${converted.error}`);
1095
+ }
1096
+ } else if (binary.error) {
1097
+ notes.push(`Binary fetch failed: ${binary.error}`);
1098
+ }
1099
+ }
1100
+
1101
+ const llmResult = await tryLlmEndpoints(finalUrl, timeout, signal);
1102
+ if (llmResult) {
1103
+ notes.push(`Used llms.txt fallback: ${llmResult.endpoint}`);
1104
+ const output = finalizeOutput(llmResult.content);
1105
+ return {
1106
+ url,
1107
+ finalUrl,
1108
+ contentType: "text/plain",
1109
+ method: "llms.txt",
1110
+ content: output.content,
1111
+ fetchedAt,
1112
+ truncated: output.truncated,
1113
+ notes,
1114
+ };
1115
+ }
1116
+
1117
+ notes.push("Page appears to require JavaScript or is mostly navigation");
1118
+ }
1119
+
1120
+ const output = finalizeOutput(htmlResult.content);
1121
+ return {
1122
+ url,
1123
+ finalUrl,
1124
+ contentType: mime,
1125
+ method: htmlResult.method,
1126
+ content: output.content,
1127
+ fetchedAt,
1128
+ truncated: output.truncated,
1129
+ notes,
1130
+ };
1131
+ }
1132
+
1133
+ // Fallback: return raw content
1134
+ const output = finalizeOutput(rawContent);
1135
+ return {
1136
+ url,
1137
+ finalUrl,
1138
+ contentType: mime,
1139
+ method: "raw",
1140
+ content: output.content,
1141
+ fetchedAt,
1142
+ truncated: output.truncated,
1143
+ notes,
1144
+ };
1145
+ }
1146
+
1147
+ // =============================================================================
1148
+ // Tool Definition
1149
+ // =============================================================================
1150
+
1151
+ export interface ReadUrlToolDetails {
1152
+ kind: "url";
1153
+ url: string;
1154
+ finalUrl: string;
1155
+ contentType: string;
1156
+ method: string;
1157
+ truncated: boolean;
1158
+ notes: string[];
1159
+ meta?: OutputMeta;
1160
+ }
1161
+
1162
+ interface ReadUrlCacheEntry {
1163
+ artifactId?: string;
1164
+ details: ReadUrlToolDetails;
1165
+ image?: FetchImagePayload;
1166
+ output: string;
1167
+ }
1168
+
1169
+ const readUrlCache = new Map<string, ReadUrlCacheEntry>();
1170
+
1171
+ function getReadUrlCacheKey(session: ToolSession, requestedUrl: string, raw: boolean): string {
1172
+ const scope = session.getSessionFile() ?? session.cwd;
1173
+ return `${scope}::${raw ? "raw" : "rendered"}::${normalizeUrl(requestedUrl)}`;
1174
+ }
1175
+
1176
+ async function readArtifactOutput(session: ToolSession, artifactId: string): Promise<string | null> {
1177
+ const artifactsDir = session.getArtifactsDir?.();
1178
+ if (!artifactsDir) return null;
1179
+
1180
+ try {
1181
+ const files = await fs.readdir(artifactsDir);
1182
+ const match = files.find(file => file.startsWith(`${artifactId}.`));
1183
+ if (!match) return null;
1184
+ return await Bun.file(path.join(artifactsDir, match)).text();
1185
+ } catch {
1186
+ return null;
1187
+ }
1188
+ }
1189
+
1190
+ async function materializeReadUrlCacheEntry(
1191
+ session: ToolSession,
1192
+ entry: ReadUrlCacheEntry,
1193
+ ): Promise<ReadUrlCacheEntry | null> {
1194
+ if (entry.artifactId) {
1195
+ const artifactOutput = await readArtifactOutput(session, entry.artifactId);
1196
+ if (artifactOutput !== null) {
1197
+ return { ...entry, output: artifactOutput };
1198
+ }
1199
+ }
1200
+
1201
+ return entry.output.length > 0 ? entry : null;
1202
+ }
1203
+
1204
+ async function persistReadUrlArtifact(session: ToolSession, output: string): Promise<string | undefined> {
1205
+ const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
1206
+ if (!artifactPath) return undefined;
1207
+ await Bun.write(artifactPath, output);
1208
+ return id;
1209
+ }
1210
+
1211
+ async function ensureReadUrlCacheArtifact(session: ToolSession, entry: ReadUrlCacheEntry): Promise<ReadUrlCacheEntry> {
1212
+ if (entry.artifactId) return entry;
1213
+ const artifactId = await persistReadUrlArtifact(session, entry.output);
1214
+ return artifactId ? { ...entry, artifactId } : entry;
1215
+ }
1216
+
1217
+ function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: boolean, entry: ReadUrlCacheEntry): void {
1218
+ readUrlCache.set(getReadUrlCacheKey(session, requestedUrl, raw), entry);
1219
+ readUrlCache.set(getReadUrlCacheKey(session, entry.details.finalUrl, raw), entry);
1220
+ }
1221
+
1222
+ async function buildReadUrlCacheEntry(
1223
+ session: ToolSession,
1224
+ params: { path: string; timeout?: number; raw?: boolean },
1225
+ signal?: AbortSignal,
1226
+ options?: { ensureArtifact?: boolean },
1227
+ ): Promise<ReadUrlCacheEntry> {
1228
+ const { path: url, timeout: rawTimeout = 20, raw = false } = params;
1229
+
1230
+ const effectiveTimeout = clampTimeout("fetch", rawTimeout);
1231
+
1232
+ if (signal?.aborted) {
1233
+ throw new ToolAbortError();
1234
+ }
1235
+
1236
+ const result = await renderUrl(url, effectiveTimeout, raw, session.settings, signal);
1237
+ const output = buildUrlReadOutput(result, result.content);
1238
+ const artifactId = options?.ensureArtifact ? await persistReadUrlArtifact(session, output) : undefined;
1239
+
1240
+ return {
1241
+ artifactId,
1242
+ details: {
1243
+ kind: "url",
1244
+ url: result.url,
1245
+ finalUrl: result.finalUrl,
1246
+ contentType: result.contentType,
1247
+ method: result.method,
1248
+ truncated: Boolean(result.truncated),
1249
+ notes: result.notes,
1250
+ },
1251
+ image: result.image,
1252
+ output,
1253
+ };
1254
+ }
1255
+
1256
+ export async function loadReadUrlCacheEntry(
1257
+ session: ToolSession,
1258
+ params: { path: string; timeout?: number; raw?: boolean },
1259
+ signal?: AbortSignal,
1260
+ options?: { ensureArtifact?: boolean; preferCached?: boolean },
1261
+ ): Promise<ReadUrlCacheEntry> {
1262
+ const raw = params.raw ?? false;
1263
+ const cached = readUrlCache.get(getReadUrlCacheKey(session, params.path, raw));
1264
+ if (options?.preferCached && cached) {
1265
+ const prepared = options.ensureArtifact ? await ensureReadUrlCacheArtifact(session, cached) : cached;
1266
+ const materialized = await materializeReadUrlCacheEntry(session, prepared);
1267
+ if (materialized) {
1268
+ cacheReadUrlEntry(session, params.path, raw, materialized);
1269
+ return materialized;
1270
+ }
1271
+ }
1272
+
1273
+ const fresh = await buildReadUrlCacheEntry(session, params, signal, {
1274
+ ensureArtifact: options?.ensureArtifact,
1275
+ });
1276
+ cacheReadUrlEntry(session, params.path, raw, fresh);
1277
+ return fresh;
1278
+ }
1279
+
1280
+ function buildUrlReadOutput(result: FetchRenderResult, content: string): string {
1281
+ let output = "";
1282
+ output += `URL: ${result.finalUrl}\n`;
1283
+ output += `Content-Type: ${result.contentType}\n`;
1284
+ output += `Method: ${result.method}\n`;
1285
+ if (result.notes.length > 0) {
1286
+ output += `Notes: ${result.notes.join("; ")}\n`;
1287
+ }
1288
+ output += `\n---\n\n`;
1289
+ output += content;
1290
+ return output;
1291
+ }
1292
+
1293
+ export async function executeReadUrl(
1294
+ session: ToolSession,
1295
+ params: { path: string; timeout?: number; raw?: boolean },
1296
+ signal?: AbortSignal,
1297
+ ): Promise<AgentToolResult<ReadUrlToolDetails>> {
1298
+ let cacheEntry = await loadReadUrlCacheEntry(session, params, signal, { preferCached: true });
1299
+ const truncation = truncateHead(cacheEntry.output, {
1300
+ maxBytes: DEFAULT_MAX_BYTES,
1301
+ maxLines: FETCH_DEFAULT_MAX_LINES,
1302
+ });
1303
+ const needsArtifact = truncation.truncated;
1304
+ if (needsArtifact && !cacheEntry.artifactId) {
1305
+ cacheEntry = await ensureReadUrlCacheArtifact(session, cacheEntry);
1306
+ cacheReadUrlEntry(session, params.path, params.raw ?? false, cacheEntry);
1307
+ }
1308
+ const output = needsArtifact ? truncation.content : cacheEntry.output;
1309
+ const details: ReadUrlToolDetails = {
1310
+ ...cacheEntry.details,
1311
+ truncated: Boolean(cacheEntry.details.truncated || needsArtifact),
1312
+ };
1313
+
1314
+ const contentBlocks: Array<TextContent | ImageContent> = [{ type: "text", text: output }];
1315
+ if (cacheEntry.image) {
1316
+ contentBlocks.push({ type: "image", data: cacheEntry.image.data, mimeType: cacheEntry.image.mimeType });
1317
+ }
1318
+
1319
+ const resultBuilder = toolResult(details).content(contentBlocks).sourceUrl(details.finalUrl);
1320
+ if (needsArtifact) {
1321
+ resultBuilder.truncation(truncation, { direction: "head", artifactId: cacheEntry.artifactId });
1322
+ } else if (cacheEntry.details.truncated) {
1323
+ const outputLines = cacheEntry.output.split("\n").length;
1324
+ const outputBytes = Buffer.byteLength(cacheEntry.output, "utf-8");
1325
+ const totalBytes = Math.max(outputBytes + 1, MAX_OUTPUT_CHARS + 1);
1326
+ const totalLines = outputLines + 1;
1327
+ resultBuilder.truncationFromText(cacheEntry.output, {
1328
+ direction: "tail",
1329
+ totalLines,
1330
+ totalBytes,
1331
+ maxBytes: MAX_OUTPUT_CHARS,
1332
+ });
1333
+ }
1334
+
1335
+ return resultBuilder.done();
1336
+ }
1337
+
1338
+ // =============================================================================
1339
+ // TUI Rendering
1340
+ // =============================================================================
1341
+
1342
+ /** Count non-empty lines */
1343
+ function countNonEmptyLines(text: string): number {
1344
+ return text.split("\n").filter(l => l.trim()).length;
1345
+ }
1346
+
1347
+ /** Render URL read call (URL preview) */
1348
+ export function renderReadUrlCall(
1349
+ args: { path?: string; url?: string; timeout?: number; raw?: boolean },
1350
+ _options: RenderResultOptions,
1351
+ uiTheme: Theme = theme,
1352
+ ): Component {
1353
+ const url = args.path ?? args.url ?? "";
1354
+ const domain = getDomain(url);
1355
+ const path = truncate(url.replace(/^https?:\/\/[^/]+/, ""), 50, "\u2026");
1356
+ const description = `${domain}${path ? ` ${path}` : ""}`.trim();
1357
+ const meta: string[] = [];
1358
+ if (args.raw) meta.push("raw");
1359
+ if (args.timeout !== undefined) meta.push(`timeout:${args.timeout}s`);
1360
+ const text = renderStatusLine({ icon: "pending", title: "Read", description, meta }, uiTheme);
1361
+ return new Text(text, 0, 0);
1362
+ }
1363
+
1364
+ /** Render URL read result with tree-based layout */
1365
+ export function renderReadUrlResult(
1366
+ result: { content: Array<{ type: string; text?: string }>; details?: ReadUrlToolDetails },
1367
+ options: RenderResultOptions,
1368
+ uiTheme: Theme = theme,
1369
+ ): Component {
1370
+ const details = result.details;
1371
+
1372
+ if (!details) {
1373
+ return new Text(uiTheme.fg("error", "No response data"), 0, 0);
1374
+ }
1375
+
1376
+ const domain = getDomain(details.finalUrl);
1377
+ const path = truncate(details.finalUrl.replace(/^https?:\/\/[^/]+/, ""), 50, "…");
1378
+ const hasRedirect = details.url !== details.finalUrl;
1379
+ const hasNotes = details.notes.length > 0;
1380
+ const truncation = details.meta?.truncation;
1381
+ const truncated = Boolean(details.truncated || truncation);
1382
+
1383
+ const header = renderStatusLine(
1384
+ {
1385
+ title: "Read",
1386
+ description: `${domain}${path ? ` ${path}` : ""}`,
1387
+ },
1388
+ uiTheme,
1389
+ );
1390
+
1391
+ const contentText = result.content[0]?.text ?? "";
1392
+ const contentBody = contentText.includes("---\n\n")
1393
+ ? contentText.split("---\n\n").slice(1).join("---\n\n")
1394
+ : contentText;
1395
+ const lineCount = countNonEmptyLines(contentBody);
1396
+ const charCount = contentBody.trim().length;
1397
+ const contentLines = contentBody.split("\n").filter(l => l.trim());
1398
+
1399
+ const metadataLines: string[] = [
1400
+ `${uiTheme.fg("muted", "Content-Type:")} ${details.contentType || "unknown"}`,
1401
+ `${uiTheme.fg("muted", "Method:")} ${details.method}`,
1402
+ ];
1403
+ if (hasRedirect) {
1404
+ metadataLines.push(`${uiTheme.fg("muted", "Final URL:")} ${uiTheme.fg("mdLinkUrl", details.finalUrl)}`);
1405
+ }
1406
+ const lineLabel = `${lineCount} line${lineCount === 1 ? "" : "s"}`;
1407
+ metadataLines.push(`${uiTheme.fg("muted", "Lines:")} ${lineLabel}`);
1408
+ metadataLines.push(`${uiTheme.fg("muted", "Chars:")} ${charCount}`);
1409
+ if (truncated) {
1410
+ metadataLines.push(uiTheme.fg("warning", `${uiTheme.status.warning} Output truncated`));
1411
+ if (truncation?.artifactId) metadataLines.push(formatStyledArtifactReference(truncation.artifactId, uiTheme));
1412
+ }
1413
+ if (hasNotes) {
1414
+ metadataLines.push(`${uiTheme.fg("muted", "Notes:")} ${details.notes.join("; ")}`);
1415
+ }
1416
+
1417
+ const outputBlock = new CachedOutputBlock();
1418
+ let lastExpanded: boolean | undefined;
1419
+ let contentPreviewLines: string[] | undefined;
1420
+
1421
+ return {
1422
+ render: (width: number) => {
1423
+ const { expanded } = options;
1424
+
1425
+ if (contentPreviewLines === undefined || lastExpanded !== expanded) {
1426
+ const previewLimit = expanded ? 12 : 3;
1427
+ const previewList = applyListLimit(contentLines, { headLimit: previewLimit });
1428
+ const previewLines = previewList.items.map(line => line.trimEnd());
1429
+ const remaining = Math.max(0, contentLines.length - previewList.items.length);
1430
+ contentPreviewLines =
1431
+ previewLines.length > 0
1432
+ ? previewLines.map(line => uiTheme.fg("dim", line))
1433
+ : [uiTheme.fg("dim", "(no content)")];
1434
+ if (remaining > 0) {
1435
+ const hint = formatExpandHint(uiTheme, expanded, true);
1436
+ contentPreviewLines.push(uiTheme.fg("muted", `… ${remaining} more lines${hint ? ` ${hint}` : ""}`));
1437
+ }
1438
+ lastExpanded = expanded;
1439
+ outputBlock.invalidate();
1440
+ }
1441
+
1442
+ return outputBlock.render(
1443
+ {
1444
+ header,
1445
+ state: truncated ? "warning" : "success",
1446
+ sections: [
1447
+ { label: uiTheme.fg("toolTitle", "Metadata"), lines: metadataLines },
1448
+ { label: uiTheme.fg("toolTitle", "Content Preview"), lines: contentPreviewLines },
1449
+ ],
1450
+ width,
1451
+ applyBg: false,
1452
+ },
1453
+ uiTheme,
1454
+ );
1455
+ },
1456
+ invalidate: () => {
1457
+ outputBlock.invalidate();
1458
+ contentPreviewLines = undefined;
1459
+ lastExpanded = undefined;
1460
+ },
1461
+ };
1462
+ }