triflux 3.2.0-dev.3 → 3.2.0-dev.6

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.
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const { execFileSync } = require("child_process");
5
+ const { existsSync, readFileSync } = require("fs");
6
+ const { dirname, isAbsolute, join, resolve } = require("path");
7
+
8
+ function resolvePluginRoot() {
9
+ if (process.env.CLAUDE_PLUGIN_ROOT) return process.env.CLAUDE_PLUGIN_ROOT;
10
+ return dirname(__dirname);
11
+ }
12
+
13
+ function resolveTargetPath(rawTarget) {
14
+ if (!rawTarget || typeof rawTarget !== "string") return null;
15
+
16
+ const pluginRoot = resolvePluginRoot();
17
+ const trimmed = rawTarget.trim();
18
+
19
+ if (trimmed.startsWith("${CLAUDE_PLUGIN_ROOT}/")) {
20
+ return join(pluginRoot, trimmed.replace("${CLAUDE_PLUGIN_ROOT}/", ""));
21
+ }
22
+
23
+ if (trimmed.startsWith("/scripts/")) {
24
+ return join(pluginRoot, trimmed.replace(/^\/+/, ""));
25
+ }
26
+
27
+ if (isAbsolute(trimmed)) return trimmed;
28
+ return resolve(process.cwd(), trimmed);
29
+ }
30
+
31
+ const targetArg = process.argv[2];
32
+ if (!targetArg) {
33
+ process.exit(0);
34
+ }
35
+
36
+ const targetPath = resolveTargetPath(targetArg);
37
+ if (!targetPath || !existsSync(targetPath)) {
38
+ process.exit(0);
39
+ }
40
+
41
+ const stdinBuffer = (() => {
42
+ try {
43
+ return readFileSync(0);
44
+ } catch {
45
+ return Buffer.alloc(0);
46
+ }
47
+ })();
48
+
49
+ try {
50
+ execFileSync(process.execPath, [targetPath, ...process.argv.slice(3)], {
51
+ env: process.env,
52
+ stdio: ["pipe", "inherit", "inherit"],
53
+ input: stdinBuffer,
54
+ windowsHide: true
55
+ });
56
+ process.exit(0);
57
+ } catch (error) {
58
+ if (typeof error?.status === "number") {
59
+ process.exit(error.status);
60
+ }
61
+ process.exit(0);
62
+ }
package/scripts/setup.mjs CHANGED
@@ -4,9 +4,10 @@
4
4
  // - hud-qos-status.mjs를 ~/.claude/hud/에 동기화
5
5
  // - skills/를 ~/.claude/skills/에 동기화
6
6
 
7
- import { copyFileSync, mkdirSync, readFileSync, writeFileSync, readdirSync, existsSync, chmodSync, unlinkSync } from "fs";
8
- import { join, dirname } from "path";
9
- import { homedir } from "os";
7
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync, readdirSync, existsSync, chmodSync, unlinkSync } from "fs";
8
+ import { join, dirname } from "path";
9
+ import { homedir } from "os";
10
+ import { spawn } from "child_process";
10
11
 
11
12
  const PLUGIN_ROOT = dirname(dirname(new URL(import.meta.url).pathname)).replace(/^\/([A-Z]:)/, "$1");
12
13
  const CLAUDE_DIR = join(homedir(), ".claude");
@@ -291,18 +292,36 @@ if (codexProfilesAdded > 0) {
291
292
  synced++;
292
293
  }
293
294
 
