triflux 10.26.0 → 10.28.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.
Files changed (53) hide show
  1. package/README.ko.md +30 -25
  2. package/README.md +23 -19
  3. package/bin/triflux.mjs +327 -27
  4. package/config/mcp-registry.json +10 -6
  5. package/docs/assets/architecture.svg +211 -0
  6. package/docs/assets/consensus-flow.svg +125 -0
  7. package/docs/assets/deep-vs-light.svg +193 -0
  8. package/docs/assets/demo-dark.gif +0 -0
  9. package/docs/assets/demo-light.gif +0 -0
  10. package/docs/assets/demo-multi.gif +0 -0
  11. package/docs/assets/demo-psmux.gif +0 -0
  12. package/docs/assets/logo-dark.svg +29 -0
  13. package/docs/assets/logo-light.svg +29 -0
  14. package/docs/assets/social-preview.png +0 -0
  15. package/docs/assets/social-preview.svg +80 -0
  16. package/hub/lib/tfx-route-args.mjs +10 -3
  17. package/hub/team/claude-daemon-control.mjs +214 -0
  18. package/hub/team/claude-native-bridge.mjs +51 -73
  19. package/hub/team/daemon-pty-tmux-bridge.mjs +146 -0
  20. package/hub/team/headless.mjs +23 -42
  21. package/hub/team/interactive-native-launcher.mjs +88 -75
  22. package/hub/team/retry-state-machine.mjs +2 -2
  23. package/hub/team/swarm-hypervisor.mjs +35 -8
  24. package/hub/team/uds-orchestrator.mjs +348 -0
  25. package/hud/context-monitor.mjs +7 -2
  26. package/package.json +6 -3
  27. package/scripts/__tests__/mcp-gateway-health-check.test.mjs +53 -0
  28. package/scripts/__tests__/mcp-guard-engine-policy-sync.test.mjs +8 -8
  29. package/scripts/codex-gateway-preflight.mjs +82 -22
  30. package/scripts/codex-mcp-gateway-sync.mjs +65 -31
  31. package/scripts/completions/tfx.bash +47 -14
  32. package/scripts/completions/tfx.fish +50 -33
  33. package/scripts/completions/tfx.zsh +68 -43
  34. package/scripts/demo.mjs +15 -5
  35. package/scripts/lib/mcp-gateway-health-check.mjs +65 -0
  36. package/scripts/lib/mcp-guard-engine.mjs +52 -8
  37. package/scripts/mcp-gateway-config.mjs +13 -13
  38. package/scripts/mcp-gateway-ensure.mjs +27 -12
  39. package/scripts/mcp-gateway-integration-test.mjs +21 -15
  40. package/scripts/mcp-gateway-start.mjs +6 -3
  41. package/scripts/mcp-gateway-start.ps1 +3 -3
  42. package/scripts/mcp-gateway-verify.mjs +1 -1
  43. package/scripts/release/check-packages-mirror.mjs +5 -0
  44. package/scripts/session-stale-cleanup.mjs +31 -1
  45. package/scripts/sync-hub-mcp-settings.mjs +30 -14
  46. package/skills/tfx-setup/SKILL.md +4 -4
  47. package/tui/codex-profile.mjs +409 -0
  48. package/tui/core.mjs +266 -0
  49. package/tui/doctor.mjs +375 -0
  50. package/tui/gemini-profile.mjs +299 -0
  51. package/tui/monitor-data.mjs +152 -0
  52. package/tui/monitor.mjs +317 -0
  53. package/tui/setup.mjs +633 -0
package/bin/triflux.mjs CHANGED
@@ -143,6 +143,34 @@ const JSON_OUTPUT = RAW_ARGS.includes("--json");
143
143
  const NORMALIZED_ARGS = RAW_ARGS.filter((arg) => arg !== "--json");
144
144
 
145
145
  const CLI_COMMAND_SCHEMAS = Object.freeze({
146
+ auto: {
147
+ usage:
148
+ "tfx auto [--cli auto|codex|antigravity|claude] [--mode quick|deep|consensus] [--parallel 1|N|swarm] [--json]",
149
+ description:
150
+ "tfx-auto 라우팅 결정을 CLI에서 미리보기/직렬화 (실행 skill front door와 같은 flag surface)",
151
+ options: [
152
+ {
153
+ name: "--cli <name>",
154
+ type: "string",
155
+ description: "실행 lane 강제: auto|codex|antigravity|claude",
156
+ },
157
+ {
158
+ name: "--mode <name>",
159
+ type: "string",
160
+ description: "라우팅 모드: quick|deep|consensus",
161
+ },
162
+ {
163
+ name: "--parallel <1|N|swarm>",
164
+ type: "string",
165
+ description: "단일/로컬 병렬/PRD swarm 라우팅 힌트",
166
+ },
167
+ {
168
+ name: "--json",
169
+ type: "boolean",
170
+ description: "parse 결과와 dispatch 결정을 JSON으로 출력",
171
+ },
172
+ ],
173
+ },
146
174
  setup: {
147
175
  usage: "tfx setup [--dry-run] [--enable-hub-autostart]",
148
176
  description: "파일 동기화 + HUD/MCP 설정",
@@ -498,6 +526,102 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
498
526
  },
499
527
  },
500
528
  },
