@cad0p/pi-usage 0.0.0-stage → 0.11.2
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/LICENSE +21 -0
- package/README.md +509 -2
- package/bin/piquota.js +199 -0
- package/extensions/quota-panel.ts +963 -0
- package/package.json +59 -4
- package/src/auth/claude-code-auth.js +490 -0
- package/src/auth/jwt.js +84 -0
- package/src/auth/pi-auth.js +424 -0
- package/src/browser/cookies.js +557 -0
- package/src/browser/history.js +173 -0
- package/src/cache.js +224 -0
- package/src/cli/args.js +154 -0
- package/src/cli/commands/auth.js +216 -0
- package/src/cli/commands/gentle-pi.js +44 -0
- package/src/cli/commands/omarchy.js +75 -0
- package/src/cli/explain.js +99 -0
- package/src/cli/output.js +34 -0
- package/src/engine.js +404 -0
- package/src/exec.js +31 -0
- package/src/gentle-pi/rail-patch.js +335 -0
- package/src/http.js +182 -0
- package/src/model.js +332 -0
- package/src/omarchy/assets/antigravity.svg +3 -0
- package/src/omarchy/assets/claude.svg +1 -0
- package/src/omarchy/assets/codex.svg +1 -0
- package/src/omarchy/assets/github-actions.svg +3 -0
- package/src/omarchy/assets/opencode-go.svg +3 -0
- package/src/omarchy/publish.js +152 -0
- package/src/omarchy/record.js +462 -0
- package/src/opencode/dashboard.js +337 -0
- package/src/opencode/session.js +485 -0
- package/src/providers/antigravity-oauth.js +173 -0
- package/src/providers/antigravity.js +225 -0
- package/src/providers/backoff.js +146 -0
- package/src/providers/claude.js +194 -0
- package/src/providers/codex.js +151 -0
- package/src/providers/error-kind.js +128 -0
- package/src/providers/github-actions.js +379 -0
- package/src/providers/opencode-go.js +274 -0
- package/src/refresh.js +292 -0
- package/src/render/panel.js +411 -0
- package/src/render/theme.js +102 -0
- package/src/sticky.js +319 -0
package/bin/piquota.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* piquota — independent, read-only quota for the credentials Pi already owns.
|
|
4
|
+
*
|
|
5
|
+
* piquota panel for Claude, Codex, Antigravity, OpenCode Go
|
|
6
|
+
* piquota --json normalized report (used by rp-host / tools)
|
|
7
|
+
* piquota --compact | --status | --explain
|
|
8
|
+
* piquota auth opencode open the OpenCode login and capture the session
|
|
9
|
+
* piquota auth status show which credential sources are reachable
|
|
10
|
+
* piquota omarchy publish pi-*.json records for Omarchy's Agents bar panel
|
|
11
|
+
* piquota gentle-pi <status|apply|revert>
|
|
12
|
+
* the rail slot gentle-pi needs to paint the quota card
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { clearCache, withCache } from "../src/cache.js";
|
|
16
|
+
import { parseArgs } from "../src/cli/args.js";
|
|
17
|
+
import { runAuthCommand } from "../src/cli/commands/auth.js";
|
|
18
|
+
import { runGentlePiCommand } from "../src/cli/commands/gentle-pi.js";
|
|
19
|
+
import { runOmarchyCommand } from "../src/cli/commands/omarchy.js";
|
|
20
|
+
import { explainLines, resolveFamilies } from "../src/cli/explain.js";
|
|
21
|
+
import { err, out } from "../src/cli/output.js";
|
|
22
|
+
import { collectQuota, enabledFamilies } from "../src/engine.js";
|
|
23
|
+
import { redact } from "../src/http.js";
|
|
24
|
+
import { mergeLastGood } from "../src/sticky.js";
|
|
25
|
+
import { renderBoxWidget, renderCompact, renderPanel, renderStatusLine } from "../src/render/panel.js";
|
|
26
|
+
import { ansiPalette } from "../src/render/theme.js";
|
|
27
|
+
|
|
28
|
+
const VERSION = "0.11.1";
|
|
29
|
+
|
|
30
|
+
const HELP = `piquota ${VERSION} — read-only quota from Pi's provider credentials
|
|
31
|
+
|
|
32
|
+
Usage:
|
|
33
|
+
piquota [families...] [flags]
|
|
34
|
+
piquota auth <opencode|status> [flags]
|
|
35
|
+
piquota gentle-pi <status|apply|revert>
|
|
36
|
+
piquota omarchy
|
|
37
|
+
|
|
38
|
+
Quota:
|
|
39
|
+
--json Emit the normalized report as JSON (for rp-host and automations)
|
|
40
|
+
--compact, -c One line per provider
|
|
41
|
+
--status Single line with rings
|
|
42
|
+
--box Compact right-aligned 5-row box widget
|
|
43
|
+
--no-color Disable ANSI colors
|
|
44
|
+
--no-cache Skip the local cache
|
|
45
|
+
--force Ignore a still-fresh cache entry
|
|
46
|
+
--ttl <seconds> Cache TTL (default 60)
|
|
47
|
+
--timeout <ms> Per-request timeout (default 15000)
|
|
48
|
+
--no-refresh Never refresh Antigravity's token in memory
|
|
49
|
+
--explain Show which stores/fields are read (names only)
|
|
50
|
+
--clear-cache Delete the local cache and exit
|
|
51
|
+
--version Print the version and exit
|
|
52
|
+
|
|
53
|
+
Auth (OpenCode Go session, the only credential Pi does not store):
|
|
54
|
+
piquota auth opencode open the login in Firefox and capture the cookie
|
|
55
|
+
piquota auth opencode --paste read the cookie from stdin instead
|
|
56
|
+
piquota auth opencode --no-browser
|
|
57
|
+
print the URL without launching a browser
|
|
58
|
+
piquota auth opencode --wait <s> how long to wait for the login (default 180)
|
|
59
|
+
piquota auth status report every credential source
|
|
60
|
+
|
|
61
|
+
Omarchy:
|
|
62
|
+
piquota omarchy write pi-<family>.json records for Omarchy's Agents bar panel
|
|
63
|
+
(dir: $PI_QUOTA_OMARCHY_DIR or $XDG_STATE_HOME/omarchy/agents/usage)
|
|
64
|
+
run it on a timer: ./install.sh --omarchy-timer
|
|
65
|
+
|
|
66
|
+
Guarantees:
|
|
67
|
+
* ~/.pi/agent/auth.json is opened read-only. Never written, synced or refreshed.
|
|
68
|
+
* Antigravity's access token may be refreshed in memory; it is never persisted.
|
|
69
|
+
* Browser cookie databases are copied and opened read-only; values are never logged.
|
|
70
|
+
* Only percentages, window labels, reset times and plan names leave this machine.
|
|
71
|
+
* \`omarchy\` (opt-in) writes only pi-*.json records, atomically, into Omarchy's usage dir;
|
|
72
|
+
records hold plan names, percentages, window labels and reset times, never accounts.
|
|
73
|
+
* \`gentle-pi\` is the one file this tool edits outside its own state: it appends
|
|
74
|
+
piQuota's part to gentle-pi's rail allowlist so the card can be painted there.
|
|
75
|
+
The edit is one array literal, it is reversible with \`piquota gentle-pi revert\`,
|
|
76
|
+
and the previous revision is kept next to the file as a \`.pi-quota-backup\`.
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const argv = process.argv.slice(2);
|
|
81
|
+
const args = parseArgs(argv);
|
|
82
|
+
const bare = args.positionals;
|
|
83
|
+
const command = bare[0] ?? null;
|
|
84
|
+
const agentMode = "native";
|
|
85
|
+
const refresh = !argv.includes("--no-refresh");
|
|
86
|
+
|
|
87
|
+
if (args.help) {
|
|
88
|
+
process.stdout.write(HELP);
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
if (args.version) {
|
|
92
|
+
out(`piquota ${VERSION}`);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
if (args.clearCache) {
|
|
96
|
+
const result = clearCache({});
|
|
97
|
+
out(result.removed ? `cache cleared: ${result.path}` : `no cache at ${result.path}`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (args.unknown.length > 0) {
|
|
102
|
+
err(`unknown flag: ${args.unknown.join(" ")}\n`);
|
|
103
|
+
process.stdout.write(HELP);
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (command === "auth") {
|
|
108
|
+
const sub = bare[1] ?? "status";
|
|
109
|
+
return runAuthCommand(sub, argv);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (command === "gentle-pi") {
|
|
113
|
+
const sub = bare[1] ?? "status";
|
|
114
|
+
return runGentlePiCommand(sub);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (command === "omarchy") {
|
|
118
|
+
const load = async () => {
|
|
119
|
+
const everyFamily = enabledFamilies({ env: process.env });
|
|
120
|
+
const loadFresh = () => collectQuota({ families: everyFamily, timeoutMs: args.timeoutMs, refresh, force: args.force });
|
|
121
|
+
const fresh = args.noCache
|
|
122
|
+
? await loadFresh()
|
|
123
|
+
: (await withCache({ ttlMs: args.ttlMs, force: args.force, families: everyFamily }, loadFresh)).report;
|
|
124
|
+
return mergeLastGood(fresh, {}).report;
|
|
125
|
+
};
|
|
126
|
+
return runOmarchyCommand({ load });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { families, unknown, explicit } = resolveFamilies(
|
|
130
|
+
command === "quota" ? bare.slice(1) : bare.filter((value) => value !== "quota"),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (unknown.length > 0) {
|
|
134
|
+
err(`unknown argument: ${unknown.join(" ")}\n`);
|
|
135
|
+
process.stdout.write(HELP);
|
|
136
|
+
return 2;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const selected = enabledFamilies({
|
|
140
|
+
env: process.env,
|
|
141
|
+
requested: families.length > 0 ? families : undefined,
|
|
142
|
+
explicit,
|
|
143
|
+
});
|
|
144
|
+
const load = () => collectQuota({ families: selected, explicit, timeoutMs: args.timeoutMs, refresh, force: args.force });
|
|
145
|
+
|
|
146
|
+
let report;
|
|
147
|
+
let cached = false;
|
|
148
|
+
let ageMs = 0;
|
|
149
|
+
if (args.noCache) {
|
|
150
|
+
report = await load();
|
|
151
|
+
} else {
|
|
152
|
+
const result = await withCache({ ttlMs: args.ttlMs, force: args.force, families: selected }, load);
|
|
153
|
+
report = result.report;
|
|
154
|
+
cached = result.cached;
|
|
155
|
+
ageMs = result.ageMs;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { report: reportWithHistory, restored } = mergeLastGood(report, {});
|
|
159
|
+
report = reportWithHistory;
|
|
160
|
+
if (restored.length > 0 && !args.json && !args.compact && !args.status) {
|
|
161
|
+
process.stderr.write(`note: showing last known values for ${restored.join(", ")}\n`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const paint = ansiPalette({ color: args.color });
|
|
165
|
+
|
|
166
|
+
if (args.json) {
|
|
167
|
+
out(JSON.stringify({ ...report, cache: { used: cached, ageMs } }, null, 2));
|
|
168
|
+
return report.providers.some((provider) => provider.ok) ? 0 : 1;
|
|
169
|
+
}
|
|
170
|
+
if (args.status) {
|
|
171
|
+
out(renderStatusLine(report.providers, paint));
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
if (args.box) {
|
|
175
|
+
for (const line of renderBoxWidget(report.providers, paint)) out(line);
|
|
176
|
+
} else if (args.compact) {
|
|
177
|
+
for (const line of renderCompact(report.providers, paint)) out(line);
|
|
178
|
+
} else {
|
|
179
|
+
for (const line of renderPanel(report.providers, paint, {
|
|
180
|
+
generatedAt: report.generatedAt,
|
|
181
|
+
warnings: report.warnings,
|
|
182
|
+
})) {
|
|
183
|
+
out(line);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (args.explain) {
|
|
187
|
+
for (const line of explainLines(selected)) out(line);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return report.providers.some((provider) => provider.ok) ? 0 : 1;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
main().then(
|
|
194
|
+
(code) => process.exit(code),
|
|
195
|
+
(error) => {
|
|
196
|
+
err(`piquota failed: ${redact(/** @type {{ message?: string }} */ (error)?.message ?? error)}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
},
|
|
199
|
+
);
|