triflux 10.58.0 → 10.59.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 +496 -396
- package/package.json +1 -1
- package/scripts/lib/cli-output.mjs +228 -0
- package/scripts/lib/legacy-mcp-cleanup.mjs +2 -4
- package/scripts/lib/tmr-install.mjs +4 -3
- package/scripts/setup.mjs +279 -222
package/package.json
CHANGED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const AGENT_ENV = [
|
|
2
|
+
["claude-code", ["CLAUDECODE", "CLAUDE_CODE"]],
|
|
3
|
+
["codex", ["CODEX_SANDBOX", "CODEX_CI", "CODEX_THREAD_ID"]],
|
|
4
|
+
["gemini-cli", ["GEMINI_CLI"]],
|
|
5
|
+
["antigravity", ["ANTIGRAVITY_AGENT"]],
|
|
6
|
+
["opencode", ["OPENCODE_CLIENT"]],
|
|
7
|
+
["cursor", ["CURSOR_AGENT"]],
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
const enabled = (value) =>
|
|
11
|
+
Boolean(value) && !/^(0|false|no|off)$/iu.test(value);
|
|
12
|
+
|
|
13
|
+
export function outputOptions(
|
|
14
|
+
args = [],
|
|
15
|
+
{ env = process.env, input = process.stdin, output = process.stdout } = {},
|
|
16
|
+
) {
|
|
17
|
+
const agent =
|
|
18
|
+
AGENT_ENV.find(([, keys]) => keys.some((key) => enabled(env[key])))?.[0] ||
|
|
19
|
+
(enabled(env.AI_AGENT)
|
|
20
|
+
? env.AI_AGENT === "1"
|
|
21
|
+
? "ai-agent"
|
|
22
|
+
: env.AI_AGENT
|
|
23
|
+
: null);
|
|
24
|
+
const json = args.includes("--json");
|
|
25
|
+
return {
|
|
26
|
+
json,
|
|
27
|
+
quiet: args.includes("--quiet") || args.includes("-q"),
|
|
28
|
+
verbose: args.includes("--verbose"),
|
|
29
|
+
check: args.includes("--check"),
|
|
30
|
+
agent,
|
|
31
|
+
nonInteractive:
|
|
32
|
+
json ||
|
|
33
|
+
Boolean(agent) ||
|
|
34
|
+
!input?.isTTY ||
|
|
35
|
+
!output?.isTTY ||
|
|
36
|
+
enabled(env.CI) ||
|
|
37
|
+
enabled(env.DOCKER) ||
|
|
38
|
+
env.npm_lifecycle_event === "postinstall",
|
|
39
|
+
color:
|
|
40
|
+
Boolean(output?.isTTY) &&
|
|
41
|
+
env.NO_COLOR === undefined &&
|
|
42
|
+
env.TERM !== "dumb" &&
|
|
43
|
+
(env.FORCE_COLOR === undefined ||
|
|
44
|
+
["", "true", "1", "2", "3"].includes(env.FORCE_COLOR)),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// setup/update JSON 계약: command, outcome, message, alreadyInGoalState,
|
|
49
|
+
// from, to, changed, unchanged, targets, warnings, notices.
|
|
50
|
+
// 해당 결과에만 포함: installMode, installPath, exitCode.
|
|
51
|
+
// 내부 자식 결과에만 포함: files, steps, elapsed.
|
|
52
|
+
export function createCliResult(command, version) {
|
|
53
|
+
return {
|
|
54
|
+
command,
|
|
55
|
+
outcome: "ok",
|
|
56
|
+
message: "",
|
|
57
|
+
alreadyInGoalState: true,
|
|
58
|
+
from: version,
|
|
59
|
+
to: version,
|
|
60
|
+
changed: [],
|
|
61
|
+
unchanged: 0,
|
|
62
|
+
targets: [],
|
|
63
|
+
warnings: [],
|
|
64
|
+
notices: [],
|
|
65
|
+
files: [],
|
|
66
|
+
steps: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function recordFile(result, path, change) {
|
|
71
|
+
result.files.push({ path, change });
|
|
72
|
+
if (change === "noop") result.unchanged++;
|
|
73
|
+
else {
|
|
74
|
+
result.changed.push(path);
|
|
75
|
+
result.alreadyInGoalState = false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function addWarning(result, code, message) {
|
|
80
|
+
if (!result.warnings.some((warning) => warning.message === message))
|
|
81
|
+
result.warnings.push({ code, message });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function addStep(result, message) {
|
|
85
|
+
result.steps.push(message);
|
|
86
|
+
result.alreadyInGoalState = false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function addNotice(result, message) {
|
|
90
|
+
if (!result.notices.includes(message)) result.notices.push(message);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function commandErrorMessage(error) {
|
|
94
|
+
const message = error.message.split(/\r?\n/)[0];
|
|
95
|
+
const cause = String(error.stderr || "")
|
|
96
|
+
.split(/\r?\n/)
|
|
97
|
+
.find((line) => line.trim())
|
|
98
|
+
?.trim();
|
|
99
|
+
return cause && !message.includes(cause) ? `${message}: ${cause}` : message;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function updateCommand(result, rollback = false) {
|
|
103
|
+
if (["git-local", "plugin"].includes(result.installMode))
|
|
104
|
+
return rollback ? `git checkout v${result.from}` : "git pull";
|
|
105
|
+
const version = rollback ? result.from : "latest";
|
|
106
|
+
return result.installMode === "npm-local"
|
|
107
|
+
? rollback
|
|
108
|
+
? `npm i triflux@${version}`
|
|
109
|
+
: "npm update triflux"
|
|
110
|
+
: `npm i -g triflux@${version}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function renderCliResult(result, options = {}) {
|
|
114
|
+
const { json, quiet, verbose, check, agent, nonInteractive, color, details } =
|
|
115
|
+
options;
|
|
116
|
+
if (json) {
|
|
117
|
+
const { files, steps, elapsed, ...publicResult } = result;
|
|
118
|
+
return `${JSON.stringify(details ? result : publicResult)}\n`;
|
|
119
|
+
}
|
|
120
|
+
const mark = (symbol) =>
|
|
121
|
+
color
|
|
122
|
+
? `\x1b[${symbol === "✗" ? 31 : symbol === "!" ? 33 : 32}m${symbol}\x1b[0m`
|
|
123
|
+
: symbol;
|
|
124
|
+
const lines = [];
|
|
125
|
+
if (result.outcome === "failed") {
|
|
126
|
+
lines.push(
|
|
127
|
+
`${mark("✗")} ${result.command === "update" ? "업데이트" : "설정"} 실패: ${result.message}`,
|
|
128
|
+
);
|
|
129
|
+
if (result.command === "update") {
|
|
130
|
+
if (result.installPath) lines.push(`실행 경로: ${result.installPath}`);
|
|
131
|
+
lines.push(`직접 실행: ${updateCommand(result)}`);
|
|
132
|
+
if (result.from !== result.to)
|
|
133
|
+
lines.push(`되돌리기: ${updateCommand(result, true)}`);
|
|
134
|
+
}
|
|
135
|
+
} else if (result.outcome === "skipped") {
|
|
136
|
+
if (!quiet) lines.push(`skipped: ${result.message}`);
|
|
137
|
+
} else if (!quiet) {
|
|
138
|
+
if (result.command === "update") {
|
|
139
|
+
const versionChanged = result.from !== result.to;
|
|
140
|
+
const version = versionChanged
|
|
141
|
+
? `${result.from} -> ${result.to}`
|
|
142
|
+
: `${result.to} 최신`;
|
|
143
|
+
if (check)
|
|
144
|
+
lines.push(
|
|
145
|
+
versionChanged
|
|
146
|
+
? `업데이트 있음: ${result.from} -> ${result.to}`
|
|
147
|
+
: `${mark("✓")} triflux ${version}`,
|
|
148
|
+
);
|
|
149
|
+
else {
|
|
150
|
+
const time = `${(result.elapsed / 1000).toFixed(1)}s`;
|
|
151
|
+
const detail =
|
|
152
|
+
!versionChanged && !result.changed.length
|
|
153
|
+
? `파일 ${result.unchanged}개 일치, ${time}`
|
|
154
|
+
: time;
|
|
155
|
+
lines.push(`${mark("✓")} triflux ${version} (${detail})`);
|
|
156
|
+
if (versionChanged)
|
|
157
|
+
lines.push(
|
|
158
|
+
` https://github.com/tellang/triflux/releases/tag/v${result.to}`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (result.changed.length)
|
|
162
|
+
lines.push(
|
|
163
|
+
`파일 ${result.changed.length}개 반영 (${result.unchanged}개 그대로)`,
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
const context = agent
|
|
167
|
+
? ` (에이전트: ${agent}, 비대화식)`
|
|
168
|
+
: nonInteractive
|
|
169
|
+
? " (비대화식)"
|
|
170
|
+
: "";
|
|
171
|
+
lines.push(`triflux ${result.to} setup${context}`);
|
|
172
|
+
}
|
|
173
|
+
if (!check) {
|
|
174
|
+
if (result.command === "update" || verbose) {
|
|
175
|
+
for (const { path, change } of result.files) {
|
|
176
|
+
if (change === "noop" && !verbose) continue;
|
|
177
|
+
lines.push(
|
|
178
|
+
` ${mark(change === "create" ? "+" : change === "noop" ? "✓" : "~")} ${path}`,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
result.command === "setup" ||
|
|
184
|
+
!result.alreadyInGoalState ||
|
|
185
|
+
result.warnings.length
|
|
186
|
+
) {
|
|
187
|
+
for (const { name, status, detail } of result.targets)
|
|
188
|
+
lines.push(
|
|
189
|
+
` ${mark(status === "ok" ? "✓" : status === "skipped" ? "-" : "✗")} ${name.padEnd(8)} ${detail}`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
for (const step of result.steps) lines.push(` ${mark("✓")} ${step}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const warning of result.warnings)
|
|
196
|
+
lines.push(`${mark("!")} ${warning.message}`);
|
|
197
|
+
if (!quiet)
|
|
198
|
+
for (const notice of result.notices) lines.push(`안내: ${notice}`);
|
|
199
|
+
if (!quiet && result.outcome === "ok" && !check) {
|
|
200
|
+
if (result.command === "setup") {
|
|
201
|
+
lines.push(
|
|
202
|
+
`설정 완료${result.warnings.length ? `, 경고 ${result.warnings.length}건` : ""}`,
|
|
203
|
+
"다음: tfx doctor",
|
|
204
|
+
);
|
|
205
|
+
} else if (result.warnings.length || result.from !== result.to) {
|
|
206
|
+
if (result.from !== result.to && result.installPath)
|
|
207
|
+
lines.push(`되돌리기 실행 경로: ${result.installPath}`);
|
|
208
|
+
lines.push(
|
|
209
|
+
`${result.warnings.length ? `경고 ${result.warnings.length}건. ` : ""}${result.from !== result.to ? `되돌리기: ${updateCommand(result, true)}` : ""}`.trim(),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return lines.length ? `${lines.join("\n")}\n` : "";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function postinstallMessage(version, report) {
|
|
217
|
+
if (!report) return "저장소 체크아웃이라 setup 을 건너뜀. 설정: tfx setup\n";
|
|
218
|
+
const status =
|
|
219
|
+
report.outcome === "failed"
|
|
220
|
+
? `실패: ${report.message}`
|
|
221
|
+
: report.outcome === "skipped"
|
|
222
|
+
? `건너뜀: ${report.message}`
|
|
223
|
+
: "완료";
|
|
224
|
+
const warnings = report.warnings
|
|
225
|
+
.map(({ message }) => `! ${message}\n`)
|
|
226
|
+
.join("");
|
|
227
|
+
return `triflux ${version} 설치됨. 설정 ${status}${report.warnings.length ? `, 경고 ${report.warnings.length}건` : ""}. 확인: tfx setup\n${warnings}`;
|
|
228
|
+
}
|
|
@@ -1053,7 +1053,7 @@ function listHubProcesses(platform, run) {
|
|
|
1053
1053
|
|
|
1054
1054
|
// 허브는 제거됐으므로 triflux 패키지의 hub/server.mjs 로 확인된 프로세스는 설치본,
|
|
1055
1055
|
// 체크아웃, worktree 를 가리지 않고 종료한다(2026-10-08 결정).
|
|
1056
|
-
function stopHubs(home, platform, run, result
|
|
1056
|
+
function stopHubs(home, platform, run, result) {
|
|
1057
1057
|
const processes = listHubProcesses(platform, run);
|
|
1058
1058
|
if (!processes) {
|
|
1059
1059
|
result.warnings.push("허브 프로세스 조회 실패, 종료하지 않음");
|
|
@@ -1064,7 +1064,6 @@ function stopHubs(home, platform, run, result, log) {
|
|
|
1064
1064
|
if (Number(pid) === process.pid || typeof command !== "string") continue;
|
|
1065
1065
|
const root = trifluxHubRoot(processArgv(pid, command, platform));
|
|
1066
1066
|
if (!root) continue;
|
|
1067
|
-
log(`허브 종료: pid ${pid} (${root})`);
|
|
1068
1067
|
const stopped =
|
|
1069
1068
|
platform === "win32"
|
|
1070
1069
|
? tryRun(run, "taskkill", ["/F", "/T", "/PID", String(pid)])
|
|
@@ -1157,7 +1156,6 @@ export function cleanupTfxHub({
|
|
|
1157
1156
|
platform = osPlatform(),
|
|
1158
1157
|
run = defaultRun,
|
|
1159
1158
|
pluginRoot,
|
|
1160
|
-
log = () => {},
|
|
1161
1159
|
backups,
|
|
1162
1160
|
} = {}) {
|
|
1163
1161
|
const result = {
|
|
@@ -1210,7 +1208,7 @@ export function cleanupTfxHub({
|
|
|
1210
1208
|
(resolve(home) === resolve(homedir()) && platform === osPlatform());
|
|
1211
1209
|
if (realSystem) {
|
|
1212
1210
|
try {
|
|
1213
|
-
stopHubs(home, platform, run, result
|
|
1211
|
+
stopHubs(home, platform, run, result);
|
|
1214
1212
|
if (platform === "win32") removeHubTasks(run, result);
|
|
1215
1213
|
} catch (error) {
|
|
1216
1214
|
result.ok = false;
|
|
@@ -151,15 +151,16 @@ export async function offerTmrInstall({
|
|
|
151
151
|
install = installTmr,
|
|
152
152
|
log = console.log,
|
|
153
153
|
warn = console.warn,
|
|
154
|
+
notice = console.log,
|
|
154
155
|
}) {
|
|
155
156
|
if (!tmrSupported(platform, arch)) return "not-needed";
|
|
156
157
|
if (findTmr({ home, env })) return "present";
|
|
157
158
|
if (!interactive) {
|
|
158
|
-
|
|
159
|
+
notice("tmr 이 없다. tfx setup 에서 설치를 물을 수 있다.");
|
|
159
160
|
return "deferred";
|
|
160
161
|
}
|
|
161
162
|
if (!(await ask("tmr 을 설치할까요? (tmux 세션 목록 도구, ~/.local/bin)"))) {
|
|
162
|
-
|
|
163
|
+
notice("tmr 설치를 건너뜀.");
|
|
163
164
|
return "declined";
|
|
164
165
|
}
|
|
165
166
|
try {
|
|
@@ -174,7 +175,7 @@ export async function offerTmrInstall({
|
|
|
174
175
|
.split(delimiter)
|
|
175
176
|
.includes(binDir)
|
|
176
177
|
)
|
|
177
|
-
|
|
178
|
+
warn(`${binDir} 가 PATH 에 없다. 셸 설정에 추가한다.`);
|
|
178
179
|
return "installed";
|
|
179
180
|
} catch (error) {
|
|
180
181
|
warn(`tmr 설치 실패: ${error?.message || error}`);
|