u1s1-cli 1.14.6 → 1.15.1
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/dist/agent-setup.d.ts +7 -0
- package/dist/agent-setup.js +14 -0
- package/dist/agents/agent-config.d.ts +83 -0
- package/dist/agents/agent-config.js +154 -0
- package/dist/agents/claude.d.ts +113 -0
- package/dist/agents/claude.js +411 -0
- package/dist/agents/codex.d.ts +111 -0
- package/dist/agents/codex.js +392 -0
- package/dist/agents/launch.d.ts +109 -0
- package/dist/agents/launch.js +444 -0
- package/dist/agents/pi.d.ts +9 -0
- package/dist/agents/pi.js +562 -0
- package/dist/agents/prefs.d.ts +35 -0
- package/dist/agents/prefs.js +96 -0
- package/dist/agents/runtime.d.ts +354 -0
- package/dist/agents/runtime.js +884 -0
- package/dist/agents/status.d.ts +20 -0
- package/dist/agents/status.js +241 -0
- package/dist/config-command.d.ts +18 -0
- package/dist/config-command.js +335 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.js +35 -1
- package/dist/device-auth.d.ts +37 -1
- package/dist/device-auth.js +43 -8
- package/dist/embed.d.ts +13 -0
- package/dist/embed.js +15 -0
- package/dist/index.js +22 -556
- package/dist/launcher.d.ts +48 -0
- package/dist/launcher.js +235 -0
- package/dist/mcp-server.d.ts +105 -0
- package/dist/mcp-server.js +449 -0
- package/dist/pi-ui-text.d.ts +57 -0
- package/dist/pi-ui-text.js +148 -0
- package/dist/usage.js +2 -2
- package/package.json +1 -1
- package/scripts/p0-claude-code/README.md +39 -0
- package/scripts/p0-claude-code/launch-claude.ts +94 -0
- package/scripts/p0-claude-code/replay.ts +74 -0
- package/scripts/p0-claude-code/seed-local.mjs +145 -0
- package/scripts/p0-claude-code/tap.mjs +105 -0
- package/scripts/patch-pi.js +76 -55
- package/scripts/pi-patch-report.mjs +224 -0
- package/scripts/pi-upgrade.mjs +829 -0
- package/scripts/render-regression.mjs +67 -5
package/dist/index.js
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
3
|
-
import { writeFileSync } from "node:fs";
|
|
4
|
-
import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, removeLegacyWorkflowPromptTemplate, scrubForeignProviderEnv, toProviderModels, writeAnnouncementsExtension, writeAttributionExtension, writeMcpExtension, removeLegacyCompactUiExtension, writeErrorHumanizeExtension, writeWebToolsExtension, } from "./agent-setup.js";
|
|
5
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
6
2
|
import { printConsoleBanner } from "./brand.js";
|
|
7
|
-
import { activeSite,
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { applyBrandUi, setAnnouncement, setUpdateNotice } from "./style.js";
|
|
12
|
-
import { AccessDeniedError, AuthError, fetchModels, loadCustomEndpoints, readJsonResponseCapped } from "./api.js";
|
|
13
|
-
import { ensureSigningProxy, onGatewayFetchFailure } from "./device-auth.js";
|
|
14
|
-
import { installChildEnvGuard } from "./secret-env.js";
|
|
15
|
-
import { deployHintText, markNudge, shouldShowDeployHint } from "./nudges.js";
|
|
16
|
-
import { createGatewayTelemetry } from "./telemetry.js";
|
|
17
|
-
import { installCommand, siteLinks, siteOf } from "./site.js";
|
|
18
|
-
import { isEnglish, lang, tr } from "./i18n.js";
|
|
19
|
-
const PACKAGE_NAME = "u1s1-cli";
|
|
20
|
-
/** 启动时检测到的可自动安装的新版;TUI 退出后才装(见 installPendingUpdate)。 */
|
|
21
|
-
let pendingUpdate;
|
|
3
|
+
import { activeSite, loadConfig, readSettings, VERSION } from "./config.js";
|
|
4
|
+
import { siteLinks, siteOf } from "./site.js";
|
|
5
|
+
import { isEnglish, tr } from "./i18n.js";
|
|
6
|
+
import { isLauncherWord, launcherHelpLines, launcherHelpRows, runLauncher } from "./launcher.js";
|
|
22
7
|
/**
|
|
23
8
|
* Configure the same undici dispatcher used by the embedded kernel before any
|
|
24
9
|
* CLI startup request runs. The kernel keeps this helper in an internal file,
|
|
@@ -32,537 +17,6 @@ async function configureStartupHttp() {
|
|
|
32
17
|
dispatcher.applyHttpProxySettings(readSettings().httpProxy);
|
|
33
18
|
dispatcher.configureHttpDispatcher();
|
|
34
19
|
}
|
|
35
|
-
/**
|
|
36
|
-
* 启动时自动检查 npm 最新版:autoUpdate 开着就记下退出后安装,关着也在启动横幅的
|
|
37
|
-
* 版本号后面提示一句(u1s1 vX.Y.Z 后跟升级状态,见 setUpdateNotice)。
|
|
38
|
-
* 不阻塞启动流程,失败也不报错(留到手动 `u1s1 update`)。
|
|
39
|
-
*
|
|
40
|
-
* 绝不能边跑 TUI 边后台 npm install -g 原地更新:npm 会整棵重铺
|
|
41
|
-
* u1s1-cli/node_modules,而 pi-ai 的 API 适配器要到首次模型调用才按路径懒加载
|
|
42
|
-
* (openai-completions.lazy.js),路径被砸掉就 ERR_MODULE_NOT_FOUND 当场炸
|
|
43
|
-
* (0.13.2 之前云端沙箱首任务必崩的现场)。所以这里只记 pending,退出后再装。
|
|
44
|
-
*/
|
|
45
|
-
async function checkForUpdate() {
|
|
46
|
-
let latest;
|
|
47
|
-
try {
|
|
48
|
-
const url = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
49
|
-
const res = await fetch(url, {
|
|
50
|
-
headers: { Accept: "application/json" },
|
|
51
|
-
signal: AbortSignal.timeout(5_000),
|
|
52
|
-
});
|
|
53
|
-
if (res.ok) {
|
|
54
|
-
const body = await readJsonResponseCapped(res, 64 * 1024);
|
|
55
|
-
latest = typeof body.version === "string"
|
|
56
|
-
&& body.version.length <= 100
|
|
57
|
-
&& /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(body.version)
|
|
58
|
-
? body.version
|
|
59
|
-
: undefined;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return; // 网络不可用,静默跳过
|
|
64
|
-
}
|
|
65
|
-
if (!latest)
|
|
66
|
-
return;
|
|
67
|
-
// parseInt 而非 Number:预发布段("0-beta.1")取前导数字,避免 NaN 让比较全军覆没
|
|
68
|
-
const pa = VERSION.split(".").map((s) => parseInt(s, 10) || 0);
|
|
69
|
-
const pb = latest.split(".").map((s) => parseInt(s, 10) || 0);
|
|
70
|
-
let newer = false;
|
|
71
|
-
for (let i = 0; i < 3; i++) {
|
|
72
|
-
const diff = (pb[i] ?? 0) - (pa[i] ?? 0);
|
|
73
|
-
if (diff > 0) {
|
|
74
|
-
newer = true;
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
if (diff < 0)
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
if (!newer)
|
|
81
|
-
return;
|
|
82
|
-
const settings = readSettings();
|
|
83
|
-
// 便携版没法用 npm 自更新;autoUpdate 关闭同理只提示。u1s1 update 会给出正确升级方式
|
|
84
|
-
if (settings.autoUpdate === false || isPortableInstall()) {
|
|
85
|
-
setUpdateNotice(tr(`⬆ 新版 v${latest} 可用 · u1s1 update 升级`, `⬆ v${latest} available · run u1s1 update`));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
// 检测包管理器
|
|
89
|
-
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
90
|
-
const pm = userAgent.startsWith("pnpm") ? "pnpm"
|
|
91
|
-
: userAgent.startsWith("yarn") ? "yarn"
|
|
92
|
-
: userAgent.startsWith("bun") ? "bun"
|
|
93
|
-
: "npm";
|
|
94
|
-
const installCmd = pm === "npm"
|
|
95
|
-
? `npm install -g --loglevel=error ${PACKAGE_NAME}@latest`
|
|
96
|
-
: `${pm} add -g ${PACKAGE_NAME}@latest`;
|
|
97
|
-
pendingUpdate = { latest, installCmd };
|
|
98
|
-
setUpdateNotice(tr(`⬆ 发现新版 v${latest},退出后自动更新`, `⬆ v${latest} available, will update on exit`));
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* TUI 退出后前台安装新版:此刻不再有模型调用和懒加载,重铺 node_modules 才安全。
|
|
102
|
-
* 不设 timeout——半路杀掉 npm 会留下残缺的全局安装,之后连启动都起不来;
|
|
103
|
-
* stdio 直通,装多久用户看得见。(同机另开的 u1s1 实例仍会被重铺波及,属残余
|
|
104
|
-
* 风险,但远好于更新自己脚下这棵树。)
|
|
105
|
-
*/
|
|
106
|
-
function installPendingUpdate() {
|
|
107
|
-
if (!pendingUpdate)
|
|
108
|
-
return;
|
|
109
|
-
const { latest, installCmd } = pendingUpdate;
|
|
110
|
-
console.log(tr(`\n⬆ 正在更新到 v${latest}…`, `\n⬆ Updating to v${latest}…`));
|
|
111
|
-
console.log(tr(" (可能需要几分钟,期间请不要关闭窗口或按 Ctrl+C,中途打断会装出半截)", " (This may take a few minutes. Don't close the window or press Ctrl+C; interrupting leaves a broken install.)"));
|
|
112
|
-
try {
|
|
113
|
-
execSync(installCmd, { stdio: "inherit" });
|
|
114
|
-
console.log(tr(`✅ 已更新到 v${latest},下次运行 u1s1 生效。`, `✅ Updated to v${latest}. It takes effect the next time you run u1s1.`));
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// 失败不挡退出,下次启动还会再试。npm 全局目录没写权限时 `u1s1 update` 也会
|
|
118
|
-
// 走同一条命令再失败一次,所以这里直接给出能跳出循环的路径(官网安装脚本)
|
|
119
|
-
console.error(tr("自动更新失败(上面是 npm 的报错,常见原因是没有全局安装权限)。", "Auto-update failed (npm's error is above; usually missing permission for global installs)."));
|
|
120
|
-
console.error(tr("可以改用官网安装脚本重装,一步到位:", "Reinstall with the official install script instead:"));
|
|
121
|
-
console.error(process.platform === "win32"
|
|
122
|
-
? ` powershell -c "${installCommand(activeSite(), "ps1")}"`
|
|
123
|
-
: ` ${installCommand(activeSite(), "sh")}`);
|
|
124
|
-
console.error(tr("不想每次退出都尝试更新的话,可在 ~/.u1s1/agent/settings.json 里设 \"autoUpdate\": false", "To stop trying to update on every exit, set \"autoUpdate\": false in ~/.u1s1/agent/settings.json"));
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* tmux 默认不转发键盘修饰键,会把 Shift+Enter 当成普通回车发出去,消息没写完就被发送。
|
|
129
|
-
* 只开 extended-keys 不够:默认 terminal-features 里 xterm* 没有 extkeys,tmux 根本不会
|
|
130
|
-
* 向外层终端要 modifyOtherKeys,S-Enter 绑定也就永远触发不了。
|
|
131
|
-
* 启动时自动做这些事(运行时生效,不写用户 tmux.conf;失败则静默忽略,仍可用 Ctrl+J 换行):
|
|
132
|
-
* 1. 开启 extended-keys,并给当前终端补上 extkeys;
|
|
133
|
-
* 2. tmux 3.5+ 再切到 csi-u(3.4 及以下没有这个选项,忽略即可);
|
|
134
|
-
* 3. 兜底把 S-Enter / C-Enter 原样转成 pi 认得的 CSI-u 序列。
|
|
135
|
-
*/
|
|
136
|
-
function ensureTmuxKeyboardProtocol() {
|
|
137
|
-
if (!process.env.TMUX)
|
|
138
|
-
return;
|
|
139
|
-
const runTmux = (args) => spawnSync("tmux", args, { timeout: 1500, encoding: "utf8" });
|
|
140
|
-
if (runTmux(["-V"]).status !== 0)
|
|
141
|
-
return; // tmux 不可用(如沙箱)则跳过
|
|
142
|
-
// 当前会话 + 全局默认都开,避免只改 -g 时已有会话仍是 off
|
|
143
|
-
runTmux(["set-option", "-g", "extended-keys", "on"]);
|
|
144
|
-
runTmux(["set-option", "extended-keys", "on"]);
|
|
145
|
-
// 3.5+; 3.4 会失败,忽略
|
|
146
|
-
runTmux(["set-option", "-g", "extended-keys-format", "csi-u"]);
|
|
147
|
-
runTmux(["set-option", "extended-keys-format", "csi-u"]);
|
|
148
|
-
const features = runTmux(["show", "-gv", "terminal-features"]);
|
|
149
|
-
if (features.status === 0 && !/\bextkeys\b/.test(features.stdout ?? "")) {
|
|
150
|
-
runTmux(["set-option", "-ga", "terminal-features", "xterm*:extkeys"]);
|
|
151
|
-
}
|
|
152
|
-
// 已挂上的客户端不会自动重读 terminal-features;直接往当前客户端 tty
|
|
153
|
-
// 发 modifyOtherKeys,让外层终端立刻开始区分 Shift+Enter。
|
|
154
|
-
const tty = runTmux(["display-message", "-p", "#{client_tty}"]).stdout?.trim();
|
|
155
|
-
if (tty) {
|
|
156
|
-
try {
|
|
157
|
-
writeFileSync(tty, "\x1b[>4;2m");
|
|
158
|
-
}
|
|
159
|
-
catch {
|
|
160
|
-
// 没权限或不是 tty 就忽略
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// -l: 按字面量发送,避免 send-keys 把转义序列拆成一串普通按键
|
|
164
|
-
runTmux(["bind-key", "-n", "S-Enter", "send-keys", "-l", "\x1b[13;2u"]);
|
|
165
|
-
runTmux(["bind-key", "-n", "C-Enter", "send-keys", "-l", "\x1b[13;5u"]);
|
|
166
|
-
}
|
|
167
|
-
async function runAgent(cfg, args) {
|
|
168
|
-
cleanupBrandThemes();
|
|
169
|
-
ensureDefaultSettings();
|
|
170
|
-
// Set constant environment values before startup I/O; values derived from
|
|
171
|
-
// responses are configured below, before loading the kernel.
|
|
172
|
-
process.env["PI_CODING_AGENT_DIR"] = agentDir;
|
|
173
|
-
process.env["U1S1_TOOLS_VIA_EXTENSION"] = "1";
|
|
174
|
-
// hide pi's own "Run pi update" banner; users should run `u1s1 update` instead
|
|
175
|
-
process.env["PI_SKIP_VERSION_CHECK"] = "1";
|
|
176
|
-
// Windows shell 体检异步先行,与取模型列表/搜索工具下载并行;首启验活
|
|
177
|
-
// (Defender 首扫 bash.exe 可达秒级)藏进网络等待,进 pi 前再 await
|
|
178
|
-
const shellReady = ensureUsableShell();
|
|
179
|
-
// 预 seed fd/rg(国内直连 GitHub 不通,pi 自己下不动);与取模型列表并行,
|
|
180
|
-
// 但必须在进 pi 之前就位,否则 pi 会自己去 GitHub 下载
|
|
181
|
-
const searchToolsReady = ensureSearchTools(cfg);
|
|
182
|
-
// Fetch model list from server; fall back to built-in MODELS on error.
|
|
183
|
-
// 服务端没开搜索(或老网关没有 features 字段)时不注册 web_search,模型就不会白调
|
|
184
|
-
// 自定义端点与模型列表并行拉取,失败各自兜底(内置列表 / 本地缓存)
|
|
185
|
-
let webSearchEnabled = true;
|
|
186
|
-
// 老网关没有 /v1/fetch,web_fetch_render 缺失时按关闭处理,直连失败不去白调
|
|
187
|
-
let webFetchRenderEnabled = false;
|
|
188
|
-
// 老网关没有 /v1/image,image_gen 缺失时按关闭处理,不注册生图工具
|
|
189
|
-
let imageGenEnabled = false;
|
|
190
|
-
const endpointsReady = loadCustomEndpoints(cfg);
|
|
191
|
-
const modelsReady = fetchModels(cfg);
|
|
192
|
-
let modelsResp;
|
|
193
|
-
try {
|
|
194
|
-
modelsResp = await modelsReady;
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
if (e instanceof AuthError) {
|
|
198
|
-
// 凭证形状还在但服务端已作废(设备被移除/换过钥匙):直接进 TUI 每条消息都会失败,
|
|
199
|
-
// 不如现在就引导重新登录
|
|
200
|
-
console.error(tr(" 登录已失效(这台设备可能被移除,或账号更换过钥匙),需要重新登录。", " Your session has expired (this device may have been removed, or the account's keys changed). Please sign in again."));
|
|
201
|
-
const { login } = await import("./login.js");
|
|
202
|
-
cfg = await login();
|
|
203
|
-
modelsResp = await fetchModels(cfg).catch((e2) => {
|
|
204
|
-
console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e2.message);
|
|
205
|
-
return undefined;
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
else if (e instanceof AccessDeniedError) {
|
|
209
|
-
// 封禁/停用/设备不受信任:进 TUI 后每条消息都会失败,不如现在就把原因说清楚并退出
|
|
210
|
-
console.error(` ${e.message}`);
|
|
211
|
-
const site = siteOf(cfg);
|
|
212
|
-
console.error(tr(` 这个账号当前无法使用 u1s1。如有疑问可发邮件到 ${site.contactEmail},或在 ${siteLinks(site).dashboard} 提交工单。`, ` This account can't use u1s1 right now. Questions? Email ${site.contactEmail} or open a support ticket at ${siteLinks(site).dashboard}.`));
|
|
213
|
-
process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e.message);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
if (modelsResp) {
|
|
220
|
-
const { models, features, announcement } = modelsResp;
|
|
221
|
-
setModelsFromApi(models.map(apiModelToDef));
|
|
222
|
-
webSearchEnabled = features.web_search !== false;
|
|
223
|
-
webFetchRenderEnabled = features.web_fetch_render === true;
|
|
224
|
-
imageGenEnabled = features.image_gen === true;
|
|
225
|
-
// 后台下发的启动公告(与模型列表同一次请求捎回,零额外开销);空/老网关不显示
|
|
226
|
-
if (announcement?.text)
|
|
227
|
-
setAnnouncement(announcement);
|
|
228
|
-
}
|
|
229
|
-
await endpointsReady;
|
|
230
|
-
// Gateway metadata owns each official model's default thinking level. Seed
|
|
231
|
-
// it after the live model list arrives; explicit user choices remain intact.
|
|
232
|
-
ensureDefaultSettings(MODELS);
|
|
233
|
-
// pi provider 只支持静态 header;指向本机 signing proxy,由它逐请求附 DPoP proof。
|
|
234
|
-
const signing = await ensureSigningProxy(cfg, "terminal", {
|
|
235
|
-
token: modelsResp?.clientAttestation,
|
|
236
|
-
expiresInSeconds: modelsResp?.clientAttestationExpiresInSeconds,
|
|
237
|
-
refresh: async () => {
|
|
238
|
-
const refreshed = await fetchModels(cfg);
|
|
239
|
-
return {
|
|
240
|
-
token: refreshed.clientAttestation,
|
|
241
|
-
expiresInSeconds: refreshed.clientAttestationExpiresInSeconds,
|
|
242
|
-
};
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
const officialCfg = { ...cfg, baseUrl: signing.baseUrl, apiKey: signing.localKey };
|
|
246
|
-
ensureBrandPrompt(await shellReady);
|
|
247
|
-
ensureProviderModels(officialCfg);
|
|
248
|
-
removeLegacyWorkflowPromptTemplate();
|
|
249
|
-
// 联网工具经 agentDir/extensions 投影,TUI 和 Desktop App 共用一份注册
|
|
250
|
-
writeWebToolsExtension(officialCfg, {
|
|
251
|
-
webSearch: webSearchEnabled,
|
|
252
|
-
webFetchRender: webFetchRenderEnabled,
|
|
253
|
-
imageGen: imageGenEnabled,
|
|
254
|
-
});
|
|
255
|
-
// 精简 UI 已下线,界面回到 Pi 默认;清掉旧版写进 agentDir/extensions 的扩展,否则 Pi 仍会加载它
|
|
256
|
-
removeLegacyCompactUiExtension();
|
|
257
|
-
// 模型调用错误人话化(同样投影,Desktop App 共享)
|
|
258
|
-
writeErrorHumanizeExtension();
|
|
259
|
-
// 会话内公告触达:5 分钟轮询,公告变化时推进会话(同样投影,Desktop 共享)
|
|
260
|
-
writeAnnouncementsExtension();
|
|
261
|
-
// OpenRouter 应用归因:直连流量计入 u1s1 的公开排行
|
|
262
|
-
writeAttributionExtension();
|
|
263
|
-
// 自定义 MCP 服务:TUI 用 pi 内置 MCP 读 agent/mcp.json;这里迁旧配置,并给 Desktop 的旧引擎投影兜底扩展
|
|
264
|
-
writeMcpExtension();
|
|
265
|
-
ensureTmuxKeyboardProtocol();
|
|
266
|
-
// must be set before pi reads them (getAgentDir() reads at call time);
|
|
267
|
-
// 常量部分已在 runAgent 开头、发起 import 之前设好
|
|
268
|
-
process.env["U1S1_API_KEY"] = officialCfg.apiKey;
|
|
269
|
-
process.env["U1S1_SIGNING_PROXY_URL"] = officialCfg.baseUrl;
|
|
270
|
-
// 自定义端点的密钥走环境变量引用(models.json 里只有 $VAR,不落明文)
|
|
271
|
-
Object.assign(process.env, endpointKeyEnv());
|
|
272
|
-
// 剔掉环境里的其他厂商密钥,/model 只展示 u1s1 官方模型 + 自定义端点
|
|
273
|
-
scrubForeignProviderEnv();
|
|
274
|
-
// 密钥只留在本进程:模型跑的 shell 命令、子 agent、扩展派生的任何子进程都拿不到
|
|
275
|
-
installChildEnvGuard();
|
|
276
|
-
// 客户端事件上报(见 telemetry.ts):状态放在 runAgent 作用域,/clear、/resume
|
|
277
|
-
// 会重建扩展运行时、重跑下面的 factory,但一个 TUI 进程只算一个会话
|
|
278
|
-
const telemetry = createGatewayTelemetry(cfg);
|
|
279
|
-
const sessionStartedAt = Date.now();
|
|
280
|
-
const stats = { turns: 0, toolCalls: 0, filesWritten: 0, writesThisRun: 0 };
|
|
281
|
-
let firstToolSuccessSent = false;
|
|
282
|
-
let firstTaskDoneSent = false;
|
|
283
|
-
let sessionEndSent = false;
|
|
284
|
-
telemetry.track("session_start", { surface: "terminal" });
|
|
285
|
-
// 「连不上 u1s1 服务器」只在用户本机可见;记下底层错误码,网络恢复后随批次上报
|
|
286
|
-
onGatewayFetchFailure((failure) => telemetry.track("gateway_unreachable", { code: failure.code, surface: "terminal" }));
|
|
287
|
-
const trackSessionEnd = () => {
|
|
288
|
-
if (sessionEndSent)
|
|
289
|
-
return;
|
|
290
|
-
sessionEndSent = true;
|
|
291
|
-
telemetry.track("session_end", {
|
|
292
|
-
duration_ms: Date.now() - sessionStartedAt,
|
|
293
|
-
turns: stats.turns,
|
|
294
|
-
tool_calls: stats.toolCalls,
|
|
295
|
-
files_written: stats.filesWritten,
|
|
296
|
-
});
|
|
297
|
-
};
|
|
298
|
-
await searchToolsReady;
|
|
299
|
-
// 界面语言跟站点走(见 i18n.ts)。pi 引擎的补丁文案(patches/)读不到 u1s1 的配置,
|
|
300
|
-
// 只认 U1S1_LANG;登录/换站都已在上面处理完,这里定下来再加载内核,TUI 与 -p 模式都覆盖
|
|
301
|
-
process.env["U1S1_LANG"] = lang();
|
|
302
|
-
// Module evaluation can block the event loop for seconds on slow filesystems.
|
|
303
|
-
// Finish startup requests and fallback/login handling before loading the kernel.
|
|
304
|
-
const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
|
|
305
|
-
// Start this optional request after module evaluation so its timeout can progress.
|
|
306
|
-
void checkForUpdate();
|
|
307
|
-
// 网页终端(如 Taikula)把 Shift+Enter 发成 ESC+CR(\x1b\r)。pi 在没开 Kitty
|
|
308
|
-
// 协议时把它当成 Alt+Enter:空闲就直接发送,忙碌才排队。先改成 CSI-u 的
|
|
309
|
-
// Shift+Enter,输入框就会换行,真·Alt+Enter(\x1b[13;3u)不受影响。
|
|
310
|
-
// TUI 里 console.log 会被下一帧重绘吞掉,拦截提示必须走 ui.notify;
|
|
311
|
-
// session_start 时把 notify 句柄存出来给编辑器拦截用
|
|
312
|
-
let notifyUi;
|
|
313
|
-
class ShiftEnterEditor extends CustomEditor {
|
|
314
|
-
handleInput(data) {
|
|
315
|
-
// 拦截 /login 和 /logout,不让 pi 弹出内置供应商列表
|
|
316
|
-
if (data === "\r" || data === "\n") {
|
|
317
|
-
const text = this.getText().trim();
|
|
318
|
-
const tip = (msg) => (notifyUi ? notifyUi(msg) : console.log(` ${msg}`));
|
|
319
|
-
if (text.startsWith("/login")) {
|
|
320
|
-
tip(tr("切换模型用 /model;要换 u1s1 账号:先 /exit 退出,再在终端运行 u1s1 logout && u1s1 login", "Use /model to switch models. To switch u1s1 accounts, /exit first, then run u1s1 logout && u1s1 login in your terminal"));
|
|
321
|
-
this.setText("");
|
|
322
|
-
this.addToHistory?.(text);
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (text === "/logout") {
|
|
326
|
-
tip(tr("退出登录请先 /exit 回到终端,再运行:u1s1 logout", "To sign out, /exit to your terminal first, then run: u1s1 logout"));
|
|
327
|
-
this.setText("");
|
|
328
|
-
this.addToHistory?.(text);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
super.handleInput(data === "\x1b\r" ? "\x1b[13;2u" : data);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
const extension = [
|
|
336
|
-
{
|
|
337
|
-
name: "u1s1",
|
|
338
|
-
factory: (pi) => {
|
|
339
|
-
applyBrandUi(pi, VERSION);
|
|
340
|
-
// /loop: 会话级定时循环执行提示词(对标 Claude Code),实现见 loop.ts
|
|
341
|
-
registerLoopCommand(pi);
|
|
342
|
-
// 会话标题:拿用户第一条正经输入的前半截当名字(像 Claude Code 那样),
|
|
343
|
-
// /resume 的会话列表里就能一眼认出每个会话。/clear 后 session_start
|
|
344
|
-
// 会重置标记,新会话重新起标题。
|
|
345
|
-
let sessionTitled = false;
|
|
346
|
-
pi.on("session_start", () => {
|
|
347
|
-
sessionTitled = false;
|
|
348
|
-
});
|
|
349
|
-
pi.on("input", async (event) => {
|
|
350
|
-
if (sessionTitled || event.source === "extension")
|
|
351
|
-
return;
|
|
352
|
-
const text = event.text.trim().replace(/\s+/g, " ");
|
|
353
|
-
// 空输入(纯图片粘贴)和斜杠命令不当标题
|
|
354
|
-
if (!text || text.startsWith("/"))
|
|
355
|
-
return;
|
|
356
|
-
sessionTitled = true;
|
|
357
|
-
const title = text.length > 30 ? `${text.slice(0, 30)}…` : text;
|
|
358
|
-
pi.setSessionName(title);
|
|
359
|
-
});
|
|
360
|
-
// pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
|
|
361
|
-
pi.registerCommand("exit", {
|
|
362
|
-
description: tr("退出 u1s1", "Exit u1s1"),
|
|
363
|
-
handler: async (_args, ctx) => {
|
|
364
|
-
ctx.shutdown();
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
// /help:启动横幅和官网教程都在引导用户输入它,必须真实存在
|
|
368
|
-
pi.registerEntryRenderer("u1s1-help", (_entry, _opts, theme) => {
|
|
369
|
-
const site = siteOf(cfg);
|
|
370
|
-
const text = [
|
|
371
|
-
theme.bold(theme.fg("text", tr("u1s1 常用操作", "u1s1 basics"))),
|
|
372
|
-
theme.fg("text", tr("直接打字说需求,回车发送 · Shift+Enter 换行 · Ctrl+V 贴图 · Esc 中断", "Type what you need, Enter to send · Shift+Enter newline · Ctrl+V paste image · Esc stop")),
|
|
373
|
-
theme.fg("text", tr("/model 切换模型(留意免费/价格提示) · /clear 清空上下文开新会话", "/model switch models (check the price hints) · /clear clear context, start a new session")),
|
|
374
|
-
theme.fg("text", tr("/usage 查剩余额度 · /context 看上下文占用与压缩预算 · /resume 恢复历史会话", "/usage remaining balance · /context context usage and compaction budget · /resume past sessions")),
|
|
375
|
-
theme.fg("text", tr("/feedback 一句话反馈问题或建议(自动附带版本与请求编号建工单)", "/feedback report a problem or idea (opens a support ticket with version and request IDs)")),
|
|
376
|
-
theme.fg("dim", tr("/compact 手动压缩上下文 · /settings 设置 · /hotkeys 全部快捷键 · /exit 退出", "/compact compact context now · /settings settings · /hotkeys all shortcuts · /exit exit")),
|
|
377
|
-
// 国际站没有网页托管,不提 u1s1 deploy
|
|
378
|
-
theme.fg("dim", site.id === "intl"
|
|
379
|
-
? tr("退出后在终端:u1s1 update 升级", "Back in your terminal: u1s1 update to upgrade")
|
|
380
|
-
: tr("退出后在终端:u1s1 deploy 发布网页 · u1s1 update 升级", "Back in your terminal: u1s1 deploy to publish a web page · u1s1 update to upgrade")),
|
|
381
|
-
theme.fg("dim", tr("匿名使用统计只记会话开始/结束等节点,不含任何内容;U1S1_TELEMETRY=0 可关闭", "Anonymous usage stats only record events like session start/end, never content; U1S1_TELEMETRY=0 turns them off")),
|
|
382
|
-
theme.fg("dim", tr(`新手教程 → ${siteLinks(site).guides}`, `Guides → ${siteLinks(site).guides}`)),
|
|
383
|
-
].join("\n");
|
|
384
|
-
return new Text(text, 1, 0);
|
|
385
|
-
});
|
|
386
|
-
pi.registerCommand("help", {
|
|
387
|
-
description: tr("查看常用操作和命令", "Show common actions and commands"),
|
|
388
|
-
handler: async () => {
|
|
389
|
-
pi.appendEntry("u1s1-help");
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
// /usage:会话内查额度。此前额度只有用完撞 429 才可见(审计 G1)
|
|
393
|
-
pi.registerEntryRenderer("u1s1-usage", (entry, _opts, theme) => {
|
|
394
|
-
const data = entry.data ?? {};
|
|
395
|
-
if (data.error)
|
|
396
|
-
return new Text(theme.fg("dim", data.error), 1, 0);
|
|
397
|
-
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
398
|
-
});
|
|
399
|
-
pi.registerCommand("usage", {
|
|
400
|
-
description: tr("查看剩余额度和本月用量", "Show remaining balance and this month's usage"),
|
|
401
|
-
handler: async () => {
|
|
402
|
-
const { usageEntryData } = await import("./usage.js");
|
|
403
|
-
pi.appendEntry("u1s1-usage", await usageEntryData());
|
|
404
|
-
},
|
|
405
|
-
});
|
|
406
|
-
// /context:上下文占用与自动压缩预算。注册给 pi 的 contextWindow 是压缩预算
|
|
407
|
-
// (默认 200K,见 effectiveContextWindow),不是模型真实窗口,这里把两者都说清楚,
|
|
408
|
-
// 免得用户看着 1M 的模型却在 20 万 token 就被压缩时以为出了问题
|
|
409
|
-
pi.registerEntryRenderer("u1s1-context", (entry, _opts, theme) => {
|
|
410
|
-
const data = entry.data ?? {};
|
|
411
|
-
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
412
|
-
});
|
|
413
|
-
pi.registerCommand("context", {
|
|
414
|
-
description: tr("查看上下文占用与自动压缩预算", "Show context usage and the auto-compaction budget"),
|
|
415
|
-
handler: async (_args, ctx) => {
|
|
416
|
-
const { contextEntryLines } = await import("./context-usage.js");
|
|
417
|
-
const settings = readSettings();
|
|
418
|
-
pi.appendEntry("u1s1-context", {
|
|
419
|
-
lines: contextEntryLines({
|
|
420
|
-
usage: ctx.getContextUsage(),
|
|
421
|
-
model: ctx.model ? { provider: ctx.model.provider, id: ctx.model.id } : undefined,
|
|
422
|
-
budgetSetting: readCompactionBudgetSetting(settings),
|
|
423
|
-
reserveTokens: readCompactionReserveTokens(settings),
|
|
424
|
-
}),
|
|
425
|
-
});
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
// /feedback:会话内直接建工单,附版本/平台/最近请求编号(运营清单 E4)
|
|
429
|
-
pi.registerEntryRenderer("u1s1-feedback", (entry, _opts, theme) => {
|
|
430
|
-
const data = entry.data ?? {};
|
|
431
|
-
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
432
|
-
});
|
|
433
|
-
pi.registerCommand("feedback", {
|
|
434
|
-
description: tr("反馈问题或建议(自动附带诊断信息建工单)", "Report a problem or idea (opens a support ticket with diagnostics)"),
|
|
435
|
-
handler: async (cmdArgs, ctx) => {
|
|
436
|
-
const { feedbackInSession, parseFeedbackArgs } = await import("./feedback.js");
|
|
437
|
-
let raw = cmdArgs.trim();
|
|
438
|
-
let parsed;
|
|
439
|
-
try {
|
|
440
|
-
parsed = parseFeedbackArgs(raw.split(/\s+/).filter(Boolean));
|
|
441
|
-
}
|
|
442
|
-
catch (e) {
|
|
443
|
-
pi.appendEntry("u1s1-feedback", { lines: [e instanceof Error ? e.message : String(e)] });
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
if (!parsed.help && !parsed.text && ctx.hasUI) {
|
|
447
|
-
const text = (await ctx.ui.input(tr("反馈内容", "Feedback"), tr("一句话说清楚问题或建议", "Describe the problem or idea in a sentence")))?.trim();
|
|
448
|
-
if (!text)
|
|
449
|
-
return;
|
|
450
|
-
raw = `--${parsed.category} ${text}`;
|
|
451
|
-
}
|
|
452
|
-
const model = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
|
|
453
|
-
pi.appendEntry("u1s1-feedback", { lines: await feedbackInSession(raw, model) });
|
|
454
|
-
},
|
|
455
|
-
});
|
|
456
|
-
// ---- 事件上报 + 首次写文件后的 deploy 提示 ----
|
|
457
|
-
pi.on("agent_start", () => {
|
|
458
|
-
stats.writesThisRun = 0;
|
|
459
|
-
});
|
|
460
|
-
pi.on("turn_end", () => {
|
|
461
|
-
stats.turns++;
|
|
462
|
-
});
|
|
463
|
-
pi.on("tool_result", (event) => {
|
|
464
|
-
stats.toolCalls++;
|
|
465
|
-
if (event.isError)
|
|
466
|
-
return;
|
|
467
|
-
if (!firstToolSuccessSent) {
|
|
468
|
-
firstToolSuccessSent = true;
|
|
469
|
-
telemetry.track("first_tool_success", { tool: event.toolName });
|
|
470
|
-
}
|
|
471
|
-
if (event.toolName === "write" || event.toolName === "edit") {
|
|
472
|
-
stats.filesWritten++;
|
|
473
|
-
stats.writesThisRun++;
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
pi.on("agent_end", (_event, ctx) => {
|
|
477
|
-
if (stats.writesThisRun === 0)
|
|
478
|
-
return;
|
|
479
|
-
if (!firstTaskDoneSent) {
|
|
480
|
-
firstTaskDoneSent = true;
|
|
481
|
-
telemetry.track("first_task_done", { files_written: stats.filesWritten });
|
|
482
|
-
}
|
|
483
|
-
// 每台机器只提示一次;跑过 u1s1 deploy 后永不再提(见 nudges.ts)。国际站没有网页托管,不提示
|
|
484
|
-
if (ctx.hasUI && siteOf(cfg).id !== "intl" && shouldShowDeployHint()) {
|
|
485
|
-
markNudge("deploy_hint_shown");
|
|
486
|
-
ctx.ui.notify(deployHintText(), "info");
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
// pi 在 session_shutdown 处理完后直接 process.exit,session_end 只能在这里
|
|
490
|
-
// 发并等冲刷(最多 1 秒);/clear、/resume 的 reason 不是 quit,不算会话结束
|
|
491
|
-
pi.on("session_shutdown", async (event) => {
|
|
492
|
-
if (event.reason !== "quit")
|
|
493
|
-
return;
|
|
494
|
-
trackSessionEnd();
|
|
495
|
-
await telemetry.shutdown();
|
|
496
|
-
});
|
|
497
|
-
// 模型调用错误人话化不在这里注册:走 writeErrorHumanizeExtension 投影,
|
|
498
|
-
// TUI 和 Desktop App 共用一份(见 agent-setup.ts)
|
|
499
|
-
// /clear: 清空当前对话上下文,开始新会话
|
|
500
|
-
pi.registerCommand("clear", {
|
|
501
|
-
description: tr("清除当前对话上下文,开始新会话", "Clear the conversation context and start a new session"),
|
|
502
|
-
handler: async (_args, ctx) => {
|
|
503
|
-
await ctx.newSession({
|
|
504
|
-
withSession: async (sessionContext) => {
|
|
505
|
-
sessionContext.ui.notify(tr("🗑️ 上下文已清除", "🗑️ Context cleared"), "info");
|
|
506
|
-
},
|
|
507
|
-
});
|
|
508
|
-
},
|
|
509
|
-
});
|
|
510
|
-
// 进程内注册直接给字面量密钥:不经 $VAR 环境变量引用,也不落文件
|
|
511
|
-
pi.registerProvider(PROVIDER_ID, {
|
|
512
|
-
name: "u1s1",
|
|
513
|
-
baseUrl: officialCfg.baseUrl,
|
|
514
|
-
api: "openai-completions",
|
|
515
|
-
apiKey: officialCfg.apiKey,
|
|
516
|
-
models: toProviderModels(MODELS),
|
|
517
|
-
});
|
|
518
|
-
// 用户在云端配置的自定义端点,一个端点一个 provider,/model 里即可切换
|
|
519
|
-
for (const ep of CUSTOM_ENDPOINTS) {
|
|
520
|
-
pi.registerProvider(ep.id, endpointProviderEntry(ep, { literalKey: true }));
|
|
521
|
-
}
|
|
522
|
-
// /model and Ctrl+P already write pi settings; also keep ~/.u1s1/config.json in sync
|
|
523
|
-
pi.on("model_select", (event) => {
|
|
524
|
-
if (event.source === "restore")
|
|
525
|
-
return;
|
|
526
|
-
const ref = { provider: event.model.provider, id: event.model.id };
|
|
527
|
-
if (!refValid(ref))
|
|
528
|
-
return;
|
|
529
|
-
persistPreferredModel(loadConfig(), ref.provider, ref.id);
|
|
530
|
-
});
|
|
531
|
-
pi.on("session_start", (_event, ctx) => {
|
|
532
|
-
notifyUi = (msg) => ctx.ui.notify(msg, "info");
|
|
533
|
-
const previous = ctx.ui.getEditorComponent();
|
|
534
|
-
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
535
|
-
if (previous) {
|
|
536
|
-
const inner = previous(tui, theme, keybindings);
|
|
537
|
-
const orig = inner.handleInput.bind(inner);
|
|
538
|
-
inner.handleInput = (data) => orig(data === "\x1b\r" ? "\x1b[13;2u" : data);
|
|
539
|
-
return inner;
|
|
540
|
-
}
|
|
541
|
-
return new ShiftEnterEditor(tui, theme, keybindings);
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
];
|
|
547
|
-
const hasModelArg = args.some((a) => a === "--model" || a.startsWith("--model=") || a === "--provider");
|
|
548
|
-
const pref = resolvePreferredModel(cfg);
|
|
549
|
-
// 拿到了目录但本站一个模型都没上(国际站上线初期就是这样):别让 pi 报「Model not found」
|
|
550
|
-
if (!hasModelArg && modelsResp && MODELS.length === 0 && pref.provider === PROVIDER_ID) {
|
|
551
|
-
console.error(tr(` ${siteOf(cfg).origin} 暂时还没有可用的模型,请稍后再试。`, ` No models are available on ${siteOf(cfg).origin} yet. Please try again later.`));
|
|
552
|
-
process.exit(1);
|
|
553
|
-
}
|
|
554
|
-
if (cfg.model !== pref.id || (cfg.modelProvider ?? PROVIDER_ID) !== pref.provider) {
|
|
555
|
-
persistPreferredModel(cfg, pref.provider, pref.id);
|
|
556
|
-
}
|
|
557
|
-
const finalArgs = hasModelArg ? args : ["--model", `${pref.provider}/${pref.id}`, ...args];
|
|
558
|
-
try {
|
|
559
|
-
await main(finalArgs, { extensionFactories: extension });
|
|
560
|
-
}
|
|
561
|
-
finally {
|
|
562
|
-
trackSessionEnd();
|
|
563
|
-
await telemetry.shutdown();
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
20
|
function printHelp() {
|
|
567
21
|
printConsoleBanner(VERSION);
|
|
568
22
|
if (isEnglish()) {
|
|
@@ -572,6 +26,8 @@ function printHelp() {
|
|
|
572
26
|
console.log(" 命令:");
|
|
573
27
|
console.log(" u1s1 进入对话界面(最常用,不带参数)");
|
|
574
28
|
console.log(" u1s1 -p \"一句话\" 不进界面直接回答,适合脚本里用");
|
|
29
|
+
for (const line of launcherHelpLines())
|
|
30
|
+
console.log(line);
|
|
575
31
|
console.log(" u1s1 login / logout 登录 / 退出登录");
|
|
576
32
|
console.log(" u1s1 model 查看或切换默认模型");
|
|
577
33
|
console.log(" u1s1 usage 查看剩余额度");
|
|
@@ -598,6 +54,7 @@ function printHelpEn() {
|
|
|
598
54
|
const commands = [
|
|
599
55
|
["u1s1", "Start an interactive session (most common, no arguments)"],
|
|
600
56
|
["u1s1 -p \"prompt\"", "Answer once without the UI, handy in scripts"],
|
|
57
|
+
...launcherHelpRows(),
|
|
601
58
|
["u1s1 login / logout", "Sign in / sign out"],
|
|
602
59
|
["u1s1 model", "Show or change the default model"],
|
|
603
60
|
["u1s1 usage", "Show your remaining balance"],
|
|
@@ -721,6 +178,12 @@ const COMMANDS = {
|
|
|
721
178
|
const { mcpCommand } = await import("./mcp/command.js");
|
|
722
179
|
await mcpCommand(rest);
|
|
723
180
|
},
|
|
181
|
+
// 给 Claude Code / Codex 用的 stdio MCP 工具服务(由 u1s1 claude / codex 注入的配置拉起)
|
|
182
|
+
"mcp-server": async () => {
|
|
183
|
+
const { runMcpServer } = await import("./mcp-server.js");
|
|
184
|
+
await runMcpServer();
|
|
185
|
+
process.exit(0);
|
|
186
|
+
},
|
|
724
187
|
model: async (rest) => {
|
|
725
188
|
const { modelCommand } = await import("./model.js");
|
|
726
189
|
await modelCommand(rest[0]);
|
|
@@ -792,15 +255,18 @@ async function run() {
|
|
|
792
255
|
await COMMANDS[cmd](rest);
|
|
793
256
|
return;
|
|
794
257
|
}
|
|
258
|
+
// pi / claude / codex / use / agents / config:启动器的保留字,先于拼错检测
|
|
259
|
+
if (cmd && isLauncherWord(cmd)) {
|
|
260
|
+
await runLauncher(args);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
795
263
|
if (cmd && rejectMistypedCommand(cmd, args.length))
|
|
796
264
|
return;
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
await runAgent(cfg, args);
|
|
800
|
-
// autoUpdate 开着且启动时发现了新版:现在装(TUI 已退出,原地重铺安全)
|
|
801
|
-
installPendingUpdate();
|
|
265
|
+
// 不带参数或其余未知参数:交给默认客户端(存量用户默认 Pi,见 launcher.ts)
|
|
266
|
+
await runLauncher(args);
|
|
802
267
|
}
|
|
803
|
-
|
|
268
|
+
// 不收 pi / use / codex:它们太短,收进来会把「u1s1 hi」「u1s1 user」「u1s1 code」这类一词 prompt 当拼错拦下
|
|
269
|
+
const KNOWN_COMMANDS = ["deploy", "login", "logout", "usage", "whoami", "model", "update", "import", "mcp", "mcp-server", "bench", "feedback", "help", "claude", "agents", "config"];
|
|
804
270
|
/** 没有自己 --help 处理的简单子命令:问用法时只打印说明,不执行任何动作。 */
|
|
805
271
|
function simpleCommandHelp() {
|
|
806
272
|
return isEnglish() ? SIMPLE_COMMAND_HELP_EN : SIMPLE_COMMAND_HELP;
|