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,31 +1,47 @@
1
1
  // ============================================================================
2
2
  // Gemini 쿼터 API / 세션 토큰 / RPM 트래커
3
3
  // ============================================================================
4
- import { existsSync, readFileSync, readdirSync } from "node:fs";
4
+
5
+ import { spawn } from "node:child_process";
6
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
7
+ import https from "node:https";
5
8
  import { homedir } from "node:os";
6
9
  import { join } from "node:path";
7
- import https from "node:https";
8
- import { spawn } from "node:child_process";
9
10
  import {
10
- GEMINI_OAUTH_PATH, GEMINI_QUOTA_CACHE_PATH, GEMINI_PROJECT_CACHE_PATH,
11
- GEMINI_SESSION_CACHE_PATH, GEMINI_RPM_TRACKER_PATH,
12
- LEGACY_GEMINI_QUOTA_CACHE, LEGACY_GEMINI_PROJECT_CACHE,
13
- LEGACY_GEMINI_SESSION_CACHE, LEGACY_GEMINI_RPM_TRACKER,
14
- GEMINI_RPM_WINDOW_MS, GEMINI_QUOTA_STALE_MS, GEMINI_SESSION_STALE_MS,
15
11
  GEMINI_API_TIMEOUT_MS,
16
- GEMINI_REFRESH_FLAG, GEMINI_SESSION_REFRESH_FLAG,
17
- GEMINI_QUOTA_REFRESH_LOCK_PATH, GEMINI_SESSION_REFRESH_LOCK_PATH, SPAWN_LOCK_TTL_MS,
12
+ GEMINI_OAUTH_PATH,
13
+ GEMINI_PROJECT_CACHE_PATH,
14
+ GEMINI_QUOTA_CACHE_PATH,
15
+ GEMINI_QUOTA_REFRESH_LOCK_PATH,
16
+ GEMINI_QUOTA_STALE_MS,
17
+ GEMINI_REFRESH_FLAG,
18
+ GEMINI_RPM_TRACKER_PATH,
19
+ GEMINI_RPM_WINDOW_MS,
20
+ GEMINI_SESSION_CACHE_PATH,
21
+ GEMINI_SESSION_REFRESH_FLAG,
22
+ GEMINI_SESSION_REFRESH_LOCK_PATH,
23
+ GEMINI_SESSION_STALE_MS,
24
+ LEGACY_GEMINI_PROJECT_CACHE,
25
+ LEGACY_GEMINI_QUOTA_CACHE,
26
+ LEGACY_GEMINI_RPM_TRACKER,
27
+ LEGACY_GEMINI_SESSION_CACHE,
28
+ SPAWN_LOCK_TTL_MS,
18
29
  } from "../constants.mjs";
19
30
  import {
20
- readJson, writeJsonSafe, readJsonMigrate, makeHash, clampPercent,
21
- decodeJwtEmail, createHttpsPost,
31
+ clampPercent,
32
+ createHttpsPost,
33
+ decodeJwtEmail,
34
+ makeHash,
35
+ readJson,
36
+ readJsonMigrate,
37
+ writeJsonSafe,
22
38
  } from "../utils.mjs";
23
39
 
24
40
  const httpsPost = createHttpsPost(https, GEMINI_API_TIMEOUT_MS);
25
41
 
26
42
  // Gemini 모델별 RPM 한도 (실측 기반: Pro 25, Flash 300)
27
43
  export function getGeminiRpmLimit(model) {
28
- if (model && model.includes("pro")) return 25;
44
+ if (model?.includes("pro")) return 25;
29
45
  return 300; // Flash 기본
30
46
  }
31
47
 
