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
@@ -35,7 +35,7 @@ import {
35
35
  writeFileSync
36
36
  } from "fs";
37
37
  import { join as join6 } from "path";
38
- import { env } from "process";
38
+ import { env as env2 } from "process";
39
39
  import { spawn } from "child_process";
40
40
 
41
41
  // src/agents/providers.ts
@@ -309,9 +309,531 @@ function extractPlanDirs(plan) {
309
309
  return [...dirs];
310
310
  }
311
311
 
312
+ // src/agents/adapters/usage.ts
313
+ import { cwd, env } from "process";
314
+ function sleep(ms) {
315
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
316
+ }
317
+ async function createPtyProcess(command, args) {
318
+ try {
319
+ const nodePty = await import("node-pty");
320
+ if (typeof nodePty.spawn !== "function") {
321
+ return null;
322
+ }
323
+ return nodePty.spawn(command, args, {
324
+ name: "xterm-color",
325
+ cols: 160,
326
+ rows: 48,
327
+ cwd: cwd(),
328
+ env
329
+ });
330
+ } catch (error) {
331
+ logger.debug(`Failed to initialize node-pty for ${command}: ${String(error)}`);
332
+ return null;
333
+ }
334
+ }
335
+ function stripTerminalEscapes(input) {
336
+ return input.replace(/\x1B\[[?>=!]?[0-9;]*[A-HJKSTdfG]/g, " ").replace(/\x1B\[[?>=!]?[0-9;]*[a-zI-Ri-z~]/g, "").replace(/\x1B\[[?>=!]?[0-9;]*[A-Za-z~]/g, "").replace(/\x1B\][^\x07\x1B]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[()#][A-Za-z0-9]/g, "").replace(/\x1B[A-Za-z]/g, "").replace(/[^\x20-\x7E\r\n\t]/g, "").replace(/[ \t]{2,}/g, " ");
337
+ }
338
+ function parseNumber(value) {
339
+ if (!value) return 0;
340
+ const clean = value.replace(/[^\d]/g, "");
341
+ if (!clean) return 0;
342
+ const parsed = Number.parseInt(clean, 10);
343
+ return Number.isFinite(parsed) ? parsed : 0;
344
+ }
345
+ function parseTokenQuantity(value) {
346
+ if (!value) return 0;
347
+ const match = value.replace(/,/g, "").trim().match(/^([0-9]+(?:\.[0-9]+)?)([kKmMbBtT]?)$/);
348
+ if (!match) {
349
+ const fallback = parseNumber(value);
350
+ return fallback;
351
+ }
352
+ const number = Number.parseFloat(match[1]);
353
+ if (!Number.isFinite(number)) return 0;
354
+ const suffix = match[2].toLowerCase();
355
+ if (suffix === "t") return number * 1e12;
356
+ if (suffix === "b") return number * 1e9;
357
+ if (suffix === "m") return number * 1e6;
358
+ if (suffix === "k") return number * 1e3;
359
+ return number;
360
+ }
361
+ function parsePercent(value) {
362
+ if (!value) return null;
363
+ const parsed = Number.parseFloat(value.replace("%", ""));
364
+ return Number.isFinite(parsed) ? parsed : null;
365
+ }
366
+ function keepLargest(current, incoming) {
367
+ if (!Number.isFinite(incoming)) return current ?? 0;
368
+ if (incoming <= 0) return current ?? 0;
369
+ return current === null ? incoming : Math.max(current, incoming);
370
+ }
371
+ var MONTH_MAP = {
372
+ jan: 0,
373
+ feb: 1,
374
+ mar: 2,
375
+ apr: 3,
376
+ may: 4,
377
+ jun: 5,
378
+ jul: 6,
379
+ aug: 7,
380
+ sep: 8,
381
+ oct: 9,
382
+ nov: 10,
383
+ dec: 11
384
+ };
385
+ function toMonthIndex(value) {
386
+ const index = MONTH_MAP[value.toLowerCase().slice(0, 3)];
387
+ return typeof index === "number" ? index : null;
388
+ }
389
+ function normalizeResetText(value) {
390
+ return value.replace(/\([^)]*\)/g, " ").replace(/\s+/g, " ").trim();
391
+ }
392
+ function parseResetTime(value) {
393
+ const match = value.trim().match(/^(\d{1,2})(?::(\d{2}))?\s*([AaPp][Mm])?$/);
394
+ if (!match) return null;
395
+ let hour = Number.parseInt(match[1], 10);
396
+ const minute = Number.parseInt(match[2] || "0", 10);
397
+ if (!Number.isFinite(hour) || !Number.isFinite(minute)) return null;
398
+ if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null;
399
+ const suffix = match[3]?.toLowerCase();
400
+ if (suffix === "pm" && hour !== 12) hour += 12;
401
+ if (suffix === "am" && hour === 12) hour = 0;
402
+ return { hour, minute };
403
+ }
404
+ function parseResetDateFromText(raw) {
405
+ const text = normalizeResetText(raw);
406
+ if (!text) return null;
407
+ const now2 = /* @__PURE__ */ new Date();
408
+ const explicitDateMatch = text.match(/([A-Za-z]{3,9})\s*(\d{1,2})(?:,?|\s+)(\d{1,2}:\d{2}(?:\s*[AaPp][Mm])?)/i) || text.match(/(\d{1,2})\s+([A-Za-z]{3,9})(?:,\s*)?(\d{1,2}:\d{2}(?:\s*[AaPp][Mm])?)/i);
409
+ if (explicitDateMatch) {
410
+ const [, monthRaw, dayRaw, timeRaw] = explicitDateMatch;
411
+ const day = Number.parseInt(dayRaw, 10);
412
+ const monthIndex = toMonthIndex(monthRaw);
413
+ const time2 = parseResetTime(timeRaw);
414
+ if (!Number.isNaN(day) && monthIndex !== null && time2) {
415
+ const candidate = new Date(now2);
416
+ candidate.setMonth(monthIndex);
417
+ candidate.setDate(day);
418
+ candidate.setHours(time2.hour, time2.minute, 0, 0);
419
+ if (candidate.getTime() <= now2.getTime()) {
420
+ candidate.setFullYear(now2.getFullYear() + 1);
421
+ }
422
+ return candidate.toISOString();
423
+ }
424
+ }
425
+ const timeMatch = text.match(/(\d{1,2}:\d{2}(?:\s*[AaPp][Mm])?)/i) || text.match(/(\d{1,2})(?:\s*([AaPp][Mm]))/i);
426
+ const time = timeMatch?.[1] ? parseResetTime(timeMatch[1]) : null;
427
+ if (time) {
428
+ const candidate = new Date(now2);
429
+ candidate.setHours(time.hour, time.minute, 0, 0);
430
+ if (candidate.getTime() <= now2.getTime()) {
431
+ candidate.setDate(candidate.getDate() + 1);
432
+ }
433
+ return candidate.toISOString();
434
+ }
435
+ return null;
436
+ }
437
+ function initSnapshot(raw) {
438
+ return {
439
+ currentModel: null,
440
+ allTimeInputTokens: null,
441
+ allTimeOutputTokens: null,
442
+ todayInputTokens: null,
443
+ todayOutputTokens: null,
444
+ thisWeekInputTokens: null,
445
+ thisWeekOutputTokens: null,
446
+ last5HoursInputTokens: null,
447
+ last5HoursOutputTokens: null,
448
+ allTimeSessions: null,
449
+ todaySessions: null,
450
+ thisWeekSessions: null,
451
+ last5HoursSessions: null,
452
+ weeklyLimitEstimate: null,
453
+ weeklyPercentUsed: null,
454
+ resetInfo: null,
455
+ nextResetAt: null,
456
+ version: null,
457
+ plan: null,
458
+ account: null,
459
+ effort: null,
460
+ sessionPercentUsed: null,
461
+ sessionResetInfo: null,
462
+ rateLimits: [],
463
+ raw
464
+ };
465
+ }
466
+ function waitForOutput(getOutput, pattern, timeoutMs, pollMs = 100) {
467
+ const deadline = Date.now() + timeoutMs;
468
+ return new Promise((resolve2) => {
469
+ const check = () => {
470
+ if (pattern.test(getOutput())) return resolve2(true);
471
+ if (Date.now() >= deadline) return resolve2(false);
472
+ setTimeout(check, pollMs);
473
+ };
474
+ check();
475
+ });
476
+ }
477
+ var TRUST_PROMPT_RE = /trust this folder|trust this project|safety check|Do you trust/i;
478
+ var CLI_READY_RE = /[❯›▶>]\s|Type your message|for shortcuts|\/model to change/;
479
+ async function collectProviderStatusText(command, args, statusCommand) {
480
+ try {
481
+ const ptyProcess = await createPtyProcess(command, args);
482
+ if (!ptyProcess) return null;
483
+ let output = "";
484
+ ptyProcess.onData((data) => {
485
+ output += data;
486
+ });
487
+ const gotPrompt = await waitForOutput(
488
+ () => output,
489
+ new RegExp(`${TRUST_PROMPT_RE.source}|${CLI_READY_RE.source}`, "i"),
490
+ 5e3,
491
+ 100
492
+ );
493
+ if (gotPrompt && TRUST_PROMPT_RE.test(output)) {
494
+ logger.debug(`Trust prompt detected for ${command}, auto-accepting`);
495
+ ptyProcess.write("\r");
496
+ await waitForOutput(() => output, CLI_READY_RE, 5e3, 100);
497
+ } else if (!gotPrompt) {
498
+ logger.debug(`PTY prompt not detected for ${command}, sending command after fallback wait`);
499
+ await sleep(3e3);
500
+ }
501
+ let prevLen = output.length;
502
+ await sleep(800);
503
+ if (output.length !== prevLen) {
504
+ await sleep(1200);
505
+ }
506
+ ptyProcess.write(`${statusCommand}\r`);
507
+ await sleep(3e3);
508
+ ptyProcess.write("\x1B");
509
+ await sleep(200);
510
+ ptyProcess.write("");
511
+ await sleep(200);
512
+ try {
513
+ ptyProcess.kill();
514
+ } catch {
515
+ }
516
+ return stripTerminalEscapes(output).trim();
517
+ } catch (error) {
518
+ logger.debug(`Failed collecting status via pty for ${command}: ${String(error)}`);
519
+ return null;
520
+ }
521
+ }
522
+ function parseClaudeUsageHeading(line) {
523
+ if (/current week.*all models/i.test(line)) return { section: "current-week-all" };
524
+ const modelWeekMatch = line.match(/current week\s*\((\w+)\s+only\)/i);
525
+ if (modelWeekMatch) return { section: "current-week-model", modelScope: modelWeekMatch[1].toLowerCase() };
526
+ if (/current session/i.test(line)) return { section: "current-session" };
527
+ return null;
528
+ }
529
+ function formatClaudeModelFromLabel(label) {
530
+ const normalized = label.trim().toLowerCase();
531
+ const compact = normalized.replace(/\s+/g, "-");
532
+ if (compact.includes("opus")) return `claude-${compact}`;
533
+ if (compact.includes("sonnet")) return `claude-${compact}`;
534
+ if (compact.includes("haiku")) return `claude-${compact}`;
535
+ if (compact.startsWith("claude-")) return compact;
536
+ return label.trim();
537
+ }
538
+ function parseClaudeUsageFromStatus(raw) {
539
+ const base = initSnapshot(raw);
540
+ base.weeklyPercentUsed = null;
541
+ const lines = raw.split(/[\r\n]+/).map((line) => line.trim()).filter(Boolean);
542
+ let currentHeading = null;
543
+ let lastPercentSection = null;
544
+ let lastPercentUsed = null;
545
+ for (const line of lines) {
546
+ const normalized = line.toLowerCase();
547
+ if (/^esc to cancel/i.test(normalized)) continue;
548
+ if (/^loading/i.test(normalized)) continue;
549
+ if (/^status dialog/i.test(normalized)) continue;
550
+ const versionMatch = line.match(/Claude Code v(\d+\.\d+\.\d+)/i);
551
+ if (versionMatch?.[1]) {
552
+ base.version = versionMatch[1];
553
+ continue;
554
+ }
555
+ const effortMatch = line.match(/with\s+(high|medium|low|extra\s*high)\s+effort/i);
556
+ if (effortMatch?.[1]) {
557
+ base.effort = effortMatch[1].toLowerCase();
558
+ const afterEffort = line.slice(line.indexOf(effortMatch[0]) + effortMatch[0].length).trim();
559
+ if (afterEffort && /^[A-Z]/.test(afterEffort)) {
560
+ base.plan = afterEffort;
561
+ }
562
+ }
563
+ const heading = parseClaudeUsageHeading(line);
564
+ if (heading) {
565
+ currentHeading = heading;
566
+ continue;
567
+ }
568
+ const modelMatch = line.match(/\b(opus|sonnet|haiku)\s*\d+(?:\.\d+)?(?:\s*-\d+)?/i);
569
+ if (modelMatch?.[0]) {
570
+ base.currentModel = formatClaudeModelFromLabel(modelMatch[0]);
571
+ }
572
+ const percentMatch = normalized.match(/(\d+)%\s*used/i);
573
+ if (percentMatch?.[1] && currentHeading) {
574
+ const used = parseInt(percentMatch[1], 10);
575
+ lastPercentSection = currentHeading;
576
+ lastPercentUsed = used;
577
+ if (currentHeading.section === "current-week-all") {
578
+ base.weeklyPercentUsed = keepLargest(base.weeklyPercentUsed, used);
579
+ }
580
+ if (currentHeading.section === "current-session") {
581
+ base.sessionPercentUsed = used;
582
+ base.currentModel = base.currentModel || "claude";
583
+ }
584
+ const scope = currentHeading.section === "current-session" ? "session" : currentHeading.section === "current-week-model" ? currentHeading.modelScope || "unknown" : "global";
585
+ const period = currentHeading.section === "current-session" ? "session" : "weekly";
586
+ base.rateLimits.push({ scope, period, percentUsed: used, resetInfo: null, nextResetAt: null });
587
+ }
588
+ const resetMatch = line.match(/Rese\w*\s+(.+?)$/i);
589
+ if (resetMatch?.[1] && lastPercentSection) {
590
+ const resetText = resetMatch[1].trim();
591
+ const nextResetAt = parseResetDateFromText(resetText);
592
+ if (lastPercentSection.section === "current-week-all") {
593
+ base.resetInfo = `Current week resets ${resetText}`;
594
+ base.nextResetAt = nextResetAt || base.nextResetAt;
595
+ }
596
+ if (lastPercentSection.section === "current-session") {
597
+ base.sessionResetInfo = `Resets ${resetText}`;
598
+ }
599
+ const last = base.rateLimits[base.rateLimits.length - 1];
600
+ if (last) {
601
+ last.resetInfo = `Resets ${resetText}`;
602
+ last.nextResetAt = nextResetAt;
603
+ }
604
+ lastPercentSection = null;
605
+ lastPercentUsed = null;
606
+ }
607
+ }
608
+ const allModelsLine = lines.find((line) => parseClaudeUsageHeading(line)?.section === "current-week-all");
609
+ if (!base.currentModel && allModelsLine) {
610
+ const modelName = allModelsLine.match(/\b(opus|sonnet|haiku)\s*\d+(?:\.\d+)?(?:\s*-\d+)?/i)?.[0];
611
+ if (modelName) base.currentModel = formatClaudeModelFromLabel(modelName);
612
+ }
613
+ return base;
614
+ }
615
+ function parseCodexUsageFromStatus(raw) {
616
+ const base = initSnapshot(raw);
617
+ base.weeklyPercentUsed = null;
618
+ base.weeklyLimitEstimate = null;
619
+ const lines = raw.split(/[\r\n]+/).map((line) => line.trim()).filter(Boolean);
620
+ let limitScope = "global";
621
+ for (const line of lines) {
622
+ const normalizedLine = line.replace(/[│]/g, " ").trim();
623
+ if (!normalizedLine) continue;
624
+ if (/^[─╭╰]+$/.test(normalizedLine) || /^╮|^╯/.test(normalizedLine)) continue;
625
+ const versionMatch = normalizedLine.match(/(?:OpenAI\s+)?Codex\s+\(v([^)]+)\)/i);
626
+ if (versionMatch?.[1] && !base.version) {
627
+ base.version = versionMatch[1];
628
+ continue;
629
+ }
630
+ const modelDetailMatch = normalizedLine.match(
631
+ /^Model:\s+([a-z0-9][a-z0-9._\-\/]+)\s+\(([^)]+)\)/i
632
+ );
633
+ if (modelDetailMatch?.[1]) {
634
+ base.currentModel = modelDetailMatch[1];
635
+ const reasoningMatch = modelDetailMatch[2].match(/reasoning\s+(\w+)/i);
636
+ if (reasoningMatch?.[1]) {
637
+ base.effort = reasoningMatch[1].toLowerCase();
638
+ }
639
+ continue;
640
+ }
641
+ const modelLineMatch = normalizedLine.match(/^model:\s+([a-z0-9][a-z0-9._\-\/]+)(?:\s+(high|medium|low|extra\s*high))?/i);
642
+ if (modelLineMatch?.[1]) {
643
+ base.currentModel = modelLineMatch[1];
644
+ if (modelLineMatch[2]) {
645
+ base.effort = modelLineMatch[2].toLowerCase();
646
+ }
647
+ continue;
648
+ }
649
+ const accountMatch = normalizedLine.match(/^Account:\s+(\S+@\S+)\s+\((\w+)\)/i);
650
+ if (accountMatch) {
651
+ base.account = accountMatch[1];
652
+ base.plan = accountMatch[2];
653
+ continue;
654
+ }
655
+ if (/context window:/i.test(normalizedLine)) continue;
656
+ if (/weekly limit:/i.test(normalizedLine)) {
657
+ const leftMatch = normalizedLine.match(/(\d+(?:\.\d+)?)\s*%\s*left/i);
658
+ const leftPct = leftMatch?.[1] ? parsePercent(leftMatch[1]) : null;
659
+ const used = leftPct !== null ? Math.floor(100 - leftPct) : null;
660
+ const resetMatch = normalizedLine.match(/resets\s+([0-9]{1,2}:\d{2})(?:\s+on\s+(.+))?/i);
661
+ const resetTime = resetMatch?.[1] || "";
662
+ const resetDate = resetMatch?.[2]?.trim() || "";
663
+ const resetText = resetTime ? `${resetTime}${resetDate ? ` on ${resetDate}` : ""}` : null;
664
+ const nextResetAt = resetText ? parseResetDateFromText(resetText) : null;
665
+ if (used !== null) {
666
+ if (limitScope === "global") {
667
+ base.weeklyPercentUsed = keepLargest(base.weeklyPercentUsed, used);
668
+ if (resetText) {
669
+ base.resetInfo = `Weekly reset: ${resetText}`;
670
+ base.nextResetAt = nextResetAt || base.nextResetAt;
671
+ }
672
+ }
673
+ base.rateLimits.push({
674
+ scope: limitScope,
675
+ period: "weekly",
676
+ percentUsed: used,
677
+ resetInfo: resetText ? `Resets ${resetText}` : null,
678
+ nextResetAt
679
+ });
680
+ }
681
+ continue;
682
+ }
683
+ if (/5h limit:/i.test(normalizedLine)) {
684
+ const leftMatch = normalizedLine.match(/(\d+(?:\.\d+)?)\s*%\s*left/i);
685
+ const leftPct = leftMatch?.[1] ? parsePercent(leftMatch[1]) : null;
686
+ const used = leftPct !== null ? Math.floor(100 - leftPct) : null;
687
+ const resetMatch = normalizedLine.match(/resets\s+([0-9]{1,2}:\d{2})/i);
688
+ const resetText = resetMatch?.[1] || null;
689
+ const nextResetAt = resetText ? parseResetDateFromText(resetText) : null;
690
+ const usedTokenMatch = normalizedLine.match(/([0-9]+(?:\.[0-9]+)?[kKmMbBtT]?)\s+used/i);
691
+ if (usedTokenMatch?.[1]) {
692
+ const tokens = parseTokenQuantity(usedTokenMatch[1]);
693
+ base.last5HoursInputTokens = keepLargest(base.last5HoursInputTokens, tokens);
694
+ }
695
+ if (used !== null) {
696
+ base.rateLimits.push({
697
+ scope: limitScope,
698
+ period: "5h",
699
+ percentUsed: used,
700
+ resetInfo: resetText ? `Resets ${resetText}` : null,
701
+ nextResetAt
702
+ });
703
+ }
704
+ continue;
705
+ }
706
+ const modelLimitHeader = normalizedLine.match(/^([a-z0-9][a-z0-9._\-\/]+)\s+limit:/i);
707
+ if (modelLimitHeader?.[1] && !/^(5h|weekly)\b/i.test(normalizedLine)) {
708
+ limitScope = modelLimitHeader[1].toLowerCase();
709
+ if (!base.currentModel) base.currentModel = limitScope;
710
+ continue;
711
+ }
712
+ const usedMatch = normalizedLine.match(/([0-9]+(?:\.[0-9]+)?[kKmMbBtT]?)\s+used/i);
713
+ if (usedMatch?.[1] && /\b5h\b/i.test(normalizedLine)) {
714
+ const tokens = parseTokenQuantity(usedMatch[1]);
715
+ if (tokens > 0) {
716
+ base.last5HoursInputTokens = keepLargest(base.last5HoursInputTokens, tokens);
717
+ if (!base.currentModel && limitScope !== "global") {
718
+ base.currentModel = limitScope;
719
+ }
720
+ }
721
+ }
722
+ if (normalizedLine.length > 80) {
723
+ if (base.weeklyPercentUsed === null) {
724
+ const statusLeftMatch = normalizedLine.match(/(\d+)%\s*left/i);
725
+ if (statusLeftMatch?.[1]) {
726
+ const leftPct = parsePercent(statusLeftMatch[1]);
727
+ if (leftPct !== null) {
728
+ base.weeklyPercentUsed = Math.floor(100 - leftPct);
729
+ }
730
+ }
731
+ }
732
+ const fiveHMatch = normalizedLine.match(/5h\s+(\d+)%/i);
733
+ if (fiveHMatch?.[1] && base.rateLimits.length === 0) {
734
+ const leftPct = parsePercent(fiveHMatch[1]);
735
+ if (leftPct !== null) {
736
+ base.rateLimits.push({
737
+ scope: "global",
738
+ period: "5h",
739
+ percentUsed: Math.floor(100 - leftPct),
740
+ resetInfo: null,
741
+ nextResetAt: null
742
+ });
743
+ }
744
+ }
745
+ }
746
+ }
747
+ return base;
748
+ }
749
+ function parseGeminiModelUsageLine(line) {
750
+ const match = line.match(
751
+ /\b(gemini-[a-z0-9][a-z0-9._-]*)\b.*?(\d{1,3})\s*%\s+([0-9]{1,2}:\d{2}(?:\s*(?:AM|PM|am|pm))?)\s*(?:\(([^)]+)\))?/
752
+ );
753
+ if (!match) {
754
+ return { model: null, percentUsed: null, resetAt: null };
755
+ }
756
+ const model = match[1];
757
+ const percentUsed = parsePercent(match[2]);
758
+ if (percentUsed === null) return { model: null, percentUsed: null, resetAt: null };
759
+ const resetTime = match[3]?.trim() ?? "";
760
+ const resetIn = match[4]?.trim() ?? "";
761
+ const resetAt = resetIn ? `${resetTime} (${resetIn})` : resetTime;
762
+ return { model, percentUsed, resetAt: resetAt || null };
763
+ }
764
+ function parseGeminiUsageFromStatus(raw) {
765
+ const base = initSnapshot(raw);
766
+ base.weeklyPercentUsed = null;
767
+ base.weeklyLimitEstimate = null;
768
+ if (!raw) return base;
769
+ const lines = raw.split(/[\r\n]+/).map((line) => line.trim()).filter(Boolean);
770
+ for (const line of lines) {
771
+ const normalizedLine = line.replace(/[│]/g, " ").trim();
772
+ if (!normalizedLine) continue;
773
+ if (/^[─╭╰]+$/.test(normalizedLine) || /^╮|^╯/.test(normalizedLine)) {
774
+ continue;
775
+ }
776
+ const versionMatch = normalizedLine.match(/Gemini CLI v(\d+\.\d+\.\d+)/i);
777
+ if (versionMatch?.[1] && !base.version) {
778
+ base.version = versionMatch[1];
779
+ continue;
780
+ }
781
+ const accountMatch = normalizedLine.match(/Signed in with Google:\s+(\S+@\S+)/i);
782
+ if (accountMatch?.[1]) {
783
+ base.account = accountMatch[1].replace(/\s*\/auth$/, "");
784
+ continue;
785
+ }
786
+ const planMatch = normalizedLine.match(/^Plan:\s+(.+?)(?:\s+\/\w+)?$/i);
787
+ if (planMatch?.[1]) {
788
+ base.plan = planMatch[1].trim();
789
+ continue;
790
+ }
791
+ const tierMatch = normalizedLine.match(/^Tier:\s+(.+)/i);
792
+ if (tierMatch?.[1] && !base.plan) {
793
+ base.plan = tierMatch[1].trim();
794
+ continue;
795
+ }
796
+ if (normalizedLine.startsWith("Model")) continue;
797
+ const modelUsage = parseGeminiModelUsageLine(normalizedLine);
798
+ if (modelUsage.model && modelUsage.percentUsed !== null) {
799
+ if (!base.currentModel) {
800
+ base.currentModel = modelUsage.model;
801
+ }
802
+ base.weeklyPercentUsed = keepLargest(base.weeklyPercentUsed, modelUsage.percentUsed);
803
+ const nextResetAt = modelUsage.resetAt ? parseResetDateFromText(modelUsage.resetAt) : null;
804
+ if (modelUsage.resetAt) {
805
+ base.resetInfo = `Usage reset: ${modelUsage.resetAt}`;
806
+ base.nextResetAt = nextResetAt || base.nextResetAt;
807
+ }
808
+ base.rateLimits.push({
809
+ scope: modelUsage.model,
810
+ period: "daily",
811
+ percentUsed: modelUsage.percentUsed,
812
+ resetInfo: modelUsage.resetAt ? `Resets ${modelUsage.resetAt}` : null,
813
+ nextResetAt
814
+ });
815
+ continue;
816
+ }
817
+ if (/Session Stats/i.test(normalizedLine)) {
818
+ base.resetInfo = base.resetInfo || "Gemini session stats unavailable";
819
+ }
820
+ }
821
+ return base;
822
+ }
823
+ function collectProviderUsageSnapshotFromCli(command, usageCommand, parseSnapshot, args = []) {
824
+ return collectProviderStatusText(command, args, usageCommand).then((raw) => {
825
+ if (!raw) return null;
826
+ return parseSnapshot(raw);
827
+ });
828
+ }
829
+
312
830
  // src/agents/adapters/claude.ts
