u1s1-cli 1.11.8 → 1.13.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/dist/agent-setup.d.ts +19 -3
- package/dist/agent-setup.js +85 -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 +2 -1
- package/dist/brand.js +38 -12
- package/dist/config.d.ts +11 -1
- package/dist/config.js +25 -8
- package/dist/context-usage.js +12 -9
- package/dist/deploy.js +49 -45
- package/dist/device-auth.js +7 -5
- package/dist/error-humanize.d.ts +1 -0
- package/dist/error-humanize.js +18 -6
- package/dist/feedback.js +48 -31
- 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 +140 -49
- package/dist/login.js +39 -28
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.d.ts +2 -2
- package/dist/mcp/command.js +109 -69
- package/dist/mcp/config.d.ts +22 -6
- package/dist/mcp/config.js +0 -0
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +10 -5
- package/dist/model.js +11 -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/subagent.js +5 -4
- package/dist/tools.js +53 -37
- package/dist/update.js +29 -28
- package/dist/usage.d.ts +1 -1
- package/dist/usage.js +96 -43
- 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 +4 -4
- package/scripts/patch-pi.js +347 -278
- package/scripts/render-regression.mjs +55 -4
package/dist/index.js
CHANGED
|
@@ -12,9 +12,10 @@ import { applyBrandUi, setAnnouncement, setUpdateNotice } from "./style.js";
|
|
|
12
12
|
import { AccessDeniedError, AuthError, fetchModels, loadCustomEndpoints, readJsonResponseCapped } from "./api.js";
|
|
13
13
|
import { ensureSigningProxy } from "./device-auth.js";
|
|
14
14
|
import { installChildEnvGuard } from "./secret-env.js";
|
|
15
|
-
import {
|
|
15
|
+
import { deployHintText, markNudge, shouldShowDeployHint } from "./nudges.js";
|
|
16
16
|
import { createGatewayTelemetry } from "./telemetry.js";
|
|
17
17
|
import { installCommand, siteLinks, siteOf } from "./site.js";
|
|
18
|
+
import { isEnglish, lang, tr } from "./i18n.js";
|
|
18
19
|
const PACKAGE_NAME = "u1s1-cli";
|
|
19
20
|
/** 启动时检测到的可自动安装的新版;TUI 退出后才装(见 installPendingUpdate)。 */
|
|
20
21
|
let pendingUpdate;
|
|
@@ -81,7 +82,7 @@ async function checkForUpdate() {
|
|
|
81
82
|
const settings = readSettings();
|
|
82
83
|
// 便携版没法用 npm 自更新;autoUpdate 关闭同理只提示。u1s1 update 会给出正确升级方式
|
|
83
84
|
if (settings.autoUpdate === false || isPortableInstall()) {
|
|
84
|
-
setUpdateNotice(`⬆ 新版 v${latest} 可用 · u1s1 update
|
|
85
|
+
setUpdateNotice(tr(`⬆ 新版 v${latest} 可用 · u1s1 update 升级`, `⬆ v${latest} available · run u1s1 update`));
|
|
85
86
|
return;
|
|
86
87
|
}
|
|
87
88
|
// 检测包管理器
|
|
@@ -94,7 +95,7 @@ async function checkForUpdate() {
|
|
|
94
95
|
? `npm install -g --loglevel=error ${PACKAGE_NAME}@latest`
|
|
95
96
|
: `${pm} add -g ${PACKAGE_NAME}@latest`;
|
|
96
97
|
pendingUpdate = { latest, installCmd };
|
|
97
|
-
setUpdateNotice(`⬆ 发现新版 v${latest}
|
|
98
|
+
setUpdateNotice(tr(`⬆ 发现新版 v${latest},退出后自动更新`, `⬆ v${latest} available, will update on exit`));
|
|
98
99
|
}
|
|
99
100
|
/**
|
|
100
101
|
* TUI 退出后前台安装新版:此刻不再有模型调用和懒加载,重铺 node_modules 才安全。
|
|
@@ -106,21 +107,21 @@ function installPendingUpdate() {
|
|
|
106
107
|
if (!pendingUpdate)
|
|
107
108
|
return;
|
|
108
109
|
const { latest, installCmd } = pendingUpdate;
|
|
109
|
-
console.log(`\n⬆ 正在更新到 v${latest}…`);
|
|
110
|
-
console.log(" (可能需要几分钟,期间请不要关闭窗口或按 Ctrl+C,中途打断会装出半截)");
|
|
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.)"));
|
|
111
112
|
try {
|
|
112
113
|
execSync(installCmd, { stdio: "inherit" });
|
|
113
|
-
console.log(`✅ 已更新到 v${latest},下次运行 u1s1
|
|
114
|
+
console.log(tr(`✅ 已更新到 v${latest},下次运行 u1s1 生效。`, `✅ Updated to v${latest}. It takes effect the next time you run u1s1.`));
|
|
114
115
|
}
|
|
115
116
|
catch {
|
|
116
117
|
// 失败不挡退出,下次启动还会再试。npm 全局目录没写权限时 `u1s1 update` 也会
|
|
117
118
|
// 走同一条命令再失败一次,所以这里直接给出能跳出循环的路径(官网安装脚本)
|
|
118
|
-
console.error("自动更新失败(上面是 npm 的报错,常见原因是没有全局安装权限)。");
|
|
119
|
-
console.error("可以改用官网安装脚本重装,一步到位:");
|
|
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:"));
|
|
120
121
|
console.error(process.platform === "win32"
|
|
121
122
|
? ` powershell -c "${installCommand(activeSite(), "ps1")}"`
|
|
122
123
|
: ` ${installCommand(activeSite(), "sh")}`);
|
|
123
|
-
console.error("不想每次退出都尝试更新的话,可在 ~/.u1s1/agent/settings.json 里设 \"autoUpdate\": false");
|
|
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"));
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
127
|
/**
|
|
@@ -196,11 +197,11 @@ async function runAgent(cfg, args) {
|
|
|
196
197
|
if (e instanceof AuthError) {
|
|
197
198
|
// 凭证形状还在但服务端已作废(设备被移除/换过钥匙):直接进 TUI 每条消息都会失败,
|
|
198
199
|
// 不如现在就引导重新登录
|
|
199
|
-
console.error(" 登录已失效(这台设备可能被移除,或账号更换过钥匙),需要重新登录。");
|
|
200
|
+
console.error(tr(" 登录已失效(这台设备可能被移除,或账号更换过钥匙),需要重新登录。", " Your session has expired (this device may have been removed, or the account's keys changed). Please sign in again."));
|
|
200
201
|
const { login } = await import("./login.js");
|
|
201
202
|
cfg = await login();
|
|
202
203
|
modelsResp = await fetchModels(cfg).catch((e2) => {
|
|
203
|
-
console.error(" 获取模型列表失败,使用内置列表:", e2.message);
|
|
204
|
+
console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e2.message);
|
|
204
205
|
return undefined;
|
|
205
206
|
});
|
|
206
207
|
}
|
|
@@ -208,11 +209,11 @@ async function runAgent(cfg, args) {
|
|
|
208
209
|
// 封禁/停用/设备不受信任:进 TUI 后每条消息都会失败,不如现在就把原因说清楚并退出
|
|
209
210
|
console.error(` ${e.message}`);
|
|
210
211
|
const site = siteOf(cfg);
|
|
211
|
-
console.error(` 这个账号当前无法使用 u1s1。如有疑问可发邮件到 ${site.contactEmail},或在 ${siteLinks(site).dashboard}
|
|
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}.`));
|
|
212
213
|
process.exit(1);
|
|
213
214
|
}
|
|
214
215
|
else {
|
|
215
|
-
console.error(" 获取模型列表失败,使用内置列表:", e.message);
|
|
216
|
+
console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e.message);
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
if (modelsResp) {
|
|
@@ -259,7 +260,7 @@ async function runAgent(cfg, args) {
|
|
|
259
260
|
writeAnnouncementsExtension();
|
|
260
261
|
// OpenRouter 应用归因:直连流量计入 u1s1 的公开排行
|
|
261
262
|
writeAttributionExtension();
|
|
262
|
-
// 自定义 MCP
|
|
263
|
+
// 自定义 MCP 服务:TUI 用 pi 内置 MCP 读 agent/mcp.json;这里迁旧配置,并给 Desktop 的旧引擎投影兜底扩展
|
|
263
264
|
writeMcpExtension();
|
|
264
265
|
ensureTmuxKeyboardProtocol();
|
|
265
266
|
// must be set before pi reads them (getAgentDir() reads at call time);
|
|
@@ -293,6 +294,9 @@ async function runAgent(cfg, args) {
|
|
|
293
294
|
});
|
|
294
295
|
};
|
|
295
296
|
await searchToolsReady;
|
|
297
|
+
// 界面语言跟站点走(见 i18n.ts)。pi 引擎的补丁文案(patches/)读不到 u1s1 的配置,
|
|
298
|
+
// 只认 U1S1_LANG;登录/换站都已在上面处理完,这里定下来再加载内核,TUI 与 -p 模式都覆盖
|
|
299
|
+
process.env["U1S1_LANG"] = lang();
|
|
296
300
|
// Module evaluation can block the event loop for seconds on slow filesystems.
|
|
297
301
|
// Finish startup requests and fallback/login handling before loading the kernel.
|
|
298
302
|
const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
|
|
@@ -311,13 +315,13 @@ async function runAgent(cfg, args) {
|
|
|
311
315
|
const text = this.getText().trim();
|
|
312
316
|
const tip = (msg) => (notifyUi ? notifyUi(msg) : console.log(` ${msg}`));
|
|
313
317
|
if (text.startsWith("/login")) {
|
|
314
|
-
tip("切换模型用 /model;要换 u1s1 账号:先 /exit 退出,再在终端运行 u1s1 logout && u1s1 login");
|
|
318
|
+
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"));
|
|
315
319
|
this.setText("");
|
|
316
320
|
this.addToHistory?.(text);
|
|
317
321
|
return;
|
|
318
322
|
}
|
|
319
323
|
if (text === "/logout") {
|
|
320
|
-
tip("退出登录请先 /exit 回到终端,再运行:u1s1 logout");
|
|
324
|
+
tip(tr("退出登录请先 /exit 回到终端,再运行:u1s1 logout", "To sign out, /exit to your terminal first, then run: u1s1 logout"));
|
|
321
325
|
this.setText("");
|
|
322
326
|
this.addToHistory?.(text);
|
|
323
327
|
return;
|
|
@@ -353,28 +357,32 @@ async function runAgent(cfg, args) {
|
|
|
353
357
|
});
|
|
354
358
|
// pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
|
|
355
359
|
pi.registerCommand("exit", {
|
|
356
|
-
description: "退出 u1s1",
|
|
360
|
+
description: tr("退出 u1s1", "Exit u1s1"),
|
|
357
361
|
handler: async (_args, ctx) => {
|
|
358
362
|
ctx.shutdown();
|
|
359
363
|
},
|
|
360
364
|
});
|
|
361
365
|
// /help:启动横幅和官网教程都在引导用户输入它,必须真实存在
|
|
362
366
|
pi.registerEntryRenderer("u1s1-help", (_entry, _opts, theme) => {
|
|
367
|
+
const site = siteOf(cfg);
|
|
363
368
|
const text = [
|
|
364
|
-
theme.bold(theme.fg("text", "u1s1 常用操作")),
|
|
365
|
-
theme.fg("text", "直接打字说需求,回车发送 · Shift+Enter 换行 · Ctrl+V 贴图 · Esc 中断"),
|
|
366
|
-
theme.fg("text", "/model 切换模型(留意免费/价格提示) · /clear 清空上下文开新会话"),
|
|
367
|
-
theme.fg("text", "/usage 查剩余额度 · /context 看上下文占用与压缩预算 · /resume 恢复历史会话"),
|
|
368
|
-
theme.fg("text", "/feedback 一句话反馈问题或建议(自动附带版本与请求编号建工单)"),
|
|
369
|
-
theme.fg("dim", "/compact 手动压缩上下文 · /settings 设置 · /hotkeys 全部快捷键 · /exit 退出"),
|
|
370
|
-
|
|
371
|
-
theme.fg("dim",
|
|
372
|
-
|
|
369
|
+
theme.bold(theme.fg("text", tr("u1s1 常用操作", "u1s1 basics"))),
|
|
370
|
+
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")),
|
|
371
|
+
theme.fg("text", tr("/model 切换模型(留意免费/价格提示) · /clear 清空上下文开新会话", "/model switch models (check the price hints) · /clear clear context, start a new session")),
|
|
372
|
+
theme.fg("text", tr("/usage 查剩余额度 · /context 看上下文占用与压缩预算 · /resume 恢复历史会话", "/usage remaining balance · /context context usage and compaction budget · /resume past sessions")),
|
|
373
|
+
theme.fg("text", tr("/feedback 一句话反馈问题或建议(自动附带版本与请求编号建工单)", "/feedback report a problem or idea (opens a support ticket with version and request IDs)")),
|
|
374
|
+
theme.fg("dim", tr("/compact 手动压缩上下文 · /settings 设置 · /hotkeys 全部快捷键 · /exit 退出", "/compact compact context now · /settings settings · /hotkeys all shortcuts · /exit exit")),
|
|
375
|
+
// 国际站没有网页托管,不提 u1s1 deploy
|
|
376
|
+
theme.fg("dim", site.id === "intl"
|
|
377
|
+
? tr("退出后在终端:u1s1 update 升级", "Back in your terminal: u1s1 update to upgrade")
|
|
378
|
+
: tr("退出后在终端:u1s1 deploy 发布网页 · u1s1 update 升级", "Back in your terminal: u1s1 deploy to publish a web page · u1s1 update to upgrade")),
|
|
379
|
+
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")),
|
|
380
|
+
theme.fg("dim", tr(`新手教程 → ${siteLinks(site).guides}`, `Guides → ${siteLinks(site).guides}`)),
|
|
373
381
|
].join("\n");
|
|
374
382
|
return new Text(text, 1, 0);
|
|
375
383
|
});
|
|
376
384
|
pi.registerCommand("help", {
|
|
377
|
-
description: "查看常用操作和命令",
|
|
385
|
+
description: tr("查看常用操作和命令", "Show common actions and commands"),
|
|
378
386
|
handler: async () => {
|
|
379
387
|
pi.appendEntry("u1s1-help");
|
|
380
388
|
},
|
|
@@ -387,7 +395,7 @@ async function runAgent(cfg, args) {
|
|
|
387
395
|
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
388
396
|
});
|
|
389
397
|
pi.registerCommand("usage", {
|
|
390
|
-
description: "查看剩余额度和本月用量",
|
|
398
|
+
description: tr("查看剩余额度和本月用量", "Show remaining balance and this month's usage"),
|
|
391
399
|
handler: async () => {
|
|
392
400
|
const { usageEntryData } = await import("./usage.js");
|
|
393
401
|
pi.appendEntry("u1s1-usage", await usageEntryData());
|
|
@@ -401,7 +409,7 @@ async function runAgent(cfg, args) {
|
|
|
401
409
|
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
402
410
|
});
|
|
403
411
|
pi.registerCommand("context", {
|
|
404
|
-
description: "查看上下文占用与自动压缩预算",
|
|
412
|
+
description: tr("查看上下文占用与自动压缩预算", "Show context usage and the auto-compaction budget"),
|
|
405
413
|
handler: async (_args, ctx) => {
|
|
406
414
|
const { contextEntryLines } = await import("./context-usage.js");
|
|
407
415
|
const settings = readSettings();
|
|
@@ -421,7 +429,7 @@ async function runAgent(cfg, args) {
|
|
|
421
429
|
return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
|
|
422
430
|
});
|
|
423
431
|
pi.registerCommand("feedback", {
|
|
424
|
-
description: "反馈问题或建议(自动附带诊断信息建工单)",
|
|
432
|
+
description: tr("反馈问题或建议(自动附带诊断信息建工单)", "Report a problem or idea (opens a support ticket with diagnostics)"),
|
|
425
433
|
handler: async (cmdArgs, ctx) => {
|
|
426
434
|
const { feedbackInSession, parseFeedbackArgs } = await import("./feedback.js");
|
|
427
435
|
let raw = cmdArgs.trim();
|
|
@@ -434,7 +442,7 @@ async function runAgent(cfg, args) {
|
|
|
434
442
|
return;
|
|
435
443
|
}
|
|
436
444
|
if (!parsed.help && !parsed.text && ctx.hasUI) {
|
|
437
|
-
const text = (await ctx.ui.input("反馈内容", "一句话说清楚问题或建议"))?.trim();
|
|
445
|
+
const text = (await ctx.ui.input(tr("反馈内容", "Feedback"), tr("一句话说清楚问题或建议", "Describe the problem or idea in a sentence")))?.trim();
|
|
438
446
|
if (!text)
|
|
439
447
|
return;
|
|
440
448
|
raw = `--${parsed.category} ${text}`;
|
|
@@ -470,10 +478,10 @@ async function runAgent(cfg, args) {
|
|
|
470
478
|
firstTaskDoneSent = true;
|
|
471
479
|
telemetry.track("first_task_done", { files_written: stats.filesWritten });
|
|
472
480
|
}
|
|
473
|
-
// 每台机器只提示一次;跑过 u1s1 deploy 后永不再提(见 nudges.ts)
|
|
474
|
-
if (ctx.hasUI && shouldShowDeployHint()) {
|
|
481
|
+
// 每台机器只提示一次;跑过 u1s1 deploy 后永不再提(见 nudges.ts)。国际站没有网页托管,不提示
|
|
482
|
+
if (ctx.hasUI && siteOf(cfg).id !== "intl" && shouldShowDeployHint()) {
|
|
475
483
|
markNudge("deploy_hint_shown");
|
|
476
|
-
ctx.ui.notify(
|
|
484
|
+
ctx.ui.notify(deployHintText(), "info");
|
|
477
485
|
}
|
|
478
486
|
});
|
|
479
487
|
// pi 在 session_shutdown 处理完后直接 process.exit,session_end 只能在这里
|
|
@@ -488,11 +496,11 @@ async function runAgent(cfg, args) {
|
|
|
488
496
|
// TUI 和 Desktop App 共用一份(见 agent-setup.ts)
|
|
489
497
|
// /clear: 清空当前对话上下文,开始新会话
|
|
490
498
|
pi.registerCommand("clear", {
|
|
491
|
-
description: "清除当前对话上下文,开始新会话",
|
|
499
|
+
description: tr("清除当前对话上下文,开始新会话", "Clear the conversation context and start a new session"),
|
|
492
500
|
handler: async (_args, ctx) => {
|
|
493
501
|
await ctx.newSession({
|
|
494
502
|
withSession: async (sessionContext) => {
|
|
495
|
-
sessionContext.ui.notify("🗑️ 上下文已清除", "info");
|
|
503
|
+
sessionContext.ui.notify(tr("🗑️ 上下文已清除", "🗑️ Context cleared"), "info");
|
|
496
504
|
},
|
|
497
505
|
});
|
|
498
506
|
},
|
|
@@ -550,6 +558,10 @@ async function runAgent(cfg, args) {
|
|
|
550
558
|
}
|
|
551
559
|
function printHelp() {
|
|
552
560
|
printConsoleBanner(VERSION);
|
|
561
|
+
if (isEnglish()) {
|
|
562
|
+
printHelpEn();
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
553
565
|
console.log(" 命令:");
|
|
554
566
|
console.log(" u1s1 进入对话界面(最常用,不带参数)");
|
|
555
567
|
console.log(" u1s1 -p \"一句话\" 不进界面直接回答,适合脚本里用");
|
|
@@ -558,9 +570,12 @@ function printHelp() {
|
|
|
558
570
|
console.log(" u1s1 usage 查看剩余额度");
|
|
559
571
|
console.log(" u1s1 whoami 查看当前登录的账号");
|
|
560
572
|
console.log(" u1s1 update 升级到最新版");
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
573
|
+
// 国际站没有网页托管(网关拒绝),不列 deploy
|
|
574
|
+
if (activeSite().id !== "intl") {
|
|
575
|
+
console.log(" u1s1 deploy 发布网页(--public / --private)");
|
|
576
|
+
console.log(" u1s1 deploy list 查看已发布的站点");
|
|
577
|
+
console.log(" u1s1 deploy remove 删除已发布的站点");
|
|
578
|
+
}
|
|
564
579
|
console.log(" u1s1 feedback \"一句话\" 反馈问题或建议(自动建工单,--bug/--question…)");
|
|
565
580
|
console.log(" u1s1 import 导入历史会话(import skills 导入技能)");
|
|
566
581
|
console.log(" u1s1 mcp 配置第三方 MCP 服务(add/list/test/remove/import)");
|
|
@@ -571,10 +586,43 @@ function printHelp() {
|
|
|
571
586
|
console.log(" 匿名使用统计只记会话开始/结束等节点,不含内容;设 U1S1_TELEMETRY=0 关闭");
|
|
572
587
|
console.log(` 更多帮助 → ${siteLinks(activeSite()).guides}`);
|
|
573
588
|
}
|
|
589
|
+
/** 英文帮助:命令列按 ASCII 宽度对齐(中文版靠 CJK 双宽,两边不能共用一套空格)。 */
|
|
590
|
+
function printHelpEn() {
|
|
591
|
+
const commands = [
|
|
592
|
+
["u1s1", "Start an interactive session (most common, no arguments)"],
|
|
593
|
+
["u1s1 -p \"prompt\"", "Answer once without the UI, handy in scripts"],
|
|
594
|
+
["u1s1 login / logout", "Sign in / sign out"],
|
|
595
|
+
["u1s1 model", "Show or change the default model"],
|
|
596
|
+
["u1s1 usage", "Show your remaining balance"],
|
|
597
|
+
["u1s1 whoami", "Show the signed-in account"],
|
|
598
|
+
["u1s1 update", "Update to the latest version"],
|
|
599
|
+
];
|
|
600
|
+
if (activeSite().id !== "intl") {
|
|
601
|
+
commands.push(["u1s1 deploy", "Publish a web page (--public / --private)"], ["u1s1 deploy list", "List published sites"], ["u1s1 deploy remove", "Remove a published site"]);
|
|
602
|
+
}
|
|
603
|
+
commands.push(["u1s1 feedback \"text\"", "Send feedback or report a bug (opens a support ticket; --bug/--question…)"], ["u1s1 import", "Import past sessions (import skills to import skills)"], ["u1s1 mcp", "Configure third-party MCP servers (add/list/test/remove/import)"], ["u1s1 bench", "Benchmark model coding ability"], ["u1s1 --version", "Show the version"]);
|
|
604
|
+
console.log(" Commands:");
|
|
605
|
+
for (const [cmd, desc] of commands)
|
|
606
|
+
console.log(` ${cmd.padEnd(22)} ${desc}`);
|
|
607
|
+
console.log("");
|
|
608
|
+
console.log(" In a session, type /help for session commands (/model /clear /feedback /exit …)");
|
|
609
|
+
console.log(" Anonymous usage stats only record events like session start/end, never content; set U1S1_TELEMETRY=0 to turn them off");
|
|
610
|
+
console.log(` More help → ${siteLinks(activeSite()).guides}`);
|
|
611
|
+
}
|
|
574
612
|
function wantsHelp(rest) {
|
|
575
613
|
return rest.includes("--help") || rest.includes("-h");
|
|
576
614
|
}
|
|
615
|
+
/** 国际站没有网页托管;只在国际站打印,所以只有英文。 */
|
|
616
|
+
function hostingUnavailableText(site) {
|
|
617
|
+
return `Site hosting isn't available on ${new URL(site.origin).host}.`;
|
|
618
|
+
}
|
|
577
619
|
async function deployEntry(rest) {
|
|
620
|
+
// 国际站没有网页托管(网关也会拒绝):在这里挡住,不进 deploy.ts,也不先拽去登录
|
|
621
|
+
const site = activeSite();
|
|
622
|
+
if (site.id === "intl") {
|
|
623
|
+
console.error(hostingUnavailableText(site));
|
|
624
|
+
process.exit(1);
|
|
625
|
+
}
|
|
578
626
|
const { deployCommand, printDeployHelp } = await import("./deploy.js");
|
|
579
627
|
// --help 不该先被拽去登录
|
|
580
628
|
if (wantsHelp(rest)) {
|
|
@@ -590,7 +638,7 @@ async function whoamiCommand() {
|
|
|
590
638
|
const { hasDeviceCredential } = await import("./device-auth.js");
|
|
591
639
|
const cfg = loadConfig();
|
|
592
640
|
if (!hasDeviceCredential(cfg)) {
|
|
593
|
-
console.error("还没登录,先跑 u1s1 login");
|
|
641
|
+
console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
594
642
|
process.exit(1);
|
|
595
643
|
}
|
|
596
644
|
const { fetchMe } = await import("./api.js");
|
|
@@ -598,6 +646,12 @@ async function whoamiCommand() {
|
|
|
598
646
|
console.error(e.message);
|
|
599
647
|
process.exit(1);
|
|
600
648
|
});
|
|
649
|
+
if (isEnglish()) {
|
|
650
|
+
console.log(me.email ?? "(no email linked)");
|
|
651
|
+
console.log(` Server ${cfg.baseUrl}`);
|
|
652
|
+
console.log(" Switch accounts: u1s1 logout, then u1s1 login. Check your balance: u1s1 usage");
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
601
655
|
console.log(me.email ?? "(未绑定邮箱)");
|
|
602
656
|
console.log(` 服务器 ${cfg.baseUrl}`);
|
|
603
657
|
console.log(" 换账号:u1s1 logout 再 u1s1 login;看额度:u1s1 usage");
|
|
@@ -606,7 +660,7 @@ async function logoutCommand() {
|
|
|
606
660
|
const { saveConfig } = await import("./config.js");
|
|
607
661
|
const current = loadConfig();
|
|
608
662
|
if (!current.deviceToken && !current.apiKey) {
|
|
609
|
-
console.log("当前本来就没有登录。运行 u1s1 即可登录。");
|
|
663
|
+
console.log(tr("当前本来就没有登录。运行 u1s1 即可登录。", "You're not signed in. Run u1s1 to sign in."));
|
|
610
664
|
return;
|
|
611
665
|
}
|
|
612
666
|
let serverRevoked = false;
|
|
@@ -625,11 +679,12 @@ async function logoutCommand() {
|
|
|
625
679
|
email: undefined,
|
|
626
680
|
});
|
|
627
681
|
if (serverRevoked) {
|
|
628
|
-
console.log("已退出登录,这台设备在服务端的授权也已注销。");
|
|
682
|
+
console.log(tr("已退出登录,这台设备在服务端的授权也已注销。", "Signed out. This device's authorization on the server has been revoked too."));
|
|
629
683
|
}
|
|
630
684
|
else {
|
|
631
|
-
console.log("已退出本机登录。");
|
|
632
|
-
|
|
685
|
+
console.log(tr("已退出本机登录。", "Signed out on this machine."));
|
|
686
|
+
const dashboard = siteLinks(siteOf(current)).dashboard;
|
|
687
|
+
console.log(tr(`(没连上服务器注销这台设备;如有需要,可在 ${dashboard} 的设备列表里手动移除。)`, `(Couldn't reach the server to revoke this device. If needed, remove it from the device list at ${dashboard}.)`));
|
|
633
688
|
}
|
|
634
689
|
}
|
|
635
690
|
/** 子命令表:每条都按需 import,不带子命令直接进对话界面的路径不多加载任何模块。 */
|
|
@@ -676,7 +731,7 @@ const COMMANDS = {
|
|
|
676
731
|
await benchCommand(rest);
|
|
677
732
|
},
|
|
678
733
|
web: async () => {
|
|
679
|
-
console.error(`u1s1 web 已下线,请使用 Desktop App:${siteLinks(activeSite()).download}`);
|
|
734
|
+
console.error(tr(`u1s1 web 已下线,请使用 Desktop App:${siteLinks(activeSite()).download}`, `u1s1 web has been removed. Use the Desktop App instead: ${siteLinks(activeSite()).download}`));
|
|
680
735
|
process.exitCode = 1;
|
|
681
736
|
},
|
|
682
737
|
};
|
|
@@ -691,8 +746,15 @@ function rejectMistypedCommand(cmd, argCount) {
|
|
|
691
746
|
const near = KNOWN_COMMANDS.find((k) => editDistance(k, cmd) <= (k.length <= 4 ? 1 : 2));
|
|
692
747
|
if (!near)
|
|
693
748
|
return false;
|
|
694
|
-
|
|
695
|
-
|
|
749
|
+
// 国际站没有 deploy:拼错的 deploy 照样拦下(不当 prompt 发给模型),但不引导去跑它
|
|
750
|
+
const site = activeSite();
|
|
751
|
+
if (near === "deploy" && site.id === "intl") {
|
|
752
|
+
console.error(hostingUnavailableText(site));
|
|
753
|
+
process.exitCode = 1;
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
756
|
+
console.error(tr(`不认识的命令「${cmd}」,你是不是想运行:u1s1 ${near}`, `Unknown command "${cmd}". Did you mean: u1s1 ${near}`));
|
|
757
|
+
console.error(tr("查看全部命令:u1s1 --help;直接开始对话:运行 u1s1(不带参数)", "See all commands: u1s1 --help. To start a session, run u1s1 with no arguments."));
|
|
696
758
|
process.exitCode = 1;
|
|
697
759
|
return true;
|
|
698
760
|
}
|
|
@@ -713,8 +775,9 @@ async function run() {
|
|
|
713
775
|
return;
|
|
714
776
|
}
|
|
715
777
|
// `u1s1 login --help` 不该真的去登录、`logout --help` 不该真的退出:先看是不是在问用法
|
|
716
|
-
|
|
717
|
-
|
|
778
|
+
const simpleHelp = simpleCommandHelp();
|
|
779
|
+
if (cmd && Object.hasOwn(simpleHelp, cmd) && wantsHelp(rest)) {
|
|
780
|
+
for (const line of simpleHelp[cmd])
|
|
718
781
|
console.log(line);
|
|
719
782
|
return;
|
|
720
783
|
}
|
|
@@ -732,6 +795,9 @@ async function run() {
|
|
|
732
795
|
}
|
|
733
796
|
const KNOWN_COMMANDS = ["deploy", "login", "logout", "usage", "whoami", "model", "update", "import", "mcp", "bench", "feedback", "help"];
|
|
734
797
|
/** 没有自己 --help 处理的简单子命令:问用法时只打印说明,不执行任何动作。 */
|
|
798
|
+
function simpleCommandHelp() {
|
|
799
|
+
return isEnglish() ? SIMPLE_COMMAND_HELP_EN : SIMPLE_COMMAND_HELP;
|
|
800
|
+
}
|
|
735
801
|
const SIMPLE_COMMAND_HELP = {
|
|
736
802
|
login: [
|
|
737
803
|
"用法: u1s1 login",
|
|
@@ -756,6 +822,31 @@ const SIMPLE_COMMAND_HELP = {
|
|
|
756
822
|
" 不想每次退出自动更新:在 ~/.u1s1/agent/settings.json 设 \"autoUpdate\": false",
|
|
757
823
|
],
|
|
758
824
|
};
|
|
825
|
+
/** 英文版只在国际站出现:那边没有免费包/用量包,usage 只说余额。 */
|
|
826
|
+
const SIMPLE_COMMAND_HELP_EN = {
|
|
827
|
+
login: [
|
|
828
|
+
"Usage: u1s1 login",
|
|
829
|
+
" Approve this device in your browser to sign in to u1s1; you'll be guided to sign up if you don't have an account.",
|
|
830
|
+
" Sign-in info is stored in ~/.u1s1/config.json. Sign out with u1s1 logout.",
|
|
831
|
+
],
|
|
832
|
+
logout: [
|
|
833
|
+
"Usage: u1s1 logout",
|
|
834
|
+
" Sign out on this machine and revoke this device's authorization on the server (only clears local data if the server is unreachable).",
|
|
835
|
+
],
|
|
836
|
+
usage: [
|
|
837
|
+
"Usage: u1s1 usage",
|
|
838
|
+
" Show your remaining balance. In a session you can also type /usage.",
|
|
839
|
+
],
|
|
840
|
+
whoami: [
|
|
841
|
+
"Usage: u1s1 whoami",
|
|
842
|
+
" Print the signed-in account email and server URL. To switch accounts, run u1s1 logout, then u1s1 login.",
|
|
843
|
+
],
|
|
844
|
+
update: [
|
|
845
|
+
"Usage: u1s1 update",
|
|
846
|
+
" Check for and install the latest version: npm installs update via npm, the portable build updates itself in place.",
|
|
847
|
+
" To stop auto-updating on exit, set \"autoUpdate\": false in ~/.u1s1/agent/settings.json",
|
|
848
|
+
],
|
|
849
|
+
};
|
|
759
850
|
/** 经典 Levenshtein,命令名都很短,O(nm) 足够。 */
|
|
760
851
|
function editDistance(a, b) {
|
|
761
852
|
const dp = Array.from({ length: a.length + 1 }, (_, i) => i);
|
package/dist/login.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fetchMe, readJsonResponseCapped } from "./api.js";
|
|
|
4
4
|
import { printConsoleBanner } from "./brand.js";
|
|
5
5
|
import { loadConfig, loadFileConfig, saveConfig } from "./config.js";
|
|
6
6
|
import { generateDeviceKeyPair, hasDeviceCredential } from "./device-auth.js";
|
|
7
|
+
import { isEnglish, tr } from "./i18n.js";
|
|
7
8
|
import { isSiteUrl, parseSiteArg, siteLinks, siteOf, SITES } from "./site.js";
|
|
8
9
|
const require = createRequire(import.meta.url);
|
|
9
10
|
const VERSION = require("../package.json").version;
|
|
@@ -149,8 +150,9 @@ export function parseLoginArgs(rest) {
|
|
|
149
150
|
if (arg === "--site" || arg.startsWith("--site=")) {
|
|
150
151
|
const raw = arg === "--site" ? rest[++i] : arg.slice("--site=".length);
|
|
151
152
|
const site = parseSiteArg(raw);
|
|
152
|
-
if (!site)
|
|
153
|
-
return { error: `--site 只接受 cn 或 global(收到:${raw ?? "空"})` };
|
|
153
|
+
if (!site) {
|
|
154
|
+
return { error: tr(`--site 只接受 cn 或 global(收到:${raw ?? "空"})`, `--site accepts cn or global (got: ${raw ?? "nothing"})`) };
|
|
155
|
+
}
|
|
154
156
|
out.site = site;
|
|
155
157
|
}
|
|
156
158
|
else if (out.key === undefined) {
|
|
@@ -174,13 +176,16 @@ function applySiteChoice(cfg, site) {
|
|
|
174
176
|
: {};
|
|
175
177
|
if (envBase) {
|
|
176
178
|
saveConfig({ ...cfg, ...cleared, baseUrl: target.apiBase });
|
|
177
|
-
|
|
179
|
+
// 环境变量优先,界面语言仍跟 U1S1_BASE_URL 所在站
|
|
180
|
+
console.log(tr(` 已把 ${target.origin} 写入配置;但环境变量 U1S1_BASE_URL 优先,本次仍连 ${envBase}。`, ` Saved ${target.origin} to your config, but U1S1_BASE_URL takes precedence; still connecting to ${envBase} this time.`));
|
|
178
181
|
return { ...cfg, ...cleared };
|
|
179
182
|
}
|
|
180
183
|
const next = { ...cfg, ...cleared, baseUrl: target.apiBase };
|
|
184
|
+
// saveConfig 之后界面语言已换成目标站的(resetLang),下面起按新站语言说话
|
|
181
185
|
saveConfig(next);
|
|
182
|
-
if (switching)
|
|
183
|
-
console.log(` 已切换到 ${target.origin}(两站账号不通,需要在该站重新登录)
|
|
186
|
+
if (switching) {
|
|
187
|
+
console.log(tr(` 已切换到 ${target.origin}(两站账号不通,需要在该站重新登录)。`, ` Switched to ${target.origin}. Accounts aren't shared between sites, so please sign in here.`));
|
|
188
|
+
}
|
|
184
189
|
return next;
|
|
185
190
|
}
|
|
186
191
|
export async function login(keyArg, opts = {}) {
|
|
@@ -189,40 +194,40 @@ export async function login(keyArg, opts = {}) {
|
|
|
189
194
|
if (opts.site)
|
|
190
195
|
cfg = applySiteChoice(cfg, opts.site);
|
|
191
196
|
if (keyArg?.trim()) {
|
|
192
|
-
console.log(" API Key 不能证明请求来自 u1s1 客户端,将改用浏览器批准本机设备。");
|
|
197
|
+
console.log(tr(" API Key 不能证明请求来自 u1s1 客户端,将改用浏览器批准本机设备。", " An API key can't prove requests come from the u1s1 client; approving this device in the browser instead."));
|
|
193
198
|
}
|
|
194
199
|
printConsoleBanner(VERSION);
|
|
195
200
|
const origin = apiOrigin(cfg);
|
|
196
201
|
const failure = {};
|
|
197
202
|
// 慢网下这一步最长 15 秒,横幅之后一片安静像是卡死;先说一声在连
|
|
198
|
-
console.log(" 正在连接 u1s1 服务器…");
|
|
203
|
+
console.log(tr(" 正在连接 u1s1 服务器…", " Connecting to the u1s1 server…"));
|
|
199
204
|
const start = await startDeviceLogin(origin, failure);
|
|
200
205
|
if (!start) {
|
|
201
206
|
if (failure.message) {
|
|
202
207
|
// 服务器有明确说法(限流/风控等),原样转达,别伪装成网络问题
|
|
203
|
-
console.error(` 服务器没有接受登录请求:${failure.message}`);
|
|
208
|
+
console.error(tr(` 服务器没有接受登录请求:${failure.message}`, ` The server rejected the sign-in request: ${failure.message}`));
|
|
204
209
|
if (failure.status === 429)
|
|
205
|
-
console.error(" 稍等几分钟再运行 u1s1 login 重试即可。");
|
|
210
|
+
console.error(tr(" 稍等几分钟再运行 u1s1 login 重试即可。", " Wait a few minutes, then run u1s1 login again."));
|
|
206
211
|
}
|
|
207
212
|
else {
|
|
208
|
-
console.error(" 连不上 u1s1 服务器,请检查网络后重试。");
|
|
209
|
-
console.error(" 如果网络正常,可能是 u1s1 版本太旧,运行 u1s1 update 升级后再试。");
|
|
213
|
+
console.error(tr(" 连不上 u1s1 服务器,请检查网络后重试。", " Can't reach the u1s1 server. Check your network and try again."));
|
|
214
|
+
console.error(tr(" 如果网络正常,可能是 u1s1 版本太旧,运行 u1s1 update 升级后再试。", " If your network is fine, your u1s1 may be too old; run u1s1 update and try again."));
|
|
210
215
|
}
|
|
211
216
|
process.exit(1);
|
|
212
217
|
}
|
|
213
|
-
console.log(" 用浏览器登录并批准这台设备:");
|
|
218
|
+
console.log(tr(" 用浏览器登录并批准这台设备:", " Sign in with your browser and approve this device:"));
|
|
214
219
|
console.log("");
|
|
215
220
|
console.log(` ${start.verify_url}`);
|
|
216
221
|
console.log("");
|
|
217
|
-
console.log(" 正在尝试打开浏览器;如果没有自动打开,把上面这行网址复制到浏览器打开。");
|
|
218
|
-
console.log(" 批准后,本机会用设备私钥为每次 API 请求签名。");
|
|
222
|
+
console.log(tr(" 正在尝试打开浏览器;如果没有自动打开,把上面这行网址复制到浏览器打开。", " Opening your browser; if it doesn't open, copy the URL above into your browser."));
|
|
223
|
+
console.log(tr(" 批准后,本机会用设备私钥为每次 API 请求签名。", " Once approved, this machine signs every API request with its device key."));
|
|
219
224
|
tryOpenBrowser(start.verify_url);
|
|
220
225
|
console.log("");
|
|
221
|
-
console.log(" 等待浏览器里完成批准……(按 Ctrl+C 取消)");
|
|
226
|
+
console.log(tr(" 等待浏览器里完成批准……(按 Ctrl+C 取消)", " Waiting for approval in the browser… (Ctrl+C to cancel)"));
|
|
222
227
|
const waitStartedAt = Date.now();
|
|
223
228
|
const waitTimer = setInterval(() => {
|
|
224
229
|
const mins = Math.round((Date.now() - waitStartedAt) / 60_000);
|
|
225
|
-
console.log(` 仍在等待批准(已等 ${mins} 分钟)
|
|
230
|
+
console.log(tr(` 仍在等待批准(已等 ${mins} 分钟),在浏览器里点「批准」后这里会自动继续。`, ` Still waiting for approval (${mins} min so far). Click "Approve" in the browser and this will continue automatically.`));
|
|
226
231
|
}, 120_000);
|
|
227
232
|
const outcome = {};
|
|
228
233
|
try {
|
|
@@ -233,11 +238,11 @@ export async function login(keyArg, opts = {}) {
|
|
|
233
238
|
}
|
|
234
239
|
if (!credential) {
|
|
235
240
|
if (outcome.reason === "expired" || outcome.reason === "timeout") {
|
|
236
|
-
console.error(" 一直没等到浏览器里的批准,这个登录链接已失效。");
|
|
237
|
-
console.error(" 重新运行 u1s1 会生成新的登录链接;网页打不开的话检查一下网络。");
|
|
241
|
+
console.error(tr(" 一直没等到浏览器里的批准,这个登录链接已失效。", " No approval arrived from the browser and this sign-in link has expired."));
|
|
242
|
+
console.error(tr(" 重新运行 u1s1 会生成新的登录链接;网页打不开的话检查一下网络。", " Run u1s1 again for a new sign-in link; if the page won't load, check your network."));
|
|
238
243
|
}
|
|
239
244
|
else {
|
|
240
|
-
console.error(" 没能取得设备凭证,请确认网络正常后重试。");
|
|
245
|
+
console.error(tr(" 没能取得设备凭证,请确认网络正常后重试。", " Couldn't get a device credential. Check your network and try again."));
|
|
241
246
|
}
|
|
242
247
|
process.exit(1);
|
|
243
248
|
}
|
|
@@ -245,35 +250,41 @@ export async function login(keyArg, opts = {}) {
|
|
|
245
250
|
// 浏览器批准已经成功,先把凭证落盘;后面的账号信息确认失败也不用重走登录
|
|
246
251
|
saveConfig(next);
|
|
247
252
|
const me = await fetchMe(next).catch((e) => {
|
|
248
|
-
console.log(" ✓ 设备已批准,登录凭证已保存。");
|
|
249
|
-
console.error(` 只是暂时没连上服务器确认账号信息(${e.message})
|
|
250
|
-
console.error(" 稍后直接运行 u1s1 即可,不需要重新登录。");
|
|
253
|
+
console.log(tr(" ✓ 设备已批准,登录凭证已保存。", " ✓ Device approved and credentials saved."));
|
|
254
|
+
console.error(tr(` 只是暂时没连上服务器确认账号信息(${e.message})。`, ` Couldn't confirm your account details just now (${e.message}).`));
|
|
255
|
+
console.error(tr(" 稍后直接运行 u1s1 即可,不需要重新登录。", " Just run u1s1 later; no need to sign in again."));
|
|
251
256
|
process.exit(0);
|
|
252
257
|
});
|
|
253
258
|
const tpu = me.tokens_per_usd ?? 0;
|
|
254
259
|
let quotaNote;
|
|
255
|
-
if (
|
|
260
|
+
if (siteOf(next).id === "intl") {
|
|
261
|
+
// 国际站没有免费包/每日免费额度,只报按量余额(USD)
|
|
262
|
+
quotaNote = tr(`余额 $${me.bonus_balance_usd.toFixed(2)}`, `balance $${me.bonus_balance_usd.toFixed(2)}`);
|
|
263
|
+
}
|
|
264
|
+
else if (me.free_claim) {
|
|
256
265
|
// 新网关且免费包可领/可续,登录成功时顺手提醒
|
|
257
266
|
const dashboard = siteLinks(siteOf(next)).dashboard;
|
|
258
267
|
quotaNote =
|
|
259
268
|
me.free_claim === "first"
|
|
260
|
-
? `有免费用量包可领,去 ${dashboard}
|
|
261
|
-
: `免费用量包状态有更新,去 ${dashboard}
|
|
269
|
+
? tr(`有免费用量包可领,去 ${dashboard} 查看`, `a free usage pack is available — see ${dashboard}`)
|
|
270
|
+
: tr(`免费用量包状态有更新,去 ${dashboard} 查看`, `your free usage pack status changed — see ${dashboard}`);
|
|
262
271
|
}
|
|
263
272
|
else if (tpu > 0) {
|
|
264
273
|
const tok = (usd) => {
|
|
265
274
|
const t = usd * tpu;
|
|
275
|
+
if (isEnglish())
|
|
276
|
+
return t >= 1e6 ? `${Math.round((t / 1e6) * 10) / 10}M` : `${Math.round(t / 1e3)}K`;
|
|
266
277
|
if (t >= 1e8)
|
|
267
278
|
return `${Math.round((t / 1e8) * 10) / 10} 亿`;
|
|
268
279
|
return `${Math.round(t / 1e4).toLocaleString("en-US")} 万`;
|
|
269
280
|
};
|
|
270
|
-
quotaNote = `今日免费还剩约 ${tok(me.daily_free_remaining_usd)} Token
|
|
281
|
+
quotaNote = tr(`今日免费还剩约 ${tok(me.daily_free_remaining_usd)} Token`, `about ${tok(me.daily_free_remaining_usd)} free tokens left today`);
|
|
271
282
|
}
|
|
272
283
|
else {
|
|
273
284
|
// 老网关:没有 tokens_per_usd,退回金额显示
|
|
274
|
-
quotaNote = `今日免费还剩 $${me.daily_free_remaining_usd.toFixed(2)},永久余额 $${me.remaining_usd.toFixed(2)}
|
|
285
|
+
quotaNote = tr(`今日免费还剩 $${me.daily_free_remaining_usd.toFixed(2)},永久余额 $${me.remaining_usd.toFixed(2)}`, `$${me.daily_free_remaining_usd.toFixed(2)} free left today, balance $${me.remaining_usd.toFixed(2)}`);
|
|
275
286
|
}
|
|
276
|
-
console.log(` ✓ 登录成功${me.email ? `(${me.email})` : ""},${quotaNote}
|
|
287
|
+
console.log(tr(` ✓ 登录成功${me.email ? `(${me.email})` : ""},${quotaNote}。`, ` ✓ Signed in${me.email ? ` as ${me.email}` : ""}; ${quotaNote}.`));
|
|
277
288
|
// 邮箱记进 config,启动横幅直接显示「账号」,不用再问「我现在登的是哪个号」
|
|
278
289
|
const withAccount = { ...next, email: me.email ?? undefined };
|
|
279
290
|
saveConfig(withAccount);
|