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.
- package/README.ko.md +19 -13
- package/README.md +19 -13
- package/bin/triflux.mjs +144 -67
- package/hooks/hooks.json +2 -2
- package/hooks/keyword-rules.json +338 -0
- package/hub/server.mjs +19 -10
- package/hub/team/cli.mjs +606 -442
- package/hub/team/dashboard.mjs +164 -55
- package/hub/team/native.mjs +38 -0
- package/hub/team/session.mjs +39 -23
- package/hud/hud-qos-status.mjs +56 -1
- package/package.json +3 -2
- package/scripts/__tests__/keyword-detector.test.mjs +234 -0
- package/scripts/hub-ensure.mjs +82 -0
- package/scripts/keyword-detector.mjs +257 -0
- package/scripts/keyword-rules-expander.mjs +521 -0
- package/scripts/lib/keyword-rules.mjs +165 -0
- package/scripts/run.cjs +62 -0
- package/scripts/setup.mjs +36 -16
- package/scripts/test-tfx-route-no-claude-native.mjs +49 -0
- package/scripts/tfx-route.sh +482 -418
- package/skills/tfx-auto-codex/SKILL.md +79 -0
- package/skills/tfx-team/SKILL.md +108 -63
- package/scripts/team-keyword.mjs +0 -35
package/scripts/run.cjs
ADDED
|
@@ -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
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
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}
|
|
347
|
-
${C}/tfx-
|
|
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
|
+
|