web-access-cli 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +104 -207
  2. package/README_CN.md +106 -209
  3. package/THIRD_PARTY_NOTICES.md +0 -13
  4. package/dist/config/config.js +10 -2
  5. package/dist/config/config.js.map +1 -1
  6. package/dist/core/router.js +4 -2
  7. package/dist/core/router.js.map +1 -1
  8. package/dist/core/schema.js +1 -1
  9. package/dist/core/types.js +1 -0
  10. package/dist/core/types.js.map +1 -1
  11. package/dist/gateway/admin.js +670 -0
  12. package/dist/gateway/admin.js.map +1 -0
  13. package/dist/gateway/panel.js +2405 -0
  14. package/dist/gateway/panel.js.map +1 -0
  15. package/dist/gateway/records.js +464 -0
  16. package/dist/gateway/records.js.map +1 -0
  17. package/dist/gateway/schema.js +115 -0
  18. package/dist/gateway/schema.js.map +1 -0
  19. package/dist/gateway/server.js +663 -0
  20. package/dist/gateway/server.js.map +1 -0
  21. package/dist/gateway/service.js +607 -0
  22. package/dist/gateway/service.js.map +1 -0
  23. package/dist/providers/anysearch.js +5 -3
  24. package/dist/providers/anysearch.js.map +1 -1
  25. package/dist/providers/deepseek.js +173 -106
  26. package/dist/providers/deepseek.js.map +1 -1
  27. package/dist/providers/registry.js +2 -0
  28. package/dist/providers/registry.js.map +1 -1
  29. package/dist/providers/search.js +4 -2
  30. package/dist/providers/search.js.map +1 -1
  31. package/dist/providers/xai.js +16 -8
  32. package/dist/providers/xai.js.map +1 -1
  33. package/dist/providers/xcrawl.js +1 -1
  34. package/dist/providers/xcrawl.js.map +1 -1
  35. package/dist/providers/yuanbao.js +214 -0
  36. package/dist/providers/yuanbao.js.map +1 -0
  37. package/dist/server.js +127 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/transport/http.js +8 -0
  40. package/dist/transport/http.js.map +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +4 -2
  43. package/schemas/config.schema.json +4 -0
  44. package/schemas/gatewayError.schema.json +33 -0
  45. package/schemas/gatewayFetchRequest.schema.json +13 -0
  46. package/schemas/gatewayFetchResponse.schema.json +16 -0
  47. package/schemas/gatewaySearchRequest.schema.json +37 -0
  48. package/schemas/gatewaySearchResponse.schema.json +81 -0
  49. package/schemas/output.schema.json +52 -0
  50. package/schemas/searchRequest.schema.json +1 -1
