u1s1-cli 1.11.8 → 1.12.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 (45) hide show
  1. package/dist/agent-setup.d.ts +5 -0
  2. package/dist/agent-setup.js +31 -7
  3. package/dist/api.js +42 -34
  4. package/dist/bench-report.js +12 -9
  5. package/dist/bench.js +34 -31
  6. package/dist/brand.d.ts +2 -1
  7. package/dist/brand.js +38 -12
  8. package/dist/config.d.ts +11 -1
  9. package/dist/config.js +25 -8
  10. package/dist/context-usage.js +12 -9
  11. package/dist/deploy.js +49 -45
  12. package/dist/device-auth.js +7 -5
  13. package/dist/error-humanize.d.ts +1 -0
  14. package/dist/error-humanize.js +18 -6
  15. package/dist/feedback.js +48 -31
  16. package/dist/i18n.d.ts +21 -0
  17. package/dist/i18n.js +46 -0
  18. package/dist/import/index.js +64 -28
  19. package/dist/import/skills.js +72 -41
  20. package/dist/import/util.js +3 -2
  21. package/dist/import/write.js +2 -1
  22. package/dist/index.js +139 -48
  23. package/dist/login.js +39 -28
  24. package/dist/loop.js +18 -12
  25. package/dist/mcp/client.js +15 -14
  26. package/dist/mcp/command.js +76 -48
  27. package/dist/mcp/extension.js +5 -3
  28. package/dist/mcp/tools.js +6 -5
  29. package/dist/model.js +11 -10
  30. package/dist/nudges.d.ts +8 -2
  31. package/dist/nudges.js +22 -3
  32. package/dist/search-tools.js +7 -6
  33. package/dist/shell-doctor.js +9 -8
  34. package/dist/subagent.js +5 -4
  35. package/dist/tools.js +53 -37
  36. package/dist/update.js +29 -28
  37. package/dist/usage.d.ts +1 -1
  38. package/dist/usage.js +96 -43
  39. package/dist/web.js +2 -1
  40. package/dist/workflow/runner.js +33 -29
  41. package/dist/workflow/templates.js +70 -45
  42. package/dist/workflow/tool.js +15 -12
  43. package/package.json +2 -2
  44. package/scripts/patch-pi.js +243 -278
  45. package/scripts/render-regression.mjs +55 -4
package/dist/loop.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { tr } from "./i18n.js";
1
2
  /**
2
3
  * /loop —— 会话级定时循环执行提示词(对标 Claude Code 的 /loop)。
3
4
  *
@@ -34,12 +35,16 @@ function parseIntervalMs(token) {
34
35
  }
35
36
  function formatInterval(ms) {
36
37
  if (ms >= 3_600_000 && ms % 3_600_000 === 0) {
37
- return `${ms / 3_600_000}小时`;
38
+ return tr(`${ms / 3_600_000}小时`, `${ms / 3_600_000}h`);
38
39
  }
39
40
  if (ms % 60_000 === 0) {
40
- return `${ms / 60_000}分钟`;
41
+ return tr(`${ms / 60_000}分钟`, `${ms / 60_000}m`);
41
42
  }
42
- return `${Math.round(ms / 1_000)}秒`;
43
+ return tr(`${Math.round(ms / 1_000)}秒`, `${Math.round(ms / 1_000)}s`);
44
+ }
45
+ /** 「每5分钟」/ "every 5m" */
46
+ function everyInterval(ms) {
47
+ return tr(`每${formatInterval(ms)}`, `every ${formatInterval(ms)}`);
43
48
  }
