token-stats-timer 1.0.9 → 1.0.11

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # token-stats-timer
2
2
 
3
- `@carlosgtrz/pi-run-timer` 与 `@liziy/token-stats` 的合并插件。
3
+ `@liziy/token-stats` 的修改版本。
4
4
 
5
5
  一个扩展、一个 footer,同时提供 run 计时与 token 用量/配额监控。
6
6
 
@@ -23,10 +23,9 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
23
23
 
24
24
  ## macOS 完成通知
25
25
 
26
- 每次 run(agent 任务)结束后弹系统通知,区分三种结果:
26
+ 每次 run(agent 任务)结束后弹系统通知,区分两种结果:
27
27
 
28
28
  - ✅ pi 执行完成 —— 正常结束(含耗时)
29
- - ❌ pi 执行失败 —— 模型调用出错 / 工具执行失败(含错误摘要)
30
29
  - ⏹ pi 已中止 —— 用户 Esc 中止
31
30
 
32
31
  会话关闭时另有 👋 提示(可关)。通知经 `osascript` 发送,需在系统设置 → 通知 中允许终端 App 通知。
@@ -69,7 +68,7 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
69
68
 
70
69
  - `/stats` —— 无参进入套餐配置(为当前 provider 选择/关闭配额套餐)
71
70
  - `/stats day [YYYY-MM-DD]` / `hour` / `week` / `month [YYYY-MM]` —— 统计查询
72
- - `/stats config` —— 显示样式 / 显示内容(含「计时器」开关)/ 配额刷新时间
71
+ - `/stats config` —— 显示样式 / 显示内容 / 配额刷新时间
73
72
  - `/notify [on|off]` —— macOS 完成通知开关(无参查看状态)
74
73
 
75
74
  ## 与两个原包的兼容性
@@ -81,7 +80,6 @@ Footer 下行:cwd + git 分支 + 其他扩展状态。
81
80
  ## 安装(替换两个原包)
82
81
 
83
82
  ```bash
84
- pi remove npm:@carlosgtrz/pi-run-timer
85
83
  pi remove npm:@liziy/token-stats
86
84
  pi install npm:token-stats-timer
87
85
  ```
package/index.ts CHANGED
@@ -18,7 +18,6 @@
18
18
 
19
19
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
20
20
  import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
21
- import { createRunTimer } from "./run-timer.ts";
22
21
  import { createTokenStats, formatUserPath, type SharedState } from "./token-stats.ts";
23
22
  import { createNotifier } from "./notify.ts";
24
23
  import { createStepTimer } from "./step-timer.ts";
