@zhushanwen/pi-statusline 0.4.2 → 0.4.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/package.json +1 -1
- package/src/__tests__/format.test.ts +11 -10
- package/src/format.ts +9 -8
- package/src/index.ts +82 -171
- package/src/setup.ts +2 -0
package/package.json
CHANGED
|
@@ -6,24 +6,25 @@
|
|
|
6
6
|
* 2. Mock 回归测试:用固定数据防止未来格式变化破坏对齐
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import type { NormalizedQuotaRow,QuotaProvider, QuotaWindow } from "@zhushanwen/pi-quota-providers";
|
|
10
10
|
import { INFINITE_WIN } from "@zhushanwen/pi-quota-providers";
|
|
11
|
-
import
|
|
11
|
+
import { describe, expect,it } from "vitest";
|
|
12
|
+
|
|
12
13
|
import {
|
|
13
|
-
formatWinCol,
|
|
14
|
-
buildTokenPlanLines,
|
|
15
14
|
buildSearchLine,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
splitPath,
|
|
19
|
-
tailSessionId,
|
|
20
|
-
fmtResetSec,
|
|
15
|
+
buildTokenPlanLines,
|
|
16
|
+
fmtCount,
|
|
21
17
|
fmtDuration,
|
|
18
|
+
fmtResetSec,
|
|
22
19
|
fmtTokens,
|
|
23
|
-
|
|
20
|
+
formatSpeedPart,
|
|
21
|
+
formatWinCol,
|
|
22
|
+
normalizeRows,
|
|
24
23
|
pctColor,
|
|
25
24
|
plainPallet,
|
|
26
25
|
plainThemeFg,
|
|
26
|
+
splitPath,
|
|
27
|
+
tailSessionId,
|
|
27
28
|
} from "../format.js";
|
|
28
29
|
|
|
29
30
|
// ── 辅助:从渲染文本中提取 `·` 的位置 ─────────────────
|
package/src/format.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* 不依赖 Pi 运行时(ExtensionAPI / Theme),只做数据→字符串转换。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { QuotaWindow
|
|
8
|
+
import type { QuotaProvider,QuotaWindow } from "@zhushanwen/pi-quota-providers";
|
|
9
9
|
|
|
10
10
|
// ── 时间常量 ───────────────────────────────────────────
|
|
11
11
|
|
|
12
|
-
const MS_PER_SEC = 1000;
|
|
13
|
-
const SEC_PER_MIN = 60;
|
|
12
|
+
export const MS_PER_SEC = 1000;
|
|
13
|
+
export const SEC_PER_MIN = 60;
|
|
14
14
|
const MIN_PER_HOUR = 60;
|
|
15
15
|
const HOURS_PER_DAY = 24;
|
|
16
16
|
const SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR;
|
|
@@ -18,7 +18,7 @@ const SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY;
|
|
|
18
18
|
|
|
19
19
|
// ── token 数字单位阈值 ─────────────────────────────────
|
|
20
20
|
|
|
21
|
-
const KILO = 1_000;
|
|
21
|
+
export const KILO = 1_000;
|
|
22
22
|
const MILLION = 1_000_000;
|
|
23
23
|
|
|
24
24
|
// ── 百分比阈值 ─────────────────────────────────────────
|
|
@@ -26,7 +26,7 @@ const MILLION = 1_000_000;
|
|
|
26
26
|
const PCT_HIGH = 80;
|
|
27
27
|
const PCT_MED = 60;
|
|
28
28
|
const PCT_LOW = 40;
|
|
29
|
-
const PERCENT_SCALE = 100;
|
|
29
|
+
export const PERCENT_SCALE = 100;
|
|
30
30
|
|
|
31
31
|
// ── 渲染常量 ───────────────────────────────────────────
|
|
32
32
|
|
|
@@ -47,7 +47,7 @@ export const COLS = [
|
|
|
47
47
|
|
|
48
48
|
// ── 格式化函数 ─────────────────────────────────────────
|
|
49
49
|
|
|
50
|
-
const MIN_PAD = 2;
|
|
50
|
+
export const MIN_PAD = 2;
|
|
51
51
|
|
|
52
52
|
export function fmtDuration(ms: number): string {
|
|
53
53
|
const s = Math.floor(ms / MS_PER_SEC);
|
|
@@ -161,8 +161,9 @@ export function normalizeRows(
|
|
|
161
161
|
if (!norm) continue;
|
|
162
162
|
// 优先使用 providers.json 配置的 label,fallback 到 normalize 返回的 label
|
|
163
163
|
rows.push({ name: p.label || norm.label, wins: norm.wins });
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
// eslint-disable-next-line taste/no-silent-catch
|
|
165
|
+
} catch (normalizeErr) {
|
|
166
|
+
console.warn("[statusline] normalize failed:", normalizeErr);
|
|
166
167
|
}
|
|
167
168
|
}
|
|
168
169
|
return rows;
|
package/src/index.ts
CHANGED
|
@@ -13,22 +13,34 @@
|
|
|
13
13
|
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import { join, sep } from "node:path";
|
|
16
|
+
|
|
16
17
|
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
|
17
18
|
import type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider, Theme } from "@mariozechner/pi-coding-agent";
|
|
18
19
|
import { truncateToWidth } from "@mariozechner/pi-tui";
|
|
19
|
-
|
|
20
20
|
import {
|
|
21
|
-
readCache,
|
|
22
|
-
triggerUpdate,
|
|
23
|
-
trackSpeed,
|
|
24
21
|
buildRuntimeProviders,
|
|
25
|
-
|
|
22
|
+
readCache,
|
|
26
23
|
type SpeedData,
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
trackSpeed,
|
|
25
|
+
triggerUpdate,
|
|
29
26
|
} from "@zhushanwen/pi-quota-providers";
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
buildSearchLine,
|
|
30
|
+
buildTokenPlanLines,
|
|
31
|
+
fmtCount,
|
|
32
|
+
fmtDuration,
|
|
33
|
+
fmtTokens,
|
|
34
|
+
formatSpeedPart,
|
|
35
|
+
MIN_PAD,
|
|
36
|
+
MS_PER_SEC,
|
|
37
|
+
pctColor,
|
|
38
|
+
PERCENT_SCALE,
|
|
39
|
+
SEC_PER_MIN,
|
|
40
|
+
splitPath,
|
|
41
|
+
tailSessionId,
|
|
42
|
+
} from "./format.js";
|
|
30
43
|
import { registerSetupCommand } from "./setup.js";
|
|
31
|
-
import { formatSpeedPart, splitPath, tailSessionId } from "./format.js";
|
|
32
44
|
|
|
33
45
|
// ── 本地事件类型 ───────────────────────────────────────
|
|
34
46
|
interface PiMessageEvent {
|
|
@@ -39,93 +51,79 @@ interface PiThinkingLevelEvent {
|
|
|
39
51
|
level: string;
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
// ── 时间常量 ───────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
const MS_PER_SEC = 1000;
|
|
45
|
-
const SEC_PER_MIN = 60;
|
|
46
|
-
const MIN_PER_HOUR = 60;
|
|
47
|
-
const HOURS_PER_DAY = 24;
|
|
48
|
-
const SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR;
|
|
49
|
-
const SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY;
|
|
50
|
-
|
|
51
54
|
// ── 渲染常量 ───────────────────────────────────────────
|
|
52
55
|
|
|
53
56
|
const SEP = "│";
|
|
54
57
|
const DOT = "·";
|
|
55
58
|
const RUN_UPDATE_MS = 5000;
|
|
56
|
-
/** 标题列宽(按最长 "minimax-token-plan"=18,+1 空格余量) */
|
|
57
|
-
const TITLE_COL_W = 19;
|
|
58
|
-
/** reset 时间列宽(fmtResetSec 最长 "12d23h"=6 + 1 空格余量) */
|
|
59
|
-
const RESET_COL_W = 7;
|
|
60
|
-
/** pct 列宽("100%"=4,但 padStart(3) 给 " 23%"=4) */
|
|
61
|
-
const PCT_COL_W = 3;
|
|
62
59
|
/** sessionId 截取末尾字符数 */
|
|
63
60
|
const SESSION_ID_TAIL = 12;
|
|
64
61
|
/** 路径展示的层数(cwd 倒数 N 段) */
|
|
65
62
|
const DIR_DEPTH = 2;
|
|
66
|
-
/** 分/秒 pad 宽度 */
|
|
67
|
-
const MIN_PAD = 2;
|
|
68
63
|
/** bogus replay 阈值:output > 50 tokens 但 duration < 100ms 视为重放,跳过速度统计 */
|
|
69
64
|
const BOGUS_OUTPUT_THRESHOLD = 50;
|
|
70
65
|
const BOGUS_DURATION_THRESHOLD_MS = 100;
|
|
71
66
|
|
|
72
|
-
// ── 阈值常量 ───────────────────────────────────────────
|
|
73
|
-
|
|
74
|
-
/** token 数字单位阈值 */
|
|
75
|
-
const KILO = 1_000;
|
|
76
|
-
const MILLION = 1_000_000;
|
|
77
|
-
|
|
78
|
-
/** pct 颜色分档 */
|
|
79
|
-
const PCT_HIGH = 80;
|
|
80
|
-
const PCT_MED = 60;
|
|
81
|
-
const PCT_LOW = 40;
|
|
82
|
-
/** 百分比标度 */
|
|
83
|
-
const PERCENT_SCALE = 100;
|
|
84
|
-
|
|
85
67
|
/** contextWindow fallback */
|
|
86
68
|
const DEFAULT_CONTEXT_WINDOW = 128_000;
|
|
87
69
|
|
|
88
70
|
// ── 工具函数 ───────────────────────────────────────────
|
|
89
71
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const m = Math.floor(s / SEC_PER_MIN);
|
|
94
|
-
if (m < MIN_PER_HOUR) return `${m}m${String(s % SEC_PER_MIN).padStart(MIN_PAD, "0")}s`;
|
|
95
|
-
return `${Math.floor(m / MIN_PER_HOUR)}h${String(m % MIN_PER_HOUR).padStart(MIN_PAD, "0")}m`;
|
|
72
|
+
/** 当前 cwd 是否在 git worktree 内(粗略:看 .git 是文件还是目录) */
|
|
73
|
+
function isWorktree(cwd: string): boolean {
|
|
74
|
+
return existsSync(join(cwd, ".git"));
|
|
96
75
|
}
|
|
97
76
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
// ── Footer API 适配类型 ─────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/** Tui 句柄(Pi TUI 提供的渲染接口) */
|
|
80
|
+
interface TuiHandle {
|
|
81
|
+
requestRender(): void;
|
|
102
82
|
}
|
|
103
83
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (d > 0) return `${d}d${h}h`;
|
|
110
|
-
if (h > 0) return `${h}h${m}m`;
|
|
111
|
-
return `${m}m`;
|
|
84
|
+
/** Footer 渲染句柄(setFooter 回调的返回值) */
|
|
85
|
+
interface FooterHandle {
|
|
86
|
+
dispose(): void;
|
|
87
|
+
invalidate(): void;
|
|
88
|
+
render(width: number): string[];
|
|
112
89
|
}
|
|
113
90
|
|
|
114
|
-
|
|
115
|
-
|
|
91
|
+
/** SDK 缺失的 setFooter 类型 — 仅本扩展需要
|
|
92
|
+
* 绕过 `as any`:先用 `as unknown as` 明确意图,配合类型接口提供类型检查
|
|
93
|
+
* @todo SDK 补齐 setFooter 类型后移除本接口 */
|
|
94
|
+
interface UiWithFooter {
|
|
95
|
+
setFooter(
|
|
96
|
+
fn: (tui: TuiHandle, theme: Theme, footerData: ReadonlyFooterDataProvider) => FooterHandle,
|
|
97
|
+
): void;
|
|
116
98
|
}
|
|
117
99
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (pct >= PCT_MED) return "warning";
|
|
122
|
-
if (pct >= PCT_LOW) return "accent";
|
|
123
|
-
return "success";
|
|
100
|
+
/** 引用包装:让 helper 写入闭包变量 */
|
|
101
|
+
interface TuiRef {
|
|
102
|
+
current: TuiHandle | null;
|
|
124
103
|
}
|
|
125
104
|
|
|
126
|
-
/**
|
|
127
|
-
function
|
|
128
|
-
|
|
105
|
+
/** 注册 statusline footer。从 session_start handler 提取。 */
|
|
106
|
+
function initFooter(
|
|
107
|
+
ctx: ExtensionContext,
|
|
108
|
+
state: StatuslineRuntimeState,
|
|
109
|
+
tuiRef: TuiRef,
|
|
110
|
+
): void {
|
|
111
|
+
(ctx.ui as unknown as UiWithFooter).setFooter(
|
|
112
|
+
(t: TuiHandle, theme: Theme, footerData: ReadonlyFooterDataProvider) => {
|
|
113
|
+
tuiRef.current = t;
|
|
114
|
+
const unsub = footerData.onBranchChange(() => t.requestRender());
|
|
115
|
+
return {
|
|
116
|
+
dispose() {
|
|
117
|
+
unsub();
|
|
118
|
+
tuiRef.current = null;
|
|
119
|
+
},
|
|
120
|
+
invalidate() {},
|
|
121
|
+
render(width: number) {
|
|
122
|
+
return buildLines(ctx, theme, footerData, width, state);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
);
|
|
129
127
|
}
|
|
130
128
|
|
|
131
129
|
// ── 状态 ───────────────────────────────────────────────
|
|
@@ -173,7 +171,7 @@ export default function statuslineExtension(pi: ExtensionAPI) {
|
|
|
173
171
|
|
|
174
172
|
function registerSessionLifecycle(pi: ExtensionAPI): void {
|
|
175
173
|
const state: StatuslineRuntimeState = makeInitialState();
|
|
176
|
-
|
|
174
|
+
const tuiRef: TuiRef = { current: null };
|
|
177
175
|
|
|
178
176
|
pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
|
|
179
177
|
Object.assign(state, makeInitialState(), {
|
|
@@ -181,19 +179,7 @@ function registerSessionLifecycle(pi: ExtensionAPI): void {
|
|
|
181
179
|
thinkingLevel: pi.getThinkingLevel(),
|
|
182
180
|
});
|
|
183
181
|
refreshTotals(state, ctx);
|
|
184
|
-
|
|
185
|
-
ctx.ui.setFooter((t: { requestRender(): void }, theme: Theme, footerData: ReadonlyFooterDataProvider) => {
|
|
186
|
-
tui = t;
|
|
187
|
-
const unsub = footerData.onBranchChange(() => t.requestRender());
|
|
188
|
-
return {
|
|
189
|
-
dispose() { unsub(); tui = null; },
|
|
190
|
-
invalidate() {},
|
|
191
|
-
render(width: number) {
|
|
192
|
-
return buildLines(ctx, theme, footerData, width, state);
|
|
193
|
-
},
|
|
194
|
-
};
|
|
195
|
-
});
|
|
196
|
-
|
|
182
|
+
initFooter(ctx, state, tuiRef);
|
|
197
183
|
triggerUpdate();
|
|
198
184
|
});
|
|
199
185
|
|
|
@@ -219,27 +205,34 @@ function registerSessionLifecycle(pi: ExtensionAPI): void {
|
|
|
219
205
|
state.totalOut += msg.usage.output;
|
|
220
206
|
state.totalCost += msg.usage.cost.total;
|
|
221
207
|
refreshContextUsage(state, ctx);
|
|
222
|
-
|
|
208
|
+
tuiRef.current?.requestRender();
|
|
223
209
|
triggerUpdate();
|
|
224
210
|
});
|
|
225
211
|
|
|
226
212
|
pi.on("turn_end", () => {
|
|
227
213
|
state.isAgentBusy = false;
|
|
228
214
|
state.lastRunUpdate = Date.now();
|
|
229
|
-
|
|
215
|
+
tuiRef.current?.requestRender();
|
|
230
216
|
});
|
|
231
217
|
pi.on("agent_end", () => {
|
|
232
218
|
state.isAgentBusy = false;
|
|
233
219
|
state.lastRunUpdate = Date.now();
|
|
234
|
-
|
|
220
|
+
tuiRef.current?.requestRender();
|
|
235
221
|
});
|
|
222
|
+
pi.on("session_tree", async (_event: unknown, ctx: ExtensionContext) => {
|
|
223
|
+
// 切换分支后重建状态栏数据
|
|
224
|
+
Object.assign(state, makeInitialState(), { sessionStart: Date.now() });
|
|
225
|
+
refreshTotals(state, ctx);
|
|
226
|
+
triggerUpdate();
|
|
227
|
+
});
|
|
228
|
+
|
|
236
229
|
pi.on("model_select", () => {
|
|
237
230
|
state.thinkingLevel = pi.getThinkingLevel();
|
|
238
|
-
|
|
231
|
+
tuiRef.current?.requestRender();
|
|
239
232
|
});
|
|
240
233
|
pi.on("thinking_level_select", (event: PiThinkingLevelEvent) => {
|
|
241
234
|
state.thinkingLevel = event.level;
|
|
242
|
-
if (!state.isAgentBusy)
|
|
235
|
+
if (!state.isAgentBusy) tuiRef.current?.requestRender();
|
|
243
236
|
});
|
|
244
237
|
}
|
|
245
238
|
|
|
@@ -273,17 +266,6 @@ function refreshContextUsage(st: StatuslineRuntimeState, ctx: ExtensionContext):
|
|
|
273
266
|
|
|
274
267
|
// ── 渲染 ───────────────────────────────────────────────
|
|
275
268
|
|
|
276
|
-
interface QuotaRow {
|
|
277
|
-
name: string;
|
|
278
|
-
wins: [QuotaWindow, QuotaWindow, QuotaWindow];
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const COLS = [
|
|
282
|
-
{ key: "5h", label: "5h" },
|
|
283
|
-
{ key: "week", label: "wk" },
|
|
284
|
-
{ key: "month", label: "mh" },
|
|
285
|
-
] as const;
|
|
286
|
-
|
|
287
269
|
type Pallet = {
|
|
288
270
|
d: (s: string) => string;
|
|
289
271
|
v: (s: string) => string;
|
|
@@ -305,26 +287,6 @@ function makePalette(theme: Theme): Pallet {
|
|
|
305
287
|
};
|
|
306
288
|
}
|
|
307
289
|
|
|
308
|
-
/** 缓存数据 → 归一化行(用于 token-plans 显示) */
|
|
309
|
-
function normalizeRows(cache: CacheData, providers: QuotaProvider[]): QuotaRow[] {
|
|
310
|
-
const rows: QuotaRow[] = [];
|
|
311
|
-
for (const p of providers) {
|
|
312
|
-
if (p.category !== "token-plan") continue;
|
|
313
|
-
try {
|
|
314
|
-
const raw = (cache as Record<string, unknown>)[p.id];
|
|
315
|
-
if (!raw) continue;
|
|
316
|
-
const norm = p.normalize(raw);
|
|
317
|
-
if (!norm) continue;
|
|
318
|
-
// 优先使用 providers.json 配置的 label,fallback 到 normalize 返回的 label
|
|
319
|
-
rows.push({ name: p.label || norm.label, wins: norm.wins });
|
|
320
|
-
// eslint-disable-next-line taste/no-silent-catch -- render 容错:单 provider normalize 失败不应拖垮整个 statusline
|
|
321
|
-
} catch (e) {
|
|
322
|
-
console.warn(`[statusline] normalize failed for ${p.id}:`, e);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
return rows;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
290
|
// ── 5 个独立行渲染函数 ─────────────────────────────────
|
|
329
291
|
|
|
330
292
|
function buildLine1(ctx: ExtensionContext, fd: ReadonlyFooterDataProvider, p: Pallet): string {
|
|
@@ -399,58 +361,6 @@ function computeRunMs(st: StatuslineRuntimeState): number {
|
|
|
399
361
|
return 0;
|
|
400
362
|
}
|
|
401
363
|
|
|
402
|
-
function buildSearchLine(
|
|
403
|
-
cache: CacheData,
|
|
404
|
-
providers: QuotaProvider[],
|
|
405
|
-
p: Pallet,
|
|
406
|
-
theme: Theme,
|
|
407
|
-
): string {
|
|
408
|
-
const parts: string[] = [];
|
|
409
|
-
for (const prov of providers) {
|
|
410
|
-
if (prov.category !== "search-tool") continue;
|
|
411
|
-
const raw = (cache as Record<string, unknown>)[prov.id] as Record<string, unknown> | undefined;
|
|
412
|
-
if (!raw) continue;
|
|
413
|
-
// 优先使用 planUsage/planLimit(API 调用次数),fallback 到 available/total(key 数量)
|
|
414
|
-
const used = (raw.planUsage as number) ?? (raw.available as number);
|
|
415
|
-
const total = (raw.planLimit as number) ?? (raw.total as number);
|
|
416
|
-
if (used === undefined || !total || total <= 0) continue;
|
|
417
|
-
const pct = Math.round((used / total) * PERCENT_SCALE);
|
|
418
|
-
const pctCol = theme.fg(pctColor(pct), `${pct}%`);
|
|
419
|
-
parts.push(`${p.d(prov.label)} ${p.g(`${used}`)}/${p.v(`${total}`)} ${pctCol}`);
|
|
420
|
-
}
|
|
421
|
-
return parts.join(" | ");
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
function buildTokenPlanLines(
|
|
425
|
-
cache: CacheData,
|
|
426
|
-
providers: QuotaProvider[],
|
|
427
|
-
p: Pallet,
|
|
428
|
-
theme: Theme,
|
|
429
|
-
): string[] {
|
|
430
|
-
const rows = normalizeRows(cache, providers);
|
|
431
|
-
return rows.map((row) => {
|
|
432
|
-
const title = p.d(row.name.padEnd(TITLE_COL_W));
|
|
433
|
-
const cells = COLS.map((col, i) => {
|
|
434
|
-
const win = row.wins[i]!;
|
|
435
|
-
return formatWinCol(col.label, win, p, theme);
|
|
436
|
-
});
|
|
437
|
-
return title + cells.join(` ${DOT} `);
|
|
438
|
-
});
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/** 渲染单个窗口列:label pct% [reset](无 bar) */
|
|
442
|
-
function formatWinCol(label: string, win: QuotaWindow, p: Pallet, theme: Theme): string {
|
|
443
|
-
const pctWidth = PCT_COL_W + 1; // "NNN%" = padStart(3) + 1 = 4 chars
|
|
444
|
-
if (win.pct === null) {
|
|
445
|
-
// 无限:∞ 右对齐到 pctStr 宽度,reset 用 -- 占位
|
|
446
|
-
return `${p.d(label)} ${p.v("∞".padStart(pctWidth))} ${p.v("--".padStart(RESET_COL_W))}`;
|
|
447
|
-
}
|
|
448
|
-
const pctStr = `${String(Math.round(win.pct)).padStart(PCT_COL_W)}%`;
|
|
449
|
-
const rtRaw = win.resetSec != null && win.resetSec > 0 ? fmtResetSec(win.resetSec) : "";
|
|
450
|
-
const rtStr = rtRaw ? p.v(rtRaw.padStart(RESET_COL_W)) : " ".repeat(RESET_COL_W);
|
|
451
|
-
return `${p.d(label)} ${theme.fg(pctColor(win.pct), pctStr)} ${rtStr}`;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
364
|
function buildLines(
|
|
455
365
|
ctx: ExtensionContext,
|
|
456
366
|
theme: Theme,
|
|
@@ -461,13 +371,14 @@ function buildLines(
|
|
|
461
371
|
const cache = readCache();
|
|
462
372
|
const providers = buildRuntimeProviders();
|
|
463
373
|
const palette = makePalette(theme);
|
|
374
|
+
const themeFg = (token: string, text: string) => theme.fg(token, text);
|
|
464
375
|
|
|
465
376
|
const lines: string[] = [
|
|
466
377
|
buildLine1(ctx, fd, palette),
|
|
467
378
|
buildLine2(ctx, st, palette),
|
|
468
379
|
buildLine3(ctx, st, palette, theme),
|
|
469
|
-
buildSearchLine(cache, providers, palette,
|
|
470
|
-
...buildTokenPlanLines(cache, providers, palette,
|
|
380
|
+
buildSearchLine(cache, providers, palette, themeFg),
|
|
381
|
+
...buildTokenPlanLines(cache, providers, palette, themeFg),
|
|
471
382
|
];
|
|
472
383
|
|
|
473
384
|
// 过滤空行(line2/line3 在某些状态下可能空,line4 没搜索工具时空)
|
package/src/setup.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync, mkdirSync } from "node:fs";
|
|
13
|
+
|
|
13
14
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
14
15
|
import type { ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
|
|
15
16
|
import {
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
loadProvidersConfig,
|
|
20
21
|
loadSecrets,
|
|
21
22
|
} from "@zhushanwen/pi-quota-providers";
|
|
23
|
+
|
|
22
24
|
import { buildGenerateDemoPrompt } from "./setup-prompts.js";
|
|
23
25
|
|
|
24
26
|
export function registerSetupCommand(pi: ExtensionAPI): void {
|