triflux 10.3.4 → 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 (329) hide show
  1. package/LICENSE +21 -21
  2. package/bin/tfx-doctor-tui.mjs +1 -1
  3. package/bin/tfx-doctor.mjs +6 -1
  4. package/bin/tfx-profile.mjs +1 -1
  5. package/bin/tfx-setup-tui.mjs +1 -1
  6. package/bin/tfx-setup.mjs +6 -1
  7. package/bin/triflux.mjs +2396 -1140
  8. package/hooks/agent-route-guard.mjs +12 -8
  9. package/hooks/cross-review-tracker.mjs +21 -8
  10. package/hooks/error-context.mjs +19 -7
  11. package/hooks/hook-adaptive-collector.mjs +18 -16
  12. package/hooks/hook-manager.mjs +93 -32
  13. package/hooks/hook-orchestrator.mjs +108 -24
  14. package/hooks/hook-registry.json +11 -0
  15. package/hooks/keyword-rules.json +6 -10
  16. package/hooks/lib/resolve-root.mjs +1 -1
  17. package/hooks/mcp-config-watcher.mjs +6 -2
  18. package/hooks/pipeline-stop.mjs +3 -6
  19. package/hooks/safety-guard.mjs +99 -28
  20. package/hooks/session-start-fast.mjs +143 -0
  21. package/hooks/subagent-verifier.mjs +5 -4
  22. package/hub/account-broker.mjs +256 -60
  23. package/hub/adaptive-diagnostic.mjs +75 -48
  24. package/hub/adaptive-inject.mjs +95 -57
  25. package/hub/adaptive-memory.mjs +156 -42
  26. package/hub/adaptive.mjs +60 -31
  27. package/hub/assign-callbacks.mjs +67 -30
  28. package/hub/bridge.mjs +0 -1
  29. package/hub/cli-adapter-base.mjs +200 -48
  30. package/hub/codex-adapter.mjs +76 -96
  31. package/hub/codex-compat.mjs +3 -3
  32. package/hub/codex-preflight.mjs +63 -37
  33. package/hub/delegator/contracts.mjs +19 -23
  34. package/hub/delegator/index.mjs +3 -3
  35. package/hub/delegator/service.mjs +88 -64
  36. package/hub/delegator/tool-definitions.mjs +5 -5
  37. package/hub/fullcycle.mjs +33 -17
  38. package/hub/gemini-adapter.mjs +69 -94
  39. package/hub/hitl.mjs +89 -30
  40. package/hub/intent.mjs +161 -38
  41. package/hub/lib/cache-guard.mjs +43 -17
  42. package/hub/lib/mcp-response-cache.mjs +66 -32
  43. package/hub/lib/memory-store.mjs +285 -111
  44. package/hub/lib/path-utils.mjs +35 -37
  45. package/hub/lib/process-utils.mjs +106 -37
  46. package/hub/lib/spawn-trace.mjs +527 -0
  47. package/hub/lib/ssh-command.mjs +34 -4
  48. package/hub/lib/ssh-retry.mjs +5 -1
  49. package/hub/lib/uuidv7.mjs +4 -3
  50. package/hub/memory-doctor.mjs +266 -106
  51. package/hub/middleware/request-logger.mjs +61 -34
  52. package/hub/paths.mjs +9 -9
  53. package/hub/pipeline/gates/confidence.mjs +34 -15
  54. package/hub/pipeline/gates/consensus.mjs +27 -15
  55. package/hub/pipeline/gates/index.mjs +7 -3
  56. package/hub/pipeline/gates/selfcheck.mjs +57 -19
  57. package/hub/pipeline/index.mjs +77 -42
  58. package/hub/pipeline/state.mjs +10 -10
  59. package/hub/pipeline/transitions.mjs +40 -23
  60. package/hub/platform.mjs +57 -48
  61. package/hub/promote-penalties.mjs +25 -7
  62. package/hub/quality/deslop.mjs +70 -49
  63. package/hub/research.mjs +32 -25
  64. package/hub/router.mjs +240 -107
  65. package/hub/routing/complexity.mjs +132 -29
  66. package/hub/routing/index.mjs +17 -12
  67. package/hub/routing/q-learning.mjs +76 -28
  68. package/hub/server.mjs +4 -4
  69. package/hub/session-fingerprint.mjs +126 -60
  70. package/hub/state.mjs +84 -43
  71. package/hub/store-adapter.mjs +59 -26
  72. package/hub/store.mjs +356 -153
  73. package/hub/team/agent-map.json +22 -7
  74. package/hub/team/ansi.mjs +186 -122
  75. package/hub/team/backend.mjs +28 -10
  76. package/hub/team/cli/commands/attach.mjs +29 -9
  77. package/hub/team/cli/commands/control.mjs +29 -8
  78. package/hub/team/cli/commands/debug.mjs +32 -11
  79. package/hub/team/cli/commands/focus.mjs +38 -11
  80. package/hub/team/cli/commands/interrupt.mjs +18 -6
  81. package/hub/team/cli/commands/kill.mjs +16 -5
  82. package/hub/team/cli/commands/list.mjs +11 -4
  83. package/hub/team/cli/commands/send.mjs +19 -6
  84. package/hub/team/cli/commands/start/index.mjs +154 -31
  85. package/hub/team/cli/commands/start/parse-args.mjs +38 -11
  86. package/hub/team/cli/commands/start/start-headless.mjs +112 -36
  87. package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
  88. package/hub/team/cli/commands/start/start-mux.mjs +70 -21
  89. package/hub/team/cli/commands/start/start-wt.mjs +29 -12
  90. package/hub/team/cli/commands/status.mjs +43 -14
  91. package/hub/team/cli/commands/stop.mjs +11 -4
  92. package/hub/team/cli/commands/task.mjs +8 -3
  93. package/hub/team/cli/commands/tasks.mjs +1 -1
  94. package/hub/team/cli/index.mjs +2 -2
  95. package/hub/team/cli/manifest.mjs +38 -8
  96. package/hub/team/cli/render.mjs +30 -8
  97. package/hub/team/cli/services/attach-fallback.mjs +31 -11
  98. package/hub/team/cli/services/hub-client.mjs +42 -14
  99. package/hub/team/cli/services/member-selector.mjs +11 -4
  100. package/hub/team/cli/services/native-control.mjs +48 -21
  101. package/hub/team/cli/services/runtime-mode.mjs +2 -1
  102. package/hub/team/cli/services/state-store.mjs +25 -8
  103. package/hub/team/cli/services/task-model.mjs +16 -6
  104. package/hub/team/conductor-mesh-bridge.mjs +24 -23
  105. package/hub/team/conductor.mjs +8 -4
  106. package/hub/team/dashboard-anchor.mjs +4 -5
  107. package/hub/team/dashboard-layout.mjs +3 -1
  108. package/hub/team/dashboard-open.mjs +41 -21
  109. package/hub/team/dashboard.mjs +76 -28
  110. package/hub/team/event-log.mjs +18 -10
  111. package/hub/team/handoff.mjs +31 -15
  112. package/hub/team/headless.mjs +2 -1
  113. package/hub/team/health-probe.mjs +69 -54
  114. package/hub/team/launcher-template.mjs +16 -13
  115. package/hub/team/native-supervisor.mjs +65 -21
  116. package/hub/team/native.mjs +74 -35
  117. package/hub/team/nativeProxy.mjs +184 -113
  118. package/hub/team/notify.mjs +119 -76
  119. package/hub/team/orchestrator.mjs +9 -4
  120. package/hub/team/pane.mjs +12 -7
  121. package/hub/team/process-cleanup.mjs +25 -16
  122. package/hub/team/psmux.mjs +491 -201
  123. package/hub/team/remote-probe.mjs +68 -52
  124. package/hub/team/remote-session.mjs +117 -59
  125. package/hub/team/remote-watcher.mjs +61 -33
  126. package/hub/team/routing.mjs +51 -25
  127. package/hub/team/runtime-strategy.mjs +3 -1
  128. package/hub/team/session.mjs +98 -34
  129. package/hub/team/staleState.mjs +72 -30
  130. package/hub/team/swarm-locks.mjs +15 -13
  131. package/hub/team/swarm-planner.mjs +32 -21
  132. package/hub/team/swarm-reconciler.mjs +48 -23
  133. package/hub/team/tui-lite.mjs +266 -68
  134. package/hub/team/tui-remote-adapter.mjs +14 -10
  135. package/hub/team/tui-viewer.mjs +99 -43
  136. package/hub/team/tui.mjs +708 -271
  137. package/hub/team/worktree-lifecycle.mjs +152 -58
  138. package/hub/team/wt-manager.mjs +24 -14
  139. package/hub/token-mode.mjs +71 -71
  140. package/hub/tray.mjs +66 -23
  141. package/hub/workers/claude-worker.mjs +162 -118
  142. package/hub/workers/codex-mcp.mjs +192 -141
  143. package/hub/workers/delegator-mcp.mjs +507 -333
  144. package/hub/workers/factory.mjs +8 -8
  145. package/hub/workers/gemini-worker.mjs +115 -84
  146. package/hub/workers/interface.mjs +6 -1
  147. package/hub/workers/worker-utils.mjs +21 -14
  148. package/hud/colors.mjs +27 -9
  149. package/hud/constants.mjs +162 -26
  150. package/hud/context-monitor.mjs +82 -41
  151. package/hud/hud-qos-status.mjs +129 -49
  152. package/hud/mission-board.mjs +6 -3
  153. package/hud/providers/claude.mjs +226 -115
  154. package/hud/providers/codex.mjs +62 -22
  155. package/hud/providers/gemini.mjs +168 -56
  156. package/hud/renderers.mjs +384 -119
  157. package/hud/terminal.mjs +101 -31
  158. package/hud/utils.mjs +78 -38
  159. package/mesh/index.mjs +11 -5
  160. package/mesh/mesh-budget.mjs +18 -9
  161. package/mesh/mesh-heartbeat.mjs +1 -1
  162. package/mesh/mesh-queue.mjs +3 -5
  163. package/mesh/mesh-router.mjs +5 -4
  164. package/package.json +2 -1
  165. package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
  166. package/scripts/__tests__/keyword-detector.test.mjs +77 -28
  167. package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
  168. package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
  169. package/scripts/__tests__/remote-spawn.test.mjs +10 -4
  170. package/scripts/__tests__/session-start-fast.test.mjs +36 -0
  171. package/scripts/__tests__/skill-template.test.mjs +98 -50
  172. package/scripts/__tests__/smoke.test.mjs +1 -1
  173. package/scripts/__tests__/spawn-trace.test.mjs +102 -0
  174. package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
  175. package/scripts/cache-doctor.mjs +11 -4
  176. package/scripts/cache-warmup.mjs +96 -37
  177. package/scripts/claudemd-sync.mjs +27 -17
  178. package/scripts/codex-gateway-preflight.mjs +52 -37
  179. package/scripts/codex-mcp-gateway-sync.mjs +59 -39
  180. package/scripts/completions/tfx.bash +47 -47
  181. package/scripts/completions/tfx.fish +44 -44
  182. package/scripts/completions/tfx.zsh +83 -83
  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 +45 -26
  193. package/scripts/keyword-detector.mjs +41 -20
  194. package/scripts/keyword-rules-expander.mjs +47 -30
  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 +300 -63
  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 +199 -84
  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 +16 -12
  229. package/scripts/tfx-batch-stats.mjs +32 -11
  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/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
  237. package/skills/.omc/state/idle-notif-cooldown.json +3 -0
  238. package/skills/.omc/state/last-tool-error.json +7 -0
  239. package/skills/.omc/state/subagent-tracking.json +7 -0
  240. package/skills/_templates/base.md +1 -6
  241. package/skills/merge-worktree/SKILL.md.tmpl +144 -0
  242. package/skills/shared/telemetry-segment.md +6 -0
  243. package/skills/star-prompt/SKILL.md.tmpl +222 -0
  244. package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
  245. package/skills/tfx-analysis/skill.json +1 -6
  246. package/skills/tfx-auto/SKILL.md +1 -0
  247. package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
  248. package/skills/tfx-auto-codex/skill.json +1 -3
  249. package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
  250. package/skills/tfx-autopilot/skill.json +1 -5
  251. package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
  252. package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
  253. package/skills/tfx-autoroute/skill.json +1 -7
  254. package/skills/tfx-codex/SKILL.md +1 -0
  255. package/skills/tfx-codex/skill.json +1 -3
  256. package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
  257. package/skills/tfx-codex-swarm/evals/evals.json +1 -1
  258. package/skills/tfx-codex-swarm/skill.json +1 -4
  259. package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
  260. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
  261. package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
  262. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
  263. package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
  264. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
  265. package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
  266. package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
  267. package/skills/tfx-debate/SKILL.md.tmpl +192 -0
  268. package/skills/tfx-debate/skill.json +1 -7
  269. package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
  270. package/skills/tfx-deep-analysis/skill.json +1 -5
  271. package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
  272. package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
  273. package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
  274. package/skills/tfx-deep-qa/skill.json +1 -6
  275. package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
  276. package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
  277. package/skills/tfx-doctor/SKILL.md +21 -0
  278. package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
  279. package/skills/tfx-doctor/skill.json +1 -3
  280. package/skills/tfx-find/SKILL.md +1 -0
  281. package/skills/tfx-forge/SKILL.md.tmpl +187 -0
  282. package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
  283. package/skills/tfx-fullcycle/skill.json +1 -6
  284. package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
  285. package/skills/tfx-gemini/skill.json +1 -3
  286. package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
  287. package/skills/tfx-hooks/skill.json +1 -3
  288. package/skills/tfx-hub/SKILL.md.tmpl +212 -0
  289. package/skills/tfx-hub/skill.json +1 -3
  290. package/skills/tfx-index/SKILL.md +1 -0
  291. package/skills/tfx-index/skill.json +1 -6
  292. package/skills/tfx-interview/SKILL.md.tmpl +285 -0
  293. package/skills/tfx-multi/SKILL.md.tmpl +183 -0
  294. package/skills/tfx-multi/skill.json +1 -3
  295. package/skills/tfx-panel/SKILL.md.tmpl +189 -0
  296. package/skills/tfx-panel/skill.json +1 -7
  297. package/skills/tfx-persist/SKILL.md.tmpl +270 -0
  298. package/skills/tfx-persist/skill.json +1 -7
  299. package/skills/tfx-plan/SKILL.md +1 -0
  300. package/skills/tfx-plan/skill.json +1 -6
  301. package/skills/tfx-profile/SKILL.md.tmpl +239 -0
  302. package/skills/tfx-profile/skill.json +1 -3
  303. package/skills/tfx-prune/SKILL.md.tmpl +200 -0
  304. package/skills/tfx-prune/skill.json +1 -7
  305. package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
  306. package/skills/tfx-psmux-rules/skill.json +1 -4
  307. package/skills/tfx-qa/SKILL.md +1 -0
  308. package/skills/tfx-qa/skill.json +1 -6
  309. package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
  310. package/skills/tfx-ralph/skill.json +1 -4
  311. package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
  312. package/skills/tfx-remote-setup/skill.json +1 -3
  313. package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
  314. package/skills/tfx-remote-spawn/references/hosts.json +16 -0
  315. package/skills/tfx-remote-spawn/skill.json +1 -4
  316. package/skills/tfx-research/SKILL.md +1 -0
  317. package/skills/tfx-review/SKILL.md +1 -0
  318. package/skills/tfx-review/skill.json +1 -6
  319. package/skills/tfx-setup/SKILL.md.tmpl +504 -0
  320. package/skills/tfx-setup/skill.json +1 -3
  321. package/skills/tfx-swarm/SKILL.md +22 -0
  322. package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
  323. package/tui/codex-profile.mjs +88 -33
  324. package/tui/core.mjs +45 -15
  325. package/tui/doctor.mjs +75 -28
  326. package/tui/gemini-profile.mjs +74 -29
  327. package/tui/monitor-data.mjs +8 -4
  328. package/tui/monitor.mjs +71 -27
  329. package/tui/setup.mjs +133 -42
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // SessionStart 훅에서 호출되는 Hub 보장 스크립트.
3
4
  // - /status 기반 헬스체크
