triflux 10.56.0 → 10.57.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/constants.mjs +3 -0
- package/hud/hud-qos-status.mjs +63 -19
- package/hud/renderers.mjs +25 -3
- package/package.json +1 -1
package/hud/constants.mjs
CHANGED
|
@@ -14,6 +14,9 @@ export const CLAUDE_BAND_MARKER_DIR = join(
|
|
|
14
14
|
"triflux",
|
|
15
15
|
"claude-band",
|
|
16
16
|
);
|
|
17
|
+
export const BAND_FLAG = "--band";
|
|
18
|
+
export const BAND_HEADER = "tfx-band 1";
|
|
19
|
+
export const BAND_ALL_TTL_MS = 3 * 60 * 1000;
|
|
17
20
|
export const CLAUDE_BAND_MARKER_TTL_MS = 12 * 60 * 60 * 1000;
|
|
18
21
|
|
|
19
22
|
export const CONTEXT_MONITOR_CACHE_PATH = join(
|
package/hud/hud-qos-status.mjs
CHANGED
|
@@ -15,6 +15,9 @@ import {
|
|
|
15
15
|
} from "./colors.mjs";
|
|
16
16
|
import {
|
|
17
17
|
ANTIGRAVITY_REFRESH_FLAG,
|
|
18
|
+
BAND_ALL_TTL_MS,
|
|
19
|
+
BAND_FLAG,
|
|
20
|
+
BAND_HEADER,
|
|
18
21
|
CLAUDE_BAND_MARKER_DIR,
|
|
19
22
|
CLAUDE_BAND_MARKER_TTL_MS,
|
|
20
23
|
CLAUDE_REFRESH_FLAG,
|
|
@@ -64,7 +67,14 @@ async function main() {
|
|
|
64
67
|
return;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
// --band: triflux mods 가 입력창 위에 그릴 줄을 만든다. statusLine 은 band 가 다 그리는 세션에서 비운다.
|
|
71
|
+
const bandMode = process.argv.includes(BAND_FLAG);
|
|
72
|
+
const stdin = await readStdinJson();
|
|
73
|
+
const bandState = bandMode ? "none" : readBandState(stdin?.session_id);
|
|
74
|
+
if (bandState === "all") return;
|
|
75
|
+
// mods 가 넘긴 세션 사용량이 있으면 Claude API 를 따로 조회하지 않는다.
|
|
76
|
+
const bandClaudeUsage = claudeUsageFromRateLimits(stdin?.claude_rate_limits);
|
|
77
|
+
|
|
68
78
|
const { showCodex, antigravityAllowed } = resolveHudCliVisibility();
|
|
69
79
|
const claudeUsageSnapshot = readClaudeUsageSnapshot();
|
|
70
80
|
const codexSnapshot = readCodexRateLimitSnapshot();
|
|
@@ -74,6 +84,7 @@ async function main() {
|
|
|
74
84
|
if (antigravitySnapshot?.shouldRefresh) scheduleAntigravityQuotaRefresh();
|
|
75
85
|
// 설정이 없는 홈에서는 갱신 프로세스를 시작하지 않는다.
|
|
76
86
|
if (
|
|
87
|
+
!bandClaudeUsage &&
|
|
77
88
|
claudeUsageSnapshot.shouldRefresh &&
|
|
78
89
|
existsSync(join(homedir(), ".claude"))
|
|
79
90
|
) {
|
|
@@ -83,11 +94,12 @@ async function main() {
|
|
|
83
94
|
scheduleCodexRateLimitRefresh();
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
const stdin = await stdinPromise;
|
|
87
97
|
const contextView = buildContextUsageView(stdin);
|
|
88
|
-
const claudeUsage =
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
const claudeUsage =
|
|
99
|
+
bandClaudeUsage ??
|
|
100
|
+
(claudeUsageSnapshot.data
|
|
101
|
+
? { ...claudeUsageSnapshot.data, stale: claudeUsageSnapshot.isStale }
|
|
102
|
+
: null);
|
|
91
103
|
const codexBuckets = codexSnapshot.buckets;
|
|
92
104
|
const antigravityQuota = antigravityAllowed
|
|
93
105
|
? { ...antigravitySnapshot?.data, auth: getAntigravityAuthKind() }
|
|
@@ -103,13 +115,15 @@ async function main() {
|
|
|
103
115
|
showAntigravity: antigravityAllowed,
|
|
104
116
|
antigravityQuota,
|
|
105
117
|
});
|
|
106
|
-
|
|
118
|
+
const header = bandMode ? `${BAND_HEADER}\n` : "";
|
|
119
|
+
process.stdout.write(`${header}\x1b[0m${microLine}\n`);
|
|
107
120
|
return;
|
|
108
121
|
}
|
|
109
122
|
|
|
110
|
-
const rows =
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
const rows =
|
|
124
|
+
bandState === "claude"
|
|
125
|
+
? []
|
|
126
|
+
: getClaudeRows(currentTier, contextView, claudeUsage);
|
|
113
127
|
let codexRowIndex = -1;
|
|
114
128
|
if (showCodex) {
|
|
115
129
|
codexRowIndex = rows.length;
|
|
@@ -141,10 +155,16 @@ async function main() {
|
|
|
141
155
|
if (outputLines[codexRowIndex] != null && codexLoggedOut) {
|
|
142
156
|
outputLines[codexRowIndex] = `${DIM}${outputLines[codexRowIndex]}${RESET}`;
|
|
143
157
|
}
|
|
144
|
-
// 알림 배너와 TUI 스타일이 HUD 내용에 겹치지 않도록 한다.
|
|
145
|
-
const leadingBreaks =
|
|
158
|
+
// 알림 배너와 TUI 스타일이 HUD 내용에 겹치지 않도록 한다. band 는 자기 자리에 그리니 띄우지 않는다.
|
|
159
|
+
const leadingBreaks = bandMode
|
|
160
|
+
? ""
|
|
161
|
+
: contextView.percent >= 85
|
|
162
|
+
? "\n\n"
|
|
163
|
+
: "\n";
|
|
146
164
|
const resetLines = outputLines.map((line) => `\x1b[0m${line}`);
|
|
147
|
-
|
|
165
|
+
// band 는 첫 줄 표지로 이 HUD 가 --band 를 아는지 확인한다.
|
|
166
|
+
const header = bandMode ? `${BAND_HEADER}\n` : "";
|
|
167
|
+
process.stdout.write(`${header}${leadingBreaks}${resetLines.join("\n")}\n`);
|
|
148
168
|
}
|
|
149
169
|
|
|
150
170
|
main().catch(() => {
|
|
@@ -153,15 +173,39 @@ main().catch(() => {
|
|
|
153
173
|
);
|
|
154
174
|
});
|
|
155
175
|
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
176
|
+
// 입력창 위 band 가 무엇을 그리는지: "all" 은 모든 행, "claude" 는 c 행만(10.56.0 mods), "none" 은 없음.
|
|
177
|
+
// band 위치를 statusline 으로 고른 세션은 표식에 "off" 를 쓴다.
|
|
178
|
+
function readBandState(sessionId) {
|
|
179
|
+
if (!sessionId) return "none";
|
|
159
180
|
try {
|
|
160
181
|
const marker = join(CLAUDE_BAND_MARKER_DIR, sessionId);
|
|
161
|
-
|
|
162
|
-
if (
|
|
163
|
-
|
|
182
|
+
const content = readFileSync(marker, "utf8").trim();
|
|
183
|
+
if (content === "off") return "none";
|
|
184
|
+
const age = Date.now() - statSync(marker).mtimeMs;
|
|
185
|
+
// all 표식은 band 가 1분마다 다시 쓴다. band 가 멈추면 짧게 끊어 statusLine 이 다시 그린다.
|
|
186
|
+
if (content.startsWith("all:"))
|
|
187
|
+
return age < BAND_ALL_TTL_MS ? "all" : "none";
|
|
188
|
+
return age < CLAUDE_BAND_MARKER_TTL_MS ? "claude" : "none";
|
|
164
189
|
} catch {
|
|
165
|
-
return
|
|
190
|
+
return "none";
|
|
166
191
|
}
|
|
167
192
|
}
|
|
193
|
+
|
|
194
|
+
// mods 의 $.session.usage() rateLimits 를 HUD 의 Claude 사용량 형식으로 바꾼다.
|
|
195
|
+
function claudeUsageFromRateLimits(rateLimits) {
|
|
196
|
+
if (!Array.isArray(rateLimits)) return null;
|
|
197
|
+
const find = (kind) => rateLimits.find((w) => w?.kind === kind);
|
|
198
|
+
const fiveHour = find("five_hour");
|
|
199
|
+
const weekly = find("seven_day");
|
|
200
|
+
const spendLimit = find("spend_limit");
|
|
201
|
+
if (!fiveHour && !weekly && !spendLimit) return null;
|
|
202
|
+
const percent = (w) =>
|
|
203
|
+
Number.isFinite(w?.percentUsed) ? Math.round(w.percentUsed) : null;
|
|
204
|
+
return {
|
|
205
|
+
fiveHourPercent: percent(fiveHour),
|
|
206
|
+
weeklyPercent: percent(weekly),
|
|
207
|
+
fiveHourResetsAt: fiveHour?.resetsAt || null,
|
|
208
|
+
weeklyResetsAt: weekly?.resetsAt || null,
|
|
209
|
+
spendLimitPercent: percent(spendLimit),
|
|
210
|
+
};
|
|
211
|
+
}
|
package/hud/renderers.mjs
CHANGED
|
@@ -100,10 +100,13 @@ export function getMicroLine(
|
|
|
100
100
|
claudeUsage?.weeklyPercent != null
|
|
101
101
|
? clampPercent(claudeUsage.weeklyPercent)
|
|
102
102
|
: null;
|
|
103
|
+
const cSpend = spendOnlyPercent(claudeUsage);
|
|
103
104
|
const cVal =
|
|
104
|
-
|
|
105
|
-
?
|
|
106
|
-
:
|
|
105
|
+
cSpend != null
|
|
106
|
+
? colorByProvider(cSpend, `$${cSpend}%`, claudeOrange)
|
|
107
|
+
: claudeUsage != null
|
|
108
|
+
? `${cF != null ? colorByProvider(cF, `${cF}%`, claudeOrange) : dim("--%")}${dim("/")}${cW != null ? colorByProvider(cW, `${cW}%`, claudeOrange) : dim("--%")}`
|
|
109
|
+
: dim("--%/--%");
|
|
107
110
|
|
|
108
111
|
// Codex 5h/1w
|
|
109
112
|
let xVal = dim("--%/--%");
|
|
@@ -163,12 +166,31 @@ function contextPercentText(ctxView) {
|
|
|
163
166
|
return colorByPercent(ctxView.percent, `${ctxView.percent}%`);
|
|
164
167
|
}
|
|
165
168
|
|
|
169
|
+
// gateway 세션은 5h, 1w 대신 spend_limit 창 하나만 받는다.
|
|
170
|
+
function spendOnlyPercent(claudeUsage) {
|
|
171
|
+
if (claudeUsage?.spendLimitPercent == null) return null;
|
|
172
|
+
if (claudeUsage.fiveHourPercent != null || claudeUsage.weeklyPercent != null)
|
|
173
|
+
return null;
|
|
174
|
+
return clampPercent(claudeUsage.spendLimitPercent);
|
|
175
|
+
}
|
|
176
|
+
|
|
166
177
|
// ============================================================================
|
|
167
178
|
// Claude 행 렌더러
|
|
168
179
|
// ============================================================================
|
|
169
180
|
export function getClaudeRows(currentTier, contextView, claudeUsage) {
|
|
170
181
|
const ctxView = contextView || buildContextUsageView({});
|
|
171
182
|
const prefix = `${bold(claudeOrange("c"))}:`;
|
|
183
|
+
const spend = spendOnlyPercent(claudeUsage);
|
|
184
|
+
if (spend != null) {
|
|
185
|
+
if (currentTier === "nano" || currentTier === "micro") {
|
|
186
|
+
const left = colorByProvider(spend, `${spend}%`, claudeOrange);
|
|
187
|
+
return [{ prefix, left, right: "" }];
|
|
188
|
+
}
|
|
189
|
+
const pct = colorByProvider(spend, formatPercentCell(spend), claudeOrange);
|
|
190
|
+
const left = `${dim("$:")}${tierBar(currentTier, spend, CLAUDE_ORANGE)}${pct}`;
|
|
191
|
+
const right = `${dim("CTX:")}${contextPercentText(ctxView)}`;
|
|
192
|
+
return [{ prefix, left, right }];
|
|
193
|
+
}
|
|
172
194
|
// API 실측 데이터
|
|
173
195
|
const fiveHourPercent = claudeUsage?.fiveHourPercent ?? null;
|
|
174
196
|
const weeklyPercent = claudeUsage?.weeklyPercent ?? null;
|