pi-tokenburn 0.3.0 → 0.3.1
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 +4 -1
- package/extensions/tokenburn.ts +30 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,9 +86,12 @@ Environment overrides for the session directory: `TOKENBURN_PI_SESSIONS`, `PI_CO
|
|
|
86
86
|
```bash
|
|
87
87
|
bun install
|
|
88
88
|
just check # typecheck + tests + pack check + nushell tests (what CI runs)
|
|
89
|
-
just test # bun test only
|
|
89
|
+
just test # bun test only (with coverage)
|
|
90
|
+
just coverage # tests + a coverage table
|
|
90
91
|
```
|
|
91
92
|
|
|
93
|
+
**Coverage:** every `bun test` collects coverage (`bunfig.toml`) and **fails below 95% lines / 95% functions**. CI shows the table on the run page and uploads `lcov.info`.
|
|
94
|
+
|
|
92
95
|
CI scripts are [nushell](https://www.nushell.sh) (`scripts/`), the same ones locally and in GitHub / Gitea Actions.
|
|
93
96
|
`just --list` shows every task.
|
|
94
97
|
|
package/extensions/tokenburn.ts
CHANGED
|
@@ -78,18 +78,24 @@ const DEFAULT_CONFIG: TokenBurnConfig = {
|
|
|
78
78
|
budgets: {},
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (
|
|
84
|
-
return join(
|
|
81
|
+
/** Where pi keeps its config: PI_CODING_AGENT_DIR, then $XDG_CONFIG_HOME/pi/agent, then ~/.pi/agent. */
|
|
82
|
+
export function resolveAgentDir(env: Record<string, string | undefined>, home: string): string {
|
|
83
|
+
if (env.PI_CODING_AGENT_DIR) return env.PI_CODING_AGENT_DIR;
|
|
84
|
+
if (env.XDG_CONFIG_HOME) return join(env.XDG_CONFIG_HOME, "pi", "agent");
|
|
85
|
+
return join(home, ".pi", "agent");
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
88
|
+
/** Pi session logs: TOKENBURN_PI_SESSIONS, PI_CODING_AGENT_SESSION_DIR, then <agent dir>/sessions. */
|
|
89
|
+
export function resolveSessionsDir(env: Record<string, string | undefined>, home: string): string {
|
|
90
|
+
if (env.TOKENBURN_PI_SESSIONS) return env.TOKENBURN_PI_SESSIONS;
|
|
91
|
+
if (env.PI_CODING_AGENT_SESSION_DIR) return env.PI_CODING_AGENT_SESSION_DIR;
|
|
92
|
+
return join(resolveAgentDir(env, home), "sessions");
|
|
91
93
|
}
|
|
92
94
|
|
|
95
|
+
const getConfigDir = () => resolveAgentDir(process.env, homedir());
|
|
96
|
+
|
|
97
|
+
const getSessionsDir = () => resolveSessionsDir(process.env, homedir());
|
|
98
|
+
|
|
93
99
|
export async function loadConfig(): Promise<TokenBurnConfig> {
|
|
94
100
|
try {
|
|
95
101
|
const raw = await readFile(join(getConfigDir(), "tokenburn.json"), "utf8");
|
|
@@ -172,6 +178,16 @@ async function parseFile(file: string): Promise<Turn[]> {
|
|
|
172
178
|
return turns;
|
|
173
179
|
}
|
|
174
180
|
|
|
181
|
+
/** Cache key for a file: mtime + size. `undefined` when it cannot be stat'ed. */
|
|
182
|
+
export function fileSignature(file: string): string | undefined {
|
|
183
|
+
try {
|
|
184
|
+
const st = statSync(file);
|
|
185
|
+
return `${st.mtimeMs}:${st.size}`;
|
|
186
|
+
} catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
175
191
|
function listSessionFiles(root: string): string[] {
|
|
176
192
|
const files: string[] = [];
|
|
177
193
|
const walk = (dir: string) => {
|
|
@@ -209,13 +225,8 @@ async function loadAll(sessionsRoot: string): Promise<Turn[][]> {
|
|
|
209
225
|
|
|
210
226
|
const out: Turn[][] = [];
|
|
211
227
|
for (const file of files) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const st = statSync(file);
|
|
215
|
-
sig = `${st.mtimeMs}:${st.size}`;
|
|
216
|
-
} catch {
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
228
|
+
const sig = fileSignature(file);
|
|
229
|
+
if (sig === undefined) continue; // vanished between listing and stat
|
|
219
230
|
let entry = FILE_CACHE.get(file);
|
|
220
231
|
if (!entry || entry.sig !== sig) {
|
|
221
232
|
entry = { sig, turns: await parseFile(file) };
|
|
@@ -750,13 +761,14 @@ const TOP_LEVEL: CompletionItem[] = [
|
|
|
750
761
|
/** `prefix` is the whole argument text typed so far (pi replaces it with `value`). */
|
|
751
762
|
export function completions(prefix: string): CompletionItem[] | null {
|
|
752
763
|
const text = (prefix ?? "").toLowerCase().replace(/^\s+/, "");
|
|
753
|
-
|
|
754
|
-
|
|
764
|
+
// `tail` = " " when the completed text still needs another word (a budget amount).
|
|
765
|
+
const sub = (head: string, words: readonly string[], desc: (w: string) => string, tail = ""): CompletionItem[] =>
|
|
766
|
+
words.map((w) => ({ value: `${head} ${w}${tail}`, label: w, description: desc(w) }));
|
|
755
767
|
|
|
756
768
|
let pool: CompletionItem[];
|
|
757
769
|
if (text.startsWith("status ")) pool = sub("status", STATUS_WINDOWS, (w) => `Show ${w} in the status bar`);
|
|
758
770
|
else if (text.startsWith("budget ") && !/^budget \S+ /.test(text))
|
|
759
|
-
pool = sub("budget", BUDGET_PERIODS, (w) => `Set the ${w} budget (tokens or $cost)
|
|
771
|
+
pool = sub("budget", BUDGET_PERIODS, (w) => `Set the ${w} budget (tokens or $cost)`, " ");
|
|
760
772
|
else if (/^(report|chart) /.test(text)) {
|
|
761
773
|
const head = text.split(" ")[0];
|
|
762
774
|
pool = sub(head, REPORT_VIEWS, (w) => `${head} for ${w}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-tokenburn",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|