triflux 10.16.0 → 10.17.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/.claude-plugin/marketplace.json +34 -0
- package/.claude-plugin/plugin.json +22 -0
- package/bin/triflux.mjs +78 -27
- package/config/mcp-registry.json +29 -0
- package/hooks/hook-registry.json +1 -1
- package/hooks/keyword-rules.json +12 -0
- package/hooks/safety-guard.mjs +3 -0
- package/hub/cli-adapter-base.mjs +10 -5
- package/hub/lib/hosts-compat.mjs +19 -4
- package/hub/lib/process-utils.mjs +676 -86
- package/hub/lib/ssh-command.mjs +4 -10
- package/hub/router.mjs +20 -3
- package/hub/server.mjs +70 -6
- package/hub/team/agent-map.json +2 -0
- package/hub/team/codex-review.mjs +14 -9
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/psmux.mjs +1 -1
- package/hub/team/swarm-hypervisor.mjs +59 -10
- package/hub/team/worktree-lifecycle.mjs +51 -9
- package/hub/workers/codex-app-server-worker.mjs +4 -6
- package/hub/workers/lib/jsonrpc-stdio.mjs +0 -1
- package/hub/workers/worker-utils.mjs +1 -2
- package/package.json +67 -23
- package/scripts/codex-mcp-gateway-sync.mjs +22 -0
- package/scripts/doctor-diagnose.mjs +24 -11
- package/scripts/lib/mcp-guard-engine.mjs +20 -0
- package/scripts/mcp-cleanup.ps1 +9 -0
- package/scripts/session-stale-cleanup.mjs +102 -1
- package/scripts/setup.mjs +29 -1
- package/scripts/sync-hub-mcp-settings.mjs +38 -1
- package/scripts/test-lock.mjs +134 -36
- package/scripts/tfx-route-post.mjs +5 -1
- package/tui/codex-profile.mjs +459 -0
- package/tui/core.mjs +266 -0
- package/tui/doctor.mjs +375 -0
- package/tui/gemini-profile.mjs +299 -0
- package/tui/monitor-data.mjs +152 -0
- package/tui/monitor.mjs +333 -0
- package/tui/setup.mjs +599 -0
- package/CLAUDE.md +0 -169
- package/references/cli-parameter-reference.md +0 -240
- package/references/codex-plugin-cc-analysis.md +0 -706
- package/references/codex-plugin-cc-code-patterns.md +0 -468
- package/references/hosts.json +0 -46
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "triflux",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator — Codex/Gemini/Claude routing with DAG execution, auto-triage, and cost optimization",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "triflux",
|
|
11
|
+
"description": "Tri-CLI orchestrator for Claude Code. Routes tasks across Claude + Codex + Gemini with consensus intelligence, natural language routing, 42 skills, and cross-model review.",
|
|
12
|
+
"version": "10.17.1",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "tellang"
|
|
15
|
+
},
|
|
16
|
+
"source": {
|
|
17
|
+
"source": "npm",
|
|
18
|
+
"package": "triflux"
|
|
19
|
+
},
|
|
20
|
+
"category": "productivity",
|
|
21
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
22
|
+
"tags": [
|
|
23
|
+
"multi-model",
|
|
24
|
+
"codex",
|
|
25
|
+
"gemini",
|
|
26
|
+
"cli-routing",
|
|
27
|
+
"orchestration",
|
|
28
|
+
"cost-optimization",
|
|
29
|
+
"dag-execution"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"version": "10.17.1"
|
|
34
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "triflux",
|
|
3
|
+
"version": "10.17.1",
|
|
4
|
+
"description": "CLI-first multi-model orchestrator for Claude Code — route tasks to Codex, Gemini, and Claude",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "tellang"
|
|
7
|
+
},
|
|
8
|
+
"repository": "https://github.com/tellang/triflux",
|
|
9
|
+
"homepage": "https://github.com/tellang/triflux",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"plugin",
|
|
14
|
+
"codex",
|
|
15
|
+
"gemini",
|
|
16
|
+
"cli-routing",
|
|
17
|
+
"orchestration",
|
|
18
|
+
"multi-model"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"hooks": "./hooks/hooks.json"
|
|
22
|
+
}
|
package/bin/triflux.mjs
CHANGED
|
@@ -462,14 +462,14 @@ function printJson(payload) {
|
|
|
462
462
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
-
function withConsoleSilenced(enabled, fn) {
|
|
465
|
+
async function withConsoleSilenced(enabled, fn) {
|
|
466
466
|
if (!enabled) return fn();
|
|
467
467
|
const originalLog = console.log;
|
|
468
468
|
const originalError = console.error;
|
|
469
469
|
console.log = () => {};
|
|
470
470
|
console.error = () => {};
|
|
471
471
|
try {
|
|
472
|
-
return fn();
|
|
472
|
+
return await fn();
|
|
473
473
|
} finally {
|
|
474
474
|
console.log = originalLog;
|
|
475
475
|
console.error = originalError;
|
|
@@ -1846,6 +1846,7 @@ async function cmdDoctor(options = {}) {
|
|
|
1846
1846
|
checks: [],
|
|
1847
1847
|
actions: [],
|
|
1848
1848
|
hook_coverage: { total: 0, registered: 0, missing: [] },
|
|
1849
|
+
fsmonitorDaemons: { stale: 0, killed: 0 },
|
|
1849
1850
|
issue_count: 0,
|
|
1850
1851
|
};
|
|
1851
1852
|
|
|
@@ -3126,9 +3127,11 @@ async function cmdDoctor(options = {}) {
|
|
|
3126
3127
|
section("Orphan Processes");
|
|
3127
3128
|
if (process.platform === "win32") {
|
|
3128
3129
|
try {
|
|
3129
|
-
const {
|
|
3130
|
-
|
|
3131
|
-
|
|
3130
|
+
const {
|
|
3131
|
+
cleanupOrphanNodeProcesses,
|
|
3132
|
+
cleanupStaleFsmonitorDaemons,
|
|
3133
|
+
findFsmonitorDaemons,
|
|
3134
|
+
} = await import("../hub/lib/process-utils.mjs");
|
|
3132
3135
|
if (fix) {
|
|
3133
3136
|
const { killed, remaining } = cleanupOrphanNodeProcesses();
|
|
3134
3137
|
if (killed > 0) {
|
|
@@ -3157,6 +3160,55 @@ async function cmdDoctor(options = {}) {
|
|
|
3157
3160
|
ok(`node.exe ${count}개 (정상 범위)`);
|
|
3158
3161
|
}
|
|
3159
3162
|
}
|
|
3163
|
+
|
|
3164
|
+
const fsmonitorStale = findFsmonitorDaemons({
|
|
3165
|
+
minAgeMs: 24 * 60 * 60 * 1000,
|
|
3166
|
+
});
|
|
3167
|
+
let fsmonitorKilled = 0;
|
|
3168
|
+
if (fix && fsmonitorStale.length > 0) {
|
|
3169
|
+
const cleanupResult = cleanupStaleFsmonitorDaemons({
|
|
3170
|
+
minAgeMs: 24 * 60 * 60 * 1000,
|
|
3171
|
+
});
|
|
3172
|
+
fsmonitorKilled = cleanupResult.killed;
|
|
3173
|
+
report.actions.push({
|
|
3174
|
+
type: "git-fsmonitor-cleanup",
|
|
3175
|
+
status:
|
|
3176
|
+
fsmonitorKilled === fsmonitorStale.length ? "ok" : "partial",
|
|
3177
|
+
stale: fsmonitorStale.length,
|
|
3178
|
+
killed: fsmonitorKilled,
|
|
3179
|
+
});
|
|
3180
|
+
warn(
|
|
3181
|
+
`stale git fsmonitor daemon ${fsmonitorKilled}/${fsmonitorStale.length}개 정리`,
|
|
3182
|
+
);
|
|
3183
|
+
} else if (fsmonitorStale.length > 0) {
|
|
3184
|
+
warn(
|
|
3185
|
+
`stale git fsmonitor daemon ${fsmonitorStale.length}개 발견 (24h+). 정리: tfx doctor --fix`,
|
|
3186
|
+
);
|
|
3187
|
+
} else {
|
|
3188
|
+
ok("stale git fsmonitor daemon 없음");
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
report.fsmonitorDaemons = {
|
|
3192
|
+
stale: fsmonitorStale.length,
|
|
3193
|
+
killed: fsmonitorKilled,
|
|
3194
|
+
};
|
|
3195
|
+
addDoctorCheck(report, {
|
|
3196
|
+
name: "fsmonitor-daemons",
|
|
3197
|
+
status: fsmonitorStale.length > 0 ? "warning" : "ok",
|
|
3198
|
+
stale: fsmonitorStale.length,
|
|
3199
|
+
killed: fsmonitorKilled,
|
|
3200
|
+
detail: fsmonitorStale.map((p) => ({
|
|
3201
|
+
pid: p.pid,
|
|
3202
|
+
parentPid: p.parentPid,
|
|
3203
|
+
ageHours: Number((p.ageMs / (60 * 60 * 1000)).toFixed(1)),
|
|
3204
|
+
})),
|
|
3205
|
+
});
|
|
3206
|
+
if (
|
|
3207
|
+
fsmonitorStale.length > 0 &&
|
|
3208
|
+
(!fix || fsmonitorKilled < fsmonitorStale.length)
|
|
3209
|
+
) {
|
|
3210
|
+
issues++;
|
|
3211
|
+
}
|
|
3160
3212
|
} catch (e) {
|
|
3161
3213
|
info(`고아 프로세스 검사 실패: ${e.message}`);
|
|
3162
3214
|
}
|
|
@@ -5073,29 +5125,28 @@ function startHubAfterUpdate(info) {
|
|
|
5073
5125
|
function autoRegisterMcp(mcpUrl, { codexEnabled = false } = {}) {
|
|
5074
5126
|
section("MCP 자동 등록");
|
|
5075
5127
|
|
|
5076
|
-
// Codex — config.json에 기본 disabled 엔트리로
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
warn(`Codex 등록 실패: ${e.message}`);
|
|
5128
|
+
// Codex — config.json에 기본 disabled 엔트리로 등록.
|
|
5129
|
+
// Hub startup must keep the MCP config fresh even on CI/dev machines where
|
|
5130
|
+
// the Codex CLI binary itself is not installed.
|
|
5131
|
+
try {
|
|
5132
|
+
const result = ensureCodexHubServerConfig({
|
|
5133
|
+
mcpUrl,
|
|
5134
|
+
createIfMissing: true,
|
|
5135
|
+
enabled: codexEnabled,
|
|
5136
|
+
});
|
|
5137
|
+
if (!result.ok) throw new Error(result.reason || "unknown");
|
|
5138
|
+
const suffix = which("codex") ? "" : " (CLI 미설치)";
|
|
5139
|
+
if (result.changed) {
|
|
5140
|
+
ok(
|
|
5141
|
+
`Codex: config.json에 등록 완료 (${codexEnabled ? "enabled" : "기본 disabled"})${suffix}`,
|
|
5142
|
+
);
|
|
5143
|
+
} else {
|
|
5144
|
+
ok(
|
|
5145
|
+
`Codex: 이미 등록됨 (${codexEnabled ? "enabled" : "기본 disabled"})${suffix}`,
|
|
5146
|
+
);
|
|
5096
5147
|
}
|
|
5097
|
-
}
|
|
5098
|
-
|
|
5148
|
+
} catch (e) {
|
|
5149
|
+
warn(`Codex 등록 실패: ${e.message}`);
|
|
5099
5150
|
}
|
|
5100
5151
|
|
|
5101
5152
|
// Gemini — settings.json 직접 수정
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "mcp-registry-schema",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"description": "MCP 서버 중앙 레지스트리 — 진실의 원천",
|
|
5
|
+
"defaults": {
|
|
6
|
+
"transport": "hub-url",
|
|
7
|
+
"hub_base": "http://127.0.0.1:27888"
|
|
8
|
+
},
|
|
9
|
+
"servers": {
|
|
10
|
+
"tfx-hub": {
|
|
11
|
+
"transport": "hub-url",
|
|
12
|
+
"url": "http://127.0.0.1:27888/mcp",
|
|
13
|
+
"safe": true,
|
|
14
|
+
"targets": ["claude", "gemini", "codex"],
|
|
15
|
+
"description": "triflux Hub MCP 서버"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"policies": {
|
|
19
|
+
"stdio_action": "replace-with-hub",
|
|
20
|
+
"unknown_server_action": "warn",
|
|
21
|
+
"watched_paths": [
|
|
22
|
+
"~/.gemini/settings.json",
|
|
23
|
+
"~/.codex/config.toml",
|
|
24
|
+
"~/.claude/settings.json",
|
|
25
|
+
"~/.claude/settings.local.json",
|
|
26
|
+
".mcp.json"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
package/hooks/hook-registry.json
CHANGED
|
@@ -244,7 +244,7 @@
|
|
|
244
244
|
"matcher": "*",
|
|
245
245
|
"command": "powershell -NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File \"${HOME}/.claude/scripts/mcp-cleanup.ps1\"",
|
|
246
246
|
"priority": 100,
|
|
247
|
-
"enabled":
|
|
247
|
+
"enabled": false,
|
|
248
248
|
"timeout": 8,
|
|
249
249
|
"blocking": false,
|
|
250
250
|
"description": "MCP 고아 프로세스 정리"
|
package/hooks/keyword-rules.json
CHANGED
|
@@ -97,6 +97,18 @@
|
|
|
97
97
|
"source": "(?:정리해|슬롭|클린업)",
|
|
98
98
|
"flags": "i"
|
|
99
99
|
},
|
|
100
|
+
{
|
|
101
|
+
"source": "(?:병렬|동시에|parallel|concurrent)",
|
|
102
|
+
"flags": "i"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"source": "(?:점검|진단|확인해)",
|
|
106
|
+
"flags": "i"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"source": "(?:계속해|이어서|진행해)",
|
|
110
|
+
"flags": "i"
|
|
111
|
+
},
|
|
100
112
|
{
|
|
101
113
|
"source": "\\b(?:implement|build|fix|review|test|plan|analyze)\\b",
|
|
102
114
|
"flags": "i"
|
package/hooks/safety-guard.mjs
CHANGED
|
@@ -123,9 +123,12 @@ function getWindowsHostIds() {
|
|
|
123
123
|
ids.add(name);
|
|
124
124
|
if (cfg.tailscale?.ip) ids.add(cfg.tailscale.ip);
|
|
125
125
|
if (cfg.tailscale?.dns) ids.add(cfg.tailscale.dns);
|
|
126
|
+
if (cfg.ssh?.host) ids.add(cfg.ssh.host);
|
|
126
127
|
if (cfg.ssh?.user) {
|
|
127
128
|
ids.add(`${cfg.ssh.user}@${name}`);
|
|
128
129
|
if (cfg.tailscale?.ip) ids.add(`${cfg.ssh.user}@${cfg.tailscale.ip}`);
|
|
130
|
+
if (cfg.tailscale?.dns) ids.add(`${cfg.ssh.user}@${cfg.tailscale.dns}`);
|
|
131
|
+
if (cfg.ssh?.host) ids.add(`${cfg.ssh.user}@${cfg.ssh.host}`);
|
|
129
132
|
}
|
|
130
133
|
}
|
|
131
134
|
} catch {
|
package/hub/cli-adapter-base.mjs
CHANGED
|
@@ -72,6 +72,11 @@ let _cachedVersion = null;
|
|
|
72
72
|
*/
|
|
73
73
|
export function getCodexVersion() {
|
|
74
74
|
if (_cachedVersion !== null) return _cachedVersion;
|
|
75
|
+
const override = Number(process.env.TFX_CODEX_VERSION_MINOR);
|
|
76
|
+
if (Number.isFinite(override) && override > 0) {
|
|
77
|
+
_cachedVersion = override;
|
|
78
|
+
return _cachedVersion;
|
|
79
|
+
}
|
|
75
80
|
try {
|
|
76
81
|
const out = execSync("codex --version", {
|
|
77
82
|
encoding: "utf8",
|
|
@@ -80,7 +85,10 @@ export function getCodexVersion() {
|
|
|
80
85
|
const match = out.match(/(\d+)\.(\d+)\.(\d+)/);
|
|
81
86
|
_cachedVersion = match ? Number.parseInt(match[2], 10) : 0;
|
|
82
87
|
} catch {
|
|
83
|
-
|
|
88
|
+
// Command builders should remain stable in CI even when the real Codex
|
|
89
|
+
// CLI is absent. Runtime preflight still reports/install-gates Codex
|
|
90
|
+
// separately; this fallback only selects the modern argv shape.
|
|
91
|
+
_cachedVersion = 117;
|
|
84
92
|
}
|
|
85
93
|
return _cachedVersion;
|
|
86
94
|
}
|
|
@@ -182,10 +190,7 @@ export function buildExecCommand(prompt, resultFile = null, opts = {}) {
|
|
|
182
190
|
// ── Sleep ───────────────────────────────────────────────────────
|
|
183
191
|
|
|
184
192
|
export function sleep(ms) {
|
|
185
|
-
return new Promise((resolve) =>
|
|
186
|
-
const timer = setTimeout(resolve, ms);
|
|
187
|
-
timer.unref?.();
|
|
188
|
-
});
|
|
193
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
// ── Result factory ──────────────────────────────────────────────
|
package/hub/lib/hosts-compat.mjs
CHANGED
|
@@ -133,8 +133,20 @@ function normalizeLastProbe(rawProbe) {
|
|
|
133
133
|
return Object.keys(probe).length > 0 ? probe : null;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
function normalizeResources(rawHost) {
|
|
137
|
+
const rawResources =
|
|
138
|
+
rawHost.resources && typeof rawHost.resources === "object"
|
|
139
|
+
? rawHost.resources
|
|
140
|
+
: {};
|
|
141
|
+
const rawSpecs =
|
|
142
|
+
rawHost.specs && typeof rawHost.specs === "object" ? rawHost.specs : {};
|
|
143
|
+
return { ...rawResources, ...rawSpecs };
|
|
144
|
+
}
|
|
145
|
+
|
|
136
146
|
export function normalizeHost(rawHost = {}, name = "") {
|
|
137
147
|
const sshUser = rawHost.ssh_user || rawHost.ssh?.user || rawHost.user || null;
|
|
148
|
+
const sshHost = rawHost.ssh?.host || rawHost.host || null;
|
|
149
|
+
const resources = normalizeResources(rawHost);
|
|
138
150
|
const tailscale = {
|
|
139
151
|
ip: rawHost.tailscale?.ip || null,
|
|
140
152
|
dns: rawHost.tailscale?.dns || null,
|
|
@@ -167,15 +179,14 @@ export function normalizeHost(rawHost = {}, name = "") {
|
|
|
167
179
|
ssh: {
|
|
168
180
|
...(rawHost.ssh && typeof rawHost.ssh === "object" ? rawHost.ssh : {}),
|
|
169
181
|
user: sshUser,
|
|
182
|
+
host: sshHost,
|
|
170
183
|
},
|
|
171
184
|
tailscale,
|
|
172
185
|
capabilities,
|
|
173
186
|
capabilities_v2,
|
|
174
187
|
last_probe: normalizeLastProbe(rawHost.last_probe),
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
? { ...rawHost.specs }
|
|
178
|
-
: {},
|
|
188
|
+
resources,
|
|
189
|
+
specs: { ...resources },
|
|
179
190
|
raw: { ...rawHost },
|
|
180
191
|
};
|
|
181
192
|
}
|
|
@@ -234,6 +245,7 @@ export function resolveHost(nameOrAlias, repoRoot) {
|
|
|
234
245
|
...host.aliases,
|
|
235
246
|
host.tailscale.ip,
|
|
236
247
|
host.tailscale.dns,
|
|
248
|
+
host.ssh.host,
|
|
237
249
|
host.ssh_user ? `${host.ssh_user}@${name}` : null,
|
|
238
250
|
host.ssh_user && host.tailscale.ip
|
|
239
251
|
? `${host.ssh_user}@${host.tailscale.ip}`
|
|
@@ -241,6 +253,9 @@ export function resolveHost(nameOrAlias, repoRoot) {
|
|
|
241
253
|
host.ssh_user && host.tailscale.dns
|
|
242
254
|
? `${host.ssh_user}@${host.tailscale.dns}`
|
|
243
255
|
: null,
|
|
256
|
+
host.ssh_user && host.ssh.host
|
|
257
|
+
? `${host.ssh_user}@${host.ssh.host}`
|
|
258
|
+
: null,
|
|
244
259
|
]);
|
|
245
260
|
for (const alias of aliases) {
|
|
246
261
|
if (alias && String(alias).toLowerCase() === lowered) {
|