@cad0p/pi-usage 0.11.3 → 0.11.4-20261103.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/bin/piquota.js CHANGED
@@ -19,6 +19,7 @@ import { parseArgs } from "../src/cli/args.js";
19
19
  import { runAuthCommand } from "../src/cli/commands/auth.js";
20
20
  import { runGentlePiCommand } from "../src/cli/commands/gentle-pi.js";
21
21
  import { runOmarchyCommand } from "../src/cli/commands/omarchy.js";
22
+ import { runStatsCommand } from "../src/cli/commands/stats.js";
22
23
  import { explainLines, resolveFamilies } from "../src/cli/explain.js";
23
24
  import { err, out } from "../src/cli/output.js";
24
25
  import { collectQuota, enabledFamilies, normalizeClaudeSourceMode } from "../src/engine.js";
@@ -35,6 +36,20 @@ const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.u
35
36
  */
36
37
  const CACHE_KEY = `claude-source:${normalizeClaudeSourceMode(process.env.PI_QUOTA_CLAUDE_SOURCE)}`;
37
38
 
39
+ /**
40
+ * Local token statistics from Pi's session files are on for the CLI: the Omarchy
41
+ * records and /usage both show them. `PI_QUOTA_LOCAL_STATS=0` turns the scan
42
+ * off (e.g. on a machine where the session store is huge and network quota is
43
+ * all that matters), and the budget keeps a first full scan from delaying a
44
+ * report past the 30 s the /usage extension allows.
45
+ */
46
+ const LOCAL_STATS_BUDGET_MS = Number(process.env.PI_QUOTA_LOCAL_STATS_BUDGET_MS);
47
+ const LOCAL_STATS = process.env.PI_QUOTA_LOCAL_STATS === "0"
48
+ ? false
49
+ : LOCAL_STATS_BUDGET_MS > 0
50
+ ? { budgetMs: LOCAL_STATS_BUDGET_MS }
51
+ : {};
52
+
38
53
  const HELP = `piquota ${VERSION} — read-only quota from Pi's provider credentials
39
54
 
40
55
  Usage:
@@ -42,6 +57,7 @@ Usage:
42
57
  piquota auth <opencode|status> [flags]
43
58
  piquota gentle-pi <status|apply|revert>
44
59
  piquota omarchy
60
+ piquota stats [--json] [--month YYYY-MM] [--rebuild-totals]
45
61
 
46
62
  Quota:
47
63
  --json Emit the normalized report as JSON (for rp-host and automations)
@@ -56,6 +72,8 @@ Quota:
56
72
  --no-refresh Never refresh Antigravity's token in memory
57
73
  --explain Show which stores/fields are read (names only)
58
74
  --clear-cache Delete the local cache and exit
75
+ --rebuild-totals With \`stats\`: rebuild the token-history totals from its month files
76
+ --month YYYY-MM With \`stats\`: read one month (default: the current local month)
59
77
  --version Print the version and exit
60
78
 
61
79
  Auth (OpenCode Go session, the only credential Pi does not store):
@@ -66,18 +84,35 @@ Auth (OpenCode Go session, the only credential Pi does not store):
66
84
  piquota auth opencode --wait <s> how long to wait for the login (default 180)
67
85
  piquota auth status report every credential source
68
86
 
87
+ Stats:
88
+ piquota stats summarise the durable token history
89
+ (dir: \${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-usage)
90
+ piquota stats --json [--month] read-only JSON: months (the live month always),
91
+ one month's days per family/slot/model, all-time
92
+ totals, notes
93
+ piquota stats --rebuild-totals recompute totals.json from the frozen month files
94
+
69
95
  Omarchy:
70
96
  piquota omarchy write pi-<family>.json records for Omarchy's Agents bar panel
71
97
  (dir: $PI_QUOTA_OMARCHY_DIR or $XDG_STATE_HOME/omarchy/agents/usage)
72
98
  run it on a timer: ./install.sh --omarchy-timer
73
99
 
100
+ Environment:
101
+ PI_QUOTA_LOCAL_STATS=0 skip the token scan of Pi's session files
102
+ PI_QUOTA_LOCAL_STATS_BUDGET_MS wall-clock budget for that scan (default 12000)
103
+ PI_QUOTA_ACCOUNT_CONCURRENCY accounts of one provider queried at once (1-32, default 8)
104
+
74
105
  Guarantees:
75
106
  * ~/.pi/agent/auth.json is opened read-only. Never written, synced or refreshed.
107
+ * Pi's session files are opened read-only too; the token scan keeps only
108
+ aggregate counts per day and model, never prompt text or session paths.
76
109
  * Antigravity's access token may be refreshed in memory; it is never persisted.
77
110
  * Browser cookie databases are copied and opened read-only; values are never logged.
78
- * Only percentages, window labels, reset times and plan names leave this machine.
111
+ * Only percentages, window labels, reset times, plan names and aggregate token
112
+ counts leave this machine.
79
113
  * \`omarchy\` (opt-in) writes only pi-*.json records, atomically, into Omarchy's usage dir;
80
- records hold plan names, percentages, window labels and reset times, never accounts.
114
+ records hold plan names, percentages, window labels, reset times and aggregate
115
+ token counts, never prompt text or accounts.
81
116
  * \`gentle-pi\` is the one file this tool edits outside its own state: it appends
82
117
  piQuota's part to gentle-pi's rail allowlist so the card can be painted there.
83
118
  The edit is one array literal, it is reversible with \`piquota gentle-pi revert\`,
@@ -122,10 +157,30 @@ async function main() {
122
157
  return runGentlePiCommand(sub);
123
158
  }
124
159
 
160
+ if (args.rebuildTotals && command !== "stats") {
161
+ // The flag repairs one thing (the all-time rollup) and reads nothing else,
162
+ // so running it beside `piquota` or `piquota omarchy` would silently skip
163
+ // the command the user asked for.
164
+ err("--rebuild-totals only works with `piquota stats`\n");
165
+ process.stdout.write(HELP);
166
+ return 2;
167
+ }
168
+
169
+ if (args.month && command !== "stats") {
170
+ err("--month only works with \`piquota stats\`\n");
171
+ process.stdout.write(HELP);
172
+ return 2;
173
+ }
174
+
175
+ if (command === "stats") {
176
+ return runStatsCommand({ rebuild: args.rebuildTotals, json: args.json, month: args.month, env: process.env });
177
+ }
178
+
125
179
  if (command === "omarchy") {
126
180
  const load = async () => {
127
181
  const everyFamily = enabledFamilies({ env: process.env });
128
- const loadFresh = () => collectQuota({ families: everyFamily, timeoutMs: args.timeoutMs, refresh, force: args.force });
182
+ const loadFresh = () =>
183
+ collectQuota({ families: everyFamily, timeoutMs: args.timeoutMs, refresh, force: args.force, localStats: LOCAL_STATS });
129
184
  const fresh = args.noCache
130
185
  ? await loadFresh()
131
186
  : (await withCache({ ttlMs: args.ttlMs, force: args.force, families: everyFamily, key: CACHE_KEY }, loadFresh)).report;
@@ -149,7 +204,8 @@ async function main() {
149
204
  requested: families.length > 0 ? families : undefined,
150
205
  explicit,
151
206
  });
152
- const load = () => collectQuota({ families: selected, explicit, timeoutMs: args.timeoutMs, refresh, force: args.force });
207
+ const load = () =>
208
+ collectQuota({ families: selected, explicit, timeoutMs: args.timeoutMs, refresh, force: args.force, localStats: LOCAL_STATS });
153
209
 
154
210
  let report;
155
211
  let cached = false;