fifony 0.1.31 → 0.1.32

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.
Files changed (33) hide show
  1. package/README.md +36 -3
  2. package/app/dist/assets/CommandPalette-DuugDkZo.js +1 -0
  3. package/app/dist/assets/KeyboardShortcutsHelp-B1a9ZDDu.js +1 -0
  4. package/app/dist/assets/OnboardingWizard-BF43aq_I.js +1 -0
  5. package/app/dist/assets/analytics.lazy-Bc6WBoMs.js +1 -0
  6. package/app/dist/assets/api-ChEctgc5.js +1 -0
  7. package/app/dist/assets/{createLucideIcon-DgMTp0yx.js → createLucideIcon-ggBfAlHh.js} +1 -1
  8. package/app/dist/assets/{index-lf3jEP_3.css → index-CGCZneFa.css} +1 -1
  9. package/app/dist/assets/index-Dx5YOoMm.js +49 -0
  10. package/app/dist/assets/rolldown-runtime-Dw2cE7zH.js +1 -0
  11. package/app/dist/assets/{vendor-D-IqxHHu.js → vendor-CSL5bxVU.js} +1 -1
  12. package/app/dist/index.html +6 -5
  13. package/app/dist/service-worker.js +1 -1
  14. package/dist/agent/run-local.js +4 -4
  15. package/dist/{agent-OBLUHG2W.js → agent-3NYJEHH5.js} +5 -5
  16. package/dist/{chunk-IYAF3SY6.js → chunk-2D5P75F6.js} +556 -17
  17. package/dist/{chunk-7AMUAUY5.js → chunk-FAFGDK62.js} +625 -80
  18. package/dist/{chunk-VUNMXX7N.js → chunk-FPUTP743.js} +11 -11
  19. package/dist/{chunk-CXFEPU5Q.js → chunk-H2QRC6UQ.js} +8 -17
  20. package/dist/cli.js +4 -4
  21. package/dist/{issue-runner-CI7IUBBD.js → issue-runner-ZNDKLOCZ.js} +5 -5
  22. package/dist/{issue-state-machine-ETAJLBS6.js → issue-state-machine-NBSYRDZW.js} +3 -3
  23. package/dist/{issues-6VCD27PA.js → issues-GBWLDBVU.js} +5 -5
  24. package/dist/{queue-workers-IEI23UUO.js → queue-workers-OHPJKAPM.js} +2 -2
  25. package/dist/{scheduler-JSH55NAQ.js → scheduler-OU35EYUH.js} +5 -5
  26. package/dist/{store-MTE6H7WQ.js → store-FFHHRNVI.js} +5 -5
  27. package/dist/{workspace-3PV5PTR5.js → workspace-5XOCMZ57.js} +2 -2
  28. package/package.json +4 -1
  29. package/app/dist/assets/KeyboardShortcutsHelp-3vwtTKsQ.js +0 -1
  30. package/app/dist/assets/OnboardingWizard-urUin_Ob.js +0 -1
  31. package/app/dist/assets/analytics.lazy-CbuW3ebu.js +0 -1
  32. package/app/dist/assets/index-ywtlX6S8.js +0 -45
  33. package/app/dist/assets/rolldown-runtime-DF2fYuay.js +0 -1
@@ -21,7 +21,7 @@ import {
21
21
  snapshotAndClearDirtyEventIds,
22
22
  snapshotAndClearDirtyIssueIds,
23
23
  snapshotAndClearDirtyIssuePlanIds
24
- } from "./chunk-CXFEPU5Q.js";
24
+ } from "./chunk-H2QRC6UQ.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-IYAF3SY6.js";
55
+ } from "./chunk-2D5P75F6.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-VUNMXX7N.js";
80
+ } from "./chunk-FPUTP743.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
- return [];
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,9 @@ async function collectClaudeUsage() {
2487
2560
  }
2488
2561
  }
2489
2562
  const models = [
2490
- { slug: "claude-opus-4-6", displayName: "Claude Opus 4.6", description: "Most capable model for complex tasks" },
2491
- { slug: "claude-sonnet-4-6", displayName: "Claude Sonnet 4.6", description: "Balanced performance and speed" },
2492
- { slug: "claude-haiku-4-5", displayName: "Claude Haiku 4.5", description: "Fast and efficient model" }
2563
+ { slug: "claude-opus-4-6", displayName: "claude opus 4.6", description: "Most capable model for complex tasks" },
2564
+ { slug: "claude-sonnet-4-6", displayName: "claude sonnet 4.6", description: "Balanced performance and speed" },
2565
+ { slug: "claude-haiku-4-5", displayName: "claude haiku 4.5", description: "Fast and efficient model" }
2493
2566
  ];
2494
2567
  let plan = "pro";
2495
2568
  let resetInfo = "Weekly reset (every Monday 00:00 UTC)";
@@ -2508,10 +2581,53 @@ async function collectClaudeUsage() {
2508
2581
  } catch {
2509
2582
  }
2510
2583
  }
2511
- const nextResetAt = computeNextMonday().toISOString();
2512
- const weeklyLimit = CLAUDE_PLAN_LIMITS[plan] ?? null;
2584
+ let nextResetAt = computeNextMonday().toISOString();
2585
+ let weeklyLimit = CLAUDE_PLAN_LIMITS[plan] ?? null;
2513
2586
  const weeklyUsed = weekInputTokens + weekOutputTokens;
2514
2587
  const percentUsed = weeklyLimit ? Math.min(100, Math.round(weeklyUsed / weeklyLimit * 100)) : null;