529
+ update: {
530
+ usage: "tfx update [--dev]",
531
+ description:
532
+ "설치 방식(plugin/npm/git)을 감지해 triflux를 업데이트하고 setup/cache를 재동기화",
533
+ options: [
534
+ {
535
+ name: "--dev / dev",
536
+ type: "boolean",
537
+ description: "npm 설치 모드에서 dev tag로 업데이트",
538
+ },
539
+ {
540
+ name: "--help",
541
+ type: "boolean",
542
+ description: "업데이트를 실행하지 않고 도움말만 출력",
543
+ },
544
+ ],
545
+ },
546
+ tray: {
547
+ usage: "tfx tray [--attach]",
548
+ description: "트레이/HUD 상태 표시 프로세스 실행",
549
+ options: [
550
+ {
551
+ name: "--attach",
552
+ type: "boolean",
553
+ description: "디버깅용 foreground 실행 (기본은 detach)",
554
+ },
555
+ ],
556
+ },
557
+ "codex-team": {
558
+ usage:
559
+ "tfx codex-team [status|debug|send|attach|stop|<task>] [--layout 1xN|Nx1] [--json]",
560
+ description:
561
+ "Codex lead + Codex workers 기본값으로 tfx multi 팀 모드를 시작/제어",
562
+ subcommands: {
563
+ status: "현재 Codex team 상태 확인",
564
+ debug: "최근 로그/상태 진단 출력",
565
+ send: "워커에게 메시지 전송: tfx codex-team send <N> <msg>",
566
+ attach: "팀 pane/session attach",
567
+ stop: "팀 세션 정리",
568
+ },
569
+ options: [
570
+ {
571
+ name: "--layout <shape>",
572
+ type: "string",
573
+ description: "기본 1xN. Nx1 등 team layout 전달",
574
+ },
575
+ {
576
+ name: "--json",
577
+ type: "boolean",
578
+ description: "지원 subcommand의 출력을 JSON으로 전환",
579
+ },
580
+ ],
581
+ },
582
+ "notion-read": {
583
+ usage: "tfx notion-read <notion-url-or-page-id> [options]",
584
+ description: "Notion page/database를 markdown/JSON으로 읽기 (nr alias)",
585
+ aliases: ["nr"],
586
+ options: [
587
+ {
588
+ name: "--json",
589
+ type: "boolean",
590
+ description: "가능한 경우 구조화된 JSON 출력",
591
+ },
592
+ ],
593
+ },
594
+ review: {
595
+ usage:
596
+ "tfx review [ref] [--base <ref>] [--timeout <seconds>] [--shard off|per-file] [--json]",
597
+ description: "Codex 기반 git diff review 실행",
598
+ options: [
599
+ {
600
+ name: "--base <ref>",
601
+ type: "string",
602
+ description: "비교 기준 ref",
603
+ },
604
+ {
605
+ name: "--timeout <seconds>",
606
+ type: "number",
607
+ description: "review 실행 timeout (기본 180)",
608
+ },
609
+ {
610
+ name: "--shard <mode>",
611
+ type: "string",
612
+ description: "off 또는 per-file",
613
+ },
614
+ {
615
+ name: "--json",
616
+ type: "boolean",
617
+ description: "review 결과 JSON 출력",
618
+ },
619
+ ],
620
+ },
621
+ monitor: {
622
+ usage: "tfx monitor",
623
+ description: "터미널 TUI 모니터 실행",
624
+ },
501
625
  });
502
626
 
503
627
  // ── 유틸리티 ──
@@ -525,6 +649,53 @@ function printJson(payload) {
525
649
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
526
650
  }
527
651
 
