feat: 导出 SproutClaw .sproutclaw 配置
包含 extensions、skills、prompts、settings、auth、models、mcp 等配置。 排除 node_modules、npm 缓存、sessions 等运行时数据。
This commit is contained in:
172
agent/extensions/startup-chinese/index.ts
Normal file
172
agent/extensions/startup-chinese/index.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 启动界面中文翻译扩展
|
||||
*
|
||||
* 将 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;
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user