44
49
  function preview(text) {
45
50
  return text.length > PROMPT_PREVIEW_MAX ? `${text.slice(0, PROMPT_PREVIEW_MAX)}…` : text;
@@ -86,7 +91,7 @@ function parseArgs(ui, raw) {
86
91
  }
87
92
  const prompt = rest.join(" ").trim();
88
93
  if (!prompt) {
89
- ui.notify("用法: /loop [间隔] <提示词>,如 /loop 5m 检查部署状态;停止用 /loop stop", "error");
94
+ ui.notify(tr("用法: /loop [间隔] <提示词>,如 /loop 5m 检查部署状态;停止用 /loop stop", "Usage: /loop [interval] <prompt>, e.g. /loop 5m check the build status; /loop stop to stop"), "error");
90
95
  return null;
91
96
  }
92
97
  return { intervalMs: intervalMs ?? DEFAULT_INTERVAL_MS, prompt };
@@ -95,22 +100,22 @@ export function registerLoopCommand(pi) {
95
100
  // 会话重置(/clear、新会话)时循环随之消亡——和 Claude Code 的会话级语义一致
96
101
  pi.on("session_start", (_event, ctx) => stopLoop(ctx.ui));
97
102
  pi.registerCommand("loop", {
98
- description: "定时重复执行提示词,如 /loop 5m 检查部署状态;/loop stop 停止",
103
+ description: tr("定时重复执行提示词,如 /loop 5m 检查部署状态;/loop stop 停止", "Repeat a prompt on an interval, e.g. /loop 5m check the build status; /loop stop to stop"),
99
104
  handler: async (args, ctx) => {
100
105
  const input = args.trim();
101
106
  if (!input) {
102
107
  ctx.ui.notify(timer
103
- ? `🔁 当前循环:每${formatInterval(activeIntervalMs)} · ${preview(activePrompt)}(/loop stop 停止)`
104
- : "用法: /loop [间隔] <提示词>,如 /loop 5m 检查部署状态", "info");
108
+ ? tr(`🔁 当前循环:${everyInterval(activeIntervalMs)} · ${preview(activePrompt)}(/loop stop 停止)`, `🔁 Current loop: ${everyInterval(activeIntervalMs)} · ${preview(activePrompt)} (/loop stop to stop)`)
109
+ : tr("用法: /loop [间隔] <提示词>,如 /loop 5m 检查部署状态", "Usage: /loop [interval] <prompt>, e.g. /loop 5m check the build status"), "info");
105
110
  return;
106
111
  }
107
112
  if (/^(stop|off|取消|停止)$/i.test(input)) {
108
113
  if (!timer) {
109
- ctx.ui.notify("没有正在运行的循环", "info");
114
+ ctx.ui.notify(tr("没有正在运行的循环", "No loop is running"), "info");
110
115
  return;
111
116
  }
112
117
  stopLoop(ctx.ui);
113
- ctx.ui.notify("⏹ 循环已停止", "info");
118
+ ctx.ui.notify(tr("⏹ 循环已停止", "⏹ Loop stopped"), "info");
114
119
  return;
115
120
  }
116
121
  const parsed = parseArgs(ctx.ui, input);
@@ -128,14 +133,15 @@ export function registerLoopCommand(pi) {
128
133
  });
129
134
  }
130
135
  catch (e) {
131
- ctx.ui.notify(`loop 执行失败: ${e instanceof Error ? e.message : String(e)}`, "error");
136
+ const reason = e instanceof Error ? e.message : String(e);
137
+ ctx.ui.notify(tr(`loop 执行失败: ${reason}`, `Loop run failed: ${reason}`), "error");
132
138
  }
133
139
  };
134
140
  // 第一轮立刻跑:用户写完命令就想看到结果,不该干等一个间隔
135
141
  fire();
136
142
  timer = setInterval(fire, activeIntervalMs);
137
- ctx.ui.setStatus(STATUS_KEY, `🔁 每${formatInterval(activeIntervalMs)} · ${preview(activePrompt)}`);
138
- ctx.ui.notify(`🔁 已开始:现在先跑第一轮,之后每${formatInterval(activeIntervalMs)}一次「${preview(activePrompt)}」`, "info");
143
+ ctx.ui.setStatus(STATUS_KEY, `🔁 ${everyInterval(activeIntervalMs)} · ${preview(activePrompt)}`);
144
+ ctx.ui.notify(tr(`🔁 已开始:现在先跑第一轮,之后${everyInterval(activeIntervalMs)}一次「${preview(activePrompt)}」`, `🔁 Started: running the first round now, then ${everyInterval(activeIntervalMs)}: "${preview(activePrompt)}"`), "info");
139
145
  },
140
146
  });
