triflux 9.3.0 → 9.5.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/.claude-plugin/plugin.json +1 -1
- package/bin/triflux.mjs +171 -231
- package/hub/pipe.mjs +1 -8
- package/hub/team/psmux.mjs +64 -8
- package/hub/team/tui-viewer.mjs +1 -1
- package/hub/workers/claude-worker.mjs +1 -24
- package/hub/workers/codex-mcp.mjs +0 -4
- package/hub/workers/gemini-worker.mjs +108 -28
- package/hub/workers/interface.mjs +0 -1
- package/hub/workers/worker-utils.mjs +26 -0
- package/package.json +1 -1
- package/scripts/__tests__/remote-spawn.test.mjs +17 -3
- package/scripts/cross-review-gate.mjs +11 -65
- package/scripts/cross-review-tracker.mjs +10 -51
- package/scripts/headless-guard.mjs +5 -17
- package/scripts/lib/cross-review-utils.mjs +51 -0
- package/scripts/lib/hook-utils.mjs +14 -0
- package/scripts/lib/mcp-filter.mjs +10 -1
- package/scripts/psmux-safety-guard.mjs +64 -0
- package/scripts/remote-spawn.mjs +77 -23
- package/scripts/session-spawn-helper.mjs +3 -2
- package/scripts/setup.mjs +129 -9
- package/scripts/tfx-route.sh +93 -10
- package/skills/tfx-psmux-rules/SKILL.md +100 -13
- package/skills/tfx-research/SKILL.md +1 -1
- package/skills/tfx-setup/SKILL.md +17 -1
package/bin/triflux.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// triflux CLI — setup, doctor, version
|
|
3
|
-
import { copyFileSync, existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, readdirSync, unlinkSync,
|
|
3
|
+
import { copyFileSync, existsSync, readFileSync, readSync, writeFileSync, mkdirSync, chmodSync, readdirSync, unlinkSync, statSync, openSync, closeSync } from "fs";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { homedir } from "os";
|
|
6
6
|
import { execSync, execFileSync, spawn } from "child_process";
|
|
@@ -12,6 +12,11 @@ import { forceCleanupTeam } from "../hub/team/nativeProxy.mjs";
|
|
|
12
12
|
import { cleanupStaleOmcTeams, inspectStaleOmcTeams } from "../hub/team/staleState.mjs";
|
|
13
13
|
import { getPipelineStateDbPath } from "../hub/pipeline/state.mjs";
|
|
14
14
|
import { ensureGeminiProfiles } from "../scripts/lib/gemini-profiles.mjs";
|
|
15
|
+
import {
|
|
16
|
+
SYNC_MAP, SKILL_ALIASES, REQUIRED_CODEX_PROFILES, LEGACY_CODEX_MODELS,
|
|
17
|
+
syncAliasedSkillDir, hasProfileSection, replaceProfileSection,
|
|
18
|
+
ensureCodexProfiles, getVersion, cleanupStaleSkills, DEPRECATED_SKILLS,
|
|
19
|
+
} from "../scripts/setup.mjs";
|
|
15
20
|
|
|
16
21
|
const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
17
22
|
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
@@ -22,72 +27,6 @@ const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
|
22
27
|
// 이 배열에 포함된 버전에서만 star prompt를 표시한다 (빈 배열 = 모든 버전에서 표시)
|
|
23
28
|
const STAR_PROMPT_VERSIONS = [];
|
|
24
29
|
|
|
25
|
-
const REQUIRED_CODEX_PROFILES = [
|
|
26
|
-
{
|
|
27
|
-
name: "codex53_high",
|
|
28
|
-
lines: [
|
|
29
|
-
'model = "gpt-5.3-codex"',
|
|
30
|
-
'model_reasoning_effort = "high"',
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: "codex53_xhigh",
|
|
35
|
-
lines: [
|
|
36
|
-
'model = "gpt-5.3-codex"',
|
|
37
|
-
'model_reasoning_effort = "xhigh"',
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: "spark53_low",
|
|
42
|
-
lines: [
|
|
43
|
-
'model = "gpt-5.3-codex-spark"',
|
|
44
|
-
'model_reasoning_effort = "low"',
|
|
45
|
-
],
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
const SKILL_ALIASES = [
|
|
50
|
-
{
|
|
51
|
-
alias: "tfx-ralph",
|
|
52
|
-
source: "tfx-persist",
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
function buildAliasedSkillContent(srcContent, { alias, source }) {
|
|
57
|
-
return srcContent
|
|
58
|
-
.replace(/^name:\s*.+$/m, `name: ${alias}`)
|
|
59
|
-
.replaceAll(source, alias)
|
|
60
|
-
.replace(/^#\s+.+$/m, `# ${alias} — Compatibility Alias for ${source}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function syncAliasedSkillDir(srcDir, dstDir, { alias, source }) {
|
|
64
|
-
if (!existsSync(dstDir)) mkdirSync(dstDir, { recursive: true });
|
|
65
|
-
|
|
66
|
-
let count = 0;
|
|
67
|
-
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
68
|
-
const srcPath = join(srcDir, entry.name);
|
|
69
|
-
const dstPath = join(dstDir, entry.name);
|
|
70
|
-
|
|
71
|
-
if (entry.isDirectory()) {
|
|
72
|
-
count += syncAliasedSkillDir(srcPath, dstPath, { alias, source });
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!entry.name.endsWith(".md")) continue;
|
|
77
|
-
|
|
78
|
-
const rawContent = readFileSync(srcPath, "utf8");
|
|
79
|
-
const nextContent = entry.name === "SKILL.md"
|
|
80
|
-
? buildAliasedSkillContent(rawContent, { alias, source })
|
|
81
|
-
: rawContent;
|
|
82
|
-
|
|
83
|
-
if (!existsSync(dstPath) || readFileSync(dstPath, "utf8") !== nextContent) {
|
|
84
|
-
writeFileSync(dstPath, nextContent, "utf8");
|
|
85
|
-
count++;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return count;
|
|
90
|
-
}
|
|
91
30
|
|
|
92
31
|
// ── 색상 체계 (triflux brand: amber/orange accent) ──
|
|
93
32
|
const CYAN = "\x1b[36m";
|
|
@@ -328,13 +267,6 @@ function checkShellAvailable(shell) {
|
|
|
328
267
|
} catch { return false; }
|
|
329
268
|
}
|
|
330
269
|
|
|
331
|
-
function getVersion(filePath) {
|
|
332
|
-
try {
|
|
333
|
-
const content = readFileSync(filePath, "utf8");
|
|
334
|
-
const match = content.match(/VERSION\s*=\s*"([^"]+)"/);
|
|
335
|
-
return match ? match[1] : null;
|
|
336
|
-
} catch { return null; }
|
|
337
|
-
}
|
|
338
270
|
|
|
339
271
|
function parseSessionCreated(rawValue) {
|
|
340
272
|
const value = String(rawValue || "").trim();
|
|
@@ -459,57 +391,35 @@ async function cleanupStaleTeamSessions(staleSessions) {
|
|
|
459
391
|
return { cleaned, failed };
|
|
460
392
|
}
|
|
461
393
|
|
|
462
|
-
function escapeRegExp(value) {
|
|
463
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function hasProfileSection(tomlContent, profileName) {
|
|
467
|
-
const section = `^\\[profiles\\.${escapeRegExp(profileName)}\\]\\s*$`;
|
|
468
|
-
return new RegExp(section, "m").test(tomlContent);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function ensureCodexProfiles() {
|
|
472
|
-
try {
|
|
473
|
-
if (!existsSync(CODEX_DIR)) mkdirSync(CODEX_DIR, { recursive: true });
|
|
474
|
-
|
|
475
|
-
const original = existsSync(CODEX_CONFIG_PATH)
|
|
476
|
-
? readFileSync(CODEX_CONFIG_PATH, "utf8")
|
|
477
|
-
: "";
|
|
478
|
-
|
|
479
|
-
let updated = original;
|
|
480
|
-
let added = 0;
|
|
481
394
|
|
|
482
|
-
|
|
483
|
-
|
|
395
|
+
function previewCodexProfiles() {
|
|
396
|
+
const original = existsSync(CODEX_CONFIG_PATH)
|
|
397
|
+
? readFileSync(CODEX_CONFIG_PATH, "utf8")
|
|
398
|
+
: "";
|
|
399
|
+
let updated = original;
|
|
400
|
+
const profiles = [];
|
|
484
401
|
|
|
402
|
+
for (const profile of REQUIRED_CODEX_PROFILES) {
|
|
403
|
+
const before = updated;
|
|
404
|
+
if (hasProfileSection(updated, profile.name)) {
|
|
405
|
+
updated = replaceProfileSection(updated, profile.name, profile.lines);
|
|
406
|
+
} else {
|
|
485
407
|
if (updated.length > 0 && !updated.endsWith("\n")) updated += "\n";
|
|
486
408
|
if (updated.trim().length > 0) updated += "\n";
|
|
487
409
|
updated += `[profiles.${profile.name}]\n${profile.lines.join("\n")}\n`;
|
|
488
|
-
added++;
|
|
489
410
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
writeFileSync(CODEX_CONFIG_PATH, updated, "utf8");
|
|
411
|
+
if (updated !== before) {
|
|
412
|
+
profiles.push(profile.name);
|
|
493
413
|
}
|
|
494
|
-
|
|
495
|
-
return { ok: true, added };
|
|
496
|
-
} catch (e) {
|
|
497
|
-
return { ok: false, added: 0, message: e.message };
|
|
498
414
|
}
|
|
499
|
-
}
|
|
500
415
|
|
|
501
|
-
|
|
502
|
-
const original = existsSync(CODEX_CONFIG_PATH)
|
|
503
|
-
? readFileSync(CODEX_CONFIG_PATH, "utf8")
|
|
504
|
-
: "";
|
|
505
|
-
const missingProfiles = REQUIRED_CODEX_PROFILES
|
|
506
|
-
.filter((profile) => !hasProfileSection(original, profile.name))
|
|
507
|
-
.map((profile) => profile.name);
|
|
416
|
+
const windowsSandbox = process.platform === "win32" && !updated.includes("[windows]");
|
|
508
417
|
|
|
509
418
|
return {
|
|
510
419
|
path: CODEX_CONFIG_PATH,
|
|
511
|
-
|
|
512
|
-
|
|
420
|
+
profiles,
|
|
421
|
+
windowsSandbox,
|
|
422
|
+
change: profiles.length > 0 || windowsSandbox ? (original ? "update" : "create") : "noop",
|
|
513
423
|
};
|
|
514
424
|
}
|
|
515
425
|
|
|
@@ -637,101 +547,6 @@ function checkCliCrossShell(cmd, installHint) {
|
|
|
637
547
|
|
|
638
548
|
// ── 명령어 ──
|
|
639
549
|
|
|
640
|
-
function getSetupSyncTargets() {
|
|
641
|
-
return [
|
|
642
|
-
{
|
|
643
|
-
src: join(PKG_ROOT, "scripts", "tfx-route.sh"),
|
|
644
|
-
dst: join(CLAUDE_DIR, "scripts", "tfx-route.sh"),
|
|
645
|
-
label: "tfx-route.sh",
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
src: join(PKG_ROOT, "hud", "hud-qos-status.mjs"),
|
|
649
|
-
dst: join(CLAUDE_DIR, "hud", "hud-qos-status.mjs"),
|
|
650
|
-
label: "hud-qos-status.mjs",
|
|
651
|
-
},
|
|
652
|
-
{
|
|
653
|
-
src: join(PKG_ROOT, "scripts", "notion-read.mjs"),
|
|
654
|
-
dst: join(CLAUDE_DIR, "scripts", "notion-read.mjs"),
|
|
655
|
-
label: "notion-read.mjs",
|
|
656
|
-
},
|
|
657
|
-
{
|
|
658
|
-
src: join(PKG_ROOT, "scripts", "tfx-route-post.mjs"),
|
|
659
|
-
dst: join(CLAUDE_DIR, "scripts", "tfx-route-post.mjs"),
|
|
660
|
-
label: "tfx-route-post.mjs",
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
src: join(PKG_ROOT, "scripts", "tfx-batch-stats.mjs"),
|
|
664
|
-
dst: join(CLAUDE_DIR, "scripts", "tfx-batch-stats.mjs"),
|
|
665
|
-
label: "tfx-batch-stats.mjs",
|
|
666
|
-
},
|
|
667
|
-
{
|
|
668
|
-
src: join(PKG_ROOT, "scripts", "lib", "mcp-filter.mjs"),
|
|
669
|
-
dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-filter.mjs"),
|
|
670
|
-
label: "lib/mcp-filter.mjs",
|
|
671
|
-
},
|
|
672
|
-
{
|
|
673
|
-
src: join(PKG_ROOT, "scripts", "lib", "mcp-server-catalog.mjs"),
|
|
674
|
-
dst: join(CLAUDE_DIR, "scripts", "lib", "mcp-server-catalog.mjs"),
|
|
675
|
-
label: "lib/mcp-server-catalog.mjs",
|
|
676
|
-
},
|
|
677
|
-
{
|
|
678
|
-
src: join(PKG_ROOT, "scripts", "lib", "keyword-rules.mjs"),
|
|
679
|
-
dst: join(CLAUDE_DIR, "scripts", "lib", "keyword-rules.mjs"),
|
|
680
|
-
label: "lib/keyword-rules.mjs",
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
src: join(PKG_ROOT, "scripts", "lib", "gemini-profiles.mjs"),
|
|
684
|
-
dst: join(CLAUDE_DIR, "scripts", "lib", "gemini-profiles.mjs"),
|
|
685
|
-
label: "lib/gemini-profiles.mjs",
|
|
686
|
-
},
|
|
687
|
-
{
|
|
688
|
-
src: join(PKG_ROOT, "scripts", "tfx-route-worker.mjs"),
|
|
689
|
-
dst: join(CLAUDE_DIR, "scripts", "tfx-route-worker.mjs"),
|
|
690
|
-
label: "tfx-route-worker.mjs",
|
|
691
|
-
},
|
|
692
|
-
{
|
|
693
|
-
src: join(PKG_ROOT, "hub", "workers", "codex-mcp.mjs"),
|
|
694
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "codex-mcp.mjs"),
|
|
695
|
-
label: "hub/workers/codex-mcp.mjs",
|
|
696
|
-
},
|
|
697
|
-
{
|
|
698
|
-
src: join(PKG_ROOT, "hub", "workers", "delegator-mcp.mjs"),
|
|
699
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "delegator-mcp.mjs"),
|
|
700
|
-
label: "hub/workers/delegator-mcp.mjs",
|
|
701
|
-
},
|
|
702
|
-
{
|
|
703
|
-
src: join(PKG_ROOT, "hub", "workers", "interface.mjs"),
|
|
704
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "interface.mjs"),
|
|
705
|
-
label: "hub/workers/interface.mjs",
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
src: join(PKG_ROOT, "hub", "workers", "gemini-worker.mjs"),
|
|
709
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "gemini-worker.mjs"),
|
|
710
|
-
label: "hub/workers/gemini-worker.mjs",
|
|
711
|
-
},
|
|
712
|
-
{
|
|
713
|
-
src: join(PKG_ROOT, "hub", "workers", "claude-worker.mjs"),
|
|
714
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "claude-worker.mjs"),
|
|
715
|
-
label: "hub/workers/claude-worker.mjs",
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
src: join(PKG_ROOT, "hub", "workers", "factory.mjs"),
|
|
719
|
-
dst: join(CLAUDE_DIR, "scripts", "hub", "workers", "factory.mjs"),
|
|
720
|
-
label: "hub/workers/factory.mjs",
|
|
721
|
-
},
|
|
722
|
-
{
|
|
723
|
-
src: join(PKG_ROOT, "scripts", "remote-spawn.mjs"),
|
|
724
|
-
dst: join(CLAUDE_DIR, "scripts", "remote-spawn.mjs"),
|
|
725
|
-
label: "remote-spawn.mjs",
|
|
726
|
-
},
|
|
727
|
-
{
|
|
728
|
-
src: join(PKG_ROOT, "hub", "team", "psmux.mjs"),
|
|
729
|
-
dst: join(CLAUDE_DIR, "hub", "team", "psmux.mjs"),
|
|
730
|
-
label: "hub/team/psmux.mjs",
|
|
731
|
-
},
|
|
732
|
-
];
|
|
733
|
-
}
|
|
734
|
-
|
|
735
550
|
function listSkillSyncActions() {
|
|
736
551
|
const skillsSrc = join(PKG_ROOT, "skills");
|
|
737
552
|
if (!existsSync(skillsSrc)) return [];
|
|
@@ -816,7 +631,7 @@ function previewMcpRegistrationActions(mcpUrl) {
|
|
|
816
631
|
|
|
817
632
|
function buildSetupDryRunPlan() {
|
|
818
633
|
const actions = [
|
|
819
|
-
...
|
|
634
|
+
...SYNC_MAP.map(({ src, dst, label }) => describeSyncAction(src, dst, label)),
|
|
820
635
|
...listSkillSyncActions(),
|
|
821
636
|
];
|
|
822
637
|
const codexProfiles = previewCodexProfiles();
|
|
@@ -824,7 +639,8 @@ function buildSetupDryRunPlan() {
|
|
|
824
639
|
type: "codex-profiles",
|
|
825
640
|
path: codexProfiles.path,
|
|
826
641
|
change: codexProfiles.change,
|
|
827
|
-
profiles: codexProfiles.
|
|
642
|
+
profiles: codexProfiles.profiles,
|
|
643
|
+
windowsSandbox: codexProfiles.windowsSandbox,
|
|
828
644
|
});
|
|
829
645
|
|
|
830
646
|
const defaultHubUrl = `http://127.0.0.1:${process.env.TFX_HUB_PORT || "27888"}/mcp`;
|
|
@@ -846,7 +662,7 @@ function cmdSetup(options = {}) {
|
|
|
846
662
|
|
|
847
663
|
console.log(`\n${BOLD}triflux setup${RESET}\n`);
|
|
848
664
|
|
|
849
|
-
for (const target of
|
|
665
|
+
for (const target of SYNC_MAP) {
|
|
850
666
|
syncFile(target.src, target.dst, target.label);
|
|
851
667
|
}
|
|
852
668
|
|
|
@@ -904,6 +720,11 @@ function cmdSetup(options = {}) {
|
|
|
904
720
|
} else {
|
|
905
721
|
ok(`스킬: ${skillTotal}개 최신 상태`);
|
|
906
722
|
}
|
|
723
|
+
// Stale 스킬 정리 (패키지에서 제거된 tfx-* 스킬 삭제)
|
|
724
|
+
const staleCleanup = cleanupStaleSkills(skillsDst, skillsSrc);
|
|
725
|
+
if (staleCleanup.count > 0) {
|
|
726
|
+
ok(`구형 스킬 ${staleCleanup.count}개 제거: ${staleCleanup.removed.join(", ")}`);
|
|
727
|
+
}
|
|
907
728
|
}
|
|
908
729
|
|
|
909
730
|
// ── 결과 추적 ──
|
|
@@ -913,9 +734,9 @@ function cmdSetup(options = {}) {
|
|
|
913
734
|
if (!codexProfileResult.ok) {
|
|
914
735
|
warn(`Codex profiles 설정 실패: ${codexProfileResult.message}`);
|
|
915
736
|
summary.push({ item: "Codex profiles", status: "⚠️", detail: codexProfileResult.message });
|
|
916
|
-
} else if (codexProfileResult.
|
|
917
|
-
ok(`Codex profiles: ${codexProfileResult.
|
|
918
|
-
summary.push({ item: "Codex profiles", status: "✅", detail: `${codexProfileResult.
|
|
737
|
+
} else if (codexProfileResult.changed > 0) {
|
|
738
|
+
ok(`Codex profiles: ${codexProfileResult.changed}개 반영됨 (~/.codex/config.toml)`);
|
|
739
|
+
summary.push({ item: "Codex profiles", status: "✅", detail: `${codexProfileResult.changed}개 반영됨` });
|
|
919
740
|
} else {
|
|
920
741
|
ok("Codex profiles: 이미 준비됨");
|
|
921
742
|
summary.push({ item: "Codex profiles", status: "✅", detail: "이미 준비됨" });
|
|
@@ -1006,22 +827,55 @@ function cmdSetup(options = {}) {
|
|
|
1006
827
|
}
|
|
1007
828
|
}
|
|
1008
829
|
|
|
1009
|
-
// Star request (버전 게이팅)
|
|
830
|
+
// Star request (버전 게이팅 + 인터랙티브 [y/n])
|
|
1010
831
|
const showStar = STAR_PROMPT_VERSIONS.length === 0 || STAR_PROMPT_VERSIONS.includes(PKG.version);
|
|
1011
832
|
if (showStar) {
|
|
833
|
+
let ghOk = false;
|
|
1012
834
|
try {
|
|
1013
835
|
execFileSync("gh", ["auth", "status"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
836
|
+
ghOk = true;
|
|
837
|
+
} catch {}
|
|
838
|
+
|
|
839
|
+
if (!ghOk) {
|
|
840
|
+
// gh 미설치/미인증 — URL만 표시
|
|
841
|
+
console.log();
|
|
842
|
+
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
843
|
+
} else {
|
|
844
|
+
let alreadyStarred = false;
|
|
1014
845
|
try {
|
|
1015
846
|
execFileSync("gh", ["api", "user/starred/tellang/triflux"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
847
|
+
alreadyStarred = true;
|
|
848
|
+
} catch {}
|
|
849
|
+
|
|
850
|
+
if (alreadyStarred) {
|
|
1016
851
|
console.log();
|
|
1017
852
|
ok(`이미 함께하고 계시군요. ${AMBER}⭐${RESET}`);
|
|
1018
|
-
}
|
|
853
|
+
} else {
|
|
854
|
+
// 인터랙티브 confirm
|
|
1019
855
|
console.log();
|
|
1020
|
-
|
|
856
|
+
process.stdout.write(` ${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. Star? ${DIM}[y/N]${RESET} `);
|
|
857
|
+
let answer = "";
|
|
858
|
+
try {
|
|
859
|
+
const buf = Buffer.alloc(128);
|
|
860
|
+
const n = readSync(0, buf, 0, 128);
|
|
861
|
+
answer = buf.toString("utf8", 0, n).trim().toLowerCase();
|
|
862
|
+
} catch {
|
|
863
|
+
// non-interactive stdin — 건너뜀
|
|
864
|
+
}
|
|
865
|
+
if (answer.startsWith("y")) {
|
|
866
|
+
try {
|
|
867
|
+
execFileSync("gh", ["api", "-X", "PUT", "/user/starred/tellang/triflux"], {
|
|
868
|
+
timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
869
|
+
});
|
|
870
|
+
ok(`함께해 주셔서 감사합니다. ${AMBER}⭐${RESET}`);
|
|
871
|
+
} catch {
|
|
872
|
+
info(`${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
873
|
+
}
|
|
874
|
+
} else if (answer === "") {
|
|
875
|
+
// 아무 입력 없이 Enter — 조용히 URL만
|
|
876
|
+
console.log(` ${DIM}https://github.com/tellang/triflux${RESET}`);
|
|
877
|
+
}
|
|
1021
878
|
}
|
|
1022
|
-
} catch {
|
|
1023
|
-
console.log();
|
|
1024
|
-
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
1025
879
|
}
|
|
1026
880
|
}
|
|
1027
881
|
|
|
@@ -1158,7 +1012,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1158
1012
|
// ── fix 모드: 파일 동기화 + 캐시 정리 후 진단 ──
|
|
1159
1013
|
if (fix) {
|
|
1160
1014
|
section("Auto Fix");
|
|
1161
|
-
for (const target of
|
|
1015
|
+
for (const target of SYNC_MAP) {
|
|
1162
1016
|
syncFile(target.src, target.dst, target.label);
|
|
1163
1017
|
}
|
|
1164
1018
|
// 스킬 동기화
|
|
@@ -1182,8 +1036,8 @@ async function cmdDoctor(options = {}) {
|
|
|
1182
1036
|
const profileFix = ensureCodexProfiles();
|
|
1183
1037
|
if (!profileFix.ok) {
|
|
1184
1038
|
warn(`Codex Profiles 자동 복구 실패: ${profileFix.message}`);
|
|
1185
|
-
} else if (profileFix.
|
|
1186
|
-
ok(`Codex Profiles: ${profileFix.
|
|
1039
|
+
} else if (profileFix.changed > 0) {
|
|
1040
|
+
ok(`Codex Profiles: ${profileFix.changed}개 반영됨`);
|
|
1187
1041
|
} else {
|
|
1188
1042
|
info("Codex Profiles: 이미 최신 상태");
|
|
1189
1043
|
}
|
|
@@ -1263,7 +1117,9 @@ async function cmdDoctor(options = {}) {
|
|
|
1263
1117
|
const missingProfiles = [];
|
|
1264
1118
|
for (const profile of REQUIRED_CODEX_PROFILES) {
|
|
1265
1119
|
if (hasProfileSection(codexConfig, profile.name)) {
|
|
1266
|
-
ok(`${profile.name}:
|
|
1120
|
+
ok(`${profile.name}: 정상${profile.proOnly ? ` ${DIM}(Pro 전용)${RESET}` : ""}`);
|
|
1121
|
+
} else if (profile.proOnly) {
|
|
1122
|
+
info(`${profile.name}: 미설정 ${DIM}(Pro 전용 — Plus/기본에서는 불필요)${RESET}`);
|
|
1267
1123
|
} else {
|
|
1268
1124
|
missingProfiles.push(profile.name);
|
|
1269
1125
|
warn(`${profile.name}: 미설정`);
|
|
@@ -1283,6 +1139,18 @@ async function cmdDoctor(options = {}) {
|
|
|
1283
1139
|
issues++;
|
|
1284
1140
|
}
|
|
1285
1141
|
|
|
1142
|
+
// Codex 구형 모델 감지
|
|
1143
|
+
if (existsSync(CODEX_CONFIG_PATH)) {
|
|
1144
|
+
const codexContent = readFileSync(CODEX_CONFIG_PATH, "utf8");
|
|
1145
|
+
const legacyFound = LEGACY_CODEX_MODELS.filter(m => codexContent.includes(`"${m}"`));
|
|
1146
|
+
if (legacyFound.length > 0) {
|
|
1147
|
+
warn(`구형 모델 감지: ${legacyFound.join(", ")}`);
|
|
1148
|
+
info("최신 프로필로 마이그레이션: tfx setup 또는 tfx profile");
|
|
1149
|
+
addDoctorCheck(report, { name: "codex-legacy-models", status: "issues", models: legacyFound, fix: "tfx setup" });
|
|
1150
|
+
issues++;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1286
1154
|
// 5. Gemini CLI
|
|
1287
1155
|
section(`Gemini CLI ${BLUE}●${RESET}`);
|
|
1288
1156
|
const geminiCli = checkCliCrossShell("gemini", "npm install -g @google/gemini-cli");
|
|
@@ -1295,6 +1163,22 @@ async function cmdDoctor(options = {}) {
|
|
|
1295
1163
|
});
|
|
1296
1164
|
// API 키 검사 제거 — bash exec 기반이므로 API 키 불필요
|
|
1297
1165
|
|
|
1166
|
+
// Gemini 구형 모델 감지
|
|
1167
|
+
const geminiProfilesPath = join(homedir(), ".gemini", "triflux-profiles.json");
|
|
1168
|
+
const LEGACY_GEMINI_MODELS = ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash", "gemini-2.5-pro-preview"];
|
|
1169
|
+
if (existsSync(geminiProfilesPath)) {
|
|
1170
|
+
try {
|
|
1171
|
+
const geminiContent = readFileSync(geminiProfilesPath, "utf8");
|
|
1172
|
+
const geminiLegacy = LEGACY_GEMINI_MODELS.filter(m => geminiContent.includes(m));
|
|
1173
|
+
if (geminiLegacy.length > 0) {
|
|
1174
|
+
warn(`구형 모델 감지: ${geminiLegacy.join(", ")}`);
|
|
1175
|
+
info("최신 프로필로 마이그레이션: tfx setup 또는 tfx profile");
|
|
1176
|
+
addDoctorCheck(report, { name: "gemini-legacy-models", status: "issues", models: geminiLegacy, fix: "tfx setup" });
|
|
1177
|
+
issues++;
|
|
1178
|
+
}
|
|
1179
|
+
} catch {}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1298
1182
|
// 6. Claude Code
|
|
1299
1183
|
section(`Claude Code ${AMBER}●${RESET}`);
|
|
1300
1184
|
const claudePath = which("claude");
|
|
@@ -1307,7 +1191,38 @@ async function cmdDoctor(options = {}) {
|
|
|
1307
1191
|
issues++;
|
|
1308
1192
|
}
|
|
1309
1193
|
|
|
1310
|
-
|
|
1194
|
+
// 7. psmux (Windows only)
|
|
1195
|
+
if (process.platform === "win32") {
|
|
1196
|
+
section("psmux (터미널 멀티플렉서)");
|
|
1197
|
+
const psmuxPath = which("psmux");
|
|
1198
|
+
if (psmuxPath) {
|
|
1199
|
+
ok("설치됨");
|
|
1200
|
+
// 기본 셸 확인: psmux 세션의 기본 셸이 PowerShell인지 cmd.exe인지
|
|
1201
|
+
let shellOk = false;
|
|
1202
|
+
try {
|
|
1203
|
+
const defaultShell = execSync("psmux show-options -g default-shell 2>NUL", { encoding: "utf8", timeout: 3000 }).trim();
|
|
1204
|
+
shellOk = /powershell|pwsh/i.test(defaultShell);
|
|
1205
|
+
} catch {
|
|
1206
|
+
// show-options 실패 시 pwsh/powershell 존재 여부로 판단
|
|
1207
|
+
shellOk = !!which("pwsh") || !!which("powershell.exe");
|
|
1208
|
+
}
|
|
1209
|
+
if (shellOk) {
|
|
1210
|
+
ok("기본 셸: PowerShell");
|
|
1211
|
+
addDoctorCheck(report, { name: "psmux", status: "ok", path: psmuxPath, shell: "powershell" });
|
|
1212
|
+
} else {
|
|
1213
|
+
warn("기본 셸이 cmd.exe — headless 명령 실패 가능");
|
|
1214
|
+
info("수정: psmux set-option -g default-shell \"powershell.exe\"");
|
|
1215
|
+
addDoctorCheck(report, { name: "psmux", status: "issues", path: psmuxPath, shell: "cmd", fix: 'psmux set-option -g default-shell "powershell.exe"' });
|
|
1216
|
+
issues++;
|
|
1217
|
+
}
|
|
1218
|
+
} else {
|
|
1219
|
+
info(`미설치 ${GRAY}(선택 — 멀티모델 병렬 실행에 필요)${RESET}`);
|
|
1220
|
+
info(`설치: winget install marlocarlo.psmux`);
|
|
1221
|
+
addDoctorCheck(report, { name: "psmux", status: "skipped", detail: "미설치 (선택)", fix: "winget install marlocarlo.psmux" });
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// 8. 스킬 설치 상태
|
|
1311
1226
|
section("Skills");
|
|
1312
1227
|
const skillsSrc = join(PKG_ROOT, "skills");
|
|
1313
1228
|
const skillsDst = join(CLAUDE_DIR, "skills");
|
|
@@ -1337,7 +1252,32 @@ async function cmdDoctor(options = {}) {
|
|
|
1337
1252
|
addDoctorCheck(report, { name: "skills", status: "missing", installed: 0, total: 0, fix: "패키지 skills 디렉토리를 확인하세요." });
|
|
1338
1253
|
}
|
|
1339
1254
|
|
|
1340
|
-
//
|
|
1255
|
+
// Stale 스킬 체크
|
|
1256
|
+
const staleSkills = [];
|
|
1257
|
+
const userSkillsDir = join(CLAUDE_DIR, "skills");
|
|
1258
|
+
if (existsSync(userSkillsDir)) {
|
|
1259
|
+
const pkgSkillsDir = join(PKG_ROOT, "skills");
|
|
1260
|
+
const pkgSkills = new Set();
|
|
1261
|
+
if (existsSync(pkgSkillsDir)) {
|
|
1262
|
+
for (const n of readdirSync(pkgSkillsDir)) pkgSkills.add(n);
|
|
1263
|
+
}
|
|
1264
|
+
for (const { alias } of SKILL_ALIASES) pkgSkills.add(alias);
|
|
1265
|
+
|
|
1266
|
+
for (const n of readdirSync(userSkillsDir)) {
|
|
1267
|
+
if (!n.startsWith("tfx-")) continue;
|
|
1268
|
+
if (!pkgSkills.has(n)) staleSkills.push(n);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
if (staleSkills.length > 0) {
|
|
1272
|
+
warn(`구형 스킬 ${staleSkills.length}개 감지: ${staleSkills.join(", ")}`);
|
|
1273
|
+
info("제거: tfx setup 또는 tfx update");
|
|
1274
|
+
addDoctorCheck(report, { name: "stale-skills", status: "issues", skills: staleSkills, fix: "tfx setup" });
|
|
1275
|
+
issues++;
|
|
1276
|
+
} else {
|
|
1277
|
+
addDoctorCheck(report, { name: "stale-skills", status: "ok" });
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// 9. 플러그인 등록
|
|
1341
1281
|
section("Plugin");
|
|
1342
1282
|
const pluginsFile = join(CLAUDE_DIR, "plugins", "installed_plugins.json");
|
|
1343
1283
|
if (existsSync(pluginsFile)) {
|
|
@@ -1355,7 +1295,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1355
1295
|
info("플러그인 시스템 감지 안 됨 — npm 단독 사용");
|
|
1356
1296
|
}
|
|
1357
1297
|
|
|
1358
|
-
//
|
|
1298
|
+
// 10. MCP 인벤토리
|
|
1359
1299
|
section("MCP Inventory");
|
|
1360
1300
|
const mcpCache = join(CLAUDE_DIR, "cache", "mcp-inventory.json");
|
|
1361
1301
|
if (existsSync(mcpCache)) {
|
|
@@ -1424,7 +1364,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1424
1364
|
issues++;
|
|
1425
1365
|
}
|
|
1426
1366
|
|
|
1427
|
-
//
|
|
1367
|
+
// 11. CLI 이슈 트래커
|
|
1428
1368
|
section("CLI Issues");
|
|
1429
1369
|
const issuesFile = join(CLAUDE_DIR, "cache", "cli-issues.jsonl");
|
|
1430
1370
|
if (existsSync(issuesFile)) {
|
|
@@ -1504,7 +1444,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1504
1444
|
ok("이슈 로그 없음 (정상)");
|
|
1505
1445
|
}
|
|
1506
1446
|
|
|
1507
|
-
//
|
|
1447
|
+
// 12. Team Sessions
|
|
1508
1448
|
section("Team Sessions");
|
|
1509
1449
|
const teamSessionReport = inspectTeamSessions();
|
|
1510
1450
|
if (!teamSessionReport.mux) {
|
|
@@ -1554,7 +1494,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1554
1494
|
}
|
|
1555
1495
|
}
|
|
1556
1496
|
|
|
1557
|
-
//
|
|
1497
|
+
// 13. OMC stale team 상태
|
|
1558
1498
|
section("OMC Stale Teams");
|
|
1559
1499
|
const omcTeamReport = inspectStaleOmcTeams({
|
|
1560
1500
|
startDir: process.cwd(),
|
|
@@ -1644,7 +1584,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1644
1584
|
ok("Windows 전용 검사 — 건너뜀");
|
|
1645
1585
|
}
|
|
1646
1586
|
|
|
1647
|
-
//
|
|
1587
|
+
// 14. Stale Teams (Claude teams/ + tasks/ 자동 감지)
|
|
1648
1588
|
section("Stale Teams");
|
|
1649
1589
|
const teamsDir = join(CLAUDE_DIR, "teams");
|
|
1650
1590
|
const tasksDir = join(CLAUDE_DIR, "tasks");
|
package/hub/pipe.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
listPipelineStates,
|
|
19
19
|
readPipelineState,
|
|
20
20
|
} from './pipeline/state.mjs';
|
|
21
|
+
import { safeJsonParse } from './workers/worker-utils.mjs';
|
|
21
22
|
|
|
22
23
|
const DEFAULT_HEARTBEAT_TTL_MS = 60000;
|
|
23
24
|
|
|
@@ -29,14 +30,6 @@ export function getPipePath(sessionId = process.pid) {
|
|
|
29
30
|
return join('/tmp', `triflux-${sessionId}.sock`);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function safeJsonParse(line) {
|
|
33
|
-
try {
|
|
34
|
-
return JSON.parse(line);
|
|
35
|
-
} catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
33
|
function normalizeTopics(topics) {
|
|
41
34
|
if (!Array.isArray(topics)) return [];
|
|
42
35
|
return topics
|