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.
Files changed (52) hide show
  1. package/README.md +5 -5
  2. package/dist/agent-setup.d.ts +7 -1
  3. package/dist/agent-setup.js +34 -10
  4. package/dist/api.js +42 -34
  5. package/dist/bench-report.js +12 -9
  6. package/dist/bench.js +34 -31
  7. package/dist/brand.d.ts +7 -2
  8. package/dist/brand.js +44 -13
  9. package/dist/config.d.ts +28 -2
  10. package/dist/config.js +52 -14
  11. package/dist/context-usage.js +12 -9
  12. package/dist/deploy.js +53 -47
  13. package/dist/device-auth.js +7 -5
  14. package/dist/embed.d.ts +2 -1
  15. package/dist/embed.js +2 -1
  16. package/dist/error-humanize.d.ts +3 -1
  17. package/dist/error-humanize.js +24 -9
  18. package/dist/feedback.d.ts +4 -2
  19. package/dist/feedback.js +60 -39
  20. package/dist/i18n.d.ts +21 -0
  21. package/dist/i18n.js +46 -0
  22. package/dist/import/index.js +64 -28
  23. package/dist/import/skills.js +72 -41
  24. package/dist/import/util.js +3 -2
  25. package/dist/import/write.js +2 -1
  26. package/dist/index.js +153 -54
  27. package/dist/login.d.ts +13 -2
  28. package/dist/login.js +84 -27
  29. package/dist/loop.js +18 -12
  30. package/dist/mcp/client.js +15 -14
  31. package/dist/mcp/command.js +76 -48
  32. package/dist/mcp/extension.js +5 -3
  33. package/dist/mcp/tools.js +6 -5
  34. package/dist/model.js +12 -10
  35. package/dist/nudges.d.ts +8 -2
  36. package/dist/nudges.js +22 -3
  37. package/dist/search-tools.js +7 -6
  38. package/dist/shell-doctor.js +9 -8
  39. package/dist/site.d.ts +71 -0
  40. package/dist/site.js +123 -0
  41. package/dist/subagent.js +5 -4
  42. package/dist/tools.js +53 -37
  43. package/dist/update.js +47 -39
  44. package/dist/usage.d.ts +6 -4
  45. package/dist/usage.js +111 -53
  46. package/dist/web.js +2 -1
  47. package/dist/workflow/runner.js +33 -29
  48. package/dist/workflow/templates.js +70 -45
  49. package/dist/workflow/tool.js +15 -12
  50. package/package.json +3 -3
  51. package/scripts/patch-pi.js +243 -278
  52. package/scripts/render-regression.mjs +55 -4
package/dist/update.js CHANGED
@@ -5,7 +5,9 @@ import { createRequire } from "node:module";
5
5
  import { tmpdir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
- import { isPortableInstall } from "./config.js";
8
+ import { activeSite, isPortableInstall } from "./config.js";
9
+ import { tr } from "./i18n.js";
10
+ import { installCommand } from "./site.js";
9
11
  import { readJsonResponseCapped, readResponseTextCapped } from "./api.js";
10
12
  const require = createRequire(import.meta.url);
11
13
  const pkg = require("../package.json");
@@ -65,8 +67,13 @@ export function compareVersions(a, b) {
65
67
  }
66
68
  return 0;
67
69
  }
68
- /** 发布件只认这一个 https 源;任何跳转(哪怕同域)都视为异常,不跟。 */
69
- const RELEASE_ORIGIN = "https://u1s1.io";
70
+ /**
71
+ * 发布件只认当前站点的 https 发布源(跟随 baseUrl 所在站,见 site.ts);任何跳转
72
+ * (哪怕同域)都视为异常,不跟。老版本写死 u1s1.io/releases,所以中国站该路径必须 200 直出。
73
+ */
74
+ function releaseOrigin() {
75
+ return activeSite().releaseOrigin;
76
+ }
70
77
  const INSTALL_SCRIPT_NAME = "install.sh";
71
78
  const MAX_SUMS_BYTES = 64 * 1024;
72
79
  /** 生成 Windows 更新启动器:优先使用 PowerShell 7,没有时回退 Windows PowerShell。 */
@@ -75,16 +82,16 @@ export function buildWindowsUpdateLauncherScript() {
75
82
  "$inner = '-NoProfile -ExecutionPolicy Bypass -NoExit -File \"' + $PSScriptRoot + '\\update.ps1\"'",
76
83
  "$ps = (Get-Command pwsh -ErrorAction SilentlyContinue).Source",
77
84
  "if (-not $ps) { $ps = (Get-Command powershell -ErrorAction SilentlyContinue).Source }",
78
- "if (-not $ps) { throw '找不到可用的 PowerShell' }",
85
+ tr("if (-not $ps) { throw '找不到可用的 PowerShell' }", "if (-not $ps) { throw 'No usable PowerShell found' }"),
79
86
  "Start-Process -FilePath $ps -ArgumentList $inner",
80
87
  "",
81
88
  ].join("\n");
82
89
  }
83
90
  /** 从固定发布源拉一个文本文件:强制 https、拒绝重定向、限长。 */
