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/index.js CHANGED
@@ -4,7 +4,7 @@ import { writeFileSync } from "node:fs";
4
4
  import { cleanupBrandThemes, endpointKeyEnv, endpointProviderEntry, ensureBrandPrompt, ensureDefaultSettings, ensureProviderModels, ensureWorkflowPromptTemplate, scrubForeignProviderEnv, toProviderModels, writeAnnouncementsExtension, writeAttributionExtension, writeMcpExtension, writeCompactUiExtension, writeErrorHumanizeExtension, writeWebToolsExtension, } from "./agent-setup.js";
5
5
  import { Text } from "@earendil-works/pi-tui";
6
6
  import { printConsoleBanner } from "./brand.js";
7
- import { agentDir, apiModelToDef, CUSTOM_ENDPOINTS, isPortableInstall, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readCompactionBudgetSetting, readCompactionReserveTokens, readSettings, refValid, resolvePreferredModel, setModelsFromApi, VERSION, } from "./config.js";
7
+ import { activeSite, agentDir, apiModelToDef, CUSTOM_ENDPOINTS, isPortableInstall, loadConfig, MODELS, persistPreferredModel, PROVIDER_ID, readCompactionBudgetSetting, readCompactionReserveTokens, readSettings, refValid, resolvePreferredModel, setModelsFromApi, VERSION, } from "./config.js";
8
8
  import { registerLoopCommand } from "./loop.js";
9
9
  import { ensureSearchTools } from "./search-tools.js";
10
10
  import { ensureUsableShell } from "./shell-doctor.js";
@@ -12,8 +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 { DEPLOY_HINT_TEXT, markNudge, shouldShowDeployHint } from "./nudges.js";
15
+ import { deployHintText, markNudge, shouldShowDeployHint } from "./nudges.js";
16
16
  import { createGatewayTelemetry } from "./telemetry.js";
17
+ import { installCommand, siteLinks, siteOf } from "./site.js";
18
+ import { isEnglish, lang, tr } from "./i18n.js";
17
19
  const PACKAGE_NAME = "u1s1-cli";
18
20
  /** 启动时检测到的可自动安装的新版;TUI 退出后才装(见 installPendingUpdate)。 */
19
21
  let pendingUpdate;
