triflux 8.5.1 → 8.7.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/bin/triflux.mjs +6 -4
- package/hub/team/ansi.mjs +134 -11
- package/hub/team/backend.mjs +1 -1
- package/hub/team/psmux.mjs +1 -1
- package/hub/team/tui.mjs +288 -53
- package/package.json +1 -1
- package/scripts/demo-tui.mjs +59 -0
- package/scripts/headless-guard.mjs +1 -1
- package/scripts/tfx-route-worker.mjs +6 -2
- package/skills/tfx-analysis/SKILL.md +101 -101
- package/skills/tfx-autopilot/SKILL.md +112 -112
- package/skills/tfx-autoroute/SKILL.md +184 -184
- package/skills/tfx-consensus/SKILL.md +114 -112
- package/skills/tfx-debate/SKILL.md +9 -9
- package/skills/tfx-deep-analysis/SKILL.md +191 -186
- package/skills/tfx-deep-plan/SKILL.md +20 -16
- package/skills/tfx-deep-qa/SKILL.md +14 -13
- package/skills/tfx-deep-research/SKILL.md +9 -9
- package/skills/tfx-deep-review/SKILL.md +96 -91
- package/skills/tfx-fullcycle/SKILL.md +18 -19
- package/skills/tfx-panel/SKILL.md +6 -6
- package/skills/tfx-qa/SKILL.md +117 -117
- package/skills/tfx-review/SKILL.md +51 -51
package/bin/triflux.mjs
CHANGED
|
@@ -735,7 +735,7 @@ function previewMcpRegistrationActions(mcpUrl) {
|
|
|
735
735
|
type: "mcp-register",
|
|
736
736
|
cli: "claude",
|
|
737
737
|
target: "tfx-hub",
|
|
738
|
-
path: join(
|
|
738
|
+
path: join(process.cwd(), ".claude", "mcp.json"),
|
|
739
739
|
url: mcpUrl,
|
|
740
740
|
change: "check",
|
|
741
741
|
});
|
|
@@ -2161,16 +2161,18 @@ function autoRegisterMcp(mcpUrl) {
|
|
|
2161
2161
|
info("Gemini: 미설치 (건너뜀)");
|
|
2162
2162
|
}
|
|
2163
2163
|
|
|
2164
|
-
// Claude —
|
|
2164
|
+
// Claude — .claude/mcp.json에 등록 (Claude Code 공식 경로)
|
|
2165
2165
|
try {
|
|
2166
|
-
const
|
|
2166
|
+
const claudeDir = join(process.cwd(), ".claude");
|
|
2167
|
+
if (!existsSync(claudeDir)) mkdirSync(claudeDir, { recursive: true });
|
|
2168
|
+
const mcpJsonPath = join(claudeDir, "mcp.json");
|
|
2167
2169
|
let mcpJson = {};
|
|
2168
2170
|
if (existsSync(mcpJsonPath)) mcpJson = JSON.parse(readFileSync(mcpJsonPath, "utf8"));
|
|
2169
2171
|
if (!mcpJson.mcpServers) mcpJson.mcpServers = {};
|
|
2170
2172
|
if (!mcpJson.mcpServers["tfx-hub"]) {
|
|
2171
2173
|
mcpJson.mcpServers["tfx-hub"] = { type: "url", url: mcpUrl };
|
|
2172
2174
|
writeFileSync(mcpJsonPath, JSON.stringify(mcpJson, null, 2) + "\n");
|
|
2173
|
-
ok("Claude: .mcp.json에 등록 완료");
|
|
2175
|
+
ok("Claude: .claude/mcp.json에 등록 완료");
|
|
2174
2176
|
} else {
|
|
2175
2177
|
ok("Claude: 이미 등록됨");
|
|
2176
2178
|
}
|
package/hub/team/ansi.mjs
CHANGED
|
@@ -65,16 +65,92 @@ export function color(text, fg, bg) {
|
|
|
65
65
|
export function bold(text) { return `${BOLD}${text}${RESET}`; }
|
|
66
66
|
export function dim(text) { return `${DIM}${text}${RESET}`; }
|
|
67
67
|
|
|
68
|
+
export function lerpRgb(a, b, t) {
|
|
69
|
+
return {
|
|
70
|
+
r: Math.round(a.r + (b.r - a.r) * t),
|
|
71
|
+
g: Math.round(a.g + (b.g - a.g) * t),
|
|
72
|
+
b: Math.round(a.b + (b.b - a.b) * t),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function rgbSeq(rgb, mode = 38) {
|
|
77
|
+
return `${ESC}[${mode};2;${rgb.r};${rgb.g};${rgb.b}m`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function brightenRgb(rgb, amount = 0.3) {
|
|
81
|
+
return lerpRgb(rgb, { r: 255, g: 255, b: 255 }, amount);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function parseRgbSeq(seq) {
|
|
85
|
+
const match = typeof seq === "string"
|
|
86
|
+
? seq.match(/\x1b\[(?:38|48);2;(\d+);(\d+);(\d+)m/)
|
|
87
|
+
: null;
|
|
88
|
+
if (!match) return null;
|
|
89
|
+
return {
|
|
90
|
+
r: Number.parseInt(match[1], 10),
|
|
91
|
+
g: Number.parseInt(match[2], 10),
|
|
92
|
+
b: Number.parseInt(match[3], 10),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function reapplyBackground(text, bgSeq) {
|
|
97
|
+
if (!bgSeq) return text;
|
|
98
|
+
return `${bgSeq}${String(text).replaceAll(RESET, `${RESET}${bgSeq}`)}${RESET}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
// ── 박스 그리기 (유니코드 테두리) ──
|
|
69
102
|
const BOX = { tl: "┌", tr: "┐", bl: "└", br: "┘", h: "─", v: "│", ml: "├", mr: "┤" };
|
|
70
103
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
function borderHighlightCell(width, totalRows, highlightPos) {
|
|
105
|
+
if (!Number.isFinite(highlightPos)) return null;
|
|
106
|
+
const perimeter = 2 * (width - 2) + 2 * totalRows;
|
|
107
|
+
if (perimeter <= 0) return null;
|
|
108
|
+
let pos = Math.floor(highlightPos) % perimeter;
|
|
109
|
+
if (pos < 0) pos += perimeter;
|
|
110
|
+
|
|
111
|
+
if (pos < width - 2) return { row: 0, col: pos + 1 };
|
|
112
|
+
pos -= width - 2;
|
|
113
|
+
if (pos < totalRows) return { row: pos, col: width - 1 };
|
|
114
|
+
pos -= totalRows;
|
|
115
|
+
if (pos < width - 2) return { row: totalRows - 1, col: width - 2 - pos };
|
|
116
|
+
pos -= width - 2;
|
|
117
|
+
return { row: totalRows - 1 - pos, col: 0 };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function renderBorderChar(glyph, row, col, highlightCell, borderSeq, highlightSeq) {
|
|
121
|
+
if (highlightCell && highlightCell.row === row && highlightCell.col === col) {
|
|
122
|
+
return `${highlightSeq}${glyph}${RESET}`;
|
|
123
|
+
}
|
|
124
|
+
return borderSeq ? `${borderSeq}${glyph}${RESET}` : glyph;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function box(lines, width, borderColor = "", options = {}) {
|
|
128
|
+
const isFn = typeof borderColor === "function";
|
|
129
|
+
const totalRows = lines.length + 2;
|
|
130
|
+
const bc = isFn ? (row) => borderColor(row, totalRows) : () => borderColor;
|
|
131
|
+
const rst = (isFn || borderColor) ? RESET : "";
|
|
132
|
+
const highlightCell = borderHighlightCell(width, totalRows, options.highlightPos);
|
|
133
|
+
const highlightSeq = options.highlightColor
|
|
134
|
+
|| (() => {
|
|
135
|
+
const parsed = parseRgbSeq(typeof borderColor === "string" ? borderColor : "");
|
|
136
|
+
return parsed ? rgbSeq(brightenRgb(parsed, 0.45)) : `${BOLD}${FG.white}`;
|
|
137
|
+
})();
|
|
138
|
+
const topChars = [BOX.tl, ...Array.from({ length: width - 2 }, () => BOX.h), BOX.tr];
|
|
139
|
+
const botChars = [BOX.bl, ...Array.from({ length: width - 2 }, () => BOX.h), BOX.br];
|
|
140
|
+
const top = topChars
|
|
141
|
+
.map((glyph, col) => renderBorderChar(glyph, 0, col, highlightCell, bc(0), highlightSeq))
|
|
142
|
+
.join("");
|
|
143
|
+
const bot = botChars
|
|
144
|
+
.map((glyph, col) => renderBorderChar(glyph, totalRows - 1, col, highlightCell, bc(totalRows - 1), highlightSeq))
|
|
145
|
+
.join("");
|
|
146
|
+
const mid = `${bc(Math.floor(totalRows / 2))}${BOX.ml}${BOX.h.repeat(width - 2)}${BOX.mr}${rst}`;
|
|
147
|
+
const body = lines.map((l, i) => {
|
|
148
|
+
const row = i + 1;
|
|
149
|
+
const content = options.titleFlashBg && i === 0
|
|
150
|
+
? reapplyBackground(padRight(l, width - 4), options.titleFlashBg)
|
|
151
|
+
: padRight(l, width - 4);
|
|
152
|
+
return `${renderBorderChar(BOX.v, row, 0, highlightCell, bc(row), highlightSeq)} ${content} ${renderBorderChar(BOX.v, row, width - 1, highlightCell, bc(row), highlightSeq)}`;
|
|
153
|
+
});
|
|
78
154
|
return { top, body, bot, mid };
|
|
79
155
|
}
|
|
80
156
|
|
|
@@ -205,6 +281,12 @@ export const MOCHA = {
|
|
|
205
281
|
surface0: `${ESC}[38;2;49;50;68m`, // #313244 surface0
|
|
206
282
|
};
|
|
207
283
|
|
|
284
|
+
const MOCHA_RGB = {
|
|
285
|
+
ok: { r: 166, g: 227, b: 161 },
|
|
286
|
+
partial: { r: 250, g: 179, b: 135 },
|
|
287
|
+
fail: { r: 243, g: 139, b: 168 },
|
|
288
|
+
};
|
|
289
|
+
|
|
208
290
|
// ── badge 헬퍼 ──
|
|
209
291
|
// statusBadge(status) → ANSI 색상 문자열
|
|
210
292
|
export function statusBadge(status) {
|
|
@@ -231,13 +313,48 @@ export function statusBadge(status) {
|
|
|
231
313
|
}
|
|
232
314
|
|
|
233
315
|
// ── 진행률 바 ──
|
|
234
|
-
// progressBar(percent, width) — percent: 0~100,
|
|
235
|
-
export function progressBar(percent, width = 20) {
|
|
316
|
+
// progressBar(percent, width, time) — percent: 0~100, time 전달 시 shimmer sweep
|
|
317
|
+
export function progressBar(percent, width = 20, time) {
|
|
318
|
+
const ratio = Math.max(0, Math.min(100, percent)) / 100;
|
|
319
|
+
const filled = Math.round(ratio * width);
|
|
320
|
+
const empty = width - filled;
|
|
321
|
+
const fillRgb = percent >= 100 ? MOCHA_RGB.ok : percent >= 50 ? MOCHA_RGB.partial : MOCHA_RGB.fail;
|
|
322
|
+
const fillColor = rgbSeq(fillRgb);
|
|
323
|
+
let fillText = "█".repeat(filled);
|
|
324
|
+
|
|
325
|
+
if (filled > 0 && Number.isFinite(time)) {
|
|
326
|
+
const shinePos = Math.min(
|
|
327
|
+
filled - 1,
|
|
328
|
+
Math.floor(((((time % 2000) + 2000) % 2000) / 2000) * filled),
|
|
329
|
+
);
|
|
330
|
+
const shineColor = rgbSeq(brightenRgb(fillRgb, 0.3));
|
|
331
|
+
fillText = Array.from({ length: filled }, (_, idx) =>
|
|
332
|
+
idx === shinePos ? `${shineColor}█${RESET}` : `${fillColor}█${RESET}`
|
|
333
|
+
).join("");
|
|
334
|
+
} else if (filled > 0) {
|
|
335
|
+
fillText = `${fillColor}${fillText}${RESET}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const emptyText = empty > 0 ? `${MOCHA.border}${"░".repeat(empty)}${RESET}` : "";
|
|
339
|
+
return `${fillText}${emptyText}`;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ── 애니메이션 진행률 바 (shimmer sweep) ──
|
|
343
|
+
export function animatedProgressBar(percent, width = 20, tick = 0) {
|
|
236
344
|
const ratio = Math.max(0, Math.min(100, percent)) / 100;
|
|
237
345
|
const filled = Math.round(ratio * width);
|
|
238
346
|
const empty = width - filled;
|
|
239
|
-
|
|
240
|
-
|
|
347
|
+
if (filled === 0 || percent >= 100) return progressBar(percent, width);
|
|
348
|
+
const baseClr = percent >= 50 ? MOCHA.partial : MOCHA.fail;
|
|
349
|
+
const pos = tick % (filled + 3);
|
|
350
|
+
let bar = "";
|
|
351
|
+
for (let i = 0; i < filled; i++) {
|
|
352
|
+
const d = Math.abs(i - pos);
|
|
353
|
+
if (d === 0) bar += `${ESC}[97m█`;
|
|
354
|
+
else if (d === 1) bar += `${baseClr}▓`;
|
|
355
|
+
else bar += `${baseClr}█`;
|
|
356
|
+
}
|
|
357
|
+
return `${bar}${MOCHA.border}${"░".repeat(empty)}${RESET}`;
|
|
241
358
|
}
|
|
242
359
|
|
|
243
360
|
// ── 상태 아이콘 ──
|
|
@@ -253,3 +370,9 @@ export const CLI_ICON = {
|
|
|
253
370
|
gemini: `${FG.gemini}🔵${RESET}`,
|
|
254
371
|
claude: `${FG.claude}🟠${RESET}`,
|
|
255
372
|
};
|
|
373
|
+
|
|
374
|
+
// ── 로딩 도트 (braille spinner) ──
|
|
375
|
+
const BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
376
|
+
export function loadingDots(tick = 0, clr = MOCHA.thinking) {
|
|
377
|
+
return `${clr}${BRAILLE_FRAMES[tick % BRAILLE_FRAMES.length]}${RESET}`;
|
|
378
|
+
}
|
package/hub/team/backend.mjs
CHANGED
|
@@ -32,7 +32,7 @@ export class GeminiBackend {
|
|
|
32
32
|
command() { return "gemini"; }
|
|
33
33
|
|
|
34
34
|
buildArgs(prompt, resultFile, opts = {}) {
|
|
35
|
-
return `gemini
|
|
35
|
+
return `gemini --prompt ${prompt} --output-format text > '${resultFile}' 2>'${resultFile}.err'`;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
env() { return {}; }
|
package/hub/team/psmux.mjs
CHANGED
|
@@ -758,7 +758,7 @@ export function startCapture(sessionName, paneNameOrTarget) {
|
|
|
758
758
|
* CLI 명령(codex/gemini)이 psmux pane의 PowerShell 환경에서 단축 플래그 충돌을
|
|
759
759
|
* 일으키는 문제를 방지하기 위해 bash -c '...' 로 감싼다.
|
|
760
760
|
* - codex -o flag → PS -OutVariable/OutBuffer 충돌
|
|
761
|
-
* - gemini -p
|
|
761
|
+
* - gemini --prompt flag (v8.6.0: -p → --prompt, PS 충돌 해소)
|
|
762
762
|
* @param {string} cmd
|
|
763
763
|
* @returns {string}
|
|
764
764
|
*/
|