package/README.md CHANGED
@@ -5,277 +5,174 @@ English | [简体中文](README_CN.md)
5
5
  [![CI](https://github.com/Juexe/web-access-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/Juexe/web-access-cli/actions/workflows/ci.yml)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- An agent-neutral CLI for web capabilities. It defines search and web content extraction as stable capabilities while normalizing differences among Tavily, Exa, Bocha, Brave, SearXNG, AnySearch, XCrawl, DeepSeek, Firecrawl, Jina, and other providers behind a unified internal schema.
8
+ Two entry points over one core: a **CLI** for direct invocation and a **DeepSeek-compatible HTTP gateway** for long-running agent runtimes. Both expose the same two capabilities — search and content extraction — and normalize Tavily, Exa, Bocha, Yuanbao Search, Brave, SearXNG, AnySearch, XCrawl, DeepSeek, Firecrawl, Jina, and others behind one schema.
9
9
 
10
- The CLI is the primary product interface. It is not tied to Pi, Claude Code, Codex, Cursor, OpenCode, or any other agent. Skills, MCP integrations, and agent plugins may only integrate as adapters on top of the CLI; they must not leak into the core capabilities or provider implementations.
10
+ Neither entry point is tied to Pi, Claude Code, Codex, Cursor, OpenCode, or any other agent. Skills, MCP integrations, and agent plugins are adapters on top; they never enter the core. Requires Node.js 22.19+.
11
11
 
12
- ## Capabilities and providers
12
+ ## Capabilities
13
13
 
14
14
  | Capability | Provider types | Normalized output |
15
15
  | --- | --- | --- |
16
- | `search` | Tavily, Exa, Bocha, Brave, SearXNG, AnySearch, XCrawl, DeepSeek, xAI x_search, xAI web_search | `rank`, `title`, `url`, `snippet` |
17
- | `extract` | Firecrawl v2, Jina Reader, Exa Contents, AnySearch, XCrawl, HTTP | Markdown `Document` |
16
+ | `search` | Tavily, Exa, Bocha, Yuanbao Search, Brave, SearXNG, AnySearch, XCrawl, DeepSeek, xAI x_search, xAI web_search | `rank`, `title`, `url`, `snippet` |
17
+ | `extract` | Firecrawl v2, Jina Reader, Exa Contents, AnySearch, XCrawl, DeepSeek Fetch, HTTP | Markdown `Document` |
18
18
 
19
- A provider type describes an implementation, while a provider instance is a configurable instance of that type. One type can have multiple instances, such as `exa_team` and `exa_personal`. Each capability has a primary `providers` route and a `providers_fallback` route. Both arrays determine enabled instances and their current `auto` order; the CLI only reorders members within their existing group.
19
+ A provider type is an implementation; a provider instance is a configured instance of that type, so `exa_team` and `exa_personal` can coexist. Each capability has a primary `providers` route and a `providers_fallback` route; both define the enabled instances and their `auto` order.
20
20
 
21
- ## Installation
22
-
23
- Node.js 22.19 or later is required.
24
-
25
- Install the CLI from npm:
21
+ ## Install
26
22
 
27
23
  ```sh
28
- npm install --global web-access-cli
24
+ npm install --global web-access-cli # or: pnpm add --global web-access-cli
29
25
  web-access --help
30
26
  ```
31
27
 
32
- Alternatively, install it with pnpm:
28
+ From a checkout: `pnpm install --frozen-lockfile && pnpm check`. The gateway runs from the same checkout (`node --import tsx src/server.ts`) or built output (`pnpm start:gateway`); production deployment uses the GHCR image, whose Compose template and secret conventions are in [deploy/README.md](deploy/README.md).
33
29
 
34
- ```sh
35
- pnpm add --global web-access-cli
36
- web-access --help
37
- ```
30
+ Layout: `src/cli.ts` and `src/server.ts` are the two entries, `src/gateway/` holds the tool protocol, and `src/core/`, `src/providers/`, `src/transport/` are shared by both. Adapters map protocols only; fallback, deadlines, envelopes, and exit codes live in `src/core`.
38
31
 
39
- For development from source:
32
+ ## CLI
40
33
 
41
34
  ```sh
42
- git clone https://github.com/juexe/web-access-cli.git
43
- cd web-access-cli
44
- pnpm install --frozen-lockfile
45
- pnpm check
46
- pnpm pack:check
47
- ```
48
-
49
- ## Project structure
35
+ web-access search "TypeScript release" --provider exa --limit 10 --freshness month
36
+ web-access search "browser automation" --include-domain example.com --exclude-domain blocked.example.com
37
+ web-access extract https://example.com/article --provider http --timeout 30000
38
+ web-access extract https://example.com/article --json
50
39
 
51
- ```text
52
- src/cli.ts Commander CLI and exit codes
53
- src/config/ Configuration files, environment variables, and route merging
54
- src/core/ Public types, schemas, errors, routing, and diagnostics
55
- src/providers/ Provider adapters, registry, and HTTP/RSC extraction
56
- src/transport/ Proxies, timeouts, redirects, and response size limits
57
- schemas/ Generated JSON Schemas
58
- test/ node:test unit tests and local HTTP integration tests
40
+ web-access providers # route, capability, credential and URL status
41
+ web-access doctor # local configuration checks, no network
42
+ web-access config init # scaffold from credential env vars
43
+ web-access config edit # open in $VISUAL/$EDITOR
59
44
  ```
60
45
 
61
- Provider adapters should handle protocol mapping only. Fallback, deadlines, attempts, envelopes, and exit codes are managed centrally by `src/core`. Do not add lifecycle or tool registration code for Pi, Claude Code, Codex, or any other agent to an adapter.
46
+ Global options: `--config <path>`, `--json`, `--help`, `--version`. Capability commands print human-readable Markdown, `config init` prints the created path, and every failure prints one JSON envelope. Exit codes: `0` success, `2` invalid input/config or a disabled provider, `1` runtime or doctor failure, `130` cancelled.
62
47
 
63
- ## Development workflow
48
+ Not provided: batch/all, answer, a PDF-only path, a Node SDK, or generic MCP. An optional [Agent Skill](skills/web-access-cli/SKILL.md) exists for source checkouts and is not published to npm.
64
49
 
65
- ```sh
66
- pnpm install
67
- pnpm lint
68
- pnpm typecheck
69
- pnpm test
70
- pnpm build
71
- pnpm pack:check
72
- ```
50
+ ## HTTP gateway
73
51
 
74
- `pnpm check` runs linting, type checking, tests, and the build in that order. After changing `src/core/schema.ts`, run `pnpm build` again and commit the updated `schemas/*.schema.json` files. Provider tests should use the mock transport or local HTTP server from `test/helpers.ts`; they must not depend on real API keys or external network reliability.
52
+ A long-running process that serves the same providers, routing and transport over DeepSeek's [Search and Fetch API](https://api-docs.deepseek.com/zh-cn/guides/search): same endpoints, auth header, request fields and error envelope, so a client written for the official service only changes its base URL. It changes nothing about the CLI contract.
75
53
 
76
- ## Commands
54
+ Run it as a Docker image — `ghcr.io/juexe/web-access-cli`, multi-arch `linux/amd64` and `linux/arm64`, published on every GitHub Release with exact version tags plus `latest` for stable releases. Deployment needs Docker only, no Node.js or pnpm:
77
55
 
78
56
  ```sh
79
- web-access search "Agent-neutral web CLI"
80
- web-access search "TypeScript release" --provider exa --limit 10 --freshness month
81
- web-access search "browser automation" --include-domain example.com --exclude-domain blocked.example.com
82
-
83
- web-access extract https://example.com/article
84
- web-access extract https://example.com/article --provider http --timeout 30000
85
- web-access extract https://example.com/article --json
57
+ docker pull ghcr.io/juexe/web-access-cli:0.7.0
58
+ docker compose -f deploy/compose.yaml --env-file personal/deploy.env up -d --wait gateway
59
+ ```
86
60
 
87
- web-access providers
88
- web-access doctor
61
+ Or from source; it listens on `127.0.0.1:8080` by default:
89
62
 
90
- web-access config edit
91
- web-access --config "/path/to/config.json" config edit
92
- web-access config init
93
- web-access --config "/path/to/config.json" config init --json
63
+ ```sh
64
+ # Source
65
+ WEB_ACCESS_CONFIG=~/.config/web-access-cli/config.json \
66
+ WEB_ACCESS_GATEWAY_API_KEY_FILE=/path/to/gateway-api-key \
67
+ node --import tsx src/server.ts
94
68
  ```
95
69
 
96
- The CLI provides the `search` and `extract` capability commands, the `providers` and `doctor` diagnostic commands, and the `config init` and `config edit` configuration commands. The current version does not provide batch/all, answer, a dedicated PDF path, a Node.js SDK, or a generic MCP integration. The repository includes an optional [web-access-cli Agent Skill](skills/web-access-cli/SKILL.md) for source-checkout use; it is not included in the npm package.
97
-
98
- ### Global options
70
+ | Method | Path | Notes |
71
+ | --- | --- | --- |
72
+ | `POST` | `/v1/tools/search` | DeepSeek Search compatible |
73
+ | `POST` | `/v1/tools/fetch` | DeepSeek Fetch compatible |
74
+ | `GET` | `/healthz`, `/readyz` | Liveness and configuration readiness, no auth |
75
+ | `GET` | `/panel` | Admin UI shell, no auth and no data |
76
+ | `GET`/`DELETE` | `/admin/api/records` | Request records, Bearer key required |
77
+ | `GET` | `/admin/api/overview` | Runtime stats, route readiness, per-instance counters |
78
+ | `PUT` | `/admin/api/config` | Patch routes, budgets, instances; hot-applies and persists |
79
+ | `POST` | `/admin/api/config/reload` | Reload the config file from disk |
80
+
81
+ - The Bearer key is read from a file only, compared as a fixed-length digest, and duplicate `Authorization` headers are rejected. Requests are single-user private, so the gateway adds no local admission, body size limit, or body read deadline and never returns 413 or 408 itself. Responses use `Cache-Control: no-store`.
82
+ - Search fields: `query` (1–128 characters), `count` (1–50, default 10), `ori_question` (1–128 when set), `localization` (an IP address when non-empty), `need_content` (boolean, default `false`). Fetch takes one absolute HTTP(S) URL of at most 8192 characters. Unknown fields are ignored and cannot override provider, credentials, timeout, or configuration.
83
+ - Responses are protocol-compatible, not equal to the official engine: `summary` reuses `snippet`, `rerank_score` is always `0`, `authority` is always `-1`, and `site_name`/`site_icon`/`date` are `null`. Clients filtering on score or authority must change strategy, and no result count, ranking, or quality is promised.
84
+ - `ori_question` and `localization` are validated but have no core equivalent, so they never reach providers.
85
+ - `need_content=true` backfills by rank with two concurrent tasks, a 30-second window, 10 seconds per item, and a 3-second attempt cap; failures keep the result with empty content. Backfill and responses have no size cap, and **backfill spends extract quota**. Fetch and backfill derive their own config with a 1-character minimum so short pages succeed, while the source Markdown probe still requires 500 characters. Client disconnects cancel in-flight work.
86
+ - Errors are always `{ "error": { "message", "type": "…", "param": null, "code" } }`: 400 `invalid_request_error`, 401 `invalid_api_key`, 402 `insufficient_quota`, 404 `no_usable_content` or an unknown route, 405 wrong method, 503 while shutting down, 504 `upstream_timeout`, and 500 for provider, credential, or configuration failures. 429 only ever means the upstream provider rate-limited.
87
+ - Tool requests never rewrite provider order: concurrent traffic does not trigger config write-back. The panel's config workspace and `/admin/api/config*` are the only writers, and they hot-apply plus persist atomically (with a read-only fallback when the file is mounted as a secret).
88
+ - The panel keeps the last 500 requests in memory: status, duration, error, provider, query/URL, attempt chain, snippets, and truncated content previews (2 000 chars for search results, 4 000 for Fetch) — never full bodies, headers, or credentials. A `#apikey=<key>` fragment logs in, is stripped from the address bar, and stores the key in `sessionStorage`.
89
+
90
+ ### Use it from DSH
91
+
92
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) ships `@deepseek-ai/dsh-web-deepseek-api`, a provider that speaks exactly this API, so pointing `web_search` and `web_fetch` here needs no extra plugin — only a base URL. In `~/.dsh/cordis.patch.yml`:
93
+
94
+ ```yaml
95
+ - id: web
96
+ name: "@deepseek-ai/dsh-web"
97
+ config:
98
+ searchProvider: deepseek-search-api
99
+ fetchProvider: deepseek-fetch-api
100
+ - id: web-deepseek-api
101
+ name: "@deepseek-ai/dsh-web-deepseek-api"
102
+ config:
103
+ apiKeyEnv: DEEPSEEK_API_KEY # must hold the gateway key, not a DeepSeek key
104
+ baseURL: http://127.0.0.1:8080/v1/tools
105
+ ```
99
106
 
100
- - `--config <path>`: Explicitly select a configuration file.
101
- - `--json`: Output a compact schema v2 JSON envelope. Without it, successful commands write human-readable Markdown; all failures always write JSON.
102
- - `--help`, `--version`: Print standard CLI help or version text.
107
+ `baseURL` is the full base including the path prefix and excluding `/search` or `/fetch`; the default `apiKeyEnv` is `DEEPSEEK_API_KEY`, so point it at a variable holding the gateway key. `web_search` and `web_fetch` then hit this gateway with no other change — only the placeholder score/authority fields differ, as noted above. Because the gateway is private, keep it on a trusted network rather than exposing it publicly.
103
108
 
104
- Successful commands default to Markdown for human readers. `search` writes a YAML front matter block followed by a numbered result list; `extract` writes YAML front matter followed by the normalized Markdown body; `providers`, `doctor`, and `config edit` write a heading followed by pretty-printed JSON data; `config init` writes the created absolute path on one line. Add `--json` when a schema v2 envelope is required. Extract `partial` results and all failures, including invalid input and runtime errors, always remain JSON envelopes. Diagnostic messages are never mixed into stdout.
109
+ Deployment, secrets, upgrades and rollback: [deploy/README.md](deploy/README.md). Decision record: [ADR 0019](docs/adr/0019-deepseek-tool-gateway.md).
105
110
 
106
111
  ## Configuration
107
112
 
108
- Configuration files use strict JSON; unknown fields are rejected. The CLI reads only an explicit path or a user-level path and does not search parent project directories for configuration files.
113
+ Strict JSON; unknown fields are rejected. Resolution order is `--config`, then `WEB_ACCESS_CONFIG`, then `~/.config/web-access-cli/config.json`. A missing default file falls back to built-in defaults; a missing explicit file is a `config_error`. No parent directories are scanned.
109
114
 
110
- The default path on every platform is `~/.config/web-access-cli/config.json`, where `~` is the current user's home directory. The `WEB_ACCESS_CONFIG` environment variable can select another path. The `--config` command-line option takes precedence.
111
-
112
- `web-access config edit` creates parent directories and a complete default configuration when the file is missing, then opens it with the command in `VISUAL`, or `EDITOR` when `VISUAL` is empty. The value may contain a quoted executable path and arguments; it is parsed without shell evaluation and the configuration path is appended as a separate argument. An existing file is opened byte-for-byte as-is, even when it is temporarily invalid JSON; it is never overwritten or reformatted. The command waits only for the editor process to start, not for it to close. When neither variable is set, the command returns `open_failed`; a configuration file that was created successfully remains available for manual editing.
113
-
114
- `config init` creates a new configuration from standard credential environment variables and refuses to overwrite an existing file. It keeps all built-in instance definitions, writes detected Search providers to the primary route, leaves Search fallback empty, writes detected remote Extract providers to the primary route, and places `http` in the Extract fallback route. After an `auto` capability call succeeds or encounters fallback-eligible failures, the CLI atomically reorders the matching `providers` or `providers_fallback` array in place. Members never move between groups. The legacy `_providers` and `_providers_fallback` fields are rejected as unknown configuration fields.
115
-
116
- The complete JSON Schema is available at [schemas/config.schema.json](schemas/config.schema.json). Example:
115
+ `config edit` creates a full default file when absent and opens it with `VISUAL`, else `EDITOR`, without shell evaluation or reformatting; `config init` scaffolds from standard credential environment variables and refuses to overwrite. Environment variables override plaintext keys. The full schema is [schemas/config.schema.json](schemas/config.schema.json):
117
116
 
118
117
  ```json
119
118
  {
120
- "$schema": "https://unpkg.com/web-access-cli@0.5.0/schemas/config.schema.json",
119
+ "$schema": "https://unpkg.com/web-access-cli@0.7.0/schemas/config.schema.json",
121
120
  "providers": [
122
- {
123
- "id": "exa_team",
124
- "type": "exa",
125
- "apiKeyEnv": "TEAM_EXA_API_KEY",
126
- "headers": {
127
- "X-Team": "docs"
128
- }
129
- },
130
- {
131
- "id": "searx_local",
132
- "type": "searxng",
133
- "baseUrl": "http://127.0.0.1:8080"
134
- },
135
- {
136
- "id": "firecrawl_local",
137
- "type": "firecrawl",
138
- "baseUrl": "http://127.0.0.1:3002"
139
- }
121
+ { "id": "exa_team", "type": "exa", "apiKeyEnv": "TEAM_EXA_API_KEY" },
122
+ { "id": "firecrawl_local", "type": "firecrawl", "baseUrl": "http://127.0.0.1:3002" }
140
123
  ],
141
- "search": {
142
- "providers": ["searx_local", "exa_team", "brave"],
143
- "providers_fallback": ["deepseek"],
144
- "limit": 5,
145
- "timeoutMs": 120000,
146
- "attemptTimeoutMs": 60000,
147
- "maxResponseBytes": 5242880
148
- },
149
- "extract": {
150
- "providers": ["firecrawl_local", "jina", "exa_team"],
151
- "providers_fallback": ["http"],
152
- "timeoutMs": 120000,
153
- "attemptTimeoutMs": 45000,
154
- "maxResponseBytes": 5242880,
155
- "minContentCharacters": 500
156
- }
124
+ "search": { "providers": ["exa_team"], "providers_fallback": ["brave"] },
125
+ "extract": { "providers": ["firecrawl_local"], "providers_fallback": ["http"] }
157
126
  }
158
127
  ```
159
128
 
160
- The built-in instances are `tavily`, `exa`, `bocha`, `brave`, `searxng`, `firecrawl`, `jina`, `http`, `anysearch`, `xcrawl`, `deepseek`, `xai_x_search`, and `xai_web_search`. A configuration entry with the same ID overrides fields on the built-in instance. A custom ID creates another instance of the selected type. An instance is enabled when it appears in either corresponding route. Bocha defaults to `https://api.bocha.cn` and requires an API key; AnySearch defaults to `https://api.anysearch.com` and supports anonymous calls; XCrawl defaults to `https://run.xcrawl.com` and requires an API key; DeepSeek defaults to `https://api.deepseek.com/anthropic/v1` and requires an API key.
161
-
162
- Default routes:
163
-
164
- - Search primary: `tavily -> exa -> bocha -> brave -> searxng -> anysearch -> xcrawl`
165
- - Search fallback: `deepseek -> xai_x_search -> xai_web_search`
166
- - Extract primary: `firecrawl -> jina -> exa -> anysearch -> xcrawl`
167
- - Extract fallback: `http`
168
-
169
- The default routes split built-in providers into a primary group and a fallback group. Custom instance IDs are merged into the instance list but must still be added to a route explicitly. An omitted capability route uses the defaults above; an explicit Search primary empty array is invalid, while Extract may be explicitly disabled with an empty primary route. In `auto` mode, incompletely configured instances are skipped. AnySearch and XCrawl accept `searchFilterMode`: `strict` (default; freshness skips the provider) or `best_effort` (rewrites freshness into a query fragment). Domain constraints are rewritten into the query and strictly re-applied locally. XCrawl Extract always uses synchronous Scrape with Markdown output; Map, Crawl, and asynchronous jobs are outside the current CLI capabilities.
129
+ Built-in instances: `tavily`, `exa`, `bocha`, `yuanbao`, `brave`, `searxng`, `anysearch`, `xcrawl`, `deepseek`, `firecrawl`, `jina`, `http`, `xai_x_search`, `xai_web_search`. Reusing a built-in ID overrides that instance; a new ID adds another of the same type. An instance is enabled only when listed in a route. Default routes:
170
130
 
171
- DeepSeek Search performs a full Anthropic-compatible Messages model turn with the native `web_search_20250305` server tool, so it can have higher latency and cost than a dedicated search endpoint. It is in the default Search fallback group and is reached only after every primary provider is unavailable, returns a final non-2xx HTTP response, or otherwise fails recoverably. The adapter accepts URLs only from structured `web_search_tool_result` blocks, joins citation excerpts by URL, and never extracts URLs from model prose. Domain constraints are rewritten into the query and strictly re-applied locally; `freshness` is unsupported and skips DeepSeek with a recoverable error. Redirects are rejected without contacting the `Location` target. Provider-private `encrypted_content` payloads are omitted from `raw` because they are opaque, cannot be displayed or decoded by the CLI, and add no diagnostic value.
131
+ - Search: `tavily -> exa -> bocha -> yuanbao -> brave -> searxng -> anysearch -> xcrawl -> deepseek`, fallback `xai_x_search -> xai_web_search`
132
+ - Extract: `firecrawl -> jina -> exa -> anysearch -> xcrawl -> deepseek`, fallback `http`
172
133
 
173
- XAI hosted Search reads an external OAuth JSON file from `XAI_AUTH_JSON` on every call and uses only its `access_token`; an external CLIProxyAPI process owns refresh and write-back. Instance fields `authJson`/`authJsonEnv` and `model`/`modelEnv` override the path and model. `xai_web_search` accepts at most five allow or exclude domains (mutually exclusive); `xai_x_search` rejects `freshness`. Model-backed search can take longer than ordinary API searches; Search defaults allow up to 60 seconds per attempt and 120 seconds total.
174
-
175
- ### Credentials and URLs
176
-
177
- Environment variables take precedence over plaintext keys in JSON. Built-in instances support these standard variables:
134
+ An explicit empty Search primary route is invalid; Extract can be disabled with an empty primary route. AnySearch and XCrawl take `searchFilterMode`: `strict` (skip on freshness) or `best_effort` (rewrite freshness into the query). Yuanbao posts `Query`/`Site`/`Freshness` to `/SearchPro` and keeps in-body errors fallback-eligible. DeepSeek uses `/v1/tools/search` and `/v1/tools/fetch`, rewriting freshness and domains into `after:`/`site:` hints that are re-applied locally. xAI reads an OAuth JSON from `XAI_AUTH_JSON` without refreshing it and needs a raised `attemptTimeoutMs`.
178
135
 
179
136
  | Type | API key | Base URL |
180
137
  | --- | --- | --- |
181
- | Tavily | `TAVILY_API_KEY` | `TAVILY_BASE_URL` |
182
- | Exa | `EXA_API_KEY` | `EXA_BASE_URL` |
138
+ | Tavily / Exa / Brave / Firecrawl / XCrawl | `<NAME>_API_KEY` | `<NAME>_BASE_URL` |
183
139
  | Bocha | `BOCHA_API_KEY` | `BOCHA_BASE_URL`, default `https://api.bocha.cn` |
184
- | Brave | `BRAVE_API_KEY` | `BRAVE_BASE_URL` |
185
140
  | SearXNG | None | `SEARXNG_BASE_URL`, required |
186
- | Firecrawl | `FIRECRAWL_API_KEY` | `FIRECRAWL_BASE_URL` |
187
- | Jina | `JINA_API_KEY`, optional | `JINA_BASE_URL` |
188
- | HTTP | None | None |
189
- | AnySearch | `ANYSEARCH_API_KEY`, optional | `ANYSEARCH_BASE_URL`, default `https://api.anysearch.com` |
190
- | XCrawl | `XCRAWL_API_KEY` | `XCRAWL_BASE_URL`, default `https://run.xcrawl.com` |
191
- | DeepSeek | `DEEPSEEK_API_KEY` | No standard environment variable; default `https://api.deepseek.com/anthropic/v1` |
192
- | xAI Search | `XAI_AUTH_JSON` | No standard API-key variable; default `https://cli-chat-proxy.grok.com/v1`, model `grok-4.6` |
193
-
194
- Custom instances use `apiKeyEnv` and `baseUrlEnv` to name their environment variables. Every remote provider can define a `baseUrl` and additional `headers`. The public `api.firecrawl.dev` service requires a key; a self-hosted Firecrawl v2 instance can run without one.
141
+ | Jina / AnySearch | `JINA_API_KEY` / `ANYSEARCH_API_KEY`, optional | `<NAME>_BASE_URL` |
142
+ | DeepSeek | `DEEPSEEK_API_KEY` | fixed `https://api.deepseek.com` |
143
+ | xAI Search | `XAI_AUTH_JSON` (OAuth file) | default `https://cli-chat-proxy.grok.com/v1`, `grok-4.6` |
195
144
 
196
- ## Execution and fallback
145
+ Custom instances name their variables with `apiKeyEnv`/`baseUrlEnv` and may set `baseUrl` and `headers`. Public Firecrawl Cloud needs a key; self-hosted Firecrawl v2 does not.
197
146
 
198
- `--provider auto` follows the configured `providers` order first and enters `providers_fallback` only after the complete primary group fails with a fallback-eligible result:
147
+ ## Routing and limits
199
148
 
200
- 1. An instance with incomplete configuration is recorded as a failed attempt and skipped.
201
- 2. A final provider HTTP response outside 2xx, including authentication, request, rate-limit, and server errors, causes the router to try the next instance.
202
- 3. Other recoverable errors, including network errors, timeouts, oversized responses, and missing usable content, also cause the router to continue.
203
- 4. Non-recoverable failures without a final non-2xx response, including invalid input and unsupported content detected in a successful response, stop execution immediately.
204
- 5. If every instance in both groups fails, the command returns `provider_exhausted`. The best short content produced during extraction is preserved in `partial`.
149
+ Both entry points call the same router and differ only in output shape, per-command options, and the gateway's fixed Fetch/backfill thresholds.
205
150
 
206
- Before those routes run, `extract --provider auto` checks one Markdown variant when an HTTP instance is enabled in either group. It appends `.md` to file-like paths or uses `index.md` for directory paths, requests `text/markdown` and `text/plain`, and accepts only a sufficiently long non-HTML text response. A miss or request failure is transparent and falls through to the configured routes. A hit is reported as the enabled HTTP instance; it does not change provider order. Explicit non-HTTP selections are unchanged, while an explicit HTTP selection checks the Markdown variant before fetching the original URL.
151
+ `auto` walks the primary route, then the fallback route once the primary group has failed in a way that is fallback-eligible. Incomplete instances are recorded and skipped; non-2xx provider responses and recoverable errors (network, timeout, oversized, no usable content) continue to the next instance; invalid input and unsupported content stop immediately; full exhaustion returns `provider_exhausted` with the best short body in `partial`. An explicit `--provider` runs once with no fallback and returns `provider_disabled` if not routed. `retryable` describes retrying the same provider, not the switch condition.
207
152
 
208
- After each `auto` call, a successful instance moves to the front of its group, untried instances remain in the middle, and instances with the fallback-eligible failures above move stably to the back. Relative order within each group is preserved; no provider crosses between primary and fallback. The order stays unchanged when every instance in a group fails. Search and Extract learn independently; explicit instances, non-fallback failures, and user cancellation never update the order. Writes use atomic replacement, and the last writer wins across concurrent processes.
153
+ `extract --provider auto` first probes one Markdown variant when HTTP is enabled — `.md` for file-like paths, `index.md` for directories — accepting only sufficiently long non-HTML text. Misses fall through transparently; hits are attributed to the HTTP instance without reordering.
209
154
 
210
- An attempt keeps the provider's original error code, HTTP status, and `retryable` value. `retryable` describes whether the original operation can be retried against the same provider; it is not the sole condition for switching to the next provider in an automatic route.
155
+ In the CLI, after each `auto` call the successful instance moves to the front of its group and fallback-eligible failures move to the back; nothing crosses groups. Search and Extract learn independently. Explicit instances, non-fallback failures, and cancellations never reorder, and writes are atomic with last-writer-wins. The gateway never rewrites routes.
211
156
 
212
- Selecting an instance explicitly executes it strictly without fallback. If the instance is not in the route, the command returns `provider_disabled`.
157
+ Defaults: 120 s total and 60 s per attempt for Search; 120 s and 45 s for Extract; 5 MiB per response. `--timeout` overrides only the total timeout.
213
158
 
214
- Default limits are a 120-second total timeout and a 60-second per-attempt timeout for Search, and 120 seconds and 45 seconds respectively for Extract. Each response has a hard limit of 5 MiB. `--timeout` overrides only the total timeout for the current command.
159
+ ## Output
215
160
 
216
- ## Output contract
217
-
218
- Capability commands use output schema version 2. Their default envelope is intentionally small:
161
+ Capability commands emit schema version 2. With `--json`, output contains `schemaVersion`, `ok`, the final instance and `data`; failures add a compact `error`, `attempts` (instance, code, optional status), and possibly a `partial` document. A failed order write adds `warnings` without replacing the result. Human-readable output is Markdown: a `provider` front matter block plus either a numbered result list or the extracted document; diagnostics print a heading plus JSON. Scripts should pass `--json`.
219
162
 
220
163
  ```json
221
- {
222
- "schemaVersion": 2,
223
- "ok": true,
224
- "provider": "exa",
225
- "data": {
226
- "results": [
227
- {
228
- "rank": 1,
229
- "title": "Example",
230
- "url": "https://example.com/",
231
- "snippet": "Example snippet"
232
- }
233
- ]
234
- }
235
- }
164
+ { "schemaVersion": 2, "ok": true, "provider": "exa",
165
+ "data": { "results": [{ "rank": 1, "title": "Example", "url": "https://example.com/", "snippet": "..." }] } }
236
166
  ```
237
167
 
238
- With `--json`, capability output contains only `schemaVersion`, `ok`, the final Instance ID and normalized `data`. Failures contain a compact `error` and, when providers were attempted, `attempts` entries with only Instance ID, error code and optional HTTP status. Extract quality failures may include a `partial` document without raw provider data. If the learned order cannot be written, either a success or failure envelope additionally contains `warnings: [{ "code": "provider_order_update_failed", "message": "..." }]` without replacing the primary result. Search and extract input errors use the same compact failure shape.
239
-
240
- For human-readable output, extraction starts with YAML front matter and then the provider-normalized Markdown body. Search results use a numbered Markdown list; diagnostics and config edit use a heading plus pretty-printed JSON data. Metadata values use JSON double-quoted scalar encoding and the body is not modified. Existing scripts and other machine consumers should append `--json` explicitly.
241
-
242
- Example default extract output:
243
-
244
- ```markdown
245
- ---
246
- provider: "local_http"
247
- url: "https://example.com/article"
248
- title: "Example title"
249
- ---
250
-
251
- # Article content
252
- ```
253
-
254
- All success and failure envelopes use schema version 2. Stable schemas are available in [schemas](schemas).
255
-
256
- Exit codes:
257
-
258
- - `0`: Success
259
- - `2`: Invalid input or configuration, or an unknown or disabled provider
260
- - `1`: Runtime, provider, doctor, or editor launch failure
261
- - `130`: User cancellation
262
-
263
- `providers` lists each instance's type, capabilities, route status, credential source, and base URL source. Its `searchRoute`/`searchFallbackRoute` and `extractRoute`/`extractFallbackRoute` fields show the two effective orders for the next `auto` call. `doctor` performs local configuration checks only and does not call remote APIs. If an enabled route contains an unconfigured instance, the command returns `doctor_failed` with exit code `1`.
264
-
265
- ## HTTP and security boundaries
266
-
267
- - Honors `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` by default.
268
- - Follows up to five redirects. Cross-origin redirects remove authentication, Cookie, and common token headers.
269
- - Enforces a streaming byte limit on every response instead of buffering an unbounded response first.
270
- - HTTP Extract uses LinkeDOM, Mozilla Readability, and Turndown, with fallback parsing for Next.js RSC payloads.
271
- - HTTP Extract prefers an enabled source Markdown variant (`.md` or directory `index.md`) before provider routes when that variant returns usable Markdown.
272
- - Returns `unsupported_content` for PDF, image, audio, video, zip, and general binary content.
273
- - By design, the CLI does not block localhost, private network addresses, or cloud metadata URLs. For untrusted input, callers must enforce their own URL allowlist, network isolation, or outbound proxy policy.
168
+ `providers` reports each instance's type, capabilities, route status, and credential/URL sources together with the effective orders for the next call; `doctor` fails with `1` when an enabled route contains an unconfigured instance.
274
169
 
275
- ## Project scope
170
+ ## Security boundaries
276
171
 
277
- This project draws heavily from the provider and content extraction implementations in `pi-web-access`, but does not include Pi-specific registration, tool protocols, or UI code.
172
+ - Honors `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY`; follows up to five redirects and strips authentication, Cookie, and token headers across origins.
173
+ - Enforces a streaming 5 MiB limit rather than buffering first. HTTP extraction uses LinkeDOM, Readability, and Turndown with an RSC fallback; binaries and media return `unsupported_content`.
174
+ - Neither entry point blocks localhost, private addresses, or cloud metadata URLs by design. Untrusted callers must add their own allowlist, isolation, or egress policy; the gateway targets a private single-user deployment behind its Bearer key.
278
175
 
279
- Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) before opening an issue or pull request. Report security vulnerabilities privately according to [SECURITY.md](SECURITY.md).
176
+ ## Scope
280
177
 
281
- Licensed under MIT. See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) for third-party and upstream attribution.
178
+ Provider and extraction implementations draw heavily on `pi-web-access`; Pi-specific registration, tool protocols, and UI are excluded. See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md). Licensed under MIT with attribution in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).