triflux 10.9.21 → 10.9.23
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/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/config/mcp-registry.json +29 -0
- package/hub/account-broker.mjs +6 -4
- package/hub/cli-adapter-base.mjs +14 -14
- package/hub/lib/env-detect.mjs +47 -20
- package/hub/server.mjs +17 -15
- package/hub/team/headless.mjs +10 -0
- package/hub/team/swarm-hypervisor.mjs +2 -2
- package/hub/workers/delegator-mcp.mjs +129 -1
- package/hud/constants.mjs +24 -13
- package/hud/renderers.mjs +2 -1
- package/package.json +62 -21
- package/scripts/__tests__/keyword-detector.test.mjs +4 -4
- package/scripts/__tests__/release-governance.test.mjs +148 -0
- package/scripts/doctor-diagnose.mjs +6 -7
- package/scripts/lib/cross-review-utils.mjs +2 -2
- package/scripts/lib/mcp-filter.mjs +12 -24
- package/scripts/release/bump-version.mjs +77 -0
- package/scripts/release/check-sync.mjs +51 -0
- package/scripts/release/lib.mjs +303 -0
- package/scripts/release/prepare.mjs +85 -0
- package/scripts/release/publish.mjs +87 -0
- package/scripts/release/verify.mjs +81 -0
- package/scripts/release/version-manifest.json +26 -0
- package/scripts/remote-spawn.mjs +3 -3
- package/scripts/setup.mjs +18 -15
- package/scripts/tfx-route.sh +64 -8
- package/tui/codex-profile.mjs +457 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +339 -0
- package/tui/setup.mjs +598 -0
- package/CLAUDE.md +0 -212
- package/references/hosts.json +0 -46
- package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
- package/skills/tfx-workspace/evals/evals.json +0 -79
- package/skills/tfx-workspace/iteration-1/benchmark.json +0 -524
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
- package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -25
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
- package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -20
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
- package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
- package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
- package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/review.html +0 -1325
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
- package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -30
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
- package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/benchmark.json +0 -144
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -35
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
- package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
- package/skills/tfx-workspace/iteration-2/review.html +0 -1325
- package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
- package/skills/tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md +0 -77
- package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
- package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
- package/skills/tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md +0 -82
- package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
- package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
- package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
package/tui/setup.mjs
ADDED
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tui/setup.mjs — Interactive triflux setup wizard TUI
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { DEFAULT_GEMINI_PROFILES } from "../scripts/lib/gemini-profiles.mjs";
|
|
9
|
+
import {
|
|
10
|
+
AMBER,
|
|
11
|
+
BOLD,
|
|
12
|
+
box,
|
|
13
|
+
CYAN,
|
|
14
|
+
clear,
|
|
15
|
+
confirm,
|
|
16
|
+
DIM,
|
|
17
|
+
divider,
|
|
18
|
+
fail,
|
|
19
|
+
GRAY,
|
|
20
|
+
GREEN,
|
|
21
|
+
info,
|
|
22
|
+
ok,
|
|
23
|
+
onExit,
|
|
24
|
+
RESET,
|
|
25
|
+
select,
|
|
26
|
+
showCursor,
|
|
27
|
+
spinner,
|
|
28
|
+
table,
|
|
29
|
+
warn,
|
|
30
|
+
YELLOW,
|
|
31
|
+
} from "./core.mjs";
|
|
32
|
+
|
|
33
|
+
const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
34
|
+
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
35
|
+
const CODEX_DIR = join(homedir(), ".codex");
|
|
36
|
+
const GEMINI_DIR = join(homedir(), ".gemini");
|
|
37
|
+
const SETTINGS_PATH = join(CLAUDE_DIR, "settings.json");
|
|
38
|
+
|
|
39
|
+
// ── Step Definitions ──
|
|
40
|
+
|
|
41
|
+
const STEPS = [
|
|
42
|
+
{
|
|
43
|
+
id: "sync",
|
|
44
|
+
name: "파일 동기화",
|
|
45
|
+
desc: "스크립트/HUD/스킬을 ~/.claude/에 배포",
|
|
46
|
+
},
|
|
47
|
+
{ id: "hud", name: "HUD 설정", desc: "settings.json에 statusLine 등록" },
|
|
48
|
+
{ id: "profiles", name: "Codex 프로파일", desc: "필수 프로파일 생성/확인" },
|
|
49
|
+
{
|
|
50
|
+
id: "gemini-profiles",
|
|
51
|
+
name: "Gemini 프로필",
|
|
52
|
+
desc: "triflux-profiles.json 생성/확인",
|
|
53
|
+
},
|
|
54
|
+
{ id: "cli", name: "CLI 진단", desc: "Codex/Gemini/Claude CLI 확인" },
|
|
55
|
+
{ id: "mcp", name: "MCP 서버 확인", desc: "MCP 서버 인벤토리 점검" },
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
// ── Step Implementations ──
|
|
59
|
+
|
|
60
|
+
function stepSync() {
|
|
61
|
+
try {
|
|
62
|
+
const _out = execFileSync(
|
|
63
|
+
process.execPath,
|
|
64
|
+
[join(PKG_ROOT, "bin", "triflux.mjs"), "setup", "--json"],
|
|
65
|
+
{ timeout: 30000, encoding: "utf8", windowsHide: true },
|
|
66
|
+
);
|
|
67
|
+
return { ok: true, detail: "파일 동기화 완료" };
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return { ok: false, detail: e.message };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stepHud() {
|
|
74
|
+
try {
|
|
75
|
+
if (!existsSync(SETTINGS_PATH)) {
|
|
76
|
+
return { ok: false, detail: "settings.json 미존재", action: "create" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const raw = readFileSync(SETTINGS_PATH, "utf8");
|
|
80
|
+
let settings;
|
|
81
|
+
try {
|
|
82
|
+
settings = JSON.parse(raw);
|
|
83
|
+
} catch {
|
|
84
|
+
return { ok: false, detail: "settings.json 파싱 실패", action: "fix" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const hudScript = join(CLAUDE_DIR, "hud", "hud-qos-status.mjs");
|
|
88
|
+
const nodePath = process.execPath;
|
|
89
|
+
|
|
90
|
+
// Check if statusLine already configured correctly
|
|
91
|
+
if (settings.statusLine?.command?.includes("hud-qos-status")) {
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
detail: "statusLine 이미 설정됨",
|
|
95
|
+
current: settings.statusLine,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
ok: false,
|
|
101
|
+
detail: settings.statusLine
|
|
102
|
+
? "statusLine이 다른 HUD를 가리킴"
|
|
103
|
+
: "statusLine 미설정",
|
|
104
|
+
action: "configure",
|
|
105
|
+
current: settings.statusLine || null,
|
|
106
|
+
target: {
|
|
107
|
+
type: "command",
|
|
108
|
+
command: `"${nodePath}" "${hudScript}"`,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return { ok: false, detail: e.message };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function stepProfiles() {
|
|
117
|
+
const configPath = join(CODEX_DIR, "config.toml");
|
|
118
|
+
if (!existsSync(configPath)) {
|
|
119
|
+
return { ok: false, detail: "config.toml 미존재", action: "skip" };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const content = readFileSync(configPath, "utf8");
|
|
123
|
+
const required = ["codex53_high", "codex53_xhigh", "spark53_low"];
|
|
124
|
+
const missing = required.filter((name) => {
|
|
125
|
+
const re = new RegExp(`^\\[profiles\\.${name}\\]`, "m");
|
|
126
|
+
return !re.test(content);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (missing.length === 0) {
|
|
130
|
+
return { ok: true, detail: `필수 프로파일 ${required.length}개 확인됨` };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
detail: `누락: ${missing.join(", ")}`,
|
|
136
|
+
missing,
|
|
137
|
+
action: "create",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function stepGeminiProfiles() {
|
|
142
|
+
const configPath = join(GEMINI_DIR, "triflux-profiles.json");
|
|
143
|
+
if (!existsSync(configPath)) {
|
|
144
|
+
if (!existsSync(GEMINI_DIR)) mkdirSync(GEMINI_DIR, { recursive: true });
|
|
145
|
+
writeFileSync(
|
|
146
|
+
configPath,
|
|
147
|
+
JSON.stringify(DEFAULT_GEMINI_PROFILES, null, 2) + "\n",
|
|
148
|
+
"utf8",
|
|
149
|
+
);
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
detail: `기본 프로필 ${Object.keys(DEFAULT_GEMINI_PROFILES.profiles).length}개 자동 생성됨`,
|
|
153
|
+
action: "created",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
const cfg = JSON.parse(readFileSync(configPath, "utf8"));
|
|
159
|
+
const required = ["pro31", "flash3"];
|
|
160
|
+
const missing = required.filter((name) => !cfg.profiles?.[name]);
|
|
161
|
+
if (missing.length === 0) {
|
|
162
|
+
const count = Object.keys(cfg.profiles || {}).length;
|
|
163
|
+
return { ok: true, detail: `프로필 ${count}개 확인됨` };
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
detail: `누락: ${missing.join(", ")}`,
|
|
168
|
+
action: "update",
|
|
169
|
+
};
|
|
170
|
+
} catch {
|
|
171
|
+
return {
|
|
172
|
+
ok: false,
|
|
173
|
+
detail: "triflux-profiles.json 파싱 실패",
|
|
174
|
+
action: "recreate",
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function stepCli() {
|
|
180
|
+
const results = [];
|
|
181
|
+
for (const [name, installCmd] of [
|
|
182
|
+
["codex", "npm i -g @openai/codex"],
|
|
183
|
+
["gemini", "npm i -g @google/gemini-cli"],
|
|
184
|
+
["claude", "Claude Code 설치"],
|
|
185
|
+
]) {
|
|
186
|
+
try {
|
|
187
|
+
execFileSync(process.platform === "win32" ? "where" : "which", [name], {
|
|
188
|
+
timeout: 5000,
|
|
189
|
+
encoding: "utf8",
|
|
190
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
191
|
+
});
|
|
192
|
+
results.push({ name, found: true });
|
|
193
|
+
} catch {
|
|
194
|
+
results.push({ name, found: false, install: installCmd });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
const allFound = results.every((r) => r.found);
|
|
198
|
+
return {
|
|
199
|
+
ok: allFound,
|
|
200
|
+
results,
|
|
201
|
+
detail: allFound ? "모든 CLI 확인됨" : "일부 CLI 미설치",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function stepMcp() {
|
|
206
|
+
const inventoryPath = join(CLAUDE_DIR, "cache", "mcp-inventory.json");
|
|
207
|
+
if (!existsSync(inventoryPath)) {
|
|
208
|
+
return { ok: false, detail: "MCP 인벤토리 미존재", action: "rebuild" };
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
const inventory = JSON.parse(readFileSync(inventoryPath, "utf8"));
|
|
212
|
+
const count = Object.keys(inventory.servers || inventory).length;
|
|
213
|
+
return { ok: true, detail: `${count}개 MCP 서버 등록됨` };
|
|
214
|
+
} catch {
|
|
215
|
+
return { ok: false, detail: "MCP 인벤토리 파싱 실패", action: "rebuild" };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const STEP_RUNNERS = {
|
|
220
|
+
sync: stepSync,
|
|
221
|
+
hud: stepHud,
|
|
222
|
+
profiles: stepProfiles,
|
|
223
|
+
"gemini-profiles": stepGeminiProfiles,
|
|
224
|
+
cli: stepCli,
|
|
225
|
+
mcp: stepMcp,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// ── UI ──
|
|
229
|
+
|
|
230
|
+
function showStepResult(step, result) {
|
|
231
|
+
if (result.ok) {
|
|
232
|
+
ok(`${step.name}: ${result.detail}`);
|
|
233
|
+
} else {
|
|
234
|
+
warn(`${step.name}: ${result.detail}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function runWizard() {
|
|
239
|
+
console.log();
|
|
240
|
+
info(`${STEPS.length}개 단계를 순서대로 실행합니다.`);
|
|
241
|
+
console.log();
|
|
242
|
+
|
|
243
|
+
const results = {};
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < STEPS.length; i++) {
|
|
246
|
+
const step = STEPS[i];
|
|
247
|
+
const progress = `${DIM}[${i + 1}/${STEPS.length}]${RESET}`;
|
|
248
|
+
|
|
249
|
+
console.log(
|
|
250
|
+
` ${progress} ${BOLD}${step.name}${RESET} ${DIM}— ${step.desc}${RESET}`,
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const spin = spinner(`${step.name} 실행 중...`);
|
|
254
|
+
const result = STEP_RUNNERS[step.id]();
|
|
255
|
+
spin.stop();
|
|
256
|
+
|
|
257
|
+
showStepResult(step, result);
|
|
258
|
+
results[step.id] = result;
|
|
259
|
+
|
|
260
|
+
// Handle fixable issues
|
|
261
|
+
if (!result.ok && result.action) {
|
|
262
|
+
await handleStepFix(step, result);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
console.log();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return results;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function handleStepFix(step, result) {
|
|
272
|
+
switch (step.id) {
|
|
273
|
+
case "hud": {
|
|
274
|
+
if (result.action === "configure") {
|
|
275
|
+
if (result.current) {
|
|
276
|
+
warn(`현재 statusLine: ${JSON.stringify(result.current)}`);
|
|
277
|
+
if (!(await confirm("triflux HUD로 덮어쓰시겠습니까?", false)))
|
|
278
|
+
return;
|
|
279
|
+
} else {
|
|
280
|
+
if (!(await confirm("statusLine을 설정하시겠습니까?"))) return;
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
const raw = readFileSync(SETTINGS_PATH, "utf8");
|
|
284
|
+
const settings = JSON.parse(raw);
|
|
285
|
+
settings.statusLine = result.target;
|
|
286
|
+
writeFileSync(
|
|
287
|
+
SETTINGS_PATH,
|
|
288
|
+
JSON.stringify(settings, null, 2),
|
|
289
|
+
"utf8",
|
|
290
|
+
);
|
|
291
|
+
ok("statusLine 설정 완료");
|
|
292
|
+
} catch (e) {
|
|
293
|
+
fail(`설정 실패: ${e.message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case "profiles": {
|
|
300
|
+
if (result.action === "create" && result.missing) {
|
|
301
|
+
if (
|
|
302
|
+
await confirm(
|
|
303
|
+
`누락된 프로파일 ${result.missing.length}개를 생성하시겠습니까?`,
|
|
304
|
+
)
|
|
305
|
+
) {
|
|
306
|
+
try {
|
|
307
|
+
execFileSync(
|
|
308
|
+
process.execPath,
|
|
309
|
+
[join(PKG_ROOT, "bin", "triflux.mjs"), "setup"],
|
|
310
|
+
{ timeout: 15000, stdio: "ignore", windowsHide: true },
|
|
311
|
+
);
|
|
312
|
+
ok("프로파일 생성 완료");
|
|
313
|
+
} catch {
|
|
314
|
+
warn("프로파일 생성 실패 — triflux setup으로 재시도");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case "mcp": {
|
|
322
|
+
if (result.action === "rebuild") {
|
|
323
|
+
if (await confirm("MCP 인벤토리를 재생성하시겠습니까?")) {
|
|
324
|
+
const mcpCheck = join(PKG_ROOT, "scripts", "mcp-check.mjs");
|
|
325
|
+
if (existsSync(mcpCheck)) {
|
|
326
|
+
try {
|
|
327
|
+
execFileSync(process.execPath, [mcpCheck], {
|
|
328
|
+
timeout: 15000,
|
|
329
|
+
stdio: "ignore",
|
|
330
|
+
windowsHide: true,
|
|
331
|
+
});
|
|
332
|
+
ok("MCP 인벤토리 재생성 완료");
|
|
333
|
+
} catch {
|
|
334
|
+
warn("MCP 인벤토리 재생성 실패 — 다음 세션에서 자동 재시도");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function runSelective() {
|
|
345
|
+
const options = STEPS.map((s) => ({
|
|
346
|
+
label: s.name,
|
|
347
|
+
hint: s.desc,
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
const picked = await select("실행할 단계", options);
|
|
351
|
+
if (!picked) return;
|
|
352
|
+
|
|
353
|
+
const step = STEPS[picked.index];
|
|
354
|
+
console.log();
|
|
355
|
+
const spin = spinner(`${step.name} 실행 중...`);
|
|
356
|
+
const result = STEP_RUNNERS[step.id]();
|
|
357
|
+
spin.stop();
|
|
358
|
+
|
|
359
|
+
showStepResult(step, result);
|
|
360
|
+
|
|
361
|
+
if (!result.ok && result.action) {
|
|
362
|
+
await handleStepFix(step, result);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// CLI step has extra detail
|
|
366
|
+
if (step.id === "cli" && result.results) {
|
|
367
|
+
console.log();
|
|
368
|
+
for (const r of result.results) {
|
|
369
|
+
if (r.found) ok(`${r.name}: 설치됨`);
|
|
370
|
+
else warn(`${r.name}: 미설치 → ${DIM}${r.install}${RESET}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function showSummary(results) {
|
|
376
|
+
console.log();
|
|
377
|
+
divider(46);
|
|
378
|
+
box("Setup 완료", 46);
|
|
379
|
+
console.log();
|
|
380
|
+
|
|
381
|
+
const headers = ["항목", "상태"];
|
|
382
|
+
const rows = STEPS.map((s) => {
|
|
383
|
+
const r = results[s.id];
|
|
384
|
+
if (!r) return [s.name, `${GRAY}건너뜀${RESET}`];
|
|
385
|
+
return [
|
|
386
|
+
s.name,
|
|
387
|
+
r.ok ? `${GREEN}✓ 정상${RESET}` : `${YELLOW}⚠ ${r.detail}${RESET}`,
|
|
388
|
+
];
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
table(headers, rows);
|
|
392
|
+
|
|
393
|
+
const issues = Object.values(results).filter((r) => r && !r.ok).length;
|
|
394
|
+
console.log();
|
|
395
|
+
if (issues === 0) {
|
|
396
|
+
ok(`${BOLD}모든 항목 정상${RESET} — 세션을 재시작하면 적용됩니다`);
|
|
397
|
+
} else {
|
|
398
|
+
warn(`${issues}개 항목에 주의가 필요합니다`);
|
|
399
|
+
info("/tfx-doctor --fix 로 자동 수정을 시도하세요");
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// ── Star Request ──
|
|
404
|
+
|
|
405
|
+
const STAR_OWNER = "tellang";
|
|
406
|
+
const STAR_REPO = "triflux";
|
|
407
|
+
const STAR_URL = `https://github.com/${STAR_OWNER}/${STAR_REPO}`;
|
|
408
|
+
const STAR_MARKER_DIR = join(homedir(), ".config", "star-prompt");
|
|
409
|
+
const STAR_MARKER = join(STAR_MARKER_DIR, `${STAR_OWNER}-${STAR_REPO}.prompted`);
|
|
410
|
+
|
|
411
|
+
function markStarPrompted() {
|
|
412
|
+
mkdirSync(STAR_MARKER_DIR, { recursive: true });
|
|
413
|
+
writeFileSync(STAR_MARKER, new Date().toISOString(), "utf8");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function runGh(args) {
|
|
417
|
+
return execFileSync("gh", args, {
|
|
418
|
+
timeout: 10000,
|
|
419
|
+
encoding: "utf8",
|
|
420
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function getGhHttpStatus(error) {
|
|
425
|
+
const out = [error?.stdout, error?.stderr].filter(Boolean).join("\n");
|
|
426
|
+
const match = out.match(/HTTP\s+(\d{3})/i) || out.match(/\b(\d{3})\b/);
|
|
427
|
+
return match ? Number(match[1]) : null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function starRequest({ soft = false } = {}) {
|
|
431
|
+
const interactive =
|
|
432
|
+
process.stdout.isTTY && !process.env.CI && process.env.TERM !== "dumb";
|
|
433
|
+
const useSoft = soft || !interactive;
|
|
434
|
+
|
|
435
|
+
// 이미 프롬프트 본 유저 스킵
|
|
436
|
+
if (existsSync(STAR_MARKER)) return;
|
|
437
|
+
|
|
438
|
+
// gh 설치 확인
|
|
439
|
+
try {
|
|
440
|
+
runGh(["--version"]);
|
|
441
|
+
} catch {
|
|
442
|
+
console.log();
|
|
443
|
+
info(`${AMBER}⭐${RESET} ${CYAN}${STAR_URL}${RESET}`);
|
|
444
|
+
console.log();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// gh 인증 확인
|
|
449
|
+
try {
|
|
450
|
+
runGh(["auth", "status"]);
|
|
451
|
+
} catch {
|
|
452
|
+
console.log();
|
|
453
|
+
info(`${AMBER}⭐${RESET} ${CYAN}${STAR_URL}${RESET}`);
|
|
454
|
+
console.log();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// 스타 여부 체크
|
|
459
|
+
let alreadyStarred = false;
|
|
460
|
+
try {
|
|
461
|
+
runGh(["api", `user/starred/${STAR_OWNER}/${STAR_REPO}`]);
|
|
462
|
+
alreadyStarred = true;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
const status = getGhHttpStatus(error);
|
|
465
|
+
if (status === 404) {
|
|
466
|
+
alreadyStarred = false;
|
|
467
|
+
} else {
|
|
468
|
+
// API 에러 (404 외): 프롬프트 없이 URL만 출력, 마커 미기록
|
|
469
|
+
console.log();
|
|
470
|
+
info(`${CYAN}${STAR_URL}${RESET}`);
|
|
471
|
+
console.log();
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
console.log();
|
|
477
|
+
|
|
478
|
+
if (alreadyStarred) {
|
|
479
|
+
ok(`이미 함께하고 계시군요. ${AMBER}⭐${RESET}`);
|
|
480
|
+
markStarPrompted();
|
|
481
|
+
console.log();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let accepted = false;
|
|
486
|
+
if (useSoft) {
|
|
487
|
+
accepted = await confirm(
|
|
488
|
+
`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다.`,
|
|
489
|
+
true,
|
|
490
|
+
);
|
|
491
|
+
} else {
|
|
492
|
+
const answer = await select(
|
|
493
|
+
`${AMBER}⭐${RESET} 이 프로젝트가 마음에 드셨나요?`,
|
|
494
|
+
[
|
|
495
|
+
{ label: "예, 누를게요", hint: "GitHub Star" },
|
|
496
|
+
{ label: "아니오", hint: "" },
|
|
497
|
+
],
|
|
498
|
+
);
|
|
499
|
+
accepted = answer?.index === 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (!accepted) {
|
|
503
|
+
if (useSoft) {
|
|
504
|
+
info(`${CYAN}${STAR_URL}${RESET}`);
|
|
505
|
+
} else {
|
|
506
|
+
info(`괜찮습니다. 나중에 마음이 바뀌시면: ${CYAN}${STAR_URL}${RESET}`);
|
|
507
|
+
}
|
|
508
|
+
markStarPrompted();
|
|
509
|
+
console.log();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
runGh(["api", "-X", "PUT", `/user/starred/${STAR_OWNER}/${STAR_REPO}`]);
|
|
515
|
+
if (useSoft) {
|
|
516
|
+
ok(`함께해 주셔서 감사합니다. ${AMBER}⭐${RESET}`);
|
|
517
|
+
} else {
|
|
518
|
+
ok(
|
|
519
|
+
`감사합니다! 여러분의 ${AMBER}⭐${RESET}가 프로젝트를 성장시킵니다.`,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
} catch {
|
|
523
|
+
info(`${CYAN}${STAR_URL}${RESET}`);
|
|
524
|
+
}
|
|
525
|
+
markStarPrompted();
|
|
526
|
+
console.log();
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── Main Menu ──
|
|
530
|
+
|
|
531
|
+
const MENU = [
|
|
532
|
+
{ label: "전체 설정 (Full Setup)", hint: "6단계 순서 실행" },
|
|
533
|
+
{ label: "단계별 선택 (Selective)", hint: "특정 단계만 실행" },
|
|
534
|
+
{ label: "현재 상태 확인 (Status)", hint: "설정 없이 진단만" },
|
|
535
|
+
{ label: "종료", hint: "Ctrl+C" },
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
async function main() {
|
|
539
|
+
onExit(() => {});
|
|
540
|
+
clear();
|
|
541
|
+
|
|
542
|
+
while (true) {
|
|
543
|
+
box("triflux Setup Wizard", 46);
|
|
544
|
+
console.log();
|
|
545
|
+
|
|
546
|
+
const choice = await select("작업 선택", MENU);
|
|
547
|
+
if (!choice || choice.index === 3) {
|
|
548
|
+
console.log();
|
|
549
|
+
info("종료합니다.");
|
|
550
|
+
showCursor();
|
|
551
|
+
break;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
console.log();
|
|
555
|
+
|
|
556
|
+
switch (choice.index) {
|
|
557
|
+
case 0: {
|
|
558
|
+
const results = await runWizard();
|
|
559
|
+
showSummary(results);
|
|
560
|
+
await starRequest();
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
case 1: {
|
|
565
|
+
await runSelective();
|
|
566
|
+
break;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
case 2: {
|
|
570
|
+
info("현재 상태를 확인합니다...");
|
|
571
|
+
console.log();
|
|
572
|
+
const results = {};
|
|
573
|
+
for (const step of STEPS) {
|
|
574
|
+
if (step.id === "sync") continue; // sync는 상태 확인 불가
|
|
575
|
+
const result = STEP_RUNNERS[step.id]();
|
|
576
|
+
showStepResult(step, result);
|
|
577
|
+
results[step.id] = result;
|
|
578
|
+
if (step.id === "cli" && result.results) {
|
|
579
|
+
for (const r of result.results) {
|
|
580
|
+
if (r.found) ok(` ${r.name}: 설치됨`);
|
|
581
|
+
else warn(` ${r.name}: 미설치`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
console.log();
|
|
590
|
+
divider(46);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
main().catch((e) => {
|
|
595
|
+
showCursor();
|
|
596
|
+
console.error(e);
|
|
597
|
+
process.exit(1);
|
|
598
|
+
});
|