pi-tokenburn 0.3.1 → 0.4.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 +14 -11
- package/extensions/tokenburn.ts +54 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Totals match the `tokenburn` CLI exactly.
|
|
|
10
10
|
|
|
11
11
|
- **Status bar:** `🔥 1.20M ($52.79) [today]`, turns red with `OVER BUDGET` past your limit.
|
|
12
12
|
- **Report chart:** `/tokenburn` shows the summary plus a vertical column chart (y-axis, budget line, current period marked, over-budget columns in red, peak/avg footer). Daily (14 days), weekly (8 weeks) or monthly (6 months).
|
|
13
|
-
- **
|
|
13
|
+
- **Status line:** `/tokenburn day|week|month|year|all` (or `cycle`) changes the period shown at the bottom. Charts live under `/tokenburn chart <view>`.
|
|
14
14
|
- **Window panel (optional):** Today / Week / Month / Total below the editor, with in/out tokens and cost.
|
|
15
15
|
- **Budgets** per day, week and month, in tokens and/or dollars.
|
|
16
16
|
- **Fast:** per-file cache keyed on mtime+size. First scan of ~400 MB of sessions takes under a second; refreshes after that take milliseconds. Refresh never blocks the agent loop.
|
|
@@ -27,22 +27,19 @@ pi install git:github.com/sorinirimies/pi-tokenburn
|
|
|
27
27
|
|
|
28
28
|
| Command | What it does |
|
|
29
29
|
|---|---|
|
|
30
|
-
| `/tokenburn
|
|
31
|
-
| `/tokenburn
|
|
32
|
-
| `/tokenburn
|
|
33
|
-
| `/tokenburn
|
|
34
|
-
| `/tokenburn
|
|
35
|
-
| `/tokenburn report <view>` · `chart <view>` | Same as above, explicit form |
|
|
36
|
-
| `/tokenburn status <today\|week\|month\|year\|total>` | Set the status-bar period |
|
|
37
|
-
| `/tokenburn cycle` | Rotate the status-bar period: today → week → month → year → total |
|
|
30
|
+
| `/tokenburn day\|week\|month\|year\|all` | **Switch the bottom status line**: today, this week, this month, this year, or the all-time total |
|
|
31
|
+
| `/tokenburn chart <view>` | Report + **chart**. Views: `day` (14 days), `week` (8 weeks), `month` (6 months), `year` (5 years), `all` (whole history; monthly columns up to 18 months, yearly beyond) |
|
|
32
|
+
| `/tokenburn` | Report + daily chart (same as `/tokenburn chart day`) |
|
|
33
|
+
| `/tokenburn cycle` | Rotate the status line: today → week → month → year → total |
|
|
34
|
+
| `/tokenburn status <today\|week\|month\|year\|total>` | Explicit form of the first row |
|
|
38
35
|
| `/tokenburn window` | Toggle the panel below the editor |
|
|
39
36
|
| `/tokenburn budget <day\|week\|month> <tokens>` | Token budget, e.g. `budget day 5000000` |
|
|
40
37
|
| `/tokenburn budget <day\|week\|month> $<cost>` | Dollar budget, e.g. `budget week $50` |
|
|
41
38
|
| `/tokenburn cache` | Toggle counting cache read/write tokens |
|
|
42
39
|
|
|
43
|
-
Tab-completion works for all of
|
|
40
|
+
Tab-completion works for all of these. Unknown input prints usage instead of failing. `report <view>` is kept as an alias of `chart <view>`.
|
|
44
41
|
|
|
45
|
-
Chart example (`/tokenburn`, with a 600M day budget):
|
|
42
|
+
Chart example (`/tokenburn chart day`, with a 600M day budget):
|
|
46
43
|
|
|
47
44
|
```
|
|
48
45
|
Daily tokens · last 14 days ┄ budget 600.00M
|
|
@@ -81,6 +78,12 @@ Weeks start on Monday, in local time.
|
|
|
81
78
|
|
|
82
79
|
Environment overrides for the session directory: `TOKENBURN_PI_SESSIONS`, `PI_CODING_AGENT_SESSION_DIR`, `PI_CODING_AGENT_DIR`.
|
|
83
80
|
|
|
81
|
+
## Security notes
|
|
82
|
+
|
|
83
|
+
- **Local and read-only.** It reads pi's session logs and its own `tokenburn.json`. Nothing leaves your machine: no network, no telemetry, no shell commands, **zero runtime dependencies**.
|
|
84
|
+
- **Never breaks pi.** Stats refreshes are fire-and-forget and swallow their own errors, so a failure here cannot interrupt a turn or a session start.
|
|
85
|
+
- **Hand-edited config is sanitised.** Unknown or non-numeric budget values are dropped.
|
|
86
|
+
|
|
84
87
|
## Development
|
|
85
88
|
|
|
86
89
|
```bash
|
package/extensions/tokenburn.ts
CHANGED
|
@@ -108,13 +108,26 @@ export async function loadConfig(): Promise<TokenBurnConfig> {
|
|
|
108
108
|
statusWindow: (STATUS_WINDOWS as readonly string[]).includes(parsed.statusWindow)
|
|
109
109
|
? parsed.statusWindow
|
|
110
110
|
: DEFAULT_CONFIG.statusWindow,
|
|
111
|
-
budgets:
|
|
111
|
+
budgets: sanitizeBudgets(parsed.budgets),
|
|
112
112
|
};
|
|
113
113
|
} catch {
|
|
114
114
|
return freshConfig();
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
const BUDGET_KEYS = ["dayTokens", "weekTokens", "monthTokens", "dayCost", "weekCost", "monthCost"] as const;
|
|
119
|
+
|
|
120
|
+
/** Keep only known budget keys holding positive, finite numbers (the file is hand-editable). */
|
|
121
|
+
export function sanitizeBudgets(value: unknown): TokenBurnConfig["budgets"] {
|
|
122
|
+
const out: TokenBurnConfig["budgets"] = {};
|
|
123
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return out;
|
|
124
|
+
for (const key of BUDGET_KEYS) {
|
|
125
|
+
const n = (value as Record<string, unknown>)[key];
|
|
126
|
+
if (typeof n === "number" && Number.isFinite(n) && n > 0) out[key] = n;
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
/** A config that shares no mutable state with DEFAULT_CONFIG. */
|
|
119
132
|
export function freshConfig(): TokenBurnConfig {
|
|
120
133
|
return { ...DEFAULT_CONFIG, budgets: {} };
|
|
@@ -571,7 +584,8 @@ export default function tokenBurnExtension(pi: ExtensionAPI) {
|
|
|
571
584
|
|
|
572
585
|
pi.on("session_start", async (_event, ctx) => {
|
|
573
586
|
config = await loadConfig();
|
|
574
|
-
|
|
587
|
+
// A failing refresh must never break session start.
|
|
588
|
+
await updateUI(ctx).catch(() => {});
|
|
575
589
|
});
|
|
576
590
|
|
|
577
591
|
// Fire-and-forget: never block the agent loop on a stats refresh.
|
|
@@ -580,7 +594,7 @@ export default function tokenBurnExtension(pi: ExtensionAPI) {
|
|
|
580
594
|
});
|
|
581
595
|
|
|
582
596
|
pi.registerCommand("tokenburn", {
|
|
583
|
-
description: "TokenBurn: /tokenburn [day|week|month|year|all]
|
|
597
|
+
description: "TokenBurn: /tokenburn [day|week|month|year|all] sets the status bar; /tokenburn chart <day|week|month|year|all> shows the chart",
|
|
584
598
|
getArgumentCompletions: (prefix: string) => completions(prefix),
|
|
585
599
|
handler: async (args, ctx) => {
|
|
586
600
|
const cmd = parseCommand(args);
|
|
@@ -666,7 +680,7 @@ export default function tokenBurnExtension(pi: ExtensionAPI) {
|
|
|
666
680
|
rule,
|
|
667
681
|
...chart,
|
|
668
682
|
rule,
|
|
669
|
-
"/tokenburn day|week|month|year|all ·
|
|
683
|
+
"/tokenburn chart <day|week|month|year|all> · /tokenburn <day|week|month|year|all> sets the status bar · cycle · window · budget · cache",
|
|
670
684
|
].join("\n"),
|
|
671
685
|
"info",
|
|
672
686
|
);
|
|
@@ -695,24 +709,37 @@ export type Command =
|
|
|
695
709
|
| { kind: "invalid"; message: string };
|
|
696
710
|
|
|
697
711
|
export const USAGE =
|
|
698
|
-
"Usage: /tokenburn [day|week|month|year|all] · status <today|week|month|year|total> · cycle · window · budget <day|week|month> <tokens|$cost> · cache";
|
|
712
|
+
"Usage: /tokenburn [day|week|month|year|all] (status-bar period) · chart [day|week|month|year|all] · status <today|week|month|year|total> · cycle · window · budget <day|week|month> <tokens|$cost> · cache";
|
|
713
|
+
|
|
714
|
+
/** Words that pick the status-bar period: `day` shows today, `all` shows the all-time total. */
|
|
715
|
+
const WORD_TO_WINDOW: Record<string, Window> = {
|
|
716
|
+
day: "today",
|
|
717
|
+
today: "today",
|
|
718
|
+
week: "week",
|
|
719
|
+
month: "month",
|
|
720
|
+
year: "year",
|
|
721
|
+
all: "total",
|
|
722
|
+
total: "total",
|
|
723
|
+
};
|
|
699
724
|
|
|
725
|
+
/** Views the chart can show. */
|
|
700
726
|
const asView = (w: string | undefined): ReportView | undefined =>
|
|
701
|
-
w === "today" ? "day" : (REPORT_VIEWS as readonly string[]).includes(w ?? "") ? (w as ReportView) : undefined;
|
|
727
|
+
w === "today" ? "day" : w === "total" ? "all" : (REPORT_VIEWS as readonly string[]).includes(w ?? "") ? (w as ReportView) : undefined;
|
|
702
728
|
|
|
703
729
|
export function parseCommand(args: string | undefined): Command {
|
|
704
730
|
const [cmd, p1, p2] = (args ?? "").trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
705
731
|
if (!cmd) return { kind: "report", view: "day" };
|
|
706
732
|
|
|
707
|
-
|
|
708
|
-
|
|
733
|
+
// /tokenburn day|week|month|year|all → what the bottom status line shows
|
|
734
|
+
const window = WORD_TO_WINDOW[cmd];
|
|
735
|
+
if (window) return { kind: "status", window };
|
|
709
736
|
|
|
710
737
|
switch (cmd) {
|
|
711
|
-
case "
|
|
712
|
-
case "
|
|
738
|
+
case "chart":
|
|
739
|
+
case "report": {
|
|
713
740
|
if (p1 === undefined) return { kind: "report", view: "day" };
|
|
714
741
|
const v = asView(p1);
|
|
715
|
-
return v ? { kind: "report", view: v } : { kind: "invalid", message: `Unknown
|
|
742
|
+
return v ? { kind: "report", view: v } : { kind: "invalid", message: `Unknown chart "${p1}". ${USAGE}` };
|
|
716
743
|
}
|
|
717
744
|
case "status":
|
|
718
745
|
return (STATUS_WINDOWS as readonly string[]).includes(p1 ?? "")
|
|
@@ -745,13 +772,22 @@ export interface CompletionItem {
|
|
|
745
772
|
description?: string;
|
|
746
773
|
}
|
|
747
774
|
|
|
775
|
+
const CHART_SPAN: Record<ReportView, string> = {
|
|
776
|
+
day: "last 14 days",
|
|
777
|
+
week: "last 8 weeks",
|
|
778
|
+
month: "last 6 months",
|
|
779
|
+
year: "last 5 years",
|
|
780
|
+
all: "whole history",
|
|
781
|
+
};
|
|
782
|
+
|
|
748
783
|
const TOP_LEVEL: CompletionItem[] = [
|
|
749
|
-
{ value: "day", label: "day", description: "
|
|
750
|
-
{ value: "week", label: "week", description: "
|
|
751
|
-
{ value: "month", label: "month", description: "
|
|
752
|
-
{ value: "year", label: "year", description: "
|
|
753
|
-
{ value: "all", label: "all", description: "
|
|
754
|
-
{ value: "
|
|
784
|
+
{ value: "day", label: "day", description: "Status bar: today" },
|
|
785
|
+
{ value: "week", label: "week", description: "Status bar: this week" },
|
|
786
|
+
{ value: "month", label: "month", description: "Status bar: this month" },
|
|
787
|
+
{ value: "year", label: "year", description: "Status bar: this year" },
|
|
788
|
+
{ value: "all", label: "all", description: "Status bar: all-time total" },
|
|
789
|
+
{ value: "chart ", label: "chart", description: "Report + chart: day, week, month, year or all" },
|
|
790
|
+
{ value: "status ", label: "status", description: "Set the status-bar period explicitly" },
|
|
755
791
|
{ value: "cycle", label: "cycle", description: "Rotate the status-bar period" },
|
|
756
792
|
{ value: "window", label: "window", description: "Toggle the panel below the editor" },
|
|
757
793
|
{ value: "budget ", label: "budget", description: "Set a day/week/month budget" },
|
|
@@ -771,7 +807,7 @@ export function completions(prefix: string): CompletionItem[] | null {
|
|
|
771
807
|
pool = sub("budget", BUDGET_PERIODS, (w) => `Set the ${w} budget (tokens or $cost)`, " ");
|
|
772
808
|
else if (/^(report|chart) /.test(text)) {
|
|
773
809
|
const head = text.split(" ")[0];
|
|
774
|
-
pool = sub(head, REPORT_VIEWS, (w) =>
|
|
810
|
+
pool = sub(head, REPORT_VIEWS, (w) => `Chart: ${CHART_SPAN[w as ReportView]}`);
|
|
775
811
|
} else pool = TOP_LEVEL;
|
|
776
812
|
|
|
777
813
|
const out = pool.filter((i) => i.value.startsWith(text));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-tokenburn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Pi extension for live token usage monitoring and budget tracking (Day / Week / Month / Total). 0 prompt tokens.",
|
|
5
5
|
"keywords": ["pi-package", "pi-extension", "tokenburn", "tokens", "analytics", "budget", "stats"],
|
|
6
6
|
"license": "MIT",
|