triflux 4.0.5 → 4.1.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 CHANGED
@@ -218,7 +218,7 @@ function handleFatalError(error, { json = false } = {}) {
218
218
  function which(cmd) {
219
219
  try {
220
220
  const result = process.platform === "win32"
221
- ? execFileSync("where", [cmd], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"] })
221
+ ? execFileSync("where", [cmd], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"], windowsHide: true })
222
222
  : execFileSync("which", [cmd], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"] });
223
223
  return result.trim().split(/\r?\n/)[0] || null;
224
224
  } catch { return null; }
@@ -237,6 +237,7 @@ function whichInShell(cmd, shell) {
237
237
  encoding: "utf8",
238
238
  timeout: 8000,
239
239
  stdio: ["pipe", "pipe", "ignore"],
240
+ windowsHide: true,
240
241
  }).trim();
241
242
  return result.split(/\r?\n/)[0] || null;
242
243
  } catch { return null; }
@@ -590,6 +591,56 @@ function getSetupSyncTargets() {
590
591
  dst: join(CLAUDE_DIR, "scripts", "tfx-batch-stats.mjs"),
591
592
  label: "tfx-batch-stats.mjs",
592
593
  },
594
+ {
595
+ src: join(PKG_ROOT, "scripts", "lib", "mcp-filter.mjs"),
596
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-filter.mjs"),
597
+ label: "lib/mcp-filter.mjs",
598
+ },
599
+ {
600
+ src: join(PKG_ROOT, "scripts", "lib", "mcp-server-catalog.mjs"),
601
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-server-catalog.mjs"),
602
+ label: "lib/mcp-server-catalog.mjs",
603
+ },
604
+ {
605
+ src: join(PKG_ROOT, "scripts", "lib", "keyword-rules.mjs"),
606
+ dst: join(CLAUDE_DIR, "scripts", "lib", "keyword-rules.mjs"),
607
+ label: "lib/keyword-rules.mjs",
608
+ },
609
+ {
610
+ src: join(PKG_ROOT, "scripts", "tfx-route-worker.mjs"),
611
+ dst: join(CLAUDE_DIR, "scripts", "tfx-route-worker.mjs"),
612
+ label: "tfx-route-worker.mjs",
613
+ },
614
+ {
615
+ src: join(PKG_ROOT, "hub", "workers", "codex-mcp.mjs"),
616
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "codex-mcp.mjs"),
617
+ label: "hub/workers/codex-mcp.mjs",
618
+ },
619
+ {
620
+ src: join(PKG_ROOT, "hub", "workers", "delegator-mcp.mjs"),
621
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "delegator-mcp.mjs"),
622
+ label: "hub/workers/delegator-mcp.mjs",
623
+ },
624
+ {
625
+ src: join(PKG_ROOT, "hub", "workers", "interface.mjs"),
626
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "interface.mjs"),
627
+ label: "hub/workers/interface.mjs",
628
+ },
629
+ {
630
+ src: join(PKG_ROOT, "hub", "workers", "gemini-worker.mjs"),
631
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "gemini-worker.mjs"),
632
+ label: "hub/workers/gemini-worker.mjs",
633
+ },
634
+ {
635
+ src: join(PKG_ROOT, "hub", "workers", "claude-worker.mjs"),
636
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "claude-worker.mjs"),
637
+ label: "hub/workers/claude-worker.mjs",
638
+ },
639
+ {
640
+ src: join(PKG_ROOT, "hub", "workers", "factory.mjs"),
641
+ dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "factory.mjs"),
642
+ label: "hub/workers/factory.mjs",
643
+ },
593
644
  ];
594
645
  }
595
646
 