652
+ function isHelpArg(arg) {
653
+ return ["help", "--help", "-h"].includes(String(arg || "").toLowerCase());
654
+ }
655
+
656
+ function formatSubcommandHelp(name, entry) {
657
+ if (typeof entry === "string") return `${name} — ${entry}`;
658
+ if (entry?.usage && entry?.description) {
659
+ return `${entry.usage} — ${entry.description}`;
660
+ }
661
+ if (entry?.usage) return entry.usage;
662
+ if (entry?.description) return `${name} — ${entry.description}`;
663
+ return String(name);
664
+ }
665
+
666
+ function printCommandHelp(command) {
667
+ const schema = CLI_COMMAND_SCHEMAS[command];
668
+ if (!schema) return false;
669
+ const subcommands = schema.subcommands
670
+ ? Object.entries(schema.subcommands)
671
+ .map(
672
+ ([name, entry]) =>
673
+ ` ${WHITE_BRIGHT}${formatSubcommandHelp(name, entry)}${RESET}`,
674
+ )
675
+ .join("\n")
676
+ : "";
677
+ const options = schema.options
678
+ ? schema.options
679
+ .map(
680
+ (option) =>
681
+ ` ${DIM}${String(option.name).padEnd(22)}${RESET} ${GRAY}${option.description || ""}${RESET}`,
682
+ )
683
+ .join("\n")
684
+ : "";
685
+ const aliases = schema.aliases?.length
686
+ ? `\n ${BOLD}Aliases${RESET}\n ${schema.aliases.join(", ")}\n`
687
+ : "";
688
+ console.log(`
689
+ ${AMBER}${BOLD}⬡ tfx ${command}${RESET}
690
+
691
+ ${GRAY}${schema.description || ""}${RESET}
692
+
693
+ ${BOLD}Usage${RESET}
694
+ ${WHITE_BRIGHT}${schema.usage}${RESET}
695
+ ${aliases}${subcommands ? `\n ${BOLD}Subcommands${RESET}\n${subcommands}\n` : ""}${options ? `\n ${BOLD}Options${RESET}\n${options}\n` : ""}`);
696
+ return true;
697
+ }
698
+
528
699
  async function withConsoleSilenced(enabled, fn) {
529
700
  if (!enabled) return fn();
530
701
  const originalLog = console.log;
@@ -2064,32 +2235,36 @@ function cmdSetup(options = {}) {
2064
2235
  });
2065
2236
  }
2066
2237
 
2067
- // Gemini 프로필
2238
+ // Antigravity/Gemini 호환 프로필
2068
2239
  const geminiResult = ensureGeminiProfiles();
