@herbertgao/pi-extensions 2026.8.5 → 2026.8.7

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 (389) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +48 -6
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  17. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  18. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +16 -0
  19. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  20. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  21. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  23. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  24. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +22 -15
  25. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  26. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  27. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  28. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  29. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  30. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  32. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  33. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +34 -10
  34. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  35. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +29 -5
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +3 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +46 -10
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +8 -1
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  49. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  50. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  51. package/node_modules/@narumitw/pi-btw/README.md +119 -103
  52. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  53. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  54. package/node_modules/@narumitw/pi-btw/package.json +13 -9
  55. package/node_modules/@narumitw/pi-btw/src/btw.ts +40 -42
  56. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  57. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  58. package/node_modules/@narumitw/pi-btw/src/menu.ts +78 -15
  59. package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
  60. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  61. package/node_modules/pi-lens/CHANGELOG.md +1004 -0
  62. package/node_modules/pi-lens/README.md +11 -3
  63. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  64. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  65. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +427 -0
  66. package/node_modules/pi-lens/dist/clients/agent-nudge.js +79 -16
  67. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  68. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  69. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  70. package/node_modules/pi-lens/dist/clients/biome-client.js +176 -37
  71. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  72. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  73. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  74. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  75. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  76. package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
  77. package/node_modules/pi-lens/dist/clients/bus-publish.js +11 -3
  78. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  79. package/node_modules/pi-lens/dist/clients/cascade-format.js +76 -2
  80. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  81. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  82. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  83. package/node_modules/pi-lens/dist/clients/console-guard-install.js +16 -4
  84. package/node_modules/pi-lens/dist/clients/dead-code-client.js +249 -52
  85. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  86. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  87. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  88. package/node_modules/pi-lens/dist/clients/dependency-checker.js +43 -9
  89. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +153 -37
  90. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  91. package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +10 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +165 -11
  93. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +325 -72
  94. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  95. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +81 -41
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +488 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +84 -0
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +943 -93
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +106 -0
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  129. package/node_modules/pi-lens/dist/clients/disposition-publish.js +10 -3
  130. package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
  131. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  132. package/node_modules/pi-lens/dist/clients/extension-log.js +317 -3
  133. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  134. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  135. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  136. package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
  137. package/node_modules/pi-lens/dist/clients/format-events-publish.js +39 -8
  138. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  139. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  140. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  141. package/node_modules/pi-lens/dist/clients/git-guard.js +49 -21
  142. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  143. package/node_modules/pi-lens/dist/clients/go-client.js +21 -39
  144. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +219 -10
  145. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  146. package/node_modules/pi-lens/dist/clients/host-ports.js +9 -1
  147. package/node_modules/pi-lens/dist/clients/installer/index.js +1119 -101
  148. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  149. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  150. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  151. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +86 -15
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +361 -24
  154. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  155. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/latency-logger.js +487 -9
  157. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  158. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  159. package/node_modules/pi-lens/dist/clients/lens-events.js +56 -25
  160. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
  161. package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +45 -2
  162. package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
  163. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +160 -20
  164. package/node_modules/pi-lens/dist/clients/lsp/client.js +1666 -148
  165. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +111 -0
  166. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  167. package/node_modules/pi-lens/dist/clients/lsp/index.js +2496 -171
  168. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  169. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  170. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  171. package/node_modules/pi-lens/dist/clients/lsp/server.js +517 -69
  172. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  173. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  174. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +172 -0
  175. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
  176. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +61 -3
  177. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  178. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  179. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  180. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  181. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +4 -0
  182. package/node_modules/pi-lens/dist/clients/mcp/session.js +39 -18
  183. package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
  184. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  185. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  186. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  187. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  188. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  189. package/node_modules/pi-lens/dist/clients/pipeline.js +178 -30
  190. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  191. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  192. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  193. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +85 -15
  194. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  195. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
  196. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  197. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  198. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  199. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  200. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  201. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  202. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  203. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  204. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  205. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  206. package/node_modules/pi-lens/dist/clients/review-graph/query.js +76 -4
  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 +248 -13
  209. package/node_modules/pi-lens/dist/clients/runtime-context.js +112 -12
  210. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +472 -61
  211. package/node_modules/pi-lens/dist/clients/runtime-session.js +359 -13
  212. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  213. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +269 -22
  214. package/node_modules/pi-lens/dist/clients/runtime-turn.js +948 -87
  215. package/node_modules/pi-lens/dist/clients/rust-client.js +21 -37
  216. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  217. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  218. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  219. package/node_modules/pi-lens/dist/clients/security-scan-client.js +224 -8
  220. package/node_modules/pi-lens/dist/clients/sg-runner.js +249 -37
  221. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  222. package/node_modules/pi-lens/dist/clients/smells-rollup.js +18 -11
  223. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  224. package/node_modules/pi-lens/dist/clients/startup-timing.js +26 -2
  225. package/node_modules/pi-lens/dist/clients/test-runner-client.js +574 -32
  226. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  227. package/node_modules/pi-lens/dist/clients/tool-policy.js +127 -20
  228. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
  229. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  230. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  231. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  232. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  233. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  234. package/node_modules/pi-lens/dist/clients/warm-attach.js +17 -0
  235. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  236. package/node_modules/pi-lens/dist/clients/word-index.js +305 -33
  237. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  238. package/node_modules/pi-lens/dist/index.js +64677 -49484
  239. package/node_modules/pi-lens/dist/mcp/server.js +8 -2
  240. package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
  241. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
  242. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
  243. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +469 -56
  244. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +190 -46
  245. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  246. package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
  247. package/node_modules/pi-lens/docs/agent-guide.md +44 -14
  248. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +518 -501
  249. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  250. package/node_modules/pi-lens/docs/features.md +33 -6
  251. package/node_modules/pi-lens/docs/globalconfig.md +14 -0
  252. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  253. package/node_modules/pi-lens/docs/mcp.md +2 -2
  254. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  255. package/node_modules/pi-lens/docs/settings.md +25 -1
  256. package/node_modules/pi-lens/docs/usage.md +24 -5
  257. package/node_modules/pi-lens/docs/word-index.md +35 -0
  258. package/node_modules/pi-lens/package.json +10 -6
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +22 -0
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +9 -0
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +9 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +9 -0
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +9 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  288. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  289. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  290. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  291. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  292. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  293. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  294. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +66 -0
  295. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  296. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
  297. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
  298. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  299. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  300. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  301. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  302. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  303. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  304. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +29 -0
  305. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +30 -0
  306. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +41 -0
  307. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +42 -0
  308. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  309. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  310. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  311. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  312. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  313. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  314. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +32 -0
  315. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  316. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  317. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  318. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  319. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  320. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  321. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  322. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  323. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  324. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +132 -1
  325. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  326. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  327. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +12 -11
  328. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
  329. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +87 -116
  330. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +170 -0
  331. package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
  332. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  333. package/node_modules/pi-mcp-adapter/CHANGELOG.md +49 -0
  334. package/node_modules/pi-mcp-adapter/README.md +51 -6
  335. package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
  336. package/node_modules/pi-mcp-adapter/cli.js +188 -5
  337. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  338. package/node_modules/pi-mcp-adapter/config.ts +28 -10
  339. package/node_modules/pi-mcp-adapter/direct-tools.ts +23 -19
  340. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  341. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  342. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  343. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  344. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  345. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  346. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  347. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  348. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
  349. package/node_modules/pi-mcp-adapter/metadata-cache.ts +46 -25
  350. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  351. package/node_modules/pi-mcp-adapter/package.json +4 -1
  352. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  353. package/node_modules/pi-mcp-adapter/request-headers-command.ts +330 -0
  354. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  355. package/node_modules/pi-mcp-adapter/server-manager.ts +145 -2
  356. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  357. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  358. package/node_modules/pi-mcp-adapter/tool-metadata.ts +38 -18
  359. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  360. package/node_modules/pi-mcp-adapter/types.ts +95 -10
  361. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  362. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  363. package/node_modules/pi-web-access/CHANGELOG.md +37 -0
  364. package/node_modules/pi-web-access/README.md +38 -14
  365. package/node_modules/pi-web-access/anysearch.ts +4 -2
  366. package/node_modules/pi-web-access/auth-fetch.ts +148 -0
  367. package/node_modules/pi-web-access/brave.ts +16 -4
  368. package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
  369. package/node_modules/pi-web-access/curator-page.ts +8 -3
  370. package/node_modules/pi-web-access/curator-server.ts +5 -1
  371. package/node_modules/pi-web-access/exa.ts +18 -7
  372. package/node_modules/pi-web-access/extract.ts +134 -37
  373. package/node_modules/pi-web-access/fetch-params.ts +17 -3
  374. package/node_modules/pi-web-access/firecrawl.ts +172 -12
  375. package/node_modules/pi-web-access/gemini-search.ts +33 -7
  376. package/node_modules/pi-web-access/index.ts +160 -104
  377. package/node_modules/pi-web-access/package.json +2 -2
  378. package/node_modules/pi-web-access/page-query.ts +4 -2
  379. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  380. package/node_modules/pi-web-access/parallel.ts +2 -2
  381. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  382. package/node_modules/pi-web-access/serper.ts +211 -0
  383. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  384. package/node_modules/pi-web-access/summary-review.ts +51 -23
  385. package/node_modules/pi-web-access/tavily.ts +16 -4
  386. package/node_modules/pi-web-access/utils.ts +87 -0
  387. package/node_modules/pi-web-access/valyu.ts +199 -0
  388. package/node_modules/pi-web-access/youtube-extract.ts +2 -2
  389. package/package.json +13 -13
