@triflux/core 10.1.0 → 10.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/hooks/agent-route-guard.mjs +14 -10
  2. package/hooks/cross-review-tracker.mjs +21 -8
  3. package/hooks/error-context.mjs +20 -8
  4. package/hooks/hook-adaptive-collector.mjs +18 -16
  5. package/hooks/hook-manager.mjs +93 -32
  6. package/hooks/hook-orchestrator.mjs +267 -35
  7. package/hooks/hook-registry.json +343 -256
  8. package/hooks/hooks.json +49 -1
  9. package/hooks/keyword-rules.json +191 -31
  10. package/hooks/lib/resolve-root.mjs +1 -1
  11. package/hooks/mcp-config-watcher.mjs +6 -2
  12. package/hooks/permission-safe-allow.mjs +233 -0
  13. package/hooks/pipeline-stop.mjs +369 -22
  14. package/hooks/post-tool-tips.mjs +306 -0
  15. package/hooks/pre-compact-snapshot.mjs +181 -0
  16. package/hooks/safety-guard.mjs +262 -34
  17. package/hooks/session-end-cleanup.mjs +345 -0
  18. package/hooks/session-start-fast.mjs +214 -0
  19. package/hooks/subagent-tracker.mjs +267 -0
  20. package/hooks/subagent-verifier.mjs +9 -4
  21. package/hub/account-broker.mjs +1036 -66
  22. package/hub/adaptive-diagnostic.mjs +75 -48
  23. package/hub/adaptive-inject.mjs +96 -58
  24. package/hub/adaptive-memory.mjs +156 -42
  25. package/hub/adaptive.mjs +60 -31
  26. package/hub/assign-callbacks.mjs +163 -126
  27. package/hub/bridge.mjs +1051 -290
  28. package/hub/cli-adapter-base.mjs +321 -97
  29. package/hub/codex-adapter.mjs +179 -93
  30. package/hub/codex-compat.mjs +4 -4
  31. package/hub/codex-preflight.mjs +113 -37
  32. package/hub/delegator/contracts.mjs +22 -21
  33. package/hub/delegator/index.mjs +14 -14
  34. package/hub/delegator/schema/delegator-tools.schema.json +1 -1
  35. package/hub/delegator/service.mjs +93 -64
  36. package/hub/delegator/tool-definitions.mjs +35 -35
  37. package/hub/dynamic-routing-engine.mjs +511 -0
  38. package/hub/fullcycle.mjs +34 -18
  39. package/hub/gemini-adapter.mjs +82 -108
  40. package/hub/hitl.mjs +202 -143
  41. package/hub/intent.mjs +167 -38
  42. package/hub/lib/bash-path.mjs +73 -0
  43. package/hub/lib/cache-guard.mjs +43 -17
  44. package/hub/lib/env-detect.mjs +145 -0
  45. package/hub/lib/hosts-compat.mjs +319 -0
  46. package/hub/lib/mcp-response-cache.mjs +239 -0
  47. package/hub/lib/memory-store.mjs +285 -111
  48. package/hub/lib/path-utils.mjs +165 -0
  49. package/hub/lib/phase-manager.mjs +493 -0
  50. package/hub/lib/process-utils.mjs +797 -104
  51. package/hub/lib/prompt-tmp.mjs +33 -0
  52. package/hub/lib/risk-tier.mjs +53 -0
  53. package/hub/lib/spawn-trace.mjs +561 -0
  54. package/hub/lib/ssh-command.mjs +44 -24
  55. package/hub/lib/ssh-retry.mjs +5 -1
  56. package/hub/lib/staged-file-detect.mjs +43 -0
  57. package/hub/lib/state-snapshot.mjs +293 -0
  58. package/hub/lib/tfx-route-args.mjs +309 -0
  59. package/hub/lib/timeline-adapter.mjs +190 -0
  60. package/hub/lib/trace-recorder.mjs +153 -0
  61. package/hub/lib/uuidv7.mjs +4 -3
  62. package/hub/memory-doctor.mjs +266 -106
  63. package/hub/middleware/quota-middleware.mjs +70 -0
  64. package/hub/middleware/request-logger.mjs +61 -34
  65. package/hub/paths.mjs +9 -9
  66. package/hub/pipeline/gates/confidence.mjs +34 -15
  67. package/hub/pipeline/gates/consensus.mjs +27 -15
  68. package/hub/pipeline/gates/index.mjs +7 -3
  69. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  70. package/hub/pipeline/index.mjs +77 -42
  71. package/hub/pipeline/state.mjs +10 -10
  72. package/hub/pipeline/transitions.mjs +40 -23
  73. package/hub/platform.mjs +65 -48
  74. package/hub/quality/deslop.mjs +70 -49
  75. package/hub/reflexion.mjs +87 -13
  76. package/hub/research.mjs +33 -25
  77. package/hub/router.mjs +941 -791
  78. package/hub/routing/complexity.mjs +132 -29
  79. package/hub/routing/index.mjs +17 -12
  80. package/hub/routing/q-learning.mjs +78 -30
  81. package/hub/routing-snapshot.mjs +261 -0
  82. package/hub/session-fingerprint.mjs +127 -61
  83. package/hub/state.mjs +86 -44
  84. package/hub/team/claude-daemon-control.mjs +1130 -0
  85. package/hub/team/retry-state-machine.mjs +336 -0
  86. package/hub/token-mode.mjs +72 -71
  87. package/hub/workers/worker-utils.mjs +23 -17
  88. package/hud/colors.mjs +27 -9
  89. package/hud/constants.mjs +190 -27
  90. package/hud/context-monitor.mjs +133 -44
  91. package/hud/hud-qos-status.mjs +157 -50
  92. package/hud/mission-board.mjs +56 -0
  93. package/hud/providers/claude.mjs +398 -114
  94. package/hud/providers/codex.mjs +96 -21
  95. package/hud/providers/gemini.mjs +210 -52
  96. package/hud/renderers.mjs +440 -127
  97. package/hud/terminal.mjs +101 -31
  98. package/hud/utils.mjs +81 -39
  99. package/mesh/index.mjs +69 -0
  100. package/mesh/mesh-budget.mjs +137 -0
  101. package/mesh/mesh-heartbeat.mjs +100 -0
  102. package/mesh/mesh-protocol.mjs +96 -0
  103. package/mesh/mesh-queue.mjs +163 -0
  104. package/mesh/mesh-registry.mjs +78 -0
  105. package/mesh/mesh-router.mjs +77 -0
  106. package/package.json +5 -1
  107. package/scripts/lib/agent-json.mjs +27 -0
  108. package/scripts/lib/async.mjs +174 -0
  109. package/scripts/lib/claudemd-scanner.mjs +6 -1
  110. package/scripts/lib/cli-agy.mjs +62 -0
  111. package/scripts/lib/cli-claude.mjs +78 -0
  112. package/scripts/lib/cli-codex.mjs +199 -0
  113. package/scripts/lib/cli-gemini.mjs +37 -0
  114. package/scripts/lib/codex-recovery.sh +50 -0
  115. package/scripts/lib/context.mjs +3 -3
  116. package/scripts/lib/cross-review-utils.mjs +13 -5
  117. package/scripts/lib/doctor-env-checks.mjs +121 -0
  118. package/scripts/lib/dynamic-route-cli.mjs +107 -0
  119. package/scripts/lib/env-probe.mjs +58 -30
  120. package/scripts/lib/env.mjs +14 -0
  121. package/scripts/lib/gemini-profiles.mjs +44 -10
  122. package/scripts/lib/handoff.mjs +33 -17
  123. package/scripts/lib/hook-utils.mjs +8 -6
  124. package/scripts/lib/hub.mjs +48 -0
  125. package/scripts/lib/keyword-rules.mjs +49 -20
  126. package/scripts/lib/logger.mjs +24 -24
  127. package/scripts/lib/mcp-filter.mjs +391 -247
  128. package/scripts/lib/mcp-gateway-health-check.mjs +159 -0
  129. package/scripts/lib/mcp-gateway-servers.mjs +70 -0
  130. package/scripts/lib/mcp-gateway-wrapper-check.mjs +35 -0
  131. package/scripts/lib/mcp-guard-engine.mjs +1281 -115
  132. package/scripts/lib/mcp-health.mjs +773 -0
  133. package/scripts/lib/mcp-manifest.mjs +24 -14
  134. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  135. package/scripts/lib/pid.mjs +63 -0
  136. package/scripts/lib/process-utils.mjs +26 -0
  137. package/scripts/lib/psmux-info.mjs +117 -12
  138. package/scripts/lib/quota.mjs +47 -0
  139. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  140. package/scripts/lib/skill-state.mjs +220 -0
  141. package/scripts/lib/skill-template.mjs +32 -7
  142. package/scripts/lib/team.mjs +75 -0
  143. package/scripts/lib/timeout.mjs +66 -0
  144. package/scripts/lib/tmp.mjs +26 -0
  145. package/scripts/lib/toml.mjs +113 -0
