triflux 10.4.0 → 10.7.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 (391) hide show
  1. package/.claude-plugin/marketplace.json +34 -0
  2. package/.claude-plugin/plugin.json +22 -0
  3. package/bin/tfx-doctor-tui.mjs +1 -1
  4. package/bin/tfx-doctor.mjs +6 -1
  5. package/bin/tfx-profile.mjs +1 -1
  6. package/bin/tfx-setup-tui.mjs +1 -1
  7. package/bin/tfx-setup.mjs +6 -1
  8. package/bin/triflux.mjs +2396 -1140
  9. package/config/mcp-registry.json +29 -0
  10. package/hooks/agent-route-guard.mjs +12 -8
  11. package/hooks/cross-review-tracker.mjs +21 -8
  12. package/hooks/error-context.mjs +19 -7
  13. package/hooks/hook-adaptive-collector.mjs +18 -16
  14. package/hooks/hook-manager.mjs +93 -32
  15. package/hooks/hook-orchestrator.mjs +108 -24
  16. package/hooks/hook-registry.json +267 -256
  17. package/hooks/keyword-rules.json +6 -10
  18. package/hooks/lib/resolve-root.mjs +1 -1
  19. package/hooks/mcp-config-watcher.mjs +6 -2
  20. package/hooks/pipeline-stop.mjs +3 -6
  21. package/hooks/safety-guard.mjs +99 -28
  22. package/hooks/session-start-fast.mjs +143 -0
  23. package/hooks/subagent-verifier.mjs +5 -4
  24. package/hub/account-broker.mjs +256 -60
  25. package/hub/adaptive-diagnostic.mjs +75 -48
  26. package/hub/adaptive-inject.mjs +96 -58
  27. package/hub/adaptive-memory.mjs +156 -42
  28. package/hub/adaptive.mjs +60 -31
  29. package/hub/assign-callbacks.mjs +163 -126
  30. package/hub/bridge.mjs +0 -1
  31. package/hub/cli-adapter-base.mjs +200 -48
  32. package/hub/codex-adapter.mjs +76 -96
  33. package/hub/codex-compat.mjs +3 -3
  34. package/hub/codex-preflight.mjs +63 -37
  35. package/hub/delegator/contracts.mjs +19 -23
  36. package/hub/delegator/index.mjs +14 -14
  37. package/hub/delegator/service.mjs +88 -64
  38. package/hub/delegator/tool-definitions.mjs +35 -35
  39. package/hub/fullcycle.mjs +33 -17
  40. package/hub/gemini-adapter.mjs +69 -94
  41. package/hub/hitl.mjs +202 -143
  42. package/hub/intent.mjs +161 -38
  43. package/hub/lib/cache-guard.mjs +43 -17
  44. package/hub/lib/mcp-response-cache.mjs +66 -32
  45. package/hub/lib/memory-store.mjs +285 -111
  46. package/hub/lib/path-utils.mjs +35 -37
  47. package/hub/lib/process-utils.mjs +106 -37
  48. package/hub/lib/spawn-trace.mjs +527 -0
  49. package/hub/lib/ssh-command.mjs +34 -4
  50. package/hub/lib/ssh-retry.mjs +5 -1
  51. package/hub/lib/uuidv7.mjs +4 -3
  52. package/hub/memory-doctor.mjs +266 -106
  53. package/hub/middleware/request-logger.mjs +61 -34
  54. package/hub/paths.mjs +9 -9
  55. package/hub/pipeline/gates/confidence.mjs +34 -15
  56. package/hub/pipeline/gates/consensus.mjs +27 -15
  57. package/hub/pipeline/gates/index.mjs +7 -3
  58. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  59. package/hub/pipeline/index.mjs +77 -42
  60. package/hub/pipeline/state.mjs +10 -10
  61. package/hub/pipeline/transitions.mjs +40 -23
  62. package/hub/platform.mjs +57 -48
  63. package/hub/promote-penalties.mjs +25 -7
  64. package/hub/quality/deslop.mjs +70 -49
  65. package/hub/research.mjs +32 -25
  66. package/hub/router.mjs +924 -791
  67. package/hub/routing/complexity.mjs +132 -29
  68. package/hub/routing/index.mjs +17 -12
  69. package/hub/routing/q-learning.mjs +76 -28
  70. package/hub/server.mjs +4 -4
  71. package/hub/session-fingerprint.mjs +127 -61
  72. package/hub/state.mjs +84 -43
  73. package/hub/store-adapter.mjs +59 -26
  74. package/hub/store.mjs +356 -153
  75. package/hub/team/agent-map.json +22 -7
  76. package/hub/team/ansi.mjs +186 -122
  77. package/hub/team/backend.mjs +28 -10
  78. package/hub/team/cli/commands/attach.mjs +57 -37
  79. package/hub/team/cli/commands/control.mjs +29 -8
  80. package/hub/team/cli/commands/debug.mjs +95 -74
  81. package/hub/team/cli/commands/focus.mjs +80 -53
  82. package/hub/team/cli/commands/interrupt.mjs +18 -6
  83. package/hub/team/cli/commands/kill.mjs +16 -5
  84. package/hub/team/cli/commands/list.mjs +31 -24
  85. package/hub/team/cli/commands/send.mjs +19 -6
  86. package/hub/team/cli/commands/start/index.mjs +154 -31
  87. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  88. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  89. package/hub/team/cli/commands/start/start-in-process.mjs +50 -40
  90. package/hub/team/cli/commands/start/start-mux.mjs +122 -73
  91. package/hub/team/cli/commands/start/start-wt.mjs +86 -69
  92. package/hub/team/cli/commands/status.mjs +43 -14
  93. package/hub/team/cli/commands/stop.mjs +11 -4
  94. package/hub/team/cli/commands/task.mjs +8 -3
  95. package/hub/team/cli/commands/tasks.mjs +13 -13
  96. package/hub/team/cli/index.mjs +2 -2
  97. package/hub/team/cli/manifest.mjs +38 -8
  98. package/hub/team/cli/render.mjs +52 -30
  99. package/hub/team/cli/services/attach-fallback.mjs +74 -54
  100. package/hub/team/cli/services/hub-client.mjs +42 -14
  101. package/hub/team/cli/services/member-selector.mjs +37 -30
  102. package/hub/team/cli/services/native-control.mjs +143 -116
  103. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  104. package/hub/team/cli/services/state-store.mjs +25 -8
  105. package/hub/team/cli/services/task-model.mjs +40 -30
  106. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  107. package/hub/team/conductor.mjs +8 -4
  108. package/hub/team/dashboard-anchor.mjs +4 -5
  109. package/hub/team/dashboard-layout.mjs +3 -1
  110. package/hub/team/dashboard-open.mjs +41 -21
  111. package/hub/team/dashboard.mjs +76 -28
  112. package/hub/team/event-log.mjs +18 -10
  113. package/hub/team/handoff.mjs +31 -15
  114. package/hub/team/headless.mjs +2 -1
  115. package/hub/team/health-probe.mjs +69 -54
  116. package/hub/team/launcher-template.mjs +16 -13
  117. package/hub/team/native-supervisor.mjs +65 -21
  118. package/hub/team/native.mjs +74 -35
  119. package/hub/team/nativeProxy.mjs +184 -113
  120. package/hub/team/notify.mjs +120 -77
  121. package/hub/team/orchestrator.mjs +166 -161
  122. package/hub/team/pane.mjs +12 -7
  123. package/hub/team/process-cleanup.mjs +25 -16
  124. package/hub/team/psmux.mjs +491 -201
  125. package/hub/team/remote-probe.mjs +68 -52
  126. package/hub/team/remote-session.mjs +117 -59
  127. package/hub/team/remote-watcher.mjs +61 -33
  128. package/hub/team/routing.mjs +51 -25
  129. package/hub/team/runtime-strategy.mjs +3 -1
  130. package/hub/team/session.mjs +675 -611
  131. package/hub/team/shared.mjs +13 -13
  132. package/hub/team/staleState.mjs +72 -30
  133. package/hub/team/swarm-locks.mjs +15 -13
  134. package/hub/team/swarm-planner.mjs +32 -21
  135. package/hub/team/swarm-reconciler.mjs +48 -23
  136. package/hub/team/tui-lite.mjs +266 -68
  137. package/hub/team/tui-remote-adapter.mjs +14 -10
  138. package/hub/team/tui-viewer.mjs +99 -43
  139. package/hub/team/tui.mjs +708 -271
  140. package/hub/team/worktree-lifecycle.mjs +152 -58
  141. package/hub/team/wt-manager.mjs +24 -14
  142. package/hub/token-mode.mjs +71 -71
  143. package/hub/tray.mjs +411 -368
  144. package/hub/workers/claude-worker.mjs +162 -118
  145. package/hub/workers/codex-mcp.mjs +558 -507
  146. package/hub/workers/delegator-mcp.mjs +507 -333
  147. package/hub/workers/factory.mjs +21 -21
  148. package/hub/workers/gemini-worker.mjs +115 -84
  149. package/hub/workers/interface.mjs +6 -1
  150. package/hub/workers/worker-utils.mjs +21 -14
  151. package/hud/colors.mjs +27 -9
  152. package/hud/constants.mjs +162 -26
  153. package/hud/context-monitor.mjs +82 -41
  154. package/hud/hud-qos-status.mjs +129 -49
  155. package/hud/mission-board.mjs +6 -3
  156. package/hud/providers/claude.mjs +226 -115
  157. package/hud/providers/codex.mjs +62 -22
  158. package/hud/providers/gemini.mjs +168 -56
  159. package/hud/renderers.mjs +384 -119
  160. package/hud/terminal.mjs +101 -31
  161. package/hud/utils.mjs +78 -38
  162. package/mesh/index.mjs +11 -5
  163. package/mesh/mesh-budget.mjs +18 -9
  164. package/mesh/mesh-heartbeat.mjs +1 -1
  165. package/mesh/mesh-queue.mjs +3 -5
  166. package/mesh/mesh-router.mjs +5 -4
  167. package/package.json +56 -21
  168. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  169. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  170. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  171. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  172. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  173. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  174. package/scripts/__tests__/skill-template.test.mjs +98 -50
  175. package/scripts/__tests__/smoke.test.mjs +1 -1
  176. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  177. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  178. package/scripts/cache-doctor.mjs +11 -4
  179. package/scripts/cache-warmup.mjs +96 -37
  180. package/scripts/claudemd-sync.mjs +27 -17
  181. package/scripts/codex-gateway-preflight.mjs +52 -37
  182. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  183. package/scripts/config-audit.mjs +232 -0
  184. package/scripts/convert-to-tmpl.mjs +54 -0
  185. package/scripts/cross-review-gate.mjs +35 -12
  186. package/scripts/cross-review-tracker.mjs +21 -8
  187. package/scripts/demo.mjs +35 -17
  188. package/scripts/doctor-diagnose.mjs +284 -0
  189. package/scripts/gen-skill-docs.mjs +7 -2
  190. package/scripts/gen-skill-manifest.mjs +2 -1
  191. package/scripts/headless-guard.mjs +86 -48
  192. package/scripts/hub-ensure.mjs +139 -120
  193. package/scripts/keyword-detector.mjs +293 -272
  194. package/scripts/keyword-rules-expander.mjs +538 -521
  195. package/scripts/lib/claudemd-scanner.mjs +6 -1
  196. package/scripts/lib/context.mjs +3 -3
  197. package/scripts/lib/cross-review-utils.mjs +6 -3
  198. package/scripts/lib/env-probe.mjs +47 -28
  199. package/scripts/lib/gemini-profiles.mjs +44 -10
  200. package/scripts/lib/handoff.mjs +33 -17
  201. package/scripts/lib/hook-utils.mjs +8 -6
  202. package/scripts/lib/keyword-rules.mjs +43 -19
  203. package/scripts/lib/logger.mjs +24 -24
  204. package/scripts/lib/mcp-filter.mjs +377 -239
  205. package/scripts/lib/mcp-guard-engine.mjs +194 -79
  206. package/scripts/lib/mcp-manifest.mjs +23 -13
  207. package/scripts/lib/mcp-server-catalog.mjs +355 -118
  208. package/scripts/lib/psmux-info.mjs +11 -6
  209. package/scripts/lib/remote-spawn-transfer.mjs +44 -14
  210. package/scripts/lib/skill-template.mjs +30 -7
  211. package/scripts/mcp-check.mjs +58 -39
  212. package/scripts/mcp-gateway-config.mjs +83 -39
  213. package/scripts/mcp-gateway-ensure.mjs +43 -35
  214. package/scripts/mcp-gateway-integration-test.mjs +70 -58
  215. package/scripts/mcp-gateway-start.mjs +126 -60
  216. package/scripts/mcp-gateway-verify.mjs +24 -22
  217. package/scripts/mcp-safety-guard.mjs +44 -11
  218. package/scripts/notion-read.mjs +669 -554
  219. package/scripts/pack.mjs +94 -89
  220. package/scripts/preflight-cache.mjs +27 -10
  221. package/scripts/preinstall.mjs +42 -13
  222. package/scripts/remote-spawn.mjs +309 -94
  223. package/scripts/run.cjs +8 -5
  224. package/scripts/session-spawn-helper.mjs +130 -39
  225. package/scripts/session-stale-cleanup.mjs +123 -0
  226. package/scripts/setup.mjs +941 -492
  227. package/scripts/test-lock.mjs +20 -7
  228. package/scripts/test-tfx-route-no-claude-native.mjs +61 -57
  229. package/scripts/tfx-batch-stats.mjs +117 -96
  230. package/scripts/tfx-gate-activate.mjs +11 -4
  231. package/scripts/tfx-route-post.mjs +87 -20
  232. package/scripts/tfx-route-worker.mjs +57 -51
  233. package/scripts/tfx-route.sh +41 -124
  234. package/scripts/tmp-cleanup.mjs +21 -7
  235. package/scripts/token-snapshot.mjs +204 -85
  236. package/skills/.omc/state/idle-notif-cooldown.json +1 -1
  237. package/skills/.omc/state/last-tool-error.json +1 -1
  238. package/skills/.omc/state/subagent-tracking.json +1 -1
  239. package/skills/_templates/base.md +1 -6
  240. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  241. package/skills/shared/telemetry-segment.md +6 -0
  242. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  243. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  244. package/skills/tfx-analysis/skill.json +1 -6
  245. package/skills/tfx-auto/SKILL.md +1 -0
  246. package/skills/{tfx-workspace/skill-snapshot/tfx-auto-codex/SKILL.md → tfx-auto-codex/SKILL.md.tmpl} +33 -4
  247. package/skills/tfx-auto-codex/skill.json +1 -3
  248. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  249. package/skills/tfx-autopilot/skill.json +1 -5
  250. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  251. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  252. package/skills/tfx-autoroute/skill.json +1 -7
  253. package/skills/tfx-codex/SKILL.md +1 -0
  254. package/skills/tfx-codex/skill.json +1 -3
  255. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  256. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  257. package/skills/tfx-codex-swarm/skill.json +1 -4
  258. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  265. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  266. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  267. package/skills/tfx-debate/skill.json +1 -7
  268. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  269. package/skills/tfx-deep-analysis/skill.json +1 -5
  270. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  271. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  272. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  273. package/skills/tfx-deep-qa/skill.json +1 -6
  274. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  275. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  276. package/skills/tfx-doctor/SKILL.md +21 -0
  277. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  278. package/skills/tfx-doctor/skill.json +1 -3
  279. package/skills/tfx-find/SKILL.md +1 -0
  280. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  281. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  282. package/skills/tfx-fullcycle/skill.json +1 -6
  283. package/skills/{tfx-workspace/skill-snapshot/tfx-gemini/SKILL.md → tfx-gemini/SKILL.md.tmpl} +16 -7
  284. package/skills/tfx-gemini/skill.json +1 -3
  285. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  286. package/skills/tfx-hooks/skill.json +1 -3
  287. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  288. package/skills/tfx-hub/skill.json +1 -3
  289. package/skills/tfx-index/SKILL.md +1 -0
  290. package/skills/tfx-index/skill.json +1 -6
  291. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  292. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  293. package/skills/tfx-multi/skill.json +1 -3
  294. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  295. package/skills/tfx-panel/skill.json +1 -7
  296. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  297. package/skills/tfx-persist/skill.json +1 -7
  298. package/skills/tfx-plan/SKILL.md +1 -0
  299. package/skills/tfx-plan/skill.json +1 -6
  300. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  301. package/skills/tfx-profile/skill.json +1 -3
  302. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  303. package/skills/tfx-prune/skill.json +1 -7
  304. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  305. package/skills/tfx-psmux-rules/skill.json +1 -4
  306. package/skills/tfx-qa/SKILL.md +1 -0
  307. package/skills/tfx-qa/skill.json +1 -6
  308. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  309. package/skills/tfx-ralph/skill.json +1 -4
  310. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  311. package/skills/tfx-remote-setup/skill.json +1 -3
  312. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  313. package/skills/tfx-remote-spawn/references/hosts.json +16 -16
  314. package/skills/tfx-remote-spawn/skill.json +1 -4
  315. package/skills/tfx-research/SKILL.md +1 -0
  316. package/skills/tfx-review/SKILL.md +1 -0
  317. package/skills/tfx-review/skill.json +1 -6
  318. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  319. package/skills/tfx-setup/skill.json +1 -3
  320. package/skills/tfx-swarm/SKILL.md +22 -0
  321. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  322. package/tui/codex-profile.mjs +457 -0
  323. package/tui/core.mjs +266 -0
  324. package/tui/doctor.mjs +375 -0
  325. package/tui/gemini-profile.mjs +299 -0
  326. package/tui/monitor-data.mjs +152 -0
  327. package/tui/monitor.mjs +339 -0
  328. package/tui/setup.mjs +533 -0
  329. package/CLAUDE.md +0 -193
  330. package/references/hosts.json +0 -33
  331. package/skills/tfx-workspace/async-tests/run-tests.sh +0 -203
  332. package/skills/tfx-workspace/evals/evals.json +0 -79
  333. package/skills/tfx-workspace/iteration-1/benchmark.json +0 -162
  334. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/eval_metadata.json +0 -11
  335. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/grading.json +0 -9
  336. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/outputs/analysis.md +0 -154
  337. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/old_skill/timing.json +0 -5
  338. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/grading.json +0 -9
  339. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/outputs/analysis.md +0 -126
  340. package/skills/tfx-workspace/iteration-1/codex-gemini-remap/with_skill/timing.json +0 -5
  341. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/eval_metadata.json +0 -11
  342. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/grading.json +0 -9
  343. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/outputs/analysis.md +0 -119
  344. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/old_skill/timing.json +0 -5
  345. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/grading.json +0 -9
  346. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/outputs/analysis.md +0 -115
  347. package/skills/tfx-workspace/iteration-1/doctor-diagnosis/with_skill/timing.json +0 -5
  348. package/skills/tfx-workspace/iteration-1/hub-start-sequence/eval_metadata.json +0 -10
  349. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/grading.json +0 -8
  350. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/outputs/analysis.md +0 -86
  351. package/skills/tfx-workspace/iteration-1/hub-start-sequence/old_skill/timing.json +0 -5
  352. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/grading.json +0 -8
  353. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/outputs/analysis.md +0 -81
  354. package/skills/tfx-workspace/iteration-1/hub-start-sequence/with_skill/timing.json +0 -5
  355. package/skills/tfx-workspace/iteration-1/multi-team-creation/eval_metadata.json +0 -12
  356. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/grading.json +0 -10
  357. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/outputs/analysis.md +0 -316
  358. package/skills/tfx-workspace/iteration-1/multi-team-creation/old_skill/timing.json +0 -5
  359. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/grading.json +0 -10
  360. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/outputs/analysis.md +0 -352
  361. package/skills/tfx-workspace/iteration-1/multi-team-creation/with_skill/timing.json +0 -5
  362. package/skills/tfx-workspace/iteration-1/review.html +0 -1325
  363. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/eval_metadata.json +0 -12
  364. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/grading.json +0 -10
  365. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/outputs/analysis.md +0 -97
  366. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/old_skill/timing.json +0 -5
  367. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/grading.json +0 -10
  368. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/outputs/analysis.md +0 -94
  369. package/skills/tfx-workspace/iteration-1/routing-implement-shortcut/with_skill/timing.json +0 -5
  370. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/eval_metadata.json +0 -12
  371. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/grading.json +0 -10
  372. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/outputs/analysis.md +0 -209
  373. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/old_skill/timing.json +0 -5
  374. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/grading.json +0 -10
  375. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/outputs/analysis.md +0 -193
  376. package/skills/tfx-workspace/iteration-1/routing-multi-task-triage/with_skill/timing.json +0 -5
  377. package/skills/tfx-workspace/iteration-2/benchmark.json +0 -62
  378. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/eval_metadata.json +0 -13
  379. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/grading.json +0 -11
  380. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/outputs/analysis.md +0 -382
  381. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/old_skill/timing.json +0 -5
  382. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/grading.json +0 -11
  383. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/outputs/analysis.md +0 -333
  384. package/skills/tfx-workspace/iteration-2/multi-team-creation-refactored/with_skill/timing.json +0 -5
  385. package/skills/tfx-workspace/iteration-2/review.html +0 -1325
  386. package/skills/tfx-workspace/skill-snapshot/tfx-auto/SKILL.md +0 -217
  387. package/skills/tfx-workspace/skill-snapshot/tfx-codex/SKILL.md +0 -65
  388. package/skills/tfx-workspace/skill-snapshot/tfx-doctor/SKILL.md +0 -94
  389. package/skills/tfx-workspace/skill-snapshot/tfx-hub/SKILL.md +0 -133
  390. package/skills/tfx-workspace/skill-snapshot/tfx-multi/SKILL.md +0 -426
  391. package/skills/tfx-workspace/skill-snapshot/tfx-setup/SKILL.md +0 -101