294
- // ── MCP 인벤토리 백그라운드 갱신 ──
295
-
296
- import { spawn } from "child_process";
297
-
298
- const mcpCheck = join(PLUGIN_ROOT, "scripts", "mcp-check.mjs");
299
- if (existsSync(mcpCheck)) {
300
- const child = spawn(process.execPath, [mcpCheck], {
295
+ // ── MCP 인벤토리 백그라운드 갱신 ──
296
+
297
+ const mcpCheck = join(PLUGIN_ROOT, "scripts", "mcp-check.mjs");
298
+ if (existsSync(mcpCheck)) {
299
+ const child = spawn(process.execPath, [mcpCheck], {
301
300
  detached: true,
302
301
  stdio: "ignore",
303
302
  });
304
- child.unref(); // 부모 프로세스와 분리 — 비동기 실행
305
- }
303
+ child.unref(); // 부모 프로세스와 분리 — 비동기 실행
304
+ }
305
+
306
+ // ── Hub 헬스체크 + 자동 기동 (세션 시작 백그라운드) ──
307
+ // setup 훅이 포그라운드 지연을 만들지 않도록 별도 detached 프로세스로 처리한다.
308
+ const hubEnsure = join(PLUGIN_ROOT, "scripts", "hub-ensure.mjs");
309
+ const isPostinstall = process.env.npm_lifecycle_event === "postinstall";
310
+ const isCi = /^(1|true)$/i.test(process.env.CI || "");
311
+ const disableHubAutostart = process.env.TFX_DISABLE_HUB_AUTOSTART === "1";
312
+
313
+ if (!isPostinstall && !isCi && !disableHubAutostart && existsSync(hubEnsure)) {
314
+ try {
315
+ const child = spawn(process.execPath, [hubEnsure], {
316
+ env: process.env,
317
+ detached: true,
318
+ stdio: "ignore",
319
+ });
320
+ child.unref();
321
+ } catch {
322
+ // best effort: 실패해도 setup 흐름은 지속
323
+ }
324
+ }
306
325
 
307
326
  // ── postinstall 배너 (npm install 시에만 출력) ──
308
327
 
@@ -341,10 +360,11 @@ ${B}Shortcuts:${R}
341
360
  ${C}tfx-setup${R} triflux setup
342
361
  ${C}tfx-doctor${R} triflux doctor
343
362
 
344
- ${B}Skills (Claude Code):${R}
345
- ${C}/tfx-auto${R} "작업" 자동 분류 + 병렬 실행
346
- ${C}/tfx-codex${R} "작업" Codex 전용 모드
347
- ${C}/tfx-gemini${R} "작업" Gemini 전용 모드
363
+ ${B}Skills (Claude Code):${R}
364
+ ${C}/tfx-auto${R} "작업" 자동 분류 + 병렬 실행
365
+ ${C}/tfx-auto-codex${R} "작업" Codex 리드 + Gemini 유지
366
+ ${C}/tfx-codex${R} "작업" Codex 전용 모드
367
+ ${C}/tfx-gemini${R} "작업" Gemini 전용 모드
348
368
  ${C}/tfx-setup${R} HUD 설정 + 진단
349
369
 
350
370
  ${Y}!${R} 세션 재시작 후 스킬이 활성화됩니다
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import test from "node:test";
3
+ import assert from "node:assert/strict";
4
+ import { spawnSync } from "node:child_process";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
+ const PROJECT_ROOT = resolve(SCRIPT_DIR, "..");
10
+
11
+ function runBash(command) {
12
+ return spawnSync("bash", ["-lc", command], {
13
+ cwd: PROJECT_ROOT,
14
+ encoding: "utf8"
15
+ });
16
+ }
17
+
18
+ function out(result) {
19
+ return `${result.stdout || ""}\n${result.stderr || ""}`;
20
+ }
21
+
22
+ test("gemini 모드에서는 no-claude-native 강제 치환이 적용되지 않는다", () => {
23
+ const result = runBash(
24
+ "TFX_CLI_MODE=gemini TFX_NO_CLAUDE_NATIVE=1 bash scripts/tfx-route.sh explore 'test-case'"
25
+ );
26
+
27
+ assert.equal(result.status, 0, out(result));
28
+ assert.match(out(result), /ROUTE_TYPE=claude-native/, out(result));
29
+ });
30
+
31
+ test("auto 모드 + no-claude-native=1이면 explore가 codex로 치환된다", () => {
32
+ const result = runBash(
33
+ "TFX_CLI_MODE=auto TFX_NO_CLAUDE_NATIVE=1 CODEX_BIN=true bash scripts/tfx-route.sh explore 'test-case' minimal 5"
34
+ );
35
+
36
+ assert.equal(result.status, 0, out(result));
37
+ assert.match(out(result), /TFX_NO_CLAUDE_NATIVE=1: explore -> codex/, out(result));
38
+ assert.match(out(result), /type=codex|cli:\\s*codex/i, out(result));
39
+ });
40
+
41
+ test("TFX_NO_CLAUDE_NATIVE는 0/1 값만 허용한다", () => {
42
+ const result = runBash(
43
+ "TFX_NO_CLAUDE_NATIVE=2 bash scripts/tfx-route.sh explore 'test-case'"
44
+ );
45
+
46
+ assert.notEqual(result.status, 0, out(result));
47
+ assert.match(out(result), /0 또는 1/, out(result));
48
+ });
49
+