u1s1-cli 1.11.8 → 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 (45) hide show
  1. package/dist/agent-setup.d.ts +5 -0
  2. package/dist/agent-setup.js +31 -7
  3. package/dist/api.js +42 -34
  4. package/dist/bench-report.js +12 -9
  5. package/dist/bench.js +34 -31
  6. package/dist/brand.d.ts +2 -1
  7. package/dist/brand.js +38 -12
  8. package/dist/config.d.ts +11 -1
  9. package/dist/config.js +25 -8
  10. package/dist/context-usage.js +12 -9
  11. package/dist/deploy.js +49 -45
  12. package/dist/device-auth.js +7 -5
  13. package/dist/error-humanize.d.ts +1 -0
  14. package/dist/error-humanize.js +18 -6
  15. package/dist/feedback.js +48 -31
  16. package/dist/i18n.d.ts +21 -0
  17. package/dist/i18n.js +46 -0
  18. package/dist/import/index.js +64 -28
  19. package/dist/import/skills.js +72 -41
  20. package/dist/import/util.js +3 -2
  21. package/dist/import/write.js +2 -1
  22. package/dist/index.js +139 -48
  23. package/dist/login.js +39 -28
  24. package/dist/loop.js +18 -12
  25. package/dist/mcp/client.js +15 -14
  26. package/dist/mcp/command.js +76 -48
  27. package/dist/mcp/extension.js +5 -3
  28. package/dist/mcp/tools.js +6 -5
  29. package/dist/model.js +11 -10
  30. package/dist/nudges.d.ts +8 -2
  31. package/dist/nudges.js +22 -3
  32. package/dist/search-tools.js +7 -6
  33. package/dist/shell-doctor.js +9 -8
  34. package/dist/subagent.js +5 -4
  35. package/dist/tools.js +53 -37
  36. package/dist/update.js +29 -28
  37. package/dist/usage.d.ts +1 -1
  38. package/dist/usage.js +96 -43
  39. package/dist/web.js +2 -1
  40. package/dist/workflow/runner.js +33 -29
  41. package/dist/workflow/templates.js +70 -45
  42. package/dist/workflow/tool.js +15 -12
  43. package/package.json +2 -2
  44. package/scripts/patch-pi.js +243 -278
  45. package/scripts/render-regression.mjs +55 -4
@@ -1,4 +1,5 @@
1
1
  import { activeSite } from "./config.js";
2
+ import { isEnglish, tr } from "./i18n.js";
2
3
  import { siteLinks } from "./site.js";
3
4
  /**
4
5
  * 模型调用失败时,网关的中文说明会被 OpenAI SDK + pi-ai 包成
@@ -17,8 +18,11 @@ import { siteLinks } from "./site.js";
17
18
  * 额度用尽(reason=exhausted)且今天已打卡时网关还带 error.resets_at(下次可打卡时刻,
18
19
  * 即北京时间 0 点,ISO),这里换算成「距下次可打卡还有 N 小时 M 分」接在正文后:比
19
20
  * 「北京时间 0 点」更省用户脑子。今天没打卡时网关不带 resets_at(现在就能领)。
21
+ * 国际站没有打卡/免费包(网关也不带 resets_at),英文界面一律不给这句。
20
22
  */