package/hud/renderers.mjs CHANGED
@@ -5,26 +5,51 @@ import { existsSync, readdirSync } from "node:fs";
5
5
  import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import {
8
- dim, bold, green, red, yellow, cyan,
9
- claudeOrange, codexWhite, geminiBlue,
10
- colorByPercent, colorByProvider,
11
- CLAUDE_ORANGE, CODEX_WHITE, GEMINI_BLUE,
8
+ bold,
9
+ CLAUDE_ORANGE,
10
+ CODEX_WHITE,
11
+ claudeOrange,
12
+ codexWhite,
13
+ colorByPercent,
14
+ colorByProvider,
15
+ dim,
16
+ GEMINI_BLUE,
17
+ geminiBlue,
18
+ green,
19
+ red,
20
+ yellow,
12
21
  } from "./colors.mjs";
13
22
  import {
14
- PROVIDER_PREFIX_WIDTH, ACCOUNT_LABEL_WIDTH,
15
- FIVE_HOUR_MS, SEVEN_DAY_MS, ONE_DAY_MS,
16
- TEAM_STATE_PATH, SV_ACCUMULATOR_PATH, LEGACY_SV_ACCUMULATOR,
23
+ ACCOUNT_LABEL_WIDTH,
24
+ FIVE_HOUR_MS,
25
+ LEGACY_SV_ACCUMULATOR,
26
+ ONE_DAY_MS,
27
+ PROVIDER_PREFIX_WIDTH,
28
+ SEVEN_DAY_MS,
29
+ SV_ACCUMULATOR_PATH,
30
+ TEAM_STATE_PATH,
17
31
  } from "./constants.mjs";
32
+ import { buildContextUsageView } from "./context-monitor.mjs";
33
+ import { deriveGeminiLimits } from "./providers/gemini.mjs";
34
+ import { getTerminalColumns, tierBar, tierDimBar } from "./terminal.mjs";
18
35
  import {
19
- readJson, readJsonMigrate, stripAnsi, padAnsiRight, fitText, truncateAnsi,
20
- clampPercent, formatPercentCell, formatPlaceholderPercentCell,
21
- formatTimeCell, formatTimeCellDH,
22
- formatResetRemaining, formatResetRemainingDayHour,
23
- formatTokenCount, formatSvPct, formatSavings,
36
+ clampPercent,
37
+ fitText,
38
+ formatPercentCell,
39
+ formatPlaceholderPercentCell,
40
+ formatResetRemaining,
41
+ formatResetRemainingDayHour,
42
+ formatSavings,
43
+ formatSvPct,
44
+ formatTimeCell,
45
+ formatTimeCellDH,
46
+ formatTokenCount,
47
+ padAnsiRight,
48
+ readJson,
49
+ readJsonMigrate,
50
+ stripAnsi,
51
+ truncateAnsi,
24
52
  } from "./utils.mjs";
25
- import { tierBar, tierDimBar, getTerminalColumns } from "./terminal.mjs";
26
- import { deriveGeminiLimits } from "./providers/gemini.mjs";
27
- import { buildContextUsageView } from "./context-monitor.mjs";
28
53
 
29
54
  // ============================================================================
30
55
  // 최근 벤치마크 diff 파일 읽기
@@ -33,15 +58,26 @@ export function readLatestBenchmarkDiff() {
33
58
  const diffsDir = join(homedir(), ".omc", "state", "cx-auto-tokens", "diffs");
34
59
  if (!existsSync(diffsDir)) return null;
35
60
  try {
36
- const files = readdirSync(diffsDir).filter(f => f.endsWith(".json")).sort().reverse();
61
+ const files = readdirSync(diffsDir)
62
+ .filter((f) => f.endsWith(".json"))
63
+ .sort()
64
+ .reverse();
37
65
  if (files.length === 0) return null;
38
66
  return readJson(join(diffsDir, files[0]), null);
39
- } catch { return null; }
67
+ } catch {
68
+ return null;
69
+ }
40
70
  }
41
71
 
