pi-zh-cn 0.1.1 → 0.2.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 +23 -32
- package/extensions/index.ts +286 -316
- package/extensions/{i18n.ts → zh.ts} +40 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,58 +1,49 @@
|
|
|
1
1
|
# pi-zh-cn
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
纯扩展 API 实现,不 patch 源码、不侵入安装;正常情况下可随 pi 升级保持兼容。
|
|
3
|
+
把 [pi](https://github.com/earendil-works/pi) 编码代理的界面变成简体中文。装上重启即可,不想要了随时卸载。
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
原理上它只是一个普通扩展:调用官方扩展 API 替换文案,不打补丁、不改安装目录里的任何文件,所以 pi 正常升级后一般也能直接用。
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
10
|
-
| 启动 header(标题 + 快捷键提示行 + 引导语) | `ctx.ui.setHeader()`,快捷键跟随用户键位配置 |
|
|
11
|
-
| 流式加载提示(默认 "Working...") | `ctx.ui.setWorkingMessage()` → 「正在处理…」 |
|
|
12
|
-
| thinking 折叠块标签 | `ctx.ui.setHiddenThinkingLabel()` → 「思考过程已折叠」 |
|
|
13
|
-
| 底部状态栏 | `ctx.ui.setFooter()` 中文重建:目录/分支/会话名、token/缓存/费用/上下文用量、模型/思考等级、扩展状态行 |
|
|
14
|
-
| read 工具渲染 | 同名重注册,标签「读取」+ 中文状态文案 |
|
|
15
|
-
| bash 工具渲染 | 同名重注册,标签「执行」+「完成 / 退出码 N / 输出过长已截断」 |
|
|
16
|
-
| edit 工具渲染 | 同名重注册,标签「编辑」+「已修改 / +N / -N」 |
|
|
17
|
-
| write 工具渲染 | 同名重注册,标签「写入」+「已写入」 |
|
|
18
|
-
| grep 工具渲染 | 同名重注册,标签「搜索内容」+ 中文搜索结果状态 |
|
|
19
|
-
| find 工具渲染 | 同名重注册,标签「查找文件」+ 中文查找结果状态 |
|
|
20
|
-
| ls 工具渲染 | 同名重注册,标签「列出目录」+ 中文目录状态 |
|
|
21
|
-
| PowerShell 工具渲染 | 同名重注册,标签「执行 PowerShell」+ 中文执行状态 |
|
|
7
|
+
## 会变成中文的部分
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
- 启动画面:标题、快捷键提示、引导语(快捷键显示跟随你自己的键位配置)
|
|
10
|
+
- 底部状态栏:当前目录 / 分支 / 会话名、token 与缓存用量、费用、上下文占比、模型和思考等级
|
|
11
|
+
- 加载提示:「正在处理…」;thinking 折叠块的标签
|
|
12
|
+
- 八个内置工具的执行结果:读取、执行(bash)、编辑、写入、搜索内容、查找文件、列出目录、PowerShell——状态行都是中文,比如「完成」「退出码 N」「输出过长已截断」
|
|
25
13
|
|
|
26
|
-
|
|
14
|
+
发给模型的内容不变。工具的 description 保持英文原文,避免影响模型行为。
|
|
27
15
|
|
|
28
|
-
|
|
16
|
+
## 保持原样的部分
|
|
29
17
|
|
|
30
|
-
|
|
18
|
+
- 内置菜单和其他硬编码在 pi 源码里的英文(扩展 API 够不到,除非 fork 上游重编译,不值得)
|
|
19
|
+
- 工具的启用范围:`grep`、`find`、`ls` 或 `powershell` 没开就不会被这个插件偷偷打开
|
|
31
20
|
|
|
32
|
-
|
|
33
|
-
# 本地路径安装(路径引用,不复制文件)
|
|
34
|
-
pi install /path/to/pi-zh
|
|
21
|
+
## 使用
|
|
35
22
|
|
|
36
|
-
|
|
23
|
+
```bash
|
|
24
|
+
# 从 npm 安装
|
|
37
25
|
pi install npm:pi-zh-cn
|
|
38
26
|
|
|
39
|
-
#
|
|
27
|
+
# 查看/卸载用这里显示的 source 名
|
|
40
28
|
pi list
|
|
41
29
|
```
|
|
42
30
|
|
|
43
|
-
|
|
31
|
+
觉得中文状态栏不如原生好读,可以临时切回英文:
|
|
44
32
|
|
|
45
|
-
|
|
46
|
-
|
|
33
|
+
```text
|
|
34
|
+
/footer
|
|
35
|
+
```
|
|
47
36
|
|
|
48
37
|
## 开发
|
|
49
38
|
|
|
50
39
|
```bash
|
|
51
|
-
npm install
|
|
40
|
+
npm install
|
|
52
41
|
./node_modules/.bin/tsc -p . # 类型检查
|
|
53
42
|
|
|
54
43
|
# 本地试跑
|
|
55
44
|
pi --no-session -e ./extensions/index.ts -p "hi"
|
|
56
45
|
```
|
|
57
46
|
|
|
58
|
-
|
|
47
|
+
所有文案都集中在 [`extensions/zh.ts`](extensions/zh.ts),想改词只动这一个文件就够了。
|
|
48
|
+
|
|
49
|
+
改动记录见 [CHANGELOG.md](CHANGELOG.md)。pi 大版本升级后如果出现错位或残留英文,欢迎提 issue。
|
package/extensions/index.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* - thinking 折叠块标签(setHiddenThinkingLabel)
|
|
7
7
|
* - 启动 header(setHeader)
|
|
8
8
|
* - 底部状态栏(setFooter)
|
|
9
|
+
* - 项目信任弹窗(project_trust handler + 中文 select)
|
|
10
|
+
* - 内置斜杠命令描述(addAutocompleteProvider 包一层翻译)
|
|
9
11
|
* - 内置 read/bash/edit/write 工具的渲染文案(同名重注册,执行委托原实现)
|
|
10
12
|
*
|
|
11
13
|
* 不改动发给模型的内容(工具 description 保持英文)。
|
|
@@ -15,10 +17,6 @@ import type {
|
|
|
15
17
|
BashToolDetails,
|
|
16
18
|
EditToolDetails,
|
|
17
19
|
ExtensionAPI,
|
|
18
|
-
FindToolDetails,
|
|
19
|
-
GrepToolDetails,
|
|
20
|
-
LsToolDetails,
|
|
21
|
-
PowerShellToolDetails,
|
|
22
20
|
ReadToolDetails,
|
|
23
21
|
} from "@earendil-works/pi-coding-agent";
|
|
24
22
|
import {
|
|
@@ -38,11 +36,11 @@ import {
|
|
|
38
36
|
VERSION,
|
|
39
37
|
} from "@earendil-works/pi-coding-agent";
|
|
40
38
|
import { homedir } from "node:os";
|
|
41
|
-
import { readFileSync } from "node:fs";
|
|
42
|
-
import { join, relative } from "node:path";
|
|
39
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
40
|
+
import { dirname, join, relative } from "node:path";
|
|
43
41
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
44
42
|
import { Text, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
45
|
-
import { zh } from "./
|
|
43
|
+
import { zh } from "./zh.ts";
|
|
46
44
|
|
|
47
45
|
export default function (pi: ExtensionAPI) {
|
|
48
46
|
let toggleFooter: (() => void) | undefined;
|
|
@@ -55,13 +53,84 @@ export default function (pi: ExtensionAPI) {
|
|
|
55
53
|
},
|
|
56
54
|
});
|
|
57
55
|
|
|
56
|
+
// ---------- 项目信任弹窗(中文选择器代替内置英文界面) ----------
|
|
57
|
+
// 复刻 ~/「agent dir」/trust.json 的祖先目录查找逻辑;已有决定的目录不再弹窗。
|
|
58
|
+
// 与内置的差异:「信任父文件夹」选项无法通过扩展 API 写入父目录决定,故省略。
|
|
59
|
+
const savedTrustDecision = (dir: string): boolean | null => {
|
|
60
|
+
let current = dir;
|
|
61
|
+
try {
|
|
62
|
+
current = realpathSync(current); // 与 core 的 normalizeCwd 一致
|
|
63
|
+
} catch {
|
|
64
|
+
// 目录不存在时保持原样
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const store = JSON.parse(readFileSync(join(getAgentDir(), "trust.json"), "utf8"));
|
|
68
|
+
for (;;) {
|
|
69
|
+
const decision = store[current];
|
|
70
|
+
if (typeof decision === "boolean") return decision;
|
|
71
|
+
const parent = dirname(current);
|
|
72
|
+
if (parent === current) return null;
|
|
73
|
+
current = parent;
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
return null; // 无 trust.json 或内容无效 → 视作没有已存决定
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
pi.on("project_trust", async (event, ctx) => {
|
|
81
|
+
// 无 UI(rpc/print 等模式)或已有保存的决定时返回 undecided,让 core 走默认流程。
|
|
82
|
+
if (!ctx.hasUI || savedTrustDecision(event.cwd) !== null) return { trusted: "undecided" };
|
|
83
|
+
|
|
84
|
+
const options = [
|
|
85
|
+
zh.trust.options.trustAndRemember,
|
|
86
|
+
zh.trust.options.trustSessionOnly,
|
|
87
|
+
zh.trust.options.distrustAndRemember,
|
|
88
|
+
zh.trust.options.distrustSessionOnly,
|
|
89
|
+
];
|
|
90
|
+
const selected = await ctx.ui.select(zh.trust.title(event.cwd), options);
|
|
91
|
+
switch (selected ?? zh.trust.options.distrustSessionOnly) {
|
|
92
|
+
case zh.trust.options.trustAndRemember:
|
|
93
|
+
return { trusted: "yes", remember: true };
|
|
94
|
+
case zh.trust.options.trustSessionOnly:
|
|
95
|
+
return { trusted: "yes" };
|
|
96
|
+
case zh.trust.options.distrustAndRemember:
|
|
97
|
+
return { trusted: "no", remember: true };
|
|
98
|
+
default:
|
|
99
|
+
return { trusted: "no" }; // 取消 = 本次不信任(与内置行为一致)
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
58
103
|
// ---------- UI 文案 + 中文 header/footer(session 启动时应用) ----------
|
|
104
|
+
let autocompleteTranslated = false;
|
|
59
105
|
pi.on("session_start", (_event, ctx) => {
|
|
60
106
|
if (!ctx.hasUI) return;
|
|
61
107
|
|
|
62
108
|
ctx.ui.setWorkingMessage(zh.workingMessage);
|
|
63
109
|
ctx.ui.setHiddenThinkingLabel(zh.hiddenThinkingLabel);
|
|
64
110
|
|
|
111
|
+
// 中文化内置斜杠命令描述:包一层自动补全 provider,只替换命令项的
|
|
112
|
+
// description(文件补全等其他建议原样透传)。替换是幂等的,跨 session 只包一次。
|
|
113
|
+
if (ctx.mode === "tui" && !autocompleteTranslated) {
|
|
114
|
+
autocompleteTranslated = true;
|
|
115
|
+
ctx.ui.addAutocompleteProvider((current) => ({
|
|
116
|
+
...(current.triggerCharacters ? { triggerCharacters: current.triggerCharacters } : {}),
|
|
117
|
+
getSuggestions: async (lines, cursorLine, cursorCol, options) => {
|
|
118
|
+
const suggestions = await current.getSuggestions(lines, cursorLine, cursorCol, options);
|
|
119
|
+
if (!suggestions) return null;
|
|
120
|
+
return {
|
|
121
|
+
...suggestions,
|
|
122
|
+
items: suggestions.items.map((item) => {
|
|
123
|
+
const key = item.value.replace(/^\//, "") as keyof typeof zh.builtinCommands;
|
|
124
|
+
const zhDesc = zh.builtinCommands[key];
|
|
125
|
+
return zhDesc ? { ...item, description: zhDesc } : item;
|
|
126
|
+
}),
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
applyCompletion: (lines, cursorLine, cursorCol, item, prefix) =>
|
|
130
|
+
current.applyCompletion(lines, cursorLine, cursorCol, item, prefix),
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
|
|
65
134
|
// 中文 header:复刻内置结构(标题 + 折叠提示行 + 引导语),快捷键跟随用户键位配置。
|
|
66
135
|
if (ctx.mode === "tui") {
|
|
67
136
|
ctx.ui.setHeader((_tui, theme) => ({
|
|
@@ -264,9 +333,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
264
333
|
const contextClause = `${zh.footer.ratioLabel}${pctText}/${fmt(windowSize)}${getAutoCompactSuffix()}`;
|
|
265
334
|
const contextIndex = clauses.length;
|
|
266
335
|
clauses.push(contextClause);
|
|
267
|
-
const xpIndex =
|
|
268
|
-
process.env.PI_EXPERIMENTAL === "1" ? clauses.length : -1;
|
|
269
|
-
if (xpIndex >= 0) clauses.push("xp");
|
|
270
336
|
|
|
271
337
|
// 占比按阈值变色(>90% 红、>70% 黄);实验标记沿用 built-in 样式。
|
|
272
338
|
const statsLeft = clauses
|
|
@@ -278,9 +344,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
278
344
|
? theme.fg("warning", clause)
|
|
279
345
|
: theme.fg("dim", clause);
|
|
280
346
|
}
|
|
281
|
-
if (index === xpIndex) {
|
|
282
|
-
return `${theme.fg("dim", "•")} ${theme.bold(theme.fg("warning", "xp"))}`;
|
|
283
|
-
}
|
|
284
347
|
return theme.fg("dim", clause);
|
|
285
348
|
})
|
|
286
349
|
.join(" ");
|
|
@@ -376,6 +439,137 @@ export default function (pi: ExtensionAPI) {
|
|
|
376
439
|
registerLocalizedTools(ctx.cwd);
|
|
377
440
|
});
|
|
378
441
|
|
|
442
|
+
// ---------- 工具渲染通用小工具 ----------
|
|
443
|
+
const textOf = (result: any): string =>
|
|
444
|
+
result.content?.[0]?.type === "text" ? result.content[0].text : "";
|
|
445
|
+
|
|
446
|
+
const countNonEmpty = (s: string): number =>
|
|
447
|
+
s.split("\n").filter((l) => l.trim()).length;
|
|
448
|
+
|
|
449
|
+
const notePartial = (msg: string, theme: any): Text =>
|
|
450
|
+
new Text(theme.fg("warning", msg), 0, 0);
|
|
451
|
+
|
|
452
|
+
// 错误结果:内容以 "Error" 开头时只展示首行(edit/write 共用)。
|
|
453
|
+
const errorFirstLine = (result: any, theme: any): Text | undefined => {
|
|
454
|
+
const out = textOf(result);
|
|
455
|
+
return out.startsWith("Error")
|
|
456
|
+
? new Text(theme.fg("error", out.split("\n")[0]), 0, 0)
|
|
457
|
+
: undefined;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
// 展开态:在摘要后追加逐行弱化的原始输出。
|
|
461
|
+
const withExpanded = (raw: string, summary: string, theme: any): string =>
|
|
462
|
+
raw
|
|
463
|
+
? `${summary}\n${raw
|
|
464
|
+
.split("\n")
|
|
465
|
+
.map((l) => theme.fg("dim", l))
|
|
466
|
+
.join("\n")}`
|
|
467
|
+
: summary;
|
|
468
|
+
|
|
469
|
+
// 失败判定(bash/powershell 共用):解析退出码;powershell 通过 extraRe
|
|
470
|
+
// 追加 Windows 平台提示的识别。
|
|
471
|
+
function failureState(output: string, extraRe: RegExp[] = []) {
|
|
472
|
+
const m = output.match(
|
|
473
|
+
/(?:exit code:\s*|command exited with code\s+)(\d+)/i,
|
|
474
|
+
);
|
|
475
|
+
const exitCode = m ? parseInt(m[1], 10) : null;
|
|
476
|
+
return {
|
|
477
|
+
exitCode,
|
|
478
|
+
failed:
|
|
479
|
+
exitCode !== null ||
|
|
480
|
+
/^error\b/i.test(output) ||
|
|
481
|
+
/\n\ncommand (?:timed out|was aborted|failed)/i.test(output) ||
|
|
482
|
+
extraRe.some((re) => re.test(output)),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// shell 类工具(bash/powershell 文案同构)的渲染对。
|
|
487
|
+
type ShellCopy =
|
|
488
|
+
| typeof zh.tools.bash
|
|
489
|
+
| typeof zh.tools.powershell;
|
|
490
|
+
const shellRenders = (w: ShellCopy, extraRe: RegExp[] = []) => ({
|
|
491
|
+
renderCall(args: any, theme: any): Text {
|
|
492
|
+
let text = theme.fg("toolTitle", theme.bold(w.title));
|
|
493
|
+
const cmd: string =
|
|
494
|
+
args.command.length > 80
|
|
495
|
+
? `${args.command.slice(0, 77)}...`
|
|
496
|
+
: args.command;
|
|
497
|
+
text += theme.fg("accent", cmd);
|
|
498
|
+
if (args.timeout) text += theme.fg("dim", w.timeout(args.timeout));
|
|
499
|
+
return new Text(text, 0, 0);
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
renderResult(
|
|
503
|
+
result: any,
|
|
504
|
+
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
505
|
+
theme: any,
|
|
506
|
+
): Text {
|
|
507
|
+
if (isPartial) return notePartial(w.running, theme);
|
|
508
|
+
const output = textOf(result);
|
|
509
|
+
const { exitCode, failed } = failureState(output, extraRe);
|
|
510
|
+
let text = failed
|
|
511
|
+
? theme.fg(
|
|
512
|
+
"error",
|
|
513
|
+
exitCode !== null ? w.exit(exitCode) : w.failed,
|
|
514
|
+
)
|
|
515
|
+
: theme.fg("success", w.done);
|
|
516
|
+
text += theme.fg("dim", ` (${w.lines(countNonEmpty(output))})`);
|
|
517
|
+
if (
|
|
518
|
+
(result.details as BashToolDetails | undefined)?.truncation?.truncated
|
|
519
|
+
) {
|
|
520
|
+
text += theme.fg("warning", ` ${w.truncated}`);
|
|
521
|
+
}
|
|
522
|
+
return new Text(withExpanded(expanded ? output : "", text, theme), 0, 0);
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// 计数类工具(grep/find/ls 文案同构)的结果渲染:无匹配哨兵、条数统计、
|
|
527
|
+
// 上限提示与截断警告四处逻辑一致,仅文案不同。
|
|
528
|
+
function countResultRender(
|
|
529
|
+
labels: {
|
|
530
|
+
searching: string;
|
|
531
|
+
noMatch: string;
|
|
532
|
+
found: (n: number) => string;
|
|
533
|
+
limit?: (n: number) => string;
|
|
534
|
+
truncated: string;
|
|
535
|
+
},
|
|
536
|
+
sentinel: string,
|
|
537
|
+
limitField: string,
|
|
538
|
+
) {
|
|
539
|
+
return (
|
|
540
|
+
result: any,
|
|
541
|
+
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
542
|
+
theme: any,
|
|
543
|
+
): Text => {
|
|
544
|
+
if (isPartial) return notePartial(labels.searching, theme);
|
|
545
|
+
const details = result.details ?? {};
|
|
546
|
+
const output = textOf(result);
|
|
547
|
+
const isEmpty = output === sentinel;
|
|
548
|
+
let text = theme.fg(
|
|
549
|
+
isEmpty ? "muted" : "success",
|
|
550
|
+
isEmpty ? labels.noMatch : labels.found(countNonEmpty(output)),
|
|
551
|
+
);
|
|
552
|
+
if (labels.limit && typeof details[limitField] === "number") {
|
|
553
|
+
text += theme.fg("warning", labels.limit(details[limitField]));
|
|
554
|
+
}
|
|
555
|
+
// 与旧实现的行为差异:grep 原本同时检查 linesTruncated,find/ls 只查
|
|
556
|
+
// truncation.truncated;这里统一为两者都检查,Find/LsToolDetails 目前无
|
|
557
|
+
// linesTruncated 字段,运行时行为等价。若上游新增该字段需复核。
|
|
558
|
+
if (details.linesTruncated || details.truncation?.truncated) {
|
|
559
|
+
text += theme.fg("warning", labels.truncated);
|
|
560
|
+
}
|
|
561
|
+
return new Text(
|
|
562
|
+
withExpanded(
|
|
563
|
+
expanded ? (isEmpty ? labels.noMatch : output) : "",
|
|
564
|
+
text,
|
|
565
|
+
theme,
|
|
566
|
+
),
|
|
567
|
+
0,
|
|
568
|
+
0,
|
|
569
|
+
);
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
379
573
|
function registerLocalizedTools(cwd: string) {
|
|
380
574
|
const activeTools = pi.getActiveTools();
|
|
381
575
|
const builtinTools = new Set(
|
|
@@ -387,12 +581,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
387
581
|
|
|
388
582
|
// ---------- 内置工具汉化渲染 ----------
|
|
389
583
|
// read
|
|
390
|
-
const originalRead = createReadTool(cwd);
|
|
391
584
|
pi.registerTool({
|
|
392
|
-
...
|
|
585
|
+
...createReadTool(cwd),
|
|
393
586
|
label: zh.tools.read.label,
|
|
394
587
|
|
|
395
|
-
renderCall(args: any, theme: any
|
|
588
|
+
renderCall(args: any, theme: any) {
|
|
396
589
|
let text = theme.fg("toolTitle", theme.bold(`${zh.tools.read.title} `));
|
|
397
590
|
text += theme.fg("accent", args.path);
|
|
398
591
|
if (args.offset || args.limit) {
|
|
@@ -408,14 +601,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
408
601
|
result: any,
|
|
409
602
|
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
410
603
|
theme: any,
|
|
411
|
-
_context: any,
|
|
412
604
|
) {
|
|
413
|
-
if (isPartial)
|
|
414
|
-
return new Text(theme.fg("warning", zh.tools.read.reading), 0, 0);
|
|
605
|
+
if (isPartial) return notePartial(zh.tools.read.reading, theme);
|
|
415
606
|
|
|
416
|
-
const details = result.details as ReadToolDetails | undefined;
|
|
417
607
|
const content = result.content[0];
|
|
418
|
-
|
|
419
608
|
if (content?.type === "image") {
|
|
420
609
|
return new Text(theme.fg("success", zh.tools.read.imageLoaded), 0, 0);
|
|
421
610
|
}
|
|
@@ -423,103 +612,40 @@ export default function (pi: ExtensionAPI) {
|
|
|
423
612
|
return new Text(theme.fg("error", zh.tools.read.noContent), 0, 0);
|
|
424
613
|
}
|
|
425
614
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
615
|
+
let text = theme.fg(
|
|
616
|
+
"success",
|
|
617
|
+
zh.tools.read.lines(content.text.split("\n").length),
|
|
618
|
+
);
|
|
619
|
+
const truncation = (result.details as ReadToolDetails | undefined)
|
|
620
|
+
?.truncation;
|
|
621
|
+
if (truncation?.truncated) {
|
|
430
622
|
text += theme.fg(
|
|
431
623
|
"warning",
|
|
432
|
-
zh.tools.read.truncatedFrom(
|
|
624
|
+
zh.tools.read.truncatedFrom(truncation.totalLines),
|
|
433
625
|
);
|
|
434
626
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
return new Text(text, 0, 0);
|
|
627
|
+
return new Text(
|
|
628
|
+
withExpanded(expanded ? content.text : "", text, theme),
|
|
629
|
+
0,
|
|
630
|
+
0,
|
|
631
|
+
);
|
|
443
632
|
},
|
|
444
633
|
});
|
|
445
634
|
|
|
446
|
-
// bash
|
|
447
|
-
const originalBash = createBashTool(cwd);
|
|
635
|
+
// bash / powershell(渲染逻辑同构,仅文案与失败判定集合不同)
|
|
448
636
|
pi.registerTool({
|
|
449
|
-
...
|
|
637
|
+
...createBashTool(cwd),
|
|
450
638
|
label: zh.tools.bash.label,
|
|
451
|
-
|
|
452
|
-
renderCall(args: any, theme: any, _context: any) {
|
|
453
|
-
let text = theme.fg("toolTitle", theme.bold(zh.tools.bash.title));
|
|
454
|
-
const cmd: string =
|
|
455
|
-
args.command.length > 80
|
|
456
|
-
? `${args.command.slice(0, 77)}...`
|
|
457
|
-
: args.command;
|
|
458
|
-
text += theme.fg("accent", cmd);
|
|
459
|
-
if (args.timeout) {
|
|
460
|
-
text += theme.fg("dim", zh.tools.bash.timeout(args.timeout));
|
|
461
|
-
}
|
|
462
|
-
return new Text(text, 0, 0);
|
|
463
|
-
},
|
|
464
|
-
|
|
465
|
-
renderResult(
|
|
466
|
-
result: any,
|
|
467
|
-
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
468
|
-
theme: any,
|
|
469
|
-
_context: any,
|
|
470
|
-
) {
|
|
471
|
-
if (isPartial)
|
|
472
|
-
return new Text(theme.fg("warning", zh.tools.bash.running), 0, 0);
|
|
473
|
-
|
|
474
|
-
const details = result.details as BashToolDetails | undefined;
|
|
475
|
-
const content = result.content[0];
|
|
476
|
-
const output: string = content?.type === "text" ? content.text : "";
|
|
477
|
-
|
|
478
|
-
const exitMatch = output.match(
|
|
479
|
-
/(?:exit code:\s*|command exited with code\s+)(\d+)/i,
|
|
480
|
-
);
|
|
481
|
-
const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : null;
|
|
482
|
-
const failed =
|
|
483
|
-
exitCode !== null ||
|
|
484
|
-
/^error\b/i.test(output) ||
|
|
485
|
-
/\n\ncommand (?:timed out|was aborted|failed)/i.test(output);
|
|
486
|
-
const lineCount = output.split("\n").filter((l) => l.trim()).length;
|
|
487
|
-
|
|
488
|
-
let text = "";
|
|
489
|
-
if (!failed) {
|
|
490
|
-
text += theme.fg("success", zh.tools.bash.done);
|
|
491
|
-
} else {
|
|
492
|
-
text += theme.fg(
|
|
493
|
-
"error",
|
|
494
|
-
exitCode !== null
|
|
495
|
-
? zh.tools.bash.exit(exitCode)
|
|
496
|
-
: zh.tools.bash.failed,
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
text += theme.fg("dim", ` (${zh.tools.bash.lines(lineCount)})`);
|
|
500
|
-
|
|
501
|
-
if (details?.truncation?.truncated) {
|
|
502
|
-
text += theme.fg("warning", ` ${zh.tools.bash.truncated}`);
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (expanded) {
|
|
506
|
-
for (const line of output.split("\n")) {
|
|
507
|
-
text += `\n${theme.fg("dim", line)}`;
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return new Text(text, 0, 0);
|
|
512
|
-
},
|
|
639
|
+
...shellRenders(zh.tools.bash),
|
|
513
640
|
});
|
|
514
641
|
|
|
515
642
|
// edit
|
|
516
|
-
const originalEdit = createEditTool(cwd);
|
|
517
643
|
pi.registerTool({
|
|
518
|
-
...
|
|
644
|
+
...createEditTool(cwd),
|
|
519
645
|
label: zh.tools.edit.label,
|
|
520
646
|
renderShell: "self",
|
|
521
647
|
|
|
522
|
-
renderCall(args: any, theme: any
|
|
648
|
+
renderCall(args: any, theme: any) {
|
|
523
649
|
let text = theme.fg("toolTitle", theme.bold(zh.tools.edit.title));
|
|
524
650
|
text += theme.fg("accent", args.path);
|
|
525
651
|
return new Text(text, 0, 0);
|
|
@@ -529,25 +655,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
529
655
|
result: any,
|
|
530
656
|
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
531
657
|
theme: any,
|
|
532
|
-
_context: any,
|
|
533
658
|
) {
|
|
534
|
-
if (isPartial)
|
|
535
|
-
return new Text(theme.fg("warning", zh.tools.edit.editing), 0, 0);
|
|
659
|
+
if (isPartial) return notePartial(zh.tools.edit.editing, theme);
|
|
536
660
|
|
|
537
|
-
const
|
|
538
|
-
|
|
661
|
+
const err = errorFirstLine(result, theme);
|
|
662
|
+
if (err) return err;
|
|
539
663
|
|
|
540
|
-
|
|
541
|
-
return new Text(theme.fg("error", content.text.split("\n")[0]), 0, 0);
|
|
542
|
-
}
|
|
664
|
+
const details = result.details as EditToolDetails | undefined;
|
|
543
665
|
if (!details?.diff) {
|
|
544
666
|
return new Text(theme.fg("success", zh.tools.edit.applied), 0, 0);
|
|
545
667
|
}
|
|
546
668
|
|
|
547
|
-
const diffLines: string[] = details.diff.split("\n");
|
|
548
669
|
let additions = 0,
|
|
549
670
|
removals = 0;
|
|
550
|
-
for (const line of
|
|
671
|
+
for (const line of details.diff.split("\n")) {
|
|
551
672
|
if (line.startsWith("+") && !line.startsWith("+++")) additions++;
|
|
552
673
|
if (line.startsWith("-") && !line.startsWith("---")) removals++;
|
|
553
674
|
}
|
|
@@ -556,23 +677,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
556
677
|
text += theme.fg("dim", " / ");
|
|
557
678
|
text += theme.fg("error", zh.tools.edit.removals(removals));
|
|
558
679
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return new Text(text, 0, 0);
|
|
680
|
+
return new Text(
|
|
681
|
+
withExpanded(expanded ? details.diff : "", text, theme),
|
|
682
|
+
0,
|
|
683
|
+
0,
|
|
684
|
+
);
|
|
566
685
|
},
|
|
567
686
|
});
|
|
568
687
|
|
|
569
688
|
// write
|
|
570
|
-
const originalWrite = createWriteTool(cwd);
|
|
571
689
|
pi.registerTool({
|
|
572
|
-
...
|
|
690
|
+
...createWriteTool(cwd),
|
|
573
691
|
label: zh.tools.write.label,
|
|
574
692
|
|
|
575
|
-
renderCall(args: any, theme: any
|
|
693
|
+
renderCall(args: any, theme: any) {
|
|
576
694
|
let text = theme.fg("toolTitle", theme.bold(zh.tools.write.title));
|
|
577
695
|
text += theme.fg("accent", args.path);
|
|
578
696
|
const lineCount = String(args.content).split("\n").length;
|
|
@@ -580,31 +698,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
580
698
|
return new Text(text, 0, 0);
|
|
581
699
|
},
|
|
582
700
|
|
|
583
|
-
renderResult(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
_context: any,
|
|
588
|
-
) {
|
|
589
|
-
if (isPartial)
|
|
590
|
-
return new Text(theme.fg("warning", zh.tools.write.writing), 0, 0);
|
|
591
|
-
|
|
592
|
-
const content = result.content[0];
|
|
593
|
-
if (content?.type === "text" && content.text.startsWith("Error")) {
|
|
594
|
-
return new Text(theme.fg("error", content.text.split("\n")[0]), 0, 0);
|
|
595
|
-
}
|
|
596
|
-
|
|
701
|
+
renderResult(result: any, { isPartial }: { isPartial: boolean }, theme: any) {
|
|
702
|
+
if (isPartial) return notePartial(zh.tools.write.writing, theme);
|
|
703
|
+
const err = errorFirstLine(result, theme);
|
|
704
|
+
if (err) return err;
|
|
597
705
|
return new Text(theme.fg("success", zh.tools.write.written), 0, 0);
|
|
598
706
|
},
|
|
599
707
|
});
|
|
600
708
|
|
|
709
|
+
// grep / find / ls:调用渲染各自略有差异,结果渲染走同一个计数模板。
|
|
601
710
|
if (builtinTools.has("grep")) {
|
|
602
|
-
const originalGrep = createGrepTool(cwd);
|
|
603
711
|
pi.registerTool({
|
|
604
|
-
...
|
|
712
|
+
...createGrepTool(cwd),
|
|
605
713
|
label: zh.tools.grep.label,
|
|
606
714
|
|
|
607
|
-
renderCall(args: any, theme: any
|
|
715
|
+
renderCall(args: any, theme: any) {
|
|
608
716
|
let text = theme.fg("toolTitle", theme.bold(zh.tools.grep.title));
|
|
609
717
|
text += theme.fg("accent", args.pattern);
|
|
610
718
|
if (args.path) text += theme.fg("dim", ` · ${args.path}`);
|
|
@@ -612,214 +720,76 @@ export default function (pi: ExtensionAPI) {
|
|
|
612
720
|
return new Text(text, 0, 0);
|
|
613
721
|
},
|
|
614
722
|
|
|
615
|
-
renderResult(
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
const output = content?.type === "text" ? content.text : "";
|
|
627
|
-
const noMatches = output === "No matches found";
|
|
628
|
-
const count = output
|
|
629
|
-
.split("\n")
|
|
630
|
-
.filter((line: string) => line.trim()).length;
|
|
631
|
-
let text = theme.fg(
|
|
632
|
-
noMatches ? "muted" : "success",
|
|
633
|
-
noMatches ? zh.tools.grep.noMatches : zh.tools.grep.matches(count),
|
|
634
|
-
);
|
|
635
|
-
if (details?.matchLimitReached !== undefined) {
|
|
636
|
-
text += theme.fg(
|
|
637
|
-
"warning",
|
|
638
|
-
zh.tools.grep.matchLimit(details.matchLimitReached),
|
|
639
|
-
);
|
|
640
|
-
}
|
|
641
|
-
if (details?.linesTruncated || details?.truncation?.truncated) {
|
|
642
|
-
text += theme.fg("warning", zh.tools.grep.truncated);
|
|
643
|
-
}
|
|
644
|
-
if (expanded && output) {
|
|
645
|
-
const expandedOutput = noMatches ? zh.tools.grep.noMatches : output;
|
|
646
|
-
text += `\n${theme.fg("dim", expandedOutput)}`;
|
|
647
|
-
}
|
|
648
|
-
return new Text(text, 0, 0);
|
|
649
|
-
},
|
|
723
|
+
renderResult: countResultRender(
|
|
724
|
+
{
|
|
725
|
+
searching: zh.tools.grep.searching,
|
|
726
|
+
noMatch: zh.tools.grep.noMatches,
|
|
727
|
+
found: zh.tools.grep.matches,
|
|
728
|
+
limit: zh.tools.grep.matchLimit,
|
|
729
|
+
truncated: zh.tools.grep.truncated,
|
|
730
|
+
},
|
|
731
|
+
"No matches found",
|
|
732
|
+
"matchLimitReached",
|
|
733
|
+
),
|
|
650
734
|
});
|
|
651
735
|
}
|
|
652
736
|
|
|
653
737
|
if (builtinTools.has("find")) {
|
|
654
|
-
const originalFind = createFindTool(cwd);
|
|
655
738
|
pi.registerTool({
|
|
656
|
-
...
|
|
739
|
+
...createFindTool(cwd),
|
|
657
740
|
label: zh.tools.find.label,
|
|
658
741
|
|
|
659
|
-
renderCall(args: any, theme: any
|
|
742
|
+
renderCall(args: any, theme: any) {
|
|
660
743
|
let text = theme.fg("toolTitle", theme.bold(zh.tools.find.title));
|
|
661
744
|
text += theme.fg("accent", args.pattern);
|
|
662
745
|
if (args.path) text += theme.fg("dim", ` · ${args.path}`);
|
|
663
746
|
return new Text(text, 0, 0);
|
|
664
747
|
},
|
|
665
748
|
|
|
666
|
-
renderResult(
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const output = content?.type === "text" ? content.text : "";
|
|
678
|
-
const noFiles = output === "No files found matching pattern";
|
|
679
|
-
const count = output
|
|
680
|
-
.split("\n")
|
|
681
|
-
.filter((line: string) => line.trim()).length;
|
|
682
|
-
let text = theme.fg(
|
|
683
|
-
noFiles ? "muted" : "success",
|
|
684
|
-
noFiles ? zh.tools.find.noFiles : zh.tools.find.results(count),
|
|
685
|
-
);
|
|
686
|
-
if (details?.resultLimitReached !== undefined) {
|
|
687
|
-
text += theme.fg(
|
|
688
|
-
"warning",
|
|
689
|
-
zh.tools.find.resultLimit(details.resultLimitReached),
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
if (details?.truncation?.truncated) {
|
|
693
|
-
text += theme.fg("warning", zh.tools.grep.truncated);
|
|
694
|
-
}
|
|
695
|
-
if (expanded && output) {
|
|
696
|
-
const expandedOutput = noFiles ? zh.tools.find.noFiles : output;
|
|
697
|
-
text += `\n${theme.fg("dim", expandedOutput)}`;
|
|
698
|
-
}
|
|
699
|
-
return new Text(text, 0, 0);
|
|
700
|
-
},
|
|
749
|
+
renderResult: countResultRender(
|
|
750
|
+
{
|
|
751
|
+
searching: zh.tools.find.searching,
|
|
752
|
+
noMatch: zh.tools.find.noFiles,
|
|
753
|
+
found: zh.tools.find.results,
|
|
754
|
+
limit: zh.tools.find.resultLimit,
|
|
755
|
+
truncated: zh.tools.grep.truncated,
|
|
756
|
+
},
|
|
757
|
+
"No files found matching pattern",
|
|
758
|
+
"resultLimitReached",
|
|
759
|
+
),
|
|
701
760
|
});
|
|
702
761
|
}
|
|
703
762
|
|
|
704
763
|
if (builtinTools.has("ls")) {
|
|
705
|
-
const originalLs = createLsTool(cwd);
|
|
706
764
|
pi.registerTool({
|
|
707
|
-
...
|
|
765
|
+
...createLsTool(cwd),
|
|
708
766
|
label: zh.tools.ls.label,
|
|
709
767
|
|
|
710
|
-
renderCall(args: any, theme: any
|
|
768
|
+
renderCall(args: any, theme: any) {
|
|
711
769
|
let text = theme.fg("toolTitle", theme.bold(zh.tools.ls.title));
|
|
712
770
|
text += theme.fg("accent", args.path || ".");
|
|
713
771
|
return new Text(text, 0, 0);
|
|
714
772
|
},
|
|
715
773
|
|
|
716
|
-
renderResult(
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const output = content?.type === "text" ? content.text : "";
|
|
728
|
-
const empty = output === "(empty directory)";
|
|
729
|
-
const count = output
|
|
730
|
-
.split("\n")
|
|
731
|
-
.filter((line: string) => line.trim()).length;
|
|
732
|
-
let text = theme.fg(
|
|
733
|
-
empty ? "muted" : "success",
|
|
734
|
-
empty ? zh.tools.ls.empty : zh.tools.ls.entries(count),
|
|
735
|
-
);
|
|
736
|
-
if (details?.entryLimitReached !== undefined) {
|
|
737
|
-
text += theme.fg(
|
|
738
|
-
"warning",
|
|
739
|
-
zh.tools.ls.entryLimit(details.entryLimitReached),
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
if (details?.truncation?.truncated) {
|
|
743
|
-
text += theme.fg("warning", zh.tools.grep.truncated);
|
|
744
|
-
}
|
|
745
|
-
if (expanded && output) {
|
|
746
|
-
const expandedOutput = empty ? zh.tools.ls.empty : output;
|
|
747
|
-
text += `\n${theme.fg("dim", expandedOutput)}`;
|
|
748
|
-
}
|
|
749
|
-
return new Text(text, 0, 0);
|
|
750
|
-
},
|
|
774
|
+
renderResult: countResultRender(
|
|
775
|
+
{
|
|
776
|
+
searching: zh.tools.ls.listing,
|
|
777
|
+
noMatch: zh.tools.ls.empty,
|
|
778
|
+
found: zh.tools.ls.entries,
|
|
779
|
+
limit: zh.tools.ls.entryLimit,
|
|
780
|
+
truncated: zh.tools.grep.truncated,
|
|
781
|
+
},
|
|
782
|
+
"(empty directory)",
|
|
783
|
+
"entryLimitReached",
|
|
784
|
+
),
|
|
751
785
|
});
|
|
752
786
|
}
|
|
753
787
|
|
|
754
788
|
if (builtinTools.has("powershell")) {
|
|
755
|
-
const originalPowerShell = createPowerShellTool(cwd);
|
|
756
789
|
pi.registerTool({
|
|
757
|
-
...
|
|
790
|
+
...createPowerShellTool(cwd),
|
|
758
791
|
label: zh.tools.powershell.label,
|
|
759
|
-
|
|
760
|
-
renderCall(args: any, theme: any, _context: any) {
|
|
761
|
-
let text = theme.fg(
|
|
762
|
-
"toolTitle",
|
|
763
|
-
theme.bold(zh.tools.powershell.title),
|
|
764
|
-
);
|
|
765
|
-
const command =
|
|
766
|
-
args.command.length > 80
|
|
767
|
-
? `${args.command.slice(0, 77)}...`
|
|
768
|
-
: args.command;
|
|
769
|
-
text += theme.fg("accent", command);
|
|
770
|
-
if (args.timeout) {
|
|
771
|
-
text += theme.fg("dim", zh.tools.powershell.timeout(args.timeout));
|
|
772
|
-
}
|
|
773
|
-
return new Text(text, 0, 0);
|
|
774
|
-
},
|
|
775
|
-
|
|
776
|
-
renderResult(
|
|
777
|
-
result: any,
|
|
778
|
-
{ expanded, isPartial }: { expanded: boolean; isPartial: boolean },
|
|
779
|
-
theme: any,
|
|
780
|
-
_context: any,
|
|
781
|
-
) {
|
|
782
|
-
if (isPartial)
|
|
783
|
-
return new Text(
|
|
784
|
-
theme.fg("warning", zh.tools.powershell.running),
|
|
785
|
-
0,
|
|
786
|
-
0,
|
|
787
|
-
);
|
|
788
|
-
|
|
789
|
-
const details = result.details as PowerShellToolDetails | undefined;
|
|
790
|
-
const content = result.content[0];
|
|
791
|
-
const output = content?.type === "text" ? content.text : "";
|
|
792
|
-
const exitMatch = output.match(
|
|
793
|
-
/(?:exit code:\s*|command exited with code\s+)(\d+)/i,
|
|
794
|
-
);
|
|
795
|
-
const exitCode = exitMatch ? parseInt(exitMatch[1], 10) : null;
|
|
796
|
-
const failed =
|
|
797
|
-
exitCode !== null ||
|
|
798
|
-
/^error\b/i.test(output) ||
|
|
799
|
-
/only available on windows/i.test(output) ||
|
|
800
|
-
/\n\ncommand (?:timed out|was aborted|failed)/i.test(output);
|
|
801
|
-
const lineCount = output
|
|
802
|
-
.split("\n")
|
|
803
|
-
.filter((line: string) => line.trim()).length;
|
|
804
|
-
let text = theme.fg(
|
|
805
|
-
failed ? "error" : "success",
|
|
806
|
-
failed
|
|
807
|
-
? exitCode !== null
|
|
808
|
-
? zh.tools.powershell.exit(exitCode)
|
|
809
|
-
: zh.tools.powershell.failed
|
|
810
|
-
: zh.tools.powershell.done,
|
|
811
|
-
);
|
|
812
|
-
text += theme.fg("dim", ` (${zh.tools.powershell.lines(lineCount)})`);
|
|
813
|
-
if (details?.truncation?.truncated) {
|
|
814
|
-
text += theme.fg("warning", ` ${zh.tools.powershell.truncated}`);
|
|
815
|
-
}
|
|
816
|
-
if (expanded) {
|
|
817
|
-
for (const line of output.split("\n")) {
|
|
818
|
-
text += `\n${theme.fg("dim", line)}`;
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
return new Text(text, 0, 0);
|
|
822
|
-
},
|
|
792
|
+
...shellRenders(zh.tools.powershell, [/only available on windows/i]),
|
|
823
793
|
});
|
|
824
794
|
}
|
|
825
795
|
|
|
@@ -124,6 +124,45 @@ export const zh = {
|
|
|
124
124
|
truncated: "[输出过长,已截断]",
|
|
125
125
|
},
|
|
126
126
|
},
|
|
127
|
+
|
|
128
|
+
// 项目信任弹窗(复刻内置“信任/不信任”选项;“信任父文件夹”变体不支持)
|
|
129
|
+
trust: {
|
|
130
|
+
title: (cwd: string) =>
|
|
131
|
+
`是否信任项目目录:${cwd}?将允许 pi 加载项目配置并执行项目扩展`,
|
|
132
|
+
options: {
|
|
133
|
+
trustAndRemember: "信任并记住此决定",
|
|
134
|
+
trustSessionOnly: "仅本次会话信任",
|
|
135
|
+
distrustAndRemember: "不信任并记住此决定",
|
|
136
|
+
distrustSessionOnly: "本次会话不信任",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// 内置斜杠命令的中文描述(key 为命令名;带参数提示的条目已按 core 的
|
|
141
|
+
// `hint — description` 合成格式写好,避免运行时拼字符串)
|
|
142
|
+
builtinCommands: {
|
|
143
|
+
settings: "打开设置菜单",
|
|
144
|
+
model: "<provider/model> — 选择模型(打开选择器界面)",
|
|
145
|
+
tree: "浏览会话树(切换分支)",
|
|
146
|
+
thinking: "<level> — 设置思考等级",
|
|
147
|
+
"scoped-models": "启用/停用 Ctrl+P 轮换用的模型",
|
|
148
|
+
export: "导出会话(默认 HTML,可指定 .html/.jsonl 路径)",
|
|
149
|
+
import: "从 JSONL 文件导入并恢复会话",
|
|
150
|
+
share: "通过 GitHub 私密 gist 分享会话",
|
|
151
|
+
copy: "复制最后一条助手消息到剪贴板",
|
|
152
|
+
name: "设置会话显示名称",
|
|
153
|
+
session: "显示会话信息与统计",
|
|
154
|
+
changelog: "显示更新日志",
|
|
155
|
+
hotkeys: "显示全部快捷键",
|
|
156
|
+
fork: "从历史用户消息创建新分叉",
|
|
157
|
+
clone: "在当前位置复制当前会话",
|
|
158
|
+
trust: "保存项目信任决定以便后续会话使用",
|
|
159
|
+
login: "<provider> — 配置提供商认证",
|
|
160
|
+
logout: "移除提供商认证",
|
|
161
|
+
new: "开始新会话",
|
|
162
|
+
compact: "手动压缩会话上下文",
|
|
163
|
+
resume: "恢复另一个会话",
|
|
164
|
+
reload: "重新加载快捷键、扩展、技能、提示词、主题和上下文文件",
|
|
165
|
+
quit: "退出 pi",
|
|
166
|
+
},
|
|
127
167
|
} as const;
|
|
128
168
|
|
|
129
|
-
export type Zh = typeof zh;
|