web-access-cli 0.6.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.
- package/README.md +102 -262
- package/README_CN.md +104 -264
- package/dist/gateway/admin.js +670 -0
- package/dist/gateway/admin.js.map +1 -0
- package/dist/gateway/panel.js +1741 -127
- package/dist/gateway/panel.js.map +1 -1
- package/dist/gateway/records.js +384 -7
- package/dist/gateway/records.js.map +1 -1
- package/dist/gateway/server.js +270 -104
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/service.js +243 -53
- package/dist/gateway/service.js.map +1 -1
- package/dist/providers/xai.js +16 -8
- package/dist/providers/xai.js.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,334 +5,174 @@ English | [简体中文](README_CN.md)
|
|
|
5
5
|
[](https://github.com/Juexe/web-access-cli/actions/workflows/ci.yml)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
|
|
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
|
-
|
|
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
|
|
12
|
+
## Capabilities
|
|
13
13
|
|
|
14
14
|
| Capability | Provider types | Normalized output |
|
|
15
15
|
| --- | --- | --- |
|
|
16
16
|
| `search` | Tavily, Exa, Bocha, Yuanbao Search, Brave, SearXNG, AnySearch, XCrawl, DeepSeek, xAI x_search, xAI web_search | `rank`, `title`, `url`, `snippet` |
|
|
17
17
|
| `extract` | Firecrawl v2, Jina Reader, Exa Contents, AnySearch, XCrawl, DeepSeek Fetch, HTTP | Markdown `Document` |
|
|
18
18
|
|
|
19
|
-
A provider type
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
+
## CLI
|
|
40
33
|
|
|
41
34
|
```sh
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
pnpm pack:check
|
|
47
|
-
```
|
|
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
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
src/server.ts Standalone DeepSeek-compatible gateway entry point
|
|
54
|
-
src/config/ Configuration files, environment variables, and route merging
|
|
55
|
-
src/core/ Public types, schemas, errors, routing, and diagnostics
|
|
56
|
-
src/gateway/ Gateway protocol schema, service mapping, and HTTP boundary
|
|
57
|
-
src/providers/ Provider adapters, registry, and HTTP/RSC extraction
|
|
58
|
-
src/transport/ Proxies, timeouts, redirects, and response size limits
|
|
59
|
-
schemas/ Generated JSON Schemas
|
|
60
|
-
test/ node:test unit tests and local HTTP integration tests
|
|
61
|
-
deploy/ Public gateway Compose template, env/config examples, operations guide
|
|
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
|
|
62
44
|
```
|
|
63
45
|
|
|
64
|
-
|
|
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.
|
|
65
47
|
|
|
66
|
-
|
|
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.
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
pnpm install
|
|
70
|
-
pnpm lint
|
|
71
|
-
pnpm typecheck
|
|
72
|
-
pnpm test
|
|
73
|
-
pnpm build
|
|
74
|
-
pnpm pack:check
|
|
75
|
-
pnpm test:docker
|
|
76
|
-
```
|
|
50
|
+
## HTTP gateway
|
|
77
51
|
|
|
78
|
-
|
|
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.
|
|
79
53
|
|
|
80
|
-
|
|
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:
|
|
81
55
|
|
|
82
56
|
```sh
|
|
83
|
-
web-access
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
web-access extract https://example.com/article
|
|
88
|
-
web-access extract https://example.com/article --provider http --timeout 30000
|
|
89
|
-
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
|
+
```
|
|
90
60
|
|
|
91
|
-
|
|
92
|
-
web-access doctor
|
|
61
|
+
Or from source; it listens on `127.0.0.1:8080` by default:
|
|
93
62
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
web-access
|
|
97
|
-
|
|
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
|
|
98
68
|
```
|
|
99
69
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
+
```
|
|
103
106
|
|
|
104
|
-
|
|
105
|
-
- `--json`: Output a compact schema v2 JSON envelope. Without it, successful commands write human-readable Markdown; all failures always write JSON.
|
|
106
|
-
- `--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.
|
|
107
108
|
|
|
108
|
-
|
|
109
|
+
Deployment, secrets, upgrades and rollback: [deploy/README.md](deploy/README.md). Decision record: [ADR 0019](docs/adr/0019-deepseek-tool-gateway.md).
|
|
109
110
|
|
|
110
111
|
## Configuration
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
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.
|
|
115
|
-
|
|
116
|
-
`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
|
+
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.
|
|
117
114
|
|
|
118
|
-
`config
|
|
119
|
-
|
|
120
|
-
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):
|
|
121
116
|
|
|
122
117
|
```json
|
|
123
118
|
{
|
|
124
|
-
"$schema": "https://unpkg.com/web-access-cli@0.
|
|
119
|
+
"$schema": "https://unpkg.com/web-access-cli@0.7.0/schemas/config.schema.json",
|
|
125
120
|
"providers": [
|
|
126
|
-
{
|
|
127
|
-
|
|
128
|
-
"type": "exa",
|
|
129
|
-
"apiKeyEnv": "TEAM_EXA_API_KEY",
|
|
130
|
-
"headers": {
|
|
131
|
-
"X-Team": "docs"
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
"id": "searx_local",
|
|
136
|
-
"type": "searxng",
|
|
137
|
-
"baseUrl": "http://127.0.0.1:8080"
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
"id": "firecrawl_local",
|
|
141
|
-
"type": "firecrawl",
|
|
142
|
-
"baseUrl": "http://127.0.0.1:3002"
|
|
143
|
-
}
|
|
121
|
+
{ "id": "exa_team", "type": "exa", "apiKeyEnv": "TEAM_EXA_API_KEY" },
|
|
122
|
+
{ "id": "firecrawl_local", "type": "firecrawl", "baseUrl": "http://127.0.0.1:3002" }
|
|
144
123
|
],
|
|
145
|
-
"search": {
|
|
146
|
-
|
|
147
|
-
"providers_fallback": ["deepseek"],
|
|
148
|
-
"limit": 5,
|
|
149
|
-
"timeoutMs": 120000,
|
|
150
|
-
"attemptTimeoutMs": 60000,
|
|
151
|
-
"maxResponseBytes": 5242880
|
|
152
|
-
},
|
|
153
|
-
"extract": {
|
|
154
|
-
"providers": ["firecrawl_local", "jina", "exa_team"],
|
|
155
|
-
"providers_fallback": ["http"],
|
|
156
|
-
"timeoutMs": 120000,
|
|
157
|
-
"attemptTimeoutMs": 45000,
|
|
158
|
-
"maxResponseBytes": 5242880,
|
|
159
|
-
"minContentCharacters": 500
|
|
160
|
-
}
|
|
124
|
+
"search": { "providers": ["exa_team"], "providers_fallback": ["brave"] },
|
|
125
|
+
"extract": { "providers": ["firecrawl_local"], "providers_fallback": ["http"] }
|
|
161
126
|
}
|
|
162
127
|
```
|
|
163
128
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
Default routes:
|
|
167
|
-
|
|
168
|
-
- Search primary: `tavily -> exa -> bocha -> yuanbao -> brave -> searxng -> anysearch -> xcrawl -> deepseek`
|
|
169
|
-
- Search fallback: `xai_x_search -> xai_web_search`
|
|
170
|
-
- Extract primary: `firecrawl -> jina -> exa -> anysearch -> xcrawl -> deepseek`
|
|
171
|
-
- Extract fallback: `http`
|
|
172
|
-
|
|
173
|
-
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.
|
|
174
|
-
|
|
175
|
-
Yuanbao Search calls Tencent Cloud `POST /SearchPro` with a bearer service API key and a lower-case request body (`Query`, plus optional `Site` and `Freshness`). One `site:` include becomes `Site=<domain>`; up to five excludes become `Site=exclude:<domain>|<domain>`; other combinations are filtered locally only. Each entry of `Pages` is a JSON string and is mapped to `title`/`url`/`snippet`, preferring the standard `passage` summary over the dynamic `content`. Errors returned inside a successful HTTP response, such as `UnauthorizedOperation` or `ResourceNotFound`, stay fallback-eligible so `auto` can continue to the next provider. Only Search is supported; `Cnt` is omitted while `--limit` stays at or below 10 so the lite edition keeps working, and is rounded up to the next multiple of 10 (maximum 50) above that.
|
|
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:
|
|
176
130
|
|
|
177
|
-
|
|
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`
|
|
178
133
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
### Credentials and URLs
|
|
182
|
-
|
|
183
|
-
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`.
|
|
184
135
|
|
|
185
136
|
| Type | API key | Base URL |
|
|
186
137
|
| --- | --- | --- |
|
|
187
|
-
| Tavily | `
|
|
188
|
-
| Exa | `EXA_API_KEY` | `EXA_BASE_URL` |
|
|
138
|
+
| Tavily / Exa / Brave / Firecrawl / XCrawl | `<NAME>_API_KEY` | `<NAME>_BASE_URL` |
|
|
189
139
|
| Bocha | `BOCHA_API_KEY` | `BOCHA_BASE_URL`, default `https://api.bocha.cn` |
|
|
190
|
-
| Brave | `BRAVE_API_KEY` | `BRAVE_BASE_URL` |
|
|
191
140
|
| SearXNG | None | `SEARXNG_BASE_URL`, required |
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
| AnySearch | `ANYSEARCH_API_KEY`, optional | `ANYSEARCH_BASE_URL`, default `https://api.anysearch.com` |
|
|
196
|
-
| XCrawl | `XCRAWL_API_KEY` | `XCRAWL_BASE_URL`, default `https://run.xcrawl.com` |
|
|
197
|
-
| DeepSeek | `DEEPSEEK_API_KEY` | No standard environment variable; default `https://api.deepseek.com` |
|
|
198
|
-
| xAI Search | `XAI_AUTH_JSON` | No standard API-key variable; default `https://cli-chat-proxy.grok.com/v1`, model `grok-4.6` |
|
|
199
|
-
|
|
200
|
-
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.
|
|
201
|
-
|
|
202
|
-
## Execution and fallback
|
|
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` |
|
|
203
144
|
|
|
204
|
-
|
|
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.
|
|
205
146
|
|
|
206
|
-
|
|
207
|
-
2. A final provider HTTP response outside 2xx, including authentication, request, rate-limit, and server errors, causes the router to try the next instance.
|
|
208
|
-
3. Other recoverable errors, including network errors, timeouts, oversized responses, and missing usable content, also cause the router to continue.
|
|
209
|
-
4. Non-recoverable failures without a final non-2xx response, including invalid input and unsupported content detected in a successful response, stop execution immediately.
|
|
210
|
-
5. If every instance in both groups fails, the command returns `provider_exhausted`. The best short content produced during extraction is preserved in `partial`.
|
|
147
|
+
## Routing and limits
|
|
211
148
|
|
|
212
|
-
|
|
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.
|
|
213
150
|
|
|
214
|
-
|
|
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.
|
|
215
152
|
|
|
216
|
-
|
|
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.
|
|
217
154
|
|
|
218
|
-
|
|
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.
|
|
219
156
|
|
|
220
|
-
|
|
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.
|
|
221
158
|
|
|
222
|
-
## Output
|
|
159
|
+
## Output
|
|
223
160
|
|
|
224
|
-
Capability commands
|
|
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`.
|
|
225
162
|
|
|
226
163
|
```json
|
|
227
|
-
{
|
|
228
|
-
"
|
|
229
|
-
"ok": true,
|
|
230
|
-
"provider": "exa",
|
|
231
|
-
"data": {
|
|
232
|
-
"results": [
|
|
233
|
-
{
|
|
234
|
-
"rank": 1,
|
|
235
|
-
"title": "Example",
|
|
236
|
-
"url": "https://example.com/",
|
|
237
|
-
"snippet": "Example snippet"
|
|
238
|
-
}
|
|
239
|
-
]
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
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.
|
|
245
|
-
|
|
246
|
-
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.
|
|
247
|
-
|
|
248
|
-
Example default extract output:
|
|
249
|
-
|
|
250
|
-
```markdown
|
|
251
|
-
---
|
|
252
|
-
provider: "local_http"
|
|
253
|
-
url: "https://example.com/article"
|
|
254
|
-
title: "Example title"
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
# Article content
|
|
164
|
+
{ "schemaVersion": 2, "ok": true, "provider": "exa",
|
|
165
|
+
"data": { "results": [{ "rank": 1, "title": "Example", "url": "https://example.com/", "snippet": "..." }] } }
|
|
258
166
|
```
|
|
259
167
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
Exit codes:
|
|
263
|
-
|
|
264
|
-
- `0`: Success
|
|
265
|
-
- `2`: Invalid input or configuration, or an unknown or disabled provider
|
|
266
|
-
- `1`: Runtime, provider, doctor, or editor launch failure
|
|
267
|
-
- `130`: User cancellation
|
|
268
|
-
|
|
269
|
-
`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`.
|
|
270
|
-
|
|
271
|
-
## DeepSeek-compatible HTTP gateway
|
|
272
|
-
|
|
273
|
-
The repository also ships a **separate** HTTP service that wraps the same provider, route and transport implementations in the DeepSeek tool protocol. It is not a replacement for the CLI: commands, exit codes, the schema v2 envelope and the npm bin contract are unchanged.
|
|
274
|
-
|
|
275
|
-
```sh
|
|
276
|
-
# From source (listens on 127.0.0.1:8080 by default)
|
|
277
|
-
WEB_ACCESS_CONFIG=~/.config/web-access-cli/config.json \
|
|
278
|
-
WEB_ACCESS_GATEWAY_API_KEY_FILE=/path/to/gateway-api-key \
|
|
279
|
-
pnpm start:gateway # or node --import tsx src/server.ts
|
|
280
|
-
|
|
281
|
-
# Built output
|
|
282
|
-
node dist/server.js
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
| Method | Path | Notes |
|
|
286
|
-
| --- | --- | --- |
|
|
287
|
-
| `POST` | `/v1/tools/search` | DeepSeek Search compatible |
|
|
288
|
-
| `POST` | `/v1/tools/fetch` | DeepSeek Fetch compatible |
|
|
289
|
-
| `GET` | `/`, `/panel` | Request records panel (HTML, no data in the page itself) |
|
|
290
|
-
| `GET` | `/admin/api/records` | Recent request records (requires the Bearer key) |
|
|
291
|
-
| `GET` | `/healthz` | Process liveness, no authentication |
|
|
292
|
-
| `GET` | `/readyz` | Configuration/lifecycle readiness, no authentication |
|
|
293
|
-
|
|
294
|
-
The gateway reads its own Bearer key from a file only (`WEB_ACCESS_GATEWAY_API_KEY_FILE`), compares fixed-length digests with Node crypto, and rejects duplicate `Authorization` headers. Request bodies are counted as a stream with a 64 KiB cap and a 10-second read timeout; at most four tool requests may be in flight, and excess requests receive 429. Success and error responses use `Cache-Control: no-store`.
|
|
295
|
-
|
|
296
|
-
Request fields match the official contract:
|
|
297
|
-
|
|
298
|
-
| Field | Behavior |
|
|
299
|
-
| --- | --- |
|
|
300
|
-
| `query` | Required, 1–128 Unicode characters, not entirely whitespace; search operators are preserved |
|
|
301
|
-
| `count` | Integer 1–50, default 10 |
|
|
302
|
-
| `ori_question` | Optional or `null`; when non-null, 1–128 characters |
|
|
303
|
-
| `localization` | Optional, default empty string; when non-empty it must be an IP address |
|
|
304
|
-
| `need_content` | Optional, default `false`; must be a boolean |
|
|
305
|
-
|
|
306
|
-
Fetch accepts only a non-empty absolute HTTP(S) URL of at most 8192 characters with a hostname and no userinfo. Unknown request fields are ignored and cannot select a provider, credential, timeout or configuration override.
|
|
307
|
-
|
|
308
|
-
**Responses are protocol-compatible, not equivalent to the official search engine**: `summary` reuses `snippet`, `rerank_score` is always `0`, `authority` is always `-1`, and `site_name`/`site_icon`/`date` are always `null`. The placeholder score is **not** a DeepSeek quality score; clients filtering on `rerank_score >= 1.5` or `authority >= 3` must change strategy or they will drop every result. Result count, ranking and summary quality are not promised, and the gateway never paginates or merges providers to fill `count`.
|
|
309
|
-
|
|
310
|
-
`ori_question` and `localization` are validated and accepted, but the neutral core has no equivalent capability, so they do not affect provider requests and are not folded into `query`.
|
|
311
|
-
|
|
312
|
-
With `need_content=true`, content is backfilled in rank order with at most two concurrent tasks, a 30-second total window, 10 seconds per item, and a 3-second per-provider attempt cap. Failed, timed-out or over-limit content stays empty while the result is kept; one content value is capped at 256 KiB JSON-encoded UTF-8 and the complete response at 8 MiB, clearing content from the lowest ranks when needed. **Backfill spends extract provider quota.**
|
|
313
|
-
|
|
314
|
-
The gateway derives its own config for Fetch and backfill with a 1-character minimum, so valid short pages succeed, while the source Markdown probe still requires at least 500 characters (or the original threshold, whichever is larger) so a short `Not found` page cannot win. Search and Fetch each use the configured total timeout; backfill has an independent 30-second window, and client disconnects cancel in-flight work.
|
|
315
|
-
|
|
316
|
-
Errors are always `{ "error": { "message": "...", "type": "...", "param": null, "code": "..." } }`: 400 invalid input, 401 `invalid_api_key`, 402 insufficient quota, 404 no usable content or an unavailable direct target, 408 body read timeout, 413 body too large, 429 rate limited (with `Retry-After`), 504 upstream timeout, and 500 for credential/configuration failures, unknown failures or oversized output. Target-site 401/403 means Fetch is unavailable; cloud-provider 401/403 returns 500.
|
|
317
|
-
|
|
318
|
-
The gateway never writes provider order and never modifies configuration: it uses the snapshot loaded at startup. Deployment, Docker Compose templates, secret mounts, diagnostics, upgrades and rollback are documented in [deploy/README.md](deploy/README.md); the architectural decision is in [docs/adr/0019-deepseek-tool-gateway.md](docs/adr/0019-deepseek-tool-gateway.md).
|
|
319
|
-
|
|
320
|
-
Open the gateway root or `/panel` in a browser and enter the gateway key to inspect the last 500 tool requests (status, duration, error code, provider, query/URL and the first five result titles). Records are in memory only, are cleared on restart, and never contain response bodies, headers or credentials; unauthenticated requests are excluded. The panel HTML carries no data, and `/admin/api/records` uses the same Bearer key as the tool endpoints. A bookmark with an `#apikey=<key>` fragment logs in directly; the script strips it from the address bar immediately. Do not pass the key in a query string.
|
|
321
|
-
|
|
322
|
-
## HTTP and security boundaries
|
|
323
|
-
|
|
324
|
-
- Honors `HTTP_PROXY`, `HTTPS_PROXY`, `ALL_PROXY`, and `NO_PROXY` by default.
|
|
325
|
-
- Follows up to five redirects. Cross-origin redirects remove authentication, Cookie, and common token headers.
|
|
326
|
-
- Enforces a streaming byte limit on every response instead of buffering an unbounded response first.
|
|
327
|
-
- HTTP Extract uses LinkeDOM, Mozilla Readability, and Turndown, with fallback parsing for Next.js RSC payloads.
|
|
328
|
-
- HTTP Extract prefers an enabled source Markdown variant (`.md` or directory `index.md`) before provider routes when that variant returns usable Markdown.
|
|
329
|
-
- Returns `unsupported_content` for PDF, image, audio, video, zip, and general binary content.
|
|
330
|
-
- 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.
|
|
331
169
|
|
|
332
|
-
##
|
|
170
|
+
## Security boundaries
|
|
333
171
|
|
|
334
|
-
|
|
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.
|
|
335
175
|
|
|
336
|
-
|
|
176
|
+
## Scope
|
|
337
177
|
|
|
338
|
-
|
|
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).
|