141
147
  }
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { VERSION } from "../config.js";
3
+ import { tr } from "../i18n.js";
3
4
  /**
4
5
  * 极简 MCP 客户端:只做 initialize / tools/list / tools/call 三件事,
5
6
  * 支持 stdio(本机进程,按行 JSON-RPC)和 Streamable HTTP(POST JSON,
@@ -29,8 +30,8 @@ function withTimeout(promise, timeoutMs, signal, what) {
29
30
  signal?.removeEventListener("abort", onAbort);
30
31
  fn(v);
31
32
  };
32
- const onAbort = () => finish(reject)(new McpError(`${what} 已取消`));
33
- const timer = setTimeout(() => finish(reject)(new McpError(`${what} 超过 ${Math.round(timeoutMs / 1000)}s 未响应`)), timeoutMs);
33
+ const onAbort = () => finish(reject)(new McpError(tr(`${what} 已取消`, `${what} was canceled`)));
34
+ const timer = setTimeout(() => finish(reject)(new McpError(tr(`${what} 超过 ${Math.round(timeoutMs / 1000)}s 未响应`, `${what} timed out after ${Math.round(timeoutMs / 1000)}s`))), timeoutMs);
34
35
  promise.then(finish(resolve), finish(reject));
35
36
  if (signal?.aborted)
36
37
  onAbort();
@@ -92,12 +93,12 @@ class StdioTransport {
92
93
  // spawn 失败(ENOENT 等)不会再有 exit 事件
93
94
  if (child.pid === undefined)
94
95
  process.removeListener("exit", onParentExit);
95
- this.fail(new McpError(`启动 ${this.command} 失败:${err.message}`));
96
+ this.fail(new McpError(tr(`启动 ${this.command} 失败:${err.message}`, `Failed to start ${this.command}: ${err.message}`)));
96
97
  });
97
98
  child.on("exit", (code, sig) => {
98
99
  process.removeListener("exit", onParentExit);
99
100
  const tail = this.stderrTail.slice(-5).join("\n");
100
- this.fail(new McpError(`MCP 服务进程已退出(${sig ?? `code ${code ?? 0}`})${tail ? `\n${tail}` : ""}`));
101
+ this.fail(new McpError(`${tr("MCP 服务进程已退出", "MCP server process exited")}(${sig ?? `code ${code ?? 0}`})${tail ? `\n${tail}` : ""}`));
101
102
  });
102
103
  }
103
104
  fail(err) {
@@ -146,7 +147,7 @@ class StdioTransport {
146
147
  throw this.exited;
147
148
  const stdin = this.child?.stdin;
148
149
  if (!stdin || stdin.destroyed)
149
- throw new McpError("MCP 服务进程 stdin 已关闭");
150
+ throw new McpError(tr("MCP 服务进程 stdin 已关闭", "MCP server process stdin is closed"));
150
151
  stdin.write(`${JSON.stringify(msg)}\n`);
151
152
  }
152
153
  request(msg, timeoutMs, signal) {
@@ -161,7 +162,7 @@ class StdioTransport {
161
162
  reject(err);
162
163
  }
163
164
  });
164
- return withTimeout(promise, timeoutMs, signal, msg.method ?? "请求").finally(() => this.pending.delete(id));
165
+ return withTimeout(promise, timeoutMs, signal, msg.method ?? tr("请求", "request")).finally(() => this.pending.delete(id));
165
166
  }
166
167
  async notify(msg) {
167
168
  this.write(msg);
@@ -170,7 +171,7 @@ class StdioTransport {
170
171
  const child = this.child;
171
172
  if (!child)
172
173
  return;
173
- this.fail(new McpError("MCP 连接已关闭"));
174
+ this.fail(new McpError(tr("MCP 连接已关闭", "MCP connection closed")));
174
175
  try {
175
176
  child.stdin?.end();
176
177
  }
@@ -252,10 +253,10 @@ class HttpTransport {
252
253
  }
253
254
  catch (err) {
254
255
  if (signal?.aborted)
255
- throw new McpError(`${msg.method ?? "请求"} 已取消`);
256
+ throw new McpError(tr(`${msg.method ?? "请求"} 已取消`, `${msg.method ?? "request"} was canceled`));
256
257
  if (ctrl.signal.aborted)
257
- throw new McpError(`${msg.method ?? "请求"} 超过 ${Math.round(timeoutMs / 1000)}s 未响应`);
258
- throw new McpError(`连接 ${this.url} 失败:${err.message}`);
258
+ throw new McpError(tr(`${msg.method ?? "请求"} 超过 ${Math.round(timeoutMs / 1000)}s 未响应`, `${msg.method ?? "request"} timed out after ${Math.round(timeoutMs / 1000)}s`));
259
+ throw new McpError(tr(`连接 ${this.url} 失败:${err.message}`, `Failed to connect to ${this.url}: ${err.message}`));
259
260
  }
260
261
  finally {
261
262
  clearTimeout(timer);
@@ -270,7 +271,7 @@ class HttpTransport {
270
271
  if (!resp.ok) {
271
272
  const text = (await resp.text().catch(() => "")).slice(0, 300);
272
273
  if (resp.status === 401 || resp.status === 403) {
273
- throw new McpError(`服务拒绝访问(HTTP ${resp.status}),请检查 headers 里的鉴权信息${text ? `:${text}` : ""}`, resp.status);
274
+ throw new McpError(tr(`服务拒绝访问(HTTP ${resp.status}),请检查 headers 里的鉴权信息${text ? `:${text}` : ""}`, `Access denied by server (HTTP ${resp.status}); check the auth headers${text ? `: ${text}` : ""}`), resp.status);
274
275
  }
275
276
  throw new McpError(`HTTP ${resp.status}${text ? `:${text}` : ""}`, resp.status);
276
277
  }
@@ -292,13 +293,13 @@ class HttpTransport {
292
293
  return m;
293
294
  }
294
295
  }
295
- throw new McpError(`服务没有返回 ${msg.method ?? "请求"} 的结果(content-type ${ctype || "unknown"})`);
296
+ throw new McpError(tr(`服务没有返回 ${msg.method ?? "请求"} 的结果(content-type ${ctype || "unknown"})`, `Server returned no result for ${msg.method ?? "request"} (content-type ${ctype || "unknown"})`));
296
297
  }
297
298
  async notify(msg) {
298
299
  const resp = await this.post(msg, 10_000);
299
300
  // 规范要求 202;有些实现回 200/204,都当成功。鉴权失败要冒出来
300
301
  if (resp.status === 401 || resp.status === 403)
301
- throw new McpError(`服务拒绝访问(HTTP ${resp.status})`, resp.status);
302
+ throw new McpError(tr(`服务拒绝访问(HTTP ${resp.status})`, `Access denied by server (HTTP ${resp.status})`), resp.status);
302
303
  await resp.body?.cancel().catch(() => { });
303
304
  }
304
305
  async close() {
@@ -409,7 +410,7 @@ export class McpClient {
409
410
  async rpc(method, params, timeoutMs, signal) {
410
411
  const transport = this.transport;
411
412
  if (!transport)
412
- throw new McpError("尚未连接");
413
+ throw new McpError(tr("尚未连接", "Not connected"));
413
414
  const id = this.nextId++;
414
415
  const resp = await transport.request({ jsonrpc: "2.0", id, method, params }, timeoutMs, signal);
415
416
  if (resp.error) {
@@ -2,6 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join, resolve } from "node:path";
4
4
  import { formatHomePath } from "../brand.js";
5
+ import { isEnglish, tr } from "../i18n.js";
5
6
  import { McpClient } from "./client.js";
6
7
  import { loadMcpConfig, loadMcpToolCache, maskSecret, MCP_SERVER_NAME_RE, mcpConfigPath, normalizeServerConfig, parseKeyValue, saveMcpConfig, saveMcpToolCache, } from "./config.js";
7
8
  import { mcpToolName } from "./tools.js";
@@ -10,6 +11,27 @@ import { mcpToolName } from "./tools.js";
10
11
  * add 时先连一次拉工具清单再落盘,坏配置不会拖慢每次启动。
11
12
  */
