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.
- package/README.md +5 -5
- package/dist/agent-setup.d.ts +7 -1
- package/dist/agent-setup.js +34 -10
- package/dist/api.js +42 -34
- package/dist/bench-report.js +12 -9
- package/dist/bench.js +34 -31
- package/dist/brand.d.ts +7 -2
- package/dist/brand.js +44 -13
- package/dist/config.d.ts +28 -2
- package/dist/config.js +52 -14
- package/dist/context-usage.js +12 -9
- package/dist/deploy.js +53 -47
- package/dist/device-auth.js +7 -5
- package/dist/embed.d.ts +2 -1
- package/dist/embed.js +2 -1
- package/dist/error-humanize.d.ts +3 -1
- package/dist/error-humanize.js +24 -9
- package/dist/feedback.d.ts +4 -2
- package/dist/feedback.js +60 -39
- 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 +153 -54
- package/dist/login.d.ts +13 -2
- package/dist/login.js +84 -27
- 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 +12 -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/site.d.ts +71 -0
- package/dist/site.js +123 -0
- package/dist/subagent.js +5 -4
- package/dist/tools.js +53 -37
- package/dist/update.js +47 -39
- package/dist/usage.d.ts +6 -4
- package/dist/usage.js +111 -53
- 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 +3 -3
- package/scripts/patch-pi.js +243 -278
- package/scripts/render-regression.mjs +55 -4
package/dist/feedback.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { readJsonResponseCapped } from "./api.js";
|
|
2
|
-
import { loadConfig, PROVIDER_ID, resolvePreferredModel, VERSION } from "./config.js";
|
|
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";
|
|
7
|
+
import { siteLinks, siteOf } from "./site.js";
|
|
6
8
|
/**
|
|
7
9
|
* `u1s1 feedback [text]` / 会话内 `/feedback`:一条命令直接建工单,并附上
|
|
8
10
|
* 版本、平台、最近一次请求编号等诊断快照(运营清单 E4),不用再手写邮件。
|
|
9
11
|
* 走 POST /v1/support/tickets(设备签名),201 返回 {id, url}。
|
|
10
12
|
*/
|
|
11
13
|
export const FEEDBACK_CATEGORIES = ["question", "bug", "billing", "suggestion", "other"];
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
});
|
|
19
22
|
export const FEEDBACK_SUBJECT_MAX = 50;
|
|
20
23
|
export const FEEDBACK_MESSAGE_MAX = 4_000;
|
|
21
|
-
|
|
24
|
+
/** 当前站点仪表盘的工单/留言区,提交失败或服务端没回链接时的兜底入口。 */
|
|
25
|
+
export function supportUrl(site = activeSite()) {
|
|
26
|
+
return siteLinks(site).support;
|
|
27
|
+
}
|
|
22
28
|
const MAX_TICKET_RESPONSE_BYTES = 64 * 1024;
|
|
23
29
|
/** `--bug` 等旗标可放在任意位置,其余 token 拼成正文。 */
|
|
24
30
|
export function parseFeedbackArgs(args) {
|
|
@@ -32,7 +38,7 @@ export function parseFeedbackArgs(args) {
|
|
|
32
38
|
continue;
|
|
33
39
|
}
|
|
34
40
|
if (arg.startsWith("--"))
|
|
35
|
-
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)`));
|
|
36
42
|
words.push(arg);
|
|
37
43
|
}
|
|
38
44
|
return { category, text: words.join(" ").trim() };
|
|
@@ -41,7 +47,7 @@ export function parseFeedbackArgs(args) {
|
|
|
41
47
|
export function buildFeedbackRequest(input) {
|
|
42
48
|
const message = input.text.replace(/\r\n?/g, "\n").trim();
|
|
43
49
|
if (!message)
|
|
44
|
-
throw new Error("反馈内容不能为空");
|
|
50
|
+
throw new Error(tr("反馈内容不能为空", "Feedback can't be empty"));
|
|
45
51
|
const firstLine = message.split("\n").find((l) => l.trim())?.replace(/\s+/g, " ").trim() ?? message;
|
|
46
52
|
const subject = firstLine.length > FEEDBACK_SUBJECT_MAX
|
|
47
53
|
? `${firstLine.slice(0, FEEDBACK_SUBJECT_MAX - 1)}…`
|
|
@@ -86,12 +92,12 @@ export async function submitFeedback(cfg, body) {
|
|
|
86
92
|
});
|
|
87
93
|
}
|
|
88
94
|
catch {
|
|
89
|
-
throw new Error(`连不上 ${cfg.baseUrl}
|
|
95
|
+
throw new Error(tr(`连不上 ${cfg.baseUrl},检查一下网络?`, `Can't reach ${cfg.baseUrl}. Check your network?`));
|
|
90
96
|
}
|
|
91
97
|
const value = await readJsonResponseCapped(resp, MAX_TICKET_RESPONSE_BYTES).catch(() => null);
|
|
92
98
|
const data = value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
93
99
|
if (resp.status === 201 || resp.status === 200)
|
|
94
|
-
return createdOutcome(resp.status, data);
|
|
100
|
+
return createdOutcome(resp.status, data, siteOf(cfg));
|
|
95
101
|
return {
|
|
96
102
|
ok: false,
|
|
97
103
|
status: resp.status,
|
|
@@ -99,11 +105,11 @@ export async function submitFeedback(cfg, body) {
|
|
|
99
105
|
retryAfterSeconds: retryAfterSeconds(resp, data),
|
|
100
106
|
};
|
|
101
107
|
}
|
|
102
|
-
function createdOutcome(status, data) {
|
|
108
|
+
function createdOutcome(status, data, site) {
|
|
103
109
|
const id = typeof data?.id === "string" || typeof data?.id === "number" ? String(data.id) : "";
|
|
104
110
|
if (!id)
|
|
105
|
-
return { ok: false, status, message: "服务端工单响应格式不正确" };
|
|
106
|
-
const url = typeof data?.url === "string" && data.url.startsWith("https://") ? data.url :
|
|
111
|
+
return { ok: false, status, message: tr("服务端工单响应格式不正确", "Unexpected support ticket response from the server") };
|
|
112
|
+
const url = typeof data?.url === "string" && data.url.startsWith("https://") ? data.url : supportUrl(site);
|
|
107
113
|
return { ok: true, id, url };
|
|
108
114
|
}
|
|
109
115
|
/** 优先 Retry-After 头,其次响应体里的 retry_after;都没有就不给等待时长。 */
|
|
@@ -117,32 +123,40 @@ function retryAfterSeconds(resp, data) {
|
|
|
117
123
|
function failureMessage(status, data) {
|
|
118
124
|
if (typeof data?.error?.message === "string")
|
|
119
125
|
return data.error.message.slice(0, 500);
|
|
120
|
-
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.`);
|
|
121
129
|
}
|
|
122
130
|
/** 结果措辞,终端与会话内共用(每项一行)。 */
|
|
123
|
-
export function feedbackOutcomeLines(outcome) {
|
|
131
|
+
export function feedbackOutcomeLines(outcome, site = activeSite()) {
|
|
124
132
|
if (outcome.ok) {
|
|
125
133
|
return [
|
|
126
|
-
`✅ 反馈已提交,工单编号 ${outcome.id}。`,
|
|
127
|
-
`进度与回复在这里看:${outcome.url}`,
|
|
128
|
-
"我们通常会在 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."),
|
|
129
137
|
];
|
|
130
138
|
}
|
|
131
139
|
if (outcome.status === 429) {
|
|
132
140
|
const wait = outcome.retryAfterSeconds
|
|
133
|
-
? `请等 ${Math.ceil(outcome.retryAfterSeconds / 60) || 1}
|
|
134
|
-
: "请稍等几分钟再试";
|
|
135
|
-
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
|
+
];
|
|
136
147
|
}
|
|
137
|
-
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
|
+
];
|
|
138
152
|
}
|
|
139
153
|
export function printFeedbackHelp() {
|
|
140
154
|
console.log("");
|
|
141
|
-
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]"));
|
|
142
156
|
console.log("");
|
|
143
|
-
console.log(" 不带描述时会问你一行;分类不写默认「其他」。");
|
|
144
|
-
console.log(" 会自动附上版本、平台、最近一次报错的请求编号,方便我们定位。");
|
|
145
|
-
console.log(` 也可以在后台留言:${
|
|
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()}`));
|
|
146
160
|
console.log("");
|
|
147
161
|
}
|
|
148
162
|
/** 终端命令入口。 */
|
|
@@ -154,16 +168,16 @@ export async function feedbackCommand(args) {
|
|
|
154
168
|
}
|
|
155
169
|
const cfg = loadConfig();
|
|
156
170
|
if (!hasDeviceCredential(cfg)) {
|
|
157
|
-
console.error("还没登录,先跑 u1s1 login");
|
|
171
|
+
console.error(tr("还没登录,先跑 u1s1 login", "Not signed in. Run u1s1 login first."));
|
|
158
172
|
process.exitCode = 1;
|
|
159
173
|
return;
|
|
160
174
|
}
|
|
161
175
|
let text = parsed.text;
|
|
162
176
|
if (!text) {
|
|
163
|
-
text = await askLine(` 想反馈什么?(${
|
|
177
|
+
text = await askLine(tr(` 想反馈什么?(${categoryLabels()[parsed.category]},一行说清即可):`, ` What's your feedback? (${categoryLabels()[parsed.category]}, one line is enough): `));
|
|
164
178
|
}
|
|
165
179
|
if (!text) {
|
|
166
|
-
console.error("反馈内容不能为空。用法:u1s1 feedback \"一句话描述\"");
|
|
180
|
+
console.error(tr("反馈内容不能为空。用法:u1s1 feedback \"一句话描述\"", "Feedback can't be empty. Usage: u1s1 feedback \"short description\""));
|
|
167
181
|
process.exitCode = 1;
|
|
168
182
|
return;
|
|
169
183
|
}
|
|
@@ -174,7 +188,7 @@ export async function feedbackCommand(args) {
|
|
|
174
188
|
});
|
|
175
189
|
const outcome = await submitFeedback(cfg, body);
|
|
176
190
|
console.log("");
|
|
177
|
-
for (const line of feedbackOutcomeLines(outcome))
|
|
191
|
+
for (const line of feedbackOutcomeLines(outcome, siteOf(cfg)))
|
|
178
192
|
console.log(` ${line}`);
|
|
179
193
|
console.log("");
|
|
180
194
|
if (!outcome.ok)
|
|
@@ -187,22 +201,29 @@ export async function feedbackCommand(args) {
|
|
|
187
201
|
export async function feedbackInSession(rawArgs, model) {
|
|
188
202
|
const parsed = parseFeedbackArgs(rawArgs.split(/\s+/).filter(Boolean));
|
|
189
203
|
if (parsed.help) {
|
|
190
|
-
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
|
+
];
|
|
191
208
|
}
|
|
192
209
|
const cfg = loadConfig();
|
|
193
210
|
if (!hasDeviceCredential(cfg))
|
|
194
|
-
return ["还没登录,先在终端运行 u1s1 login"];
|
|
195
|
-
if (!parsed.text)
|
|
196
|
-
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
|
+
}
|
|
197
215
|
const body = buildFeedbackRequest({
|
|
198
216
|
text: parsed.text,
|
|
199
217
|
category: parsed.category,
|
|
200
218
|
diagnostics: collectDiagnostics({ model, surface: "session" }),
|
|
201
219
|
});
|
|
202
220
|
try {
|
|
203
|
-
return feedbackOutcomeLines(await submitFeedback(cfg, body));
|
|
221
|
+
return feedbackOutcomeLines(await submitFeedback(cfg, body), siteOf(cfg));
|
|
204
222
|
}
|
|
205
223
|
catch (e) {
|
|
206
|
-
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
|
+
];
|
|
207
228
|
}
|
|
208
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;
|