pi-ark-usage 0.1.1 → 0.1.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 +6 -3
- package/extensions/index.ts +29 -4
- package/extensions/lib/cache.ts +5 -0
- package/extensions/lib/widget.ts +53 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ Volc Usage: sess 30%(reset after 4h18m) / wk 37%(reset after 4d) / mo 19%(reset
|
|
|
10
10
|
|
|
11
11
|
- **AK/SK 直连 OpenAPI**:不走子进程、不依赖 SSO 登录态;凭据只从环境变量或配置文件读取,本扩展不保存、不上传任何 Key
|
|
12
12
|
- **三个周期同屏**:session(会话)/ weekly(周)/ monthly(月),并显示各周期重置倒计时
|
|
13
|
-
-
|
|
13
|
+
- **Powerline 状态段**:通过 `ctx.ui.setStatus("ark-usage", …)` 发布单行摘要(`1% 4h38m | 15% 5d4h | 54% 23d4h`,固定按 session/weekly/monthly 顺序),可直接显示在 [pi-powerline-footer](https://www.npmjs.com/package/pi-powerline-footer) 的 `extension_statuses` / customItems 上;百分比按阈值绿/黄/红着色,时间部分紫色,分隔符跟随主题 `thinkingHigh`(编辑器边框色)
|
|
14
|
+
- **阈值着色**:低用量绿色(<50%)、接近上限黄色(50–79%)、超过红线红色(≥80%,默认值,可用 `/arkset` 调整)
|
|
14
15
|
- **单轮消耗**:以每轮对话开始前的快照为基线,结算时标注各周期增量(如 `(+2)`)
|
|
15
16
|
- **产品切换感知**:在个人版/团队版、Coding Plan/Agent Plan 之间切换时标注 `(变更)`;套餐周期重置时标注 `(已重置)`
|
|
16
17
|
- **持久化缓存**:重启 pi 后立即显示上次快照(`~/.pi/agent/pi-ark-usage/cache.json`,0600 权限,临时文件原子写入)
|
|
@@ -67,7 +68,7 @@ pi install npm:pi-ark-usage
|
|
|
67
68
|
|---|---|
|
|
68
69
|
| `/arkset yellow <n>` | 黄色阈值(百分比,默认 40),须低于红线 |
|
|
69
70
|
| `/arkset red <n>` | 红色阈值(百分比,默认 80),须高于黄线 |
|
|
70
|
-
| `/arkset auto <分钟>` | 空闲自动刷新间隔,0–30 的整数,`0`
|
|
71
|
+
| `/arkset auto <分钟>` | 空闲自动刷新间隔,0–30 的整数,`0` 关闭(默认 5 分钟) |
|
|
71
72
|
| `/arkset product <id>` | 展示映射的产品:`auto`(默认)/ `coding-plan` / `agent-plan` / `coding-plan-team` / `agent-plan-team` |
|
|
72
73
|
| `/arkset seat <id>` | 团队版指定 SeatID;`/arkset seat none` 清除 |
|
|
73
74
|
| `/arkset lang <zh\|en>` | UI language, default English |
|
|
@@ -89,7 +90,9 @@ pi install npm:pi-ark-usage
|
|
|
89
90
|
| `agent_start` | 建立本轮基线(缓存 1 小时内有效;否则限时 3 秒补抓,超时沿用缓存) |
|
|
90
91
|
| `agent_settled` | 本轮结算抓取,并与基线对比标注消耗 |
|
|
91
92
|
| `/arkcheck` | 手动强制刷新 |
|
|
92
|
-
| 可选自动刷新 |
|
|
93
|
+
| 可选自动刷新 | 默认每 5 分钟(`/arkset auto` 可调 0–30,`0` 关闭) |
|
|
94
|
+
|
|
95
|
+
> 注意:`auto` 设置会持久化到磁盘 cache;旧版本用户若曾显式设过 `0`,升级后仍是关闭,需手动 `/arkset auto 5` 开启。
|
|
93
96
|
|
|
94
97
|
## 工作原理
|
|
95
98
|
|
package/extensions/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
ArkUsageComponent,
|
|
27
27
|
AUTO_REFRESH_MAX_MINUTES,
|
|
28
28
|
LOCALES,
|
|
29
|
+
buildCompactStatus,
|
|
29
30
|
buildItems,
|
|
30
31
|
formatRemaining,
|
|
31
32
|
getSettings,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
flushWrites,
|
|
37
38
|
loadCache,
|
|
38
39
|
markShuttingDown,
|
|
40
|
+
resetShutdownState,
|
|
39
41
|
saveCache,
|
|
40
42
|
} from "./lib/cache.js";
|
|
41
43
|
import type {
|
|
@@ -47,6 +49,8 @@ import type {
|
|
|
47
49
|
} from "./lib/types.js";
|
|
48
50
|
|
|
49
51
|
const WIDGET_KEY = "pi-ark-usage";
|
|
52
|
+
/** Extension-status key consumed by pi-powerline-footer (powerline slot). */
|
|
53
|
+
const STATUS_KEY = "ark-usage";
|
|
50
54
|
const TICK_MS = 60_000;
|
|
51
55
|
const AGENT_START_TIMEOUT_MS = 3_000;
|
|
52
56
|
const BASELINE_FRESH_MS = 60 * 60_000;
|
|
@@ -127,14 +131,24 @@ function activeSnapshot(): PlanSnapshot | null {
|
|
|
127
131
|
function renderWidget(ctx: ExtensionContext): void {
|
|
128
132
|
lastCtx = ctx;
|
|
129
133
|
const snap = activeSnapshot();
|
|
134
|
+
const widgetStatus = status === "ok" && !snap ? "none" : status;
|
|
130
135
|
const items: RenderItem[] = buildItems(
|
|
131
136
|
snap,
|
|
132
137
|
language,
|
|
133
|
-
|
|
138
|
+
widgetStatus,
|
|
134
139
|
ctx.isIdle(),
|
|
135
140
|
deltas,
|
|
136
141
|
note,
|
|
137
142
|
);
|
|
143
|
+
// Publish the one-line summary to the extension-status slot (powerline).
|
|
144
|
+
// The ticker calls renderWidget every minute while idle, keeping the
|
|
145
|
+
// reset countdown fresh without refetching.
|
|
146
|
+
const compact = buildCompactStatus(snap, widgetStatus, ctx.ui.theme);
|
|
147
|
+
if (compact) {
|
|
148
|
+
ctx.ui.setStatus(STATUS_KEY, compact);
|
|
149
|
+
} else {
|
|
150
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
151
|
+
}
|
|
138
152
|
if (activeWidget) {
|
|
139
153
|
activeWidget.update(items);
|
|
140
154
|
return;
|
|
@@ -504,15 +518,26 @@ export default function (pi: ExtensionAPI): void {
|
|
|
504
518
|
}
|
|
505
519
|
}
|
|
506
520
|
|
|
507
|
-
pi.on("session_shutdown", async () => {
|
|
508
|
-
shuttingDown = true;
|
|
509
|
-
markShuttingDown();
|
|
521
|
+
pi.on("session_shutdown", async (event) => {
|
|
510
522
|
stopTicker();
|
|
511
523
|
inFlight?.abort();
|
|
524
|
+
// Only a real quit is terminal. Session switches (/new, /resume, /fork,
|
|
525
|
+
// /reload) tear down and then re-enter session_start in the same process;
|
|
526
|
+
// marking terminal shutdown there would block every future refresh.
|
|
527
|
+
if (event.reason === "quit") {
|
|
528
|
+
shuttingDown = true;
|
|
529
|
+
markShuttingDown();
|
|
530
|
+
}
|
|
512
531
|
await flushWrites();
|
|
513
532
|
});
|
|
514
533
|
|
|
515
534
|
pi.on("session_start", (_event, ctx) => {
|
|
535
|
+
// Session switches reuse this module instance in-process: pi has disposed
|
|
536
|
+
// the old TUI widget and torn down the previous runtime, so drop stale
|
|
537
|
+
// handles and revive the shutdown flags before doing anything else.
|
|
538
|
+
shuttingDown = false;
|
|
539
|
+
resetShutdownState();
|
|
540
|
+
activeWidget = null;
|
|
516
541
|
activeProduct = resolveProduct(ctx);
|
|
517
542
|
deltas = null;
|
|
518
543
|
note = null;
|
package/extensions/lib/cache.ts
CHANGED
|
@@ -172,6 +172,11 @@ export function markShuttingDown(): void {
|
|
|
172
172
|
shuttingDown = true;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
/** Revive cache writes after an in-process session switch (see index.ts). */
|
|
176
|
+
export function resetShutdownState(): void {
|
|
177
|
+
shuttingDown = false;
|
|
178
|
+
}
|
|
179
|
+
|
|
175
180
|
async function writeFile(data: string): Promise<void> {
|
|
176
181
|
await mkdir(CACHE_DIR, { recursive: true, mode: DIR_MODE });
|
|
177
182
|
const dir = await lstat(CACHE_DIR);
|
package/extensions/lib/widget.ts
CHANGED
|
@@ -122,9 +122,9 @@ export const LOCALES: Record<Language, Locale> = { zh: ZH, en: EN };
|
|
|
122
122
|
export const AUTO_REFRESH_MAX_MINUTES = 30;
|
|
123
123
|
|
|
124
124
|
export const DEFAULT_SETTINGS: ArkSettings = {
|
|
125
|
-
pctYellow:
|
|
125
|
+
pctYellow: 50,
|
|
126
126
|
pctRed: 80,
|
|
127
|
-
autoRefreshMinutes:
|
|
127
|
+
autoRefreshMinutes: 5,
|
|
128
128
|
product: "auto",
|
|
129
129
|
seat: "",
|
|
130
130
|
};
|
|
@@ -209,6 +209,57 @@ export function formatAge(fetchedAt: number, lang: Language): string {
|
|
|
209
209
|
return LOCALES[lang].ageMinutesAgo(m);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
+
/* ---------------------- compact status (powerline slot) -------------------- */
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Compact reset countdown for the powerline status: `38m`, `4h38m`, `5d4h`.
|
|
216
|
+
* Lower-precision units are dropped to keep the line short and stable.
|
|
217
|
+
*/
|
|
218
|
+
export function formatCompactRemaining(ms: number): string {
|
|
219
|
+
if (!Number.isFinite(ms) || ms <= 0) return "";
|
|
220
|
+
const totalMin = Math.floor(ms / 60_000);
|
|
221
|
+
const d = Math.floor(totalMin / 1440);
|
|
222
|
+
const h = Math.floor((totalMin % 1440) / 60);
|
|
223
|
+
const m = totalMin % 60;
|
|
224
|
+
if (d > 0) return `${d}d${h}h`;
|
|
225
|
+
if (h > 0) return `${h}h${m}m`;
|
|
226
|
+
return `${m}m`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* One-line colored summary for the powerline extension-status slot:
|
|
231
|
+
* `1% 4h38m | 15% 5d4h | 54% 23d4h`
|
|
232
|
+
*
|
|
233
|
+
* Periods are shown in API order (session / weekly / monthly) without
|
|
234
|
+
* labels — the order itself is the convention. Percent follows the
|
|
235
|
+
* yellow/red thresholds (green below yellow, yellow below red, red at/above
|
|
236
|
+
* red); the time part is always purple; the ` | ` separator uses the
|
|
237
|
+
* editor border color (`thinkingHigh`, pink in the dark theme).
|
|
238
|
+
*
|
|
239
|
+
* Returns null when nothing should be shown (the powerline item hides).
|
|
240
|
+
*/
|
|
241
|
+
export function buildCompactStatus(
|
|
242
|
+
snapshot: PlanSnapshot | null,
|
|
243
|
+
status: WidgetStatus,
|
|
244
|
+
theme: Theme,
|
|
245
|
+
): string | null {
|
|
246
|
+
if (snapshot && snapshot.periods.length > 0) {
|
|
247
|
+
const sep = theme.fg("thinkingHigh", " | ");
|
|
248
|
+
return snapshot.periods
|
|
249
|
+
.map((p) => {
|
|
250
|
+
const pct = coloredPct(clampPct(p.percent));
|
|
251
|
+
const reset = p.reset_at
|
|
252
|
+
? formatCompactRemaining(new Date(p.reset_at).getTime() - Date.now())
|
|
253
|
+
: "";
|
|
254
|
+
return reset ? `${pct} ${hexFg(HEX_COLORS.purple, reset)}` : pct;
|
|
255
|
+
})
|
|
256
|
+
.join(sep);
|
|
257
|
+
}
|
|
258
|
+
if (status === "fetching") return hexFg(HEX_COLORS.purple, "…");
|
|
259
|
+
if (status === "failed") return hexFg(HEX_COLORS.red, "failed");
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
212
263
|
/* ----------------------------- snapshot -> items --------------------------- */
|
|
213
264
|
|
|
214
265
|
export type WidgetStatus = "ok" | "fetching" | "failed" | "no-binary" | "none";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-ark-usage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Show Volcengine Ark Coding Plan / Agent Plan usage in the pi TUI (AK/SK OpenAPI)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi",
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
},
|
|
34
34
|
"main": "extensions/index.ts",
|
|
35
35
|
"scripts": {
|
|
36
|
-
"typecheck": "tsc --noEmit"
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "node --import ./test/ts-resolve-hook.mjs --test --experimental-strip-types test/*.test.ts"
|
|
37
38
|
},
|
|
38
39
|
"devDependencies": {
|
|
39
40
|
"@types/node": "^22.19.19",
|