@herbertgao/pi-extensions 2026.8.4 → 2026.8.6

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 (360) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
  14. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  15. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
  16. package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
  17. package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
  18. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
  19. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
  21. package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
  22. package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
  25. package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
  26. package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
  27. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
  28. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
  29. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
  30. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
  31. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
  32. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  33. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
  36. package/node_modules/@narumitw/pi-btw/README.md +32 -24
  37. package/node_modules/@narumitw/pi-btw/package.json +6 -6
  38. package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
  39. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
  40. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  41. package/node_modules/pi-lens/CHANGELOG.md +2535 -954
  42. package/node_modules/pi-lens/README.md +13 -0
  43. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
  44. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
  45. package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
  46. package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
  47. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
  48. package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
  49. package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
  50. package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
  51. package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
  52. package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
  53. package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
  54. package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
  55. package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
  56. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
  57. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  58. package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
  59. package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
  60. package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
  61. package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
  62. package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
  63. package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
  64. package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
  65. package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
  66. package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
  67. package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
  68. package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
  69. package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
  70. package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
  71. package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
  72. package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
  73. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
  74. package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
  75. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
  76. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
  77. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
  78. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
  79. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
  80. package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
  81. package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
  82. package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
  83. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
  84. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
  86. package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
  87. package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
  88. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
  114. package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
  115. package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
  116. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  117. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
  118. package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
  119. package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
  120. package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
  121. package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
  122. package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
  123. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  124. package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
  125. package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
  126. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
  127. package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
  128. package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
  129. package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
  130. package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
  131. package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
  134. package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
  135. package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
  136. package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
  137. package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
  138. package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
  139. package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
  140. package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
  141. package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
  142. package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
  143. package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
  144. package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
  145. package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
  146. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  147. package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
  148. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
  149. package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
  150. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  151. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
  152. package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
  153. package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
  154. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
  155. package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
  156. package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
  157. package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
  158. package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
  161. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
  162. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  163. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
  164. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
  165. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
  166. package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
  167. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
  168. package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
  169. package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
  170. package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
  171. package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
  172. package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
  173. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  174. package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
  175. package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
  176. package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
  177. package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
  178. package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
  179. package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
  180. package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
  181. package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
  182. package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
  183. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
  184. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
  185. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
  186. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  187. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
  188. package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
  189. package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
  190. package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
  191. package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
  192. package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
  193. package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
  194. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
  195. package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
  196. package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
  197. package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
  198. package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
  199. package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
  200. package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
  201. package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
  202. package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
  203. package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
  204. package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
  205. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
  206. package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
  207. package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
  208. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
  217. package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
  218. package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
  219. package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
  220. package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
  221. package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
  222. package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
  223. package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
  224. package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
  225. package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
  226. package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
  227. package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
  228. package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
  229. package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
  230. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
  233. package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
  234. package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
  235. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
  236. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
  237. package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
  238. package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
  239. package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
  240. package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
  241. package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
  242. package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
  243. package/node_modules/pi-lens/dist/index.js +51785 -36118
  244. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
  245. package/node_modules/pi-lens/dist/mcp/server.js +219 -46
  246. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  247. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  248. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
  249. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  250. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
  251. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
  252. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
  253. package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
  254. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  255. package/node_modules/pi-lens/docs/agent-guide.md +40 -15
  256. package/node_modules/pi-lens/docs/agent-tools.md +21 -10
  257. package/node_modules/pi-lens/docs/analysisall.md +192 -0
  258. package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
  259. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
  260. package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
  261. package/node_modules/pi-lens/docs/environment-variables.md +44 -0
  262. package/node_modules/pi-lens/docs/fable.md +27 -0
  263. package/node_modules/pi-lens/docs/features.md +45 -3
  264. package/node_modules/pi-lens/docs/globalconfig.md +56 -1
  265. package/node_modules/pi-lens/docs/language-coverage.md +4 -3
  266. package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
  267. package/node_modules/pi-lens/docs/mcp.md +37 -4
  268. package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
  269. package/node_modules/pi-lens/docs/settings.md +18 -1
  270. package/node_modules/pi-lens/docs/tools.md +4 -1
  271. package/node_modules/pi-lens/docs/usage.md +41 -5
  272. package/node_modules/pi-lens/docs/word-index.md +35 -0
  273. package/node_modules/pi-lens/package.json +10 -10
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
  300. package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
  301. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
  302. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
  303. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
  304. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  305. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
  306. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
  307. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  308. package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
  309. package/node_modules/pi-mcp-adapter/README.md +38 -4
  310. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  311. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
  312. package/node_modules/pi-mcp-adapter/cli.js +25 -4
  313. package/node_modules/pi-mcp-adapter/commands.ts +15 -7
  314. package/node_modules/pi-mcp-adapter/config.ts +4 -4
  315. package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
  316. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
  317. package/node_modules/pi-mcp-adapter/index.ts +8 -3
  318. package/node_modules/pi-mcp-adapter/init.ts +29 -5
  319. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
  320. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
  321. package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
  322. package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
  323. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
  324. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  325. package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
  326. package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
  327. package/node_modules/pi-mcp-adapter/package.json +4 -3
  328. package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
  329. package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
  330. package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
  331. package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
  332. package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
  333. package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
  334. package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
  335. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
  336. package/node_modules/pi-mcp-adapter/types.ts +176 -27
  337. package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
  338. package/node_modules/pi-web-access/CHANGELOG.md +14 -0
  339. package/node_modules/pi-web-access/README.md +18 -12
  340. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  341. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  342. package/node_modules/pi-web-access/curator-page.ts +5 -3
  343. package/node_modules/pi-web-access/curator-server.ts +2 -1
  344. package/node_modules/pi-web-access/extract.ts +106 -34
  345. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  346. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  347. package/node_modules/pi-web-access/gemini-search.ts +18 -4
  348. package/node_modules/pi-web-access/index.ts +120 -48
  349. package/node_modules/pi-web-access/package.json +2 -2
  350. package/node_modules/pi-web-access/summary-review.ts +11 -5
  351. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  352. package/package.json +12 -13
  353. package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
  354. package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
  355. package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
  356. package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
  357. package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
  358. package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
  359. package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
  360. package/node_modules/pi-lens/dist/clients/types.js +0 -11