@@ -30,7 +29,6 @@ const shared: SharedState = {
30
29
 
31
30
  export default function runTokenStatsExtension(pi: ExtensionAPI) {
32
31
  const stats = createTokenStats(pi, shared);
33
- const timer = createRunTimer(pi, shared);
34
32
  // macOS 完成通知(成功/失败/中止),配置见 notify-config.json
35
33
  createNotifier(pi);
36
34
  // 每步耗时:Thinking.../Working... 实时耗时 + 每 turn/总耗时会话摘要
@@ -54,13 +52,9 @@ export default function runTokenStatsExtension(pi: ExtensionAPI) {
54
52
  // session 替换后旧 footer 可能仍被 TUI 渲染,此时 ctx 已失效,直接返回空
55
53
  if (!shared.sessionActive) return [];
56
54
 
57
- // ── 上行:计时 + 指标左对齐,模型名右对齐 ──
58
- // 计时器放在指标行末尾(紧邻右侧模型名)
55
+ // ── 上行:指标左对齐,模型名右对齐 ──
59
56
  const leftParts: string[] = [];
60
57
  leftParts.push(...stats.getMetricParts(theme, ctx));
61
- if (stats.isTimerEnabled()) {
62
- leftParts.push(timer.getStatusText(theme));
63
- }
64
58
  const left = leftParts.join(" | ");
65
59
 
66
60
  const modelName = ctx.model?.id || "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-stats-timer",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "pi extension to display token usage and time spend.",
5
5
  "keywords": [
6
6
  "pi-package",
package/step-timer.ts CHANGED
@@ -92,11 +92,12 @@ export function createStepTimer(pi: ExtensionAPI): void {
92
92
  lastCtx = undefined;
93
93
  });
94
94
 
95
- pi.registerEntryRenderer<FinalTimingData>(FINAL_TYPE, (entry, _opts, _theme) => {
95
+ pi.registerEntryRenderer<FinalTimingData>(FINAL_TYPE, (entry, _opts, theme) => {
96
96
  const d = entry.data;
97
97
  if (!d) return undefined;
98
- const box = new Box(1, 1);
99
- box.addChild(new Text(`总耗时 ${formatDuration(d.totalMs)}`, 0, 0));
98
+ const title = theme.fg("accent", "总耗时");
99
+ const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
100
+ box.addChild(new Text(`${title} ${formatDuration(d.totalMs)}`, 0, 0));
100
101
  return box;
101
102
  });
102
103
  }
package/token-stats.ts CHANGED
@@ -138,7 +138,6 @@ export type DisplayKey =
138
138
  | "quota5h" // 5h 额度
139
139
  | "quotaWeek" // 周额度
140
140
  | "quotaClock" // 刷新时间(⏱)
141
- | "timer"; // run 计时(⏱ run/prev/max)
142
141
 
143
142
  export interface DisplayConfig {
144
143
  items: Record<DisplayKey, boolean>;
@@ -465,7 +464,6 @@ const DEFAULT_DISPLAY_CONFIG: DisplayConfig = {
465
464
  quota5h: true,
466
465
  quotaWeek: true,
467
466
  quotaClock: true,
468
- timer: true,
469
467
  },
470
468
  contextStyle: "pct-window",
471
469
  speedStyle: "t/s",
@@ -476,8 +474,6 @@ const DEFAULT_DISPLAY_CONFIG: DisplayConfig = {
476
474
  export interface TokenStatsHandle {
477
475
  /** footer 上行指标段(不含 run 计时;计时由 index.ts 拼接) */
478
476
  getMetricParts(theme: Theme, ctx: ExtensionContext): string[];
479
- /** 是否显示 run 计时段 */
480
- isTimerEnabled(): boolean;
481
477
  }
482
478
 
483
479
  export function createTokenStats(
@@ -1750,10 +1746,10 @@ export function createTokenStats(
1750
1746
  } else if (subChoice === "显示内容") {
1751
1747
  const itemLabels: DisplayKey[] = [
1752
1748
  "input", "output", "totalTokens", "cacheHit", "speed", "context",
1753
- "quota5h", "quotaWeek", "quotaClock", "timer",
1749
+ "quota5h", "quotaWeek", "quotaClock",
1754
1750
  ];
1755
1751
  const itemNames: Record<DisplayKey, string> = {
1756
- timer: "计时器", input: "输入", output: "输出", totalTokens: "总token",
1752
+ input: "输入", output: "输出", totalTokens: "总token",
1757
1753
  cacheHit: "缓存命中", speed: "速度", context: "容量",
1758
1754
  quota5h: "5h额度", quotaWeek: "周额度", quotaClock: "刷新时间",
1759
1755
  };
@@ -1843,6 +1839,5 @@ export function createTokenStats(
1843
1839
 
1844
1840
  return {
1845
1841
  getMetricParts,
1846
- isTimerEnabled: () => displayConfig.items.timer === true,
1847
1842
  };
1848
1843
  }
package/run-timer.ts DELETED
@@ -1,181 +0,0 @@
1
- // run-token-stats / run-timer 模块
2
- // =============================================================================
3
- // 由 @carlosgtrz/pi-run-timer 0.2.0 移植而来,与 @liziy/token-stats 合并为一个插件。
4
- // 原实现通过 ctx.ui.setStatus 注入 footer;合并后改为提供 getStatusText(),
5
- // 由 index.ts 把计时文本拼进统一 footer 的指标行。
6
- //
7
- // 显示:当前 run 已耗时 / 上一次 run 耗时 / 会话分支内最长 run 耗时(带 prompt 预览)
8
- // 一次 run = 从空闲后的第一个 agent_start 到 agent_settled 的连续忙碌期,
9
- // 包含重试、压缩恢复以及排队的 steering/follow-up 提示。
10
- //
11
- // 状态通过 pi.appendEntry 持久化到 session,/reload 后自动恢复。
12
-
13
- import type {
14
- ExtensionAPI,
15
- ExtensionContext,
16
- Theme,
17
- } from "@earendil-works/pi-coding-agent";
18
- import type { SharedState } from "./token-stats.ts";
19
-
20
- const STATE_TYPE = "run-timer-state";
21
- const LEGACY_STATE_TYPE = "turn-timer-state";
22
-
23
- type TimerState = {
24
- currentRunStartMs?: number;
25
- previousRunDurationMs?: number;
26
- longestRunDurationMs?: number;
27
- };
28
-
29
- function formatDuration(ms: number): string {
30
- const totalSeconds = Math.max(0, Math.floor(ms / 1000));
31
- const hours = Math.floor(totalSeconds / 3600);
32
- const minutes = Math.floor((totalSeconds % 3600) / 60);
33
- const seconds = totalSeconds % 60;
34
-
35
- if (hours > 0) {
36
- return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
37
- }
38
- return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`;
39
- }
40
-
41
- function isTimerState(value: unknown): value is TimerState {
42
- if (!value || typeof value !== "object") return false;
43
- const state = value as TimerState;
44
- return (
45
- (state.currentRunStartMs === undefined || typeof state.currentRunStartMs === "number") &&
46
- (state.previousRunDurationMs === undefined || typeof state.previousRunDurationMs === "number") &&
47
- (state.longestRunDurationMs === undefined || typeof state.longestRunDurationMs === "number")
48
- );
49
- }
50
-
51
- export interface RunTimerHandle {
52
- /** 计时段文本,例如 "● run 01:23 · prev 00:40 · max 03:12 (Review README…)" */
53
- getStatusText(theme: Theme): string;
54
- }
55
-
56
- export function createRunTimer(
57
- pi: ExtensionAPI,
58
- shared: SharedState,
59
- ): RunTimerHandle {
60
- let timer: ReturnType<typeof setInterval> | undefined;
61
-
62
- let currentRunStartMs: number | undefined;
63
- let previousRunDurationMs: number | undefined;
64
- let longestRunDurationMs: number | undefined;
65
-
66
- let lastCtx: ExtensionContext | undefined;
67
-
68
- function getState(): TimerState {
69
- return {
70
- currentRunStartMs,
71
- previousRunDurationMs,
72
- longestRunDurationMs,
73
- };
74
- }
75
-
76
- function applyState(state: TimerState): void {
77
- currentRunStartMs = state.currentRunStartMs;
78
- previousRunDurationMs = state.previousRunDurationMs;
79
- longestRunDurationMs = state.longestRunDurationMs;
80
- }
81
-
82
- function saveState(): void {
83
- pi.appendEntry(STATE_TYPE, getState());
84
- }
85
-
86
- function restoreState(ctx: ExtensionContext): void {
87
- for (const entry of [...ctx.sessionManager.getBranch()].reverse()) {
88
- if (entry.type !== "custom") continue;
89
- if (entry.customType !== STATE_TYPE && entry.customType !== LEGACY_STATE_TYPE) continue;
90
- if (isTimerState(entry.data)) {
91
- applyState(entry.data);
92
- }
93
- return;
94
- }
95
- }
96
-
97
- function stopTimer(): void {
98
- if (!timer) return;
99
- clearInterval(timer);
100
- timer = undefined;
101
- }
102
-
103
- function startTimer(): void {
104
- stopTimer();
105
- // 运行期间每秒刷一次 footer,让计时文本跳动
106
- timer = setInterval(() => {
107
- if (!lastCtx?.hasUI) return;
108
- shared.requestRender?.();
109
- }, 1000);
110
- }
111
-
112
- pi.on("session_start", (_event, ctx) => {
113
- lastCtx = ctx;
114
- stopTimer();
115
- restoreState(ctx);
116
- if (!ctx.hasUI) return;
117
- if (currentRunStartMs !== undefined) startTimer();
118
- shared.requestRender?.();
119
- });
120
-
121
- pi.on("agent_start", (_event, ctx) => {
122
- lastCtx = ctx;
123
- if (!ctx.hasUI) return;
124
-
125
- if (currentRunStartMs === undefined) {
126
- currentRunStartMs = Date.now();
127
- startTimer();
128
- }
129
-
130
- shared.requestRender?.();
131
- });
132
-
133
- pi.on("agent_settled", (_event, ctx) => {
134
- lastCtx = ctx;
135
- if (!ctx.hasUI) {
136
- stopTimer();
137
- return;
138
- }
139
- if (currentRunStartMs === undefined) {
140
- shared.requestRender?.();
141
- return;
142
- }
143
-
144
- const duration = Date.now() - currentRunStartMs;
145
- previousRunDurationMs = duration;
146
-
147
- if (longestRunDurationMs === undefined || duration > longestRunDurationMs) {
148
- longestRunDurationMs = duration;
149
- }
150
-
151
- currentRunStartMs = undefined;
152
- stopTimer();
153
- saveState();
154
- shared.requestRender?.();
155
- });
156
-
157
- pi.on("session_shutdown", (_event, _ctx) => {
158
- // 保存计时状态(persist 到 session),停掉 tick 定时器
159
- saveState();
160
- stopTimer();
161
- lastCtx = undefined;
162
- });
163
-
164
- return {
165
- getStatusText(theme: Theme): string {
166
- const startMs = currentRunStartMs;
167
- const working = startMs !== undefined;
168
-
169
- const elapsed = startMs !== undefined ? formatDuration(Date.now() - startMs) : "idle";
170
- const prev = previousRunDurationMs !== undefined ? formatDuration(previousRunDurationMs) : "--:--";
171
- const max = longestRunDurationMs !== undefined ? formatDuration(longestRunDurationMs) : "--:--";
172
-
173
- const indicator = working ? theme.fg("accent", "●") : theme.fg("dim", "○");
174
- const elapsedText = working ? theme.fg("text", elapsed) : theme.fg("dim", elapsed);
175
- const label = theme.fg("dim", " run ");
176
- const stats = theme.fg("dim", ` · prev ${prev} · max ${max}`);
177
-
178
- return `${indicator}${label}${elapsedText}${stats}`;
179
- },
180
- };
181
- }