@triflux/core 10.0.0-alpha.1
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/agent-route-guard.mjs +109 -0
- package/hooks/cross-review-tracker.mjs +122 -0
- package/hooks/error-context.mjs +148 -0
- package/hooks/hook-manager.mjs +352 -0
- package/hooks/hook-orchestrator.mjs +312 -0
- package/hooks/hook-registry.json +213 -0
- package/hooks/hooks.json +89 -0
- package/hooks/keyword-rules.json +581 -0
- package/hooks/lib/resolve-root.mjs +59 -0
- package/hooks/mcp-config-watcher.mjs +85 -0
- package/hooks/pipeline-stop.mjs +76 -0
- package/hooks/safety-guard.mjs +106 -0
- package/hooks/subagent-verifier.mjs +80 -0
- package/hub/assign-callbacks.mjs +133 -0
- package/hub/bridge.mjs +799 -0
- package/hub/cli-adapter-base.mjs +192 -0
- package/hub/codex-adapter.mjs +190 -0
- package/hub/codex-compat.mjs +78 -0
- package/hub/codex-preflight.mjs +147 -0
- package/hub/delegator/contracts.mjs +37 -0
- package/hub/delegator/index.mjs +14 -0
- package/hub/delegator/schema/delegator-tools.schema.json +250 -0
- package/hub/delegator/service.mjs +307 -0
- package/hub/delegator/tool-definitions.mjs +35 -0
- package/hub/fullcycle.mjs +96 -0
- package/hub/gemini-adapter.mjs +179 -0
- package/hub/hitl.mjs +143 -0
- package/hub/intent.mjs +193 -0
- package/hub/lib/process-utils.mjs +361 -0
- package/hub/middleware/request-logger.mjs +81 -0
- package/hub/paths.mjs +30 -0
- package/hub/pipeline/gates/confidence.mjs +56 -0
- package/hub/pipeline/gates/consensus.mjs +94 -0
- package/hub/pipeline/gates/index.mjs +5 -0
- package/hub/pipeline/gates/selfcheck.mjs +82 -0
- package/hub/pipeline/index.mjs +318 -0
- package/hub/pipeline/state.mjs +191 -0
- package/hub/pipeline/transitions.mjs +124 -0
- package/hub/platform.mjs +225 -0
- package/hub/quality/deslop.mjs +253 -0
- package/hub/reflexion.mjs +372 -0
- package/hub/research.mjs +146 -0
- package/hub/router.mjs +791 -0
- package/hub/routing/complexity.mjs +166 -0
- package/hub/routing/index.mjs +117 -0
- package/hub/routing/q-learning.mjs +336 -0
- package/hub/session-fingerprint.mjs +352 -0
- package/hub/state.mjs +245 -0
- package/hub/team-bridge.mjs +25 -0
- package/hub/token-mode.mjs +224 -0
- package/hub/workers/worker-utils.mjs +104 -0
- package/hud/colors.mjs +88 -0
- package/hud/constants.mjs +81 -0
- package/hud/hud-qos-status.mjs +206 -0
- package/hud/providers/claude.mjs +309 -0
- package/hud/providers/codex.mjs +151 -0
- package/hud/providers/gemini.mjs +320 -0
- package/hud/renderers.mjs +424 -0
- package/hud/terminal.mjs +140 -0
- package/hud/utils.mjs +287 -0
- package/package.json +31 -0
- package/scripts/lib/claudemd-manager.mjs +325 -0
- package/scripts/lib/context.mjs +67 -0
- package/scripts/lib/cross-review-utils.mjs +51 -0
- package/scripts/lib/env-probe.mjs +241 -0
- package/scripts/lib/gemini-profiles.mjs +85 -0
- package/scripts/lib/hook-utils.mjs +14 -0
- package/scripts/lib/keyword-rules.mjs +166 -0
- package/scripts/lib/logger.mjs +105 -0
- package/scripts/lib/mcp-filter.mjs +739 -0
- package/scripts/lib/mcp-guard-engine.mjs +940 -0
- package/scripts/lib/mcp-manifest.mjs +79 -0
- package/scripts/lib/mcp-server-catalog.mjs +118 -0
- package/scripts/lib/psmux-info.mjs +119 -0
- package/scripts/lib/remote-spawn-transfer.mjs +196 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// hub/token-mode.mjs — Token Efficiency Mode
|
|
2
|
+
// GAP 분석 P2 #7: 심볼 통신 + 약어로 30-50% 토큰 절감
|
|
3
|
+
|
|
4
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
5
|
+
export const COMPACT_RULES = [
|
|
6
|
+
// ── 심볼 치환 (긴 매칭 우선 정렬) ──
|
|
7
|
+
{ from: ['greater than or equal'], to: '≥', type: 'symbol' },
|
|
8
|
+
{ from: ['less than or equal'], to: '≤', type: 'symbol' },
|
|
9
|
+
{ from: ['results in', '결과적으로'], to: '→', type: 'symbol' },
|
|
10
|
+
{ from: ['therefore', '따라서'], to: '∴', type: 'symbol' },
|
|
11
|
+
{ from: ['because', '왜냐하면'], to: '∵', type: 'symbol' },
|
|
12
|
+
{ from: ['approximately', '대략'], to: '≈', type: 'symbol' },
|
|
13
|
+
{ from: ['not equal', '같지 않'], to: '≠', type: 'symbol' },
|
|
14
|
+
{ from: ['in progress', '진행 중'], to: '⏳', type: 'symbol' },
|
|
15
|
+
{ from: ['completed', '완료'], to: '✓', type: 'symbol' },
|
|
16
|
+
{ from: ['success', '성공'], to: '✓', type: 'symbol' },
|
|
17
|
+
{ from: ['failure', '실패'], to: '✗', type: 'symbol' },
|
|
18
|
+
{ from: ['warning', '경고'], to: '⚠', type: 'symbol' },
|
|
19
|
+
{ from: ['error', '에러'], to: '✗', type: 'symbol' },
|
|
20
|
+
{ from: ['pending', '대기'], to: '⏸', type: 'symbol' },
|
|
21
|
+
|
|
22
|
+
// ── 약어 (긴 매칭 우선 정렬) ──
|
|
23
|
+
{ from: ['configuration', '설정'], to: 'cfg', type: 'abbrev' },
|
|
24
|
+
{ from: ['implementation', '구현'], to: 'impl', type: 'abbrev' },
|
|
25
|
+
{ from: ['architecture', '아키텍처'], to: 'arch', type: 'abbrev' },
|
|
26
|
+
{ from: ['dependency', '의존성'], to: 'dep', type: 'abbrev' },
|
|
27
|
+
{ from: ['function', '함수'], to: 'fn', type: 'abbrev' },
|
|
28
|
+
{ from: ['parameter', '파라미터'], to: 'param', type: 'abbrev' },
|
|
29
|
+
{ from: ['repository', '저장소'], to: 'repo', type: 'abbrev' },
|
|
30
|
+
{ from: ['environment', '환경'], to: 'env', type: 'abbrev' },
|
|
31
|
+
{ from: ['variable', '변수'], to: 'var', type: 'abbrev' },
|
|
32
|
+
{ from: ['directory', '디렉토리'], to: 'dir', type: 'abbrev' },
|
|
33
|
+
|
|
34
|
+
// ── 한국어 동사/명령형 약어 ──
|
|
35
|
+
{ from: ['구현해'], to: 'impl', type: 'abbrev' },
|
|
36
|
+
{ from: ['확인해'], to: 'check', type: 'abbrev' },
|
|
37
|
+
{ from: ['수정해'], to: 'fix', type: 'abbrev' },
|
|
38
|
+
{ from: ['테스트'], to: 'test', type: 'abbrev' },
|
|
39
|
+
{ from: ['리뷰'], to: 'review', type: 'abbrev' },
|
|
40
|
+
{ from: ['분석'], to: 'analyze', type: 'abbrev' },
|
|
41
|
+
{ from: ['설계'], to: 'design', type: 'abbrev' },
|
|
42
|
+
{ from: ['문서화'], to: 'docs', type: 'abbrev' },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
46
|
+
export const REVIEW_RULES = [
|
|
47
|
+
{ from: ['looks good to me', 'lgtm'], to: '✓lgtm', type: 'abbrev' },
|
|
48
|
+
{ from: ['needs changes', '수정 필요'], to: '✗chg', type: 'abbrev' },
|
|
49
|
+
{ from: ['nitpick', '사소한'], to: 'nit', type: 'abbrev' },
|
|
50
|
+
{ from: ['blocking', '블로킹'], to: 'blk', type: 'abbrev' },
|
|
51
|
+
{ from: ['suggestion', '제안'], to: 'sug', type: 'abbrev' },
|
|
52
|
+
{ from: ['question', '질문'], to: 'q', type: 'abbrev' },
|
|
53
|
+
{ from: ['approved', '승인'], to: '✓apv', type: 'abbrev' },
|
|
54
|
+
{ from: ['request changes', '변경 요청'], to: '✗req', type: 'abbrev' },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
58
|
+
export const DESIGN_RULES = [
|
|
59
|
+
{ from: ['component', '컴포넌트'], to: 'cmp', type: 'abbrev' },
|
|
60
|
+
{ from: ['interface', '인터페이스'], to: 'iface', type: 'abbrev' },
|
|
61
|
+
{ from: ['abstraction', '추상화'], to: 'abs', type: 'abbrev' },
|
|
62
|
+
{ from: ['pattern', '패턴'], to: 'ptn', type: 'abbrev' },
|
|
63
|
+
{ from: ['dependency injection', '의존성 주입'], to: 'di', type: 'abbrev' },
|
|
64
|
+
{ from: ['single responsibility', '단일 책임'], to: 'srp', type: 'abbrev' },
|
|
65
|
+
{ from: ['open closed', '개방 폐쇄'], to: 'ocp', type: 'abbrev' },
|
|
66
|
+
{ from: ['inheritance', '상속'], to: 'inh', type: 'abbrev' },
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
/** @type {Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>} */
|
|
70
|
+
export const DOCS_RULES = [
|
|
71
|
+
{ from: ['description', '설명'], to: 'desc', type: 'abbrev' },
|
|
72
|
+
{ from: ['example', '예시'], to: 'ex', type: 'abbrev' },
|
|
73
|
+
{ from: ['reference', '참조'], to: 'ref', type: 'abbrev' },
|
|
74
|
+
{ from: ['introduction', '소개'], to: 'intro', type: 'abbrev' },
|
|
75
|
+
{ from: ['deprecated', '사용 중단'], to: 'dep', type: 'abbrev' },
|
|
76
|
+
{ from: ['optional', '선택적'], to: 'opt', type: 'abbrev' },
|
|
77
|
+
{ from: ['required', '필수'], to: 'req', type: 'abbrev' },
|
|
78
|
+
{ from: ['returns', '반환'], to: 'ret', type: 'abbrev' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// ── 프로필 맵 ──
|
|
82
|
+
|
|
83
|
+
/** @type {Record<string, Array<{ from: string[], to: string, type: 'symbol'|'abbrev' }>>} */
|
|
84
|
+
const PROFILE_MAP = {
|
|
85
|
+
default: COMPACT_RULES,
|
|
86
|
+
review: [...COMPACT_RULES, ...REVIEW_RULES],
|
|
87
|
+
design: [...COMPACT_RULES, ...DESIGN_RULES],
|
|
88
|
+
docs: [...COMPACT_RULES, ...DOCS_RULES],
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ── 내부: 정렬된 치환 쌍 빌드 헬퍼 ──
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 규칙 배열로부터 패턴 쌍을 빌드하고 긴 패턴 우선 정렬
|
|
95
|
+
* @param {Array<{ from: string[], to: string }>} rules
|
|
96
|
+
* @returns {Array<{ pattern: string, to: string, len: number }>}
|
|
97
|
+
*/
|
|
98
|
+
function buildCompactPairs(rules) {
|
|
99
|
+
const pairs = [];
|
|
100
|
+
for (const rule of rules) {
|
|
101
|
+
for (const keyword of rule.from) {
|
|
102
|
+
pairs.push({ pattern: keyword, to: rule.to, len: keyword.length });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
pairs.sort((a, b) => b.len - a.len);
|
|
106
|
+
return pairs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 규칙 배열로부터 확장 쌍을 빌드
|
|
111
|
+
* @param {Array<{ from: string[], to: string }>} rules
|
|
112
|
+
* @returns {Array<{ symbol: string, restore: string }>}
|
|
113
|
+
*/
|
|
114
|
+
function buildExpandPairs(rules) {
|
|
115
|
+
return rules.map((rule) => ({ symbol: rule.to, restore: rule.from[0] }));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 기본 프로필 쌍 (기존 동작 유지)
|
|
119
|
+
const _compactPairs = buildCompactPairs(COMPACT_RULES);
|
|
120
|
+
const _expandPairs = buildExpandPairs(COMPACT_RULES);
|
|
121
|
+
|
|
122
|
+
// ── 코드 블록 보호 유틸 ──
|
|
123
|
+
|
|
124
|
+
const CODE_BLOCK_RE = /```[\s\S]*?```/g;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 코드 블록을 플레이스홀더로 치환, 변환 후 복원
|
|
128
|
+
* @param {string} text
|
|
129
|
+
* @param {(segment: string) => string} transform
|
|
130
|
+
* @returns {string}
|
|
131
|
+
*/
|
|
132
|
+
function withCodeProtection(text, transform) {
|
|
133
|
+
const blocks = [];
|
|
134
|
+
const placeholder = '\x00CB';
|
|
135
|
+
let idx = 0;
|
|
136
|
+
const masked = text.replace(CODE_BLOCK_RE, (match) => {
|
|
137
|
+
blocks.push(match);
|
|
138
|
+
return `${placeholder}${idx++}${placeholder}`;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const transformed = transform(masked);
|
|
142
|
+
|
|
143
|
+
// 플레이스홀더 복원
|
|
144
|
+
return transformed.replace(
|
|
145
|
+
new RegExp(`${placeholder}(\\d+)${placeholder}`, 'g'),
|
|
146
|
+
(_, i) => blocks[Number(i)],
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── compact 모드 상태 ──
|
|
151
|
+
|
|
152
|
+
let _compactMode = false;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* compact 모드 활성 여부
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
*/
|
|
158
|
+
export function isCompactMode() {
|
|
159
|
+
return _compactMode;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 텍스트를 compact 모드로 변환
|
|
164
|
+
* 심볼 치환 + 약어 적용, 코드 블록 내부는 보호
|
|
165
|
+
* @param {string} text
|
|
166
|
+
* @returns {string}
|
|
167
|
+
*/
|
|
168
|
+
export function compactify(text) {
|
|
169
|
+
if (!text || typeof text !== 'string') return text ?? '';
|
|
170
|
+
_compactMode = true;
|
|
171
|
+
|
|
172
|
+
return withCodeProtection(text, (segment) => {
|
|
173
|
+
let result = segment;
|
|
174
|
+
for (const { pattern, to } of _compactPairs) {
|
|
175
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
176
|
+
const re = new RegExp(escaped, 'gi');
|
|
177
|
+
result = result.replace(re, to);
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* 텍스트를 compact 모드로 변환 (도메인 프로필 선택 가능)
|
|
185
|
+
* @param {string} text
|
|
186
|
+
* @param {'default'|'review'|'design'|'docs'} [profile='default'] — 도메인 프로필
|
|
187
|
+
* @returns {string}
|
|
188
|
+
*/
|
|
189
|
+
export function applyCompactRules(text, profile = 'default') {
|
|
190
|
+
if (!text || typeof text !== 'string') return text ?? '';
|
|
191
|
+
|
|
192
|
+
const rules = PROFILE_MAP[profile] ?? COMPACT_RULES;
|
|
193
|
+
const pairs = buildCompactPairs(rules);
|
|
194
|
+
|
|
195
|
+
return withCodeProtection(text, (segment) => {
|
|
196
|
+
let result = segment;
|
|
197
|
+
for (const { pattern, to } of pairs) {
|
|
198
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
199
|
+
const re = new RegExp(escaped, 'gi');
|
|
200
|
+
result = result.replace(re, to);
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* compact 텍스트를 원래 형태로 복원 (best-effort)
|
|
208
|
+
* @param {string} text
|
|
209
|
+
* @returns {string}
|
|
210
|
+
*/
|
|
211
|
+
export function expand(text) {
|
|
212
|
+
if (!text || typeof text !== 'string') return text ?? '';
|
|
213
|
+
_compactMode = false;
|
|
214
|
+
|
|
215
|
+
return withCodeProtection(text, (segment) => {
|
|
216
|
+
let result = segment;
|
|
217
|
+
for (const { symbol, restore } of _expandPairs) {
|
|
218
|
+
const escaped = symbol.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
219
|
+
const re = new RegExp(escaped, 'g');
|
|
220
|
+
result = result.replace(re, restore);
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
});
|
|
224
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// hub/workers/worker-utils.mjs — 워커 공통 유틸리티
|
|
2
|
+
// claude-worker, gemini-worker, pipe 등에서 공유하는 순수 유틸 함수 모음.
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_TIMEOUT_MS = 15 * 60 * 1000;
|
|
5
|
+
export const DEFAULT_KILL_GRACE_MS = 1000;
|
|
6
|
+
|
|
7
|
+
export function toStringList(value) {
|
|
8
|
+
if (!Array.isArray(value)) return [];
|
|
9
|
+
return value
|
|
10
|
+
.map((item) => String(item ?? '').trim())
|
|
11
|
+
.filter(Boolean);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function safeJsonParse(line) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(line);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createWorkerError(message, details = {}) {
|
|
23
|
+
const error = new Error(message);
|
|
24
|
+
Object.assign(error, details);
|
|
25
|
+
return error;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sleep(delayMs) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const timer = setTimeout(resolve, Math.max(0, delayMs));
|
|
31
|
+
timer.unref?.();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function withRetry(fn, opts = {}) {
|
|
36
|
+
const {
|
|
37
|
+
maxAttempts = 3,
|
|
38
|
+
baseDelayMs = 1000,
|
|
39
|
+
maxDelayMs = 15000,
|
|
40
|
+
shouldRetry = () => true,
|
|
41
|
+
} = opts;
|
|
42
|
+
|
|
43
|
+
let lastError;
|
|
44
|
+
|
|
45
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
46
|
+
try {
|
|
47
|
+
return await fn();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
lastError = error;
|
|
50
|
+
if (attempt >= maxAttempts || !shouldRetry(error, attempt)) {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const delay = Math.min(baseDelayMs * 2 ** (attempt - 1), maxDelayMs)
|
|
55
|
+
* (0.5 + Math.random() * 0.5);
|
|
56
|
+
await sleep(delay);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
throw lastError;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function appendTextFragments(value, parts) {
|
|
64
|
+
if (value == null) return;
|
|
65
|
+
|
|
66
|
+
if (typeof value === 'string') {
|
|
67
|
+
const trimmed = value.trim();
|
|
68
|
+
if (trimmed) parts.push(trimmed);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
for (const item of value) appendTextFragments(item, parts);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof value !== 'object') return;
|
|
78
|
+
|
|
79
|
+
if (typeof value.text === 'string') appendTextFragments(value.text, parts);
|
|
80
|
+
if (typeof value.response === 'string') appendTextFragments(value.response, parts);
|
|
81
|
+
if (typeof value.result === 'string') appendTextFragments(value.result, parts);
|
|
82
|
+
if (value.content != null) appendTextFragments(value.content, parts);
|
|
83
|
+
if (value.message != null) appendTextFragments(value.message, parts);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function extractText(value) {
|
|
87
|
+
const parts = [];
|
|
88
|
+
appendTextFragments(value, parts);
|
|
89
|
+
return parts.join('\n').trim();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function terminateChild(child, killGraceMs) {
|
|
93
|
+
if (!child || child.exitCode !== null || child.killed) return;
|
|
94
|
+
|
|
95
|
+
try { child.stdin.end(); } catch {}
|
|
96
|
+
try { child.kill(); } catch {}
|
|
97
|
+
|
|
98
|
+
const timer = setTimeout(() => {
|
|
99
|
+
if (child.exitCode === null) {
|
|
100
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
101
|
+
}
|
|
102
|
+
}, killGraceMs);
|
|
103
|
+
timer.unref?.();
|
|
104
|
+
}
|
package/hud/colors.mjs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// ANSI 색상 (OMC colors.js 스키마 일치)
|
|
3
|
+
// ============================================================================
|
|
4
|
+
export const RESET = "\x1b[0m";
|
|
5
|
+
export const DIM = "\x1b[2m";
|
|
6
|
+
export const BOLD = "\x1b[1m";
|
|
7
|
+
export const RED = "\x1b[31m";
|
|
8
|
+
export const GREEN = "\x1b[32m";
|
|
9
|
+
export const YELLOW = "\x1b[33m";
|
|
10
|
+
export const CYAN = "\x1b[36m";
|
|
11
|
+
export const CLAUDE_ORANGE = "\x1b[38;2;232;112;64m"; // #E87040 (Claude 공식 오렌지)
|
|
12
|
+
export const CODEX_WHITE = "\x1b[97m"; // bright white (SGR 37은 Windows Terminal에서 연회색 매핑)
|
|
13
|
+
export const GEMINI_BLUE = "\x1b[38;5;39m";
|
|
14
|
+
|
|
15
|
+
export function green(t) { return `${GREEN}${t}${RESET}`; }
|
|
16
|
+
export function yellow(t) { return `${YELLOW}${t}${RESET}`; }
|
|
17
|
+
export function red(t) { return `${RED}${t}${RESET}`; }
|
|
18
|
+
export function cyan(t) { return `${CYAN}${t}${RESET}`; }
|
|
19
|
+
export function dim(t) { return `${DIM}${t}${RESET}`; }
|
|
20
|
+
export function bold(t) { return `${BOLD}${t}${RESET}`; }
|
|
21
|
+
export function claudeOrange(t) { return `${CLAUDE_ORANGE}${t}${RESET}`; }
|
|
22
|
+
export function codexWhite(t) { return `${CODEX_WHITE}${t}${RESET}`; }
|
|
23
|
+
export function geminiBlue(t) { return `${GEMINI_BLUE}${t}${RESET}`; }
|
|
24
|
+
|
|
25
|
+
export function colorByPercent(value, text) {
|
|
26
|
+
if (value >= 85) return red(text);
|
|
27
|
+
if (value >= 70) return yellow(text);
|
|
28
|
+
if (value >= 50) return cyan(text);
|
|
29
|
+
return green(text);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function colorCooldown(seconds, text) {
|
|
33
|
+
if (seconds > 120) return red(text);
|
|
34
|
+
if (seconds > 0) return yellow(text);
|
|
35
|
+
return dim(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function colorParallel(current, cap) {
|
|
39
|
+
if (current >= cap) return green(`${current}/${cap}`);
|
|
40
|
+
if (current > 1) return yellow(`${current}/${cap}`);
|
|
41
|
+
return red(`${current}/${cap}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const GAUGE_WIDTH = 5;
|
|
45
|
+
export const GAUGE_BLOCKS = ["░", "▒", "▓", "█"]; // 밝기 0~3
|
|
46
|
+
|
|
47
|
+
export function coloredBar(percent, width = GAUGE_WIDTH, baseColor = null) {
|
|
48
|
+
const safePercent = Math.min(100, Math.max(0, percent));
|
|
49
|
+
const perBlock = 100 / width;
|
|
50
|
+
|
|
51
|
+
// 상태별 색상
|
|
52
|
+
let barColor;
|
|
53
|
+
if (safePercent >= 85) barColor = RED;
|
|
54
|
+
else if (safePercent >= 70) barColor = YELLOW;
|
|
55
|
+
else barColor = baseColor || GREEN;
|
|
56
|
+
|
|
57
|
+
let bar = "";
|
|
58
|
+
for (let i = 0; i < width; i++) {
|
|
59
|
+
const blockStart = i * perBlock;
|
|
60
|
+
const blockEnd = (i + 1) * perBlock;
|
|
61
|
+
|
|
62
|
+
if (safePercent >= blockEnd) {
|
|
63
|
+
bar += "█"; // 완전 채움
|
|
64
|
+
} else if (safePercent > blockStart) {
|
|
65
|
+
// 프론티어: 구간 내 진행률
|
|
66
|
+
const progress = (safePercent - blockStart) / perBlock;
|
|
67
|
+
if (progress >= 0.75) bar += "▓";
|
|
68
|
+
else if (progress >= 0.33) bar += "▒";
|
|
69
|
+
else bar += "░";
|
|
70
|
+
} else {
|
|
71
|
+
bar += "░"; // 미도달
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 채워진 부분 = barColor, 빈 부분 = DIM
|
|
76
|
+
const filledEnd = Math.ceil(safePercent / perBlock);
|
|
77
|
+
const coloredPart = barColor + bar.slice(0, filledEnd) + RESET;
|
|
78
|
+
const dimPart = filledEnd < width ? DIM + bar.slice(filledEnd) + RESET : "";
|
|
79
|
+
|
|
80
|
+
return coloredPart + dimPart;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 프로바이더별 색상 % (< 70%: 프로바이더 색, ≥ 70%: 경고색)
|
|
84
|
+
export function colorByProvider(value, text, providerColorFn) {
|
|
85
|
+
if (value >= 85) return red(text);
|
|
86
|
+
if (value >= 70) return yellow(text);
|
|
87
|
+
return providerColorFn(text);
|
|
88
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// 상수 / 경로
|
|
3
|
+
// ============================================================================
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
export const VERSION = "2.0";
|
|
8
|
+
|
|
9
|
+
export const QOS_PATH = join(homedir(), ".omc", "state", "cli_qos_profile.json");
|
|
10
|
+
export const ACCOUNTS_CONFIG_PATH = join(homedir(), ".omc", "router", "accounts.json");
|
|
11
|
+
export const ACCOUNTS_STATE_PATH = join(homedir(), ".omc", "state", "cli_accounts_state.json");
|
|
12
|
+
|
|
13
|
+
// tfx-multi 상태 (v2.2 HUD 통합)
|
|
14
|
+
export const TEAM_STATE_PATH = join(homedir(), ".claude", "cache", "tfx-hub", "team-state.json");
|
|
15
|
+
|
|
16
|
+
// Claude OAuth Usage API (api.anthropic.com/api/oauth/usage)
|
|
17
|
+
export const CLAUDE_CREDENTIALS_PATH = join(homedir(), ".claude", ".credentials.json");
|
|
18
|
+
export const CLAUDE_USAGE_CACHE_PATH = join(homedir(), ".claude", "cache", "claude-usage-cache.json");
|
|
19
|
+
export const OMC_PLUGIN_USAGE_CACHE_PATH = join(homedir(), ".claude", "plugins", "oh-my-claudecode", ".usage-cache.json");
|
|
20
|
+
export const CLAUDE_USAGE_STALE_MS_SOLO = 5 * 60 * 1000; // OMC 없을 때: 5분 캐시
|
|
21
|
+
export const CLAUDE_USAGE_STALE_MS_WITH_OMC = 15 * 60 * 1000; // OMC 있을 때: 15분 (OMC가 30초마다 갱신)
|
|
22
|
+
export const CLAUDE_USAGE_429_BACKOFF_MS = 10 * 60 * 1000; // 429 에러 시 10분 backoff
|
|
23
|
+
export const GEMINI_429_BASE_DELAY_MS = 2000;
|
|
24
|
+
export const GEMINI_429_MAX_RETRIES = 3;
|
|
25
|
+
export const GEMINI_429_COOLDOWN_MS = 30000;
|
|
26
|
+
export const CLAUDE_USAGE_ERROR_BACKOFF_MS = 3 * 60 * 1000; // 기타 에러 시 3분 backoff
|
|
27
|
+
export const CLAUDE_API_TIMEOUT_MS = 10_000;
|
|
28
|
+
export const FIVE_HOUR_MS = 5 * 60 * 60 * 1000;
|
|
29
|
+
export const SEVEN_DAY_MS = 7 * 24 * 60 * 60 * 1000;
|
|
30
|
+
export const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
31
|
+
export const DEFAULT_OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
32
|
+
|
|
33
|
+
export const CODEX_AUTH_PATH = join(homedir(), ".codex", "auth.json");
|
|
34
|
+
export const CODEX_QUOTA_CACHE_PATH = join(homedir(), ".claude", "cache", "codex-rate-limits-cache.json");
|
|
35
|
+
export const CODEX_QUOTA_STALE_MS = 15 * 1000; // 15초
|
|
36
|
+
export const CODEX_MIN_BUCKETS = 2;
|
|
37
|
+
|
|
38
|
+
// Gemini 쿼터 API 관련
|
|
39
|
+
export const GEMINI_OAUTH_PATH = join(homedir(), ".gemini", "oauth_creds.json");
|
|
40
|
+
export const GEMINI_QUOTA_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-quota-cache.json");
|
|
41
|
+
export const GEMINI_PROJECT_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-project-id.json");
|
|
42
|
+
export const GEMINI_SESSION_CACHE_PATH = join(homedir(), ".claude", "cache", "gemini-session-cache.json");
|
|
43
|
+
export const GEMINI_RPM_TRACKER_PATH = join(homedir(), ".claude", "cache", "gemini-rpm-tracker.json");
|
|
44
|
+
export const SV_ACCUMULATOR_PATH = join(homedir(), ".claude", "cache", "sv-accumulator.json");
|
|
45
|
+
// 이전 .omc/ 경로 fallback (기존 환경 호환)
|
|
46
|
+
export const LEGACY_GEMINI_QUOTA_CACHE = join(homedir(), ".omc", "state", "gemini_quota_cache.json");
|
|
47
|
+
export const LEGACY_GEMINI_PROJECT_CACHE = join(homedir(), ".omc", "state", "gemini_project_id.json");
|
|
48
|
+
export const LEGACY_GEMINI_SESSION_CACHE = join(homedir(), ".omc", "state", "gemini_session_tokens_cache.json");
|
|
49
|
+
export const LEGACY_GEMINI_RPM_TRACKER = join(homedir(), ".omc", "state", "gemini_rpm_tracker.json");
|
|
50
|
+
export const LEGACY_SV_ACCUMULATOR = join(homedir(), ".omc", "state", "sv-accumulator.json");
|
|
51
|
+
|
|
52
|
+
export const GEMINI_RPM_WINDOW_MS = 60 * 1000; // 60초 슬라이딩 윈도우
|
|
53
|
+
export const GEMINI_QUOTA_STALE_MS = 5 * 60 * 1000; // 5분
|
|
54
|
+
export const GEMINI_SESSION_STALE_MS = 15 * 1000; // 15초
|
|
55
|
+
export const GEMINI_API_TIMEOUT_MS = 3000; // 3초
|
|
56
|
+
|
|
57
|
+
export const ACCOUNT_LABEL_WIDTH = 10;
|
|
58
|
+
export const PROVIDER_PREFIX_WIDTH = 2;
|
|
59
|
+
export const PERCENT_CELL_WIDTH = 3;
|
|
60
|
+
export const TIME_CELL_INNER_WIDTH = 6;
|
|
61
|
+
export const SV_CELL_WIDTH = 5;
|
|
62
|
+
|
|
63
|
+
export const CLAUDE_REFRESH_FLAG = "--refresh-claude-usage";
|
|
64
|
+
export const CODEX_REFRESH_FLAG = "--refresh-codex-rate-limits";
|
|
65
|
+
export const GEMINI_REFRESH_FLAG = "--refresh-gemini-quota";
|
|
66
|
+
export const GEMINI_SESSION_REFRESH_FLAG = "--refresh-gemini-session";
|
|
67
|
+
|
|
68
|
+
// 모바일/Termux 컴팩트 모드 감지
|
|
69
|
+
export const HUD_CONFIG_PATH = join(homedir(), ".omc", "config", "hud.json");
|
|
70
|
+
export const COMPACT_COLS_THRESHOLD = 80;
|
|
71
|
+
export const MINIMAL_COLS_THRESHOLD = 60;
|
|
72
|
+
|
|
73
|
+
// rows 임계값 상수 (selectTier 에서 tier 결정에 사용)
|
|
74
|
+
export const ROWS_BUDGET_FULL = 40;
|
|
75
|
+
export const ROWS_BUDGET_LARGE = 35;
|
|
76
|
+
export const ROWS_BUDGET_MEDIUM = 28;
|
|
77
|
+
export const ROWS_BUDGET_SMALL = 22;
|
|
78
|
+
|
|
79
|
+
// Gemini Pro 풀 공유 그룹: 같은 remainingFraction을 공유하는 모델 ID들
|
|
80
|
+
export const GEMINI_PRO_POOL = new Set(["gemini-2.5-pro", "gemini-3-pro-preview", "gemini-3.1-pro-preview"]);
|
|
81
|
+
export const GEMINI_FLASH_POOL = new Set(["gemini-2.5-flash", "gemini-3-flash-preview"]);
|