triflux 9.4.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/bin/triflux.mjs +141 -17
- package/hub/team/psmux.mjs +43 -7
- package/hub/team/tui-viewer.mjs +1 -1
- package/package.json +1 -1
- package/scripts/session-spawn-helper.mjs +1 -1
- package/scripts/setup.mjs +93 -3
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, statSync, openSync, closeSync } from "fs";
|
|
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";
|
|
@@ -13,9 +13,9 @@ import { cleanupStaleOmcTeams, inspectStaleOmcTeams } from "../hub/team/staleSta
|
|
|
13
13
|
import { getPipelineStateDbPath } from "../hub/pipeline/state.mjs";
|
|
14
14
|
import { ensureGeminiProfiles } from "../scripts/lib/gemini-profiles.mjs";
|
|
15
15
|
import {
|
|
16
|
-
SYNC_MAP, SKILL_ALIASES, REQUIRED_CODEX_PROFILES,
|
|
16
|
+
SYNC_MAP, SKILL_ALIASES, REQUIRED_CODEX_PROFILES, LEGACY_CODEX_MODELS,
|
|
17
17
|
syncAliasedSkillDir, hasProfileSection, replaceProfileSection,
|
|
18
|
-
ensureCodexProfiles, getVersion,
|
|
18
|
+
ensureCodexProfiles, getVersion, cleanupStaleSkills, DEPRECATED_SKILLS,
|
|
19
19
|
} from "../scripts/setup.mjs";
|
|
20
20
|
|
|
21
21
|
const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -720,6 +720,11 @@ function cmdSetup(options = {}) {
|
|
|
720
720
|
} else {
|
|
721
721
|
ok(`스킬: ${skillTotal}개 최신 상태`);
|
|
722
722
|
}
|
|
723
|
+
// Stale 스킬 정리 (패키지에서 제거된 tfx-* 스킬 삭제)
|
|
724
|
+
const staleCleanup = cleanupStaleSkills(skillsDst, skillsSrc);
|
|
725
|
+
if (staleCleanup.count > 0) {
|
|
726
|
+
ok(`구형 스킬 ${staleCleanup.count}개 제거: ${staleCleanup.removed.join(", ")}`);
|
|
727
|
+
}
|
|
723
728
|
}
|
|
724
729
|
|
|
725
730
|
// ── 결과 추적 ──
|
|
@@ -822,22 +827,55 @@ function cmdSetup(options = {}) {
|
|
|
822
827
|
}
|
|
823
828
|
}
|
|
824
829
|
|
|
825
|
-
// Star request (버전 게이팅)
|
|
830
|
+
// Star request (버전 게이팅 + 인터랙티브 [y/n])
|
|
826
831
|
const showStar = STAR_PROMPT_VERSIONS.length === 0 || STAR_PROMPT_VERSIONS.includes(PKG.version);
|
|
827
832
|
if (showStar) {
|
|
833
|
+
let ghOk = false;
|
|
828
834
|
try {
|
|
829
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;
|
|
830
845
|
try {
|
|
831
846
|
execFileSync("gh", ["api", "user/starred/tellang/triflux"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
847
|
+
alreadyStarred = true;
|
|
848
|
+
} catch {}
|
|
849
|
+
|
|
850
|
+
if (alreadyStarred) {
|
|
832
851
|
console.log();
|
|
833
852
|
ok(`이미 함께하고 계시군요. ${AMBER}⭐${RESET}`);
|
|
834
|
-
}
|
|
853
|
+
} else {
|
|
854
|
+
// 인터랙티브 confirm
|
|
835
855
|
console.log();
|
|
836
|
-
|
|
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
|
+
}
|
|
837
878
|
}
|
|
838
|
-
} catch {
|
|
839
|
-
console.log();
|
|
840
|
-
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
841
879
|
}
|
|
842
880
|
}
|
|
843
881
|
|
|
@@ -1079,7 +1117,9 @@ async function cmdDoctor(options = {}) {
|
|
|
1079
1117
|
const missingProfiles = [];
|
|
1080
1118
|
for (const profile of REQUIRED_CODEX_PROFILES) {
|
|
1081
1119
|
if (hasProfileSection(codexConfig, profile.name)) {
|
|
1082
|
-
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}`);
|
|
1083
1123
|
} else {
|
|
1084
1124
|
missingProfiles.push(profile.name);
|
|
1085
1125
|
warn(`${profile.name}: 미설정`);
|
|
@@ -1099,6 +1139,18 @@ async function cmdDoctor(options = {}) {
|
|
|
1099
1139
|
issues++;
|
|
1100
1140
|
}
|
|
1101
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
|
+
|
|
1102
1154
|
// 5. Gemini CLI
|
|
1103
1155
|
section(`Gemini CLI ${BLUE}●${RESET}`);
|
|
1104
1156
|
const geminiCli = checkCliCrossShell("gemini", "npm install -g @google/gemini-cli");
|
|
@@ -1111,6 +1163,22 @@ async function cmdDoctor(options = {}) {
|
|
|
1111
1163
|
});
|
|
1112
1164
|
// API 키 검사 제거 — bash exec 기반이므로 API 키 불필요
|
|
1113
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
|
+
|
|
1114
1182
|
// 6. Claude Code
|
|
1115
1183
|
section(`Claude Code ${AMBER}●${RESET}`);
|
|
1116
1184
|
const claudePath = which("claude");
|
|
@@ -1123,7 +1191,38 @@ async function cmdDoctor(options = {}) {
|
|
|
1123
1191
|
issues++;
|
|
1124
1192
|
}
|
|
1125
1193
|
|
|
1126
|
-
|
|
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. 스킬 설치 상태
|
|
1127
1226
|
section("Skills");
|
|
1128
1227
|
const skillsSrc = join(PKG_ROOT, "skills");
|
|
1129
1228
|
const skillsDst = join(CLAUDE_DIR, "skills");
|
|
@@ -1153,7 +1252,32 @@ async function cmdDoctor(options = {}) {
|
|
|
1153
1252
|
addDoctorCheck(report, { name: "skills", status: "missing", installed: 0, total: 0, fix: "패키지 skills 디렉토리를 확인하세요." });
|
|
1154
1253
|
}
|
|
1155
1254
|
|
|
1156
|
-
//
|
|
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. 플러그인 등록
|
|
1157
1281
|
section("Plugin");
|
|
1158
1282
|
const pluginsFile = join(CLAUDE_DIR, "plugins", "installed_plugins.json");
|
|
1159
1283
|
if (existsSync(pluginsFile)) {
|
|
@@ -1171,7 +1295,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1171
1295
|
info("플러그인 시스템 감지 안 됨 — npm 단독 사용");
|
|
1172
1296
|
}
|
|
1173
1297
|
|
|
1174
|
-
//
|
|
1298
|
+
// 10. MCP 인벤토리
|
|
1175
1299
|
section("MCP Inventory");
|
|
1176
1300
|
const mcpCache = join(CLAUDE_DIR, "cache", "mcp-inventory.json");
|
|
1177
1301
|
if (existsSync(mcpCache)) {
|
|
@@ -1240,7 +1364,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1240
1364
|
issues++;
|
|
1241
1365
|
}
|
|
1242
1366
|
|
|
1243
|
-
//
|
|
1367
|
+
// 11. CLI 이슈 트래커
|
|
1244
1368
|
section("CLI Issues");
|
|
1245
1369
|
const issuesFile = join(CLAUDE_DIR, "cache", "cli-issues.jsonl");
|
|
1246
1370
|
if (existsSync(issuesFile)) {
|
|
@@ -1320,7 +1444,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1320
1444
|
ok("이슈 로그 없음 (정상)");
|
|
1321
1445
|
}
|
|
1322
1446
|
|
|
1323
|
-
//
|
|
1447
|
+
// 12. Team Sessions
|
|
1324
1448
|
section("Team Sessions");
|
|
1325
1449
|
const teamSessionReport = inspectTeamSessions();
|
|
1326
1450
|
if (!teamSessionReport.mux) {
|
|
@@ -1370,7 +1494,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1370
1494
|
}
|
|
1371
1495
|
}
|
|
1372
1496
|
|
|
1373
|
-
//
|
|
1497
|
+
// 13. OMC stale team 상태
|
|
1374
1498
|
section("OMC Stale Teams");
|
|
1375
1499
|
const omcTeamReport = inspectStaleOmcTeams({
|
|
1376
1500
|
startDir: process.cwd(),
|
|
@@ -1460,7 +1584,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1460
1584
|
ok("Windows 전용 검사 — 건너뜀");
|
|
1461
1585
|
}
|
|
1462
1586
|
|
|
1463
|
-
//
|
|
1587
|
+
// 14. Stale Teams (Claude teams/ + tasks/ 자동 감지)
|
|
1464
1588
|
section("Stale Teams");
|
|
1465
1589
|
const teamsDir = join(CLAUDE_DIR, "teams");
|
|
1466
1590
|
const tasksDir = join(CLAUDE_DIR, "tasks");
|
package/hub/team/psmux.mjs
CHANGED
|
@@ -28,6 +28,23 @@ const PSMUX_BIN = (() => {
|
|
|
28
28
|
})();
|
|
29
29
|
const GIT_BASH = process.env.GIT_BASH_PATH || "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
30
30
|
const IS_WINDOWS = process.platform === "win32";
|
|
31
|
+
|
|
32
|
+
/** Windows psmux 세션의 기본 셸을 PowerShell로 강제한다 (pwsh7 우선, ps5 fallback). */
|
|
33
|
+
const PWSH_BIN = (() => {
|
|
34
|
+
if (!IS_WINDOWS) return "";
|
|
35
|
+
if (process.env.PSMUX_SHELL) return process.env.PSMUX_SHELL;
|
|
36
|
+
// pwsh 7 우선
|
|
37
|
+
try {
|
|
38
|
+
childProcess.execFileSync("pwsh", ["-NoLogo", "-NoProfile", "-Command", "exit 0"], { stdio: "ignore", timeout: 3000, windowsHide: true });
|
|
39
|
+
return "pwsh";
|
|
40
|
+
} catch { /* not found */ }
|
|
41
|
+
// powershell 5 fallback
|
|
42
|
+
try {
|
|
43
|
+
childProcess.execFileSync("powershell.exe", ["-NoLogo", "-NoProfile", "-Command", "exit 0"], { stdio: "ignore", timeout: 3000, windowsHide: true });
|
|
44
|
+
return "powershell.exe";
|
|
45
|
+
} catch { /* not found */ }
|
|
46
|
+
return ""; // 둘 다 없으면 psmux 기본 셸 사용
|
|
47
|
+
})();
|
|
31
48
|
const PSMUX_TIMEOUT_MS = 10000;
|
|
32
49
|
const COMPLETION_PREFIX = "__TRIFLUX_DONE__:";
|
|
33
50
|
const CAPTURE_ROOT = process.env.PSMUX_CAPTURE_ROOT || join(tmpdir(), "psmux-steering");
|
|
@@ -145,7 +162,15 @@ function randomToken(prefix) {
|
|
|
145
162
|
|
|
146
163
|
function ensurePsmuxInstalled() {
|
|
147
164
|
if (!hasPsmux()) {
|
|
148
|
-
throw new Error(
|
|
165
|
+
throw new Error(
|
|
166
|
+
"psmux가 설치되어 있지 않습니다.\n\n" +
|
|
167
|
+
"psmux는 Codex/Gemini CLI를 병렬 세션으로 실행하는 터미널 멀티플렉서입니다.\n" +
|
|
168
|
+
"설치 방법 (택 1):\n" +
|
|
169
|
+
" winget install marlocarlo.psmux\n" +
|
|
170
|
+
" scoop install psmux\n" +
|
|
171
|
+
" npm install -g psmux\n\n" +
|
|
172
|
+
"설치 후 터미널을 재시작하세요."
|
|
173
|
+
);
|
|
149
174
|
}
|
|
150
175
|
}
|
|
151
176
|
|
|
@@ -409,7 +434,7 @@ export function createPsmuxSession(sessionName, opts = {}) {
|
|
|
409
434
|
const limitedPaneCount = layout === "2x2" ? Math.min(paneCount, 4) : paneCount;
|
|
410
435
|
const sessionTarget = `${sessionName}:0`;
|
|
411
436
|
|
|
412
|
-
const
|
|
437
|
+
const newSessionArgs = [
|
|
413
438
|
"new-session",
|
|
414
439
|
"-d",
|
|
415
440
|
"-P",
|
|
@@ -421,7 +446,15 @@ export function createPsmuxSession(sessionName, opts = {}) {
|
|
|
421
446
|
"220",
|
|
422
447
|
"-y",
|
|
423
448
|
"55",
|
|
424
|
-
]
|
|
449
|
+
];
|
|
450
|
+
// Windows: psmux 기본 셸이 cmd.exe일 수 있으므로 PowerShell 강제
|
|
451
|
+
if (PWSH_BIN) newSessionArgs.push(PWSH_BIN, "-NoLogo", "-NoProfile");
|
|
452
|
+
const leadPane = psmuxExec(newSessionArgs);
|
|
453
|
+
|
|
454
|
+
// split-window로 생성되는 pane도 동일 셸 사용
|
|
455
|
+
if (PWSH_BIN) {
|
|
456
|
+
try { psmuxExec(["set-option", "-t", sessionName, "default-command", `${PWSH_BIN} -NoLogo -NoProfile`]); } catch { /* 미지원 시 무시 */ }
|
|
457
|
+
}
|
|
425
458
|
|
|
426
459
|
if (layout === "2x2" && limitedPaneCount >= 3) {
|
|
427
460
|
const rightPane = psmuxExec([
|
|
@@ -976,7 +1009,10 @@ export async function waitForCompletion(sessionName, paneNameOrTarget, token, ti
|
|
|
976
1009
|
*/
|
|
977
1010
|
export function spawnWorker(sessionName, workerName, cmd) {
|
|
978
1011
|
if (!hasPsmux()) {
|
|
979
|
-
throw new Error(
|
|
1012
|
+
throw new Error(
|
|
1013
|
+
"psmux가 설치되어 있지 않습니다.\n" +
|
|
1014
|
+
"설치: winget install marlocarlo.psmux (또는 scoop install psmux / npm i -g psmux)"
|
|
1015
|
+
);
|
|
980
1016
|
}
|
|
981
1017
|
|
|
982
1018
|
// remain-on-exit: 종료된 pane이 즉시 사라지는 것 방지
|
|
@@ -1017,7 +1053,7 @@ export function spawnWorker(sessionName, workerName, cmd) {
|
|
|
1017
1053
|
*/
|
|
1018
1054
|
export function getWorkerStatus(sessionName, workerName) {
|
|
1019
1055
|
if (!hasPsmux()) {
|
|
1020
|
-
throw new Error("psmux
|
|
1056
|
+
throw new Error("psmux 미설치. 설치: winget install marlocarlo.psmux (또는 npm i -g psmux)");
|
|
1021
1057
|
}
|
|
1022
1058
|
try {
|
|
1023
1059
|
const pane = resolvePane(sessionName, workerName);
|
|
@@ -1042,7 +1078,7 @@ export function getWorkerStatus(sessionName, workerName) {
|
|
|
1042
1078
|
*/
|
|
1043
1079
|
export function killWorker(sessionName, workerName) {
|
|
1044
1080
|
if (!hasPsmux()) {
|
|
1045
|
-
throw new Error("psmux
|
|
1081
|
+
throw new Error("psmux 미설치. 설치: winget install marlocarlo.psmux (또는 npm i -g psmux)");
|
|
1046
1082
|
}
|
|
1047
1083
|
try {
|
|
1048
1084
|
const { paneId, status } = getWorkerStatus(sessionName, workerName);
|
|
@@ -1101,7 +1137,7 @@ export function killWorker(sessionName, workerName) {
|
|
|
1101
1137
|
*/
|
|
1102
1138
|
export function captureWorkerOutput(sessionName, workerName, lines = 50) {
|
|
1103
1139
|
if (!hasPsmux()) {
|
|
1104
|
-
throw new Error("psmux
|
|
1140
|
+
throw new Error("psmux 미설치. 설치: winget install marlocarlo.psmux (또는 npm i -g psmux)");
|
|
1105
1141
|
}
|
|
1106
1142
|
try {
|
|
1107
1143
|
const { paneId } = getWorkerStatus(sessionName, workerName);
|
package/hub/team/tui-viewer.mjs
CHANGED
|
@@ -35,7 +35,7 @@ try {
|
|
|
35
35
|
execFileSync("psmux", ["--version"], { encoding: "utf8", timeout: 2000 });
|
|
36
36
|
} catch {
|
|
37
37
|
process.stderr.write(
|
|
38
|
-
"ERROR: psmux
|
|
38
|
+
"ERROR: psmux 미설치. 설치: winget install marlocarlo.psmux (또는 npm i -g psmux)\n",
|
|
39
39
|
);
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
package/package.json
CHANGED
|
@@ -155,7 +155,7 @@ async function main() {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
if (!hasPsmux()) {
|
|
158
|
-
process.stderr.write("ERROR: psmux
|
|
158
|
+
process.stderr.write("ERROR: psmux 미설치. 설치: winget install marlocarlo.psmux (또는 npm i -g psmux)\n");
|
|
159
159
|
process.exit(1);
|
|
160
160
|
}
|
|
161
161
|
|
package/scripts/setup.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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";
|
|
7
|
+
import { copyFileSync, mkdirSync, readFileSync, writeFileSync, readdirSync, existsSync, chmodSync, unlinkSync, rmSync } from "fs";
|
|
8
8
|
import { join, dirname } from "path";
|
|
9
9
|
import { homedir } from "os";
|
|
10
10
|
import { spawn, execFileSync } from "child_process";
|
|
@@ -52,6 +52,7 @@ const REQUIRED_CODEX_PROFILES = [
|
|
|
52
52
|
'model = "gpt-5.3-codex-spark"',
|
|
53
53
|
'model_reasoning_effort = "low"',
|
|
54
54
|
],
|
|
55
|
+
proOnly: true, // Pro 플랜 전용 — Plus/기본에서는 미동작
|
|
55
56
|
},
|
|
56
57
|
];
|
|
57
58
|
|
|
@@ -62,6 +63,54 @@ const SKILL_ALIASES = [
|
|
|
62
63
|
},
|
|
63
64
|
];
|
|
64
65
|
|
|
66
|
+
/** 패키지에서 제거된 스킬 목록 — setup/update 시 ~/.claude/skills/에서 자동 삭제 */
|
|
67
|
+
const DEPRECATED_SKILLS = [
|
|
68
|
+
"tfx-eval",
|
|
69
|
+
"tfx-learn",
|
|
70
|
+
"tfx-wrapup",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/** 마이그레이션 대상 구형 Codex 모델 — 이 모델을 사용하는 프로필을 감지하여 안내 */
|
|
74
|
+
const LEGACY_CODEX_MODELS = ["o4-mini", "o3", "o3-pro", "o1", "o1-mini", "o1-pro", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "codex-mini-latest"];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* ~/.claude/skills/ 에서 패키지에 없는 stale tfx-* 스킬을 제거한다.
|
|
78
|
+
* @param {string} skillsDst - ~/.claude/skills/ 경로
|
|
79
|
+
* @param {string} skillsSrc - 패키지의 skills/ 경로
|
|
80
|
+
* @returns {{ removed: string[], count: number }}
|
|
81
|
+
*/
|
|
82
|
+
function cleanupStaleSkills(skillsDst, skillsSrc) {
|
|
83
|
+
const removed = [];
|
|
84
|
+
if (!existsSync(skillsDst)) return { removed, count: 0 };
|
|
85
|
+
|
|
86
|
+
const packageSkills = new Set();
|
|
87
|
+
if (existsSync(skillsSrc)) {
|
|
88
|
+
for (const name of readdirSync(skillsSrc)) {
|
|
89
|
+
packageSkills.add(name);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// aliases도 유효한 스킬로 등록
|
|
93
|
+
for (const { alias } of SKILL_ALIASES) {
|
|
94
|
+
packageSkills.add(alias);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (const name of readdirSync(skillsDst)) {
|
|
98
|
+
// tfx- 접두사가 아닌 스킬은 사용자 커스텀 — 건드리지 않음
|
|
99
|
+
if (!name.startsWith("tfx-")) continue;
|
|
100
|
+
if (packageSkills.has(name)) continue;
|
|
101
|
+
|
|
102
|
+
// 패키지에 없는 tfx-* 스킬 → 삭제
|
|
103
|
+
const skillDir = join(skillsDst, name);
|
|
104
|
+
try {
|
|
105
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
106
|
+
removed.push(name);
|
|
107
|
+
} catch {
|
|
108
|
+
// 삭제 실패 시 무시
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { removed, count: removed.length };
|
|
112
|
+
}
|
|
113
|
+
|
|
65
114
|
function buildAliasedSkillContent(srcContent, { alias, source }) {
|
|
66
115
|
return srcContent
|
|
67
116
|
.replace(/^name:\s*.+$/m, `name: ${alias}`)
|
|
@@ -366,7 +415,9 @@ export {
|
|
|
366
415
|
replaceProfileSection, hasProfileSection, escapeRegExp, detectDevMode,
|
|
367
416
|
SYNC_MAP, BREADCRUMB_PATH, PLUGIN_ROOT, CLAUDE_DIR,
|
|
368
417
|
SKILL_ALIASES, REQUIRED_CODEX_PROFILES,
|
|
418
|
+
DEPRECATED_SKILLS, LEGACY_CODEX_MODELS,
|
|
369
419
|
buildAliasedSkillContent, syncAliasedSkillDir, getVersion, ensureCodexProfiles,
|
|
420
|
+
cleanupStaleSkills,
|
|
370
421
|
};
|
|
371
422
|
|
|
372
423
|
async function main() {
|
|
@@ -769,24 +820,49 @@ if (existsSync(HUB_PID_FILE)) {
|
|
|
769
820
|
}
|
|
770
821
|
|
|
771
822
|
// ── psmux 자동 설치 (Windows, headless 모드용) ──
|
|
823
|
+
// psmux: Windows용 터미널 멀티플렉서. Codex/Gemini CLI를 병렬 세션으로 실행할 때 필요.
|
|
824
|
+
// 없어도 triflux 기본 기능은 동작하지만, headless 멀티모델 오케스트레이션이 비활성화됨.
|
|
772
825
|
|
|
826
|
+
let psmuxInstalled = false;
|
|
773
827
|
if (process.platform === "win32") {
|
|
774
828
|
try {
|
|
775
829
|
execFileSync("where", ["psmux"], { stdio: "ignore" });
|
|
830
|
+
psmuxInstalled = true;
|
|
776
831
|
} catch {
|
|
777
832
|
// psmux 미설치 — winget으로 자동 설치 시도
|
|
778
|
-
console.log(" psmux 미설치 —
|
|
833
|
+
console.log(" psmux 미설치 — 자동 설치 시도 중...");
|
|
779
834
|
try {
|
|
780
835
|
execFileSync("winget", ["install", "--id", "marlocarlo.psmux", "--accept-package-agreements", "--accept-source-agreements"], {
|
|
781
836
|
stdio: ["ignore", "pipe", "pipe"],
|
|
782
837
|
timeout: 60000,
|
|
783
838
|
});
|
|
784
839
|
console.log(" \x1b[32m✓\x1b[0m psmux 설치 완료");
|
|
840
|
+
psmuxInstalled = true;
|
|
785
841
|
synced++;
|
|
786
842
|
} catch {
|
|
787
|
-
console.log(
|
|
843
|
+
console.log([
|
|
844
|
+
" \x1b[33m⚠\x1b[0m psmux 자동 설치 실패 — 수동 설치 방법:",
|
|
845
|
+
" \x1b[36m옵션 1:\x1b[0m winget install marlocarlo.psmux",
|
|
846
|
+
" \x1b[36m옵션 2:\x1b[0m scoop install psmux",
|
|
847
|
+
" \x1b[36m옵션 3:\x1b[0m npm install -g psmux",
|
|
848
|
+
" \x1b[2m(없어도 기본 기능은 동작합니다 — 멀티모델 병렬 실행만 비활성화)\x1b[0m",
|
|
849
|
+
].join("\n"));
|
|
788
850
|
}
|
|
789
851
|
}
|
|
852
|
+
} else {
|
|
853
|
+
// non-Windows: tmux 사용 (psmux 불필요)
|
|
854
|
+
psmuxInstalled = true;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// ── stale 스킬 정리 (패키지에서 제거된 tfx-* 스킬 삭제) ──
|
|
858
|
+
{
|
|
859
|
+
const skillsDst = join(CLAUDE_DIR, "skills");
|
|
860
|
+
const skillsSrc = join(PLUGIN_ROOT, "skills");
|
|
861
|
+
const cleanup = cleanupStaleSkills(skillsDst, skillsSrc);
|
|
862
|
+
if (cleanup.count > 0) {
|
|
863
|
+
console.log(` \x1b[32m✓\x1b[0m ${cleanup.count}개 구형 스킬 제거: ${cleanup.removed.join(", ")}`);
|
|
864
|
+
synced++;
|
|
865
|
+
}
|
|
790
866
|
}
|
|
791
867
|
|
|
792
868
|
// ── HUD 에러 캐시 자동 클리어 (업데이트/재설치 시) ──
|
|
@@ -907,6 +983,7 @@ ${B}╚════════════════════════
|
|
|
907
983
|
${G}✓${R} hud-qos-status → ~/.claude/hud/
|
|
908
984
|
${G}✓${R} ${synced > 0 ? synced + " files synced" : "all files up to date"}
|
|
909
985
|
${G}✓${R} HUD statusLine → settings.json
|
|
986
|
+
${psmuxInstalled ? `${G}✓${R} psmux → headless 멀티모델 오케스트레이션` : `${Y}○${R} psmux 미설치 → ${D}winget install marlocarlo.psmux${R} ${D}(선택)${R}`}
|
|
910
987
|
|
|
911
988
|
${B}Commands:${R}
|
|
912
989
|
${C}triflux${R} setup 파일 동기화 + HUD 설정
|
|
@@ -930,6 +1007,19 @@ ${B}Skills (Claude Code):${R}
|
|
|
930
1007
|
${Y}!${R} 세션 재시작 후 스킬이 활성화됩니다
|
|
931
1008
|
${D}https://github.com/tellang/triflux${R}
|
|
932
1009
|
`);
|
|
1010
|
+
|
|
1011
|
+
// ── GitHub Star 체크 (비인터랙티브 — postinstall에서는 confirm 불가) ──
|
|
1012
|
+
try {
|
|
1013
|
+
execFileSync("gh", ["auth", "status"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1014
|
+
try {
|
|
1015
|
+
execFileSync("gh", ["api", "user/starred/tellang/triflux"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1016
|
+
console.log(` ${G}⭐${R} 이미 함께하고 계시군요.`);
|
|
1017
|
+
} catch {
|
|
1018
|
+
console.log(` ${Y}⭐${R} 하나가 큰 차이를 만듭니다. ${D}https://github.com/tellang/triflux${R}`);
|
|
1019
|
+
}
|
|
1020
|
+
} catch {
|
|
1021
|
+
// gh 미설치/미인증 — 무시
|
|
1022
|
+
}
|
|
933
1023
|
}
|
|
934
1024
|
|
|
935
1025
|
process.exit(0);
|