12
13
  export function printMcpHelp() {
14
+ if (isEnglish()) {
15
+ console.log("");
16
+ console.log(" Configure third-party MCP servers so their tools (search, databases, browsers…) are available in chat");
17
+ console.log("");
18
+ console.log(" Usage:");
19
+ console.log(" u1s1 mcp add <name> <command> [args...] Local command server (stdio), e.g.:");
20
+ console.log(" u1s1 mcp add tavily --env TAVILY_API_KEY=tvly-xxx npx -y tavily-mcp");
21
+ console.log(" u1s1 mcp add <name> --url <url> HTTP server (Streamable HTTP), e.g.:");
22
+ console.log(" u1s1 mcp add exa --url https://mcp.exa.ai/mcp --header \"Authorization: Bearer xxx\"");
23
+ console.log(" --env K=V / --header \"K: V\" can be repeated; --force saves even if connecting fails");
24
+ console.log(" u1s1 mcp list List configured servers and their tools");
25
+ console.log(" u1s1 mcp test <name> Connect once and list the tools it provides");
26
+ console.log(" u1s1 mcp remove <name> Remove a server");
27
+ console.log(" u1s1 mcp import [--dry-run] Import from Claude Code config (~/.claude.json, project .mcp.json)");
28
+ console.log("");
29
+ console.log(` Config is stored in ${formatHomePath(mcpConfigPath())} (same format as Claude Code's mcpServers; safe to edit by hand).`);
30
+ console.log(" It stays on this machine and is never uploaded. Tools are available from your next chat, named mcp_<name>_<tool>.");
31
+ console.log(" Command servers run locally with your permissions; only add sources you trust.");
32
+ console.log("");
33
+ return;
34
+ }
13
35
  console.log("");
14
36
  console.log(" 配置第三方 MCP 服务,让它提供的工具(搜索、数据库、浏览器…)在对话里直接可用");
15
37
  console.log("");
@@ -55,7 +77,7 @@ async function probe(name, server) {
55
77
  }
56
78
  function printTools(name, tools) {
57
79
  if (tools.length === 0) {
58
- console.log(" (该服务没有提供任何工具)");
80
+ console.log(tr(" (该服务没有提供任何工具)", " (this server provides no tools)"));
59
81
  return;
60
82
  }
61
83
  for (const t of tools) {
@@ -86,24 +108,24 @@ function applyAddOption(arg, args, cursor, opts) {
86
108
  else if (arg === "--url" || arg.startsWith("--url=")) {
87
109
  opts.url = optionValue(arg, "--url=", args, cursor);
88
110
  if (!opts.url)
89
- fail("--url 后面要跟地址");
111
+ fail(tr("--url 后面要跟地址", "--url requires a URL"));
90
112
  }
91
113
  else if (arg === "--env" || arg === "-e" || arg.startsWith("--env=")) {
92
114
  const raw = optionValue(arg, "--env=", args, cursor);
93
115
  const kv = raw ? parseKeyValue(raw, "=") : undefined;
94
116
  if (!kv)
95
- fail(`--env 要写成 KEY=VALUE:${raw ?? ""}`);
117
+ fail(tr(`--env 要写成 KEY=VALUE:${raw ?? ""}`, `--env must be KEY=VALUE: ${raw ?? ""}`));
96
118
  opts.env[kv[0]] = kv[1];
97
119
  }
98
120
  else if (arg === "--header" || arg === "-H" || arg.startsWith("--header=")) {
99
121
  const raw = optionValue(arg, "--header=", args, cursor);
100
122
  const kv = raw ? parseKeyValue(raw, ":") : undefined;
101
123
  if (!kv)
102
- fail(`--header 要写成 "Name: value":${raw ?? ""}`);
124
+ fail(tr(`--header 要写成 "Name: value":${raw ?? ""}`, `--header must be "Name: value": ${raw ?? ""}`));
103
125
  opts.headers[kv[0]] = kv[1];
104
126
  }
105
127
  else if (arg.startsWith("-") && arg.length > 1) {
106
- fail(`不认识的选项 ${arg}(命令自己的参数请放在命令后面,或用 -- 隔开)`);
128
+ fail(tr(`不认识的选项 ${arg}(命令自己的参数请放在命令后面,或用 -- 隔开)`, `Unknown option ${arg} (put the command's own arguments after the command, or separate them with --)`));
107
129
  }
108
130
  else {
109
131
  opts.command = arg;
@@ -113,26 +135,26 @@ function applyAddOption(arg, args, cursor, opts) {
113
135
  function serverFromAddOptions(name, opts) {
114
136
  const { url, command, force } = opts;
115
137
  if (url && command)
116
- fail("--url 和命令只能二选一");
138
+ fail(tr("--url 和命令只能二选一", "Use either --url or a command, not both"));
117
139
  if (url) {
118
140
  if (!/^https?:\/\//i.test(url))
119
- fail(`--url 必须是 http(s) 地址:${url}`);
141
+ fail(tr(`--url 必须是 http(s) 地址:${url}`, `--url must be an http(s) URL: ${url}`));
120
142
  if (Object.keys(opts.env).length)
121
- fail("HTTP 型服务不支持 --env,鉴权请用 --header");
143
+ fail(tr("HTTP 型服务不支持 --env,鉴权请用 --header", "HTTP servers don't support --env; use --header for auth"));
122
144
  return { name, server: { type: "http", url, headers: opts.headers }, force };
123
145
  }
124
146
  if (!command)
125
- fail("请给出启动命令(如 npx -y some-mcp-server)或 --url 地址");
147
+ fail(tr("请给出启动命令(如 npx -y some-mcp-server)或 --url 地址", "Provide a start command (e.g. npx -y some-mcp-server) or --url"));
126
148
  if (Object.keys(opts.headers).length)
127
- fail("命令型服务不支持 --header,请用 --env 传密钥");
149
+ fail(tr("命令型服务不支持 --header,请用 --env 传密钥", "Command servers don't support --header; pass secrets with --env"));
128
150
  return { name, server: { type: "stdio", command, args: opts.commandArgs, env: opts.env }, force };
129
151
  }
130
152
  export function parseAddArgs(args) {
131
153
  const name = args[0];
132
154
  if (!name || name.startsWith("-"))
133
- fail("请给服务起个名字,例:u1s1 mcp add tavily npx -y tavily-mcp");
155
+ fail(tr("请给服务起个名字,例:u1s1 mcp add tavily npx -y tavily-mcp", "Give the server a name, e.g. u1s1 mcp add tavily npx -y tavily-mcp"));
134
156
  if (!MCP_SERVER_NAME_RE.test(name))
135
- fail(`服务名只能用小写字母、数字、- 和 _,且以字母或数字开头(最长 32 位):${name}`);
157
+ fail(tr(`服务名只能用小写字母、数字、- 和 _,且以字母或数字开头(最长 32 位):${name}`, `Server names may only use lowercase letters, digits, - and _, must start with a letter or digit, and be at most 32 characters: ${name}`));
136
158
  const opts = { commandArgs: [], env: {}, headers: {}, force: false };
137
159
  const cursor = { i: 1 };
138
160
  for (; cursor.i < args.length; cursor.i++) {
@@ -145,7 +167,7 @@ export function parseAddArgs(args) {
145
167
  opts.command = args[cursor.i + 1];
146
168
  opts.commandArgs.push(...args.slice(cursor.i + 2));
147
169
  if (opts.command === undefined)
148
- fail("-- 后面要跟命令");
170
+ fail(tr("-- 后面要跟命令", "-- must be followed by a command"));
149
171
  break;
150
172
  }
151
173
  applyAddOption(a, args, cursor, opts);
@@ -156,8 +178,8 @@ async function addCommand(args) {
156
178
  const { name, server, force } = parseAddArgs(args);
157
179
  const config = loadMcpConfig();
158
180
  const existed = Boolean(config.mcpServers[name]);
159
- console.log(`${existed ? "更新" : "添加"} MCP 服务 ${name}:${describeServer(server)}`);
160
- console.log("连接中…");
181
+ console.log(tr(`${existed ? "更新" : "添加"} MCP 服务 ${name}:${describeServer(server)}`, `${existed ? "Updating" : "Adding"} MCP server ${name}: ${describeServer(server)}`));
182
+ console.log(tr("连接中…", "Connecting…"));
161
183
  let tools = [];
162
184
  let serverName;
163
185
  let probed = false;
@@ -167,54 +189,57 @@ async function addCommand(args) {
167
189
  }
168
190
  catch (err) {
169
191
  const message = err instanceof Error ? err.message : String(err);
170
- if (!force)
171
- fail(`连接失败,未保存:${message}\n 确认命令/地址和密钥无误后重试;想先保存加 --force`);
172
- console.error(`⚠ 连接失败:${message}\n 已按 --force 保存,进对话前请用 u1s1 mcp test ${name} 确认可用`);
192
+ if (!force) {
193
+ fail(tr(`连接失败,未保存:${message}\n 确认命令/地址和密钥无误后重试;想先保存加 --force`, `Connection failed; nothing saved: ${message}\n Check the command/URL and keys, then retry. To save anyway, add --force`));
194
+ }
195
+ console.error(tr(`⚠ 连接失败:${message}\n 已按 --force 保存,进对话前请用 u1s1 mcp test ${name} 确认可用`, `⚠ Connection failed: ${message}\n Saved anyway (--force). Run u1s1 mcp test ${name} before using it in chat`));
173
196
  }
174
197
  config.mcpServers[name] = server;
175
198
  saveMcpConfig(config);
176
199
  if (probed) {
177
200
  cacheTools(name, tools, serverName);
178
- console.log(`✓ 已连接${serverName ? ` ${serverName}` : ""},提供 ${tools.length} 个工具:`);
201
+ console.log(tr(`✓ 已连接${serverName ? ` ${serverName}` : ""},提供 ${tools.length} 个工具:`, `✓ Connected${serverName ? ` to ${serverName}` : ""}, ${tools.length} tool(s):`));
179
202
  printTools(name, tools);
180
203
  }
181
204
  else {
182
205
  dropCache(name);
183
206
  }
184
- console.log(`✓ 已保存到 ${formatHomePath(mcpConfigPath())},下次进入对话即可使用`);
185
- if (server.type === "stdio")
186
- console.log(" 提示:命令型服务会以你的权限在本机运行,只添加可信来源。");
207
+ console.log(tr(`✓ 已保存到 ${formatHomePath(mcpConfigPath())},下次进入对话即可使用`, `✓ Saved to ${formatHomePath(mcpConfigPath())}; available from your next chat`));
208
+ if (server.type === "stdio") {
209
+ console.log(tr(" 提示:命令型服务会以你的权限在本机运行,只添加可信来源。", " Note: command servers run locally with your permissions; only add sources you trust."));
210
+ }
187
211
  }
188
212
  function listCommand() {
189
213
  const config = loadMcpConfig();
190
214
  const names = Object.keys(config.mcpServers);
191
215
  if (names.length === 0) {
192
- console.log("还没有配置 MCP 服务。用 u1s1 mcp add 添加,或 u1s1 mcp import 从 Claude Code 导入。");
216
+ console.log(tr("还没有配置 MCP 服务。用 u1s1 mcp add 添加,或 u1s1 mcp import 从 Claude Code 导入。", "No MCP servers configured yet. Add one with u1s1 mcp add, or import from Claude Code with u1s1 mcp import."));
193
217
  return;
194
218
  }
195
219
  const cache = loadMcpToolCache();
196
- console.log(`已配置 ${names.length} 个 MCP 服务(${formatHomePath(mcpConfigPath())}):`);
220
+ console.log(tr(`已配置 ${names.length} 个 MCP 服务(${formatHomePath(mcpConfigPath())}):`, `${names.length} MCP server(s) configured (${formatHomePath(mcpConfigPath())}):`));
197
221
  for (const name of names) {
198
222
  const server = config.mcpServers[name];
199
223
  const cached = cache.servers[name];
200
224
  console.log("");
201
225
  console.log(`${name} [${server.type}] ${describeServer(server)}`);
202
226
  if (!cached) {
203
- console.log(" 工具清单尚未获取(进对话时会自动连一次;也可 u1s1 mcp test 看看)");
227
+ console.log(tr(" 工具清单尚未获取(进对话时会自动连一次;也可 u1s1 mcp test 看看)", " Tool list not fetched yet (fetched automatically when you start a chat, or run u1s1 mcp test)"));
204
228
  continue;
205
229
  }
206
- console.log(` ${cached.tools.length} 个工具(${cached.fetchedAt.slice(0, 16).replace("T", " ")} 获取):`);
230
+ const fetched = cached.fetchedAt.slice(0, 16).replace("T", " ");
231
+ console.log(tr(` ${cached.tools.length} 个工具(${fetched} 获取):`, ` ${cached.tools.length} tool(s) (fetched ${fetched}):`));
207
232
  printTools(name, cached.tools);
208
233
  }
209
234
  }
210
235
  async function testCommand(name) {
211
236
  if (!name)
212
- fail("用法:u1s1 mcp test 名称");
237
+ fail(tr("用法:u1s1 mcp test 名称", "Usage: u1s1 mcp test <name>"));
213
238
  const config = loadMcpConfig();
214
239
  const server = config.mcpServers[name];
215
240
  if (!server)
216
- fail(`没有叫 ${name} 的 MCP 服务,u1s1 mcp list 看看已有的`);
217
- console.log(`连接 ${name}:${describeServer(server)}`);
241
+ fail(tr(`没有叫 ${name} 的 MCP 服务,u1s1 mcp list 看看已有的`, `No MCP server named ${name}; run u1s1 mcp list to see configured servers`));
242
+ console.log(tr(`连接 ${name}:${describeServer(server)}`, `Connecting to ${name}: ${describeServer(server)}`));
218
243
  const started = Date.now();
219
244
  let tools;
220
245
  let serverName;
@@ -222,22 +247,24 @@ async function testCommand(name) {
222
247
  ({ tools, serverName } = await probe(name, server));
223
248
  }
224
249
  catch (err) {
225
- fail(`连接失败:${err instanceof Error ? err.message : String(err)}`);
250
+ const message = err instanceof Error ? err.message : String(err);
251
+ fail(tr(`连接失败:${message}`, `Connection failed: ${message}`));
226
252
  }
227
253
  cacheTools(name, tools, serverName);
228
- console.log(`✓ 连接成功${serverName ? `(${serverName})` : ""},${Date.now() - started} ms,提供 ${tools.length} 个工具:`);
254
+ const ms = Date.now() - started;
255
+ console.log(tr(`✓ 连接成功${serverName ? `(${serverName})` : ""},${ms} ms,提供 ${tools.length} 个工具:`, `✓ Connected${serverName ? ` (${serverName})` : ""} in ${ms} ms, ${tools.length} tool(s):`));
229
256
  printTools(name, tools);
230
257
  }
231
258
  function removeCommand(name) {
232
259
  if (!name)
233
- fail("用法:u1s1 mcp remove 名称");
260
+ fail(tr("用法:u1s1 mcp remove 名称", "Usage: u1s1 mcp remove <name>"));
234
261
  const config = loadMcpConfig();
235
262
  if (!config.mcpServers[name])
236
- fail(`没有叫 ${name} 的 MCP 服务`);
263
+ fail(tr(`没有叫 ${name} 的 MCP 服务`, `No MCP server named ${name}`));
237
264
  delete config.mcpServers[name];
238
265
  saveMcpConfig(config);
239
266
  dropCache(name);
240
- console.log(`✓ 已删除 ${name}`);
267
+ console.log(tr(`✓ 已删除 ${name}`, `✓ Removed ${name}`));
241
268
  }
242
269
  /** Claude Code 的 MCP 配置:~/.claude.json(用户级 + 各项目)与项目 .mcp.json */
243
270
  export function collectClaudeMcpServers(home = homedir(), cwd = process.cwd()) {
@@ -253,11 +280,11 @@ export function collectClaudeMcpServers(home = homedir(), cwd = process.cwd()) {
253
280
  continue;
254
281
  const server = normalizeServerConfig(raw);
255
282
  if (!server) {
256
- unsupported.push(`${name}(${source})`);
283
+ unsupported.push(tr(`${name}(${source})`, `${name} (${source})`));
257
284
  continue;
258
285
  }
259
286
  if (!MCP_SERVER_NAME_RE.test(name)) {
260
- unsupported.push(`${name}(${source},名字不合规)`);
287
+ unsupported.push(tr(`${name}(${source},名字不合规)`, `${name} (${source}, invalid name)`));
261
288
  continue;
262
289
  }
263
290
  seen.add(key);
@@ -282,7 +309,7 @@ export function collectClaudeMcpServers(home = homedir(), cwd = process.cwd()) {
282
309
  if (projects && typeof projects === "object") {
283
310
  const entry = projects[resolve(cwd)];
284
311
  if (entry && typeof entry === "object")
285
- take(entry["mcpServers"], "~/.claude.json 项目配置");
312
+ take(entry["mcpServers"], tr("~/.claude.json 项目配置", "~/.claude.json project config"));
286
313
  }
287
314
  take(user?.["mcpServers"], "~/.claude.json");
288
315
  return { candidates, unsupported };
@@ -291,32 +318,32 @@ async function importCommand(args) {
291
318
  const dryRun = args.includes("--dry-run");
292
319
  const { candidates, unsupported } = collectClaudeMcpServers();
293
320
  for (const u of unsupported)
294
- console.log(`⚠ 跳过 ${u}:只支持命令型(stdio)和 HTTP 型服务`);
321
+ console.log(tr(`⚠ 跳过 ${u}:只支持命令型(stdio)和 HTTP 型服务`, `⚠ Skipped ${u}: only command (stdio) and HTTP servers are supported`));
295
322
  if (candidates.length === 0) {
296
- console.log("没有在 Claude Code 的配置里找到可导入的 MCP 服务。");
323
+ console.log(tr("没有在 Claude Code 的配置里找到可导入的 MCP 服务。", "No importable MCP servers found in Claude Code's config."));
297
324
  return;
298
325
  }
299
326
  const config = loadMcpConfig();
300
- console.log(`找到 ${candidates.length} 个 MCP 服务:`);
327
+ console.log(tr(`找到 ${candidates.length} 个 MCP 服务:`, `Found ${candidates.length} MCP server(s):`));
301
328
  for (const c of candidates) {
302
- const tag = config.mcpServers[c.name] ? "(已存在,将覆盖)" : "";
329
+ const tag = config.mcpServers[c.name] ? tr("(已存在,将覆盖)", "(exists, will be overwritten)") : "";
303
330
  console.log(` ${c.name} ${describeServer(c.server)} ← ${c.source} ${tag}`);
304
331
  }
305
332
  if (dryRun) {
306
- console.log("--dry-run:未写盘");
333
+ console.log(tr("--dry-run:未写盘", "--dry-run: nothing written"));
307
334
  return;
308
335
  }
309
336
  for (const c of candidates)
310
337
  config.mcpServers[c.name] = c.server;
311
338
  saveMcpConfig(config);
312
- console.log(`✓ 已保存到 ${formatHomePath(mcpConfigPath())}`);
339
+ console.log(tr(`✓ 已保存到 ${formatHomePath(mcpConfigPath())}`, `✓ Saved to ${formatHomePath(mcpConfigPath())}`));
313
340
  let failed = 0;
314
341
  for (const c of candidates) {
315
- process.stdout.write(`连接 ${c.name}… `);
342
+ process.stdout.write(tr(`连接 ${c.name}… `, `Connecting to ${c.name}… `));
316
343
  try {
317
344
  const { tools, serverName } = await probe(c.name, c.server);
318
345
  cacheTools(c.name, tools, serverName);
319
- console.log(`✓ ${tools.length} 个工具`);
346
+ console.log(tr(`✓ ${tools.length} 个工具`, `✓ ${tools.length} tool(s)`));
320
347
  }
321
348
  catch (err) {
322
349
  failed += 1;
@@ -324,8 +351,9 @@ async function importCommand(args) {
324
351
  console.log(`✗ ${(err instanceof Error ? err.message : String(err)).split("\n")[0]}`);
325
352
  }
326
353
  }
327
- if (failed)
328
- console.log(`${failed} 个服务连接失败,进对话前请用 u1s1 mcp test 名称 排查(失败的服务不会影响其他工具)`);
354
+ if (failed) {
355
+ console.log(tr(`${failed} 个服务连接失败,进对话前请用 u1s1 mcp test 名称 排查(失败的服务不会影响其他工具)`, `${failed} server(s) failed to connect; run u1s1 mcp test <name> before chatting (failed servers don't affect other tools)`));
356
+ }
329
357
  }
330
358
  export async function mcpCommand(args) {
331
359
  const sub = args[0];
@@ -344,7 +372,7 @@ export async function mcpCommand(args) {
344
372
  return removeCommand(args[1]);
345
373
  if (sub === "import")
346
374
  return await importCommand(args.slice(1));
347
- fail(`不认识的子命令 ${sub},u1s1 mcp --help 看用法`);
375
+ fail(tr(`不认识的子命令 ${sub},u1s1 mcp --help 看用法`, `Unknown subcommand ${sub}; see u1s1 mcp --help`));
348
376
  }
349
377
  catch (err) {
350
378
  if (err instanceof McpUsageError) {
@@ -1,4 +1,5 @@
1
1
  import { loadMcpConfig, loadMcpToolCache, sameToolList, saveMcpToolCache, } from "./config.js";
2
+ import { tr } from "../i18n.js";
2
3
  import { McpClient } from "./client.js";
3
4
  import { createMcpToolSet } from "./tools.js";
4
5
  /**
@@ -63,7 +64,7 @@ export async function registerMcpServers(pi) {
63
64
  catch (err) {
64
65
  const message = err instanceof Error ? err.message : String(err);
65
66
  result.failed.push({ server, error: message });
66
- warnings.push(`MCP 服务 ${server} 连接失败,本次会话不可用:${message.split("\n")[0]}\n 用 u1s1 mcp test ${server} 排查`);
67
+ warnings.push(tr(`MCP 服务 ${server} 连接失败,本次会话不可用:${message.split("\n")[0]}\n 用 u1s1 mcp test ${server} 排查`, `MCP server ${server} failed to connect and is unavailable this session: ${message.split("\n")[0]}\n Run u1s1 mcp test ${server} to troubleshoot`));
67
68
  }
68
69
  }));
69
70
  if (cacheDirty) {
@@ -123,6 +124,7 @@ async function refreshCache(config, cache, warn) {
123
124
  }
124
125
  catch { }
125
126
  }
126
- if (changed.length)
127
- warn(`MCP 服务 ${changed.join("、")} 的工具列表有变化,重新进入会话后生效`);
127
+ if (changed.length) {
128
+ warn(tr(`MCP 服务 ${changed.join("、")} 的工具列表有变化,重新进入会话后生效`, `Tool list changed for MCP server ${changed.join(", ")}; takes effect in your next session`));
129
+ }
128
130
  }
package/dist/mcp/tools.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Text } from "@earendil-works/pi-tui";
2
+ import { tr } from "../i18n.js";
2
3
  import { compactResultRender } from "../tools.js";
3
4
  import { McpClient } from "./client.js";
4
5
  /**
@@ -15,7 +16,7 @@ export function mcpToolName(server, tool) {
15
16
  export function truncateMcpText(text, max = MCP_MAX_TEXT_CHARS) {
16
17
  if (text.length <= max)
17
18
  return text;
18
- return `${text.slice(0, max)}\n\n…(MCP 工具返回内容过长已截断,共 ${text.length} 字符,保留前 ${max} 字符)`;
19
+ return `${text.slice(0, max)}\n\n${tr(`…(MCP 工具返回内容过长已截断,共 ${text.length} 字符,保留前 ${max} 字符)`, `… (MCP tool output too long, truncated: ${text.length} chars total, first ${max} kept)`)}`;
19
20
  }
20
21
  /** 模型 API 只认 object 顶层;$schema 之类元字段去掉,缺 properties 补空 */
21
22
  export function toParameterSchema(inputSchema) {
@@ -46,12 +47,12 @@ export function createMcpToolSet(server, config, tools, opts = {}) {
46
47
  catch (err) {
47
48
  const message = err instanceof Error ? err.message : String(err);
48
49
  // 进程退出 / 连接断了:丢掉旧客户端,下次调用重新拉起
49
- if (/已退出|已关闭|stdin/.test(message)) {
50
+ if (/已退出|已关闭|exited|closed|stdin/.test(message)) {
50
51
  const old = client;
51
52
  client = new McpClient(server, config, opts);
52
53
  old.close().catch(() => { });
53
54
  }
54
- throw new Error(`MCP 服务 ${server} 调用 ${tool.name} 失败:${message}`, { cause: err });
55
+ throw new Error(tr(`MCP 服务 ${server} 调用 ${tool.name} 失败:${message}`, `MCP server ${server}: calling ${tool.name} failed: ${message}`), { cause: err });
55
56
  }
56
57
  }
57
58
  for (const tool of tools) {
@@ -69,14 +70,14 @@ export function createMcpToolSet(server, config, tools, opts = {}) {
69
70
  renderResult(result, options, theme, context) {
70
71
  const d = result.details;
71
72
  const args = d?.args ? ` ${d.args}` : "";
72
- const n = typeof d?.chars === "number" ? ` · ${d.chars} 字符${d.truncated ? "(已截断)" : ""}` : "";
73
+ const n = typeof d?.chars === "number" ? ` · ${tr(`${d.chars} 字符`, `${d.chars} chars`)}${d.truncated ? tr("(已截断)", " (truncated)") : ""}` : "";
73
74
  return compactResultRender({ result, options, theme, context, summaryLine: `🔌 ${server}:${tool.name}${args}${n}` });
74
75
  },
75
76
  async execute(_toolCallId, params, signal) {
76
77
  const args = (params && typeof params === "object" ? params : {});
77
78
  const res = await call(tool, args, signal);
78
79
  if (res.isError)
79
- throw new Error(`MCP 服务 ${server} 的 ${tool.name} 返回错误:${res.text || "(无详情)"}`);
80
+ throw new Error(tr(`MCP 服务 ${server} 的 ${tool.name} 返回错误:${res.text || "(无详情)"}`, `MCP server ${server}: ${tool.name} returned an error: ${res.text || "(no details)"}`));
80
81
  const text = res.text || "(empty result)";
81
82
  return {
82
83
  content: [{ type: "text", text: truncateMcpText(text) }],