@@ -49,21 +65,36 @@ export function deriveGeminiLimits(bucket) {
49
65
  if (bucket.remainingAmount != null) {
50
66
  const remaining = parseInt(bucket.remainingAmount, 10);
51
67
  const limit = fraction > 0 ? Math.round(remaining / fraction) : 0;
52
- return { usedPct, remaining, limit, resetTime: bucket.resetTime, modelId: bucket.modelId };
68
+ return {
69
+ usedPct,
70
+ remaining,
71
+ limit,
72
+ resetTime: bucket.resetTime,
73
+ modelId: bucket.modelId,
74
+ };
53
75
  }
54
- return { usedPct, remaining: null, limit: null, resetTime: bucket.resetTime, modelId: bucket.modelId };
76
+ return {
77
+ usedPct,
78
+ remaining: null,
79
+ limit: null,
80
+ resetTime: bucket.resetTime,
81
+ modelId: bucket.modelId,
82
+ };
55
83
  }
56
84
 
57
85
  export function getGeminiEmail() {
58
86
  try {
59
87
  const oauth = readJson(GEMINI_OAUTH_PATH, null);
60
88
  return decodeJwtEmail(oauth?.id_token);
61
- } catch { return null; }
89
+ } catch {
90
+ return null;
91
+ }
62
92
  }
63
93
 