2588
+ const statusUsage = await collectClaudeUsageFromCli();
2589
+ if (statusUsage) {
2590
+ if (statusUsage.currentModel) {
2591
+ currentModel = statusUsage.currentModel;
2592
+ }
2593
+ if (statusUsage.plan) {
2594
+ const planKey = resolveClaudePlanKey(statusUsage.plan);
2595
+ if (planKey && CLAUDE_PLAN_LIMITS[planKey]) {
2596
+ plan = planKey;
2597
+ weeklyLimit = CLAUDE_PLAN_LIMITS[planKey];
2598
+ }
2599
+ }
2600
+ if (statusUsage.weeklyLimitEstimate !== null) {
2601
+ weeklyLimit = statusUsage.weeklyLimitEstimate;
2602
+ }
2603
+ if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
2604
+ weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
2605
+ weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
2606
+ if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
2607
+ }
2608
+ if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
2609
+ todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
2610
+ todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
2611
+ if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
2612
+ }
2613
+ if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
2614
+ last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
2615
+ last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
2616
+ if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
2617
+ }
2618
+ if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
2619
+ totalInputTokens = statusUsage.allTimeInputTokens ?? totalInputTokens;
2620
+ totalOutputTokens = statusUsage.allTimeOutputTokens ?? totalOutputTokens;
2621
+ if (statusUsage.allTimeSessions !== null) totalSessions = statusUsage.allTimeSessions;
2622
+ }
2623
+ if (statusUsage.resetInfo) {
2624
+ resetInfo = statusUsage.resetInfo;
2625
+ }
2626
+ if (statusUsage.nextResetAt) {
2627
+ nextResetAt = statusUsage.nextResetAt;
2628
+ }
2629
+ }
2630
+ const finalPercentUsed = weeklyLimit ? Math.min(100, Math.round((weekInputTokens + weekOutputTokens) / weeklyLimit * 100)) : percentUsed;
2515
2631
  return {
2516
2632
  name: "claude",
2517
2633
  available,
@@ -2520,14 +2636,95 @@ async function collectClaudeUsage() {
2520
2636
  usage: {
2521
2637
  today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
2522
2638
  thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
2639
+ last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
2523
2640
  allTime: makePeriod(totalInputTokens, totalOutputTokens, totalSessions, "")
2524
2641
  },
2525
2642
  resetInfo,
2526
2643
  nextResetAt,
2527
2644
  weeklyLimitEstimate: weeklyLimit,
2528
- percentUsed
2645
+ percentUsed: finalPercentUsed,
2646
+ version: statusUsage?.version ?? null,
2647
+ plan: statusUsage?.plan ?? (plan !== "pro" ? plan.toUpperCase() : "Pro"),
2648
+ account: statusUsage?.account ?? null,
2649
+ effort: statusUsage?.effort ?? null,
2650
+ rateLimits: statusUsage?.rateLimits ?? []
2651
+ };
2652
+ }
2653
+ function aggregateCodexSessionUsageFromJsonl(lines) {
2654
+ let maxInput = 0;
2655
+ let maxOutput = 0;
2656
+ let maxTotal = 0;
2657
+ let sessionTs = 0;
2658
+ for (const line of lines) {
2659
+ if (!line.trim()) continue;
2660
+ let entry;
2661
+ try {
2662
+ entry = JSON.parse(line);
2663
+ } catch {
2664
+ continue;
2665
+ }
2666
+ const lineTs = parseTimestamp(entry?.timestamp);
2667
+ const payloadTs = parseTimestamp(entry?.payload?.timestamp);
2668
+ const candidateTs = Math.max(lineTs, payloadTs);
2669
+ if (candidateTs > sessionTs) sessionTs = candidateTs;
2670
+ if (entry.type !== "event_msg") continue;
2671
+ if (entry.payload?.type !== "token_count") continue;
2672
+ const info = entry.payload?.info || {};
2673
+ const usage = info.total_token_usage || info.last_token_usage;
2674
+ if (!usage || typeof usage !== "object") continue;
2675
+ const input = toNumber(usage.input_tokens);
2676
+ const output = toNumber(usage.output_tokens);
2677
+ const total = toNumber(usage.total_tokens);
2678
+ if (total <= 0) continue;
2679
+ if (total > maxTotal) {
2680
+ maxTotal = total;
2681
+ maxInput = input;
2682
+ maxOutput = output;
2683
+ }
2684
+ }
2685
+ if (maxTotal <= 0) return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: sessionTs };
2686
+ return {
2687
+ inputTokens: maxInput,
2688
+ outputTokens: maxOutput,
2689
+ totalTokens: maxTotal,
2690
+ timestampMs: sessionTs
2529
2691
  };
2530
2692
  }
