Files
shumengya 50edff80f5 feat: 导出 SproutClaw .sproutclaw 配置
包含 extensions、skills、prompts、settings、auth、models、mcp 等配置。
排除 node_modules、npm 缓存、sessions 等运行时数据。
2026-06-26 15:48:56 +08:00

173 lines
6.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 启动界面中文翻译扩展
*
* 将 pi 的初始启动界面中的介绍和引导文字翻译为中文,
* 快捷键和命令提示保持原样。
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { keyHint, keyText, rawKeyHint, VERSION } from "@earendil-works/pi-coding-agent";
import { truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
const SPROUTCLAW_LOGO = [
"███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗██╗ █████╗ ██╗ ██╗",
"██╔════╝██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██╔══██╗██║ ██║",
"███████╗██████╔╝██████╔╝██║ ██║██║ ██║ ██║ ██║ ██║ ███████║██║ █╗ ██║",
"╚════██║██╔═══╝ ██╔══██╗██║ ██║██║ ██║ ██║ ██║ ██║ ██╔══██║██║███╗██║",
"███████║██║ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
"╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
];
const LOGO_PALETTE = [
[125, 255, 230],
[154, 255, 140],
[255, 245, 135],
[255, 176, 235],
[150, 210, 255],
[190, 170, 255],
];
function rgb(text: string, color: number[]): string {
return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${text}\x1b[39m`;
}
function lerp(a: number, b: number, t: number): number {
return Math.round(a + (b - a) * t);
}
function gradientText(text: string, offset = 0): string {
const chars = [...text];
const last = Math.max(1, chars.length - 1);
return chars
.map((char, index) => {
if (char === " ") return char;
const position = (index / last + offset) % 1;
const scaled = position * (LOGO_PALETTE.length - 1);
const startIndex = Math.floor(scaled);
const endIndex = Math.min(LOGO_PALETTE.length - 1, startIndex + 1);
const t = scaled - startIndex;
const start = LOGO_PALETTE[startIndex];
const end = LOGO_PALETTE[endIndex];
return rgb(char, [
lerp(start[0], end[0], t),
lerp(start[1], end[1], t),
lerp(start[2], end[2], t),
]);
})
.join("");
}
function startupLine(label: string, text: string, color: number[]): string {
return `${rgb(label, color)} ${rgb(text, [210, 235, 255])}`;
}
function keyTextOr(action: string, fallback: string): string {
const text = keyText(action as any).trim();
return text || fallback;
}
function renderDivider(theme: { fg: (color: string, text: string) => string }, width: number): string {
return theme.fg("dim", "─".repeat(Math.max(1, width)));
}
function fitWidth(lines: string[], width: number): string[] {
const result: string[] = [];
for (const line of lines) {
for (const segment of line.split("\n")) {
for (const wrapped of wrapTextWithAnsi(segment, width)) {
result.push(truncateToWidth(wrapped, width));
}
}
}
return result;
}
const WIDE_LOGO_MIN_WIDTH = 88;
function renderSproutclawLogo(theme: any, width: number): string[] {
if (width >= WIDE_LOGO_MIN_WIDTH) {
const lines: string[] = [];
for (let i = 0; i < SPROUTCLAW_LOGO.length; i++) {
lines.push(theme.bold(gradientText(SPROUTCLAW_LOGO[i], i * 0.08)));
}
return lines;
}
return [
[
rgb(" ", [100, 200, 255]),
theme.bold(gradientText("SproutClaw")),
rgb("-萌芽Bot ", [190, 170, 255]),
rgb(`v${VERSION}`, [170, 235, 255]),
].join(""),
];
}
export default function (pi: ExtensionAPI) {
pi.on("session_start", async (_event, ctx) => {
if (!ctx.hasUI) return;
ctx.ui.setHeader((_tui, theme) => {
const expandedInstructions = [
keyHint("app.interrupt", "中断当前任务"),
keyHint("app.clear", "清空输入"),
rawKeyHint(`${keyText("app.clear")} twice`, "退出"),
keyHint("app.exit", "空输入退出"),
keyHint("app.suspend", "挂起进程"),
keyHint("tui.editor.deleteToLineEnd" as any, "删除到行尾"),
keyHint("app.thinking.cycle", "切换思考强度"),
rawKeyHint(
`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`,
"切换模型",
),
keyHint("app.model.select", "选择模型"),
keyHint("app.tools.expand", "展开工具输出"),
keyHint("app.thinking.toggle", "展开思考过程"),
keyHint("app.editor.external", "外部编辑器"),
rawKeyHint("/", "Agent 命令"),
rawKeyHint("!", "执行 shell"),
rawKeyHint("!!", "执行 shell 且不进上下文"),
keyHint("app.message.followUp", "追加后续消息"),
keyHint("app.message.dequeue", "编辑队列消息"),
keyHint("app.clipboard.pasteImage", "粘贴图片"),
rawKeyHint("drop files", "附加文件"),
].join("\n");
const compactOnboarding = startupLine(
"工作台",
`描述要处理的代码、部署或服务器问题,我会执行命令、修改文件并同步进度。按 ${keyTextOr("app.tools.expand", "Ctrl+O")} 展开详情。`,
[150, 255, 210],
);
const onboarding = [
startupLine("SproutClaw", "树萌芽运维开发助手,负责代码修改、服务部署、服务器操作、排障和知识检索。", [255, 232, 140]),
rgb("直接输入任务即可开始。", [255, 190, 235]),
].join("\n");
let expanded = false;
return {
render(width: number): string[] {
const divider = renderDivider(theme, width);
const lines: string[] = [
divider,
...renderSproutclawLogo(theme, width),
divider,
rgb("萌芽运维开发Agent助手", [170, 235, 255]),
];
if (expanded) {
lines.push(expandedInstructions);
lines.push(onboarding);
} else {
lines.push(compactOnboarding);
lines.push(onboarding);
}
return fitWidth(lines, width);
},
invalidate() {},
setExpanded(v: boolean) {
expanded = v;
},
};
});
});
}