when-does-my-quota-refresh 1.0.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.
@@ -0,0 +1,2329 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ OAuthClient,
4
+ SourceUnavailableError,
5
+ debug,
6
+ ensureDirs,
7
+ getActiveAccountTokens,
8
+ getAllAccountTokens,
9
+ getConfigDirPath,
10
+ getDataDirPath,
11
+ listAccounts,
12
+ loadConfig,
13
+ loginAccount,
14
+ removeAccount,
15
+ saveConfig,
16
+ setActiveAccount,
17
+ setVerbose
18
+ } from "../chunk-YZVX5QIP.js";
19
+
20
+ // src/cli/index.ts
21
+ import { Command } from "commander";
22
+ import { readFileSync as readFileSync2 } from "fs";
23
+ import { join as join3, dirname } from "path";
24
+ import { fileURLToPath } from "url";
25
+
26
+ // src/cli/commands/quota.ts
27
+ import chalk3 from "chalk";
28
+ import ora from "ora";
29
+
30
+ // src/core/platform.ts
31
+ import { exec } from "child_process";
32
+ import { promisify } from "util";
33
+ var execAsync = promisify(exec);
34
+ function isWindows() {
35
+ return process.platform === "win32";
36
+ }
37
+ function isMacOS() {
38
+ return process.platform === "darwin";
39
+ }
40
+ function isLinux() {
41
+ return process.platform === "linux";
42
+ }
43
+ async function runCommand(cmd) {
44
+ try {
45
+ const { stdout } = await execAsync(cmd, { timeout: 1e4 });
46
+ return stdout;
47
+ } catch {
48
+ return "";
49
+ }
50
+ }
51
+ function formatCountdown(ms) {
52
+ if (ms <= 0) return "now";
53
+ const totalMinutes = Math.floor(ms / 6e4);
54
+ const hours = Math.floor(totalMinutes / 60);
55
+ const minutes = totalMinutes % 60;
56
+ if (hours > 0) return `${hours}h ${minutes}m`;
57
+ return `${minutes}m`;
58
+ }
59
+
60
+ // src/sources/local/process-detector.ts
61
+ var ANTIMATTER_MARKERS = [
62
+ "antigravity",
63
+ "language_server",
64
+ "language-server",
65
+ "antigravity-cli",
66
+ "antigravity_cli",
67
+ "agy"
68
+ ];
69
+ var ANTIMATTER_PATH_MARKERS = [
70
+ "/antigravity/",
71
+ "Antigravity",
72
+ "antigravity.app",
73
+ "AntigravityStatusProbe",
74
+ "app_data_dir antigravity"
75
+ ];
76
+ async function detectAntigravityProcesses() {
77
+ debug("process-detector", `Detecting on platform: ${process.platform}`);
78
+ let processes = [];
79
+ if (isMacOS()) {
80
+ processes = await detectOnMacOS();
81
+ } else if (isLinux()) {
82
+ processes = await detectOnLinux();
83
+ } else if (isWindows()) {
84
+ processes = await detectOnWindows();
85
+ }
86
+ debug("process-detector", `Found ${processes.length} process(es)`);
87
+ return processes;
88
+ }
89
+ async function detectOnMacOS() {
90
+ const output = await runCommand("ps -ax -o pid=,comm=,args=");
91
+ return parsePsOutput(output);
92
+ }
93
+ async function detectOnLinux() {
94
+ const output = await runCommand("ps -eo pid,comm,args --no-headers");
95
+ return parsePsOutput(output);
96
+ }
97
+ async function detectOnWindows() {
98
+ let output = await runCommand(
99
+ `wmic process where "name like '%language%' or commandline like '%antigravity%'" get ProcessId,Name,CommandLine /format:csv`
100
+ );
101
+ let processes = parseWmicOutput(output);
102
+ if (processes.length > 0) return processes;
103
+ output = await runCommand(
104
+ `powershell -Command "Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -match 'antigravity|language.server' } | Select-Object ProcessId,Name,CommandLine | Format-Table -AutoSize"`
105
+ );
106
+ processes = parsePowerShellOutput(output);
107
+ if (processes.length > 0) return processes;
108
+ debug("process-detector", "Windows: Falling back to port-based detection");
109
+ return [];
110
+ }
111
+ function parsePsOutput(output) {
112
+ const processes = [];
113
+ const lines = output.split("\n").filter(Boolean);
114
+ for (const line of lines) {
115
+ const trimmed = line.trim();
116
+ if (!trimmed) continue;
117
+ const parts = trimmed.split(/\s+/);
118
+ const pid = parseInt(parts[0], 10);
119
+ if (isNaN(pid)) continue;
120
+ const rest = parts.slice(1).join(" ");
121
+ if (matchesAntigravity(rest)) {
122
+ processes.push({
123
+ pid,
124
+ name: parts[1] || "unknown",
125
+ commandLine: rest,
126
+ scope: classifyProcess(rest)
127
+ });
128
+ }
129
+ }
130
+ return processes;
131
+ }
132
+ function parseWmicOutput(output) {
133
+ const processes = [];
134
+ const lines = output.split("\n").filter(Boolean);
135
+ for (const line of lines) {
136
+ const parts = line.split(",").map((p) => p.trim().replace(/"/g, ""));
137
+ if (parts.length < 4) continue;
138
+ const pid = parseInt(parts[3], 10);
139
+ if (isNaN(pid)) continue;
140
+ const commandLine = parts[1] || "";
141
+ const name = parts[2] || "";
142
+ if (matchesAntigravity(commandLine) || matchesAntigravity(name)) {
143
+ processes.push({
144
+ pid,
145
+ name,
146
+ commandLine,
147
+ scope: classifyProcess(commandLine)
148
+ });
149
+ }
150
+ }
151
+ return processes;
152
+ }
153
+ function parsePowerShellOutput(output) {
154
+ const processes = [];
155
+ const lines = output.split("\n").filter(Boolean);
156
+ for (const line of lines) {
157
+ const trimmed = line.trim();
158
+ if (!trimmed || trimmed.startsWith("---") || trimmed.startsWith("ProcessId")) continue;
159
+ const match = trimmed.match(/^\s*(\d+)\s+(\S+)\s+(.*)$/);
160
+ if (!match) continue;
161
+ const pid = parseInt(match[1], 10);
162
+ const name = match[2];
163
+ const commandLine = match[3];
164
+ if (matchesAntigravity(commandLine) || matchesAntigravity(name)) {
165
+ processes.push({
166
+ pid,
167
+ name,
168
+ commandLine,
169
+ scope: classifyProcess(commandLine)
170
+ });
171
+ }
172
+ }
173
+ return processes;
174
+ }
175
+ function matchesAntigravity(text) {
176
+ const lower = text.toLowerCase();
177
+ return ANTIMATTER_PATH_MARKERS.some(
178
+ (marker) => lower.includes(marker.toLowerCase())
179
+ ) || lower.includes("language") && lower.includes("server") && ANTIMATTER_MARKERS.some((m) => lower.includes(m));
180
+ }
181
+ function classifyProcess(commandLine) {
182
+ const lower = commandLine.toLowerCase();
183
+ if (lower.includes("antigravity-cli") || lower.includes("antigravity_cli") || lower.includes("agy")) {
184
+ return "cli";
185
+ }
186
+ if (lower.includes("antigravity ide") || lower.includes("antigravity-ide") || lower.includes("extensions/antigravity")) {
187
+ return "ide";
188
+ }
189
+ if (lower.includes("app_data_dir antigravity") || lower.includes("antigravity.app")) {
190
+ return "app";
191
+ }
192
+ return "unknown";
193
+ }
194
+ function extractFlags(commandLine) {
195
+ const flags = /* @__PURE__ */ new Map();
196
+ if (!commandLine) return flags;
197
+ const flagRegex = /--(\S+)\s+([^\s-][^\s]*)/g;
198
+ let match;
199
+ while ((match = flagRegex.exec(commandLine)) !== null) {
200
+ flags.set(match[1], match[2]);
201
+ }
202
+ return flags;
203
+ }
204
+
205
+ // src/sources/local/port-detective.ts
206
+ async function discoverPorts(pid) {
207
+ debug("port-detective", `Discovering ports for PID ${pid} on ${process.platform}`);
208
+ if (isWindows()) {
209
+ return discoverPortsOnWindows(pid);
210
+ } else if (isMacOS()) {
211
+ return discoverPortsOnMacOS(pid);
212
+ } else {
213
+ return discoverPortsOnLinux(pid);
214
+ }
215
+ }
216
+ async function discoverPortsOnMacOS(pid) {
217
+ try {
218
+ const output = await runCommand(`lsof -nP -iTCP -sTCP:LISTEN -a -p ${pid}`);
219
+ const ports = [];
220
+ for (const line of output.split("\n")) {
221
+ const match = line.match(/:(\d+)\s+\(LISTEN\)/);
222
+ if (match) {
223
+ const port = parseInt(match[1], 10);
224
+ if (!isNaN(port) && !ports.includes(port)) {
225
+ ports.push(port);
226
+ }
227
+ }
228
+ }
229
+ debug("port-detective", `macOS ports: ${ports.join(", ")}`);
230
+ return ports;
231
+ } catch {
232
+ return [];
233
+ }
234
+ }
235
+ async function discoverPortsOnLinux(pid) {
236
+ try {
237
+ const output = await runCommand(`ss -tlnp | grep "pid=${pid},"`);
238
+ const ports = parseListenPorts(output);
239
+ if (ports.length > 0) {
240
+ debug("port-detective", `Linux ports (ss): ${ports.join(", ")}`);
241
+ return ports;
242
+ }
243
+ } catch {
244
+ }
245
+ try {
246
+ const output = await runCommand(`netstat -tlnp 2>/dev/null | grep "${pid}/"`);
247
+ const ports = parseListenPorts(output);
248
+ debug("port-detective", `Linux ports (netstat): ${ports.join(", ")}`);
249
+ return ports;
250
+ } catch {
251
+ return [];
252
+ }
253
+ }
254
+ async function discoverPortsOnWindows(pid) {
255
+ try {
256
+ const output = await runCommand("netstat -ano");
257
+ const ports = [];
258
+ for (const line of output.split("\n")) {
259
+ if (!line.includes("LISTENING")) continue;
260
+ const parts = line.trim().split(/\s+/);
261
+ const linePid = parseInt(parts[parts.length - 1], 10);
262
+ if (linePid === pid) {
263
+ const localAddr = parts[1];
264
+ const portMatch = localAddr.match(/:(\d+)$/);
265
+ if (portMatch) {
266
+ const port = parseInt(portMatch[1], 10);
267
+ if (!isNaN(port) && !ports.includes(port)) {
268
+ ports.push(port);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ if (ports.length > 0) {
274
+ debug("port-detective", `Windows ports (netstat): ${ports.join(", ")}`);
275
+ return ports;
276
+ }
277
+ } catch {
278
+ }
279
+ try {
280
+ const output = await runCommand(
281
+ `powershell -Command "Get-NetTCPConnection -OwningProcess ${pid} -State Listen -ErrorAction SilentlyContinue | Select-Object LocalPort | Format-Table -AutoSize"`
282
+ );
283
+ const ports = [];
284
+ for (const line of output.split("\n")) {
285
+ const match = line.match(/^\s*(\d+)\s*$/);
286
+ if (match) {
287
+ const port = parseInt(match[1], 10);
288
+ if (!isNaN(port) && !ports.includes(port)) {
289
+ ports.push(port);
290
+ }
291
+ }
292
+ }
293
+ if (ports.length > 0) {
294
+ debug("port-detective", `Windows ports (PowerShell): ${ports.join(", ")}`);
295
+ return ports;
296
+ }
297
+ } catch {
298
+ }
299
+ debug("port-detective", "Windows: Could not find ports via system commands");
300
+ return [];
301
+ }
302
+ function parseListenPorts(output) {
303
+ const ports = [];
304
+ for (const line of output.split("\n")) {
305
+ const match = line.match(/:(\d+)\s/);
306
+ if (match) {
307
+ const port = parseInt(match[1], 10);
308
+ if (!isNaN(port) && !ports.includes(port)) {
309
+ ports.push(port);
310
+ }
311
+ }
312
+ }
313
+ return ports;
314
+ }
315
+ async function scanPortRange(startPort = 8e3, endPort = 9500) {
316
+ debug("port-detective", `Scanning port range ${startPort}-${endPort}`);
317
+ const foundPorts = [];
318
+ if (isWindows()) {
319
+ const output = await runCommand("netstat -ano");
320
+ for (const line of output.split("\n")) {
321
+ if (!line.includes("LISTENING")) continue;
322
+ const parts = line.trim().split(/\s+/);
323
+ const localAddr = parts[1];
324
+ const portMatch = localAddr.match(/:(\d+)$/);
325
+ if (portMatch) {
326
+ const port = parseInt(portMatch[1], 10);
327
+ if (port >= startPort && port <= endPort) {
328
+ foundPorts.push(port);
329
+ }
330
+ }
331
+ }
332
+ } else {
333
+ const cmd = isMacOS() ? `lsof -nP -iTCP -sTCP:LISTEN` : `ss -tlnp`;
334
+ const output = await runCommand(cmd);
335
+ for (const line of output.split("\n")) {
336
+ const match = line.match(/:(\d+)\s/);
337
+ if (match) {
338
+ const port = parseInt(match[1], 10);
339
+ if (port >= startPort && port <= endPort && !foundPorts.includes(port)) {
340
+ foundPorts.push(port);
341
+ }
342
+ }
343
+ }
344
+ }
345
+ debug("port-detective", `Found ${foundPorts.length} ports in range`);
346
+ return foundPorts;
347
+ }
348
+
349
+ // src/sources/local/connect-client.ts
350
+ import https from "https";
351
+ import http from "http";
352
+ var ENDPOINTS = {
353
+ quotaSummary: "/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
354
+ userStatus: "/exa.language_server_pb.LanguageServerService/GetUserStatus",
355
+ modelConfigs: "/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs",
356
+ unleash: "/exa.language_server_pb.LanguageServerService/GetUnleashData",
357
+ availableModels: "/exa.language_server_pb.LanguageServerService/GetAvailableModels"
358
+ };
359
+ var ConnectClient = class {
360
+ baseUrl;
361
+ csrfToken;
362
+ isHttps;
363
+ constructor(baseUrl, csrfToken) {
364
+ this.baseUrl = baseUrl;
365
+ this.csrfToken = csrfToken;
366
+ this.isHttps = baseUrl.startsWith("https://");
367
+ debug("connect-client", `Init: ${baseUrl}, hasToken: ${!!csrfToken}`);
368
+ }
369
+ /**
370
+ * Test if this server is reachable and speaks Connect protocol
371
+ */
372
+ async probe() {
373
+ try {
374
+ await this.request("POST", ENDPOINTS.unleash, {});
375
+ return true;
376
+ } catch {
377
+ return false;
378
+ }
379
+ }
380
+ /**
381
+ * Fetch quota summary (richest data: weekly + session buckets)
382
+ */
383
+ async fetchQuotaSummary() {
384
+ debug("connect-client", "Fetching RetrieveUserQuotaSummary");
385
+ try {
386
+ const response = await this.request("POST", ENDPOINTS.quotaSummary, {
387
+ metadata: {
388
+ ideName: "antigravity",
389
+ extensionName: "antigravity",
390
+ locale: "en"
391
+ }
392
+ });
393
+ if (response) {
394
+ return this.parseQuotaSummary(response);
395
+ }
396
+ } catch (err) {
397
+ debug("connect-client", `QuotaSummary failed: ${err}`);
398
+ }
399
+ return null;
400
+ }
401
+ /**
402
+ * Fetch user status (fallback for older servers)
403
+ */
404
+ async fetchUserStatus() {
405
+ debug("connect-client", "Fetching GetUserStatus");
406
+ try {
407
+ const response = await this.request("POST", ENDPOINTS.userStatus, {
408
+ metadata: {
409
+ ideName: "antigravity",
410
+ extensionName: "antigravity",
411
+ locale: "en"
412
+ }
413
+ });
414
+ if (response) {
415
+ return this.parseUserStatus(response);
416
+ }
417
+ } catch (err) {
418
+ debug("connect-client", `UserStatus failed: ${err}`);
419
+ }
420
+ return null;
421
+ }
422
+ /**
423
+ * Fetch model configs (last-resort fallback)
424
+ */
425
+ async fetchModelConfigs() {
426
+ debug("connect-client", "Fetching GetCascadeModelConfigData");
427
+ try {
428
+ return await this.request("POST", ENDPOINTS.modelConfigs, {
429
+ metadata: {
430
+ ideName: "antigravity",
431
+ extensionName: "antigravity",
432
+ locale: "en"
433
+ }
434
+ });
435
+ } catch (err) {
436
+ debug("connect-client", `ModelConfigs failed: ${err}`);
437
+ return null;
438
+ }
439
+ }
440
+ /**
441
+ * Make a Connect protocol HTTP(S) request
442
+ */
443
+ request(method, path, body) {
444
+ return new Promise((resolve, reject) => {
445
+ const url = new URL(path, this.baseUrl);
446
+ const headers = {
447
+ "Accept": "application/json",
448
+ "Content-Type": "application/json",
449
+ "Connect-Protocol-Version": "1"
450
+ };
451
+ if (this.csrfToken) {
452
+ headers["X-Codeium-Csrf-Token"] = this.csrfToken;
453
+ }
454
+ const options = {
455
+ hostname: url.hostname,
456
+ port: url.port,
457
+ path: url.pathname,
458
+ method,
459
+ headers,
460
+ timeout: 5e3,
461
+ rejectUnauthorized: false
462
+ };
463
+ const protocol = this.isHttps ? https : http;
464
+ const req = protocol.request(options, (res) => {
465
+ let data = "";
466
+ res.on("data", (chunk) => {
467
+ data += chunk;
468
+ });
469
+ res.on("end", () => {
470
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
471
+ try {
472
+ resolve(JSON.parse(data));
473
+ } catch {
474
+ resolve(data);
475
+ }
476
+ } else if (res.statusCode === 404) {
477
+ reject(new Error(`404: ${path}`));
478
+ } else {
479
+ reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
480
+ }
481
+ });
482
+ });
483
+ req.on("error", reject);
484
+ req.on("timeout", () => {
485
+ req.destroy();
486
+ reject(new Error("Timeout"));
487
+ });
488
+ if (body) req.write(JSON.stringify(body));
489
+ req.end();
490
+ });
491
+ }
492
+ /**
493
+ * Parse RetrieveUserQuotaSummary response
494
+ */
495
+ parseQuotaSummary(response) {
496
+ const result = { groups: [], raw: response };
497
+ if (typeof response !== "object" || response === null) return result;
498
+ const data = response;
499
+ const userStatus = data.userStatus;
500
+ const groups = data.groups || userStatus?.groups;
501
+ if (Array.isArray(groups)) {
502
+ result.groups = groups.map((group) => {
503
+ const buckets = Array.isArray(group.buckets) ? group.buckets.map((b) => ({
504
+ bucketId: String(b.bucketId || ""),
505
+ displayName: String(b.displayName || ""),
506
+ description: String(b.description || ""),
507
+ remainingFraction: typeof b.remaining?.remainingFraction === "number" ? b.remaining.remainingFraction : typeof b.remainingFraction === "number" ? b.remainingFraction : 1,
508
+ resetTime: typeof b.resetTime === "string" ? b.resetTime : void 0
509
+ })) : [];
510
+ return {
511
+ displayName: String(group.displayName || ""),
512
+ buckets
513
+ };
514
+ });
515
+ }
516
+ debug("connect-client", `Parsed ${result.groups.length} groups from quota summary`);
517
+ return result;
518
+ }
519
+ /**
520
+ * Parse GetUserStatus response (legacy fallback)
521
+ */
522
+ parseUserStatus(response) {
523
+ const status = { raw: response };
524
+ if (typeof response !== "object" || response === null) return status;
525
+ const data = response;
526
+ const userStatus = data.userStatus || data;
527
+ if (typeof userStatus.email === "string") status.email = userStatus.email;
528
+ if (typeof userStatus.isAuthenticated === "boolean") status.isAuthenticated = userStatus.isAuthenticated;
529
+ if (typeof userStatus.planType === "string") status.planType = userStatus.planType;
530
+ const planStatus = userStatus.planStatus;
531
+ if (planStatus) {
532
+ const available = planStatus.availablePromptCredits;
533
+ const planInfo = planStatus.planInfo;
534
+ const monthly = planInfo?.monthlyPromptCredits;
535
+ if (typeof available === "number" && typeof monthly === "number") {
536
+ status.quota = {
537
+ promptCredits: {
538
+ used: monthly - available,
539
+ limit: monthly,
540
+ remaining: available
541
+ }
542
+ };
543
+ }
544
+ }
545
+ const cascadeData = userStatus.cascadeModelConfigData;
546
+ const clientModelConfigs = cascadeData?.clientModelConfigs;
547
+ if (Array.isArray(clientModelConfigs)) {
548
+ status.quota = status.quota || {};
549
+ status.quota.models = clientModelConfigs.map((m) => {
550
+ const modelOrAlias = m.modelOrAlias;
551
+ const modelId = typeof modelOrAlias?.model === "string" ? modelOrAlias.model : "unknown";
552
+ const quotaInfo = m.quotaInfo;
553
+ const remainingFraction = typeof quotaInfo?.remainingFraction === "number" ? quotaInfo.remainingFraction : void 0;
554
+ const resetTime = typeof quotaInfo?.resetTime === "string" ? quotaInfo.resetTime : void 0;
555
+ return {
556
+ modelId,
557
+ displayName: typeof m.label === "string" ? m.label : void 0,
558
+ label: typeof m.label === "string" ? m.label : void 0,
559
+ quota: {
560
+ remainingPercentage: remainingFraction,
561
+ resetTime,
562
+ timeUntilResetMs: resetTime ? new Date(resetTime).getTime() - Date.now() : void 0
563
+ },
564
+ isExhausted: remainingFraction === 0
565
+ };
566
+ });
567
+ }
568
+ return status;
569
+ }
570
+ };
571
+
572
+ // src/sources/local/parser.ts
573
+ function parseQuotaSummary(summary, email) {
574
+ debug("parser", `Parsing quota summary: ${summary.groups.length} groups`);
575
+ return {
576
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
577
+ method: "local",
578
+ email,
579
+ groups: summary.groups.map((g) => ({
580
+ displayName: g.displayName,
581
+ buckets: g.buckets.map((b) => ({
582
+ bucketId: b.bucketId,
583
+ displayName: b.displayName,
584
+ description: b.description,
585
+ remainingFraction: clampFraction(b.remainingFraction),
586
+ resetTime: b.resetTime,
587
+ timeUntilResetMs: b.resetTime ? Math.max(0, new Date(b.resetTime).getTime() - Date.now()) : void 0
588
+ }))
589
+ }))
590
+ };
591
+ }
592
+ function parseUserStatus(userStatus) {
593
+ debug("parser", "Parsing GetUserStatus fallback");
594
+ const snapshot = {
595
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
596
+ method: "local",
597
+ email: userStatus.email,
598
+ groups: []
599
+ };
600
+ if (userStatus.quota?.promptCredits) {
601
+ snapshot.promptCredits = parsePromptCredits(userStatus.quota.promptCredits);
602
+ }
603
+ if (userStatus.quota?.models) {
604
+ const geminiModels = [];
605
+ const claudeModels = [];
606
+ for (const model of userStatus.quota.models) {
607
+ const info = {
608
+ label: model.label || model.displayName || model.modelId,
609
+ modelId: model.modelId,
610
+ remainingPercentage: model.quota?.remainingPercentage,
611
+ isExhausted: model.isExhausted ?? model.quota?.remainingPercentage === 0,
612
+ resetTime: model.quota?.resetTime,
613
+ timeUntilResetMs: model.quota?.timeUntilResetMs,
614
+ isAutocompleteOnly: detectAutocomplete(model.modelId, model.label)
615
+ };
616
+ if (info.isAutocompleteOnly) continue;
617
+ if (isGeminiModel(model.modelId)) {
618
+ geminiModels.push(info);
619
+ } else {
620
+ claudeModels.push(info);
621
+ }
622
+ }
623
+ if (geminiModels.length > 0) {
624
+ snapshot.groups.push(createGroupFromModels("Gemini Models", geminiModels));
625
+ }
626
+ if (claudeModels.length > 0) {
627
+ snapshot.groups.push(createGroupFromModels("Claude + GPT Models", claudeModels));
628
+ }
629
+ }
630
+ return snapshot;
631
+ }
632
+ function parsePromptCredits(credits) {
633
+ if (!credits) return void 0;
634
+ const limit = credits.limit ?? 0;
635
+ const remaining = credits.remaining ?? limit;
636
+ const used = credits.used ?? limit - remaining;
637
+ if (limit === 0) return void 0;
638
+ return {
639
+ available: remaining,
640
+ monthly: limit,
641
+ usedPercentage: limit > 0 ? used / limit : 0,
642
+ remainingPercentage: limit > 0 ? remaining / limit : 1
643
+ };
644
+ }
645
+ function createGroupFromModels(displayName, models) {
646
+ const bestModel = models.reduce((best, m) => {
647
+ const remaining = m.remainingPercentage ?? 1;
648
+ const bestRemaining = best.remainingPercentage ?? 1;
649
+ return remaining < bestRemaining ? m : best;
650
+ }, models[0]);
651
+ const bucket = {
652
+ bucketId: "session",
653
+ displayName: "Session (5-hour)",
654
+ description: bestModel.resetTime ? `Resets at ${new Date(bestModel.resetTime).toLocaleTimeString()}` : "5-hour session window",
655
+ remainingFraction: bestModel.remainingPercentage ?? 1,
656
+ resetTime: bestModel.resetTime,
657
+ timeUntilResetMs: bestModel.timeUntilResetMs
658
+ };
659
+ return {
660
+ displayName,
661
+ buckets: [bucket]
662
+ };
663
+ }
664
+ function isGeminiModel(modelId) {
665
+ const lower = modelId.toLowerCase();
666
+ return lower.includes("gemini");
667
+ }
668
+ function detectAutocomplete(modelId, label) {
669
+ const text = `${modelId} ${label || ""}`.toLowerCase();
670
+ return text.includes("autocomplete") || text.includes("gemini-2.5") || text.includes("gemini 2.5");
671
+ }
672
+ function clampFraction(value) {
673
+ if (value === void 0 || value === null) return 1;
674
+ return Math.max(0, Math.min(1, value));
675
+ }
676
+
677
+ // src/sources/local/index.ts
678
+ var LocalSource = class {
679
+ name = "local";
680
+ priority = 1;
681
+ async isAvailable() {
682
+ try {
683
+ const processes = await detectAntigravityProcesses();
684
+ return processes.length > 0;
685
+ } catch {
686
+ return false;
687
+ }
688
+ }
689
+ async fetchQuota() {
690
+ debug("local", "Starting local fetch");
691
+ const processes = await detectAntigravityProcesses();
692
+ if (processes.length === 0) {
693
+ throw new SourceUnavailableError("local", "No Antigravity process found");
694
+ }
695
+ for (const proc of processes) {
696
+ debug("local", `Trying PID ${proc.pid} (${proc.scope})`);
697
+ const flags = extractFlags(proc.commandLine || "");
698
+ let ports = [];
699
+ const directPort = flags.get("extension_server_port");
700
+ if (directPort) {
701
+ const portNum = parseInt(directPort, 10);
702
+ if (!isNaN(portNum)) {
703
+ ports.push(portNum);
704
+ debug("local", `Found port from command line: ${portNum}`);
705
+ }
706
+ }
707
+ const discoveredPorts = await discoverPorts(proc.pid);
708
+ for (const p of discoveredPorts) {
709
+ if (!ports.includes(p)) ports.push(p);
710
+ }
711
+ const csrfToken = flags.get("csrf_token") || flags.get("extension_server_csrf_token");
712
+ for (const port of ports) {
713
+ const quota = await this.probePort(port, csrfToken);
714
+ if (quota) return quota;
715
+ }
716
+ }
717
+ debug("local", "Direct detection failed, scanning port range");
718
+ const scannedPorts = await scanPortRange(8e3, 9500);
719
+ for (const port of scannedPorts) {
720
+ const quota = await this.probePort(port);
721
+ if (quota) return quota;
722
+ }
723
+ throw new SourceUnavailableError("local", "Could not connect to any local server");
724
+ }
725
+ /**
726
+ * Probe a single port and try to fetch quota
727
+ */
728
+ async probePort(port, csrfToken) {
729
+ for (const scheme of ["https", "http"]) {
730
+ const baseUrl = `${scheme}://127.0.0.1:${port}`;
731
+ const client = new ConnectClient(baseUrl, csrfToken);
732
+ const reachable = await client.probe();
733
+ if (!reachable) continue;
734
+ debug("local", `Probing ${baseUrl}`);
735
+ const summary = await client.fetchQuotaSummary();
736
+ if (summary && summary.groups.length > 0) {
737
+ const email = (await client.fetchUserStatus())?.email;
738
+ debug("local", `Got quota summary from ${baseUrl}`);
739
+ return parseQuotaSummary(summary, email);
740
+ }
741
+ const userStatus = await client.fetchUserStatus();
742
+ if (userStatus) {
743
+ debug("local", `Got user status from ${baseUrl}`);
744
+ return parseUserStatus(userStatus);
745
+ }
746
+ }
747
+ return null;
748
+ }
749
+ };
750
+
751
+ // src/sources/cli/index.ts
752
+ import { exec as exec2 } from "child_process";
753
+ import { promisify as promisify2 } from "util";
754
+ var execAsync2 = promisify2(exec2);
755
+ var CliSource = class {
756
+ name = "cli";
757
+ priority = 2;
758
+ agyProcess = null;
759
+ async isAvailable() {
760
+ const path = await findAgyBinary();
761
+ return path !== null;
762
+ }
763
+ async fetchQuota() {
764
+ debug("cli", "Starting CLI source fetch");
765
+ const agyPath = await findAgyBinary();
766
+ if (!agyPath) {
767
+ throw new SourceUnavailableError("cli", "agy binary not found");
768
+ }
769
+ const existingQuota = await this.tryExistingAgy();
770
+ if (existingQuota) return existingQuota;
771
+ return await this.launchAndFetch(agyPath);
772
+ }
773
+ /**
774
+ * Check if there's already a running agy instance we can reuse
775
+ */
776
+ async tryExistingAgy() {
777
+ const cmd = isWindows() ? `wmic process where "commandline like '%agy%'" get ProcessId,CommandLine /format:csv` : "ps -ax -o pid=,args=";
778
+ const output = await runCommand(cmd);
779
+ const pids = output.split("\n").map((line) => {
780
+ const match = line.match(/^\s*(\d+)/);
781
+ return match ? parseInt(match[1], 10) : NaN;
782
+ }).filter((p) => !isNaN(p));
783
+ for (const pid of pids) {
784
+ const ports = await discoverPorts(pid);
785
+ for (const port of ports) {
786
+ const quota = await this.probePort(port);
787
+ if (quota) return quota;
788
+ }
789
+ }
790
+ return null;
791
+ }
792
+ /**
793
+ * Launch agy and wait for its server, then fetch quota
794
+ */
795
+ async launchAndFetch(agyPath) {
796
+ debug("cli", `Launching agy: ${agyPath}`);
797
+ return new Promise((resolve, reject) => {
798
+ const timeout = setTimeout(() => {
799
+ this.agyProcess?.kill();
800
+ reject(new SourceUnavailableError("cli", "agy startup timed out"));
801
+ }, 15e3);
802
+ this.agyProcess = exec2(agyPath, { env: { ...process.env, NO_COLOR: "1" } });
803
+ const pollInterval = setInterval(async () => {
804
+ try {
805
+ if (!this.agyProcess?.pid) return;
806
+ const ports = await discoverPorts(this.agyProcess.pid);
807
+ for (const port of ports) {
808
+ const quota = await this.probePort(port);
809
+ if (quota) {
810
+ clearTimeout(timeout);
811
+ clearInterval(pollInterval);
812
+ this.agyProcess?.kill();
813
+ resolve(quota);
814
+ return;
815
+ }
816
+ }
817
+ } catch {
818
+ }
819
+ }, 1e3);
820
+ this.agyProcess.on("error", (err) => {
821
+ clearTimeout(timeout);
822
+ clearInterval(pollInterval);
823
+ reject(new SourceUnavailableError("cli", `agy failed: ${err.message}`));
824
+ });
825
+ this.agyProcess.on("exit", () => {
826
+ clearTimeout(timeout);
827
+ clearInterval(pollInterval);
828
+ });
829
+ });
830
+ }
831
+ /**
832
+ * Probe a port for quota data
833
+ */
834
+ async probePort(port) {
835
+ for (const scheme of ["https", "http"]) {
836
+ const baseUrl = `${scheme}://127.0.0.1:${port}`;
837
+ const client = new ConnectClient(baseUrl);
838
+ try {
839
+ const reachable = await client.probe();
840
+ if (!reachable) continue;
841
+ const summary = await client.fetchQuotaSummary();
842
+ if (summary && summary.groups.length > 0) {
843
+ const email = (await client.fetchUserStatus())?.email;
844
+ return parseQuotaSummary(summary, email);
845
+ }
846
+ const userStatus = await client.fetchUserStatus();
847
+ if (userStatus) return parseUserStatus(userStatus);
848
+ } catch {
849
+ }
850
+ }
851
+ return null;
852
+ }
853
+ destroy() {
854
+ this.agyProcess?.kill();
855
+ this.agyProcess = null;
856
+ }
857
+ };
858
+ async function findAgyBinary() {
859
+ if (process.env.ANTIGRAVITY_CLI_PATH) {
860
+ return process.env.ANTIGRAVITY_CLI_PATH;
861
+ }
862
+ try {
863
+ const cmd = isWindows() ? "where agy" : "which agy";
864
+ const output = await execAsync2(cmd);
865
+ const path = output.stdout.trim();
866
+ if (path) return path;
867
+ } catch {
868
+ }
869
+ const commonPaths = isWindows() ? [
870
+ `${process.env.LOCALAPPDATA || ""}/antigravity-cli/agy.exe`,
871
+ `${process.env.PROGRAMFILES || ""}/antigravity-cli/agy.exe`
872
+ ] : [
873
+ `${process.env.HOME}/.local/bin/agy`,
874
+ "/opt/homebrew/bin/agy",
875
+ "/usr/local/bin/agy"
876
+ ];
877
+ for (const p of commonPaths) {
878
+ if (!p) continue;
879
+ try {
880
+ await execAsync2(`test -x "${p}"`);
881
+ return p;
882
+ } catch {
883
+ }
884
+ }
885
+ return null;
886
+ }
887
+
888
+ // src/sources/oauth/index.ts
889
+ var OAuthSource = class {
890
+ name = "oauth";
891
+ priority = 3;
892
+ client;
893
+ constructor(client) {
894
+ this.client = client;
895
+ }
896
+ async isAvailable() {
897
+ try {
898
+ await this.client.getAccessToken();
899
+ return true;
900
+ } catch {
901
+ return false;
902
+ }
903
+ }
904
+ async fetchQuota() {
905
+ debug("oauth", "Fetching quota via Cloud Code API");
906
+ try {
907
+ const summaryRaw = await this.client.fetchQuotaSummary();
908
+ const summary = this.parseRemoteSummary(summaryRaw);
909
+ if (summary && summary.groups.length > 0) {
910
+ const statusRaw2 = await this.client.fetchUserStatus();
911
+ const email = this.extractEmail(statusRaw2);
912
+ debug("oauth", "Got quota summary via OAuth");
913
+ return parseQuotaSummary(summary, email);
914
+ }
915
+ const statusRaw = await this.client.fetchUserStatus();
916
+ const status = this.parseRemoteStatus(statusRaw);
917
+ if (status) {
918
+ debug("oauth", "Got user status via OAuth");
919
+ return parseUserStatus(status);
920
+ }
921
+ } catch (err) {
922
+ debug("oauth", `Fetch failed: ${err}`);
923
+ }
924
+ throw new SourceUnavailableError("oauth", "Could not fetch quota via Cloud Code API");
925
+ }
926
+ /**
927
+ * Parse remote summary response into QuotaSummaryResponse format
928
+ */
929
+ parseRemoteSummary(raw) {
930
+ if (typeof raw !== "object" || raw === null) return null;
931
+ const data = raw;
932
+ const groups = data.groups;
933
+ if (!Array.isArray(groups)) return null;
934
+ return {
935
+ groups: groups.map((g) => ({
936
+ displayName: String(g.displayName || ""),
937
+ buckets: Array.isArray(g.buckets) ? g.buckets.map((b) => ({
938
+ bucketId: String(b.bucketId || ""),
939
+ displayName: String(b.displayName || ""),
940
+ description: String(b.description || ""),
941
+ remainingFraction: typeof b.remaining?.remainingFraction === "number" ? b.remaining.remainingFraction : typeof b.remainingFraction === "number" ? b.remainingFraction : 1,
942
+ resetTime: typeof b.resetTime === "string" ? b.resetTime : void 0
943
+ })) : []
944
+ })),
945
+ raw
946
+ };
947
+ }
948
+ /**
949
+ * Parse remote user status response
950
+ */
951
+ parseRemoteStatus(raw) {
952
+ if (typeof raw !== "object" || raw === null) return null;
953
+ return { raw };
954
+ }
955
+ extractEmail(raw) {
956
+ if (typeof raw !== "object" || raw === null) return void 0;
957
+ const data = raw;
958
+ const userStatus = data.userStatus;
959
+ return userStatus?.email || data.email;
960
+ }
961
+ };
962
+
963
+ // src/sources/index.ts
964
+ var SourceRegistry = class {
965
+ sources = [];
966
+ health = /* @__PURE__ */ new Map();
967
+ constructor() {
968
+ this.sources.push(new LocalSource());
969
+ this.sources.push(new CliSource());
970
+ }
971
+ /**
972
+ * Register an additional data source (e.g., OAuth)
973
+ */
974
+ addSource(source) {
975
+ const idx = this.sources.findIndex((s) => s.priority > source.priority);
976
+ if (idx === -1) {
977
+ this.sources.push(source);
978
+ } else {
979
+ this.sources.splice(idx, 0, source);
980
+ }
981
+ debug("registry", `Registered source: ${source.name} (priority ${source.priority})`);
982
+ }
983
+ /**
984
+ * Remove a source by name
985
+ */
986
+ removeSource(name) {
987
+ this.sources = this.sources.filter((s) => s.name !== name);
988
+ }
989
+ /**
990
+ * Fetch quota using the best available source
991
+ *
992
+ * @param preferredSource Force a specific source
993
+ */
994
+ async fetchQuota(preferredSource) {
995
+ if (preferredSource) {
996
+ const source = this.sources.find((s) => s.name === preferredSource);
997
+ if (!source) throw new Error(`Source "${preferredSource}" not registered`);
998
+ const snapshot = await source.fetchQuota();
999
+ this.health.set(preferredSource, { available: true, lastCheck: Date.now() });
1000
+ return snapshot;
1001
+ }
1002
+ const errors = [];
1003
+ for (const source of this.sources) {
1004
+ debug("registry", `Trying source: ${source.name}`);
1005
+ try {
1006
+ const available = await source.isAvailable();
1007
+ if (!available) {
1008
+ debug("registry", `Source ${source.name} not available`);
1009
+ this.health.set(source.name, { available: false, lastCheck: Date.now() });
1010
+ continue;
1011
+ }
1012
+ const snapshot = await source.fetchQuota();
1013
+ this.health.set(source.name, { available: true, lastCheck: Date.now() });
1014
+ debug("registry", `Success with source: ${source.name}`);
1015
+ return snapshot;
1016
+ } catch (err) {
1017
+ const msg = err instanceof Error ? err.message : String(err);
1018
+ errors.push(`${source.name}: ${msg}`);
1019
+ this.health.set(source.name, { available: false, lastCheck: Date.now() });
1020
+ debug("registry", `Source ${source.name} failed: ${msg}`);
1021
+ }
1022
+ }
1023
+ throw new SourceUnavailableError("all", `No sources available:
1024
+ ${errors.map((e) => ` \u2022 ${e}`).join("\n")}`);
1025
+ }
1026
+ /**
1027
+ * Get health status of all sources
1028
+ */
1029
+ getHealth() {
1030
+ return new Map(this.health);
1031
+ }
1032
+ /**
1033
+ * Get registered source names
1034
+ */
1035
+ getSourceNames() {
1036
+ return this.sources.map((s) => s.name);
1037
+ }
1038
+ };
1039
+
1040
+ // src/sdk/cache.ts
1041
+ var QuotaCache = class {
1042
+ cache = /* @__PURE__ */ new Map();
1043
+ ttlMs;
1044
+ constructor(ttlMs = 5 * 60 * 1e3) {
1045
+ this.ttlMs = ttlMs;
1046
+ }
1047
+ /**
1048
+ * Get cached quota if available and not expired
1049
+ */
1050
+ get(email, source) {
1051
+ const key = `${email}:${source}`;
1052
+ const entry = this.cache.get(key);
1053
+ if (!entry) return null;
1054
+ const age = Date.now() - entry.cachedAt;
1055
+ if (age > this.ttlMs) {
1056
+ this.cache.delete(key);
1057
+ debug("cache", `Expired entry for ${email} (age: ${Math.round(age / 1e3)}s)`);
1058
+ return null;
1059
+ }
1060
+ debug("cache", `Cache hit for ${email} (age: ${Math.round(age / 1e3)}s)`);
1061
+ return entry.snapshot;
1062
+ }
1063
+ /**
1064
+ * Store a quota snapshot in cache
1065
+ */
1066
+ set(email, source, snapshot) {
1067
+ const key = `${email}:${source}`;
1068
+ this.cache.set(key, {
1069
+ snapshot,
1070
+ cachedAt: Date.now()
1071
+ });
1072
+ debug("cache", `Cached snapshot for ${email}`);
1073
+ }
1074
+ /**
1075
+ * Check if a cached entry is still fresh
1076
+ */
1077
+ isFresh(email, source) {
1078
+ const key = `${email}:${source}`;
1079
+ const entry = this.cache.get(key);
1080
+ if (!entry) return false;
1081
+ return Date.now() - entry.cachedAt <= this.ttlMs;
1082
+ }
1083
+ /**
1084
+ * Clear all cached entries
1085
+ */
1086
+ clear() {
1087
+ this.cache.clear();
1088
+ debug("cache", "Cleared all cache entries");
1089
+ }
1090
+ /**
1091
+ * Get cache age in seconds for a key
1092
+ */
1093
+ getAgeMs(email, source) {
1094
+ const key = `${email}:${source}`;
1095
+ const entry = this.cache.get(key);
1096
+ if (!entry) return null;
1097
+ return Date.now() - entry.cachedAt;
1098
+ }
1099
+ };
1100
+
1101
+ // src/sdk/store.ts
1102
+ import Database from "better-sqlite3";
1103
+ import { join } from "path";
1104
+ var db = null;
1105
+ function getDb() {
1106
+ if (db) return db;
1107
+ ensureDirs();
1108
+ const dbPath = join(getDataDirPath(), "history.db");
1109
+ db = new Database(dbPath);
1110
+ db.pragma("journal_mode = WAL");
1111
+ initSchema(db);
1112
+ debug("store", `Opened database at ${dbPath}`);
1113
+ return db;
1114
+ }
1115
+ function initSchema(database) {
1116
+ database.exec(`
1117
+ CREATE TABLE IF NOT EXISTS quota_snapshots (
1118
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1119
+ timestamp TEXT NOT NULL,
1120
+ email TEXT NOT NULL,
1121
+ method TEXT NOT NULL,
1122
+ gemini_weekly_remaining REAL,
1123
+ gemini_session_remaining REAL,
1124
+ claude_weekly_remaining REAL,
1125
+ claude_session_remaining REAL,
1126
+ raw_json TEXT
1127
+ );
1128
+
1129
+ CREATE INDEX IF NOT EXISTS idx_snapshots_email_ts
1130
+ ON quota_snapshots(email, timestamp);
1131
+ `);
1132
+ }
1133
+ function storeSnapshot(snapshot) {
1134
+ const database = getDb();
1135
+ let geminiWeekly = null;
1136
+ let geminiSession = null;
1137
+ let claudeWeekly = null;
1138
+ let claudeSession = null;
1139
+ for (const group of snapshot.groups) {
1140
+ const lower = group.displayName.toLowerCase();
1141
+ for (const bucket of group.buckets) {
1142
+ const bucketLower = bucket.displayName.toLowerCase();
1143
+ if (lower.includes("gemini")) {
1144
+ if (bucketLower.includes("weekly") || bucketLower.includes("week")) {
1145
+ geminiWeekly = bucket.remainingFraction;
1146
+ } else {
1147
+ geminiSession = bucket.remainingFraction;
1148
+ }
1149
+ } else if (lower.includes("claude") || lower.includes("gpt")) {
1150
+ if (bucketLower.includes("weekly") || bucketLower.includes("week")) {
1151
+ claudeWeekly = bucket.remainingFraction;
1152
+ } else {
1153
+ claudeSession = bucket.remainingFraction;
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ const stmt = database.prepare(`
1159
+ INSERT INTO quota_snapshots
1160
+ (timestamp, email, method, gemini_weekly_remaining, gemini_session_remaining,
1161
+ claude_weekly_remaining, claude_session_remaining, raw_json)
1162
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1163
+ `);
1164
+ stmt.run(
1165
+ snapshot.timestamp,
1166
+ snapshot.email || "unknown",
1167
+ snapshot.method,
1168
+ geminiWeekly,
1169
+ geminiSession,
1170
+ claudeWeekly,
1171
+ claudeSession,
1172
+ JSON.stringify(snapshot)
1173
+ );
1174
+ debug("store", `Stored snapshot for ${snapshot.email}`);
1175
+ }
1176
+ function getHistory(email, options) {
1177
+ const database = getDb();
1178
+ const days = options?.days || 7;
1179
+ const limit = options?.limit || 1e3;
1180
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
1181
+ const stmt = database.prepare(`
1182
+ SELECT timestamp, email, gemini_weekly_remaining, gemini_session_remaining,
1183
+ claude_weekly_remaining, claude_session_remaining
1184
+ FROM quota_snapshots
1185
+ WHERE email = ? AND timestamp >= ?
1186
+ ORDER BY timestamp DESC
1187
+ LIMIT ?
1188
+ `);
1189
+ const rows = stmt.all(email, since, limit);
1190
+ return rows.map((row) => ({
1191
+ timestamp: String(row.timestamp),
1192
+ email: String(row.email),
1193
+ geminiWeeklyRemaining: row.gemini_weekly_remaining,
1194
+ geminiSessionRemaining: row.gemini_session_remaining,
1195
+ claudeWeeklyRemaining: row.claude_weekly_remaining,
1196
+ claudeSessionRemaining: row.claude_session_remaining
1197
+ }));
1198
+ }
1199
+ function getDailySummary(email, days = 7) {
1200
+ const database = getDb();
1201
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
1202
+ const stmt = database.prepare(`
1203
+ SELECT
1204
+ date(timestamp) as date,
1205
+ AVG(gemini_weekly_remaining) as gemini_weekly_avg,
1206
+ AVG(gemini_session_remaining) as gemini_session_avg,
1207
+ AVG(claude_weekly_remaining) as claude_weekly_avg,
1208
+ AVG(claude_session_remaining) as claude_session_avg,
1209
+ COUNT(*) as count
1210
+ FROM quota_snapshots
1211
+ WHERE email = ? AND timestamp >= ?
1212
+ GROUP BY date(timestamp)
1213
+ ORDER BY date(timestamp) ASC
1214
+ `);
1215
+ const rows = stmt.all(email, since);
1216
+ return rows.map((row) => ({
1217
+ date: String(row.date),
1218
+ geminiWeeklyAvg: row.gemini_weekly_avg,
1219
+ geminiSessionAvg: row.gemini_session_avg,
1220
+ claudeWeeklyAvg: row.claude_weekly_avg,
1221
+ claudeSessionAvg: row.claude_session_avg,
1222
+ count: Number(row.count)
1223
+ }));
1224
+ }
1225
+ function closeStore() {
1226
+ if (db) {
1227
+ db.close();
1228
+ db = null;
1229
+ debug("store", "Database closed");
1230
+ }
1231
+ }
1232
+
1233
+ // src/sdk/quota.ts
1234
+ var QuotaClient = class {
1235
+ registry;
1236
+ cache;
1237
+ verbose;
1238
+ constructor(options) {
1239
+ this.verbose = options?.verbose || false;
1240
+ setVerbose(this.verbose);
1241
+ const config = loadConfig();
1242
+ this.registry = new SourceRegistry();
1243
+ this.cache = new QuotaCache(options?.cacheTtlMs || config.cacheTtlMs);
1244
+ this.registerOAuthSources();
1245
+ }
1246
+ /**
1247
+ * Fetch quota for the active account (or all accounts)
1248
+ */
1249
+ async fetchQuota(options) {
1250
+ if (options?.allAccounts) {
1251
+ return this.fetchAllAccounts(options);
1252
+ }
1253
+ if (!options?.refresh) {
1254
+ }
1255
+ const snapshot = await this.registry.fetchQuota(options?.source);
1256
+ if (snapshot.email) {
1257
+ this.cache.set(snapshot.email, snapshot.method, snapshot);
1258
+ storeSnapshot(snapshot);
1259
+ }
1260
+ return snapshot;
1261
+ }
1262
+ /**
1263
+ * Fetch quota for all stored accounts
1264
+ */
1265
+ async fetchAllAccounts(options) {
1266
+ const tokens = getAllAccountTokens();
1267
+ const snapshots = [];
1268
+ try {
1269
+ const snapshot = await this.registry.fetchQuota(options?.source);
1270
+ snapshots.push(snapshot);
1271
+ } catch (err) {
1272
+ debug("quota", `Active account fetch failed: ${err}`);
1273
+ }
1274
+ for (const token of tokens) {
1275
+ if (!token.email) continue;
1276
+ if (snapshots.some((s) => s.email === token.email)) continue;
1277
+ try {
1278
+ const client = new OAuthClient(token);
1279
+ const source = new OAuthSource(client);
1280
+ const snapshot = await source.fetchQuota();
1281
+ snapshots.push(snapshot);
1282
+ } catch (err) {
1283
+ debug("quota", `Failed to fetch for ${token.email}: ${err}`);
1284
+ }
1285
+ }
1286
+ return snapshots;
1287
+ }
1288
+ /**
1289
+ * Get the full dashboard data for all accounts
1290
+ */
1291
+ async getFullDashboard() {
1292
+ const snapshots = await this.fetchAllAccounts();
1293
+ const history = {};
1294
+ const dailySummary = {};
1295
+ for (const snapshot of snapshots) {
1296
+ if (!snapshot.email) continue;
1297
+ history[snapshot.email] = getHistory(snapshot.email, { days: 1, limit: 50 });
1298
+ dailySummary[snapshot.email] = getDailySummary(snapshot.email, 7);
1299
+ }
1300
+ return { snapshots, history, dailySummary };
1301
+ }
1302
+ /**
1303
+ * Get usage history for an account
1304
+ */
1305
+ getHistory(email, days) {
1306
+ return getHistory(email, { days });
1307
+ }
1308
+ /**
1309
+ * Get daily summary for an account
1310
+ */
1311
+ getDailySummary(email, days) {
1312
+ return getDailySummary(email, days);
1313
+ }
1314
+ /**
1315
+ * Register OAuth sources for all stored accounts
1316
+ */
1317
+ registerOAuthSources() {
1318
+ const tokens = getAllAccountTokens();
1319
+ for (const token of tokens) {
1320
+ if (!token.accessToken) continue;
1321
+ try {
1322
+ const client = new OAuthClient(token);
1323
+ const source = new OAuthSource(client);
1324
+ this.registry.addSource(source);
1325
+ debug("quota", `Registered OAuth source for ${token.email}`);
1326
+ } catch (err) {
1327
+ debug("quota", `Failed to register OAuth for ${token.email}: ${err}`);
1328
+ }
1329
+ }
1330
+ }
1331
+ /**
1332
+ * Get the source registry (for doctor/status checks)
1333
+ */
1334
+ getRegistry() {
1335
+ return this.registry;
1336
+ }
1337
+ };
1338
+
1339
+ // src/cli/render/quota-table.ts
1340
+ import chalk from "chalk";
1341
+ var BAR_WIDTH = 30;
1342
+ function renderQuotaCompact(snapshot) {
1343
+ const lines = [];
1344
+ const email = snapshot.email || "unknown";
1345
+ const method = snapshot.method === "local" ? "\u{1F7E2}" : snapshot.method === "oauth" ? "\u{1F535}" : "\u{1F7E1}";
1346
+ lines.push(`${method} ${chalk.bold(email)}`);
1347
+ lines.push("");
1348
+ if (snapshot.groups.length === 0) {
1349
+ lines.push(chalk.dim(" No quota data available"));
1350
+ return lines.join("\n");
1351
+ }
1352
+ for (const group of snapshot.groups) {
1353
+ lines.push(chalk.bold(` ${group.displayName}`));
1354
+ for (const bucket of group.buckets) {
1355
+ lines.push(renderBucketLine(bucket));
1356
+ }
1357
+ lines.push("");
1358
+ }
1359
+ return lines.join("\n").trimEnd();
1360
+ }
1361
+ function renderBucketLine(bucket) {
1362
+ const pct = Math.round(bucket.remainingFraction * 100);
1363
+ const bar = renderProgressBar(bucket.remainingFraction);
1364
+ const resetText = bucket.timeUntilResetMs ? chalk.dim(`resets ${formatCountdown(bucket.timeUntilResetMs)}`) : "";
1365
+ return ` ${bar} ${colorPct(pct)} ${resetText}`;
1366
+ }
1367
+ function renderProgressBar(fraction, width = BAR_WIDTH) {
1368
+ const filled = Math.round(fraction * width);
1369
+ const empty = width - filled;
1370
+ const barChar = "\u2588";
1371
+ const emptyChar = "\u2591";
1372
+ const color = getQuotaColor(fraction);
1373
+ const filledStr = color(Array(filled).fill(barChar).join(""));
1374
+ const emptyStr = chalk.dim(Array(empty).fill(emptyChar).join(""));
1375
+ return `[${filledStr}${emptyStr}]`;
1376
+ }
1377
+ function colorPct(pct) {
1378
+ if (pct >= 70) return chalk.green(`${pct}%`);
1379
+ if (pct >= 30) return chalk.yellow(`${pct}%`);
1380
+ return chalk.red.bold(`${pct}%`);
1381
+ }
1382
+ function getQuotaColor(fraction) {
1383
+ if (fraction >= 0.7) return chalk.green;
1384
+ if (fraction >= 0.3) return chalk.yellow;
1385
+ return chalk.red;
1386
+ }
1387
+
1388
+ // src/cli/render/compare-table.ts
1389
+ import Table from "cli-table3";
1390
+ import chalk2 from "chalk";
1391
+ function renderComparisonTable(snapshots) {
1392
+ if (snapshots.length === 0) {
1393
+ return chalk2.dim(" No accounts to compare");
1394
+ }
1395
+ const table = new Table({
1396
+ head: ["Account", "Gemini 5h", "Gemini Week", "Claude 5h", "Claude Week"],
1397
+ style: { head: ["cyan", "cyan", "cyan", "cyan", "cyan"] },
1398
+ colWidths: [25, 16, 16, 16, 16]
1399
+ });
1400
+ for (const snapshot of snapshots) {
1401
+ const email = snapshot.email || "unknown";
1402
+ const vals = extractValues(snapshot);
1403
+ table.push([
1404
+ chalk2.bold(email),
1405
+ formatCell(vals.geminiSession, vals.geminiSessionReset),
1406
+ formatCell(vals.geminiWeekly, vals.geminiWeeklyReset),
1407
+ formatCell(vals.claudeSession, vals.claudeSessionReset),
1408
+ formatCell(vals.claudeWeekly, vals.claudeWeeklyReset)
1409
+ ]);
1410
+ }
1411
+ return table.toString();
1412
+ }
1413
+ function extractValues(snapshot) {
1414
+ let geminiSession = null;
1415
+ let geminiSessionReset;
1416
+ let geminiWeekly = null;
1417
+ let geminiWeeklyReset;
1418
+ let claudeSession = null;
1419
+ let claudeSessionReset;
1420
+ let claudeWeekly = null;
1421
+ let claudeWeeklyReset;
1422
+ for (const group of snapshot.groups) {
1423
+ const lower = group.displayName.toLowerCase();
1424
+ for (const bucket of group.buckets) {
1425
+ const bLower = bucket.displayName.toLowerCase();
1426
+ const isWeekly = bLower.includes("weekly") || bLower.includes("week");
1427
+ if (lower.includes("gemini")) {
1428
+ if (isWeekly) {
1429
+ geminiWeekly = bucket.remainingFraction;
1430
+ geminiWeeklyReset = bucket.timeUntilResetMs;
1431
+ } else {
1432
+ geminiSession = bucket.remainingFraction;
1433
+ geminiSessionReset = bucket.timeUntilResetMs;
1434
+ }
1435
+ } else if (lower.includes("claude") || lower.includes("gpt")) {
1436
+ if (isWeekly) {
1437
+ claudeWeekly = bucket.remainingFraction;
1438
+ claudeWeeklyReset = bucket.timeUntilResetMs;
1439
+ } else {
1440
+ claudeSession = bucket.remainingFraction;
1441
+ claudeSessionReset = bucket.timeUntilResetMs;
1442
+ }
1443
+ }
1444
+ }
1445
+ }
1446
+ return {
1447
+ geminiSession,
1448
+ geminiSessionReset,
1449
+ geminiWeekly,
1450
+ geminiWeeklyReset,
1451
+ claudeSession,
1452
+ claudeSessionReset,
1453
+ claudeWeekly,
1454
+ claudeWeeklyReset
1455
+ };
1456
+ }
1457
+ function formatCell(fraction, resetMs) {
1458
+ if (fraction === null) return chalk2.dim("\u2014");
1459
+ const pct = Math.round(fraction * 100);
1460
+ const reset = resetMs ? chalk2.dim(` ${formatCountdown(resetMs)}`) : "";
1461
+ if (pct >= 70) return chalk2.green(`${pct}%`) + reset;
1462
+ if (pct >= 30) return chalk2.yellow(`${pct}%`) + reset;
1463
+ return chalk2.red.bold(`${pct}%`) + reset;
1464
+ }
1465
+
1466
+ // src/cli/commands/quota.ts
1467
+ function registerQuotaCommand(program2) {
1468
+ program2.command("quota").description("Display current quota (default command)").option("-a, --all", "Fetch all accounts side-by-side").option("-r, --refresh", "Force fresh fetch (bypass cache)").option("-m, --method <method>", "Force data source: local, cli, oauth").option("--json", "Output JSON").action(async (opts) => {
1469
+ const spinner = ora("Fetching quota...").start();
1470
+ try {
1471
+ const client = new QuotaClient({ verbose: opts.verbose });
1472
+ if (opts.all) {
1473
+ const snapshots = await client.fetchQuota({ allAccounts: true, source: opts.method, refresh: opts.refresh });
1474
+ spinner.stop();
1475
+ if (opts.json) {
1476
+ console.log(JSON.stringify(snapshots, null, 2));
1477
+ } else {
1478
+ console.log(renderComparisonTable(Array.isArray(snapshots) ? snapshots : [snapshots]));
1479
+ }
1480
+ } else {
1481
+ const snapshot = await client.fetchQuota({ source: opts.method, refresh: opts.refresh });
1482
+ spinner.stop();
1483
+ if (opts.json) {
1484
+ console.log(JSON.stringify(snapshot, null, 2));
1485
+ } else {
1486
+ console.log(renderQuotaCompact(snapshot));
1487
+ }
1488
+ }
1489
+ } catch (err) {
1490
+ spinner.fail(chalk3.red(`Failed to fetch quota: ${err.message}`));
1491
+ process.exit(1);
1492
+ }
1493
+ });
1494
+ }
1495
+
1496
+ // src/cli/commands/full.ts
1497
+ import chalk5 from "chalk";
1498
+ import ora2 from "ora";
1499
+
1500
+ // src/cli/render/full-dashboard.ts
1501
+ import chalk4 from "chalk";
1502
+ import boxen from "boxen";
1503
+ var SPARKLINE_WIDTH = 24;
1504
+ function renderFullDashboard(snapshots, history) {
1505
+ const now = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1506
+ const parts = [];
1507
+ parts.push(chalk4.bold.cyan(` when-does-my-quota-refresh \u2014 Full Dashboard`) + chalk4.dim(` ${now}`));
1508
+ parts.push("");
1509
+ if (snapshots.length === 0) {
1510
+ parts.push(chalk4.dim(" No accounts configured. Run `aq accounts add` to get started."));
1511
+ return boxen(parts.join("\n"), {
1512
+ padding: 1,
1513
+ margin: 0,
1514
+ borderStyle: "round",
1515
+ title: "aq",
1516
+ titleAlignment: "center"
1517
+ });
1518
+ }
1519
+ for (let i = 0; i < snapshots.length; i++) {
1520
+ const snapshot = snapshots[i];
1521
+ parts.push(renderAccountSection(snapshot, history[snapshot.email || ""]));
1522
+ if (i < snapshots.length - 1) {
1523
+ parts.push(chalk4.dim(" \u2500".repeat(24)));
1524
+ }
1525
+ }
1526
+ parts.push("");
1527
+ parts.push(chalk4.dim(` Source: ${snapshots[0]?.method || "unknown"}`));
1528
+ return boxen(parts.join("\n"), {
1529
+ padding: { top: 1, bottom: 1, left: 2, right: 2 },
1530
+ margin: 0,
1531
+ borderStyle: "round",
1532
+ title: "aq",
1533
+ titleAlignment: "center"
1534
+ });
1535
+ }
1536
+ function renderAccountSection(snapshot, accountHistory) {
1537
+ const lines = [];
1538
+ const email = snapshot.email || "unknown";
1539
+ const method = snapshot.method === "local" ? chalk4.green("\u25CF") : snapshot.method === "oauth" ? chalk4.blue("\u25CF") : chalk4.yellow("\u25CF");
1540
+ lines.push(`${method} ${chalk4.bold(email)}`);
1541
+ if (snapshot.groups.length === 0) {
1542
+ lines.push(chalk4.dim(" No quota data available"));
1543
+ return lines.join("\n");
1544
+ }
1545
+ for (const group of snapshot.groups) {
1546
+ lines.push("");
1547
+ lines.push(chalk4.underline(` ${group.displayName}`));
1548
+ for (const bucket of group.buckets) {
1549
+ const pct = Math.round(bucket.remainingFraction * 100);
1550
+ const bar = renderProgressBar(bucket.remainingFraction, 28);
1551
+ const resetText = bucket.timeUntilResetMs ? chalk4.dim(`resets ${formatCountdown(bucket.timeUntilResetMs)}`) : "";
1552
+ const colorFn = getQuotaColor(bucket.remainingFraction);
1553
+ const label = bucket.displayName.length > 12 ? bucket.displayName.slice(0, 12) : bucket.displayName.padEnd(12);
1554
+ lines.push(` ${chalk4.dim(label)} ${bar} ${colorFn(pct.toString().padStart(3))}% ${resetText}`);
1555
+ }
1556
+ }
1557
+ if (accountHistory && accountHistory.length > 0) {
1558
+ lines.push("");
1559
+ lines.push(` ${chalk4.dim("\u{1F4C8} Recent")} ${renderSparkline(accountHistory)}`);
1560
+ }
1561
+ return lines.join("\n");
1562
+ }
1563
+ function renderSparkline(records) {
1564
+ const values = records.slice(-SPARKLINE_WIDTH).map((r) => {
1565
+ const vals = [r.geminiSessionRemaining, r.claudeSessionRemaining].filter((v) => v !== null);
1566
+ return vals.length > 0 ? Math.max(...vals) : 0;
1567
+ });
1568
+ if (values.length === 0) return chalk4.dim("no data");
1569
+ const blocks = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
1570
+ return values.map((v) => {
1571
+ const idx = Math.min(Math.round(v * (blocks.length - 1)), blocks.length - 1);
1572
+ const colorFn = getQuotaColor(v);
1573
+ return colorFn(blocks[idx]);
1574
+ }).join("");
1575
+ }
1576
+ function renderFullDashboardJson(snapshots, history) {
1577
+ return JSON.stringify({
1578
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1579
+ accounts: snapshots.map((s) => ({
1580
+ email: s.email,
1581
+ method: s.method,
1582
+ groups: s.groups,
1583
+ promptCredits: s.promptCredits,
1584
+ recentHistory: history[s.email || ""]?.slice(0, 10) || []
1585
+ }))
1586
+ }, null, 2);
1587
+ }
1588
+
1589
+ // src/cli/commands/full.ts
1590
+ async function executeFullDashboard(opts) {
1591
+ const spinner = ora2("Loading full dashboard...").start();
1592
+ try {
1593
+ const client = new QuotaClient({ verbose: opts.verbose });
1594
+ if (opts.watch) {
1595
+ spinner.stop();
1596
+ await runWatchMode(client, opts);
1597
+ return;
1598
+ }
1599
+ const { snapshots, history } = await client.getFullDashboard();
1600
+ spinner.stop();
1601
+ if (opts.json) {
1602
+ console.log(renderFullDashboardJson(snapshots, opts.noHistory ? {} : history));
1603
+ } else {
1604
+ console.log(renderFullDashboard(snapshots, opts.noHistory ? {} : history));
1605
+ }
1606
+ } catch (err) {
1607
+ spinner.fail(chalk5.red(`Failed: ${err.message}`));
1608
+ process.exit(1);
1609
+ }
1610
+ }
1611
+ async function runWatchMode(client, opts) {
1612
+ const REFRESH_MS = 3e4;
1613
+ const refresh = async () => {
1614
+ try {
1615
+ const { snapshots, history } = await client.getFullDashboard();
1616
+ process.stdout.write("\x1B[2J\x1B[0f");
1617
+ if (opts.json) {
1618
+ console.log(renderFullDashboardJson(snapshots, opts.noHistory ? {} : history));
1619
+ } else {
1620
+ console.log(renderFullDashboard(snapshots, opts.noHistory ? {} : history));
1621
+ }
1622
+ console.log(chalk5.dim(`
1623
+ Auto-refreshing every ${REFRESH_MS / 1e3}s \u2014 Press Ctrl+C to exit`));
1624
+ } catch (err) {
1625
+ console.error(chalk5.red(`Refresh error: ${err.message}`));
1626
+ }
1627
+ };
1628
+ await refresh();
1629
+ setInterval(refresh, REFRESH_MS);
1630
+ await new Promise(() => {
1631
+ });
1632
+ }
1633
+
1634
+ // src/cli/commands/accounts.ts
1635
+ import inquirer from "inquirer";
1636
+ import chalk6 from "chalk";
1637
+ import Table2 from "cli-table3";
1638
+ function registerAccountsCommand(program2) {
1639
+ const accounts = program2.command("accounts").description("Manage Google accounts");
1640
+ accounts.command("list").description("List all managed accounts").action(() => {
1641
+ const accounts2 = listAccounts();
1642
+ if (accounts2.length === 0) {
1643
+ console.log(chalk6.dim(" No accounts configured. Run `aq accounts add` to get started."));
1644
+ return;
1645
+ }
1646
+ const table = new Table2({
1647
+ head: ["Email", "Status", "Added"],
1648
+ style: { head: ["cyan"] }
1649
+ });
1650
+ for (const acct of accounts2) {
1651
+ table.push([
1652
+ acct.email,
1653
+ acct.isActive ? chalk6.green("\u25CF Active") : chalk6.dim("\u25CB Inactive"),
1654
+ new Date(acct.addedAt).toLocaleDateString()
1655
+ ]);
1656
+ }
1657
+ console.log(table.toString());
1658
+ });
1659
+ accounts.command("add").description("Login a new Google account").option("--manual", "Manual login for headless/SSH (get URL instead of browser)").action(async (opts) => {
1660
+ try {
1661
+ if (opts.manual) {
1662
+ const { getManualLoginUrl: getManualLoginUrl2, completeManualLogin: completeManualLogin2 } = await import("../auth-S43EEJYA.js");
1663
+ const url = getManualLoginUrl2();
1664
+ console.log(chalk6.bold("\nOpen this URL in your browser:\n"));
1665
+ console.log(chalk6.cyan(url));
1666
+ console.log(chalk6.dim("\nPaste the authorization code here:"));
1667
+ const { code } = await inquirer.prompt([{
1668
+ type: "input",
1669
+ name: "code",
1670
+ message: "Authorization code:"
1671
+ }]);
1672
+ const email = await completeManualLogin2(code);
1673
+ console.log(chalk6.green(`
1674
+ \u2713 Logged in as ${email}`));
1675
+ } else {
1676
+ const email = await loginAccount();
1677
+ console.log(chalk6.green(`
1678
+ \u2713 Logged in as ${email}`));
1679
+ }
1680
+ } catch (err) {
1681
+ console.error(chalk6.red(`
1682
+ \u2717 Login failed: ${err.message}`));
1683
+ process.exit(1);
1684
+ }
1685
+ });
1686
+ accounts.command("switch <email>").description("Set the active account").action((email) => {
1687
+ if (setActiveAccount(email)) {
1688
+ console.log(chalk6.green(`\u2713 Switched to ${email}`));
1689
+ } else {
1690
+ console.error(chalk6.red(`\u2717 Account "${email}" not found`));
1691
+ process.exit(1);
1692
+ }
1693
+ });
1694
+ accounts.command("remove <email>").description("Remove an account").action(async (email) => {
1695
+ const { confirm } = await inquirer.prompt([{
1696
+ type: "confirm",
1697
+ name: "confirm",
1698
+ message: `Remove account ${email}?`,
1699
+ default: false
1700
+ }]);
1701
+ if (confirm) {
1702
+ if (removeAccount(email)) {
1703
+ console.log(chalk6.green(`\u2713 Removed ${email}`));
1704
+ } else {
1705
+ console.error(chalk6.red(`\u2717 Account "${email}" not found`));
1706
+ }
1707
+ }
1708
+ });
1709
+ }
1710
+
1711
+ // src/cli/commands/history.ts
1712
+ import chalk8 from "chalk";
1713
+
1714
+ // src/cli/render/history-chart.ts
1715
+ import chalk7 from "chalk";
1716
+ var CHART_HEIGHT = 10;
1717
+ var CHART_WIDTH = 40;
1718
+ function renderHistoryChart(records, label = "Gemini Session") {
1719
+ if (records.length === 0) {
1720
+ return chalk7.dim(" No history data available");
1721
+ }
1722
+ const sorted = [...records].sort(
1723
+ (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
1724
+ );
1725
+ const values = sorted.map((r) => {
1726
+ if (label.toLowerCase().includes("gemini")) {
1727
+ if (label.toLowerCase().includes("weekly")) return r.geminiWeeklyRemaining;
1728
+ return r.geminiSessionRemaining;
1729
+ } else {
1730
+ if (label.toLowerCase().includes("weekly")) return r.claudeWeeklyRemaining;
1731
+ return r.claudeSessionRemaining;
1732
+ }
1733
+ }).filter((v) => v !== null);
1734
+ if (values.length === 0) {
1735
+ return chalk7.dim(` No ${label} data available`);
1736
+ }
1737
+ const lines = [];
1738
+ lines.push(chalk7.bold(` \u{1F4CA} ${label} \u2014 Last ${values.length} readings`));
1739
+ lines.push("");
1740
+ const sampled = downsample(values, CHART_WIDTH);
1741
+ for (let row = CHART_HEIGHT; row >= 0; row--) {
1742
+ const threshold = row / CHART_HEIGHT;
1743
+ const isAxis = row === 0;
1744
+ let line = isAxis ? chalk7.dim(" 0% ") : chalk7.dim(" ");
1745
+ for (const val of sampled) {
1746
+ if (val >= threshold) {
1747
+ line += getQuotaColor(val)("\u2588");
1748
+ } else {
1749
+ line += chalk7.dim("\xB7");
1750
+ }
1751
+ }
1752
+ if (row === CHART_HEIGHT) line += chalk7.dim(" 100%");
1753
+ lines.push(line);
1754
+ }
1755
+ if (sorted.length >= 2) {
1756
+ const first = new Date(sorted[0].timestamp);
1757
+ const last = new Date(sorted[sorted.length - 1].timestamp);
1758
+ lines.push(chalk7.dim(
1759
+ ` ${formatTime(first)}${" ".repeat(Math.max(0, CHART_WIDTH - 12))}${formatTime(last)}`
1760
+ ));
1761
+ }
1762
+ return lines.join("\n");
1763
+ }
1764
+ function renderComparisonChart(recordsByEmail, metric) {
1765
+ const lines = [];
1766
+ const emails = Object.keys(recordsByEmail);
1767
+ lines.push(chalk7.bold(` \u{1F4CA} Cross-Account Comparison \u2014 ${metricLabel(metric)}`));
1768
+ lines.push("");
1769
+ const maxEmailLen = Math.max(...emails.map((e) => e.length), 8);
1770
+ for (const email of emails) {
1771
+ const records = recordsByEmail[email];
1772
+ const latest = records[0];
1773
+ if (!latest) continue;
1774
+ let value = null;
1775
+ switch (metric) {
1776
+ case "geminiSession":
1777
+ value = latest.geminiSessionRemaining;
1778
+ break;
1779
+ case "claudeSession":
1780
+ value = latest.claudeSessionRemaining;
1781
+ break;
1782
+ case "geminiWeekly":
1783
+ value = latest.geminiWeeklyRemaining;
1784
+ break;
1785
+ case "claudeWeekly":
1786
+ value = latest.claudeWeeklyRemaining;
1787
+ break;
1788
+ }
1789
+ const label = email.slice(0, maxEmailLen).padEnd(maxEmailLen);
1790
+ const pct = value !== null ? Math.round(value * 100) : -1;
1791
+ const bar = value !== null ? renderMiniBar(value, 20) : chalk7.dim("\u2591".repeat(20));
1792
+ const pctStr = pct >= 0 ? `${pct}%` : "N/A";
1793
+ lines.push(` ${chalk7.dim(label)} ${bar} ${pctStr}`);
1794
+ }
1795
+ return lines.join("\n");
1796
+ }
1797
+ function renderMiniBar(fraction, width) {
1798
+ const filled = Math.round(fraction * width);
1799
+ const colorFn = getQuotaColor(fraction);
1800
+ return colorFn("\u2588".repeat(filled)) + chalk7.dim("\u2591".repeat(width - filled));
1801
+ }
1802
+ function downsample(values, targetWidth) {
1803
+ if (values.length <= targetWidth) return values;
1804
+ const result = [];
1805
+ const step = values.length / targetWidth;
1806
+ for (let i = 0; i < targetWidth; i++) {
1807
+ const idx = Math.round(i * step);
1808
+ result.push(values[Math.min(idx, values.length - 1)]);
1809
+ }
1810
+ return result;
1811
+ }
1812
+ function formatTime(date) {
1813
+ return `${date.getHours().toString().padStart(2, "0")}:${date.getMinutes().toString().padStart(2, "0")}`;
1814
+ }
1815
+ function metricLabel(metric) {
1816
+ switch (metric) {
1817
+ case "geminiSession":
1818
+ return "Gemini 5-Hour";
1819
+ case "claudeSession":
1820
+ return "Claude + GPT 5-Hour";
1821
+ case "geminiWeekly":
1822
+ return "Gemini Weekly";
1823
+ case "claudeWeekly":
1824
+ return "Claude + GPT Weekly";
1825
+ default:
1826
+ return metric;
1827
+ }
1828
+ }
1829
+
1830
+ // src/cli/commands/history.ts
1831
+ function registerHistoryCommand(program2) {
1832
+ program2.command("history").description("Show usage history and trends").option("-e, --email <email>", "Account email (default: active account)").option("-d, --days <days>", "Number of days to show", "7").option("--group <group>", "Filter: gemini, claude, all", "all").option("--json", "Output JSON").option("--compare", "Compare all accounts").action(async (opts) => {
1833
+ try {
1834
+ const client = new QuotaClient({ verbose: opts.verbose });
1835
+ const email = opts.email || getActiveEmail();
1836
+ if (!email) {
1837
+ console.log(chalk8.dim(" No active account. Run `aq accounts add` or use --email."));
1838
+ return;
1839
+ }
1840
+ const days = parseInt(opts.days, 10);
1841
+ const history = client.getHistory(email, days);
1842
+ const summary = client.getDailySummary(email, days);
1843
+ if (opts.json) {
1844
+ console.log(JSON.stringify({ email, days, history, summary }, null, 2));
1845
+ return;
1846
+ }
1847
+ if (opts.compare) {
1848
+ const allAccounts = listAccounts();
1849
+ const allHistory = {};
1850
+ for (const acct of allAccounts) {
1851
+ allHistory[acct.email] = client.getHistory(acct.email, days);
1852
+ }
1853
+ console.log(renderComparisonChart(allHistory, "geminiSession"));
1854
+ console.log("");
1855
+ console.log(renderComparisonChart(allHistory, "claudeSession"));
1856
+ return;
1857
+ }
1858
+ console.log(chalk8.bold(`
1859
+ \u{1F4CA} Usage History \u2014 ${email} (last ${days} days)
1860
+ `));
1861
+ if (opts.group === "all" || opts.group === "gemini") {
1862
+ console.log(renderHistoryChart(history, "Gemini Session (5-hour)"));
1863
+ console.log("");
1864
+ console.log(renderHistoryChart(history, "Gemini Weekly"));
1865
+ console.log("");
1866
+ }
1867
+ if (opts.group === "all" || opts.group === "claude") {
1868
+ console.log(renderHistoryChart(history, "Claude + GPT Session (5-hour)"));
1869
+ console.log("");
1870
+ console.log(renderHistoryChart(history, "Claude + GPT Weekly"));
1871
+ console.log("");
1872
+ }
1873
+ if (summary.length > 0) {
1874
+ console.log(chalk8.bold(" \u{1F4C5} Daily Summary"));
1875
+ for (const day of summary) {
1876
+ const gemini = day.geminiSessionAvg !== null ? `${Math.round(day.geminiSessionAvg * 100)}%` : "\u2014";
1877
+ const claude = day.claudeSessionAvg !== null ? `${Math.round(day.claudeSessionAvg * 100)}%` : "\u2014";
1878
+ console.log(chalk8.dim(` ${day.date} `) + `Gemini: ${gemini} Claude: ${claude} (${day.count} readings)`);
1879
+ }
1880
+ }
1881
+ } catch (err) {
1882
+ console.error(chalk8.red(`Error: ${err.message}`));
1883
+ process.exit(1);
1884
+ }
1885
+ });
1886
+ }
1887
+ function getActiveEmail() {
1888
+ const accounts = listAccounts();
1889
+ const active = accounts.find((a) => a.isActive);
1890
+ return active?.email || null;
1891
+ }
1892
+
1893
+ // src/cli/commands/doctor.ts
1894
+ import chalk9 from "chalk";
1895
+ function registerDoctorCommand(program2) {
1896
+ program2.command("doctor").description("Diagnose setup issues").action(async () => {
1897
+ console.log(chalk9.bold.cyan("\n when-does-my-quota-refresh \u2014 Doctor\n"));
1898
+ const checks = [];
1899
+ const platform = isMacOS() ? "macOS" : isLinux() ? "Linux" : isWindows() ? "Windows" : process.platform;
1900
+ checks.push({
1901
+ label: "Platform",
1902
+ status: "ok",
1903
+ detail: platform
1904
+ });
1905
+ checks.push({
1906
+ label: "Node.js",
1907
+ status: process.version >= "v18" ? "ok" : "warn",
1908
+ detail: process.version
1909
+ });
1910
+ try {
1911
+ const configDir = getConfigDirPath();
1912
+ checks.push({ label: "Config directory", status: "ok", detail: configDir });
1913
+ } catch (err) {
1914
+ checks.push({ label: "Config directory", status: "error", detail: err.message });
1915
+ }
1916
+ const config = loadConfig();
1917
+ checks.push({
1918
+ label: "Config loaded",
1919
+ status: "ok",
1920
+ detail: `mode=${config.defaultMode}, cache=${config.cacheTtlMs / 1e3}s`
1921
+ });
1922
+ const accounts = listAccounts();
1923
+ if (accounts.length > 0) {
1924
+ checks.push({
1925
+ label: "Accounts",
1926
+ status: "ok",
1927
+ detail: `${accounts.length} configured (${accounts.find((a) => a.isActive)?.email || "none active"})`
1928
+ });
1929
+ } else {
1930
+ checks.push({
1931
+ label: "Accounts",
1932
+ status: "warn",
1933
+ detail: "No accounts configured (local mode may still work)"
1934
+ });
1935
+ }
1936
+ const tokens = getActiveAccountTokens();
1937
+ if (tokens) {
1938
+ const expired = Date.now() >= (tokens.expiresAt || 0);
1939
+ checks.push({
1940
+ label: "Token status",
1941
+ status: expired ? "warn" : "ok",
1942
+ detail: expired ? "Access token expired (will refresh)" : "Valid"
1943
+ });
1944
+ }
1945
+ try {
1946
+ const processes = await detectAntigravityProcesses();
1947
+ if (processes.length > 0) {
1948
+ checks.push({
1949
+ label: "Antigravity process",
1950
+ status: "ok",
1951
+ detail: `Found ${processes.length} process(es): ${processes.map((p) => `PID ${p.pid} (${p.scope})`).join(", ")}`
1952
+ });
1953
+ } else {
1954
+ checks.push({
1955
+ label: "Antigravity process",
1956
+ status: "warn",
1957
+ detail: "No process found (try opening Antigravity in your IDE)"
1958
+ });
1959
+ }
1960
+ } catch (err) {
1961
+ checks.push({
1962
+ label: "Process detection",
1963
+ status: "error",
1964
+ detail: err.message
1965
+ });
1966
+ }
1967
+ try {
1968
+ const client = new QuotaClient();
1969
+ const snapshot = await client.fetchQuota();
1970
+ checks.push({
1971
+ label: "Quota fetch",
1972
+ status: "ok",
1973
+ detail: `Source: ${snapshot.method}, Email: ${snapshot.email || "unknown"}, Groups: ${snapshot.groups.length}`
1974
+ });
1975
+ } catch (err) {
1976
+ checks.push({
1977
+ label: "Quota fetch",
1978
+ status: "error",
1979
+ detail: err.message
1980
+ });
1981
+ }
1982
+ for (const check of checks) {
1983
+ const icon = check.status === "ok" ? chalk9.green("\u2713") : check.status === "warn" ? chalk9.yellow("\u26A0") : chalk9.red("\u2717");
1984
+ const label = chalk9.bold(check.label.padEnd(22));
1985
+ console.log(` ${icon} ${label} ${check.detail}`);
1986
+ }
1987
+ console.log("");
1988
+ const errors = checks.filter((c) => c.status === "error").length;
1989
+ const warnings = checks.filter((c) => c.status === "warn").length;
1990
+ if (errors === 0 && warnings === 0) {
1991
+ console.log(chalk9.green.bold(" \u2705 Everything looks good!"));
1992
+ } else {
1993
+ console.log(chalk9.yellow(` ${errors} error(s), ${warnings} warning(s)`));
1994
+ }
1995
+ console.log("");
1996
+ closeStore();
1997
+ });
1998
+ }
1999
+
2000
+ // src/cli/commands/config.ts
2001
+ import chalk10 from "chalk";
2002
+ function registerConfigCommand(program2) {
2003
+ const config = program2.command("config").description("Manage settings");
2004
+ config.command("show").description("Show current configuration").action(() => {
2005
+ const cfg = loadConfig();
2006
+ console.log(chalk10.bold("\n Current Configuration\n"));
2007
+ console.log(JSON.stringify(cfg, null, 2));
2008
+ console.log(chalk10.dim(`
2009
+ Config path: ${getConfigDirPath()}/config.json`));
2010
+ });
2011
+ config.command("set <key> <value>").description("Set a config value").action((key, value) => {
2012
+ const cfg = loadConfig();
2013
+ const keys = key.split(".");
2014
+ let target = cfg;
2015
+ for (let i = 0; i < keys.length - 1; i++) {
2016
+ if (!(keys[i] in target)) {
2017
+ target[keys[i]] = {};
2018
+ }
2019
+ target = target[keys[i]];
2020
+ }
2021
+ if (value === "true") value = true;
2022
+ else if (value === "false") value = false;
2023
+ else if (!isNaN(Number(value))) value = Number(value);
2024
+ target[keys[keys.length - 1]] = value;
2025
+ saveConfig(cfg);
2026
+ console.log(chalk10.green(`\u2713 Set ${key} = ${value}`));
2027
+ });
2028
+ config.command("get <key>").description("Get a config value").action((key) => {
2029
+ const cfg = loadConfig();
2030
+ const keys = key.split(".");
2031
+ let target = cfg;
2032
+ for (const k of keys) {
2033
+ if (target === void 0 || target === null) break;
2034
+ target = target[k];
2035
+ }
2036
+ console.log(target !== void 0 ? String(target) : chalk10.dim("not set"));
2037
+ });
2038
+ config.command("path").description("Show config directory path").action(() => {
2039
+ console.log(getConfigDirPath());
2040
+ });
2041
+ }
2042
+
2043
+ // src/cli/commands/wakeup.ts
2044
+ import chalk11 from "chalk";
2045
+ import { execSync } from "child_process";
2046
+ import { writeFileSync } from "fs";
2047
+ import { join as join2 } from "path";
2048
+ import { homedir } from "os";
2049
+ import inquirer2 from "inquirer";
2050
+ var CRON_MARKER = "# when-does-my-quota-refresh-wakeup";
2051
+ var TASK_NAME = "when-does-my-quota-refresh-wakeup";
2052
+ function registerWakeupCommand(program2) {
2053
+ const wakeup = program2.command("wakeup").description("Auto-trigger quota refresh to maximize daily limits");
2054
+ wakeup.command("config").description("Interactive wakeup setup").action(async () => {
2055
+ console.log(chalk11.bold.cyan("\n Wakeup Configuration\n"));
2056
+ const config = loadConfig();
2057
+ const answers = await inquirer2.prompt([
2058
+ {
2059
+ type: "list",
2060
+ name: "mode",
2061
+ message: "Trigger mode:",
2062
+ choices: [
2063
+ { name: "Smart detection \u2014 trigger when quota resets (Recommended)", value: "smart" },
2064
+ { name: "Interval \u2014 trigger every N hours", value: "interval" },
2065
+ { name: "Daily \u2014 trigger at specific times", value: "daily" }
2066
+ ]
2067
+ },
2068
+ {
2069
+ type: "number",
2070
+ name: "intervalHours",
2071
+ message: "Interval in hours:",
2072
+ default: 6,
2073
+ when: (a) => a.mode === "interval"
2074
+ },
2075
+ {
2076
+ type: "input",
2077
+ name: "dailyTimes",
2078
+ message: "Times to trigger (comma-separated, e.g. 9,17):",
2079
+ default: "9,17",
2080
+ when: (a) => a.mode === "daily"
2081
+ },
2082
+ {
2083
+ type: "confirm",
2084
+ name: "allAccounts",
2085
+ message: "Trigger for all accounts?",
2086
+ default: true
2087
+ }
2088
+ ]);
2089
+ config.wakeupAccounts = answers.allAccounts ? [] : [];
2090
+ saveConfig(config);
2091
+ console.log(chalk11.green("\n \u2713 Configuration saved"));
2092
+ console.log(chalk11.dim(` Run \`aq wakeup install\` to activate`));
2093
+ });
2094
+ wakeup.command("install").description("Install wakeup to system scheduler").action(async () => {
2095
+ try {
2096
+ if (isWindows()) {
2097
+ installWindowsTask();
2098
+ } else {
2099
+ installCrontab();
2100
+ }
2101
+ console.log(chalk11.green("\n \u2713 Wakeup installed to system scheduler"));
2102
+ } catch (err) {
2103
+ console.error(chalk11.red(`
2104
+ \u2717 Failed: ${err.message}`));
2105
+ }
2106
+ });
2107
+ wakeup.command("uninstall").description("Remove wakeup from system scheduler").action(() => {
2108
+ try {
2109
+ if (isWindows()) {
2110
+ uninstallWindowsTask();
2111
+ } else {
2112
+ uninstallCrontab();
2113
+ }
2114
+ console.log(chalk11.green("\n \u2713 Wakeup removed from system scheduler"));
2115
+ } catch (err) {
2116
+ console.error(chalk11.red(`
2117
+ \u2717 Failed: ${err.message}`));
2118
+ }
2119
+ });
2120
+ wakeup.command("status").description("Check wakeup status").action(() => {
2121
+ const config = loadConfig();
2122
+ console.log(chalk11.bold("\n Wakeup Status\n"));
2123
+ console.log(` Models: ${config.wakeupModels.join(", ")}`);
2124
+ console.log(` Accounts: ${config.wakeupAccounts.length > 0 ? config.wakeupAccounts.join(", ") : "all"}`);
2125
+ if (isWindows()) {
2126
+ const installed = checkWindowsTask();
2127
+ console.log(` Scheduler: ${installed ? chalk11.green("Installed") : chalk11.dim("Not installed")}`);
2128
+ } else {
2129
+ const installed = checkCrontab();
2130
+ console.log(` Cron: ${installed ? chalk11.green("Installed") : chalk11.dim("Not installed")}`);
2131
+ }
2132
+ });
2133
+ wakeup.command("test").description("Test wakeup trigger manually").option("-e, --email <email>", "Account email").option("-m, --model <model>", "Model to test").option("-p, --prompt <prompt>", "Test prompt", "hi").action(async (opts) => {
2134
+ console.log(chalk11.cyan("\n Testing wakeup trigger...\n"));
2135
+ try {
2136
+ const aqPath = process.argv[1];
2137
+ execSync(`"${aqPath}" quota --refresh`, { stdio: "inherit" });
2138
+ console.log(chalk11.green("\n \u2713 Wakeup test completed"));
2139
+ } catch (err) {
2140
+ console.error(chalk11.red(`
2141
+ \u2717 Test failed: ${err.message}`));
2142
+ }
2143
+ });
2144
+ }
2145
+ function installCrontab() {
2146
+ const aqPath = process.argv[1];
2147
+ const config = loadConfig();
2148
+ uninstallCrontab();
2149
+ const intervalHours = 6;
2150
+ const cronLine = `0 */${intervalHours} * * * "${aqPath}" quota --refresh --json > /dev/null 2>&1 ${CRON_MARKER}`;
2151
+ try {
2152
+ const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" }).trim();
2153
+ const newCron = existing ? `${existing}
2154
+ ${cronLine}` : cronLine;
2155
+ execSync(`echo '${newCron}' | crontab -`, { encoding: "utf-8" });
2156
+ } catch {
2157
+ execSync(`echo '${cronLine}' | crontab -`, { encoding: "utf-8" });
2158
+ }
2159
+ }
2160
+ function uninstallCrontab() {
2161
+ try {
2162
+ const existing = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
2163
+ const filtered = existing.split("\n").filter((l) => !l.includes(CRON_MARKER)).join("\n").trim();
2164
+ execSync(`echo '${filtered}' | crontab -`, { encoding: "utf-8" });
2165
+ } catch {
2166
+ }
2167
+ }
2168
+ function checkCrontab() {
2169
+ try {
2170
+ const output = execSync("crontab -l 2>/dev/null", { encoding: "utf-8" });
2171
+ return output.includes(CRON_MARKER);
2172
+ } catch {
2173
+ return false;
2174
+ }
2175
+ }
2176
+ function installWindowsTask() {
2177
+ const aqPath = process.argv[1];
2178
+ const xmlPath = join2(homedir(), "when-does-my-quota-refresh-wakeup.xml");
2179
+ uninstallWindowsTask();
2180
+ const xml = `<?xml version="1.0" encoding="UTF-16"?>
2181
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
2182
+ <Triggers>
2183
+ <TimeTrigger>
2184
+ <Repetition>
2185
+ <Interval>PT6H</Interval>
2186
+ </Repetition>
2187
+ <StartBoundary>2024-01-01T09:00:00</StartBoundary>
2188
+ </TimeTrigger>
2189
+ </Triggers>
2190
+ <Actions>
2191
+ <Exec>
2192
+ <Command>node</Command>
2193
+ <Arguments>"${aqPath}" quota --refresh</Arguments>
2194
+ </Exec>
2195
+ </Actions>
2196
+ <Settings>
2197
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
2198
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
2199
+ </Settings>
2200
+ </Task>`;
2201
+ writeFileSync(xmlPath, xml);
2202
+ execSync(`schtasks /create /tn "${TASK_NAME}" /xml "${xmlPath}" /f`, { encoding: "utf-8" });
2203
+ }
2204
+ function uninstallWindowsTask() {
2205
+ try {
2206
+ execSync(`schtasks /delete /tn "${TASK_NAME}" /f`, { encoding: "utf-8", stdio: "ignore" });
2207
+ } catch {
2208
+ }
2209
+ }
2210
+ function checkWindowsTask() {
2211
+ try {
2212
+ execSync(`schtasks /query /tn "${TASK_NAME}"`, { encoding: "utf-8", stdio: "ignore" });
2213
+ return true;
2214
+ } catch {
2215
+ return false;
2216
+ }
2217
+ }
2218
+
2219
+ // src/cli/commands/watch.ts
2220
+ import chalk12 from "chalk";
2221
+ function registerWatchCommand(program2) {
2222
+ const watch = program2.command("watch").description("Real-time quota monitoring daemon");
2223
+ watch.command("start").description("Start watching in the foreground").option("--interval <seconds>", "Poll interval in seconds", "60").action(async (opts) => {
2224
+ const client = new QuotaClient({ verbose: opts.verbose });
2225
+ const interval = parseInt(opts.interval, 10) * 1e3;
2226
+ console.log(chalk12.bold.cyan(`
2227
+ \u{1F504} Starting watch mode (every ${interval / 1e3}s)
2228
+ `));
2229
+ const refresh = async () => {
2230
+ try {
2231
+ const { snapshots, history } = await client.getFullDashboard();
2232
+ process.stdout.write("\x1B[2J\x1B[0f");
2233
+ console.log(renderFullDashboard(snapshots, history));
2234
+ console.log(chalk12.dim(`
2235
+ Polling every ${interval / 1e3}s \u2014 Ctrl+C to exit`));
2236
+ } catch (err) {
2237
+ console.error(chalk12.red(` Error: ${err.message}`));
2238
+ }
2239
+ };
2240
+ await refresh();
2241
+ setInterval(refresh, interval);
2242
+ await new Promise(() => {
2243
+ });
2244
+ });
2245
+ watch.command("status").description("Check daemon status").action(() => {
2246
+ console.log(chalk12.bold("\n Watch Status\n"));
2247
+ console.log(chalk12.dim(" Use `aq watch start` to start monitoring in foreground"));
2248
+ });
2249
+ }
2250
+
2251
+ // src/cli/commands/plugin.ts
2252
+ import chalk13 from "chalk";
2253
+ function registerPluginCommand(program2) {
2254
+ const plugin = program2.command("plugin").description("Manage plugins");
2255
+ plugin.command("list").description("List installed plugins").action(() => {
2256
+ const config = loadConfig();
2257
+ if (config.plugins.length === 0) {
2258
+ console.log(chalk13.dim(" No plugins installed"));
2259
+ return;
2260
+ }
2261
+ console.log(chalk13.bold("\n Installed Plugins\n"));
2262
+ for (const p of config.plugins) {
2263
+ console.log(` \u{1F4E6} ${p}`);
2264
+ }
2265
+ });
2266
+ plugin.command("install <name>").description("Install a plugin").action((name) => {
2267
+ const config = loadConfig();
2268
+ if (config.plugins.includes(name)) {
2269
+ console.log(chalk13.yellow(` Plugin "${name}" already installed`));
2270
+ return;
2271
+ }
2272
+ config.plugins.push(name);
2273
+ saveConfig(config);
2274
+ console.log(chalk13.green(`\u2713 Plugin "${name}" installed`));
2275
+ });
2276
+ plugin.command("remove <name>").description("Remove a plugin").action((name) => {
2277
+ const config = loadConfig();
2278
+ const idx = config.plugins.indexOf(name);
2279
+ if (idx < 0) {
2280
+ console.log(chalk13.red(` Plugin "${name}" not found`));
2281
+ return;
2282
+ }
2283
+ config.plugins.splice(idx, 1);
2284
+ saveConfig(config);
2285
+ console.log(chalk13.green(`\u2713 Plugin "${name}" removed`));
2286
+ });
2287
+ }
2288
+
2289
+ // src/cli/index.ts
2290
+ var VERSION = "1.0.0";
2291
+ try {
2292
+ const __dirname = dirname(fileURLToPath(import.meta.url));
2293
+ const pkg = JSON.parse(readFileSync2(join3(__dirname, "..", "..", "package.json"), "utf-8"));
2294
+ VERSION = pkg.version;
2295
+ } catch {
2296
+ }
2297
+ var program = new Command();
2298
+ program.name("aq").description("Cross-platform CLI for tracking Antigravity AI model quotas").version(VERSION).option("--verbose", "Enable debug output");
2299
+ registerQuotaCommand(program);
2300
+ registerAccountsCommand(program);
2301
+ registerHistoryCommand(program);
2302
+ registerDoctorCommand(program);
2303
+ registerConfigCommand(program);
2304
+ registerWakeupCommand(program);
2305
+ registerWatchCommand(program);
2306
+ registerPluginCommand(program);
2307
+ program.option("--full", "Show full dashboard with all accounts, groups, and sparklines");
2308
+ program.option("--json", "Output JSON (global)");
2309
+ program.option("--no-history", "Skip history sparkline in full dashboard");
2310
+ program.option("--watch", "Auto-refresh mode");
2311
+ var args = process.argv.slice(2);
2312
+ if (args.includes("--verbose")) {
2313
+ setVerbose(true);
2314
+ }
2315
+ var isFull = args.includes("--full");
2316
+ program.action(async (opts) => {
2317
+ if (isFull || opts.full) {
2318
+ await executeFullDashboard({
2319
+ json: opts.json,
2320
+ watch: opts.watch,
2321
+ noHistory: opts.noHistory,
2322
+ verbose: opts.verbose
2323
+ });
2324
+ } else {
2325
+ await program.parseAsync(["node", "aq", "quota", ...args.filter((a) => a !== "--full")]);
2326
+ }
2327
+ });
2328
+ program.parse();
2329
+ //# sourceMappingURL=index.js.map