2693
+ function collectCodexSessionUsagesFromJsonl(codexDir) {
2694
+ const sessionsDir = join8(codexDir, "sessions");
2695
+ if (!existsSync6(sessionsDir)) return [];
2696
+ const stack = [sessionsDir];
2697
+ const usageByFile = [];
2698
+ const seen = /* @__PURE__ */ new Set();
2699
+ while (stack.length > 0) {
2700
+ const current = stack.pop();
2701
+ if (seen.has(current)) continue;
2702
+ seen.add(current);
2703
+ let entries = [];
2704
+ try {
2705
+ entries = readdirSync2(current, { withFileTypes: true });
2706
+ } catch {
2707
+ continue;
2708
+ }
2709
+ for (const entry of entries) {
2710
+ const next = join8(current, entry.name);
2711
+ if (entry.isDirectory()) {
2712
+ stack.push(next);
2713
+ continue;
2714
+ }
2715
+ if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
2716
+ try {
2717
+ const content = readFileSync5(next, "utf8");
2718
+ const usage = aggregateCodexSessionUsageFromJsonl(content.split("\n"));
2719
+ if (usage.totalTokens > 0) {
2720
+ usageByFile.push(usage);
2721
+ }
2722
+ } catch {
2723
+ }
2724
+ }
2725
+ }
2726
+ return usageByFile;
2727
+ }
2531
2728
  async function collectCodexUsage() {
2532
2729
  const codexDir = resolveCodexDir();
2533
2730
  if (!codexDir) return null;
@@ -2541,7 +2738,7 @@ async function collectCodexUsage() {
2541
2738
  for (const m of cache.models || []) {
2542
2739
  models.push({
2543
2740
  slug: m.slug,
2544
- displayName: m.display_name || m.slug,
2741
+ displayName: (m.display_name || m.slug).toLowerCase(),
2545
2742
  description: (m.description || "").slice(0, 80)
2546
2743
  });
2547
2744
  }
@@ -2559,60 +2756,199 @@ async function collectCodexUsage() {
2559
2756
  }
2560
2757
  const todayStart = computeTodayStart();
2561
2758
  const weekStart = computeWeekStart();
2562
- const nextResetAt = computeNextMonday().toISOString();
2759
+ let nextResetAt = computeNextMonday().toISOString();
2760
+ let resetInfo = "Weekly rate limit resets every Monday";
2761
+ const last5hStart = computeLastHoursStart(5);
2762
+ const last5hMs = last5hStart.getTime();
2763
+ const todayMs = todayStart.getTime();
2764
+ const weekMs = weekStart.getTime();
2563
2765
  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
2766
  let allTimeTokens = 0;
2582
2767
  let allTimeSessions = 0;
2583
2768
  let todayTokens = 0;
2584
2769
  let todaySessions = 0;
2585
2770
  let weekTokens = 0;
2586
2771
  let weekSessions = 0;
2772
+ let last5hTokens = 0;
2773
+ let last5hSessions = 0;
2774
+ let allTimeInputTokens = 0;
2775
+ let allTimeOutputTokens = 0;
2776
+ let todayInputTokens = 0;
2777
+ let todayOutputTokens = 0;
2778
+ let weekInputTokens = 0;
2779
+ let weekOutputTokens = 0;
2780
+ let last5hInputTokens = 0;
2781
+ let last5hOutputTokens = 0;
2587
2782
  const todayUnix = Math.floor(todayStart.getTime() / 1e3);
2588
2783
  const weekUnix = Math.floor(weekStart.getTime() / 1e3);
2589
- try {
2590
- const query = `
2591
- SELECT
2592
- SUM(tokens_used) as total_tokens,
2593
- COUNT(*) as total_sessions,
2594
- SUM(CASE WHEN created_at >= ${todayUnix} THEN tokens_used ELSE 0 END) as today_tokens,
2595
- SUM(CASE WHEN created_at >= ${todayUnix} THEN 1 ELSE 0 END) as today_sessions,
2596
- SUM(CASE WHEN created_at >= ${weekUnix} THEN tokens_used ELSE 0 END) as week_tokens,
2597
- SUM(CASE WHEN created_at >= ${weekUnix} THEN 1 ELSE 0 END) as week_sessions
2598
- FROM threads;
2599
- `;
2600
- const { stdout } = await execFileAsync("sqlite3", [dbPath, query], {
2601
- encoding: "utf8",
2602
- timeout: 5e3
2603
- });
2604
- const result = stdout.trim();
2605
- if (result) {
2606
- const parts = result.split("|");
2607
- allTimeTokens = parseInt(parts[0], 10) || 0;
2608
- allTimeSessions = parseInt(parts[1], 10) || 0;
2609
- todayTokens = parseInt(parts[2], 10) || 0;
2610
- todaySessions = parseInt(parts[3], 10) || 0;
2611
- weekTokens = parseInt(parts[4], 10) || 0;
2612
- weekSessions = parseInt(parts[5], 10) || 0;
2784
+ const last5hUnix = Math.floor(last5hStart.getTime() / 1e3);
2785
+ if (dbPath) {
2786
+ try {
2787
+ const query = `
2788
+ SELECT
2789
+ SUM(tokens_used) as total_tokens,
2790
+ COUNT(*) as total_sessions,
2791
+ SUM(CASE WHEN created_at >= ${todayUnix} THEN tokens_used ELSE 0 END) as today_tokens,
2792
+ SUM(CASE WHEN created_at >= ${todayUnix} THEN 1 ELSE 0 END) as today_sessions,
2793
+ SUM(CASE WHEN created_at >= ${weekUnix} THEN tokens_used ELSE 0 END) as week_tokens,
2794
+ SUM(CASE WHEN created_at >= ${weekUnix} THEN 1 ELSE 0 END) as week_sessions,
2795
+ SUM(CASE WHEN created_at >= ${last5hUnix} THEN tokens_used ELSE 0 END) as last5h_tokens,
2796
+ SUM(CASE WHEN created_at >= ${last5hUnix} THEN 1 ELSE 0 END) as last5h_sessions
2797
+ FROM threads;
2798
+ `;
2799
+ const { stdout } = await execFileAsync("sqlite3", [dbPath, query], {
2800
+ encoding: "utf8",
2801
+ timeout: 5e3
2802
+ });
2803
+ const result = stdout.trim();
2804
+ if (result) {
2805
+ const parts = result.split("|");
2806
+ allTimeTokens = parseInt(parts[0], 10) || 0;
2807
+ allTimeSessions = parseInt(parts[1], 10) || 0;
2808
+ todayTokens = parseInt(parts[2], 10) || 0;
2809
+ todaySessions = parseInt(parts[3], 10) || 0;
2810
+ weekTokens = parseInt(parts[4], 10) || 0;
2811
+ weekSessions = parseInt(parts[5], 10) || 0;
2812
+ last5hTokens = parseInt(parts[6], 10) || 0;
2813
+ last5hSessions = parseInt(parts[7], 10) || 0;
2814
+ }
2815
+ } catch (err) {
2816
+ logger.debug(`Failed to query Codex SQLite: ${String(err)}`);
2817
+ }
2818
+ }
2819
+ const sessionUsages = collectCodexSessionUsagesFromJsonl(codexDir);
2820
+ if (sessionUsages.length > 0) {
2821
+ let jsonlAllTimeInput = 0;
2822
+ let jsonlAllTimeOutput = 0;
2823
+ let jsonlAllTimeTokens = 0;
2824
+ let jsonlAllTimeSessions = 0;
2825
+ let jsonlTodayInput = 0;
2826
+ let jsonlTodayOutput = 0;
2827
+ let jsonlTodaySessions = 0;
2828
+ let jsonlWeekInput = 0;
2829
+ let jsonlWeekOutput = 0;
2830
+ let jsonlWeekSessions = 0;
2831
+ let jsonlLast5hInput = 0;
2832
+ let jsonlLast5hOutput = 0;
2833
+ let jsonlLast5hSessions = 0;
2834
+ for (const usage of sessionUsages) {
2835
+ if (usage.totalTokens <= 0) continue;
2836
+ jsonlAllTimeInput += usage.inputTokens;
2837
+ jsonlAllTimeOutput += usage.outputTokens;
2838
+ jsonlAllTimeTokens += usage.totalTokens;
2839
+ jsonlAllTimeSessions++;
2840
+ if (usage.timestampMs >= todayMs) {
2841
+ jsonlTodayInput += usage.inputTokens;
2842
+ jsonlTodayOutput += usage.outputTokens;
2843
+ jsonlTodaySessions++;
2844
+ }
2845
+ if (usage.timestampMs >= weekMs) {
2846
+ jsonlWeekInput += usage.inputTokens;
2847
+ jsonlWeekOutput += usage.outputTokens;
2848
+ jsonlWeekSessions++;
2849
+ }
2850
+ if (usage.timestampMs >= last5hMs) {
2851
+ jsonlLast5hInput += usage.inputTokens;
2852
+ jsonlLast5hOutput += usage.outputTokens;
2853
+ jsonlLast5hSessions++;
2854
+ }
2855
+ }
2856
+ if (allTimeTokens === 0 && allTimeSessions === 0) {
2857
+ allTimeTokens = jsonlAllTimeTokens;
2858
+ allTimeSessions = jsonlAllTimeSessions;
2859
+ todayTokens = jsonlTodayInput + jsonlTodayOutput;
2860
+ todaySessions = jsonlTodaySessions;
2861
+ weekTokens = jsonlWeekInput + jsonlWeekOutput;
2862
+ weekSessions = jsonlWeekSessions;
2863
+ last5hTokens = jsonlLast5hInput + jsonlLast5hOutput;
2864
+ last5hSessions = jsonlLast5hSessions;
2865
+ } else {
2866
+ allTimeInputTokens = jsonlAllTimeInput;
2867
+ allTimeOutputTokens = jsonlAllTimeOutput;
2868
+ todayInputTokens = jsonlTodayInput;
2869
+ todayOutputTokens = jsonlTodayOutput;
2870
+ weekInputTokens = jsonlWeekInput;
2871
+ weekOutputTokens = jsonlWeekOutput;
2872
+ last5hInputTokens = jsonlLast5hInput;
2873
+ last5hOutputTokens = jsonlLast5hOutput;
2874
+ }
2875
+ if (allTimeSessions === 0) allTimeSessions = jsonlAllTimeSessions;
2876
+ if (todaySessions === 0) todaySessions = jsonlTodaySessions;
2877
+ if (weekSessions === 0) weekSessions = jsonlWeekSessions;
2878
+ if (last5hSessions === 0) last5hSessions = jsonlLast5hSessions;
2879
+ }
2880
+ if (allTimeInputTokens === 0 && allTimeOutputTokens === 0 && sessionUsages.length > 0) {
2881
+ for (const usage of sessionUsages) {
2882
+ if (usage.totalTokens <= 0) continue;
2883
+ allTimeInputTokens += usage.inputTokens;
2884
+ allTimeOutputTokens += usage.outputTokens;
2885
+ }
2886
+ if (allTimeInputTokens > 0 || allTimeOutputTokens > 0) {
2887
+ todayInputTokens = 0;
2888
+ todayOutputTokens = 0;
2889
+ weekInputTokens = 0;
2890
+ weekOutputTokens = 0;
2891
+ last5hInputTokens = 0;
2892
+ last5hOutputTokens = 0;
2893
+ for (const usage of sessionUsages) {
2894
+ if (usage.timestampMs >= todayMs) {
2895
+ todayInputTokens += usage.inputTokens;
2896
+ todayOutputTokens += usage.outputTokens;
2897
+ }
2898
+ if (usage.timestampMs >= weekMs) {
2899
+ weekInputTokens += usage.inputTokens;
2900
+ weekOutputTokens += usage.outputTokens;
2901
+ }
2902
+ if (usage.timestampMs >= last5hMs) {
2903
+ last5hInputTokens += usage.inputTokens;
2904
+ last5hOutputTokens += usage.outputTokens;
2905
+ }
2906
+ }
2907
+ }
2908
+ }
2909
+ if (allTimeInputTokens === 0 && allTimeOutputTokens === 0) {
2910
+ allTimeInputTokens = allTimeTokens;
2911
+ }
2912
+ if (todayInputTokens === 0 && todayOutputTokens === 0) {
2913
+ todayInputTokens = todayTokens;
2914
+ }
2915
+ if (weekInputTokens === 0 && weekOutputTokens === 0) {
2916
+ weekInputTokens = weekTokens;
2917
+ }
2918
+ if (last5hInputTokens === 0 && last5hOutputTokens === 0) {
2919
+ last5hInputTokens = last5hTokens;
2920
+ }
2921
+ const statusUsage = await collectCodexUsageFromCli();
2922
+ if (statusUsage) {
2923
+ if (statusUsage.currentModel) {
2924
+ currentModel = statusUsage.currentModel;
2925
+ }
2926
+ if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
2927
+ allTimeInputTokens = statusUsage.allTimeInputTokens ?? allTimeInputTokens;
2928
+ allTimeOutputTokens = statusUsage.allTimeOutputTokens ?? allTimeOutputTokens;
2929
+ if (statusUsage.allTimeSessions !== null) allTimeSessions = statusUsage.allTimeSessions;
2930
+ }
2931
+ if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
2932
+ todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
2933
+ todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
2934
+ if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
2935
+ }
2936
+ if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
2937
+ weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
2938
+ weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
2939
+ if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
2940
+ }
2941
+ if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
2942
+ last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
2943
+ last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
2944
+ if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
2945
+ }
2946
+ if (statusUsage.resetInfo) {
2947
+ resetInfo = statusUsage.resetInfo;
2948
+ }
2949
+ if (statusUsage.nextResetAt) {
2950
+ nextResetAt = statusUsage.nextResetAt;
2613
2951
  }
2614
- } catch (err) {
2615
- logger.debug(`Failed to query Codex SQLite: ${String(err)}`);
2616
2952
  }