@@ -80,7 +82,7 @@ async function checkForUpdate() {
80
82
  const settings = readSettings();
81
83
  // 便携版没法用 npm 自更新;autoUpdate 关闭同理只提示。u1s1 update 会给出正确升级方式
82
84
  if (settings.autoUpdate === false || isPortableInstall()) {
83
- setUpdateNotice(`⬆ 新版 v${latest} 可用 · u1s1 update 升级`);
85
+ setUpdateNotice(tr(`⬆ 新版 v${latest} 可用 · u1s1 update 升级`, `⬆ v${latest} available · run u1s1 update`));
84
86
  return;
85
87
  }
86
88
  // 检测包管理器
@@ -93,7 +95,7 @@ async function checkForUpdate() {
93
95
  ? `npm install -g --loglevel=error ${PACKAGE_NAME}@latest`
94
96
  : `${pm} add -g ${PACKAGE_NAME}@latest`;
95
97
  pendingUpdate = { latest, installCmd };
96
- setUpdateNotice(`⬆ 发现新版 v${latest},退出后自动更新`);
98
+ setUpdateNotice(tr(`⬆ 发现新版 v${latest},退出后自动更新`, `⬆ v${latest} available, will update on exit`));
97
99
  }
98
100
  /**
99
101
  * TUI 退出后前台安装新版:此刻不再有模型调用和懒加载,重铺 node_modules 才安全。
@@ -105,21 +107,21 @@ function installPendingUpdate() {
105
107
  if (!pendingUpdate)
106
108
  return;
107
109
  const { latest, installCmd } = pendingUpdate;
108
- console.log(`\n⬆ 正在更新到 v${latest}…`);
109
- 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.)"));
110
112
  try {
111
113
  execSync(installCmd, { stdio: "inherit" });
112
- 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.`));
113
115
  }
114
116
  catch {
115
117
  // 失败不挡退出,下次启动还会再试。npm 全局目录没写权限时 `u1s1 update` 也会
116
118
  // 走同一条命令再失败一次,所以这里直接给出能跳出循环的路径(官网安装脚本)
117
- console.error("自动更新失败(上面是 npm 的报错,常见原因是没有全局安装权限)。");
118
- 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:"));
119
121
  console.error(process.platform === "win32"
120
- ? " powershell -c \"irm https://u1s1.io/releases/install.ps1 | iex\""
121
- : " curl -fsSL https://u1s1.io/releases/install.sh | bash");
122
- console.error("不想每次退出都尝试更新的话,可在 ~/.u1s1/agent/settings.json 里设 \"autoUpdate\": false");
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"));
123
125
  }
124
126
  }
125
127
  /**
@@ -195,22 +197,23 @@ async function runAgent(cfg, args) {
195
197
  if (e instanceof AuthError) {
196
198
  // 凭证形状还在但服务端已作废(设备被移除/换过钥匙):直接进 TUI 每条消息都会失败,
197
199
  // 不如现在就引导重新登录
198
- 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."));
199
201
  const { login } = await import("./login.js");
200
202
  cfg = await login();
201
203
  modelsResp = await fetchModels(cfg).catch((e2) => {
202
- console.error(" 获取模型列表失败,使用内置列表:", e2.message);
204
+ console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e2.message);
203
205
  return undefined;
204
206
  });
205
207
  }
206
208
  else if (e instanceof AccessDeniedError) {
207
209
  // 封禁/停用/设备不受信任:进 TUI 后每条消息都会失败,不如现在就把原因说清楚并退出
208
210
  console.error(` ${e.message}`);
209
- console.error(" 这个账号当前无法使用 u1s1。如有疑问可发邮件到 contact@u1s1.io,或在 https://u1s1.io/dashboard 提交工单。");
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}.`));
210
213
  process.exit(1);
211
214
  }
212
215
  else {
213
- console.error(" 获取模型列表失败,使用内置列表:", e.message);
216
+ console.error(tr(" 获取模型列表失败,使用内置列表:", " Couldn't fetch the model list; using the built-in list:"), e.message);
214
217
  }
215
218
  }
216
219
  if (modelsResp) {
@@ -291,6 +294,9 @@ async function runAgent(cfg, args) {
291
294
  });
292
295
  };
293
296
  await searchToolsReady;
297
+ // 界面语言跟站点走(见 i18n.ts)。pi 引擎的补丁文案(patches/)读不到 u1s1 的配置,
298
+ // 只认 U1S1_LANG;登录/换站都已在上面处理完,这里定下来再加载内核,TUI 与 -p 模式都覆盖
299
+ process.env["U1S1_LANG"] = lang();
294
300
  // Module evaluation can block the event loop for seconds on slow filesystems.
295
301
  // Finish startup requests and fallback/login handling before loading the kernel.
296
302
  const { CustomEditor, main } = await import("@earendil-works/pi-coding-agent");
@@ -309,13 +315,13 @@ async function runAgent(cfg, args) {
309
315
  const text = this.getText().trim();
310
316
  const tip = (msg) => (notifyUi ? notifyUi(msg) : console.log(` ${msg}`));
311
317
  if (text.startsWith("/login")) {
312
- 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"));
313
319
  this.setText("");
314
320
  this.addToHistory?.(text);
315
321
  return;
316
322
  }
317
323
  if (text === "/logout") {
318
- tip("退出登录请先 /exit 回到终端,再运行:u1s1 logout");
324
+ tip(tr("退出登录请先 /exit 回到终端,再运行:u1s1 logout", "To sign out, /exit to your terminal first, then run: u1s1 logout"));
319
325
  this.setText("");
320
326
  this.addToHistory?.(text);
321
327
  return;
@@ -351,28 +357,32 @@ async function runAgent(cfg, args) {
351
357
  });
352
358
  // pi 内置只有 /quit;补一个更常见的 /exit,方便新手退出
353
359
  pi.registerCommand("exit", {
354
- description: "退出 u1s1",
360
+ description: tr("退出 u1s1", "Exit u1s1"),
355
361
  handler: async (_args, ctx) => {
356
362
  ctx.shutdown();
357
363
  },
358
364
  });
359
365
  // /help:启动横幅和官网教程都在引导用户输入它,必须真实存在
360
366
  pi.registerEntryRenderer("u1s1-help", (_entry, _opts, theme) => {
367
+ const site = siteOf(cfg);
361
368
  const text = [
362
- theme.bold(theme.fg("text", "u1s1 常用操作")),
363
- theme.fg("text", "直接打字说需求,回车发送 · Shift+Enter 换行 · Ctrl+V 贴图 · Esc 中断"),
364
- theme.fg("text", "/model 切换模型(留意免费/价格提示) · /clear 清空上下文开新会话"),
365
- theme.fg("text", "/usage 查剩余额度 · /context 看上下文占用与压缩预算 · /resume 恢复历史会话"),
366
- theme.fg("text", "/feedback 一句话反馈问题或建议(自动附带版本与请求编号建工单)"),
367
- theme.fg("dim", "/compact 手动压缩上下文 · /settings 设置 · /hotkeys 全部快捷键 · /exit 退出"),
368
- theme.fg("dim", "退出后在终端:u1s1 deploy 发布网页 · u1s1 update 升级"),
369
- theme.fg("dim", "匿名使用统计只记会话开始/结束等节点,不含任何内容;U1S1_TELEMETRY=0 可关闭"),
370
- theme.fg("dim", "新手教程 → https://u1s1.io/guides"),
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}`)),
371
381
  ].join("\n");
372
382
  return new Text(text, 1, 0);
373
383
  });
374
384
  pi.registerCommand("help", {
375
- description: "查看常用操作和命令",
385
+ description: tr("查看常用操作和命令", "Show common actions and commands"),
376
386
  handler: async () => {
377
387
  pi.appendEntry("u1s1-help");
378
388
  },
@@ -385,7 +395,7 @@ async function runAgent(cfg, args) {
385
395
  return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
386
396
  });
387
397
  pi.registerCommand("usage", {
388
- description: "查看剩余额度和本月用量",
398
+ description: tr("查看剩余额度和本月用量", "Show remaining balance and this month's usage"),
389
399
  handler: async () => {
390
400
  const { usageEntryData } = await import("./usage.js");
391
401
  pi.appendEntry("u1s1-usage", await usageEntryData());
@@ -399,7 +409,7 @@ async function runAgent(cfg, args) {
399
409
  return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
400
410
  });
401
411
  pi.registerCommand("context", {
402
- description: "查看上下文占用与自动压缩预算",
412
+ description: tr("查看上下文占用与自动压缩预算", "Show context usage and the auto-compaction budget"),
403
413
  handler: async (_args, ctx) => {
404
414
  const { contextEntryLines } = await import("./context-usage.js");
405
415
  const settings = readSettings();
@@ -419,7 +429,7 @@ async function runAgent(cfg, args) {
419
429
  return new Text((data.lines ?? []).map((l) => theme.fg("text", l)).join("\n"), 1, 0);
420
430
  });
421
431
  pi.registerCommand("feedback", {
422
- description: "反馈问题或建议(自动附带诊断信息建工单)",
432
+ description: tr("反馈问题或建议(自动附带诊断信息建工单)", "Report a problem or idea (opens a support ticket with diagnostics)"),
423
433
  handler: async (cmdArgs, ctx) => {
424
434
  const { feedbackInSession, parseFeedbackArgs } = await import("./feedback.js");
425
435
  let raw = cmdArgs.trim();
@@ -432,7 +442,7 @@ async function runAgent(cfg, args) {
432
442
  return;
433
443
  }
434
444
  if (!parsed.help && !parsed.text && ctx.hasUI) {
435
- 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();
436
446
  if (!text)
437
447
  return;
438
448
  raw = `--${parsed.category} ${text}`;
@@ -468,10 +478,10 @@ async function runAgent(cfg, args) {
468
478
  firstTaskDoneSent = true;
469
479
  telemetry.track("first_task_done", { files_written: stats.filesWritten });
470
480
  }
471
- // 每台机器只提示一次;跑过 u1s1 deploy 后永不再提(见 nudges.ts)
472
- if (ctx.hasUI && shouldShowDeployHint()) {
481
+ // 每台机器只提示一次;跑过 u1s1 deploy 后永不再提(见 nudges.ts)。国际站没有网页托管,不提示
482
+ if (ctx.hasUI && siteOf(cfg).id !== "intl" && shouldShowDeployHint()) {
473
483
  markNudge("deploy_hint_shown");
474
- ctx.ui.notify(DEPLOY_HINT_TEXT, "info");
484
+ ctx.ui.notify(deployHintText(), "info");
475
485
  }
476
486
  });
477
487
  // pi 在 session_shutdown 处理完后直接 process.exit,session_end 只能在这里
@@ -486,11 +496,11 @@ async function runAgent(cfg, args) {
486
496
  // TUI 和 Desktop App 共用一份(见 agent-setup.ts)
487
497
  // /clear: 清空当前对话上下文,开始新会话
488
498
  pi.registerCommand("clear", {
489
- description: "清除当前对话上下文,开始新会话",
499
+ description: tr("清除当前对话上下文,开始新会话", "Clear the conversation context and start a new session"),
490
500
  handler: async (_args, ctx) => {
491
501
  await ctx.newSession({
492
502
  withSession: async (sessionContext) => {
493
- sessionContext.ui.notify("🗑️ 上下文已清除", "info");
503
+ sessionContext.ui.notify(tr("🗑️ 上下文已清除", "🗑️ Context cleared"), "info");
494
504
  },
495
505
  });
496
506
  },
@@ -548,6 +558,10 @@ async function runAgent(cfg, args) {
548
558
  }
549
559
  function printHelp() {
550
560
  printConsoleBanner(VERSION);
561
+ if (isEnglish()) {
562
+ printHelpEn();
563
+ return;
564
+ }
551
565
  console.log(" 命令:");
552
566
  console.log(" u1s1 进入对话界面(最常用,不带参数)");
553
567
  console.log(" u1s1 -p \"一句话\" 不进界面直接回答,适合脚本里用");
@@ -556,9 +570,12 @@ function printHelp() {
556
570
  console.log(" u1s1 usage 查看剩余额度");
557
571
  console.log(" u1s1 whoami 查看当前登录的账号");
558
572
  console.log(" u1s1 update 升级到最新版");
559
- console.log(" u1s1 deploy 发布网页(--public / --private)");
560
- console.log(" u1s1 deploy list 查看已发布的站点");
561
- console.log(" u1s1 deploy remove 删除已发布的站点");
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
+ }
562
579
  console.log(" u1s1 feedback \"一句话\" 反馈问题或建议(自动建工单,--bug/--question…)");
563
580
  console.log(" u1s1 import 导入历史会话(import skills 导入技能)");
564
581
  console.log(" u1s1 mcp 配置第三方 MCP 服务(add/list/test/remove/import)");
@@ -567,12 +584,45 @@ function printHelp() {
567
584
  console.log("");
568
585
  console.log(" 对话里输入 /help 看会话内命令(/model /clear /feedback /exit …)");
569
586
  console.log(" 匿名使用统计只记会话开始/结束等节点,不含内容;设 U1S1_TELEMETRY=0 关闭");
570
- console.log(" 更多帮助 → https://u1s1.io/guides");
587
+ console.log(` 更多帮助 → ${siteLinks(activeSite()).guides}`);
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}`);
571
611
  }
572
612
  function wantsHelp(rest) {
573
613
  return rest.includes("--help") || rest.includes("-h");
574
614
  }
615
+ /** 国际站没有网页托管;只在国际站打印,所以只有英文。 */
616
+ function hostingUnavailableText(site) {
617
+ return `Site hosting isn't available on ${new URL(site.origin).host}.`;
618
+ }
575
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
+ }
576
626
  const { deployCommand, printDeployHelp } = await import("./deploy.js");