84
91
  export async function fetchReleaseText(name, maxBytes, timeoutMs = 30_000) {
85
- const url = new URL(`/releases/${name}`, RELEASE_ORIGIN);
92
+ const url = new URL(`/releases/${name}`, releaseOrigin());
86
93
  if (url.protocol !== "https:")
87
- throw new Error(`拒绝非 https 发布源: ${url.href}`);
94
+ throw new Error(tr(`拒绝非 https 发布源: ${url.href}`, `Refusing non-https release source: ${url.href}`));
88
95
  const res = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(timeoutMs) });
89
96
  if (!res.ok)
90
97
  throw new Error(`${name}: HTTP ${res.status}`);
@@ -119,7 +126,7 @@ export async function downloadInstallScript() {
119
126
  ]);
120
127
  const check = verifyInstallScript(script, sums);
121
128
  if (check === "mismatch")
122
- throw new Error("安装脚本与发布清单的 SHA256 不符,已拒绝执行");
129
+ throw new Error(tr("安装脚本与发布清单的 SHA256 不符,已拒绝执行", "Install script SHA256 doesn't match the release manifest; refusing to run it"));
123
130
  return { script, verified: check === "verified" };
124
131
  }
125
132
  /**
@@ -132,10 +139,11 @@ export async function downloadInstallScript() {
132
139
  * 包括本进程,属预期),本进程立刻退出把文件锁让出来。
133
140
  */