2617
2953
  return {
2618
2954
  name: "codex",
@@ -2620,22 +2956,113 @@ async function collectCodexUsage() {
2620
2956
  models,
2621
2957
  currentModel,
2622
2958
  usage: {
2623
- today: makePeriod(todayTokens, 0, todaySessions, todayStart.toISOString()),
2624
- thisWeek: makePeriod(weekTokens, 0, weekSessions, weekStart.toISOString()),
2625
- allTime: makePeriod(allTimeTokens, 0, allTimeSessions, "")
2959
+ today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
2960
+ thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
2961
+ last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
2962
+ allTime: makePeriod(allTimeInputTokens, allTimeOutputTokens, allTimeSessions, "")
2626
2963
  },
2627
- resetInfo: "Weekly rate limit resets every Monday",
2964
+ resetInfo,
2628
2965
  nextResetAt,
2629
2966
  weeklyLimitEstimate: null,
2630
- percentUsed: null
2967
+ percentUsed: statusUsage?.weeklyPercentUsed ?? null,
2968
+ version: statusUsage?.version ?? null,
2969
+ plan: statusUsage?.plan ?? null,
2970
+ account: statusUsage?.account ?? null,
2971
+ effort: statusUsage?.effort ?? null,
2972
+ rateLimits: statusUsage?.rateLimits ?? []
2631
2973
  };
2632
2974
  }
