triflux 2.0.3 → 2.1.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.
- package/README.ko.md +1 -8
- package/README.md +1 -8
- package/bin/tfx-doctor.mjs +1 -1
- package/bin/tfx-setup.mjs +1 -1
- package/bin/triflux.mjs +1 -1
- package/hud/hud-qos-status.mjs +190 -76
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -51,14 +51,7 @@
|
|
|
51
51
|
<details>
|
|
52
52
|
<summary><strong>설치</strong></summary>
|
|
53
53
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
/plugin marketplace add https://github.com/tellang/triflux
|
|
58
|
-
/plugin install triflux
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### npm (전역)
|
|
54
|
+
### npm (권장)
|
|
62
55
|
|
|
63
56
|
```bash
|
|
64
57
|
npm install -g triflux
|
package/README.md
CHANGED
|
@@ -51,14 +51,7 @@
|
|
|
51
51
|
<details>
|
|
52
52
|
<summary><strong>Install</strong></summary>
|
|
53
53
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
/plugin marketplace add https://github.com/tellang/triflux
|
|
58
|
-
/plugin install triflux
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### npm (global)
|
|
54
|
+
### npm (recommended)
|
|
62
55
|
|
|
63
56
|
```bash
|
|
64
57
|
npm install -g triflux
|
package/bin/tfx-doctor.mjs
CHANGED
package/bin/tfx-setup.mjs
CHANGED
package/bin/triflux.mjs
CHANGED
package/hud/hud-qos-status.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import https from "node:https";
|
|
|
7
7
|
import { createHash } from "node:crypto";
|
|
8
8
|
import { spawn, execSync } from "node:child_process";
|
|
9
9
|
|
|
10
|
-
const VERSION = "1.
|
|
10
|
+
const VERSION = "1.8";
|
|
11
11
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// ANSI 색상 (OMC colors.js 스키마 일치)
|
|
@@ -52,17 +52,24 @@ function colorParallel(current, cap) {
|
|
|
52
52
|
return red(`${current}/${cap}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function coloredBar(percent, width = 8) {
|
|
55
|
+
function coloredBar(percent, width = 8, baseColor = null) {
|
|
56
56
|
const safePercent = Math.min(100, Math.max(0, percent));
|
|
57
57
|
const filled = Math.round((safePercent / 100) * width);
|
|
58
58
|
const empty = width - filled;
|
|
59
59
|
let barColor;
|
|
60
60
|
if (safePercent >= 85) barColor = RED;
|
|
61
61
|
else if (safePercent >= 70) barColor = YELLOW;
|
|
62
|
-
else barColor = GREEN;
|
|
62
|
+
else barColor = baseColor || GREEN;
|
|
63
63
|
return `${barColor}${"█".repeat(filled)}${DIM}${"░".repeat(empty)}${RESET}`;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// 프로바이더별 색상 % (< 70%: 프로바이더 색, ≥ 70%: 경고색)
|
|
67
|
+
function colorByProvider(value, text, providerColorFn) {
|
|
68
|
+
if (value >= 85) return red(text);
|
|
69
|
+
if (value >= 70) return yellow(text);
|
|
70
|
+
return providerColorFn(text);
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
// ============================================================================
|
|
67
74
|
// 상수 / 경로
|
|
68
75
|
// ============================================================================
|
|
@@ -151,6 +158,28 @@ function getTerminalColumns() {
|
|
|
151
158
|
return 0;
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
let _cachedRows = 0;
|
|
162
|
+
function getTerminalRows() {
|
|
163
|
+
if (_cachedRows > 0) return _cachedRows;
|
|
164
|
+
if (process.stdout.rows) { _cachedRows = process.stdout.rows; return _cachedRows; }
|
|
165
|
+
if (process.stderr.rows) { _cachedRows = process.stderr.rows; return _cachedRows; }
|
|
166
|
+
const envLines = Number(process.env.LINES);
|
|
167
|
+
if (envLines > 0) { _cachedRows = envLines; return _cachedRows; }
|
|
168
|
+
try {
|
|
169
|
+
if (process.platform === "win32") {
|
|
170
|
+
const raw = execSync("mode con", { timeout: 2000, stdio: ["pipe", "pipe", "pipe"], windowsHide: true }).toString();
|
|
171
|
+
const m = raw.match(/Lines[^:]*:\s*(\d+)/i) || raw.match(/줄[^:]*:\s*(\d+)/);
|
|
172
|
+
if (m) { _cachedRows = Number(m[1]); return _cachedRows; }
|
|
173
|
+
} else {
|
|
174
|
+
const raw = execSync("tput lines 2>/dev/null || stty size 2>/dev/null | awk '{print $1}'", {
|
|
175
|
+
timeout: 2000, stdio: ["pipe", "pipe", "pipe"],
|
|
176
|
+
}).toString().trim();
|
|
177
|
+
if (raw && !isNaN(Number(raw))) { _cachedRows = Number(raw); return _cachedRows; }
|
|
178
|
+
}
|
|
179
|
+
} catch { /* 감지 실패 */ }
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
|
|
154
183
|
function detectCompactMode() {
|
|
155
184
|
// 1. 명시적 CLI 플래그
|
|
156
185
|
if (process.argv.includes("--compact")) return true;
|
|
@@ -191,6 +220,74 @@ function detectMinimalMode() {
|
|
|
191
220
|
|
|
192
221
|
const MINIMAL_MODE = detectMinimalMode();
|
|
193
222
|
|
|
223
|
+
// ============================================================================
|
|
224
|
+
// 4-Tier 적응형 렌더링: full > normal > compact > nano
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// 초기 tier (stdin 없이 결정 가능한 수준)
|
|
227
|
+
let CURRENT_TIER = MINIMAL_MODE ? "nano" : COMPACT_MODE ? "compact" : "normal";
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 인디케이터 인식 + 터미널 크기 기반 tier 자동 선택.
|
|
231
|
+
* main()에서 stdin 수신 후 호출하여 CURRENT_TIER 갱신.
|
|
232
|
+
*/
|
|
233
|
+
function selectTier(stdin, claudeUsage = null) {
|
|
234
|
+
const hudConfig = readJson(HUD_CONFIG_PATH, null);
|
|
235
|
+
|
|
236
|
+
// 1) 명시적 tier 강제 설정
|
|
237
|
+
const forcedTier = hudConfig?.tier;
|
|
238
|
+
if (["full", "normal", "compact", "nano"].includes(forcedTier)) return forcedTier;
|
|
239
|
+
|
|
240
|
+
// 2) 기존 모드 플래그 존중
|
|
241
|
+
if (MINIMAL_MODE) return "nano";
|
|
242
|
+
if (COMPACT_MODE) return "compact";
|
|
243
|
+
|
|
244
|
+
// 3) autoResize 비활성이면 normal 유지
|
|
245
|
+
if (hudConfig?.autoResize === false) return "normal";
|
|
246
|
+
|
|
247
|
+
// 4) 터미널 행/열에서 상태영역 예산 추정
|
|
248
|
+
const rows = getTerminalRows();
|
|
249
|
+
const cols = getTerminalColumns() || 120;
|
|
250
|
+
let budget;
|
|
251
|
+
if (rows >= 40) budget = 6;
|
|
252
|
+
else if (rows >= 35) budget = 5;
|
|
253
|
+
else if (rows >= 28) budget = 4;
|
|
254
|
+
else if (rows >= 22) budget = 3;
|
|
255
|
+
else if (rows > 0) budget = 2;
|
|
256
|
+
else budget = 5; // rows 감지 불가 → 넉넉하게
|
|
257
|
+
|
|
258
|
+
// 5) 인디케이터 줄 추정
|
|
259
|
+
let indicatorRows = 1; // bypass permissions (거의 항상 표시)
|
|
260
|
+
const contextPercent = getContextPercent(stdin);
|
|
261
|
+
if (contextPercent >= 85) indicatorRows += 1; // "Context low" 배너
|
|
262
|
+
// Claude Code 사용량 경고 (노란색 배너: "You've used X% of your ... limit")
|
|
263
|
+
const weeklyPct = claudeUsage?.weeklyPercent ?? 0;
|
|
264
|
+
const fiveHourPct = claudeUsage?.fiveHourPercent ?? 0;
|
|
265
|
+
if (weeklyPct >= 80) indicatorRows += 1;
|
|
266
|
+
if (fiveHourPct >= 80) indicatorRows += 1;
|
|
267
|
+
|
|
268
|
+
// 6) 각 tier에서 줄바꿈 없이 3줄 가용한지 확인
|
|
269
|
+
const tierWidths = { full: 75, normal: 60, compact: 40, nano: 34 };
|
|
270
|
+
for (const tier of ["full", "normal", "compact", "nano"]) {
|
|
271
|
+
const lineWidth = tierWidths[tier];
|
|
272
|
+
const visualRowsPerLine = Math.ceil(lineWidth / Math.max(cols, 1));
|
|
273
|
+
const totalVisualRows = (3 * visualRowsPerLine) + indicatorRows;
|
|
274
|
+
if (totalVisualRows <= budget) return tier;
|
|
275
|
+
}
|
|
276
|
+
return "nano";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// full tier 전용: 게이지 바 접두사 (normal 이하 tier에서는 빈 문자열)
|
|
280
|
+
function tierBar(percent, baseColor = null) {
|
|
281
|
+
return CURRENT_TIER === "full" ? `${coloredBar(percent, 8, baseColor)} ` : "";
|
|
282
|
+
}
|
|
283
|
+
function tierDimBar() {
|
|
284
|
+
return CURRENT_TIER === "full" ? `${dim("░".repeat(8))} ` : "";
|
|
285
|
+
}
|
|
286
|
+
// Gemini ∞% 전용: 무한 쿼터이므로 dim 회색 바
|
|
287
|
+
function tierInfBar() {
|
|
288
|
+
return CURRENT_TIER === "full" ? `${DIM}${"█".repeat(8)}${RESET} ` : "";
|
|
289
|
+
}
|
|
290
|
+
|
|
194
291
|
// ============================================================================
|
|
195
292
|
// 유틸
|
|
196
293
|
// ============================================================================
|
|
@@ -308,15 +405,11 @@ function normalizeTimeToken(value) {
|
|
|
308
405
|
const text = String(value || "n/a");
|
|
309
406
|
const hourMinute = text.match(/^(\d+)h(\d+)m$/);
|
|
310
407
|
if (hourMinute) {
|
|
311
|
-
|
|
312
|
-
const minutes = String(Number(hourMinute[2])).padStart(2, "0");
|
|
313
|
-
return `${hours}h${minutes}m`;
|
|
408
|
+
return `${Number(hourMinute[1])}h${Number(hourMinute[2])}m`;
|
|
314
409
|
}
|
|
315
410
|
const dayHour = text.match(/^(\d+)d(\d+)h$/);
|
|
316
411
|
if (dayHour) {
|
|
317
|
-
|
|
318
|
-
const hours = String(Number(dayHour[2])).padStart(2, "0");
|
|
319
|
-
return `${days}d${hours}h`;
|
|
412
|
+
return `${Number(dayHour[1])}d${Number(dayHour[2])}h`;
|
|
320
413
|
}
|
|
321
414
|
return text;
|
|
322
415
|
}
|
|
@@ -655,11 +748,11 @@ function formatResetRemaining(isoOrUnix) {
|
|
|
655
748
|
const d = typeof isoOrUnix === "string" ? new Date(isoOrUnix) : new Date(isoOrUnix * 1000);
|
|
656
749
|
if (isNaN(d.getTime())) return "";
|
|
657
750
|
const diffMs = d.getTime() - Date.now();
|
|
658
|
-
if (diffMs <= 0) return "
|
|
751
|
+
if (diffMs <= 0) return "0h0m";
|
|
659
752
|
const totalMinutes = Math.floor(diffMs / 60000);
|
|
660
753
|
const totalHours = Math.floor(totalMinutes / 60);
|
|
661
754
|
const minutes = totalMinutes % 60;
|
|
662
|
-
return `${
|
|
755
|
+
return `${totalHours}h${minutes}m`;
|
|
663
756
|
}
|
|
664
757
|
|
|
665
758
|
function isResetPast(isoOrUnix) {
|
|
@@ -673,11 +766,11 @@ function formatResetRemainingDayHour(isoOrUnix) {
|
|
|
673
766
|
const d = typeof isoOrUnix === "string" ? new Date(isoOrUnix) : new Date(isoOrUnix * 1000);
|
|
674
767
|
if (isNaN(d.getTime())) return "";
|
|
675
768
|
const diffMs = d.getTime() - Date.now();
|
|
676
|
-
if (diffMs <= 0) return "
|
|
769
|
+
if (diffMs <= 0) return "0d0h";
|
|
677
770
|
const totalMinutes = Math.floor(diffMs / 60000);
|
|
678
771
|
const days = Math.floor(totalMinutes / (60 * 24));
|
|
679
772
|
const hours = Math.floor((totalMinutes % (60 * 24)) / 60);
|
|
680
|
-
return `${days}d${
|
|
773
|
+
return `${days}d${hours}h`;
|
|
681
774
|
}
|
|
682
775
|
|
|
683
776
|
function calcCooldownLeftSeconds(isoDatetime) {
|
|
@@ -1056,13 +1149,22 @@ function formatSavings(dollars) {
|
|
|
1056
1149
|
return `$${dollars.toFixed(2)}`;
|
|
1057
1150
|
}
|
|
1058
1151
|
|
|
1059
|
-
|
|
1152
|
+
// sv 퍼센트 포맷 (1000+ → k 표기, 5자 고정폭)
|
|
1153
|
+
const SV_CELL_WIDTH = 5;
|
|
1154
|
+
function formatSvPct(value) {
|
|
1155
|
+
if (value == null) return "--%".padStart(SV_CELL_WIDTH);
|
|
1156
|
+
if (value >= 10000) return `${Math.round(value / 1000)}k%`.padStart(SV_CELL_WIDTH);
|
|
1157
|
+
if (value >= 1000) return `${(value / 1000).toFixed(1)}k%`.padStart(SV_CELL_WIDTH);
|
|
1158
|
+
return `${value}%`.padStart(SV_CELL_WIDTH);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
function getClaudeRows(stdin, claudeUsage, combinedSvPct) {
|
|
1060
1162
|
const contextPercent = getContextPercent(stdin);
|
|
1061
1163
|
const prefix = `${bold(claudeOrange("c"))}:`;
|
|
1062
1164
|
|
|
1063
|
-
// 절약
|
|
1064
|
-
const
|
|
1065
|
-
const svSuffix = `${dim("sv:")}${
|
|
1165
|
+
// 절약 퍼센트 (Codex+Gemini sv% 합산, x/g와 동일 형식)
|
|
1166
|
+
const svStr = formatSvPct(combinedSvPct || 0);
|
|
1167
|
+
const svSuffix = `${dim("sv:")}${svStr}`;
|
|
1066
1168
|
|
|
1067
1169
|
// API 실측 데이터 사용 (없으면 플레이스홀더)
|
|
1068
1170
|
const fiveHourPercent = claudeUsage?.fiveHourPercent ?? 0;
|
|
@@ -1076,7 +1178,7 @@ function getClaudeRows(stdin, claudeUsage, totalSvDollar) {
|
|
|
1076
1178
|
|
|
1077
1179
|
const hasData = claudeUsage != null;
|
|
1078
1180
|
|
|
1079
|
-
if (
|
|
1181
|
+
if (CURRENT_TIER === "nano") {
|
|
1080
1182
|
const cols = getTerminalColumns() || 80;
|
|
1081
1183
|
if (!hasData) {
|
|
1082
1184
|
const quotaSection = cols < 40
|
|
@@ -1085,38 +1187,37 @@ function getClaudeRows(stdin, claudeUsage, totalSvDollar) {
|
|
|
1085
1187
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
1086
1188
|
}
|
|
1087
1189
|
if (cols < 40) {
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
`${colorByPercent(weeklyPercent, `${weeklyPercent}%`)} ` +
|
|
1190
|
+
const quotaSection = `${colorByProvider(fiveHourPercent, `${fiveHourPercent}%`, claudeOrange)}${dim("/")}` +
|
|
1191
|
+
`${colorByProvider(weeklyPercent, `${weeklyPercent}%`, claudeOrange)} ` +
|
|
1091
1192
|
`${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1092
1193
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
1093
1194
|
}
|
|
1094
|
-
//
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
`${dim("
|
|
1098
|
-
`${svCompact} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1195
|
+
// nano: c: 5h 12% 1w 95% sv: 191% ctx:90%
|
|
1196
|
+
const quotaSection = `${dim("5h")} ${colorByProvider(fiveHourPercent, formatPercentCell(fiveHourPercent), claudeOrange)} ` +
|
|
1197
|
+
`${dim("1w")} ${colorByProvider(weeklyPercent, formatPercentCell(weeklyPercent), claudeOrange)} ` +
|
|
1198
|
+
`${dim("sv:")}${svStr} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1099
1199
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
1100
1200
|
}
|
|
1101
1201
|
|
|
1102
|
-
if (
|
|
1103
|
-
// 데이터 없을 때: 전체 dim 처리 (green 0% 대신)
|
|
1202
|
+
if (CURRENT_TIER === "compact") {
|
|
1104
1203
|
if (!hasData) {
|
|
1105
|
-
const quotaSection = `${dim("5h:
|
|
1106
|
-
`${
|
|
1204
|
+
const quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ` +
|
|
1205
|
+
`${dim("1w:")}${dim(formatPlaceholderPercentCell())} ` +
|
|
1206
|
+
`${dim("|")} ${svSuffix} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1107
1207
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
1108
1208
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
`${
|
|
1209
|
+
// compact: c: 5h: 14% 1w: 96% | sv: 191% ctx:43%
|
|
1210
|
+
const quotaSection = `${dim("5h:")}${colorByProvider(fiveHourPercent, formatPercentCell(fiveHourPercent), claudeOrange)} ` +
|
|
1211
|
+
`${dim("1w:")}${colorByProvider(weeklyPercent, formatPercentCell(weeklyPercent), claudeOrange)} ` +
|
|
1212
|
+
`${dim("|")} ${svSuffix} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1112
1213
|
return [{ prefix, left: quotaSection, right: "" }];
|
|
1113
1214
|
}
|
|
1114
1215
|
|
|
1115
1216
|
// 데이터 없을 때: 퍼센트+시간 모두 dim 처리 (green 0% 대신)
|
|
1116
1217
|
if (!hasData) {
|
|
1117
|
-
const quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ` +
|
|
1218
|
+
const quotaSection = `${dim("5h:")}${tierDimBar()}${dim(formatPlaceholderPercentCell())} ` +
|
|
1118
1219
|
`${dim(formatTimeCell(fiveHourReset))} ` +
|
|
1119
|
-
`${dim("1w:")}${dim(formatPlaceholderPercentCell())} ` +
|
|
1220
|
+
`${dim("1w:")}${tierDimBar()}${dim(formatPlaceholderPercentCell())} ` +
|
|
1120
1221
|
`${dim(formatTimeCellDH(weeklyReset))}`;
|
|
1121
1222
|
const contextSection = `${svSuffix} ${dim("|")} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1122
1223
|
return [{ prefix, left: quotaSection, right: contextSection }];
|
|
@@ -1126,9 +1227,9 @@ function getClaudeRows(stdin, claudeUsage, totalSvDollar) {
|
|
|
1126
1227
|
const weeklyPercentCell = formatPercentCell(weeklyPercent);
|
|
1127
1228
|
const fiveHourTimeCell = formatTimeCell(fiveHourReset);
|
|
1128
1229
|
const weeklyTimeCell = formatTimeCellDH(weeklyReset);
|
|
1129
|
-
const quotaSection = `${dim("5h:")}${
|
|
1230
|
+
const quotaSection = `${dim("5h:")}${tierBar(fiveHourPercent, CLAUDE_ORANGE)}${colorByProvider(fiveHourPercent, fiveHourPercentCell, claudeOrange)} ` +
|
|
1130
1231
|
`${dim(fiveHourTimeCell)} ` +
|
|
1131
|
-
`${dim("1w:")}${
|
|
1232
|
+
`${dim("1w:")}${tierBar(weeklyPercent, CLAUDE_ORANGE)}${colorByProvider(weeklyPercent, weeklyPercentCell, claudeOrange)} ` +
|
|
1132
1233
|
`${dim(weeklyTimeCell)}`;
|
|
1133
1234
|
const contextSection = `${svSuffix} ${dim("|")} ${dim("ctx:")}${colorByPercent(contextPercent, `${contextPercent}%`)}`;
|
|
1134
1235
|
return [{ prefix, left: quotaSection, right: contextSection }];
|
|
@@ -1151,26 +1252,30 @@ function getProviderRow(provider, marker, markerColor, qosProfile, accountsConfi
|
|
|
1151
1252
|
|
|
1152
1253
|
// 절약 퍼센트 섹션 (context window 대비 %, 4자리 고정폭)
|
|
1153
1254
|
const svPct = savingsMultiplier != null ? Math.round(savingsMultiplier * 100) : null;
|
|
1154
|
-
const svStr = svPct
|
|
1255
|
+
const svStr = formatSvPct(svPct);
|
|
1155
1256
|
const modelLabelStr = modelLabel ? ` ${markerColor(modelLabel)}` : "";
|
|
1156
1257
|
|
|
1258
|
+
// ── 프로바이더별 색상 프로필 ──
|
|
1259
|
+
const provAnsi = provider === "codex" ? CODEX_WHITE : provider === "gemini" ? GEMINI_BLUE : GREEN;
|
|
1260
|
+
const provFn = provider === "codex" ? codexWhite : provider === "gemini" ? geminiBlue : green;
|
|
1261
|
+
|
|
1157
1262
|
// ── 쿼터 섹션 ──
|
|
1158
1263
|
let quotaSection;
|
|
1159
1264
|
let extraRightSection = "";
|
|
1160
1265
|
|
|
1161
|
-
if (
|
|
1266
|
+
if (CURRENT_TIER === "nano") {
|
|
1162
1267
|
const cols = getTerminalColumns() || 80;
|
|
1163
1268
|
const minPrefix = `${bold(markerColor(`${marker}`))}:`;
|
|
1164
|
-
const svCompact = svStr ? ` ${dim("sv:")}${cyan(svStr
|
|
1269
|
+
const svCompact = svStr ? ` ${dim("sv:")}${cyan(svStr)}` : "";
|
|
1165
1270
|
if (realQuota?.type === "codex") {
|
|
1166
1271
|
const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
|
|
1167
1272
|
if (main) {
|
|
1168
1273
|
const fiveP = isResetPast(main.primary?.resets_at) ? 0 : clampPercent(main.primary?.used_percent ?? 0);
|
|
1169
1274
|
const weekP = isResetPast(main.secondary?.resets_at) ? 0 : clampPercent(main.secondary?.used_percent ?? 0);
|
|
1170
1275
|
if (cols < 40) {
|
|
1171
|
-
return { prefix: minPrefix, left: `${
|
|
1276
|
+
return { prefix: minPrefix, left: `${colorByProvider(fiveP, formatPercentCell(fiveP), provFn)}${dim("/")}${colorByProvider(weekP, formatPercentCell(weekP), provFn)}${svCompact}`, right: "" };
|
|
1172
1277
|
}
|
|
1173
|
-
return { prefix: minPrefix, left: `${dim("5h")} ${
|
|
1278
|
+
return { prefix: minPrefix, left: `${dim("5h")} ${colorByProvider(fiveP, formatPercentCell(fiveP), provFn)} ${dim("1w")} ${colorByProvider(weekP, formatPercentCell(weekP), provFn)}${svCompact}`, right: "" };
|
|
1174
1279
|
}
|
|
1175
1280
|
}
|
|
1176
1281
|
if (realQuota?.type === "gemini") {
|
|
@@ -1178,39 +1283,39 @@ function getProviderRow(provider, marker, markerColor, qosProfile, accountsConfi
|
|
|
1178
1283
|
if (bucket) {
|
|
1179
1284
|
const usedP = clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
|
|
1180
1285
|
if (cols < 40) {
|
|
1181
|
-
return { prefix: minPrefix, left: `${
|
|
1286
|
+
return { prefix: minPrefix, left: `${colorByProvider(usedP, formatPercentCell(usedP), provFn)}${svCompact}`, right: "" };
|
|
1182
1287
|
}
|
|
1183
|
-
return { prefix: minPrefix, left: `${dim("1d")} ${
|
|
1288
|
+
return { prefix: minPrefix, left: `${dim("1d")} ${colorByProvider(usedP, formatPercentCell(usedP), provFn)} ${dim("1w")} ${dim("\u221E%".padStart(PERCENT_CELL_WIDTH))}${svCompact}`, right: "" };
|
|
1184
1289
|
}
|
|
1185
1290
|
}
|
|
1186
|
-
return { prefix: minPrefix, left: dim("--%"), right: "" };
|
|
1291
|
+
return { prefix: minPrefix, left: dim("--%".padStart(PERCENT_CELL_WIDTH)), right: "" };
|
|
1187
1292
|
}
|
|
1188
1293
|
|
|
1189
|
-
if (
|
|
1190
|
-
// 컴팩트 모드: 바 없이 퍼센트만, right 섹션 생략
|
|
1294
|
+
if (CURRENT_TIER === "compact") {
|
|
1191
1295
|
if (realQuota?.type === "codex") {
|
|
1192
1296
|
const main = realQuota.buckets.codex || realQuota.buckets[Object.keys(realQuota.buckets)[0]];
|
|
1193
1297
|
if (main) {
|
|
1194
1298
|
const fiveP = isResetPast(main.primary?.resets_at) ? 0 : clampPercent(main.primary?.used_percent ?? 0);
|
|
1195
1299
|
const weekP = isResetPast(main.secondary?.resets_at) ? 0 : clampPercent(main.secondary?.used_percent ?? 0);
|
|
1196
|
-
quotaSection = `${dim("5h:")}${
|
|
1197
|
-
`${dim("1w:")}${
|
|
1300
|
+
quotaSection = `${dim("5h:")}${colorByProvider(fiveP, formatPercentCell(fiveP), provFn)} ` +
|
|
1301
|
+
`${dim("1w:")}${colorByProvider(weekP, formatPercentCell(weekP), provFn)}`;
|
|
1198
1302
|
}
|
|
1199
1303
|
}
|
|
1200
1304
|
if (realQuota?.type === "gemini") {
|
|
1201
1305
|
const bucket = realQuota.quotaBucket;
|
|
1202
1306
|
if (bucket) {
|
|
1203
1307
|
const usedP = clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
|
|
1204
|
-
quotaSection = `${dim("1d:")}${
|
|
1308
|
+
quotaSection = `${dim("1d:")}${colorByProvider(usedP, formatPercentCell(usedP), provFn)} ${dim("1w:")}${dim("\u221E%".padStart(PERCENT_CELL_WIDTH))}`;
|
|
1205
1309
|
} else {
|
|
1206
|
-
quotaSection = `${dim("1d:")}${dim("--%")} ${dim("1w:")}${
|
|
1310
|
+
quotaSection = `${dim("1d:")}${dim("--%".padStart(PERCENT_CELL_WIDTH))} ${dim("1w:")}${dim("\u221E%".padStart(PERCENT_CELL_WIDTH))}`;
|
|
1207
1311
|
}
|
|
1208
1312
|
}
|
|
1209
1313
|
if (!quotaSection) {
|
|
1210
|
-
quotaSection = `${dim("5h:")}${
|
|
1314
|
+
quotaSection = `${dim("5h:")}${provFn("0%".padStart(PERCENT_CELL_WIDTH))} ${dim("1w:")}${provFn("0%".padStart(PERCENT_CELL_WIDTH))}`;
|
|
1211
1315
|
}
|
|
1212
1316
|
const prefix = `${bold(markerColor(`${marker}`))}:`;
|
|
1213
|
-
|
|
1317
|
+
// compact: sv + 계정 (모델 라벨 제거)
|
|
1318
|
+
const compactRight = [svStr ? `${dim("sv:")}${svStr}` : "", accountLabel ? markerColor(accountLabel) : ""].filter(Boolean).join(" ");
|
|
1214
1319
|
return { prefix, left: quotaSection, right: compactRight };
|
|
1215
1320
|
}
|
|
1216
1321
|
|
|
@@ -1221,9 +1326,9 @@ function getProviderRow(provider, marker, markerColor, qosProfile, accountsConfi
|
|
|
1221
1326
|
const weekP = isResetPast(main.secondary?.resets_at) ? 0 : clampPercent(main.secondary?.used_percent ?? 0);
|
|
1222
1327
|
const fiveReset = formatResetRemaining(main.primary?.resets_at) || "n/a";
|
|
1223
1328
|
const weekReset = formatResetRemainingDayHour(main.secondary?.resets_at) || "n/a";
|
|
1224
|
-
quotaSection = `${dim("5h:")}${
|
|
1329
|
+
quotaSection = `${dim("5h:")}${tierBar(fiveP, provAnsi)}${colorByProvider(fiveP, formatPercentCell(fiveP), provFn)} ` +
|
|
1225
1330
|
`${dim(formatTimeCell(fiveReset))} ` +
|
|
1226
|
-
`${dim("1w:")}${
|
|
1331
|
+
`${dim("1w:")}${tierBar(weekP, provAnsi)}${colorByProvider(weekP, formatPercentCell(weekP), provFn)} ` +
|
|
1227
1332
|
`${dim(formatTimeCellDH(weekReset))}`;
|
|
1228
1333
|
}
|
|
1229
1334
|
}
|
|
@@ -1233,17 +1338,17 @@ function getProviderRow(provider, marker, markerColor, qosProfile, accountsConfi
|
|
|
1233
1338
|
if (bucket) {
|
|
1234
1339
|
const usedP = clampPercent((1 - (bucket.remainingFraction ?? 1)) * 100);
|
|
1235
1340
|
const rstRemaining = formatResetRemaining(bucket.resetTime) || "n/a";
|
|
1236
|
-
quotaSection = `${dim("1d:")}${
|
|
1237
|
-
`${dim("1w:")}${
|
|
1341
|
+
quotaSection = `${dim("1d:")}${tierBar(usedP, provAnsi)}${colorByProvider(usedP, formatPercentCell(usedP), provFn)} ${dim(formatTimeCell(rstRemaining))} ` +
|
|
1342
|
+
`${dim("1w:")}${tierInfBar()}${dim("\u221E%".padStart(PERCENT_CELL_WIDTH))} ${dim(formatTimeCellDH("-d--h"))}`;
|
|
1238
1343
|
} else {
|
|
1239
|
-
quotaSection = `${dim("1d:")}${dim(formatPlaceholderPercentCell())} ` +
|
|
1240
|
-
`${dim(formatTimeCell("--h--m"))} ${dim("1w:")}${
|
|
1344
|
+
quotaSection = `${dim("1d:")}${tierDimBar()}${dim(formatPlaceholderPercentCell())} ` +
|
|
1345
|
+
`${dim(formatTimeCell("--h--m"))} ${dim("1w:")}${tierInfBar()}${dim("\u221E%".padStart(PERCENT_CELL_WIDTH))} ${dim(formatTimeCellDH("-d--h"))}`;
|
|
1241
1346
|
}
|
|
1242
1347
|
}
|
|
1243
1348
|
|
|
1244
1349
|
// 폴백: 쿼터 데이터 없을 때
|
|
1245
1350
|
if (!quotaSection) {
|
|
1246
|
-
quotaSection = `${dim("5h:")}${dim("--%")} ${dim("1w:")}${dim("--%")}`;
|
|
1351
|
+
quotaSection = `${dim("5h:")}${tierDimBar()}${dim("--%")} ${dim("1w:")}${tierDimBar()}${dim("--%")}`;
|
|
1247
1352
|
}
|
|
1248
1353
|
|
|
1249
1354
|
const prefix = `${bold(markerColor(`${marker}`))}:`;
|
|
@@ -1347,32 +1452,41 @@ async function main() {
|
|
|
1347
1452
|
|| geminiQuota?.buckets?.find((b) => b.modelId === "gemini-3-flash-preview")
|
|
1348
1453
|
|| null;
|
|
1349
1454
|
|
|
1350
|
-
// 합산 절약:
|
|
1351
|
-
const
|
|
1455
|
+
// 합산 절약: Codex+Gemini sv% 합산 (컨텍스트 대비 위임 토큰 비율)
|
|
1456
|
+
const combinedSvPct = Math.round(((codexSv ?? 0) + (geminiSv ?? 0)) * 100);
|
|
1457
|
+
|
|
1458
|
+
// 인디케이터 인식 tier 선택 (stdin + Claude 사용량 기반)
|
|
1459
|
+
CURRENT_TIER = selectTier(stdin, claudeUsageSnapshot.data);
|
|
1460
|
+
|
|
1461
|
+
const codexQuotaData = codexBuckets ? { type: "codex", buckets: codexBuckets } : null;
|
|
1462
|
+
const geminiQuotaData = { type: "gemini", quotaBucket: geminiBucket, session: geminiSession };
|
|
1352
1463
|
|
|
1353
1464
|
const rows = [
|
|
1354
|
-
...getClaudeRows(stdin, claudeUsageSnapshot.data,
|
|
1465
|
+
...getClaudeRows(stdin, claudeUsageSnapshot.data, combinedSvPct),
|
|
1355
1466
|
getProviderRow("codex", "x", codexWhite, qosProfile, accountsConfig, accountsState,
|
|
1356
|
-
|
|
1357
|
-
codexSv, null),
|
|
1467
|
+
codexQuotaData, codexEmail, codexSv, null),
|
|
1358
1468
|
getProviderRow("gemini", "g", geminiBlue, qosProfile, accountsConfig, accountsState,
|
|
1359
|
-
|
|
1360
|
-
geminiSv, getGeminiModelLabel(geminiModel)),
|
|
1469
|
+
geminiQuotaData, geminiEmail, geminiSv, null),
|
|
1361
1470
|
];
|
|
1471
|
+
|
|
1472
|
+
// 비활성 프로바이더 dim 처리: 데이터 없으면 전체 줄 dim
|
|
1473
|
+
const codexActive = codexBuckets != null;
|
|
1474
|
+
const geminiActive = (geminiSession?.total || 0) > 0 || geminiBucket != null;
|
|
1475
|
+
|
|
1362
1476
|
let outputLines = renderAlignedRows(rows);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
if (
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
outputLines[outputLines.length - 1] += ` ${last.trim()}`;
|
|
1369
|
-
}
|
|
1477
|
+
|
|
1478
|
+
// 비활성 줄 dim 래핑 (rows 순서: [claude, codex, gemini])
|
|
1479
|
+
if (outputLines.length >= 3) {
|
|
1480
|
+
if (!codexActive) outputLines[1] = `${DIM}${stripAnsi(outputLines[1])}${RESET}`;
|
|
1481
|
+
if (!geminiActive) outputLines[2] = `${DIM}${stripAnsi(outputLines[2])}${RESET}`;
|
|
1370
1482
|
}
|
|
1483
|
+
|
|
1371
1484
|
// Context low 메시지 뒤에 HUD가 분리되도록 선행 개행 추가
|
|
1372
1485
|
const contextPercent = getContextPercent(stdin);
|
|
1373
1486
|
const contextLowPrefix = contextPercent >= 85 ? "\n" : "";
|
|
1374
|
-
// RESET
|
|
1375
|
-
|
|
1487
|
+
// 줄별 RESET: Claude Code TUI 스타일 간섭 방지 (색상 밝기 버그 수정)
|
|
1488
|
+
const resetedLines = outputLines.map(line => `\x1b[0m${line}`);
|
|
1489
|
+
process.stdout.write(`${contextLowPrefix}${resetedLines.join("\n")}\n`);
|
|
1376
1490
|
}
|
|
1377
1491
|
|
|
1378
1492
|
main().catch(() => {
|