quotacap 0.0.33 → 0.0.34
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/dist/adapters/codex.js +7 -1
- package/dist/adapters/muse.js +3 -0
- package/dist/cli/runtime.d.ts +8 -0
- package/dist/cli/runtime.js +32 -0
- package/dist/diagnostics/failure.d.ts +1 -1
- package/dist/diagnostics/failure.js +22 -0
- package/dist/version.js +1 -1
- package/dist/web/src/App.js +5 -7
- package/dist/web/src/components/FaultBanner.d.ts +2 -1
- package/dist/web/src/components/FaultBanner.js +10 -2
- package/dist/web/src/components/Header.d.ts +3 -3
- package/dist/web/src/components/Header.js +5 -5
- package/dist/webAssets.js +1 -1
- package/dist/webHtml.js +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{index-4-7ERxB2.js → index-irnos0UG.js} +1 -1
- package/web/dist/index.html +1 -1
package/dist/adapters/codex.js
CHANGED
|
@@ -52,6 +52,12 @@ function parseFiveReset(raw, now) {
|
|
|
52
52
|
}
|
|
53
53
|
export function parseCodexTui(text, now = new Date()) {
|
|
54
54
|
const cleaned = stripAnsi(text);
|
|
55
|
+
if (/refresh token was already used|please log out and sign in again|access token could not be refreshed/i.test(cleaned)) {
|
|
56
|
+
throw new Error("codex: your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.");
|
|
57
|
+
}
|
|
58
|
+
if (/Limits:\s*refresh requested/i.test(cleaned)) {
|
|
59
|
+
throw new Error("codex: limits refresh requested, run /status again shortly");
|
|
60
|
+
}
|
|
55
61
|
let weeklyLeft = null;
|
|
56
62
|
let fiveLeft = null;
|
|
57
63
|
let weeklyRaw = null;
|
|
@@ -146,7 +152,7 @@ export const codexAdapter = {
|
|
|
146
152
|
// Complete on the /status panel only. The startup statusline footer
|
|
147
153
|
// ("· 5h N% left · weekly N% left") carries no reset timestamps and
|
|
148
154
|
// would otherwise win the race, forcing a drifting now+7d estimate.
|
|
149
|
-
completionRegex: /Weekly limit
|
|
155
|
+
completionRegex: /Weekly limit:|Limits:\s*refresh requested|refresh token was already used|log out and sign in again/i,
|
|
150
156
|
abortOn: /Do you trust|Trust.*folder|trust the files in this folder/i,
|
|
151
157
|
timeoutMs: 12000,
|
|
152
158
|
maxBytes: 256 * 1024,
|
package/dist/adapters/muse.js
CHANGED
|
@@ -14,6 +14,9 @@ export function parseMuseTui(text, now = new Date()) {
|
|
|
14
14
|
// After ANSI stripping the panel is ONE line (cursor-addressed, no
|
|
15
15
|
// newlines), so no pattern here may anchor on ^, $ or \n.
|
|
16
16
|
const cleaned = stripAnsi(text);
|
|
17
|
+
if (/currently unavailable|subscriptions aren't currently available|subscription_unavailable/i.test(cleaned)) {
|
|
18
|
+
throw new Error("muse: subscription currently unavailable in TUI output");
|
|
19
|
+
}
|
|
17
20
|
const weekly = cleaned.match(/Weekly\s+(\d+)%\s+used/i);
|
|
18
21
|
if (!weekly)
|
|
19
22
|
throw new Error("muse: weekly usage not found in TUI output");
|
package/dist/cli/runtime.d.ts
CHANGED
|
@@ -31,4 +31,12 @@ export interface WebLaunchOptions {
|
|
|
31
31
|
open?: boolean;
|
|
32
32
|
}
|
|
33
33
|
export declare function launchWeb(o: WebLaunchOptions, deps?: RuntimeCommandDeps): Promise<void>;
|
|
34
|
+
export declare function waitForInitialPoll(port: number, opts?: {
|
|
35
|
+
createClient?: (opts: {
|
|
36
|
+
port: number;
|
|
37
|
+
timeoutMs: number;
|
|
38
|
+
}) => any;
|
|
39
|
+
sleep?: SleepFn;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
}): Promise<void>;
|
|
34
42
|
export declare function registerRuntimeCommands(program: Command, deps?: RuntimeCommandDeps): void;
|
package/dist/cli/runtime.js
CHANGED
|
@@ -55,16 +55,25 @@ export async function launchWeb(o, deps = {}) {
|
|
|
55
55
|
if (health?.ok) {
|
|
56
56
|
const skew = checkSkew(health, VERSION, process.execPath);
|
|
57
57
|
if (skew === "match") {
|
|
58
|
+
if (health.polling === "in-progress" && !health.lastCompletedPollAt) {
|
|
59
|
+
await waitForInitialPoll(port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
60
|
+
}
|
|
58
61
|
await openDashboard(`http://127.0.0.1:${port}`, openBrowser);
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
61
64
|
if (skew === "exec-only") {
|
|
62
65
|
console.error(execSkewWarning(String(health.exec)));
|
|
66
|
+
if (health.polling === "in-progress" && !health.lastCompletedPollAt) {
|
|
67
|
+
await waitForInitialPoll(port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
68
|
+
}
|
|
63
69
|
await openDashboard(`http://127.0.0.1:${port}`, openBrowser);
|
|
64
70
|
return;
|
|
65
71
|
}
|
|
66
72
|
if (skew === "cli-older") {
|
|
67
73
|
console.error(olderCliWarning(String(health.version ?? "unknown")));
|
|
74
|
+
if (health.polling === "in-progress" && !health.lastCompletedPollAt) {
|
|
75
|
+
await waitForInitialPoll(port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
76
|
+
}
|
|
68
77
|
await openDashboard(`http://127.0.0.1:${port}`, openBrowser);
|
|
69
78
|
return;
|
|
70
79
|
}
|
|
@@ -86,6 +95,7 @@ export async function launchWeb(o, deps = {}) {
|
|
|
86
95
|
exit(1);
|
|
87
96
|
return;
|
|
88
97
|
}
|
|
98
|
+
await waitForInitialPoll(port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
89
99
|
await openDashboard(`http://127.0.0.1:${port}`, openBrowser);
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
@@ -106,6 +116,7 @@ export async function launchWeb(o, deps = {}) {
|
|
|
106
116
|
}
|
|
107
117
|
// Unmanaged successor: foreground-start the new daemon and open it.
|
|
108
118
|
const started = await start(o.port ? { port } : undefined);
|
|
119
|
+
await waitForInitialPoll(started.port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
109
120
|
await openDashboard(`http://127.0.0.1:${started.port}`, openBrowser);
|
|
110
121
|
return;
|
|
111
122
|
}
|
|
@@ -144,6 +155,7 @@ export async function launchWeb(o, deps = {}) {
|
|
|
144
155
|
detail = `not ready within ${Math.round(readyTimeoutMs / 1000)}s`;
|
|
145
156
|
}
|
|
146
157
|
if (!detail) {
|
|
158
|
+
await waitForInitialPoll(port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
147
159
|
await openDashboard(`http://127.0.0.1:${port}`, openBrowser);
|
|
148
160
|
return;
|
|
149
161
|
}
|
|
@@ -155,8 +167,28 @@ export async function launchWeb(o, deps = {}) {
|
|
|
155
167
|
if (!foregroundOnly && !attemptedServiceStart && supported()) {
|
|
156
168
|
console.error("for a background service that survives terminal closes, run 'quotacap service install'");
|
|
157
169
|
}
|
|
170
|
+
await waitForInitialPoll(started.port, { createClient, sleep, timeoutMs: Math.min(readyTimeoutMs, 5000) });
|
|
158
171
|
await openDashboard(`http://127.0.0.1:${started.port}`, openBrowser);
|
|
159
172
|
}
|
|
173
|
+
export async function waitForInitialPoll(port, opts = {}) {
|
|
174
|
+
const createClient = opts.createClient ?? createServiceClient;
|
|
175
|
+
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
176
|
+
const timeoutMs = opts.timeoutMs ?? 5000;
|
|
177
|
+
const deadline = Date.now() + timeoutMs;
|
|
178
|
+
const client = createClient({ port, timeoutMs: 1000 });
|
|
179
|
+
while (Date.now() < deadline) {
|
|
180
|
+
await sleep(250);
|
|
181
|
+
try {
|
|
182
|
+
const h = await client.get("/health");
|
|
183
|
+
if (!h?.ok || h?.polling !== "in-progress" || h?.lastCompletedPollAt) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
160
192
|
async function defaultOpenBrowser(url) {
|
|
161
193
|
const cmd = process.platform === "darwin"
|
|
162
194
|
? "open"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FailureCategory } from "../store/attempts.js";
|
|
2
|
-
export type DiagnosticCode = "terminal_error" | "auth" | "command_not_found" | "trust_prompt" | "parse_error" | "rate_limit" | "timeout" | "network" | "unknown";
|
|
2
|
+
export type DiagnosticCode = "terminal_error" | "auth" | "command_not_found" | "trust_prompt" | "parse_error" | "rate_limit" | "service_unavailable" | "timeout" | "network" | "unknown";
|
|
3
3
|
export interface FailureEvidence {
|
|
4
4
|
source: "pty" | "exec";
|
|
5
5
|
checkpoint?: "spawn" | "before ready" | "during settle" | "before input" | "before completion" | "ready timeout" | "completion timeout" | "abort" | "trust prompt" | "write" | "transcript overflow";
|
|
@@ -20,6 +20,8 @@ export function formatProviderName(provider) {
|
|
|
20
20
|
return "Antigravity";
|
|
21
21
|
case "agy:3p":
|
|
22
22
|
return "Antigravity (3rd-party)";
|
|
23
|
+
case "muse":
|
|
24
|
+
return "Muse";
|
|
23
25
|
case "all":
|
|
24
26
|
return "QuotaCap service";
|
|
25
27
|
default:
|
|
@@ -134,6 +136,12 @@ function matchPrecedence(evidence, errorName, errorCode, text) {
|
|
|
134
136
|
if (/\bexpired credentials?\b/i.test(text) || /\bexpired token\b/i.test(text) || /\bexpired api key\b/i.test(text)) {
|
|
135
137
|
return { code: "auth", phrase: "expired credentials" };
|
|
136
138
|
}
|
|
139
|
+
if (/\brefresh token was already used\b/i.test(text) ||
|
|
140
|
+
/\blog out and sign in again\b/i.test(text) ||
|
|
141
|
+
/\baccess token could not be refreshed\b/i.test(text) ||
|
|
142
|
+
/\bsign in again\b/i.test(text)) {
|
|
143
|
+
return { code: "auth", phrase: "login required" };
|
|
144
|
+
}
|
|
137
145
|
if (/\bauthentication failed\b/i.test(text) ||
|
|
138
146
|
/\bauthentication required\b/i.test(text) ||
|
|
139
147
|
/\bmissing authentication\b/i.test(text)) {
|
|
@@ -216,6 +224,14 @@ function matchPrecedence(evidence, errorName, errorCode, text) {
|
|
|
216
224
|
if (/\bno weekly bucket\b/i.test(text)) {
|
|
217
225
|
return { code: "parse_error", phrase: "no weekly bucket" };
|
|
218
226
|
}
|
|
227
|
+
// Order 8b: service_unavailable — subscription or service currently unavailable
|
|
228
|
+
if (/\b(?:subscription|service|usage)\s+currently\s+unavailable\b/i.test(text) ||
|
|
229
|
+
/\bsubscriptions aren't currently available\b/i.test(text)) {
|
|
230
|
+
return { code: "service_unavailable", phrase: "subscription currently unavailable" };
|
|
231
|
+
}
|
|
232
|
+
if (/\blimits refresh requested\b/i.test(text) || /\brefresh requested\b/i.test(text)) {
|
|
233
|
+
return { code: "service_unavailable", phrase: "limits refresh requested" };
|
|
234
|
+
}
|
|
219
235
|
// Order 9: timeout — Remaining explicit timeout or timed out errors
|
|
220
236
|
if (/\btimed out\b/i.test(text) || /\btimeout\b/i.test(text)) {
|
|
221
237
|
return { code: "timeout", phrase: "timed out" };
|
|
@@ -386,6 +402,7 @@ function mapCategory(code) {
|
|
|
386
402
|
case "command_not_found":
|
|
387
403
|
case "trust_prompt":
|
|
388
404
|
case "rate_limit":
|
|
405
|
+
case "service_unavailable":
|
|
389
406
|
case "unknown":
|
|
390
407
|
default:
|
|
391
408
|
return "unknown";
|
|
@@ -423,6 +440,11 @@ function getSummaryAndAction(code, providerName) {
|
|
|
423
440
|
summary: `${providerName} rate limited`,
|
|
424
441
|
action: `Wait before retrying. While the service is running, QuotaCap will try again on its next scheduled poll.`,
|
|
425
442
|
};
|
|
443
|
+
case "service_unavailable":
|
|
444
|
+
return {
|
|
445
|
+
summary: `${providerName} usage currently unavailable`,
|
|
446
|
+
action: `${providerName} reported that subscription usage is currently unavailable. Send a prompt in ${providerName} to refresh its session limits, or wait for the next scheduled poll.`,
|
|
447
|
+
};
|
|
426
448
|
case "timeout":
|
|
427
449
|
return {
|
|
428
450
|
summary: `${providerName} took too long to respond`,
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// generated by scripts/build-embed.mjs — do not edit
|
|
2
|
-
export const VERSION = "0.0.
|
|
2
|
+
export const VERSION = "0.0.34";
|
package/dist/web/src/App.js
CHANGED
|
@@ -63,13 +63,11 @@ function App() {
|
|
|
63
63
|
useEffect(() => {
|
|
64
64
|
void load();
|
|
65
65
|
}, [load]);
|
|
66
|
-
// While the daemon reports
|
|
67
|
-
// settles. Without this the pill ("Polling", "Cooling down") sticks forever
|
|
68
|
-
// on the single post-refresh snapshot, which is always mid-cooldown.
|
|
66
|
+
// While the daemon reports an active poll, keep re-reading until it settles.
|
|
69
67
|
useEffect(() => {
|
|
70
|
-
if (!snapshot || snapshot.runtime.polling
|
|
68
|
+
if (!snapshot || snapshot.runtime.polling !== "in-progress")
|
|
71
69
|
return;
|
|
72
|
-
const t = setInterval(() => void load(),
|
|
70
|
+
const t = setInterval(() => void load(), 1000);
|
|
73
71
|
return () => clearInterval(t);
|
|
74
72
|
}, [snapshot, load]);
|
|
75
73
|
const refresh = useCallback(async () => {
|
|
@@ -81,7 +79,7 @@ function App() {
|
|
|
81
79
|
setNotice(result.message);
|
|
82
80
|
}
|
|
83
81
|
else if (result.raw?.cooldown === true) {
|
|
84
|
-
setNotice("
|
|
82
|
+
setNotice("Already up to date — showing latest readings.");
|
|
85
83
|
}
|
|
86
84
|
await load();
|
|
87
85
|
}
|
|
@@ -116,7 +114,7 @@ function App() {
|
|
|
116
114
|
border: i === 0 ? "1px solid var(--rec)" : "1px solid var(--line)",
|
|
117
115
|
borderRadius: 8,
|
|
118
116
|
marginBottom: 8,
|
|
119
|
-
} }, i))) })), error?.kind === "service-unavailable" && !snapshot && (_jsx(UnavailablePanel, { endpoint: daemonEndpoint(), lastGood: snapshot, onRetry: () => void load() })), error?.kind === "http-error" && !snapshot && (_jsx(HttpErrorPanel, { status: error.status, message: error.message, lastGood: snapshot, onRetry: () => void load() })), error && snapshot && (_jsxs("div", { "data-testid": "state-stale-error", role: "alert", children: [error.kind === "http-error" ? `Request failed (HTTP ${error.status}): ` : "Service unavailable: ", error.message, " Showing last readings (", ageLabel(snapshot.asOf), ").", " ", _jsx("button", { type: "button", onClick: () => void load(), children: "Retry" })] })), snapshot && (_jsx(FaultBanner, { providers: snapshot.providers, onRepoll: () => void refresh(), repolling: refreshing })), snapshot && (_jsx(ReadyView, { snapshot: snapshot, onSelectProvider: (id) => {
|
|
117
|
+
} }, i))) })), error?.kind === "service-unavailable" && !snapshot && (_jsx(UnavailablePanel, { endpoint: daemonEndpoint(), lastGood: snapshot, onRetry: () => void load() })), error?.kind === "http-error" && !snapshot && (_jsx(HttpErrorPanel, { status: error.status, message: error.message, lastGood: snapshot, onRetry: () => void load() })), error && snapshot && (_jsxs("div", { "data-testid": "state-stale-error", role: "alert", children: [error.kind === "http-error" ? `Request failed (HTTP ${error.status}): ` : "Service unavailable: ", error.message, " Showing last readings (", ageLabel(snapshot.asOf), ").", " ", _jsx("button", { type: "button", onClick: () => void load(), children: "Retry" })] })), snapshot && (_jsx(FaultBanner, { providers: snapshot.providers, onRepoll: () => void refresh(), repolling: refreshing, polling: snapshot.runtime.polling })), snapshot && (_jsx(ReadyView, { snapshot: snapshot, onSelectProvider: (id) => {
|
|
120
118
|
setAdviceOpen(false);
|
|
121
119
|
setSelectedProvider(id);
|
|
122
120
|
}, onViewAdvice: () => {
|
|
@@ -10,8 +10,9 @@ export interface Repair {
|
|
|
10
10
|
* service-unavailable panel when the daemon itself is down.
|
|
11
11
|
*/
|
|
12
12
|
export declare function repairFor(provider: ProviderView): Repair;
|
|
13
|
-
export declare function FaultBanner({ providers, onRepoll, repolling, }: {
|
|
13
|
+
export declare function FaultBanner({ providers, onRepoll, repolling, polling, }: {
|
|
14
14
|
providers: ProviderView[];
|
|
15
15
|
onRepoll: () => void;
|
|
16
16
|
repolling: boolean;
|
|
17
|
+
polling?: "idle" | "in-progress" | "degraded" | string;
|
|
17
18
|
}): React.JSX.Element | null;
|
|
@@ -53,8 +53,16 @@ function bannerVerb(provider) {
|
|
|
53
53
|
return "needs attention";
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
export function FaultBanner({ providers, onRepoll, repolling, }) {
|
|
57
|
-
const
|
|
56
|
+
export function FaultBanner({ providers, onRepoll, repolling, polling, }) {
|
|
57
|
+
const isPolling = repolling || polling === "in-progress";
|
|
58
|
+
const faulted = providers.filter((p) => {
|
|
59
|
+
if (!p.enabled || p.exclusionReason === null)
|
|
60
|
+
return false;
|
|
61
|
+
if (isPolling && (p.exclusionReason === "stale" || p.exclusionReason === "not-reporting")) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
58
66
|
if (faulted.length === 0)
|
|
59
67
|
return null;
|
|
60
68
|
return (_jsx("div", { children: faulted.map((p) => {
|
|
@@ -2,9 +2,9 @@ import React from "react";
|
|
|
2
2
|
import type { RuntimeView } from "../state.js";
|
|
3
3
|
export type PillState = "live" | "polling" | "unreachable" | "not-ready";
|
|
4
4
|
/**
|
|
5
|
-
* Header pill from runtime fields. Unreachable is red, polling
|
|
6
|
-
* amber, ready
|
|
7
|
-
* their own explicit state, never as green.
|
|
5
|
+
* Header pill from runtime fields. Unreachable is red, active polling is
|
|
6
|
+
* amber, ready is green (including during refresh cooldown). Not-ready and
|
|
7
|
+
* unknown-version surface as their own explicit state, never as green.
|
|
8
8
|
*/
|
|
9
9
|
export declare function pillFor(runtime: {
|
|
10
10
|
available: boolean;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ThemeToggle } from "../Theme.js";
|
|
3
3
|
/**
|
|
4
|
-
* Header pill from runtime fields. Unreachable is red, polling
|
|
5
|
-
* amber, ready
|
|
6
|
-
* their own explicit state, never as green.
|
|
4
|
+
* Header pill from runtime fields. Unreachable is red, active polling is
|
|
5
|
+
* amber, ready is green (including during refresh cooldown). Not-ready and
|
|
6
|
+
* unknown-version surface as their own explicit state, never as green.
|
|
7
7
|
*/
|
|
8
8
|
export function pillFor(runtime) {
|
|
9
9
|
if (!runtime.available)
|
|
10
10
|
return "unreachable";
|
|
11
|
-
if (runtime.polling === "in-progress"
|
|
11
|
+
if (runtime.polling === "in-progress")
|
|
12
12
|
return "polling";
|
|
13
13
|
if (!runtime.ready)
|
|
14
14
|
return "not-ready";
|
|
@@ -29,7 +29,7 @@ function pillText(pill, runtime) {
|
|
|
29
29
|
case "live":
|
|
30
30
|
return "daemon live";
|
|
31
31
|
case "polling":
|
|
32
|
-
return
|
|
32
|
+
return "Polling";
|
|
33
33
|
case "unreachable":
|
|
34
34
|
return "Daemon unreachable";
|
|
35
35
|
case "not-ready":
|