@@ -103,9 +103,11 @@ function parseResponse(value: unknown): AnySearchResponse {
103
103
  if (typeof title !== "string") throw invalidResponse(`expected data.results[${index}].title string`);
104
104
  if (typeof url !== "string") throw invalidResponse(`expected data.results[${index}].url string`);
105
105
  if (typeof snippet !== "string") throw invalidResponse(`expected data.results[${index}].snippet string`);
106
- if (typeof content !== "string") throw invalidResponse(`expected data.results[${index}].content string`);
106
+ if (content !== undefined && content !== null && typeof content !== "string") {
107
+ throw invalidResponse(`expected data.results[${index}].content string`);
108
+ }
107
109
  if (!url) throw invalidResponse(`expected data.results[${index}].url to be non-empty`);
108
- results.push({ title, url, snippet, content });
110
+ results.push({ title, url, snippet, content: typeof content === "string" ? content : "" });
109
111
  }
110
112
 
111
113
  return { code: 0, data: { results, metadata: data.metadata as Record<string, unknown> } };
@@ -0,0 +1,148 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { getWebSearchConfigPath } from "./utils.ts";
3
+
4
+ const WEB_SEARCH_CONFIG_PATH = getWebSearchConfigPath();
5
+ const AUTH_PROFILE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
6
+
7
+ export type AuthFetchRequest = true | string;
8
+ export type AuthFetchCache = "session" | "off";
9
+
10
+ export interface AuthFetchProfile {
11
+ name: string;
12
+ hosts: string[];
13
+ chromeProfile?: string;
14
+ redirects: "same-origin";
15
+ cache: AuthFetchCache;
16
+ }
17
+
18
+ interface AuthFetchConfigRoot {
19
+ authFetch?: unknown;
20
+ }
21
+
22
+ export function resolveAuthFetchProfile(request: AuthFetchRequest): AuthFetchProfile {
23
+ const profiles = loadAuthFetchProfiles();
24
+ if (profiles.length === 0) {
25
+ throw new Error(`auth requires at least one authFetch profile in ${WEB_SEARCH_CONFIG_PATH}`);
26
+ }
27
+ if (request === true) {
28
+ if (profiles.length !== 1) {
29
+ throw new Error("auth: true requires exactly one authFetch profile; use a profile name instead");
30
+ }
31
+ return profiles[0];
32
+ }
33
+ const name = request.trim();
34
+ const profile = profiles.find(candidate => candidate.name === name);
35
+ if (!profile) throw new Error(`Unknown authFetch profile: ${name}`);
36
+ return profile;
37
+ }
38
+
39
+ export function assertAuthFetchUrl(profile: AuthFetchProfile, rawUrl: string): URL {
40
+ let url: URL;
41
+ try {
42
+ url = new URL(rawUrl);
43
+ } catch (err) {
44
+ const message = err instanceof Error ? err.message : String(err);
45
+ throw new Error(`Authenticated fetch requires an HTTPS URL: ${message}`);
46
+ }
47
+ if (url.protocol !== "https:") throw new Error("Authenticated fetch requires an HTTPS URL");
48
+ const hostname = normalizeHostname(url.hostname);
49
+ if (!profile.hosts.some(host => hostMatches(hostname, host))) {
50
+ throw new Error(`URL host ${hostname} is not allowed by authFetch profile ${profile.name}`);
51
+ }
52
+ return url;
53
+ }
54
+
55
+ export function authFetchRedirectGuard(profile: AuthFetchProfile, from: URL, to: URL): void {
56
+ if (profile.redirects === "same-origin" && to.origin !== from.origin) {
57
+ throw new Error(`Authenticated fetch refused cross-origin redirect: ${from.origin} -> ${to.origin}`);
58
+ }
59
+ }
60
+
61
+ function loadAuthFetchProfiles(): AuthFetchProfile[] {
62
+ if (!existsSync(WEB_SEARCH_CONFIG_PATH)) return [];
63
+ const raw = readFileSync(WEB_SEARCH_CONFIG_PATH, "utf-8");
64
+ let parsed: AuthFetchConfigRoot;
65
+ try {
66
+ const value: unknown = JSON.parse(raw);
67
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("expected a JSON object");
68
+ parsed = value as AuthFetchConfigRoot;
69
+ } catch (err) {
70
+ const message = err instanceof Error ? err.message : String(err);
71
+ throw new Error(`Failed to parse ${WEB_SEARCH_CONFIG_PATH}: ${message}`);
72
+ }
73
+ if (parsed.authFetch === undefined || parsed.authFetch === null) return [];
74
+ if (typeof parsed.authFetch !== "object" || Array.isArray(parsed.authFetch)) {
75
+ throw new Error(`authFetch in ${WEB_SEARCH_CONFIG_PATH} must be an object`);
76
+ }
77
+ return Object.entries(parsed.authFetch as Record<string, unknown>).map(([name, value]) => parseProfile(name, value));
78
+ }
79
+
80
+ function parseProfile(name: string, value: unknown): AuthFetchProfile {
81
+ if (!AUTH_PROFILE_NAME_PATTERN.test(name)) {
82
+ throw new Error(`authFetch profile name ${JSON.stringify(name)} must start with a letter and contain only letters, numbers, underscores, or hyphens`);
83
+ }
84
+ if (Array.isArray(value)) {
85
+ return { name, hosts: parseHosts(value, `authFetch.${name}`), redirects: "same-origin", cache: "session" };
86
+ }
87
+ if (!value || typeof value !== "object") {
88
+ throw new Error(`authFetch.${name} in ${WEB_SEARCH_CONFIG_PATH} must be an array of hosts or an object`);
89
+ }
90
+ const config = value as Record<string, unknown>;
91
+ if (!Array.isArray(config.hosts)) {
92
+ throw new Error(`authFetch.${name}.hosts in ${WEB_SEARCH_CONFIG_PATH} must be a non-empty array of hostnames`);
93
+ }
94
+ const redirects = config.redirects ?? "same-origin";
95
+ if (redirects !== "same-origin") {
96
+ throw new Error(`authFetch.${name}.redirects in ${WEB_SEARCH_CONFIG_PATH} must be "same-origin"`);
97
+ }
98
+ const cache = config.cache ?? "session";
99
+ if (cache !== "session" && cache !== "off") {
100
+ throw new Error(`authFetch.${name}.cache in ${WEB_SEARCH_CONFIG_PATH} must be "session" or "off"`);
101
+ }
102
+ const chromeProfile = parseChromeProfile(config.chromeProfile, `authFetch.${name}.chromeProfile`);
103
+ return {
104
+ name,
105
+ hosts: parseHosts(config.hosts, `authFetch.${name}.hosts`),
106
+ ...(chromeProfile ? { chromeProfile } : {}),
107
+ redirects,
108
+ cache,
109
+ };
110
+ }
111
+
112
+ function parseHosts(value: unknown[], label: string): string[] {
113
+ if (value.length === 0) throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} must be a non-empty array of hostnames`);
114
+ const hosts = value.map((entry) => {
115
+ if (typeof entry !== "string") throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} must contain only hostnames`);
116
+ return parseHost(entry, label);
117
+ });
118
+ return [...new Set(hosts)];
119
+ }
120
+
121
+ function parseHost(value: string, label: string): string {
122
+ const host = normalizeHostname(value.trim());
123
+ if (!host || host.startsWith(".") || host.endsWith(".") || /\s|[\\/?:#@*]/.test(host)) {
124
+ throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} contains an invalid hostname: ${JSON.stringify(value)}`);
125
+ }
126
+ if (host.length > 253 || !/^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i.test(host)) {
127
+ throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} contains an invalid hostname: ${JSON.stringify(value)}`);
128
+ }
129
+ return host;
130
+ }
131
+
132
+ function parseChromeProfile(value: unknown, label: string): string | undefined {
133
+ if (value === undefined || value === null) return undefined;
134
+ if (typeof value !== "string") throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} must be a string`);
135
+ const normalized = value.trim();
136
+ if (!normalized || normalized === "." || normalized === ".." || normalized.includes("/") || normalized.includes("\\")) {
137
+ throw new Error(`${label} in ${WEB_SEARCH_CONFIG_PATH} must be a profile directory name, not a path`);
138
+ }
139
+ return normalized;
140
+ }
141
+
142
+ function normalizeHostname(hostname: string): string {
143
+ return hostname.toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "");
144
+ }
145
+
146
+ function hostMatches(hostname: string, allowedHost: string): boolean {
147
+ return hostname === allowedHost || hostname.endsWith(`.${allowedHost}`);
148
+ }
@@ -2,14 +2,15 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.ts";
3
3
  import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
4
4
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
5
- import { getWebSearchConfigPath } from "./utils.ts";
5
+ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
6
6
 
7
- const BRAVE_API_URL = "https://api.search.brave.com/res/v1/web/search";
7
+ const BRAVE_API_BASE_URL = "https://api.search.brave.com/res/v1";
8
8
  const CONFIG_PATH = getWebSearchConfigPath();
9
9
  const SEARCH_TIMEOUT_MS = 30_000;
10
10
 
11
11
  interface WebSearchConfig {
12
12
  braveApiKey?: unknown;
13
+ braveBaseUrl?: unknown;
13
14
  }
14
15
 
15
16
  interface NormalizedDomainFilters {
@@ -45,6 +46,16 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
45
46
  });
46
47
  }
47
48
 
49
+ function getApiUrl(): string {
50
+ return `${resolveApiBaseUrl({
51
+ configKey: "braveBaseUrl",
52
+ configuredValue: loadConfig().braveBaseUrl,
53
+ defaultValue: BRAVE_API_BASE_URL,
54
+ environmentKey: "BRAVE_BASE_URL",
55
+ environmentValue: process.env.BRAVE_BASE_URL,
56
+ })}/web/search`;
57
+ }
58
+
48
59
  function normalizeCount(value: number | undefined): number {
49
60
  if (typeof value !== "number" || !Number.isFinite(value)) return 5;
50
61
  return Math.max(1, Math.min(Math.floor(value), 20));
@@ -129,6 +140,7 @@ export async function searchWithBrave(
129
140
  query: string,
130
141
  options: SearchOptions = {},
131
142
  ): Promise<SearchResponse> {
143
+ const apiUrl = getApiUrl();
132
144
  const apiKey = await getApiKey(options.signal);
133
145
  if (!apiKey) {
134
146
  throw new Error(
@@ -160,7 +172,7 @@ export async function searchWithBrave(
160
172
  }
161
173
 
162
174
  try {
163
- const response = await fetch(`${BRAVE_API_URL}?${params.toString()}`, {
175
+ const response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
164
176
  method: "GET",
165
177
  headers: {
166
178
  "X-Subscription-Token": apiKey,
@@ -170,7 +182,7 @@ export async function searchWithBrave(
170
182
  signal: options.signal
171
183
  ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
172
184
  : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
173
- });
185
+ }, ["X-Subscription-Token"]);
174
186
 
175
187
  if (!response.ok) {
176
188
  activityMonitor.logError(activityId, `HTTP ${response.status}`);
@@ -1,7 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { pbkdf2Sync, createDecipheriv } from "node:crypto";
3
3
  import { copyFileSync, existsSync, mkdtempSync, readdirSync, realpathSync, rmSync } from "node:fs";
4
- import { tmpdir, homedir, platform } from "node:os";
4
+ import { tmpdir, homedir } from "node:os";
5
5
  import { isAbsolute, join, sep } from "node:path";
6
6
  import { isBrowserCookieAccessAllowed } from "./gemini-web-config.ts";
7
7
 
@@ -18,6 +18,12 @@ interface BrowserConfig {
18
18
  type SqliteRow = Record<string, unknown>;
19
19
  type SqliteFailure = "unavailable" | "query";
20
20
 
21
+ interface BrowserCookieEntry {
22
+ name: string;
23
+ value: string;
24
+ path: string;
25
+ }
26
+
21
27
  const GOOGLE_ORIGINS = [
22
28
  "https://gemini.google.com",
23
29
  "https://accounts.google.com",
@@ -33,6 +39,7 @@ const ALL_COOKIE_NAMES = new Set([
33
39
  const MACOS_BROWSER_CONFIGS: BrowserConfig[] = [
34
40
  { name: "Helium", baseDir: "Library/Application Support/net.imput.helium", keychainService: "Helium Storage Key", keychainAccount: "Helium" },
35
41
  { name: "Chrome", baseDir: "Library/Application Support/Google/Chrome", keychainService: "Chrome Safe Storage", keychainAccount: "Chrome" },
42
+ { name: "Brave", baseDir: "Library/Application Support/BraveSoftware/Brave-Browser", keychainService: "Brave Safe Storage", keychainAccount: "Brave" },
36
43
  { name: "Arc", baseDir: "Library/Application Support/Arc/User Data", keychainService: "Arc Safe Storage", keychainAccount: "Arc" },
37
44
  ];
38
45
 
@@ -50,16 +57,32 @@ export function getLastGoogleCookieDiagnostic(): string | null {
50
57
  return lastCookieDiagnostic;
51
58
  }
52
59
 
60
+ export function getLastBrowserCookieDiagnostic(): string | null {
61
+ return lastCookieDiagnostic;
62
+ }
63
+
53
64
  export async function getGoogleCookies(
54
65
  options?: { profile?: string; requiredCookies?: string[] },
55
66
  ): Promise<{ cookies: CookieMap; warnings: string[] } | null> {
67
+ return getBrowserCookiesForHosts({
68
+ hosts: GOOGLE_ORIGINS.map((origin) => new URL(origin).hostname),
69
+ profile: options?.profile,
70
+ requiredCookies: options?.requiredCookies,
71
+ cookieNames: ALL_COOKIE_NAMES,
72
+ requiredLabel: "Gemini",
73
+ });
74
+ }
75
+
76
+ export async function getBrowserCookiesForHosts(
77
+ options: { hosts: string[]; profile?: string; requiredCookies?: string[]; cookieNames?: Iterable<string>; requiredLabel?: string; requestUrl?: URL },
78
+ ): Promise<{ cookies: CookieMap; warnings: string[]; cookieHeader?: string } | null> {
56
79
  lastCookieDiagnostic = null;
57
80
  if (!isBrowserCookieAccessAllowed()) {
58
- lastCookieDiagnostic = "Browser cookie access is disabled; enable allowBrowserCookies to use Gemini Web cookies.";
81
+ lastCookieDiagnostic = "Browser cookie access is disabled; enable allowBrowserCookies to use browser cookies.";
59
82
  return null;
60
83
  }
61
84
 
62
- const currentPlatform = platform();
85
+ const currentPlatform = process.platform;
63
86
  const configs = currentPlatform === "darwin" ? MACOS_BROWSER_CONFIGS : currentPlatform === "linux" ? LINUX_BROWSER_CONFIGS : [];
64
87
  if (configs.length === 0) {
65
88
  lastCookieDiagnostic = "Chromium cookie extraction is unsupported on this platform.";
@@ -67,17 +90,23 @@ export async function getGoogleCookies(
67
90
  }
68
91
 
69
92
  const warningSet = new Set<string>();
70
- const rawProfile = typeof options?.profile === "string" ? options.profile.trim() : "";
71
- const requestedProfile = normalizeProfileName(options?.profile);
93
+ const rawProfile = typeof options.profile === "string" ? options.profile.trim() : "";
94
+ const requestedProfile = normalizeProfileName(options.profile);
72
95
  if (rawProfile && !requestedProfile) {
73
96
  lastCookieDiagnostic = "Configured Chromium profile must be a profile directory name, not a path.";
74
97
  return null;
75
98
  }
76
- const requiredCookies = normalizeCookieNames(options?.requiredCookies);
77
- const hosts = GOOGLE_ORIGINS.map((origin) => new URL(origin).hostname);
99
+ const requiredCookies = normalizeCookieNames(options.requiredCookies);
100
+ const cookieNames = normalizeCookieNames(options.cookieNames ? [...options.cookieNames] : undefined);
101
+ const hosts = normalizeHosts(options.hosts);
102
+ if (hosts.length === 0) {
103
+ lastCookieDiagnostic = "No valid cookie hosts were requested.";
104
+ return null;
105
+ }
78
106
  const home = homedir();
79
107
  let sawCookieDatabase = false;
80
108
  let sawRequiredCookies = false;
109
+ let sawAnyHostCookie = false;
81
110
  let sawBackendFailure: SqliteFailure | undefined;
82
111
  let sawUnsafeProfilePath = false;
83
112
 
@@ -117,25 +146,36 @@ export async function getGoogleCookies(
117
146
  const metaVersion = await readMetaVersion(tempDb);
118
147
  if (metaVersion.failure) sawBackendFailure = metaVersion.failure;
119
148
  if (metaVersion.value === null) continue;
120
- const rowsResult = await queryCookieRows(tempDb, hosts, ALL_COOKIE_NAMES);
149
+ const rowsResult = await queryCookieRows(tempDb, hosts, cookieNames ?? null, Boolean(options.requestUrl));
121
150
  if (rowsResult.status === "failure") {
122
151
  sawBackendFailure = rowsResult.failure;
123
152
  continue;
124
153
  }
125
154
 
155
+ const entries: BrowserCookieEntry[] = [];
126
156
  const cookies: CookieMap = {};
127
157
  for (const row of rowsResult.rows) {
128
158
  const name = typeof row.name === "string" ? row.name : "";
129
- if (!ALL_COOKIE_NAMES.has(name) || cookies[name]) continue;
159
+ if (!name) continue;
130
160
  let value = typeof row.value === "string" && row.value.length > 0 ? row.value : null;
131
161
  if (!value && typeof row.encrypted_value_hex === "string" && /^[0-9a-f]*$/i.test(row.encrypted_value_hex)) {
132
162
  value = decryptCookieValue(Buffer.from(row.encrypted_value_hex, "hex"), key, metaVersion.value >= 24);
133
163
  }
134
- if (value) cookies[name] = value;
164
+ if (!value) continue;
165
+ const path = typeof row.path === "string" && row.path.startsWith("/") ? row.path : "/";
166
+ if (options.requestUrl && !pathMatches(options.requestUrl.pathname || "/", path)) continue;
167
+ entries.push({ name, value, path });
168
+ if (!cookies[name]) cookies[name] = value;
135
169
  }
136
170
 
171
+ if (entries.length > 0) sawAnyHostCookie = true;
137
172
  if (requiredCookies?.length && !requiredCookies.every((name) => Boolean(cookies[name]))) continue;
138
- return { cookies, warnings: [...warningSet] };
173
+ if (entries.length === 0) continue;
174
+ return {
175
+ cookies,
176
+ warnings: [...warningSet],
177
+ ...(options.requestUrl ? { cookieHeader: buildCookieHeader(entries) } : {}),
178
+ };
139
179
  } finally {
140
180
  rmSync(tempDir, { recursive: true, force: true });
141
181
  }
@@ -153,7 +193,11 @@ export async function getGoogleCookies(
153
193
  ? `Chromium profile '${requestedProfile}' does not contain a cookie database.`
154
194
  : "No detected Chromium profile contains a cookie database.";
155
195
  } else if (requiredCookies?.length && !sawRequiredCookies) {
156
- lastCookieDiagnostic = "No detected Chromium profile contains the required Gemini cookies.";
196
+ lastCookieDiagnostic = `No detected Chromium profile contains the required ${options.requiredLabel ?? "browser"} cookies.`;
197
+ } else if (!sawAnyHostCookie) {
198
+ lastCookieDiagnostic = options.requestUrl
199
+ ? "No detected Chromium profile contains cookies for the requested URL."
200
+ : "No detected Chromium profile contains cookies for the requested host.";
157
201
  } else if (warningSet.size > 0) {
158
202
  lastCookieDiagnostic = [...warningSet][0];
159
203
  } else {
@@ -193,6 +237,10 @@ function normalizeCookieNames(names: string[] | undefined): string[] | undefined
193
237
  return normalized.length > 0 ? [...new Set(normalized)] : undefined;
194
238
  }
195
239
 
240
+ function normalizeHosts(hosts: string[]): string[] {
241
+ return [...new Set(hosts.map(host => host.trim().toLowerCase().replace(/^\[|\]$/g, "").replace(/\.$/, "")).filter(Boolean))];
242
+ }
243
+
196
244
  function listBrowserProfiles(home: string, config: BrowserConfig): string[] {
197
245
  const basePath = join(home, config.baseDir);
198
246
  if (!existsSync(basePath)) return ["Default"];
@@ -246,7 +294,7 @@ function removePkcs7Padding(buf: Buffer): Buffer {
246
294
  return !padding || padding > 16 ? buf : buf.subarray(0, buf.length - padding);
247
295
  }
248
296
 
249
- function readBrowserPassword(config: BrowserConfig, currentPlatform: ReturnType<typeof platform>): Promise<string | null> {
297
+ function readBrowserPassword(config: BrowserConfig, currentPlatform: typeof process.platform): Promise<string | null> {
250
298
  const cacheKey = `${currentPlatform}:${config.name}`;
251
299
  const cached = browserPasswordCache.get(cacheKey);
252
300
  if (cached) return cached;
@@ -386,19 +434,58 @@ async function hasCookieNames(dbPath: string, hosts: string[], names: string[]):
386
434
  return { present: names.every((name) => present.has(name)) };
387
435
  }
388
436
 
389
- async function queryCookieRows(dbPath: string, hosts: string[], names: Iterable<string>): Promise<QueryResult> {
390
- return runSqliteQuery(dbPath, `SELECT name, value, host_key, hex(encrypted_value) AS encrypted_value_hex FROM cookies WHERE ${buildCookieWhere(hosts, names)} ORDER BY expires_utc DESC`);
437
+ async function queryCookieRows(dbPath: string, hosts: string[], names: Iterable<string> | null, filterExpired: boolean): Promise<QueryResult> {
438
+ const columns = await readCookieColumns(dbPath);
439
+ if (columns.status === "failure") return columns;
440
+ const pathExpr = columns.columns.has("path") ? "path" : "'/' AS path";
441
+ const expiresExpr = columns.columns.has("expires_utc") ? "expires_utc" : "0 AS expires_utc";
442
+ const expiryFilter = filterExpired && columns.columns.has("expires_utc") ? ` AND (expires_utc = 0 OR expires_utc > ${chromeExpiryNowMicros()})` : "";
443
+ const partitionFilter = filterExpired ? unpartitionedCookieFilter(columns.columns) : "";
444
+ return runSqliteQuery(dbPath, `SELECT name, value, host_key, ${pathExpr}, ${expiresExpr}, hex(encrypted_value) AS encrypted_value_hex FROM cookies WHERE ${buildCookieWhere(hosts, names ?? undefined)}${expiryFilter}${partitionFilter} ORDER BY length(path) DESC, expires_utc ASC`);
445
+ }
446
+
447
+ async function readCookieColumns(dbPath: string): Promise<{ status: "success"; columns: Set<string> } | { status: "failure"; failure: SqliteFailure }> {
448
+ const result = await runSqliteQuery(dbPath, "PRAGMA table_info(cookies)");
449
+ if (result.status === "failure") return result;
450
+ return { status: "success", columns: new Set(result.rows.map(row => typeof row.name === "string" ? row.name : "")) };
451
+ }
452
+
453
+ function chromeExpiryNowMicros(): number {
454
+ return (Date.now() + 11644473600000) * 1000;
455
+ }
456
+
457
+ function unpartitionedCookieFilter(columns: Set<string>): string {
458
+ const clauses: string[] = [];
459
+ if (columns.has("top_frame_site_key")) clauses.push("(top_frame_site_key IS NULL OR top_frame_site_key = '')");
460
+ if (columns.has("partition_key")) clauses.push("(partition_key IS NULL OR partition_key = '')");
461
+ if (columns.has("is_partitioned")) clauses.push("(is_partitioned IS NULL OR is_partitioned = 0)");
462
+ return clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "";
463
+ }
464
+
465
+ function buildCookieHeader(entries: BrowserCookieEntry[]): string {
466
+ return entries
467
+ .sort((a, b) => b.path.length - a.path.length)
468
+ .map(({ name, value }) => `${name}=${value}`)
469
+ .join("; ");
470
+ }
471
+
472
+ function pathMatches(requestPath: string, cookiePath: string): boolean {
473
+ if (requestPath === cookiePath) return true;
474
+ if (!requestPath.startsWith(cookiePath)) return false;
475
+ if (cookiePath.endsWith("/")) return true;
476
+ return requestPath[cookiePath.length] === "/";
391
477
  }
392
478
 
393
479
  function buildCookieWhere(hosts: string[], cookieNames?: Iterable<string>): string {
394
480
  const hostClauses: string[] = [];
395
481
  for (const host of hosts) {
396
- for (const candidate of expandHosts(host)) {
397
- const escaped = escapeSqlString(candidate);
398
- hostClauses.push(`host_key = '${escaped}'`, `host_key = '.${escaped}'`, `host_key LIKE '%.${escaped}'`);
482
+ const escapedHost = escapeSqlString(host);
483
+ hostClauses.push(`host_key = '${escapedHost}'`);
484
+ for (const candidate of domainCookieHosts(host)) {
485
+ hostClauses.push(`host_key = '.${escapeSqlString(candidate)}'`);
399
486
  }
400
487
  }
401
- let where = `(${hostClauses.join(" OR ")})`;
488
+ let where = `(${[...new Set(hostClauses)].join(" OR ")})`;
402
489
  const names = cookieNames ? [...cookieNames].filter(Boolean) : [];
403
490
  if (names.length) where += ` AND name IN (${names.map((name) => `'${escapeSqlString(name)}'`).join(", ")})`;
404
491
  return where;
@@ -408,11 +495,11 @@ function escapeSqlString(value: string): string {
408
495
  return value.replaceAll("'", "''");
409
496
  }
410
497
 
411
- function expandHosts(host: string): string[] {
498
+ function domainCookieHosts(host: string): string[] {
412
499
  const parts = host.split(".").filter(Boolean);
413
- if (parts.length <= 1) return [host];
414
- const candidates = new Set([host]);
415
- for (let i = 1; i <= parts.length - 2; i++) candidates.add(parts.slice(i).join("."));
500
+ if (parts.length <= 1) return [];
501
+ const candidates = new Set<string>();
502
+ for (let i = 0; i <= parts.length - 2; i++) candidates.add(parts.slice(i).join("."));
416
503
  return [...candidates];
417
504
  }
418
505
 
@@ -8,7 +8,7 @@ function safeInlineJSON(data: unknown): string {
8
8
  }
9
9
 
10
10
  function buildProviderButtons(
11
- available: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean },
11
+ available: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean },
12
12
  selected: string,
13
13
  hasInitialQueries: boolean,
14
14
  ): string {
@@ -18,11 +18,13 @@ function buildProviderButtons(
18
18
  { value: "exa", label: "Exa", available: available.exa },
19
19
  { value: "brave", label: "Brave", available: available.brave },
20
20
  { value: "parallel", label: "Parallel", available: available.parallel },
21
+ { value: "parallel-mcp", label: "Parallel MCP", available: available["parallel-mcp"] },
21
22
  { value: "tinyfish", label: "TinyFish", available: available.tinyfish },
22
23
  { value: "search1api", label: "Search1API", available: available.search1api },
23
24
  { value: "searchinfinity", label: "Searchinfinity", available: available.searchinfinity },
24
25
  { value: "querit", label: "Querit", available: available.querit },
25
26
  { value: "tavily", label: "Tavily", available: available.tavily },
27
+ { value: "firecrawl", label: "Firecrawl", available: available.firecrawl },
26
28
  { value: "jina", label: "Jina", available: available.jina },
27
29
  { value: "serpdive", label: "SERPdive", available: available.serpdive },
28
30
  { value: "kagi", label: "Kagi", available: available.kagi },
@@ -36,6 +38,8 @@ function buildProviderButtons(
36
38
  { value: "xai", label: "xAI", available: available.xai },
37
39
  { value: "brightdata", label: "Bright Data", available: available.brightdata },
38
40
  { value: "serpbase", label: "SerpBase", available: available.serpbase },
41
+ { value: "serper", label: "Serper", available: available.serper },
42
+ { value: "valyu", label: "Valyu", available: available.valyu },
39
43
  ];
40
44
 
41
45
  return providers
@@ -54,7 +58,7 @@ export function generateCuratorPage(
54
58
  queries: string[],
55
59
  sessionToken: string,
56
60
  timeout: number,
57
- availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean },
61
+ availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean },
58
62
  defaultProvider: string,
59
63
  searchProvider: string,
60
64
  summaryModels: Array<{ value: string; label: string }>,
@@ -1455,7 +1459,7 @@ const SCRIPT = `(function() {
1455
1459
  var token = DATA.sessionToken;
1456
1460
  var timeoutSec = DATA.timeout;
1457
1461
  var queries = Array.isArray(DATA.queries) ? DATA.queries : [];
1458
- var providers = ["all", "openai", "exa", "brave", "parallel", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "anysearch", "xai", "brightdata", "serpbase"];
1462
+ var providers = ["all", "openai", "exa", "brave", "parallel", "parallel-mcp", "tinyfish", "search1api", "searchinfinity", "querit", "tavily", "firecrawl", "jina", "serpdive", "kagi", "bocha", "ollama", "searxng", "duckduckgo", "perplexity", "gemini", "anysearch", "xai", "brightdata", "serpbase", "serper", "valyu"];
1459
1463
  var availProviders = DATA.availableProviders && typeof DATA.availableProviders === "object" ? DATA.availableProviders : {};
1460
1464
  var workflow = "summary-review";
1461
1465
  var initialDefaultProvider = typeof DATA.defaultProvider === "string" ? DATA.defaultProvider : "exa";
@@ -1667,6 +1671,7 @@ const SCRIPT = `(function() {
1667
1671
  if (provider === "searchinfinity") return "Searchinfinity";
1668
1672
  if (provider === "querit") return "Querit";
1669
1673
  if (provider === "tavily") return "Tavily";
1674
+ if (provider === "firecrawl") return "Firecrawl";
1670
1675
  if (provider === "jina") return "Jina";
1671
1676
  if (provider === "serpdive") return "SERPdive";
1672
1677
  if (provider === "kagi") return "Kagi";
@@ -18,7 +18,7 @@ export interface CuratorServerOptions {
18
18
  queries: string[];
19
19
  sessionToken: string;
20
20
  timeout: number;
21
- availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean };
21
+ availableProviders: { all: boolean; openai: boolean; brave: boolean; parallel: boolean; "parallel-mcp": boolean; tinyfish: boolean; search1api: boolean; searchinfinity: boolean; querit: boolean; tavily: boolean; firecrawl: boolean; jina: boolean; serpdive: boolean; kagi: boolean; bocha: boolean; ollama: boolean; searxng: boolean; duckduckgo: boolean; perplexity: boolean; exa: boolean; gemini: boolean; anysearch: boolean; xai: boolean; brightdata: boolean; serpbase: boolean; serper: boolean; valyu: boolean };
22
22
  defaultProvider: string;
23
23
  searchProvider: string;
24
24
  summaryModels: Array<{ value: string; label: string }>;
@@ -275,11 +275,13 @@ export function startCuratorServer(
275
275
  if (provider === "openai") return availableProviders.openai;
276
276
  if (provider === "brave") return availableProviders.brave;
277
277
  if (provider === "parallel") return availableProviders.parallel;
278
+ if (provider === "parallel-mcp") return availableProviders["parallel-mcp"];
278
279
  if (provider === "tinyfish") return availableProviders.tinyfish;
279
280
  if (provider === "search1api") return availableProviders.search1api;
280
281
  if (provider === "searchinfinity") return availableProviders.searchinfinity;
281
282
  if (provider === "querit") return availableProviders.querit;
282
283
  if (provider === "tavily") return availableProviders.tavily;
284
+ if (provider === "firecrawl") return availableProviders.firecrawl;
283
285
  if (provider === "jina") return availableProviders.jina;
284
286
  if (provider === "serpdive") return availableProviders.serpdive;
285
287
  if (provider === "kagi") return availableProviders.kagi;
@@ -294,6 +296,8 @@ export function startCuratorServer(
294
296
  if (provider === "xai") return availableProviders.xai;
295
297
  if (provider === "brightdata") return availableProviders.brightdata;
296
298
  if (provider === "serpbase") return availableProviders.serpbase;
299
+ if (provider === "serper") return availableProviders.serper;
300
+ if (provider === "valyu") return availableProviders.valyu;
297
301
  return false;
298
302
  }
299
303
 
@@ -3,10 +3,9 @@ import { activityMonitor } from "./activity.ts";
3
3
  import type { ExtractedContent } from "./extract.ts";
4
4
  import type { SearchOptions, SearchResponse } from "./perplexity.ts";
5
5
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
6
- import { getWebSearchConfigPath } from "./utils.ts";
6
+ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
7
7
 
8
- const EXA_ANSWER_URL = "https://api.exa.ai/answer";
9
- const EXA_SEARCH_URL = "https://api.exa.ai/search";
8
+ const EXA_API_BASE_URL = "https://api.exa.ai";
10
9
  const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
11
10
  const CONFIG_PATH = getWebSearchConfigPath();
12
11
  const EXA_MCP_ADVANCED_TOOL = "web_search_advanced_exa";
@@ -14,6 +13,7 @@ const EXA_MCP_BASIC_TOOL = "web_search_exa";
14
13
 
15
14
  interface WebSearchConfig {
16
15
  exaApiKey?: unknown;
16
+ exaBaseUrl?: unknown;
17
17
  }
18
18
 
19
19
  interface ExaAnswerResponse {
@@ -80,6 +80,16 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
80
80
  });
81
81
  }
82
82
 
83
+ function getApiBaseUrl(): string {
84
+ return resolveApiBaseUrl({
85
+ configKey: "exaBaseUrl",
86
+ configuredValue: loadConfig().exaBaseUrl,
87
+ defaultValue: EXA_API_BASE_URL,
88
+ environmentKey: "EXA_BASE_URL",
89
+ environmentValue: process.env.EXA_BASE_URL,
90
+ });
91
+ }
92
+
83
93
  function exaApiHeaders(apiKey: string): Record<string, string> {
84
94
  return {
85
95
  "x-api-key": apiKey,
@@ -447,6 +457,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
447
457
  return searchWithExaMcp(query, options);
448
458
  }
449
459
 
460
+ const apiBaseUrl = getApiBaseUrl();
450
461
  const useSearch = options.includeContent
451
462
  || !!options.recencyFilter
452
463
  || !!options.domainFilter?.length
@@ -456,12 +467,12 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
456
467
 
457
468
  try {
458
469
  if (!useSearch) {
459
- const response = await fetch(EXA_ANSWER_URL, {
470
+ const response = await fetchWithCredentialRedirects(`${apiBaseUrl}/answer`, {
460
471
  method: "POST",
461
472
  headers: exaApiHeaders(apiKey),
462
473
  body: JSON.stringify({ query }),
463
474
  signal: requestSignal(options.signal),
464
- });
475
+ }, ["x-api-key"]);
465
476
 
466
477
  if (!response.ok) {
467
478
  const errorText = redactCredential(await response.text(), apiKey);
@@ -476,7 +487,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
476
487
  };
477
488
  }
478
489
 
479
- const response = await fetch(EXA_SEARCH_URL, {
490
+ const response = await fetchWithCredentialRedirects(`${apiBaseUrl}/search`, {
480
491
  method: "POST",
481
492
  headers: exaApiHeaders(apiKey),
482
493
  body: JSON.stringify({
@@ -486,7 +497,7 @@ export async function searchWithExa(query: string, options: ExaSearchOptions = {
486
497
  : { highlights: true },
487
498
  }),
488
499
  signal: requestSignal(options.signal),
489
- });
500
+ }, ["x-api-key"]);
490
501
 
491
502
  if (!response.ok) {
492
503
  const errorText = redactCredential(await response.text(), apiKey);