@@ -21,9 +21,11 @@ import { extractWithFirecrawl, isFirecrawlAvailable } from "./firecrawl.ts";
21
21
  import { extractWithBrightDataUnlocker, isBrightDataUnlockerAvailable } from "./brightdata-unlocker.ts";
22
22
  import { isVideoFile, extractVideo, extractVideoFrame, getLocalVideoDuration } from "./video-extract.ts";
23
23
  import { appendDeclaredWebLinks, discoverDeclaredWebLinks, type DeclaredWebLink } from "./declared-web-links.ts";
24
- import { fetchRemoteUrl, loadFetchContentDomainPolicy, loadSsrfConfig, validateRemoteUrl, type Lookup } from "./ssrf-protection.ts";
24
+ import { fetchRemoteUrl, loadFetchContentDomainPolicy, loadSsrfConfig, validateRemoteUrl, type DomainPolicy, type Lookup, type SsrfConfig } from "./ssrf-protection.ts";
25
25
  import { formatSeconds, getWebSearchConfigPath } from "./utils.ts";
26
26
  import { isImageEnabled } from "./feature-config.ts";
27
+ import { assertAuthFetchUrl, authFetchRedirectGuard, type AuthFetchProfile } from "./auth-fetch.ts";
28
+ import { getBrowserCookiesForHosts, getLastBrowserCookieDiagnostic } from "./chrome-cookies.ts";
27
29
 
28
30
  const DEFAULT_TIMEOUT_MS = 30000;
29
31
  const CONCURRENT_LIMIT = 3;
@@ -57,7 +59,8 @@ function isAbortError(err: unknown): boolean {
57
59
  }
58
60
 
59
61
  function isRedirectPolicyError(message: string): boolean {
60
- return message.startsWith("Blocked internal ") ||
62
+ return message.startsWith("Authenticated fetch refused cross-origin redirect") ||
63
+ message.startsWith("Blocked internal ") ||
61
64
  message.startsWith("Blocked hostname by fetch_content domain policy") ||
62
65
  message.startsWith("Hostname not allowed by fetch_content domain policy") ||
63
66
  message.startsWith("Too many redirects fetching ") ||
@@ -74,6 +77,56 @@ function imageGateError(): string | null {
74
77
  }
75
78
  }
76
79
 
