u1s1-cli 1.11.8 → 1.13.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 +19 -3
- package/dist/agent-setup.js +85 -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 +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 +140 -49
- package/dist/login.js +39 -28
- package/dist/loop.js +18 -12
- package/dist/mcp/client.js +15 -14
- package/dist/mcp/command.d.ts +2 -2
- package/dist/mcp/command.js +109 -69
- package/dist/mcp/config.d.ts +22 -6
- package/dist/mcp/config.js +0 -0
- package/dist/mcp/extension.js +5 -3
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +10 -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 +4 -4
- package/scripts/patch-pi.js +347 -278
- package/scripts/render-regression.mjs +55 -4
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;
|
package/dist/import/skills.js
CHANGED
|
@@ -4,13 +4,17 @@ import { basename, join, resolve } from "node:path";
|
|
|
4
4
|
import { loadSkillsFromDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { formatHomePath } from "../brand.js";
|
|
6
6
|
import { agentDir } from "../config.js";
|
|
7
|
+
import { isEnglish, tr } from "../i18n.js";
|
|
7
8
|
import { asRecord, asString, formatBytes, listHomeClaudeDirs, oneLine, uniqueExistingDirs, confirm } from "./util.js";
|
|
8
9
|
import { readJsonIfExists, writeJson } from "./write.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// 用时求值:界面语言要等配置读完才定
|
|
11
|
+
function toolLabel(tool) {
|
|
12
|
+
if (tool === "claude")
|
|
13
|
+
return "Claude Code";
|
|
14
|
+
if (tool === "codex")
|
|
15
|
+
return "Codex";
|
|
16
|
+
return tr("自定义目录", "Custom directory");
|
|
17
|
+
}
|
|
14
18
|
const SCRIPT_EXTENSIONS = new Set([".sh", ".bash", ".zsh", ".ps1", ".bat", ".cmd", ".py", ".js", ".mjs", ".cjs", ".ts", ".rb", ".pl"]);
|
|
15
19
|
/** 超过这个体积的技能目录多半塞了数据集或依赖,先不动。 */
|
|
16
20
|
export const MAX_SKILL_BYTES = 20 * 1024 * 1024;
|
|
@@ -38,12 +42,12 @@ export function defaultSkillSources(cwd, home = homedir()) {
|
|
|
38
42
|
if (!dir || seen.has(dir))
|
|
39
43
|
continue;
|
|
40
44
|
seen.add(dir);
|
|
41
|
-
out.push({ tool: c.tool, label:
|
|
45
|
+
out.push({ tool: c.tool, label: toolLabel(c.tool), dir, scope: c.scope });
|
|
42
46
|
}
|
|
43
47
|
return out;
|
|
44
48
|
}
|
|
45
49
|
export function customSkillSource(dir) {
|
|
46
|
-
return { tool: "custom", label:
|
|
50
|
+
return { tool: "custom", label: toolLabel("custom"), dir: resolve(dir), scope: "custom" };
|
|
47
51
|
}
|
|
48
52
|
function walkStats(dir, stats, depth = 0) {
|
|
49
53
|
if (stats.truncated || depth > 16)
|
|
@@ -87,9 +91,9 @@ function walkStats(dir, stats, depth = 0) {
|
|
|
87
91
|
function skillHints(stats) {
|
|
88
92
|
const hints = [];
|
|
89
93
|
if (stats.scripts > 0)
|
|
90
|
-
hints.push(`含 ${stats.scripts}
|
|
94
|
+
hints.push(tr(`含 ${stats.scripts} 个脚本,只导可信来源`, `contains ${stats.scripts} script(s); only import from trusted sources`));
|
|
91
95
|
if (stats.bytes > LARGE_SKILL_BYTES)
|
|
92
|
-
hints.push(`体积 ${formatBytes(stats.bytes)}`);
|
|
96
|
+
hints.push(tr(`体积 ${formatBytes(stats.bytes)}`, `size ${formatBytes(stats.bytes)}`));
|
|
93
97
|
return hints;
|
|
94
98
|
}
|
|
95
99
|
const SKILL_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -137,7 +141,8 @@ export function discoverSkills(sources) {
|
|
|
137
141
|
const singleFile = basename(skill.filePath) !== "SKILL.md";
|
|
138
142
|
const name = singleFile ? singleFileSkillName(skill.filePath, skill.name) : skill.name;
|
|
139
143
|
if (!SKILL_NAME_RE.test(name) || name.length > 64) {
|
|
140
|
-
problems.push({ path: skill.filePath, reason: `技能名「${name}
|
|
144
|
+
problems.push({ path: skill.filePath, reason: tr(`技能名「${name}」不合规:只能用小写字母、数字和连字符`, `invalid skill name "${name}": only lowercase letters, digits and hyphens are allowed`),
|
|
145
|
+
});
|
|
141
146
|
continue;
|
|
142
147
|
}
|
|
143
148
|
const stats = { files: 0, bytes: 0, scripts: 0, truncated: false };
|
|
@@ -156,7 +161,7 @@ export function discoverSkills(sources) {
|
|
|
156
161
|
if (stats.truncated) {
|
|
157
162
|
problems.push({
|
|
158
163
|
path: singleFile ? skill.filePath : skill.baseDir,
|
|
159
|
-
reason: `技能目录太大(超过 ${formatBytes(MAX_SKILL_BYTES)} 或 ${MAX_SKILL_FILES} 个文件),先跳过`,
|
|
164
|
+
reason: tr(`技能目录太大(超过 ${formatBytes(MAX_SKILL_BYTES)} 或 ${MAX_SKILL_FILES} 个文件),先跳过`, `skill directory too large (over ${formatBytes(MAX_SKILL_BYTES)} or ${MAX_SKILL_FILES} files), skipped`),
|
|
160
165
|
});
|
|
161
166
|
continue;
|
|
162
167
|
}
|
|
@@ -227,7 +232,7 @@ export function planSkillImport(skills, opts) {
|
|
|
227
232
|
export function copySkill(skill, destDir) {
|
|
228
233
|
const dest = join(destDir, skill.name);
|
|
229
234
|
if (resolve(skill.baseDir) === resolve(dest))
|
|
230
|
-
throw new Error("来源就是目标目录");
|
|
235
|
+
throw new Error(tr("来源就是目标目录", "Source is the destination directory"));
|
|
231
236
|
mkdirSync(destDir, { recursive: true });
|
|
232
237
|
rmSync(dest, { recursive: true, force: true });
|
|
233
238
|
if (skill.singleFile) {
|
|
@@ -270,10 +275,13 @@ export function runSkillImport(pending, index, destDir) {
|
|
|
270
275
|
export function removeImportedSkill(name, index, destDir) {
|
|
271
276
|
const record = index.items[name];
|
|
272
277
|
if (!record)
|
|
273
|
-
return { ok: false, reason: "不是 u1s1 import skills 导入的技能,请手动处理" };
|
|
278
|
+
return { ok: false, reason: tr("不是 u1s1 import skills 导入的技能,请手动处理", "not imported by u1s1 import skills; please handle it manually") };
|
|
274
279
|
const expected = join(destDir, name);
|
|
275
280
|
if (resolve(record.destPath) !== resolve(expected)) {
|
|
276
|
-
return {
|
|
281
|
+
return {
|
|
282
|
+
ok: false,
|
|
283
|
+
reason: tr(`记录的位置 ${record.destPath} 与技能目录不一致,请手动处理`, `recorded location ${record.destPath} doesn't match the skills directory; please handle it manually`),
|
|
284
|
+
};
|
|
277
285
|
}
|
|
278
286
|
try {
|
|
279
287
|
if (existsSync(expected) && lstatSync(expected).isDirectory())
|
|
@@ -299,7 +307,7 @@ function parseSkillsArgs(args) {
|
|
|
299
307
|
const needValue = (flag, i) => {
|
|
300
308
|
const next = args[i + 1];
|
|
301
309
|
if (!next || next.startsWith("-")) {
|
|
302
|
-
console.error(` ${flag}
|
|
310
|
+
console.error(tr(` ${flag} 后面要跟一个值`, ` ${flag} requires a value`));
|
|
303
311
|
process.exit(1);
|
|
304
312
|
}
|
|
305
313
|
return next;
|
|
@@ -321,7 +329,7 @@ function parseSkillsArgs(args) {
|
|
|
321
329
|
else if (a === "--remove")
|
|
322
330
|
opts.remove.push(needValue(a, i++));
|
|
323
331
|
else if (a.startsWith("-")) {
|
|
324
|
-
console.error(` 不认识参数 ${a}`);
|
|
332
|
+
console.error(tr(` 不认识参数 ${a}`, ` Unknown option ${a}`));
|
|
325
333
|
process.exit(1);
|
|
326
334
|
}
|
|
327
335
|
else {
|
|
@@ -342,12 +350,32 @@ function parseToolArg(raw) {
|
|
|
342
350
|
};
|
|
343
351
|
const tool = aliases[raw.toLowerCase()];
|
|
344
352
|
if (!tool) {
|
|
345
|
-
console.error(` 不认识来源「${raw}」,可选: claude / codex;其他工具用 --from
|
|
353
|
+
console.error(tr(` 不认识来源「${raw}」,可选: claude / codex;其他工具用 --from 目录`, ` Unknown source "${raw}"; choose claude / codex, or use --from <dir> for other tools`));
|
|
346
354
|
process.exit(1);
|
|
347
355
|
}
|
|
348
356
|
return tool;
|
|
349
357
|
}
|
|
350
358
|
export function printSkillsHelp() {
|
|
359
|
+
if (isEnglish()) {
|
|
360
|
+
console.log("");
|
|
361
|
+
console.log(" Import skills (SKILL.md) from Claude Code / Codex and other tools into u1s1");
|
|
362
|
+
console.log("");
|
|
363
|
+
console.log(" Usage:");
|
|
364
|
+
console.log(" u1s1 import skills Find Claude Code / Codex skills on this machine");
|
|
365
|
+
console.log(" u1s1 import skills claude Claude Code only");
|
|
366
|
+
console.log(" u1s1 import skills codex Codex only");
|
|
367
|
+
console.log(" u1s1 import skills --from <dir> Other tools: a directory containing SKILL.md files (repeatable)");
|
|
368
|
+
console.log(" u1s1 import skills --cwd <dir> Look for project skills in this directory (default: current directory)");
|
|
369
|
+
console.log(" u1s1 import skills --dry-run Show what would be imported without writing anything");
|
|
370
|
+
console.log(" u1s1 import skills --update Re-copy skills already imported (same as --force)");
|
|
371
|
+
console.log(" u1s1 import skills --list List imported skills");
|
|
372
|
+
console.log(" u1s1 import skills --remove <name> Remove an imported skill");
|
|
373
|
+
console.log("");
|
|
374
|
+
console.log(` Skills are copied to ${formatHomePath(skillsDestDir())} and are available from your next chat:`);
|
|
375
|
+
console.log(" the model invokes them automatically based on their description, or type /skill:<name> to invoke one manually.");
|
|
376
|
+
console.log("");
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
351
379
|
console.log("");
|
|
352
380
|
console.log(" 把 Claude Code / Codex 等工具的技能(SKILL.md)导入 u1s1");
|
|
353
381
|
console.log("");
|
|
@@ -373,37 +401,38 @@ function printList(index) {
|
|
|
373
401
|
const items = Object.values(index.items).sort((a, b) => a.name.localeCompare(b.name));
|
|
374
402
|
console.log("");
|
|
375
403
|
if (items.length === 0) {
|
|
376
|
-
console.log(" 还没导入过技能。跑 u1s1 import skills 试试。");
|
|
404
|
+
console.log(tr(" 还没导入过技能。跑 u1s1 import skills 试试。", " No skills imported yet. Try u1s1 import skills."));
|
|
377
405
|
console.log("");
|
|
378
406
|
return;
|
|
379
407
|
}
|
|
380
|
-
console.log(` 已导入 ${items.length} 个技能(${formatHomePath(skillsDestDir())}):`);
|
|
408
|
+
console.log(tr(` 已导入 ${items.length} 个技能(${formatHomePath(skillsDestDir())}):`, ` ${items.length} imported skill(s) (${formatHomePath(skillsDestDir())}):`));
|
|
381
409
|
for (const item of items) {
|
|
382
|
-
const missing = existsSync(item.destPath) ? "" : "(目录已不在)";
|
|
383
|
-
console.log(` · ${item.name} [${
|
|
384
|
-
console.log(` 来自 ${formatHomePath(item.sourcePath)}`);
|
|
410
|
+
const missing = existsSync(item.destPath) ? "" : tr("(目录已不在)", "(directory missing)");
|
|
411
|
+
console.log(` · ${item.name} [${toolLabel(item.tool)}] ${missing}`);
|
|
412
|
+
console.log(tr(` 来自 ${formatHomePath(item.sourcePath)}`, ` from ${formatHomePath(item.sourcePath)}`));
|
|
385
413
|
}
|
|
386
414
|
console.log("");
|
|
387
415
|
}
|
|
388
416
|
function printPreview(skills, plan, problems, sources) {
|
|
389
417
|
console.log("");
|
|
390
|
-
console.log(` 在 ${sources.length} 个目录里找到 ${skills.length}
|
|
418
|
+
console.log(tr(` 在 ${sources.length} 个目录里找到 ${skills.length} 个技能:`, ` Found ${skills.length} skill(s) in ${sources.length} director${sources.length === 1 ? "y" : "ies"}:`));
|
|
391
419
|
for (const source of sources)
|
|
392
420
|
console.log(` ${formatHomePath(source.dir)} [${source.label}]`);
|
|
393
421
|
console.log("");
|
|
394
422
|
for (const skill of plan.pending) {
|
|
395
423
|
console.log(` + ${skill.name} [${skill.label}] ${oneLine(skill.description, 60)}`);
|
|
396
|
-
console.log(` ${describeSource(skill)}${skill.hints.length ? ` ⚠ ${skill.hints.join(";")}` : ""}`);
|
|
424
|
+
console.log(` ${describeSource(skill)}${skill.hints.length ? ` ⚠ ${skill.hints.join(tr(";", "; "))}` : ""}`);
|
|
397
425
|
}
|
|
398
426
|
for (const skill of plan.alreadyImported)
|
|
399
|
-
console.log(` · ${skill.name} 已导过(加 --update 重导)`);
|
|
427
|
+
console.log(tr(` · ${skill.name} 已导过(加 --update 重导)`, ` · ${skill.name} already imported (add --update to re-import)`));
|
|
400
428
|
for (const skill of plan.conflicts) {
|
|
401
|
-
console.log(` · ${skill.name} 目标目录已有同名技能(不是本命令导的)
|
|
429
|
+
console.log(tr(` · ${skill.name} 目标目录已有同名技能(不是本命令导的),不覆盖`, ` · ${skill.name} a skill with this name already exists (not imported by this command); not overwriting`));
|
|
430
|
+
}
|
|
431
|
+
for (const skill of plan.duplicates) {
|
|
432
|
+
console.log(tr(` · ${skill.name} 与前面同名,只保留先发现的(${describeSource(skill)})`, ` · ${skill.name} duplicate name; keeping the first one found (${describeSource(skill)})`));
|
|
402
433
|
}
|
|
403
|
-
for (const skill of plan.duplicates)
|
|
404
|
-
console.log(` · ${skill.name} 与前面同名,只保留先发现的(${describeSource(skill)})`);
|
|
405
434
|
for (const problem of problems) {
|
|
406
|
-
console.log(` × ${formatHomePath(problem.path)} 不兼容,跳过: ${oneLine(problem.reason, 80)}`);
|
|
435
|
+
console.log(tr(` × ${formatHomePath(problem.path)} 不兼容,跳过: ${oneLine(problem.reason, 80)}`, ` × ${formatHomePath(problem.path)} incompatible, skipped: ${oneLine(problem.reason, 80)}`));
|
|
407
436
|
}
|
|
408
437
|
}
|
|
409
438
|
export async function importSkillsCommand(args) {
|
|
@@ -418,7 +447,7 @@ export async function importSkillsCommand(args) {
|
|
|
418
447
|
for (const name of opts.remove) {
|
|
419
448
|
const result = removeImportedSkill(name, index, destDir);
|
|
420
449
|
if (result.ok)
|
|
421
|
-
console.log(` ✓ 已删除技能 ${name}`);
|
|
450
|
+
console.log(tr(` ✓ 已删除技能 ${name}`, ` ✓ Removed skill ${name}`));
|
|
422
451
|
else
|
|
423
452
|
console.log(` × ${name}: ${result.reason}`);
|
|
424
453
|
}
|
|
@@ -435,11 +464,11 @@ export async function importSkillsCommand(args) {
|
|
|
435
464
|
];
|
|
436
465
|
const missingFrom = opts.from.filter((dir) => !existsSync(dir));
|
|
437
466
|
for (const dir of missingFrom)
|
|
438
|
-
console.log(` --from 目录不存在: ${dir}`);
|
|
467
|
+
console.log(tr(` --from 目录不存在: ${dir}`, ` --from directory doesn't exist: ${dir}`));
|
|
439
468
|
if (sources.length === 0) {
|
|
440
469
|
console.log("");
|
|
441
|
-
console.log(" 没找到 Claude Code / Codex 的技能目录(~/.claude/skills、~/.codex/skills、项目里的 .claude/skills 或 .agents/skills)。");
|
|
442
|
-
console.log(" 其他工具的技能可以用 --from 指定目录,只要里面有 SKILL.md。");
|
|
470
|
+
console.log(tr(" 没找到 Claude Code / Codex 的技能目录(~/.claude/skills、~/.codex/skills、项目里的 .claude/skills 或 .agents/skills)。", " No Claude Code / Codex skill directories found (~/.claude/skills, ~/.codex/skills, or .claude/skills / .agents/skills in the project)."));
|
|
471
|
+
console.log(tr(" 其他工具的技能可以用 --from 指定目录,只要里面有 SKILL.md。", " For other tools, point --from at any directory containing SKILL.md files."));
|
|
443
472
|
console.log("");
|
|
444
473
|
return;
|
|
445
474
|
}
|
|
@@ -448,18 +477,20 @@ export async function importSkillsCommand(args) {
|
|
|
448
477
|
printPreview(skills, plan, problems, sources);
|
|
449
478
|
if (plan.pending.length === 0) {
|
|
450
479
|
console.log("");
|
|
451
|
-
console.log(skills.length === 0
|
|
480
|
+
console.log(skills.length === 0
|
|
481
|
+
? tr(" 这些目录里没有能用的技能。", " No usable skills in these directories.")
|
|
482
|
+
: tr(" 没有新的可导。想重导一遍就加 --update。", " Nothing new to import. Add --update to re-import."));
|
|
452
483
|
console.log("");
|
|
453
484
|
return;
|
|
454
485
|
}
|
|
455
486
|
if (opts.dryRun) {
|
|
456
487
|
console.log("");
|
|
457
|
-
console.log(` 预演结束,以上 ${plan.pending.length}
|
|
488
|
+
console.log(tr(` 预演结束,以上 ${plan.pending.length} 个还没真正导入。`, ` Dry run complete; the ${plan.pending.length} skill(s) above were not imported.`));
|
|
458
489
|
console.log("");
|
|
459
490
|
return;
|
|
460
491
|
}
|
|
461
|
-
if (!opts.yes && !(await confirm(` 导入这 ${plan.pending.length} 个技能?(回车=好 / n=取消) `))) {
|
|
462
|
-
console.log(" 已取消。");
|
|
492
|
+
if (!opts.yes && !(await confirm(tr(` 导入这 ${plan.pending.length} 个技能?(回车=好 / n=取消) `, ` Import these ${plan.pending.length} skill(s)? (Enter = yes / n = cancel) `)))) {
|
|
493
|
+
console.log(tr(" 已取消。", " Canceled."));
|
|
463
494
|
return;
|
|
464
495
|
}
|
|
465
496
|
const results = runSkillImport(plan.pending, index, destDir);
|
|
@@ -467,12 +498,12 @@ export async function importSkillsCommand(args) {
|
|
|
467
498
|
const imported = results.filter((r) => r.status === "imported").length;
|
|
468
499
|
const errors = results.filter((r) => r.status === "error");
|
|
469
500
|
console.log("");
|
|
470
|
-
console.log(` ✓ 导入 ${imported} 个技能到 ${formatHomePath(destDir)}${errors.length ? `,失败 ${errors.length}` : ""}
|
|
501
|
+
console.log(tr(` ✓ 导入 ${imported} 个技能到 ${formatHomePath(destDir)}${errors.length ? `,失败 ${errors.length}` : ""}。`, ` ✓ Imported ${imported} skill(s) to ${formatHomePath(destDir)}${errors.length ? `, ${errors.length} failed` : ""}.`));
|
|
471
502
|
for (const item of errors)
|
|
472
|
-
console.log(` × ${item.name}: ${item.detail ?? "失败"}`);
|
|
503
|
+
console.log(` × ${item.name}: ${item.detail ?? tr("失败", "failed")}`);
|
|
473
504
|
if (imported > 0) {
|
|
474
|
-
console.log(" 下次进 u1s1 对话就能用:模型会按说明自动调用,或输入 /skill:名称 手动调用。");
|
|
475
|
-
console.log(
|
|
505
|
+
console.log(tr(" 下次进 u1s1 对话就能用:模型会按说明自动调用,或输入 /skill:名称 手动调用。", " Available from your next u1s1 chat: the model invokes them based on their description, or type /skill:<name>."));
|
|
506
|
+
console.log(tr(" 管理: u1s1 import skills --list / --update / --remove 名称", " Manage: u1s1 import skills --list / --update / --remove <name>"));
|
|
476
507
|
}
|
|
477
508
|
console.log("");
|
|
478
509
|
}
|
package/dist/import/util.js
CHANGED
|
@@ -3,6 +3,7 @@ import { stdin as input, stdout as output } from "node:process";
|
|
|
3
3
|
import { closeSync, existsSync, openSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { tr } from "../i18n.js";
|
|
6
7
|
export const MAX_TOOL_RESULT_CHARS = 80_000;
|
|
7
8
|
export const MAX_TEXT_CHARS = 200_000;
|
|
8
9
|
export const PREVIEW_TITLE_CHARS = 48;
|
|
@@ -133,11 +134,11 @@ export function truncateText(text, max) {
|
|
|
133
134
|
if (text.length <= max)
|
|
134
135
|
return text;
|
|
135
136
|
const omitted = text.length - max;
|
|
136
|
-
return `${text.slice(0, max)}\n\n[已截断,原文还有约 ${formatBytes(omitted)} ]`;
|
|
137
|
+
return `${text.slice(0, max)}\n\n${tr(`[已截断,原文还有约 ${formatBytes(omitted)} ]`, `[truncated, about ${formatBytes(omitted)} more omitted]`)}`;
|
|
137
138
|
}
|
|
138
139
|
export function formatBytes(n) {
|
|
139
140
|
if (n < 1024)
|
|
140
|
-
return `${n}
|
|
141
|
+
return tr(`${n} 字`, `${n} B`);
|
|
141
142
|
if (n < 1024 * 1024)
|
|
142
143
|
return `${(n / 1024).toFixed(1)} KB`;
|
|
143
144
|
return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
package/dist/import/write.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import { agentDir, loadConfig, resolvePreferredModel } from "../config.js";
|
|
5
|
+
import { tr } from "../i18n.js";
|
|
5
6
|
const EMPTY_USAGE = {
|
|
6
7
|
input: 0,
|
|
7
8
|
output: 0,
|
|
@@ -78,7 +79,7 @@ export function writeConvertedSession(converted, destCwd) {
|
|
|
78
79
|
sm.appendModelChange(pref.provider, pref.id);
|
|
79
80
|
const dest = sm.getSessionFile();
|
|
80
81
|
if (!dest)
|
|
81
|
-
throw new Error("会话写盘失败");
|
|
82
|
+
throw new Error(tr("会话写盘失败", "Failed to write session to disk"));
|
|
82
83
|
return dest;
|
|
83
84
|
}
|
|
84
85
|
export function readJsonIfExists(path) {
|