triflux 2.1.0 → 2.3.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/hud/hud-qos-status.mjs +132 -33
- package/package.json +1 -1
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 = "
|
|
10
|
+
const VERSION = "2.0";
|
|
11
11
|
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// ANSI 색상 (OMC colors.js 스키마 일치)
|
|
@@ -19,7 +19,7 @@ const RED = "\x1b[31m";
|
|
|
19
19
|
const GREEN = "\x1b[32m";
|
|
20
20
|
const YELLOW = "\x1b[33m";
|
|
21
21
|
const CYAN = "\x1b[36m";
|
|
22
|
-
const CLAUDE_ORANGE = "\x1b[38;5;
|
|
22
|
+
const CLAUDE_ORANGE = "\x1b[38;5;173m"; // #D87656 (Claude 테라코타)
|
|
23
23
|
const CODEX_WHITE = "\x1b[97m"; // bright white (SGR 37은 Windows Terminal에서 연회색 매핑)
|
|
24
24
|
const GEMINI_BLUE = "\x1b[38;5;39m";
|
|
25
25
|
|
|
@@ -52,15 +52,43 @@ function colorParallel(current, cap) {
|
|
|
52
52
|
return red(`${current}/${cap}`);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
const GAUGE_WIDTH = 5;
|
|
56
|
+
const GAUGE_BLOCKS = ["░", "▒", "▓", "█"]; // 밝기 0~3
|
|
57
|
+
|
|
58
|
+
function coloredBar(percent, width = GAUGE_WIDTH, baseColor = null) {
|
|
56
59
|
const safePercent = Math.min(100, Math.max(0, percent));
|
|
57
|
-
const
|
|
58
|
-
|
|
60
|
+
const perBlock = 100 / width;
|
|
61
|
+
|
|
62
|
+
// 상태별 색상
|
|
59
63
|
let barColor;
|
|
60
64
|
if (safePercent >= 85) barColor = RED;
|
|
61
65
|
else if (safePercent >= 70) barColor = YELLOW;
|
|
62
66
|
else barColor = baseColor || GREEN;
|
|
63
|
-
|
|
67
|
+
|
|
68
|
+
let bar = "";
|
|
69
|
+
for (let i = 0; i < width; i++) {
|
|
70
|
+
const blockStart = i * perBlock;
|
|
71
|
+
const blockEnd = (i + 1) * perBlock;
|
|
72
|
+
|
|
73
|
+
if (safePercent >= blockEnd) {
|
|
74
|
+
bar += "█"; // 완전 채움
|
|
75
|
+
} else if (safePercent > blockStart) {
|
|
76
|
+
// 프론티어: 구간 내 진행률
|
|
77
|
+
const progress = (safePercent - blockStart) / perBlock;
|
|
78
|
+
if (progress >= 0.75) bar += "▓";
|
|
79
|
+
else if (progress >= 0.33) bar += "▒";
|
|
80
|
+
else bar += "░";
|
|
81
|
+
} else {
|
|
82
|
+
bar += "░"; // 미도달
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 채워진 부분 = barColor, 빈 부분 = DIM
|
|
87
|
+
const filledEnd = Math.ceil(safePercent / perBlock);
|
|
88
|
+
const coloredPart = barColor + bar.slice(0, filledEnd) + RESET;
|
|
89
|
+
const dimPart = filledEnd < width ? DIM + bar.slice(filledEnd) + RESET : "";
|
|
90
|
+
|
|
91
|
+
return coloredPart + dimPart;
|
|
64
92
|
}
|
|
65
93
|
|
|
66
94
|
// 프로바이더별 색상 % (< 70%: 프로바이더 색, ≥ 70%: 경고색)
|
|
@@ -81,7 +109,15 @@ const ACCOUNTS_STATE_PATH = join(homedir(), ".omc", "state", "cli_accounts_state
|
|
|
81
109
|
const CLAUDE_CREDENTIALS_PATH = join(homedir(), ".claude", ".credentials.json");
|
|
82
110
|
const CLAUDE_USAGE_CACHE_PATH = join(homedir(), ".claude", "cache", "claude-usage-cache.json");
|
|
83
111
|
const OMC_PLUGIN_USAGE_CACHE_PATH = join(homedir(), ".claude", "plugins", "oh-my-claudecode", ".usage-cache.json");
|
|
84
|
-
const
|
|
112
|
+
const CLAUDE_USAGE_STALE_MS_SOLO = 5 * 60 * 1000; // OMC 없을 때: 5분 캐시
|
|
113
|
+
const CLAUDE_USAGE_STALE_MS_WITH_OMC = 15 * 60 * 1000; // OMC 있을 때: 15분 (OMC가 30초마다 갱신)
|
|
114
|
+
|
|
115
|
+
// OMC 활성 여부에 따라 캐시 TTL 동적 결정
|
|
116
|
+
function getClaudeUsageStaleMs() {
|
|
117
|
+
return existsSync(OMC_PLUGIN_USAGE_CACHE_PATH)
|
|
118
|
+
? CLAUDE_USAGE_STALE_MS_WITH_OMC
|
|
119
|
+
: CLAUDE_USAGE_STALE_MS_SOLO;
|
|
120
|
+
}
|
|
85
121
|
const CLAUDE_USAGE_429_BACKOFF_MS = 10 * 60 * 1000; // 429 에러 시 10분 backoff
|
|
86
122
|
const CLAUDE_USAGE_ERROR_BACKOFF_MS = 3 * 60 * 1000; // 기타 에러 시 3분 backoff
|
|
87
123
|
const CLAUDE_API_TIMEOUT_MS = 10_000;
|
|
@@ -122,7 +158,7 @@ const GEMINI_SESSION_STALE_MS = 15 * 1000; // 15초
|
|
|
122
158
|
const GEMINI_API_TIMEOUT_MS = 3000; // 3초
|
|
123
159
|
const ACCOUNT_LABEL_WIDTH = 10;
|
|
124
160
|
const PROVIDER_PREFIX_WIDTH = 2;
|
|
125
|
-
const PERCENT_CELL_WIDTH =
|
|
161
|
+
const PERCENT_CELL_WIDTH = 3;
|
|
126
162
|
const TIME_CELL_INNER_WIDTH = 6;
|
|
127
163
|
const CLAUDE_REFRESH_FLAG = "--refresh-claude-usage";
|
|
128
164
|
const CODEX_REFRESH_FLAG = "--refresh-codex-rate-limits";
|
|
@@ -235,7 +271,13 @@ function selectTier(stdin, claudeUsage = null) {
|
|
|
235
271
|
|
|
236
272
|
// 1) 명시적 tier 강제 설정
|
|
237
273
|
const forcedTier = hudConfig?.tier;
|
|
238
|
-
if (["full", "normal", "compact", "nano"].includes(forcedTier)) return forcedTier;
|
|
274
|
+
if (["full", "normal", "compact", "nano", "micro"].includes(forcedTier)) return forcedTier;
|
|
275
|
+
|
|
276
|
+
// 1.5) maxLines=1 → micro (1줄 모드: 알림 배너/분할화면 대응)
|
|
277
|
+
if (Number(hudConfig?.lines) === 1) return "micro";
|
|
278
|
+
|
|
279
|
+
// 1.6) 분할화면 감지: 열 < 80이면 micro (COMPACT_MODE보다 우선)
|
|
280
|
+
if ((getTerminalColumns() || 120) < 80) return "micro";
|
|
239
281
|
|
|
240
282
|
// 2) 기존 모드 플래그 존중
|
|
241
283
|
if (MINIMAL_MODE) return "nano";
|
|
@@ -247,6 +289,7 @@ function selectTier(stdin, claudeUsage = null) {
|
|
|
247
289
|
// 4) 터미널 행/열에서 상태영역 예산 추정
|
|
248
290
|
const rows = getTerminalRows();
|
|
249
291
|
const cols = getTerminalColumns() || 120;
|
|
292
|
+
|
|
250
293
|
let budget;
|
|
251
294
|
if (rows >= 40) budget = 6;
|
|
252
295
|
else if (rows >= 35) budget = 5;
|
|
@@ -257,6 +300,7 @@ function selectTier(stdin, claudeUsage = null) {
|
|
|
257
300
|
|
|
258
301
|
// 5) 인디케이터 줄 추정
|
|
259
302
|
let indicatorRows = 1; // bypass permissions (거의 항상 표시)
|
|
303
|
+
indicatorRows += 1; // 선행 개행 가드 (알림 배너 우회용 빈 줄)
|
|
260
304
|
const contextPercent = getContextPercent(stdin);
|
|
261
305
|
if (contextPercent >= 85) indicatorRows += 1; // "Context low" 배너
|
|
262
306
|
// Claude Code 사용량 경고 (노란색 배너: "You've used X% of your ... limit")
|
|
@@ -266,26 +310,27 @@ function selectTier(stdin, claudeUsage = null) {
|
|
|
266
310
|
if (fiveHourPct >= 80) indicatorRows += 1;
|
|
267
311
|
|
|
268
312
|
// 6) 각 tier에서 줄바꿈 없이 3줄 가용한지 확인
|
|
269
|
-
const tierWidths = { full:
|
|
313
|
+
const tierWidths = { full: 70, normal: 60, compact: 40, nano: 34 };
|
|
270
314
|
for (const tier of ["full", "normal", "compact", "nano"]) {
|
|
271
315
|
const lineWidth = tierWidths[tier];
|
|
272
316
|
const visualRowsPerLine = Math.ceil(lineWidth / Math.max(cols, 1));
|
|
273
317
|
const totalVisualRows = (3 * visualRowsPerLine) + indicatorRows;
|
|
274
318
|
if (totalVisualRows <= budget) return tier;
|
|
275
319
|
}
|
|
276
|
-
|
|
320
|
+
// 어떤 tier도 budget에 안 맞으면 micro (1줄 모드)
|
|
321
|
+
return "micro";
|
|
277
322
|
}
|
|
278
323
|
|
|
279
324
|
// full tier 전용: 게이지 바 접두사 (normal 이하 tier에서는 빈 문자열)
|
|
280
325
|
function tierBar(percent, baseColor = null) {
|
|
281
|
-
return CURRENT_TIER === "full" ?
|
|
326
|
+
return CURRENT_TIER === "full" ? coloredBar(percent, GAUGE_WIDTH, baseColor) + " " : "";
|
|
282
327
|
}
|
|
283
328
|
function tierDimBar() {
|
|
284
|
-
return CURRENT_TIER === "full" ?
|
|
329
|
+
return CURRENT_TIER === "full" ? DIM + "░".repeat(GAUGE_WIDTH) + RESET + " " : "";
|
|
285
330
|
}
|
|
286
331
|
// Gemini ∞% 전용: 무한 쿼터이므로 dim 회색 바
|
|
287
332
|
function tierInfBar() {
|
|
288
|
-
return CURRENT_TIER === "full" ?
|
|
333
|
+
return CURRENT_TIER === "full" ? DIM + "█".repeat(GAUGE_WIDTH) + RESET + " " : "";
|
|
289
334
|
}
|
|
290
335
|
|
|
291
336
|
// ============================================================================
|
|
@@ -387,6 +432,50 @@ function renderAlignedRows(rows) {
|
|
|
387
432
|
});
|
|
388
433
|
}
|
|
389
434
|
|
|
435
|
+
// micro tier: 모든 프로바이더를 1줄로 압축 (알림 배너/분할화면 대응)
|
|
436
|
+
// 형식: c:16/3 x:5/2 g:∞ sv:143% ctx:53%
|
|
437
|
+
function getMicroLine(stdin, claudeUsage, codexBuckets, geminiSession, geminiBucket, combinedSvPct) {
|
|
438
|
+
const ctx = getContextPercent(stdin);
|
|
439
|
+
|
|
440
|
+
// Claude 5h/1w
|
|
441
|
+
const cF = claudeUsage ? clampPercent(claudeUsage.fiveHourPercent ?? 0) : null;
|
|
442
|
+
const cW = claudeUsage ? clampPercent(claudeUsage.weeklyPercent ?? 0) : null;
|
|
443
|
+
const cVal = cF != null
|
|
444
|
+
? `${colorByProvider(cF, `${cF}`, claudeOrange)}${dim("/")}${colorByProvider(cW, `${cW}`, claudeOrange)}`
|
|
445
|
+
: dim("--/--");
|
|
446
|
+
|
|
447
|
+
// Codex 5h/1w
|
|
448
|
+
let xVal = dim("--/--");
|
|
449
|
+
if (codexBuckets) {
|
|
450
|
+
const mb = codexBuckets.codex || codexBuckets[Object.keys(codexBuckets)[0]];
|
|
451
|
+
if (mb) {
|
|
452
|
+
const xF = isResetPast(mb.primary?.resets_at) ? 0 : clampPercent(mb.primary?.used_percent ?? 0);
|
|
453
|
+
const xW = isResetPast(mb.secondary?.resets_at) ? 0 : clampPercent(mb.secondary?.used_percent ?? 0);
|
|
454
|
+
xVal = `${colorByProvider(xF, `${xF}`, codexWhite)}${dim("/")}${colorByProvider(xW, `${xW}`, codexWhite)}`;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Gemini
|
|
459
|
+
let gVal;
|
|
460
|
+
if (geminiBucket) {
|
|
461
|
+
const gU = clampPercent(geminiBucket.usedPercent ?? 0);
|
|
462
|
+
gVal = colorByProvider(gU, `${gU}`, geminiBlue);
|
|
463
|
+
} else if ((geminiSession?.total || 0) > 0) {
|
|
464
|
+
gVal = geminiBlue("\u221E");
|
|
465
|
+
} else {
|
|
466
|
+
gVal = dim("--");
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// sv (trimmed)
|
|
470
|
+
const sv = formatSvPct(combinedSvPct || 0).trim();
|
|
471
|
+
|
|
472
|
+
return `${bold(claudeOrange("c"))}${dim(":")}${cVal} ` +
|
|
473
|
+
`${bold(codexWhite("x"))}${dim(":")}${xVal} ` +
|
|
474
|
+
`${bold(geminiBlue("g"))}${dim(":")}${gVal} ` +
|
|
475
|
+
`${dim("sv:")}${sv} ` +
|
|
476
|
+
`${dim("ctx:")}${colorByPercent(ctx, `${ctx}%`)}`;
|
|
477
|
+
}
|
|
478
|
+
|
|
390
479
|
function clampPercent(value) {
|
|
391
480
|
const numeric = Number(value);
|
|
392
481
|
if (!Number.isFinite(numeric)) return 0;
|
|
@@ -405,7 +494,7 @@ function normalizeTimeToken(value) {
|
|
|
405
494
|
const text = String(value || "n/a");
|
|
406
495
|
const hourMinute = text.match(/^(\d+)h(\d+)m$/);
|
|
407
496
|
if (hourMinute) {
|
|
408
|
-
return `${Number(hourMinute[1])}h${Number(hourMinute[2])}m`;
|
|
497
|
+
return `${Number(hourMinute[1])}h${String(Number(hourMinute[2])).padStart(2, "0")}m`;
|
|
409
498
|
}
|
|
410
499
|
const dayHour = text.match(/^(\d+)d(\d+)h$/);
|
|
411
500
|
if (dayHour) {
|
|
@@ -416,7 +505,9 @@ function normalizeTimeToken(value) {
|
|
|
416
505
|
|
|
417
506
|
function formatTimeCell(value) {
|
|
418
507
|
const text = normalizeTimeToken(value);
|
|
419
|
-
|
|
508
|
+
// 시간값(숫자 포함)은 0패딩, 비시간값(n/a 등)은 공백패딩
|
|
509
|
+
const padChar = /\d/.test(text) ? "0" : " ";
|
|
510
|
+
return `(${text.padStart(TIME_CELL_INNER_WIDTH, padChar)})`;
|
|
420
511
|
}
|
|
421
512
|
|
|
422
513
|
// 주간(d/h) 전용 — 최대 7d00h(5자)이므로 공백 불필요
|
|
@@ -607,7 +698,7 @@ function readClaudeUsageSnapshot() {
|
|
|
607
698
|
|
|
608
699
|
// 1차: 자체 캐시에 유효 데이터가 있는 경우
|
|
609
700
|
if (cache?.data) {
|
|
610
|
-
const isFresh = ageMs <
|
|
701
|
+
const isFresh = ageMs < getClaudeUsageStaleMs();
|
|
611
702
|
return { data: cache.data, shouldRefresh: !isFresh };
|
|
612
703
|
}
|
|
613
704
|
|
|
@@ -623,10 +714,9 @@ function readClaudeUsageSnapshot() {
|
|
|
623
714
|
writeClaudeUsageCache(omcCache.data);
|
|
624
715
|
return { data: omcCache.data, shouldRefresh: false };
|
|
625
716
|
}
|
|
626
|
-
// stale OMC fallback 또는
|
|
717
|
+
// stale OMC fallback 또는 null (--% 플레이스홀더 표시, 가짜 0% 방지)
|
|
627
718
|
const staleData = omcCache?.data?.fiveHourPercent != null ? stripStaleResets(omcCache.data) : null;
|
|
628
|
-
|
|
629
|
-
return { data: fallback, shouldRefresh: false };
|
|
719
|
+
return { data: staleData, shouldRefresh: false };
|
|
630
720
|
}
|
|
631
721
|
}
|
|
632
722
|
|
|
@@ -637,14 +727,14 @@ function readClaudeUsageSnapshot() {
|
|
|
637
727
|
const omcAge = Number.isFinite(omcCache.timestamp) ? Date.now() - omcCache.timestamp : Number.MAX_SAFE_INTEGER;
|
|
638
728
|
if (omcAge < OMC_CACHE_MAX_AGE_MS) {
|
|
639
729
|
writeClaudeUsageCache(omcCache.data);
|
|
640
|
-
return { data: omcCache.data, shouldRefresh: omcAge >
|
|
730
|
+
return { data: omcCache.data, shouldRefresh: omcAge > getClaudeUsageStaleMs() };
|
|
641
731
|
}
|
|
642
732
|
// stale이어도 data: null보다는 오래된 데이터를 fallback으로 표시
|
|
643
733
|
return { data: stripStaleResets(omcCache.data), shouldRefresh: true };
|
|
644
734
|
}
|
|
645
735
|
|
|
646
|
-
// 캐시/fallback 모두 없음:
|
|
647
|
-
return { data:
|
|
736
|
+
// 캐시/fallback 모두 없음: null 반환 → --% 플레이스홀더 + 리프레시 시도
|
|
737
|
+
return { data: null, shouldRefresh: true };
|
|
648
738
|
}
|
|
649
739
|
|
|
650
740
|
function writeClaudeUsageCache(data, errorInfo = null) {
|
|
@@ -704,7 +794,7 @@ function scheduleClaudeUsageRefresh() {
|
|
|
704
794
|
const omcCache = readJson(OMC_PLUGIN_USAGE_CACHE_PATH, null);
|
|
705
795
|
if (omcCache?.data?.fiveHourPercent != null) {
|
|
706
796
|
const omcAge = Number.isFinite(omcCache.timestamp) ? Date.now() - omcCache.timestamp : Infinity;
|
|
707
|
-
if (omcAge <
|
|
797
|
+
if (omcAge < getClaudeUsageStaleMs()) {
|
|
708
798
|
writeClaudeUsageCache(omcCache.data); // HUD 캐시에 복사만
|
|
709
799
|
return;
|
|
710
800
|
}
|
|
@@ -723,7 +813,7 @@ function scheduleClaudeUsageRefresh() {
|
|
|
723
813
|
|
|
724
814
|
try {
|
|
725
815
|
const child = spawn(process.execPath, [scriptPath, CLAUDE_REFRESH_FLAG], {
|
|
726
|
-
detached:
|
|
816
|
+
detached: process.platform !== "win32",
|
|
727
817
|
stdio: "ignore",
|
|
728
818
|
windowsHide: true,
|
|
729
819
|
});
|
|
@@ -748,11 +838,11 @@ function formatResetRemaining(isoOrUnix) {
|
|
|
748
838
|
const d = typeof isoOrUnix === "string" ? new Date(isoOrUnix) : new Date(isoOrUnix * 1000);
|
|
749
839
|
if (isNaN(d.getTime())) return "";
|
|
750
840
|
const diffMs = d.getTime() - Date.now();
|
|
751
|
-
if (diffMs <= 0) return "
|
|
841
|
+
if (diffMs <= 0) return "0h00m";
|
|
752
842
|
const totalMinutes = Math.floor(diffMs / 60000);
|
|
753
843
|
const totalHours = Math.floor(totalMinutes / 60);
|
|
754
844
|
const minutes = totalMinutes % 60;
|
|
755
|
-
return `${totalHours}h${minutes}m`;
|
|
845
|
+
return `${totalHours}h${String(minutes).padStart(2, "0")}m`;
|
|
756
846
|
}
|
|
757
847
|
|
|
758
848
|
function isResetPast(isoOrUnix) {
|
|
@@ -1022,7 +1112,7 @@ function scheduleGeminiQuotaRefresh(accountId) {
|
|
|
1022
1112
|
process.execPath,
|
|
1023
1113
|
[scriptPath, GEMINI_REFRESH_FLAG, "--account", accountId || "gemini-main"],
|
|
1024
1114
|
{
|
|
1025
|
-
detached:
|
|
1115
|
+
detached: process.platform !== "win32",
|
|
1026
1116
|
stdio: "ignore",
|
|
1027
1117
|
windowsHide: true,
|
|
1028
1118
|
},
|
|
@@ -1054,7 +1144,7 @@ function scheduleCodexRateLimitRefresh() {
|
|
|
1054
1144
|
if (!scriptPath) return;
|
|
1055
1145
|
try {
|
|
1056
1146
|
const child = spawn(process.execPath, [scriptPath, CODEX_REFRESH_FLAG], {
|
|
1057
|
-
detached:
|
|
1147
|
+
detached: process.platform !== "win32",
|
|
1058
1148
|
stdio: "ignore",
|
|
1059
1149
|
windowsHide: true,
|
|
1060
1150
|
});
|
|
@@ -1085,7 +1175,7 @@ function scheduleGeminiSessionRefresh() {
|
|
|
1085
1175
|
if (!scriptPath) return;
|
|
1086
1176
|
try {
|
|
1087
1177
|
const child = spawn(process.execPath, [scriptPath, GEMINI_SESSION_REFRESH_FLAG], {
|
|
1088
|
-
detached:
|
|
1178
|
+
detached: process.platform !== "win32",
|
|
1089
1179
|
stdio: "ignore",
|
|
1090
1180
|
windowsHide: true,
|
|
1091
1181
|
});
|
|
@@ -1458,6 +1548,14 @@ async function main() {
|
|
|
1458
1548
|
// 인디케이터 인식 tier 선택 (stdin + Claude 사용량 기반)
|
|
1459
1549
|
CURRENT_TIER = selectTier(stdin, claudeUsageSnapshot.data);
|
|
1460
1550
|
|
|
1551
|
+
// micro tier: 1줄 모드 (알림 배너/분할화면 대응)
|
|
1552
|
+
if (CURRENT_TIER === "micro") {
|
|
1553
|
+
const microLine = getMicroLine(stdin, claudeUsageSnapshot.data, codexBuckets,
|
|
1554
|
+
geminiSession, geminiBucket, combinedSvPct);
|
|
1555
|
+
process.stdout.write(`\x1b[0m${microLine}\n`);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1461
1559
|
const codexQuotaData = codexBuckets ? { type: "codex", buckets: codexBuckets } : null;
|
|
1462
1560
|
const geminiQuotaData = { type: "gemini", quotaBucket: geminiBucket, session: geminiSession };
|
|
1463
1561
|
|
|
@@ -1481,12 +1579,13 @@ async function main() {
|
|
|
1481
1579
|
if (!geminiActive) outputLines[2] = `${DIM}${stripAnsi(outputLines[2])}${RESET}`;
|
|
1482
1580
|
}
|
|
1483
1581
|
|
|
1484
|
-
//
|
|
1582
|
+
// 선행 개행: 알림 배너(노란 글씨)가 빈 첫 줄에 오도록 → HUD 내용 보호
|
|
1583
|
+
// Context low(≥85%) 시 추가 개행으로 배너 분리
|
|
1485
1584
|
const contextPercent = getContextPercent(stdin);
|
|
1486
|
-
const
|
|
1585
|
+
const leadingBreaks = contextPercent >= 85 ? "\n\n" : "\n";
|
|
1487
1586
|
// 줄별 RESET: Claude Code TUI 스타일 간섭 방지 (색상 밝기 버그 수정)
|
|
1488
1587
|
const resetedLines = outputLines.map(line => `\x1b[0m${line}`);
|
|
1489
|
-
process.stdout.write(`${
|
|
1588
|
+
process.stdout.write(`${leadingBreaks}${resetedLines.join("\n")}\n`);
|
|
1490
1589
|
}
|
|
1491
1590
|
|
|
1492
1591
|
main().catch(() => {
|