42
72
  // 토큰 절약액 누적치 읽기 (tfx-auto token tracker)
43
73
  export function readTokenSavings() {
44
- const savingsPath = join(homedir(), ".omc", "state", "tfx-auto-tokens", "savings-total.json");
74
+ const savingsPath = join(
75
+ homedir(),
76
+ ".omc",
77
+ "state",
78
+ "tfx-auto-tokens",
79
+ "savings-total.json",
80
+ );
45
81
  const data = readJson(savingsPath, null);
46
82
  if (!data || data.totalSaved === 0) return null;
47
83
  return data;
@@ -63,14 +99,15 @@ export function formatTokenSummary(diff) {
63
99
  const inputStr = formatTokenCount(t.input);
64
100
  const outputStr = formatTokenCount(t.output);
65
101
  const actualStr = formatSavings(s.actualCost);
66
- const claudeStr = formatSavings(s.claudeCost);
67
- const savedPct = s.claudeCost > 0
68
- ? Math.round((s.saved / s.claudeCost) * 100)
69
- : 0;
102
+ const _claudeStr = formatSavings(s.claudeCost);
103
+ const savedPct =
104
+ s.claudeCost > 0 ? Math.round((s.saved / s.claudeCost) * 100) : 0;
70
105
 
71
- return `${dim("tok:")}${inputStr}${dim("in")} ${outputStr}${dim("out")} ` +
106
+ return (
107
+ `${dim("tok:")}${inputStr}${dim("in")} ${outputStr}${dim("out")} ` +
72
108
  `${dim("cost:")}${actualStr} ` +
73
- `${dim("sv:")}${green(formatSavings(s.saved))}${dim("(")}${savedPct}%${dim(")")}`;
109
+ `${dim("sv:")}${green(formatSavings(s.saved))}${dim("(")}${savedPct}%${dim(")")}`
110
+ );
74
111
  }
75
112
 
76
113
  // ============================================================================
@@ -78,10 +115,14 @@ export function formatTokenSummary(diff) {
78
115
  // ============================================================================
79
116
  export function getTeamRow(currentTier) {
80
117
  const teamState = readJson(TEAM_STATE_PATH, null);
81
- if (!teamState || !teamState.sessionName) return null;
118
+ if (!teamState?.sessionName) return null;
82
119
 
83
120
  // 팀 생존 확인: startedAt 기준 24시간 초과면 stale로 간주
84
- if (teamState.startedAt && (Date.now() - teamState.startedAt) > 24 * 60 * 60 * 1000) return null;
121
+ if (
122
+ teamState.startedAt &&
123
+ Date.now() - teamState.startedAt > 24 * 60 * 60 * 1000
124
+ )
125
+ return null;
85
126
 
86
127
  const workers = (teamState.members || []).filter((m) => m.role === "worker");
87
128
  if (!workers.length) return null;
@@ -92,26 +133,44 @@ export function getTeamRow(currentTier) {
92
133
  const total = tasks.length || workers.length;
93
134
 
94
135
  // 경과 시간 (80col 이상에서만 표시)
95
- const elapsed = (teamState.startedAt && (currentTier === "full" || currentTier === "compact"))
96
- ? `${Math.round((Date.now() - teamState.startedAt) / 60000)}m`
97
- : "";
136
+ const elapsed =
137
+ teamState.startedAt && (currentTier === "full" || currentTier === "compact")
138
+ ? `${Math.round((Date.now() - teamState.startedAt) / 60000)}m`
139
+ : "";
98
140
 
99
141
  // CLI 브랜드: 단일문자 + ANSI 색상 (x=codex, g=gemini, c=claude)
100
- const cliTag = (cli) => cli === "codex" ? bold(codexWhite("x")) : cli === "gemini" ? bold(geminiBlue("g")) : bold(claudeOrange("c"));
142
+ const cliTag = (cli) =>
143
+ cli === "codex"
144
+ ? bold(codexWhite("x"))
145
+ : cli === "gemini"
146
+ ? bold(geminiBlue("g"))
147
+ : bold(claudeOrange("c"));
101
148
  // 멤버 상태: 태그 + 상태기호 (60col 이상)
102
- const memberIcons = (currentTier === "full" || currentTier === "compact" || currentTier === "minimal") ? workers.map((m) => {
103
- const task = tasks.find((t) => t.owner === m.name);
104
- const status = task?.status === "completed" ? green("\u2713")
105
- : task?.status === "in_progress" ? yellow("\u22EF")
106
- : task?.status === "failed" ? red("\u2717")
107
- : dim("\u25CC");
108
- return `${cliTag(m.cli)}${status}`;
109
- }).join(" ") : "";
149
+ const memberIcons =
150
+ currentTier === "full" ||
151
+ currentTier === "compact" ||
152
+ currentTier === "minimal"
153
+ ? workers
154
+ .map((m) => {
155
+ const task = tasks.find((t) => t.owner === m.name);
156
+ const status =
157
+ task?.status === "completed"
158
+ ? green("\u2713")
159
+ : task?.status === "in_progress"
160
+ ? yellow("\u22EF")
161
+ : task?.status === "failed"
162
+ ? red("\u2717")
163
+ : dim("\u25CC");
164
+ return `${cliTag(m.cli)}${status}`;
165
+ })
166
+ .join(" ")
167
+ : "";
110
168
 
111
169
  // 진행 텍스트
112
- const doneText = failed > 0
113
- ? `${completed}/${total} ${red(`${failed}\u2717`)}`
114
- : `${completed}/${total}`;
170
+ const doneText =
171
+ failed > 0
172
+ ? `${completed}/${total} ${red(`${failed}\u2717`)}`
173
+ : `${completed}/${total}`;
115
174
 
116
175
  const leftText = elapsed ? `${doneText} ${dim(elapsed)}` : doneText;
117
176
 
@@ -122,13 +181,50 @@ export function getTeamRow(currentTier) {
122
181
  };
123
182
  }
124
183
 
184
+ // ============================================================================
185
+ // Mission Board 렌더러
186
+ // ============================================================================
187
+
188
+ const STATUS_ICON = {
189
+ active: "*",
190
+ idle: ".",
191
+ done: "+",
192
+ failed: "!",
193
+ };
194
+
195
+ /**
196
+ * Mission Board 상태를 1줄 compact 포맷으로 렌더링한다.
197
+ * 예: "MB: exec:+ ui:* perf:. [2/3 67%]"
198
+ * @param {{ agents: Array<{name: string, status: string, progress: number}>, dagLevel: number, totalProgress: number } | null} state
199
+ * @returns {string}
200
+ */
201
+ export function renderMissionBoard(state) {
202
+ if (!state) return "";
203
+
204
+ const { agents, totalProgress } = state;
205
+ const done = agents.filter((a) => a.status === "done").length;
206
+ const total = agents.length;
207
+
208
+ const parts = agents.map((a) => {
209
+ const icon = STATUS_ICON[a.status] ?? "?";
210
+ return `${a.name}:${icon}`;
211
+ });
212
+
213
+ return `MB: ${parts.join(" ")} [${done}/${total} ${totalProgress}%]`;
214
+ }
215
+
125
216
  // ============================================================================
126
217
  // 행 정렬 렌더링
127
218
  // ============================================================================
128
219
  export function renderAlignedRows(rows) {
129
220
  const cols = getTerminalColumns() || 120;
130
- const rightRows = rows.filter((row) => stripAnsi(String(row.right || "")).trim().length > 0);
131
- const rawLeftWidth = rightRows.reduce((max, row) => Math.max(max, stripAnsi(row.left).length), 0);
221
+ const rightRows = rows.filter(
222
+ (row) => stripAnsi(String(row.right || "")).trim().length > 0,
223
+ );
224
+ const rawLeftWidth = rightRows.reduce(
225
+ (max, row) => Math.max(max, stripAnsi(row.left).length),
226
+ 0,
227
+ );
132
228
  return rows.map((row) => {
133
229
  const prefix = padAnsiRight(row.prefix, PROVIDER_PREFIX_WIDTH);
134
230
  const hasRight = stripAnsi(String(row.right || "")).trim().length > 0;
@@ -147,9 +243,10 @@ export function renderAlignedRows(rows) {
147
243
  return truncateAnsi(`${prefix} ${left}`, cols);
148
244
  }
149
245
  const rightVisible = stripAnsi(row.right).length;
150
- const right = rightVisible <= availableForRight
151
- ? row.right
152
- : truncateAnsi(row.right, availableForRight);
246
+ const right =
247
+ rightVisible <= availableForRight
248
+ ? row.right
249
+ : truncateAnsi(row.right, availableForRight);
153
250
  return `${prefix} ${left} ${dim("|")} ${right}`;
154
251
  });
155
252
  }
@@ -157,23 +254,43 @@ export function renderAlignedRows(rows) {
157
254
  // ============================================================================
158
255
  // micro tier: 모든 프로바이더를 1줄로 압축
159
256
  // ============================================================================
160
- export function getMicroLine(contextView, claudeUsage, codexBuckets, geminiSession, geminiBucket, combinedSvPct) {
257
+ export function getMicroLine(
258
+ contextView,
259
+ claudeUsage,
260
+ codexBuckets,
261
+ geminiSession,
262
+ geminiBucket,
263
+ combinedSvPct,
264
+ geminiMarker = "g",
265
+ ) {
161
266
  const ctxView = contextView || buildContextUsageView({}, null);
162
-
163
267
  // Claude 5h/1w
164
- const cF = claudeUsage?.fiveHourPercent != null ? clampPercent(claudeUsage.fiveHourPercent) : null;
165
- const cW = claudeUsage?.weeklyPercent != null ? clampPercent(claudeUsage.weeklyPercent) : null;
166
- const cVal = claudeUsage != null
167
- ? `${cF != null ? colorByProvider(cF, `${cF}`, claudeOrange) : dim("--")}${dim("/")}${cW != null ? colorByProvider(cW, `${cW}`, claudeOrange) : dim("--")}`
168
- : dim("--/--");
268
+ const cF =
269
+ claudeUsage?.fiveHourPercent != null
270
+ ? clampPercent(claudeUsage.fiveHourPercent)
271
+ : null;
272
+ const cW =
273
+ claudeUsage?.weeklyPercent != null
274
+ ? clampPercent(claudeUsage.weeklyPercent)
275
+ : null;
276
+ const cVal =
277
+ claudeUsage != null
278
+ ? `${cF != null ? colorByProvider(cF, `${cF}`, claudeOrange) : dim("--")}${dim("/")}${cW != null ? colorByProvider(cW, `${cW}`, claudeOrange) : dim("--")}`
279
+ : dim("--/--");
169
280
 
170
281
  // Codex 5h/1w
171
282
  let xVal = dim("--/--");
172
283
  if (codexBuckets) {
173
284
  const mb = codexBuckets.codex || codexBuckets[Object.keys(codexBuckets)[0]];
174
285
  if (mb) {
175
- const xF = mb.primary?.used_percent != null ? clampPercent(mb.primary.used_percent) : null;
176
- const xW = mb.secondary?.used_percent != null ? clampPercent(mb.secondary.used_percent) : null;
286
+ const xF =
287
+ mb.primary?.used_percent != null
288
+ ? clampPercent(mb.primary.used_percent)
289
+ : null;
290
+ const xW =
291
+ mb.secondary?.used_percent != null
292
+ ? clampPercent(mb.secondary.used_percent)
293
+ : null;
177
294
  xVal = `${xF != null ? colorByProvider(xF, `${xF}`, codexWhite) : dim("--")}${dim("/")}${xW != null ? colorByProvider(xW, `${xW}`, codexWhite) : dim("--")}`;
178
295
  }
179
296
  }
@@ -182,7 +299,9 @@ export function getMicroLine(contextView, claudeUsage, codexBuckets, geminiSessi
182
299
  let gVal;
183
300
  if (geminiBucket) {
184
301
  const gl = deriveGeminiLimits(geminiBucket);
185
- const gU = gl ? gl.usedPct : clampPercent((1 - (geminiBucket.remainingFraction ?? 1)) * 100);
302
+ const gU = gl
303
+ ? gl.usedPct
304
+ : clampPercent((1 - (geminiBucket.remainingFraction ?? 1)) * 100);
186
305
  gVal = colorByProvider(gU, `${gU}`, geminiBlue);
187
306
  } else if ((geminiSession?.total || 0) > 0) {
188
307
  gVal = geminiBlue("\u221E");
@@ -194,9 +313,10 @@ export function getMicroLine(contextView, claudeUsage, codexBuckets, geminiSessi
194
313
  const sv = formatSvPct(combinedSvPct || 0).trim();
195
314
 
196
315
  const cols = getTerminalColumns() || 120;
197
- const line = `${bold(claudeOrange("c"))}${dim(":")}${cVal} ` +
316
+ const line =
317
+ `${bold(claudeOrange("c"))}${dim(":")}${cVal} ` +
198
318
  `${bold(codexWhite("x"))}${dim(":")}${xVal} ` +
199
- `${bold(geminiBlue("g"))}${dim(":")}${gVal} ` +
319
+ `${bold(geminiBlue(geminiMarker))}${dim(":")}${gVal} ` +
200
320
  `${dim("sv:")}${sv} ` +
201
321
  `${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}`;
202
322
  return truncateAnsi(line, cols);
@@ -205,10 +325,14 @@ export function getMicroLine(contextView, claudeUsage, codexBuckets, geminiSessi
205
325
  // ============================================================================
206
326
  // Claude 행 렌더러
207
327
  // ============================================================================
208
- export function getClaudeRows(currentTier, contextView, claudeUsage, combinedSvPct) {
328
+ export function getClaudeRows(
329
+ currentTier,
330
+ contextView,
331
+ claudeUsage,
332
+ combinedSvPct,
333
+ ) {
209
334
  const ctxView = contextView || buildContextUsageView({}, null);
210
335
  const prefix = `${bold(claudeOrange("c"))}:`;
211
-
212
336
  // 절약 퍼센트
213
337
  const svStr = formatSvPct(combinedSvPct || 0);
214
338
  const svSuffix = `${dim("sv:")}${svStr}`;
@@ -225,36 +349,67 @@ export function getClaudeRows(currentTier, contextView, claudeUsage, combinedSvP
225
349
 
226
350
  const hasData = claudeUsage != null;
227
351
 
228
- const fStr = hasData && fiveHourPercent != null ? colorByProvider(fiveHourPercent, formatPercentCell(fiveHourPercent), claudeOrange) : dim(formatPlaceholderPercentCell());
229
- const wStr = hasData && weeklyPercent != null ? colorByProvider(weeklyPercent, formatPercentCell(weeklyPercent), claudeOrange) : dim(formatPlaceholderPercentCell());
230
- const fBar = hasData && fiveHourPercent != null ? tierBar(currentTier, fiveHourPercent, CLAUDE_ORANGE) : tierDimBar(currentTier);
231
- const wBar = hasData && weeklyPercent != null ? tierBar(currentTier, weeklyPercent, CLAUDE_ORANGE) : tierDimBar(currentTier);
352
+ const fStr =
353
+ hasData && fiveHourPercent != null
354
+ ? colorByProvider(
355
+ fiveHourPercent,
356
+ formatPercentCell(fiveHourPercent),
357
+ claudeOrange,
358
+ )
359
+ : dim(formatPlaceholderPercentCell());
360
+ const wStr =
361
+ hasData && weeklyPercent != null
362
+ ? colorByProvider(
363
+ weeklyPercent,
364
+ formatPercentCell(weeklyPercent),
365
+ claudeOrange,
366
+ )
367
+ : dim(formatPlaceholderPercentCell());
368
+ const fBar =
369
+ hasData && fiveHourPercent != null
370
+ ? tierBar(currentTier, fiveHourPercent, CLAUDE_ORANGE)
371
+ : tierDimBar(currentTier);
372
+ const wBar =
373
+ hasData && weeklyPercent != null
374
+ ? tierBar(currentTier, weeklyPercent, CLAUDE_ORANGE)
375
+ : tierDimBar(currentTier);
232
376
  const fTime = formatTimeCell(fiveHourReset);
233
377
  const wTime = formatTimeCellDH(weeklyReset);
234
378
 
235
379
  if (currentTier === "nano" || currentTier === "micro") {
236
- const fShort = hasData && fiveHourPercent != null ? colorByProvider(fiveHourPercent, `${fiveHourPercent}%`, claudeOrange) : dim("--");
237
- const wShort = hasData && weeklyPercent != null ? colorByProvider(weeklyPercent, `${weeklyPercent}%`, claudeOrange) : dim("--");
380
+ const fShort =
381
+ hasData && fiveHourPercent != null
382
+ ? colorByProvider(fiveHourPercent, `${fiveHourPercent}%`, claudeOrange)
383
+ : dim("--");
384
+ const wShort =
385
+ hasData && weeklyPercent != null
386
+ ? colorByProvider(weeklyPercent, `${weeklyPercent}%`, claudeOrange)
387
+ : dim("--");
238
388
  const quotaSection = `${fShort}${dim("/")}${wShort}`;
239
389
  return [{ prefix, left: quotaSection, right: "" }];
240
390
  }
241
391
 
242
392
  if (currentTier === "minimal") {
243
- const quotaSection = `${dim("5h:")}${fStr} ${dim("1w:")}${wStr}`;
393
+ const staleTag = claudeUsage?.stale ? ` ${dim("[stale]")}` : "";
394
+ const quotaSection = `${dim("5h:")}${fStr} ${dim("1w:")}${wStr}${staleTag}`;
244
395
  const right = `${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}`;
245
396
  return [{ prefix, left: quotaSection, right }];
246
397
  }
247
398
 
248
399
  if (currentTier === "compact") {
249
400
  const quotaSection = `${dim("5h:")}${fStr} ${dim(fTime)} ${dim("1w:")}${wStr} ${dim(wTime)}`;
250
- const warning = ctxView.warningTag ? ` ${dim("|")} ${yellow(ctxView.warningTag)}` : "";
401
+ const warning = ctxView.warningTag
402
+ ? ` ${dim("|")} ${yellow(ctxView.warningTag)}`
403
+ : "";
251
404
  const contextSection = `${svSuffix} ${dim("|")} ${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}${warning}`;
252
405
  return [{ prefix, left: quotaSection, right: contextSection }];
253
406
  }
254
407
 
255
408
  // full tier (>= 120 cols)
256
409
  const quotaSection = `${dim("5h:")}${fBar}${fStr} ${dim(fTime)} ${dim("1w:")}${wBar}${wStr} ${dim(wTime)}`;
257
- const warning = ctxView.warningTag ? ` ${dim("|")} ${yellow(ctxView.warningTag)}` : "";
410
+ const warning = ctxView.warningTag
411
+ ? ` ${dim("|")} ${yellow(ctxView.warningTag)}`
412
+ : "";
258
413
  const contextSection = `${svSuffix} ${dim("|")} ${dim("CTX:")}${colorByPercent(ctxView.percent, ctxView.display)}${warning}`;
259
414
  return [{ prefix, left: quotaSection, right: contextSection }];
260
415
  }
@@ -262,76 +417,165 @@ export function getClaudeRows(currentTier, contextView, claudeUsage, combinedSvP
262
417
  // ============================================================================
263
418
  // 계정 라벨 + 범용 프로바이더 행 렌더러
264
419
  // ============================================================================
265
- export function getAccountLabel(provider, accountsConfig, accountsState, codexEmail) {
420
+ export function getAccountLabel(
421
+ provider,
422
+ accountsConfig,
423
+ accountsState,
424
+ codexEmail,
425
+ ) {
266
426
  const providerConfig = accountsConfig?.providers?.[provider] || [];
267
427
  const providerState = accountsState?.providers?.[provider] || {};
268
428
  const lastId = providerState.last_selected_id;
269
- const picked = providerConfig.find((a) => a.id === lastId) || providerConfig[0]
270
- || { id: `${provider}-main`, label: provider };
429
+ const picked = providerConfig.find((a) => a.id === lastId) ||
430
+ providerConfig[0] || { id: `${provider}-main`, label: provider };
271
431
  let label = picked.label || picked.id;
272
432
  if (codexEmail) label = codexEmail;
273
433
  if (label.includes("@")) label = label.split("@")[0];
274
434
  return label;
275
435
  }
276
436
 
277
- export function getProviderRow(currentTier, provider, marker, markerColor, qosProfile, accountsConfig, accountsState, realQuota, codexEmail, savingsMultiplier, modelLabel) {
278
- const accountLabel = fitText(getAccountLabel(provider, accountsConfig, accountsState, codexEmail), ACCOUNT_LABEL_WIDTH);
437
+ function formatAntigravityQuotaSection(currentTier) {
438
+ if (currentTier === "minimal") {
439
+ return `${dim("q:")}${dim(formatPlaceholderPercentCell())}`;
440
+ }
441
+ if (currentTier === "compact") {
442
+ return `${dim("q:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
443
+ }
444
+ return `${dim("q:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
445
+ }
446
+
447
+ export function getProviderRow(
448
+ currentTier,
449
+ provider,
450
+ marker,
451
+ markerColor,
452
+ qosProfile,
453
+ accountsConfig,
454
+ accountsState,
455
+ realQuota,
456
+ codexEmail,
457
+ savingsMultiplier,
458
+ modelLabel,
459
+ ) {
460
+ const accountLabel = fitText(
461
+ getAccountLabel(provider, accountsConfig, accountsState, codexEmail),
462
+ ACCOUNT_LABEL_WIDTH,
463
+ );
279
464
 
280
465
  // 절약 퍼센트 섹션
281
- const svPct = savingsMultiplier != null ? Math.round(savingsMultiplier * 100) : null;
466
+ const svPct =
467
+ savingsMultiplier != null ? Math.round(savingsMultiplier * 100) : null;
282
468
  const svStr = formatSvPct(svPct);
283
- const modelLabelStr = modelLabel ? ` ${markerColor(modelLabel)}` : "";
469
+ const _modelLabelStr = modelLabel ? ` ${markerColor(modelLabel)}` : "";
284
470
 
285
471
  // 프로바이더별 색상 프로필
286
- const provAnsi = provider === "codex" ? CODEX_WHITE : provider === "gemini" ? GEMINI_BLUE : GREEN;
287
- const provFn = provider === "codex" ? codexWhite : provider === "gemini" ? geminiBlue : green;
472
+ const isGeminiFamily = provider === "gemini" || provider === "antigravity";
473
+ const provAnsi =
474
+ provider === "codex" ? CODEX_WHITE : isGeminiFamily ? GEMINI_BLUE : GREEN;
475
+ const provFn =
476
+ provider === "codex" ? codexWhite : isGeminiFamily ? geminiBlue : green;
288
477
 
289
478
  let quotaSection;
290
- let extraRightSection = "";
479
+ const _extraRightSection = "";
291
480
 
292
481
  if (currentTier === "nano" || currentTier === "micro") {
293
482
  const minPrefix = `${bold(markerColor(`${marker}`))}:`;
483
+ if (provider === "antigravity") {
484
+ return { prefix: minPrefix, left: dim("--"), right: "" };
485
+ }
294
486
  if (realQuota?.type === "codex") {
295
- const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
487
+ const main =
488
+ realQuota.buckets.codex ||
489
+ realQuota.buckets[Object.keys(realQuota.buckets)[0]];
296
490
  if (main) {
297
- const fiveP = main.primary?.used_percent != null ? clampPercent(main.primary.used_percent) : null;
298
- const weekP = main.secondary?.used_percent != null ? clampPercent(main.secondary.used_percent) : null;
299
- const fCellN = fiveP != null ? colorByProvider(fiveP, `${fiveP}%`, provFn) : dim("--%");
300
- const wCellN = weekP != null ? colorByProvider(weekP, `${weekP}%`, provFn) : dim("--%");
301
- return { prefix: minPrefix, left: `${fCellN}${dim("/")}${wCellN}`, right: "" };
491
+ const fiveP =
492
+ main.primary?.used_percent != null
493
+ ? clampPercent(main.primary.used_percent)
494
+ : null;
495
+ const weekP =
496
+ main.secondary?.used_percent != null
497
+ ? clampPercent(main.secondary.used_percent)
498
+ : null;
499
+ const fCellN =
500
+ fiveP != null
501
+ ? colorByProvider(fiveP, `${fiveP}%`, provFn)
502
+ : dim("--%");
503
+ const wCellN =
504
+ weekP != null
505
+ ? colorByProvider(weekP, `${weekP}%`, provFn)
506
+ : dim("--%");
507
+ return {
508
+ prefix: minPrefix,
509
+ left: `${fCellN}${dim("/")}${wCellN}`,
510
+ right: "",
511
+ };
302
512
  }
303
513
  }
304
- if (realQuota?.type === "gemini") {
514
+ if (provider === "gemini" && realQuota?.type === "gemini") {
305
515
  const pools = realQuota.pools || {};
306
516
  if (pools.pro || pools.flash) {
307
- const pP = pools.pro ? clampPercent(Math.round((1 - (pools.pro.remainingFraction ?? 1)) * 100)) : null;
308
- const pF = pools.flash ? clampPercent(Math.round((1 - (pools.flash.remainingFraction ?? 1)) * 100)) : null;
309
- const pStr = pP != null ? colorByProvider(pP, `${pP}`, provFn) : dim("--");
310
- const fStr = pF != null ? colorByProvider(pF, `${pF}`, provFn) : dim("--");
311
- return { prefix: minPrefix, left: `${pStr}${dim("/")}${fStr}`, right: "" };
517
+ const pP = pools.pro
518
+ ? clampPercent(
519
+ Math.round((1 - (pools.pro.remainingFraction ?? 1)) * 100),
520
+ )
521
+ : null;
522
+ const pF = pools.flash
523
+ ? clampPercent(
524
+ Math.round((1 - (pools.flash.remainingFraction ?? 1)) * 100),
525
+ )
526
+ : null;
527
+ const pStr =
528
+ pP != null ? colorByProvider(pP, `${pP}`, provFn) : dim("--");
529
+ const fStr =
530
+ pF != null ? colorByProvider(pF, `${pF}`, provFn) : dim("--");
531
+ return {
532
+ prefix: minPrefix,
533
+ left: `${pStr}${dim("/")}${fStr}`,
534
+ right: "",
535
+ };
312
536
  }
313
537
  }
314
538
  return { prefix: minPrefix, left: dim("--/--"), right: "" };
315
539
  }
316
540
 
317
541
  if (currentTier === "minimal") {
542
+ if (provider === "antigravity") {
543
+ quotaSection = formatAntigravityQuotaSection(currentTier);
544
+ }
318
545
  if (realQuota?.type === "codex") {
319
- const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
546
+ const main =
547
+ realQuota.buckets.codex ||
548
+ realQuota.buckets[Object.keys(realQuota.buckets)[0]];
320
549
  if (main) {
321
- const fiveP = main.primary?.used_percent != null ? clampPercent(main.primary.used_percent) : null;
322
- const weekP = main.secondary?.used_percent != null ? clampPercent(main.secondary.used_percent) : null;
323
- const fCell = fiveP != null ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn) : dim(formatPlaceholderPercentCell());
324
- const wCell = weekP != null ? colorByProvider(weekP, formatPercentCell(weekP), provFn) : dim(formatPlaceholderPercentCell());
550
+ const fiveP =
551
+ main.primary?.used_percent != null
552
+ ? clampPercent(main.primary.used_percent)
553
+ : null;
554
+ const weekP =
555
+ main.secondary?.used_percent != null
556
+ ? clampPercent(main.secondary.used_percent)
557
+ : null;
558
+ const fCell =
559
+ fiveP != null
560
+ ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn)
561
+ : dim(formatPlaceholderPercentCell());
562
+ const wCell =
563
+ weekP != null
564
+ ? colorByProvider(weekP, formatPercentCell(weekP), provFn)
565
+ : dim(formatPlaceholderPercentCell());
325
566
  quotaSection = `${dim("5h:")}${fCell} ${dim("1w:")}${wCell}`;
326
567
  }
327
568
  }
328
- if (realQuota?.type === "gemini") {
569
+ if (provider === "gemini" && realQuota?.type === "gemini") {
329
570
  const pools = realQuota.pools || {};
330
571
  if (pools.pro || pools.flash) {
331
572
  const slot = (bucket, label) => {
332
- if (!bucket) return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())}`;
573
+ if (!bucket)
574
+ return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())}`;
333
575
  const gl = deriveGeminiLimits(bucket);
334
- const usedP = gl ? gl.usedPct : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
576
+ const usedP = gl
577
+ ? gl.usedPct
578
+ : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
335
579
  return `${dim(label + ":")}${colorByProvider(usedP, formatPercentCell(usedP), provFn)}`;
336
580
  };
337
581
  quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
@@ -343,31 +587,61 @@ export function getProviderRow(currentTier, provider, marker, markerColor, qosPr
343
587
  quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ${dim("1w:")}${dim(formatPlaceholderPercentCell())}`;
344
588
  }
345
589
  const prefix = `${bold(markerColor(`${marker}`))}:`;
346
- return { prefix, left: quotaSection, right: accountLabel ? markerColor(accountLabel) : "" };
590
+ return {
591
+ prefix,
592
+ left: quotaSection,
593
+ right: accountLabel ? markerColor(accountLabel) : "",
594
+ };
347
595
  }
348
596
 
349
597
  if (currentTier === "compact") {
598
+ if (provider === "antigravity") {
599
+ quotaSection = formatAntigravityQuotaSection(currentTier);
600
+ }
350
601
  if (realQuota?.type === "codex") {
351
- const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
602
+ const main =
603
+ realQuota.buckets.codex ||
604
+ realQuota.buckets[Object.keys(realQuota.buckets)[0]];
352
605
  if (main) {
353
- const fiveP = main.primary?.used_percent != null ? clampPercent(main.primary.used_percent) : null;
354
- const weekP = main.secondary?.used_percent != null ? clampPercent(main.secondary.used_percent) : null;
355
- const fCell = fiveP != null ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn) : dim(formatPlaceholderPercentCell());
356
- const wCell = weekP != null ? colorByProvider(weekP, formatPercentCell(weekP), provFn) : dim(formatPlaceholderPercentCell());
357
- const fiveReset = formatResetRemaining(main.primary?.resets_at, FIVE_HOUR_MS) || "n/a";
358
- const weekReset = formatResetRemainingDayHour(main.secondary?.resets_at, SEVEN_DAY_MS) || "n/a";
606
+ const fiveP =
607
+ main.primary?.used_percent != null
608
+ ? clampPercent(main.primary.used_percent)
609
+ : null;
610
+ const weekP =
611
+ main.secondary?.used_percent != null
612
+ ? clampPercent(main.secondary.used_percent)
613
+ : null;
614
+ const fCell =
615
+ fiveP != null
616
+ ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn)
617
+ : dim(formatPlaceholderPercentCell());
618
+ const wCell =
619
+ weekP != null
620
+ ? colorByProvider(weekP, formatPercentCell(weekP), provFn)
621
+ : dim(formatPlaceholderPercentCell());
622
+ const fiveReset =
623
+ formatResetRemaining(main.primary?.resets_at, FIVE_HOUR_MS) || "n/a";
624
+ const weekReset =
625
+ formatResetRemainingDayHour(
626
+ main.secondary?.resets_at,
627
+ SEVEN_DAY_MS,
628
+ ) || "n/a";
359
629
  quotaSection = `${dim("5h:")}${fCell} ${dim(formatTimeCell(fiveReset))} ${dim("1w:")}${wCell} ${dim(formatTimeCellDH(weekReset))}`;
360
630
  }
361
631
  }
362
- if (realQuota?.type === "gemini") {
632
+ if (provider === "gemini" && realQuota?.type === "gemini") {
363
633
  const pools = realQuota.pools || {};
364
634
  const hasAnyPool = pools.pro || pools.flash;
365
635
  if (hasAnyPool) {
366
636
  const slot = (bucket, label) => {
367
- if (!bucket) return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
637
+ if (!bucket)
638
+ return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
368
639
  const gl = deriveGeminiLimits(bucket);
369
- const usedP = gl ? gl.usedPct : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
370
- const rstRemaining = formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
640
+ const usedP = gl
641
+ ? gl.usedPct
642
+ : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
643
+ const rstRemaining =
644
+ formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
371
645
  return `${dim(label + ":")}${colorByProvider(usedP, formatPercentCell(usedP), provFn)} ${dim(formatTimeCell(rstRemaining))}`;
372
646
  };
373
647
  quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
@@ -379,30 +653,63 @@ export function getProviderRow(currentTier, provider, marker, markerColor, qosPr
379
653
  quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("1w:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCellDH("--d--h"))}`;
380
654
  }
381
655
  const prefix = `${bold(markerColor(`${marker}`))}:`;
382
- const compactRight = [svStr ? `${dim("sv:")}${svStr}` : "", accountLabel ? markerColor(accountLabel) : ""].filter(Boolean).join(" ");
656
+ const compactRight = [
657
+ svStr ? `${dim("sv:")}${svStr}` : "",
658
+ accountLabel ? markerColor(accountLabel) : "",
659
+ ]
660
+ .filter(Boolean)
661
+ .join(" ");
383
662
  return { prefix, left: quotaSection, right: compactRight };
384
663
  }
385
664
 
386
665
  // full tier
666
+ if (provider === "antigravity") {
667
+ quotaSection = formatAntigravityQuotaSection(currentTier);
668
+ }
669
+
387
670
  if (realQuota?.type === "codex") {
388
- const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
671
+ const main =
672
+ realQuota.buckets.codex ||
673
+ realQuota.buckets[Object.keys(realQuota.buckets)[0]];
389
674
  if (main) {
390
- const fiveP = main.primary?.used_percent != null ? clampPercent(main.primary.used_percent) : null;
391
- const weekP = main.secondary?.used_percent != null ? clampPercent(main.secondary.used_percent) : null;
392
- const fiveReset = formatResetRemaining(main.primary?.resets_at, FIVE_HOUR_MS) || "n/a";
393
- const weekReset = formatResetRemainingDayHour(main.secondary?.resets_at, SEVEN_DAY_MS) || "n/a";
394
- const fCell = fiveP != null ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn) : dim(formatPlaceholderPercentCell());
395
- const wCell = weekP != null ? colorByProvider(weekP, formatPercentCell(weekP), provFn) : dim(formatPlaceholderPercentCell());
396
- const fBar = fiveP != null ? tierBar(currentTier, fiveP, provAnsi) : tierDimBar(currentTier);
397
- const wBar = weekP != null ? tierBar(currentTier, weekP, provAnsi) : tierDimBar(currentTier);
398
- quotaSection = `${dim("5h:")}${fBar}${fCell} ` +
675
+ const fiveP =
676
+ main.primary?.used_percent != null
677
+ ? clampPercent(main.primary.used_percent)
678
+ : null;
679
+ const weekP =
680
+ main.secondary?.used_percent != null
681
+ ? clampPercent(main.secondary.used_percent)
682
+ : null;
683
+ const fiveReset =
684
+ formatResetRemaining(main.primary?.resets_at, FIVE_HOUR_MS) || "n/a";
685
+ const weekReset =
686
+ formatResetRemainingDayHour(main.secondary?.resets_at, SEVEN_DAY_MS) ||
687
+ "n/a";
688
+ const fCell =
689
+ fiveP != null
690
+ ? colorByProvider(fiveP, formatPercentCell(fiveP), provFn)
691
+ : dim(formatPlaceholderPercentCell());
692
+ const wCell =
693
+ weekP != null
694
+ ? colorByProvider(weekP, formatPercentCell(weekP), provFn)
695
+ : dim(formatPlaceholderPercentCell());
696
+ const fBar =
697
+ fiveP != null
698
+ ? tierBar(currentTier, fiveP, provAnsi)
699
+ : tierDimBar(currentTier);
700
+ const wBar =
701
+ weekP != null
702
+ ? tierBar(currentTier, weekP, provAnsi)
703
+ : tierDimBar(currentTier);
704
+ quotaSection =
705
+ `${dim("5h:")}${fBar}${fCell} ` +
399
706
  `${dim(formatTimeCell(fiveReset))} ` +
400
707
  `${dim("1w:")}${wBar}${wCell} ` +
401
708
  `${dim(formatTimeCellDH(weekReset))}`;
402
709
  }
403
710
  }
404
711
 
405
- if (realQuota?.type === "gemini") {
712
+ if (provider === "gemini" && realQuota?.type === "gemini") {
406
713
  const pools = realQuota.pools || {};
407
714
  const hasAnyPool = pools.pro || pools.flash;
408
715
 
@@ -412,14 +719,18 @@ export function getProviderRow(currentTier, provider, marker, markerColor, qosPr
412
719
  return `${dim(label + ":")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
413
720
  }
414
721
  const gl = deriveGeminiLimits(bucket);
415
- const usedP = gl ? gl.usedPct : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
416
- const rstRemaining = formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
722
+ const usedP = gl
723
+ ? gl.usedPct
724
+ : clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
725
+ const rstRemaining =
726
+ formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
417
727
  return `${dim(label + ":")}${tierBar(currentTier, usedP, provAnsi)}${colorByProvider(usedP, formatPercentCell(usedP), provFn)} ${dim(formatTimeCell(rstRemaining))}`;
418
728
  };
419
729
 
420
730
  quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
421
731
  } else {
422
- quotaSection = `${dim("Pr:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ` +
732
+ quotaSection =
733
+ `${dim("Pr:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ` +
423
734
  `${dim("Fl:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
424
735
  }
425
736
  }
@@ -433,7 +744,9 @@ export function getProviderRow(currentTier, provider, marker, markerColor, qosPr
433
744
  const accountSection = `${markerColor(accountLabel)}`;
434
745
  const svSection = svStr ? `${dim("sv:")}${svStr}` : "";
435
746
  const modelLabelSection = modelLabel ? markerColor(modelLabel) : "";
436
- const rightParts = [svSection, accountSection, modelLabelSection].filter(Boolean);
747
+ const rightParts = [svSection, accountSection, modelLabelSection].filter(
748
+ Boolean,
749
+ );
437
750
  return {
438
751
  prefix,
439
752
  left: quotaSection,