@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.
- package/README.md +5 -5
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +11 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +24 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +91 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +74 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts +7 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +21 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +4 -5
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/markdown-enhance.ts +53 -14
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +22 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +2 -4
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +18 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +8 -4
- package/node_modules/@herbertgao/pi-subagents/src/agent-color.ts +72 -67
- package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +255 -0
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +191 -3
- package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +20 -4
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +289 -113
- package/node_modules/@herbertgao/pi-subagents/src/output-file.ts +24 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +24 -2
- package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +15 -6
- package/node_modules/@herbertgao/pi-subagents/src/ui/schedule-menu.ts +9 -8
- package/node_modules/@herbertgao/pi-subagents/src/ui/select-item.ts +48 -0
- package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +9 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +2 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +20 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +6 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/docs/keyboard.md +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +7 -0
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +16 -5
- package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +4 -2
- package/node_modules/@narumitw/pi-btw/README.md +32 -24
- package/node_modules/@narumitw/pi-btw/package.json +6 -6
- package/node_modules/@narumitw/pi-btw/src/btw.ts +4 -2
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -13
- package/node_modules/@narumitw/pi-btw/src/settings.ts +22 -2
- package/node_modules/pi-lens/CHANGELOG.md +2535 -954
- package/node_modules/pi-lens/README.md +13 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +73 -18
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +314 -0
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +21 -4
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +14 -7
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +68 -16
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +37 -10
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +63 -0
- package/node_modules/pi-lens/dist/clients/atomic-write.js +114 -14
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +193 -23
- package/node_modules/pi-lens/dist/clients/biome-client.js +136 -24
- package/node_modules/pi-lens/dist/clients/bootstrap.js +11 -2
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +34 -0
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +192 -0
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +62 -6
- package/node_modules/pi-lens/dist/clients/bus-publish.js +19 -4
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +47 -5
- package/node_modules/pi-lens/dist/clients/cache-manager.js +97 -9
- package/node_modules/pi-lens/dist/clients/cache-observability.js +301 -14
- package/node_modules/pi-lens/dist/clients/call-graph.js +449 -117
- package/node_modules/pi-lens/dist/clients/cascade-format.js +57 -2
- package/node_modules/pi-lens/dist/clients/child-unref.js +85 -0
- package/node_modules/pi-lens/dist/clients/codebase-model.js +52 -23
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +40 -0
- package/node_modules/pi-lens/dist/clients/complexity-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +26 -0
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +42 -0
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +137 -31
- package/node_modules/pi-lens/dist/clients/debug-handles.js +206 -0
- package/node_modules/pi-lens/dist/clients/debug-heap.js +167 -0
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +153 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +361 -77
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +65 -13
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +176 -29
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +23 -7
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +8 -18
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +8 -5
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +35 -0
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +1172 -683
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +14 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +50 -0
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +135 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +23 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +5 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +154 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +3 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +47 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +9 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +4 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +163 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +37 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +3 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +13 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +226 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +69 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +3 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +473 -42
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +22 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +97 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +5 -4
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +18 -4
- package/node_modules/pi-lens/dist/clients/durable-store.js +97 -0
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +17 -0
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +94 -3
- package/node_modules/pi-lens/dist/clients/extension-log.js +432 -0
- package/node_modules/pi-lens/dist/clients/extension-mode.js +73 -0
- package/node_modules/pi-lens/dist/clients/file-kinds.js +57 -0
- package/node_modules/pi-lens/dist/clients/file-role.js +18 -3
- package/node_modules/pi-lens/dist/clients/file-utils.js +56 -34
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +5 -1
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +51 -7
- package/node_modules/pi-lens/dist/clients/format-service.js +24 -8
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +358 -61
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +148 -15
- package/node_modules/pi-lens/dist/clients/git-guard.js +678 -11
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +4 -0
- package/node_modules/pi-lens/dist/clients/go-client.js +23 -40
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +124 -7
- package/node_modules/pi-lens/dist/clients/grammar-source.js +12 -4
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +21 -3
- package/node_modules/pi-lens/dist/clients/host-ports.js +31 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +395 -100
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +142 -172
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +27 -55
- package/node_modules/pi-lens/dist/clients/knip-client.js +58 -42
- package/node_modules/pi-lens/dist/clients/language-policy.js +14 -2
- package/node_modules/pi-lens/dist/clients/language-profile.js +13 -7
- package/node_modules/pi-lens/dist/clients/latency-logger.js +72 -1
- package/node_modules/pi-lens/dist/clients/lens-config.js +12 -1
- package/node_modules/pi-lens/dist/clients/lens-engine.js +86 -17
- package/node_modules/pi-lens/dist/clients/lens-events.js +63 -9
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +16 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +94 -19
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +73 -0
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +2 -1
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +30 -4
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +59 -14
- package/node_modules/pi-lens/dist/clients/lsp/client.js +662 -56
- package/node_modules/pi-lens/dist/clients/lsp/config.js +13 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +115 -0
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +1012 -121
- package/node_modules/pi-lens/dist/clients/lsp/index.js +2025 -800
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +32 -26
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +83 -0
- package/node_modules/pi-lens/dist/clients/lsp/server.js +437 -86
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +96 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +21 -5
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +18 -3
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +28 -4
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +8 -0
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +248 -0
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +127 -32
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +157 -24
- package/node_modules/pi-lens/dist/clients/mcp/session.js +235 -14
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +115 -0
- package/node_modules/pi-lens/dist/clients/metrics-client.js +2 -1
- package/node_modules/pi-lens/dist/clients/metrics-history.js +2 -1
- package/node_modules/pi-lens/dist/clients/model-provider.js +53 -0
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +5 -0
- package/node_modules/pi-lens/dist/clients/module-report.js +168 -3
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +319 -118
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +1 -1
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +120 -5
- package/node_modules/pi-lens/dist/clients/path-utils.js +110 -9
- package/node_modules/pi-lens/dist/clients/pipeline.js +76 -15
- package/node_modules/pi-lens/dist/clients/print-mode.js +21 -0
- package/node_modules/pi-lens/dist/clients/project-changes.js +40 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +3 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +1 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +8 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +23 -2
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +21 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +86 -9
- package/node_modules/pi-lens/dist/clients/project-report.js +27 -13
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +175 -20
- package/node_modules/pi-lens/dist/clients/project-trust.js +181 -0
- package/node_modules/pi-lens/dist/clients/quiet-window.js +18 -4
- package/node_modules/pi-lens/dist/clients/read-bridge.js +147 -0
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +159 -1
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +150 -29
- package/node_modules/pi-lens/dist/clients/read-guard.js +185 -4
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +70 -84
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +1122 -221
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +2 -1
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +21 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +24 -0
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +46 -0
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +57 -6
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +51 -3
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +30 -1
- package/node_modules/pi-lens/dist/clients/ruff-client.js +27 -44
- package/node_modules/pi-lens/dist/clients/run-duration.js +55 -0
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +257 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +129 -12
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +242 -26
- package/node_modules/pi-lens/dist/clients/runtime-session.js +428 -117
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +71 -48
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +342 -33
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +619 -93
- package/node_modules/pi-lens/dist/clients/rust-client.js +23 -38
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +777 -62
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +90 -6
- package/node_modules/pi-lens/dist/clients/session-state-store.js +3 -2
- package/node_modules/pi-lens/dist/clients/sg-runner.js +424 -196
- package/node_modules/pi-lens/dist/clients/slow-fs.js +2 -1
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +231 -0
- package/node_modules/pi-lens/dist/clients/source-filter.js +184 -20
- package/node_modules/pi-lens/dist/clients/source-walker.js +66 -10
- package/node_modules/pi-lens/dist/clients/startup-scan.js +15 -16
- package/node_modules/pi-lens/dist/clients/startup-timing.js +7 -1
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +469 -32
- package/node_modules/pi-lens/dist/clients/tool-event.js +35 -0
- package/node_modules/pi-lens/dist/clients/tool-policy.js +390 -39
- package/node_modules/pi-lens/dist/clients/tool-render.js +155 -0
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +76 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +6 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +419 -50
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +21 -0
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +7 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +7 -2
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +44 -7
- package/node_modules/pi-lens/dist/clients/user-notify.js +51 -0
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +57 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +50 -13
- package/node_modules/pi-lens/dist/clients/widget-state.js +300 -43
- package/node_modules/pi-lens/dist/clients/word-index.js +680 -81
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +89 -6
- package/node_modules/pi-lens/dist/index.js +51785 -36118
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +182 -29
- package/node_modules/pi-lens/dist/mcp/server.js +219 -46
- package/node_modules/pi-lens/dist/tools/activate-tools.js +17 -5
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +9 -4
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +93 -27
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +5 -2
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +203 -38
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +210 -42
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -3
- package/node_modules/pi-lens/dist/tools/module-report.js +9 -0
- package/node_modules/pi-lens/dist/tools/symbol-search.js +1 -1
- package/node_modules/pi-lens/docs/agent-guide.md +40 -15
- package/node_modules/pi-lens/docs/agent-tools.md +21 -10
- package/node_modules/pi-lens/docs/analysisall.md +192 -0
- package/node_modules/pi-lens/docs/api-ports-inventory.md +165 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +14 -3
- package/node_modules/pi-lens/docs/durable-store-audit-1202.md +32 -0
- package/node_modules/pi-lens/docs/environment-variables.md +44 -0
- package/node_modules/pi-lens/docs/fable.md +27 -0
- package/node_modules/pi-lens/docs/features.md +45 -3
- package/node_modules/pi-lens/docs/globalconfig.md +56 -1
- package/node_modules/pi-lens/docs/language-coverage.md +4 -3
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +2 -2
- package/node_modules/pi-lens/docs/mcp.md +37 -4
- package/node_modules/pi-lens/docs/servercapabilities.md +3 -3
- package/node_modules/pi-lens/docs/settings.md +18 -1
- package/node_modules/pi-lens/docs/tools.md +4 -1
- package/node_modules/pi-lens/docs/usage.md +41 -5
- package/node_modules/pi-lens/docs/word-index.md +35 -0
- package/node_modules/pi-lens/package.json +10 -10
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-except-test.yml +49 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-host-path-in-win32-branch-test.yml +87 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-js-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-conditional-empty-object-spread-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-nested-links-test.yml +11 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-json-store-write-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +8 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-laundering-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-win32-isabsolute-for-qualification-test.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-except.yml +3 -15
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread-js.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-conditional-empty-object-spread.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-nested-links.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +27 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +23 -0
- package/node_modules/pi-lens/rules/tree-sitter-queries/tsx/no-nested-links.yml +4 -6
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/duplicate-function-arg.yml +2 -2
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +134 -2
- package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +10 -11
- package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +22 -22
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +8 -114
- package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +129 -0
- package/node_modules/pi-lens/skills/pi-lens-write-tree-sitter-rule/SKILL.md +3 -1
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +38 -0
- package/node_modules/pi-mcp-adapter/README.md +38 -4
- package/node_modules/pi-mcp-adapter/agent-dir.ts +12 -4
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +1 -1
- package/node_modules/pi-mcp-adapter/cli.js +25 -4
- package/node_modules/pi-mcp-adapter/commands.ts +15 -7
- package/node_modules/pi-mcp-adapter/config.ts +4 -4
- package/node_modules/pi-mcp-adapter/direct-tools.ts +58 -8
- package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -8
- package/node_modules/pi-mcp-adapter/index.ts +8 -3
- package/node_modules/pi-mcp-adapter/init.ts +29 -5
- package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -4
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +60 -2
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +45 -9
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +22 -20
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +2 -1
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +37 -4
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +81 -26
- package/node_modules/pi-mcp-adapter/package.json +4 -3
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +108 -26
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +336 -0
- package/node_modules/pi-mcp-adapter/search-ranking.ts +86 -7
- package/node_modules/pi-mcp-adapter/server-manager.ts +41 -7
- package/node_modules/pi-mcp-adapter/session-recovery.ts +8 -2
- package/node_modules/pi-mcp-adapter/tool-approval.ts +48 -7
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +62 -4
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +138 -12
- package/node_modules/pi-mcp-adapter/types.ts +176 -27
- package/node_modules/pi-mcp-adapter/ui-server.ts +20 -1
- package/node_modules/pi-web-access/CHANGELOG.md +14 -0
- package/node_modules/pi-web-access/README.md +18 -12
- package/node_modules/pi-web-access/auth-fetch.ts +148 -0
- package/node_modules/pi-web-access/chrome-cookies.ts +110 -23
- package/node_modules/pi-web-access/curator-page.ts +5 -3
- package/node_modules/pi-web-access/curator-server.ts +2 -1
- package/node_modules/pi-web-access/extract.ts +106 -34
- package/node_modules/pi-web-access/fetch-params.ts +17 -3
- package/node_modules/pi-web-access/firecrawl.ts +172 -12
- package/node_modules/pi-web-access/gemini-search.ts +18 -4
- package/node_modules/pi-web-access/index.ts +120 -48
- package/node_modules/pi-web-access/package.json +2 -2
- package/node_modules/pi-web-access/summary-review.ts +11 -5
- package/node_modules/pi-web-access/youtube-extract.ts +2 -2
- package/package.json +12 -13
- package/node_modules/pi-lens/dist/clients/ast-grep-parser.js +0 -86
- package/node_modules/pi-lens/dist/clients/lsp/interactive-install.js +0 -367
- package/node_modules/pi-lens/dist/clients/lsp/lsp-index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/server-strategies.js +0 -5
- package/node_modules/pi-lens/dist/clients/project-metadata.js +0 -690
- package/node_modules/pi-lens/dist/clients/source-groups.js +0 -96
- package/node_modules/pi-lens/dist/clients/startup-marker.js +0 -6
- 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("
|
|
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
|
-
|
|
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
|
-
|
|
377
|
+
const info = isVideoFile(url);
|
|
378
|
+
return info ? { status: "video", info } : { status: "not-video" };
|
|
319
379
|
} catch (err) {
|
|
320
|
-
return {
|
|
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.
|
|
451
|
+
if (localVideo.status === "invalid") {
|
|
384
452
|
return { url, title: "", content: "", error: localVideo.error };
|
|
385
453
|
}
|
|
386
|
-
if (localVideo.
|
|
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.
|
|
534
|
+
if (localVideo.status === "invalid") {
|
|
467
535
|
return { url, title: "", content: "", error: localVideo.error };
|
|
468
536
|
}
|
|
469
|
-
if (localVideo.
|
|
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.
|
|
566
|
+
if (localVideo.status === "invalid") {
|
|
499
567
|
return { url, title: "", content: "", error: localVideo.error };
|
|
500
568
|
}
|
|
501
|
-
if (localVideo.
|
|
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
|
|
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
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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 =
|
|
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
|
|
78
|
-
if (
|
|
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 {
|
|
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 ??
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
380
|
+
throw new Error(`Firecrawl ${label} unsuccessful: ${redactCredential(reason, apiKey)}`);
|
|
239
381
|
}
|
|
240
382
|
if (envelope.success !== true) {
|
|
241
|
-
throw new Error(
|
|
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"
|