llm-api-gateway-cli 1.0.5 → 1.0.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.en.md CHANGED
@@ -21,6 +21,12 @@ model actually reads and writes files in a directory you pick:
21
21
  Modes 4, 5 and 6 are plain Node.js with no additional dependencies — the two SDKs above are only
22
22
  needed by modes 1 and 2. Modes 4 and 5 run on **one port** (`3100`) as one service.
23
23
 
24
+ **Output language.** Every command-line entry point accepts `--lang en` (or the environment
25
+ variable `LLM_GATEWAY_LANG=en`) and then prints its help, banner, errors, REPL prompts and
26
+ `config` / `setup` output in English. The default is Chinese, and the Web UI and the manual page
27
+ stay Chinese either way — this switch covers the command line only (details under
28
+ [Output language](#output-language-command-line-only) below).
29
+
24
30
  ## Requirements
25
31
 
26
32
  - **Node.js >= 18** (`node -v`). The installers check this and stop with a clear message; they never
@@ -120,6 +126,112 @@ Bare `llm-api-gateway-cli` (or `gateway-agent`) with no arguments asks two quest
120
126
 
121
127
  Slash commands are available inside the REPL (`/help` lists them; the CLI REPL has 10 of them).
122
128
 
129
+ ### Managing MCP servers from the CLI (`/mcp ...`)
130
+
131
+ `/mcp` used to only print the tool-list status. It now also manages the binding and registers your
132
+ own HTTP MCP server (BYO-A) with the same `sk-` key you already use:
133
+
134
+ | Subcommand | What it does |
135
+ | --- | --- |
136
+ | `/mcp` | Overview: what this key has **bound**, what you may **self-add**, what you **registered**, plus the mode and tool count |
137
+ | `/mcp refresh` | Force-reload the tool list (unchanged behaviour) |
138
+ | `/mcp bind +3 -5` | Add `+id` / remove `-id` (the id must be in the gateway's `selectable_ids`; anything else is rejected locally with **no request sent**) |
139
+ | `/mcp bind =2,7` | Set the whole binding (ordered) |
140
+ | `/mcp mode off\|inject\|loop` | Change the mode only — the request body carries `mode` alone, so the binding is left untouched |
141
+ | `/mcp add <name> <URL> [--header "k: v"]...` | Register your own HTTP MCP server: the gateway probes it once and binds it to this key |
142
+ | `/mcp rm <name\|id>` | Delete one of **your own** registrations (asks first; in a non-interactive shell it refuses and asks for the argument) |
143
+
144
+ Three things worth knowing:
145
+
146
+ - **The gateway decides, the CLI never guesses.** Whether self-registration is open comes from
147
+ `self_server.allowed` in `GET /v1/mcp/binding`. While it is closed, `/mcp add` only tells you how to
148
+ open it (set `MCP_ALLOW_SELF_SERVERS=true` in the gateway's `.env` and restart the gateway) and does
149
+ **not** send the request it knows would 403.
150
+ - **Credentials are only ever shown masked.** Query parameters such as `?key=` / `?ak=` and the values
151
+ of `--header` go to the gateway in plaintext but are never printed: every output path (including the
152
+ `GATEWAY_MCP_DEBUG=1` diagnostics and the URL a gateway error echoes back) goes through one masking
153
+ implementation (`maskUrl` / `maskSecrets` in `lib/mcpadmin.js`). Masking is by parameter *name*, so
154
+ do not hide a credential behind a custom name — `?mykey=` is not on the list and shows up as-is.
155
+ - **Every change reloads the list.** `bind` / `mode` / `add` / `rm` refresh the tool list and print the
156
+ new count right away, so "changed it but the list looks stale" cannot happen. "Registered, but the
157
+ probe failed" (row created, server disabled, reason in `probe_error`) is a different outcome from
158
+ "registration failed", and you are told which one you got.
159
+
160
+ > BYO-A only (public http/https addresses): no CLI-side direct connections and no task-page (browser)
161
+ > entry point — those APIs need the `sk-` key while the page only holds an admin JWT, which is a
162
+ > separate decision with its own security questions.
163
+
164
+ #### The same thing in the browser: `/mcp` (the key never reaches the browser)
165
+
166
+ If you would rather not use the terminal, switch to the "MCP" tab in the shell's tab bar (same page,
167
+ no new browser tab; Alt+5), or open **http://127.0.0.1:3100/mcp** directly (the standalone task page
168
+ links there from its sidebar too). It covers the same ground as the CLI
169
+ subcommands: see the servers this key has **bound**, the ones you may **self-add** and the ones you
170
+ **registered**, change the binding by ticking boxes, change the mode (mode only — the binding is left
171
+ untouched), register your own server with a name and a URL, and delete your own registrations after a
172
+ confirmation.
173
+
174
+ - **The scope is your `sk-` key, not the whole gateway.**
175
+ - **The key never reaches the browser:** the page only calls the local `/api/mcp*` endpoints, and the
176
+ server-side hub forwards them to the gateway with the key it already holds (the same pattern as the
177
+ chat page's `/api/chat`). Neither the page source nor the network requests contain the key.
178
+ - **Gated like the task records:** these endpoints are served only while the service listens on
179
+ loopback (`--allow-remote-fs` opens them up), and a missing key produces a readable message instead
180
+ of a 500.
181
+ - **It does not replace the gateway's admin page:** global servers, auditing and statistics stay at
182
+ `http://localhost:9000/#/mcp` (that menu item requires an admin login). Whether self-registration is
183
+ allowed at all remains a deployment-time `.env` switch on the gateway — deliberately not a button
184
+ here.
185
+
186
+ API: `GET /api/mcp` (binding plus tool list), `PATCH /api/mcp/binding` (`{serverIds}` or `{mode}`),
187
+ `POST /api/mcp/servers`, `DELETE /api/mcp/servers/<id>` — pinned by `tests/mcp-ui.test.mjs`.
188
+
189
+ Interactive sessions are persisted: each run writes `~/.llm-api-gateway-cli/sessions/<uuid>.json`
190
+ (sliding 15 days, at most 50 files, 1 MB each), and `--continue` / `--resume <id>` — or `/resume`
191
+ inside the REPL — brings one back with its context and any write that was still waiting for
192
+ approval. These sessions are **separate from the Web UI's tasks**: the Web sidebar lists `tasks/`
193
+ only, so a conversation you had in the terminal never shows up there, and a Web task is never
194
+ offered by `--resume`. Resuming also restores the working directory recorded in the session, so you
195
+ can start it from anywhere (`-C` overrides it).
196
+
197
+ **Reading them in the browser:** the server serves a read-only viewer at
198
+ `http://127.0.0.1:3100/sessions`, or just switch to the sessions tab in the shell's tab bar (same
199
+ page, no new browser tab; Alt+4). The standalone task page links there from its sidebar too.
200
+ It lists the sessions
201
+ in `sessions/` with their working directory, model, message count, token usage and last activity,
202
+ and replays a full transcript — tool calls and reasoning included. It is read-only: no input box,
203
+ and to actually continue a conversation you go back to the terminal. Data comes from
204
+ `GET /api/cli-sessions` and `GET /api/cli-sessions/<id>`, which are gated like the task records:
205
+ loopback only unless you pass `--allow-remote-fs`.
206
+
207
+ ### Output language (command line only)
208
+
209
+ The default output language is Chinese. Add `--lang en`, or set `LLM_GATEWAY_LANG=en`; the flag
210
+ wins when both are given.
211
+
212
+ ```bash
213
+ gateway-agent --help --lang en # English help
214
+ gateway-agent -i --lang en # English REPL: prompts, banner, approval gate
215
+ LLM_GATEWAY_LANG=en gateway-agent -p "…" # same thing through the environment
216
+ node server.js --help --lang en # the server entry accepts it too (help output only)
217
+ ```
218
+
219
+ Recognised values are `zh` / `zh-CN` / `cn` and `en` / `en-US` / `english`. An unrecognised value
220
+ is ignored rather than fatal, and a missing English string falls back to Chinese, so the output is
221
+ never half translated.
222
+
223
+ What switches: `--help` of all five entry points, the startup banner, errors and the missing-key
224
+ hint, the REPL prompts (`/help` and its command table included), the write-approval gate, and the
225
+ `config` / `setup` subcommand output.
226
+
227
+ What does **not** switch (Chinese by design): the **Web UI** (chat, task and settings panels), the
228
+ **manual page** at `/manual` together with the command tables it renders, the `/api/commands`
229
+ payload served to the browser, and the system prompts / tool descriptions sent to the model —
230
+ that last one is model behaviour, a separate decision from interface language.
231
+
232
+ The implementation is `lib/i18n.js` (about 80 lines, no dependencies): language is a process-level
233
+ switch set once by each entry point, and `pick(zh, en)` returns Chinese whenever English is missing.
234
+
123
235
  ### Web UI — one page, three tabs
124
236
 
125
237
  ```bash
@@ -132,13 +244,14 @@ gateway-web # or: node server.js
132
244
  | Chat panel | `http://127.0.0.1:3100/chat` | Pure conversation, no file system access |
133
245
  | Task panel | `http://127.0.0.1:3100/task` | Pick a directory, the model really reads/writes it |
134
246
  | Manual | `http://127.0.0.1:3100/manual` | Operation manual (Chinese) generated from the live command tables |
247
+ | CLI sessions (read-only) | `http://127.0.0.1:3100/sessions` | Archive of terminal `gateway-agent -i` conversations: list + full replay, no input box |
135
248
 
136
249
  The shell owns the single top bar (tabs, per-panel actions, theme, reload); panels are loaded
137
250
  lazily on first activation and keep their state when you switch away. `Alt+1/2/3` switches tabs.
138
251
  Each panel also opens standalone, which is what external systems embed.
139
252
 
140
253
  Useful flags: `--port`, `--host`, `--store <dir>`, `--mode manual|auto|plan`, `--max-steps`,
141
- `--allow-bash`, `--allow-remote-fs`, `--verbose` (`node server.js --help`).
254
+ `--allow-bash`, `--allow-remote-fs`, `--verbose`, `--lang zh|en` (`node server.js --help`).
142
255
 
143
256
  ### Task mode in one minute
144
257
 
@@ -176,7 +289,8 @@ model-side session per task. Default retention is a sliding **15 days / at most
176
289
  | --- | --- |
177
290
  | `~/.llm-api-gateway-cli/config.json` | Settings (never the key) |
178
291
  | `~/.llm-api-gateway-cli/credentials.json` | The key, owner-readable only |
179
- | `~/.llm-api-gateway-cli/tasks/` | Task records + per-task sessions |
292
+ | `~/.llm-api-gateway-cli/tasks/` | Task records + per-task sessions (what the Web UI lists) |
293
+ | `~/.llm-api-gateway-cli/sessions/` | CLI REPL sessions (`--continue` / `--resume`); **not** listed in the Web UI |
180
294
 
181
295
  Precedence everywhere is **command-line flag > environment variable > `.env` > `config.json` >
182
296
  built-in default**, and every surface (CLI, REPL, Web UI) reads and writes the same files.
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  > 🌐 **English entry:** [`README.en.md`](README.en.md) —— 英文入口(是什么 / 怎么装 / 怎么跑)。
4
4
  > 本页是**权威全文**(中文,最全):所有参数、全部接口、存储内部细节、Docker 发布与排错。
5
+ > 🈯 **命令行英文输出**:所有 CLI 入口都认 `--lang en`(或环境变量 `LLM_GATEWAY_LANG=en`),不给就是中文;
6
+ > 覆盖范围、优先级与「哪些不变」见下文「命令行输出语言」一节,**Web 界面与手册页仍是中文**。
5
7
 
6
8
  验证 `llm-api-gateway`(`http://127.0.0.1:9000`)除了 Web 对话页 / SDK 之外,**CLI 方式同样可用**。多个脚本覆盖多种接入方言,其中**原生 Agent(`cli-agent.js`)是终端里干活的推荐方式** —— 自带工具循环,无需安装 Claude Code。
7
9
 
@@ -505,6 +507,7 @@ node task-server.js --mode auto # 默认审批模式:manual / auto
505
507
  | `GET /chat` | 聊天面板(单独打开;老链接 `/index.html` 也是它) |
506
508
  | `GET /task` | 任务面板(单独打开,选目录、模型真读写) |
507
509
  | `GET /manual` | 操作手册面板(安装与启动 + 任务流程 + 三端斜杠指令对照) |
510
+ | `GET /sessions` | **命令行会话的只读存档**(终端 `gateway-agent -i` 的对话:列表 + 全文回放,不能在这里续写) |
508
511
  | `GET /api/commands` | 三端(命令行 / 本机聊天页 / 任务页)指令的机器可读清单,手册页据此生成,不手抄 |
509
512
  | `GET /api/cost?prompt=&completion=&model=` | 费用粗估(与 CLI `/cost` 同一份单价表 `lib/pricing.js`) |
510
513
  | `GET /api/config` | 网关地址、模型、工具清单、上限、密钥是否就绪(只回脱敏值 + 来源 + 密钥文件路径) |
@@ -522,12 +525,14 @@ node task-server.js --mode auto # 默认审批模式:manual / auto
522
525
  | `GET /api/tasks` | 任务列表(顺手清理过期任务,返回 `removed`) |
523
526
  | `POST /api/tasks` | 新建一条任务(id 由页面生成 UUID,服务端校验) |
524
527
  | `GET /api/tasks/<id>` | 读一条任务的完整内容 |
528
+ | `GET /api/cli-sessions` | **命令行会话**(模式六)元信息列表:`dir / ttlDays / maxSessions / sessions[]`(**不含正文**) |
529
+ | `GET /api/cli-sessions/<id>` | 读一条命令行会话的完整 `messages`(只读;id 必须是 UUID) |
525
530
  | `PUT /api/tasks/<id>` | 覆盖保存(改名、换工作目录、写入消息) |
526
531
  | `DELETE /api/tasks/<id>` | 删除一条任务(连带删掉它的会话文件) |
527
532
  | `POST /api/tasks/prune` | 立即按 15 天 / 20 条清理一次(同样连带删会话) |
528
533
 
529
534
  > `/api/config` 里的 `tools` 是**当前真正可用的工具**:`bash` 未由宿主开启时不会出现在列表里。
530
- > `/api/store`、`/api/tasks*` 与 `/api/settings` 和目录浏览一样只在本机监听时开放(开在 `0.0.0.0` 上时一律 403 —— 它们能读本机路径、也能改你的配置)。任务 id 必须是 UUID 形状,服务端按格式校验,`..%2F` 之类的穿越 id 一律 400。
535
+ > `/api/store`、`/api/tasks*`、`/api/cli-sessions*` 与 `/api/settings` 和目录浏览一样只在本机监听时开放(开在 `0.0.0.0` 上时一律 403 —— 它们能读本机路径、也能改你的配置)。任务 id 必须是 UUID 形状,服务端按格式校验,`..%2F` 之类的穿越 id 一律 400。
531
536
 
532
537
  ### 沙箱边界
533
538
 
@@ -575,6 +580,15 @@ node task-server.js --mode auto # 默认审批模式:manual / auto
575
580
  | CLI 会话(模式六的 `--continue`) | `~/.llm-api-gateway-cli/sessions/` |
576
581
  | 配置文件 | `~/.llm-api-gateway-cli/config.json` |
577
582
 
583
+ > **两份「会话」各管各的,互不相通**:网页任务页的侧边栏只列 `tasks/` 里的**任务**(模式五),
584
+ > 命令行 REPL 的对话在另一个目录 `sessions/` 里(模式六),网页侧**没有任何接口读它** ——
585
+ > 所以**终端里聊的内容不会出现在网页任务列表里**,反过来网页任务也不在 `--resume` 的候选里
586
+ > (两者的记录字段本来就不同:任务有标题、更新时间、回复索引,会话只有消息与用量)。
587
+ > 要接着终端那条聊:`gateway-agent -i --continue`(最近一条)或 `--resume <id>`,REPL 里 `/resume` 列最近几条。
588
+ > 想在网页里**看**它:服务起着时打开 **http://127.0.0.1:3100/sessions**(任务页顶栏也有「命令行」入口)——
589
+ > 那一页是**只读回放**(列表 + 全文 + 用量),没有输入框,改不了任何东西;接口是 `GET /api/cli-sessions[ /<id>]`,
590
+ > 与任务记录同一道本机闸门(服务没绑在本机地址且没加 `--allow-remote-fs` 时回 403)。
591
+
578
592
  > 老版本用的是平台数据目录(`%LOCALAPPDATA%` / `~/Library/Application Support` / `$XDG_DATA_HOME`),
579
593
  > 早期版本还会退到**脚本目录下的 `.tasks/`** —— 同一台机器上数据可能在两三个地方,而且会把 `.tasks`
580
594
  > 写进项目目录。现在统一到主目录一处:**启动时如果发现旧位置有数据,会自动复制到新位置**,
@@ -670,6 +684,7 @@ node cli-agent.js -i --resume <会话id> # 切到指定
670
684
  | `--yes` | 自动批准所有**文件写入**(**危险**:模型可直接改文件,慎用)。MCP 调用不需要它 |
671
685
  | `--verbose` | 打印工具结果正文(默认只显示一行摘要) |
672
686
  | `--no-color` | 关闭彩色输出(也遵循 `NO_COLOR`) |
687
+ | `--lang <zh\|en>` | 输出语言(默认 `zh`,也可用 `LLM_GATEWAY_LANG`)—— 帮助 / 横幅 / 报错 / REPL 提示都跟着变,详见「命令行输出语言」 |
673
688
 
674
689
  工具集与模式五完全相同(`list_dir` / `read_file` / `search_files` / `glob` / `grep` / `apply_patch` / `write_file` / `bash`),其中 `bash` **默认关闭**、加 `--allow-bash` 才可用。单个任务默认最多 100 轮模型调用(`--max-steps` 可调)。**写入闸门在终端里,而且只管本机文件**:模型每次写入(`write_file` / `apply_patch`,以及开启后的 `bash`)都会先打印预览框(路径、新建/覆盖、行数与字节数;覆盖已有文件时还打印行级 `- / +` diff),再问你 `y/N`。非交互环境(如管道)**默认拒绝文件写入**,确需自动批准请显式加 `--yes` —— 这样脚本误改文件的风险最小。
675
690
 
@@ -681,7 +696,7 @@ node cli-agent.js -i --resume <会话id> # 切到指定
681
696
  > 「计划模式不给 MCP 工具」这条边界**没有变**(它只做本机只读调研):它是「给不给模型」的问题,
682
697
  > 和「要不要人工批准」是两件事,在代码里也是两个谓词(`isWriteTool` / `isFileWriteTool`)。
683
698
 
684
- **会话落盘**:交互会话默认写到用户主目录下的 `.llm-api-gateway-cli/sessions/`(与任务存储同一个根;主目录不可写时才退到旧位置、再到系统临时目录),滑动保留 15 天、最多 50 条、单文件 1MB 上限。`--continue` 续最近一条、`--resume <id>` 切指定一条;恢复时会连同「待批准的写入」一起还原,进程退出前没批完的写入,回来还能接着批。用 `--no-session` 可完全关掉落盘。
699
+ **会话落盘**:交互会话默认写到用户主目录下的 `.llm-api-gateway-cli/sessions/`(与任务存储同一个根;主目录不可写时才退到旧位置、再到系统临时目录),滑动保留 15 天、最多 50 条、单文件 1MB 上限。`--continue` 续最近一条、`--resume <id>` 切指定一条;恢复时会连同「待批准的写入」一起还原,进程退出前没批完的写入,回来还能接着批。用 `--no-session` 可完全关掉落盘。**这份会话不会出现在网页任务页的列表里**(网页列的是 `tasks/` 里的任务,见上文「存储位置」那条说明)—— 想在网页里看它,打开 `/sessions`(**只读**回放,见「命令行会话(只读)」);要接着聊就在终端 `--continue` / `--resume`;恢复时工作目录沿用会话里记下的那个(`-C` 才覆盖),所以在哪个目录敲都行。
685
700
 
686
701
  **项目记忆**:启动时若工作目录下有 `AGENTS.md` 或 `CLAUDE.md`,会读进来注入 system prompt(上限 4KB,超出截断);`--no-memory` 可关闭。
687
702
 
@@ -689,6 +704,41 @@ node cli-agent.js -i --resume <会话id> # 切到指定
689
704
 
690
705
  > 命令表(`lib/commands.js`)与网关 Web 对话页的斜杠指令**共用一套元数据**(`capability` 能不能跑 / `surfaces` 在哪一端有意义 / `args`);同名命令两端语义一致,这是被两侧的跨端契约断言钉住的。网关 Web 的「恢复默认设置」因此把 `/reset` 让名给 CLI 的「重开会话」、自己改叫 `/defaults`(口径见网关仓库 `docs/SLASH-CLI-ALIGNMENT-20260916.md`)。
691
706
 
707
+ #### `/mcp`:看绑定、改绑与「登记自己的 MCP 服务器」
708
+
709
+ 以前在 CLI 里只能**读** MCP 工具(`/mcp` 打一行清单状态),要改绑定得去管理员后台。现在 `/mcp` 多了一组子命令,用你手里这把 `sk-` 密钥就能自助办:
710
+
711
+ | 子命令 | 作用 |
712
+ | --- | --- |
713
+ | `/mcp` | 总览:本密钥**已绑**什么、**可自助加**哪些、**我自建**了哪些(各带 id 与名字)+ 当前模式与工具数 |
714
+ | `/mcp refresh` | 强制重拉工具清单(既有行为,不变) |
715
+ | `/mcp bind +3 -5` | 改绑:`+id` 加上、`-id` 去掉(id 必须出现在网关给的 `selectable_ids` 里,越权 id **本地就拒绝、不发请求**) |
716
+ | `/mcp bind =2,7` | 直接设定整份绑定(有序) |
717
+ | `/mcp mode off\|inject\|loop` | 只改模式,**不动绑定**(下发体里只有 `mode`) |
718
+ | `/mcp add <名字> <URL> [--header "k: v"]...` | 登记自己的 HTTP MCP 服务器(BYO-A):网关探测一次并自动绑到本密钥 |
719
+ | `/mcp rm <名字\|id>` | 删掉**自己登记**的(先确认;非交互环境不自动删,要求写成参数) |
720
+
721
+ 三点要知道的:
722
+
723
+ - **判据来自网关,CLI 不猜**:能不能自助登记由 `GET /v1/mcp/binding` 的 `self_server.allowed` 决定。没开放时 `/mcp add` 只告诉你开启办法(部署方在网关 `.env` 设 `MCP_ALLOW_SELF_SERVERS=true` 并**重启网关**),**不发那次注定 403 的请求**;
724
+ - **凭据只显示掩码**:地址里的 `?key=` / `?ak=` 这类查询参数与 `--header` 的值**明文只发给网关**,CLI 的所有输出(含 `GATEWAY_MCP_DEBUG=1` 的排障行、以及网关错误里回显的那份 URL)一律走同一把掩码(`lib/mcpadmin.js` 的 `maskUrl` / `maskSecrets`)。所以**别把凭据放在自定义参数名里**(掩码是按名字认的):`?mykey=` 不在名单里,会原样显示;
725
+ - **改完立刻重拉清单**:`bind` / `mode` / `add` / `rm` 成功后马上刷新并打印新的工具数 —— 否则「改成功了但清单还是旧的」看起来就像没生效。登记失败与「登记上了但探测连不通」(置为停用 + `probe_error`)是**两种结局**,分开告诉你。
726
+
727
+ > 只做 **BYO-A**(登记 http/https 公网地址);不做 CLI 直连回传,也不做任务页(浏览器)入口 —— 那些接口要的是 `sk-` 密钥,而页面手里只有管理员 JWT,属于另一件带安全决策的事。
728
+ > 五个子命令的失败语义、掩码形状、403 引导与「管理操作不改运行面」都钉在 `tests/mcpadmin.test.mjs`(56 条,含 `GATEWAY_MCP_DEBUG=1` 那条打印路径与「管理面连接跟着 `ctx.cfg` 走」)。
729
+
730
+ #### 同一件事的网页入口:`/mcp`(密钥不进浏览器)
731
+
732
+ 不想敲终端时,起服务后点首页 Tab 栏的「**MCP**」(同页切换,不开新标签;Alt+5),或直接开 **http://127.0.0.1:3100/mcp**(单独打开任务页时它左侧栏也有入口):
733
+
734
+ - **范围就是你这把 `sk-` 密钥**(不是全局):看已绑定 / 可自助加 / 我自建的,勾选改绑、选模式(只改模式不动绑定)、填名字 + URL 登记自己的服务器、删掉自己登记的(删前确认);
735
+ - **密钥不下发**:页面只打本机的 `/api/mcp*`,由 hub 用服务端保存的密钥代发给网关(与聊天页 `/api/chat` 同一套姿势)——浏览器源码、网络请求里都没有 `sk-`;
736
+ - **闸门与任务记录同级**:只在本机监听时开放(服务绑到 `0.0.0.0` 要显式 `--allow-remote-fs`);没配密钥时给可读提示而不是 500;
737
+ - **它替代不了网关的管理员页面**:全局服务器、审计与统计仍在网关 `http://localhost:9000/#/mcp`(管理员登录才看得见那一项)。**「开不开自助登记」永远是网关 `.env` 的部署期开关**,不会变成这里的一个按钮。
738
+
739
+ > 接口:`GET /api/mcp`(绑定 + 工具清单)、`PATCH /api/mcp/binding`(`{serverIds}` 或 `{mode}`)、`POST /api/mcp/servers`、`DELETE /api/mcp/servers/<id>`;契约钉在 `tests/mcp-ui.test.mjs`。
740
+
741
+
692
742
  任务页(`/task`)另有一组**只影响这个页面、不发模型**的斜杠指令(E 组,与上面两端同一套元数据口径,共 14 条):
693
743
 
694
744
  | 组 | 指令 | 作用 |
@@ -724,6 +774,17 @@ node cli-agent.js -i --resume <会话id> # 切到指定
724
774
 
725
775
  > 本模式与模式三是**两种不同的接入姿态**:模式三让 Claude Code 这个外部 Agent 走网关(验证「网关能当 Anthropic 后端」),模式六是**网关自带 Agent**(验证「不装 Claude Code 也能在终端干活」)。两者共存,按需选用。
726
776
 
777
+ ### 命令行会话(只读):`/sessions`
778
+
779
+ 终端里 `gateway-agent -i` 聊过的东西,可以在网页里翻出来看:**http://127.0.0.1:3100/sessions**,或者直接切首页 Tab 栏的「**命令行**」(同页切换,不开新标签;Alt+4)。单独打开任务页时,它左侧栏也有一个入口。
780
+
781
+ - **它是存档,不是任务**:列的是 `<数据根>/sessions/`(模式六),与网页任务页的 `tasks/` 是两套存储,两边不会互相出现(原因见上文「存储位置」那条说明);
782
+ - **只读**:页面上没有输入框、没有写入接口(只有 `GET /api/cli-sessions` 与 `GET /api/cli-sessions/<id>`),要接着聊得回终端跑页面上给出的 `gateway-agent -i --resume <id>`(一键复制);
783
+ - **能看到什么**:工作目录、模型、消息条数与 token 用量、最后活动时间;点开回放全文,含工具调用与参数、思考过程、以及「这条会话还有待批准的写入」这类挂起态提示;
784
+ - **闸门与任务记录同级**:只在本机监听时开放,服务绑到 `0.0.0.0` 要显式 `--allow-remote-fs`;会话 id 必须是 UUID(防目录穿越),列表里那条滑动 15 天 / 最多 50 条的清理口径与 CLI 完全一致。
785
+
786
+ > 写这一页的直接原因:用户在终端聊完,去网页任务列表里找,找不到(2026-09-22 的真实反馈)。存储布局本身没变,只是补了一个**看**的入口。
787
+
727
788
  ### 与网关任务模式共用的内核
728
789
 
729
790
  模式五(Web)与模式六(CLI)共用同一套实现,避免两处各写一遍后行为漂移:
@@ -743,6 +804,7 @@ node cli-agent.js -i --resume <会话id> # 切到指定
743
804
  - `lib/sessionstore.js`:模式六的会话落盘(`--continue` / `--resume`,TTL 15 天 / 50 条 / 单文件 1MB);
744
805
  - `lib/jsonstore.js`:共享的原子写入(tmp → rename,Windows 上 EPERM/EACCES/EBUSY 重试 5 次),`runstore` 与 `sessionstore` 都引用它;
745
806
  - `lib/config.js`:四个 CLI 共用的配置解析(`--flag > env > .env` 收在 `resolveConfig` 一处)+ 统一的失败出口 `fail(e, { exit })`;
807
+ - `lib/i18n.js`:**命令行输出语言的唯一实现** —— `--lang` > `LLM_GATEWAY_LANG` > `zh` 的解析(`resolveLang`)、进程级开关(`setLang` / `getLang`)、双语取词 `pick(中文, English)`(缺英文回落中文);**只管命令行**,Web 界面与手册页不读它;
746
808
  - `lib/commands.js`:模式六的斜杠命令表(`COMMANDS` / `parseSlash` / `helpText`)+ 审批预览用的极简行级 `diffLines`;
747
809
  - `public/task-slash.js`:任务页的斜杠指令表(14 条)+ `commandRows()`(与 `lib/commands.js` 同字段,供 `/api/commands` 与手册页共用);`public/manual.js` 把接口数据渲染成手册页的对照表 —— 页面里没有第二份手抄清单;
748
810
  - `public/models.js`:设置面板「模型」下拉的唯一实现(两页共用)—— 选项只来自 `/api/models`,并处理「当前值不在列表里」「一个都没拉到」这两种不能丢配置的情况;`public/render.js` 同理是两页共用的渲染实现;
@@ -767,10 +829,13 @@ npm run test:all # 先离线再联网
767
829
  | `tests/config.test.mjs` | 配置解析优先级(`--flag > env > .env > 默认值`)、`--` 透传、非法数字不塞 NaN、`fail(e, { exit:false })` 不杀 REPL |
768
830
  | `tests/sessionstore.test.mjs` | 会话落盘:目录选择、脏文件与 TTL、体积裁剪、`isSafeId` 防穿越、与 `runTurn` 的 `persist` 回调打通、挂起态恢复与续批 |
769
831
  | `tests/commands.test.mjs` | 斜杠命令表逐项(含 `/model` / `/cost` / `/compact` / `/resume`)、`diffLines` 行级 diff、定价表 |
832
+ | `tests/i18n.test.mjs` | **命令行输出语言**(`--lang en` / `LLM_GATEWAY_LANG`):机制(默认 zh、flag 压过环境变量、认不出的值退回默认、英文缺失回落中文)、五个入口的接线与 `--lang` 参数表、命令表的双语与 `commandRows()` 默认仍中文、缺密钥 / 启动器 / MCP 状态行的英文,以及**真起子进程**跑五个入口的 `--help`(英文输出里不许有中文或全角字符);最后钉住 README(中 / 英)与手册页确实写了这个开关 |
770
833
  | `tests/model-select.test.mjs` | 「模型」只可选:默认项在首位、选项只来自接口、**当前值不在列表里时补项并保持选中(不悄悄换成别的模型)**、空列表只剩「默认 + 当前值」、重复模型名去重、`ensure()` 补项(对照真实 `<select>` 赋不存在的值会变空)、`match()` 大小写;以及真服务一侧:`GET /api/models` 拉不到上游时**也回 `default`**、`/models.js` 可取、两个页面都是 `<select>` 且没有 datalist 退路 |
771
834
  | `tests/task-slash.test.mjs` | 任务页指令表(14 条):元数据与自检、解析口径(别名 / 大小写 / 全角斜杠 / `//` 转义 / 未知指令不发给模型)、**空前缀返回全部候选**(敲 `/` 就该看见全貌)、逐条行为、**`/model` 只认列表里的名字**(列表外被拒并列出可选项)、`commandRows()` 字段与 CLI 对齐、fs 闸门 |
772
835
  | `tests/manual.test.mjs` | 操作手册页:`/manual` 与静态资源可访问、正文覆盖操作流程要点、**安装与启动一节(三条路线 + 首次配置 + 启动自检)与 `package.json` / 两份安装脚本 / `lib/common.js`(密钥别名、`.env` 查找顺序、默认网关地址)/ `lib/settings.js`(config.json 拒收密钥)/ `lib/secrets.js`(密钥文件名与落点)交叉一致,且「Web 服务没密钥也能起」与「命令行 agent 缺密钥即退」两半都写明**、`GET /api/commands` 一条不多一条不少地覆盖两张表、`GET /api/cost` 与 `lib/pricing.js` 逐字同口径,以及**「表里每一条都出现在手册里」**的回归闸门(含渲染后的文本断言) |
773
836
  | `tests/mcp-batch.test.mjs` | **MCP ×「一批工具调用」**:模拟网关**真的**执行「带 tool_calls 的 assistant 消息必须被逐条回应」这条协议约束(破了就回 400,与真实网关一致),据此压两个实测踩到的问题——一批里排在被批准调用**后面**的调用原先既不执行也不回应、恢复时又只补一个 ⇒ 下一轮请求 400 打死整条任务;以及 MCP 原先被当「写入类」逐个要批准 ⇒ 非交互 `-p` 下所有 MCP 调用全被拒。同时守住反向边界:本机写入仍然必须批准、计划模式仍然拿不到 MCP 工具 |
837
+ | `tests/mcpadmin.test.mjs` | **MCP 管理面**(`/mcp bind\|mode\|add\|rm`):与清单面相反的性质——管理面失败**必须吵**(原因 + 怎么修)、清单面失败要静默;掩码(查询参数与 `--header` 的值在 stdout/stderr/`GATEWAY_MCP_DEBUG` 里都只有掩码,含网关错误回显的那份 URL)、越权 id 本地就拒且不发请求、`mode` 的 body 不带 `server_ids`、`self_server.allowed=false` 时 `/mcp add` 一次请求都不发、`rm` 先确认(非 TTY 不删)、老网关 502 → 「可能没重启或版本较旧」,以及「管理操作不改 `mcpHeadersForChat()` 的双重执行保护」这条不变量 |
838
+ | `tests/mcp-ui.test.mjs` | **网页 MCP 面板**(`/mcp` + `/api/mcp*`):真 hub + 假网关跑完整链路 —— 列表形状、`mode` 的请求体不带 `server_ids`、非法值与越权 400、登记时 URL 原文发给网关但**回包只有掩码**、探测失败 ≠ 登记失败、删除、403 闸门、没配密钥 409 而不是 500,以及**响应与页面源码里都没有密钥**(只在服务端);最后把 `public/mcp.js` 放进 DOM 垫片真跑一遍(渲染 + 点「保存模式」发出 `{mode}`) |
774
839
  | `tests/modes.test.mjs` | 三种审批模式:计划模式拿不到写入工具、硬写也被挡下且不落盘、自动模式直接执行并标 `auto`、自动模式仍守沙箱、非计划模式不误标 `planReady`、系统提示词按模式分流;以及挂起态落盘:TTL 清理、脏文件容错、id 防穿越、**换模块实例模拟重启后仍能取回待批准** |
775
840
  | `tests/plandoc.test.mjs` | **计划落盘**:概要清洗与日期命名(`docs/YYYYMMDD-<概要>.md`)、`docs/` 不存在时自动创建、同名同内容幂等、同名异内容**只追加「更新」不覆盖**、概要里的 `../` 出不去工作目录、系统提示词按「有没有已落盘计划」分流;以及端到端:真起 hub 服务 + 模拟上游跑一轮计划模式,确认 SSE 发过 `plan_saved`、磁盘上真有那份计划、下一轮自动模式的 system 里带着它的路径、会话文件里记着 `planFile` |
776
841
  | `tests/dom.test.mjs` | JS 引用的 DOM id 是否都在 HTML 中、`dom.xxx` 引用的 key 是否都定义过、服务端每种事件前端是否都处理、CSS 变量与 class 是否都有定义、两个页面的侧边栏是否同构、**两个页面的「模型」都是 `<select>` 且没有 datalist 退路**;以及**切任务不打断**的源码契约(`switchTask` 里不许再出现 `abort`、必须走缓存、落盘按任务走、停止只停当前那条) |
@@ -788,6 +853,38 @@ npm run test:all # 先离线再联网
788
853
  | `tests/secrets.test.mjs` | 免 `.env` 的密钥配置:密钥文件路径(跟数据根走 / `LLM_GATEWAY_SECRET_FILE` 覆盖)、读写往返与覆盖、POSIX `0600` 与「权限被放松」告警、坏文件降级、优先级(`--key`/env > 文件)、`keyRow` 只回掩码、`config set/get/list/unset/path key` 的落点,以及**没有密钥也能起服务** + 接口回 409 `needsKey` + 界面 PUT 一次即生效/手工改文件刷新即生效 |
789
854
  | `tests/config-source.test.mjs` | **配置来源与首次使用引导**:`loadDotEnv` 记下「哪个变量由哪个 `.env` 提供」且真环境变量不算、来源文案四层(内置默认 / 启动参数 / 环境变量 + `.env` 路径 / 配置文件路径)、`summarizeConfigSources` 的「全默认 + 无 config.json = 全新机器」判定、密钥来源说到具体别名、**四个 CLI 也认 `credentials.json`**(本轮修掉的缺陷),以及 CLI/hub 横幅必须带来源、缺密钥时仍退 1 的源码契约 |
790
855
 
856
+ ## 命令行输出语言(`--lang en`)
857
+
858
+ 命令行侧可以切成英文,**默认仍然是中文**(不给语言时与加这个开关之前逐字一致)。两种给法等价,`--lang` 优先:
859
+
860
+ ```bash
861
+ node cli-agent.js --help --lang en # 英文帮助
862
+ gateway-agent -i --lang en # 英文 REPL(提示语、写入批准闸门都变英文)
863
+ LLM_GATEWAY_LANG=en gateway-agent -p "…" # 环境变量写法,与上面等价
864
+ # Windows PowerShell:$env:LLM_GATEWAY_LANG='en'; gateway-agent -i
865
+ node server.js --help --lang en # 起服务的方式也认(只影响这一行帮助)
866
+ ```
867
+
868
+ | 给法 | 优先级 | 说明 |
869
+ | --- | --- | --- |
870
+ | `--lang en` / `--lang zh` | 最高 | `zh-CN`、`en-US`、`english`、`cn` 这类写法都认 |
871
+ | 环境变量 `LLM_GATEWAY_LANG=en` | 次之 | 认不出的值**不报错**,直接退回默认中文 —— 语言不该拦住任何一次运行 |
872
+ | 都不给 | 默认 | `zh` |
873
+
874
+ **覆盖哪些输出**:
875
+
876
+ | 会跟着变 | 不会变(始终中文) |
877
+ | --- | --- |
878
+ | 四个 CLI 入口与 `server.js` 的 `--help` | **Web 界面**:聊天页 / 任务页 / 设置面板 |
879
+ | 启动横幅(网关、模型、密钥来源那几行) | **操作手册页 `/manual`**(包括它的指令表格) |
880
+ | 各种报错、缺密钥提示 | `GET /api/commands` 下发给网页的指令清单 |
881
+ | REPL 提示(`你>`、退出语、`/help` 的指令表) | 发给模型的系统提示词与工具描述(那是**模型行为语言**,不是界面语言) |
882
+ | `config list/get/set/unset/path` 与 `setup` 的输出 | `sendJson` 下发的接口文案(Web 与集成方的契约) |
883
+
884
+ **机制与边界**(`lib/i18n.js`,约 80 行,零依赖):语言是**进程级**开关,只在各入口脚本里 `setLang(resolveLang(args, env))` 一次,其余模块只管 `pick(中文, English)`;**缺英文时自动回落中文**,所以不会出现半中半英或 `undefined`。手册页的表格之所以不受 `server.js --lang en` 影响,是因为 `commandRows()` 的默认语言就是 `zh`,只有显式要英文时才取 `en`。
885
+
886
+ **验证**:`node tests/i18n.test.mjs` —— 机制(15)· 入口接线(14)· 命令表(9)· 其它文案(8)· 真子进程端到端(12)· 文档同步(6),共 **64 条**;离线全量 `npm test` 里也带它。
887
+
791
888
  ## 配置:住在磁盘上,CLI / Web / REPL 同一套
792
889
 
793
890
  **优先级一句话**:`--flag` > 环境变量 > `config.json` > 内置默认;密钥是唯一的例外 —— `--key` / 环境变量 / `.env` > `credentials.json`,而它**永远不进 `config.json`**。
@@ -1152,4 +1249,6 @@ docker run --rm -v llm-api-gateway-data:/data -v "${PWD}:/backup" alpine tar czf
1152
1249
  - **模型名**:按密钥绑定的上游选择 —— Ollama 常用 `qwen3:8b` / `qwen3:8b-nothink`;DeepSeek 常用 `deepseek-v4-pro` / `deepseek-v4-flash`(详见网关 `USEAGE.md`)。
1153
1250
  - **模式六只出思维链没有正文**:思考模型会先把 token 花在 `reasoning_content` 上,`--max-tokens` 给小了就会如此。调大 `--max-tokens` 再试。
1154
1251
  - **想接着上次的会话继续**:`node cli-agent.js -i --continue`(或 `--resume <id>`)。会话默认存在 `~/.llm-api-gateway-cli/sessions`,`--session-dir` 可换地方,`--no-session` 完全关闭落盘。
1252
+ - **刚才命令行聊的,怎么在网页任务列表里找不到**:这是设计如此,不是丢了 —— 网页侧边栏列的是 `tasks/` 里的**任务**(模式五),命令行 REPL 的对话在独立的 `sessions/` 目录(模式六),网页侧没有任何接口读它;`--lang en` 与这件事无关。文件就在 `~/.llm-api-gateway-cli/sessions/<uuid>.json`(一条一个文件),数一下 `ls ~/.llm-api-gateway-cli/sessions`(Windows:`dir %USERPROFILE%\.llm-api-gateway-cli\sessions`)能看到它;接着聊用 `gateway-agent -i --resume <那个 uuid>`,或在 REPL 里敲 `/resume` 从列表里挑。反过来,网页任务也不会出现在 `--resume` 的候选里。
1253
+ - **想在网页里看命令行聊的那些**:起服务后打开 **http://127.0.0.1:3100/sessions**(任务页顶栏「命令行」也是它)。这一页列出 `<数据根>/sessions/` 里的会话(工作目录 / 模型 / 消息数 / token 用量 / 最后活动时间),点开回放全文,含工具调用与思考过程;它**只读**(页面上没有输入框),要接着聊还是回终端 `--resume`。数据源是 `GET /api/cli-sessions` 与 `GET /api/cli-sessions/<id>`,与任务记录同级别:只在本机可访问,服务绑到 `0.0.0.0` 时要显式 `--allow-remote-fs` 才放行。
1155
1254
  - **Web 服务怎么关 / 端口被占**:见上面「[关闭服务](#关闭服务)」。一句话版 —— 同一个终端里 `Ctrl+C`;找不到终端就 `netstat -ano | findstr :3100` 拿 PID 再 `Stop-Process -Id <PID> -Force`(Linux/macOS 用 `lsof -i :3100` 或 `fuser -k 3100/tcp`)。