21
23
  export function quotaResetHint(resetsAt, now = Date.now()) {
24
+ if (isEnglish())
25
+ return "";
22
26
  if (typeof resetsAt !== "string")
23
27
  return "";
24
28
  const at = Date.parse(resetsAt);
@@ -72,10 +76,10 @@ function humanizeOpaqueError(raw, site) {
72
76
  return undefined;
73
77
  const n = Number(status);
74
78
  const body = n >= 500
75
- ? `服务暂时不可用,稍等再试;持续出现可看状态页 ${siteLinks(site).status}`
79
+ ? tr(`服务暂时不可用,稍等再试;持续出现可看状态页 ${siteLinks(site).status}`, `The service is temporarily unavailable. Please try again shortly; if it keeps happening, check the status page: ${siteLinks(site).status}`)
76
80
  : n === 429
77
- ? "请求太频繁,稍等再试"
78
- : "请求被拒绝,重试仍失败可运行 u1s1 feedback 反馈";
81
+ ? tr("请求太频繁,稍等再试", "Too many requests. Please wait a moment and try again.")
82
+ : tr("请求被拒绝,重试仍失败可运行 u1s1 feedback 反馈", "The request was rejected. If retrying doesn't help, run u1s1 feedback to report it.");
79
83
  return `${body} (HTTP ${status})`;
80
84
  }
81
85
  export function humanizeModelError(raw, now = Date.now(), site) {
@@ -91,13 +95,21 @@ export function humanizeModelError(raw, now = Date.now(), site) {
91
95
  const type = typeof err["type"] === "string" ? err["type"] : "";
92
96
  const status = /(?:^|\s|\()(\d{3})\b/.exec(raw.slice(0, jsonStart))?.[1];
93
97
  const requestId = typeof err["request_id"] === "string" ? err["request_id"] : "";
98
+ const insufficient = type === "insufficient_quota" || code === "quota_exceeded";
94
99
  const tags = [
95
100
  status ? `HTTP ${status}` : "",
96
- type === "insufficient_quota" || code === "quota_exceeded" ? "insufficient_quota" : code,
97
- requestId ? `请求编号 ${requestId}` : "",
101
+ insufficient ? "insufficient_quota" : code,
102
+ requestId ? tr(`请求编号 ${requestId}`, `request ID ${requestId}`) : "",
98
103
  ].filter(Boolean);
99
104
  const resetHint = err["reason"] === "exhausted" ? quotaResetHint(err["resets_at"], now) : "";
100
- const body = resetHint ? `${message}〔${resetHint}〕` : message;
105
+ let body = resetHint ? `${message}〔${resetHint}〕` : message;
106
+ // 国际站(英文界面)余额不足:网关正文本该带充值链接;老网关/个别路由没带时补一句,指向本站充值卡片。
107
+ // 中文界面不动(中国站网关文案自带仪表盘链接,另有免费包等分流说明)
108
+ if (insufficient && isEnglish()) {
109
+ const links = siteLinks(site ?? activeSite());
110
+ if (!message.includes(links.dashboard))
111
+ body = `${body} Top up to continue → ${links.topup}`;
112
+ }
101
113
  const friendly = tags.length ? `${body} (${tags.join(" · ")})` : body;
102
114
  return friendly === raw ? undefined : friendly;
103
115
  }
package/dist/feedback.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { readJsonResponseCapped } from "./api.js";
2
2
  import { activeSite, loadConfig, PROVIDER_ID, resolvePreferredModel, VERSION } from "./config.js";
3
3
  import { authorizedFetch, hasDeviceCredential } from "./device-auth.js";
4
+ import { tr } from "./i18n.js";
4
5
  import { askLine } from "./prompt.js";
5
6
  import { lastRequestId } from "./request-trace.js";
6
7
  import { siteLinks, siteOf } from "./site.js";
@@ -10,13 +11,14 @@ import { siteLinks, siteOf } from "./site.js";
10
11
  * 走 POST /v1/support/tickets(设备签名),201 返回 {id, url}。
11
12
  */
12
13
  export const FEEDBACK_CATEGORIES = ["question", "bug", "billing", "suggestion", "other"];
13
- const CATEGORY_LABELS = {
14
- question: "提问",
15
- bug: "故障",
16
- billing: "计费",
17
- suggestion: "建议",
18
- other: "其他",
19
- };
14
+ // 用时求值:模块加载时还不知道站点/语言
15
+ const categoryLabels = () => ({
16
+ question: tr("提问", "question"),
17
+ bug: tr("故障", "bug"),
18
+ billing: tr("计费", "billing"),
19
+ suggestion: tr("建议", "suggestion"),
20
+ other: tr("其他", "other"),
21
+ });
20
22
  export const FEEDBACK_SUBJECT_MAX = 50;
21
23
  export const FEEDBACK_MESSAGE_MAX = 4_000;
22
24
  /** 当前站点仪表盘的工单/留言区,提交失败或服务端没回链接时的兜底入口。 */
@@ -36,7 +38,7 @@ export function parseFeedbackArgs(args) {
36
38
  continue;
37
39
  }
38
40
  if (arg.startsWith("--"))
39
- throw new Error(`不认识的 feedback 参数:${arg}(可用 --bug --question --suggestion --billing)`);
41
+ throw new Error(tr(`不认识的 feedback 参数:${arg}(可用 --bug --question --suggestion --billing)`, `Unknown feedback option: ${arg} (use --bug --question --suggestion --billing)`));
40
42
  words.push(arg);
41
43
  }
42
44
  return { category, text: words.join(" ").trim() };
@@ -45,7 +47,7 @@ export function parseFeedbackArgs(args) {
45
47
  export function buildFeedbackRequest(input) {
46
48
  const message = input.text.replace(/\r\n?/g, "\n").trim();
47
49
  if (!message)
48
- throw new Error("反馈内容不能为空");
50
+ throw new Error(tr("反馈内容不能为空", "Feedback can't be empty"));
49
51
  const firstLine = message.split("\n").find((l) => l.trim())?.replace(/\s+/g, " ").trim() ?? message;
50
52
  const subject = firstLine.length > FEEDBACK_SUBJECT_MAX
51
53
  ? `${firstLine.slice(0, FEEDBACK_SUBJECT_MAX - 1)}…`
@@ -90,7 +92,7 @@ export async function submitFeedback(cfg, body) {
90
92
  });
91
93
  }
92
94
  catch {
93
- throw new Error(`连不上 ${cfg.baseUrl},检查一下网络?`);
95
+ throw new Error(tr(`连不上 ${cfg.baseUrl},检查一下网络?`, `Can't reach ${cfg.baseUrl}. Check your network?`));
94
96
  }
95
97
  const value = await readJsonResponseCapped(resp, MAX_TICKET_RESPONSE_BYTES).catch(() => null);
96
98
  const data = value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -106,7 +108,7 @@ export async function submitFeedback(cfg, body) {
106
108
  function createdOutcome(status, data, site) {
107
109
  const id = typeof data?.id === "string" || typeof data?.id === "number" ? String(data.id) : "";
108
110
  if (!id)
109
- return { ok: false, status, message: "服务端工单响应格式不正确" };
111
+ return { ok: false, status, message: tr("服务端工单响应格式不正确", "Unexpected support ticket response from the server") };
110
112
  const url = typeof data?.url === "string" && data.url.startsWith("https://") ? data.url : supportUrl(site);
111
113
  return { ok: true, id, url };
112
114
  }
@@ -121,32 +123,40 @@ function retryAfterSeconds(resp, data) {
121
123
  function failureMessage(status, data) {
122
124
  if (typeof data?.error?.message === "string")
123
125
  return data.error.message.slice(0, 500);
124
- return status === 401 ? "登录已失效,请重新运行 u1s1 login" : `服务端返回 ${status},稍后再试`;
126
+ return status === 401
127
+ ? tr("登录已失效,请重新运行 u1s1 login", "Your session has expired. Run u1s1 login to sign in again.")
128
+ : tr(`服务端返回 ${status},稍后再试`, `Server returned ${status}. Please try again later.`);
125
129
  }
126
130
  /** 结果措辞,终端与会话内共用(每项一行)。 */
127
131
  export function feedbackOutcomeLines(outcome, site = activeSite()) {
128
132
  if (outcome.ok) {
129
133
  return [
130
- `✅ 反馈已提交,工单编号 ${outcome.id}。`,
131
- `进度与回复在这里看:${outcome.url}`,
132
- "我们通常会在 24 小时内回复到你的注册邮箱。",
134
+ tr(`✅ 反馈已提交,工单编号 ${outcome.id}。`, `✅ Feedback sent. Ticket #${outcome.id}.`),
135
+ tr(`进度与回复在这里看:${outcome.url}`, `Track progress and replies here: ${outcome.url}`),
136
+ tr("我们通常会在 24 小时内回复到你的注册邮箱。", "We usually reply to your account email within 24 hours."),
133
137
  ];
134
138
  }
135
139
  if (outcome.status === 429) {
136
140
  const wait = outcome.retryAfterSeconds
137
- ? `请等 ${Math.ceil(outcome.retryAfterSeconds / 60) || 1} 分钟后再试`
138
- : "请稍等几分钟再试";
139
- return [`提交太频繁了,${wait}。`, `等不及的话直接到后台留言:${supportUrl(site)}`];
141
+ ? tr(`请等 ${Math.ceil(outcome.retryAfterSeconds / 60) || 1} 分钟后再试`, `please wait ${Math.ceil(outcome.retryAfterSeconds / 60) || 1} min and try again`)
142
+ : tr("请稍等几分钟再试", "please wait a few minutes and try again");
143
+ return [
144
+ tr(`提交太频繁了,${wait}。`, `Too many submissions; ${wait}.`),
145
+ tr(`等不及的话直接到后台留言:${supportUrl(site)}`, `Or leave a message on the dashboard: ${supportUrl(site)}`),
146
+ ];
140
147
  }
141
- return [`提交失败:${outcome.message}`, `可以直接到后台留言:${supportUrl(site)}`];
148
+ return [
149
+ tr(`提交失败:${outcome.message}`, `Submission failed: ${outcome.message}`),
150
+ tr(`可以直接到后台留言:${supportUrl(site)}`, `You can leave a message on the dashboard instead: ${supportUrl(site)}`),
151
+ ];
142
152
  }
143
153
  export function printFeedbackHelp() {
144
154
  console.log("");
145
- console.log(" u1s1 feedback [--bug|--question|--suggestion|--billing] [一句话描述]");
155
+ console.log(tr(" u1s1 feedback [--bug|--question|--suggestion|--billing] [一句话描述]", " u1s1 feedback [--bug|--question|--suggestion|--billing] [short description]"));
146
156
  console.log("");
147
- console.log(" 不带描述时会问你一行;分类不写默认「其他」。");
148
- console.log(" 会自动附上版本、平台、最近一次报错的请求编号,方便我们定位。");
149
- console.log(` 也可以在后台留言:${supportUrl()}`);
157
+ console.log(tr(" 不带描述时会问你一行;分类不写默认「其他」。", " Without a description you'll be asked for one line; the category defaults to \"other\"."));
158
+ console.log(tr(" 会自动附上版本、平台、最近一次报错的请求编号,方便我们定位。", " Your version, platform and the request ID of the last error are attached automatically to help us debug."));
159
+ console.log(tr(` 也可以在后台留言:${supportUrl()}`, ` You can also leave a message on the dashboard: ${supportUrl()}`));
150
160
  console.log("");
151
161
  }
152
162
  /** 终端命令入口。 */
@@ -158,16 +168,16 @@ export async function feedbackCommand(args) {
158
168
  }
159
169
  const cfg = loadConfig();
160
170
  if (!hasDeviceCredential(cfg)) {
161
- console.error("还没登录,先跑 u1s1 login");
171
+ console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
162
172
  process.exitCode = 1;
163
173
  return;
164
174
  }
165
175
  let text = parsed.text;
166
176
  if (!text) {
167
- text = await askLine(` 想反馈什么?(${CATEGORY_LABELS[parsed.category]},一行说清即可):`);
177
+ text = await askLine(tr(` 想反馈什么?(${categoryLabels()[parsed.category]},一行说清即可):`, ` What's your feedback? (${categoryLabels()[parsed.category]}, one line is enough): `));
168
178
  }
169
179
  if (!text) {
170
- console.error("反馈内容不能为空。用法:u1s1 feedback \"一句话描述\"");
180
+ console.error(tr("反馈内容不能为空。用法:u1s1 feedback \"一句话描述\"", "Feedback can't be empty. Usage: u1s1 feedback \"short description\""));
171
181
  process.exitCode = 1;
172
182
  return;
173
183
  }
@@ -191,13 +201,17 @@ export async function feedbackCommand(args) {
191
201
  export async function feedbackInSession(rawArgs, model) {
192
202
  const parsed = parseFeedbackArgs(rawArgs.split(/\s+/).filter(Boolean));
193
203
  if (parsed.help) {
194
- return ["/feedback [--bug|--question|--suggestion|--billing] 一句话描述", `或到后台留言:${supportUrl()}`];
204
+ return [
205
+ tr("/feedback [--bug|--question|--suggestion|--billing] 一句话描述", "/feedback [--bug|--question|--suggestion|--billing] short description"),
206
+ tr(`或到后台留言:${supportUrl()}`, `Or leave a message on the dashboard: ${supportUrl()}`),
207
+ ];
195
208
  }
196
209
  const cfg = loadConfig();
197
210
  if (!hasDeviceCredential(cfg))
198
- return ["还没登录,先在终端运行 u1s1 login"];
199
- if (!parsed.text)
200
- return ["反馈内容不能为空,例如:/feedback --bug 生成的网页打不开"];
211
+ return [tr("还没登录,先在终端运行 u1s1 login", "Not signed in. Run u1s1 login in your terminal first.")];
212
+ if (!parsed.text) {
213
+ return [tr("反馈内容不能为空,例如:/feedback --bug 生成的网页打不开", "Feedback can't be empty, e.g. /feedback --bug the generated page won't open")];
214
+ }
201
215
  const body = buildFeedbackRequest({
202
216
  text: parsed.text,
203
217
  category: parsed.category,
@@ -207,6 +221,9 @@ export async function feedbackInSession(rawArgs, model) {
207
221
  return feedbackOutcomeLines(await submitFeedback(cfg, body), siteOf(cfg));
208
222
  }
209
223
  catch (e) {
210
- return [e instanceof Error ? e.message : String(e), `可以直接到后台留言:${supportUrl(siteOf(cfg))}`];
224
+ return [
225
+ e instanceof Error ? e.message : String(e),
226
+ tr(`可以直接到后台留言:${supportUrl(siteOf(cfg))}`, `You can leave a message on the dashboard instead: ${supportUrl(siteOf(cfg))}`),
227
+ ];
211
228
  }
212
229
  }
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * 界面语言跟站点走:国际站(u1s1.dev)英文,中国站中文;不提供语言切换
3
+ * (运营者 2026-10-01 定「国际版只要英文」)。站点按下载渠道定,见 site.ts。
4
+ *
5
+ * 文案就地写成 tr("中文", "English"),不做 key 表:两种语言挨着,改一处顺手改另一处。
6
+ * 模块顶层常量不能直接调 tr —— 那时配置可能还没读,要写成函数在用时求值。
7
+ *
8
+ * 本模块不 import 任何本地模块:config.ts 在加载时用 setLangResolver 注册「按当前站点
9
+ * 判定」,saveConfig 后 resetLang(),`u1s1 login --site` 切站后同进程立即换语言。
10
+ */
11
+ export type Lang = "zh" | "en";
12
+ export declare function setLangResolver(fn: () => Lang): void;
13
+ /** 配置变了(换站)后调用,下次 lang() 重新判定。 */
14
+ export declare function resetLang(): void;
15
+ /**
16
+ * 当前界面语言。U1S1_LANG=en|zh 可强制(测试用;CLI 拉起 pi 前也写它,
17
+ * 让 pi 补丁里的文案跟着走,见 patches/ 与 scripts/patch-pi.js)。
18
+ */
19
+ export declare function lang(): Lang;
20
+ export declare function isEnglish(): boolean;
21
+ export declare function tr(zh: string, en: string): string;
package/dist/i18n.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 界面语言跟站点走:国际站(u1s1.dev)英文,中国站中文;不提供语言切换
3
+ * (运营者 2026-10-01 定「国际版只要英文」)。站点按下载渠道定,见 site.ts。
4
+ *
5
+ * 文案就地写成 tr("中文", "English"),不做 key 表:两种语言挨着,改一处顺手改另一处。
6
+ * 模块顶层常量不能直接调 tr —— 那时配置可能还没读,要写成函数在用时求值。
7
+ *
8
+ * 本模块不 import 任何本地模块:config.ts 在加载时用 setLangResolver 注册「按当前站点
9
+ * 判定」,saveConfig 后 resetLang(),`u1s1 login --site` 切站后同进程立即换语言。
10
+ */
11
+ let resolver;
12
+ let cached;
13
+ export function setLangResolver(fn) {
14
+ resolver = fn;
15
+ cached = undefined;
16
+ }
17
+ /** 配置变了(换站)后调用,下次 lang() 重新判定。 */
18
+ export function resetLang() {
19
+ cached = undefined;
20
+ }
21
+ /**
22
+ * 当前界面语言。U1S1_LANG=en|zh 可强制(测试用;CLI 拉起 pi 前也写它,
23
+ * 让 pi 补丁里的文案跟着走,见 patches/ 与 scripts/patch-pi.js)。
24
+ */
25
+ export function lang() {
26
+ const forced = process.env["U1S1_LANG"];
27
+ if (forced === "en" || forced === "zh")
28
+ return forced;
29
+ if (cached)
30
+ return cached;
31
+ if (!resolver)
32
+ return "zh"; // config.ts 还没加载:不缓存,免得把默认值钉死
33
+ try {
34
+ cached = resolver();
35
+ }
36
+ catch {
37
+ cached = "zh";
38
+ }
39
+ return cached;
40
+ }
41
+ export function isEnglish() {
42
+ return lang() === "en";
43
+ }
44
+ export function tr(zh, en) {
45
+ return lang() === "en" ? en : zh;
46
+ }
@@ -1,5 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { formatHomePath } from "../brand.js";
3
+ import { isEnglish, tr } from "../i18n.js";
3
4
  import { agentDir } from "../config.js";
4
5
  import { claudeAdapter, hydrateClaudeSession } from "./claude.js";
5
6
  import { codexAdapter, hydrateCodexSession } from "./codex.js";
@@ -65,7 +66,7 @@ function parseSourceArg(raw) {
65
66
  };
66
67
  const id = aliases[raw.toLowerCase()];
67
68
  if (!id) {
68
- console.error(` 不认识来源「${raw}」,可选: claude / codex / all`);
69
+ console.error(tr(` 不认识来源「${raw}」,可选: claude / codex / all`, ` Unknown source "${raw}"; choose claude / codex / all`));
69
70
  process.exit(1);
70
71
  }
71
72
  return [id];
@@ -88,7 +89,7 @@ function parseFlags(args) {
88
89
  else if (a === "--cwd") {
89
90
  const next = args[++i];
90
91
  if (!next) {
91
- console.error(" --cwd 后面要跟一个目录");
92
+ console.error(tr(" --cwd 后面要跟一个目录", " --cwd requires a directory"));
92
93
  process.exit(1);
93
94
  }
94
95
  cwd = next;
@@ -97,20 +98,20 @@ function parseFlags(args) {
97
98
  const next = args[++i];
98
99
  const n = Number(next);
99
100
  if (!Number.isFinite(n) || n <= 0) {
100
- console.error(" --limit 要是正整数");
101
+ console.error(tr(" --limit 要是正整数", " --limit must be a positive integer"));
101
102
  process.exit(1);
102
103
  }
103
104
  limit = Math.floor(n);
104
105
  }
105
106
  else if (a.startsWith("-")) {
106
- console.error(` 不认识参数 ${a}`);
107
+ console.error(tr(` 不认识参数 ${a}`, ` Unknown option ${a}`));
107
108
  process.exit(1);
108
109
  }
109
110
  else if (!sourceRaw) {
110
111
  sourceRaw = a;
111
112
  }
112
113
  else {
113
- console.error(` 多余参数 ${a}`);
114
+ console.error(tr(` 多余参数 ${a}`, ` Unexpected argument ${a}`));
114
115
  process.exit(1);
115
116
  }
116
117
  }
@@ -123,6 +124,24 @@ function parseFlags(args) {
123
124
  };
124
125
  }
125
126
  function printHelp() {
127
+ if (isEnglish()) {
128
+ console.log("");
129
+ console.log(" Import past conversations and skills from Claude Code / Codex into u1s1");
130
+ console.log("");
131
+ console.log(" Usage:");
132
+ console.log(" u1s1 import Import conversations for the current directory");
133
+ console.log(" u1s1 import claude Claude Code only");
134
+ console.log(" u1s1 import codex Codex only");
135
+ console.log(" u1s1 import --all Import every conversation found on this machine");
136
+ console.log(" u1s1 import --cwd <dir> Project directory (default: current directory)");
137
+ console.log(" u1s1 import --dry-run Show what would be imported without writing anything");
138
+ console.log(" u1s1 import --force Re-import conversations already imported");
139
+ console.log(" u1s1 import skills Import skills (SKILL.md) from Claude Code / Codex and others; see u1s1 import skills -h");
140
+ console.log("");
141
+ console.log(" After importing, run u1s1 in that project and type /resume to see them.");
142
+ console.log("");
143
+ return;
144
+ }
126
145
  console.log("");
127
146
  console.log(" 把 Claude Code / Codex 的历史对话和技能导入 u1s1");
128
147
  console.log("");
@@ -179,65 +198,82 @@ function pendingSessions(sessions, index, force) {
179
198
  return { pending, alreadyImported };
180
199
  }
181
200
  function printNoSessions(opts) {
182
- const where = opts.cwd ? `当前目录 ${formatHomePath(opts.cwd)}` : "这台电脑";
183
201
  const who = opts.sources.map(sourceLabel).join(" / ");
184
202
  console.log("");
185
- console.log(` 在${where}没找到 ${who} 的历史对话。`);
203
+ if (isEnglish()) {
204
+ const where = opts.cwd ? `the current directory ${formatHomePath(opts.cwd)}` : "this computer";
205
+ console.log(` No ${who} conversations found in ${where}.`);
206
+ }
207
+ else {
208
+ const where = opts.cwd ? `当前目录 ${formatHomePath(opts.cwd)}` : "这台电脑";
209
+ console.log(` 在${where}没找到 ${who} 的历史对话。`);
210
+ }
186
211
  if (opts.cwd)
187
- console.log(" 想全盘扫一遍可以: u1s1 import --all");
212
+ console.log(tr(" 想全盘扫一遍可以: u1s1 import --all", " To scan everything: u1s1 import --all"));
188
213
  console.log("");
189
214
  }
190
215
  async function printSessionPreview({ sessions, pending, alreadyImported, opts, }) {
191
216
  console.log("");
192
- console.log(` 找到 ${sessions.length} 段对话`
193
- + (opts.cwd ? `(${formatHomePath(opts.cwd)})` : "")
194
- + (alreadyImported ? `,其中 ${alreadyImported} 段以前导过` : "")
195
- + "。");
217
+ console.log(isEnglish()
218
+ ? ` Found ${sessions.length} conversation(s)`
219
+ + (opts.cwd ? ` (${formatHomePath(opts.cwd)})` : "")
220
+ + (alreadyImported ? `, ${alreadyImported} already imported` : "")
221
+ + "."
222
+ : ` 找到 ${sessions.length} 段对话`
223
+ + (opts.cwd ? `(${formatHomePath(opts.cwd)})` : "")
224
+ + (alreadyImported ? `,其中 ${alreadyImported} 段以前导过` : "")
225
+ + "。");
196
226
  const preview = await Promise.all(pending.slice(0, 12).map(hydrateSession));
197
227
  for (const session of preview) {
198
- const title = session.title ? oneLine(session.title, 56) : "(无标题)";
228
+ const title = session.title ? oneLine(session.title, 56) : tr("(无标题)", "(untitled)");
199
229
  console.log(` · [${sourceLabel(session.source)}] ${title}`);
200
230
  if (!opts.cwd && session.cwd)
201
231
  console.log(` ${formatHomePath(session.cwd)}`);
202
232
  }
203
233
  if (pending.length > preview.length) {
204
- console.log(` …还有 ${pending.length - preview.length} 段`);
234
+ console.log(tr(` …还有 ${pending.length - preview.length} 段`, ` …and ${pending.length - preview.length} more`));
205
235
  }
206
236
  }
207
237
  async function shouldCancelImport(pending, opts, assumeYes) {
208
238
  if (pending.length === 0) {
209
239
  console.log("");
210
- console.log(" 没有新的可导。想重导一遍就加 --force。");
240
+ console.log(tr(" 没有新的可导。想重导一遍就加 --force。", " Nothing new to import. Add --force to re-import."));
211
241
  console.log("");
212
242
  return true;
213
243
  }
214
244
  if (opts.dryRun) {
215
245
  console.log("");
216
- console.log(` 预演结束,以上 ${pending.length} 段还没真正导入。`);
246
+ console.log(tr(` 预演结束,以上 ${pending.length} 段还没真正导入。`, ` Dry run complete; the ${pending.length} conversation(s) above were not imported.`));
217
247
  console.log("");
218
248
  return true;
219
249
  }
220
- if (assumeYes || await confirm(` 导入这 ${pending.length} 段?(回车=好 / n=取消) `)) {
250
+ if (assumeYes || await confirm(tr(` 导入这 ${pending.length} 段?(回车=好 / n=取消) `, ` Import these ${pending.length}? (Enter = yes / n = cancel) `))) {
221
251
  return false;
222
252
  }
223
- console.log(" 已取消。");
253
+ console.log(tr(" 已取消。", " Canceled."));
224
254
  return true;
225
255
  }
226
256
  function printImportSummary(summary) {
227
257
  console.log("");
228
- console.log(` ✓ 导入 ${summary.imported} 段`
229
- + (summary.skipped ? `,跳过 ${summary.skipped}` : "")
230
- + (summary.empty ? `,空对话 ${summary.empty}` : "")
231
- + (summary.errors ? `,失败 ${summary.errors}` : "")
232
- + "。");
258
+ console.log(isEnglish()
259
+ ? ` ✓ Imported ${summary.imported}`
260
+ + (summary.skipped ? `, skipped ${summary.skipped}` : "")
261
+ + (summary.empty ? `, empty ${summary.empty}` : "")
262
+ + (summary.errors ? `, failed ${summary.errors}` : "")
263
+ + "."
264
+ : ` ✓ 导入 ${summary.imported} 段`
265
+ + (summary.skipped ? `,跳过 ${summary.skipped}` : "")
266
+ + (summary.empty ? `,空对话 ${summary.empty}` : "")
267
+ + (summary.errors ? `,失败 ${summary.errors}` : "")
268
+ + "。");
233
269
  if (summary.imported > 0) {
234
- console.log(" 进对应项目跑 u1s1,输入 /resume 就能接着聊。");
270
+ console.log(tr(" 进对应项目跑 u1s1,输入 /resume 就能接着聊。", " Run u1s1 in that project and type /resume to pick up where you left off."));
235
271
  }
236
272
  for (const item of summary.items) {
237
273
  if (item.status === "error")
238
- console.log(` × ${item.title}: ${item.detail ?? "失败"}`);
274
+ console.log(` × ${item.title}: ${item.detail ?? tr("失败", "failed")}`);
239
275
  else if (item.status === "skipped" && item.detail)
240
- console.log(` · 跳过 ${item.title}: ${item.detail}`);
276
+ console.log(tr(` · 跳过 ${item.title}: ${item.detail}`, ` · Skipped ${item.title}: ${item.detail}`));
241
277
  }
242
278
  console.log("");
243
279
  }
@@ -292,7 +328,7 @@ function runImport(sessions, index, opts) {
292
328
  title,
293
329
  cwd: session.cwd,
294
330
  status: "skipped",
295
- detail: `原始记录太大(${formatBytes(size)}),先跳过`,
331
+ detail: tr(`原始记录太大(${formatBytes(size)}),先跳过`, `source log too large (${formatBytes(size)}), skipped`),
296
332
  });
297
333
  skipped += 1;
298
334
  continue;
@@ -305,7 +341,7 @@ function runImport(sessions, index, opts) {
305
341
  title,
306
342
  cwd: converted.cwd,
307
343
  status: "skipped",
308
- detail: "不属于当前目录",
344
+ detail: tr("不属于当前目录", "not in the current directory"),
309
345
  });
310
346
  skipped += 1;
311
347
  continue;