@lokiyou/pi-nano-footer 0.15.0 → 0.15.2
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 +25 -31
- package/index.ts +53 -36
- package/package.json +37 -37
package/README.md
CHANGED
|
@@ -1,45 +1,39 @@
|
|
|
1
1
|
# @lokiyou/pi-nano-footer
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lightweight powerline-style footer for Pi Coding Agent.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This extension replaces the default footer with a compact single-line status bar while keeping Pi's built-in `Working...` indicator. It is designed to stay small and focused.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
10
|
pi install npm:@lokiyou/pi-nano-footer
|
|
11
|
+
/reload
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
## What it shows
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
From left to right, the footer shows:
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
- current model
|
|
19
|
+
- thinking level
|
|
20
|
+
- current directory name
|
|
21
|
+
- MCP status summary when MCP status is available
|
|
22
|
+
- context usage
|
|
23
|
+
- token totals
|
|
24
|
+
- estimated cost
|
|
25
|
+
|
|
26
|
+
## Behavior
|
|
27
|
+
|
|
28
|
+
- Uses a compact powerline-style separator layout.
|
|
29
|
+
- Keeps Pi's built-in `Working...` indicator instead of replacing it.
|
|
30
|
+
- Reads MCP status from extension status data when an MCP adapter exposes it.
|
|
31
|
+
- Focuses only on the footer and does not replace the editor component.
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
34
|
+
|
|
35
|
+
No additional configuration is required.
|
|
20
36
|
|
|
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
|
-
## 许可
|
|
37
|
+
## License
|
|
44
38
|
|
|
45
39
|
MIT
|
package/index.ts
CHANGED
|
@@ -11,13 +11,14 @@ import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
|
11
11
|
|
|
12
12
|
// ── Nerd Font 图标(与 pi-powerline-footer 完全一致) ──
|
|
13
13
|
const icons = {
|
|
14
|
-
model: "\uec19",
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
model: "\uec19", // nf-md-chip
|
|
15
|
+
mcp: "\u{f048d}", // nf-md-server-network
|
|
16
|
+
folder: "\uf115", // nf-fa-folder_open
|
|
17
|
+
context: "\ue70f", // nf-dev-database
|
|
18
|
+
cache: "\uf1c0", // nf-fa-database
|
|
19
|
+
input: "\uf090", // nf-fa-sign_in
|
|
20
|
+
cost: "\uf155", // nf-fa-dollar
|
|
21
|
+
sep: "\ue0b1", // powerline-thin
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
// ── 用户自定义霓虹配色 ──
|
|
@@ -60,9 +61,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
60
61
|
|
|
61
62
|
pi.on("session_start", async (_event, ctx) => {
|
|
62
63
|
thinkingLevel = pi.getThinkingLevel();
|
|
63
|
-
|
|
64
|
-
// 隐藏内置的 "Working..." 指示器,用输入框呼吸代替
|
|
65
|
-
ctx.ui.setWorkingVisible(false);
|
|
64
|
+
ctx.ui.setWorkingVisible(true);
|
|
66
65
|
|
|
67
66
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
68
67
|
requestRender = () => tui.requestRender();
|
|
@@ -89,14 +88,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
89
88
|
const dir = ctx.cwd.replace(/\\/g, "/").split("/").filter(Boolean).pop() || ctx.cwd;
|
|
90
89
|
parts.push(ansi(C.path, `${icons.folder} ${dir}`));
|
|
91
90
|
|
|
92
|
-
// 4.
|
|
91
|
+
// 4. MCP 摘要 —— 紧凑图标版
|
|
92
|
+
const mcp = renderMcpN(footerData);
|
|
93
|
+
if (mcp) parts.push(mcp);
|
|
94
|
+
|
|
95
|
+
// 5. 上下文用量 —— #6c5ce7 紫 / #fdcb6e 黄 / #ff3366 红
|
|
93
96
|
parts.push(renderContextN(ctx));
|
|
94
97
|
|
|
95
|
-
//
|
|
98
|
+
// 6. Token 用量 —— #00ff87 荧光绿
|
|
96
99
|
const { input, cost } = calcTotals(ctx);
|
|
97
100
|
parts.push(ansi(C.tokens, `${icons.cache} ${icons.input} ${fmt(input)}`));
|
|
98
101
|
|
|
99
|
-
//
|
|
102
|
+
// 7. 费用 —— #fdcb6e 明黄色
|
|
100
103
|
parts.push(ansi(C.cost, `${icons.cost} ${cost.toFixed(2)}`));
|
|
101
104
|
|
|
102
105
|
return [truncateToWidth(parts.join(S), width, "")];
|
|
@@ -112,35 +115,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
112
115
|
refresh();
|
|
113
116
|
});
|
|
114
117
|
pi.on("model_select", () => refresh());
|
|
115
|
-
|
|
116
|
-
// 工作状态切换 → 控制呼吸发光
|
|
117
|
-
pi.on("agent_start", () => {
|
|
118
|
-
working = true;
|
|
119
|
-
// 50ms 间隔主动刷新,保证呼吸动画 20fps 流畅
|
|
120
|
-
if (requestRender) {
|
|
121
|
-
animInterval = setInterval(requestRender, 50);
|
|
122
|
-
}
|
|
123
|
-
refresh();
|
|
124
|
-
});
|
|
125
|
-
pi.on("agent_end", () => {
|
|
126
|
-
working = false;
|
|
127
|
-
if (animInterval) {
|
|
128
|
-
clearInterval(animInterval);
|
|
129
|
-
animInterval = undefined;
|
|
130
|
-
}
|
|
131
|
-
refresh();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
118
|
pi.on("turn_start", () => refresh());
|
|
135
119
|
pi.on("turn_end", () => refresh());
|
|
136
120
|
|
|
137
121
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
138
122
|
ctx.ui.setFooter(undefined);
|
|
139
123
|
requestRender = undefined;
|
|
140
|
-
if (animInterval) {
|
|
141
|
-
clearInterval(animInterval);
|
|
142
|
-
animInterval = undefined;
|
|
143
|
-
}
|
|
144
124
|
});
|
|
145
125
|
}
|
|
146
126
|
|
|
@@ -193,6 +173,43 @@ function renderContextN(ctx: any): string {
|
|
|
193
173
|
return ansi(color, `${icons.context} ${pct?.toFixed(1) ?? "?"}%${maxStr}`);
|
|
194
174
|
}
|
|
195
175
|
|
|
176
|
+
/** 渲染 MCP 状态摘要,优先显示已连接/总数 */
|
|
177
|
+
function renderMcpN(footerData: any): string | null {
|
|
178
|
+
const statuses = footerData?.getExtensionStatuses?.();
|
|
179
|
+
if (!statuses || typeof statuses.get !== "function") return null;
|
|
180
|
+
|
|
181
|
+
const raw = typeof statuses.get("mcp") === "string"
|
|
182
|
+
? statuses.get("mcp")
|
|
183
|
+
: Array.from(statuses.values()).find((value: unknown) => typeof value === "string" && value.includes("MCP:"));
|
|
184
|
+
if (typeof raw !== "string") return null;
|
|
185
|
+
|
|
186
|
+
const status = raw.replace(/\x1b\[[0-9;]*m/g, "").replace(/\s+/g, " ").trim();
|
|
187
|
+
|
|
188
|
+
const ratio = /MCP:\s*(\d+)\s*\/\s*(\d+)\s+servers?/i.exec(status);
|
|
189
|
+
if (ratio) {
|
|
190
|
+
const connected = Number(ratio[1]);
|
|
191
|
+
const total = Number(ratio[2]);
|
|
192
|
+
const color = connected >= total ? C.tokens : C.thinkingMedium;
|
|
193
|
+
return ansi(color, `${icons.mcp} ${connected}/${total}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const connected = /MCP:\s*(\d+)\s+servers connected(?:\s*\((\d+)\s+tools\))?/i.exec(status);
|
|
197
|
+
if (connected) {
|
|
198
|
+
return ansi(C.tokens, `${icons.mcp} ${connected[1]}`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const connecting = /MCP:\s*connecting to\s+(\d+)\s+servers?/i.exec(status);
|
|
202
|
+
if (connecting) {
|
|
203
|
+
return ansi(C.thinkingMedium, `${icons.mcp} …/${connecting[1]}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (/failed|error|needs-auth|oauth/i.test(status)) {
|
|
207
|
+
return ansi(C.contextError, `${icons.mcp} !`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return ansi(C.context, `${icons.mcp} ?`);
|
|
211
|
+
}
|
|
212
|
+
|
|
196
213
|
/** 遍历会话分支,累计 input token 数和总费用 */
|
|
197
214
|
function calcTotals(ctx: any): { input: number; cost: number } {
|
|
198
215
|
let input = 0, cost = 0;
|
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@lokiyou/pi-nano-footer",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"description": "
|
|
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
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@lokiyou/pi-nano-footer",
|
|
3
|
+
"version": "0.15.2",
|
|
4
|
+
"description": "Lightweight powerline-style footer for Pi Coding Agent that shows model, thinking level, directory, MCP status, context usage, tokens, and cost while keeping the built-in Working indicator.",
|
|
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
|
+
}
|