2975
+ function aggregateGeminiSessionUsageFromJson(content) {
2976
+ let sessionInput = 0;
2977
+ let sessionOutput = 0;
2978
+ let sessionTotal = 0;
2979
+ let sessionTs = 0;
2980
+ let session;
2981
+ try {
2982
+ session = JSON.parse(content);
2983
+ } catch {
2984
+ return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: 0 };
2985
+ }
2986
+ const fallbackTs = parseTimestamp(session.startTime) || parseTimestamp(session.lastUpdated);
2987
+ if (fallbackTs > sessionTs) sessionTs = fallbackTs;
2988
+ const messages = Array.isArray(session.messages) ? session.messages : [];
2989
+ for (const message of messages) {
2990
+ if (!message || message.type !== "gemini" || !message.tokens) continue;
2991
+ const tokens = message.tokens;
2992
+ const input = toNumber(tokens.input);
2993
+ const output = toNumber(tokens.output);
2994
+ const total = toNumber(tokens.total);
2995
+ if (input === 0 && output === 0 && total === 0) continue;
2996
+ sessionInput += input;
2997
+ sessionOutput += output;
2998
+ sessionTotal += total > 0 ? total : input + output;
2999
+ const messageTs = parseTimestamp(message.timestamp);
3000
+ if (messageTs > sessionTs) sessionTs = messageTs;
3001
+ }
3002
+ if (sessionTotal <= 0) return { inputTokens: 0, outputTokens: 0, totalTokens: 0, timestampMs: sessionTs };
3003
+ return {
3004
+ inputTokens: sessionInput,
3005
+ outputTokens: sessionOutput,
3006
+ totalTokens: sessionTotal,
3007
+ timestampMs: sessionTs
3008
+ };
3009
+ }
3010
+ function collectGeminiSessionUsages() {
3011
+ const geminiTmp = join8(homedir2(), ".gemini", "tmp");
3012
+ if (!existsSync6(geminiTmp)) return [];
3013
+ const usages = [];
3014
+ let entries = [];
3015
+ try {
3016
+ entries = readdirSync2(geminiTmp, { withFileTypes: true });
3017
+ } catch {
3018
+ return usages;
3019
+ }
3020
+ for (const profile of entries) {
3021
+ if (!profile.isDirectory()) continue;
3022
+ const chatsDir = join8(geminiTmp, profile.name, "chats");
3023
+ if (!existsSync6(chatsDir)) continue;
3024
+ let sessions = [];
3025
+ try {
3026
+ sessions = readdirSync2(chatsDir).filter((name) => name.startsWith("session-") && (name.endsWith(".json") || name.endsWith(".jsonl")));
3027
+ } catch {
3028
+ continue;
3029
+ }
3030
+ for (const sessionFile of sessions) {
3031
+ const sessionPath = join8(chatsDir, sessionFile);
3032
+ try {
3033
+ const usage = aggregateGeminiSessionUsageFromJson(readFileSync5(sessionPath, "utf8"));
3034
+ if (usage.totalTokens > 0) usages.push(usage);
3035
+ } catch {
3036
+ }
3037
+ }
3038
+ }
3039
+ return usages;
3040
+ }
2633
3041
  async function collectGeminiUsage() {
2634
3042
  const available = await whichExists("gemini");
2635
3043
  if (!available) return null;
3044
+ let version = null;
3045
+ try {
3046
+ const { stdout } = await execFileAsync("gemini", ["--version"], { encoding: "utf8", timeout: 5e3 });
3047
+ const trimmed = stdout.trim();
3048
+ if (/^\d+\.\d+/.test(trimmed)) version = trimmed;
3049
+ } catch {
3050
+ }
3051
+ let account = null;
3052
+ const accountsPath = join8(homedir2(), ".gemini", "google_accounts.json");
3053
+ if (existsSync6(accountsPath)) {
3054
+ try {
3055
+ const data = JSON.parse(readFileSync5(accountsPath, "utf8"));
3056
+ if (typeof data.active === "string" && data.active.includes("@")) {
3057
+ account = data.active;
3058
+ }
3059
+ } catch {
3060
+ }
3061
+ }
2636
3062
  const todayStart = computeTodayStart();
2637
3063
  const weekStart = computeWeekStart();
2638
- const nextResetAt = computeNextMonday().toISOString();
3064
+ let nextResetAt = computeNextMonday().toISOString();
3065
+ const last5hStart = computeLastHoursStart(5);
2639
3066
  const models = [];
2640
3067
  try {
2641
3068
  const { stdout: binPath } = await execFileAsync("which", ["gemini"], { encoding: "utf8", timeout: 3e3 });
@@ -2670,20 +3097,99 @@ async function collectGeminiUsage() {
2670
3097
  } catch {
2671
3098
  }
2672
3099
  }
3100
+ const todayMs = todayStart.getTime();
3101
+ const weekMs = weekStart.getTime();
3102
+ const last5hMs = last5hStart.getTime();
3103
+ let resetInfo = "Usage from local Gemini session logs";
3104
+ let todayInputTokens = 0;
3105
+ let todayOutputTokens = 0;
3106
+ let todaySessions = 0;
3107
+ let weekInputTokens = 0;
3108
+ let weekOutputTokens = 0;
3109
+ let weekSessions = 0;
3110
+ let last5hInputTokens = 0;
3111
+ let last5hOutputTokens = 0;
3112
+ let last5hSessions = 0;
3113
+ let allTimeInputTokens = 0;
3114
+ let allTimeOutputTokens = 0;
3115
+ let allTimeSessions = 0;
3116
+ const sessionUsages = collectGeminiSessionUsages();
3117
+ for (const usage of sessionUsages) {
3118
+ allTimeInputTokens += usage.inputTokens;
3119
+ allTimeOutputTokens += usage.outputTokens;
3120
+ allTimeSessions++;
3121
+ if (usage.timestampMs >= todayMs) {
3122
+ todayInputTokens += usage.inputTokens;
3123
+ todayOutputTokens += usage.outputTokens;
3124
+ todaySessions++;
3125
+ }
3126
+ if (usage.timestampMs >= weekMs) {
3127
+ weekInputTokens += usage.inputTokens;
3128
+ weekOutputTokens += usage.outputTokens;
3129
+ weekSessions++;
3130
+ }
3131
+ if (usage.timestampMs >= last5hMs) {
3132
+ last5hInputTokens += usage.inputTokens;
3133
+ last5hOutputTokens += usage.outputTokens;
3134
+ last5hSessions++;
3135
+ }
3136
+ }
3137
+ const statusUsage = await collectGeminiUsageFromCli();
3138
+ if (statusUsage) {
3139
+ if (statusUsage.currentModel) {
3140
+ currentModel = statusUsage.currentModel;
3141
+ }
3142
+ if (statusUsage.allTimeInputTokens !== null || statusUsage.allTimeOutputTokens !== null) {
3143
+ allTimeInputTokens = statusUsage.allTimeInputTokens ?? allTimeInputTokens;
3144
+ allTimeOutputTokens = statusUsage.allTimeOutputTokens ?? allTimeOutputTokens;
3145
+ if (statusUsage.allTimeSessions !== null) allTimeSessions = statusUsage.allTimeSessions;
3146
+ }
3147
+ if (statusUsage.todayInputTokens !== null || statusUsage.todayOutputTokens !== null) {
3148
+ todayInputTokens = statusUsage.todayInputTokens ?? todayInputTokens;
3149
+ todayOutputTokens = statusUsage.todayOutputTokens ?? todayOutputTokens;
3150
+ if (statusUsage.todaySessions !== null) todaySessions = statusUsage.todaySessions;
3151
+ }
3152
+ if (statusUsage.thisWeekInputTokens !== null || statusUsage.thisWeekOutputTokens !== null) {
3153
+ weekInputTokens = statusUsage.thisWeekInputTokens ?? weekInputTokens;
3154
+ weekOutputTokens = statusUsage.thisWeekOutputTokens ?? weekOutputTokens;
3155
+ if (statusUsage.thisWeekSessions !== null) weekSessions = statusUsage.thisWeekSessions;
3156
+ }
3157
+ if (statusUsage.last5HoursInputTokens !== null || statusUsage.last5HoursOutputTokens !== null) {
3158
+ last5hInputTokens = statusUsage.last5HoursInputTokens ?? last5hInputTokens;
3159
+ last5hOutputTokens = statusUsage.last5HoursOutputTokens ?? last5hOutputTokens;
3160
+ if (statusUsage.last5HoursSessions !== null) last5hSessions = statusUsage.last5HoursSessions;
3161
+ }
3162
+ if (statusUsage.resetInfo) {
3163
+ resetInfo = statusUsage.resetInfo;
3164
+ }
3165
+ if (statusUsage.nextResetAt) {
3166
+ nextResetAt = statusUsage.nextResetAt;
3167
+ }
3168
+ if (statusUsage.weeklyLimitEstimate !== null && statusUsage.weeklyPercentUsed !== null) {
3169
+ resetInfo = `Estimated weekly used: ${statusUsage.weeklyPercentUsed}%`;
3170
+ }
3171
+ }
2673
3172
  return {
2674
3173
  name: "gemini",
2675
3174
  available,
2676
3175
  models,
2677
3176
  currentModel,
2678
3177
  usage: {
2679
- today: makePeriod(0, 0, 0, todayStart.toISOString()),
2680
- thisWeek: makePeriod(0, 0, 0, weekStart.toISOString()),
2681
- allTime: makePeriod(0, 0, 0, "")
3178
+ today: makePeriod(todayInputTokens, todayOutputTokens, todaySessions, todayStart.toISOString()),
3179
+ thisWeek: makePeriod(weekInputTokens, weekOutputTokens, weekSessions, weekStart.toISOString()),
3180
+ last5Hours: makePeriod(last5hInputTokens, last5hOutputTokens, last5hSessions, last5hStart.toISOString()),
3181
+ allTime: makePeriod(allTimeInputTokens, allTimeOutputTokens, allTimeSessions, "")
2682
3182
  },
2683
- resetInfo: "Usage data not available for Gemini CLI",
3183
+ resetInfo,
2684
3184
  nextResetAt,
2685
3185
  weeklyLimitEstimate: null,
2686
- percentUsed: null
3186
+ percentUsed: statusUsage?.weeklyPercentUsed ?? null,
3187
+ version: statusUsage?.version ?? version,
3188
+ plan: statusUsage?.plan ?? null,
3189
+ account: statusUsage?.account ?? account,
3190
+ effort: null,
3191
+ // Gemini has no effort/reasoning concept
3192
+ rateLimits: statusUsage?.rateLimits ?? []
2687
3193
  };
2688
3194
  }