4
5
  // - 비정상 시 Hub를 detached로 기동
5
6
 
7
+ import { spawn } from "child_process";
6
8
  import { existsSync, readFileSync } from "fs";
7
- import { join, dirname } from "path";
8
9
  import { homedir } from "os";
9
- import { spawn } from "child_process";
10
+ import { dirname, join } from "path";
10
11
  import { fileURLToPath } from "url";
11
12
 
12
13
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
@@ -23,7 +24,8 @@ function buildHubBaseUrl(host, port) {
23
24
 
24
25
  function resolveHubTarget() {
25
26
  const envPortRaw = Number(process.env.TFX_HUB_PORT || "");
26
- const envPort = Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : null;
27
+ const envPort =
28
+ Number.isFinite(envPortRaw) && envPortRaw > 0 ? envPortRaw : null;
27
29
  const target = {
28
30
  host: "127.0.0.1",
29
31
  port: envPort || 27888,
@@ -68,13 +70,15 @@ function startHubDetached(port) {
68
70
  try {
69
71
  const env = { ...process.env, TFX_HUB_PORT: String(port) };
70
72
  if (process.platform === "win32") {
71
- // Windows: cmd.exe /c start /b → 완전 독립 프로세스 트리 생성
72
- // hook timeout 시 프로세스 트리 킬에서 살아남음
73
- const child = spawn("cmd.exe", ["/c", "start", "/b", "", process.execPath, serverPath], {
74
- env,
75
- stdio: "ignore",
76
- windowsHide: true,
77
- });
73
+ const child = spawn(
74
+ "cmd.exe",
75
+ ["/c", "start", "/b", "", process.execPath, serverPath],
76
+ {
77
+ env,
78
+ stdio: "ignore",
79
+ windowsHide: true,
80
+ },
81
+ );
78
82
  child.unref();
79
83
  } else {
80
84
  const child = spawn(process.execPath, [serverPath], {
@@ -90,31 +94,46 @@ function startHubDetached(port) {
90
94
  }
91
95
  }
92
96
 
93
- /** Hub 기동 후 ready 상태까지 대기 (최대 maxWaitMs) */
94
97
  async function waitForHubReady(host, port, maxWaitMs = 5000) {
95
98
  const interval = 250;
96
99
  const deadline = Date.now() + maxWaitMs;
97
100
  while (Date.now() < deadline) {
98
101
  if (await isHubHealthy(host, port)) return true;
99
- await new Promise((r) => setTimeout(r, interval));
102
+ await new Promise((resolve) => setTimeout(resolve, interval));
100
103
  }
101
104
  return false;
102
105
  }
103
106
 
104
- const { host, port } = resolveHubTarget();
105
- if (!(await isHubHealthy(host, port))) {
107
+ export async function run(stdinData) {
108
+ void stdinData;
109
+
110
+ const { host, port } = resolveHubTarget();
111
+ if (await isHubHealthy(host, port)) {
112
+ return { code: 0, stdout: "hub: ok", stderr: "" };
113
+ }
114
+
106
115
  const started = startHubDetached(port);
107
- if (started) {
108
- const ready = await waitForHubReady(host, port, 3000);
109
- if (ready) {
110
- process.stdout.write("hub: ok");
111
- } else {
112
- // fire-and-forget: hub이 아직 기동 중일 수 있음 — 에러가 아닌 경고
113
- process.stdout.write("hub: starting");
114
- }
115
- } else {
116
- process.stderr.write("[hub-ensure] hub 시작 실패");
116
+ if (!started) {
117
+ return { code: 0, stdout: "", stderr: "[hub-ensure] hub 시작 실패" };
117
118
  }
118
- } else {
119
- process.stdout.write("hub: ok");
119
+
120
+ const ready = await waitForHubReady(host, port, 3000);
121
+ return {
122
+ code: 0,
123
+ stdout: ready ? "hub: ok" : "hub: starting",
124
+ stderr: "",
125
+ };
126
+ }
127
+
128
+ const isMain =
129
+ process.argv[1] &&
130
+ import.meta.url.endsWith(
131
+ process.argv[1].replace(/\\/g, "/").split("/").pop(),
132
+ );
133
+
134
+ if (isMain) {
135
+ const result = await run();
136
+ if (result.stdout) process.stdout.write(result.stdout);
137
+ if (result.stderr) process.stderr.write(result.stderr);
138
+ process.exit(result.code);
120
139
  }
@@ -3,7 +3,12 @@
3
3
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
- import { compileRules, loadRules, matchRules, resolveConflicts } from "./lib/keyword-rules.mjs";
6
+ import {
7
+ compileRules,
8
+ loadRules,
9
+ matchRules,
10
+ resolveConflicts,
11
+ } from "./lib/keyword-rules.mjs";
7
12
 
8
13
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
9
14
  const PROJECT_ROOT = dirname(SCRIPT_DIR);
@@ -33,7 +38,10 @@ export function extractPrompt(payload) {
33
38
  return payload.prompt;
34
39
  }
35
40
 
36
- if (typeof payload.message?.content === "string" && payload.message.content.trim()) {
41
+ if (
42
+ typeof payload.message?.content === "string" &&
43
+ payload.message.content.trim()
44
+ ) {
37
45
  return payload.message.content;
38
46
  }
39
47
 
@@ -87,8 +95,8 @@ function createHookOutput(additionalContext) {
87
95
  continue: true,
88
96
  hookSpecificOutput: {
89
97
  hookEventName: "UserPromptSubmit",
90
- additionalContext
91
- }
98
+ additionalContext,
99
+ },
92
100
  };
93
101
  }
94
102
 
@@ -144,17 +152,19 @@ function isSkipRequested() {
144
152
  }
145
153
 
146
154
  function activateState(baseDir, stateConfig, prompt, payload) {
147
- if (!stateConfig || stateConfig.activate !== true || !stateConfig.name) return;
155
+ if (!stateConfig || stateConfig.activate !== true || !stateConfig.name)
156
+ return;
148
157
 
149
158
  try {
150
159
  const stateRoot = join(baseDir, ".triflux", "state");
151
160
  mkdirSync(stateRoot, { recursive: true });
152
161
 
153
- const sessionId = typeof payload?.session_id === "string"
154
- ? payload.session_id
155
- : typeof payload?.sessionId === "string"
156
- ? payload.sessionId
157
- : "";
162
+ const sessionId =
163
+ typeof payload?.session_id === "string"
164
+ ? payload.session_id
165
+ : typeof payload?.sessionId === "string"
166
+ ? payload.sessionId
167
+ : "";
158
168
 
159
169
  let stateDir = stateRoot;
160
170
  if (sessionId && /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,255}$/.test(sessionId)) {
@@ -167,12 +177,14 @@ function activateState(baseDir, stateConfig, prompt, payload) {
167
177
  active: true,
168
178
  name: stateConfig.name,
169
179
  started_at: new Date().toISOString(),
170
- original_prompt: prompt
180
+ original_prompt: prompt,
171
181
  };
172
182
 
173
183
  writeFileSync(statePath, JSON.stringify(statePayload, null, 2), "utf8");
174
184
  } catch (error) {
175
- console.error(`[triflux-keyword-detector] 상태 저장 실패: ${error.message}`);
185
+ console.error(
186
+ `[triflux-keyword-detector] 상태 저장 실패: ${error.message}`,
187
+ );
176
188
  }
177
189
  }
178
190
 
@@ -238,26 +250,35 @@ function main() {
238
250
  }
239
251
 
240
252
  const selected = resolvedMatches[0];
241
- const baseDir = typeof payload.cwd === "string" && payload.cwd
242
- ? payload.cwd
243
- : typeof payload.directory === "string" && payload.directory
244
- ? payload.directory
245
- : process.cwd();
253
+ const baseDir =
254
+ typeof payload.cwd === "string" && payload.cwd
255
+ ? payload.cwd
256
+ : typeof payload.directory === "string" && payload.directory
257
+ ? payload.directory
258
+ : process.cwd();
246
259
 
247
260
  activateState(baseDir, selected.state, prompt, payload);
248
261
 
249
262
  if (selected.action === "suppress_omc") {
250
- console.log(JSON.stringify(createHookOutput(createSuppressOmcContext(selected, prompt))));
263
+ console.log(
264
+ JSON.stringify(
265
+ createHookOutput(createSuppressOmcContext(selected, prompt)),
266
+ ),
267
+ );
251
268
  return;
252
269
  }
253
270
 
254
271
  if (selected.skill) {
255
- console.log(JSON.stringify(createHookOutput(createSkillContext(selected, prompt))));
272
+ console.log(
273
+ JSON.stringify(createHookOutput(createSkillContext(selected, prompt))),
274
+ );
256
275
  return;
257
276
  }
258
277
 
259
278
  if (selected.mcp_route) {
260
- console.log(JSON.stringify(createHookOutput(createMcpRouteContext(selected, prompt))));
279
+ console.log(
280
+ JSON.stringify(createHookOutput(createMcpRouteContext(selected, prompt))),
281
+ );
261
282
  return;
262
283
  }
263
284
 
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
+
2
3
  // session-vault 태그 빈도 기반으로 keyword-rules.json 확장 후보를 제안하는 스크립트
3
4
 
4
- import Database from "better-sqlite3";
5
5
  import { readFileSync, writeFileSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { dirname, join, resolve } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
+ import Database from "better-sqlite3";
9
10
 
10
11
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
11
12
  const PROJECT_ROOT = dirname(SCRIPT_DIR);
@@ -25,7 +26,7 @@ const MCP_SERVICE_ROUTE_MAP = {
25
26
  gmail: "gemini",
26
27
  email: "gemini",
27
28
  github: "codex",
28
- figma: "gemini"
29
+ figma: "gemini",
29
30
  };
30
31
 
31
32
  const MCP_SERVICE_NAMES = new Set([
@@ -37,7 +38,7 @@ const MCP_SERVICE_NAMES = new Set([
37
38
  "asana",
38
39
  "drive",
39
40
  "sheets",
40
- "docs"
41
+ "docs",
41
42
  ]);
42
43
 
43
44
  function printUsage() {
@@ -54,7 +55,7 @@ function parseArgs(argv) {
54
55
  apply: false,
55
56
  threshold: DEFAULT_THRESHOLD,
56
57
  dbPath: DEFAULT_DB_PATH,
57
- help: false
58
+ help: false,
58
59
  };
59
60
 
60
61
  for (let i = 0; i < argv.length; i += 1) {
@@ -202,7 +203,7 @@ function aggregateByNormalizedTag(rows) {
202
203
  normalized,
203
204
  frequency: 0,
204
205
  variants: new Set(),
205
- sources: new Set()
206
+ sources: new Set(),
206
207
  });
207
208
  }
208
209
 
@@ -230,8 +231,11 @@ function readRulesDocument(rulesPath) {
230
231
  }
231
232
 
232
233
  function extractLiteralWords(patternSource) {
233
- const words = patternSource.toLowerCase().match(/[a-z0-9][a-z0-9-]{1,}/g) || [];
234
- return words.filter((word) => !["true", "false", "null", "route", "skill"].includes(word));
234
+ const words =
235
+ patternSource.toLowerCase().match(/[a-z0-9][a-z0-9-]{1,}/g) || [];
236
+ return words.filter(
237
+ (word) => !["true", "false", "null", "route", "skill"].includes(word),
238
+ );
235
239
  }
236
240
 
237
241
  function buildRuleIndex(rules) {
@@ -243,16 +247,24 @@ function buildRuleIndex(rules) {
243
247
 
244
248
  const ruleId = typeof rule.id === "string" ? rule.id.trim() : "";
245
249
  const skill = typeof rule.skill === "string" ? rule.skill.trim() : "";
246
- const route = typeof rule.mcp_route === "string" ? rule.mcp_route.trim() : "";
250
+ const route =
251
+ typeof rule.mcp_route === "string" ? rule.mcp_route.trim() : "";
247
252
 
248
253
  if (ruleId) aliases.add(normalizeKeyword(ruleId));
249
- if (ruleId.endsWith("-route")) aliases.add(normalizeKeyword(ruleId.slice(0, -"-route".length)));
250
- if (ruleId.endsWith("-skill")) aliases.add(normalizeKeyword(ruleId.slice(0, -"-skill".length)));
254
+ if (ruleId.endsWith("-route"))
255
+ aliases.add(normalizeKeyword(ruleId.slice(0, -"-route".length)));
256
+ if (ruleId.endsWith("-skill"))
257
+ aliases.add(normalizeKeyword(ruleId.slice(0, -"-skill".length)));
251
258
  if (skill) aliases.add(normalizeKeyword(skill));
252
259
  if (route) aliases.add(normalizeKeyword(route));
253
260
 
254
261
  for (const pattern of Array.isArray(rule.patterns) ? rule.patterns : []) {
255
- if (!pattern || typeof pattern.source !== "string" || typeof pattern.flags !== "string") continue;
262
+ if (
263
+ !pattern ||
264
+ typeof pattern.source !== "string" ||
265
+ typeof pattern.flags !== "string"
266
+ )
267
+ continue;
256
268
  try {
257
269
  regexes.push(new RegExp(pattern.source, pattern.flags));
258
270
  } catch {
@@ -267,7 +279,7 @@ function buildRuleIndex(rules) {
267
279
  indexed.push({
268
280
  id: ruleId || "(unknown-rule)",
269
281
  aliases,
270
- regexes
282
+ regexes,
271
283
  });
272
284
  }
273
285
 
@@ -300,7 +312,7 @@ function classifyCandidate(keyword) {
300
312
  type: "skill",
301
313
  label: "skill 규칙 후보",
302
314
  skill: normalized,
303
- mcpRoute: null
315
+ mcpRoute: null,
304
316
  };
305
317
  }
306
318
 
@@ -309,7 +321,7 @@ function classifyCandidate(keyword) {
309
321
  type: "mcp_route",
310
322
  label: "mcp_route 규칙 후보",
311
323
  skill: null,
312
- mcpRoute: MCP_SERVICE_ROUTE_MAP[normalized] || "gemini"
324
+ mcpRoute: MCP_SERVICE_ROUTE_MAP[normalized] || "gemini",
313
325
  };
314
326
  }
315
327
 
@@ -317,7 +329,7 @@ function classifyCandidate(keyword) {
317
329
  type: "general",
318
330
  label: "분류 미정",
319
331
  skill: null,
320
- mcpRoute: null
332
+ mcpRoute: null,
321
333
  };
322
334
  }
323
335
 
@@ -353,15 +365,15 @@ function buildRuleFromCandidate(candidate, existingIds) {
353
365
  patterns: [
354
366
  {
355
367
  source: createPatternSource(candidate.keyword),
356
- flags: "i"
357
- }
368
+ flags: "i",
369
+ },
358
370
  ],
359
371
  skill: candidate.classification.skill,
360
372
  priority: 20,
361
373
  supersedes: [],
362
374
  exclusive: false,
363
375
  state: null,
364
- mcp_route: null
376
+ mcp_route: null,
365
377
  };
366
378
  }
367
379
 
@@ -373,15 +385,15 @@ function buildRuleFromCandidate(candidate, existingIds) {
373
385
  patterns: [
374
386
  {
375
387
  source: createPatternSource(candidate.keyword),
376
- flags: "i"
377
- }
388
+ flags: "i",
389
+ },
378
390
  ],
379
391
  skill: null,
380
392
  priority: 20,
381
393
  supersedes: [],
382
394
  exclusive: false,
383
395
  state: null,
384
- mcp_route: candidate.classification.mcpRoute
396
+ mcp_route: candidate.classification.mcpRoute,
385
397
  };
386
398
  }
387
399
 
@@ -405,7 +417,7 @@ function printAnalysis({
405
417
  coveredCount,
406
418
  threshold,
407
419
  candidates,
408
- covered
420
+ covered,
409
421
  }) {
410
422
  console.log("=== keyword-rules-expander 분석 결과 ===");
411
423
  console.log("");
@@ -419,7 +431,9 @@ function printAnalysis({
419
431
  console.log(" (없음)");
420
432
  } else {
421
433
  for (const item of candidates) {
422
- console.log(` ${item.keyword} (${item.frequency}회) → ${item.classification.label}`);
434
+ console.log(
435
+ ` ${item.keyword} (${item.frequency}회) → ${item.classification.label}`,
436
+ );
423
437
  }
424
438
  }
425
439
 
@@ -429,7 +443,9 @@ function printAnalysis({
429
443
  console.log(" (없음)");
430
444
  } else {
431
445
  for (const item of covered) {
432
- console.log(` ${item.keyword} (${item.frequency}회) → ${item.ruleId} 규칙 있음`);
446
+ console.log(
447
+ ` ${item.keyword} (${item.frequency}회) → ${item.ruleId} 규칙 있음`,
448
+ );
433
449
  }
434
450
  }
435
451
  }
@@ -458,7 +474,7 @@ function main() {
458
474
  covered.push({
459
475
  keyword: tag.keyword,
460
476
  frequency: tag.frequency,
461
- ruleId: matchedRuleId
477
+ ruleId: matchedRuleId,
462
478
  });
463
479
  continue;
464
480
  }
@@ -469,7 +485,7 @@ function main() {
469
485
  keyword: tag.keyword,
470
486
  normalized: tag.normalized,
471
487
  frequency: tag.frequency,
472
- classification: classifyCandidate(tag.keyword)
488
+ classification: classifyCandidate(tag.keyword),
473
489
  });
474
490
  }
475
491
 
@@ -480,7 +496,7 @@ function main() {
480
496
  coveredCount: covered.length,
481
497
  threshold: args.threshold,
482
498
  candidates,
483
- covered
499
+ covered,
484
500
  });
485
501
 
486
502
  if (!args.apply) return;
@@ -488,10 +504,12 @@ function main() {
488
504
  const existingIds = new Set(
489
505
  rulesDoc.rules
490
506
  .map((rule) => (typeof rule.id === "string" ? rule.id.trim() : ""))
491
- .filter(Boolean)
507
+ .filter(Boolean),
492
508
  );
493
509
 
494
- const autoApplicable = candidates.filter((item) => item.classification.type !== "general");
510
+ const autoApplicable = candidates.filter(
511
+ (item) => item.classification.type !== "general",
512
+ );
495
513
  const manualReviewCount = candidates.length - autoApplicable.length;
496
514
 
497
515
  const newRules = autoApplicable
@@ -518,4 +536,3 @@ try {
518
536
  console.error(`[keyword-rules-expander] 오류: ${error.message}`);
519
537
  process.exit(1);
520
538
  }
521
-
@@ -23,7 +23,12 @@ const DEFAULT_TFX_TEMPLATE = [
23
23
  ].join("\n");
24
24
 
25
25
  const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
26
- const DEFAULT_TEMPLATE_PATH = join(SCRIPT_DIR, "..", "templates", "claudemd-tfx-section.md");
26
+ const DEFAULT_TEMPLATE_PATH = join(
27
+ SCRIPT_DIR,
28
+ "..",
29
+ "templates",
30
+ "claudemd-tfx-section.md",
31
+ );
27
32
 
28
33
  function resolveVersion(version) {
29
34
  if (version) return version;
@@ -19,10 +19,10 @@
19
19
  * log.info('result.processed'); // correlationId 자동 포함
20
20
  * }
21
21
  */
22
- import { AsyncLocalStorage } from 'node:async_hooks';
23
- import { randomUUID } from 'node:crypto';
22
+ import { AsyncLocalStorage } from "node:async_hooks";
23
+ import { randomUUID } from "node:crypto";
24
24
 
25
- import { logger } from './logger.mjs';
25
+ import { logger } from "./logger.mjs";
26
26
 
27
27
  /** @type {AsyncLocalStorage<{logger: import('pino').Logger, correlationId: string}>} */
28
28
  export const asyncLocalStorage = new AsyncLocalStorage();
@@ -28,8 +28,10 @@ export function nowSec() {
28
28
  }
29
29
 
30
30
  export function resolveBaseDir(payload) {
31
- if (typeof payload?.cwd === "string" && payload.cwd.trim()) return payload.cwd;
32
- if (typeof payload?.directory === "string" && payload.directory.trim()) return payload.directory;
31
+ if (typeof payload?.cwd === "string" && payload.cwd.trim())
32
+ return payload.cwd;
33
+ if (typeof payload?.directory === "string" && payload.directory.trim())
34
+ return payload.directory;
33
35
  return process.cwd();
34
36
  }
35
37
 
@@ -38,7 +40,8 @@ export function shouldTrackPath(filePath) {
38
40
 
39
41
  const lower = filePath.toLowerCase();
40
42
  if (lower.startsWith(".omc/") || lower.startsWith(".claude/")) return false;
41
- if (lower === "package-lock.json" || lower.endsWith("/package-lock.json")) return false;
43
+ if (lower === "package-lock.json" || lower.endsWith("/package-lock.json"))
44
+ return false;
42
45
  if (/\.(md|lock|yml|yaml)$/i.test(lower)) return false;
43
46
  return true;
44
47
  }
@@ -1,9 +1,9 @@
1
+ import { execSync, spawn } from "node:child_process";
2
+ import { createHash } from "node:crypto";
1
3
  import { existsSync, readFileSync } from "node:fs";
2
- import { join, dirname } from "node:path";
3
4
  import { homedir } from "node:os";
4
- import { execSync, spawn } from "node:child_process";
5
+ import { dirname, join } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
6
- import { createHash } from "node:crypto";
7
7
  import { whichCommand, whichCommandAsync } from "../../hub/platform.mjs";
8
8
 
9
9
  const DEFAULT_STATUS_URL = "http://127.0.0.1:27888/status";
@@ -73,10 +73,13 @@ function normalizeCliNames(names) {
73
73
  }
74
74
 
75
75
  function mapCliResults(names, results) {
76
- return names.reduce((acc, name, index) => ({
77
- ...acc,
78
- [name]: results[index],
79
- }), {});
76
+ return names.reduce(
77
+ (acc, name, index) => ({
78
+ ...acc,
79
+ [name]: results[index],
80
+ }),
81
+ {},
82
+ );
80
83
  }
81
84
 
82
85
  async function resolveCliProbe(name, options = {}) {
@@ -115,13 +118,18 @@ export function checkCliSync(name, options = {}) {
115
118
  const cached = readCachedCliResult(cliName);
116
119
  if (cached) return cached;
117
120
 
118
- const path = (options.whichCommandFn || whichCommand)(cliName, buildCliProbeOptions(options));
121
+ const path = (options.whichCommandFn || whichCommand)(
122
+ cliName,
123
+ buildCliProbeOptions(options),
124
+ );
119
125
  return storeCliResult(cliName, toCliResult(path));
120
126
  }
121
127
 
122
128
  export async function probeClis(names, options = {}) {
123
129
  const cliNames = normalizeCliNames(names);
124
- const results = await Promise.all(cliNames.map((name) => checkCli(name, options)));
130
+ const results = await Promise.all(
131
+ cliNames.map((name) => checkCli(name, options)),
132
+ );
125
133
  return mapCliResults(cliNames, results);
126
134
  }
127
135
 
@@ -137,15 +145,18 @@ export function detectCodexAuthState({
137
145
  } = {}) {
138
146
  try {
139
147
  const authPath = join(homeDir, ".codex", "auth.json");
140
- if (!existsSyncFn(authPath)) return { plan: "unknown", source: "no_auth", fingerprint: "no_auth" };
148
+ if (!existsSyncFn(authPath))
149
+ return { plan: "unknown", source: "no_auth", fingerprint: "no_auth" };
141
150
 
142
151
  const auth = JSON.parse(readFileSyncFn(authPath, "utf8"));
143
152
  if (auth.auth_mode !== "chatgpt") {
144
153
  const fingerprint = createHash("sha256")
145
- .update(JSON.stringify({
146
- auth_mode: auth.auth_mode || "api_key",
147
- has_api_key: Boolean(auth.api_key || auth.apiKey),
148
- }))
154
+ .update(
155
+ JSON.stringify({
156
+ auth_mode: auth.auth_mode || "api_key",
157
+ has_api_key: Boolean(auth.api_key || auth.apiKey),
158
+ }),
159
+ )
149
160
  .digest("hex");
150
161
  return { plan: "api", source: "api_key", fingerprint };
151
162
  }
@@ -156,20 +167,30 @@ export function detectCodexAuthState({
156
167
  plan: "unknown",
157
168
  source: "no_token",
158
169
  fingerprint: createHash("sha256")
159
- .update(JSON.stringify({ auth_mode: auth.auth_mode || "chatgpt", token: null }))
170
+ .update(
171
+ JSON.stringify({
172
+ auth_mode: auth.auth_mode || "chatgpt",
173
+ token: null,
174
+ }),
175
+ )
160
176
  .digest("hex"),
161
177
  };
162
178
  }
163
179
 
164
- const payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url").toString());
165
- const plan = payload?.["https://api.openai.com/auth"]?.chatgpt_plan_type || "unknown";
180
+ const payload = JSON.parse(
181
+ Buffer.from(token.split(".")[1], "base64url").toString(),
182
+ );
183
+ const plan =
184
+ payload?.["https://api.openai.com/auth"]?.chatgpt_plan_type || "unknown";
166
185
  const fingerprint = createHash("sha256")
167
- .update(JSON.stringify({
168
- auth_mode: auth.auth_mode || "chatgpt",
169
- plan,
170
- sub: payload?.sub || null,
171
- exp: payload?.exp || null,
172
- }))
186
+ .update(
187
+ JSON.stringify({
188
+ auth_mode: auth.auth_mode || "chatgpt",
189
+ plan,
190
+ sub: payload?.sub || null,
191
+ exp: payload?.exp || null,
192
+ }),
193
+ )
173
194
  .digest("hex");
174
195
  return { plan, source: "jwt", fingerprint };
175
196
  } catch {
@@ -205,7 +226,8 @@ export function checkHub({
205
226
  if (!restart) return { ok: false, state: "unreachable", restart: "disabled" };
206
227
 
207
228
  const serverPath = join(pkgRoot, "hub", "server.mjs");
208
- if (!existsSyncFn(serverPath)) return { ok: false, state: "unreachable", restart: "no_server" };
229
+ if (!existsSyncFn(serverPath))
230
+ return { ok: false, state: "unreachable", restart: "no_server" };
209
231
 
210
232
  try {
211
233
  const child = spawnFn(process.execPath, [serverPath], {
@@ -235,7 +257,4 @@ export function checkHub({
235
257
  return { ok: false, state: "unreachable", restart: "timeout" };
236
258
  }
237
259
 
238
- export {
239
- DEFAULT_PKG_ROOT,
240
- DEFAULT_STATUS_URL,
241
- };
260
+ export { DEFAULT_PKG_ROOT, DEFAULT_STATUS_URL };