@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.
Files changed (3) hide show
  1. package/README.md +21 -21
  2. package/index.ts +79 -11
  3. package/package.json +37 -37
package/README.md CHANGED
@@ -1,39 +1,39 @@
1
1
  # @lokiyou/pi-nano-footer
2
2
 
3
- 超轻量 powerline 风格 footer for **Pi Coding Agent**。
3
+ Lightweight powerline-style footer for Pi Coding Agent.
4
4
 
5
- 它保留 Pi 内置的 `Working...` 指示器,同时在底部显示模型、思考等级、目录、上下文、token 和费用,整体走的是清爽、紧凑的霓虹风。
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
- 安装后重载 Pi:
14
+ ## What it shows
14
15
 
15
- ```bash
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
- - 采用 powerline 分隔符
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", // nf-md-chip
15
- folder: "\uf115", // nf-fa-folder_open
16
- context: "\ue70f", // nf-dev-database
17
- cache: "\uf1c0", // nf-fa-database
18
- input: "\uf090", // nf-fa-sign_in
19
- cost: "\uf155", // nf-fa-dollar
20
- sep: "\ue0b1", // powerline-thin
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() { unsubBranch(); clearInterval(clock); },
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. 上下文用量 —— #6c5ce7 紫 / #fdcb6e 黄 / #ff3366 红
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
- // 5. Token 用量 —— #00ff87 荧光绿
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
- // 6. 费用 —— #fdcb6e 明黄色
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.1",
4
- "description": "超轻量 powerline 风格 footer for Pi Coding Agent,保留内置 Working... 指示器,霓虹配色,实时显示模型、目录、上下文、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
- }
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
+ }