64
94
  export function buildGeminiAuthContext(accountId) {
65
95
  const oauth = readJson(GEMINI_OAUTH_PATH, null);
66
- const tokenSource = oauth?.refresh_token || oauth?.id_token || oauth?.access_token || "";
96
+ const tokenSource =
97
+ oauth?.refresh_token || oauth?.id_token || oauth?.access_token || "";
67
98
  const tokenFingerprint = tokenSource ? makeHash(tokenSource) : "none";
68
99
  const cacheKey = `${accountId || "gemini-main"}::${tokenFingerprint}`;
69
100
  return { oauth, tokenFingerprint, cacheKey };
@@ -78,11 +109,17 @@ export async function fetchGeminiQuota(accountId, options = {}) {
78
109
  const forceRefresh = options.forceRefresh === true;
79
110
 
80
111
  // 1. 캐시 확인 (계정/토큰별)
81
- const cache = readJsonMigrate(GEMINI_QUOTA_CACHE_PATH, LEGACY_GEMINI_QUOTA_CACHE, null);
82
- if (!forceRefresh
83
- && cache?.cacheKey === cacheKey
84
- && cache?.timestamp
85
- && (Date.now() - cache.timestamp < GEMINI_QUOTA_STALE_MS)) {
112
+ const cache = readJsonMigrate(
113
+ GEMINI_QUOTA_CACHE_PATH,
114
+ LEGACY_GEMINI_QUOTA_CACHE,
115
+ null,
116
+ );
117
+ if (
118
+ !forceRefresh &&
119
+ cache?.cacheKey === cacheKey &&
120
+ cache?.timestamp &&
121
+ Date.now() - cache.timestamp < GEMINI_QUOTA_STALE_MS
122
+ ) {
86
123
  return cache;
87
124
  }
88
125
 
@@ -117,12 +154,22 @@ export async function fetchGeminiQuota(accountId, options = {}) {
117
154
  oauth.access_token,
118
155
  );
119
156
  const id = loadRes?.cloudaicompanionProject;
120
- if (id) writeJsonSafe(GEMINI_PROJECT_CACHE_PATH, { cacheKey, projectId: id, timestamp: Date.now() });
157
+ if (id)
158
+ writeJsonSafe(GEMINI_PROJECT_CACHE_PATH, {
159
+ cacheKey,
160
+ projectId: id,
161
+ timestamp: Date.now(),
162
+ });
121
163
  return id || null;
122
164
  };
123
165
 
124
- const projCache = readJsonMigrate(GEMINI_PROJECT_CACHE_PATH, LEGACY_GEMINI_PROJECT_CACHE, null);
125
- let projectId = projCache?.cacheKey === cacheKey ? projCache?.projectId : null;
166
+ const projCache = readJsonMigrate(
167
+ GEMINI_PROJECT_CACHE_PATH,
168
+ LEGACY_GEMINI_PROJECT_CACHE,
169
+ null,
170
+ );
171
+ let projectId =
172
+ projCache?.cacheKey === cacheKey ? projCache?.projectId : null;
126
173
  if (!projectId) projectId = await fetchProjectId();
127
174
  if (!projectId) return cache;
128
175
 
@@ -146,7 +193,11 @@ export async function fetchGeminiQuota(accountId, options = {}) {
146
193
 
147
194
  if (!quotaRes?.buckets) {
148
195
  // API 응답에 buckets 없음: 에러 코드 또는 응답 내용을 캐시에 기록
149
- const apiError = quotaRes?.error?.message || quotaRes?.error?.code || quotaRes?.error || "no buckets in response";
196
+ const apiError =
197
+ quotaRes?.error?.message ||
198
+ quotaRes?.error?.code ||
199
+ quotaRes?.error ||
200
+ "no buckets in response";
150
201
  writeJsonSafe(GEMINI_QUOTA_CACHE_PATH, {
151
202
  ...(cache || {}),
152
203
  timestamp: cache?.timestamp || Date.now(),
@@ -176,12 +227,17 @@ export async function fetchGeminiQuota(accountId, options = {}) {
176
227
  export function readGeminiRpm(model) {
177
228
  try {
178
229
  // 새 경로 → 레거시 경로 fallback
179
- const rpmPath = existsSync(GEMINI_RPM_TRACKER_PATH) ? GEMINI_RPM_TRACKER_PATH
180
- : existsSync(LEGACY_GEMINI_RPM_TRACKER) ? LEGACY_GEMINI_RPM_TRACKER : null;
230
+ const rpmPath = existsSync(GEMINI_RPM_TRACKER_PATH)
231
+ ? GEMINI_RPM_TRACKER_PATH
232
+ : existsSync(LEGACY_GEMINI_RPM_TRACKER)
233
+ ? LEGACY_GEMINI_RPM_TRACKER
234
+ : null;
181
235
  if (!rpmPath) return { count: 0, percent: 0, remainingSec: 60 };
182
236
  const raw = readFileSync(rpmPath, "utf-8");
183
237
  const parsed = JSON.parse(raw);
184
- const timestamps = Array.isArray(parsed.timestamps) ? parsed.timestamps : [];
238
+ const timestamps = Array.isArray(parsed.timestamps)
239
+ ? parsed.timestamps
240
+ : [];
185
241
  const now = Date.now();
186
242
  const recent = timestamps.filter((t) => now - t < GEMINI_RPM_WINDOW_MS);
187
243
  const count = recent.length;
@@ -189,9 +245,15 @@ export function readGeminiRpm(model) {
189
245
  const percent = clampPercent(Math.round((count / rpmLimit) * 100));
190
246
  // 가장 오래된 엔트리가 윈도우에서 빠지기까지 남은 초 (0건이면 0s)
191
247
  // 5초 단위 반올림으로 HUD 깜빡임 감소
192
- const rawRemainingSec = recent.length > 0
193
- ? Math.max(0, Math.ceil((GEMINI_RPM_WINDOW_MS - (now - Math.min(...recent))) / 1000))
194
- : 0;
248
+ const rawRemainingSec =
249
+ recent.length > 0
250
+ ? Math.max(
251
+ 0,
252
+ Math.ceil(
253
+ (GEMINI_RPM_WINDOW_MS - (now - Math.min(...recent))) / 1000,
254
+ ),
255
+ )
256
+ : 0;
195
257
  const remainingSec = Math.ceil(rawRemainingSec / 5) * 5;
196
258
  return { count, percent, remainingSec };
197
259
  } catch {
@@ -200,7 +262,11 @@ export function readGeminiRpm(model) {
200
262
  }
201
263
 
202
264
  export function readGeminiQuotaSnapshot(accountId, authContext) {
203
- const cache = readJsonMigrate(GEMINI_QUOTA_CACHE_PATH, LEGACY_GEMINI_QUOTA_CACHE, null);
265
+ const cache = readJsonMigrate(
266
+ GEMINI_QUOTA_CACHE_PATH,
267
+ LEGACY_GEMINI_QUOTA_CACHE,
268
+ null,
269
+ );
204
270
  if (!cache?.buckets) {
205
271
  return { quota: null, shouldRefresh: true };
206
272
  }
@@ -209,19 +275,24 @@ export function readGeminiQuotaSnapshot(accountId, authContext) {
209
275
  const isLegacyCache = !cache.cacheKey;
210
276
  const keyMatched = cache.cacheKey === cacheKey;
211
277
  const cacheTs = Number(cache.timestamp);
212
- const ageMs = Number.isFinite(cacheTs) ? Date.now() - cacheTs : Number.MAX_SAFE_INTEGER;
278
+ const ageMs = Number.isFinite(cacheTs)
279
+ ? Date.now() - cacheTs
280
+ : Number.MAX_SAFE_INTEGER;
213
281
  const isFresh = ageMs < GEMINI_QUOTA_STALE_MS;
214
282
 
215
283
  if (keyMatched) {
216
284
  // resetTime이 지난 버킷의 remainingFraction을 1로 보정 (stale 캐시 방지)
217
285
  if (Array.isArray(cache.buckets)) {
218
286
  const now = Date.now();
219
- const patchedBuckets = cache.buckets.map(b =>
287
+ const patchedBuckets = cache.buckets.map((b) =>
220
288
  b?.resetTime && new Date(b.resetTime).getTime() <= now
221
289
  ? { ...b, remainingFraction: 1 }
222
- : b
290
+ : b,
223
291
  );
224
- return { quota: { ...cache, buckets: patchedBuckets }, shouldRefresh: !isFresh };
292
+ return {
293
+ quota: { ...cache, buckets: patchedBuckets },
294
+ shouldRefresh: !isFresh,
295
+ };
225
296
  }
226
297
  return { quota: cache, shouldRefresh: !isFresh };
227
298
  }
@@ -238,16 +309,24 @@ export function scheduleGeminiQuotaRefresh(accountId) {
238
309
  // 스폰 락: 30초 내 이미 스폰했으면 중복 방지
239
310
  try {
240
311
  if (existsSync(GEMINI_QUOTA_REFRESH_LOCK_PATH)) {
241
- const lockAge = Date.now() - readJson(GEMINI_QUOTA_REFRESH_LOCK_PATH, {}).t;
312
+ const lockAge =
313
+ Date.now() - readJson(GEMINI_QUOTA_REFRESH_LOCK_PATH, {}).t;
242
314
  if (lockAge < SPAWN_LOCK_TTL_MS) return;
243
315
  }
244
316
  writeJsonSafe(GEMINI_QUOTA_REFRESH_LOCK_PATH, { t: Date.now() });
245
- } catch { /* 락 실패 무시 — 스폰 진행 */ }
317
+ } catch {
318
+ /* 락 실패 무시 — 스폰 진행 */
319
+ }
246
320
 
247
321
  try {
248
322
  const child = spawn(
249
323
  process.execPath,
250
- [scriptPath, GEMINI_REFRESH_FLAG, "--account", accountId || "gemini-main"],
324
+ [
325
+ scriptPath,
326
+ GEMINI_REFRESH_FLAG,
327
+ "--account",
328
+ accountId || "gemini-main",
329
+ ],
251
330
  {
252
331
  detached: true,
253
332
  stdio: "ignore",
@@ -266,7 +345,11 @@ export function scheduleGeminiQuotaRefresh(accountId) {
266
345
  }
267
346
 
268
347
  export function readGeminiSessionSnapshot() {
269
- const cache = readJsonMigrate(GEMINI_SESSION_CACHE_PATH, LEGACY_GEMINI_SESSION_CACHE, null);
348
+ const cache = readJsonMigrate(
349
+ GEMINI_SESSION_CACHE_PATH,
350
+ LEGACY_GEMINI_SESSION_CACHE,
351
+ null,
352
+ );
270
353
  if (!cache?.session) {
271
354
  return { session: null, shouldRefresh: true };
272
355
  }
@@ -290,20 +373,29 @@ export function scheduleGeminiSessionRefresh() {
290
373
  // 스폰 락: 30초 내 이미 스폰했으면 중복 방지
291
374
  try {
292
375
  if (existsSync(GEMINI_SESSION_REFRESH_LOCK_PATH)) {
293
- const lockAge = Date.now() - readJson(GEMINI_SESSION_REFRESH_LOCK_PATH, {}).t;
376
+ const lockAge =
377
+ Date.now() - readJson(GEMINI_SESSION_REFRESH_LOCK_PATH, {}).t;
294
378
  if (lockAge < SPAWN_LOCK_TTL_MS) return;
295
379
  }
296
380
  writeJsonSafe(GEMINI_SESSION_REFRESH_LOCK_PATH, { t: Date.now() });
297
- } catch { /* 락 실패 무시 — 스폰 진행 */ }
381
+ } catch {
382
+ /* 락 실패 무시 — 스폰 진행 */
383
+ }
298
384
 
299
385
  try {
300
- const child = spawn(process.execPath, [scriptPath, GEMINI_SESSION_REFRESH_FLAG], {
301
- detached: true,
302
- stdio: "ignore",
303
- windowsHide: true,
304
- });
386
+ const child = spawn(
387
+ process.execPath,
388
+ [scriptPath, GEMINI_SESSION_REFRESH_FLAG],
389
+ {
390
+ detached: true,
391
+ stdio: "ignore",
392
+ windowsHide: true,
393
+ },
394
+ );
305
395
  child.unref();
306
- } catch { /* 백그라운드 실행 실패 무시 */ }
396
+ } catch {
397
+ /* 백그라운드 실행 실패 무시 */
398
+ }
307
399
  }
308
400
 
309
401
  // ============================================================================
@@ -315,27 +407,47 @@ export function scanGeminiSessionTokens() {
315
407
  let best = null;
316
408
  let bestTime = 0;
317
409
  try {
318
- const dirs = readdirSync(tmpDir).filter((d) => existsSync(join(tmpDir, d, "chats")));
410
+ const dirs = readdirSync(tmpDir).filter((d) =>
411
+ existsSync(join(tmpDir, d, "chats")),
412
+ );
319
413
  for (const dir of dirs) {
320
414
  const chatsDir = join(tmpDir, dir, "chats");
321
415
  let files;
322
- try { files = readdirSync(chatsDir).filter((f) => f.endsWith(".json")); } catch { continue; }
416
+ try {
417
+ files = readdirSync(chatsDir).filter((f) => f.endsWith(".json"));
418
+ } catch {
419
+ continue;
420
+ }
323
421
  for (const file of files) {
324
422
  try {
325
423
  const data = JSON.parse(readFileSync(join(chatsDir, file), "utf-8"));
326
424
  const updatedAt = new Date(data.lastUpdated || 0).getTime();
327
425
  if (updatedAt <= bestTime) continue;
328
- let input = 0, output = 0;
426
+ let input = 0,
427
+ output = 0;
329
428
  let model = "unknown";
330
429
  for (const msg of data.messages || []) {
331
- if (msg.tokens) { input += msg.tokens.input || 0; output += msg.tokens.output || 0; }
430
+ if (msg.tokens) {
431
+ input += msg.tokens.input || 0;
432
+ output += msg.tokens.output || 0;
433
+ }
332
434
  if (msg.model) model = msg.model;
333
435
  }
334
436
  bestTime = updatedAt;
335
- best = { input, output, total: input + output, model, lastUpdated: data.lastUpdated };
336
- } catch { /* 무시 */ }
437
+ best = {
438
+ input,
439
+ output,
440
+ total: input + output,
441
+ model,
442
+ lastUpdated: data.lastUpdated,
443
+ };
444
+ } catch {
445
+ /* 무시 */
446
+ }
337
447
  }
338
448
  }
339
- } catch { /* 무시 */ }
449
+ } catch {
450
+ /* 무시 */
451
+ }
340
452
  return best;
341
453
  }