pi-ark-usage 0.1.0
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 +106 -0
- package/extensions/index.ts +552 -0
- package/extensions/lib/arkcli.ts +129 -0
- package/extensions/lib/cache.ts +196 -0
- package/extensions/lib/types.ts +73 -0
- package/extensions/lib/widget.ts +432 -0
- package/package.json +42 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rendering: locales, threshold settings, percent coloring, remaining-time
|
|
3
|
+
* formatting, and the TUI Component for the below-editor widget.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import type { ArkSettings, Language, PlanSnapshot, RenderItem } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/* ---------------------------------- i18n ---------------------------------- */
|
|
10
|
+
|
|
11
|
+
type Locale = {
|
|
12
|
+
title: string;
|
|
13
|
+
labels: Record<string, string>;
|
|
14
|
+
fetching: string;
|
|
15
|
+
failed: string;
|
|
16
|
+
noBinary: string;
|
|
17
|
+
notSubscribed: string;
|
|
18
|
+
noData: string;
|
|
19
|
+
using: string;
|
|
20
|
+
changed: string;
|
|
21
|
+
reset: string;
|
|
22
|
+
resetsIn: (t: string) => string;
|
|
23
|
+
ageNow: string;
|
|
24
|
+
ageMinutesAgo: (m: number) => string;
|
|
25
|
+
ageHoursAgo: (h: number, m: number) => string;
|
|
26
|
+
ageDaysAgo: (d: number, h: number) => string;
|
|
27
|
+
noActiveProduct: string;
|
|
28
|
+
settingsShow: (s: ArkSettings) => string;
|
|
29
|
+
settingsApplied: string;
|
|
30
|
+
settingsReset: string;
|
|
31
|
+
settingsUsage: string;
|
|
32
|
+
autoOn: (m: number) => string;
|
|
33
|
+
autoOff: string;
|
|
34
|
+
languageChanged: (l: Language) => string;
|
|
35
|
+
productSet: (p: string) => string;
|
|
36
|
+
seatSet: (s: string) => string;
|
|
37
|
+
seatCleared: string;
|
|
38
|
+
refreshOk: (summary: string) => string;
|
|
39
|
+
refreshFailed: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const ZH: Locale = {
|
|
43
|
+
title: "火山用量",
|
|
44
|
+
labels: {
|
|
45
|
+
session: "会话",
|
|
46
|
+
"5h": "5h",
|
|
47
|
+
weekly: "周",
|
|
48
|
+
monthly: "月",
|
|
49
|
+
},
|
|
50
|
+
fetching: "请求中",
|
|
51
|
+
failed: "失败",
|
|
52
|
+
noBinary: "未找到 arkcli",
|
|
53
|
+
notSubscribed: "未订阅",
|
|
54
|
+
noData: "暂无数据",
|
|
55
|
+
using: "使用中",
|
|
56
|
+
changed: "变更",
|
|
57
|
+
reset: "已重置",
|
|
58
|
+
resetsIn: (t) => `${t}后重置`,
|
|
59
|
+
ageNow: "刚刚",
|
|
60
|
+
ageMinutesAgo: (m) => `${m}分钟前`,
|
|
61
|
+
ageHoursAgo: (h, m) => (m > 0 ? `${h}小时${m}分前` : `${h}小时前`),
|
|
62
|
+
ageDaysAgo: (d, h) => (h > 0 ? `${d}天${h}小时前` : `${d}天前`),
|
|
63
|
+
noActiveProduct: "当前没有可查询的火山套餐",
|
|
64
|
+
settingsShow: (s) =>
|
|
65
|
+
`当前设置:产品=${s.product} 黄线=${s.pctYellow} 红线=${s.pctRed} 自动刷新=${s.autoRefreshMinutes}分钟 seat=${s.seat || "无"}`,
|
|
66
|
+
settingsApplied: "设置已保存",
|
|
67
|
+
settingsReset: "已恢复默认设置",
|
|
68
|
+
settingsUsage:
|
|
69
|
+
"用法:/arkset <yellow|red|auto|product|seat|reset> ...,如 /arkset red 80、/arkset product coding-plan、/arkset auto 5、/arkset reset",
|
|
70
|
+
autoOn: (m) => `自动刷新已开启:每 ${m} 分钟`,
|
|
71
|
+
autoOff: "自动刷新已关闭",
|
|
72
|
+
languageChanged: (l) => `语言已切换为${l === "zh" ? "中文" : "英文"}`,
|
|
73
|
+
productSet: (p) => `产品已切换为 ${p}`,
|
|
74
|
+
seatSet: (s) => `seat 已设置为 ${s}`,
|
|
75
|
+
seatCleared: "seat 已清除",
|
|
76
|
+
refreshOk: (summary) => `用量已刷新:${summary}`,
|
|
77
|
+
refreshFailed: "用量刷新失败",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const EN: Locale = {
|
|
81
|
+
title: "Volc Usage",
|
|
82
|
+
labels: {
|
|
83
|
+
session: "sess",
|
|
84
|
+
"5h": "5h",
|
|
85
|
+
weekly: "wk",
|
|
86
|
+
monthly: "mo",
|
|
87
|
+
},
|
|
88
|
+
fetching: "fetching",
|
|
89
|
+
failed: "failed",
|
|
90
|
+
noBinary: "arkcli not found",
|
|
91
|
+
notSubscribed: "not subscribed",
|
|
92
|
+
noData: "no data",
|
|
93
|
+
using: "using",
|
|
94
|
+
changed: "changed",
|
|
95
|
+
reset: "reset",
|
|
96
|
+
resetsIn: (t) => `reset after ${t}`,
|
|
97
|
+
ageNow: "now",
|
|
98
|
+
ageMinutesAgo: (m) => `${m}m ago`,
|
|
99
|
+
ageHoursAgo: (h, m) => (m > 0 ? `${h}h${m}m ago` : `${h}h ago`),
|
|
100
|
+
ageDaysAgo: (d, h) => (h > 0 ? `${d}d${h}h ago` : `${d}d ago`),
|
|
101
|
+
noActiveProduct: "No active Volcengine plan",
|
|
102
|
+
settingsShow: (s) =>
|
|
103
|
+
`Settings: product=${s.product} yellow=${s.pctYellow} red=${s.pctRed} autoRefresh=${s.autoRefreshMinutes}m seat=${s.seat || "none"}`,
|
|
104
|
+
settingsApplied: "Settings saved",
|
|
105
|
+
settingsReset: "Settings reset to defaults",
|
|
106
|
+
settingsUsage:
|
|
107
|
+
"Usage: /arkset <yellow|red|auto|product|seat|reset> ..., e.g. /arkset red 80, /arkset product coding-plan, /arkset auto 5, /arkset reset",
|
|
108
|
+
autoOn: (m) => `Auto refresh enabled: every ${m} min`,
|
|
109
|
+
autoOff: "Auto refresh disabled",
|
|
110
|
+
languageChanged: (l) => `Language switched to ${l === "zh" ? "Chinese" : "English"}`,
|
|
111
|
+
productSet: (p) => `Product switched to ${p}`,
|
|
112
|
+
seatSet: (s) => `Seat set to ${s}`,
|
|
113
|
+
seatCleared: "Seat cleared",
|
|
114
|
+
refreshOk: (summary) => `Usage refreshed: ${summary}`,
|
|
115
|
+
refreshFailed: "Failed to refresh usage",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const LOCALES: Record<Language, Locale> = { zh: ZH, en: EN };
|
|
119
|
+
|
|
120
|
+
/* -------------------------------- settings -------------------------------- */
|
|
121
|
+
|
|
122
|
+
export const AUTO_REFRESH_MAX_MINUTES = 30;
|
|
123
|
+
|
|
124
|
+
export const DEFAULT_SETTINGS: ArkSettings = {
|
|
125
|
+
pctYellow: 40,
|
|
126
|
+
pctRed: 80,
|
|
127
|
+
autoRefreshMinutes: 0,
|
|
128
|
+
product: "auto",
|
|
129
|
+
seat: "",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
let settings: ArkSettings = { ...DEFAULT_SETTINGS };
|
|
133
|
+
|
|
134
|
+
export function getSettings(): Readonly<ArkSettings> {
|
|
135
|
+
return settings;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function patchSettings(patch: Partial<ArkSettings>): Readonly<ArkSettings> {
|
|
139
|
+
settings = { ...settings, ...patch };
|
|
140
|
+
return settings;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function resetSettings(): Readonly<ArkSettings> {
|
|
144
|
+
settings = { ...DEFAULT_SETTINGS };
|
|
145
|
+
return settings;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* --------------------------------- colors --------------------------------- */
|
|
149
|
+
|
|
150
|
+
export const HEX_COLORS = {
|
|
151
|
+
green: "#1FA87A",
|
|
152
|
+
yellow: "#F09A3E",
|
|
153
|
+
red: "#EE7A5F",
|
|
154
|
+
purple: "#7A5FD0",
|
|
155
|
+
} as const;
|
|
156
|
+
|
|
157
|
+
export function hexFg(hex: string, text: string): string {
|
|
158
|
+
const r = Number.parseInt(hex.slice(1, 3), 16);
|
|
159
|
+
const g = Number.parseInt(hex.slice(3, 5), 16);
|
|
160
|
+
const b = Number.parseInt(hex.slice(5, 7), 16);
|
|
161
|
+
return `\x1b[38;2;${r};${g};${b}m${text}\x1b[39m`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function pctText(pct: number): string {
|
|
165
|
+
return `${Math.round(pct)}%`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function coloredPct(pct: number): string {
|
|
169
|
+
const t = pctText(pct);
|
|
170
|
+
if (pct >= settings.pctRed) return hexFg(HEX_COLORS.red, t);
|
|
171
|
+
if (pct >= settings.pctYellow) return hexFg(HEX_COLORS.yellow, t);
|
|
172
|
+
return hexFg(HEX_COLORS.green, t);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/* --------------------------------- time fmt -------------------------------- */
|
|
176
|
+
|
|
177
|
+
export function clampPct(pct: unknown): number {
|
|
178
|
+
const n = Number(pct);
|
|
179
|
+
if (!Number.isFinite(n)) return 0;
|
|
180
|
+
return Math.min(100, Math.max(0, n));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function formatRemaining(ms: number, lang: Language): string {
|
|
184
|
+
if (!Number.isFinite(ms) || ms <= 0) return "";
|
|
185
|
+
const totalMin = Math.floor(ms / 60_000);
|
|
186
|
+
const d = Math.floor(totalMin / 1440);
|
|
187
|
+
const h = Math.floor((totalMin % 1440) / 60);
|
|
188
|
+
const m = totalMin % 60;
|
|
189
|
+
if (lang === "zh") {
|
|
190
|
+
if (d > 0) return h > 0 ? `${d}天${h}小时` : `${d}天`;
|
|
191
|
+
if (h > 0) return m > 0 ? `${h}小时${m}分` : `${h}小时`;
|
|
192
|
+
return `${m}分钟`;
|
|
193
|
+
}
|
|
194
|
+
if (d > 0) return h > 0 ? `${d}d${h}h` : `${d}d`;
|
|
195
|
+
if (h > 0) return m > 0 ? `${h}h${m}m` : `${h}h`;
|
|
196
|
+
return `${m}m`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function formatAge(fetchedAt: number, lang: Language): string {
|
|
200
|
+
const age = Date.now() - fetchedAt;
|
|
201
|
+
if (!Number.isFinite(age) || age < 0) return "";
|
|
202
|
+
if (age < 60_000) return LOCALES[lang].ageNow;
|
|
203
|
+
const totalMin = Math.floor(age / 60_000);
|
|
204
|
+
const h = Math.floor(totalMin / 60);
|
|
205
|
+
const m = totalMin % 60;
|
|
206
|
+
const d = Math.floor(h / 24);
|
|
207
|
+
if (d > 0) return LOCALES[lang].ageDaysAgo(d, h % 24);
|
|
208
|
+
if (h > 0) return LOCALES[lang].ageHoursAgo(h, m);
|
|
209
|
+
return LOCALES[lang].ageMinutesAgo(m);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* ----------------------------- snapshot -> items --------------------------- */
|
|
213
|
+
|
|
214
|
+
export type WidgetStatus = "ok" | "fetching" | "failed" | "no-binary" | "none";
|
|
215
|
+
|
|
216
|
+
export function buildItems(
|
|
217
|
+
snapshot: PlanSnapshot | null,
|
|
218
|
+
lang: Language,
|
|
219
|
+
status: WidgetStatus,
|
|
220
|
+
isIdle: boolean,
|
|
221
|
+
/** delta annotations keyed by period label; negative = consumed */
|
|
222
|
+
deltas: Record<string, number> | null,
|
|
223
|
+
note?: "changed" | "reset" | null,
|
|
224
|
+
): RenderItem[] {
|
|
225
|
+
const locale = LOCALES[lang];
|
|
226
|
+
const items: RenderItem[] = [];
|
|
227
|
+
|
|
228
|
+
if (snapshot && snapshot.periods.length > 0) {
|
|
229
|
+
items.push({ kind: "text", text: `${locale.title}: ` });
|
|
230
|
+
snapshot.periods.forEach((p, i) => {
|
|
231
|
+
if (i > 0) items.push({ kind: "text", text: " / " });
|
|
232
|
+
const label = locale.labels[p.label] ?? p.label;
|
|
233
|
+
items.push({ kind: "text", text: `${label} ` });
|
|
234
|
+
items.push({ kind: "pct", pct: clampPct(p.percent), metric: p.label });
|
|
235
|
+
const delta = deltas?.[p.label];
|
|
236
|
+
if (typeof delta === "number" && delta !== 0) {
|
|
237
|
+
const sign = delta > 0 ? "+" : "-";
|
|
238
|
+
items.push({ kind: "annotation", text: `(${sign}${Math.abs(Math.round(delta))})` });
|
|
239
|
+
}
|
|
240
|
+
if (p.reset_at) {
|
|
241
|
+
const remaining = new Date(p.reset_at).getTime() - Date.now();
|
|
242
|
+
const t = formatRemaining(remaining, lang);
|
|
243
|
+
if (t) items.push({ kind: "annotation", text: `(${locale.resetsIn(t)})` });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
} else if (status === "fetching") {
|
|
247
|
+
items.push({ kind: "text", text: `${locale.title}: ` });
|
|
248
|
+
items.push({ kind: "annotation", text: locale.fetching });
|
|
249
|
+
} else if (status === "failed") {
|
|
250
|
+
items.push({ kind: "text", text: `${locale.title}: ` });
|
|
251
|
+
items.push({ kind: "annotation", text: locale.failed });
|
|
252
|
+
} else if (status === "no-binary") {
|
|
253
|
+
items.push({ kind: "annotation", text: locale.noBinary });
|
|
254
|
+
} else {
|
|
255
|
+
items.push({ kind: "annotation", text: locale.noData });
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!isIdle) {
|
|
259
|
+
items.push({ kind: "annotation", text: ` (${locale.using})` });
|
|
260
|
+
} else if (note) {
|
|
261
|
+
items.push({ kind: "annotation", text: ` (${note === "reset" ? locale.reset : locale.changed})` });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (snapshot) {
|
|
265
|
+
items.push({ kind: "age", text: formatAge(snapshot.fetchedAt, lang) });
|
|
266
|
+
}
|
|
267
|
+
return items;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function formatItems(items: RenderItem[], theme: Theme): string {
|
|
271
|
+
return items
|
|
272
|
+
.map((it) => {
|
|
273
|
+
switch (it.kind) {
|
|
274
|
+
case "text":
|
|
275
|
+
return theme.fg("dim", it.text);
|
|
276
|
+
case "pct":
|
|
277
|
+
return coloredPct(it.pct);
|
|
278
|
+
case "age":
|
|
279
|
+
return theme.fg("dim", it.text);
|
|
280
|
+
case "annotation":
|
|
281
|
+
default:
|
|
282
|
+
return hexFg(HEX_COLORS.purple, it.text);
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
.join("");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* ------------------------------ ansi helpers ------------------------------ */
|
|
289
|
+
|
|
290
|
+
export const ANSI_RE = /\x1b\[[0-9;]*m/g;
|
|
291
|
+
|
|
292
|
+
export function visibleWidth(s: string): number {
|
|
293
|
+
let w = 0;
|
|
294
|
+
for (const ch of s.replace(ANSI_RE, "")) {
|
|
295
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
296
|
+
if (cp === 0x200d || (cp >= 0xfe00 && cp <= 0xfe0f)) continue;
|
|
297
|
+
if (cp >= 0x0300 && cp <= 0x036f) continue;
|
|
298
|
+
w +=
|
|
299
|
+
(cp >= 0x1100 && cp <= 0x115f) ||
|
|
300
|
+
(cp >= 0x2e80 && cp <= 0xa4cf) ||
|
|
301
|
+
(cp >= 0xac00 && cp <= 0xd7a3) ||
|
|
302
|
+
(cp >= 0xf900 && cp <= 0xfaff) ||
|
|
303
|
+
(cp >= 0xfe30 && cp <= 0xfe6f) ||
|
|
304
|
+
(cp >= 0xff00 && cp <= 0xff60) ||
|
|
305
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
306
|
+
(cp >= 0x1f300 && cp <= 0x1f9ff)
|
|
307
|
+
? 2
|
|
308
|
+
: 1;
|
|
309
|
+
}
|
|
310
|
+
return w;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function truncateAnsi(s: string, maxWidth: number): string {
|
|
314
|
+
if (maxWidth <= 0) return "";
|
|
315
|
+
if (visibleWidth(s) <= maxWidth) return s;
|
|
316
|
+
const budget = maxWidth - 1;
|
|
317
|
+
let out = "";
|
|
318
|
+
let w = 0;
|
|
319
|
+
let i = 0;
|
|
320
|
+
while (i < s.length) {
|
|
321
|
+
ANSI_RE.lastIndex = i;
|
|
322
|
+
const m = ANSI_RE.exec(s);
|
|
323
|
+
if (m && m.index === i) {
|
|
324
|
+
out += m[0];
|
|
325
|
+
i = ANSI_RE.lastIndex;
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const ch = String.fromCodePoint(s.codePointAt(i) ?? 0);
|
|
329
|
+
const cw = visibleWidth(ch);
|
|
330
|
+
if (w + cw > budget) break;
|
|
331
|
+
out += ch;
|
|
332
|
+
w += cw;
|
|
333
|
+
i += ch.length;
|
|
334
|
+
}
|
|
335
|
+
return `${out}…\x1b[0m`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export function itemsEqual(a: RenderItem[], b: RenderItem[]): boolean {
|
|
339
|
+
if (a.length !== b.length) return false;
|
|
340
|
+
return a.every((it, i) => {
|
|
341
|
+
const o = b[i];
|
|
342
|
+
if (it.kind !== o.kind) return false;
|
|
343
|
+
switch (it.kind) {
|
|
344
|
+
case "pct":
|
|
345
|
+
return (
|
|
346
|
+
o.kind === "pct" && it.pct === o.pct && it.metric === o.metric
|
|
347
|
+
);
|
|
348
|
+
case "text":
|
|
349
|
+
return o.kind === "text" && it.text === o.text;
|
|
350
|
+
case "age":
|
|
351
|
+
return o.kind === "age" && it.text === o.text;
|
|
352
|
+
case "annotation":
|
|
353
|
+
return o.kind === "annotation" && it.text === o.text;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/* -------------------------------- component -------------------------------- */
|
|
359
|
+
|
|
360
|
+
export interface Component {
|
|
361
|
+
render(width: number): string[];
|
|
362
|
+
invalidate(): void;
|
|
363
|
+
dispose?(): void;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Widget rendered below the editor. The age segment is right-aligned and the
|
|
368
|
+
* content wraps to two lines when the terminal is too narrow.
|
|
369
|
+
*/
|
|
370
|
+
export class ArkUsageComponent implements Component {
|
|
371
|
+
private items: RenderItem[];
|
|
372
|
+
private readonly themeRef: () => Theme;
|
|
373
|
+
private readonly requestRender: () => void;
|
|
374
|
+
private cache: { width: number; lines: string[] } | null = null;
|
|
375
|
+
private disposed = false;
|
|
376
|
+
|
|
377
|
+
constructor(
|
|
378
|
+
items: RenderItem[],
|
|
379
|
+
themeRef: () => Theme,
|
|
380
|
+
requestRender: () => void,
|
|
381
|
+
) {
|
|
382
|
+
this.items = items;
|
|
383
|
+
this.themeRef = themeRef;
|
|
384
|
+
this.requestRender = requestRender;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
update(items: RenderItem[]): void {
|
|
388
|
+
if (this.disposed || itemsEqual(this.items, items)) return;
|
|
389
|
+
this.items = items;
|
|
390
|
+
this.invalidate();
|
|
391
|
+
this.requestRender();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
invalidate(): void {
|
|
395
|
+
this.cache = null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
render(width: number): string[] {
|
|
399
|
+
if (this.cache && this.cache.width === width) return this.cache.lines;
|
|
400
|
+
const theme = this.themeRef();
|
|
401
|
+
|
|
402
|
+
const ageIdx = this.items.findIndex((it) => it.kind === "age");
|
|
403
|
+
if (ageIdx === -1) {
|
|
404
|
+
const text = formatItems(this.items, theme);
|
|
405
|
+
const lines = text ? [truncateAnsi(text, width)] : [];
|
|
406
|
+
this.cache = { width, lines };
|
|
407
|
+
return lines;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const leftText = formatItems(this.items.slice(0, ageIdx), theme);
|
|
411
|
+
const rightText = formatItems(this.items.slice(ageIdx), theme);
|
|
412
|
+
const lw = visibleWidth(leftText);
|
|
413
|
+
const rw = visibleWidth(rightText);
|
|
414
|
+
|
|
415
|
+
let lines: string[];
|
|
416
|
+
if (lw + rw + 1 <= width) {
|
|
417
|
+
lines = [leftText + " ".repeat(width - lw - rw) + rightText];
|
|
418
|
+
} else {
|
|
419
|
+
lines = [
|
|
420
|
+
leftText ? truncateAnsi(leftText, width) : "",
|
|
421
|
+
rw <= width ? " ".repeat(Math.max(0, width - rw)) + rightText : truncateAnsi(rightText, width),
|
|
422
|
+
].filter((l) => l !== "");
|
|
423
|
+
}
|
|
424
|
+
this.cache = { width, lines };
|
|
425
|
+
return lines;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
dispose(): void {
|
|
429
|
+
this.disposed = true;
|
|
430
|
+
this.cache = null;
|
|
431
|
+
}
|
|
432
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-ark-usage",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Show Volcengine Ark Coding Plan / Agent Plan usage in the pi TUI via arkcli",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi",
|
|
7
|
+
"pi-package",
|
|
8
|
+
"extension",
|
|
9
|
+
"volcengine",
|
|
10
|
+
"ark",
|
|
11
|
+
"arkcli",
|
|
12
|
+
"coding-plan",
|
|
13
|
+
"usage",
|
|
14
|
+
"quota",
|
|
15
|
+
"tui"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"files": [
|
|
20
|
+
"extensions",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=23.6"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
28
|
+
},
|
|
29
|
+
"pi": {
|
|
30
|
+
"extensions": [
|
|
31
|
+
"./extensions/index.ts"
|
|
32
|
+
]
|
|
33
|
+
},
|
|
34
|
+
"main": "extensions/index.ts",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.19.19",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
}
|
|
42
|
+
}
|