2689
3195
  var usageCache = null;
@@ -2705,6 +3211,13 @@ async function collectProvidersUsage() {
2705
3211
  usageCacheAt = Date.now();
2706
3212
  return usageCache;
2707
3213
  }
3214
+ async function collectProviderUsage(providerName) {
3215
+ const normalized = normalizeProviderName(providerName);
3216
+ if (!normalized) return null;
3217
+ if (normalized === "claude") return collectClaudeUsage();
3218
+ if (normalized === "codex") return collectCodexUsage();
3219
+ return collectGeminiUsage();
3220
+ }
2708
3221
 
2709
3222
  // src/routes/state.ts
2710
3223
  import { existsSync as existsSync8, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
@@ -3104,6 +3617,19 @@ function registerStateRoutes(app, state) {
3104
3617
  app.get("/api/parallelism", async (c) => {
3105
3618
  return c.json(analyzeParallelizability(state.issues));
3106
3619
  });
3620
+ app.get("/api/providers/:slug/usage", async (c) => {
3621
+ const provider = c.req.param("slug") || "";
3622
+ try {
3623
+ const usage = await collectProviderUsage(provider);
3624
+ return c.json({
3625
+ providers: usage ? [usage] : [],
3626
+ collectedAt: (/* @__PURE__ */ new Date()).toISOString()
3627
+ });
3628
+ } catch (error) {
3629
+ logger.error({ err: error, provider }, "Failed to collect provider usage");
3630
+ return c.json({ providers: [] }, 500);
3631
+ }
3632
+ });
3107
3633
  app.get("/api/providers/usage", async (c) => {
3108
3634
  try {
3109
3635
  const usage = await collectProvidersUsage();
@@ -3274,7 +3800,7 @@ function registerStateRoutes(app, state) {
3274
3800
  if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
3275
3801
  const issue = findIssue(state, issueId);
3276
3802
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3277
- const { dryMerge } = await import("./workspace-3PV5PTR5.js");
3803
+ const { dryMerge } = await import("./workspace-5XOCMZ57.js");
3278
3804
  const result = dryMerge(issue);
3279
3805
  return c.json({ ok: true, ...result });
3280
3806
  } catch (error) {
@@ -3289,7 +3815,7 @@ function registerStateRoutes(app, state) {
3289
3815
  if (!issueId) return c.json({ ok: false, error: "Issue id is required." }, 400);
3290
3816
  const issue = findIssue(state, issueId);
3291
3817
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3292
- const { rebaseWorktree } = await import("./workspace-3PV5PTR5.js");
3818
+ const { rebaseWorktree } = await import("./workspace-5XOCMZ57.js");
3293
3819
  const result = rebaseWorktree(issue);
3294
3820
  if (result.success) {
3295
3821
  addEvent(state, issue.id, "info", `Branch ${issue.branchName} rebased onto ${issue.baseBranch}.`);
@@ -3421,7 +3947,7 @@ function registerStateRoutes(app, state) {
3421
3947
  const issue = findIssue(state, issueId);
3422
3948
  if (!issue) return c.json({ ok: false, error: "Issue not found." }, 404);
3423
3949
  try {
3424
- const { getIssueTransitionHistory } = await import("./issue-state-machine-ETAJLBS6.js");
3950
+ const { getIssueTransitionHistory } = await import("./issue-state-machine-NBSYRDZW.js");
3425
3951
  const limit = parseInt(c.req.query("limit") ?? "50", 10);
3426
3952
  const offset = parseInt(c.req.query("offset") ?? "0", 10);
3427
3953
  const transitions = await getIssueTransitionHistory(issue.id, { limit, offset });
@@ -3432,7 +3958,7 @@ function registerStateRoutes(app, state) {
3432
3958
  });
3433
3959
  app.get("/api/state-machine/transitions", async (c) => {
3434
3960
  try {
3435
- const { getStateMachineTransitions } = await import("./issue-state-machine-ETAJLBS6.js");
3961
+ const { getStateMachineTransitions } = await import("./issue-state-machine-NBSYRDZW.js");
3436
3962
  return c.json({ ok: true, transitions: getStateMachineTransitions() });
3437
3963
  } catch (error) {
3438
3964
  return c.json({ ok: false, error: error instanceof Error ? error.message : String(error) }, 500);
@@ -3440,7 +3966,7 @@ function registerStateRoutes(app, state) {
3440
3966
  });
3441
3967
  app.get("/api/state-machine/visualize", async (c) => {
3442
3968
  try {
3443
- const { visualizeStateMachine } = await import("./issue-state-machine-ETAJLBS6.js");
3969
+ const { visualizeStateMachine } = await import("./issue-state-machine-NBSYRDZW.js");
3444
3970
  const dot = visualizeStateMachine();
3445
3971
  if (!dot) return c.json({ ok: false, error: "Visualization not available." }, 404);
3446
3972
  return c.json({ ok: true, dot });
@@ -3762,6 +4288,7 @@ var SETTING_ID_WORKFLOW_CONFIG = "runtime.workflowConfig";
3762
4288
  var SETTING_ID_TEST_COMMAND = "runtime.testCommand";
3763
4289
  var SETTING_ID_MERGE_MODE = "runtime.mergeMode";
3764
4290
  var SETTING_ID_PR_BASE_BRANCH = "runtime.prBaseBranch";
4291
+ var SETTING_ID_AUTO_REVIEW_APPROVAL = "runtime.autoReviewApproval";
3765
4292
  async function loadRuntimeSettings() {
3766
4293
  return loadPersistedSettings();
3767
4294
  }
@@ -3780,7 +4307,8 @@ var RUNTIME_CONFIG_SETTING_IDS = /* @__PURE__ */ new Set([
3780
4307
  SETTING_ID_DEFAULT_EFFORT,
3781
4308
  SETTING_ID_TEST_COMMAND,
3782
4309
  SETTING_ID_MERGE_MODE,
3783
- SETTING_ID_PR_BASE_BRANCH
4310
+ SETTING_ID_PR_BASE_BRANCH,
4311
+ SETTING_ID_AUTO_REVIEW_APPROVAL
3784
4312
  ]);
3785
4313
  var VALID_REASONING_EFFORTS = /* @__PURE__ */ new Set(["low", "medium", "high", "extra-high"]);
3786
4314
  function parseIntegerSetting(value) {
@@ -3842,7 +4370,8 @@ function buildRuntimeConfigSettings(config, source) {
3842
4370
  { id: SETTING_ID_DEFAULT_EFFORT, scope: "runtime", value: config.defaultEffort, source, updatedAt },
3843
4371
  { id: SETTING_ID_TEST_COMMAND, scope: "runtime", value: config.testCommand ?? "", source, updatedAt },
3844
4372
  { 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 }
4373
+ { id: SETTING_ID_PR_BASE_BRANCH, scope: "runtime", value: config.prBaseBranch ?? "", source, updatedAt },
4374
+ { id: SETTING_ID_AUTO_REVIEW_APPROVAL, scope: "runtime", value: config.autoReviewApproval, source, updatedAt }
3846
4375
  ];
3847
4376
  }
3848
4377
  function applyPersistedSettings(config, settings) {
@@ -3951,6 +4480,10 @@ function applyPersistedSettings(config, settings) {
3951
4480
  }
3952
4481
  break;
3953
4482
  }
4483
+ case SETTING_ID_AUTO_REVIEW_APPROVAL: {
4484
+ nextConfig.autoReviewApproval = toBooleanValue(setting.value, true);
4485
+ break;
4486
+ }
3954
4487
  default:
3955
4488
  break;
3956
4489
  }
@@ -5719,8 +6252,11 @@ async function startApiServer(state, port) {
5719
6252
  "GET /analytics": () => serveAppShell(),
5720
6253
  "GET /agents": () => serveAppShell(),
5721
6254
  "GET /settings": () => serveAppShell(),
6255
+ "GET /settings/project": () => serveAppShell(),
5722
6256
  "GET /settings/general": () => serveAppShell(),
6257
+ "GET /settings/agents": () => serveAppShell(),
5723
6258
  "GET /settings/notifications": () => serveAppShell(),
6259
+ "GET /settings/preferences": () => serveAppShell(),
5724
6260
  "GET /settings/workflow": () => serveAppShell(),
5725
6261
  "GET /settings/providers": () => serveAppShell(),
5726
6262
  "GET /api/health": (c) => c.json({ status: state.booting ? "booting" : "ready" })
@@ -6628,7 +7164,8 @@ var AGENCY_AGENTS_EXCLUDED_DIRS = /* @__PURE__ */ new Set([
6628
7164
  var REFERENCE_REPOSITORY_PARSERS = {
6629
7165
  ring: collectStandardArtifacts,
6630
7166
  "agency-agents": collectAgencyArtifacts,
6631
- impeccable: collectImpeccableArtifacts
7167
+ impeccable: collectImpeccableArtifacts,
7168
+ "everything-claude-code": collectStandardArtifacts
6632
7169
  };
6633
7170
  function runGit(args, cwd) {
6634
7171
  return execFileSync2("git", args, {
@@ -7091,6 +7628,7 @@ function deriveConfig(args) {
7091
7628
  },
7092
7629
  maxConcurrentByState: {},
7093
7630
  runMode: "filesystem",
7631
+ autoReviewApproval: true,
7094
7632
  afterCreateHook: env4.FIFONY_AFTER_CREATE_HOOK ?? "",
7095
7633
  beforeRunHook: env4.FIFONY_BEFORE_RUN_HOOK ?? "",
7096
7634
  afterRunHook: env4.FIFONY_AFTER_RUN_HOOK ?? "",
@@ -7331,7 +7869,7 @@ async function handleStatePatch(state, issue, payload) {
7331
7869
  }
7332
7870
  if (nextState === "Running" && sourceState === "Queued") {
7333
7871
  try {
7334
- const { enqueue: enqueue2 } = await import("./queue-workers-IEI23UUO.js");
7872
+ const { enqueue: enqueue2 } = await import("./queue-workers-OHPJKAPM.js");
7335
7873
  await enqueue2(issue, "execute");
7336
7874
  } catch (error) {
7337
7875
  logger.warn({ issueId: issue.id, err: error }, "[Issues] Failed to enqueue after manual Running transition");
@@ -7409,9 +7947,15 @@ async function handleReviewStage(state, issue, workspacePath, startTs, routedPro
7409
7947
  markIssueDirty(issue.id);
7410
7948
  const container = getContainer();
7411
7949
  const reviewer = routedProviders.find((p) => p.role === "reviewer");
7950
+ const autoReviewApproval = state.config.autoReviewApproval !== false;
7412
7951
  if (!reviewer) {
7413
- issue.mergedReason = "Auto-approved: no reviewer configured.";
7414
- await transitionIssueCommand({ issue, target: "Approved", note: `No reviewer configured; auto-approved for ${issue.identifier}.` }, container);
7952
+ if (autoReviewApproval) {
7953
+ issue.mergedReason = "Auto-approved: no reviewer configured.";
7954
+ await transitionIssueCommand({ issue, target: "Approved", note: `No reviewer configured; auto-approved for ${issue.identifier}.` }, container);
7955
+ return;
7956
+ }
7957
+ issue.mergedReason = "Reviewer not configured; manual approval required.";
7958
+ await transitionIssueCommand({ issue, target: "PendingDecision", note: `No reviewer configured; manual approval required for ${issue.identifier}.` }, container);
7415
7959
  return;
7416
7960
  }
7417
7961
  addEvent(state, issue.id, "info", `Review provider: ${reviewer.role}:${reviewer.provider}${reviewer.model ? `/${reviewer.model}` : ""}${reviewer.profile ? `:${reviewer.profile}` : ""}.`);
@@ -7460,8 +8004,9 @@ async function handleReviewStage(state, issue, workspacePath, startTs, routedPro
7460
8004
  logger.warn({ err: String(vErr) }, "[Agent] Failed to write versioned review artifacts");
7461
8005
  }
7462
8006
  if (reviewResult.success) {
7463
- issue.mergedReason = `Auto-approved by reviewer in ${reviewResult.turns} turn(s).`;
8007
+ issue.mergedReason = autoReviewApproval ? `Auto-approved by reviewer in ${reviewResult.turns} turn(s).` : `Reviewer completed for ${issue.identifier}; waiting for manual approval.`;
7464
8008
  await transitionIssueCommand({ issue, target: "PendingDecision", note: `Reviewer completed for ${issue.identifier}.` }, container);
8009
+ if (!autoReviewApproval) return;
7465
8010
  const validation = await runValidationGate(issue, state.config);
7466
8011
  if (validation) {
7467
8012
  issue.validationResult = validation;
@@ -7592,7 +8137,7 @@ async function runIssueOnce(state, issue, running) {
7592
8137
  const { workspacePath, promptText, promptFile } = await prepareWorkspace(issue, state, state.config.defaultBranch);
7593
8138
  container.issueRepository.markDirty(issue.id);
7594
8139
  try {
7595
- const { getIssueStateResource: getIssueStateResource2 } = await import("./store-MTE6H7WQ.js");
8140
+ const { getIssueStateResource: getIssueStateResource2 } = await import("./store-FFHHRNVI.js");
7596
8141
  const res = getIssueStateResource2();
7597
8142
  if (res) {
7598
8143
  await res.patch(issue.id, {
@@ -7704,4 +8249,4 @@ export {
7704
8249
  syncReferenceRepositories,
7705
8250
  importReferenceArtifacts
7706
8251
  };
7707
- //# sourceMappingURL=chunk-7AMUAUY5.js.map
8252
+ //# sourceMappingURL=chunk-FAFGDK62.js.map