134
141
  async function portableSelfUpdate(latest) {
142
+ const site = activeSite();
135
143
  if (process.platform === "win32") {
136
- console.log(`正在打开更新窗口(升级到 v${latest})…`);
137
- console.log("更新在新窗口里进行,本窗口会退出;装完后重新打开终端运行 u1s1 即可。");
138
- console.log("若新窗口没有出现,可手动在 PowerShell 运行: irm https://u1s1.io/releases/install.ps1 | iex");
144
+ console.log(tr(`正在打开更新窗口(升级到 v${latest})…`, `Opening the update window (updating to v${latest})…`));
145
+ console.log(tr("更新在新窗口里进行,本窗口会退出;装完后重新打开终端运行 u1s1 即可。", "The update runs in a new window and this one will exit. When it's done, open a new terminal and run u1s1."));
146
+ console.log(tr(`若新窗口没有出现,可手动在 PowerShell 运行: ${installCommand(site, "ps1")}`, `If no window appears, run this in PowerShell: ${installCommand(site, "ps1")}`));
139
147
  // 不走 `cmd /c start`:start 只把**带引号**的首参当窗口标题,而 node 对
140
148
  // 无空格参数不加引号,标题会被当成命令执行(0.13.1 翻车现场)。也不走
141
149
  // spawn detached:Windows 上 detached 是 DETACHED_PROCESS——子进程**没有**
@@ -145,30 +153,30 @@ async function portableSelfUpdate(latest) {
145
153
  // 进程完全独立,不随本进程退出)。路径经 $PSScriptRoot 传递,不往命令行
146
154
  // 拼字符串,没有引号二次解析问题。
147
155
  const dir = mkdtempSync(join(tmpdir(), "u1s1-update-"));
148
- writeFileSync(join(dir, "update.ps1"), "irm https://u1s1.io/releases/install.ps1 | iex\n");
156
+ writeFileSync(join(dir, "update.ps1"), `${installCommand(site, "ps1")}\n`);
149
157
  writeFileSync(join(dir, "launch.ps1"), buildWindowsUpdateLauncherScript());
150
158
  const res = spawnSync("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(dir, "launch.ps1")], { stdio: "ignore", windowsHide: true, timeout: 30_000 });
151
159
  if (res.error || res.status !== 0) {
152
- console.error("无法拉起更新窗口。请手动在 PowerShell 里运行:");
153
- console.error(" irm https://u1s1.io/releases/install.ps1 | iex");
160
+ console.error(tr("无法拉起更新窗口。请手动在 PowerShell 里运行:", "Couldn't open the update window. Run this in PowerShell:"));
161
+ console.error(` ${installCommand(site, "ps1")}`);
154
162
  process.exit(1);
155
163
  }
156
164
  // 立刻退出释放 node.exe 的文件锁,让安装器能替换安装目录
157
165
  process.exit(0);
158
166
  }
159
- console.log(`正在下载安装脚本并升级到 v${latest}…`);
160
- console.log("(可能需要几分钟,期间请不要关闭窗口)");
167
+ console.log(tr(`正在下载安装脚本并升级到 v${latest}…`, `Downloading the install script and updating to v${latest}…`));
168
+ console.log(tr("(可能需要几分钟,期间请不要关闭窗口)", "(This may take a few minutes; please keep this window open)"));
161
169
  let script;
162
170
  try {
163
171
  const download = await downloadInstallScript();
164
172
  script = download.script;
165
173
  if (!download.verified) {
166
- console.log("⚠ 发布清单里没有安装脚本的校验和(旧版发布),跳过脚本校验;安装包本身仍会校验。");
174
+ console.log(tr("⚠ 发布清单里没有安装脚本的校验和(旧版发布),跳过脚本校验;安装包本身仍会校验。", "⚠ The release manifest has no checksum for the install script (older release); skipping that check. The package itself is still verified."));
167
175
  }
168
176
  }
169
177
  catch (e) {
170
- console.error(`安装脚本下载失败:${e.message}。请手动运行:`);
171
- console.error(" curl -fsSL https://u1s1.io/releases/install.sh | bash");
178
+ console.error(tr(`安装脚本下载失败:${e.message}。请手动运行:`, `Couldn't download the install script: ${e.message}. Run this manually:`));
179
+ console.error(` ${installCommand(site, "sh")}`);
172
180
  process.exit(1);
173
181
  }
174
182
  const tmp = join(mkdtempSync(join(tmpdir(), "u1s1-update-")), "install.sh");
@@ -177,8 +185,8 @@ async function portableSelfUpdate(latest) {
177
185
  execFileSync("bash", [tmp], { stdio: "inherit" });
178
186
  }
179
187
  catch {
180
- // 安装脚本已经打印了失败原因与重试提示(多半是到 u1s1.io 的链路慢),这里不再重复一整段
181
- console.error("\n自动升级没有完成,稍后重试 u1s1 update 即可;已安装版本不受影响。");
188
+ // 安装脚本已经打印了失败原因与重试提示(多半是到官网的链路慢),这里不再重复一整段
189
+ console.error(tr("\n自动升级没有完成,稍后重试 u1s1 update 即可;已安装版本不受影响。", "\nThe update didn't finish. Try u1s1 update again later; your installed version is unaffected."));
182
190
  process.exit(1);
183
191
  }
184
192
  // 脚本退出码 0 ≠ 升级成功:安装脚本认的是官网 LATEST 版本源,它若认为
@@ -193,27 +201,27 @@ async function portableSelfUpdate(latest) {
193
201
  // 老版布局没这文件,确认不了就不下结论
194
202
  }
195
203
  if (installedVer === latest) {
196
- console.log(`\n✅ 已升级到 v${latest},重新运行 u1s1 生效。`);
204
+ console.log(tr(`\n✅ 已升级到 v${latest},重新运行 u1s1 生效。`, `\n✅ Updated to v${latest}. Restart u1s1 to use it.`));
197
205
  }
198
206
  else {
199
- console.error(`\n⚠ 安装脚本跑完了,但没装上 v${latest}(官网版本源可能滞后,当前仍是 v${installedVer ?? VERSION})。`);
200
- console.error(" 请稍后重试 u1s1 update,或手动运行:");
201
- console.error(" curl -fsSL https://u1s1.io/releases/install.sh | bash");
207
+ console.error(tr(`\n⚠ 安装脚本跑完了,但没装上 v${latest}(官网版本源可能滞后,当前仍是 v${installedVer ?? VERSION})。`, `\n⚠ The install script finished but v${latest} wasn't installed (the release feed may lag; still on v${installedVer ?? VERSION}).`));
208
+ console.error(tr(" 请稍后重试 u1s1 update,或手动运行:", " Try u1s1 update again later, or run:"));
209
+ console.error(` ${installCommand(site, "sh")}`);
202
210
  process.exit(1);
203
211
  }
204
212
  }
205
213
  export async function update() {
206
- console.log("正在检查更新…");
214
+ console.log(tr("正在检查更新…", "Checking for updates…"));
207
215
  const latest = await getLatestVersion();
208
216
  if (!latest) {
209
- console.error("无法获取最新版本。请检查网络连接后重试。");
217
+ console.error(tr("无法获取最新版本。请检查网络连接后重试。", "Couldn't get the latest version. Check your connection and try again."));
210
218
  process.exit(1);
211
219
  }
212
220
  if (compareVersions(latest, VERSION) <= 0) {
213
- console.log(`当前 v${VERSION} 已是最新版本 ✓`);
221
+ console.log(tr(`当前 v${VERSION} 已是最新版本 ✓`, `v${VERSION} is the latest version ✓`));
214
222
  return;
215
223
  }
216
- console.log(`发现新版本 v${latest} (当前 v${VERSION})`);
224
+ console.log(tr(`发现新版本 v${latest} (当前 v${VERSION})`, `New version v${latest} available (current v${VERSION})`));
217
225
  // 便携版:npm 装到全局目录,但 PATH 先命中便携目录,怎么更都还是旧版。
218
226
  // 正确方式是重跑官网安装脚本整包替换 —— 这里直接替用户跑,不再让人手抄命令。
219
227
  if (isPortableInstall()) {
@@ -226,32 +234,32 @@ export async function update() {
226
234
  const required = requiredNodeVersion();
227
235
  if (required && compareVersions(process.versions.node, required) < 0) {
228
236
  const cmd = process.platform === "win32"
229
- ? "irm https://u1s1.io/releases/install.ps1 | iex (在 PowerShell 里运行)"
230
- : "curl -fsSL https://u1s1.io/releases/install.sh | bash";
237
+ ? tr(`${installCommand(activeSite(), "ps1")} (在 PowerShell 里运行)`, `${installCommand(activeSite(), "ps1")} (run in PowerShell)`)
238
+ : installCommand(activeSite(), "sh");
231
239
  console.log("");
232
- console.log(`⚠ 你电脑的 Node.js 是 v${process.versions.node},新版 u1s1 建议 v${required} 或更新。`);
233
- console.log(" 推荐用官网安装命令重装,自带合适的 Node,一步到位:");
240
+ console.log(tr(`⚠ 你电脑的 Node.js 是 v${process.versions.node},新版 u1s1 建议 v${required} 或更新。`, `⚠ Your Node.js is v${process.versions.node}; the new u1s1 recommends v${required} or later.`));
241
+ console.log(tr(" 推荐用官网安装命令重装,自带合适的 Node,一步到位:", " We recommend reinstalling with the official installer, which bundles a suitable Node:"));
234
242
  console.log(` ${cmd}`);
235
- console.log(" 下面仍会继续用 npm 更新;更新后如果运行异常,再用上面的命令重装即可。");
243
+ console.log(tr(" 下面仍会继续用 npm 更新;更新后如果运行异常,再用上面的命令重装即可。", " Continuing with the npm update anyway; if u1s1 misbehaves afterwards, reinstall with the command above."));
236
244
  console.log("");
237
245
  }
238
- console.log(`正在用 ${pm} 更新 ${PACKAGE_NAME}…`);
246
+ console.log(tr(`正在用 ${pm} 更新 ${PACKAGE_NAME}…`, `Updating ${PACKAGE_NAME} with ${pm}…`));
239
247
  try {
240
248
  // npm 压掉 EBADENGINE 等警告墙,对新手只有噪音;出错时 error 仍会显示
241
249
  const args = pm === "npm"
242
250
  ? ["install", "-g", "--loglevel=error", `${PACKAGE_NAME}@latest`]
243
251
  : ["add", "-g", `${PACKAGE_NAME}@latest`];
244
252
  execFileSync(pm, args, { stdio: "inherit" });
245
- console.log(`\n✅ 已更新到 v${latest},重启 u1s1 后生效。`);
253
+ console.log(tr(`\n✅ 已更新到 v${latest},重启 u1s1 后生效。`, `\n✅ Updated to v${latest}. Restart u1s1 to use it.`));
246
254
  }
247
255
  catch {
248
256
  // 再让用户手抄同一条命令多半还是失败(最常见是全局目录没写权限),
249
257
  // 直接给能跳出循环的路:官网安装脚本(装进用户目录,不需要管理员权限)
250
- console.error("\n更新失败(上面是包管理器的报错,常见原因是全局目录没有写权限)。");
251
- console.error("推荐改用官网安装脚本重装,不需要管理员权限,一步到位:");
258
+ console.error(tr("\n更新失败(上面是包管理器的报错,常见原因是全局目录没有写权限)。", "\nUpdate failed (see the package manager error above; usually no write access to the global directory)."));
259
+ console.error(tr("推荐改用官网安装脚本重装,不需要管理员权限,一步到位:", "We recommend reinstalling with the official installer instead; no admin rights needed:"));
252
260
  console.error(process.platform === "win32"
253
- ? " irm https://u1s1.io/releases/install.ps1 | iex (在 PowerShell 里运行)"
254
- : " curl -fsSL https://u1s1.io/releases/install.sh | bash");
261
+ ? tr(` ${installCommand(activeSite(), "ps1")} (在 PowerShell 里运行)`, ` ${installCommand(activeSite(), "ps1")} (run in PowerShell)`)
262
+ : ` ${installCommand(activeSite(), "sh")}`);
255
263
  process.exit(1);
256
264
  }
257
265
  }
package/dist/usage.d.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { type MeResponse } from "./api.js";
2
- export declare const TOPUP_URL = "https://u1s1.io/dashboard#usage-topup-card";
2
+ import { type Site } from "./site.js";
3
+ /** 当前站点的充值深链(仪表盘充值卡片)。 */
4
+ export declare function topupUrl(site?: Site): string;
3
5
  /**
4
6
  * 报告尾巴的两条行动入口:打卡/邀请领包与充值(撞线用户的直接出口,运营清单 A2)。
5
7
  * 08-25 起没有「每天自动恢复」的免费池,邀请包也和打卡包一样有有效期,别再写「永久加量」。
6
8
  * 支付通道关闭时充值是死路(仪表盘会报「支付通道未开放」),改说即将上线并指向
7
9
  * 仪表盘的加量包/打卡入口;老网关没有 pay_enabled 字段,按开放处理。
8
10
  */
9
- export declare function usageCtaLines(me?: Pick<MeResponse, "pay_enabled">): string[];
11
+ export declare function usageCtaLines(me?: Pick<MeResponse, "pay_enabled">, site?: Site): string[];
10
12
  /** 额度报告正文(不含首尾空行),终端 `u1s1 usage` 和会话内 /usage 共用。 */
11
- export declare function usageReportLines(me: MeResponse): string[];
12
- /** 会话内 /usage 用:拉取额度并打包成 entry data,错误转成中文提示而不是抛出。 */
13
+ export declare function usageReportLines(me: MeResponse, site?: Site): string[];
14
+ /** 会话内 /usage 用:拉取额度并打包成 entry data,错误转成提示文字而不是抛出。 */
13
15
  export declare function usageEntryData(): Promise<{
14
16
  lines?: string[];
15
17
  error?: string;
package/dist/usage.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { fetchMe } from "./api.js";
2
- import { loadConfig } from "./config.js";
2
+ import { activeSite, loadConfig } from "./config.js";
3
3
  import { hasDeviceCredential } from "./device-auth.js";
4
+ import { isEnglish, tr } from "./i18n.js";
5
+ import { siteLinks, siteOf } from "./site.js";
4
6
  function bar(ratio, width = 24) {
5
7
  const filled = Math.round(Math.max(0, Math.min(1, ratio)) * width);
6
8
  return "█".repeat(filled) + "░".repeat(width - filled);
@@ -20,17 +22,35 @@ function fmtTokensCn(tokens) {
20
22
  return sig2(t / 1e4).toLocaleString("en-US") + " 万";
21
23
  return sig2(t).toLocaleString("en-US");
22
24
  }
23
- const PACKAGE_LABELS = {
24
- free_first: "首月免费包",
25
- free_yearly: "年度免费包",
26
- invite: "邀请赠送",
27
- new_user: "新用户赠送",
28
- login_checkin: "登录打卡",
29
- login_checkin_bonus: "打卡加成",
30
- payment_delay_gift: "临时加量包",
31
- topup_daily: "每日加量包",
32
- admin_grant: "官方赠送",
33
- };
25
+ // 英文界面用 K/M/B,同样 2 位有效数字
26
+ function fmtTokensEn(tokens) {
27
+ const t = Number(tokens);
28
+ if (!Number.isFinite(t) || t <= 0)
29
+ return "0";
30
+ const sig2 = (n) => Number(n.toPrecision(2));
31
+ if (t >= 1e9)
32
+ return `${sig2(t / 1e9)}B`;
33
+ if (t >= 1e6)
34
+ return `${sig2(t / 1e6)}M`;
35
+ if (t >= 1e3)
36
+ return `${sig2(t / 1e3)}K`;
37
+ return String(Math.round(t));
38
+ }
39
+ function fmtTokens(tokens) {
40
+ return isEnglish() ? fmtTokensEn(tokens) : fmtTokensCn(tokens);
41
+ }
42
+ // 用时求值:模块加载时还不知道站点/语言
43
+ const packageLabels = () => ({
44
+ free_first: tr("首月免费包", "First-month free pack"),
45
+ free_yearly: tr("年度免费包", "Yearly free pack"),
46
+ invite: tr("邀请赠送", "Invite reward"),
47
+ new_user: tr("新用户赠送", "New-user gift"),
48
+ login_checkin: tr("登录打卡", "Check-in"),
49
+ login_checkin_bonus: tr("打卡加成", "Check-in bonus"),
50
+ payment_delay_gift: tr("临时加量包", "Temporary pack"),
51
+ topup_daily: tr("每日加量包", "Daily pack"),
52
+ admin_grant: tr("官方赠送", "Granted by u1s1"),
53
+ });
34
54
  function sumOptional(a, b) {
35
55
  return a == null && b == null ? null : (a ?? 0) + (b ?? 0);
36
56
  }
@@ -52,108 +72,146 @@ function groupPackages(packages) {
52
72
  }
53
73
  function packageScopeNote(pkg) {
54
74
  if (pkg.kind === "login_checkin") {
55
- return "仅限 u1s1 客户端使用 · 全模型可用";
75
+ return tr("仅限 u1s1 客户端使用 · 全模型可用", "u1s1 clients only · all models");
56
76
  }
57
77
  if (pkg.scope === "free") {
58
78
  // 只有按日发放的免费包(首月/年度包的每日额度)才 0 点恢复;一次性免费包用完就没了。
59
- return pkg.daily_tokens != null ? "免费包适用模型 · 每日额度北京时间 0 点恢复" : "免费包适用模型";
79
+ return pkg.daily_tokens != null
80
+ ? tr("免费包适用模型 · 每日额度北京时间 0 点恢复", "Free-tier models · daily quota resets at 00:00 Beijing time (UTC+8)")
81
+ : tr("免费包适用模型", "Free-tier models");
60
82
  }
61
- return "仅限 u1s1 客户端使用 · 免费包适用模型";
83
+ return tr("仅限 u1s1 客户端使用 · 免费包适用模型", "u1s1 clients only · free-tier models");
62
84
  }
63
85
  function packageLabel(pkg) {
64
86
  const count = pkg.count > 1 ? ` ×${pkg.count}` : "";
65
- return `${PACKAGE_LABELS[pkg.kind] ?? pkg.kind}${count}`.padEnd(5, " ");
87
+ const label = `${packageLabels()[pkg.kind] ?? pkg.kind}${count}`;
88
+ // 中文靠全角空格对齐到第 13 列;英文标签长短不一,直接跟两个空格
89
+ return isEnglish() ? `${label} ` : label.padEnd(5, " ");
66
90
  }
67
91
  function packageLines(pkg) {
68
92
  const isDaily = pkg.daily_tokens != null;
69
93
  const total = isDaily ? (pkg.daily_tokens ?? 0) : (pkg.total_tokens ?? 0);
70
94
  const ratio = total > 0 ? pkg.remaining / total : 0;
71
- const expiry = pkg.expires_at ? `${pkg.expires_at.slice(0, 10)} 到期` : "永不过期";
95
+ const expiry = pkg.expires_at
96
+ ? tr(`${pkg.expires_at.slice(0, 10)} 到期`, `expires ${pkg.expires_at.slice(0, 10)}`)
97
+ : tr("永不过期", "never expires");
72
98
  return [
73
- ` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)}`,
99
+ tr(` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)}`, ` ${packageLabel(pkg)} ${fmtTokensEn(pkg.remaining)} / ${fmtTokensEn(total)}${isDaily ? "/day" : ""} left ${bar(ratio)}`),
74
100
  ` ${packageScopeNote(pkg)} · ${expiry}`,
75
101
  ];
76
102
  }
77
- function packageUsageLines(me, tokensPerUsd) {
103
+ function packageUsageLines(me, tokensPerUsd, site) {
78
104
  const packages = me.packages ?? [];
79
105
  const lines = [];
80
106
  if (packages.length === 0)
81
- lines.push(" 用量包 (无生效中的用量包)");
107
+ lines.push(tr(" 用量包 (无生效中的用量包)", " Packs (no active usage packs)"));
82
108
  for (const pkg of groupPackages(packages))
83
109
  lines.push(...packageLines(pkg));
84
110
  if (me.bonus_balance_usd > 0) {
85
111
  const balance = tokensPerUsd > 0
86
- ? `${fmtTokensCn(me.bonus_balance_usd * tokensPerUsd)} Token`
112
+ ? `${fmtTokens(me.bonus_balance_usd * tokensPerUsd)} Token`
87
113
  : `$${me.bonus_balance_usd.toFixed(2)}`;
88
- lines.push(` 余额(按量) ${balance}`);
114
+ lines.push(tr(` 余额(按量) ${balance}`, ` Balance ${balance}`));
89
115
  }
90
116
  const monthlyUsage = tokensPerUsd > 0
91
- ? `约 ${fmtTokensCn(me.mtd_usd * tokensPerUsd)} Token($${me.mtd_usd.toFixed(2)})`
117
+ ? tr(`约 ${fmtTokensCn(me.mtd_usd * tokensPerUsd)} Token($${me.mtd_usd.toFixed(2)})`, `~${fmtTokensEn(me.mtd_usd * tokensPerUsd)} tokens ($${me.mtd_usd.toFixed(2)})`)
92
118
  : `$${me.mtd_usd.toFixed(2)}`;
93
- lines.push(` 本月已用 ${monthlyUsage}`);
119
+ lines.push(tr(` 本月已用 ${monthlyUsage}`, ` This month ${monthlyUsage}`));
94
120
  lines.push("");
95
- lines.push(" 免费用量包覆盖 DeepSeek Flash、GLM-5.3 Flash、Qwen3.8 Flash 和联网搜索。");
96
- lines.push(" 其他模型不走免费包,可用余额或全模型包 → https://u1s1.io/dashboard");
97
- lines.push(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
121
+ lines.push(tr(" 免费用量包覆盖 DeepSeek Flash、GLM-5.3 Flash、Qwen3.8 Flash 和联网搜索。", " Free packs cover DeepSeek Flash, GLM-5.3 Flash, Qwen3.8 Flash and web search."));
122
+ lines.push(tr(` 其他模型不走免费包,可用余额或全模型包 → ${siteLinks(site).dashboard}`, ` Other models use your balance or an all-model pack → ${siteLinks(site).dashboard}`));
123
+ lines.push(tr(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。", " Token counts are estimates at the default model's price; pricier models use them up faster."));
98
124
  lines.push("");
99
- lines.push(...usageCtaLines(me));
125
+ lines.push(...usageCtaLines(me, site));
100
126
  return lines;
101
127
  }
102
- function legacyUsageLines(me, tokensPerUsd) {
128
+ function legacyUsageLines(me, tokensPerUsd, site) {
103
129
  const freeRemain = me.daily_free_remaining_usd;
104
130
  const freeTotal = me.daily_free_usd;
105
131
  const freeRatio = freeTotal > 0 ? freeRemain / freeTotal : 0;
106
132
  const lines = [];
107
133
  if (tokensPerUsd > 0) {
108
- const tokens = (usd) => `${fmtTokensCn(usd * tokensPerUsd)} Token`;
109
- lines.push(` 今日免费 还剩 ${fmtTokensCn(freeRemain * tokensPerUsd)} / ${tokens(freeTotal)} ${bar(freeRatio)}`);
110
- lines.push(" DeepSeek Flash · 北京时间 0 点恢复");
111
- lines.push(` 永久余额 ${tokens(me.remaining_usd)}`);
112
- lines.push(` 本月已用 约 ${tokens(me.mtd_usd)}($${me.mtd_usd.toFixed(2)})`);
134
+ const tokens = (usd) => `${fmtTokens(usd * tokensPerUsd)} Token`;
135
+ lines.push(tr(` 今日免费 还剩 ${fmtTokensCn(freeRemain * tokensPerUsd)} / ${tokens(freeTotal)} ${bar(freeRatio)}`, ` Free today ${fmtTokensEn(freeRemain * tokensPerUsd)} / ${tokens(freeTotal)} left ${bar(freeRatio)}`));
136
+ lines.push(tr(" DeepSeek Flash · 北京时间 0 点恢复", " DeepSeek Flash · resets at 00:00 Beijing time (UTC+8)"));
137
+ lines.push(tr(` 永久余额 ${tokens(me.remaining_usd)}`, ` Balance ${tokens(me.remaining_usd)}`));
138
+ lines.push(tr(` 本月已用 约 ${tokens(me.mtd_usd)}($${me.mtd_usd.toFixed(2)})`, ` This month ~${tokens(me.mtd_usd)} ($${me.mtd_usd.toFixed(2)})`));
113
139
  lines.push("");
114
- lines.push(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。");
140
+ lines.push(tr(" Token 数按默认模型单价折算,为约数;用更贵的模型时消耗更快。", " Token counts are estimates at the default model's price; pricier models use them up faster."));
115
141
  }
116
142
  else {
117
- lines.push(` 今日免费 $${freeRemain.toFixed(2)} / $${freeTotal.toFixed(2)} ${bar(freeRatio)}`);
118
- lines.push(" DeepSeek Flash · 北京时间 0 点恢复");
119
- lines.push(` 永久余额 $${me.remaining_usd.toFixed(2)}`);
120
- lines.push(` 本月成本 $${me.mtd_usd.toFixed(2)}`);
143
+ lines.push(tr(` 今日免费 $${freeRemain.toFixed(2)} / $${freeTotal.toFixed(2)} ${bar(freeRatio)}`, ` Free today $${freeRemain.toFixed(2)} / $${freeTotal.toFixed(2)} ${bar(freeRatio)}`));
144
+ lines.push(tr(" DeepSeek Flash · 北京时间 0 点恢复", " DeepSeek Flash · resets at 00:00 Beijing time (UTC+8)"));
145
+ lines.push(tr(` 永久余额 $${me.remaining_usd.toFixed(2)}`, ` Balance $${me.remaining_usd.toFixed(2)}`));
146
+ lines.push(tr(` 本月成本 $${me.mtd_usd.toFixed(2)}`, ` This month $${me.mtd_usd.toFixed(2)}`));
121
147
  }
122
148
  lines.push("");
123
- lines.push(...usageCtaLines(me));
149
+ lines.push(...usageCtaLines(me, site));
124
150
  return lines;
125
151
  }
126
- export const TOPUP_URL = "https://u1s1.io/dashboard#usage-topup-card";
152
+ /**
153
+ * 国际站额度报告:没有免费包/每日免费额度/打卡/邀请(网关 withSiteGifts 全部置零),
154
+ * 只给按量余额(USD)、本月用量和充值入口。万一有运营手工发的包,照列但不写
155
+ * 「免费包适用模型」之类中国站口径的说明。国际站网关都是新版,不走 legacy 分支。
156
+ */
157
+ function intlUsageLines(me, site) {
158
+ const lines = [];
159
+ for (const pkg of groupPackages(me.packages ?? [])) {
160
+ const isDaily = pkg.daily_tokens != null;
161
+ const total = isDaily ? (pkg.daily_tokens ?? 0) : (pkg.total_tokens ?? 0);
162
+ const ratio = total > 0 ? pkg.remaining / total : 0;
163
+ const expiry = pkg.expires_at
164
+ ? tr(`${pkg.expires_at.slice(0, 10)} 到期`, `expires ${pkg.expires_at.slice(0, 10)}`)
165
+ : tr("永不过期", "never expires");
166
+ lines.push(tr(` ${packageLabel(pkg)} 还剩 ${fmtTokensCn(pkg.remaining)} / ${fmtTokensCn(total)}${isDaily ? "/天" : ""} ${bar(ratio)} · ${expiry}`, ` ${packageLabel(pkg)} ${fmtTokensEn(pkg.remaining)} / ${fmtTokensEn(total)}${isDaily ? "/day" : ""} left ${bar(ratio)} · ${expiry}`));
167
+ }
168
+ lines.push(tr(` 余额 $${me.bonus_balance_usd.toFixed(2)}`, ` Balance $${me.bonus_balance_usd.toFixed(2)}`));
169
+ lines.push(tr(` 本月已用 $${me.mtd_usd.toFixed(2)}`, ` This month $${me.mtd_usd.toFixed(2)}`));
170
+ lines.push("");
171
+ const { dashboard, topup } = siteLinks(site);
172
+ lines.push(me.pay_enabled === false
173
+ ? tr(` 充值通道即将开放 → ${dashboard}`, ` Top-ups are coming soon → ${dashboard}`)
174
+ : tr(` 余额不够用?充值 → ${topup}`, ` Need more? Top up → ${topup}`));
175
+ return lines;
176
+ }
177
+ /** 当前站点的充值深链(仪表盘充值卡片)。 */
178
+ export function topupUrl(site = activeSite()) {
179
+ return siteLinks(site).topup;
180
+ }
127
181
  /**
128
182
  * 报告尾巴的两条行动入口:打卡/邀请领包与充值(撞线用户的直接出口,运营清单 A2)。
129
183
  * 08-25 起没有「每天自动恢复」的免费池,邀请包也和打卡包一样有有效期,别再写「永久加量」。
130
184
  * 支付通道关闭时充值是死路(仪表盘会报「支付通道未开放」),改说即将上线并指向
131
185
  * 仪表盘的加量包/打卡入口;老网关没有 pay_enabled 字段,按开放处理。
132
186
  */
133
- export function usageCtaLines(me) {
187
+ export function usageCtaLines(me, site = activeSite()) {
134
188
  const payClosed = me?.pay_enabled === false;
189
+ const { dashboard, topup } = siteLinks(site);
135
190
  return [
136
- " 每天到仪表盘打卡领免费包;邀请朋友双方都加量 → https://u1s1.io/dashboard",
191
+ tr(` 每天到仪表盘打卡领免费包;邀请朋友双方都加量 → ${dashboard}`, ` Check in daily on the dashboard for free packs; invite friends and you both get more → ${dashboard}`),
137
192
  payClosed
138
- ? " 充值通道即将开放;额度不够先到仪表盘领加量包、每日打卡 → https://u1s1.io/dashboard"
139
- : ` 额度不够用?充值 → ${TOPUP_URL}`,
193
+ ? tr(` 充值通道即将开放;额度不够先到仪表盘领加量包、每日打卡 → ${dashboard}`, ` Top-ups are coming soon; meanwhile grab extra packs and check in on the dashboard → ${dashboard}`)
194
+ : tr(` 额度不够用?充值 → ${topup}`, ` Need more? Top up → ${topup}`),
140
195
  ];
141
196
  }
142
197
  /** 额度报告正文(不含首尾空行),终端 `u1s1 usage` 和会话内 /usage 共用。 */
143
- export function usageReportLines(me) {
198
+ export function usageReportLines(me, site = activeSite()) {
144
199
  const tokensPerUsd = me.tokens_per_usd ?? 0;
200
+ const body = site.id === "intl"
201
+ ? intlUsageLines(me, site)
202
+ : me.packages ? packageUsageLines(me, tokensPerUsd, site) : legacyUsageLines(me, tokensPerUsd, site);
145
203
  return [
146
- ` 账号 ${me.email ?? "(未绑定邮箱)"}`,
147
- ...(me.packages ? packageUsageLines(me, tokensPerUsd) : legacyUsageLines(me, tokensPerUsd)),
204
+ tr(` 账号 ${me.email ?? "(未绑定邮箱)"}`, ` Account ${me.email ?? "(no email linked)"}`),
205
+ ...body,
148
206
  ];
149
207
  }
150
- /** 会话内 /usage 用:拉取额度并打包成 entry data,错误转成中文提示而不是抛出。 */
208
+ /** 会话内 /usage 用:拉取额度并打包成 entry data,错误转成提示文字而不是抛出。 */
151
209
  export async function usageEntryData() {
152
210
  const cfg = loadConfig();
153
211
  if (!hasDeviceCredential(cfg))
154
- return { error: "还没登录,先在终端运行 u1s1 login" };
212
+ return { error: tr("还没登录,先在终端运行 u1s1 login", "Not signed in. Run u1s1 login in your terminal first.") };
155
213
  try {
156
- return { lines: usageReportLines(await fetchMe(cfg)) };
214
+ return { lines: usageReportLines(await fetchMe(cfg), siteOf(cfg)) };
157
215
  }
158
216
  catch (e) {
159
217
  return { error: e instanceof Error ? e.message : String(e) };
@@ -162,7 +220,7 @@ export async function usageEntryData() {
162
220
  export async function usage() {
163
221
  const cfg = loadConfig();
164
222
  if (!hasDeviceCredential(cfg)) {
165
- console.error("还没登录,先跑 u1s1 login");
223
+ console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
166
224
  process.exit(1);
167
225
  }
168
226
  const me = await fetchMe(cfg).catch((e) => {
@@ -170,7 +228,7 @@ export async function usage() {
170
228
  process.exit(1);
171
229
  });
172
230
  console.log("");
173
- for (const line of usageReportLines(me))
231
+ for (const line of usageReportLines(me, siteOf(cfg)))
174
232
  console.log(line);
175
233
  console.log("");
176
234
  }
package/dist/web.js CHANGED
@@ -7,6 +7,7 @@ import { ensureSearchTools } from "./search-tools.js";
7
7
  import { ensureUsableShell } from "./shell-doctor.js";
8
8
  import { fetchModels, loadCustomEndpoints } from "./api.js";
9
9
  import { ensureSigningProxy } from "./device-auth.js";
10
+ import { tr } from "./i18n.js";
10
11
  let webModelsDir;
11
12
  let webOfficialCfg;
12
13
  function processModelsPath() {
@@ -67,7 +68,7 @@ export async function prepareWebEnv(cfg) {
67
68
  clientAttestationExpiresInSeconds = attestationTtl;
68
69
  }
69
70
  catch (e) {
70
- console.error(" 获取模型列表失败,使用内置列表:", e.message);
71
+ console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e.message);
71
72
  }
72
73
  await endpointsReady;
73
74
  ensureDefaultSettings(MODELS);