577
627
  // --help 不该先被拽去登录
578
628
  if (wantsHelp(rest)) {
@@ -588,7 +638,7 @@ async function whoamiCommand() {
588
638
  const { hasDeviceCredential } = await import("./device-auth.js");
589
639
  const cfg = loadConfig();
590
640
  if (!hasDeviceCredential(cfg)) {
591
- console.error("还没登录,先跑 u1s1 login");
641
+ console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
592
642
  process.exit(1);
593
643
  }
594
644
  const { fetchMe } = await import("./api.js");
@@ -596,6 +646,12 @@ async function whoamiCommand() {
596
646
  console.error(e.message);
597
647
  process.exit(1);
598
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
+ }
599
655
  console.log(me.email ?? "(未绑定邮箱)");
600
656
  console.log(` 服务器 ${cfg.baseUrl}`);
601
657
  console.log(" 换账号:u1s1 logout 再 u1s1 login;看额度:u1s1 usage");
@@ -604,7 +660,7 @@ async function logoutCommand() {
604
660
  const { saveConfig } = await import("./config.js");
605
661
  const current = loadConfig();
606
662
  if (!current.deviceToken && !current.apiKey) {
607
- console.log("当前本来就没有登录。运行 u1s1 即可登录。");
663
+ console.log(tr("当前本来就没有登录。运行 u1s1 即可登录。", "You're not signed in. Run u1s1 to sign in."));
608
664
  return;
609
665
  }
610
666
  let serverRevoked = false;
@@ -623,19 +679,26 @@ async function logoutCommand() {
623
679
  email: undefined,
624
680
  });
625
681
  if (serverRevoked) {
626
- console.log("已退出登录,这台设备在服务端的授权也已注销。");
682
+ console.log(tr("已退出登录,这台设备在服务端的授权也已注销。", "Signed out. This device's authorization on the server has been revoked too."));
627
683
  }
628
684
  else {
629
- console.log("已退出本机登录。");
630
- console.log("(没连上服务器注销这台设备;如有需要,可在 https://u1s1.io/dashboard 的设备列表里手动移除。)");
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}.)`));
631
688
  }
632
689
  }
633
690
  /** 子命令表:每条都按需 import,不带子命令直接进对话界面的路径不多加载任何模块。 */
634
691
  const COMMANDS = {
635
692
  deploy: deployEntry,
636
693
  login: async (rest) => {
637
- const { login } = await import("./login.js");
638
- await login(rest[0]);
694
+ const { login, parseLoginArgs } = await import("./login.js");
695
+ const parsed = parseLoginArgs(rest);
696
+ if (parsed.error) {
697
+ console.error(parsed.error);
698
+ process.exitCode = 1;
699
+ return;
700
+ }
701
+ await login(parsed.key, { site: parsed.site });
639
702
  },
640
703
  logout: logoutCommand,
641
704
  usage: async () => {
@@ -668,7 +731,7 @@ const COMMANDS = {
668
731
  await benchCommand(rest);
669
732
  },
670
733
  web: async () => {
671
- console.error("u1s1 web 已下线,请使用 Desktop App:https://u1s1.io/#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}`));
672
735
  process.exitCode = 1;
