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.
- package/dist/agent-setup.d.ts +5 -0
- package/dist/agent-setup.js +31 -7
- 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 +139 -48
- package/dist/login.js +39 -28
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.js +76 -48
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.js +6 -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 +2 -2
- package/scripts/patch-pi.js +243 -278
- package/scripts/render-regression.mjs +55 -4
package/dist/error-humanize.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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 [
|
|
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 [
|
|
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(` 想反馈什么?(${
|
|
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 [
|
|
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 [
|
|
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
|
+
}
|
package/dist/import/index.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
193
|
-
|
|
194
|
-
|
|
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(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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;
|