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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "10.58.0",
3
+ "version": "10.59.0",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Antigravity, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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, log) {
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, log);
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
- log("tmr 이 없다. tfx setup 에서 설치를 물을 수 있다.");
159
+ notice("tmr 이 없다. tfx setup 에서 설치를 물을 수 있다.");
159
160
  return "deferred";
160
161
  }
161
162
  if (!(await ask("tmr 을 설치할까요? (tmux 세션 목록 도구, ~/.local/bin)"))) {
162
- log("tmr 설치를 건너뜀.");
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
- log(`${binDir} 가 PATH 에 없다. 셸 설정에 추가한다.`);
178
+ warn(`${binDir} 가 PATH 에 없다. 셸 설정에 추가한다.`);
178
179
  return "installed";
179
180
  } catch (error) {
180
181
  warn(`tmr 설치 실패: ${error?.message || error}`);