673
736
  },
674
737
  };
@@ -683,8 +746,15 @@ function rejectMistypedCommand(cmd, argCount) {
683
746
  const near = KNOWN_COMMANDS.find((k) => editDistance(k, cmd) <= (k.length <= 4 ? 1 : 2));
684
747
  if (!near)
685
748
  return false;
686
- console.error(`不认识的命令「${cmd}」,你是不是想运行:u1s1 ${near}`);
687
- console.error("查看全部命令:u1s1 --help;直接开始对话:运行 u1s1(不带参数)");
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."));
688
758
  process.exitCode = 1;
689
759
  return true;
690
760
  }
@@ -705,8 +775,9 @@ async function run() {
705
775
  return;
706
776
  }
707
777
  // `u1s1 login --help` 不该真的去登录、`logout --help` 不该真的退出:先看是不是在问用法
708
- if (cmd && Object.hasOwn(SIMPLE_COMMAND_HELP, cmd) && wantsHelp(rest)) {
709
- for (const line of SIMPLE_COMMAND_HELP[cmd])
778
+ const simpleHelp = simpleCommandHelp();
779
+ if (cmd && Object.hasOwn(simpleHelp, cmd) && wantsHelp(rest)) {
780
+ for (const line of simpleHelp[cmd])
710
781
  console.log(line);
711
782
  return;
712
783
  }
@@ -724,6 +795,9 @@ async function run() {
724
795
  }
725
796
  const KNOWN_COMMANDS = ["deploy", "login", "logout", "usage", "whoami", "model", "update", "import", "mcp", "bench", "feedback", "help"];
726
797
  /** 没有自己 --help 处理的简单子命令:问用法时只打印说明,不执行任何动作。 */
798
+ function simpleCommandHelp() {
799
+ return isEnglish() ? SIMPLE_COMMAND_HELP_EN : SIMPLE_COMMAND_HELP;
800
+ }
727
801
  const SIMPLE_COMMAND_HELP = {
728
802
  login: [
729
803
  "用法: u1s1 login",
@@ -748,6 +822,31 @@ const SIMPLE_COMMAND_HELP = {
748
822
  " 不想每次退出自动更新:在 ~/.u1s1/agent/settings.json 设 \"autoUpdate\": false",
749
823
  ],
750
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
+ };
751
850
  /** 经典 Levenshtein,命令名都很短,O(nm) 足够。 */
752
851
  function editDistance(a, b) {
753
852
  const dp = Array.from({ length: a.length + 1 }, (_, i) => i);
package/dist/login.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { webcrypto } from "node:crypto";
2
2
  import { type CliConfig } from "./config.js";
3
- /** baseUrl 形如 https://api.u1s1.io/v1;auth 路由挂在同一域名的根路径。 */
3
+ import { type SiteId } from "./site.js";
4
+ /** baseUrl 形如 https://api.<站点主域>/v1;auth 路由挂在同一域名的根路径。 */
4
5
  export declare function apiOrigin(cfg: CliConfig): string;
5
6
  export interface DeviceStart {
6
7
  verify_url: string;
@@ -30,6 +31,16 @@ export interface DeviceLoginResult {
30
31
  export declare function pollDeviceLogin(origin: string, start: DeviceStart, outcome?: {
31
32
  reason?: "expired" | "timeout";
32
33
  }): Promise<DeviceLoginResult | null>;
33
- export declare function login(keyArg?: string): Promise<CliConfig>;
34
+ export interface LoginArgs {
35
+ /** 老用法 `u1s1 login <key>` 的位置参数(已不再用于登录,只提示改走浏览器批准) */
36
+ key?: string;
37
+ site?: SiteId;
38
+ error?: string;
39
+ }
40
+ /** `u1s1 login [--site cn|global] [key]`;--site 也接受 --site=<值>,global 的别名 intl。 */
41
+ export declare function parseLoginArgs(rest: string[]): LoginArgs;
42
+ export declare function login(keyArg?: string, opts?: {
43
+ site?: SiteId;
44
+ }): Promise<CliConfig>;
34
45
  /** Returns a config with a browser-approved, sender-constrained device credential. */
35
46
  export declare function ensureAuth(): Promise<CliConfig>;