pi-ark-usage 0.1.4 → 0.1.6

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
@@ -3,14 +3,15 @@
3
3
  在 pi TUI 中显示**火山引擎方舟 Coding Plan / Agent Plan** 的套餐用量,数据直接来自火山方舟 OpenAPI `GetCodingPlanUsage`(AK/SK 签名,官方 [`@volcengine/openapi`](https://github.com/volcengine/volc-sdk-nodejs) SDK 的 Signer)。
4
4
 
5
5
  ```
6
- Volc Usage: sess 30%(reset after 4h18m) / wk 37%(reset after 4d) / mo 19%(reset after 29d) now
6
+ 1% 4h38m | 15% 5d4h | 54% 23d4h | 3m ago
7
7
  ```
8
8
 
9
9
  ## 特性
10
10
 
11
11
  - **AK/SK 直连 OpenAPI**:不走子进程、不依赖 SSO 登录态;凭据只从环境变量或配置文件读取,本扩展不保存、不上传任何 Key
12
12
  - **三个周期同屏**:session(会话)/ weekly(周)/ monthly(月),并显示各周期重置倒计时
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) 上;百分比按阈值绿/黄/红着色,时间部分紫色,分隔符跟随主题 `thinkingHigh`(编辑器边框色)。不安装 powerline 时也可通过内置 footer 的 extension statuses 区域或 `/arkusage` 查看
13
+ - **Powerline 状态段(唯一展示位)**:通过 `ctx.ui.setStatus("ark-usage", …)` 发布单行摘要(`1% 4h38m | 15% 5d4h | 54% 23d4h | 3m ago`,固定按 session/weekly/monthly 顺序),显示在 [pi-powerline-footer](https://www.npmjs.com/package/pi-powerline-footer) 上;百分比按阈值绿/黄/红着色,各周期重置倒计时紫色 `#7A5FD0`,` | ` 分隔符与末尾的「上次刷新」统一用粉色 `#D787AF`(与 powerline 模型名段同色)。所有颜色均为固定 hex,不随 pi 主题变化。不安装 powerline 时也可通过内置 footer 的 extension statuses 区域或 `/arkusage` 查看
14
+ - **上次刷新时间**:状态段末尾显示距今多久前**成功**刷新过(分钟精度:`just now` / `3m ago` / `4h38m ago` / `5d4h ago`)。抓取失败时沿用上一次成功的时间,因此该值持续变大即表示刷新已经失败或长时间未刷新
14
15
  - **阈值着色**:低用量绿色(<50%)、接近上限黄色(50–79%)、超过红线红色(≥80%,默认值,可用 `/arkset` 调整)
15
16
  - **持久化缓存**:重启 pi 后立即显示上次快照(`~/.pi/agent/pi-ark-usage/cache.json`,0600 权限,临时文件原子写入)
16
17
  - 可选空闲自动刷新(默认关闭)
@@ -97,7 +98,7 @@ pi install npm:pi-ark-usage
97
98
  1. 用官方 `@volcengine/openapi` 的 `Signer` 对 `POST open.volcengineapi.com?Action=GetCodingPlanUsage&Version=2024-01-01`(service `ark`,region `cn-beijing`)做 V4 签名后 fetch。
98
99
  2. 解析 `Result.QuotaUsage[]`(`Level/Percent/ResetTimestamp`,秒级时间戳转 `+08:00` ISO 字符串),做结构与取值校验。
99
100
  3. 快照存入内存 Map,并以原子写(tmp 文件 + rename)持久化到 `~/.pi/agent/pi-ark-usage/`。
100
- 4. 摘要通过 `ctx.ui.setStatus("ark-usage", …)` 发布到 extension status,由 pi-powerline-footer 渲染到 powerline 行;每分钟 ticker 在空闲时重发,保持重置倒计时刷新。
101
+ 4. 摘要通过 `ctx.ui.setStatus("ark-usage", …)` 发布到 extension status,由 pi-powerline-footer 渲染到 powerline 行;每分钟 ticker 在空闲时重发,保持重置倒计时与「上次刷新」时长显示为最新。
101
102
 
102
103
  ## 开发
103
104
 
@@ -6,7 +6,9 @@
6
6
  * the official `@volcengine/openapi` Signer. This extension never stores keys:
7
7
  * credentials come from VOLC_ACCESSKEY/VOLC_SECRETKEY or ~/.volc/config.
8
8
  *
9
- * Powerline status (via `ctx.ui.setStatus`): 1% 4h38m | 15% 5d4h | 54% 23d4h
9
+ * Powerline status (via `ctx.ui.setStatus`):
10
+ * 1% 4h38m | 15% 5d4h | 54% 23d4h | 3m ago
11
+ * The trailing segment is how long ago the data was last refreshed.
10
12
  * Commands:
11
13
  * /arkcheck force refresh
12
14
  * /arkusage show snapshot in a notification
@@ -124,7 +126,7 @@ function renderStatus(ctx: ExtensionContext): void {
124
126
  // Publish the one-line summary to the extension-status slot (powerline).
125
127
  // The ticker calls renderStatus every minute while idle, keeping the
126
128
  // reset countdown fresh without refetching.
127
- const compact = buildCompactStatus(snap, widgetStatus, ctx.ui.theme);
129
+ const compact = buildCompactStatus(snap, widgetStatus);
128
130
  if (compact) {
129
131
  ctx.ui.setStatus(STATUS_KEY, compact);
130
132
  } else {
@@ -4,7 +4,6 @@
4
4
  * colored one-line summary published via `ctx.ui.setStatus`.
5
5
  */
6
6
 
7
- import type { Theme } from "@earendil-works/pi-coding-agent";
8
7
  import type { ArkSettings, Language, PlanSnapshot } from "./types.js";
9
8
 
10
9
  /* ---------------------------------- i18n ---------------------------------- */
@@ -140,7 +139,14 @@ export const HEX_COLORS = {
140
139
  green: "#1FA87A",
141
140
  yellow: "#F09A3E",
142
141
  red: "#EE7A5F",
142
+ /** Reset countdowns, and the `…` placeholder for a fetch in flight. */
143
143
  purple: "#7A5FD0",
144
+ /**
145
+ * The ` | ` separators and the trailing refresh age. Kept in sync with the
146
+ * model-name segment of pi-powerline-footer, whose `model` color is
147
+ * #d787af, so the separators read as part of the prompt line's palette.
148
+ */
149
+ pink: "#D787AF",
144
150
  } as const;
145
151
 
146
152
  export function hexFg(hex: string, text: string): string {
@@ -202,34 +208,58 @@ export function formatCompactRemaining(ms: number): string {
202
208
  return `${m}m`;
203
209
  }
204
210
 
211
+ /**
212
+ * Age of the last successful refresh, for the trailing status segment:
213
+ * `just now`, `3m ago`, `4h38m ago`, `5d4h ago`. Minute is the finest unit —
214
+ * seconds would only make the line jitter — and a stale cached snapshot thus
215
+ * reads as old at a glance.
216
+ */
217
+ export function formatRefreshAgo(ts: number, now: number = Date.now()): string {
218
+ if (!Number.isFinite(ts) || ts <= 0) return "";
219
+ const ms = now - ts;
220
+ // Clock skew can make a fresh fetch look negative; treat it as "now".
221
+ if (ms < 60_000) return "just now";
222
+ // Drop a zero lower unit (`1h0m` -> `1h`, `5d0h` -> `5d`).
223
+ const age = formatCompactRemaining(ms).replace(/([dh])0[mh]$/, "$1");
224
+ return `${age} ago`;
225
+ }
226
+
205
227
  /**
206
228
  * One-line colored summary for the powerline extension-status slot:
207
- * `1% 4h38m | 15% 5d4h | 54% 23d4h`
229
+ * `1% 4h38m | 15% 5d4h | 54% 23d4h | 3m ago`
208
230
  *
209
231
  * Periods are shown in API order (session / weekly / monthly) without
210
232
  * labels — the order itself is the convention. Percent follows the
211
- * yellow/red thresholds (green below yellow, yellow below red, red at/above
212
- * red); the time part is always purple; the ` | ` separator uses the
213
- * editor border color (`thinkingHigh`, pink in the dark theme).
233
+ * yellow/red thresholds on the used amount (green below yellow, yellow
234
+ * below red, red at/above red); reset countdowns stay purple; the ` | `
235
+ * separators and the trailing refresh age are pink (`HEX_COLORS.pink`,
236
+ * matching the powerline model-name segment).
237
+ *
238
+ * Every color is a fixed hex value — the slot renders identically in any pi
239
+ * theme, so `HEX_COLORS` is the only palette this line depends on.
214
240
  *
215
241
  * Returns null when nothing should be shown (the powerline item hides).
216
242
  */
217
243
  export function buildCompactStatus(
218
244
  snapshot: PlanSnapshot | null,
219
245
  status: WidgetStatus,
220
- theme: Theme,
246
+ now: number = Date.now(),
221
247
  ): string | null {
222
248
  if (snapshot && snapshot.periods.length > 0) {
223
- const sep = theme.fg("thinkingHigh", " | ");
224
- return snapshot.periods
249
+ const sep = hexFg(HEX_COLORS.pink, " | ");
250
+ const periods = snapshot.periods
225
251
  .map((p) => {
226
252
  const pct = coloredPct(clampPct(p.percent));
227
253
  const reset = p.reset_at
228
- ? formatCompactRemaining(new Date(p.reset_at).getTime() - Date.now())
254
+ ? formatCompactRemaining(new Date(p.reset_at).getTime() - now)
229
255
  : "";
230
256
  return reset ? `${pct} ${hexFg(HEX_COLORS.purple, reset)}` : pct;
231
257
  })
232
258
  .join(sep);
259
+ const refreshed = formatRefreshAgo(snapshot.fetchedAt, now);
260
+ return refreshed
261
+ ? `${periods}${sep}${hexFg(HEX_COLORS.pink, refreshed)}`
262
+ : periods;
233
263
  }
234
264
  if (status === "fetching") return hexFg(HEX_COLORS.purple, "…");
235
265
  if (status === "failed") return hexFg(HEX_COLORS.red, "failed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ark-usage",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Show Volcengine Ark Coding Plan / Agent Plan usage in the pi TUI (AK/SK OpenAPI)",
5
5
  "keywords": [
6
6
  "pi",