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
package/hub/tray.mjs CHANGED
@@ -1,376 +1,419 @@
1
- #!/usr/bin/env node
2
-
3
- import _SysTrayModule from "systray2";
4
- const SysTray = _SysTrayModule.default || _SysTrayModule;
5
- import { exec } from "node:child_process";
6
- import { existsSync, readFileSync } from "node:fs";
1
+ #!/usr/bin/env node
2
+
3
+ import _SysTrayModule from "systray2";
4
+
5
+ const SysTray = _SysTrayModule.default || _SysTrayModule;
6
+
7
+ import { exec } from "node:child_process";
8
+ import { existsSync, readFileSync } from "node:fs";
7
9
  import { homedir } from "node:os";
8
10
  import { join, resolve } from "node:path";
9
11
  import { fileURLToPath } from "node:url";
10
12
  import { IS_WINDOWS } from "./platform.mjs";
11
-
12
- const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
13
- const DEFAULT_HUB_PORT = "27888";
14
-
15
- function getHubBaseUrl() {
16
- if (process.env.TFX_HUB_URL) return process.env.TFX_HUB_URL.replace(/\/+$/, "");
17
- try {
18
- const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
19
- if (info.port) return `http://${info.host || "127.0.0.1"}:${info.port}`;
20
- } catch {}
21
- const port = process.env.TFX_HUB_PORT || DEFAULT_HUB_PORT;
22
- return `http://127.0.0.1:${port}`;
23
- }
24
-
25
- function getDashboardUrl() {
26
- return `${getHubBaseUrl()}/dashboard`;
27
- }
28
-
29
- function getHubStatusUrl() {
30
- return `${getHubBaseUrl()}/status`;
31
- }
32
- const POLL_INTERVAL_MS = 10_000;
33
- const HUB_TIMEOUT_MS = 3_000;
34
- const AIMD_INITIAL = 3;
35
- const AIMD_MIN = 1;
36
- const AIMD_MAX = 10;
37
- const AIMD_WINDOW_MS = 30 * 60 * 1000;
38
-
39
- const CACHE_DIR = join(homedir(), ".claude", "cache");
40
- const BATCH_EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
41
- const CODEX_RATE_LIMITS_FILE = join(CACHE_DIR, "codex-rate-limits-cache.json");
42
- const GEMINI_QUOTA_FILE = join(CACHE_DIR, "gemini-quota-cache.json");
43
- const CLAUDE_USAGE_FILE = join(CACHE_DIR, "claude-usage-cache.json");
44
-
45
- const TRAY_ICON_BASE64 = "AAABAAEAICAAAAEAIAADAQAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAMpJREFUeJzV1UEKgzAQheEcwnXP4a17gl6n6yy7U1IIqDSTeW/m0TYwK8X/E6OW8m9rWW5Pa74SlWLYeBgRDcOQ7V42a+SIGSALkwKIQKDnroJQmy4bAgO8EDnAA4EBkV3do7VWeAOfAO0Cx/EC+vnMG2QCLND12Pp4vUcK+DQ9fBxqI47uDI23oV7F2fNVxF2A64zCTBwCWGE2Pv0WzKKp8ba8wWg4DIiGKUBWdBjP+i+E4z8BUCJccRUCimdC6HAGIiWOYiRR5doBauXshzcEs0UAAAAASUVORK5CYII=";
46
-
47
- function clampPercent(value) {
48
- if (!Number.isFinite(Number(value))) return null;
49
- return Math.max(0, Math.min(100, Math.round(Number(value))));
50
- }
51
-
52
- function readJson(filePath) {
53
- try {
54
- return JSON.parse(readFileSync(filePath, "utf8"));
55
- } catch {
56
- return null;
57
- }
58
- }
59
-
60
- function readLines(filePath) {
61
- try {
62
- return readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
63
- } catch {
64
- return [];
65
- }
66
- }
67
-
68
- function isSuccessResult(result) {
69
- return result === "success" || result === "success_with_warnings";
70
- }
71
-
72
- function getAimdBatchSize(now = Date.now()) {
73
- const sinceMs = now - AIMD_WINDOW_MS;
74
- const events = readLines(BATCH_EVENTS_FILE)
75
- .map((line) => {
76
- try {
77
- return JSON.parse(line);
78
- } catch {
79
- return null;
80
- }
81
- })
82
- .filter((event) => event && Number(event.ts) >= sinceMs);
83
-
84
- if (events.length === 0) return AIMD_INITIAL;
85
-
86
- let batchSize = AIMD_INITIAL;
87
- for (const event of events) {
88
- if (isSuccessResult(event.result)) {
89
- batchSize = Math.min(AIMD_MAX, batchSize + 1);
90
- } else {
91
- batchSize = Math.max(AIMD_MIN, Math.floor(batchSize * 0.5));
92
- }
93
- }
94
- return batchSize;
95
- }
96
-
97
- function getCodexPercent() {
98
- const data = readJson(CODEX_RATE_LIMITS_FILE);
99
- const buckets = data?.buckets && typeof data.buckets === "object" ? data.buckets : null;
100
- const primaryBucket = buckets?.codex ?? Object.values(buckets ?? {})[0] ?? null;
101
- return clampPercent(primaryBucket?.primary?.used_percent);
102
- }
103
-
104
- function pickGeminiBucket(data) {
105
- const buckets = Array.isArray(data?.buckets) ? data.buckets : [];
106
- if (buckets.length === 0) return null;
107
-
108
- const preferredModels = [
109
- "gemini-3-flash-preview",
110
- "gemini-2.5-flash",
111
- "gemini-3-flash",
112
- "gemini-2.5-flash-lite",
113
- ];
114
-
115
- for (const modelId of preferredModels) {
116
- const match = buckets.find((bucket) => bucket?.modelId === modelId);
117
- if (match) return match;
118
- }
119
-
120
- return buckets.find((bucket) => String(bucket?.modelId ?? "").includes("flash")) ?? buckets[0];
121
- }
122
-
123
- function getGeminiPercent() {
124
- const data = readJson(GEMINI_QUOTA_FILE);
125
- const bucket = pickGeminiBucket(data);
126
- if (!bucket) return null;
127
- return clampPercent((1 - Number(bucket.remainingFraction ?? 1)) * 100);
128
- }
129
-
130
- function getClaudePercent() {
131
- const data = readJson(CLAUDE_USAGE_FILE);
132
- return clampPercent(data?.data?.fiveHourPercent);
133
- }
134
-
135
- async function getHubStatusLabel() {
136
- try {
137
- const response = await fetch(getHubStatusUrl(), {
138
- signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
139
- });
140
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
141
-
142
- const data = await response.json();
143
- const state = typeof data?.hub?.state === "string" ? data.hub.state : "connected";
144
- const sessions = Number.isFinite(Number(data?.sessions)) ? Number(data.sessions) : null;
145
- return sessions == null ? `Hub: ${state}` : `Hub: ${state} | S:${sessions}`;
146
- } catch {
147
- return "Hub 미연결";
148
- }
149
- }
150
-
151
- function formatTooltipPercent(value) {
152
- return value == null ? "--%" : `${value}%`;
153
- }
154
-
155
- function formatMenuPercent(value) {
156
- return value == null ? "--%" : `${value}%`;
157
- }
158
-
159
- function buildTooltip(snapshot) {
160
- const hubTag = snapshot.hubLabel.startsWith("Hub 미") ? "H:off" : "H:on";
161
- return `tfx AIMD:${snapshot.aimd}/10 | C:${formatTooltipPercent(snapshot.claude)} X:${formatTooltipPercent(snapshot.codex)} G:${formatTooltipPercent(snapshot.gemini)} ${hubTag}`;
162
- }
163
-
164
- function buildUsageTitle(snapshot) {
165
- return `C: ${formatMenuPercent(snapshot.claude)} | X: ${formatMenuPercent(snapshot.codex)} | G: ${formatMenuPercent(snapshot.gemini)}`;
166
- }
167
-
168
- function findChromePath() {
169
- const candidates = [
170
- join(process.env.ProgramFiles || "", "Google", "Chrome", "Application", "chrome.exe"),
171
- join(process.env["ProgramFiles(x86)"] || "", "Google", "Chrome", "Application", "chrome.exe"),
172
- join(process.env.LOCALAPPDATA || "", "Google", "Chrome", "Application", "chrome.exe"),
173
- ];
174
- for (const p of candidates) {
175
- try { if (existsSync(p)) return p; } catch {}
176
- }
177
- return null;
178
- }
179
-
180
- function openDashboard() {
181
- const url = getDashboardUrl();
182
- const shell = process.env.ComSpec || "cmd.exe";
183
- const chrome = findChromePath();
184
- if (chrome) {
185
- // Chrome --app: 주소바/탭 없는 앱 윈도우로 열기
186
- exec(`start "" "${chrome}" "--app=${url}"`, { shell, windowsHide: true }, (err) => {
187
- if (err) {
188
- exec(`start "" "${url}"`, { shell, windowsHide: true }, () => {});
189
- }
190
- });
191
- } else {
192
- exec(`start "" "${url}"`, { shell, windowsHide: true }, () => {});
193
- }
194
- }
195
-
196
- const openDashboardItem = {
197
- title: "대시보드 열기",
198
- tooltip: "브라우저에서 대시보드 열기",
199
- enabled: true,
200
- click: openDashboard,
201
- };
202
-
203
- const aimdItem = {
204
- title: "AIMD: 3/10",
205
- tooltip: "최근 30분 AIMD 동시 워커",
206
- enabled: true,
207
- click: openDashboard,
208
- };
209
-
210
- const quotaItem = {
211
- title: "C: --% | X: --% | G: --%",
212
- tooltip: "Claude | Codex | Gemini 사용률",
213
- enabled: true,
214
- click: openDashboard,
215
- };
216
-
217
- const hubItem = {
218
- title: "Hub 미연결",
219
- tooltip: "Hub 연결 상태",
220
- enabled: true,
221
- click: openDashboard,
222
- };
223
-
224
- const refreshItem = {
225
- title: "새로고침",
226
- tooltip: "캐시 재읽기",
227
- enabled: true,
228
- click: () => {
229
- void scheduleRefresh();
230
- },
231
- };
232
-
233
- const exitItem = {
234
- title: "종료",
235
- tooltip: "트레이 종료",
236
- enabled: true,
237
- click: () => {
238
- void shutdown("menu-exit");
239
- },
240
- };
241
-
242
- const menu = {
243
- icon: TRAY_ICON_BASE64,
244
- title: "tfx",
245
- tooltip: "tfx AIMD:3/10 | C:--% X:--% G:--% H:off",
246
- items: [
247
- openDashboardItem,
248
- SysTray.separator,
249
- aimdItem,
250
- quotaItem,
251
- hubItem,
252
- SysTray.separator,
253
- refreshItem,
254
- exitItem,
255
- ],
256
- };
257
-
258
- let systray = null;
259
- let pollTimer = null;
260
- let refreshPromise = null;
261
- let shuttingDown = false;
262
-
263
- async function refreshMenu() {
264
- const snapshot = {
265
- aimd: getAimdBatchSize(),
266
- codex: getCodexPercent(),
267
- gemini: getGeminiPercent(),
268
- claude: getClaudePercent(),
269
- hubLabel: await getHubStatusLabel(),
270
- };
271
-
272
- aimdItem.title = `AIMD: ${snapshot.aimd}/10`;
273
- quotaItem.title = buildUsageTitle(snapshot);
274
- hubItem.title = snapshot.hubLabel;
275
-
276
- if (systray) {
277
- await systray.sendAction({ type: "update-item", item: aimdItem });
278
- await systray.sendAction({ type: "update-item", item: quotaItem });
279
- await systray.sendAction({ type: "update-item", item: hubItem });
280
- await systray.sendAction({ type: "update-item-and-title", item: { title: buildTooltip(snapshot) } });
281
- }
282
- }
283
-
284
- function scheduleRefresh() {
285
- if (refreshPromise) return refreshPromise;
286
- refreshPromise = refreshMenu().catch((error) => {
287
- console.error(`[tfx-tray] refresh failed: ${error.message}`);
288
- }).finally(() => {
289
- refreshPromise = null;
290
- });
291
- return refreshPromise;
292
- }
293
-
294
- async function shutdown(reason = "shutdown") {
295
- if (shuttingDown) return;
296
- shuttingDown = true;
297
-
298
- if (pollTimer) {
299
- clearInterval(pollTimer);
300
- pollTimer = null;
301
- }
302
-
303
- try {
304
- if (systray && !systray.killed) {
305
- await systray.kill(false);
306
- }
307
- } catch (error) {
308
- console.error(`[tfx-tray] ${reason} cleanup failed: ${error.message}`);
309
- } finally {
310
- systray = null;
311
- process.exit(0);
312
- }
313
- }
314
-
13
+
14
+ const HUB_PID_FILE = join(homedir(), ".claude", "cache", "tfx-hub", "hub.pid");
15
+ const DEFAULT_HUB_PORT = "27888";
16
+
17
+ function getHubBaseUrl() {
18
+ if (process.env.TFX_HUB_URL)
19
+ return process.env.TFX_HUB_URL.replace(/\/+$/, "");
20
+ try {
21
+ const info = JSON.parse(readFileSync(HUB_PID_FILE, "utf8"));
22
+ if (info.port) return `http://${info.host || "127.0.0.1"}:${info.port}`;
23
+ } catch {}
24
+ const port = process.env.TFX_HUB_PORT || DEFAULT_HUB_PORT;
25
+ return `http://127.0.0.1:${port}`;
26
+ }
27
+
28
+ function getDashboardUrl() {
29
+ return `${getHubBaseUrl()}/dashboard`;
30
+ }
31
+
32
+ function getHubStatusUrl() {
33
+ return `${getHubBaseUrl()}/status`;
34
+ }
35
+ const POLL_INTERVAL_MS = 10_000;
36
+ const HUB_TIMEOUT_MS = 3_000;
37
+ const AIMD_INITIAL = 3;
38
+ const AIMD_MIN = 1;
39
+ const AIMD_MAX = 10;
40
+ const AIMD_WINDOW_MS = 30 * 60 * 1000;
41
+
42
+ const CACHE_DIR = join(homedir(), ".claude", "cache");
43
+ const BATCH_EVENTS_FILE = join(CACHE_DIR, "batch-events.jsonl");
44
+ const CODEX_RATE_LIMITS_FILE = join(CACHE_DIR, "codex-rate-limits-cache.json");
45
+ const GEMINI_QUOTA_FILE = join(CACHE_DIR, "gemini-quota-cache.json");
46
+ const CLAUDE_USAGE_FILE = join(CACHE_DIR, "claude-usage-cache.json");
47
+
48
+ const TRAY_ICON_BASE64 =
49
+ "AAABAAEAICAAAAEAIAADAQAAFgAAAIlQTkcNChoKAAAADUlIRFIAAAAgAAAAIAgGAAAAc3p69AAAAMpJREFUeJzV1UEKgzAQheEcwnXP4a17gl6n6yy7U1IIqDSTeW/m0TYwK8X/E6OW8m9rWW5Pa74SlWLYeBgRDcOQ7V42a+SIGSALkwKIQKDnroJQmy4bAgO8EDnAA4EBkV3do7VWeAOfAO0Cx/EC+vnMG2QCLND12Pp4vUcK+DQ9fBxqI47uDI23oV7F2fNVxF2A64zCTBwCWGE2Pv0WzKKp8ba8wWg4DIiGKUBWdBjP+i+E4z8BUCJccRUCimdC6HAGIiWOYiRR5doBauXshzcEs0UAAAAASUVORK5CYII=";
50
+
51
+ function clampPercent(value) {
52
+ if (!Number.isFinite(Number(value))) return null;
53
+ return Math.max(0, Math.min(100, Math.round(Number(value))));
54
+ }
55
+
56
+ function readJson(filePath) {
57
+ try {
58
+ return JSON.parse(readFileSync(filePath, "utf8"));
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function readLines(filePath) {
65
+ try {
66
+ return readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
67
+ } catch {
68
+ return [];
69
+ }
70
+ }
71
+
72
+ function isSuccessResult(result) {
73
+ return result === "success" || result === "success_with_warnings";
74
+ }
75
+
76
+ function getAimdBatchSize(now = Date.now()) {
77
+ const sinceMs = now - AIMD_WINDOW_MS;
78
+ const events = readLines(BATCH_EVENTS_FILE)
79
+ .map((line) => {
80
+ try {
81
+ return JSON.parse(line);
82
+ } catch {
83
+ return null;
84
+ }
85
+ })
86
+ .filter((event) => event && Number(event.ts) >= sinceMs);
87
+
88
+ if (events.length === 0) return AIMD_INITIAL;
89
+
90
+ let batchSize = AIMD_INITIAL;
91
+ for (const event of events) {
92
+ if (isSuccessResult(event.result)) {
93
+ batchSize = Math.min(AIMD_MAX, batchSize + 1);
94
+ } else {
95
+ batchSize = Math.max(AIMD_MIN, Math.floor(batchSize * 0.5));
96
+ }
97
+ }
98
+ return batchSize;
99
+ }
100
+
101
+ function getCodexPercent() {
102
+ const data = readJson(CODEX_RATE_LIMITS_FILE);
103
+ const buckets =
104
+ data?.buckets && typeof data.buckets === "object" ? data.buckets : null;
105
+ const primaryBucket =
106
+ buckets?.codex ?? Object.values(buckets ?? {})[0] ?? null;
107
+ return clampPercent(primaryBucket?.primary?.used_percent);
108
+ }
109
+
110
+ function pickGeminiBucket(data) {
111
+ const buckets = Array.isArray(data?.buckets) ? data.buckets : [];
112
+ if (buckets.length === 0) return null;
113
+
114
+ const preferredModels = [
115
+ "gemini-3-flash-preview",
116
+ "gemini-2.5-flash",
117
+ "gemini-3-flash",
118
+ "gemini-2.5-flash-lite",
119
+ ];
120
+
121
+ for (const modelId of preferredModels) {
122
+ const match = buckets.find((bucket) => bucket?.modelId === modelId);
123
+ if (match) return match;
124
+ }
125
+
126
+ return (
127
+ buckets.find((bucket) => String(bucket?.modelId ?? "").includes("flash")) ??
128
+ buckets[0]
129
+ );
130
+ }
131
+
132
+ function getGeminiPercent() {
133
+ const data = readJson(GEMINI_QUOTA_FILE);
134
+ const bucket = pickGeminiBucket(data);
135
+ if (!bucket) return null;
136
+ return clampPercent((1 - Number(bucket.remainingFraction ?? 1)) * 100);
137
+ }
138
+
139
+ function getClaudePercent() {
140
+ const data = readJson(CLAUDE_USAGE_FILE);
141
+ return clampPercent(data?.data?.fiveHourPercent);
142
+ }
143
+
144
+ async function getHubStatusLabel() {
145
+ try {
146
+ const response = await fetch(getHubStatusUrl(), {
147
+ signal: AbortSignal.timeout(HUB_TIMEOUT_MS),
148
+ });
149
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
150
+
151
+ const data = await response.json();
152
+ const state =
153
+ typeof data?.hub?.state === "string" ? data.hub.state : "connected";
154
+ const sessions = Number.isFinite(Number(data?.sessions))
155
+ ? Number(data.sessions)
156
+ : null;
157
+ return sessions == null ? `Hub: ${state}` : `Hub: ${state} | S:${sessions}`;
158
+ } catch {
159
+ return "Hub 미연결";
160
+ }
161
+ }
162
+
163
+ function formatTooltipPercent(value) {
164
+ return value == null ? "--%" : `${value}%`;
165
+ }
166
+
167
+ function formatMenuPercent(value) {
168
+ return value == null ? "--%" : `${value}%`;
169
+ }
170
+
171
+ function buildTooltip(snapshot) {
172
+ const hubTag = snapshot.hubLabel.startsWith("Hub ") ? "H:off" : "H:on";
173
+ return `tfx AIMD:${snapshot.aimd}/10 | C:${formatTooltipPercent(snapshot.claude)} X:${formatTooltipPercent(snapshot.codex)} G:${formatTooltipPercent(snapshot.gemini)} ${hubTag}`;
174
+ }
175
+
176
+ function buildUsageTitle(snapshot) {
177
+ return `C: ${formatMenuPercent(snapshot.claude)} | X: ${formatMenuPercent(snapshot.codex)} | G: ${formatMenuPercent(snapshot.gemini)}`;
178
+ }
179
+
180
+ function findChromePath() {
181
+ const candidates = [
182
+ join(
183
+ process.env.ProgramFiles || "",
184
+ "Google",
185
+ "Chrome",
186
+ "Application",
187
+ "chrome.exe",
188
+ ),
189
+ join(
190
+ process.env["ProgramFiles(x86)"] || "",
191
+ "Google",
192
+ "Chrome",
193
+ "Application",
194
+ "chrome.exe",
195
+ ),
196
+ join(
197
+ process.env.LOCALAPPDATA || "",
198
+ "Google",
199
+ "Chrome",
200
+ "Application",
201
+ "chrome.exe",
202
+ ),
203
+ ];
204
+ for (const p of candidates) {
205
+ try {
206
+ if (existsSync(p)) return p;
207
+ } catch {}
208
+ }
209
+ return null;
210
+ }
211
+
212
+ function openDashboard() {
213
+ const url = getDashboardUrl();
214
+ const shell = process.env.ComSpec || "cmd.exe";
215
+ const chrome = findChromePath();
216
+ if (chrome) {
217
+ // Chrome --app: 주소바/탭 없는 앱 윈도우로 열기
218
+ exec(
219
+ `start "" "${chrome}" "--app=${url}"`,
220
+ { shell, windowsHide: true },
221
+ (err) => {
222
+ if (err) {
223
+ exec(`start "" "${url}"`, { shell, windowsHide: true }, () => {});
224
+ }
225
+ },
226
+ );
227
+ } else {
228
+ exec(`start "" "${url}"`, { shell, windowsHide: true }, () => {});
229
+ }
230
+ }
231
+
232
+ const openDashboardItem = {
233
+ title: "대시보드 열기",
234
+ tooltip: "브라우저에서 대시보드 열기",
235
+ enabled: true,
236
+ click: openDashboard,
237
+ };
238
+
239
+ const aimdItem = {
240
+ title: "AIMD: 3/10",
241
+ tooltip: "최근 30분 AIMD 동시 워커",
242
+ enabled: true,
243
+ click: openDashboard,
244
+ };
245
+
246
+ const quotaItem = {
247
+ title: "C: --% | X: --% | G: --%",
248
+ tooltip: "Claude | Codex | Gemini 사용률",
249
+ enabled: true,
250
+ click: openDashboard,
251
+ };
252
+
253
+ const hubItem = {
254
+ title: "Hub 미연결",
255
+ tooltip: "Hub 연결 상태",
256
+ enabled: true,
257
+ click: openDashboard,
258
+ };
259
+
260
+ const refreshItem = {
261
+ title: "새로고침",
262
+ tooltip: "캐시 재읽기",
263
+ enabled: true,
264
+ click: () => {
265
+ void scheduleRefresh();
266
+ },
267
+ };
268
+
269
+ const exitItem = {
270
+ title: "종료",
271
+ tooltip: "트레이 종료",
272
+ enabled: true,
273
+ click: () => {
274
+ void shutdown("menu-exit");
275
+ },
276
+ };
277
+
278
+ const menu = {
279
+ icon: TRAY_ICON_BASE64,
280
+ title: "tfx",
281
+ tooltip: "tfx AIMD:3/10 | C:--% X:--% G:--% H:off",
282
+ items: [
283
+ openDashboardItem,
284
+ SysTray.separator,
285
+ aimdItem,
286
+ quotaItem,
287
+ hubItem,
288
+ SysTray.separator,
289
+ refreshItem,
290
+ exitItem,
291
+ ],
292
+ };
293
+
294
+ let systray = null;
295
+ let pollTimer = null;
296
+ let refreshPromise = null;
297
+ let shuttingDown = false;
298
+
299
+ async function refreshMenu() {
300
+ const snapshot = {
301
+ aimd: getAimdBatchSize(),
302
+ codex: getCodexPercent(),
303
+ gemini: getGeminiPercent(),
304
+ claude: getClaudePercent(),
305
+ hubLabel: await getHubStatusLabel(),
306
+ };
307
+
308
+ aimdItem.title = `AIMD: ${snapshot.aimd}/10`;
309
+ quotaItem.title = buildUsageTitle(snapshot);
310
+ hubItem.title = snapshot.hubLabel;
311
+
312
+ if (systray) {
313
+ await systray.sendAction({ type: "update-item", item: aimdItem });
314
+ await systray.sendAction({ type: "update-item", item: quotaItem });
315
+ await systray.sendAction({ type: "update-item", item: hubItem });
316
+ await systray.sendAction({
317
+ type: "update-item-and-title",
318
+ item: { title: buildTooltip(snapshot) },
319
+ });
320
+ }
321
+ }
322
+
323
+ function scheduleRefresh() {
324
+ if (refreshPromise) return refreshPromise;
325
+ refreshPromise = refreshMenu()
326
+ .catch((error) => {
327
+ console.error(`[tfx-tray] refresh failed: ${error.message}`);
328
+ })
329
+ .finally(() => {
330
+ refreshPromise = null;
331
+ });
332
+ return refreshPromise;
333
+ }
334
+
335
+ async function shutdown(reason = "shutdown") {
336
+ if (shuttingDown) return;
337
+ shuttingDown = true;
338
+
339
+ if (pollTimer) {
340
+ clearInterval(pollTimer);
341
+ pollTimer = null;
342
+ }
343
+
344
+ try {
345
+ if (systray && !systray.killed) {
346
+ await systray.kill(false);
347
+ }
348
+ } catch (error) {
349
+ console.error(`[tfx-tray] ${reason} cleanup failed: ${error.message}`);
350
+ } finally {
351
+ systray = null;
352
+ process.exit(0);
353
+ }
354
+ }
355
+
315
356
  export async function startTray() {
316
357
  if (!IS_WINDOWS) {
317
358
  throw new Error("tray command is only supported on Windows.");
318
359
  }
319
-
320
- systray = new SysTray({
321
- menu,
322
- debug: false,
323
- copyDir: false,
324
- });
325
-
326
- await systray.ready();
327
-
328
- systray.onError((error) => {
329
- console.error(`[tfx-tray] ${error.message}`);
330
- });
331
-
332
- systray.onExit((code, signal) => {
333
- if (shuttingDown) return;
334
- const detail = signal ? `signal ${signal}` : `code ${code ?? 0}`;
335
- console.error(`[tfx-tray] tray exited unexpectedly (${detail})`);
336
- process.exit(typeof code === "number" ? code : 1);
337
- });
338
- await systray.onClick((action) => {
339
- if (action.item?.click) {
340
- action.item.click();
341
- return;
342
- }
343
-
344
- if (action.item?.__id === openDashboardItem.__id) {
345
- openDashboard();
346
- }
347
- });
348
-
349
- await scheduleRefresh();
350
-
351
- pollTimer = setInterval(() => {
352
- void scheduleRefresh();
353
- }, POLL_INTERVAL_MS);
354
- pollTimer.unref();
355
-
356
- process.on("SIGINT", () => {
357
- void shutdown("SIGINT");
358
- });
359
- process.on("SIGTERM", () => {
360
- void shutdown("SIGTERM");
361
- });
362
-
363
- return {
364
- systray,
365
- stop: shutdown,
366
- };
367
- }
368
-
369
- const selfRun = process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
370
-
371
- if (selfRun) {
372
- startTray().catch((error) => {
373
- console.error(`[tfx-tray] start failed: ${error.message}`);
374
- process.exit(1);
375
- });
376
- }
360
+
361
+ systray = new SysTray({
362
+ menu,
363
+ debug: false,
364
+ copyDir: false,
365
+ });
366
+
367
+ await systray.ready();
368
+
369
+ systray.onError((error) => {
370
+ console.error(`[tfx-tray] ${error.message}`);
371
+ });
372
+
373
+ systray.onExit((code, signal) => {
374
+ if (shuttingDown) return;
375
+ const detail = signal ? `signal ${signal}` : `code ${code ?? 0}`;
376
+ console.error(`[tfx-tray] tray exited unexpectedly (${detail})`);
377
+ process.exit(typeof code === "number" ? code : 1);
378
+ });
379
+ await systray.onClick((action) => {
380
+ if (action.item?.click) {
381
+ action.item.click();
382
+ return;
383
+ }
384
+
385
+ if (action.item?.__id === openDashboardItem.__id) {
386
+ openDashboard();
387
+ }
388
+ });
389
+
390
+ await scheduleRefresh();
391
+
392
+ pollTimer = setInterval(() => {
393
+ void scheduleRefresh();
394
+ }, POLL_INTERVAL_MS);
395
+ pollTimer.unref();
396
+
397
+ process.on("SIGINT", () => {
398
+ void shutdown("SIGINT");
399
+ });
400
+ process.on("SIGTERM", () => {
401
+ void shutdown("SIGTERM");
402
+ });
403
+
404
+ return {
405
+ systray,
406
+ stop: shutdown,
407
+ };
408
+ }
409
+
410
+ const selfRun =
411
+ process.argv[1] &&
412
+ resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url));
413
+
414
+ if (selfRun) {
415
+ startTray().catch((error) => {
416
+ console.error(`[tfx-tray] start failed: ${error.message}`);
417
+ process.exit(1);
418
+ });
419
+ }