80
+ async function resolveAuthCookieHeader(url: string | URL, profile: AuthFetchProfile): Promise<string> {
81
+ const parsed = assertAuthFetchUrl(profile, url.toString());
82
+ const result = await getBrowserCookiesForHosts({ hosts: [parsed.hostname], profile: profile.chromeProfile, requestUrl: parsed });
83
+ if (result?.cookieHeader) return result.cookieHeader;
84
+ if (!result) {
85
+ const diagnostic = getLastBrowserCookieDiagnostic();
86
+ throw new Error(`Authenticated fetch profile ${profile.name} could not read browser cookies${diagnostic ? `: ${diagnostic}` : ""}`);
87
+ }
88
+ throw new Error(`Authenticated fetch profile ${profile.name} could not build a cookie header`);
89
+ }
90
+
91
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
92
+
93
+ async function fetchAuthenticatedRemoteUrl(
94
+ url: string,
95
+ init: RequestInit,
96
+ validationOptions: { ssrf: SsrfConfig; domainPolicy: DomainPolicy; lookup?: Lookup },
97
+ profile: AuthFetchProfile,
98
+ ): Promise<Response> {
99
+ let current = await validateRemoteUrl(url, {
100
+ allowRanges: validationOptions.ssrf.allowRanges,
101
+ trustEnvProxy: validationOptions.ssrf.trustEnvProxy,
102
+ domainPolicy: validationOptions.domainPolicy,
103
+ ...(validationOptions.lookup ? { lookup: validationOptions.lookup } : {}),
104
+ });
105
+ let requestInit = init;
106
+ for (let redirects = 0; redirects <= 5; redirects++) {
107
+ const cookieHeader = await resolveAuthCookieHeader(current, profile);
108
+ const headers = { ...(requestInit.headers as Record<string, string>), cookie: cookieHeader };
109
+ const response = await fetch(current, { ...requestInit, headers, redirect: "manual" });
110
+ if (!REDIRECT_STATUSES.has(response.status)) return response;
111
+ const location = response.headers.get("location");
112
+ if (!location) return response;
113
+ if (redirects === 5) throw new Error(`Too many redirects fetching ${current.toString()}`);
114
+ const from = current;
115
+ current = await validateRemoteUrl(new URL(location, current), {
116
+ allowRanges: validationOptions.ssrf.allowRanges,
117
+ trustEnvProxy: validationOptions.ssrf.trustEnvProxy,
118
+ domainPolicy: validationOptions.domainPolicy,
119
+ ...(validationOptions.lookup ? { lookup: validationOptions.lookup } : {}),
120
+ });
121
+ authFetchRedirectGuard(profile, from, current);
122
+ if (response.status === 303 || ((response.status === 301 || response.status === 302) && requestInit.method?.toUpperCase() === "POST")) {
123
+ const { body: _body, ...nextInit } = requestInit;
124
+ requestInit = { ...nextInit, method: "GET" };
125
+ }
126
+ }
127
+ throw new Error(`Too many redirects fetching ${current.toString()}`);
128
+ }
129
+
77
130
  function loadFetchRouting(): FetchRouting {
78
131
  if (!existsSync(WEB_SEARCH_CONFIG_PATH)) {
79
132
  return { providers: DEFAULT_FETCH_PROVIDER_ORDER, allowRemoteHostedProviders: false };
@@ -171,6 +224,7 @@ export interface ExtractOptions {
171
224
  model?: string;
172
225
  mode?: "readable" | "raw" | "answer";
173
226
  answerModel?: string;
227
+ authFetchProfile?: AuthFetchProfile;
174
228
  /** Custom DNS resolver used for SSRF validation. Primarily a test seam. */
175
229
  lookup?: Lookup;
176
230
  }
@@ -313,11 +367,17 @@ async function extractLocalFrames(
313
367
  return { frames, error: frames.length === 0 && firstError ? firstError.error : null };
314
368
  }
315
369
 
316
- function safeVideoInfo(url: string): { info: ReturnType<typeof isVideoFile>; error?: string } {
370
+ type LocalVideoInfoResult =
371
+ | { status: "video"; info: NonNullable<ReturnType<typeof isVideoFile>> }
372
+ | { status: "not-video" }
373
+ | { status: "invalid"; error: string };
374
+
375
+ function safeVideoInfo(url: string): LocalVideoInfoResult {
317
376
  try {
318
- return { info: isVideoFile(url) };
377
+ const info = isVideoFile(url);
378
+ return info ? { status: "video", info } : { status: "not-video" };
319
379
  } catch (err) {
320
- return { info: null, error: errorMessage(err) };
380
+ return { status: "invalid", error: errorMessage(err) };
321
381
  }
322
382
  }
323
383
 
@@ -351,6 +411,14 @@ export async function extractContent(
351
411
  }
352
412
  }
353
413
 
414
+ if (options?.authFetchProfile) {
415
+ try {
416
+ return await extractViaHttp(url, signal, options);
417
+ } catch (err) {
418
+ return { url, title: "", content: "", error: errorMessage(err) };
419
+ }
420
+ }
421
+
354
422
  if (options?.mode === "raw") {
355
423
  return extractViaHttp(url, signal, options);
356
424
  }
@@ -380,10 +448,10 @@ export async function extractContent(
380
448
  }
381
449
 
382
450
  const localVideo = safeVideoInfo(url);
383
- if (localVideo.error) {
451
+ if (localVideo.status === "invalid") {
384
452
  return { url, title: "", content: "", error: localVideo.error };
385
453
  }
386
- if (localVideo.info) {
454
+ if (localVideo.status === "video") {
387
455
  const durationResult = await getLocalVideoDuration(localVideo.info.absolutePath);
388
456
  if (typeof durationResult !== "number") {
389
457
  return { url, title: "Frames", content: durationResult.error, error: durationResult.error };
@@ -463,10 +531,10 @@ export async function extractContent(
463
531
  }
464
532
 
465
533
  const localVideo = safeVideoInfo(url);
466
- if (localVideo.error) {
534
+ if (localVideo.status === "invalid") {
467
535
  return { url, title: "", content: "", error: localVideo.error };
468
536
  }
469
- if (localVideo.info) {
537
+ if (localVideo.status === "video") {
470
538
  if (spec.type === "range") {
471
539
  const timestamps = frameCount
472
540
  ? computeRangeTimestamps(spec.start, spec.end, frameCount)
@@ -495,10 +563,10 @@ export async function extractContent(
495
563
  }
496
564
 
497
565
  const localVideo = safeVideoInfo(url);
498
- if (localVideo.error) {
566
+ if (localVideo.status === "invalid") {
499
567
  return { url, title: "", content: "", error: localVideo.error };
500
568
  }
501
- if (localVideo.info) {
569
+ if (localVideo.status === "video") {
502
570
  try {
503
571
  const result = await extractVideo(localVideo.info, signal, options);
504
572
  if (signal?.aborted) return abortedResult(url);
@@ -548,7 +616,7 @@ export async function extractContent(
548
616
  url,
549
617
  title: "",
550
618
  content: "",
551
- error: "Could not extract YouTube video content. Sign into Google in Chrome for automatic access, or set GEMINI_API_KEY.",
619
+ error: "Could not extract YouTube video content. Sign into Google in a supported Chromium browser for automatic access, or set GEMINI_API_KEY.",
552
620
  };
553
621
  }
554
622
 
@@ -914,29 +982,33 @@ async function extractViaHttp(
914
982
  try {
915
983
  const ssrf = loadSsrfConfig();
916
984
  const domainPolicy = loadFetchContentDomainPolicy();
917
- const response = await fetchRemoteUrl(
918
- url,
919
- {
920
- signal: controller.signal,
921
- headers: {
922
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
923
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
924
- "Accept-Language": "en-US,en;q=0.9",
925
- "Cache-Control": "no-cache",
926
- "Sec-Fetch-Dest": "document",
927
- "Sec-Fetch-Mode": "navigate",
928
- "Sec-Fetch-Site": "none",
929
- "Sec-Fetch-User": "?1",
930
- "Upgrade-Insecure-Requests": "1",
931
- },
932
- },
933
- {
934
- allowRanges: ssrf.allowRanges,
935
- trustEnvProxy: ssrf.trustEnvProxy,
936
- domainPolicy,
937
- ...(options?.lookup ? { lookup: options.lookup } : {}),
985
+ const authProfile = options?.authFetchProfile;
986
+ const requestInit = {
987
+ signal: controller.signal,
988
+ headers: {
989
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36",
990
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
991
+ "Accept-Language": "en-US,en;q=0.9",
992
+ "Cache-Control": "no-cache",
993
+ "Sec-Fetch-Dest": "document",
994
+ "Sec-Fetch-Mode": "navigate",
995
+ "Sec-Fetch-Site": "none",
996
+ "Sec-Fetch-User": "?1",
997
+ "Upgrade-Insecure-Requests": "1",
938
998
  },
939
- );
999
+ };
1000
+ const response = authProfile
1001
+ ? await fetchAuthenticatedRemoteUrl(url, requestInit, { ssrf, domainPolicy, ...(options?.lookup ? { lookup: options.lookup } : {}) }, authProfile)
1002
+ : await fetchRemoteUrl(
1003
+ url,
1004
+ requestInit,
1005
+ {
1006
+ allowRanges: ssrf.allowRanges,
1007
+ trustEnvProxy: ssrf.trustEnvProxy,
1008
+ domainPolicy,
1009
+ ...(options?.lookup ? { lookup: options.lookup } : {}),
1010
+ },
1011
+ );
940
1012
 
941
1013
  if (!response.ok && options?.mode !== "raw") {
942
1014
  activityMonitor.logComplete(activityId, response.status);
@@ -8,6 +8,7 @@ export interface FetchContentParams {
8
8
  model?: unknown;
9
9
  mode?: unknown;
10
10
  answerModel?: unknown;
11
+ auth?: unknown;
11
12
  }
12
13
 
13
14
  export interface NormalizedFetchContentParams {
@@ -20,6 +21,7 @@ export interface NormalizedFetchContentParams {
20
21
  model?: string;
21
22
  mode?: "readable" | "raw" | "answer";
22
23
  answerModel?: string;
24
+ auth?: true | string;
23
25
  };
24
26
  }
25
27
 
@@ -28,7 +30,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
28
30
  const urlList = normalizedUrls.length > 0 ? normalizedUrls : normalizeSingleUrl(params.url);
29
31
  const prompt = normalizeOptionalString(params.prompt);
30
32
  const timestamp = normalizeOptionalString(params.timestamp);
31
- const frames = normalizeOptionalInteger(params.frames);
33
+ const frames = normalizeOptionalFrameCount(params.frames);
32
34
 
33
35
  const shouldIncludeFrames = frames !== undefined && (timestamp !== undefined || frames > 1);
34
36
 
@@ -36,6 +38,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
36
38
  const model = normalizeOptionalString(params.model);
37
39
  const mode = normalizeMode(params.mode);
38
40
  const answerModel = normalizeOptionalString(params.answerModel);
41
+ const auth = normalizeAuth(params.auth);
39
42
 
40
43
  return {
41
44
  urlList,
@@ -47,6 +50,7 @@ export function normalizeFetchContentParams(params: FetchContentParams): Normali
47
50
  ...(model !== undefined ? { model } : {}),
48
51
  ...(mode !== undefined ? { mode } : {}),
49
52
  ...(answerModel !== undefined ? { answerModel } : {}),
53
+ ...(auth !== undefined ? { auth } : {}),
50
54
  },
51
55
  };
52
56
  }
@@ -74,8 +78,18 @@ function normalizeMode(value: unknown): "readable" | "raw" | "answer" | undefine
74
78
  throw new Error('mode must be "readable", "raw", or "answer"');
75
79
  }
76
80
 
77
- function normalizeOptionalInteger(value: unknown): number | undefined {
78
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1) return undefined;
81
+ function normalizeAuth(value: unknown): true | string | undefined {
82
+ if (value === undefined || value === false) return undefined;
83
+ if (value === true) return true;
84
+ if (typeof value === "string") {
85
+ const trimmed = value.trim();
86
+ if (trimmed) return trimmed;
87
+ }
88
+ throw new Error("auth must be a profile name, true, or false");
89
+ }
90
+
91
+ function normalizeOptionalFrameCount(value: unknown): number | undefined {
92
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 12) return undefined;
79
93
  return value;
80
94
  }
81
95
 
@@ -2,12 +2,14 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.ts";
3
3
  import { redactCredential, resolveCredential } from "./credential-source.ts";
4
4
  import type { ExtractedContent, ExtractOptions } from "./extract.ts";
5
- import { validateRemoteUrl, type Lookup } from "./ssrf-protection.ts";
5
+ import type { SearchOptions, SearchResponse } from "./perplexity.ts";
6
+ import { loadSsrfConfig, validateRemoteUrl, type Lookup } from "./ssrf-protection.ts";
6
7
  import { getWebSearchConfigPath } from "./utils.ts";
7
8
 
8
9
  const CONFIG_PATH = getWebSearchConfigPath();
9
10
  const DEFAULT_API_VERSION = "v2";
10
11
  const EXTRACT_TIMEOUT_MS = 60_000;
12
+ const SEARCH_TIMEOUT_MS = 60_000;
11
13
  const DEFAULT_MAX_REDIRECTS = 5;
12
14
  const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
13
15
  const SUPPORTED_API_VERSIONS = ["v1", "v2"] as const;
@@ -22,6 +24,11 @@ export interface FirecrawlExtractOptions extends Pick<ExtractOptions, "timeoutMs
22
24
  ssrf?: FirecrawlSsrfOptions;
23
25
  }
24
26
 
27
+ interface FirecrawlSearchOptions extends SearchOptions, Pick<ExtractOptions, "timeoutMs" | "lookup"> {
28
+ includeContent?: boolean;
29
+ ssrf?: FirecrawlSsrfOptions;
30
+ }
31
+
25
32
  interface FirecrawlConfig {
26
33
  firecrawlBaseUrl?: unknown;
27
34
  firecrawlApiKey?: unknown;
@@ -35,6 +42,25 @@ interface FirecrawlScrapeData {
35
42
  metadata?: { title?: unknown };
36
43
  }
37
44
 
45
+ interface FirecrawlSearchItem {
46
+ title?: unknown;
47
+ description?: unknown;
48
+ snippet?: unknown;
49
+ url?: unknown;
50
+ markdown?: unknown;
51
+ metadata?: {
52
+ title?: unknown;
53
+ description?: unknown;
54
+ sourceURL?: unknown;
55
+ url?: unknown;
56
+ };
57
+ }
58
+
59
+ interface DomainFilters {
60
+ include: string[];
61
+ exclude: string[];
62
+ }
63
+
38
64
  let cachedConfig: FirecrawlConfig | null = null;
39
65
 
40
66
  function loadConfig(): FirecrawlConfig {
@@ -149,10 +175,11 @@ function isAbortError(err: unknown): boolean {
149
175
  return errorMessage(err).toLowerCase().includes("abort");
150
176
  }
151
177
 
152
- function ssrfOptions(options?: FirecrawlExtractOptions): { lookup?: Lookup; allowRanges: string[]; trustEnvProxy: boolean } {
178
+ function ssrfOptions(options?: FirecrawlExtractOptions | FirecrawlSearchOptions): { lookup?: Lookup; allowRanges: string[]; trustEnvProxy: boolean } {
179
+ const config = loadSsrfConfig();
153
180
  return {
154
- allowRanges: options?.ssrf?.allowRanges ?? [],
155
- trustEnvProxy: options?.ssrf?.trustEnvProxy ?? false,
181
+ allowRanges: options?.ssrf?.allowRanges ?? config.allowRanges,
182
+ trustEnvProxy: options?.ssrf?.trustEnvProxy ?? config.trustEnvProxy,
156
183
  ...(options?.lookup ? { lookup: options.lookup } : {}),
157
184
  };
158
185
  }
@@ -171,7 +198,7 @@ function withoutSensitiveHeaders(headers: Record<string, string>): Record<string
171
198
  async function fetchFirecrawlApi(
172
199
  url: string,
173
200
  init: { method: string; headers: Record<string, string>; body: string; signal: AbortSignal },
174
- options: FirecrawlExtractOptions | undefined,
201
+ options: FirecrawlExtractOptions | FirecrawlSearchOptions | undefined,
175
202
  ): Promise<Response> {
176
203
  let current = await validateRemoteUrl(url, ssrfOptions(options));
177
204
  let headers = init.headers;
@@ -199,11 +226,126 @@ function scrapeBody(url: string): Record<string, unknown> {
199
226
  };
200
227
  }
201
228
 
229
+ function normalizeCount(value: number | undefined): number {
230
+ if (typeof value !== "number" || !Number.isFinite(value)) return 5;
231
+ return Math.max(1, Math.min(Math.floor(value), 20));
232
+ }
233
+
234
+ function normalizeDomain(value: string): string | null {
235
+ let input = value.trim().toLowerCase();
236
+ if (!input) return null;
237
+ if (input.startsWith("-")) input = input.slice(1).trim();
238
+ if (!input) return null;
239
+ try {
240
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
241
+ input = parsed.hostname;
242
+ } catch {
243
+ input = input.split("/")[0]?.split(":")[0] ?? "";
244
+ }
245
+ input = input.replace(/^\.+|\.+$/g, "");
246
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
247
+ }
248
+
249
+ function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
250
+ const filters: DomainFilters = { include: [], exclude: [] };
251
+ for (const raw of domainFilter ?? []) {
252
+ const domain = normalizeDomain(raw);
253
+ if (!domain) continue;
254
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
255
+ if (!target.includes(domain)) target.push(domain);
256
+ }
257
+ return filters;
258
+ }
259
+
260
+ function passesDomainFilters(url: string, filters: DomainFilters): boolean {
261
+ if (filters.include.length === 0 && filters.exclude.length === 0) return true;
262
+ let hostname: string;
263
+ try {
264
+ hostname = new URL(url).hostname.toLowerCase();
265
+ } catch {
266
+ return false;
267
+ }
268
+ const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
269
+ if (filters.exclude.some(matches)) return false;
270
+ return filters.include.length === 0 || filters.include.some(matches);
271
+ }
272
+
273
+ function mapRecencyFilter(value: SearchOptions["recencyFilter"]): string | undefined {
274
+ switch (value) {
275
+ case "day": return "qdr:d";
276
+ case "week": return "qdr:w";
277
+ case "month": return "qdr:m";
278
+ case "year": return "qdr:y";
279
+ default: return undefined;
280
+ }
281
+ }
282
+
283
+ function searchBody(query: string, options: FirecrawlSearchOptions, numResults: number, filters: DomainFilters): Record<string, unknown> {
284
+ return {
285
+ query,
286
+ limit: numResults,
287
+ sources: ["web"],
288
+ ...(filters.include.length > 0 ? { includeDomains: filters.include } : {}),
289
+ ...(filters.include.length === 0 && filters.exclude.length > 0 ? { excludeDomains: filters.exclude } : {}),
290
+ ...(mapRecencyFilter(options.recencyFilter) ? { tbs: mapRecencyFilter(options.recencyFilter) } : {}),
291
+ ...(options.includeContent ? {
292
+ scrapeOptions: {
293
+ formats: ["markdown"],
294
+ onlyMainContent: true,
295
+ ...(allowFreshScrape() ? {} : { lockdown: true }),
296
+ },
297
+ } : {}),
298
+ };
299
+ }
300
+
301
+ function firstString(...values: unknown[]): string | null {
302
+ for (const value of values) {
303
+ if (typeof value === "string" && value.trim()) return value.trim();
304
+ }
305
+ return null;
306
+ }
307
+
308
+ function buildAnswer(results: SearchResponse["results"]): string {
309
+ return results.map((result) => {
310
+ if (result.snippet) return `${result.snippet}\nSource: ${result.title} (${result.url})`;
311
+ return `Source: ${result.title} (${result.url})`;
312
+ }).join("\n\n");
313
+ }
314
+
315
+ function mapSearchResults(data: unknown, numResults: number, filters: DomainFilters): {
316
+ results: SearchResponse["results"];
317
+ inlineContent: ExtractedContent[];
318
+ } {
319
+ const web = Array.isArray(data)
320
+ ? data
321
+ : data && typeof data === "object" ? (data as Record<string, unknown>).web : undefined;
322
+ if (!Array.isArray(web)) throw new Error("Firecrawl search returned an unexpected web result shape");
323
+ const results: SearchResponse["results"] = [];
324
+ const inlineContent: ExtractedContent[] = [];
325
+ const seen = new Set<string>();
326
+ for (const rawItem of web) {
327
+ if (!rawItem || typeof rawItem !== "object" || Array.isArray(rawItem)) continue;
328
+ const item = rawItem as FirecrawlSearchItem;
329
+ const url = firstString(item.url, item.metadata?.sourceURL, item.metadata?.url);
330
+ if (!url || seen.has(url) || !passesDomainFilters(url, filters)) continue;
331
+ seen.add(url);
332
+ const title = firstString(item.title, item.metadata?.title) ?? url;
333
+ const snippet = firstString(item.description, item.snippet, item.metadata?.description) ?? "";
334
+ results.push({ title, url, snippet });
335
+ const markdown = firstString(item.markdown);
336
+ if (markdown) inlineContent.push({ url, title, content: markdown, error: null });
337
+ if (results.length >= numResults) break;
338
+ }
339
+ return { results, inlineContent };
340
+ }
341
+
202
342
  async function firecrawlFetch(
203
343
  endpoint: string,
204
344
  body: Record<string, unknown>,
205
345
  signal: AbortSignal | undefined,
206
- options: FirecrawlExtractOptions | undefined,
346
+ options: FirecrawlExtractOptions | FirecrawlSearchOptions | undefined,
347
+ label = endpoint,
348
+ activity: { type: "api"; query: string } | { type: "fetch"; url: string } | undefined = undefined,
207
349
  ): Promise<Record<string, unknown>> {
208
350
  const baseUrl = requireBaseUrl();
209
351
  const version = getApiVersion();
@@ -211,7 +353,7 @@ async function firecrawlFetch(
211
353
  const headers: Record<string, string> = { "Content-Type": "application/json" };
212
354
  if (apiKey) headers.Authorization = `Bearer ${apiKey}`;
213
355
  const requestUrl = `${baseUrl}/${version}/${endpoint}`;
214
- const activityId = activityMonitor.logStart({ type: "fetch", url: requestUrl });
356
+ const activityId = activityMonitor.logStart(activity ?? { type: "fetch", url: requestUrl });
215
357
  try {
216
358
  const response = await fetchFirecrawlApi(requestUrl, {
217
359
  method: "POST",
@@ -221,24 +363,24 @@ async function firecrawlFetch(
221
363
  }, options);
222
364
  if (!response.ok) {
223
365
  const text = await response.text().catch(() => "");
224
- throw new Error(`Firecrawl scrape error ${response.status}: ${redactCredential(text.slice(0, 300), apiKey)}`);
366
+ throw new Error(`Firecrawl ${label} error ${response.status}: ${redactCredential(text.slice(0, 300), apiKey)}`);
225
367
  }
226
368
  let data: unknown;
227
369
  try {
228
370
  data = await response.json();
229
371
  } catch (err) {
230
- throw new Error(`Firecrawl scrape returned invalid JSON: ${errorMessage(err)}`);
372
+ throw new Error(`Firecrawl ${label} returned invalid JSON: ${errorMessage(err)}`);
231
373
  }
232
374
  if (!data || typeof data !== "object" || Array.isArray(data)) {
233
- throw new Error("Firecrawl scrape returned an unexpected response shape");
375
+ throw new Error(`Firecrawl ${label} returned an unexpected response shape`);
234
376
  }
235
377
  const envelope = data as Record<string, unknown>;
236
378
  if (envelope.success === false) {
237
379
  const reason = typeof envelope.error === "string" && envelope.error.trim() ? envelope.error : "unknown error";
238
- throw new Error(`Firecrawl scrape unsuccessful: ${redactCredential(reason, apiKey)}`);
380
+ throw new Error(`Firecrawl ${label} unsuccessful: ${redactCredential(reason, apiKey)}`);
239
381
  }
240
382
  if (envelope.success !== true) {
241
- throw new Error("Firecrawl scrape returned an unexpected response shape");
383
+ throw new Error(`Firecrawl ${label} returned an unexpected response shape`);
242
384
  }
243
385
  activityMonitor.logComplete(activityId, response.status);
244
386
  return envelope;
@@ -253,6 +395,24 @@ export function isFirecrawlAvailable(): boolean {
253
395
  return getBaseUrl() !== null;
254
396
  }
255
397
 
398
+ export async function searchWithFirecrawl(query: string, options: FirecrawlSearchOptions = {}): Promise<SearchResponse> {
399
+ requireBaseUrl();
400
+ const numResults = normalizeCount(options.numResults);
401
+ const filters = parseDomainFilter(options.domainFilter);
402
+ const envelope = await firecrawlFetch(
403
+ "search",
404
+ searchBody(query, options, numResults, filters),
405
+ options.signal,
406
+ { ...options, timeoutMs: options.timeoutMs ?? SEARCH_TIMEOUT_MS },
407
+ "search",
408
+ { type: "api", query },
409
+ );
410
+ const mapped = mapSearchResults(envelope.data, numResults, filters);
411
+ const response: SearchResponse = { answer: buildAnswer(mapped.results), results: mapped.results };
412
+ if (options.includeContent && mapped.inlineContent.length > 0) response.inlineContent = mapped.inlineContent;
413
+ return response;
414
+ }
415
+
256
416
  export async function extractWithFirecrawl(
257
417
  url: string,
258
418
  signal?: AbortSignal,
@@ -14,6 +14,7 @@ import { isSearch1APIAvailable, searchWithSearch1API } from "./search1api.ts";
14
14
  import { isSearchinfinityAvailable, searchWithSearchinfinity } from "./searchinfinity.ts";
15
15
  import { isQueritAvailable, searchWithQuerit } from "./querit.ts";
16
16
  import { isTavilyAvailable, searchWithTavily } from "./tavily.ts";
17
+ import { isFirecrawlAvailable, searchWithFirecrawl } from "./firecrawl.ts";
17
18
  import { isJinaSearchAvailable, searchWithJina } from "./jina-search.ts";
18
19
  import { isSerpdiveAvailable, searchWithSerpdive } from "./serpdive.ts";
19
20
  import { isKagiAvailable, searchWithKagi } from "./kagi.ts";
@@ -27,7 +28,7 @@ import { isBrightDataAvailable, searchWithBrightData } from "./brightdata.ts";
27
28
  import { isSerpBaseAvailable, searchWithSerpBase } from "./serpbase.ts";
28
29
  import { getWebSearchConfigPath } from "./utils.ts";
29
30
 
30
- export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "bocha"] as const;
31
+ export const RESOLVED_SEARCH_PROVIDERS = ["openai", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "searxng", "duckduckgo", "perplexity", "gemini", "exa", "serpdive", "kagi", "ollama", "anysearch", "xai", "brightdata", "serpbase", "bocha"] as const;
31
32
  export const SEARCH_PROVIDERS = ["auto", "all", ...RESOLVED_SEARCH_PROVIDERS] as const;
32
33
 
33
34
  export type ResolvedSearchProvider = typeof RESOLVED_SEARCH_PROVIDERS[number];
@@ -91,7 +92,7 @@ const CONFIG_PATH = getWebSearchConfigPath();
91
92
  const DEFAULT_SEARCH_MODEL = "gemini-3.6-flash";
92
93
  // Explicit-only providers (DuckDuckGo, AnySearch, xAI, Bright Data, SerpBase) are deliberately absent:
93
94
  // `all` must never fan out to an opt-in or paid provider without the user asking for it.
94
- const ALL_SEARCH_PROVIDERS: ResolvedSearchProvider[] = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
95
+ const ALL_SEARCH_PROVIDERS: ResolvedSearchProvider[] = ["searxng", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "ollama", "perplexity", "gemini", "bocha"];
95
96
  const VALID_ROUTING_KINDS = ["transient", "quota", "network", "invalid-response"] as const;
96
97
 
97
98
  type SearchConfig = {
@@ -304,6 +305,7 @@ async function searchWithResolvedProvider(
304
305
  if (provider === "searchinfinity") return { ...(await searchWithSearchinfinity(query, options)), provider };
305
306
  if (provider === "querit") return { ...(await searchWithQuerit(query, options)), provider };
306
307
  if (provider === "tavily") return { ...(await searchWithTavily(query, options)), provider };
308
+ if (provider === "firecrawl") return { ...(await searchWithFirecrawl(query, options)), provider };
307
309
  if (provider === "jina") return { ...(await searchWithJina(query, options)), provider };
308
310
  if (provider === "serpdive") return { ...(await searchWithSerpdive(query, options)), provider };
309
311
  if (provider === "kagi") return { ...(await searchWithKagi(query, options)), provider };
@@ -340,6 +342,7 @@ async function isResolvedProviderAvailable(provider: ResolvedSearchProvider, opt
340
342
  if (provider === "searchinfinity") return isSearchinfinityAvailable();
341
343
  if (provider === "querit") return isQueritAvailable();
342
344
  if (provider === "tavily") return isTavilyAvailable();
345
+ if (provider === "firecrawl") return isFirecrawlAvailable();
343
346
  if (provider === "jina") return isJinaSearchAvailable();
344
347
  if (provider === "serpdive") return isSerpdiveAvailable();
345
348
  if (provider === "kagi") return isKagiAvailable();
@@ -362,6 +365,7 @@ function providerLabel(provider: ResolvedSearchProvider): string {
362
365
  if (provider === "search1api") return "Search1API";
363
366
  if (provider === "searchinfinity") return "Searchinfinity";
364
367
  if (provider === "querit") return "Querit";
368
+ if (provider === "firecrawl") return "Firecrawl";
365
369
  if (provider === "serpdive") return "SERPdive";
366
370
  if (provider === "searxng") return "SearXNG";
367
371
  if (provider === "duckduckgo") return "DuckDuckGo";
@@ -600,6 +604,16 @@ export async function search(query: string, options: FullSearchOptions = {}): Pr
600
604
  }
601
605
  }
602
606
 
607
+ if (isFirecrawlAvailable()) {
608
+ try {
609
+ const result = await searchWithFirecrawl(query, options);
610
+ return { ...result, provider: "firecrawl" };
611
+ } catch (err) {
612
+ if (isAbortError(err)) throw err;
613
+ fallbackErrors.push(`Firecrawl: ${errorMessage(err)}`);
614
+ }
615
+ }
616
+
603
617
  if (isJinaSearchAvailable()) {
604
618
  try {
605
619
  const result = await searchWithJina(query, options);
@@ -675,8 +689,8 @@ export async function search(query: string, options: FullSearchOptions = {}): Pr
675
689
  throw new Error(
676
690
  "No search provider available. Either:\n" +
677
691
  " 1. Use /login to sign in with a Codex subscription for OpenAI web search\n" +
678
- ` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tinyfishApiKey, search1apiApiKey, searchinfinityApiKey, queritApiKey, tavilyApiKey, jinaApiKey, serpdiveApiKey, kagiApiKey, ollamaApiKey, searxngBaseUrl, perplexityApiKey, exaApiKey, geminiApiKey, bochaApiKey, or cloudflareApiKey in ${CONFIG_PATH}\n` +
679
- " 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\n" +
692
+ ` 2. Set openaiApiKey, braveApiKey, parallelApiKey, tinyfishApiKey, search1apiApiKey, searchinfinityApiKey, queritApiKey, tavilyApiKey, firecrawlBaseUrl, jinaApiKey, serpdiveApiKey, kagiApiKey, ollamaApiKey, searxngBaseUrl, perplexityApiKey, exaApiKey, geminiApiKey, bochaApiKey, or cloudflareApiKey in ${CONFIG_PATH}\n` +
693
+ " 3. Set OPENAI_API_KEY, BRAVE_API_KEY, PARALLEL_API_KEY, TINYFISH_API_KEY, SEARCH1API_KEY, SEARCHINFINITY_API_KEY, QUERIT_API_KEY, TAVILY_API_KEY, FIRECRAWL_BASE_URL, JINA_API_KEY, SERPDIVE_API_KEY, KAGI_API_KEY, BOCHA_API_KEY, OLLAMA_API_KEY, SEARXNG_BASE_URL, EXA_API_KEY, PERPLEXITY_API_KEY, GEMINI_API_KEY, or CLOUDFLARE_API_KEY env vars\n" +
680
694
  " 4. Set GOOGLE_GEMINI_BASE_URL with CLOUDFLARE_API_KEY for Cloudflare AI Gateway routing\n" +
681
695
  " 5. Sign into gemini.google.com in a supported Chromium-based browser\n" +
682
696
  " 6. Explicitly select provider: \"anysearch\" for anonymous AnySearch, \"xai\" for Grok, \"brightdata\" with brightdataSerpZone for paid Bright Data SERP, or \"serpbase\" with serpbaseApiKey for paid Google SERP"