fifony 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -3
- package/app/dist/apple-touch-icon.png +0 -0
- package/app/dist/assets/CommandPalette-Du7aS97R.js +1 -0
- package/app/dist/assets/KeyboardShortcutsHelp-mguoBIXk.js +1 -0
- package/app/dist/assets/OnboardingWizard-dOrVz-hX.js +1 -0
- package/app/dist/assets/analytics.lazy-CKd9S45z.js +1 -0
- package/app/dist/assets/api-ChEctgc5.js +1 -0
- package/app/dist/assets/{createLucideIcon-DgMTp0yx.js → createLucideIcon-CBw-4t9s.js} +1 -1
- package/app/dist/assets/index-nWJMQsCn.js +49 -0
- package/app/dist/assets/{index-lf3jEP_3.css → index-rLcPCr9E.css} +1 -1
- package/app/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
- package/app/dist/assets/vendor-DkWeBvNl.js +9 -0
- package/app/dist/favicon.png +0 -0
- package/app/dist/icon-128.png +0 -0
- package/app/dist/icon-144.png +0 -0
- package/app/dist/icon-152.png +0 -0
- package/app/dist/icon-16.png +0 -0
- package/app/dist/icon-192.png +0 -0
- package/app/dist/icon-32.png +0 -0
- package/app/dist/icon-384.png +0 -0
- package/app/dist/icon-48.png +0 -0
- package/app/dist/icon-512.png +0 -0
- package/app/dist/icon-72.png +0 -0
- package/app/dist/icon-96.png +0 -0
- package/app/dist/icon-maskable-192.png +0 -0
- package/app/dist/icon-maskable-512.png +0 -0
- package/app/dist/index.html +16 -8
- package/app/dist/manifest.webmanifest +19 -19
- package/app/dist/og-image.png +0 -0
- package/app/dist/service-worker.js +2 -2
- package/dist/agent/run-local.js +22 -15
- package/dist/{agent-OBLUHG2W.js → agent-7BMCWKOD.js} +5 -5
- package/dist/{chunk-CXFEPU5Q.js → chunk-DAXG76WD.js} +8 -17
- package/dist/{chunk-7AMUAUY5.js → chunk-GAYQ2TXV.js} +628 -81
- package/dist/{chunk-VUNMXX7N.js → chunk-LX666HVP.js} +11 -11
- package/dist/{chunk-IYAF3SY6.js → chunk-NNPKCBAB.js} +556 -17
- package/dist/cli.js +4 -4
- package/dist/{issue-runner-CI7IUBBD.js → issue-runner-BNAW3M6E.js} +5 -5
- package/dist/{issue-state-machine-ETAJLBS6.js → issue-state-machine-24GKGB3W.js} +3 -3
- package/dist/{issues-6VCD27PA.js → issues-C4WJIF7S.js} +5 -5
- package/dist/{queue-workers-IEI23UUO.js → queue-workers-HF7RBVV7.js} +2 -2
- package/dist/{scheduler-JSH55NAQ.js → scheduler-JZCJB2IN.js} +5 -5
- package/dist/{store-MTE6H7WQ.js → store-MHU3G2W4.js} +5 -5
- package/dist/{workspace-3PV5PTR5.js → workspace-IKR6X6OE.js} +2 -2
- package/package.json +9 -6
- package/app/dist/assets/KeyboardShortcutsHelp-3vwtTKsQ.js +0 -1
- package/app/dist/assets/OnboardingWizard-urUin_Ob.js +0 -1
- package/app/dist/assets/analytics.lazy-CbuW3ebu.js +0 -1
- package/app/dist/assets/index-ywtlX6S8.js +0 -45
- package/app/dist/assets/rolldown-runtime-DF2fYuay.js +0 -1
- package/app/dist/assets/vendor-D-IqxHHu.js +0 -9
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
snapshotAndClearDirtyEventIds,
|
|
22
22
|
snapshotAndClearDirtyIssueIds,
|
|
23
23
|
snapshotAndClearDirtyIssuePlanIds
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-DAXG76WD.js";
|
|
25
25
|
import {
|
|
26
26
|
ADAPTERS,
|
|
27
27
|
assertIssueHasGitWorktree,
|
|
@@ -31,6 +31,9 @@ import {
|
|
|
31
31
|
buildRetryContext,
|
|
32
32
|
buildTurnPrompt,
|
|
33
33
|
cleanWorkspace,
|
|
34
|
+
collectClaudeUsageFromCli,
|
|
35
|
+
collectCodexUsageFromCli,
|
|
36
|
+
collectGeminiUsageFromCli,
|
|
34
37
|
computeDiffStats,
|
|
35
38
|
detectAvailableProviders,
|
|
36
39
|
discoverModels,
|
|
@@ -49,7 +52,7 @@ import {
|
|
|
49
52
|
resolveAgentCommand,
|
|
50
53
|
runCommandWithTimeout,
|
|
51
54
|
runHook
|
|
52
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-NNPKCBAB.js";
|
|
53
56
|
import {
|
|
54
57
|
appendFileTail,
|
|
55
58
|
clamp,
|
|
@@ -74,7 +77,7 @@ import {
|
|
|
74
77
|
} from "./chunk-O5AEQXUV.js";
|
|
75
78
|
import {
|
|
76
79
|
enqueue
|
|
77
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-LX666HVP.js";
|
|
78
81
|
import {
|
|
79
82
|
logger
|
|
80
83
|
} from "./chunk-DVU3CXWA.js";
|
|
@@ -949,7 +952,34 @@ function loadAgentCatalog() {
|
|
|
949
952
|
return entries;
|
|
950
953
|
}
|
|
951
954
|
function loadSkillCatalog() {
|
|
952
|
-
|
|
955
|
+
const entries = [];
|
|
956
|
+
try {
|
|
957
|
+
const repos = listReferenceRepositories();
|
|
958
|
+
for (const repo of repos) {
|
|
959
|
+
if (!repo.present || !repo.synced) continue;
|
|
960
|
+
const artifacts = collectArtifacts(repo.path, repo.id).filter((a) => a.kind === "skill");
|
|
961
|
+
for (const artifact of artifacts) {
|
|
962
|
+
try {
|
|
963
|
+
const content = readFileSync3(artifact.sourcePath, "utf8");
|
|
964
|
+
const fm = parseFrontmatter(content);
|
|
965
|
+
entries.push({
|
|
966
|
+
name: artifact.targetName,
|
|
967
|
+
displayName: fm.name || artifact.targetName,
|
|
968
|
+
description: fm.description || "",
|
|
969
|
+
domains: fm.domains ? fm.domains.split(",").map((d) => d.trim()).filter(Boolean) : [],
|
|
970
|
+
source: repo.id,
|
|
971
|
+
installType: "bundled",
|
|
972
|
+
content
|
|
973
|
+
});
|
|
974
|
+
} catch (err) {
|
|
975
|
+
logger.warn({ err, path: artifact.sourcePath }, "Failed to read skill file");
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
} catch (error) {
|
|
980
|
+
logger.error({ err: error }, "Failed to load skill catalog from repositories");
|
|
981
|
+
}
|
|
982
|
+
return entries;
|
|
953
983
|
}
|
|
954
984
|
function filterByDomains(catalog, domains) {
|
|
955
985
|
const domainSet = new Set(domains.map((d) => d.toLowerCase().trim()));
|
|
@@ -2337,6 +2367,11 @@ async function whichExists(cmd) {
|
|
|
2337
2367
|
return false;
|
|
2338
2368
|
}
|
|
2339
2369
|
}
|
|
2370
|
+
var PROVIDER_USAGE_ORDER = ["claude", "codex", "gemini"];
|
|
2371
|
+
function normalizeProviderName(name) {
|
|
2372
|
+
const normalized = (name || "").trim().toLowerCase();
|
|
2373
|
+
return PROVIDER_USAGE_ORDER.includes(normalized) ? normalized : null;
|
|
2374
|
+
}
|
|
2340
2375
|
function resolveCodexHomeCandidates() {
|
|
2341
2376
|
const homePaths = /* @__PURE__ */ new Set([
|
|
2342
2377
|
homedir2(),
|
|
@@ -2393,9 +2428,25 @@ function computeTodayStart() {
|
|
|
2393
2428
|
d.setUTCHours(0, 0, 0, 0);
|
|
2394
2429
|
return d;
|
|
2395
2430
|
}
|
|
2431
|
+
function computeLastHoursStart(hours) {
|
|
2432
|
+
return new Date(Date.now() - hours * 60 * 60 * 1e3);
|
|
2433
|
+
}
|
|
2396
2434
|
function makePeriod(input, output, sessions, since) {
|
|
2397
2435
|
return { inputTokens: input, outputTokens: output, tokensUsed: input + output, sessions, since };
|
|
2398
2436
|
}
|
|
2437
|
+
function toNumber(value) {
|
|
2438
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : 0;
|
|
2439
|
+
if (typeof value === "string") {
|
|
2440
|
+
const parsed = Number.parseInt(value, 10);
|
|
2441
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
2442
|
+
}
|
|
2443
|
+
return 0;
|
|
2444
|
+
}
|
|
2445
|
+
function parseTimestamp(value) {
|
|
2446
|
+
if (typeof value !== "string") return 0;
|
|
2447
|
+
const timestamp = Date.parse(value);
|
|
2448
|
+
return Number.isNaN(timestamp) ? 0 : timestamp;
|
|
2449
|
+
}
|
|
2399
2450
|
var CLAUDE_PLAN_LIMITS = {
|
|
2400
2451
|
pro: 45e6,
|
|
2401
2452
|
// ~45M tokens/week (Pro plan estimate)
|
|
@@ -2404,6 +2455,14 @@ var CLAUDE_PLAN_LIMITS = {
|
|
|
2404
2455
|
max5x: 675e6
|
|
2405
2456
|
// ~675M tokens/week (Max 5x plan)
|
|
2406
2457
|
};
|
|
2458
|
+
function resolveClaudePlanKey(displayName) {
|
|
2459
|
+
const lower = displayName.toLowerCase().trim();
|
|
2460
|
+
if (/max\s*5x/i.test(lower)) return "max5x";
|
|
2461
|
+
if (/max/i.test(lower)) return "max";
|
|
2462
|
+
if (/pro/i.test(lower)) return "pro";
|
|
2463
|
+
if (/free/i.test(lower)) return null;
|
|
2464
|
+
return null;
|
|
2465
|
+
}
|
|
2407
2466
|
async function collectClaudeUsage() {
|
|
2408
2467
|
const home = homedir2();
|
|
2409
2468
|
const claudeDir = join8(home, ".claude");
|
|
@@ -2419,10 +2478,15 @@ async function collectClaudeUsage() {
|
|
|
2419
2478
|
let weekInputTokens = 0;
|
|
2420
2479
|
let weekOutputTokens = 0;
|
|
2421
2480
|
let weekSessions = 0;
|
|
2481
|
+
let last5hInputTokens = 0;
|
|
2482
|
+
let last5hOutputTokens = 0;
|
|
2483
|
+
let last5hSessions = 0;
|
|
2422
2484
|
const todayStart = computeTodayStart();
|
|
2423
2485
|
const todayMs = todayStart.getTime();
|
|
2424
2486
|
const weekStart = computeWeekStart();
|
|
2425
2487
|
const weekMs = weekStart.getTime();
|
|
2488
|
+
const last5hStart = computeLastHoursStart(5);
|
|
2489
|
+
const last5hMs = last5hStart.getTime();
|
|
2426
2490
|
if (existsSync6(projectsDir)) {
|
|
2427
2491
|
try {
|
|
2428
2492
|
const projectDirs = readdirSync2(projectsDir, { withFileTypes: true });
|
|
@@ -2446,6 +2510,7 @@ async function collectClaudeUsage() {
|
|
|
2446
2510
|
let sessionCounted = false;
|
|
2447
2511
|
let sessionTodayCounted = false;
|
|
2448
2512
|
let sessionWeekCounted = false;
|
|
2513
|
+
let sessionLast5hCounted = false;
|
|
2449
2514
|
for (const line of content.split("\n")) {
|
|
2450
2515
|
if (!line.trim()) continue;
|
|
2451
2516
|
try {
|
|
@@ -2477,6 +2542,14 @@ async function collectClaudeUsage() {
|
|
|
2477
2542
|
sessionWeekCounted = true;
|
|
2478
2543
|
}
|
|
2479
2544
|
}
|
|
2545
|
+
if (timestamp >= last5hMs) {
|
|
2546
|
+
last5hInputTokens += inputTokens;
|
|
2547
|
+
last5hOutputTokens += outputTokens;
|
|
2548
|
+
if (!sessionLast5hCounted) {
|
|
2549
|
+
last5hSessions++;
|
|
2550
|
+
sessionLast5hCounted = true;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2480
2553
|
} catch {
|
|
2481
2554
|
}
|
|
2482
2555
|
}
|
|
@@ -2487,9 +2560,10 @@ async function collectClaudeUsage() {
|
|
|
2487
2560
|
}
|
|
2488
2561
|
}
|
|
2489
2562
|
const models = [
|
|
2490
|
-
{ slug: "claude-opus-4-6", displayName: "
|
|
2491
|
-
{ slug: "claude-sonnet-4-6", displayName: "
|
|
2492
|
-
{ slug: "claude-
|
|
2563
|
+
{ slug: "claude-opus-4-6", displayName: "claude-opus-4-6", description: "Most capable for complex work" },
|
|
2564
|
+
{ slug: "claude-sonnet-4-6", displayName: "claude-sonnet-4-6", description: "Best for everyday tasks" },
|
|
2565
|
+
{ slug: "claude-sonnet-4-6-1m", displayName: "claude-sonnet-4-6 (1m context)", description: "Billed as extra usage \xB7 $3/$15 per Mtok" },
|
|
2566
|
+
{ slug: "claude-haiku-4-5", displayName: "claude-haiku-4-5", description: "Fastest for quick answers" }
|
|
2493
2567
|
];
|
|
2494
2568
|
let plan = "pro";
|
|
2495
2569
|
let resetInfo = "Weekly reset (every Monday 00:00 UTC)";
|
|
@@ -2508,10 +2582,53 @@ async function collectClaudeUsage() {
|
|
|
2508
2582
|
} catch {
|
|
2509
2583
|
}
|
|
2510
2584
|
}
|
|
2511
|
-
|
|
2512
|
-
|
|
2585
|
+
let nextResetAt = computeNextMonday().toISOString();
|
|
2586
|
+
let weeklyLimit = CLAUDE_PLAN_LIMITS[plan] ?? null;
|
|
2513
2587
|
const weeklyUsed = weekInputTokens + weekOutputTokens;
|
|
2514
2588
|
const percentUsed = weeklyLimit ? Math.min(100, Math.round(weeklyUsed / weeklyLimit * 100)) : null;
|
|
2589
|
+
const statusUsage = await collectClaudeUsageFromCli();
|
|
2590
|
+
if (statusUsage) {
|
|
2591
|
+
if (statusUsage.currentModel) {
|
|
2592
|
+
currentModel = statusUsage.currentModel;
|
|
2593
|
+
}
|
|
2594
|
+
if (statusUsage.plan) {
|
|
2595
|
+
const planKey = resolveClaudePlanKey(statusUsage.plan);
|
|
2596
|
+
if (planKey && CLAUDE_PLAN_LIMITS[planKey]) {
|
|
2597
|
+
plan = planKey;
|
|
2598
|
+
weeklyLimit = CLAUDE_PLAN_LIMITS[planKey];
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
if (statusUsage.weeklyLimitEstimate !== null) {
|
|
2602
|
+
weeklyLimit = statusUsage.weeklyLimitEstimate;
|
|
2603
|
+
}
|
|
2604
|
+
if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
|
|
2605
|
+
weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
|
|
2606
|
+
weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
|
|
2607
|
+
if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
|
|
2608
|
+
}
|
|
2609
|
+
if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
|
|
2610
|
+
todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
|
|
2611
|
+
todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
|
|
2612
|
+
if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
|
|
2613
|
+
}
|
|
2614
|
+
if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
|
|
2615
|
+
last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
|
|
2616
|
+
last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
|
|
2617
|
+
if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
|
|
2618
|
+
}
|
|
2619
|
+
if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
|
|
2620
|
+
totalInputTokens = statusUsage.allTimeInputTokens ?? totalInputTokens;
|
|
2621
|
+
totalOutputTokens = statusUsage.allTimeOutputTokens ?? totalOutputTokens;
|
|
2622
|
+
if (statusUsage.allTimeSessions !== null) totalSessions = statusUsage.allTimeSessions;
|
|
2623
|
+
}
|
|
2624
|
+
if (statusUsage.resetInfo) {
|
|
2625
|
+
resetInfo = statusUsage.resetInfo;
|
|
2626
|
+
}
|
|
2627
|
+
if (statusUsage.nextResetAt) {
|
|
2628
|
+
nextResetAt = statusUsage.nextResetAt;
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
const finalPercentUsed = weeklyLimit ? Math.min(100, Math.round((weekInputTokens + weekOutputTokens) / weeklyLimit * 100)) : percentUsed;
|
|
2515
2632
|
return {
|
|
2516
2633
|
name: "claude",
|
|
2517
2634
|
available,
|
|
@@ -2520,14 +2637,95 @@ async function collectClaudeUsage() {
|
|
|
2520
2637
|
usage: {
|
|
2521
2638
|
today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
|
|
2522
2639
|
thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
|
|
2640
|
+
last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
|
|
2523
2641
|
allTime: makePeriod(totalInputTokens, totalOutputTokens, totalSessions, "")
|
|
2524
2642
|
},
|
|
2525
2643
|
resetInfo,
|
|
2526
2644
|
nextResetAt,
|
|
2527
2645
|
weeklyLimitEstimate: weeklyLimit,
|
|
2528
|
-
percentUsed
|
|
2646
|
+
percentUsed: finalPercentUsed,
|
|
2647
|
+
version: statusUsage?.version ?? null,
|
|
2648
|
+
plan: statusUsage?.plan ?? (plan !== "pro" ? plan.toUpperCase() : "Pro"),
|
|
2649
|
+
account: statusUsage?.account ?? null,
|
|
2650
|
+
effort: statusUsage?.effort ?? null,
|
|
2651
|
+
rateLimits: statusUsage?.rateLimits ?? []
|
|
2652
|
+
};
|
|
2653
|
+
}
|
|
2654
|
+
function aggregateCodexSessionUsageFromJsonl(lines) {
|
|
2655
|
+
let maxInput = 0;
|
|
2656
|
+
let maxOutput = 0;
|
|
2657
|
+
let maxTotal = 0;
|
|
2658
|
+
let sessionTs = 0;
|
|
2659
|
+
for (const line of lines) {
|
|
2660
|
+
if (!line.trim()) continue;
|
|
2661
|
+
let entry;
|
|
2662
|
+
try {
|
|
2663
|
+
entry = JSON.parse(line);
|
|
2664
|
+
} catch {
|
|
2665
|
+
continue;
|
|
2666
|
+
}
|
|
2667
|
+
const lineTs = parseTimestamp(entry?.timestamp);
|
|
2668
|
+
const payloadTs = parseTimestamp(entry?.payload?.timestamp);
|
|
2669
|
+
const candidateTs = Math.max(lineTs, payloadTs);
|
|
2670
|
+
if (candidateTs > sessionTs) sessionTs = candidateTs;
|
|
2671
|
+
if (entry.type !== "event_msg") continue;
|
|
2672
|
+
if (entry.payload?.type !== "token_count") continue;
|
|
2673
|
+
const info = entry.payload?.info || {};
|
|
2674
|
+
const usage = info.total_token_usage || info.last_token_usage;
|
|
2675
|
+
if (!usage || typeof usage !== "object") continue;
|
|
2676
|
+
const input = toNumber(usage.input_tokens);
|
|
2677
|
+
const output = toNumber(usage.output_tokens);
|
|
2678
|
+
const total = toNumber(usage.total_tokens);
|
|
2679
|
+
if (total <= 0) continue;
|
|
2680
|
+
if (total > maxTotal) {
|
|
2681
|
+
maxTotal = total;
|
|
2682
|
+
maxInput = input;
|
|
2683
|
+
maxOutput = output;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
if (maxTotal <= 0) return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: sessionTs };
|
|
2687
|
+
return {
|
|
2688
|
+
inputTokens: maxInput,
|
|
2689
|
+
outputTokens: maxOutput,
|
|
2690
|
+
totalTokens: maxTotal,
|
|
2691
|
+
timestampMs: sessionTs
|
|
2529
2692
|
};
|
|
2530
2693
|
}
|
|
2694
|
+
function collectCodexSessionUsagesFromJsonl(codexDir) {
|
|
2695
|
+
const sessionsDir = join8(codexDir, "sessions");
|
|
2696
|
+
if (!existsSync6(sessionsDir)) return [];
|
|
2697
|
+
const stack = [sessionsDir];
|
|
2698
|
+
const usageByFile = [];
|
|
2699
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2700
|
+
while (stack.length > 0) {
|
|
2701
|
+
const current = stack.pop();
|
|
2702
|
+
if (seen.has(current)) continue;
|
|
2703
|
+
seen.add(current);
|
|
2704
|
+
let entries = [];
|
|
2705
|
+
try {
|
|
2706
|
+
entries = readdirSync2(current, { withFileTypes: true });
|
|
2707
|
+
} catch {
|
|
2708
|
+
continue;
|
|
2709
|
+
}
|
|
2710
|
+
for (const entry of entries) {
|
|
2711
|
+
const next = join8(current, entry.name);
|
|
2712
|
+
if (entry.isDirectory()) {
|
|
2713
|
+
stack.push(next);
|
|
2714
|
+
continue;
|
|
2715
|
+
}
|
|
2716
|
+
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
2717
|
+
try {
|
|
2718
|
+
const content = readFileSync5(next, "utf8");
|
|
2719
|
+
const usage = aggregateCodexSessionUsageFromJsonl(content.split("\n"));
|
|
2720
|
+
if (usage.totalTokens > 0) {
|
|
2721
|
+
usageByFile.push(usage);
|
|
2722
|
+
}
|
|
2723
|
+
} catch {
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
return usageByFile;
|
|
2728
|
+
}
|
|
2531
2729
|
async function collectCodexUsage() {
|
|
2532
2730
|
const codexDir = resolveCodexDir();
|
|
2533
2731
|
if (!codexDir) return null;
|
|
@@ -2541,7 +2739,7 @@ async function collectCodexUsage() {
|
|
|
2541
2739
|
for (const m of cache.models || []) {
|
|
2542
2740
|
models.push({
|
|
2543
2741
|
slug: m.slug,
|
|
2544
|
-
displayName: m.display_name || m.slug,
|
|
2742
|
+
displayName: (m.display_name || m.slug).toLowerCase(),
|
|
2545
2743
|
description: (m.description || "").slice(0, 80)
|
|
2546
2744
|
});
|
|
2547
2745
|
}
|
|
@@ -2559,60 +2757,199 @@ async function collectCodexUsage() {
|
|
|
2559
2757
|
}
|
|
2560
2758
|
const todayStart = computeTodayStart();
|
|
2561
2759
|
const weekStart = computeWeekStart();
|
|
2562
|
-
|
|
2760
|
+
let nextResetAt = computeNextMonday().toISOString();
|
|
2761
|
+
let resetInfo = "Weekly rate limit resets every Monday";
|
|
2762
|
+
const last5hStart = computeLastHoursStart(5);
|
|
2763
|
+
const last5hMs = last5hStart.getTime();
|
|
2764
|
+
const todayMs = todayStart.getTime();
|
|
2765
|
+
const weekMs = weekStart.getTime();
|
|
2563
2766
|
const dbPath = findLatestCodexDb(codexDir);
|
|
2564
|
-
if (!dbPath) {
|
|
2565
|
-
return {
|
|
2566
|
-
name: "codex",
|
|
2567
|
-
available,
|
|
2568
|
-
models,
|
|
2569
|
-
currentModel,
|
|
2570
|
-
usage: {
|
|
2571
|
-
today: makePeriod(0, 0, 0, todayStart.toISOString()),
|
|
2572
|
-
thisWeek: makePeriod(0, 0, 0, weekStart.toISOString()),
|
|
2573
|
-
allTime: makePeriod(0, 0, 0, "")
|
|
2574
|
-
},
|
|
2575
|
-
resetInfo: "Weekly rate limit resets every Monday",
|
|
2576
|
-
nextResetAt,
|
|
2577
|
-
weeklyLimitEstimate: null,
|
|
2578
|
-
percentUsed: null
|
|
2579
|
-
};
|
|
2580
|
-
}
|
|
2581
2767
|
let allTimeTokens = 0;
|
|
2582
2768
|
let allTimeSessions = 0;
|
|
2583
2769
|
let todayTokens = 0;
|
|
2584
2770
|
let todaySessions = 0;
|
|
2585
2771
|
let weekTokens = 0;
|
|
2586
2772
|
let weekSessions = 0;
|
|
2773
|
+
let last5hTokens = 0;
|
|
2774
|
+
let last5hSessions = 0;
|
|
2775
|
+
let allTimeInputTokens = 0;
|
|
2776
|
+
let allTimeOutputTokens = 0;
|
|
2777
|
+
let todayInputTokens = 0;
|
|
2778
|
+
let todayOutputTokens = 0;
|
|
2779
|
+
let weekInputTokens = 0;
|
|
2780
|
+
let weekOutputTokens = 0;
|
|
2781
|
+
let last5hInputTokens = 0;
|
|
2782
|
+
let last5hOutputTokens = 0;
|
|
2587
2783
|
const todayUnix = Math.floor(todayStart.getTime() / 1e3);
|
|
2588
2784
|
const weekUnix = Math.floor(weekStart.getTime() / 1e3);
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2785
|
+
const last5hUnix = Math.floor(last5hStart.getTime() / 1e3);
|
|
2786
|
+
if (dbPath) {
|
|
2787
|
+
try {
|
|
2788
|
+
const query = `
|
|
2789
|
+
SELECT
|
|
2790
|
+
SUM(tokens_used) as total_tokens,
|
|
2791
|
+
COUNT(*) as total_sessions,
|
|
2792
|
+
SUM(CASE WHEN created_at >= ${todayUnix} THEN tokens_used ELSE 0 END) as today_tokens,
|
|
2793
|
+
SUM(CASE WHEN created_at >= ${todayUnix} THEN 1 ELSE 0 END) as today_sessions,
|
|
2794
|
+
SUM(CASE WHEN created_at >= ${weekUnix} THEN tokens_used ELSE 0 END) as week_tokens,
|
|
2795
|
+
SUM(CASE WHEN created_at >= ${weekUnix} THEN 1 ELSE 0 END) as week_sessions,
|
|
2796
|
+
SUM(CASE WHEN created_at >= ${last5hUnix} THEN tokens_used ELSE 0 END) as last5h_tokens,
|
|
2797
|
+
SUM(CASE WHEN created_at >= ${last5hUnix} THEN 1 ELSE 0 END) as last5h_sessions
|
|
2798
|
+
FROM threads;
|
|
2799
|
+
`;
|
|
2800
|
+
const { stdout } = await execFileAsync("sqlite3", [dbPath, query], {
|
|
2801
|
+
encoding: "utf8",
|
|
2802
|
+
timeout: 5e3
|
|
2803
|
+
});
|
|
2804
|
+
const result = stdout.trim();
|
|
2805
|
+
if (result) {
|
|
2806
|
+
const parts = result.split("|");
|
|
2807
|
+
allTimeTokens = parseInt(parts[0], 10) || 0;
|
|
2808
|
+
allTimeSessions = parseInt(parts[1], 10) || 0;
|
|
2809
|
+
todayTokens = parseInt(parts[2], 10) || 0;
|
|
2810
|
+
todaySessions = parseInt(parts[3], 10) || 0;
|
|
2811
|
+
weekTokens = parseInt(parts[4], 10) || 0;
|
|
2812
|
+
weekSessions = parseInt(parts[5], 10) || 0;
|
|
2813
|
+
last5hTokens = parseInt(parts[6], 10) || 0;
|
|
2814
|
+
last5hSessions = parseInt(parts[7], 10) || 0;
|
|
2815
|
+
}
|
|
2816
|
+
} catch (err) {
|
|
2817
|
+
logger.debug(`Failed to query Codex SQLite: ${String(err)}`);
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
const sessionUsages = collectCodexSessionUsagesFromJsonl(codexDir);
|
|
2821
|
+
if (sessionUsages.length > 0) {
|
|
2822
|
+
let jsonlAllTimeInput = 0;
|
|
2823
|
+
let jsonlAllTimeOutput = 0;
|
|
2824
|
+
let jsonlAllTimeTokens = 0;
|
|
2825
|
+
let jsonlAllTimeSessions = 0;
|
|
2826
|
+
let jsonlTodayInput = 0;
|
|
2827
|
+
let jsonlTodayOutput = 0;
|
|
2828
|
+
let jsonlTodaySessions = 0;
|
|
2829
|
+
let jsonlWeekInput = 0;
|
|
2830
|
+
let jsonlWeekOutput = 0;
|
|
2831
|
+
let jsonlWeekSessions = 0;
|
|
2832
|
+
let jsonlLast5hInput = 0;
|
|
2833
|
+
let jsonlLast5hOutput = 0;
|
|
2834
|
+
let jsonlLast5hSessions = 0;
|
|
2835
|
+
for (const usage of sessionUsages) {
|
|
2836
|
+
if (usage.totalTokens <= 0) continue;
|
|
2837
|
+
jsonlAllTimeInput += usage.inputTokens;
|
|
2838
|
+
jsonlAllTimeOutput += usage.outputTokens;
|
|
2839
|
+
jsonlAllTimeTokens += usage.totalTokens;
|
|
2840
|
+
jsonlAllTimeSessions++;
|
|
2841
|
+
if (usage.timestampMs >= todayMs) {
|
|
2842
|
+
jsonlTodayInput += usage.inputTokens;
|
|
2843
|
+
jsonlTodayOutput += usage.outputTokens;
|
|
2844
|
+
jsonlTodaySessions++;
|
|
2845
|
+
}
|
|
2846
|
+
if (usage.timestampMs >= weekMs) {
|
|
2847
|
+
jsonlWeekInput += usage.inputTokens;
|
|
2848
|
+
jsonlWeekOutput += usage.outputTokens;
|
|
2849
|
+
jsonlWeekSessions++;
|
|
2850
|
+
}
|
|
2851
|
+
if (usage.timestampMs >= last5hMs) {
|
|
2852
|
+
jsonlLast5hInput += usage.inputTokens;
|
|
2853
|
+
jsonlLast5hOutput += usage.outputTokens;
|
|
2854
|
+
jsonlLast5hSessions++;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
if (allTimeTokens === 0 && allTimeSessions === 0) {
|
|
2858
|
+
allTimeTokens = jsonlAllTimeTokens;
|
|
2859
|
+
allTimeSessions = jsonlAllTimeSessions;
|
|
2860
|
+
todayTokens = jsonlTodayInput + jsonlTodayOutput;
|
|
2861
|
+
todaySessions = jsonlTodaySessions;
|
|
2862
|
+
weekTokens = jsonlWeekInput + jsonlWeekOutput;
|
|
2863
|
+
weekSessions = jsonlWeekSessions;
|
|
2864
|
+
last5hTokens = jsonlLast5hInput + jsonlLast5hOutput;
|
|
2865
|
+
last5hSessions = jsonlLast5hSessions;
|
|
2866
|
+
} else {
|
|
2867
|
+
allTimeInputTokens = jsonlAllTimeInput;
|
|
2868
|
+
allTimeOutputTokens = jsonlAllTimeOutput;
|
|
2869
|
+
todayInputTokens = jsonlTodayInput;
|
|
2870
|
+
todayOutputTokens = jsonlTodayOutput;
|
|
2871
|
+
weekInputTokens = jsonlWeekInput;
|
|
2872
|
+
weekOutputTokens = jsonlWeekOutput;
|
|
2873
|
+
last5hInputTokens = jsonlLast5hInput;
|
|
2874
|
+
last5hOutputTokens = jsonlLast5hOutput;
|
|
2875
|
+
}
|
|
2876
|
+
if (allTimeSessions === 0) allTimeSessions = jsonlAllTimeSessions;
|
|
2877
|
+
if (todaySessions === 0) todaySessions = jsonlTodaySessions;
|
|
2878
|
+
if (weekSessions === 0) weekSessions = jsonlWeekSessions;
|
|
2879
|
+
if (last5hSessions === 0) last5hSessions = jsonlLast5hSessions;
|
|
2880
|
+
}
|
|
2881
|
+
if (allTimeInputTokens === 0 && allTimeOutputTokens === 0 && sessionUsages.length > 0) {
|
|
2882
|
+
for (const usage of sessionUsages) {
|
|
2883
|
+
if (usage.totalTokens <= 0) continue;
|
|
2884
|
+
allTimeInputTokens += usage.inputTokens;
|
|
2885
|
+
allTimeOutputTokens += usage.outputTokens;
|
|
2886
|
+
}
|
|
2887
|
+
if (allTimeInputTokens > 0 || allTimeOutputTokens > 0) {
|
|
2888
|
+
todayInputTokens = 0;
|
|
2889
|
+
todayOutputTokens = 0;
|
|
2890
|
+
weekInputTokens = 0;
|
|
2891
|
+
weekOutputTokens = 0;
|
|
2892
|
+
last5hInputTokens = 0;
|
|
2893
|
+
last5hOutputTokens = 0;
|
|
2894
|
+
for (const usage of sessionUsages) {
|
|
2895
|
+
if (usage.timestampMs >= todayMs) {
|
|
2896
|
+
todayInputTokens += usage.inputTokens;
|
|
2897
|
+
todayOutputTokens += usage.outputTokens;
|
|
2898
|
+
}
|
|
2899
|
+
if (usage.timestampMs >= weekMs) {
|
|
2900
|
+
weekInputTokens += usage.inputTokens;
|
|
2901
|
+
weekOutputTokens += usage.outputTokens;
|
|
2902
|
+
}
|
|
2903
|
+
if (usage.timestampMs >= last5hMs) {
|
|
2904
|
+
last5hInputTokens += usage.inputTokens;
|
|
2905
|
+
last5hOutputTokens += usage.outputTokens;
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
}
|
|
2910
|
+
if (allTimeInputTokens === 0 && allTimeOutputTokens === 0) {
|
|
2911
|
+
allTimeInputTokens = allTimeTokens;
|
|
2912
|
+
}
|
|
2913
|
+
if (todayInputTokens === 0 && todayOutputTokens === 0) {
|
|
2914
|
+
todayInputTokens = todayTokens;
|
|
2915
|
+
}
|
|
2916
|
+
if (weekInputTokens === 0 && weekOutputTokens === 0) {
|
|
2917
|
+
weekInputTokens = weekTokens;
|
|
2918
|
+
}
|
|
2919
|
+
if (last5hInputTokens === 0 && last5hOutputTokens === 0) {
|
|
2920
|
+
last5hInputTokens = last5hTokens;
|
|
2921
|
+
}
|
|
2922
|
+
const statusUsage = await collectCodexUsageFromCli();
|
|
2923
|
+
if (statusUsage) {
|
|
2924
|
+
if (statusUsage.currentModel) {
|
|
2925
|
+
currentModel = statusUsage.currentModel;
|
|
2926
|
+
}
|
|
2927
|
+
if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
|
|
2928
|
+
allTimeInputTokens = statusUsage.allTimeInputTokens ?? allTimeInputTokens;
|
|
2929
|
+
allTimeOutputTokens = statusUsage.allTimeOutputTokens ?? allTimeOutputTokens;
|
|
2930
|
+
if (statusUsage.allTimeSessions !== null) allTimeSessions = statusUsage.allTimeSessions;
|
|
2931
|
+
}
|
|
2932
|
+
if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
|
|
2933
|
+
todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
|
|
2934
|
+
todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
|
|
2935
|
+
if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
|
|
2936
|
+
}
|
|
2937
|
+
if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
|
|
2938
|
+
weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
|
|
2939
|
+
weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
|
|
2940
|
+
if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
|
|
2941
|
+
}
|
|
2942
|
+
if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
|
|
2943
|
+
last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
|
|
2944
|
+
last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
|
|
2945
|
+
if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
|
|
2946
|
+
}
|
|
2947
|
+
if (statusUsage.resetInfo) {
|
|
2948
|
+
resetInfo = statusUsage.resetInfo;
|
|
2949
|
+
}
|
|
2950
|
+
if (statusUsage.nextResetAt) {
|
|
2951
|
+
nextResetAt = statusUsage.nextResetAt;
|
|
2613
2952
|
}
|
|
2614
|
-
} catch (err) {
|
|
2615
|
-
logger.debug(`Failed to query Codex SQLite: ${String(err)}`);
|
|
2616
2953
|
}
|
|
2617
2954
|
return {
|
|
2618
2955
|
name: "codex",
|
|
@@ -2620,22 +2957,113 @@ async function collectCodexUsage() {
|
|
|
2620
2957
|
models,
|
|
2621
2958
|
currentModel,
|
|
2622
2959
|
usage: {
|
|
2623
|
-
today: makePeriod(
|
|
2624
|
-
thisWeek: makePeriod(
|
|
2625
|
-
|
|
2960
|
+
today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
|
|
2961
|
+
thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
|
|
2962
|
+
last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
|
|
2963
|
+
allTime: makePeriod(allTimeInputTokens, allTimeOutputTokens, allTimeSessions, "")
|
|
2626
2964
|
},
|
|
2627
|
-
resetInfo
|
|
2965
|
+
resetInfo,
|
|
2628
2966
|
nextResetAt,
|
|
2629
2967
|
weeklyLimitEstimate: null,
|
|
2630
|
-
percentUsed: null
|
|
2968
|
+
percentUsed: statusUsage?.weeklyPercentUsed ?? null,
|
|
2969
|
+
version: statusUsage?.version ?? null,
|
|
2970
|
+
plan: statusUsage?.plan ?? null,
|
|
2971
|
+
account: statusUsage?.account ?? null,
|
|
2972
|
+
effort: statusUsage?.effort ?? null,
|
|
2973
|
+
rateLimits: statusUsage?.rateLimits ?? []
|
|
2631
2974
|
};
|
|
2632
2975
|
}
|
|
2976
|
+
function aggregateGeminiSessionUsageFromJson(content) {
|
|
2977
|
+
let sessionInput = 0;
|
|
2978
|
+
let sessionOutput = 0;
|
|
2979
|
+
let sessionTotal = 0;
|
|
2980
|
+
let sessionTs = 0;
|
|
2981
|
+
let session;
|
|
2982
|
+
try {
|
|
2983
|
+
session = JSON.parse(content);
|
|
2984
|
+
} catch {
|
|
2985
|
+
return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: 0 };
|
|
2986
|
+
}
|
|
2987
|
+
const fallbackTs = parseTimestamp(session.startTime) || parseTimestamp(session.lastUpdated);
|
|
2988
|
+
if (fallbackTs > sessionTs) sessionTs = fallbackTs;
|
|
2989
|
+
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
2990
|
+
for (const message of messages) {
|
|
2991
|
+
if (!message || message.type !== "gemini" || !message.tokens) continue;
|
|
2992
|
+
const tokens = message.tokens;
|
|
2993
|
+
const input = toNumber(tokens.input);
|
|
2994
|
+
const output = toNumber(tokens.output);
|
|
2995
|
+
const total = toNumber(tokens.total);
|
|
2996
|
+
if (input === 0 && output === 0 && total === 0) continue;
|
|
2997
|
+
sessionInput += input;
|
|
2998
|
+
sessionOutput += output;
|
|
2999
|
+
sessionTotal += total > 0 ? total : input + output;
|
|
3000
|
+
const messageTs = parseTimestamp(message.timestamp);
|
|
3001
|
+
if (messageTs > sessionTs) sessionTs = messageTs;
|
|
3002
|
+
}
|
|
3003
|
+
if (sessionTotal <= 0) return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: sessionTs };
|
|
3004
|
+
return {
|
|
3005
|
+
inputTokens: sessionInput,
|
|
3006
|
+
outputTokens: sessionOutput,
|
|
3007
|
+
totalTokens: sessionTotal,
|
|
3008
|
+
timestampMs: sessionTs
|
|
3009
|
+
};
|
|
3010
|
+
}
|
|
3011
|
+
function collectGeminiSessionUsages() {
|
|
3012
|
+
const geminiTmp = join8(homedir2(), ".gemini", "tmp");
|
|
3013
|
+
if (!existsSync6(geminiTmp)) return [];
|
|
3014
|
+
const usages = [];
|
|
3015
|
+
let entries = [];
|
|
3016
|
+
try {
|
|
3017
|
+
entries = readdirSync2(geminiTmp, { withFileTypes: true });
|
|
3018
|
+
} catch {
|
|
3019
|
+
return usages;
|
|
3020
|
+
}
|
|
3021
|
+
for (const profile of entries) {
|
|
3022
|
+
if (!profile.isDirectory()) continue;
|
|
3023
|
+
const chatsDir = join8(geminiTmp, profile.name, "chats");
|
|
3024
|
+
if (!existsSync6(chatsDir)) continue;
|
|
3025
|
+
let sessions = [];
|
|
3026
|
+
try {
|
|
3027
|
+
sessions = readdirSync2(chatsDir).filter((name) => name.startsWith("session-") && (name.endsWith(".json") || name.endsWith(".jsonl")));
|
|
3028
|
+
} catch {
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
for (const sessionFile of sessions) {
|
|
3032
|
+
const sessionPath = join8(chatsDir, sessionFile);
|
|
3033
|
+
try {
|
|
3034
|
+
const usage = aggregateGeminiSessionUsageFromJson(readFileSync5(sessionPath, "utf8"));
|
|
3035
|
+
if (usage.totalTokens > 0) usages.push(usage);
|
|
3036
|
+
} catch {
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
return usages;
|
|
3041
|
+
}
|
|
2633
3042
|
async function collectGeminiUsage() {
|
|
2634
3043
|
const available = await whichExists("gemini");
|
|
2635
3044
|
if (!available) return null;
|
|
3045
|
+
let version = null;
|
|
3046
|
+
try {
|
|
3047
|
+
const { stdout } = await execFileAsync("gemini", ["--version"], { encoding: "utf8", timeout: 5e3 });
|
|
3048
|
+
const trimmed = stdout.trim();
|
|
3049
|
+
if (/^\d+\.\d+/.test(trimmed)) version = trimmed;
|
|
3050
|
+
} catch {
|
|
3051
|
+
}
|
|
3052
|
+
let account = null;
|
|
3053
|
+
const accountsPath = join8(homedir2(), ".gemini", "google_accounts.json");
|
|
3054
|
+
if (existsSync6(accountsPath)) {
|
|
3055
|
+
try {
|
|
3056
|
+
const data = JSON.parse(readFileSync5(accountsPath, "utf8"));
|
|
3057
|
+
if (typeof data.active === "string" && data.active.includes("@")) {
|
|
3058
|
+
account = data.active;
|
|
3059
|
+
}
|
|
3060
|
+
} catch {
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2636
3063
|
const todayStart = computeTodayStart();
|
|
2637
3064
|
const weekStart = computeWeekStart();
|
|
2638
|
-
|
|
3065
|
+
let nextResetAt = computeNextMonday().toISOString();
|
|
3066
|
+
const last5hStart = computeLastHoursStart(5);
|
|
2639
3067
|
const models = [];
|
|
2640
3068
|
try {
|
|
2641
3069
|
const { stdout: binPath } = await execFileAsync("which", ["gemini"], { encoding: "utf8", timeout: 3e3 });
|
|
@@ -2670,20 +3098,99 @@ async function collectGeminiUsage() {
|
|
|
2670
3098
|
} catch {
|
|
2671
3099
|
}
|
|
2672
3100
|
}
|
|
3101
|
+
const todayMs = todayStart.getTime();
|
|
3102
|
+
const weekMs = weekStart.getTime();
|
|
3103
|
+
const last5hMs = last5hStart.getTime();
|
|
3104
|
+
let resetInfo = "Usage from local Gemini session logs";
|
|
3105
|
+
let todayInputTokens = 0;
|
|
3106
|
+
let todayOutputTokens = 0;
|
|
3107
|
+
let todaySessions = 0;
|
|
3108
|
+
let weekInputTokens = 0;
|
|
3109
|
+
let weekOutputTokens = 0;
|
|
3110
|
+
let weekSessions = 0;
|
|
3111
|
+
let last5hInputTokens = 0;
|
|
3112
|
+
let last5hOutputTokens = 0;
|
|
3113
|
+
let last5hSessions = 0;
|
|
3114
|
+
let allTimeInputTokens = 0;
|
|
3115
|
+
let allTimeOutputTokens = 0;
|
|
3116
|
+
let allTimeSessions = 0;
|
|
3117
|
+
const sessionUsages = collectGeminiSessionUsages();
|
|
3118
|
+
for (const usage of sessionUsages) {
|
|
3119
|
+
allTimeInputTokens += usage.inputTokens;
|
|
3120
|
+
allTimeOutputTokens += usage.outputTokens;
|
|
3121
|
+
allTimeSessions++;
|
|
3122
|
+
if (usage.timestampMs >= todayMs) {
|
|
3123
|
+
todayInputTokens += usage.inputTokens;
|
|
3124
|
+
todayOutputTokens += usage.outputTokens;
|
|
3125
|
+
todaySessions++;
|
|
3126
|
+
}
|
|
3127
|
+
if (usage.timestampMs >= weekMs) {
|
|
3128
|
+
weekInputTokens += usage.inputTokens;
|
|
3129
|
+
weekOutputTokens += usage.outputTokens;
|
|
3130
|
+
weekSessions++;
|
|
3131
|
+
}
|
|
3132
|
+
if (usage.timestampMs >= last5hMs) {
|
|
3133
|
+
last5hInputTokens += usage.inputTokens;
|
|
3134
|
+
last5hOutputTokens += usage.outputTokens;
|
|
3135
|
+
last5hSessions++;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
const statusUsage = await collectGeminiUsageFromCli();
|
|
3139
|
+
if (statusUsage) {
|
|
3140
|
+
if (statusUsage.currentModel) {
|
|
3141
|
+
currentModel = statusUsage.currentModel;
|
|
3142
|
+
}
|
|
3143
|
+
if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
|
|
3144
|
+
allTimeInputTokens = statusUsage.allTimeInputTokens ?? allTimeInputTokens;
|
|
3145
|
+
allTimeOutputTokens = statusUsage.allTimeOutputTokens ?? allTimeOutputTokens;
|
|
3146
|
+
if (statusUsage.allTimeSessions !== null) allTimeSessions = statusUsage.allTimeSessions;
|
|
3147
|
+
}
|
|
3148
|
+
if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
|
|
3149
|
+
todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
|
|
3150
|
+
todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
|
|
3151
|
+
if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
|
|
3152
|
+
}
|
|
3153
|
+
if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
|
|
3154
|
+
weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
|
|
3155
|
+
weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
|
|
3156
|
+
if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
|
|
3157
|
+
}
|
|
3158
|
+
if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
|
|
3159
|
+
last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
|
|
3160
|
+
last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
|
|
3161
|
+
if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
|
|
3162
|
+
}
|
|
3163
|
+
if (statusUsage.resetInfo) {
|
|
3164
|
+
resetInfo = statusUsage.resetInfo;
|
|
3165
|
+
}
|
|
3166
|
+
if (statusUsage.nextResetAt) {
|
|
3167
|
+
nextResetAt = statusUsage.nextResetAt;
|
|
3168
|
+
}
|
|
3169
|
+
if (statusUsage.weeklyLimitEstimate !== null && statusUsage.weeklyPercentUsed !== null) {
|
|
3170
|
+
resetInfo = `Estimated weekly used: ${statusUsage.weeklyPercentUsed}%`;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
2673
3173
|
return {
|
|
2674
3174
|
name: "gemini",
|
|
2675
3175
|
available,
|
|
2676
3176
|
models,
|
|
2677
3177
|
currentModel,
|
|
2678
3178
|
usage: {
|
|
2679
|
-
today: makePeriod(
|
|
2680
|
-
thisWeek: makePeriod(
|
|
2681
|
-
|
|
3179
|
+
today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
|
|
3180
|
+
thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
|
|
3181
|
+
last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
|
|
3182
|
+
allTime: makePeriod(allTimeInputTokens, allTimeOutputTokens, allTimeSessions, "")
|
|
2682
3183
|
},
|
|
2683
|
-
resetInfo
|
|
3184
|
+
resetInfo,
|
|
2684
3185
|
nextResetAt,
|
|
2685
3186
|
weeklyLimitEstimate: null,
|
|
2686
|
-
percentUsed: null
|
|
3187
|
+
percentUsed: statusUsage?.weeklyPercentUsed ?? null,
|
|
3188
|
+
version: statusUsage?.version ?? version,
|
|
3189
|
+
plan: statusUsage?.plan ?? null,
|
|
3190
|
+
account: statusUsage?.account ?? account,
|
|
3191
|
+
effort: null,
|
|
3192
|
+
// Gemini has no effort/reasoning concept
|
|
3193
|
+
rateLimits: statusUsage?.rateLimits ?? []
|
|
2687
3194
|
};
|
|
2688
3195
|
}
|
|
2689
3196
|
var usageCache = null;
|
|
@@ -2705,6 +3212,13 @@ async function collectProvidersUsage() {
|
|
|
2705
3212
|
usageCacheAt = Date.now();
|
|
2706
3213
|
return usageCache;
|
|
2707
3214
|
}
|
|
3215
|
+
async function collectProviderUsage(providerName) {
|
|
3216
|
+
const normalized = normalizeProviderName(providerName);
|
|
3217
|
+
if (!normalized) return null;
|
|
3218
|
+
if (normalized === "claude") return collectClaudeUsage();
|
|
3219
|
+
if (normalized === "codex") return collectCodexUsage();
|
|
3220
|
+
return collectGeminiUsage();
|
|
3221
|
+
}
|
|
2708
3222
|
|
|
2709
3223
|
// src/routes/state.ts
|
|
2710
3224
|
import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
@@ -3104,6 +3618,19 @@ function registerStateRoutes(app, state) {
|
|
|
3104
3618
|
app.get("/api/parallelism", async (c) => {
|
|
3105
3619
|
return c.json(analyzeParallelizability(state.issues));
|
|
3106
3620
|
});
|
|
3621
|
+
app.get("/api/providers/:slug/usage", async (c) => {
|
|
3622
|
+
const provider = c.req.param("slug") || "";
|
|
3623
|
+
try {
|
|
3624
|
+
const usage = await collectProviderUsage(provider);
|
|
3625
|
+
return c.json({
|
|
3626
|
+
providers: usage ? [usage] : [],
|
|
3627
|
+
collectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3628
|
+
});
|
|
3629
|
+
} catch (error) {
|
|
3630
|
+
logger.error({ err: error, provider }, "Failed to collect provider usage");
|
|
3631
|
+
return c.json({ providers: [] }, 500);
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3107
3634
|
app.get("/api/providers/usage", async (c) => {
|
|
3108
3635
|
try {
|
|
3109
3636
|
const usage = await collectProvidersUsage();
|
|
@@ -3274,7 +3801,7 @@ function registerStateRoutes(app, state) {
|
|
|
3274
3801
|
if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
|
|
3275
3802
|
const issue = findIssue(state, issueId);
|
|
3276
3803
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
3277
|
-
const { dryMerge } = await import("./workspace-
|
|
3804
|
+
const { dryMerge } = await import("./workspace-IKR6X6OE.js");
|
|
3278
3805
|
const result = dryMerge(issue);
|
|
3279
3806
|
return c.json({ ok: true, ...result });
|
|
3280
3807
|
} catch (error) {
|
|
@@ -3289,7 +3816,7 @@ function registerStateRoutes(app, state) {
|
|
|
3289
3816
|
if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
|
|
3290
3817
|
const issue = findIssue(state, issueId);
|
|
3291
3818
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
3292
|
-
const { rebaseWorktree } = await import("./workspace-
|
|
3819
|
+
const { rebaseWorktree } = await import("./workspace-IKR6X6OE.js");
|
|
3293
3820
|
const result = rebaseWorktree(issue);
|
|
3294
3821
|
if (result.success) {
|
|
3295
3822
|
addEvent(state, issue.id, "info", `Branch ${issue.branchName} rebased onto ${issue.baseBranch}.`);
|
|
@@ -3421,7 +3948,7 @@ function registerStateRoutes(app, state) {
|
|
|
3421
3948
|
const issue = findIssue(state, issueId);
|
|
3422
3949
|
if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
|
|
3423
3950
|
try {
|
|
3424
|
-
const { getIssueTransitionHistory } = await import("./issue-state-machine-
|
|
3951
|
+
const { getIssueTransitionHistory } = await import("./issue-state-machine-24GKGB3W.js");
|
|
3425
3952
|
const limit = parseInt(c.req.query("limit") ?? "50", 10);
|
|
3426
3953
|
const offset = parseInt(c.req.query("offset") ?? "0", 10);
|
|
3427
3954
|
const transitions = await getIssueTransitionHistory(issue.id, { limit, offset });
|
|
@@ -3432,7 +3959,7 @@ function registerStateRoutes(app, state) {
|
|
|
3432
3959
|
});
|
|
3433
3960
|
app.get("/api/state-machine/transitions", async (c) => {
|
|
3434
3961
|
try {
|
|
3435
|
-
const { getStateMachineTransitions } = await import("./issue-state-machine-
|
|
3962
|
+
const { getStateMachineTransitions } = await import("./issue-state-machine-24GKGB3W.js");
|
|
3436
3963
|
return c.json({ ok: true, transitions: getStateMachineTransitions() });
|
|
3437
3964
|
} catch (error) {
|
|
3438
3965
|
return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
|
|
@@ -3440,7 +3967,7 @@ function registerStateRoutes(app, state) {
|
|
|
3440
3967
|
});
|
|
3441
3968
|
app.get("/api/state-machine/visualize", async (c) => {
|
|
3442
3969
|
try {
|
|
3443
|
-
const { visualizeStateMachine } = await import("./issue-state-machine-
|
|
3970
|
+
const { visualizeStateMachine } = await import("./issue-state-machine-24GKGB3W.js");
|
|
3444
3971
|
const dot = visualizeStateMachine();
|
|
3445
3972
|
if (!dot) return c.json({ ok: false, error: "Visualization not available." }, 404);
|
|
3446
3973
|
return c.json({ ok: true, dot });
|
|
@@ -3762,6 +4289,7 @@ var SETTING_ID_WORKFLOW_CONFIG = "runtime.workflowConfig";
|
|
|
3762
4289
|
var SETTING_ID_TEST_COMMAND = "runtime.testCommand";
|
|
3763
4290
|
var SETTING_ID_MERGE_MODE = "runtime.mergeMode";
|
|
3764
4291
|
var SETTING_ID_PR_BASE_BRANCH = "runtime.prBaseBranch";
|
|
4292
|
+
var SETTING_ID_AUTO_REVIEW_APPROVAL = "runtime.autoReviewApproval";
|
|
3765
4293
|
async function loadRuntimeSettings() {
|
|
3766
4294
|
return loadPersistedSettings();
|
|
3767
4295
|
}
|
|
@@ -3780,7 +4308,8 @@ var RUNTIME_CONFIG_SETTING_IDS = /* @__PURE__ */ new Set([
|
|
|
3780
4308
|
SETTING_ID_DEFAULT_EFFORT,
|
|
3781
4309
|
SETTING_ID_TEST_COMMAND,
|
|
3782
4310
|
SETTING_ID_MERGE_MODE,
|
|
3783
|
-
SETTING_ID_PR_BASE_BRANCH
|
|
4311
|
+
SETTING_ID_PR_BASE_BRANCH,
|
|
4312
|
+
SETTING_ID_AUTO_REVIEW_APPROVAL
|
|
3784
4313
|
]);
|
|
3785
4314
|
var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "extra-high"]);
|
|
3786
4315
|
function parseIntegerSetting(value) {
|
|
@@ -3842,7 +4371,8 @@ function buildRuntimeConfigSettings(config, source) {
|
|
|
3842
4371
|
{ id: SETTING_ID_DEFAULT_EFFORT, scope: "runtime", value: config.defaultEffort, source, updatedAt },
|
|
3843
4372
|
{ id: SETTING_ID_TEST_COMMAND, scope: "runtime", value: config.testCommand ?? "", source, updatedAt },
|
|
3844
4373
|
{ id: SETTING_ID_MERGE_MODE, scope: "runtime", value: config.mergeMode ?? "local", source, updatedAt },
|
|
3845
|
-
{ id: SETTING_ID_PR_BASE_BRANCH, scope: "runtime", value: config.prBaseBranch ?? "", source, updatedAt }
|
|
4374
|
+
{ id: SETTING_ID_PR_BASE_BRANCH, scope: "runtime", value: config.prBaseBranch ?? "", source, updatedAt },
|
|
4375
|
+
{ id: SETTING_ID_AUTO_REVIEW_APPROVAL, scope: "runtime", value: config.autoReviewApproval, source, updatedAt }
|
|
3846
4376
|
];
|
|
3847
4377
|
}
|
|
3848
4378
|
function applyPersistedSettings(config, settings) {
|
|
@@ -3951,6 +4481,10 @@ function applyPersistedSettings(config, settings) {
|
|
|
3951
4481
|
}
|
|
3952
4482
|
break;
|
|
3953
4483
|
}
|
|
4484
|
+
case SETTING_ID_AUTO_REVIEW_APPROVAL: {
|
|
4485
|
+
nextConfig.autoReviewApproval = toBooleanValue(setting.value, true);
|
|
4486
|
+
break;
|
|
4487
|
+
}
|
|
3954
4488
|
default:
|
|
3955
4489
|
break;
|
|
3956
4490
|
}
|
|
@@ -5615,7 +6149,7 @@ var RouteCollector = class {
|
|
|
5615
6149
|
this.routes[`DELETE ${path}`] = handler;
|
|
5616
6150
|
}
|
|
5617
6151
|
};
|
|
5618
|
-
async function startApiServer(state, port) {
|
|
6152
|
+
async function startApiServer(state, port, options) {
|
|
5619
6153
|
logger.info({ port }, "[API] Starting API server");
|
|
5620
6154
|
const stateDb2 = getStateDb();
|
|
5621
6155
|
if (!stateDb2) {
|
|
@@ -5674,6 +6208,7 @@ async function startApiServer(state, port) {
|
|
|
5674
6208
|
const apiPlugin = new ApiPlugin({
|
|
5675
6209
|
port,
|
|
5676
6210
|
host: "0.0.0.0",
|
|
6211
|
+
tls: false,
|
|
5677
6212
|
versionPrefix: false,
|
|
5678
6213
|
metrics: {
|
|
5679
6214
|
logLevel: "info"
|
|
@@ -5719,8 +6254,11 @@ async function startApiServer(state, port) {
|
|
|
5719
6254
|
"GET /analytics": () => serveAppShell(),
|
|
5720
6255
|
"GET /agents": () => serveAppShell(),
|
|
5721
6256
|
"GET /settings": () => serveAppShell(),
|
|
6257
|
+
"GET /settings/project": () => serveAppShell(),
|
|
5722
6258
|
"GET /settings/general": () => serveAppShell(),
|
|
6259
|
+
"GET /settings/agents": () => serveAppShell(),
|
|
5723
6260
|
"GET /settings/notifications": () => serveAppShell(),
|
|
6261
|
+
"GET /settings/preferences": () => serveAppShell(),
|
|
5724
6262
|
"GET /settings/workflow": () => serveAppShell(),
|
|
5725
6263
|
"GET /settings/providers": () => serveAppShell(),
|
|
5726
6264
|
"GET /api/health": (c) => c.json({ status: state.booting ? "booting" : "ready" })
|
|
@@ -6628,7 +7166,8 @@ var AGENCY_AGENTS_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
|
6628
7166
|
var REFERENCE_REPOSITORY_PARSERS = {
|
|
6629
7167
|
ring: collectStandardArtifacts,
|
|
6630
7168
|
"agency-agents": collectAgencyArtifacts,
|
|
6631
|
-
impeccable: collectImpeccableArtifacts
|
|
7169
|
+
impeccable: collectImpeccableArtifacts,
|
|
7170
|
+
"everything-claude-code": collectStandardArtifacts
|
|
6632
7171
|
};
|
|
6633
7172
|
function runGit(args, cwd) {
|
|
6634
7173
|
return execFileSync2("git", args, {
|
|
@@ -7091,6 +7630,7 @@ function deriveConfig(args) {
|
|
|
7091
7630
|
},
|
|
7092
7631
|
maxConcurrentByState: {},
|
|
7093
7632
|
runMode: "filesystem",
|
|
7633
|
+
autoReviewApproval: true,
|
|
7094
7634
|
afterCreateHook: env4.FIFONY_AFTER_CREATE_HOOK ?? "",
|
|
7095
7635
|
beforeRunHook: env4.FIFONY_BEFORE_RUN_HOOK ?? "",
|
|
7096
7636
|
afterRunHook: env4.FIFONY_AFTER_RUN_HOOK ?? "",
|
|
@@ -7331,7 +7871,7 @@ async function handleStatePatch(state, issue, payload) {
|
|
|
7331
7871
|
}
|
|
7332
7872
|
if (nextState === "Running" && sourceState === "Queued") {
|
|
7333
7873
|
try {
|
|
7334
|
-
const { enqueue: enqueue2 } = await import("./queue-workers-
|
|
7874
|
+
const { enqueue: enqueue2 } = await import("./queue-workers-HF7RBVV7.js");
|
|
7335
7875
|
await enqueue2(issue, "execute");
|
|
7336
7876
|
} catch (error) {
|
|
7337
7877
|
logger.warn({ issueId: issue.id, err: error }, "[Issues] Failed to enqueue after manual Running transition");
|
|
@@ -7409,9 +7949,15 @@ async function handleReviewStage(state, issue, workspacePath, startTs, routedPro
|
|
|
7409
7949
|
markIssueDirty(issue.id);
|
|
7410
7950
|
const container = getContainer();
|
|
7411
7951
|
const reviewer = routedProviders.find((p) => p.role === "reviewer");
|
|
7952
|
+
const autoReviewApproval = state.config.autoReviewApproval !== false;
|
|
7412
7953
|
if (!reviewer) {
|
|
7413
|
-
|
|
7414
|
-
|
|
7954
|
+
if (autoReviewApproval) {
|
|
7955
|
+
issue.mergedReason = "Auto-approved: no reviewer configured.";
|
|
7956
|
+
await transitionIssueCommand({ issue, target: "Approved", note: `No reviewer configured; auto-approved for ${issue.identifier}.` }, container);
|
|
7957
|
+
return;
|
|
7958
|
+
}
|
|
7959
|
+
issue.mergedReason = "Reviewer not configured; manual approval required.";
|
|
7960
|
+
await transitionIssueCommand({ issue, target: "PendingDecision", note: `No reviewer configured; manual approval required for ${issue.identifier}.` }, container);
|
|
7415
7961
|
return;
|
|
7416
7962
|
}
|
|
7417
7963
|
addEvent(state, issue.id, "info", `Review provider: ${reviewer.role}:${reviewer.provider}${reviewer.model ? `/${reviewer.model}` : ""}${reviewer.profile ? `:${reviewer.profile}` : ""}.`);
|
|
@@ -7460,8 +8006,9 @@ async function handleReviewStage(state, issue, workspacePath, startTs, routedPro
|
|
|
7460
8006
|
logger.warn({ err: String(vErr) }, "[Agent] Failed to write versioned review artifacts");
|
|
7461
8007
|
}
|
|
7462
8008
|
if (reviewResult.success) {
|
|
7463
|
-
issue.mergedReason = `Auto-approved by reviewer in ${reviewResult.turns} turn(s).`;
|
|
8009
|
+
issue.mergedReason = autoReviewApproval ? `Auto-approved by reviewer in ${reviewResult.turns} turn(s).` : `Reviewer completed for ${issue.identifier}; waiting for manual approval.`;
|
|
7464
8010
|
await transitionIssueCommand({ issue, target: "PendingDecision", note: `Reviewer completed for ${issue.identifier}.` }, container);
|
|
8011
|
+
if (!autoReviewApproval) return;
|
|
7465
8012
|
const validation = await runValidationGate(issue, state.config);
|
|
7466
8013
|
if (validation) {
|
|
7467
8014
|
issue.validationResult = validation;
|
|
@@ -7592,7 +8139,7 @@ async function runIssueOnce(state, issue, running) {
|
|
|
7592
8139
|
const { workspacePath, promptText, promptFile } = await prepareWorkspace(issue, state, state.config.defaultBranch);
|
|
7593
8140
|
container.issueRepository.markDirty(issue.id);
|
|
7594
8141
|
try {
|
|
7595
|
-
const { getIssueStateResource: getIssueStateResource2 } = await import("./store-
|
|
8142
|
+
const { getIssueStateResource: getIssueStateResource2 } = await import("./store-MHU3G2W4.js");
|
|
7596
8143
|
const res = getIssueStateResource2();
|
|
7597
8144
|
if (res) {
|
|
7598
8145
|
await res.patch(issue.id, {
|
|
@@ -7704,4 +8251,4 @@ export {
|
|
|
7704
8251
|
syncReferenceRepositories,
|
|
7705
8252
|
importReferenceArtifacts
|
|
7706
8253
|
};
|
|
7707
|
-
//# sourceMappingURL=chunk-
|
|
8254
|
+
//# sourceMappingURL=chunk-GAYQ2TXV.js.map
|