2069
2240
  if (!geminiResult.ok) {
2070
2241
  const reason = renderErrorMessage(geminiResult.message);
2071
- warn(`Gemini profiles 설정 실패: ${reason}`);
2072
- summary.push({ item: "Gemini profiles", status: "⚠️", detail: reason });
2242
+ warn(`Antigravity/Gemini profiles 설정 실패: ${reason}`);
2243
+ summary.push({
2244
+ item: "Antigravity/Gemini profiles",
2245
+ status: "⚠️",
2246
+ detail: reason,
2247
+ });
2073
2248
  } else if (geminiResult.created) {
2074
2249
  ok(
2075
- `Gemini profiles: ${geminiResult.count}개 생성됨 (~/.gemini/triflux-profiles.json)`,
2250
+ `Antigravity/Gemini profiles: ${geminiResult.count}개 생성됨 (~/.gemini/triflux-profiles.json)`,
2076
2251
  );
2077
2252
  summary.push({
2078
- item: "Gemini profiles",
2253
+ item: "Antigravity/Gemini profiles",
2079
2254
  status: "✅",
2080
2255
  detail: `${geminiResult.count}개 생성됨`,
2081
2256
  });
2082
2257
  } else if (geminiResult.added > 0) {
2083
- ok(`Gemini profiles: ${geminiResult.added}개 추가됨`);
2258
+ ok(`Antigravity/Gemini profiles: ${geminiResult.added}개 추가됨`);
2084
2259
  summary.push({
2085
- item: "Gemini profiles",
2260
+ item: "Antigravity/Gemini profiles",
2086
2261
  status: "✅",
2087
2262
  detail: `${geminiResult.added}개 추가됨 (총 ${geminiResult.count}개)`,
2088
2263
  });
2089
2264
  } else {
2090
- ok(`Gemini profiles: ${geminiResult.count}개 준비됨`);
2265
+ ok(`Antigravity/Gemini profiles: ${geminiResult.count}개 준비됨`);
2091
2266
  summary.push({
2092
- item: "Gemini profiles",
2267
+ item: "Antigravity/Gemini profiles",
2093
2268
  status: "✅",
2094
2269
  detail: `${geminiResult.count}개 준비됨`,
2095
2270
  });
@@ -4689,10 +4864,9 @@ async function cmdDoctor(options = {}) {
4689
4864
  // skip 된 server 를 잡는다. 로그가 없으면 gateway 미설치/미실행으로 침묵.
4690
4865
  section("MCP Gateway Health");
4691
4866
  {
4692
- const { checkMcpGatewayHealth, summarizeMcpGatewayHealth } = await import(
4693
- "../scripts/lib/mcp-gateway-health-check.mjs"
4694
- );
4695
- const gatewayHealth = checkMcpGatewayHealth();
4867
+ const { checkMcpGatewayHealthLive, summarizeMcpGatewayHealth } =
4868
+ await import("../scripts/lib/mcp-gateway-health-check.mjs");
4869
+ const gatewayHealth = await checkMcpGatewayHealthLive();
4696
4870
  const summary = summarizeMcpGatewayHealth(gatewayHealth);
4697
4871
  addDoctorCheck(report, {
4698
4872
  name: "mcp-gateway-health",
@@ -4700,6 +4874,7 @@ async function cmdDoctor(options = {}) {
4700
4874
  log_path: gatewayHealth.logPath,
4701
4875
  findings: gatewayHealth.findings,
4702
4876
  started: gatewayHealth.started,
4877
+ live: gatewayHealth.live,
4703
4878
  skipped: gatewayHealth.skipped,
4704
4879
  ...(summary.fix ? { fix: summary.fix } : {}),
4705
4880
  });
@@ -5163,8 +5338,13 @@ function resolveUpdateTargets({ installMode, pluginPath }) {
5163
5338
  return [];
5164
5339
  }
5165
5340
 
5166
- async function cmdUpdate() {
5167
- const isDev = isDevUpdateRequested(NORMALIZED_ARGS);
5341
+ async function cmdUpdate(args = []) {
5342
+ if (args.some(isHelpArg)) {
5343
+ printCommandHelp("update");
5344
+ return;
5345
+ }
5346
+
5347
+ const isDev = isDevUpdateRequested(args);
5168
5348
  const tagLabel = isDev ? ` ${YELLOW}--dev${RESET}` : "";
5169
5349
  console.log(`\n${BOLD}triflux update${RESET}${tagLabel}\n`);
5170
5350
 
@@ -5520,29 +5700,76 @@ async function cmdUpdate() {
5520
5700
  console.log(`${GREEN}${BOLD}✓ 업데이트 완료${RESET}\n`);
5521
5701
  }
5522
5702
 
5703
+ function readPackagedSkillMetadata(skillName) {
5704
+ const skillPath = join(PKG_ROOT, "skills", skillName, "SKILL.md");
5705
+ if (!existsSync(skillPath)) return null;
5706
+ const metadata = { name: skillName, deprecated: false, supersededBy: null };
5707
+ let inFrontmatter = false;
5708
+ for (const line of readFileSync(skillPath, "utf8").split(/\r?\n/)) {
5709
+ if (line.trim() === "---") {
5710
+ if (!inFrontmatter) {
5711
+ inFrontmatter = true;
5712
+ continue;
5713
+ }
5714
+ break;
5715
+ }
5716
+ if (!inFrontmatter) continue;
5717
+ const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
5718
+ if (!match) continue;
5719
+ const [, key, rawValue] = match;
5720
+ const value = rawValue.trim().replace(/^["']|["']$/g, "");
5721
+ if (key === "deprecated") metadata.deprecated = value === "true";
5722
+ if (key === "superseded-by") metadata.supersededBy = value || null;
5723
+ }
5724
+ return metadata;
5725
+ }
5726
+
5727
+ function listDeprecatedPackagedSkillAliases(pluginSkills, installedSkills) {
5728
+ if (!existsSync(pluginSkills)) return [];
5729
+ return readdirSync(pluginSkills)
5730
+ .sort()
5731
+ .map((name) => readPackagedSkillMetadata(name))
5732
+ .filter((metadata) => metadata?.deprecated)
5733
+ .map((metadata) => ({
5734
+ alias: metadata.name,
5735
+ source: metadata.supersededBy || "deprecated",
5736
+ installed: existsSync(join(installedSkills, metadata.name, "SKILL.md")),
5737
+ deprecated: true,
5738
+ }));
5739
+ }
5740
+
5523
5741
  function cmdList(options = {}) {
5524
5742
  const { json = false } = options;
5525
5743
  const pluginSkills = join(PKG_ROOT, "skills");
5526
5744
  const installedSkills = join(CLAUDE_DIR, "skills");
5527
5745
  const packageSkills = [];
5528
5746
  const userSkills = [];
5529
- const aliasNames = new Set(SKILL_ALIASES.map(({ alias }) => alias));
5530
- const skillAliases = [];
5747
+ const skillAliases = listDeprecatedPackagedSkillAliases(
5748
+ pluginSkills,
5749
+ installedSkills,
5750
+ );
5751
+ const aliasNames = new Set([
5752
+ ...SKILL_ALIASES.map(({ alias }) => alias),
5753
+ ...skillAliases.map(({ alias }) => alias),
5754
+ ]);
5531
5755
 
5532
5756
  if (existsSync(pluginSkills)) {
5533
5757
  for (const name of readdirSync(pluginSkills).sort()) {
5534
5758
  const src = join(pluginSkills, name, "SKILL.md");
5535
5759
  if (!existsSync(src)) continue;
5760
+ const metadata = readPackagedSkillMetadata(name);
5536
5761
  const dst = join(installedSkills, name, "SKILL.md");
5537
- packageSkills.push({ name, installed: existsSync(dst) });
5762
+ packageSkills.push({
5763
+ name,
5764
+ installed: existsSync(dst),
5765
+ ...(metadata?.deprecated ? { deprecated: true } : {}),
5766
+ ...(metadata?.supersededBy
5767
+ ? { superseded_by: metadata.supersededBy }
5768
+ : {}),
5769
+ });
5538
5770
  }
5539
5771
  }
5540
5772
 
5541
- for (const { alias, source } of SKILL_ALIASES) {
5542
- const dst = join(installedSkills, alias, "SKILL.md");
5543
- skillAliases.push({ alias, source, installed: existsSync(dst) });
5544
- }
5545
-
5546
5773
  const pkgNames = new Set(
5547
5774
  existsSync(pluginSkills) ? readdirSync(pluginSkills) : [],
5548
5775
  );
@@ -5593,7 +5820,7 @@ function cmdList(options = {}) {
5593
5820
  : `${RED_BRIGHT}↳${RESET}`;
5594
5821
  const status = entry.installed ? "" : ` ${GRAY}(미설치)${RESET}`;
5595
5822
  console.log(
5596
- ` ${icon} ${BOLD}${entry.alias}${RESET} ${GRAY}→ ${entry.source}${RESET}${status}`,
5823
+ ` ${icon} ${BOLD}${entry.alias}${RESET} ${GRAY}→ ${entry.source}${RESET}${entry.deprecated ? ` ${YELLOW}(deprecated)${RESET}` : ""}${status}`,
5597
5824
  );
5598
5825
  }
5599
5826
  }
@@ -5624,6 +5851,11 @@ function cmdVersion(options = {}) {
5624
5851
  }
5625
5852
 
5626
5853
  function cmdHandoff(args = [], options = {}) {
5854
+ if (args.some(isHelpArg)) {
5855
+ printCommandHelp("handoff");
5856
+ return;
5857
+ }
5858
+
5627
5859
  const { json = false } = options;
5628
5860
  const parsed = {
5629
5861
  target: "remote",
@@ -5752,6 +5984,11 @@ function cmdSchema(args = []) {
5752
5984
  ? bundle["x-triflux-mcp-tools"].find((tool) => tool.name === selector)
5753
5985
  : null;
5754
5986
 
5987
+ if (isHelpArg(selector)) {
5988
+ printCommandHelp("schema");
5989
+ return;
5990
+ }
5991
+
5755
5992
  if (!selector) {
5756
5993
  printJson({
5757
5994
  $schema: bundle.$schema,
@@ -6084,18 +6321,21 @@ ${updateNotice}
6084
6321
  ${DIM} --fix${RESET} ${GRAY}진단 + 자동 수정${RESET}
6085
6322
  ${DIM} --reset${RESET} ${GRAY}캐시 전체 초기화${RESET}
6086
6323
  ${DIM} --json${RESET} ${GRAY}구조화된 진단 결과 JSON 출력${RESET}
6324
+ ${WHITE_BRIGHT}tfx auto${RESET} ${GRAY}tfx-auto 라우팅 결정 미리보기 (--cli codex|antigravity|claude)${RESET}
6087
6325
  ${WHITE_BRIGHT}tfx mcp${RESET} ${GRAY}MCP registry 관리 (list/sync/add/remove)${RESET}
6088
6326
  ${WHITE_BRIGHT}tfx update${RESET} ${GRAY}최신 안정 버전으로 업데이트${RESET}
6089
6327
  ${DIM} --dev / dev${RESET} ${GRAY}dev 태그로 업데이트${RESET}
6090
6328
  ${WHITE_BRIGHT}tfx list${RESET} ${GRAY}설치된 스킬 목록${RESET}
6091
6329
  ${WHITE_BRIGHT}tfx handoff${RESET} ${GRAY}현재 컨텍스트를 원격/로컬 핸드오프 프롬프트로 생성${RESET}
6092
6330
  ${WHITE_BRIGHT}tfx schema${RESET} ${GRAY}CLI/Hub schema JSON 출력${RESET}
6331
+ ${WHITE_BRIGHT}tfx hooks${RESET} ${GRAY}훅 오케스트레이터 scan/diff/apply/status${RESET}
6093
6332
  ${WHITE_BRIGHT}tfx hub${RESET} ${GRAY}MCP 메시지 버스 관리 (start/stop/status)${RESET}
6094
6333
  ${WHITE_BRIGHT}tfx tray${RESET} ${GRAY}Windows 시스템 트레이 실행${RESET}
6095
- ${DIM} --detach${RESET} ${GRAY}백그라운드 트레이 프로세스로 분리${RESET}
6334
+ ${DIM} --attach${RESET} ${GRAY}foreground 트레이 프로세스로 실행${RESET}
6096
6335
  ${WHITE_BRIGHT}tfx multi${RESET} ${GRAY}멀티-CLI 팀 모드 (tmux + Hub)${RESET}
6097
6336
  ${WHITE_BRIGHT}tfx swarm${RESET} ${GRAY}PRD 기반 worktree 격리 병렬 실행 (run/plan/list)${RESET}
6098
6337
  ${WHITE_BRIGHT}tfx synapse${RESET} ${GRAY}스웜 세션 registry 조회 / lease 관리${RESET}
6338
+ ${WHITE_BRIGHT}tfx review${RESET} ${GRAY}Codex 기반 git diff review${RESET}
6099
6339
  ${WHITE_BRIGHT}tfx why${RESET} ${GRAY}경로의 마지막 커밋 X-Intent 트레일러 추출${RESET}
6100
6340
  ${WHITE_BRIGHT}tfx codex-team${RESET} ${GRAY}Codex 전용 팀 모드 (기본 lead/agents: codex)${RESET}
6101
6341
  ${WHITE_BRIGHT}tfx notion-read${RESET} ${GRAY}Notion 페이지 → 마크다운 (Codex/Gemini MCP)${RESET}
@@ -6103,7 +6343,7 @@ ${updateNotice}
6103
6343
 
6104
6344
  ${BOLD}Skills${RESET} ${GRAY}(Claude Code 슬래시 커맨드)${RESET}
6105
6345
 
6106
- ${AMBER}/tfx-auto${RESET} ${GRAY}자동 분류 + 병렬 실행 (--cli codex|gemini, --parallel N|swarm)${RESET}
6346
+ ${AMBER}/tfx-auto${RESET} ${GRAY}자동 분류 + 병렬 실행 (--cli codex|antigravity|claude, --parallel N|swarm)${RESET}
6107
6347
  ${AMBER}/tfx-setup${RESET} ${GRAY}HUD 설정 + 진단${RESET}
6108
6348
  ${YELLOW}/tfx-doctor${RESET} ${GRAY}진단 + 수리 + 캐시 초기화${RESET}
6109
6349
 
@@ -7013,6 +7253,10 @@ async function main() {
7013
7253
 
7014
7254
  switch (cmd) {
7015
7255
  case "auto": {
7256
+ if (cmdArgs.some(isHelpArg)) {
7257
+ printCommandHelp("auto");
7258
+ return;
7259
+ }
7016
7260
  const parsedArgs = parseRouteArgs(cmdArgs);
7017
7261
  const decision = applyAutoDispatchDecision(parsedArgs);
7018
7262
  if (JSON_OUTPUT) {
@@ -7023,12 +7267,20 @@ async function main() {
7023
7267
  return;
7024
7268
  }
7025
7269
  case "setup":
7270
+ if (cmdArgs.some(isHelpArg)) {
7271
+ printCommandHelp("setup");
7272
+ return;
7273
+ }
7026
7274
  cmdSetup({
7027
7275
  dryRun: cmdArgs.includes("--dry-run"),
7028
7276
  enableHubAutostart: cmdArgs.includes("--enable-hub-autostart"),
7029
7277
  });
7030
7278
  return;
7031
7279
  case "doctor": {
7280
+ if (cmdArgs.some(isHelpArg)) {
7281
+ printCommandHelp("doctor");
7282
+ return;
7283
+ }
7032
7284
  if (cmdArgs.includes("--audit")) {
7033
7285
  const auditScript = join(PKG_ROOT, "scripts", "config-audit.mjs");
7034
7286
  const auditArgs = JSON_OUTPUT ? ["--json"] : [];
@@ -7156,28 +7408,44 @@ async function main() {
7156
7408
  cmdSchema(cmdArgs);
7157
7409
  return;
7158
7410
  case "update":
7159
- await cmdUpdate();
7411
+ await cmdUpdate(cmdArgs);
7160
7412
  return;
7161
7413
  case "list":
7162
7414
  case "ls":
7415
+ if (cmdArgs.some(isHelpArg)) {
7416
+ printCommandHelp("list");
7417
+ return;
7418
+ }
7163
7419
  cmdList({ json: JSON_OUTPUT });
7164
7420
  return;
7165
7421
  case "handoff":
7166
7422
  cmdHandoff(cmdArgs, { json: JSON_OUTPUT });
7167
7423
  return;
7168
7424
  case "hub":
7425
+ if (cmdArgs.some(isHelpArg)) {
7426
+ printCommandHelp("hub");
7427
+ return;
7428
+ }
7169
7429
  await cmdHub(cmdArgs, {
7170
7430
  json:
7171
7431
  JSON_OUTPUT && ["status", "ensure"].includes(cmdArgs[0] || "status"),
7172
7432
  });
7173
7433
  return;
7174
7434
  case "monitor": {
7435
+ if (cmdArgs.some(isHelpArg)) {
7436
+ printCommandHelp("monitor");
7437
+ return;
7438
+ }
7175
7439
  const { createMonitor } = await import("../tui/monitor.mjs");
7176
7440
  const mon = createMonitor();
7177
7441
  await mon.start();
7178
7442
  break;
7179
7443
  }
7180
7444
  case "tray": {
7445
+ if (cmdArgs.some(isHelpArg)) {
7446
+ printCommandHelp("tray");
7447
+ return;
7448
+ }
7181
7449
  const trayUrl = new URL("../hub/tray.mjs", import.meta.url);
7182
7450
  const trayPath = fileURLToPath(trayUrl);
7183
7451
  if (cmdArgs.includes("--attach")) {
@@ -7200,6 +7468,10 @@ async function main() {
7200
7468
  }
7201
7469
  case "multi": {
7202
7470
  const subcommand = cmdArgs[0] || "";
7471
+ if (cmdArgs.some(isHelpArg)) {
7472
+ printCommandHelp("multi");
7473
+ return;
7474
+ }
7203
7475
  if (JSON_OUTPUT) process.env.TFX_OUTPUT_JSON = "1";
7204
7476
  else delete process.env.TFX_OUTPUT_JSON;
7205
7477
  if (subcommand !== "status") {
@@ -7220,6 +7492,10 @@ async function main() {
7220
7492
  return;
7221
7493
  }
7222
7494
  case "codex-team":
7495
+ if (cmdArgs.some(isHelpArg)) {
7496
+ await cmdCodexTeam(["--help"]);
7497
+ return;
7498
+ }
7223
7499
  if (JSON_OUTPUT) process.env.TFX_OUTPUT_JSON = "1";
7224
7500
  else delete process.env.TFX_OUTPUT_JSON;
7225
7501
  await checkHubRunning();
@@ -7231,6 +7507,10 @@ async function main() {
7231
7507
  return;
7232
7508
  case "notion-read":
7233
7509
  case "nr": {
7510
+ if (cmdArgs.some(isHelpArg)) {
7511
+ printCommandHelp("notion-read");
7512
+ return;
7513
+ }
7234
7514
  const scriptPath = join(PKG_ROOT, "scripts", "notion-read.mjs");
7235
7515
  try {
7236
7516
  execFileSync(process.execPath, [scriptPath, ...cmdArgs], {
@@ -7247,6 +7527,10 @@ async function main() {
7247
7527
  return;
7248
7528
  }
7249
7529
  case "hooks": {
7530
+ if (cmdArgs.some(isHelpArg)) {
7531
+ printCommandHelp("hooks");
7532
+ return;
7533
+ }
7250
7534
  const hookManagerPath = join(PKG_ROOT, "hooks", "hook-manager.mjs");
7251
7535
  const sub = cmdArgs[0] || "status";
7252
7536
  try {
@@ -7267,6 +7551,10 @@ async function main() {
7267
7551
  case "synapse": {
7268
7552
  const { cmdSynapseStatus } = await import("../hub/team/synapse-cli.mjs");
7269
7553
  const sub = cmdArgs[0] || "status";
7554
+ if (isHelpArg(sub)) {
7555
+ printCommandHelp("synapse");
7556
+ return;
7557
+ }
7270
7558
  if (sub !== "status") {
7271
7559
  throw createCliError(`synapse 서브커맨드 미지원: ${sub}`, {
7272
7560
  exitCode: EXIT_ARG_ERROR,
@@ -7278,6 +7566,10 @@ async function main() {
7278
7566
  return;
7279
7567
  }
7280
7568
  case "review": {
7569
+ if (cmdArgs.some(isHelpArg)) {
7570
+ printCommandHelp("review");
7571
+ return;
7572
+ }
7281
7573
  const ref =
7282
7574
  cmdArgs[0] && !cmdArgs[0].startsWith("--") ? cmdArgs[0] : "HEAD";
7283
7575
  const baseIdx = cmdArgs.indexOf("--base");
@@ -7397,6 +7689,10 @@ ${s.options.map((o) => ` ${DIM}${o.name.padEnd(16)}${RESET} ${GRAY}${o.descri
7397
7689
  return;
7398
7690
  }
7399
7691
  case "why": {
7692
+ if (cmdArgs.some(isHelpArg)) {
7693
+ printCommandHelp("why");
7694
+ return;
7695
+ }
7400
7696
  const { cmdSynapseWhy } = await import("../hub/team/synapse-cli.mjs");
7401
7697
  await cmdSynapseWhy(cmdArgs, { json: JSON_OUTPUT });
7402
7698
  return;
@@ -7404,6 +7700,10 @@ ${s.options.map((o) => ` ${DIM}${o.name.padEnd(16)}${RESET} ${GRAY}${o.descri
7404
7700
  case "version":
7405
7701
  case "--version":
7406
7702
  case "-v":
7703
+ if (cmdArgs.some(isHelpArg)) {
7704
+ printCommandHelp("version");
7705
+ return;
7706
+ }
7407
7707
  cmdVersion({ json: JSON_OUTPUT });
7408
7708
  return;
7409
7709
  case "help":
@@ -7,8 +7,8 @@
7
7
  "hub_base": "http://127.0.0.1:27888"
8
8
  },
9
9
  "policy_notes": {
10
- "policy": "Server policy is the SSOT for client sync shape: hosted writes url+headers, gateway-sse writes http://127.0.0.1:81XX/sse with SSE metadata, and stdio writes command/args/env.",
11
- "transport": "Server transport accepts \"hub-url\" for triflux Hub URL flow, \"http\" for direct Streamable HTTP MCP endpoints, or \"stdio\" for upstream-stdio-only MCP servers. Gateway-backed stdio upstreams use policy:\"gateway-sse\" plus gateway_port so clients receive URL/SSE config.",
10
+ "policy": "Server policy is the SSOT for client sync shape: hosted writes url+headers, gateway-sse writes http://127.0.0.1:81XX/sse with SSE metadata, gateway-http writes http://127.0.0.1:81XX/mcp with HTTP metadata, and stdio writes command/args/env.",
11
+ "transport": "Server transport accepts \"hub-url\" for triflux Hub URL flow, \"http\" for direct Streamable HTTP MCP endpoints, or \"stdio\" for upstream-stdio-only MCP servers. Gateway-backed stdio upstreams should use policy:\"gateway-http\" plus gateway_port/gateway_path so clients receive reconnect-safe HTTP MCP config; legacy policy:\"gateway-sse\" remains supported only for backward compatibility.",
12
12
  "headers": "Optional headers are allowed only for HTTP-compatible transports. Each header value must be a descriptor: {\"value\":\"literal\"} for non-secret static values, {\"env\":\"ENV_VAR_NAME\"} for secrets resolved at sync/runtime, or {\"env\":\"ENV_VAR_NAME\",\"prefix\":\"Bearer \"} for common authorization formats.",
13
13
  "secret_safety": "Resolved secret values must not be written back to this registry file. Missing env vars warn during sync and do not emit empty secret headers.",
14
14
  "sync_denylist": "Array of client:server strings skipped by proactive registry sync, for example gemini:tfx-hub."
@@ -42,18 +42,22 @@
42
42
  "description": "Exa neural/semantic web search — 학술/기술 깊이. Key 발급: https://exa.ai/dashboard → secrets.env의 EXA_API_KEY"
43
43
  },
44
44
  "serena": {
45
- "policy": "gateway-sse",
45
+ "policy": "gateway-http",
46
+ "transport": "http",
46
47
  "gateway_port": 8105,
48
+ "gateway_path": "/mcp",
47
49
  "safe": true,
48
50
  "targets": ["claude", "gemini", "codex", "antigravity"],
49
- "description": "Serena MCP — supergateway singleton SSE endpoint (:8105)"
51
+ "description": "Serena MCP — local supergateway stateful Streamable HTTP endpoint (:8105/mcp)"
50
52
  },
51
53
  "brave-search": {
52
- "policy": "gateway-sse",
54
+ "policy": "gateway-http",
55
+ "transport": "http",
53
56
  "gateway_port": 8101,
57
+ "gateway_path": "/mcp",
54
58
  "safe": true,
55
59
  "targets": ["claude", "gemini", "codex", "antigravity"],
56
- "description": "Brave Search MCP — supergateway singleton SSE endpoint (:8101). BRAVE_API_KEY 환경변수 필요 (https://brave.com/search/api/). secrets.env 의 BRAVE_API_KEY 참조."
60
+ "description": "Brave Search MCP — local supergateway stateful Streamable HTTP endpoint (:8101/mcp). BRAVE_API_KEY 환경변수 필요 (https://brave.com/search/api/). secrets.env 의 BRAVE_API_KEY 참조."
57
61
  },
58
62
  "tavily": {
59
63
  "policy": "hosted",