elanous 0.2.8 → 0.2.10

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 (385) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
  14. package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-d4b6df905f185055.js → page-54b32bd27726588d.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/market/{page-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.js → page-6cb0e1bbefe5fb3d.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7464c1987dd76e6d.js → page-e168fa1030af0548.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
  61. package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +1 -0
  62. package/apps/pwa/out/approvals/index.html +1 -1
  63. package/apps/pwa/out/approvals/index.txt +30 -29
  64. package/apps/pwa/out/autopilot/index.html +1 -1
  65. package/apps/pwa/out/autopilot/index.txt +30 -29
  66. package/apps/pwa/out/board/index.html +1 -1
  67. package/apps/pwa/out/board/index.txt +30 -29
  68. package/apps/pwa/out/botlab/index.html +1 -1
  69. package/apps/pwa/out/botlab/index.txt +27 -26
  70. package/apps/pwa/out/bots/index.html +1 -1
  71. package/apps/pwa/out/bots/index.txt +27 -26
  72. package/apps/pwa/out/chat/index.html +1 -1
  73. package/apps/pwa/out/chat/index.txt +27 -26
  74. package/apps/pwa/out/consult/index.html +1 -1
  75. package/apps/pwa/out/consult/index.txt +30 -29
  76. package/apps/pwa/out/control/index.html +1 -1
  77. package/apps/pwa/out/control/index.txt +30 -29
  78. package/apps/pwa/out/design-check/index.html +1 -1
  79. package/apps/pwa/out/design-check/index.txt +30 -29
  80. package/apps/pwa/out/editor/index.html +1 -1
  81. package/apps/pwa/out/editor/index.txt +30 -29
  82. package/apps/pwa/out/exec/index.html +1 -1
  83. package/apps/pwa/out/exec/index.txt +30 -29
  84. package/apps/pwa/out/field/index.html +1 -1
  85. package/apps/pwa/out/field/index.txt +30 -29
  86. package/apps/pwa/out/healthz +1 -1
  87. package/apps/pwa/out/index.html +1 -1
  88. package/apps/pwa/out/index.txt +27 -26
  89. package/apps/pwa/out/intake/index.html +1 -1
  90. package/apps/pwa/out/intake/index.txt +30 -29
  91. package/apps/pwa/out/live/index.html +1 -1
  92. package/apps/pwa/out/live/index.txt +30 -29
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +27 -26
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +27 -26
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +30 -29
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +30 -29
  101. package/apps/pwa/out/ops/checklist/index.html +1 -0
  102. package/apps/pwa/out/ops/checklist/index.txt +30 -0
  103. package/apps/pwa/out/ops/release/index.html +1 -0
  104. package/apps/pwa/out/ops/release/index.txt +30 -0
  105. package/apps/pwa/out/ops/seats/index.html +1 -0
  106. package/apps/pwa/out/ops/seats/index.txt +30 -0
  107. package/apps/pwa/out/reflection/index.html +1 -1
  108. package/apps/pwa/out/reflection/index.txt +28 -27
  109. package/apps/pwa/out/scheduler/index.html +1 -1
  110. package/apps/pwa/out/scheduler/index.txt +30 -29
  111. package/apps/pwa/out/sessions/index.html +1 -1
  112. package/apps/pwa/out/sessions/index.txt +27 -26
  113. package/apps/pwa/out/settings/devices/index.html +1 -1
  114. package/apps/pwa/out/settings/devices/index.txt +30 -29
  115. package/apps/pwa/out/settings/index.html +1 -1
  116. package/apps/pwa/out/settings/index.txt +30 -29
  117. package/apps/pwa/out/setup/done/index.html +1 -1
  118. package/apps/pwa/out/setup/done/index.txt +30 -29
  119. package/apps/pwa/out/setup/index.html +1 -1
  120. package/apps/pwa/out/setup/index.txt +30 -29
  121. package/apps/pwa/out/share/index.html +1 -1
  122. package/apps/pwa/out/share/index.txt +30 -29
  123. package/apps/pwa/out/showroom/index.html +1 -1
  124. package/apps/pwa/out/showroom/index.txt +27 -26
  125. package/apps/pwa/out/tasks/index.html +1 -1
  126. package/apps/pwa/out/tasks/index.txt +30 -29
  127. package/apps/pwa/out/term/index.html +1 -1
  128. package/apps/pwa/out/term/index.txt +30 -29
  129. package/apps/pwa/out/today/index.html +1 -0
  130. package/apps/pwa/out/today/index.txt +33 -0
  131. package/apps/pwa/out/trace/index.html +1 -1
  132. package/apps/pwa/out/trace/index.txt +30 -29
  133. package/apps/pwa/out/vault/index.html +1 -1
  134. package/apps/pwa/out/vault/index.txt +30 -29
  135. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  136. package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
  137. package/apps/pwa/out/workflows/index.html +1 -1
  138. package/apps/pwa/out/workflows/index.txt +30 -29
  139. package/apps/pwa/out/workspace/index.html +1 -1
  140. package/apps/pwa/out/workspace/index.txt +30 -29
  141. package/apps/pwa/out/worktrees/index.html +1 -1
  142. package/apps/pwa/out/worktrees/index.txt +30 -29
  143. package/graphs/field/field-feed.yaml +2 -0
  144. package/graphs/implement-loop.yaml +1 -1
  145. package/graphs/release/recipes.yaml +3 -0
  146. package/graphs/release/release-loop.yaml +3 -1
  147. package/graphs/test-diet/nightly-audit.yaml +18 -0
  148. package/graphs/test-diet/recipes.yaml +3 -0
  149. package/package.json +1 -1
  150. package/plugins/card-followup/graphs/run-step.ts +48 -13
  151. package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
  152. package/plugins/doc-draft/graphs/run-step.ts +14 -7
  153. package/scripts/ci-pwa-build-gate.ts +160 -0
  154. package/scripts/coord-channel-watch.sh +42 -8
  155. package/scripts/coord-digest.ts +199 -0
  156. package/scripts/coord-post.sh +60 -3
  157. package/scripts/install.ps1 +68 -9
  158. package/scripts/install.sh +39 -12
  159. package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
  160. package/scripts/lib/tui-regress-checks.ts +146 -0
  161. package/scripts/memory-lifecycle-cycle.ts +2 -2
  162. package/scripts/ops-now-probe.ts +327 -0
  163. package/scripts/publish-internal-dist.ts +11 -1
  164. package/scripts/release-loop/cutoff.ts +31 -2
  165. package/scripts/release-loop/export-check-node.ts +85 -0
  166. package/scripts/release-loop/gate-node.ts +18 -4
  167. package/scripts/release-loop/node-verdict.ts +8 -1
  168. package/scripts/release-loop/ops-upgrade-node.ts +72 -16
  169. package/scripts/release-loop/release-readiness.ts +9 -0
  170. package/scripts/release-loop/version-node.ts +58 -10
  171. package/scripts/repo-watch-cycle.ts +1 -1
  172. package/scripts/retro-cycle.ts +2 -2
  173. package/scripts/schedule-health-report.ts +1 -1
  174. package/scripts/storyboard/storyboard.ts +5 -161
  175. package/scripts/test-diet/lib.ts +31 -1
  176. package/scripts/test-diet/node.ts +134 -17
  177. package/scripts/test-timeout-audit.ts +114 -0
  178. package/scripts/tree-sync-alert.ts +1 -1
  179. package/scripts/tui-regress.ts +71 -0
  180. package/scripts/waitlist-daily-digest.ts +1 -1
  181. package/skills/explainer-video/SKILL.md +4 -0
  182. package/skills/explainer-video/engine/reel.mjs +18 -13
  183. package/skills/explainer-video/engine/reel.sh +24 -5
  184. package/skills/explainer-video/engine/stitch.mjs +145 -0
  185. package/skills/pitch-deck/SKILL.md +36 -0
  186. package/skills/pitch-deck/template.html +80 -0
  187. package/src/acp/codex-approval-adapter.ts +9 -3
  188. package/src/acp/core-turn-bridge.ts +117 -18
  189. package/src/acp/fact-question.ts +11 -0
  190. package/src/acp/server.ts +48 -7
  191. package/src/acp/tool-approval.ts +126 -0
  192. package/src/acp/turn-runner.ts +36 -0
  193. package/src/agent/monad-agent-turn.ts +8 -0
  194. package/src/ask-user-question/pending-questions.ts +25 -10
  195. package/src/ask-user-question/types.ts +9 -0
  196. package/src/auth/setup-link-tokens.fixture.ts +19 -0
  197. package/src/auth/setup-link-tokens.ts +164 -0
  198. package/src/autopilot/build/integrity-gate.ts +12 -0
  199. package/src/boot/daemon-prompt-turn.ts +3 -0
  200. package/src/boot/daemon-tools/index.ts +8 -0
  201. package/src/boot/daemon-tools/types.ts +2 -0
  202. package/src/boot/daemon-tools/web-search.ts +1 -0
  203. package/src/budget/unified-usage.ts +75 -0
  204. package/src/channel-bot-token.ts +37 -10
  205. package/src/chat/ctrl-c-in-input.ts +14 -0
  206. package/src/chat/index.ts +61 -9
  207. package/src/chat/pickers/state.ts +6 -2
  208. package/src/cli/agent-cli.ts +7 -0
  209. package/src/cli/decisions-cli.ts +25 -3
  210. package/src/cli/doctor-bundle-upload.ts +68 -0
  211. package/src/cli/doctor-cli.ts +38 -4
  212. package/src/cli/op-proxy-sync.ts +83 -0
  213. package/src/cli/ops-cli.ts +20 -0
  214. package/src/cli/pod-cli.ts +49 -0
  215. package/src/cli/pr-cli.ts +37 -4
  216. package/src/cli/pwa-stop.ts +163 -13
  217. package/src/cli/pwa-test.ts +3 -0
  218. package/src/cli/release-cli.ts +98 -15
  219. package/src/cli/self-update.ts +14 -0
  220. package/src/cli/storyboard-cli.ts +37 -0
  221. package/src/cli/test-state-dir-flag.ts +11 -4
  222. package/src/connectors/linear.ts +146 -0
  223. package/src/context-bus/coord-events.ts +115 -0
  224. package/src/dashboard/dashboard-boot-fixture.ts +23 -0
  225. package/src/dashboard/dashboard-status-lines.ts +45 -0
  226. package/src/dashboard/first-screen-band.ts +35 -0
  227. package/src/dashboard/index.ts +128 -34
  228. package/src/dashboard/provider-slash-runtime.ts +28 -11
  229. package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
  230. package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
  231. package/src/dashboard/turn-stream-formatter.ts +1 -1
  232. package/src/decisions/decision-cards.ts +50 -2
  233. package/src/decisions/decision-ledger.ts +75 -4
  234. package/src/decisions/decision-linear-projection.ts +157 -0
  235. package/src/decisions/web-push-decision-cards.ts +50 -0
  236. package/src/directive/interpretation-card.ts +36 -0
  237. package/src/discord.ts +2 -1
  238. package/src/domains/coo-admin-tool.ts +62 -0
  239. package/src/domains/core-tools.ts +11 -3
  240. package/src/domains/elanous-card.ts +29 -0
  241. package/src/domains/ops-facts-tool.ts +76 -0
  242. package/src/domains/outbound-alert.ts +41 -13
  243. package/src/domains/release-tool.ts +168 -0
  244. package/src/domains/telegram-kind-route.ts +19 -1
  245. package/src/exec-requests/planner.ts +98 -28
  246. package/src/exec-requests/runner.ts +107 -10
  247. package/src/exec-requests/store.ts +8 -2
  248. package/src/field/field-media.ts +10 -7
  249. package/src/field/field-reel.ts +14 -4
  250. package/src/git-fs/worktree.ts +45 -6
  251. package/src/graph-runner/graph-approval-card.ts +113 -0
  252. package/src/graph-runner/graph-cli.ts +129 -4
  253. package/src/graph-runner/graph-notify.ts +25 -3
  254. package/src/graph-runner/runner.ts +142 -16
  255. package/src/harness/browser-verify.ts +25 -2
  256. package/src/harness/harness-cli-command.ts +231 -35
  257. package/src/harness/harness-incidents-writer.fixture.ts +24 -0
  258. package/src/harness/harness-incidents.ts +129 -0
  259. package/src/harness/harness-stop.ts +53 -3
  260. package/src/hitl/clarification-policy.ts +3 -1
  261. package/src/index.ts +172 -41
  262. package/src/intake-plane/author-goals.ts +37 -4
  263. package/src/intake-plane/check.ts +3 -0
  264. package/src/intake-plane/discord-seat-work.ts +47 -2
  265. package/src/intake-plane/seat-doc-route.ts +170 -6
  266. package/src/intake-plane/telegram-seat-work.ts +44 -2
  267. package/src/intake-plane/telegram-work.ts +1 -0
  268. package/src/maturity/cli-maturity.ts +34 -0
  269. package/src/maturity/feature-maturity.ts +100 -1
  270. package/src/maturity/tui-slash-maturity.ts +31 -0
  271. package/src/memory.ts +32 -4
  272. package/src/nexus/api/exec-requests.ts +33 -3
  273. package/src/nexus/api/http-server.ts +73 -1
  274. package/src/nexus/api/meta-api.ts +38 -0
  275. package/src/nexus/api/operator.ts +61 -0
  276. package/src/nexus/api/ops-api.ts +148 -0
  277. package/src/nexus/api/ops-seats-sources.ts +95 -0
  278. package/src/nexus/api/ops-seats.ts +124 -0
  279. package/src/nexus/api/public-routes.ts +14 -1
  280. package/src/nexus/api/seat-requests.ts +88 -8
  281. package/src/nexus/api/sessions-store.ts +63 -0
  282. package/src/nexus/api/setup-claim.ts +21 -0
  283. package/src/nexus/api/setup-codex-login.ts +160 -0
  284. package/src/nexus/api/usage-api.ts +102 -0
  285. package/src/nexus/config/secrets/file-backend.ts +56 -18
  286. package/src/nexus/index.ts +45 -4
  287. package/src/onboarding/first-run-doctor.ts +54 -0
  288. package/src/onboarding/first-run.ts +89 -0
  289. package/src/onboarding/lan-address.ts +45 -0
  290. package/src/onboarding/web-first.ts +119 -24
  291. package/src/onboarding.ts +4 -0
  292. package/src/ops-now/ops-now-refresh.ts +100 -0
  293. package/src/project/project-store.ts +115 -0
  294. package/src/release-loop/checklist.ts +76 -61
  295. package/src/release-loop/feature-store.ts +460 -0
  296. package/src/release-loop/manifest.ts +59 -5
  297. package/src/release-loop/release-schedule.ts +53 -0
  298. package/src/repl/index.ts +7 -0
  299. package/src/seat-dispatch/ceo-commands.ts +98 -0
  300. package/src/seat-dispatch/ceo-intent.ts +38 -0
  301. package/src/seat-loop/seat-loop.ts +231 -0
  302. package/src/seat-loop/seat-report.ts +64 -0
  303. package/src/self-dev/decompose.ts +66 -11
  304. package/src/self-dev/run-store.ts +10 -1
  305. package/src/self-implement/execution-clarification.ts +115 -0
  306. package/src/self-implement/gate-cli.ts +6 -3
  307. package/src/self-implement/orchestrator.ts +37 -3
  308. package/src/self-implement/pipeline-shape.ts +1 -1
  309. package/src/self-implement/run-status-mapping.ts +5 -3
  310. package/src/session/chat.ts +2 -0
  311. package/src/session/index.ts +9 -0
  312. package/src/shell-env-bootstrap.ts +17 -0
  313. package/src/skills/tools/usage-slash.ts +8 -7
  314. package/src/steward/directive.ts +51 -2
  315. package/src/steward/launch.ts +250 -0
  316. package/src/steward/outcome-notify.ts +115 -0
  317. package/src/steward/steward-cards.ts +8 -2
  318. package/src/steward/triage-plan.ts +32 -4
  319. package/src/steward/triage.ts +93 -9
  320. package/src/steward/working-backwards.ts +64 -0
  321. package/src/storyboard/storyboard.ts +141 -0
  322. package/src/task-cards/card-store.ts +2 -2
  323. package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
  324. package/src/telegram-card-followup.ts +233 -0
  325. package/src/telegram-commands.ts +16 -0
  326. package/src/telegram-report.ts +15 -9
  327. package/src/telegram-run.ts +90 -13
  328. package/src/telegram.ts +29 -8
  329. package/src/thinking-line.ts +28 -20
  330. package/src/tool-runtime/types.ts +4 -0
  331. package/src/tui/chat/execution-badge.ts +1 -1
  332. package/src/tui-client/dashboard-session.ts +2 -0
  333. package/src/user-config.ts +118 -11
  334. package/src/ux-sim/turn-states.ts +1 -1
  335. package/src/version/packed-revision.json +1 -1
  336. package/src/web-push/notify-exec-request.ts +32 -0
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/4836-3084c58c1725a40b.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/8894-7317998f6cb97d8c.js +0 -1
  348. package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/app/approvals/page-9aa47b0bc6d570ff.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
  382. package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
  383. package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
  384. package/src/dashboard/turn-typeahead-echo.ts +0 -23
  385. /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
package/src/chat/index.ts CHANGED
@@ -19,6 +19,7 @@ import type { ModalSurface } from '../display/modal-stack.js';
19
19
  import type { ContentBlock } from '../llm.js';
20
20
  import { debug } from '../debug/log.js';
21
21
  import { matchTextInputGlobalAction } from './global-actions.js';
22
+ import { CTRL_C_ARM_MS, resolveCtrlCInInput } from './ctrl-c-in-input.js';
22
23
  import { isTextInputQuitChord } from './input-quit-chord.js';
23
24
  // P5 — leader-chord pass-through from input focus. chat.ts swallows
24
25
  // plain letters into its textarea buffer, so the resolver never sees
@@ -431,7 +432,14 @@ export interface SlashCommand {
431
432
 
432
433
  // Default commands available in the input
433
434
  export const SLASH_COMMANDS: SlashCommand[] = [
434
- { name: 'directive', aliases: [], description: 'Record a directive as a Linear issue' },
435
+ { name: 'help', aliases: ['?'], description: '명령과 키 보기' },
436
+ { name: 'resume', aliases: [], description: '지난 대화 이어 가기 · /resume <id 앞자리>' },
437
+ { name: 'model', aliases: ['m'], description: '모델 바꾸기 — /model <codex|terra|sol|luna|opus|sonnet|grok> (list + current when no arg). OpenAI 는 Codex(Responses API)만. effort 는 /reasoning.', subcommands: ['codex', 'terra', 'sol', 'luna', 'opus', 'sonnet', 'grok'] },
438
+ { name: 'clear', aliases: ['cls'], description: '대화 기록 지우기' },
439
+ { name: 'status', aliases: ['st'], description: '지금 상태 — 모델 · 연결 · 계정' },
440
+ { name: 'remaining', aliases: [], description: '계정별 남은 사용량' },
441
+ { name: 'setup', aliases: [], description: '상세 설정 — /setup · /setup reset(다음 실행 때 마법사 다시)', subcommands: ['help', 'reset'] },
442
+ { name: 'quit', aliases: ['q', 'exit'], description: '나가기' },
435
443
  { name: 'run-skill', aliases: ['rs', 'run'], description: 'Execute a skill via its SKILL.md prompt' },
436
444
  { name: 'ad', aliases: [], description: 'Create an advertising plan from a URL, brief, or attached image' },
437
445
  // B4 — craft rulebook verdict, same resolution as `elanous repo design-check`
@@ -440,10 +448,8 @@ export const SLASH_COMMANDS: SlashCommand[] = [
440
448
  { name: 'design', aliases: ['design-check'], description: 'Design rulebooks this repository declares · /design [--declared] · /design pick [number|id] to choose a design direction', subcommands: ['--declared', 'pick'] },
441
449
  { name: 'provider', aliases: ['p'], description: 'LLM providers — /provider (list) · next (cycle, also Alt+M / pill click) · use <name> · pick (visual picker) · reset', subcommands: ['next', 'use', 'pick', 'picker', 'menu', 'reset', 'list'] },
442
450
  { name: 'reasoning', aliases: ['r', 'think'], description: 'Reasoning level (codex effort + summary, anthropic extended-thinking budget) — /reasoning [off|low|medium|high|xhigh] (cycle when no arg · xhigh 는 모델 상한이 xhigh 이상일 때만 wire 에 실린다, 아니면 high 로 깎인다)', subcommands: ['off', 'low', 'medium', 'high', 'xhigh'] },
443
- { name: 'model', aliases: ['m'], description: 'Switch active model — /model <codex|terra|sol|luna|opus|sonnet|grok> (list + current when no arg). OpenAI 는 Codex(Responses API)만. effort 는 /reasoning.', subcommands: ['codex', 'terra', 'sol', 'luna', 'opus', 'sonnet', 'grok'] },
444
451
  { name: 'local', aliases: ['ll'], description: 'Local OpenAI-compatible LLM — ping / models / test / use', subcommands: ['ping', 'models', 'test', 'use', 'status'] },
445
452
  { name: 'session', aliases: ['sess'], description: 'Session resume — list / load / sync / new (handoff from mobile)', subcommands: ['list', 'load', 'sync', 'new'] },
446
- { name: 'resume', aliases: [], description: 'Pick a past session, or load one by id prefix · /resume <id-prefix>' },
447
453
  { name: 'fork', aliases: [], description: 'Copy this session into a new one' },
448
454
  { name: 'rewind', aliases: [], description: 'Rewind — 과거 user 턴 픽커로 되감기 (원본 보존 · 새 세션 분기 · /rewind <n> 숏컷)' },
449
455
  // TUI 부활 C-a (2026-07-12): autopilot 미션 TUI 표면 — CLI 동등 단일 창구 소비.
@@ -454,12 +460,8 @@ export const SLASH_COMMANDS: SlashCommand[] = [
454
460
  { name: 'sync', aliases: ['s'], description: 'Enter sync mode' },
455
461
  { name: 'plugin', aliases: ['plugins'], description: 'Manage plugins', subcommands: ['list', 'activate', 'deactivate', 'reload'] },
456
462
  { name: 'widget', aliases: ['widgets'], description: 'Manage widgets', subcommands: ['list', 'reload', 'instances'] },
457
- { name: 'clear', aliases: ['cls'], description: 'Clear chat log' },
458
463
  { name: 'log', aliases: [], description: 'Chat Log controls — size / clear / filter / search / freeze / solo / turn / fold / help', subcommands: ['size', 'clear', 'filter', 'search', 'freeze', 'solo', 'turn', 'fold', 'help'] },
459
- { name: 'help', aliases: ['?'], description: 'Show help overlay' },
460
- { name: 'setup', aliases: [], description: 'Onboarding wizard guide — /setup (anchor) · /setup reset (re-run wizard on next boot)', subcommands: ['help', 'reset'] },
461
464
  { name: 'memory', aliases: ['mem'], description: 'Memory ops — list / show / search / add / delete (see `elanous memory --help`)', subcommands: ['list', 'show', 'search', 'add', 'delete', 'index'] },
462
- { name: 'status', aliases: ['st'], description: 'Print the claude-code-style status pills (working dir + git + model)' },
463
465
  { name: 'export', aliases: [], description: 'Export this conversation transcript to a markdown file — /export [path] (default ~/temp/elanous-transcript-<stamp>.md)' },
464
466
  { name: 'delta', aliases: ['diffs'], description: 'Source delta browser — open the latest turn-scoped file diff popup', subcommands: ['open', 'help'] },
465
467
  { name: 'theme', aliases: [], description: 'Theme controls — list / switch / use / reset / preview / export', subcommands: ['list', 'switch', 'use', 'reset', 'preview', 'export'] },
@@ -486,7 +488,6 @@ export const SLASH_COMMANDS: SlashCommand[] = [
486
488
  { name: 'acp', aliases: [], description: 'ACP chat — stream claude-code / codex / gemini replies into the chat pane (not a VW spawn). /acp codex points at the canonical codex app-server path; /acp cas is a synonym.', subcommands: ['claude', 'codex', 'gemini', 'cas', 'cancel', 'status', 'drop'] },
487
489
  { name: 'conv', aliases: [], description: 'Conversation widget/popup host — /conv list · /conv open <session-id> · /conv layout <cascade|tile|stack> · /conv focus <next|prev>.', subcommands: ['list', 'ls', 'open', 'layout', 'focus'] },
488
490
  { name: 'handoff', aliases: [], description: 'H5 P3 cross-agent context handoff: /handoff <from_session_id> <to_brand> [--channels r,p,m] [--prompt "prefix"]. Takes source session snapshot (filtered by channels if observer present) and launches target via adapter registry. Brands: codex · claude · claude-code · gemini · elanous.' },
489
- { name: 'remaining', aliases: [], description: 'What each account has left' },
490
491
  { name: 'agent-room', aliases: [], description: 'H6 P4 VW agent-room — /agent-room <N> <brands...> · /agent-room list · /agent-room close <id> · N ∈ {2,3,4} · brands: codex/claude/gemini/elanous/auto/lll:<m>', subcommands: ['list', 'close', 'preset', 'help'] },
491
492
  { name: 'showroom', aliases: ['sr'], description: 'Showroom v2 multi-LLM lane composer — /showroom (default 2-pane) · /showroom <lane1> <lane2> [<lane3> [<lane4>]] · lane = role:provider[:transport] · roles plan/build/exec/review/reflect · pair with /lane and /relay for cross-lane handoffs.', subcommands: ['help'] },
492
493
  { name: 'reply', aliases: [], description: 'H6 P5 AgentReply — /reply <target-session-id> <message...> · send message to a live session, capture the response · flags: --from · --channels r,m · --idle-ms · --timeout-ms', subcommands: ['help'] },
@@ -503,7 +504,7 @@ export const SLASH_COMMANDS: SlashCommand[] = [
503
504
  { name: 'qc', aliases: [], description: 'Quick-control — arm one-shot control mode; next message runs as control, then auto-return to chat.' },
504
505
  { name: 'voice-chat', aliases: ['vc'], description: 'Continuous voice chat mode — speak, elanous replies in voice (Phase 4-5). Subcommands: start / stop / cancel / status. Chord: Alt+R toggles enter/exit anywhere.', subcommands: ['start', 'stop', 'cancel', 'status'] },
505
506
  { name: 'auto-tts', aliases: ['tts', 'autotts'], description: 'Auto-TTS for chat responses (Phase 2) — speaks LLM replies sentence-by-sentence. Subcommands: on / off / toggle / status.', subcommands: ['on', 'off', 'toggle', 'status'] },
506
- { name: 'quit', aliases: ['q', 'exit'], description: 'Exit application' },
507
+ { name: 'directive', aliases: [], description: '(운영) 지시를 Linear 이슈로 기록' },
507
508
  ];
508
509
 
509
510
  /** Every user-visible slash spelling, normalized for runtime-catalog comparison. */
@@ -896,6 +897,10 @@ export async function textInput(opts: {
896
897
  let lastReportedLines = -1;
897
898
  let lineIdx = 0; // current line
898
899
  let colIdx = lines[0]!.length; // cursor at end of initial text
900
+ let ctrlCArmedAt: number | null = null;
901
+ let ctrlCHintTimer: ReturnType<typeof setTimeout> | null = null;
902
+ let ctrlCHint: string | null = null;
903
+ let ctrlCHintPainted = false;
899
904
 
900
905
  // KX2 — picker selection / navigation / async fetch caches live in
901
906
  // the shared state machine (chat-picker-state.ts). textInput keeps
@@ -1173,6 +1178,14 @@ export async function textInput(opts: {
1173
1178
  }
1174
1179
  prevDrawnLines = visibleLines;
1175
1180
 
1181
+ if (ctrlCHint) {
1182
+ writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K' + C.muted(truncate(ctrlCHint, width)));
1183
+ ctrlCHintPainted = true;
1184
+ } else if (ctrlCHintPainted) {
1185
+ writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K');
1186
+ ctrlCHintPainted = false;
1187
+ }
1188
+
1176
1189
  // Cursor — its buffer line is `lineIdx`; its screen row is the
1177
1190
  // bottom-anchored offset within the visible window. Critically,
1178
1191
  // `textBeforeCursor` reads from `lines[lineIdx]` (the buffer), not
@@ -1468,6 +1481,11 @@ export async function textInput(opts: {
1468
1481
  process.exit(0);
1469
1482
  }
1470
1483
 
1484
+ if (!(key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt)) {
1485
+ ctrlCArmedAt = null;
1486
+ ctrlCHint = null;
1487
+ }
1488
+
1471
1489
  // A bracketed-paste envelope is one atomic event. Handle it before
1472
1490
  // host routing so embedded newlines cannot reach a submit handler.
1473
1491
  if (key.name === 'paste') {
@@ -1571,6 +1589,40 @@ export async function textInput(opts: {
1571
1589
  continue;
1572
1590
  }
1573
1591
 
1592
+ if (key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt) {
1593
+ const text = lines.join('\n');
1594
+ const result = resolveCtrlCInInput({ text, now: Date.now(), armedAt: ctrlCArmedAt });
1595
+ debug.log('chat.input', 'ctrl-c', { action: result.action, length: text.length });
1596
+ ctrlCArmedAt = result.armedAt;
1597
+ ctrlCHint = result.hint;
1598
+ if (ctrlCHintTimer) clearTimeout(ctrlCHintTimer);
1599
+ ctrlCHintTimer = null;
1600
+ if (result.action === 'arm-exit') {
1601
+ // U1a — the hint promises «one more press exits» only while that is true.
1602
+ const armedAt = result.armedAt;
1603
+ ctrlCHintTimer = setTimeout(() => {
1604
+ ctrlCHintTimer = null;
1605
+ if (ctrlCArmedAt !== armedAt) return;
1606
+ ctrlCArmedAt = null;
1607
+ ctrlCHint = null;
1608
+ drawAll();
1609
+ }, CTRL_C_ARM_MS + 50);
1610
+ ctrlCHintTimer.unref?.();
1611
+ }
1612
+ if (result.action === 'exit') return submitBufferResult('/quit');
1613
+ if (result.action === 'clear-input') {
1614
+ lines = [''];
1615
+ lineIdx = 0;
1616
+ colIdx = 0;
1617
+ historyIdx = -1;
1618
+ savedCurrentInput = '';
1619
+ onUserBufferEdit();
1620
+ await picker.refresh(bufView());
1621
+ }
1622
+ drawAll();
1623
+ continue;
1624
+ }
1625
+
1574
1626
  const globalDispatcher = dispatchGlobalAction;
1575
1627
  const globalAction = globalDispatcher
1576
1628
  ? matchTextInputGlobalAction(key, {
@@ -33,6 +33,7 @@ import type {
33
33
  } from '../index.js';
34
34
  import { filterSlashCommands } from '../index.js';
35
35
  import { filterInputMatches } from '../../input/query-match.js';
36
+ import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
36
37
 
37
38
  /** Read-only snapshot of the textInput buffer handed to state on
38
39
  * every public call. The state machine never mutates it; it only
@@ -234,8 +235,11 @@ export function createPickerState(deps: PickerDeps): PickerState {
234
235
  let currentAtItems: AtCandidate[] = [];
235
236
  let currentSkillItems: SkillCandidate[] = [];
236
237
 
237
- const filteredSlash = (buf: PickerBufferView): SlashCommand[] =>
238
- filterSlashCommands((buf.lines[0] ?? '').slice(1), deps.commands);
238
+ const filteredSlash = (buf: PickerBufferView): SlashCommand[] => {
239
+ const { role, showBeta } = readTuiSlashAudience();
240
+ return filterSlashCommands((buf.lines[0] ?? '').slice(1), deps.commands)
241
+ .filter((cmd) => (role === 'owner' && slashMaturity(cmd.name) === undefined) || slashVisibleFor(cmd.name, role, { showBeta }));
242
+ };
239
243
 
240
244
  const refreshArgs = async (buf: PickerBufferView): Promise<ArgSuggestion[]> => {
241
245
  const ctx = parseArgContext(buf, deps.commands);
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
3
3
  import { getUserConfig, reloadUserConfig } from '../user-config.js';
4
4
  import { handleOnboardingRefusal, needsOnboarding, runOnboarding } from '../onboarding.js';
5
5
  import { runTurn, ensureCliSession, sessionBudget } from '../session/chat.js';
6
+ import { suggestProjectForFolder } from '../project/project-store.js';
6
7
  import { NoLlmProviderAvailableError, noProviderAvailableMessage } from '../llm.js';
7
8
  import { resolveSessionId, getActiveSessionId, setActiveSessionId } from '../session/index.js';
8
9
  import { getHarnessSpace } from '../harness/harness-space.js';
@@ -433,6 +434,12 @@ export async function runChatTurnCli(opts: {
433
434
  }
434
435
  const session = ensureCliSession(opts.cfg, resolvedSessionIdHint);
435
436
  setActiveSessionId(session.id);
437
+ if (!opts.json && (!resolvedSessionIdHint || session.id !== resolvedSessionIdHint)) {
438
+ try {
439
+ const project = suggestProjectForFolder(process.cwd());
440
+ if (project) console.log(`Project suggestion: ${project.name} (${project.id})`);
441
+ } catch { /* suggestions must not interrupt a CLI conversation */ }
442
+ }
436
443
  const harnessSpace = getHarnessSpace();
437
444
  // Tag the debug log with this session id so every event in this
438
445
  // process attributes correctly. enrichDebugRecord picks up the
@@ -1,4 +1,5 @@
1
1
  import type { Command } from 'commander';
2
+ import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
2
3
  import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
3
4
 
4
5
  const repeat = (value: string, values: string[]) => [...values, value];
@@ -49,22 +50,31 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
49
50
  const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
50
51
  const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
51
52
  const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
53
+ root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
54
+ .option('--dry-run').option('--json')
55
+ .action(async (o: { dryRun?: boolean; json?: boolean }) => {
56
+ const result = await projectDecisionsToLinear({ stateDir: config.stateDir, dryRun: o.dryRun });
57
+ emit(result, o.json, result.reason ?? `생성 ${result.created} · 닫음 ${result.closed} · 건너뜀 ${result.skipped} · 실패 ${result.failed}${result.plan?.length ? `\n${result.plan.map(p => `${p.action} ${p.decisionId}${p.issue ? ` ${p.issue}` : ''}`).join('\n')}` : ''}`);
58
+ });
52
59
  root.command('raise').description('SCQA·선택지와 권고로 결정 항목을 올린다')
53
60
  .requiredOption('--title <text>').requiredOption('--category <category>')
54
61
  .requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
55
62
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
56
63
  .option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
57
64
  .option('--track <track>').option('--agent <agent>').option('--session <id>')
65
+ .option('--resume-question <qid>').option('--run <runId>')
58
66
  .option('--ref <url>', '참조 (반복)', repeat, [] as string[])
59
67
  .option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
60
68
  .option('--json')
61
- .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
69
+ .action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean }) => fail(() => {
70
+ if (o.run !== undefined && o.resumeQuestion === undefined) throw new Error('--run requires --resume-question');
62
71
  if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
63
72
  if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
64
73
  const options: DecisionOption[] = o.option.map(parseOption);
65
74
  const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
66
75
  recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
67
76
  raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
77
+ ...(o.resumeQuestion !== undefined ? { resume: { questionId: o.resumeQuestion, ...(o.run !== undefined ? { runId: o.run } : {}) } } : {}),
68
78
  ...(o.due ? { dueAt: dueAt(o.due) } : {}) });
69
79
  emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
70
80
  }));
@@ -83,8 +93,20 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
83
93
  if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
84
94
  if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
85
95
  const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
86
- const entry = new DecisionLedger(config).decide(id, choice, by, o.note);
87
- emit(entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
96
+ const { entry, delivery } = new DecisionLedger(config).decideWithDelivery(id, choice, by, o.note);
97
+ if (delivery && !delivery.ok) {
98
+ // The decision is recorded; only the answer to the waiting run failed — say so and how to retry, never «success».
99
+ const hint = `⚠ 답 전달 실패(${delivery.questionId} · ${delivery.reason}) — 결정은 기록됐다 · 다시: elanous decisions retry-answer ${entry.id}`;
100
+ emit({ ...entry, delivery }, o.json, `결정: ${formatDecisionDetail(entry)}\n${hint}`);
101
+ process.exitCode = 3;
102
+ return;
103
+ }
104
+ emit(delivery ? { ...entry, delivery } : entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
105
+ }));
106
+ root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json')
107
+ .action((id: string, o: { json?: boolean }) => fail(() => {
108
+ const entry = new DecisionLedger(config).retryAnswer(id);
109
+ emit(entry, o.json, `답 전달: ${entry.id} → ${entry.resume?.questionId}`);
88
110
  }));
89
111
  root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
90
112
  .requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
@@ -0,0 +1,68 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ import { debug } from '../debug/log.js';
3
+
4
+ export interface DoctorBundleUploadOptions {
5
+ path: string;
6
+ files: readonly string[];
7
+ uploadUrl?: string;
8
+ confirm: (message: string) => Promise<boolean>;
9
+ fetch: typeof globalThis.fetch;
10
+ yes?: boolean;
11
+ /** Present the inventory before either consent or transport; JSON callers can use stderr. */
12
+ announce?: (contents: string) => void;
13
+ /** Pass the size returned by buildDoctorBundle, if available. */
14
+ bytes?: number;
15
+ }
16
+
17
+ export interface DoctorBundleUploadResult {
18
+ path: string;
19
+ uploaded: boolean;
20
+ code?: string;
21
+ reason?: string;
22
+ exitCode: number;
23
+ message: string;
24
+ }
25
+
26
+ /** The bundle is local until both an HTTPS destination and affirmative consent exist. */
27
+ export async function uploadDoctorBundle({ path, files, uploadUrl, confirm, fetch, yes = false, bytes, announce = console.log }: DoctorBundleUploadOptions): Promise<DoctorBundleUploadResult> {
28
+ const size = bytes ?? (() => { try { return statSync(path).size; } catch { return undefined; } })();
29
+ const skipped = (reason: string, message: string): DoctorBundleUploadResult => {
30
+ debug.log('doctor.bundle', 'upload-skipped', { bytes: size, reason });
31
+ return { path, uploaded: false, reason, exitCode: 0, message };
32
+ };
33
+ if (!uploadUrl?.trim()) return skipped('no-recipient', `수신처가 아직 정해지지 않았습니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
34
+ let destination: URL;
35
+ try {
36
+ destination = new URL(uploadUrl.trim());
37
+ if (destination.protocol !== 'https:' || !destination.hostname || destination.username || destination.password) throw new Error('invalid destination');
38
+ } catch {
39
+ return skipped('invalid-recipient', `수신처가 유효한 HTTPS 주소가 아닙니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
40
+ }
41
+ const contents = `내용 목록 (${size === undefined ? '크기 확인 불가' : `${size} bytes`}):\n${files.map((file) => ` ${file}`).join('\n')}`;
42
+ announce(contents);
43
+ if (!yes) {
44
+ let agreed = false;
45
+ try { agreed = await confirm('올릴까요? (y/N) '); } catch { /* An unavailable prompt is a refusal. */ }
46
+ if (!agreed) {
47
+ debug.log('doctor.bundle', 'declined', { bytes: size, reason: 'consent-declined' });
48
+ return { path, uploaded: false, reason: 'consent-declined', exitCode: 0, message: `업로드하지 않았습니다: ${path}` };
49
+ }
50
+ }
51
+ const failed = (reason: string): DoctorBundleUploadResult => {
52
+ debug.log('doctor.bundle', 'upload-failed', { bytes: size, reason });
53
+ return { path, uploaded: false, reason, exitCode: 1, message: `업로드 실패 (${reason}) — 파일: ${path} — 다시 시도해 주세요.` };
54
+ };
55
+ try {
56
+ const response = await fetch(destination.href, {
57
+ method: 'POST', headers: { 'Content-Type': 'application/gzip' }, body: readFileSync(path), redirect: 'manual',
58
+ });
59
+ if (!response.ok) return failed(`HTTP ${response.status}`);
60
+ const data: unknown = await response.json();
61
+ const code = data && typeof data === 'object' && !Array.isArray(data) ? (data as { code?: unknown }).code : undefined;
62
+ if (typeof code !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(code)) return failed('진단 코드 응답이 올바르지 않습니다');
63
+ debug.log('doctor.bundle', 'uploaded', { bytes: size, reason: 'success' });
64
+ return { path, uploaded: true, code, exitCode: 0, message: `진단 코드: ${code} — 이 코드만 지원에 알려 주세요` };
65
+ } catch {
66
+ return failed('네트워크 또는 파일 읽기 오류');
67
+ }
68
+ }
@@ -38,6 +38,7 @@ import { describeModes, formatModeLine, type DoctorMode } from './doctor-modes.j
38
38
  import { formatDoctorLocalLlm, probeDoctorLocalLlm, type DoctorLocalLlm } from './doctor-local-llm.js';
39
39
  import type { LlmInventory } from '../llm/local-manager/types.js';
40
40
  import { buildDoctorBundle } from './doctor-bundle.js';
41
+ import { uploadDoctorBundle } from './doctor-bundle-upload.js';
41
42
 
42
43
  function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
43
44
  const c = checkPythonEnv(false);
@@ -193,6 +194,9 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
193
194
  adviceInteractive?: boolean;
194
195
  /** Local-manager inventory seam; default calls getInventory(). */
195
196
  localLlmInventory?: () => Promise<LlmInventory>;
197
+ /** Inject the upload transport and consent for isolated CLI tests. */
198
+ uploadFetch?: typeof globalThis.fetch;
199
+ confirmBundleUpload?: (message: string) => Promise<boolean>;
196
200
  }
197
201
 
198
202
  const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
@@ -1217,15 +1221,21 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1217
1221
  .option('--json', 'structured output')
1218
1222
  .option('--credentials', 'show per-credential details in human-readable output')
1219
1223
  .option('--bundle', 'write a redacted diagnostics archive for support')
1224
+ .option('--upload', 'send the bundle to diagnostics.uploadUrl after consent (requires --bundle)')
1220
1225
  .option('--out <dir>', 'output directory for --bundle')
1221
1226
  .option('--fix', 'show repairs; git installation asks on a TTY (other repairs require --yes)')
1222
1227
  .option('--advise', 'ask the configured LLM to rank catalog repairs (read-only advice)')
1223
- .option('--yes', 'apply planned doctor repairs (requires --fix)')
1228
+ .option('--yes', 'apply planned doctor repairs (--fix), or confirm bundle upload (--bundle --upload)')
1224
1229
  .option('--sudo', 'also run the planned sudo install lines — only where `sudo -n true` works (requires --fix --yes)')
1225
1230
  .option('--restart', 'restart the nexus service when it runs a different version than this installed copy, then verify it (requires --fix --yes · interrupts bots, terminals and running turns)')
1226
- .action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
1231
+ .action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; upload?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
1232
+ if (opts.upload && !opts.bundle) {
1233
+ err.error('--upload requires --bundle');
1234
+ setExitCode(1);
1235
+ return;
1236
+ }
1227
1237
  if (opts.bundle) {
1228
- if (opts.fix || opts.advise || opts.yes || opts.sudo || opts.restart) {
1238
+ if (opts.fix || opts.advise || opts.sudo || opts.restart || (opts.yes && !opts.upload)) {
1229
1239
  err.error('--bundle cannot be combined with repair or advice options');
1230
1240
  setExitCode(1);
1231
1241
  return;
@@ -1235,7 +1245,31 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
1235
1245
  const doctorReport = runDoctor(deps);
1236
1246
  if (doctorReport.ok) doctorReport.localLlm = await probeDoctorLocalLlm(deps.localLlmInventory);
1237
1247
  const result = buildDoctorBundle({ outDir: opts.out ?? process.cwd(), doctorOptions: deps, doctorReport });
1238
- out.log(opts.json ? JSON.stringify(result) : [`Diagnostics: ${result.path} (${result.bytes} bytes)`, 'Contents:', ...result.files.map((file) => ` ${file}`)].join('\n'));
1248
+ if (opts.upload) {
1249
+ let uploadUrl: string | undefined;
1250
+ try { uploadUrl = (deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)()).diagnostics?.uploadUrl; }
1251
+ catch {
1252
+ const reason = '설정을 읽을 수 없습니다';
1253
+ debug.log('doctor.bundle', 'upload-failed', { bytes: result.bytes, reason: 'config-read-failed' });
1254
+ if (opts.json) out.log(JSON.stringify({ path: result.path, uploaded: false, reason }));
1255
+ else err.error(`업로드 실패 (${reason}) — 파일: ${result.path} — 다시 시도해 주세요.`);
1256
+ setExitCode(1);
1257
+ return;
1258
+ }
1259
+ const uploaded = await uploadDoctorBundle({
1260
+ ...result,
1261
+ uploadUrl,
1262
+ yes: opts.yes,
1263
+ announce: (contents) => { if (opts.json) err.error(contents); else out.log(contents); },
1264
+ confirm: deps.confirmBundleUpload ?? ((message) => confirmDoctorAdvice(message, opts.json === true)),
1265
+ fetch: deps.uploadFetch ?? globalThis.fetch,
1266
+ });
1267
+ if (opts.json) out.log(JSON.stringify({ path: uploaded.path, uploaded: uploaded.uploaded, ...(uploaded.code ? { code: uploaded.code } : {}), ...(uploaded.reason ? { reason: uploaded.reason } : {}) }));
1268
+ else out.log(uploaded.message);
1269
+ if (uploaded.exitCode) setExitCode(uploaded.exitCode);
1270
+ } else {
1271
+ out.log(opts.json ? JSON.stringify(result) : [`Diagnostics: ${result.path} (${result.bytes} bytes)`, 'Contents:', ...result.files.map((file) => ` ${file}`)].join('\n'));
1272
+ }
1239
1273
  } catch {
1240
1274
  err.error('Could not create diagnostics archive.');
1241
1275
  setExitCode(1);
@@ -0,0 +1,83 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { ACP_TOKEN_FILE } from '../auth/token-store.js';
5
+ import { debug } from '../debug/log.js';
6
+ import { getElanousConfigDir } from '../elanous-config-dir.js';
7
+ import { userConfigPath } from '../user-config.js';
8
+
9
+ export interface OpProxyConfig {
10
+ host: string;
11
+ bearerFile: string;
12
+ restart: string;
13
+ }
14
+
15
+ /** Read the optional, unnormalized nexus setting from the same config file as the CLI. */
16
+ export function readOpProxyConfig(path = userConfigPath()): OpProxyConfig | undefined {
17
+ try {
18
+ const raw = JSON.parse(readFileSync(path, 'utf8')) as { nexus?: { opProxy?: OpProxyConfig } };
19
+ return raw?.nexus?.opProxy;
20
+ } catch {
21
+ return undefined;
22
+ }
23
+ }
24
+
25
+ type SshExecutor = (argv: string[], input: string) => { status: number | null; error?: Error };
26
+
27
+ export function finishOpProxyRotation(
28
+ proxy: OpProxyConfig | undefined,
29
+ token: string,
30
+ opts: { sync: boolean; ssh?: SshExecutor; tokenFile?: string; out?: (line: string) => void },
31
+ ): boolean | undefined {
32
+ if (!proxy) return undefined;
33
+ if (!opts.sync) {
34
+ (opts.out ?? console.log)('op 프록시 토큰도 갈아야 합니다 — `elanous token rotate --sync-op-proxy`');
35
+ return undefined;
36
+ }
37
+ return syncOpProxyToken(proxy, token, opts);
38
+ }
39
+
40
+ const executeSsh: SshExecutor = (argv, input) => {
41
+ const result = spawnSync('ssh', argv, { input, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30_000 });
42
+ return { status: result.status, ...(result.error ? { error: result.error } : {}) };
43
+ };
44
+
45
+ const shellQuote = (value: string): string => `'${value.replaceAll("'", "'\\''")}'`;
46
+
47
+ /** Never interpolate the token in a command, diagnostic, or log. Only SSH stdin carries it. */
48
+ export function syncOpProxyToken(
49
+ proxy: OpProxyConfig | undefined,
50
+ token: string,
51
+ opts: { ssh?: SshExecutor; tokenFile?: string; out?: (line: string) => void } = {},
52
+ ): boolean | undefined {
53
+ if (!proxy) return undefined;
54
+ const out = opts.out ?? console.log;
55
+ const host = typeof proxy.host === 'string' ? proxy.host : '';
56
+ const file = typeof proxy.bearerFile === 'string' ? proxy.bearerFile : '';
57
+ const service = typeof proxy.restart === 'string' ? proxy.restart : '';
58
+ const valid = /^[\w][\w.-]*$/.test(host) && file.startsWith('/') && !/[\r\n\0]/.test(file)
59
+ && /^[\w@][\w@.-]*$/.test(service) && !/[\r\n\0]/.test(opts.tokenFile ?? '');
60
+ const remote = `sudo install -m 600 /dev/stdin ${shellQuote(file)} && sudo systemctl restart ${shellQuote(service)}`;
61
+ const manual = valid
62
+ ? `token=$(head -n 1 < ${shellQuote(opts.tokenFile ?? join(getElanousConfigDir(), ACP_TOKEN_FILE))}) && [ -n "$token" ] && printf 'ELANOUS_OP_BEARER=%s\\n' "$token" | ssh ${shellQuote(host)} ${shellQuote(remote)}`
63
+ : 'nexus.opProxy 의 host, bearerFile, restart 를 확인한 뒤 bearer 파일을 갱신하고 서비스를 재시작하세요';
64
+ let reason = '';
65
+ if (!valid) {
66
+ reason = 'nexus.opProxy 설정 오류';
67
+ } else {
68
+ try {
69
+ const result = (opts.ssh ?? executeSsh)([host, remote], `ELANOUS_OP_BEARER=${token}\n`);
70
+ if (result.status === 0 && !result.error) {
71
+ debug.log('auth.op-proxy-sync', 'synced', { host, reason: 'ok' });
72
+ out(`op 프록시 토큰을 바꿨습니다(${host})`);
73
+ return true;
74
+ }
75
+ reason = result.error ? 'ssh 실행 실패' : `ssh 종료 코드 ${result.status ?? '미상'}`;
76
+ } catch {
77
+ reason = 'ssh 실행 실패';
78
+ }
79
+ }
80
+ debug.log('auth.op-proxy-sync', 'failed', { host, reason });
81
+ out(`op 프록시 토큰 동기화 실패(${host}): ${reason} — 직접: ${manual}`);
82
+ return false;
83
+ }
@@ -1,6 +1,11 @@
1
1
  import type { Command } from 'commander';
2
2
  import * as ui from '../ui.js';
3
3
  import { writeStdoutJson } from './stdout-json.js';
4
+ import type { OpsNowResult } from '../ops-now/ops-now-refresh.js';
5
+
6
+ export function opsNowExitCode(outcome: OpsNowResult['outcome']): number {
7
+ return outcome === 'channel-unreadable' || outcome === 'summarize-failed' ? 2 : 0;
8
+ }
4
9
 
5
10
  export function registerOpsCommands(program: Command): void {
6
11
  const opsCmd = program.command('ops')
@@ -173,6 +178,21 @@ async function runOpsBuild(buildId: string | undefined, opts: OpsBuildOpts): Pro
173
178
  process.exit(0);
174
179
  }
175
180
 
181
+ opsCmd.command('now-refresh').description('채널 📌안내로 ops-now 고정 기억 갱신(1회)')
182
+ .option('--once', '한 번 실행(기본)')
183
+ .option('--dry-run', '기억을 쓰지 않고 새 본문 출력')
184
+ .option('--json', '결과를 JSON으로 출력')
185
+ .action(async (o: { dryRun?: boolean; json?: boolean }) => {
186
+ const { refreshOpsNow } = await import('../ops-now/ops-now-refresh.js');
187
+ const result = await refreshOpsNow({ dryRun: o.dryRun === true });
188
+ if (o.json) await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
189
+ else {
190
+ console.log(`ops-now: ${result.outcome} · 안내 ${result.notices}개`);
191
+ if (o.dryRun && result.body !== undefined) console.log(result.body);
192
+ }
193
+ process.exitCode = opsNowExitCode(result.outcome);
194
+ });
195
+
176
196
  opsCmd.command('status').description('현재 상태 종합(미션·태스크·루프·오케스트레이션·스케줄) · --all-instances 로 fleet 전체 종합(미션/태스크/loops/스케줄/오케스트레이션) · -r/--remote 로 원격 GET 조립')
177
197
  .option('--json').option('--all-instances', '등록 인스턴스 전체 종합(미션/태스크/loops/스케줄/오케스트레이션 · fleet · read-only · §10)')
178
198
  .option('--include-test', '연합에 격리 test 인스턴스도 포함(기본 제외)')
@@ -2,6 +2,11 @@
2
2
  // 산출 경로를 한 줄로 알리고 명령의 종료 코드로 끝난다. `--deadline` 생략 시 기존 Pod Job 상한.
3
3
 
4
4
  import type { Command } from 'commander';
5
+ import { debug } from '../debug/log.js';
6
+ import { getUserConfig } from '../user-config.js';
7
+ import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
8
+ import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
9
+ import { leaseKubectl, measurePoolLease, recommendConcurrency, type PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
5
10
  import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
6
11
 
7
12
  export interface PodCliIo {
@@ -13,6 +18,10 @@ export interface PodCliIo {
13
18
  export interface PodCliDeps {
14
19
  io?: PodCliIo;
15
20
  run?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
21
+ kubectl?: PoolKubectl;
22
+ accounts?: () => number;
23
+ perAccount?: () => number;
24
+ poolSpec?: (explicit?: string) => string | null;
16
25
  }
17
26
 
18
27
  const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
@@ -27,6 +36,46 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
27
36
  };
28
37
  const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
29
38
  const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
39
+ pod.command('lease').description('Pod 풀의 읽기 전용 동시 실행 권장')
40
+ .command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
41
+ .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')
42
+ .option('--json', '표 대신 동일 측정값을 JSON 으로')
43
+ .action((opts: { pool?: string; json?: boolean }) => {
44
+ try {
45
+ const kubectl = deps.kubectl ?? leaseKubectl;
46
+ const spec = (deps.poolSpec ?? ((explicit?: string) => resolvePodPoolSpec(explicit)))(opts.pool);
47
+ // No pool configured: mirror the Pod launch fallback to kubectl's current context.
48
+ const current = spec ? null : kubectl(['config', 'current-context']);
49
+ const context = spec ?? (current?.status === 0 ? current.stdout.trim() : '');
50
+ const members = context ? parsePodPool(context) : [];
51
+ const measure: PoolLeaseMeasure = members.length ? measurePoolLease(members, { kubectl }) : { members: [] };
52
+ let accounts: number | null = null;
53
+ let accountReason: string | null = null;
54
+ try { accounts = (deps.accounts ?? (() => listCodexAccountsInStore().length))(); }
55
+ catch { accountReason = '측정 불가: accounts'; }
56
+ const perAccount = (deps.perAccount ?? (() => getUserConfig().pod?.lease?.perAccount ?? 4))();
57
+ const decision = recommendConcurrency(measure, { capacity: members.reduce((sum, m) => sum + m.capacity, 0), accounts: accounts ?? -1, perAccount });
58
+ if (!context) { decision.recommended = null; decision.limitedBy = null; decision.reason = '측정 불가: cluster (current context)'; }
59
+ const result = { pool: spec, ...measure, accounts, perAccount, ...decision };
60
+ debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, recommended: decision.recommended, limitedBy: decision.limitedBy });
61
+ if (opts.json) io.log(JSON.stringify(result));
62
+ else {
63
+ io.log('컨텍스트 | 상한 | Running | Pending | 메모리(상한 합/할당 가능) | CPU(할당 가능)');
64
+ for (const m of measure.members) {
65
+ const mem = (v: number | null) => v === null ? '?' : `${(v / 1024 ** 3).toFixed(1)}Gi`;
66
+ io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
67
+ }
68
+ io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
69
+ io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'})`);
70
+ io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);
71
+ io.log(`accounts (관측만 · 상한 아님): ${decision.accountSlots ?? '?'} 칸 (${accounts ?? '?'} × ${perAccount} − Running ${decision.running ?? '?'})${accountReason ? ` · ${accountReason}` : ''}`);
72
+ }
73
+ io.exit(0);
74
+ } catch (err) {
75
+ io.error(err instanceof Error ? err.message : String(err));
76
+ io.exit(1);
77
+ }
78
+ });
30
79
  pod.command('run')
31
80
  .description(HELP)
32
81
  .option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')