@lokiyou/pi-nano-footer 0.15.1 → 0.15.3
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 +21 -21
- package/index.ts +79 -11
- package/package.json +37 -37
package/README.md
CHANGED
|
@@ -1,39 +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
|
-
/reload
|
|
17
|
-
```
|
|
16
|
+
From left to right, the footer shows:
|
|
18
17
|
|
|
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
|
|
20
25
|
|
|
21
|
-
|
|
26
|
+
## Behavior
|
|
22
27
|
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
- 💾 Token 用量
|
|
28
|
-
- 💰 费用
|
|
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.
|
|
29
32
|
|
|
30
|
-
##
|
|
33
|
+
## Notes
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
- 颜色和 `pi-powerline-footer` 的默认 preset 保持一致
|
|
34
|
-
- 不替代内置工作状态指示器
|
|
35
|
-
- 适合想要简洁 footer、但不想丢掉 Pi 原生反馈的人
|
|
35
|
+
No additional configuration is required.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## License
|
|
38
38
|
|
|
39
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
|
// ── 用户自定义霓虹配色 ──
|
|
@@ -65,10 +66,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
65
66
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
66
67
|
requestRender = () => tui.requestRender();
|
|
67
68
|
const unsubBranch = footerData.onBranchChange(() => tui.requestRender());
|
|
69
|
+
let lastStatusSnapshot = snapshotStatuses(footerData);
|
|
70
|
+
const statusPoll = setInterval(() => {
|
|
71
|
+
const nextStatusSnapshot = snapshotStatuses(footerData);
|
|
72
|
+
if (nextStatusSnapshot !== lastStatusSnapshot) {
|
|
73
|
+
lastStatusSnapshot = nextStatusSnapshot;
|
|
74
|
+
tui.requestRender();
|
|
75
|
+
}
|
|
76
|
+
}, 500);
|
|
68
77
|
const clock = setInterval(() => tui.requestRender(), 30_000);
|
|
69
78
|
|
|
70
79
|
return {
|
|
71
|
-
dispose() {
|
|
80
|
+
dispose() {
|
|
81
|
+
unsubBranch();
|
|
82
|
+
clearInterval(statusPoll);
|
|
83
|
+
clearInterval(clock);
|
|
84
|
+
},
|
|
72
85
|
invalidate() {},
|
|
73
86
|
render(width: number): string[] {
|
|
74
87
|
if (width <= 0) return [];
|
|
@@ -87,14 +100,18 @@ export default function (pi: ExtensionAPI) {
|
|
|
87
100
|
const dir = ctx.cwd.replace(/\\/g, "/").split("/").filter(Boolean).pop() || ctx.cwd;
|
|
88
101
|
parts.push(ansi(C.path, `${icons.folder} ${dir}`));
|
|
89
102
|
|
|
90
|
-
// 4.
|
|
103
|
+
// 4. MCP 摘要 —— 紧凑图标版
|
|
104
|
+
const mcp = renderMcpN(footerData);
|
|
105
|
+
if (mcp) parts.push(mcp);
|
|
106
|
+
|
|
107
|
+
// 5. 上下文用量 —— #6c5ce7 紫 / #fdcb6e 黄 / #ff3366 红
|
|
91
108
|
parts.push(renderContextN(ctx));
|
|
92
109
|
|
|
93
|
-
//
|
|
110
|
+
// 6. Token 用量 —— #00ff87 荧光绿
|
|
94
111
|
const { input, cost } = calcTotals(ctx);
|
|
95
112
|
parts.push(ansi(C.tokens, `${icons.cache} ${icons.input} ${fmt(input)}`));
|
|
96
113
|
|
|
97
|
-
//
|
|
114
|
+
// 7. 费用 —— #fdcb6e 明黄色
|
|
98
115
|
parts.push(ansi(C.cost, `${icons.cost} ${cost.toFixed(2)}`));
|
|
99
116
|
|
|
100
117
|
return [truncateToWidth(parts.join(S), width, "")];
|
|
@@ -119,10 +136,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
119
136
|
});
|
|
120
137
|
}
|
|
121
138
|
|
|
139
|
+
function snapshotStatuses(footerData: any): string {
|
|
140
|
+
const statuses = footerData?.getExtensionStatuses?.();
|
|
141
|
+
if (!statuses || typeof statuses.entries !== "function") return "";
|
|
142
|
+
|
|
143
|
+
return Array.from(statuses.entries())
|
|
144
|
+
.sort(([a], [b]) => String(a).localeCompare(String(b)))
|
|
145
|
+
.map(([key, value]) => `${String(key)}:${stripAnsi(String(value))}`)
|
|
146
|
+
.join("|");
|
|
147
|
+
}
|
|
148
|
+
|
|
122
149
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
123
150
|
// Helper 函数
|
|
124
151
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
125
152
|
|
|
153
|
+
function stripAnsi(text: string): string {
|
|
154
|
+
return text.replace(/\x1b\[[0-9;]*m/g, "");
|
|
155
|
+
}
|
|
156
|
+
|
|
126
157
|
/** 16 进制色值 → ANSI 24-bit true color 前景色转义序列 */
|
|
127
158
|
function ansi(hex: string, text: string): string {
|
|
128
159
|
const h = hex.replace("#", "");
|
|
@@ -168,6 +199,43 @@ function renderContextN(ctx: any): string {
|
|
|
168
199
|
return ansi(color, `${icons.context} ${pct?.toFixed(1) ?? "?"}%${maxStr}`);
|
|
169
200
|
}
|
|
170
201
|
|
|
202
|
+
/** 渲染 MCP 状态摘要,优先显示已连接/总数 */
|
|
203
|
+
function renderMcpN(footerData: any): string | null {
|
|
204
|
+
const statuses = footerData?.getExtensionStatuses?.();
|
|
205
|
+
if (!statuses || typeof statuses.get !== "function") return null;
|
|
206
|
+
|
|
207
|
+
const raw = typeof statuses.get("mcp") === "string"
|
|
208
|
+
? statuses.get("mcp")
|
|
209
|
+
: Array.from(statuses.values()).find((value: unknown) => typeof value === "string" && value.includes("MCP:"));
|
|
210
|
+
if (typeof raw !== "string") return null;
|
|
211
|
+
|
|
212
|
+
const status = stripAnsi(raw).replace(/\s+/g, " ").trim();
|
|
213
|
+
|
|
214
|
+
const ratio = /MCP:\s*(\d+)\s*\/\s*(\d+)\s+servers?/i.exec(status);
|
|
215
|
+
if (ratio) {
|
|
216
|
+
const connected = Number(ratio[1]);
|
|
217
|
+
const total = Number(ratio[2]);
|
|
218
|
+
const color = connected >= total ? C.tokens : C.thinkingMedium;
|
|
219
|
+
return ansi(color, `${icons.mcp} ${connected}/${total}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const connected = /MCP:\s*(\d+)\s+servers connected(?:\s*\((\d+)\s+tools\))?/i.exec(status);
|
|
223
|
+
if (connected) {
|
|
224
|
+
return ansi(C.tokens, `${icons.mcp} ${connected[1]}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const connecting = /MCP:\s*connecting to\s+(\d+)\s+servers?/i.exec(status);
|
|
228
|
+
if (connecting) {
|
|
229
|
+
return ansi(C.thinkingMedium, `${icons.mcp} …/${connecting[1]}`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (/failed|error|needs-auth|oauth/i.test(status)) {
|
|
233
|
+
return ansi(C.contextError, `${icons.mcp} !`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return ansi(C.context, `${icons.mcp} ?`);
|
|
237
|
+
}
|
|
238
|
+
|
|
171
239
|
/** 遍历会话分支,累计 input token 数和总费用 */
|
|
172
240
|
function calcTotals(ctx: any): { input: number; cost: number } {
|
|
173
241
|
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.3",
|
|
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
|
+
}
|