@triflux/core 10.52.0 → 10.53.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/hub/bridge.mjs +4 -25
- package/hub/cli-adapter-base.mjs +4 -104
- package/hub/codex-adapter.mjs +7 -116
- package/hub/index.mjs +1 -2
- package/hub/team/claude-transcript.mjs +19 -0
- package/hud/constants.mjs +7 -22
- package/hud/hud-qos-status.mjs +9 -28
- package/hud/providers/codex-probe.mjs +2 -31
- package/hud/providers/codex.mjs +16 -58
- package/hud/renderers.mjs +22 -31
- package/hud/utils.mjs +0 -8
- package/package.json +3 -4
- package/scripts/lib/mcp-guard-engine.mjs +2 -3
- package/hub/account-broker.mjs +0 -1221
package/hud/providers/codex.mjs
CHANGED
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
7
|
-
import { homedir } from "node:os";
|
|
8
7
|
import { join } from "node:path";
|
|
9
8
|
import {
|
|
10
|
-
CODEX_AUTH_PATH,
|
|
11
|
-
CODEX_BROKER_AUTH_CACHE_DIR,
|
|
12
9
|
CODEX_MIN_BUCKETS,
|
|
13
10
|
CODEX_PROBE_STATE_PATH,
|
|
14
11
|
CODEX_PROBE_TIMEOUT_MS,
|
|
@@ -17,6 +14,8 @@ import {
|
|
|
17
14
|
CODEX_QUOTA_STALE_MS,
|
|
18
15
|
CODEX_REFRESH_FLAG,
|
|
19
16
|
CODEX_REFRESH_LOCK_PATH,
|
|
17
|
+
getCodexAuthPath,
|
|
18
|
+
getCodexHome,
|
|
20
19
|
SPAWN_LOCK_TTL_MS,
|
|
21
20
|
} from "../constants.mjs";
|
|
22
21
|
import { decodeJwtEmail, readJson, writeJsonSafe } from "../utils.mjs";
|
|
@@ -59,7 +58,7 @@ export function normalizeBuckets(rl) {
|
|
|
59
58
|
|
|
60
59
|
export function getCodexEmail() {
|
|
61
60
|
try {
|
|
62
|
-
const auth = JSON.parse(readFileSync(
|
|
61
|
+
const auth = JSON.parse(readFileSync(getCodexAuthPath(), "utf-8"));
|
|
63
62
|
return decodeJwtEmail(auth?.tokens?.id_token);
|
|
64
63
|
} catch {
|
|
65
64
|
return null;
|
|
@@ -117,8 +116,8 @@ function positiveWindowMinutes(bucket) {
|
|
|
117
116
|
return minutes > 0 ? minutes : null;
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
// 계정 하나에는 같은 종류의 창이 한 번에 하나만 열린다.
|
|
121
|
-
//
|
|
119
|
+
// 계정 하나에는 같은 종류의 창이 한 번에 하나만 열린다. Codex 홈(CODEX_HOME)의
|
|
120
|
+
// sessions 와 HUD 라벨은 모두 auth.json 계정 하나의 것이다. 그래서
|
|
122
121
|
// 세션 로그에 더 늦게 리셋되는 창이 나타났고 옛 창이 그 창이 열리기 전에만 관측됐다면,
|
|
123
122
|
// 옛 창은 조기 리셋(2026-09-23 실측)이나 계정 전환으로 끝난 창이다. 프로브로만 본 창은
|
|
124
123
|
// 다른 계정일 수 있어 근거로 쓰지 않는다. 새 창이 열린 뒤에도 관측된 창은 함께 도는
|
|
@@ -135,12 +134,7 @@ function isReplacedWindowGroup(group, other) {
|
|
|
135
134
|
return lastSeenMs <= otherOpenedAtMs;
|
|
136
135
|
}
|
|
137
136
|
|
|
138
|
-
function selectCodexWindowSnapshot(
|
|
139
|
-
snapshots,
|
|
140
|
-
nowSec,
|
|
141
|
-
windowKey,
|
|
142
|
-
replaceEndedWindows,
|
|
143
|
-
) {
|
|
137
|
+
function selectCodexWindowSnapshot(snapshots, nowSec, windowKey) {
|
|
144
138
|
const groups = [];
|
|
145
139
|
const ungrouped = [];
|
|
146
140
|
|
|
@@ -179,12 +173,9 @@ function selectCodexWindowSnapshot(
|
|
|
179
173
|
const openGroups = groups.filter(
|
|
180
174
|
(group) => Number(group.latest[windowKey]?.resets_at) > nowSec,
|
|
181
175
|
);
|
|
182
|
-
const activeGroups =
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
!openGroups.some((other) => isReplacedWindowGroup(group, other)),
|
|
186
|
-
)
|
|
187
|
-
: openGroups;
|
|
176
|
+
const activeGroups = openGroups.filter(
|
|
177
|
+
(group) => !openGroups.some((other) => isReplacedWindowGroup(group, other)),
|
|
178
|
+
);
|
|
188
179
|
let selected = null;
|
|
189
180
|
if (activeGroups.length > 0) {
|
|
190
181
|
selected = activeGroups
|
|
@@ -214,18 +205,16 @@ function selectCodexWindowSnapshot(
|
|
|
214
205
|
return { snapshot: selected, activeGroupCount: activeGroups.length };
|
|
215
206
|
}
|
|
216
207
|
|
|
217
|
-
function selectCodexSnapshot(snapshots, nowSec
|
|
208
|
+
function selectCodexSnapshot(snapshots, nowSec) {
|
|
218
209
|
const primarySelection = selectCodexWindowSnapshot(
|
|
219
210
|
snapshots,
|
|
220
211
|
nowSec,
|
|
221
212
|
"primary",
|
|
222
|
-
replaceEndedWindows,
|
|
223
213
|
);
|
|
224
214
|
const secondarySelection = selectCodexWindowSnapshot(
|
|
225
215
|
snapshots,
|
|
226
216
|
nowSec,
|
|
227
217
|
"secondary",
|
|
228
|
-
replaceEndedWindows,
|
|
229
218
|
);
|
|
230
219
|
const primarySnapshot = primarySelection.snapshot;
|
|
231
220
|
if (!primarySnapshot) return null;
|
|
@@ -246,7 +235,7 @@ function selectCodexSnapshot(snapshots, nowSec, replaceEndedWindows) {
|
|
|
246
235
|
return selected;
|
|
247
236
|
}
|
|
248
237
|
|
|
249
|
-
function mergeRateLimitSnapshots(snapshots, nowSec
|
|
238
|
+
function mergeRateLimitSnapshots(snapshots, nowSec) {
|
|
250
239
|
const merged = {};
|
|
251
240
|
const codexSnapshots = [];
|
|
252
241
|
for (const snapshot of snapshots) {
|
|
@@ -259,11 +248,7 @@ function mergeRateLimitSnapshots(snapshots, nowSec, replaceEndedWindows) {
|
|
|
259
248
|
}
|
|
260
249
|
}
|
|
261
250
|
|
|
262
|
-
const codex = selectCodexSnapshot(
|
|
263
|
-
codexSnapshots,
|
|
264
|
-
nowSec,
|
|
265
|
-
replaceEndedWindows,
|
|
266
|
-
);
|
|
251
|
+
const codex = selectCodexSnapshot(codexSnapshots, nowSec);
|
|
267
252
|
if (codex) merged.codex = codex;
|
|
268
253
|
return merged;
|
|
269
254
|
}
|
|
@@ -275,12 +260,10 @@ function mergeRateLimitSnapshots(snapshots, nowSec, replaceEndedWindows) {
|
|
|
275
260
|
// 합성 버킷(token_count 기반)은 2일 이내 데이터만 허용하여 stale 방지.
|
|
276
261
|
// ============================================================================
|
|
277
262
|
export function getCodexRateLimits({
|
|
278
|
-
sessionsRoot = join(
|
|
263
|
+
sessionsRoot = join(getCodexHome(), "sessions"),
|
|
279
264
|
now = new Date(),
|
|
280
265
|
maxLinesPerFile = 800,
|
|
281
266
|
extraSnapshots = [],
|
|
282
|
-
// 교체된 창 판정(isReplacedWindowGroup). 브로커 Codex 계정이 있으면 끈다.
|
|
283
|
-
replaceEndedWindows = true,
|
|
284
267
|
} = {}) {
|
|
285
268
|
let syntheticBucket = null; // 최근 token_count에서 합성 (행 활성화 + 토큰 데이터용)
|
|
286
269
|
// 프로브도 세션 로그와 같은 window_minutes 기준으로 5h/1w 슬롯을 정한다.
|
|
@@ -372,11 +355,7 @@ export function getCodexRateLimits({
|
|
|
372
355
|
}
|
|
373
356
|
|
|
374
357
|
const snapshots = dayOffset <= 1 ? recentSnapshots : daySnapshots;
|
|
375
|
-
const mergedBuckets = mergeRateLimitSnapshots(
|
|
376
|
-
snapshots,
|
|
377
|
-
nowSec,
|
|
378
|
-
replaceEndedWindows,
|
|
379
|
-
);
|
|
358
|
+
const mergedBuckets = mergeRateLimitSnapshots(snapshots, nowSec);
|
|
380
359
|
if (Object.keys(mergedBuckets).length > 0) {
|
|
381
360
|
if (syntheticBucket) {
|
|
382
361
|
const main =
|
|
@@ -407,8 +386,7 @@ export async function collectProbeSnapshots(now = new Date()) {
|
|
|
407
386
|
let targets = [];
|
|
408
387
|
try {
|
|
409
388
|
targets = listProbeTargets({
|
|
410
|
-
codexAuthPath:
|
|
411
|
-
brokerCacheDir: CODEX_BROKER_AUTH_CACHE_DIR,
|
|
389
|
+
codexAuthPath: getCodexAuthPath(),
|
|
412
390
|
stateFilePath: CODEX_PROBE_STATE_PATH,
|
|
413
391
|
ttlMs: Number(process.env.TFX_CODEX_PROBE_TTL_MS) || CODEX_PROBE_TTL_MS,
|
|
414
392
|
nowMs: now.getTime(),
|
|
@@ -432,30 +410,10 @@ export async function collectProbeSnapshots(now = new Date()) {
|
|
|
432
410
|
);
|
|
433
411
|
}
|
|
434
412
|
|
|
435
|
-
// 브로커 계정은 자기 CODEX_HOME 에서 돌기 때문에, 옛 창이 전환 전 계정의 창이어도 그
|
|
436
|
-
// 계정이 아직 쓰이고 있을 수 있다. accounts.json 의 codex 항목과 프로브용 인증 캐시로 판정한다.
|
|
437
|
-
export function hasBrokerCodexAccounts({
|
|
438
|
-
brokerDir = CODEX_BROKER_AUTH_CACHE_DIR,
|
|
439
|
-
} = {}) {
|
|
440
|
-
const accounts = readJson(join(brokerDir, "accounts.json"), null);
|
|
441
|
-
if (Array.isArray(accounts?.codex) && accounts.codex.length > 0) return true;
|
|
442
|
-
try {
|
|
443
|
-
return readdirSync(brokerDir).some((file) =>
|
|
444
|
-
/^codex-auth-.+\.json$/.test(file),
|
|
445
|
-
);
|
|
446
|
-
} catch {
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
413
|
export async function refreshCodexRateLimitsCache() {
|
|
452
414
|
const now = new Date();
|
|
453
415
|
const extraSnapshots = await collectProbeSnapshots(now);
|
|
454
|
-
const buckets = getCodexRateLimits({
|
|
455
|
-
now,
|
|
456
|
-
extraSnapshots,
|
|
457
|
-
replaceEndedWindows: !hasBrokerCodexAccounts(),
|
|
458
|
-
});
|
|
416
|
+
const buckets = getCodexRateLimits({ now, extraSnapshots });
|
|
459
417
|
// buckets가 null이어도 캐시 갱신 (stale 데이터 제거)
|
|
460
418
|
writeJsonSafe(CODEX_QUOTA_CACHE_PATH, { timestamp: Date.now(), buckets });
|
|
461
419
|
return buckets;
|
package/hud/renderers.mjs
CHANGED
|
@@ -85,7 +85,11 @@ export function getMicroLine(
|
|
|
85
85
|
codexBuckets,
|
|
86
86
|
options = {},
|
|
87
87
|
) {
|
|
88
|
-
const {
|
|
88
|
+
const {
|
|
89
|
+
showCodex = true,
|
|
90
|
+
showAntigravity = true,
|
|
91
|
+
codexLoggedOut = false,
|
|
92
|
+
} = options;
|
|
89
93
|
const ctxView = contextView || buildContextUsageView({});
|
|
90
94
|
// Claude 5h/1w
|
|
91
95
|
const cF =
|
|
@@ -98,11 +102,11 @@ export function getMicroLine(
|
|
|
98
102
|
: null;
|
|
99
103
|
const cVal =
|
|
100
104
|
claudeUsage != null
|
|
101
|
-
? `${cF != null ? colorByProvider(cF, `${cF}
|
|
102
|
-
: dim("
|
|
105
|
+
? `${cF != null ? colorByProvider(cF, `${cF}%`, claudeOrange) : dim("--%")}${dim("/")}${cW != null ? colorByProvider(cW, `${cW}%`, claudeOrange) : dim("--%")}`
|
|
106
|
+
: dim("--%/--%");
|
|
103
107
|
|
|
104
108
|
// Codex 5h/1w
|
|
105
|
-
let xVal = dim("
|
|
109
|
+
let xVal = dim("--%/--%");
|
|
106
110
|
if (codexBuckets) {
|
|
107
111
|
const mb = codexBuckets.codex || codexBuckets[Object.keys(codexBuckets)[0]];
|
|
108
112
|
if (mb) {
|
|
@@ -114,7 +118,7 @@ export function getMicroLine(
|
|
|
114
118
|
mb.secondary?.used_percent != null
|
|
115
119
|
? clampPercent(mb.secondary.used_percent)
|
|
116
120
|
: null;
|
|
117
|
-
xVal = `${xF != null ? colorByProvider(xF, `${xF}
|
|
121
|
+
xVal = `${xF != null ? colorByProvider(xF, `${xF}%`, codexWhite) : dim("--%")}${dim("/")}${xW != null ? colorByProvider(xW, `${xW}%`, codexWhite) : dim("--%")}`;
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
|
|
@@ -122,15 +126,19 @@ export function getMicroLine(
|
|
|
122
126
|
// 세그먼트를 모아 join 한다. 차단된 프로바이더를 뺄 때 공백이 겹치지 않는다.
|
|
123
127
|
const segments = [`${bold(claudeOrange("c"))}${dim(":")}${cVal}`];
|
|
124
128
|
if (showCodex) {
|
|
125
|
-
|
|
129
|
+
const xMarker = codexLoggedOut ? dim("x") : bold(codexWhite("x"));
|
|
130
|
+
segments.push(`${xMarker}${dim(":")}${xVal}`);
|
|
126
131
|
}
|
|
127
132
|
if (showAntigravity) {
|
|
128
133
|
const marker = options.antigravityQuota?.auth
|
|
129
134
|
? bold(geminiBlue("a"))
|
|
130
135
|
: dim("a");
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
// stale 은 값을 dim 으로 보인다. 표식을 덧붙이면 최악 폭이 39칸을 넘는다.
|
|
137
|
+
const agyText = antigravityPercentText(options.antigravityQuota);
|
|
138
|
+
const agyValue = options.antigravityQuota?.stale
|
|
139
|
+
? dim(stripAnsi(agyText))
|
|
140
|
+
: agyText;
|
|
141
|
+
segments.push(`${marker}${dim(":")}${agyValue}`);
|
|
134
142
|
}
|
|
135
143
|
segments.push(`${dim("CTX:")}${contextPercentText(ctxView)}`);
|
|
136
144
|
return truncateAnsi(segments.join(" "), cols);
|
|
@@ -243,24 +251,9 @@ export function getClaudeRows(currentTier, contextView, claudeUsage) {
|
|
|
243
251
|
// ============================================================================
|
|
244
252
|
// 계정 라벨 + 범용 프로바이더 행 렌더러
|
|
245
253
|
// ============================================================================
|
|
246
|
-
export function getAccountLabel(
|
|
247
|
-
provider
|
|
248
|
-
|
|
249
|
-
accountsState,
|
|
250
|
-
codexEmail,
|
|
251
|
-
) {
|
|
252
|
-
const providerConfig = accountsConfig?.providers?.[provider] || [];
|
|
253
|
-
const providerState = accountsState?.providers?.[provider] || {};
|
|
254
|
-
const lastId = providerState.last_selected_id;
|
|
255
|
-
const picked = providerConfig.find((a) => a.id === lastId) ||
|
|
256
|
-
providerConfig[0] || {
|
|
257
|
-
id: `${provider}-main`,
|
|
258
|
-
label: provider === "antigravity" ? "agy" : provider,
|
|
259
|
-
};
|
|
260
|
-
let label = picked.label || picked.id;
|
|
261
|
-
if (codexEmail) label = codexEmail;
|
|
262
|
-
if (label.includes("@")) label = label.split("@")[0];
|
|
263
|
-
return label;
|
|
254
|
+
export function getAccountLabel(provider, accountName) {
|
|
255
|
+
const label = accountName || (provider === "antigravity" ? "agy" : provider);
|
|
256
|
+
return label.includes("@") ? label.split("@")[0] : label;
|
|
264
257
|
}
|
|
265
258
|
|
|
266
259
|
export function getProviderRow(
|
|
@@ -268,13 +261,11 @@ export function getProviderRow(
|
|
|
268
261
|
provider,
|
|
269
262
|
marker,
|
|
270
263
|
markerColor,
|
|
271
|
-
accountsConfig,
|
|
272
|
-
accountsState,
|
|
273
264
|
realQuota,
|
|
274
|
-
|
|
265
|
+
accountName,
|
|
275
266
|
) {
|
|
276
267
|
const accountLabel = fitText(
|
|
277
|
-
getAccountLabel(provider,
|
|
268
|
+
getAccountLabel(provider, accountName),
|
|
278
269
|
ACCOUNT_LABEL_WIDTH,
|
|
279
270
|
);
|
|
280
271
|
|
package/hud/utils.mjs
CHANGED
|
@@ -254,14 +254,6 @@ export function formatResetRemainingDayHour(isoOrUnix, cycleMs = 0) {
|
|
|
254
254
|
return `${String(days).padStart(2, "0")}d${String(hours).padStart(2, "0")}h`;
|
|
255
255
|
}
|
|
256
256
|
|
|
257
|
-
export function getProviderAccountId(provider, accountsConfig, accountsState) {
|
|
258
|
-
const providerState = accountsState?.providers?.[provider] || {};
|
|
259
|
-
const selectedId = providerState.last_selected_id;
|
|
260
|
-
if (selectedId) return selectedId;
|
|
261
|
-
const providerConfig = accountsConfig?.providers?.[provider] || [];
|
|
262
|
-
return providerConfig[0]?.id || `${provider}-main`;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
257
|
// JWT base64 디코딩 공통 헬퍼
|
|
266
258
|
export function decodeJwtEmail(idToken) {
|
|
267
259
|
if (!idToken) return null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@triflux/core",
|
|
3
|
-
"version": "10.
|
|
4
|
-
"description": "triflux core — CLI routing, adapters
|
|
3
|
+
"version": "10.53.0",
|
|
4
|
+
"description": "triflux core — CLI routing, adapters. Zero native dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "hub/index.mjs",
|
|
7
7
|
"exports": {
|
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
"node": ">=20"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@iarna/toml": "^2.2.5"
|
|
17
|
-
"zod": "^4.0.0"
|
|
16
|
+
"@iarna/toml": "^2.2.5"
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
20
19
|
"hub",
|
|
@@ -40,7 +40,6 @@ const DEFAULT_REGISTRY = Object.freeze({
|
|
|
40
40
|
unknown_server_action: "warn",
|
|
41
41
|
sync_denylist: [],
|
|
42
42
|
watched_paths: [
|
|
43
|
-
"~/.gemini/settings.json",
|
|
44
43
|
"~/.codex/config.toml",
|
|
45
44
|
"~/.claude.json",
|
|
46
45
|
"~/.claude/settings.json",
|
|
@@ -709,7 +708,7 @@ function serverTargets(serverConfig) {
|
|
|
709
708
|
),
|
|
710
709
|
];
|
|
711
710
|
}
|
|
712
|
-
return ["claude", "
|
|
711
|
+
return ["claude", "codex", "antigravity"];
|
|
713
712
|
}
|
|
714
713
|
|
|
715
714
|
function serverAppliesToClient(serverConfig, client) {
|
|
@@ -1718,7 +1717,7 @@ export function addRegistryServer(name, url, options = {}) {
|
|
|
1718
1717
|
.filter(Boolean),
|
|
1719
1718
|
),
|
|
1720
1719
|
]
|
|
1721
|
-
: ["claude", "
|
|
1720
|
+
: ["claude", "codex", "antigravity"],
|
|
1722
1721
|
description: options.description || `${trimmedName} MCP 서버`,
|
|
1723
1722
|
};
|
|
1724
1723
|
|