@@ -865,7 +916,7 @@ async function cmdDoctor(options = {}) {
865
916
  const mcpCheck = join(PKG_ROOT, "scripts", "mcp-check.mjs");
866
917
  if (existsSync(mcpCheck)) {
867
918
  try {
868
- execFileSync(process.execPath, [mcpCheck], { timeout: 15000, stdio: "ignore" });
919
+ execFileSync(process.execPath, [mcpCheck], { timeout: 15000, stdio: "ignore", windowsHide: true });
869
920
  report.actions.push({ type: "rebuild", name: "mcp-inventory", status: "ok" });
870
921
  ok("MCP 인벤토리 재생성됨");
871
922
  } catch {
@@ -876,7 +927,7 @@ async function cmdDoctor(options = {}) {
876
927
  const hudScript = join(CLAUDE_DIR, "hud", "hud-qos-status.mjs");
877
928
  if (existsSync(hudScript)) {
878
929
  try {
879
- execFileSync(process.execPath, [hudScript, "--refresh-claude-usage"], { timeout: 20000, stdio: "ignore" });
930
+ execFileSync(process.execPath, [hudScript, "--refresh-claude-usage"], { timeout: 20000, stdio: "ignore", windowsHide: true });
880
931
  report.actions.push({ type: "rebuild", name: "claude-usage-cache", status: "ok" });
881
932
  ok("Claude 사용량 캐시 재생성됨");
882
933
  } catch {
@@ -884,7 +935,7 @@ async function cmdDoctor(options = {}) {
884
935
  warn("Claude 사용량 캐시 재생성 실패 — 다음 API 호출 시 자동 생성");
885
936
  }
886
937
  try {
887
- execFileSync(process.execPath, [hudScript, "--refresh-codex-rate-limits"], { timeout: 15000, stdio: "ignore" });
938
+ execFileSync(process.execPath, [hudScript, "--refresh-codex-rate-limits"], { timeout: 15000, stdio: "ignore", windowsHide: true });
888
939
  report.actions.push({ type: "rebuild", name: "codex-rate-limits-cache", status: "ok" });
889
940
  ok("Codex 레이트 리밋 캐시 재생성됨");
890
941
  } catch {
@@ -892,7 +943,7 @@ async function cmdDoctor(options = {}) {
892
943
  warn("Codex 레이트 리밋 캐시 재생성 실패");
893
944
  }
894
945
  try {
895
- execFileSync(process.execPath, [hudScript, "--refresh-gemini-quota"], { timeout: 15000, stdio: "ignore" });
946
+ execFileSync(process.execPath, [hudScript, "--refresh-gemini-quota"], { timeout: 15000, stdio: "ignore", windowsHide: true });
896
947
  report.actions.push({ type: "rebuild", name: "gemini-quota-cache", status: "ok" });
897
948
  ok("Gemini 쿼터 캐시 재생성됨");
898
949
  } catch {
@@ -911,21 +962,9 @@ async function cmdDoctor(options = {}) {
911
962
  // ── fix 모드: 파일 동기화 + 캐시 정리 후 진단 ──
912
963
  if (fix) {
913
964
  section("Auto Fix");
914
- syncFile(
915
- join(PKG_ROOT, "scripts", "tfx-route.sh"),
916
- join(CLAUDE_DIR, "scripts", "tfx-route.sh"),
917
- "tfx-route.sh"
918
- );
919
- syncFile(
920
- join(PKG_ROOT, "hud", "hud-qos-status.mjs"),
921
- join(CLAUDE_DIR, "hud", "hud-qos-status.mjs"),
922
- "hud-qos-status.mjs"
923
- );
924
- syncFile(
925
- join(PKG_ROOT, "scripts", "notion-read.mjs"),
926
- join(CLAUDE_DIR, "scripts", "notion-read.mjs"),
927
- "notion-read.mjs"
928
- );
965
+ for (const target of getSetupSyncTargets()) {
966
+ syncFile(target.src, target.dst, target.label);
967
+ }
929
968
  // 스킬 동기화
930
969
  const fSkillsSrc = join(PKG_ROOT, "skills");
931
970
  const fSkillsDst = join(CLAUDE_DIR, "skills");
@@ -1955,6 +1994,7 @@ function stopHubForUpdate() {
1955
1994
  execFileSync("taskkill", ["/PID", String(info.pid), "/T", "/F"], {
1956
1995
  stdio: ["pipe", "pipe", "ignore"],
1957
1996
  timeout: 10000,
1997
+ windowsHide: true,
1958
1998
  });
1959
1999
  } else {
1960
2000
  process.kill(info.pid, "SIGTERM");
package/hub/tray.mjs CHANGED
@@ -8,8 +8,26 @@ import { homedir } from "node:os";
8
8
  import { join, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
 
11
- const DASHBOARD_URL = "http://127.0.0.1:27888/dashboard";
12
- const HUB_STATUS_URL = "http://127.0.0.1:27888/status";
11
+ const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
12
+ const DEFAULT_HUB_PORT = "27888";
13
+
14
+ function getHubBaseUrl() {
15
+ if (process.env.TFX_HUB_URL) return process.env.TFX_HUB_URL.replace(/\/+$/, "");
16
+ try {
17
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
18
+ if (info.port) return `http://${info.host || "127.0.0.1"}:${info.port}`;
19
+ } catch {}
20
+ const port = process.env.TFX_HUB_PORT || DEFAULT_HUB_PORT;
21
+ return `http://127.0.0.1:${port}`;
22
+ }
23
+
24
+ function getDashboardUrl() {
25
+ return `${getHubBaseUrl()}/dashboard`;
26
+ }
27
+
28
+ function getHubStatusUrl() {
29
+ return `${getHubBaseUrl()}/status`;
30
+ }
13
31
  const POLL_INTERVAL_MS = 10_000;
14
32
  const HUB_TIMEOUT_MS = 3_000;
15
33
  const AIMD_INITIAL = 3;
@@ -115,7 +133,7 @@ function getClaudePercent() {
115
133
 
116
134
  async function getHubStatusLabel() {
117
135
  try {
118
- const response = await fetch(HUB_STATUS_URL, {
136
+ const response = await fetch(getHubStatusUrl(), {
119
137
  signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
120
138
  });
121
139
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -138,7 +156,8 @@ function formatMenuPercent(value) {
138
156
  }
139
157
 
140
158
  function buildTooltip(snapshot) {
141
- return `tfx AIMD:${snapshot.aimd}/10 | C:${formatTooltipPercent(snapshot.claude)} X:${formatTooltipPercent(snapshot.codex)} G:${formatTooltipPercent(snapshot.gemini)}`;
159
+ const hubTag = snapshot.hubLabel.startsWith("Hub 미") ? "H:off" : "H:on";
160
+ return `tfx AIMD:${snapshot.aimd}/10 | C:${formatTooltipPercent(snapshot.claude)} X:${formatTooltipPercent(snapshot.codex)} G:${formatTooltipPercent(snapshot.gemini)} ${hubTag}`;
142
161
  }
143
162
 
144
163
  function buildUsageTitle(snapshot) {
@@ -146,10 +165,14 @@ function buildUsageTitle(snapshot) {
146
165
  }
147
166
 
148
167
  function openDashboard() {
149
- exec(`start "" "${DASHBOARD_URL}"`, {
150
- shell: process.env.ComSpec || "cmd.exe",
151
- windowsHide: true,
152
- }, () => {});
168
+ const url = getDashboardUrl();
169
+ const shell = process.env.ComSpec || "cmd.exe";
170
+ // msedge --app: 주소바/탭 없는 앱 윈도우로 열기
171
+ exec(`start "" "msedge" "--app=${url}"`, { shell, windowsHide: true }, (err) => {
172
+ if (err) {
173
+ exec(`start "" "${url}"`, { shell, windowsHide: true }, () => {});
174
+ }
175
+ });
153
176
  }
154
177
 
155
178
  const openDashboardItem = {
@@ -162,19 +185,22 @@ const openDashboardItem = {
162
185
  const aimdItem = {
163
186
  title: "AIMD: 3/10",
164
187
  tooltip: "최근 30분 AIMD 동시 워커",
165
- enabled: false,
188
+ enabled: true,
189
+ click: openDashboard,
166
190
  };
167
191
 
168
192
  const quotaItem = {
169
193
  title: "C: --% | X: --% | G: --%",
170
194
  tooltip: "Claude | Codex | Gemini 사용률",
171
- enabled: false,
195
+ enabled: true,
196
+ click: openDashboard,
172
197
  };
173
198
 
174
199
  const hubItem = {
175
200
  title: "Hub 미연결",
176
201
  tooltip: "Hub 연결 상태",
177
- enabled: false,
202
+ enabled: true,
203
+ click: openDashboard,
178
204
  };
179
205
 
180
206
  const refreshItem = {
@@ -198,7 +224,7 @@ const exitItem = {
198
224
  const menu = {
199
225
  icon: TRAY_ICON_BASE64,
200
226
  title: "tfx",
201
- tooltip: "tfx AIMD:3/10 | C:--% X:--% G:--%",
227
+ tooltip: "tfx AIMD:3/10 | C:--% X:--% G:--% H:off",
202
228
  items: [
203
229
  openDashboardItem,
204
230
  SysTray.separator,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triflux",
3
- "version": "4.0.5",
3
+ "version": "4.1.0",
4
4
  "description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/setup.mjs CHANGED
@@ -92,6 +92,31 @@ const SYNC_MAP = [
92
92
  dst: join(CLAUDE_DIR, "hud", "hud-qos-status.mjs"),
93
93
  label: "hud-qos-status.mjs",
94
94
  },
95
+ {
96
+ src: join(PLUGIN_ROOT, "scripts", "notion-read.mjs"),
97
+ dst: join(CLAUDE_DIR, "scripts", "notion-read.mjs"),
98
+ label: "notion-read.mjs",
99
+ },
100
+ {
101
+ src: join(PLUGIN_ROOT, "scripts", "tfx-batch-stats.mjs"),
102
+ dst: join(CLAUDE_DIR, "scripts", "tfx-batch-stats.mjs"),
103
+ label: "tfx-batch-stats.mjs",
104
+ },
105
+ {
106
+ src: join(PLUGIN_ROOT, "scripts", "lib", "mcp-filter.mjs"),
107
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-filter.mjs"),
108
+ label: "lib/mcp-filter.mjs",
109
+ },
110
+ {
111
+ src: join(PLUGIN_ROOT, "scripts", "lib", "mcp-server-catalog.mjs"),
112
+ dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-server-catalog.mjs"),
113
+ label: "lib/mcp-server-catalog.mjs",
114
+ },
115
+ {
116
+ src: join(PLUGIN_ROOT, "scripts", "lib", "keyword-rules.mjs"),
117
+ dst: join(CLAUDE_DIR, "scripts", "lib", "keyword-rules.mjs"),
118
+ label: "lib/keyword-rules.mjs",
119
+ },
95
120
  ];
96
121
 
97
122
  function getVersion(filePath) {