u1s1-cli 1.11.7 → 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.
- package/README.md +5 -5
- package/dist/agent-setup.d.ts +7 -1
- package/dist/agent-setup.js +34 -10
- package/dist/api.js +42 -34
- package/dist/bench-report.js +12 -9
- package/dist/bench.js +34 -31
- package/dist/brand.d.ts +7 -2
- package/dist/brand.js +44 -13
- package/dist/config.d.ts +28 -2
- package/dist/config.js +52 -14
- package/dist/context-usage.js +12 -9
- package/dist/deploy.js +53 -47
- package/dist/device-auth.js +7 -5
- package/dist/embed.d.ts +2 -1
- package/dist/embed.js +2 -1
- package/dist/error-humanize.d.ts +3 -1
- package/dist/error-humanize.js +24 -9
- package/dist/feedback.d.ts +4 -2
- package/dist/feedback.js +60 -39
- package/dist/i18n.d.ts +21 -0
- package/dist/i18n.js +46 -0
- package/dist/import/index.js +64 -28
- package/dist/import/skills.js +72 -41
- package/dist/import/util.js +3 -2
- package/dist/import/write.js +2 -1
- package/dist/index.js +153 -54
- package/dist/login.d.ts +13 -2
- package/dist/login.js +84 -27
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.js +76 -48
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.js +6 -5
- package/dist/model.js +12 -10
- package/dist/nudges.d.ts +8 -2
- package/dist/nudges.js +22 -3
- package/dist/search-tools.js +7 -6
- package/dist/shell-doctor.js +9 -8
- package/dist/site.d.ts +71 -0
- package/dist/site.js +123 -0
- package/dist/subagent.js +5 -4
- package/dist/tools.js +53 -37
- package/dist/update.js +47 -39
- package/dist/usage.d.ts +6 -4
- package/dist/usage.js +111 -53
- package/dist/web.js +2 -1
- package/dist/workflow/runner.js +33 -29
- package/dist/workflow/templates.js +70 -45
- package/dist/workflow/tool.js +15 -12
- package/package.json +3 -3
- package/scripts/patch-pi.js +243 -278
- package/scripts/render-regression.mjs +55 -4
package/dist/mcp/command.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/dist/mcp/extension.js
CHANGED
|
@@ -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
|
|
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}
|
|
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) }],
|
package/dist/model.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { fetchModels, loadCustomEndpoints } from "./api.js";
|
|
2
2
|
import { apiModelToDef, CUSTOM_ENDPOINTS, findModel, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, resolvePreferredModel, setModelsFromApi, } from "./config.js";
|
|
3
|
+
import { siteLinks, siteOf } from "./site.js";
|
|
4
|
+
import { tr } from "./i18n.js";
|
|
3
5
|
/** Refresh official models while preserving the built-in list as an explicit offline fallback. */
|
|
4
6
|
export async function refreshOfficialModels(cfg, fetcher = fetchModels) {
|
|
5
7
|
try {
|
|
6
8
|
const { models } = await fetcher(cfg);
|
|
7
9
|
if (models.length === 0)
|
|
8
|
-
return "线上模型列表为空";
|
|
10
|
+
return tr("线上模型列表为空", "the online model list is empty");
|
|
9
11
|
setModelsFromApi(models.map(apiModelToDef));
|
|
10
12
|
return null;
|
|
11
13
|
}
|
|
@@ -24,7 +26,7 @@ export async function modelCommand(nameOrAlias) {
|
|
|
24
26
|
loadCustomEndpoints(cfg),
|
|
25
27
|
]);
|
|
26
28
|
if (officialModelsError) {
|
|
27
|
-
console.error(` 获取线上模型列表失败,使用内置列表:${officialModelsError}`);
|
|
29
|
+
console.error(tr(` 获取线上模型列表失败,使用内置列表:${officialModelsError}`, ` Couldn't fetch the online model list; using the built-in list: ${officialModelsError}`));
|
|
28
30
|
}
|
|
29
31
|
const current = resolvePreferredModel(cfg);
|
|
30
32
|
if (!nameOrAlias) {
|
|
@@ -33,26 +35,26 @@ export async function modelCommand(nameOrAlias) {
|
|
|
33
35
|
const mark = current.provider === PROVIDER_ID && m.id === current.id ? "●" : " ";
|
|
34
36
|
console.log(` ${mark} ${m.aliases[0].padEnd(10)} ${m.name}`);
|
|
35
37
|
// 价格来自网关目录;离线兜底目录不带价格,只列名字与提示
|
|
36
|
-
const price = m.cost ? `$${m.cost.input}/$${m.cost.output} 每百万 token` : "";
|
|
38
|
+
const price = m.cost ? tr(`$${m.cost.input}/$${m.cost.output} 每百万 token`, `$${m.cost.input}/$${m.cost.output} per 1M tokens`) : "";
|
|
37
39
|
const detail = [m.note, price].filter(Boolean).join(" · ");
|
|
38
40
|
if (detail)
|
|
39
41
|
console.log(` ${detail}`);
|
|
40
42
|
}
|
|
41
43
|
for (const ep of CUSTOM_ENDPOINTS) {
|
|
42
44
|
console.log("");
|
|
43
|
-
console.log(` ─ 自定义端点 ${ep.name}(${ep.baseUrl},走本地直连不计费)`);
|
|
45
|
+
console.log(tr(` ─ 自定义端点 ${ep.name}(${ep.baseUrl},走本地直连不计费)`, ` ─ Custom endpoint ${ep.name} (${ep.baseUrl}, direct connection, not billed)`));
|
|
44
46
|
for (const m of ep.models) {
|
|
45
47
|
const mark = current.provider === ep.id && m.id === current.id ? "●" : " ";
|
|
46
48
|
console.log(` ${mark} ${m.id.padEnd(10)} ${m.name === m.id ? "" : m.name}`);
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
console.log("");
|
|
50
|
-
console.log(" 切换:u1s1 model <model-id>(对话里 /model 同样会记住)");
|
|
52
|
+
console.log(tr(" 切换:u1s1 model <model-id>(对话里 /model 同样会记住)", " Switch: u1s1 model <model-id> (/model in a session is remembered too)"));
|
|
51
53
|
if (CUSTOM_ENDPOINTS.length) {
|
|
52
|
-
console.log(" 端点模型重名时可加限定:u1s1 model 端点名:模型id");
|
|
54
|
+
console.log(tr(" 端点模型重名时可加限定:u1s1 model 端点名:模型id", " If endpoint model names clash, qualify them: u1s1 model <endpoint>:<model-id>"));
|
|
53
55
|
}
|
|
54
56
|
else {
|
|
55
|
-
console.log(
|
|
57
|
+
console.log(tr(` 想接自己的 API(含本机 Ollama)?去 ${siteLinks(siteOf(cfg)).dashboard} 配「自定义模型端点」`, ` Want to use your own API (including local Ollama)? Add a custom model endpoint at ${siteLinks(siteOf(cfg)).dashboard}`));
|
|
56
58
|
}
|
|
57
59
|
console.log("");
|
|
58
60
|
return;
|
|
@@ -61,14 +63,14 @@ export async function modelCommand(nameOrAlias) {
|
|
|
61
63
|
if (!m) {
|
|
62
64
|
const custom = CUSTOM_ENDPOINTS.flatMap((e) => e.models.map((x) => x.id));
|
|
63
65
|
const all = [...MODELS.map((x) => x.aliases[0]), ...custom];
|
|
64
|
-
console.error(` 没有叫「${nameOrAlias}」的模型,可选:${all.join(" / ")}`);
|
|
66
|
+
console.error(tr(` 没有叫「${nameOrAlias}」的模型,可选:${all.join(" / ")}`, ` No model named "${nameOrAlias}". Available: ${all.join(" / ")}`));
|
|
65
67
|
process.exit(1);
|
|
66
68
|
}
|
|
67
69
|
persistPreferredModel(cfg, m.provider, m.id);
|
|
68
70
|
if (m.provider === PROVIDER_ID) {
|
|
69
|
-
console.log(` ✓ 默认模型已切到 ${m.model.name}${m.model.note ? `(${m.model.note})` : ""}`);
|
|
71
|
+
console.log(tr(` ✓ 默认模型已切到 ${m.model.name}${m.model.note ? `(${m.model.note})` : ""}`, ` ✓ Default model set to ${m.model.name}${m.model.note ? ` (${m.model.note})` : ""}`));
|
|
70
72
|
}
|
|
71
73
|
else {
|
|
72
|
-
console.log(` ✓ 默认模型已切到 ${m.model.name}(自定义端点 ${m.providerName},本地直连不计费)`);
|
|
74
|
+
console.log(tr(` ✓ 默认模型已切到 ${m.model.name}(自定义端点 ${m.providerName},本地直连不计费)`, ` ✓ Default model set to ${m.model.name} (custom endpoint ${m.providerName}, direct connection, not billed)`));
|
|
73
75
|
}
|
|
74
76
|
}
|
package/dist/nudges.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SiteId } from "./site.js";
|
|
1
2
|
/**
|
|
2
3
|
* 「只提示一次」的机器级标记,落在 ~/.u1s1/nudges.json,CLI 与桌面版共用
|
|
3
4
|
* (桌面版经 u1s1-cli/embed 拿到同一份 helper)。值是 ISO 时间戳,便于事后
|
|
@@ -16,6 +17,11 @@ export declare const nudgesFile: string;
|
|
|
16
17
|
export declare function readNudges(file?: string): NudgeState;
|
|
17
18
|
export declare function hasNudge(key: NudgeKey, file?: string): boolean;
|
|
18
19
|
export declare function markNudge(key: NudgeKey, file?: string): void;
|
|
19
|
-
/**
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。
|
|
22
|
+
* 国际站没有站点托管(网关拒绝 deploy),一律不提示。
|
|
23
|
+
*/
|
|
24
|
+
export declare function shouldShowDeployHint(file?: string, site?: SiteId): boolean;
|
|
25
|
+
/** 老导出,固定中文;新代码用 deployHintText()(按当前语言现取)。 */
|
|
21
26
|
export declare const DEPLOY_HINT_TEXT = "\u505A\u597D\u4E86\u60F3\u7ED9\u670B\u53CB\u770B\uFF1F\u8F93\u5165 /exit \u56DE\u5230\u7EC8\u7AEF,\u8FD0\u884C u1s1 deploy \u4E00\u952E\u4E0A\u7EBF";
|
|
27
|
+
export declare function deployHintText(): string;
|
package/dist/nudges.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { u1s1Dir } from "./config.js";
|
|
3
|
+
import { activeSite, u1s1Dir } from "./config.js";
|
|
4
|
+
import { tr } from "./i18n.js";
|
|
4
5
|
export const nudgesFile = join(u1s1Dir, "nudges.json");
|
|
5
6
|
export function readNudges(file = nudgesFile) {
|
|
6
7
|
try {
|
|
@@ -32,9 +33,27 @@ export function markNudge(key, file = nudgesFile) {
|
|
|
32
33
|
// ignore
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
36
|
+
/**
|
|
37
|
+
* 会话里首次写/改文件后要不要提示 deploy:没提示过、也没部署过才提示。
|
|
38
|
+
* 国际站没有站点托管(网关拒绝 deploy),一律不提示。
|
|
39
|
+
*/
|
|
40
|
+
export function shouldShowDeployHint(file = nudgesFile, site) {
|
|
41
|
+
let siteId = site;
|
|
42
|
+
if (!siteId) {
|
|
43
|
+
try {
|
|
44
|
+
siteId = activeSite().id;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
siteId = "cn";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (siteId === "intl")
|
|
51
|
+
return false;
|
|
37
52
|
const state = readNudges(file);
|
|
38
53
|
return state.deploy_hint_shown === undefined && state.deploy_done === undefined;
|
|
39
54
|
}
|
|
55
|
+
/** 老导出,固定中文;新代码用 deployHintText()(按当前语言现取)。 */
|
|
40
56
|
export const DEPLOY_HINT_TEXT = "做好了想给朋友看?输入 /exit 回到终端,运行 u1s1 deploy 一键上线";
|
|
57
|
+
export function deployHintText() {
|
|
58
|
+
return tr(DEPLOY_HINT_TEXT, "Want to show it off? Type /exit to return to the terminal, then run u1s1 deploy to publish it.");
|
|
59
|
+
}
|
package/dist/search-tools.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { Readable, Transform } from "node:stream";
|
|
6
6
|
import { pipeline } from "node:stream/promises";
|
|
7
7
|
import { agentDir } from "./config.js";
|
|
8
|
+
import { tr } from "./i18n.js";
|
|
8
9
|
/**
|
|
9
10
|
* 预 seed pi 的搜索组件(fd / ripgrep)到 ~/.u1s1/agent/bin。
|
|
10
11
|
*
|
|
@@ -104,7 +105,7 @@ function findBinary(root, name) {
|
|
|
104
105
|
async function install(tool, baseUrl, binName) {
|
|
105
106
|
const asset = tool.asset(tool.version);
|
|
106
107
|
if (!asset)
|
|
107
|
-
throw new Error(`不支持的平台 ${platform()}/${arch()}`);
|
|
108
|
+
throw new Error(tr(`不支持的平台 ${platform()}/${arch()}`, `Unsupported platform ${platform()}/${arch()}`));
|
|
108
109
|
mkdirSync(binDir, { recursive: true });
|
|
109
110
|
const res = await fetch(new URL(`/tools/${asset}`, baseUrl), {
|
|
110
111
|
signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
|
|
@@ -114,7 +115,7 @@ async function install(tool, baseUrl, binName) {
|
|
|
114
115
|
const declaredLength = Number(res.headers.get("content-length"));
|
|
115
116
|
if (Number.isFinite(declaredLength) && declaredLength > MAX_TOOL_ARCHIVE_BYTES) {
|
|
116
117
|
void res.body.cancel("tool archive exceeded limit").catch(() => { });
|
|
117
|
-
throw new Error(`下载文件超过 ${MAX_TOOL_ARCHIVE_BYTES} bytes`);
|
|
118
|
+
throw new Error(tr(`下载文件超过 ${MAX_TOOL_ARCHIVE_BYTES} bytes`, `Download exceeds ${MAX_TOOL_ARCHIVE_BYTES} bytes`));
|
|
118
119
|
}
|
|
119
120
|
const archive = join(binDir, asset);
|
|
120
121
|
// fd/rg 并行安装,解压目录按工具名+pid 隔离,避免互踩
|
|
@@ -130,10 +131,10 @@ async function install(tool, baseUrl, binName) {
|
|
|
130
131
|
await pipeline(Readable.fromWeb(res.body), limiter, createWriteStream(archive));
|
|
131
132
|
mkdirSync(extractDir, { recursive: true });
|
|
132
133
|
if (!extract(archive, extractDir))
|
|
133
|
-
throw new Error(`解压失败 ${asset}`);
|
|
134
|
+
throw new Error(tr(`解压失败 ${asset}`, `Failed to extract ${asset}`));
|
|
134
135
|
const found = findBinary(extractDir, binName);
|
|
135
136
|
if (!found)
|
|
136
|
-
throw new Error(`包内没有 ${binName}`);
|
|
137
|
+
throw new Error(tr(`包内没有 ${binName}`, `${binName} not found in the archive`));
|
|
137
138
|
renameSync(found, join(binDir, binName));
|
|
138
139
|
if (platform() !== "win32")
|
|
139
140
|
chmodSync(join(binDir, binName), 0o755);
|
|
@@ -151,10 +152,10 @@ export async function ensureSearchTools(cfg) {
|
|
|
151
152
|
if (missing.length === 0)
|
|
152
153
|
return;
|
|
153
154
|
// 这是新手首启最早看到的输出之一:说清是首次准备、不用管;fd/rg 等术语不外露
|
|
154
|
-
console.log(" 首次运行,正在准备文件搜索组件(只需一次)…");
|
|
155
|
+
console.log(tr(" 首次运行,正在准备文件搜索组件(只需一次)…", " First run: setting up file search components (one-time)…"));
|
|
155
156
|
const results = await Promise.allSettled(missing.map((t) => install(t, cfg.baseUrl, t.bin + ext)));
|
|
156
157
|
if (results.some((r) => r.status === "rejected")) {
|
|
157
|
-
console.log(" 部分搜索组件暂时没装上,不影响使用,之后会自动重试。");
|
|
158
|
+
console.log(tr(" 部分搜索组件暂时没装上,不影响使用,之后会自动重试。", " Some search components couldn't be installed yet. You can keep working; we'll retry automatically later."));
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
catch {
|
package/dist/shell-doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, win32 } from "node:path";
|
|
4
4
|
import { agentDir, agentSettingsFile } from "./config.js";
|
|
5
|
+
import { tr } from "./i18n.js";
|
|
5
6
|
/** System32/Sysnative 的 bash.exe 是 WSL 转发器,不是真 bash(与 pi 同款判断)。 */
|
|
6
7
|
function isWslRelayBash(p) {
|
|
7
8
|
const normalized = p.replace(/\//g, "\\").toLowerCase();
|
|
@@ -139,8 +140,8 @@ function persistShellPath(p) {
|
|
|
139
140
|
return true;
|
|
140
141
|
}
|
|
141
142
|
function adviseInstallGitBash() {
|
|
142
|
-
console.error(" ⚠ 未找到 Git Bash,将使用 WSL 的 bash:命令会在 WSL 的 Linux 里执行,不是 Windows 本机。");
|
|
143
|
-
console.error(" 建议安装 Git for Windows(PowerShell 运行: winget install --id Git.Git)后重启 u1s1。");
|
|
143
|
+
console.error(tr(" ⚠ 未找到 Git Bash,将使用 WSL 的 bash:命令会在 WSL 的 Linux 里执行,不是 Windows 本机。", " ⚠ Git Bash not found; using WSL's bash. Commands will run inside WSL's Linux, not natively on Windows."));
|
|
144
|
+
console.error(tr(" 建议安装 Git for Windows(PowerShell 运行: winget install --id Git.Git)后重启 u1s1。", " We recommend installing Git for Windows (in PowerShell: winget install --id Git.Git), then restarting u1s1."));
|
|
144
145
|
}
|
|
145
146
|
export async function ensureUsableShell() {
|
|
146
147
|
if (process.platform !== "win32")
|
|
@@ -164,7 +165,7 @@ export async function ensureUsableShell() {
|
|
|
164
165
|
const candidate = await findUsableGitBash();
|
|
165
166
|
if (candidate) {
|
|
166
167
|
persistShellPath(candidate);
|
|
167
|
-
console.error(` 检测到 Git Bash,命令改回 Windows 本机执行: ${candidate}`);
|
|
168
|
+
console.error(tr(` 检测到 Git Bash,命令改回 Windows 本机执行: ${candidate}`, ` Found Git Bash; commands will run natively on Windows again: ${candidate}`));
|
|
168
169
|
return { status: "bash", shellPath: candidate };
|
|
169
170
|
}
|
|
170
171
|
adviseInstallGitBash();
|
|
@@ -175,7 +176,7 @@ export async function ensureUsableShell() {
|
|
|
175
176
|
const adopt = (candidate) => {
|
|
176
177
|
persistShellPath(candidate);
|
|
177
178
|
if (typeof configured === "string") {
|
|
178
|
-
console.error(` 原 shellPath 已失效,自动改用: ${candidate}`);
|
|
179
|
+
console.error(tr(` 原 shellPath 已失效,自动改用: ${candidate}`, ` The configured shellPath no longer works; switched to: ${candidate}`));
|
|
179
180
|
}
|
|
180
181
|
return { status: "bash", shellPath: candidate };
|
|
181
182
|
};
|
|
@@ -195,14 +196,14 @@ export async function ensureUsableShell() {
|
|
|
195
196
|
return { status: "bash", shellPath: fallback };
|
|
196
197
|
}
|
|
197
198
|
if (fallback) {
|
|
198
|
-
console.error(` ⚠ 检测到的 bash(${fallback})无法执行命令,u1s1
|
|
199
|
+
console.error(tr(` ⚠ 检测到的 bash(${fallback})无法执行命令,u1s1 的命令工具会全部失败。`, ` ⚠ The bash found (${fallback}) can't run commands, so all of u1s1's command tools will fail.`));
|
|
199
200
|
if (isWslRelayBash(fallback)) {
|
|
200
|
-
console.error(" 它是 WSL 转发器,当前默认发行版里没有 /bin/bash(docker-desktop 被设为默认时就会这样)。");
|
|
201
|
+
console.error(tr(" 它是 WSL 转发器,当前默认发行版里没有 /bin/bash(docker-desktop 被设为默认时就会这样)。", " It's the WSL relay, and the default distro has no /bin/bash (this happens when docker-desktop is the default)."));
|
|
201
202
|
}
|
|
202
203
|
}
|
|
203
204
|
else {
|
|
204
|
-
console.error(" ⚠ 没找到 bash,u1s1 无法执行命令。");
|
|
205
|
+
console.error(tr(" ⚠ 没找到 bash,u1s1 无法执行命令。", " ⚠ No bash found; u1s1 can't run commands."));
|
|
205
206
|
}
|
|
206
|
-
console.error(" 修复:PowerShell 里运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。");
|
|
207
|
+
console.error(tr(" 修复:PowerShell 里运行 winget install --id Git.Git 安装 Git for Windows,装完重启 u1s1。", " Fix: in PowerShell, run winget install --id Git.Git to install Git for Windows, then restart u1s1."));
|
|
207
208
|
return { status: "none" };
|
|
208
209
|
}
|