@@ -0,0 +1,218 @@
1
+ ---
2
+ name: tfx-swarm
3
+ description: "tfx-swarm — 다중 기기 x 다중 모델 스웜 오케스트레이션"
4
+ triggers:
5
+ - swarm
6
+ - 스웜
7
+ - 병렬 실행
8
+ - codex-swarm
9
+ ---
10
+
11
+ # {{SKILL_NAME}} — 다중 기기 x 다중 모델 스웜 오케스트레이션
12
+
13
+ {{> base}}
14
+
15
+ > **Multi-Machine x Multi-Model Swarm.** PRD 하나로 로컬과 원격 머신에서
16
+ > Claude + Codex + Gemini를 병렬 실행하고, file-lease로 충돌을 방지하며,
17
+ > 결과를 자동 통합한다. triflux의 킬러 스킬.
18
+
19
+ ## 트리거
20
+
21
+ - `swarm`, `스웜`, `병렬 실행`, `다중 워커`, `PRD 실행`, `swarm launch`
22
+ - `codex-swarm` (backward compat -> 이 스킬로 라우팅)
23
+
24
+ ## 핵심 기능
25
+
26
+ | 기능 | 설명 |
27
+ |------|------|
28
+ | **다중 모델** | shard별 `agent: codex\|gemini\|claude` 지정. 작업 특성에 맞는 모델 배치 |
29
+ | **다중 기기** | shard별 `host: <ssh-host>` 지정. 로컬/원격 혼합 실행 |
30
+ | **File Lease** | shard별 파일 소유권. 동일 파일 동시 수정 방지 |
31
+ | **Redundant Execution** | critical shard는 다른 모델로 이중 실행 후 reconcile |
32
+ | **자동 통합** | 의존 순서대로 merge. 원격 shard는 SSH 경유 fetch |
33
+ | **장애 폴백** | rate limit -> 다른 모델로 자동 전환 (codex<->gemini<->claude) |
34
+
35
+ ## 전제조건
36
+
37
+ - psmux >= 3.3.0
38
+ - Hub 실행 중 (`curl -sf http://127.0.0.1:27888/status`)
39
+ - Codex CLI 또는 Gemini CLI (사용할 agent에 따라)
40
+ - 원격 shard 사용 시: SSH 키 인증 + 원격 머신에 Claude Code 설치
41
+ - 프로젝트에 `docs/prd/` 디렉토리 존재
42
+
43
+ ## 실행 흐름
44
+
45
+ ### Step 1: PRD 탐색
46
+
47
+ ```bash
48
+ find docs/prd -name '*.md' -not -name '_template.md' -not -path '*/archived/*' | sort
49
+ ```
50
+
51
+ AskUserQuestion으로 실행할 PRD 선택. 복수 선택 시 각각 독립 shard.
52
+
53
+ ### Step 2: 원격/모델 구성 확인
54
+
55
+ PRD 파싱 후, shard에 `host:` 필드가 있으면 AskUserQuestion으로 원격 실행 여부를 확인한다.
56
+
57
+ AskUserQuestion:
58
+
59
+ > PRD에 원격 호스트가 지정된 shard가 있습니다:
60
+ > - `{shard_name}` -> `{host}` ({agent})
61
+ >
62
+ > 원격 실행을 어떻게 할까요?
63
+
64
+ Options:
65
+ - A) 원격 포함 실행 (PRD 그대로) — 로컬+원격 혼합
66
+ - B) 전부 로컬에서 실행 — host 필드 무시
67
+ - C) 원격만 실행 — 로컬 shard 건너뛰기
68
+
69
+ PRD에 다중 agent가 지정된 경우에도 AskUserQuestion:
70
+
71
+ > PRD에 여러 모델이 지정되어 있습니다:
72
+ > - codex: {N}개 shard
73
+ > - gemini: {N}개 shard
74
+ > - claude: {N}개 shard
75
+ >
76
+ > 모델 배치를 어떻게 할까요?
77
+
78
+ Options:
79
+ - A) PRD 그대로 — shard별 지정 모델 사용 (권장)
80
+ - B) 전부 Codex로 — 단일 모델
81
+ - C) 전부 Gemini로 — 단일 모델
82
+
83
+ ### Step 3: 계획 생성
84
+
85
+ ```javascript
86
+ import { planSwarm } from '../../hub/team/swarm-planner.mjs';
87
+
88
+ const swarmPlan = planSwarm(selectedPrdPath);
89
+ ```
90
+
91
+ 계획을 사용자에게 보여주고 승인 요청:
92
+ - shard 수, 파일 배분, lease 맵, merge 순서
93
+ - 원격 shard 표시 (host 정보)
94
+ - 모델 배분 표시 (agent 정보)
95
+ - critical shard 표시 (redundant execution 대상)
96
+
97
+ ### Step 4: Hypervisor 실행
98
+
99
+ ```javascript
100
+ import { createSwarmHypervisor } from '../../hub/team/swarm-hypervisor.mjs';
101
+
102
+ const hyper = createSwarmHypervisor({
103
+ workdir: process.cwd(),
104
+ logsDir: join(process.cwd(), '.triflux', 'swarm-logs'),
105
+ maxRestarts: 2,
106
+ });
107
+
108
+ const run = hyper.launch(swarmPlan);
109
+ ```
110
+
111
+ 실행 중 상태 모니터링:
112
+ - `hyper.on('shardLaunched', ...)` -> 진행 표시
113
+ - `hyper.on('shardCompleted', ...)` -> 완료/실패 표시
114
+ - `hyper.on('warning', ...)` -> 경고 (파일 충돌 등)
115
+
116
+ 원격 shard 실행 시:
117
+ 1. `probeRemoteEnv(host)` -> 원격 환경 감지 (OS, shell, Claude 경로)
118
+ 2. conductor가 `remote: true` 설정으로 SSH 경유 세션 실행
119
+ 3. 완료 후 `fetchRemoteShard()`로 원격 브랜치를 로컬로 fetch
120
+
121
+ ### Step 5: 결과 검증 + 통합
122
+
123
+ ```javascript
124
+ const status = hyper.getStatus();
125
+ // status.completedShards, status.failedShards, status.workers
126
+ ```
127
+
128
+ 통합 결과를 사용자에게 보고:
129
+ - 성공: merged shard 목록 (로컬/원격 구분 표시)
130
+ - 실패: 충돌/실패 shard 목록 + 수동 해결 안내
131
+
132
+ ### Step 6: 정리
133
+
134
+ ```javascript
135
+ await hyper.shutdown('completed');
136
+ ```
137
+
138
+ ## PRD 예제: 다중 기기 x 다중 모델
139
+
140
+ ```markdown
141
+ ## Shard: auth-refactor
142
+ - agent: codex
143
+ - files: src/auth.mjs, src/middleware/jwt.mjs
144
+ - prompt: JWT 인증 미들웨어 리팩터링
145
+
146
+ ## Shard: ui-dashboard
147
+ - agent: gemini
148
+ - files: src/ui/dashboard.mjs, src/ui/charts.mjs
149
+ - prompt: 대시보드 UI 개선
150
+
151
+ ## Shard: security-audit
152
+ - agent: claude
153
+ - host: ryzen5-7600
154
+ - files: src/security.mjs
155
+ - critical: true
156
+ - prompt: 보안 취약점 감사
157
+
158
+ ## Shard: perf-optimization
159
+ - agent: codex
160
+ - host: m2
161
+ - files: src/engine/optimizer.mjs
162
+ - depends: auth-refactor
163
+ - prompt: 성능 최적화 (auth 리팩터 완료 후)
164
+ ```
165
+
166
+ 위 PRD 하나로: Codex(로컬) + Gemini(로컬) + Claude(ryzen5-7600) + Codex(m2) 4개 워커가 병렬 실행된다.
167
+ security-audit는 `critical: true`이므로 다른 모델로 이중 실행 후 reconcile.
168
+
169
+ ## PRD Shard 필드 레퍼런스
170
+
171
+ | 필드 | 필수 | 기본값 | 설명 |
172
+ |------|------|--------|------|
173
+ | `agent` | - | `codex` | 실행 모델: `codex`, `gemini`, `claude` |
174
+ | `host` | - | (로컬) | SSH 호스트. 미지정 시 로컬 실행 |
175
+ | `files` | O | - | shard가 수정할 파일 목록 (file-lease 대상) |
176
+ | `depends` | - | - | 의존하는 shard 이름. 해당 shard 완료 후 실행 |
177
+ | `critical` | - | `false` | `true`면 다른 모델로 이중 실행 + reconcile |
178
+ | `mcp` | - | - | 필요한 MCP 서버 목록 |
179
+ | `prompt` | O | - | shard 실행 프롬프트. `\|`로 멀티라인 |
180
+
181
+ ## 제약
182
+
183
+ - `codex exec` / `gemini -p` 직접 호출 금지 (headless-guard)
184
+ - MAX_CONCURRENCY 기본 4 (WT 프리징 방지)
185
+ - WT 조작 간 sleep 2s (race-guard)
186
+ - config.toml과 CLI 플래그 중복 지정 금지
187
+
188
+ ## Redundant Execution
189
+
190
+ critical shard (hard lease 또는 고위험 파일)에 대해:
191
+
192
+ ```javascript
193
+ import { shouldRunRedundant, reconcile } from '../../hub/team/swarm-reconciler.mjs';
194
+
195
+ if (shouldRunRedundant(shard)) {
196
+ // primary + verifier 이중 실행
197
+ const decision = await reconcile(primaryResult, verifierResult);
198
+ // decision.selected: 'primary' | 'verifier' | 'hitl'
199
+ }
200
+ ```
201
+
202
+ HITL fallback 시 AskUserQuestion으로 사용자에게 선택 요청.
203
+
204
+ ## 장애 처리
205
+
206
+ | 장애 | 분류 | 대응 |
207
+ |------|------|------|
208
+ | 워커 크래시 | F1 | conductor auto-restart (최대 2회) |
209
+ | Rate limit | F2 | 다른 모델로 자동 전환 (codex->gemini->claude) |
210
+ | Stall | F3 | health probe 감지 -> kill -> restart |
211
+ | File lease 위반 | F4 | 워커 변경 revert, shard 실패 처리 |
212
+ | Merge 충돌 | F5 | 충돌 해결 재시도 |
213
+
214
+ ## 기존 스킬과의 관계
215
+
216
+ - **tfx-codex-swarm**: deprecated. 이 스킬로 통합됨 (backward compat alias)
217
+ - **tfx-remote-spawn**: 단독 원격 세션 관리(list, attach, send)용으로 유지. swarm은 다중 shard 병렬 관리
218
+ - **tfx-multi**: Claude Native Teams 기반 로컬 오케스트레이션. swarm은 PRD 기반 worktree 분할
@@ -0,0 +1,457 @@
1
+ #!/usr/bin/env node
2
+ // tui/codex-profile.mjs — Interactive Codex Profile Manager
3
+ import {
4
+ copyFileSync,
5
+ existsSync,
6
+ mkdirSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import {
13
+ BOLD,
14
+ box,
15
+ CYAN,
16
+ clear,
17
+ confirm,
18
+ DIM,
19
+ divider,
20
+ fail,
21
+ GREEN,
22
+ info,
23
+ input,
24
+ label,
25
+ ok,
26
+ onExit,
27
+ RED,
28
+ RESET,
29
+ select,
30
+ showCursor,
31
+ table,
32
+ WHITE,
33
+ warn,
34
+ YELLOW,
35
+ } from "./core.mjs";
36
+
37
+ const CODEX_DIR = join(homedir(), ".codex");
38
+ const CONFIG_PATH = join(CODEX_DIR, "config.toml");
39
+
40
+ const KNOWN_MODELS = [
41
+ { label: "gpt-5.4", hint: "최신 플래그십" },
42
+ { label: "gpt-5.3-codex", hint: "코딩 특화" },
43
+ { label: "gpt-5.1-codex-mini", hint: "경량 Spark" },
44
+ { label: "o3", hint: "추론 특화" },
45
+ { label: "o4-mini", hint: "추론 경량" },
46
+ { label: "직접 입력", hint: "" },
47
+ ];
48
+
49
+ const EFFORT_LEVELS = [
50
+ { label: "low", hint: "빠른 응답, 최소 추론" },
51
+ { label: "medium", hint: "균형 잡힌 추론" },
52
+ { label: "high", hint: "깊은 추론" },
53
+ { label: "xhigh", hint: "최대 추론 (느림)" },
54
+ ];
55
+
56
+ // ── TOML Parsing ──
57
+
58
+ function readConfig() {
59
+ if (!existsSync(CONFIG_PATH)) return { raw: "", defaults: {}, profiles: [] };
60
+ const raw = readFileSync(CONFIG_PATH, "utf8");
61
+ return { raw, ...parseConfig(raw) };
62
+ }
63
+
64
+ function parseConfig(raw) {
65
+ const lines = raw.split("\n");
66
+ const defaults = {};
67
+ const profiles = [];
68
+ let currentSection = null;
69
+ let currentProfile = null;
70
+
71
+ for (const line of lines) {
72
+ const trimmed = line.trim();
73
+ if (!trimmed || trimmed.startsWith("#")) continue;
74
+
75
+ const sectionMatch = trimmed.match(/^\[(.+)\]$/);
76
+ if (sectionMatch) {
77
+ const name = sectionMatch[1];
78
+ const profileMatch = name.match(/^profiles\.(\w+)$/);
79
+ if (profileMatch) {
80
+ currentSection = "profile";
81
+ currentProfile = { name: profileMatch[1] };
82
+ profiles.push(currentProfile);
83
+ } else {
84
+ currentSection = name;
85
+ currentProfile = null;
86
+ }
87
+ continue;
88
+ }
89
+
90
+ const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
91
+ if (kvMatch) {
92
+ const [, key, rawVal] = kvMatch;
93
+ const value = rawVal.replace(/^["']|["']$/g, "").trim();
94
+ if (currentSection === "profile" && currentProfile) {
95
+ currentProfile[key] = value;
96
+ } else if (!currentSection) {
97
+ defaults[key] = value;
98
+ }
99
+ }
100
+ }
101
+
102
+ return { defaults, profiles };
103
+ }
104
+
105
+ function writeProfile(raw, profileName, props) {
106
+ const lines = raw.split("\n");
107
+ const sectionRe = new RegExp(`^\\[profiles\\.${escRe(profileName)}\\]\\s*$`);
108
+ let inSection = false;
109
+ let sectionStart = -1;
110
+ let sectionEnd = lines.length;
111
+
112
+ for (let i = 0; i < lines.length; i++) {
113
+ if (sectionRe.test(lines[i].trim())) {
114
+ inSection = true;
115
+ sectionStart = i;
116
+ continue;
117
+ }
118
+ if (inSection && lines[i].trim().startsWith("[")) {
119
+ sectionEnd = i;
120
+ break;
121
+ }
122
+ }
123
+
124
+ if (sectionStart === -1) {
125
+ // Append new profile section
126
+ const newLines = [`[profiles.${profileName}]`];
127
+ for (const [k, v] of Object.entries(props)) {
128
+ newLines.push(`${k} = "${v}"`);
129
+ }
130
+ return raw.trimEnd() + "\n" + newLines.join("\n") + "\n";
131
+ }
132
+
133
+ // Replace existing section body
134
+ const newBody = [];
135
+ for (const [k, v] of Object.entries(props)) {
136
+ newBody.push(`${k} = "${v}"`);
137
+ }
138
+ lines.splice(sectionStart + 1, sectionEnd - sectionStart - 1, ...newBody);
139
+ return lines.join("\n");
140
+ }
141
+
142
+ function deleteProfile(raw, profileName) {
143
+ const lines = raw.split("\n");
144
+ const sectionRe = new RegExp(`^\\[profiles\\.${escRe(profileName)}\\]\\s*$`);
145
+ let inSection = false;
146
+ let start = -1;
147
+ let end = lines.length;
148
+
149
+ for (let i = 0; i < lines.length; i++) {
150
+ if (sectionRe.test(lines[i].trim())) {
151
+ inSection = true;
152
+ start = i;
153
+ continue;
154
+ }
155
+ if (inSection && lines[i].trim().startsWith("[")) {
156
+ end = i;
157
+ break;
158
+ }
159
+ }
160
+
161
+ if (start === -1) return raw;
162
+ // Remove trailing blank lines too
163
+ while (end < lines.length && lines[end].trim() === "") end++;
164
+ lines.splice(start, end - start);
165
+ return lines.join("\n");
166
+ }
167
+
168
+ function setDefault(raw, key, value) {
169
+ const lines = raw.split("\n");
170
+ const keyRe = new RegExp(`^${escRe(key)}\\s*=`);
171
+
172
+ for (let i = 0; i < lines.length; i++) {
173
+ if (lines[i].trim().startsWith("[")) break; // hit first section
174
+ if (keyRe.test(lines[i].trim())) {
175
+ lines[i] = `${key} = "${value}"`;
176
+ return lines.join("\n");
177
+ }
178
+ }
179
+
180
+ // Key not found — insert before first section
181
+ for (let i = 0; i < lines.length; i++) {
182
+ if (lines[i].trim().startsWith("[")) {
183
+ lines.splice(i, 0, `${key} = "${value}"`);
184
+ return lines.join("\n");
185
+ }
186
+ }
187
+
188
+ return raw + `\n${key} = "${value}"\n`;
189
+ }
190
+
191
+ function escRe(s) {
192
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
193
+ }
194
+
195
+ // ── UI Flows ──
196
+
197
+ function showStatus(config) {
198
+ const { defaults, profiles } = config;
199
+
200
+ console.log();
201
+ label("기본 모델", `${WHITE}${defaults.model || "미설정"}${RESET}`);
202
+ label(
203
+ "기본 Effort",
204
+ `${WHITE}${defaults.model_reasoning_effort || "미설정"}${RESET}`,
205
+ );
206
+ console.log();
207
+
208
+ if (profiles.length === 0) {
209
+ warn("등록된 프로파일이 없습니다.");
210
+ return;
211
+ }
212
+
213
+ const headers = ["프로파일", "모델", "Effort", "기타"];
214
+ const rows = profiles.map((p) => {
215
+ const extras = Object.entries(p)
216
+ .filter(([k]) => !["name", "model", "model_reasoning_effort"].includes(k))
217
+ .map(([k, v]) => `${k}=${v}`)
218
+ .join(", ");
219
+ return [
220
+ `${CYAN}${p.name}${RESET}`,
221
+ p.model || DIM + "inherit" + RESET,
222
+ effortColor(p.model_reasoning_effort),
223
+ extras ? `${DIM}${extras}${RESET}` : "",
224
+ ];
225
+ });
226
+
227
+ table(headers, rows);
228
+ }
229
+
230
+ function effortColor(effort) {
231
+ if (!effort) return `${DIM}inherit${RESET}`;
232
+ const colors = { low: GREEN, medium: CYAN, high: YELLOW, xhigh: RED };
233
+ return `${colors[effort] || ""}${effort}${RESET}`;
234
+ }
235
+
236
+ async function pickModel(current) {
237
+ const idx = KNOWN_MODELS.findIndex((m) => m.label === current);
238
+ const choice = await select("모델 선택", KNOWN_MODELS, {
239
+ initial: Math.max(0, idx),
240
+ });
241
+ if (!choice) return null;
242
+ if (choice.value.label === "직접 입력") {
243
+ return await input("모델 ID", current || "");
244
+ }
245
+ return choice.value.label;
246
+ }
247
+
248
+ async function pickEffort(current) {
249
+ const idx = EFFORT_LEVELS.findIndex((e) => e.label === current);
250
+ const choice = await select("Reasoning Effort 선택", EFFORT_LEVELS, {
251
+ initial: Math.max(0, idx),
252
+ });
253
+ if (!choice) return null;
254
+ return choice.value.label;
255
+ }
256
+
257
+ async function editProfile(config) {
258
+ const { profiles } = config;
259
+ if (profiles.length === 0) {
260
+ warn("편집할 프로파일이 없습니다.");
261
+ return config;
262
+ }
263
+
264
+ const options = profiles.map((p) => ({
265
+ label: p.name,
266
+ hint: `${DIM}${p.model || "inherit"} / ${p.model_reasoning_effort || "inherit"}${RESET}`,
267
+ }));
268
+
269
+ const picked = await select("편집할 프로파일", options);
270
+ if (!picked) return config;
271
+
272
+ const profile = profiles[picked.index];
273
+ console.log();
274
+ info(
275
+ `현재: ${BOLD}${profile.name}${RESET} → ${profile.model} / ${profile.model_reasoning_effort}`,
276
+ );
277
+
278
+ const newModel = await pickModel(profile.model);
279
+ if (newModel === null) return config;
280
+
281
+ const newEffort = await pickEffort(profile.model_reasoning_effort);
282
+ if (newEffort === null) return config;
283
+
284
+ console.log();
285
+ info(
286
+ `변경: ${profile.model} → ${BOLD}${newModel}${RESET}, ${profile.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`,
287
+ );
288
+
289
+ if (!(await confirm("저장하시겠습니까?"))) return config;
290
+
291
+ const props = { model: newModel, model_reasoning_effort: newEffort };
292
+ // Preserve extra props (like model_temperature)
293
+ for (const [k, v] of Object.entries(profile)) {
294
+ if (!["name", "model", "model_reasoning_effort"].includes(k)) props[k] = v;
295
+ }
296
+
297
+ const raw = writeProfile(config.raw, profile.name, props);
298
+ save(raw);
299
+ ok(`${profile.name} 프로파일 저장 완료`);
300
+ return readConfig();
301
+ }
302
+
303
+ async function editDefault(config) {
304
+ const { defaults } = config;
305
+ info(`현재 기본 모델: ${BOLD}${defaults.model || "미설정"}${RESET}`);
306
+ info(
307
+ `현재 기본 Effort: ${BOLD}${defaults.model_reasoning_effort || "미설정"}${RESET}`,
308
+ );
309
+
310
+ const newModel = await pickModel(defaults.model);
311
+ if (newModel === null) return config;
312
+
313
+ const newEffort = await pickEffort(defaults.model_reasoning_effort);
314
+ if (newEffort === null) return config;
315
+
316
+ console.log();
317
+ info(
318
+ `변경: ${defaults.model} → ${BOLD}${newModel}${RESET}, ${defaults.model_reasoning_effort} → ${BOLD}${newEffort}${RESET}`,
319
+ );
320
+
321
+ if (!(await confirm("저장하시겠습니까?"))) return config;
322
+
323
+ let raw = setDefault(config.raw, "model", newModel);
324
+ raw = setDefault(raw, "model_reasoning_effort", newEffort);
325
+ save(raw);
326
+ ok("기본 설정 저장 완료");
327
+ return readConfig();
328
+ }
329
+
330
+ async function addProfile(config) {
331
+ const name = await input("새 프로파일 이름");
332
+ if (!name) return config;
333
+
334
+ if (config.profiles.some((p) => p.name === name)) {
335
+ fail(`'${name}' 프로파일이 이미 존재합니다.`);
336
+ return config;
337
+ }
338
+
339
+ const model = await pickModel("");
340
+ if (!model) return config;
341
+
342
+ const effort = await pickEffort("");
343
+ if (!effort) return config;
344
+
345
+ console.log();
346
+ info(`추가: ${BOLD}${name}${RESET} → ${model} / ${effort}`);
347
+ if (!(await confirm("저장하시겠습니까?"))) return config;
348
+
349
+ const raw = writeProfile(config.raw, name, {
350
+ model,
351
+ model_reasoning_effort: effort,
352
+ });
353
+ save(raw);
354
+ ok(`${name} 프로파일 추가 완료`);
355
+ return readConfig();
356
+ }
357
+
358
+ async function removeProfile(config) {
359
+ const { profiles } = config;
360
+ if (profiles.length === 0) {
361
+ warn("삭제할 프로파일이 없습니다.");
362
+ return config;
363
+ }
364
+
365
+ const options = profiles.map((p) => ({ label: p.name, hint: `${p.model}` }));
366
+ const picked = await select("삭제할 프로파일", options);
367
+ if (!picked) return config;
368
+
369
+ const name = profiles[picked.index].name;
370
+ if (
371
+ !(await confirm(
372
+ `${RED}${name}${RESET} 프로파일을 삭제하시겠습니까?`,
373
+ false,
374
+ ))
375
+ ) {
376
+ return config;
377
+ }
378
+
379
+ const raw = deleteProfile(config.raw, name);
380
+ save(raw);
381
+ ok(`${name} 프로파일 삭제 완료`);
382
+ return readConfig();
383
+ }
384
+
385
+ function save(content) {
386
+ if (!existsSync(CODEX_DIR)) mkdirSync(CODEX_DIR, { recursive: true });
387
+
388
+ // Backup before write
389
+ if (existsSync(CONFIG_PATH)) {
390
+ const backupPath = CONFIG_PATH + ".bak";
391
+ copyFileSync(CONFIG_PATH, backupPath);
392
+ }
393
+
394
+ writeFileSync(CONFIG_PATH, content, "utf8");
395
+ }
396
+
397
+ // ── Main Loop ──
398
+
399
+ const MENU = [
400
+ { label: "프로파일 모델 변경", hint: "모델/effort 수정" },
401
+ { label: "기본 모델 변경", hint: "top-level default" },
402
+ { label: "프로파일 추가", hint: "새 프로파일 생성" },
403
+ { label: "프로파일 삭제", hint: "기존 프로파일 제거" },
404
+ { label: "종료", hint: "Ctrl+C" },
405
+ ];
406
+
407
+ async function main() {
408
+ onExit(() => {});
409
+ clear();
410
+
411
+ let config = readConfig();
412
+
413
+ if (!existsSync(CONFIG_PATH)) {
414
+ fail(`config.toml 미존재: ${CONFIG_PATH}`);
415
+ info("codex를 먼저 설치하거나 /tfx-setup을 실행하세요.");
416
+ process.exit(1);
417
+ }
418
+
419
+ while (true) {
420
+ box("Codex Profile Manager", 46);
421
+ showStatus(config);
422
+ console.log();
423
+
424
+ const choice = await select("작업 선택", MENU);
425
+ if (!choice || choice.index === 4) {
426
+ console.log();
427
+ info("종료합니다.");
428
+ showCursor();
429
+ break;
430
+ }
431
+
432
+ console.log();
433
+ switch (choice.index) {
434
+ case 0:
435
+ config = await editProfile(config);
436
+ break;
437
+ case 1:
438
+ config = await editDefault(config);
439
+ break;
440
+ case 2:
441
+ config = await addProfile(config);
442
+ break;
443
+ case 3:
444
+ config = await removeProfile(config);
445
+ break;
446
+ }
447
+
448
+ console.log();
449
+ divider(46);
450
+ }
451
+ }
452
+
453
+ main().catch((e) => {
454
+ showCursor();
455
+ console.error(e);
456
+ process.exit(1);
457
+ });