831
+ var CLAUDE_USAGE_COMMAND = "/usage";
832
+ var collectClaudeUsageFromCli = () => collectProviderUsageSnapshotFromCli("claude", CLAUDE_USAGE_COMMAND, parseClaudeUsageFromStatus, [
833
+ "--dangerously-skip-permissions"
834
+ ]);
313
835
  function buildClaudeCommand(options) {
314
- const parts = ["claude", "--print"];
836
+ const parts = ["claude", "--print", "--bare"];
315
837
  if (options.readOnly) {
316
838
  parts.push("--permission-mode plan");
317
839
  } else if (!options.noToolAccess) {
@@ -372,20 +894,20 @@ async function compile(issue, provider, plan, config, workspacePath, skillContex
372
894
  readOnly: isReadOnlyRole,
373
895
  maxBudgetUsd: config.maxBudgetUsd
374
896
  });
375
- const env2 = {
897
+ const env3 = {
376
898
  FIFONY_PLAN_COMPLEXITY: plan.estimatedComplexity,
377
899
  FIFONY_PLAN_STEPS: String(plan.steps.length),
378
900
  FIFONY_EXECUTION_PAYLOAD_FILE: "fifony-execution-payload.json"
379
901
  };
380
- if (plan.suggestedPaths?.length) env2.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
902
+ if (plan.suggestedPaths?.length) env3.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
381
903
  if (plan.suggestedSkills?.length) {
382
- env2.FIFONY_PLAN_SKILLS = plan.suggestedSkills.join(",");
904
+ env3.FIFONY_PLAN_SKILLS = plan.suggestedSkills.join(",");
383
905
  }
384
906
  const { pre, post } = extractValidationCommands(plan);
385
907
  return {
386
908
  prompt,
387
909
  command,
388
- env: env2,
910
+ env: env3,
389
911
  preHooks: pre,
390
912
  postHooks: post,
391
913
  outputSchema: CLAUDE_RESULT_SCHEMA,
@@ -415,6 +937,10 @@ var claudeAdapter = {
415
937
  // src/agents/adapters/codex.ts
416
938
  import { existsSync as existsSync3 } from "fs";
417
939
  import { join as join2 } from "path";
940
+ var CODEX_USAGE_COMMAND = "/status";
941
+ var collectCodexUsageFromCli = () => collectProviderUsageSnapshotFromCli("codex", CODEX_USAGE_COMMAND, parseCodexUsageFromStatus, [
942
+ "--dangerously-bypass-approvals-and-sandbox"
943
+ ]);
418
944
  var CODEX_RESULT_CONTRACT = `
419
945
  Return a JSON object with this exact schema when finished:
420
946
  {
@@ -429,7 +955,13 @@ Return a JSON object with this exact schema when finished:
429
955
  }
430
956
  `.trim();
431
957
  function buildCodexCommand(options) {
432
- const parts = ["codex", "exec", "--skip-git-repo-check", "--dangerously-bypass-approvals-and-sandbox"];
958
+ const parts = [
959
+ "codex",
960
+ "exec",
961
+ "--skip-git-repo-check",
962
+ "--dangerously-bypass-approvals-and-sandbox",
963
+ "--no-alt-screen"
964
+ ];
433
965
  if (options.model && options.model !== "codex") {
434
966
  parts.push(`--model ${options.model}`);
435
967
  }
@@ -484,18 +1016,18 @@ async function compile2(issue, provider, plan, config, workspacePath, skillConte
484
1016
  effort,
485
1017
  imagePaths: issue.images?.filter((p) => existsSync3(p))
486
1018
  });
487
- const env2 = {
1019
+ const env3 = {
488
1020
  FIFONY_PLAN_COMPLEXITY: plan.estimatedComplexity,
489
1021
  FIFONY_PLAN_STEPS: String(plan.steps.length),
490
1022
  FIFONY_PLAN_PHASES: String(plan.phases?.length || 0),
491
1023
  FIFONY_EXECUTION_PAYLOAD_FILE: "execution-payload.json"
492
1024
  };
493
- if (plan.suggestedPaths?.length) env2.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
1025
+ if (plan.suggestedPaths?.length) env3.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
494
1026
  const { pre, post } = extractValidationCommands(plan);
495
1027
  return {
496
1028
  prompt,
497
1029
  command,
498
- env: env2,
1030
+ env: env3,
499
1031
  preHooks: pre,
500
1032
  postHooks: post,
501
1033
  outputSchema: "",
@@ -523,6 +1055,8 @@ var codexAdapter = {
523
1055
  // src/agents/adapters/gemini.ts
524
1056
  import { existsSync as existsSync4 } from "fs";
525
1057
  import { join as join3 } from "path";
1058
+ var GEMINI_USAGE_COMMAND = "/stats session";
1059
+ var collectGeminiUsageFromCli = () => collectProviderUsageSnapshotFromCli("gemini", GEMINI_USAGE_COMMAND, parseGeminiUsageFromStatus);
526
1060
  var GEMINI_RESULT_CONTRACT = `
527
1061
  Return a JSON object with this exact schema when finished:
528
1062
  {
@@ -546,6 +1080,7 @@ function buildGeminiCommand(options) {
546
1080
  if (options.model) {
547
1081
  parts.push(`--model ${options.model}`);
548
1082
  }
1083
+ parts.push("--screen-reader");
549
1084
  parts.push("--output-format json");
550
1085
  if (options.addDirs?.length) {
551
1086
  parts.push(`--include-directories ${options.addDirs.map((d) => `"${d}"`).join(",")}`);
@@ -589,18 +1124,18 @@ async function compile3(issue, provider, plan, config, workspacePath, skillConte
589
1124
  addDirs: absoluteDirs,
590
1125
  readOnly: isReadOnlyRole
591
1126
  });
592
- const env2 = {
1127
+ const env3 = {
593
1128
  FIFONY_PLAN_COMPLEXITY: plan.estimatedComplexity,
594
1129
  FIFONY_PLAN_STEPS: String(plan.steps.length),
595
1130
  FIFONY_PLAN_PHASES: String(plan.phases?.length || 0),
596
1131
  FIFONY_EXECUTION_PAYLOAD_FILE: "execution-payload.json"
597
1132
  };
598
- if (plan.suggestedPaths?.length) env2.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
1133
+ if (plan.suggestedPaths?.length) env3.FIFONY_PLAN_PATHS = plan.suggestedPaths.join(",");
599
1134
  const { pre, post } = extractValidationCommands(plan);
600
1135
  return {
601
1136
  prompt,
602
1137
  command,
603
- env: env2,
1138
+ env: env3,
604
1139
  preHooks: pre,
605
1140
  postHooks: post,
606
1141
  outputSchema: "",
@@ -1107,10 +1642,11 @@ async function runHook(command, workspacePath, issue, hookName, extraEnv = {}) {
1107
1642
  retryDelayMs: 0,
1108
1643
  staleInProgressTimeoutMs: 0,
1109
1644
  logLinesTail: 12e3,
1110
- agentProvider: normalizeAgentProvider(env.FIFONY_AGENT_PROVIDER ?? "codex"),
1645
+ agentProvider: normalizeAgentProvider(env2.FIFONY_AGENT_PROVIDER ?? "codex"),
1111
1646
  agentCommand: command,
1112
1647
  maxTurns: 1,
1113
- runMode: "filesystem"
1648
+ runMode: "filesystem",
1649
+ autoReviewApproval: true
1114
1650
  }, "", "", { FIFONY_HOOK_NAME: hookName, ...extraEnv });
1115
1651
  if (!result.success) {
1116
1652
  throw new Error(`${hookName} hook failed: ${result.output}`);
@@ -1550,7 +2086,7 @@ function parseDiffStats(issue, raw) {
1550
2086
  }
1551
2087
  async function syncIssueDiffStatsToStore(issue) {
1552
2088
  if (!issue?.id) return;
1553
- const { getIssueStateResource } = await import("./store-MTE6H7WQ.js");
2089
+ const { getIssueStateResource } = await import("./store-FFHHRNVI.js");
1554
2090
  const issueResource = getIssueStateResource();
1555
2091
  if (!issueResource) return;
1556
2092
  const toNumber = (value) => {
@@ -1773,6 +2309,9 @@ function writeVersionedArtifacts(workspacePath, prefix, planVersion, attempt, so
1773
2309
  export {
1774
2310
  buildFullPlanPrompt,
1775
2311
  buildExecutionPayload,
2312
+ collectClaudeUsageFromCli,
2313
+ collectCodexUsageFromCli,
2314
+ collectGeminiUsageFromCli,
1776
2315
  ADAPTERS,
1777
2316
  discoverModels,
1778
2317
  normalizeAgentProvider,
@@ -1811,4 +2350,4 @@ export {
1811
2350
  hydrateIssuePathsFromWorkspace,
1812
2351
  writeVersionedArtifacts
1813
2352
  };
1814
- //# sourceMappingURL=chunk-IYAF3SY6.js.map
2353
+ //# sourceMappingURL=chunk-2D5P75F6.js.map