triflux 8.11.2 → 8.12.1

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": "8.11.2",
3
+ "version": "8.12.1",
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": {
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { existsSync, readFileSync } from 'node:fs';
6
6
  import { join, dirname } from 'node:path';
7
- import { spawn } from 'node:child_process';
7
+ import { execSync } from 'node:child_process';
8
8
  import { tmpdir } from 'node:os';
9
9
  import { fileURLToPath } from 'node:url';
10
10
 
@@ -35,18 +35,16 @@ function hasManifest() {
35
35
  return existsSync(PID_FILE);
36
36
  }
37
37
 
38
- /** mcp-gateway-start.mjs를 detached로 기동 */
38
+ /** mcp-gateway-start.mjs를 독립 프로세스로 기동 */
39
39
  function startGateway() {
40
40
  const scriptPath = join(PLUGIN_ROOT, 'scripts', 'mcp-gateway-start.mjs');
41
41
  if (!existsSync(scriptPath)) return false;
42
42
 
43
43
  try {
44
- // hub-ensure.mjs 패턴: cmd.exe /c start /b hook timeout에서 생존
45
- const child = spawn('cmd.exe', ['/c', 'start', '/b', '', process.execPath, scriptPath], {
46
- stdio: 'ignore',
47
- windowsHide: true,
48
- });
49
- child.unref();
44
+ // PowerShell Start-Process: Windows Job Object에서 벗어나 부모 종료 생존
45
+ execSync(
46
+ `powershell -NoProfile -Command "Start-Process -WindowStyle Hidden -FilePath '${process.execPath}' -ArgumentList '${scriptPath.replaceAll("'", "''")}'"`
47
+ , { stdio: 'ignore', timeout: 10000 });
50
48
  return true;
51
49
  } catch {
52
50
  return false;
@@ -57,23 +57,43 @@ function sleep(ms) {
57
57
  // ── 시작 ──
58
58
 
59
59
  function spawnGateway(srv) {
60
- // 단일 명령 문자열 shell: true에서 공백 인자를 올바르게 쿼팅
61
- const cmdStr = `npx -y supergateway --stdio "${srv.cmd}" --port ${srv.port} --outputTransport sse --healthEndpoint /healthz`;
62
-
63
- // detached: true → 독립 프로세스 그룹 (부모 종료 후 생존)
64
- // shell: true → Windows에서 npx.cmd 해석
65
- const child = spawn(cmdStr, [], {
66
- detached: true,
67
- stdio: 'ignore',
68
- shell: true,
69
- windowsHide: true,
70
- env: process.env,
71
- });
72
- child.unref();
73
- return child.pid;
60
+ // 임시 .cmd 파일로 quoting 문제 회피
61
+ const cmdContent = `@echo off\nnpx -y supergateway --stdio "${srv.cmd}" --port ${srv.port} --outputTransport sse --healthEndpoint /healthz --cors "http://localhost"`;
62
+ const cmdFile = join(tmpdir(), `tfx-sg-${srv.name}.cmd`);
63
+ writeFileSync(cmdFile, cmdContent);
64
+
65
+ // PowerShell Start-Process: Windows Job Object에서 벗어나 부모 종료 후 생존
66
+ execSync(
67
+ `powershell -NoProfile -Command "Start-Process -WindowStyle Hidden -FilePath cmd.exe -ArgumentList '/c','${cmdFile.replaceAll("'", "''")}'"`
68
+ , { stdio: 'ignore', timeout: 10000 });
69
+ }
70
+
71
+ function ensureFirewallRule() {
72
+ if (process.platform !== 'win32') return;
73
+ const ports = SERVERS.map((s) => s.port).join(',');
74
+ const ruleName = 'TFX-MCP-Gateway-Block-External';
75
+ try {
76
+ // 기존 규칙 있으면 스킵
77
+ const check = execSync(
78
+ `netsh advfirewall firewall show rule name="${ruleName}" 2>&1`,
79
+ { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 },
80
+ );
81
+ if (check.includes(ruleName)) return;
82
+ } catch { /* 규칙 없음 — 생성 */ }
83
+
84
+ try {
85
+ execSync(
86
+ `netsh advfirewall firewall add rule name="${ruleName}" dir=in action=block protocol=tcp localport=${ports} remoteip=any profile=any`,
87
+ { stdio: 'ignore', timeout: 5000 },
88
+ );
89
+ console.log(`[SEC] Firewall rule added: block external access to ports ${ports}`);
90
+ } catch {
91
+ console.log(`[SEC] WARNING: Could not add firewall rule — run as admin or manually block ports ${ports}`);
92
+ }
74
93
  }
75
94
 
76
95
  async function startAll() {
96
+ ensureFirewallRule();
77
97
  const launched = [];
78
98
 
79
99
  for (const srv of SERVERS) {
@@ -136,8 +156,14 @@ async function startAll() {
136
156
  function stopAll() {
137
157
  // supergateway + 하위 MCP 프로세스를 포트 기반으로 찾아 종료
138
158
  try {
139
- const ps = `Get-CimInstance Win32_Process -Filter "Name='node.exe' OR Name='cmd.exe'" | Where-Object { $_.CommandLine -match 'supergateway' } | ForEach-Object { taskkill /F /T /PID $_.ProcessId 2>$null; Write-Output "[STOP] PID $($_.ProcessId)" }`;
140
- const output = execSync(`powershell -NoProfile -Command "${ps}"`, {
159
+ // temp .ps1 파일로 bash/cmd 쿼팅 충돌 회피
160
+ const psFile = join(tmpdir(), 'tfx-sg-stop.ps1');
161
+ writeFileSync(psFile, [
162
+ `Get-CimInstance Win32_Process -Filter "Name='node.exe' OR Name='cmd.exe'" |`,
163
+ ` Where-Object { $_.CommandLine -match 'supergateway' } |`,
164
+ ` ForEach-Object { taskkill /F /T /PID $_.ProcessId 2>$null; Write-Output "[STOP] PID $($_.ProcessId)" }`,
165
+ ].join('\n'));
166
+ const output = execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${psFile}"`, {
141
167
  encoding: 'utf8',
142
168
  timeout: 10000,
143
169
  stdio: ['pipe', 'pipe', 'ignore'],
@@ -57,7 +57,7 @@ async function main() {
57
57
  : `${downCount}/${entries.length} gateways down`,
58
58
  );
59
59
 
60
- process.exit(downCount > 0 ? 1 : 0);
60
+ process.exitCode = downCount > 0 ? 1 : 0;
61
61
  }
62
62
 
63
63
  main();