@triflux/core 10.50.1 → 10.51.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/hooks/agy-session-hook.mjs +11 -56
- package/hooks/codex-session-hook.mjs +11 -101
- package/hub/bridge.mjs +1 -79
- package/hub/cli-adapter-base.mjs +12 -90
- package/hub/codex-adapter.mjs +0 -1
- package/hub/codex-preflight.mjs +5 -28
- package/hub/index.mjs +3 -9
- package/hub/lib/cto-env.mjs +0 -23
- package/hub/lib/known-errors.json +2 -2
- package/hub/lib/process-utils.mjs +0 -123
- package/hub/lib/spawn-trace.mjs +3 -2
- package/hub/lib/timeout-defaults.mjs +0 -1
- package/hub/paths.mjs +0 -6
- package/hub/pipeline/index.mjs +0 -190
- package/hub/pipeline/transitions.mjs +0 -10
- package/hub/reflexion.mjs +1 -84
- package/hub/role-contract.mjs +0 -2
- package/hud/cli-policy.mjs +0 -26
- package/hud/colors.mjs +0 -13
- package/hud/constants.mjs +7 -153
- package/hud/context-monitor.mjs +3 -64
- package/hud/hud-qos-status.mjs +54 -264
- package/hud/providers/claude.mjs +4 -16
- package/hud/providers/gemini.mjs +83 -689
- package/hud/renderers.mjs +59 -367
- package/hud/terminal.mjs +1 -63
- package/hud/utils.mjs +2 -26
- package/package.json +1 -4
- package/scripts/lib/agent-route-policy.mjs +162 -14
- package/scripts/lib/claudemd-scanner.mjs +2 -199
- package/scripts/lib/cli-claude.mjs +0 -2
- package/scripts/lib/context.mjs +3 -19
- package/scripts/lib/env-probe.mjs +5 -64
- package/scripts/lib/gemini-profiles.mjs +2 -69
- package/scripts/lib/legacy-mcp-cleanup.mjs +14 -3
- package/scripts/lib/machine-profile.mjs +3 -17
- package/scripts/lib/mcp-filter.mjs +109 -53
- package/scripts/lib/mcp-health.mjs +2 -2
- package/scripts/lib/mcp-server-catalog.mjs +2 -2
- package/scripts/lib/psmux-info.mjs +25 -104
- package/scripts/lib/session-presence.mjs +1 -1
- package/scripts/lib/skill-template.mjs +0 -271
- package/hub/codex-compat.mjs +0 -12
- package/hub/dynamic-routing-engine.mjs +0 -511
- package/hub/fullcycle.mjs +0 -113
- package/hub/gemini-adapter.mjs +0 -174
- package/hub/intent.mjs +0 -321
- package/hub/lib/mcp-response-cache.mjs +0 -239
- package/hub/lib/path-utils.mjs +0 -165
- package/hub/lib/risk-tier.mjs +0 -53
- package/hub/lib/ssh-command.mjs +0 -231
- package/hub/lib/staged-file-detect.mjs +0 -43
- package/hub/lib/tfx-route-args.mjs +0 -353
- package/hub/lib/timeline-adapter.mjs +0 -190
- package/hub/pipeline/gates/confidence.mjs +0 -75
- package/hub/pipeline/gates/consensus.mjs +0 -106
- package/hub/pipeline/gates/index.mjs +0 -9
- package/hub/pipeline/gates/selfcheck.mjs +0 -120
- package/hub/quality/deslop.mjs +0 -274
- package/hub/research.mjs +0 -155
- package/hub/role-control-events.mjs +0 -113
- package/hub/routing/complexity.mjs +0 -269
- package/hub/routing/index.mjs +0 -122
- package/hub/routing/q-learning.mjs +0 -387
- package/hub/routing-snapshot.mjs +0 -261
- package/hub/token-mode.mjs +0 -228
- package/hud/mission-board.mjs +0 -56
- package/hud/providers/cto.mjs +0 -76
- package/mesh/index.mjs +0 -69
- package/mesh/mesh-budget.mjs +0 -137
- package/mesh/mesh-heartbeat.mjs +0 -100
- package/mesh/mesh-protocol.mjs +0 -96
- package/mesh/mesh-queue.mjs +0 -163
- package/mesh/mesh-registry.mjs +0 -78
- package/mesh/mesh-router.mjs +0 -77
- package/scripts/lib/agent-json.mjs +0 -27
- package/scripts/lib/async.mjs +0 -174
- package/scripts/lib/cli-agy.mjs +0 -85
- package/scripts/lib/cli-codex.mjs +0 -62
- package/scripts/lib/cli-gemini.mjs +0 -37
- package/scripts/lib/dynamic-route-cli.mjs +0 -107
- package/scripts/lib/env.mjs +0 -14
- package/scripts/lib/hub.mjs +0 -48
- package/scripts/lib/pid.mjs +0 -63
- package/scripts/lib/quota.mjs +0 -47
- package/scripts/lib/skill-state.mjs +0 -220
- package/scripts/lib/team.mjs +0 -75
- package/scripts/lib/timeout.mjs +0 -66
- package/scripts/lib/tmp.mjs +0 -26
- package/scripts/lib/toml.mjs +0 -132
package/hud/renderers.mjs
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
2
|
// 라인 렌더러 (tier별 행 생성)
|
|
3
3
|
// ============================================================================
|
|
4
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import { join } from "node:path";
|
|
7
4
|
import {
|
|
8
5
|
bold,
|
|
9
6
|
CLAUDE_ORANGE,
|
|
@@ -13,22 +10,20 @@ import {
|
|
|
13
10
|
colorByPercent,
|
|
14
11
|
colorByProvider,
|
|
15
12
|
dim,
|
|
13
|
+
GAUGE_WIDTH,
|
|
16
14
|
GEMINI_BLUE,
|
|
17
15
|
geminiBlue,
|
|
18
|
-
green,
|
|
19
|
-
red,
|
|
20
16
|
yellow,
|
|
21
17
|
} from "./colors.mjs";
|
|
22
18
|
import {
|
|
23
19
|
ACCOUNT_LABEL_WIDTH,
|
|
24
20
|
FIVE_HOUR_MS,
|
|
25
21
|
ONE_DAY_MS,
|
|
22
|
+
PERCENT_CELL_WIDTH,
|
|
26
23
|
PROVIDER_PREFIX_WIDTH,
|
|
27
24
|
SEVEN_DAY_MS,
|
|
28
|
-
TEAM_STATE_PATH,
|
|
29
25
|
} from "./constants.mjs";
|
|
30
26
|
import { buildContextUsageView } from "./context-monitor.mjs";
|
|
31
|
-
import { deriveGeminiLimits } from "./providers/gemini.mjs";
|
|
32
27
|
import { getTerminalColumns, tierBar, tierDimBar } from "./terminal.mjs";
|
|
33
28
|
import {
|
|
34
29
|
clampPercent,
|
|
@@ -37,178 +32,13 @@ import {
|
|
|
37
32
|
formatPlaceholderPercentCell,
|
|
38
33
|
formatResetRemaining,
|
|
39
34
|
formatResetRemainingDayHour,
|
|
40
|
-
formatSavings,
|
|
41
35
|
formatTimeCell,
|
|
42
36
|
formatTimeCellDH,
|
|
43
|
-
formatTokenCount,
|
|
44
37
|
padAnsiRight,
|
|
45
|
-
readJson,
|
|
46
38
|
stripAnsi,
|
|
47
39
|
truncateAnsi,
|
|
48
40
|
} from "./utils.mjs";
|
|
49
41
|
|
|
50
|
-
function formatGeminiLimitValue(limit, provFn) {
|
|
51
|
-
if (limit?.unlimited) return provFn("\u221E");
|
|
52
|
-
const usedP = limit?.usedPct;
|
|
53
|
-
if (usedP == null) return dim(formatPlaceholderPercentCell());
|
|
54
|
-
return colorByProvider(usedP, formatPercentCell(usedP), provFn);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getGeminiLimitUsedPercent(bucket, limit) {
|
|
58
|
-
if (limit?.unlimited) return null;
|
|
59
|
-
if (limit?.usedPct != null) return limit.usedPct;
|
|
60
|
-
return clampPercent(Math.round((1 - (bucket?.remainingFraction ?? 1)) * 100));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function formatGeminiCompactValue(bucket, limit, provFn) {
|
|
64
|
-
if (limit?.unlimited) return provFn("\u221E");
|
|
65
|
-
const usedP = getGeminiLimitUsedPercent(bucket, limit);
|
|
66
|
-
return usedP != null ? colorByProvider(usedP, `${usedP}`, provFn) : dim("--");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ============================================================================
|
|
70
|
-
// 최근 벤치마크 diff 파일 읽기
|
|
71
|
-
// ============================================================================
|
|
72
|
-
export function readLatestBenchmarkDiff() {
|
|
73
|
-
const diffsDir = join(homedir(), ".omc", "state", "cx-auto-tokens", "diffs");
|
|
74
|
-
if (!existsSync(diffsDir)) return null;
|
|
75
|
-
try {
|
|
76
|
-
const files = readdirSync(diffsDir)
|
|
77
|
-
.filter((f) => f.endsWith(".json"))
|
|
78
|
-
.sort()
|
|
79
|
-
.reverse();
|
|
80
|
-
if (files.length === 0) return null;
|
|
81
|
-
return readJson(join(diffsDir, files[0]), null);
|
|
82
|
-
} catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* 파이프라인 벤치마크 diff 결과를 HUD 요약 문자열로 포맷
|
|
89
|
-
*/
|
|
90
|
-
export function formatTokenSummary(diff) {
|
|
91
|
-
if (!diff?.delta?.total || !diff?.savings) return "";
|
|
92
|
-
const t = diff.delta.total;
|
|
93
|
-
const s = diff.savings;
|
|
94
|
-
|
|
95
|
-
const inputStr = formatTokenCount(t.input);
|
|
96
|
-
const outputStr = formatTokenCount(t.output);
|
|
97
|
-
const actualStr = formatSavings(s.actualCost);
|
|
98
|
-
const _claudeStr = formatSavings(s.claudeCost);
|
|
99
|
-
const savedPct =
|
|
100
|
-
s.claudeCost > 0 ? Math.round((s.saved / s.claudeCost) * 100) : 0;
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
`${dim("tok:")}${inputStr}${dim("in")} ${outputStr}${dim("out")} ` +
|
|
104
|
-
`${dim("cost:")}${actualStr} ` +
|
|
105
|
-
`${dim("sv:")}${green(formatSavings(s.saved))}${dim("(")}${savedPct}%${dim(")")}`
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// ============================================================================
|
|
110
|
-
// tfx-multi 상태 행 생성 (v2.2 HUD 통합)
|
|
111
|
-
// ============================================================================
|
|
112
|
-
export function getTeamRow(currentTier) {
|
|
113
|
-
const teamState = readJson(TEAM_STATE_PATH, null);
|
|
114
|
-
if (!teamState?.sessionName) return null;
|
|
115
|
-
|
|
116
|
-
// 팀 생존 확인: startedAt 기준 24시간 초과면 stale로 간주
|
|
117
|
-
if (
|
|
118
|
-
teamState.startedAt &&
|
|
119
|
-
Date.now() - teamState.startedAt > 24 * 60 * 60 * 1000
|
|
120
|
-
)
|
|
121
|
-
return null;
|
|
122
|
-
|
|
123
|
-
const workers = (teamState.members || []).filter((m) => m.role === "worker");
|
|
124
|
-
if (!workers.length) return null;
|
|
125
|
-
|
|
126
|
-
const tasks = teamState.tasks || [];
|
|
127
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
128
|
-
const failed = tasks.filter((t) => t.status === "failed").length;
|
|
129
|
-
const total = tasks.length || workers.length;
|
|
130
|
-
|
|
131
|
-
// 경과 시간 (80col 이상에서만 표시)
|
|
132
|
-
const elapsed =
|
|
133
|
-
teamState.startedAt && (currentTier === "full" || currentTier === "compact")
|
|
134
|
-
? `${Math.round((Date.now() - teamState.startedAt) / 60000)}m`
|
|
135
|
-
: "";
|
|
136
|
-
|
|
137
|
-
// CLI 브랜드: 단일문자 + ANSI 색상 (x=codex, g=gemini, c=claude)
|
|
138
|
-
const cliTag = (cli) =>
|
|
139
|
-
cli === "codex"
|
|
140
|
-
? bold(codexWhite("x"))
|
|
141
|
-
: cli === "gemini"
|
|
142
|
-
? bold(geminiBlue("g"))
|
|
143
|
-
: bold(claudeOrange("c"));
|
|
144
|
-
// 멤버 상태: 태그 + 상태기호 (60col 이상)
|
|
145
|
-
const memberIcons =
|
|
146
|
-
currentTier === "full" ||
|
|
147
|
-
currentTier === "compact" ||
|
|
148
|
-
currentTier === "minimal"
|
|
149
|
-
? workers
|
|
150
|
-
.map((m) => {
|
|
151
|
-
const task = tasks.find((t) => t.owner === m.name);
|
|
152
|
-
const status =
|
|
153
|
-
task?.status === "completed"
|
|
154
|
-
? green("\u2713")
|
|
155
|
-
: task?.status === "in_progress"
|
|
156
|
-
? yellow("\u22EF")
|
|
157
|
-
: task?.status === "failed"
|
|
158
|
-
? red("\u2717")
|
|
159
|
-
: dim("\u25CC");
|
|
160
|
-
return `${cliTag(m.cli)}${status}`;
|
|
161
|
-
})
|
|
162
|
-
.join(" ")
|
|
163
|
-
: "";
|
|
164
|
-
|
|
165
|
-
// 진행 텍스트
|
|
166
|
-
const doneText =
|
|
167
|
-
failed > 0
|
|
168
|
-
? `${completed}/${total} ${red(`${failed}\u2717`)}`
|
|
169
|
-
: `${completed}/${total}`;
|
|
170
|
-
|
|
171
|
-
const leftText = elapsed ? `${doneText} ${dim(elapsed)}` : doneText;
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
prefix: bold(claudeOrange("\u25B2")),
|
|
175
|
-
left: leftText,
|
|
176
|
-
right: memberIcons,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// ============================================================================
|
|
181
|
-
// Mission Board 렌더러
|
|
182
|
-
// ============================================================================
|
|
183
|
-
|
|
184
|
-
const STATUS_ICON = {
|
|
185
|
-
active: "*",
|
|
186
|
-
idle: ".",
|
|
187
|
-
done: "+",
|
|
188
|
-
failed: "!",
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Mission Board 상태를 1줄 compact 포맷으로 렌더링한다.
|
|
193
|
-
* 예: "MB: exec:+ ui:* perf:. [2/3 67%]"
|
|
194
|
-
* @param {{ agents: Array<{name: string, status: string, progress: number}>, dagLevel: number, totalProgress: number } | null} state
|
|
195
|
-
* @returns {string}
|
|
196
|
-
*/
|
|
197
|
-
export function renderMissionBoard(state) {
|
|
198
|
-
if (!state) return "";
|
|
199
|
-
|
|
200
|
-
const { agents, totalProgress } = state;
|
|
201
|
-
const done = agents.filter((a) => a.status === "done").length;
|
|
202
|
-
const total = agents.length;
|
|
203
|
-
|
|
204
|
-
const parts = agents.map((a) => {
|
|
205
|
-
const icon = STATUS_ICON[a.status] ?? "?";
|
|
206
|
-
return `${a.name}:${icon}`;
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
return `MB: ${parts.join(" ")} [${done}/${total} ${totalProgress}%]`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
42
|
// ============================================================================
|
|
213
43
|
// 행 정렬 렌더링
|
|
214
44
|
// ============================================================================
|
|
@@ -227,9 +57,7 @@ export function renderAlignedRows(rows) {
|
|
|
227
57
|
if (!hasRight) {
|
|
228
58
|
return truncateAnsi(`${prefix} ${row.left}`, cols);
|
|
229
59
|
}
|
|
230
|
-
|
|
231
|
-
const ownLen = stripAnsi(row.left).length;
|
|
232
|
-
const effectiveWidth = Math.min(rawLeftWidth, ownLen + 2);
|
|
60
|
+
const effectiveWidth = rawLeftWidth;
|
|
233
61
|
const left = padAnsiRight(row.left, effectiveWidth);
|
|
234
62
|
// 우선순위 기반 truncate: right 먼저 축소, 그래도 넘치면 left 축소
|
|
235
63
|
// prefix(PROVIDER_PREFIX_WIDTH) + " "(1) + left(effectiveWidth) + " | "(3) + right
|
|
@@ -254,15 +82,10 @@ export function getMicroLine(
|
|
|
254
82
|
contextView,
|
|
255
83
|
claudeUsage,
|
|
256
84
|
codexBuckets,
|
|
257
|
-
geminiSession,
|
|
258
|
-
geminiBucket,
|
|
259
|
-
geminiMarker = "g",
|
|
260
85
|
options = {},
|
|
261
86
|
) {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const { showCodex = true, showGemini = true } = options;
|
|
265
|
-
const ctxView = contextView || buildContextUsageView({}, null);
|
|
87
|
+
const { showCodex = true, showAntigravity = true } = options;
|
|
88
|
+
const ctxView = contextView || buildContextUsageView({});
|
|
266
89
|
// Claude 5h/1w
|
|
267
90
|
const cF =
|
|
268
91
|
claudeUsage?.fiveHourPercent != null
|
|
@@ -294,30 +117,32 @@ export function getMicroLine(
|
|
|
294
117
|
}
|
|
295
118
|
}
|
|
296
119
|
|
|
297
|
-
// Gemini
|
|
298
|
-
let gVal;
|
|
299
|
-
if (geminiBucket) {
|
|
300
|
-
const gl = deriveGeminiLimits(geminiBucket);
|
|
301
|
-
gVal = formatGeminiCompactValue(geminiBucket, gl, geminiBlue);
|
|
302
|
-
} else if ((geminiSession?.total || 0) > 0) {
|
|
303
|
-
gVal = geminiBlue("\u221E");
|
|
304
|
-
} else {
|
|
305
|
-
gVal = dim("--");
|
|
306
|
-
}
|
|
307
|
-
|
|
308
120
|
const cols = getTerminalColumns() || 120;
|
|
309
121
|
// 세그먼트를 모아 join 한다. 차단된 프로바이더를 뺄 때 공백이 겹치지 않는다.
|
|
310
122
|
const segments = [`${bold(claudeOrange("c"))}${dim(":")}${cVal}`];
|
|
311
123
|
if (showCodex) {
|
|
312
124
|
segments.push(`${bold(codexWhite("x"))}${dim(":")}${xVal}`);
|
|
313
125
|
}
|
|
314
|
-
if (
|
|
315
|
-
|
|
126
|
+
if (showAntigravity) {
|
|
127
|
+
const marker = options.antigravityQuota ? bold(geminiBlue("a")) : dim("a");
|
|
128
|
+
segments.push(
|
|
129
|
+
`${marker}${dim(":")}${antigravityPercentText(options.antigravityQuota)}${options.antigravityQuota?.stale ? dim("*") : ""}`,
|
|
130
|
+
);
|
|
316
131
|
}
|
|
317
132
|
segments.push(`${dim("CTX:")}${contextPercentText(ctxView)}`);
|
|
318
133
|
return truncateAnsi(segments.join(" "), cols);
|
|
319
134
|
}
|
|
320
135
|
|
|
136
|
+
function antigravityPercentText(quota) {
|
|
137
|
+
return quota?.usedPercent != null
|
|
138
|
+
? colorByProvider(
|
|
139
|
+
quota.usedPercent,
|
|
140
|
+
formatPercentCell(quota.usedPercent),
|
|
141
|
+
geminiBlue,
|
|
142
|
+
)
|
|
143
|
+
: dim("--".padStart(PERCENT_CELL_WIDTH));
|
|
144
|
+
}
|
|
145
|
+
|
|
321
146
|
// context 는 토큰 수 대신 사용률만 보여 준다.
|
|
322
147
|
function contextPercentText(ctxView) {
|
|
323
148
|
const text = ctxView.display === "--" ? "--" : `${ctxView.percent}%`;
|
|
@@ -328,7 +153,7 @@ function contextPercentText(ctxView) {
|
|
|
328
153
|
// Claude 행 렌더러
|
|
329
154
|
// ============================================================================
|
|
330
155
|
export function getClaudeRows(currentTier, contextView, claudeUsage) {
|
|
331
|
-
const ctxView = contextView || buildContextUsageView({}
|
|
156
|
+
const ctxView = contextView || buildContextUsageView({});
|
|
332
157
|
const prefix = `${bold(claudeOrange("c"))}:`;
|
|
333
158
|
// API 실측 데이터
|
|
334
159
|
const fiveHourPercent = claudeUsage?.fiveHourPercent ?? null;
|
|
@@ -422,98 +247,68 @@ export function getAccountLabel(
|
|
|
422
247
|
const providerState = accountsState?.providers?.[provider] || {};
|
|
423
248
|
const lastId = providerState.last_selected_id;
|
|
424
249
|
const picked = providerConfig.find((a) => a.id === lastId) ||
|
|
425
|
-
providerConfig[0] || {
|
|
250
|
+
providerConfig[0] || {
|
|
251
|
+
id: `${provider}-main`,
|
|
252
|
+
label: provider === "antigravity" ? "agy" : provider,
|
|
253
|
+
};
|
|
426
254
|
let label = picked.label || picked.id;
|
|
427
255
|
if (codexEmail) label = codexEmail;
|
|
428
256
|
if (label.includes("@")) label = label.split("@")[0];
|
|
429
257
|
return label;
|
|
430
258
|
}
|
|
431
259
|
|
|
432
|
-
// 2슬롯: slot1 = 현재 장착 모델 약어 (Fh/Fm/Ph/Pl 등), slot2 = "Gn" Gemini family 통합
|
|
433
|
-
// realQuota.pools.{current,gemini_family} 가 있으면 실데이터, 없으면 placeholder.
|
|
434
|
-
function formatAntigravityQuotaSection(
|
|
435
|
-
currentTier,
|
|
436
|
-
realQuota,
|
|
437
|
-
provFn,
|
|
438
|
-
provAnsi,
|
|
439
|
-
) {
|
|
440
|
-
const pools = realQuota?.pools || {};
|
|
441
|
-
const abbr = realQuota?.currentAbbrev || "??";
|
|
442
|
-
const currentBucket = pools.current;
|
|
443
|
-
const familyBucket = pools.gemini_family;
|
|
444
|
-
const hasData = Boolean(currentBucket || familyBucket);
|
|
445
|
-
|
|
446
|
-
if (!hasData) {
|
|
447
|
-
// 기존 placeholder 동작 유지
|
|
448
|
-
if (currentTier === "minimal") {
|
|
449
|
-
return `${dim(`${abbr}:`)}${dim(formatPlaceholderPercentCell())} ${dim("Gn:")}${dim(formatPlaceholderPercentCell())}`;
|
|
450
|
-
}
|
|
451
|
-
if (currentTier === "compact") {
|
|
452
|
-
return `${dim(`${abbr}:`)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("Gn:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
453
|
-
}
|
|
454
|
-
return `${dim(`${abbr}:`)}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("Gn:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const slot = (bucket, label, { withBar, withTime }) => {
|
|
458
|
-
if (!bucket) {
|
|
459
|
-
const bar = withBar ? tierDimBar(currentTier) : "";
|
|
460
|
-
const base = `${dim(`${label}:`)}${bar}${dim(formatPlaceholderPercentCell())}`;
|
|
461
|
-
return withTime ? `${base} ${dim(formatTimeCell("n/a"))}` : base;
|
|
462
|
-
}
|
|
463
|
-
const gl = deriveGeminiLimits(bucket);
|
|
464
|
-
const usedP = getGeminiLimitUsedPercent(bucket, gl);
|
|
465
|
-
const bar =
|
|
466
|
-
withBar && usedP != null ? tierBar(currentTier, usedP, provAnsi) : "";
|
|
467
|
-
const base = `${dim(`${label}:`)}${bar}${formatGeminiLimitValue(gl, provFn)}`;
|
|
468
|
-
if (!withTime) return base;
|
|
469
|
-
const rstRemaining =
|
|
470
|
-
formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
|
|
471
|
-
return `${base} ${dim(formatTimeCell(rstRemaining))}`;
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
if (currentTier === "minimal") {
|
|
475
|
-
return `${slot(currentBucket, abbr, { withBar: false, withTime: false })} ${slot(familyBucket, "Gn", { withBar: false, withTime: false })}`;
|
|
476
|
-
}
|
|
477
|
-
if (currentTier === "compact") {
|
|
478
|
-
return `${slot(currentBucket, abbr, { withBar: false, withTime: true })} ${slot(familyBucket, "Gn", { withBar: false, withTime: true })}`;
|
|
479
|
-
}
|
|
480
|
-
return `${slot(currentBucket, abbr, { withBar: true, withTime: true })} ${slot(familyBucket, "Gn", { withBar: true, withTime: true })}`;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
260
|
export function getProviderRow(
|
|
484
261
|
currentTier,
|
|
485
262
|
provider,
|
|
486
263
|
marker,
|
|
487
264
|
markerColor,
|
|
488
|
-
qosProfile,
|
|
489
265
|
accountsConfig,
|
|
490
266
|
accountsState,
|
|
491
267
|
realQuota,
|
|
492
268
|
codexEmail,
|
|
493
|
-
modelLabel,
|
|
494
269
|
) {
|
|
495
270
|
const accountLabel = fitText(
|
|
496
271
|
getAccountLabel(provider, accountsConfig, accountsState, codexEmail),
|
|
497
272
|
ACCOUNT_LABEL_WIDTH,
|
|
498
273
|
);
|
|
499
274
|
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
275
|
+
const prefix = `${bold(markerColor(marker))}:`;
|
|
276
|
+
if (provider === "antigravity") {
|
|
277
|
+
const usedPercent = realQuota?.usedPercent;
|
|
278
|
+
const active = usedPercent != null;
|
|
279
|
+
const quotaPercent = antigravityPercentText(realQuota);
|
|
280
|
+
const right = `${active ? markerColor(accountLabel) : dim(accountLabel)}${realQuota?.stale ? dim(" [stale]") : ""}`;
|
|
281
|
+
if (currentTier === "nano" || currentTier === "micro") {
|
|
282
|
+
return {
|
|
283
|
+
prefix: active ? prefix : dim(`${marker}:`),
|
|
284
|
+
left: quotaPercent,
|
|
285
|
+
right,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
const bar = active
|
|
289
|
+
? tierBar(currentTier, usedPercent, GEMINI_BLUE)
|
|
290
|
+
: currentTier === "full"
|
|
291
|
+
? " ".repeat(GAUGE_WIDTH + 1)
|
|
292
|
+
: "";
|
|
293
|
+
const reset =
|
|
294
|
+
(Date.parse(realQuota?.resetTime) - Date.now() >= ONE_DAY_MS
|
|
295
|
+
? formatResetRemainingDayHour(realQuota.resetTime)
|
|
296
|
+
: formatResetRemaining(realQuota?.resetTime)) || "n/a";
|
|
297
|
+
const showTime = currentTier === "full" || currentTier === "compact";
|
|
298
|
+
// /usage의 모델별 값에는 5h/1w 식별자가 없으므로 창을 추측하지 않는다.
|
|
299
|
+
const slot = `${dim("--:")}${bar}${quotaPercent}${showTime ? ` ${dim(formatTimeCell(reset))}` : ""}`;
|
|
300
|
+
return {
|
|
301
|
+
prefix: active ? prefix : dim(`${marker}:`),
|
|
302
|
+
left: `${slot} ${" ".repeat(stripAnsi(slot).length)}`,
|
|
303
|
+
right,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const provAnsi = CODEX_WHITE;
|
|
307
|
+
const provFn = codexWhite;
|
|
509
308
|
let quotaSection;
|
|
510
|
-
const _extraRightSection = "";
|
|
511
309
|
|
|
512
310
|
if (currentTier === "nano" || currentTier === "micro") {
|
|
513
311
|
const minPrefix = `${bold(markerColor(`${marker}`))}:`;
|
|
514
|
-
if (provider === "antigravity") {
|
|
515
|
-
return { prefix: minPrefix, left: dim("--"), right: "" };
|
|
516
|
-
}
|
|
517
312
|
if (realQuota?.type === "codex") {
|
|
518
313
|
const main =
|
|
519
314
|
realQuota.buckets.codex ||
|
|
@@ -542,36 +337,10 @@ export function getProviderRow(
|
|
|
542
337
|
};
|
|
543
338
|
}
|
|
544
339
|
}
|
|
545
|
-
if (provider === "gemini" && realQuota?.type === "gemini") {
|
|
546
|
-
const pools = realQuota.pools || {};
|
|
547
|
-
if (pools.pro || pools.flash) {
|
|
548
|
-
const pP = pools.pro ? deriveGeminiLimits(pools.pro) : null;
|
|
549
|
-
const pF = pools.flash ? deriveGeminiLimits(pools.flash) : null;
|
|
550
|
-
const pStr = pools.pro
|
|
551
|
-
? formatGeminiCompactValue(pools.pro, pP, provFn)
|
|
552
|
-
: dim("--");
|
|
553
|
-
const fStr = pools.flash
|
|
554
|
-
? formatGeminiCompactValue(pools.flash, pF, provFn)
|
|
555
|
-
: dim("--");
|
|
556
|
-
return {
|
|
557
|
-
prefix: minPrefix,
|
|
558
|
-
left: `${pStr}${dim("/")}${fStr}`,
|
|
559
|
-
right: "",
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
340
|
return { prefix: minPrefix, left: dim("--/--"), right: "" };
|
|
564
341
|
}
|
|
565
342
|
|
|
566
343
|
if (currentTier === "minimal") {
|
|
567
|
-
if (provider === "antigravity") {
|
|
568
|
-
quotaSection = formatAntigravityQuotaSection(
|
|
569
|
-
currentTier,
|
|
570
|
-
realQuota,
|
|
571
|
-
provFn,
|
|
572
|
-
provAnsi,
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
344
|
if (realQuota?.type === "codex") {
|
|
576
345
|
const main =
|
|
577
346
|
realQuota.buckets.codex ||
|
|
@@ -596,24 +365,9 @@ export function getProviderRow(
|
|
|
596
365
|
quotaSection = `${dim("5h:")}${fCell} ${dim("1w:")}${wCell}`;
|
|
597
366
|
}
|
|
598
367
|
}
|
|
599
|
-
if (provider === "gemini" && realQuota?.type === "gemini") {
|
|
600
|
-
const pools = realQuota.pools || {};
|
|
601
|
-
if (pools.pro || pools.flash) {
|
|
602
|
-
const slot = (bucket, label) => {
|
|
603
|
-
if (!bucket)
|
|
604
|
-
return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())}`;
|
|
605
|
-
const gl = deriveGeminiLimits(bucket);
|
|
606
|
-
return `${dim(label + ":")}${formatGeminiLimitValue(gl, provFn)}`;
|
|
607
|
-
};
|
|
608
|
-
quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
|
|
609
|
-
} else {
|
|
610
|
-
quotaSection = `${dim("Pr:")}${dim(formatPlaceholderPercentCell())} ${dim("Fl:")}${dim(formatPlaceholderPercentCell())}`;
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
368
|
if (!quotaSection) {
|
|
614
369
|
quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ${dim("1w:")}${dim(formatPlaceholderPercentCell())}`;
|
|
615
370
|
}
|
|
616
|
-
const prefix = `${bold(markerColor(`${marker}`))}:`;
|
|
617
371
|
return {
|
|
618
372
|
prefix,
|
|
619
373
|
left: quotaSection,
|
|
@@ -622,14 +376,6 @@ export function getProviderRow(
|
|
|
622
376
|
}
|
|
623
377
|
|
|
624
378
|
if (currentTier === "compact") {
|
|
625
|
-
if (provider === "antigravity") {
|
|
626
|
-
quotaSection = formatAntigravityQuotaSection(
|
|
627
|
-
currentTier,
|
|
628
|
-
realQuota,
|
|
629
|
-
provFn,
|
|
630
|
-
provAnsi,
|
|
631
|
-
);
|
|
632
|
-
}
|
|
633
379
|
if (realQuota?.type === "codex") {
|
|
634
380
|
const main =
|
|
635
381
|
realQuota.buckets.codex ||
|
|
@@ -659,27 +405,9 @@ export function getProviderRow(
|
|
|
659
405
|
if (main.mixedWindows) quotaSection += dim("*");
|
|
660
406
|
}
|
|
661
407
|
}
|
|
662
|
-
if (provider === "gemini" && realQuota?.type === "gemini") {
|
|
663
|
-
const pools = realQuota.pools || {};
|
|
664
|
-
const hasAnyPool = pools.pro || pools.flash;
|
|
665
|
-
if (hasAnyPool) {
|
|
666
|
-
const slot = (bucket, label) => {
|
|
667
|
-
if (!bucket)
|
|
668
|
-
return `${dim(label + ":")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
669
|
-
const gl = deriveGeminiLimits(bucket);
|
|
670
|
-
const rstRemaining =
|
|
671
|
-
formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
|
|
672
|
-
return `${dim(label + ":")}${formatGeminiLimitValue(gl, provFn)} ${dim(formatTimeCell(rstRemaining))}`;
|
|
673
|
-
};
|
|
674
|
-
quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
|
|
675
|
-
} else {
|
|
676
|
-
quotaSection = `${dim("Pr:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("Fl:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
408
|
if (!quotaSection) {
|
|
680
409
|
quotaSection = `${dim("5h:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("1w:")}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCellDH("--d--h"))}`;
|
|
681
410
|
}
|
|
682
|
-
const prefix = `${bold(markerColor(`${marker}`))}:`;
|
|
683
411
|
const compactRight = [accountLabel ? markerColor(accountLabel) : ""]
|
|
684
412
|
.filter(Boolean)
|
|
685
413
|
.join(" ");
|
|
@@ -687,14 +415,6 @@ export function getProviderRow(
|
|
|
687
415
|
}
|
|
688
416
|
|
|
689
417
|
// full tier
|
|
690
|
-
if (provider === "antigravity") {
|
|
691
|
-
quotaSection = formatAntigravityQuotaSection(
|
|
692
|
-
currentTier,
|
|
693
|
-
realQuota,
|
|
694
|
-
provFn,
|
|
695
|
-
provAnsi,
|
|
696
|
-
);
|
|
697
|
-
}
|
|
698
418
|
|
|
699
419
|
if (realQuota?.type === "codex") {
|
|
700
420
|
const main =
|
|
@@ -737,43 +457,15 @@ export function getProviderRow(
|
|
|
737
457
|
}
|
|
738
458
|
}
|
|
739
459
|
|
|
740
|
-
if (provider === "gemini" && realQuota?.type === "gemini") {
|
|
741
|
-
const pools = realQuota.pools || {};
|
|
742
|
-
const hasAnyPool = pools.pro || pools.flash;
|
|
743
|
-
|
|
744
|
-
if (hasAnyPool) {
|
|
745
|
-
const slot = (bucket, label) => {
|
|
746
|
-
if (!bucket) {
|
|
747
|
-
return `${dim(label + ":")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
748
|
-
}
|
|
749
|
-
const gl = deriveGeminiLimits(bucket);
|
|
750
|
-
const usedP = getGeminiLimitUsedPercent(bucket, gl);
|
|
751
|
-
const rstRemaining =
|
|
752
|
-
formatResetRemaining(bucket.resetTime, ONE_DAY_MS) || "n/a";
|
|
753
|
-
const bar = usedP != null ? tierBar(currentTier, usedP, provAnsi) : "";
|
|
754
|
-
return `${dim(label + ":")}${bar}${formatGeminiLimitValue(gl, provFn)} ${dim(formatTimeCell(rstRemaining))}`;
|
|
755
|
-
};
|
|
756
|
-
|
|
757
|
-
quotaSection = `${slot(pools.pro, "Pr")} ${slot(pools.flash, "Fl")}`;
|
|
758
|
-
} else {
|
|
759
|
-
quotaSection =
|
|
760
|
-
`${dim("Pr:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ` +
|
|
761
|
-
`${dim("Fl:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))}`;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
|
|
765
460
|
// 폴백
|
|
766
461
|
if (!quotaSection) {
|
|
767
462
|
quotaSection = `${dim("5h:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCell("n/a"))} ${dim("1w:")}${tierDimBar(currentTier)}${dim(formatPlaceholderPercentCell())} ${dim(formatTimeCellDH("--d--h"))}`;
|
|
768
463
|
}
|
|
769
464
|
|
|
770
|
-
const prefix = `${bold(markerColor(`${marker}`))}:`;
|
|
771
465
|
const accountSection = `${markerColor(accountLabel)}`;
|
|
772
|
-
const modelLabelSection = modelLabel ? markerColor(modelLabel) : "";
|
|
773
|
-
const rightParts = [accountSection, modelLabelSection].filter(Boolean);
|
|
774
466
|
return {
|
|
775
467
|
prefix,
|
|
776
468
|
left: quotaSection,
|
|
777
|
-
right:
|
|
469
|
+
right: accountSection,
|
|
778
470
|
};
|
|
779
471
|
}
|
package/hud/terminal.mjs
CHANGED
|
@@ -60,56 +60,6 @@ export function getTerminalColumns() {
|
|
|
60
60
|
return 0;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
let _cachedRows = 0;
|
|
64
|
-
export function getTerminalRows() {
|
|
65
|
-
if (_cachedRows > 0) return _cachedRows;
|
|
66
|
-
if (process.stdout.rows) {
|
|
67
|
-
_cachedRows = process.stdout.rows;
|
|
68
|
-
return _cachedRows;
|
|
69
|
-
}
|
|
70
|
-
if (process.stderr.rows) {
|
|
71
|
-
_cachedRows = process.stderr.rows;
|
|
72
|
-
return _cachedRows;
|
|
73
|
-
}
|
|
74
|
-
const envLines = Number(process.env.LINES);
|
|
75
|
-
if (envLines > 0) {
|
|
76
|
-
_cachedRows = envLines;
|
|
77
|
-
return _cachedRows;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
if (process.platform === "win32") {
|
|
81
|
-
const raw = execSync("mode con", {
|
|
82
|
-
timeout: 2000,
|
|
83
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
84
|
-
windowsHide: true,
|
|
85
|
-
}).toString();
|
|
86
|
-
const m =
|
|
87
|
-
raw.match(/Lines[^:]*:\s*(\d+)/i) || raw.match(/줄[^:]*:\s*(\d+)/);
|
|
88
|
-
if (m) {
|
|
89
|
-
_cachedRows = Number(m[1]);
|
|
90
|
-
return _cachedRows;
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
const raw = execSync(
|
|
94
|
-
"tput lines 2>/dev/null || stty size 2>/dev/null | awk '{print $1}'",
|
|
95
|
-
{
|
|
96
|
-
timeout: 2000,
|
|
97
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
98
|
-
},
|
|
99
|
-
)
|
|
100
|
-
.toString()
|
|
101
|
-
.trim();
|
|
102
|
-
if (raw && !Number.isNaN(Number(raw))) {
|
|
103
|
-
_cachedRows = Number(raw);
|
|
104
|
-
return _cachedRows;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
} catch {
|
|
108
|
-
/* 감지 실패 */
|
|
109
|
-
}
|
|
110
|
-
return 0;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
63
|
export function detectCompactMode() {
|
|
114
64
|
// 1. 명시적 CLI 플래그
|
|
115
65
|
if (process.argv.includes("--compact")) return true;
|
|
@@ -153,7 +103,7 @@ export function detectMinimalMode() {
|
|
|
153
103
|
* 인디케이터 인식 + 터미널 크기 기반 tier 자동 선택.
|
|
154
104
|
* main()에서 stdin 수신 후 호출하여 CURRENT_TIER 갱신.
|
|
155
105
|
*/
|
|
156
|
-
export function selectTier(
|
|
106
|
+
export function selectTier() {
|
|
157
107
|
const hudConfig = readJson(HUD_CONFIG_PATH, null);
|
|
158
108
|
|
|
159
109
|
// 1) 명시적 tier 강제 설정
|
|
@@ -196,15 +146,3 @@ export function tierDimBar(currentTier) {
|
|
|
196
146
|
? DIM + "░".repeat(GAUGE_WIDTH) + RESET + " "
|
|
197
147
|
: "";
|
|
198
148
|
}
|
|
199
|
-
// Gemini ∞% 전용: 무한 쿼터이므로 dim 회색 바
|
|
200
|
-
export function tierInfBar(currentTier) {
|
|
201
|
-
return currentTier === "full"
|
|
202
|
-
? DIM + "█".repeat(GAUGE_WIDTH) + RESET + " "
|
|
203
|
-
: "";
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// 테스트 지원: 캐시 초기화
|
|
207
|
-
export function _resetTerminalCache() {
|
|
208
|
-
_cachedColumns = 0;
|
|
209
|
-
_cachedRows = 0;
|
|
210
|
-
}
|