@lokiyou/pi-nano-footer 0.1.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 +45 -0
- package/index.ts +211 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @lokiyou/pi-nano-footer
|
|
2
|
+
|
|
3
|
+
超轻量 powerline 风格 footer for **Pi Coding Agent**,霓虹配色方案。
|
|
4
|
+
|
|
5
|
+
精确复刻 pi-powerline-footer default 预设的样式 + 自定义霓虹色配色。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install npm:@lokiyou/pi-nano-footer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
或手动放入 `~/.pi/agent/extensions/` 后 `/reload`。
|
|
14
|
+
|
|
15
|
+
## 效果
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
deepseek-v4-flash think:high project 45.2%/128K 1.2k 0.03
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
从左到右依次展示:
|
|
22
|
+
- 🤖 **模型名**(热粉色)
|
|
23
|
+
- 💭 **思考等级**(按等级变色,high/xhigh 彩虹)
|
|
24
|
+
- 📁 **目录名**(青蓝色)
|
|
25
|
+
- 📊 **上下文用量**(紫 → 黄 >70% → 红 >90%)
|
|
26
|
+
- 💾 **Token 用量**(荧光绿)
|
|
27
|
+
- 💰 **费用**(明黄色)
|
|
28
|
+
|
|
29
|
+
## 配色
|
|
30
|
+
|
|
31
|
+
| 令牌 | 色值 | 用途 |
|
|
32
|
+
|------|------|------|
|
|
33
|
+
| 模型 | `#ff3cac` | 热粉 |
|
|
34
|
+
| 路径 | `#00d4ff` | 青蓝 |
|
|
35
|
+
| thinking | `#ff6b6b` | 珊瑚红 |
|
|
36
|
+
| thinking high/xhigh | rainbow | 彩虹渐变 |
|
|
37
|
+
| 上下文正常 | `#6c5ce7` | 紫色 |
|
|
38
|
+
| 上下文 >70% | `#fdcb6e` | 黄色警告 |
|
|
39
|
+
| 上下文 >90% | `#ff3366` | 红色错误 |
|
|
40
|
+
| 费用 | `#fdcb6e` | 明黄 |
|
|
41
|
+
| tokens | `#00ff87` | 荧光绿 |
|
|
42
|
+
|
|
43
|
+
## 许可
|
|
44
|
+
|
|
45
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-nano-footer — 超轻量 powerline 风格 footer
|
|
3
|
+
*
|
|
4
|
+
* 精确复刻 pi-powerline-footer default 预设的样式 +
|
|
5
|
+
* 用户自定义霓虹色配色方案。
|
|
6
|
+
*
|
|
7
|
+
* 安装:放到 ~/.pi/agent/extensions/ 后重启 pi 即可自动加载。
|
|
8
|
+
* 临时测试:pi -e ~/.pi/agent/extensions/pi-nano-footer.ts
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
12
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
14
|
+
|
|
15
|
+
// ── Nerd Font 图标(与 pi-powerline-footer 完全一致) ──
|
|
16
|
+
const icons = {
|
|
17
|
+
model: "\uec19", // nf-md-chip
|
|
18
|
+
folder: "\uf115", // nf-fa-folder_open
|
|
19
|
+
context: "\ue70f", // nf-dev-database
|
|
20
|
+
cache: "\uf1c0", // nf-fa-database
|
|
21
|
+
input: "\uf090", // nf-fa-sign_in
|
|
22
|
+
cost: "\uf155", // nf-fa-dollar
|
|
23
|
+
sep: "\ue0b1", // powerline-thin
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ── 用户自定义霓虹配色 ──
|
|
27
|
+
const C = {
|
|
28
|
+
model: "#ff3cac",
|
|
29
|
+
shellMode: "#00ff87",
|
|
30
|
+
path: "#00d4ff",
|
|
31
|
+
gitDirty: "#ff9f1c",
|
|
32
|
+
gitClean: "#00ff87",
|
|
33
|
+
thinking: "#ff6b6b",
|
|
34
|
+
thinkingMinimal: "#a855f7",
|
|
35
|
+
thinkingLow: "#00d4ff",
|
|
36
|
+
thinkingMedium: "#ff9f1c",
|
|
37
|
+
context: "#6c5ce7",
|
|
38
|
+
contextWarn: "#fdcb6e",
|
|
39
|
+
contextError: "#ff3366",
|
|
40
|
+
cost: "#fdcb6e",
|
|
41
|
+
tokens: "#00ff87",
|
|
42
|
+
separator: "#a855f7",
|
|
43
|
+
border: "#dfe6e9",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// ── Rainbow 色表(high / xhigh 思考等级用) ──
|
|
47
|
+
const RAINBOW = [
|
|
48
|
+
"#b281d6", "#d787af", "#febc38", "#e4c00f",
|
|
49
|
+
"#89d281", "#00afaf", "#178fb9", "#b281d6",
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export default function (pi: ExtensionAPI) {
|
|
53
|
+
let thinkingLevel = "off";
|
|
54
|
+
let requestRender: (() => void) | undefined;
|
|
55
|
+
const refresh = () => requestRender?.();
|
|
56
|
+
|
|
57
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
58
|
+
thinkingLevel = pi.getThinkingLevel();
|
|
59
|
+
|
|
60
|
+
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
61
|
+
requestRender = () => tui.requestRender();
|
|
62
|
+
const unsubBranch = footerData.onBranchChange(() => tui.requestRender());
|
|
63
|
+
const clock = setInterval(() => tui.requestRender(), 30_000);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
dispose() { unsubBranch(); clearInterval(clock); },
|
|
67
|
+
invalidate() {},
|
|
68
|
+
render(width: number): string[] {
|
|
69
|
+
if (width <= 0) return [];
|
|
70
|
+
|
|
71
|
+
// 分隔符:dim 色(低调,不抢眼)
|
|
72
|
+
const S = ` ${theme.fg("dim", icons.sep)} `;
|
|
73
|
+
const parts: string[] = [];
|
|
74
|
+
|
|
75
|
+
// 1. 模型 —— #ff3cac 热粉色
|
|
76
|
+
parts.push(ansi(C.model, `${icons.model} ${shorten(ctx.model?.id ?? "–")}`));
|
|
77
|
+
|
|
78
|
+
// 2. 思考等级 —— 按等级变色,high/xhigh 彩虹
|
|
79
|
+
parts.push(renderThinkingN(thinkingLevel));
|
|
80
|
+
|
|
81
|
+
// 3. 目录 basename —— #00d4ff 青蓝色
|
|
82
|
+
const dir = ctx.cwd.replace(/\\/g, "/").split("/").filter(Boolean).pop() || ctx.cwd;
|
|
83
|
+
parts.push(ansi(C.path, `${icons.folder} ${dir}`));
|
|
84
|
+
|
|
85
|
+
// 4. 上下文用量 —— #6c5ce7 紫 / #fdcb6e 黄 / #ff3366 红
|
|
86
|
+
parts.push(renderContextN(ctx));
|
|
87
|
+
|
|
88
|
+
// 5. Cache read —— #00ff87 荧光绿
|
|
89
|
+
const { input, cost } = calcTotals(ctx);
|
|
90
|
+
parts.push(ansi(C.tokens, `${icons.cache} ${icons.input} ${fmt(input)}`));
|
|
91
|
+
|
|
92
|
+
// 6. 费用 —— #fdcb6e 明黄色
|
|
93
|
+
parts.push(ansi(C.cost, `${icons.cost} ${cost.toFixed(2)}`));
|
|
94
|
+
|
|
95
|
+
return [truncateToWidth(parts.join(S), width, "")];
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ── 事件订阅:保持 footer 实时刷新 ──
|
|
102
|
+
|
|
103
|
+
pi.on("thinking_level_select", (event) => {
|
|
104
|
+
thinkingLevel = event.level;
|
|
105
|
+
refresh();
|
|
106
|
+
});
|
|
107
|
+
pi.on("model_select", () => refresh());
|
|
108
|
+
pi.on("turn_start", () => refresh());
|
|
109
|
+
pi.on("turn_end", () => refresh());
|
|
110
|
+
pi.on("agent_end", () => refresh());
|
|
111
|
+
|
|
112
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
113
|
+
ctx.ui.setFooter(undefined);
|
|
114
|
+
requestRender = undefined;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
119
|
+
// Helper 函数
|
|
120
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
121
|
+
|
|
122
|
+
/** 16 进制色值 → ANSI 24-bit true color 前景色转义序列 */
|
|
123
|
+
function ansi(hex: string, text: string): string {
|
|
124
|
+
const h = hex.replace("#", "");
|
|
125
|
+
const r = parseInt(h.slice(0, 2), 16);
|
|
126
|
+
const g = parseInt(h.slice(2, 4), 16);
|
|
127
|
+
const b = parseInt(h.slice(4, 6), 16);
|
|
128
|
+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[0m`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 渲染思考等级 */
|
|
132
|
+
function renderThinkingN(level: string): string {
|
|
133
|
+
const label = level === "minimal" ? "min"
|
|
134
|
+
: level === "medium" ? "med"
|
|
135
|
+
: level;
|
|
136
|
+
const text = `think:${label}`;
|
|
137
|
+
|
|
138
|
+
switch (level) {
|
|
139
|
+
case "high":
|
|
140
|
+
case "xhigh":
|
|
141
|
+
return rainbow(text);
|
|
142
|
+
case "minimal":
|
|
143
|
+
return ansi(C.thinkingMinimal, text);
|
|
144
|
+
case "low":
|
|
145
|
+
return ansi(C.thinkingLow, text);
|
|
146
|
+
case "medium":
|
|
147
|
+
return ansi(C.thinkingMedium, text);
|
|
148
|
+
default:
|
|
149
|
+
return ansi(C.thinking, text);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** 渲染上下文用量,带百分比阈值颜色 */
|
|
154
|
+
function renderContextN(ctx: any): string {
|
|
155
|
+
const usage = ctx.getContextUsage();
|
|
156
|
+
const pct = usage?.percent;
|
|
157
|
+
const color = pct == null ? C.context
|
|
158
|
+
: pct >= 90 ? C.contextError
|
|
159
|
+
: pct >= 70 ? C.contextWarn
|
|
160
|
+
: C.context;
|
|
161
|
+
const maxStr = ctx.model?.contextWindow
|
|
162
|
+
? `/${(ctx.model.contextWindow / 1_000_000).toFixed(1)}M`
|
|
163
|
+
: "";
|
|
164
|
+
return ansi(color, `${icons.context} ${pct?.toFixed(1) ?? "?"}%${maxStr}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** 遍历会话分支,累计 input token 数和总费用 */
|
|
168
|
+
function calcTotals(ctx: any): { input: number; cost: number } {
|
|
169
|
+
let input = 0, cost = 0;
|
|
170
|
+
for (const e of ctx.sessionManager.getBranch()) {
|
|
171
|
+
if (e.type === "message" && e.message.role === "assistant") {
|
|
172
|
+
const m = e.message as AssistantMessage;
|
|
173
|
+
input += m.usage.input ?? 0;
|
|
174
|
+
cost += m.usage.cost?.total ?? 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { input, cost };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** 格式化数字:1.2k / 45M */
|
|
181
|
+
function fmt(n: number): string {
|
|
182
|
+
if (n < 1000) return `${n}`;
|
|
183
|
+
if (n < 1_000_000) return `${(n / 1000).toFixed(n < 10_000 ? 1 : 0)}k`;
|
|
184
|
+
return `${(n / 1_000_000).toFixed(1)}m`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** 缩短模型名 */
|
|
188
|
+
function shorten(m: string): string {
|
|
189
|
+
return m
|
|
190
|
+
.replace(/^claude-/i, "")
|
|
191
|
+
.replace(/^gpt-/i, "gpt ")
|
|
192
|
+
.replace(/-20\d{6}$/, "")
|
|
193
|
+
.replace(/-latest$/i, "");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** 彩虹渐变 ANSI 渲染 */
|
|
197
|
+
function rainbow(text: string): string {
|
|
198
|
+
let out = "", ci = 0;
|
|
199
|
+
for (const ch of text) {
|
|
200
|
+
if (ch === " " || ch === ":") {
|
|
201
|
+
out += ch;
|
|
202
|
+
} else {
|
|
203
|
+
const hex = RAINBOW[ci++ % RAINBOW.length].replace("#", "");
|
|
204
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
205
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
206
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
207
|
+
out += `\x1b[38;2;${r};${g};${b}m${ch}\x1b[0m`;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lokiyou/pi-nano-footer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "超轻量 powerline 风格 footer for Pi Coding Agent — 霓虹配色,实时显示模型、思考等级、目录、上下文、token 和费用",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"footer",
|
|
10
|
+
"statusline",
|
|
11
|
+
"powerline",
|
|
12
|
+
"neon"
|
|
13
|
+
],
|
|
14
|
+
"author": "lokiyou",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"files": [
|
|
17
|
+
"index.ts",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"pi": {
|
|
21
|
+
"extensions": [
|
|
22
|
+
"./index.ts"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
27
|
+
"@earendil-works/pi-tui": "*",
|
|
28
|
+
"@earendil-works/pi-ai": "*"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": ""
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|