@ohgodtamit/pi-usage 0.1.0-alpha.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.
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Session aggregation and usage attribution.
3
+ *
4
+ * The usage panel mirrors Claude Code's `/usage` view: it shows how spend and
5
+ * tokens are distributed across models, skills, plugins, tools, and projects,
6
+ * bucketed by time window (5h / 24h / 7d / all).
7
+ *
8
+ * Data source
9
+ * -----------
10
+ * Pi stores per-turn usage (tokens + cost) on every assistant message across
11
+ * all session JSONL files under ~/.pi/agent/sessions/. We open each file once,
12
+ * walk its entries in append order, and attribute each assistant turn to:
13
+ * - a model (from message.model)
14
+ * - a project (from the session header cwd)
15
+ * - skill(s) (detected via parseSkillBlocks on the preceding user msg)
16
+ * - tools/plugins (from the tool calls inside the assistant message)
17
+ *
18
+ * Important: skills, plugins, tools and models are *independent characteristics*
19
+ * of usage, not a disjoint partition — a single turn can contribute to several
20
+ * buckets at once (exactly like Claude Code's wording). Percentages therefore
21
+ * do not sum to 100% across categories.
22
+ */
23
+ import type { Usage } from "@earendil-works/pi-ai";
24
+ import { type ExtensionAPI, type SessionInfo } from "@earendil-works/pi-coding-agent";
25
+ import type { ScanCache } from "./cache.ts";
26
+ import type { ModelPrice } from "./config.ts";
27
+ /** A lookup of manual model prices ($/Mtok), keyed by model ID. */
28
+ export type PriceMap = Record<string, ModelPrice>;
29
+ /**
30
+ * Resolve a manual price for a model: exact match first, then by base name
31
+ * (after the last `/`) so an entry like `claude-opus-4.7` covers proxied
32
+ * variants such as `kr/claude-opus-4.7` and `cx/claude-opus-4.7`.
33
+ */
34
+ export declare function resolveModelPrice(model: string, prices: PriceMap | undefined): ModelPrice | undefined;
35
+ /** Compute a USD cost from token usage and a manual price ($/Mtok). */
36
+ export declare function costFromPrice(usage: Usage, price: ModelPrice): number;
37
+ /** Time windows selectable in the panel. */
38
+ export type WindowKey = "5h" | "24h" | "7d" | "all";
39
+ export declare function windowMs(key: WindowKey): number;
40
+ export declare function windowLabel(key: WindowKey): string;
41
+ /** Mutable usage totals for a bucket. */
42
+ export interface Bucket {
43
+ cost: number;
44
+ costInput: number;
45
+ costOutput: number;
46
+ costCacheRead: number;
47
+ costCacheWrite: number;
48
+ input: number;
49
+ output: number;
50
+ cacheRead: number;
51
+ cacheWrite: number;
52
+ cacheWrite1h: number;
53
+ /** Reported reasoning tokens; a labelled subset of output, never additive. */
54
+ reasoning: number;
55
+ turns: number;
56
+ /**
57
+ * Estimated generation time (ms) summed across turns. Used to derive an
58
+ * average output-tokens/second. It's an estimate: pi's Usage carries no
59
+ * duration, so we approximate per-turn time from the gap between an
60
+ * assistant turn and the preceding session entry (idle/tool gaps clamped).
61
+ */
62
+ genMs: number;
63
+ /** Turns that contributed a usable genMs estimate (for tok/s averaging). */
64
+ timedTurns: number;
65
+ }
66
+ /** Total tokens consumed by a bucket (input + output + cache reads/writes). */
67
+ export declare function bucketTokens(b: Bucket): number;
68
+ /**
69
+ * Average output-tokens/second for a bucket, estimated from `genMs`.
70
+ * Returns 0 when no timed turns are available. Output tokens are used because
71
+ * that's the generation throughput users mean by "tok/s".
72
+ */
73
+ export declare function tokensPerSecond(b: Bucket): number;
74
+ /** A single attributed assistant turn on the timeline. */
75
+ export interface TurnEntry {
76
+ ts: number;
77
+ model: string;
78
+ provider: string;
79
+ project: string;
80
+ cost: number;
81
+ usage: Usage;
82
+ /** Primary skill (first in a multi-skill activation). */
83
+ skill: string | null;
84
+ /** All skills from multi-skill or single-skill activation. */
85
+ skills: string[];
86
+ /** Bundle names from multi-skill (@bundle) activation. */
87
+ bundles: string[];
88
+ tools: string[];
89
+ /** Estimated generation time for this turn in ms (0 when not estimable). */
90
+ genMs: number;
91
+ sessionId: string;
92
+ sessionPath: string;
93
+ delegated: boolean;
94
+ parentSessionId: string | null;
95
+ }
96
+ /**
97
+ * Backfill fields missing on legacy cached turns (pre multi-skill cache entries).
98
+ * Safe to call on every cache hit and before aggregating.
99
+ */
100
+ export declare function normalizeTurnEntry(entry: TurnEntry): TurnEntry;
101
+ /** Skills attributed to a turn (multi-skill aware, legacy-safe). */
102
+ export declare function skillsForTurn(turn: TurnEntry): string[];
103
+ /** Bundles attributed to a turn (multi-skill @bundle activation). */
104
+ export declare function bundlesForTurn(turn: TurnEntry): string[];
105
+ /** Soft metadata for one authoritative delegated transcript. */
106
+ export interface ChildSessionSummary {
107
+ id: string;
108
+ path: string;
109
+ parentSessionId: string | null;
110
+ parentLabel: string;
111
+ project: string;
112
+ task: string;
113
+ agentType: string;
114
+ status: string;
115
+ isBackground?: boolean;
116
+ startedAt: number;
117
+ endedAt: number;
118
+ timingInferred: boolean;
119
+ model?: string;
120
+ stack?: string;
121
+ thinking?: string;
122
+ compactions: number;
123
+ }
124
+ /** Full raw report built once, then windowed on demand. */
125
+ export interface Report {
126
+ computedAt: number;
127
+ sessionCount: number;
128
+ turnCount: number;
129
+ entries: TurnEntry[];
130
+ children: ChildSessionSummary[];
131
+ }
132
+ /** Runtime-derived maps for attributing tools/skills to plugin labels. */
133
+ export interface AttributionMaps {
134
+ toolToPlugin: Map<string, string>;
135
+ skillToPlugin: Map<string, string>;
136
+ }
137
+ /** Build tool->plugin and skill->plugin maps from the currently loaded resources. */
138
+ export declare function buildAttributionMaps(pi: ExtensionAPI): AttributionMaps;
139
+ /** Extract bundle names from manually_attached_skills bundles="..." attribute. */
140
+ export declare function parseSkillBundles(text: string): string[];
141
+ /** Extract all skill names from user content (multi-skill aware). */
142
+ export declare function parseSkillBlocks(text: string): string[];
143
+ /**
144
+ * Scan every session file and build the timeline of attributed turns.
145
+ *
146
+ * `onProgress` receives (loaded, total) for UI feedback. Resolves even if some
147
+ * files fail to parse — bad files are skipped with a console warning.
148
+ *
149
+ * When a `cache` is supplied, sessions whose file mtime + size are unchanged
150
+ * (and whose cost was computed with the same price table) are reused without
151
+ * re-reading/parsing the file — only new or modified sessions are parsed. The
152
+ * cache object is updated in place; the caller persists it.
153
+ */
154
+ export declare function scanSessions(maxSessions: number, excludes: string[], onProgress?: (loaded: number, total: number) => void, prices?: PriceMap, cache?: ScanCache): Promise<Report>;
155
+ /** Loose `subagents:record` metadata payload (authored by delegation frameworks). */
156
+ export type SoftRecord = Record<string, unknown>;
157
+ /** A top-level session excluded from the roots because its header names a parent. */
158
+ export interface ParentedSession {
159
+ info: SessionInfo;
160
+ parentSession: string;
161
+ }
162
+ interface DiscoveredSession {
163
+ info: SessionInfo;
164
+ project: string;
165
+ delegated: boolean;
166
+ parentSessionId: string | null;
167
+ parentLabel: string;
168
+ }
169
+ interface SessionHeader {
170
+ id?: string;
171
+ cwd?: string;
172
+ parentSession?: string;
173
+ }
174
+ /**
175
+ * Read just the file's leading lines to find the session header. Roots are
176
+ * classified before `maxSessions`, so parsing every full transcript here
177
+ * would defeat the incremental cache on large histories.
178
+ */
179
+ export declare function headerFor(path: string): SessionHeader;
180
+ export declare function discoverChildSessionFiles(rootFile: string): string[];
181
+ /**
182
+ * Resolve the selected roots plus their delegated child transcripts into the
183
+ * flat session list the scanner walks. `parented` carries top-level sessions
184
+ * excluded from the roots because their header names a parent: those whose
185
+ * parent chain (session id or path) resolves transitively to a selected root
186
+ * are attached as direct delegated children. Exported as a narrow seam so
187
+ * fixture tests can exercise discovery without touching real user sessions.
188
+ */
189
+ export declare function discoverSelectedSessions(roots: SessionInfo[], parented?: ParentedSession[]): DiscoveredSession[];
190
+ /** Per-plugin contribution detail for the Plugin usage section. */
191
+ export interface PluginContribution {
192
+ bucket: Bucket;
193
+ /** Skills of this plugin that were invoked (→ turn buckets). */
194
+ skills: Map<string, Bucket>;
195
+ /** Tools owned by this plugin that were called (→ turn buckets). */
196
+ tools: Map<string, Bucket>;
197
+ }
198
+ /** A windowed view of the report ready for rendering. */
199
+ export interface WindowedReport {
200
+ window: WindowKey;
201
+ total: Bucket;
202
+ fiveHour: Bucket;
203
+ weekly: Bucket;
204
+ byModel: Map<string, Bucket>;
205
+ bySkill: Map<string, Bucket>;
206
+ byBundle: Map<string, Bucket>;
207
+ byPlugin: Map<string, Bucket>;
208
+ /** Per-plugin detail: which skills/tools drove each plugin's usage. */
209
+ pluginDetail: Map<string, PluginContribution>;
210
+ /** Turns that used NO plugin tool/skill (builtin-only, no preceding skill). */
211
+ byCore: Bucket;
212
+ byTool: Map<string, Bucket>;
213
+ byProject: Map<string, Bucket>;
214
+ direct: Bucket;
215
+ delegated: Bucket;
216
+ children: ChildSessionSummary[];
217
+ concurrency: ConcurrencyStats;
218
+ turnCount: number;
219
+ sessionCount: number;
220
+ earliest: number;
221
+ latest: number;
222
+ }
223
+ /** Filter the timeline to a window and roll up all breakdowns. */
224
+ export declare function windowize(report: Report, key: WindowKey, maps: AttributionMaps): WindowedReport;
225
+ export interface ConcurrencyStats {
226
+ childCount: number;
227
+ parentCount: number;
228
+ peak: number | null;
229
+ unionMs: number | null;
230
+ summedMs: number | null;
231
+ parallelism: number | null;
232
+ overlapSavedMs: number | null;
233
+ inferred: boolean;
234
+ }
235
+ /** Compute overlap statistics for valid child intervals, optionally window-clamped. */
236
+ export declare function computeConcurrency(children: ChildSessionSummary[], windowStart?: number, windowEnd?: number): ConcurrencyStats;
237
+ /** Map<K, V> sorted (desc) by a numeric extractor → array of [key, V]. */
238
+ export declare function ranked<V>(map: Map<string, V>, value: (v: V) => number): Array<[string, V]>;
239
+ /** A single calendar day's rolled-up usage. */
240
+ export interface DayStat {
241
+ /** Local date key `YYYY-MM-DD`. */
242
+ dateKey: string;
243
+ /** Epoch ms at local midnight for that day (used for sorting/streaks). */
244
+ ts: number;
245
+ bucket: Bucket;
246
+ /** Per-model usage that day (model → bucket); size = distinct models. */
247
+ models: Map<string, Bucket>;
248
+ /** First / last turn timestamp that day. */
249
+ firstTs: number;
250
+ lastTs: number;
251
+ /**
252
+ * Active working time that day in ms: the sum of gaps between consecutive
253
+ * turns that are below the idle threshold. Long idle gaps (pi open but not
254
+ * working) are excluded, so this reflects time pi was actually busy.
255
+ */
256
+ activeMs: number;
257
+ }
258
+ /** A day's active working time (idle excluded), in ms. */
259
+ export declare function dayUptimeMs(d: DayStat): number;
260
+ /**
261
+ * The day's most-used model — ranked by tokens, not cost. Tokens are the
262
+ * reliable "how much did I use this model" signal: many providers are
263
+ * token-priced (cost 0), so ranking by cost would just surface whichever
264
+ * model happened to run first that day.
265
+ */
266
+ export declare function dayTopModel(d: DayStat): string | null;
267
+ /** Roll up the full timeline into per-day buckets, sorted ascending by date. */
268
+ export declare function dailyStats(report: Report): DayStat[];
269
+ /** Which metric drives the heatmap/stats intensity. */
270
+ export type Metric = "usd" | "tokens";
271
+ /** Pick the metric value out of a bucket. */
272
+ export declare function metricValue(b: Bucket, metric: Metric): number;
273
+ /** Choose the natural metric for a report: USD when there's real pricing. */
274
+ export declare function naturalMetric(days: DayStat[]): Metric;
275
+ /** One cell (one calendar day) in the contribution graph. */
276
+ export interface ContribCell {
277
+ dateKey: string;
278
+ ts: number;
279
+ value: number;
280
+ /** Intensity bucket 0..4 (0 = no activity). */
281
+ level: number;
282
+ }
283
+ /** GitHub-style contribution graph: columns = weeks, 7 rows = Sun..Sat. */
284
+ export interface ContribGraph {
285
+ /** weeks[col][row] — row 0 = Sunday. Empty cells (future/pre-range) are null. */
286
+ weeks: Array<Array<ContribCell | null>>;
287
+ maxValue: number;
288
+ metric: Metric;
289
+ }
290
+ /**
291
+ * Build a ~53-week contribution graph ending on the current week, aligned so
292
+ * each column is a Sun..Sat week (mirrors GitHub / Tokscale's Stats view).
293
+ */
294
+ export declare function contributionGraph(report: Report, weeks?: number, metric?: Metric): ContribGraph;
295
+ /** Selectable time range for the Stats view summary. */
296
+ export type StatsRange = "all" | "30d" | "7d";
297
+ /** Epoch-ms lower bound for a stats range (-1 = all time). */
298
+ export declare function rangeSince(range: StatsRange): number;
299
+ /** Human label for a stats range. */
300
+ export declare function rangeLabel(range: StatsRange): string;
301
+ /** Lifetime usage statistics for the Stats view. */
302
+ export interface UsageStats {
303
+ totalCost: number;
304
+ totalTokens: number;
305
+ totalTurns: number;
306
+ activeDays: number;
307
+ currentStreak: number;
308
+ longestStreak: number;
309
+ busiestDay: {
310
+ dateKey: string;
311
+ value: number;
312
+ } | null;
313
+ firstDay: string | null;
314
+ lastDay: string | null;
315
+ avgPerActiveDay: number;
316
+ /** Most-used model in range (by the active metric). */
317
+ favoriteModel: string | null;
318
+ /** Hour-of-day (0-23) with the most usage, or null when no activity. */
319
+ peakHour: number | null;
320
+ metric: Metric;
321
+ }
322
+ /**
323
+ * Compute usage stats (totals, active days, streaks, busiest day, favorite
324
+ * model, peak hour). `sinceMs` filters the timeline (-1 = all time).
325
+ */
326
+ export declare function computeStats(report: Report, metric?: Metric, sinceMs?: number): UsageStats;
327
+ /** Usage rolled up by local hour-of-day (0–23), across all days. */
328
+ export interface HourStat {
329
+ hour: number;
330
+ bucket: Bucket;
331
+ models: Map<string, Bucket>;
332
+ }
333
+ /** Aggregate the timeline by hour-of-day (local clock). Always returns 24 slots. */
334
+ export declare function hourlyStats(report: Report): HourStat[];
335
+ /** Top model for an hour slot (by tokens). */
336
+ export declare function hourTopModel(h: HourStat): string | null;
337
+ /** Usage rolled up by provider (agent backend). */
338
+ export interface AgentStat {
339
+ provider: string;
340
+ bucket: Bucket;
341
+ models: Map<string, Bucket>;
342
+ projects: Set<string>;
343
+ firstTs: number;
344
+ lastTs: number;
345
+ }
346
+ /** Aggregate the timeline by provider, sorted by tokens descending. */
347
+ export declare function agentStats(report: Report): AgentStat[];
348
+ /** Top model for a provider (by tokens). */
349
+ export declare function agentTopModel(a: AgentStat): string | null;
350
+ /** Calendar years present in the report (newest first). */
351
+ export declare function availableYears(report: Report): number[];
352
+ /** Default Wrapped AI year: current year if active, else the year with most tokens. */
353
+ export declare function defaultWrappedYear(report: Report): number;
354
+ /** Compact year-in-review stats for the Wrapped AI view. */
355
+ export interface WrappedStats {
356
+ year: number;
357
+ totalCost: number;
358
+ totalTokens: number;
359
+ totalTurns: number;
360
+ activeDays: number;
361
+ currentStreak: number;
362
+ longestStreak: number;
363
+ favoriteModel: string | null;
364
+ favoriteProvider: string | null;
365
+ topProject: string | null;
366
+ busiestDay: {
367
+ dateKey: string;
368
+ value: number;
369
+ } | null;
370
+ peakHour: number | null;
371
+ avgPerActiveDay: number;
372
+ modelCount: number;
373
+ providerCount: number;
374
+ projectCount: number;
375
+ /** Token totals per calendar month (Jan..Dec) for the selected year. */
376
+ monthlyTokens: number[];
377
+ topModels: Array<{
378
+ name: string;
379
+ tokens: number;
380
+ pct: number;
381
+ }>;
382
+ topProviders: Array<{
383
+ name: string;
384
+ tokens: number;
385
+ pct: number;
386
+ }>;
387
+ metric: Metric;
388
+ }
389
+ /** Build Wrapped AI stats for a calendar year. Returns null when the year has no activity. */
390
+ export declare function wrappedStats(report: Report, year: number): WrappedStats | null;
391
+ export {};
392
+ //# sourceMappingURL=aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../src/aggregate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAA8B,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EACL,KAAK,YAAY,EAGjB,KAAK,WAAW,EAEjB,MAAM,iCAAiC,CAAC;AAWzC,OAAO,KAAK,EAAiB,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,mEAAmE;AACnE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAElD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAC3B,UAAU,GAAG,SAAS,CASxB;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAOrE;AAED,4CAA4C;AAC5C,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;AAKpD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAW/C;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAWlD;AAKD,yCAAyC;AACzC,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;CACpB;AAwCD,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,0DAA0D;AAC1D,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,KAAK,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAS9D;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CAGvD;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,EAAE,CAExD;AAED,gEAAgE;AAChE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,2DAA2D;AAC3D,MAAM,WAAW,MAAM;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,YAAY,GAAG,eAAe,CAiBtE;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAOxD;AAED,qEAAqE;AACrE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAUvD;AAsBD;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAAE,EAClB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,EACpD,MAAM,CAAC,EAAE,QAAQ,EACjB,KAAK,CAAC,EAAE,SAAS,GAChB,OAAO,CAAC,MAAM,CAAC,CAoGjB;AAED,qFAAqF;AACrF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,iBAAiB;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,aAAa;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAuBD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAwBrD;AAOD,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBpE;AAuCD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,WAAW,EAAE,EACpB,QAAQ,GAAE,eAAe,EAAO,GAC/B,iBAAiB,EAAE,CAwJrB;AAoPD,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,oEAAoE;IACpE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5B;AAED,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,uEAAuE;IACvE,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC9C,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,WAAW,EAAE,gBAAgB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kEAAkE;AAClE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,GAAG,cAAc,CAyH/F;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,mBAAmB,EAAE,EAC/B,WAAW,CAAC,EAAE,MAAM,EACpB,SAAS,CAAC,EAAE,MAAM,GACjB,gBAAgB,CAyDlB;AAWD,0EAA0E;AAC1E,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAE1F;AAiBD,+CAA+C;AAC/C,MAAM,WAAW,OAAO;IACtB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAKD,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAWrD;AAqBD,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAkDpD;AAED,uDAAuD;AACvD,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEtC,6CAA6C;AAC7C,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAGrD;AAED,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,iFAAiF;IACjF,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAK,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CA2C3F;AAED,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC;AAE9C,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAKpD;AAED,qCAAqC;AACrC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CASpD;AAED,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,uDAAuD;IACvD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,UAAU,CA0FtF;AAMD,oEAAoE;AACpE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAeD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,EAAE,CAiBtD;AAED,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAEvD;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uEAAuE;AACvE,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,CA4BtD;AAED,4CAA4C;AAC5C,wBAAgB,aAAa,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAEzD;AAED,2DAA2D;AAC3D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAMvD;AAED,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAmBzD;AAED,4DAA4D;AAC5D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,wEAAwE;IACxE,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,YAAY,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8FAA8F;AAC9F,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAiF9E"}
@@ -0,0 +1,27 @@
1
+ import type { ModelPrice } from "./config.ts";
2
+ import type { ChildSessionSummary, SoftRecord, TurnEntry } from "./aggregate.ts";
3
+ export interface CachedSession {
4
+ mtimeMs: number;
5
+ size: number;
6
+ entries: TurnEntry[];
7
+ child?: ChildSessionSummary;
8
+ /** `subagents:record` metadata entries found in this transcript. */
9
+ records?: SoftRecord[];
10
+ }
11
+ export interface ScanCache {
12
+ version: number;
13
+ /** Fingerprint of the price table the cached costs were computed with. */
14
+ pricesKey: string;
15
+ /** Fingerprint of excluded project prefixes used during attribution. */
16
+ excludesKey: string;
17
+ /** Per-session-file cached attribution, keyed by absolute file path. */
18
+ sessions: Record<string, CachedSession>;
19
+ }
20
+ /** Stable fingerprint of the price table (key order-independent). */
21
+ export declare function excludesFingerprint(excludes: string[] | undefined): string;
22
+ export declare function pricesFingerprint(prices: Record<string, ModelPrice> | undefined): string;
23
+ /** Load the scan cache; returns an empty cache on any error/missing file. */
24
+ export declare function loadScanCache(): ScanCache;
25
+ /** Persist the scan cache. Never throws. */
26
+ export declare function saveScanCache(cache: ScanCache): void;
27
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAKjF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,oEAAoE;IACpE,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,WAAW,EAAE,MAAM,CAAC;IACpB,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CACzC;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,CAK1E;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS,GAAG,MAAM,CAQxF;AAMD,6EAA6E;AAC7E,wBAAgB,aAAa,IAAI,SAAS,CAwBzC;AAED,4CAA4C;AAC5C,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAQpD"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Manual price for a model, in USD per **million tokens**. Used to compute a
3
+ * cost for token-priced / proxied providers that pi records with cost 0
4
+ * (e.g. zai/GLM, 9Router `kr/…`, `cx/…`). Omitted fields count as 0.
5
+ */
6
+ export interface ModelPrice {
7
+ input?: number;
8
+ output?: number;
9
+ cacheRead?: number;
10
+ cacheWrite?: number;
11
+ }
12
+ export interface UsageConfig {
13
+ /** USD budget for the rolling 5-hour window. 0/undefined disables the bar. */
14
+ fiveHourLimit?: number;
15
+ /** USD budget for the rolling 7-day (weekly) window. 0/undefined disables the bar. */
16
+ weeklyLimit?: number;
17
+ /** Token budget for the rolling 5-hour window (for token-priced providers like zai/GLM). */
18
+ fiveHourTokenLimit?: number;
19
+ /** Token budget for the rolling 7-day (weekly) window. */
20
+ weeklyTokenLimit?: number;
21
+ /** When true, show a compact one-line usage summary widget above the editor. */
22
+ showWidget?: boolean;
23
+ /** Project cwd prefixes to exclude from aggregation (e.g. throwaway dirs). */
24
+ excludeProjects?: string[];
25
+ /** Maximum number of session files to scan (safety cap for huge histories). */
26
+ maxSessions?: number;
27
+ /**
28
+ * Manual per-model prices ($/million tokens) used to fill in cost when pi
29
+ * recorded none. Keyed by model ID; an entry keyed by the base name (without
30
+ * a proxy prefix like `kr/`) matches all proxied variants.
31
+ */
32
+ modelPrices?: Record<string, ModelPrice>;
33
+ }
34
+ /** Load config, merged with defaults. Never throws — returns defaults on error. */
35
+ export declare function loadConfig(): UsageConfig;
36
+ /** Persist config to disk. Creates the agent dir if needed. */
37
+ export declare function saveConfig(config: UsageConfig): void;
38
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAC1C;AAiBD,mFAAmF;AACnF,wBAAgB,UAAU,IAAI,WAAW,CAkBxC;AAED,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAQpD"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Number, currency, and progress-bar formatting helpers for the usage panel.
3
+ *
4
+ * Kept dependency-free so it is easy to unit-test and reuse.
5
+ */
6
+ /** Format a token count with k/M suffixes. */
7
+ export declare function formatTokens(n: number): string;
8
+ /** Format a USD cost. Small amounts get more precision. */
9
+ export declare function formatCost(n: number): string;
10
+ /** Percentage of `part` relative to `total`, as a rounded integer string. */
11
+ export declare function percent(part: number, total: number): string;
12
+ /** Render a horizontal progress bar. Returns the bar string (without ANSI). */
13
+ export declare function bar(ratio: number, width: number): string;
14
+ /** Shorten an absolute path to a friendly project label (~/... style). */
15
+ export declare function shortenPath(p: string, home: string): string;
16
+ /**
17
+ * Derive a human-friendly plugin label from a resource's SourceInfo.
18
+ *
19
+ * Priority:
20
+ * 1. Package sources (npm:/git:/github:) → package name (ref stripped)
21
+ * 2. Local/auto skills → the `/skills/<group>/` segment from the path, so
22
+ * e.g. `~/.claude/skills/bmad/core/bmad-master/SKILL.md` → "bmad" and
23
+ * `~/.pi/agent/skills/frontend-design/SKILL.md` → "frontend-design".
24
+ * 3. Local extensions → last baseDir segment (skipping generic names)
25
+ * 4. fallback "other"
26
+ */
27
+ export declare function sourceLabel(sourceInfo: {
28
+ source?: string;
29
+ baseDir?: string;
30
+ path?: string;
31
+ }): string;
32
+ /** Human day label from a `YYYY-MM-DD` key, e.g. "Mon Jun 17". */
33
+ export declare function formatDayLabel(dateKey: string): string;
34
+ /** Short month name for a 1-based month index (1 = Jan). */
35
+ export declare function monthLabel(month1: number): string;
36
+ /** Render a unicode sparkline for a series of non-negative values. */
37
+ export declare function sparkline(values: number[]): string;
38
+ /** Heatmap glyphs per intensity level (0 = empty). */
39
+ export declare const HEAT_CHARS: readonly ["·", "▪", "▩", "▣", "█"];
40
+ /** Format an hour-of-day (0-23) as a friendly 12-hour label, e.g. "2pm". */
41
+ export declare function formatHour(h: number): string;
42
+ /** Format an integer with thousands separators (e.g. 24086 → "24,086"). */
43
+ export declare function formatInt(n: number): string;
44
+ /** Format a duration in ms as a compact human string (e.g. "2h 14m"). */
45
+ export declare function formatDuration(ms: number): string;
46
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8CAA8C;AAC9C,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK9C;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,6EAA6E;AAC7E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,+EAA+E;AAC/E,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAW3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CA8BT;AASD,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKtD;AAED,4DAA4D;AAC5D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjD;AAID,sEAAsE;AACtE,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAclD;AAED,sDAAsD;AACtD,eAAO,MAAM,UAAU,oCAAqC,CAAC;AAE7D,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED,2EAA2E;AAC3E,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3C;AAED,yEAAyE;AACzE,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAYjD"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * In-memory report-cache freshness decision.
3
+ *
4
+ * The usage panel reuses a scanned `Report` for up to `CACHE_TTL` to avoid
5
+ * re-reading hundreds of session files on every `/usage` open. That TTL is
6
+ * purely time-based — which is exactly the bug behind the trend graph looking
7
+ * frozen ("always the same"): a user who opens the panel, spends more tokens,
8
+ * and reopens within the TTL window gets the *same* stale snapshot back, even
9
+ * though the live session file on disk already has the new turns.
10
+ *
11
+ * The fix is to also invalidate the cache whenever a new assistant turn has
12
+ * landed since the cache was built. Because pi flushes each turn to disk in
13
+ * realtime, a fresh scan will then pick those turns up, so the trend (and every
14
+ * other panel view) reflects current usage instead of a snapshot from up to
15
+ * two minutes ago.
16
+ *
17
+ * This module is intentionally dependency-free so the decision is unit-testable
18
+ * in isolation.
19
+ */
20
+ /** A cache entry carrying the epoch-ms timestamp it was built at. */
21
+ export interface ReportCacheStamp {
22
+ at: number;
23
+ }
24
+ /**
25
+ * Is the in-memory report cache still fresh enough to reuse without rescanning?
26
+ *
27
+ * Fresh only when ALL of:
28
+ * 1. a cache exists,
29
+ * 2. we're still inside the TTL window, and
30
+ * 3. no assistant turn has arrived after the cache was built.
31
+ *
32
+ * Condition (3) is the realtime fix: a new turn means the on-disk session file
33
+ * changed, so the cached report is stale regardless of the TTL window.
34
+ *
35
+ * @param cached The current in-memory cache stamp (or null when none yet).
36
+ * @param now Current epoch-ms.
37
+ * @param lastTurnAt Epoch-ms of the most recent assistant turn seen this pi run,
38
+ * or 0 when no turn has been observed.
39
+ * @param ttlMs Max age of a cache entry before it is considered stale.
40
+ */
41
+ export declare function isReportCacheFresh(cached: ReportCacheStamp | null, now: number, lastTurnAt: number, ttlMs: number): boolean;
42
+ //# sourceMappingURL=freshness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../src/freshness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,qEAAqE;AACrE,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,gBAAgB,GAAG,IAAI,EAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,OAAO,CAOT"}
@@ -0,0 +1,3 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export default function usageExtension(pi: ExtensionAPI): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,YAAY,EAA2B,MAAM,iCAAiC,CAAC;AAsC7F,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,EAAE,YAAY,QAudtD"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Pi-chan — lightweight anime mascot for the usage panel TUI.
3
+ * ASCII poses + per-view icons keep the menu and Wrapped view playful
4
+ * without breaking narrow terminals.
5
+ */
6
+ import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
7
+ import type { WrappedStats } from "./aggregate.ts";
8
+ /** Selectable top-level views (Tokscale-style menu + Wrapped AI). */
9
+ export type ViewKey = "overview" | "models" | "delegation" | "daily" | "stats" | "hourly" | "providers" | "wrapped";
10
+ /** Ordered list of views for tab/arrow navigation. */
11
+ export declare const VIEW_ORDER: ViewKey[];
12
+ export interface ViewTabMeta {
13
+ icon: string;
14
+ short: string;
15
+ label: string;
16
+ hint: string;
17
+ /** Accent when this tab is selected. */
18
+ color: ThemeColor;
19
+ }
20
+ export declare const VIEW_TABS: Record<ViewKey, ViewTabMeta>;
21
+ /** Cycle palette for rainbow bars (monthly chart, tool rows, etc.). */
22
+ export declare const RAINBOW: ThemeColor[];
23
+ export type MascotPose = "wave" | "celebrate" | "night" | "curious" | "sleepy" | "tools";
24
+ /** Pick a mascot pose for the active view or Wrapped stats mood. */
25
+ export declare function mascotPose(view: ViewKey, stats?: WrappedStats | null): MascotPose;
26
+ /** Theme-colored mascot lines. */
27
+ export declare function renderMascot(pose: MascotPose, theme: Theme): string[];
28
+ /** Wrapped sidebar mascot — compact, report-card styling. */
29
+ export declare function renderWrappedMascot(pose: MascotPose, theme: Theme): string[];
30
+ /** One-line insight caption for the Wrapped footer. */
31
+ export declare function wrappedMascotCaption(stats: WrappedStats | null, year: number): string;
32
+ /** Short speech line for menu hint row. */
33
+ export declare function mascotQuip(view: ViewKey, stats?: WrappedStats | null): string;
34
+ export declare function toolGlyph(name: string): string;
35
+ //# sourceMappingURL=mascot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mascot.d.ts","sourceRoot":"","sources":["../src/mascot.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,qEAAqE;AACrE,MAAM,MAAM,OAAO,GACf,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,OAAO,GACP,OAAO,GACP,QAAQ,GACR,WAAW,GACX,SAAS,CAAC;AAEd,sDAAsD;AACtD,eAAO,MAAM,UAAU,EAAE,OAAO,EAS/B,CAAC;AAEF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,KAAK,EAAE,UAAU,CAAC;CACnB;AAED,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,WAAW,CAyDlD,CAAC;AAEF,uEAAuE;AACvE,eAAO,MAAM,OAAO,EAAE,UAAU,EAa/B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAuBzF,oEAAoE;AACpE,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,UAAU,CASjF;AAYD,kCAAkC;AAClC,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CASrE;AAED,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE,CAO5E;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAUrF;AAED,2CAA2C;AAC3C,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,YAAY,GAAG,IAAI,GAAG,MAAM,CAU7E;AAgBD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG9C"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Bundled default model prices ($/million tokens).
3
+ *
4
+ * pi records `cost = 0` for token-priced / proxied providers it has no pricing
5
+ * for (zai/GLM, 9Router `kr/…` `cx/…`, MiniMax, etc.). These defaults let the
6
+ * panel show an approximate cost out of the box. They are merged under the
7
+ * user's `~/.pi/agent/usage.json` `modelPrices`, so any user entry overrides
8
+ * the matching default. Set your own with `/usage-pricing`.
9
+ *
10
+ * Figures are taken from each provider's official pricing page (June 2026):
11
+ * - Anthropic platform.claude.com/docs/en/about-claude/pricing
12
+ * - OpenAI developers.openai.com/api/docs/pricing
13
+ * - Google ai.google.dev/gemini-api/docs/pricing
14
+ * - Z.ai (GLM) docs.z.ai/guides/overview/pricing
15
+ * - MiniMax platform.minimax.io/docs/guides/pricing-paygo
16
+ *
17
+ * Keys match a model ID exactly, or by base name (after the last `/`) so an
18
+ * entry like `claude-opus-4.7` also covers `kr/claude-opus-4.7`. Prices change
19
+ * often and subscription/proxy costs differ from list rates — treat these as
20
+ * estimates and override as needed.
21
+ */
22
+ import type { ModelPrice } from "./config.ts";
23
+ export declare const DEFAULT_MODEL_PRICES: Record<string, ModelPrice>;
24
+ //# sourceMappingURL=prices.d.ts.map