@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.90

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 (409) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +3 -3
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +13 -13
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +333 -7
  94. package/dist/core/edits/format-detector.js +260 -0
  95. package/dist/core/edits/format-matrix.js +26 -0
  96. package/dist/core/edits/fuzzy-ladder.js +650 -0
  97. package/dist/core/edits/index.js +5 -1
  98. package/dist/core/edits/journal.js +199 -0
  99. package/dist/core/edits/layer-a-apply.js +15 -15
  100. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  101. package/dist/core/edits/layer-b-apply.js +9 -9
  102. package/dist/core/edits/layer-c-apply.js +6 -6
  103. package/dist/core/edits/layer-d-ast.js +557 -14
  104. package/dist/core/edits/marker-parser.js +12 -12
  105. package/dist/core/edits/security-gate.js +27 -27
  106. package/dist/core/edits/verify-hook.js +273 -0
  107. package/dist/core/edits/worktree.js +29 -29
  108. package/dist/core/engine/anvil-client.js +214 -26
  109. package/dist/core/engine/auto-compact.js +179 -0
  110. package/dist/core/engine/budgets.js +186 -0
  111. package/dist/core/engine/context-prefix.js +155 -0
  112. package/dist/core/engine/index.js +1 -1
  113. package/dist/core/engine/intensity.js +158 -0
  114. package/dist/core/engine/intent.js +260 -0
  115. package/dist/core/engine/native-pugi.js +1295 -227
  116. package/dist/core/engine/prompts.js +129 -19
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1731 -59
  119. package/dist/core/evaluation/golden-dataset.js +293 -0
  120. package/dist/core/feedback/queue.js +177 -0
  121. package/dist/core/feedback/submitter.js +145 -0
  122. package/dist/core/file-cache.js +113 -1
  123. package/dist/core/flatten/flatten-repo.js +439 -0
  124. package/dist/core/format/osc8-link.js +28 -0
  125. package/dist/core/hook-chains.js +392 -0
  126. package/dist/core/hooks/citation-verify-hook.js +138 -0
  127. package/dist/core/hooks/citation-verify.js +112 -0
  128. package/dist/core/hooks/events.js +46 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +216 -0
  131. package/dist/core/hooks/runner.js +236 -0
  132. package/dist/core/hooks/v2/event-emitter.js +115 -0
  133. package/dist/core/hooks/v2/executor.js +282 -0
  134. package/dist/core/hooks/v2/index.js +25 -0
  135. package/dist/core/hooks/v2/lifecycle.js +104 -0
  136. package/dist/core/hooks/v2/loader.js +216 -0
  137. package/dist/core/hooks/v2/matcher.js +125 -0
  138. package/dist/core/hooks/v2/trust.js +143 -0
  139. package/dist/core/hooks/v2/types.js +86 -0
  140. package/dist/core/hooks/worktree-events.js +158 -0
  141. package/dist/core/image/renderer.js +71 -0
  142. package/dist/core/init/detector.js +582 -0
  143. package/dist/core/init/template-renderer.js +242 -0
  144. package/dist/core/jobs/registry.js +18 -18
  145. package/dist/core/ledger/results-tsv.js +142 -0
  146. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  147. package/dist/core/lsp/cache.js +105 -0
  148. package/dist/core/lsp/client.js +551 -41
  149. package/dist/core/lsp/language-detect.js +66 -0
  150. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  151. package/dist/core/lsp/server-detect.js +173 -0
  152. package/dist/core/lsp/symbol-cache.js +162 -0
  153. package/dist/core/lsp/symbol-tools.js +664 -0
  154. package/dist/core/mcp/client.js +97 -28
  155. package/dist/core/mcp/http-server.js +553 -0
  156. package/dist/core/mcp/orchestrator-tools.js +662 -0
  157. package/dist/core/mcp/permission.js +190 -0
  158. package/dist/core/mcp/registry.js +39 -17
  159. package/dist/core/mcp/server-tools.js +219 -0
  160. package/dist/core/mcp/server.js +397 -0
  161. package/dist/core/mcp/trust.js +10 -10
  162. package/dist/core/memory/dual-write.js +416 -0
  163. package/dist/core/memory/passive-extract.js +130 -0
  164. package/dist/core/memory/phase1-kinds.js +20 -0
  165. package/dist/core/memory/secret-scanner.js +304 -0
  166. package/dist/core/memory-sync/queue.js +170 -0
  167. package/dist/core/metrics/extract.js +113 -0
  168. package/dist/core/modes/roo-modes.js +68 -0
  169. package/dist/core/onboarding/ensure-initialized.js +133 -0
  170. package/dist/core/onboarding/marker.js +111 -0
  171. package/dist/core/onboarding/telemetry-state.js +108 -0
  172. package/dist/core/output-style/presets.js +176 -0
  173. package/dist/core/output-style/state.js +185 -0
  174. package/dist/core/path-security.js +287 -5
  175. package/dist/core/permission.js +82 -22
  176. package/dist/core/permissions/auto-classifier.js +124 -0
  177. package/dist/core/permissions/bash-parser.js +371 -0
  178. package/dist/core/permissions/circuit-breaker.js +83 -0
  179. package/dist/core/permissions/constrained-edit.js +91 -0
  180. package/dist/core/permissions/gate.js +278 -0
  181. package/dist/core/permissions/index.js +20 -0
  182. package/dist/core/permissions/mode.js +174 -0
  183. package/dist/core/permissions/network-egress.js +137 -0
  184. package/dist/core/permissions/state.js +241 -0
  185. package/dist/core/permissions/tool-class.js +93 -0
  186. package/dist/core/plan-mode/ui-state.js +51 -0
  187. package/dist/core/plans/plan-artifact.js +721 -0
  188. package/dist/core/policy-limits/etag-store.js +122 -0
  189. package/dist/core/prd-check/parser.js +215 -0
  190. package/dist/core/prd-check/reporter.js +127 -0
  191. package/dist/core/prd-check/session-review.js +557 -0
  192. package/dist/core/prd-check/verifiers.js +223 -0
  193. package/dist/core/prompt-cache/client-cache.js +99 -0
  194. package/dist/core/prompts/assembly.js +29 -0
  195. package/dist/core/prompts/registry.js +364 -0
  196. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  197. package/dist/core/pugi-md/context-injector.js +76 -0
  198. package/dist/core/pugi-md/walk-up.js +207 -0
  199. package/dist/core/python/uv-installer.js +270 -0
  200. package/dist/core/python/uv-resolver.js +83 -0
  201. package/dist/core/rate-limit/narrator.js +146 -0
  202. package/dist/core/recipes/cli-types.js +20 -0
  203. package/dist/core/recipes/loader.js +103 -0
  204. package/dist/core/recipes/runner.js +345 -0
  205. package/dist/core/recipes/schema.js +587 -0
  206. package/dist/core/release-notes/parser.js +241 -0
  207. package/dist/core/release-notes/state.js +116 -0
  208. package/dist/core/repl/ask.js +37 -37
  209. package/dist/core/repl/cancellation.js +26 -26
  210. package/dist/core/repl/cap-warning.js +4 -4
  211. package/dist/core/repl/clipboard-read.js +11 -11
  212. package/dist/core/repl/dispatch-fsm.js +12 -12
  213. package/dist/core/repl/history-search.js +15 -15
  214. package/dist/core/repl/history.js +28 -18
  215. package/dist/core/repl/kill-ring.js +5 -5
  216. package/dist/core/repl/model-pricing.js +135 -0
  217. package/dist/core/repl/privacy-banner.js +22 -22
  218. package/dist/core/repl/session.js +2148 -217
  219. package/dist/core/repl/slash-commands.js +501 -41
  220. package/dist/core/repl/store/index.js +1 -1
  221. package/dist/core/repl/store/jsonl-log.js +22 -22
  222. package/dist/core/repl/store/lockfile.js +10 -10
  223. package/dist/core/repl/store/session-store.js +136 -107
  224. package/dist/core/repl/store/types.js +15 -15
  225. package/dist/core/repl/store/uuid-v7.js +12 -12
  226. package/dist/core/repl/workspace-context.js +43 -21
  227. package/dist/core/repo-map/build.js +125 -0
  228. package/dist/core/repo-map/cache.js +185 -0
  229. package/dist/core/repo-map/extractor.js +254 -0
  230. package/dist/core/repo-map/formatter.js +145 -0
  231. package/dist/core/repo-map/page-rank.js +105 -0
  232. package/dist/core/repo-map/scanner.js +211 -0
  233. package/dist/core/retry-budget/budget.js +284 -0
  234. package/dist/core/retry-budget/index.js +5 -0
  235. package/dist/core/retry-budget/retry-cap.js +74 -0
  236. package/dist/core/routing/lead-worker.js +43 -0
  237. package/dist/core/routing/pre-flight-estimator.js +108 -0
  238. package/dist/core/runs/run-tree.js +103 -0
  239. package/dist/core/security/injection-scanner.js +367 -0
  240. package/dist/core/security/output-filter.js +418 -0
  241. package/dist/core/session/env-file.js +105 -0
  242. package/dist/core/session/section-budgets.js +140 -0
  243. package/dist/core/session.js +92 -0
  244. package/dist/core/settings.js +324 -5
  245. package/dist/core/share/formatter.js +271 -0
  246. package/dist/core/share/redactor.js +221 -0
  247. package/dist/core/share/uploader.js +267 -0
  248. package/dist/core/skills/defaults.js +30 -30
  249. package/dist/core/skills/loader.js +22 -22
  250. package/dist/core/skills/sources.js +27 -27
  251. package/dist/core/smoke/headless-driver.js +174 -0
  252. package/dist/core/smoke/orchestrator.js +194 -0
  253. package/dist/core/smoke/runner.js +238 -0
  254. package/dist/core/smoke/scenario-parser.js +316 -0
  255. package/dist/core/statusline.js +99 -0
  256. package/dist/core/subagents/dispatcher-real.js +600 -0
  257. package/dist/core/subagents/dispatcher.js +132 -43
  258. package/dist/core/subagents/index.js +19 -6
  259. package/dist/core/subagents/isolation-matrix.js +213 -0
  260. package/dist/core/subagents/spawn.js +19 -4
  261. package/dist/core/telemetry/emitter.js +229 -0
  262. package/dist/core/telemetry/queue.js +251 -0
  263. package/dist/core/theme/context.js +91 -0
  264. package/dist/core/theme/presets.js +228 -0
  265. package/dist/core/theme/state.js +181 -0
  266. package/dist/core/todos/invariant.js +10 -0
  267. package/dist/core/todos/state.js +177 -0
  268. package/dist/core/tool-schema/compressor.js +89 -0
  269. package/dist/core/transport/version-interceptor.js +166 -0
  270. package/dist/core/trust.js +2 -2
  271. package/dist/core/tui/thinking-block.js +64 -0
  272. package/dist/core/vim/keymap.js +288 -0
  273. package/dist/core/vim/state.js +92 -0
  274. package/dist/core/watch-markers/marker-watcher.js +133 -0
  275. package/dist/core/worktree/include-parser.js +249 -0
  276. package/dist/core/worktree-manager/cleanup.js +123 -0
  277. package/dist/core/worktree-manager/manager.js +303 -0
  278. package/dist/index.js +36 -0
  279. package/dist/runtime/bootstrap.js +190 -0
  280. package/dist/runtime/cli.js +4185 -549
  281. package/dist/runtime/commands/agents.js +31 -31
  282. package/dist/runtime/commands/budget.js +5 -5
  283. package/dist/runtime/commands/cancel.js +231 -0
  284. package/dist/runtime/commands/chain.js +489 -0
  285. package/dist/runtime/commands/codegraph-status.js +227 -0
  286. package/dist/runtime/commands/compact.js +297 -0
  287. package/dist/runtime/commands/config.js +73 -39
  288. package/dist/runtime/commands/cost.js +199 -0
  289. package/dist/runtime/commands/delegate.js +27 -4
  290. package/dist/runtime/commands/dispatch.js +126 -0
  291. package/dist/runtime/commands/doctor.js +579 -0
  292. package/dist/runtime/commands/feedback.js +184 -0
  293. package/dist/runtime/commands/hooks.js +187 -0
  294. package/dist/runtime/commands/init.js +254 -0
  295. package/dist/runtime/commands/lsp.js +200 -38
  296. package/dist/runtime/commands/mcp.js +879 -0
  297. package/dist/runtime/commands/memory.js +582 -0
  298. package/dist/runtime/commands/model.js +237 -0
  299. package/dist/runtime/commands/onboarding.js +275 -0
  300. package/dist/runtime/commands/patch.js +12 -12
  301. package/dist/runtime/commands/permissions.js +112 -0
  302. package/dist/runtime/commands/plan.js +143 -0
  303. package/dist/runtime/commands/prd-check.js +285 -0
  304. package/dist/runtime/commands/privacy.js +17 -17
  305. package/dist/runtime/commands/recipe.js +325 -0
  306. package/dist/runtime/commands/redo-blob-store.js +92 -0
  307. package/dist/runtime/commands/redo.js +361 -0
  308. package/dist/runtime/commands/release-notes.js +229 -0
  309. package/dist/runtime/commands/repo-map.js +95 -0
  310. package/dist/runtime/commands/report.js +299 -0
  311. package/dist/runtime/commands/resume.js +118 -0
  312. package/dist/runtime/commands/review-consensus.js +68 -53
  313. package/dist/runtime/commands/rewind.js +333 -0
  314. package/dist/runtime/commands/roster.js +14 -14
  315. package/dist/runtime/commands/sessions.js +163 -0
  316. package/dist/runtime/commands/share.js +316 -0
  317. package/dist/runtime/commands/skills.js +31 -31
  318. package/dist/runtime/commands/status.js +186 -0
  319. package/dist/runtime/commands/stickers.js +82 -0
  320. package/dist/runtime/commands/style.js +194 -0
  321. package/dist/runtime/commands/theme.js +196 -0
  322. package/dist/runtime/commands/undo.js +54 -22
  323. package/dist/runtime/commands/update.js +289 -0
  324. package/dist/runtime/commands/vim.js +140 -0
  325. package/dist/runtime/commands/worktree.js +8 -8
  326. package/dist/runtime/commands/worktrees.js +155 -0
  327. package/dist/runtime/headless-repl.js +195 -0
  328. package/dist/runtime/headless.js +543 -0
  329. package/dist/runtime/load-hooks-or-exit.js +71 -0
  330. package/dist/runtime/plan-decompose.js +22 -22
  331. package/dist/runtime/sigint-guard.js +272 -0
  332. package/dist/runtime/update-check.js +28 -28
  333. package/dist/runtime/version.js +65 -0
  334. package/dist/runtime/worktree-bootstrap.js +579 -0
  335. package/dist/skills/bundled/batch.js +617 -0
  336. package/dist/skills/bundled/index.js +45 -0
  337. package/dist/skills/bundled/loop.js +358 -0
  338. package/dist/skills/bundled/remember.js +383 -0
  339. package/dist/skills/bundled/simplify.js +289 -0
  340. package/dist/skills/bundled/skillify.js +373 -0
  341. package/dist/skills/bundled/stuck.js +558 -0
  342. package/dist/skills/bundled/verify.js +439 -0
  343. package/dist/testing/vcr.js +486 -0
  344. package/dist/tools/agent-tool.js +229 -0
  345. package/dist/tools/apply-patch.js +89 -28
  346. package/dist/tools/ask-user-question.js +337 -0
  347. package/dist/tools/ask-user.js +115 -0
  348. package/dist/tools/bash.js +624 -46
  349. package/dist/tools/brief.js +224 -0
  350. package/dist/tools/enter-worktree.js +250 -0
  351. package/dist/tools/exit-worktree.js +147 -0
  352. package/dist/tools/file-tools.js +161 -44
  353. package/dist/tools/lsp-tools.js +377 -1
  354. package/dist/tools/mcp-tool.js +260 -0
  355. package/dist/tools/multi-edit.js +361 -0
  356. package/dist/tools/powershell.js +268 -0
  357. package/dist/tools/registry.js +86 -4
  358. package/dist/tools/skill-tool.js +96 -0
  359. package/dist/tools/sleep.js +99 -0
  360. package/dist/tools/synthetic-output.js +133 -0
  361. package/dist/tools/tasks.js +208 -0
  362. package/dist/tools/todo-write.js +184 -0
  363. package/dist/tools/verify-plan-execution.js +295 -0
  364. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  365. package/dist/tools/web-fetch.js +195 -10
  366. package/dist/tools/web-search.js +458 -0
  367. package/dist/tui/agent-progress-card.js +111 -0
  368. package/dist/tui/agent-tree.js +11 -1
  369. package/dist/tui/ask-modal.js +14 -14
  370. package/dist/tui/ask-user-question-chips.js +315 -0
  371. package/dist/tui/ask-user-question-prompt.js +203 -0
  372. package/dist/tui/compact-banner.js +81 -0
  373. package/dist/tui/conversation-pane.js +85 -11
  374. package/dist/tui/cost-table.js +111 -0
  375. package/dist/tui/device-flow.js +2 -2
  376. package/dist/tui/doctor-table.js +46 -0
  377. package/dist/tui/feedback-prompt.js +156 -0
  378. package/dist/tui/input-box.js +247 -32
  379. package/dist/tui/login-picker.js +3 -3
  380. package/dist/tui/markdown-render.js +6 -6
  381. package/dist/tui/onboarding-wizard.js +240 -0
  382. package/dist/tui/permissions-picker.js +86 -0
  383. package/dist/tui/render.js +36 -1
  384. package/dist/tui/repl-render.js +176 -25
  385. package/dist/tui/repl-splash-art.js +16 -16
  386. package/dist/tui/repl-splash-mascot.js +48 -24
  387. package/dist/tui/repl-splash.js +22 -22
  388. package/dist/tui/repl.js +125 -45
  389. package/dist/tui/slash-palette.js +6 -6
  390. package/dist/tui/splash.js +2 -2
  391. package/dist/tui/status-bar.js +109 -31
  392. package/dist/tui/status-table.js +7 -0
  393. package/dist/tui/stickers-art.js +136 -0
  394. package/dist/tui/style-table.js +28 -0
  395. package/dist/tui/theme-table.js +29 -0
  396. package/dist/tui/thinking-spinner.js +123 -0
  397. package/dist/tui/tool-stream-pane.js +53 -4
  398. package/dist/tui/update-banner.js +27 -2
  399. package/dist/tui/vim-input.js +267 -0
  400. package/dist/tui/welcome-banner.js +107 -0
  401. package/dist/tui/welcome-data.js +293 -0
  402. package/dist/tui/workspace-context.js +2 -2
  403. package/package.json +31 -16
  404. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  405. package/test/scenarios/compact-force.scenario.txt +12 -0
  406. package/test/scenarios/identity.scenario.txt +12 -0
  407. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  408. package/test/scenarios/walkback.scenario.txt +12 -0
  409. package/dist/core/engine/compaction-hook.js +0 -154
package/dist/tui/repl.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * REPL root component - Sprint α5.7 (ADR-0056 PR-PUGI-CLI-REPL-DEFAULT).
3
+ * REPL root component - Sprint .
4
4
  *
5
5
  * Three-zone layout:
6
6
  *
7
- * header - `Pugi.io · workspace: <name> · v<X> on watch`
8
- * main - conversation pane (top half) + agent tree (bottom half)
9
- * footer - input box + status bar + key hints
7
+ * header - `Pugi.io · workspace: <name> · v<X> on watch`
8
+ * main - conversation pane (top half) + agent tree (bottom half)
9
+ * footer - input box + status bar + key hints
10
10
  *
11
11
  * The component subscribes to a ReplSession instance for state. It does
12
12
  * NOT own the SSE client or the transport - the session module does.
@@ -26,14 +26,17 @@ import { ConversationPane } from './conversation-pane.js';
26
26
  import { InputBox } from './input-box.js';
27
27
  import { ReplSplash } from './repl-splash.js';
28
28
  import { StatusBar } from './status-bar.js';
29
+ import { ThinkingSpinner } from './thinking-spinner.js';
29
30
  import { ToolStreamPane } from './tool-stream-pane.js';
30
31
  import { UpdateBanner } from './update-banner.js';
32
+ import { WelcomeBanner } from './welcome-banner.js';
31
33
  import { collectWorkspaceContext } from './workspace-context.js';
34
+ import { useTheme } from '../core/theme/context.js';
32
35
  import { slugForCwd } from '../core/repl/history.js';
33
36
  import { SLASH_COMMAND_HELP, SLASH_COMMAND_GROUPS } from '../core/repl/slash-commands.js';
34
37
  const TICK_INTERVAL_MS = 200;
35
38
  const PULSE_INTERVAL_MS = 700;
36
- // α6.12: maximum transcript rows the conversation pane renders at once.
39
+ // : maximum transcript rows the conversation pane renders at once.
37
40
  // Older rows scroll off the top; full history stays in session state.
38
41
  const CONVERSATION_WINDOW = 12;
39
42
  export function Repl(props) {
@@ -41,22 +44,28 @@ export function Repl(props) {
41
44
  const [overlay, setOverlay] = useState('none');
42
45
  const [pulsePhase, setPulsePhase] = useState(0);
43
46
  const [tickNow, setTickNow] = useState((props.now ?? Date.now)());
44
- // α6.12: operator-driven collapse for the tool stream pane. The CLI
47
+ // : operator-driven collapse for the tool stream pane. The CLI
45
48
  // host can hide the pane entirely via `--no-tool-stream`; this state
46
49
  // is the runtime toggle (Ctrl+T) for operators who want the pane on
47
50
  // screen but folded to a single row while they read a long reply.
48
51
  const [toolStreamCollapsed, setToolStreamCollapsed] = useState(false);
49
- // α6.14 wave 3: boot splash visible until first input, first
52
+ // wave 3: boot splash visible until first input, first
50
53
  // `agent.spawned` event, or 10s idle. The host gates the initial
51
54
  // visibility on `--no-splash` / PUGI_SKIP_SPLASH via `skipSplash`.
52
- // α6.14.6 CEO dogfood 2026-05-25: default splash to HIDDEN at boot
53
- // (parity with Claude Code's minimal one-line banner). Operator can
55
+ // CEO dogfood: default splash to HIDDEN at boot
56
+ // (parity with the upstream tool's minimal one-line banner). Operator can
54
57
  // opt back in via `/splash` slash. The chafa pug pre-print + header
55
58
  // line already give the brand cue without the multi-row Plan/Model/
56
59
  // Tenant block crowding the top.
57
60
  const [splashVisible, setSplashVisible] = useState(false);
58
61
  const dismissSplash = useCallback(() => setSplashVisible(false), []);
59
- // α6.14 wave 3: workspace context snapshot for the status bar. We
62
+ // CEO P0 #2 : welcome banner. Visible from boot
63
+ // until the operator submits the first brief OR the session emits
64
+ // its first agent event. The host owns dismissal lifecycle (kept
65
+ // symmetric with the splash) so the welcome card never lingers
66
+ // behind а live transcript.
67
+ const [welcomeVisible, setWelcomeVisible] = useState(Boolean(props.welcomeData));
68
+ // wave 3: workspace context snapshot for the status bar. We
60
69
  // read once at mount and freeze; a brand-new PUGI.md or skill is
61
70
  // surfaced on the next REPL boot rather than via a watcher.
62
71
  const workspaceContext = useMemo(() => props.workspaceContext ?? collectWorkspaceContext(process.cwd()), [props.workspaceContext]);
@@ -88,9 +97,9 @@ export function Repl(props) {
88
97
  useEffect(() => {
89
98
  props.onOverlayChange?.(overlay);
90
99
  }, [overlay, props]);
91
- // α6.14 wave 3: dismiss the boot splash once the first agent spawns
100
+ // wave 3: dismiss the boot splash once the first agent spawns
92
101
  // (the operator has clearly engaged the system) or the transcript
93
- // gains a row. Mirrors the natural attention shift Claude Code /
102
+ // gains a row. Mirrors the natural attention shift the upstream tool /
94
103
  // Codex / Gemini CLI all do on their boot screens.
95
104
  useEffect(() => {
96
105
  if (!splashVisible)
@@ -99,6 +108,20 @@ export function Repl(props) {
99
108
  setSplashVisible(false);
100
109
  }
101
110
  }, [splashVisible, state.agents.length, state.transcript.length]);
111
+ // CEO P0 #2 v2: welcome banner stays until the operator
112
+ // actively engages the loop — first agent spawn. Boot-time auto-init
113
+ // emits system rows into `state.transcript` (skip-trust hints, dirty
114
+ // tree warnings) which used к dismiss the banner within ~2s, hiding
115
+ // the brand mascot before the operator could read it. Drop the
116
+ // `transcript.length` trigger; agent spawn (= real dispatch) remains
117
+ // the sole signal that the operator stopped reading the banner.
118
+ useEffect(() => {
119
+ if (!welcomeVisible)
120
+ return;
121
+ if (state.agents.length > 0) {
122
+ setWelcomeVisible(false);
123
+ }
124
+ }, [welcomeVisible, state.agents.length]);
102
125
  const personaNames = useMemo(() => buildPersonaNameMap(), []);
103
126
  const { exit } = useApp();
104
127
  const handleSubmit = useCallback((line) => {
@@ -106,6 +129,10 @@ export function Repl(props) {
106
129
  // `setSplashVisible(false)` is a no-op once the state already
107
130
  // settled to false (timer fired or `agent.spawned` arrived).
108
131
  setSplashVisible(false);
132
+ // CEO P0 #2 : same dismissal for the welcome banner
133
+ // — the operator engaging the input box is the cleanest signal
134
+ // they have finished reading the boot card.
135
+ setWelcomeVisible(false);
109
136
  // Run async without awaiting - the session module owns the
110
137
  // network call, errors land in the transcript automatically.
111
138
  void props.session.handleInput(line).then((verdict) => {
@@ -137,7 +164,7 @@ export function Repl(props) {
137
164
  setOverlay('none');
138
165
  }
139
166
  }, { isActive: overlay === 'help' || overlay === 'roster' });
140
- // α6.12: Ctrl+T toggles the tool stream pane between expanded and
167
+ // : Ctrl+T toggles the tool stream pane between expanded and
141
168
  // collapsed states. Active only while no overlay is open, so the
142
169
  // toggle never fights the help/roster dismiss handler. The input box
143
170
  // owns its own raw-input mode, so this listener only fires on the
@@ -147,7 +174,7 @@ export function Repl(props) {
147
174
  setToolStreamCollapsed((prev) => !prev);
148
175
  }
149
176
  }, { isActive: overlay === 'none' && props.hideToolStream !== true });
150
- // α6.3 office-hours: a pending ask or plan-review modal pauses input
177
+ // office-hours: a pending ask or plan-review modal pauses input
151
178
  // until the operator resolves it. The modal owns its own useInput
152
179
  // hook, so the InputBox unmounts while a modal is open to avoid two
153
180
  // raw-input listeners competing for the same keystroke. Resolution
@@ -161,29 +188,69 @@ export function Repl(props) {
161
188
  const handlePlanReviewResolve = useCallback((result) => {
162
189
  void props.session.resolvePlanReview(result);
163
190
  }, [props.session]);
164
- // α6.9: Ctrl+C abort handler. Forwards to ReplSession.cancel() which
191
+ // : Ctrl+C abort handler. Forwards to ReplSession.cancel() which
165
192
  // aborts the in-flight dispatch, closes the SSE stream, and surfaces
166
193
  // "Aborted." in the transcript.
167
194
  //
168
195
  // Return contract (consumed by InputBox):
169
- // - true - dispatch was cancelled (keep the buffer + DO arm
170
- // the press-again-to-exit timer; second Ctrl+C in
171
- // the window exits).
172
- // - false - idle / nothing to cancel (legacy: clear buffer +
173
- // arm the exit timer so the operator sees the hint
174
- // and can confirm exit on the next press).
175
- // - undefined - bypassed entirely (e.g. a modal owns the input).
176
- // InputBox MUST NOT arm the exit timer and MUST
177
- // NOT clear the buffer. P2 fix: previously this
178
- // returned `false` and the buffer-clear path wiped
179
- // the operator's mid-typed modal text on the first
180
- // Ctrl+C, costing a press of work.
196
+ // - true - dispatch was cancelled (keep the buffer + DO arm
197
+ // the press-again-to-exit timer; second Ctrl+C in
198
+ // the window exits).
199
+ // - false - idle / nothing to cancel (legacy: clear buffer +
200
+ // arm the exit timer so the operator sees the hint
201
+ // and can confirm exit on the next press).
202
+ // - undefined - bypassed entirely (e.g. a modal owns the input).
203
+ // InputBox MUST NOT arm the exit timer and MUST
204
+ // NOT clear the buffer. P2 fix: previously this
205
+ // returned `false` and the buffer-clear path wiped
206
+ // the operator's mid-typed modal text on the first
207
+ // Ctrl+C, costing a press of work.
181
208
  const handleCancel = useCallback(() => {
182
209
  if (modalActive)
183
210
  return undefined;
184
211
  return props.session.cancel();
185
212
  }, [props.session, modalActive]);
186
- // α6.14.5 CEO dogfood 2026-05-25 (parity with Claude Code): input
213
+ // BT 8 (the upstream tool parity): Esc-Esc walkback. Forwards to
214
+ // ReplSession.walkbackLastTurn which trims the trailing operator
215
+ // turn + its persona response from the in-memory transcript. Returns
216
+ // `'walked-back'` so the input box knows the host did the work;
217
+ // `'nothing'` covers both the empty-transcript and dispatch-in-flight
218
+ // refusals (the session module owns the refusal copy in both cases).
219
+ const handleWalkback = useCallback(() => {
220
+ if (modalActive)
221
+ return 'nothing';
222
+ const verdict = props.session.walkbackLastTurn();
223
+ return verdict === 'walked-back' ? 'walked-back' : 'nothing';
224
+ }, [props.session, modalActive]);
225
+ // — Shift+Tab cycles the 6 canonical permission modes (CC
226
+ // parity). Refuses while a modal is active so the operator does not
227
+ // accidentally flip mode mid-prompt; otherwise resolves the current
228
+ // mode through the workspace > global > default merge, advances via
229
+ // `nextPermissionMode`, и persists к .pugi/session.json. Returns the
230
+ // new mode string so the InputBox can flash a one-line toast.
231
+ const handleCyclePermissionMode = useCallback(() => {
232
+ if (modalActive)
233
+ return null;
234
+ try {
235
+ // Lazy-require так this code path doesn't drag the permissions
236
+ // module into the splash + boot stages where it isn't needed.
237
+ // The require is sync but the inner work is pure JSON IO.
238
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
239
+ const perm = require('../core/permissions/index.js');
240
+ const workspaceRoot = process.cwd();
241
+ const current = perm.resolveMode({ workspaceRoot });
242
+ const next = perm.nextPermissionMode(current);
243
+ perm.setCurrentMode(workspaceRoot, next);
244
+ return next;
245
+ }
246
+ catch {
247
+ // Persistence is best-effort — if .pugi/session.json is read-only
248
+ // или ENOENT-on-parent the toast is suppressed so we don't lie
249
+ // about the flip к the operator.
250
+ return null;
251
+ }
252
+ }, [modalActive]);
253
+ // CEO dogfood (parity with the upstream tool): input
187
254
  // box pinned to alt-screen BOTTOM, conversation grows above it.
188
255
  // Beta.3's height={rows} fix broke keystroke focus - raw echo at
189
256
  // viewport bottom. The right pattern is minHeight on the root +
@@ -191,37 +258,48 @@ export function Repl(props) {
191
258
  // input, and the input stays the sole focusable surface adjacent
192
259
  // to the cursor row, so all keystrokes route through it.
193
260
  const altScreenRows = process.stdout.rows ?? 24;
194
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, minHeight: altScreenRows, children: [props.updateBanner ? _jsx(UpdateBanner, { result: props.updateBanner }) : null, splashVisible ? (_jsx(ReplSplash, { cliVersion: state.cliVersion, workspaceLabel: state.workspaceLabel, plan: props.splashPlan, model: props.splashModel, tenant: props.splashTenant, onDismiss: dismissSplash, mascotPrePrinted: props.mascotPrePrinted === true })) : null, _jsx(Header, { state: state }), _jsx(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, justifyContent: "flex-end", children: overlay === 'help' ? (_jsx(HelpOverlay, {})) : overlay === 'roster' ? (_jsx(RosterOverlay, {})) : overlay === 'farewell' ? (_jsx(FarewellOverlay, {})) : (_jsx(MainArea, { state: state, personaNames: personaNames, nowEpochMs: tickNow, hideToolStream: props.hideToolStream === true, toolStreamCollapsed: toolStreamCollapsed })) }), state.pendingAsk ? (_jsx(Box, { marginTop: 1, children: _jsx(AskModal, { tag: state.pendingAsk, onResolve: handleAskResolve }) })) : null, state.pendingPlanReview ? (_jsx(Box, { marginTop: 1, children: _jsx(PlanReviewModal, { tag: state.pendingPlanReview, onResolve: handlePlanReviewResolve }) })) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [overlay === 'farewell' || modalActive ? null : (_jsx(InputBox, { onSubmit: handleSubmit, onExit: handleExit, onCancel: handleCancel, now: props.now,
261
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, minHeight: altScreenRows, children: [welcomeVisible && props.welcomeData ? (_jsx(WelcomeBanner, { data: props.welcomeData, mascotPrePrinted: props.mascotPrePrinted === true, autoInitStatus: props.autoInitStatus ?? null })) : null, splashVisible ? (_jsx(ReplSplash, { cliVersion: state.cliVersion, workspaceLabel: state.workspaceLabel, plan: props.splashPlan, model: props.splashModel, tenant: props.splashTenant, onDismiss: dismissSplash, mascotPrePrinted: props.mascotPrePrinted === true })) : null, _jsx(Header, { state: state }), _jsx(Box, { flexDirection: "column", marginTop: 1, flexGrow: 1, justifyContent: "flex-end", children: overlay === 'help' ? (_jsx(HelpOverlay, {})) : overlay === 'roster' ? (_jsx(RosterOverlay, {})) : overlay === 'farewell' ? (_jsx(FarewellOverlay, {})) : (_jsx(MainArea, { state: state, personaNames: personaNames, nowEpochMs: tickNow, hideToolStream: props.hideToolStream === true, toolStreamCollapsed: toolStreamCollapsed })) }), state.pendingAsk ? (_jsx(Box, { marginTop: 1, children: _jsx(AskModal, { tag: state.pendingAsk, onResolve: handleAskResolve }) })) : null, state.pendingPlanReview ? (_jsx(Box, { marginTop: 1, children: _jsx(PlanReviewModal, { tag: state.pendingPlanReview, onResolve: handlePlanReviewResolve }) })) : null, _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [overlay === 'farewell' || modalActive ? null : (_jsx(InputBox, { onSubmit: handleSubmit, onExit: handleExit, onCancel: handleCancel, onWalkback: handleWalkback, onCyclePermissionMode: handleCyclePermissionMode, now: props.now,
195
262
  // Slug from process.cwd() (full path) so two workspaces with
196
263
  // the same basename do not share history. state.workspaceLabel
197
264
  // is the basename only. Codex review P2.
198
- workspaceSlug: slugForCwd(process.cwd()) })), _jsx(StatusBar, { connection: state.connection, activeAgentCount: countActive(state), tokensDownstreamTotal: state.tokensDownstreamTotal, briefStartedAtEpochMs: state.briefStartedAtEpochMs, nowEpochMs: tickNow, pulsePhase: pulsePhase, pugiMdCount: workspaceContext.pugiMdCount, mcpServerCount: workspaceContext.mcpServerCount, skillCount: workspaceContext.skillCount, quotaPct: props.quotaPct, dispatchState: state.dispatchState, dispatchToolLabel: state.dispatchToolLabel })] })] }));
265
+ workspaceSlug: slugForCwd(process.cwd()) })), _jsx(ThinkingSpinner, { dispatchState: state.dispatchState, dispatchToolLabel: state.dispatchToolLabel }), _jsx(StatusBar, { connection: state.connection, activeAgentCount: countActive(state), tokensDownstreamTotal: state.tokensDownstreamTotal, briefStartedAtEpochMs: state.briefStartedAtEpochMs, nowEpochMs: tickNow, pulsePhase: pulsePhase, pugiMdCount: workspaceContext.pugiMdCount, mcpServerCount: workspaceContext.mcpServerCount, skillCount: workspaceContext.skillCount, quotaPct: props.quotaPct, dispatchState: state.dispatchState, dispatchToolLabel: state.dispatchToolLabel, lastCompletedOutcome: state.lastCompletedOutcome,
266
+ // cost-meter sprint — surface accumulated session totals
267
+ // + per-turn delta flash on the status bar's top row. The
268
+ // session module owns accumulation; the bar is a pure render.
269
+ sessionTokensIn: state.sessionTokensIn, sessionTokensOut: state.sessionTokensOut, sessionCostUsd: state.sessionCostUsd, sessionStartedAtEpochMs: state.sessionStartedAtEpochMs, lastTurnDelta: state.lastTurnDelta }), props.updateBanner ? _jsx(UpdateBanner, { result: props.updateBanner }) : null] })] }));
199
270
  }
200
271
  function Header({ state }) {
201
- return (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Pugi" }), _jsx(Text, { bold: true, color: "cyan", children: ".io" }), _jsx(Text, { dimColor: true, children: ` · workspace: ${state.workspaceLabel} · v${state.cliVersion} · ` }), _jsx(Text, { color: "cyan", children: state.connection === 'on_watch' ? 'on watch' : state.connection.replace('_', ' ') })] }));
272
+ // the header `.io` brand accent + connection
273
+ // pill route through `useTheme()` so the operator's `/theme` flip
274
+ // (default / dark / light / colorblind) re-tints the chrome on
275
+ // re-mount. The `useTheme` hook returns the `default` preset's
276
+ // colors when no provider is mounted, preserving the previous
277
+ // `#3da9fc` constants for tests that import `<Repl />` standalone.
278
+ const theme = useTheme();
279
+ return (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Pugi" }), _jsx(Text, { bold: true, color: theme.accent, children: ".io" }), _jsx(Text, { dimColor: true, children: ` · workspace: ${state.workspaceLabel} · v${state.cliVersion} · ` }), _jsx(Text, { color: theme.accent, children: state.connection === 'on_watch' ? 'on watch' : state.connection.replace('_', ' ') })] }));
202
280
  }
203
281
  function MainArea({ state, personaNames, nowEpochMs, hideToolStream, toolStreamCollapsed, }) {
204
- // α6.12: three vertical panes stacked above the input box.
282
+ // : three vertical panes stacked above the input box.
205
283
  //
206
- // 1. Conversation pane (top) - transcript with Markdown render.
207
- // 2. Tool stream pane (mid) - live Read/Edit/Bash/Grep lines.
208
- // Hidden when `--no-tool-stream` is
209
- // set; collapsed via Ctrl+T while
210
- // the pane is visible.
211
- // 3. Agent tree pane (bottom) - Cyber-Zoo roster with persona /
212
- // status / duration / token counts.
284
+ // 1. Conversation pane (top) - transcript with Markdown render.
285
+ // 2. Tool stream pane (mid) - live Read/Edit/Bash/Grep lines.
286
+ // Hidden when `--no-tool-stream` is
287
+ // set; collapsed via Ctrl+T while
288
+ // the pane is visible.
289
+ // 3. Agent tree pane (bottom) - Cyber-Zoo roster with persona /
290
+ // status / duration / token counts.
213
291
  //
214
292
  // The window over the transcript is small (last 12 rows) so the
215
293
  // bottom of the frame stays anchored to the input box. New agents
216
- // push the operator line up the screen, mirroring Claude Code /
217
- // Codex CLI / Gemini CLI rendering.
294
+ // push the operator line up the screen, mirroring the upstream tool /
295
+ // peer CLI / Gemini CLI rendering.
218
296
  const conversationSlice = state.transcript.slice(-CONVERSATION_WINDOW);
219
297
  return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ConversationPane, { rows: conversationSlice, personaNames: personaNames }), hideToolStream ? null : (_jsx(Box, { marginTop: 1, children: _jsx(ToolStreamPane, { calls: state.toolCalls, collapsed: toolStreamCollapsed }) })), _jsx(Box, { marginTop: 1, children: _jsx(AgentTreePane, { agents: state.agents, nowEpochMs: nowEpochMs }) })] }));
220
298
  }
221
299
  function HelpOverlay() {
222
300
  // Group commands by their `group` field so the operator scans the
223
301
  // palette by intent (dispatch → session → tools → settings → meta).
224
- // The α6.14 wave-2 expansion grew the surface from 6 to 20 commands;
302
+ // The wave-2 expansion grew the surface from 6 to 20 commands;
225
303
  // a flat list would force the operator to read 20 rows top-to-bottom
226
304
  // every time. Grouping cuts perceived complexity dramatically.
227
305
  const grouped = new Map();
@@ -238,14 +316,14 @@ function HelpOverlay() {
238
316
  const rows = grouped.get(group);
239
317
  if (!rows || rows.length === 0)
240
318
  return null;
241
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: ` -- ${group} --` }), rows.map((row) => (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: ` /${row.name} ${row.args}`.padEnd(22, ' ') }), _jsx(Text, { dimColor: true, children: row.gloss })] }, row.name)))] }, group));
319
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: ` -- ${group} --` }), rows.map((row) => (_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "#3da9fc", children: ` /${row.name} ${row.args}`.padEnd(22, ' ') }), _jsx(Text, { dimColor: true, children: row.gloss })] }, row.name)))] }, group));
242
320
  }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: `${PUGI_TAGLINE}` }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: 'Press any key to dismiss.' }) })] }));
243
321
  }
244
322
  function RosterOverlay() {
245
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "On-watch roster" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: THE_TEN.map((persona) => (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: ` ${persona.name.padEnd(10, ' ')}` }), _jsx(Text, { dimColor: true, children: `${persona.role.padEnd(20, ' ')}` }), _jsx(Text, { children: persona.oneLiner })] }, persona.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: 'Press any key to dismiss.' }) })] }));
323
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "On-watch roster" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: THE_TEN.map((persona) => (_jsxs(Box, { children: [_jsx(Text, { bold: true, children: ` ${persona.name.padEnd(10, ' ')}` }), _jsx(Text, { dimColor: true, children: `${persona.role.padEnd(20, ' ')}` }), _jsx(Text, { children: persona.oneLiner })] }, persona.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: 'Press any key to dismiss.' }) })] }));
246
324
  }
247
325
  function FarewellOverlay() {
248
- return (_jsx(Box, { flexDirection: "column", alignItems: "center", paddingY: 2, children: _jsx(Text, { bold: true, color: "cyan", children: PUGI_TAGLINE }) }));
326
+ return (_jsx(Box, { flexDirection: "column", alignItems: "center", paddingY: 2, children: _jsx(Text, { bold: true, color: "#3da9fc", children: PUGI_TAGLINE }) }));
249
327
  }
250
328
  function applyVerdictSideEffects(verdict, handlers) {
251
329
  switch (verdict.kind) {
@@ -270,8 +348,10 @@ function applyVerdictSideEffects(verdict, handlers) {
270
348
  case 'consensus':
271
349
  case 'diff':
272
350
  case 'cost':
351
+ case 'quota':
273
352
  case 'status':
274
353
  case 'resume':
354
+ case 'mcp':
275
355
  case 'stub':
276
356
  // All non-overlay verdicts: the session module already appended
277
357
  // any operator-visible system lines (and, for `ask`, set
@@ -7,7 +7,7 @@ export const PALETTE_ROW_LIMIT = 8;
7
7
  * Centralises the "starts-with-slash → filter SLASH_COMMAND_HELP"
8
8
  * logic so the input box and the unit test agree on the shape.
9
9
  *
10
- * Wave 4 fix 2026-05-25: returns the FULL filtered set, not just the
10
+ * fix: returns the FULL filtered set, not just the
11
11
  * first PALETTE_ROW_LIMIT rows. The palette renderer now windows the
12
12
  * visible slice internally based on `focusedIndex`, so the operator
13
13
  * can scroll past row 7 via ↑/↓ on a long list (e.g. 20 commands when
@@ -15,9 +15,9 @@ export const PALETTE_ROW_LIMIT = 8;
15
15
  * shape for backward compatibility but always equals `rows.length`.
16
16
  *
17
17
  * Behaviour:
18
- * - Empty / non-slash buffer → empty result; palette stays hidden.
19
- * - `/` alone → all registry rows (the operator wants to browse).
20
- * - `/he` → rows whose name starts with `he` (case-insensitive).
18
+ * - Empty / non-slash buffer → empty result; palette stays hidden.
19
+ * - `/` alone → all registry rows (the operator wants to browse).
20
+ * - `/he` → rows whose name starts with `he` (case-insensitive).
21
21
  */
22
22
  export function filterPalette(buffer) {
23
23
  if (!buffer.startsWith('/')) {
@@ -86,7 +86,7 @@ export function completePalette(buffer, rows, focusedIndex) {
86
86
  export function SlashPalette(props) {
87
87
  if (props.rows.length === 0)
88
88
  return null;
89
- // Wave 4 fix 2026-05-25: compute the visible window so the operator
89
+ // fix: compute the visible window so the operator
90
90
  // can scroll past row 7 on long lists. Focus indexes the full rows
91
91
  // array; the window slides to keep the focused row visible.
92
92
  const window = computePaletteWindow(props.rows, props.focusedIndex);
@@ -101,6 +101,6 @@ export function SlashPalette(props) {
101
101
  const glyph = focused ? '▸' : '·';
102
102
  const cmd = `/${row.name}${row.args ? ` ${row.args}` : ''}`.padEnd(22, ' ');
103
103
  return (_jsxs(Box, { children: [_jsx(Text, { color: focused ? 'cyan' : 'gray', children: `${glyph} ` }), _jsx(Text, { bold: focused, color: focused ? 'cyan' : undefined, dimColor: !focused, children: cmd }), _jsx(Text, { dimColor: true, children: row.gloss })] }, row.name));
104
- }), overflow ? (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: ` → ${focusedDisplayIndex}/${window.total}` }) })) : null, _jsx(Box, { children: _jsx(Text, { dimColor: true, children: ' ↑/↓ select · Tab complete · Enter run · Esc close' }) })] }));
104
+ }), overflow ? (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: ` → ${focusedDisplayIndex}/${window.total}` }) })) : null, _jsx(Box, { children: _jsx(Text, { dimColor: true, children: ' ↑/↓ select · Tab complete · Enter run · Esc close' }) })] }));
105
105
  }
106
106
  //# sourceMappingURL=slash-palette.js.map
@@ -21,11 +21,11 @@ export function Splash({ data }) {
21
21
  cmd: 'pugi login',
22
22
  gloss: 'Connect this terminal to your Pugi account',
23
23
  };
24
- return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: "cyan", children: "pugi.io" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `v${data.cliVersion} · ${data.apiUrl}` }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Account: " }), _jsx(Text, { children: accountLine })] }), data.isAuthenticated && data.plan ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Plan: " }), _jsx(Text, { children: data.plan })] })) : null] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Quick start:" }), _jsx(HintRow, { command: primaryHint.cmd, gloss: primaryHint.gloss }), data.isAuthenticated ? (_jsx(HintRow, { command: 'pugi login', gloss: 'Re-authenticate or switch accounts' })) : (_jsx(HintRow, { command: 'pugi code "fix the bug"', gloss: 'Run a one-shot coding task' })), _jsx(HintRow, { command: 'pugi review --triple', gloss: 'Run the Anvil triple-review gate' }), _jsx(HintRow, { command: 'pugi help', gloss: 'Full command reference' })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Docs: https://pugi.dev \u00B7 Status: https://pugi.io/status" }) })] }));
24
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(Box, { children: _jsx(Text, { bold: true, color: "#3da9fc", children: "pugi.io" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `v${data.cliVersion} · ${data.apiUrl}` }) }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Account: " }), _jsx(Text, { children: accountLine })] }), data.isAuthenticated && data.plan ? (_jsxs(Text, { children: [_jsx(Text, { dimColor: true, children: "Plan: " }), _jsx(Text, { children: data.plan })] })) : null] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "Quick start:" }), _jsx(HintRow, { command: primaryHint.cmd, gloss: primaryHint.gloss }), data.isAuthenticated ? (_jsx(HintRow, { command: 'pugi login', gloss: 'Re-authenticate or switch accounts' })) : (_jsx(HintRow, { command: 'pugi code "fix the bug"', gloss: 'Run a one-shot coding task' })), _jsx(HintRow, { command: 'pugi review --triple', gloss: 'Run the Anvil triple-review gate' }), _jsx(HintRow, { command: 'pugi help', gloss: 'Full command reference' })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Docs: https://pugi.dev \u00B7 Status: https://pugi.io/status" }) })] }));
25
25
  }
26
26
  function HintRow({ command, gloss }) {
27
27
  // Pad command names so the gloss column lines up across rows.
28
28
  const padded = command.padEnd(28, ' ');
29
- return (_jsxs(Text, { children: [_jsx(Text, { children: ` ${padded}` }), _jsx(Text, { dimColor: true, children: gloss })] }));
29
+ return (_jsxs(Text, { children: [_jsx(Text, { children: ` ${padded}` }), _jsx(Text, { dimColor: true, children: gloss })] }));
30
30
  }
31
31
  //# sourceMappingURL=splash.js.map
@@ -1,5 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { formatCostUsd, formatTokens } from '../core/repl/model-pricing.js';
4
+ /**
5
+ * Window during which the per-turn delta flash stays visible on the
6
+ * cost-meter row. CEO spec: ~2 seconds after completion. Past that, the
7
+ * flash dimms out and the row shows session totals only.
8
+ */
9
+ const TURN_DELTA_FLASH_MS = 2_000;
3
10
  /**
4
11
  * Cyan dot glyphs across the pulse cycle. Three steps keep the motion
5
12
  * subtle - a true gradient would force an Ink rerender on every
@@ -12,13 +19,78 @@ export function StatusBar(props) {
12
19
  const tokenLabel = formatTokens(props.tokensDownstreamTotal);
13
20
  const phase = clampPhase(props.pulsePhase);
14
21
  const glyph = PULSE_DOTS[Math.min(phase, PULSE_DOTS.length - 1)] ?? PULSE_DOTS[0];
15
- // α6.9: composite status label — connection problems trump dispatch
22
+ // : composite status label — connection problems trump dispatch
16
23
  // state because the operator needs to know about a dropped admin-api
17
24
  // first. When the connection is healthy (`on_watch` / `connecting`),
18
25
  // the FSM dispatch state takes over to show the dispatch lifecycle
19
26
  // (`dispatching` / `tool: read` / `aborting` / etc.).
20
- const status = composeStatusLabel(props.connection, props.dispatchState, props.dispatchToolLabel);
21
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: status.color, children: `${glyph ?? '●'} ${status.label}` }), _jsx(Text, { dimColor: true, children: ` · ${props.activeAgentCount} agents · ` }), _jsx(Text, { children: `↓ ${tokenLabel} tokens` }), _jsx(Text, { dimColor: true, children: ` · ${elapsedLabel}` })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${formatCount(props.pugiMdCount)} PUGI.md · ${formatCount(props.mcpServerCount)} MCP · ${formatCount(props.skillCount)} skills · ${formatQuota(props.quotaPct)} quota` }) })] }));
27
+ const status = composeStatusLabel(props.connection, props.dispatchState, props.dispatchToolLabel, props.lastCompletedOutcome);
28
+ // cost-meter sprint the cost row anchors above the legacy
29
+ // dispatch-state line so the operator's eye lands on the meter first
30
+ // (matches the upstream tool TUI footer rhythm). The session-elapsed slot
31
+ // uses sessionStartedAtEpochMs (REPL boot), distinct from the
32
+ // per-brief `elapsedLabel` on the row below.
33
+ const costRow = renderCostMeterRow(props.sessionTokensIn ?? 0, props.sessionTokensOut ?? 0, props.sessionCostUsd ?? 0, props.sessionStartedAtEpochMs, now);
34
+ const deltaFlash = renderTurnDeltaFlash(props.lastTurnDelta, now);
35
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: `↑ ${costRow.tokensInLabel}` }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "cyan", children: `↓ ${costRow.tokensOutLabel}` }), _jsx(Text, { dimColor: true, children: ` · ` }), _jsx(Text, { bold: true, children: costRow.costLabel }), _jsx(Text, { dimColor: true, children: ` · ${costRow.elapsedLabel}` }), deltaFlash ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "green", children: deltaFlash })] })) : null] }), _jsxs(Box, { children: [_jsx(Text, { color: status.color, children: `${glyph ?? '●'} ${status.label}` }), _jsx(Text, { dimColor: true, children: ` · ${props.activeAgentCount} agents · ` }), _jsx(Text, { children: `↓ ${tokenLabel} tokens` }), _jsx(Text, { dimColor: true, children: ` · ${elapsedLabel}` }), typeof props.externalDispatchCount === 'number' && props.externalDispatchCount > 0 ? (_jsx(Text, { color: "yellow", children: ` · ${props.externalDispatchCount} dispatch${props.externalDispatchCount === 1 ? '' : 'es'} active. /cancel к manage.` })) : null] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${formatCount(props.pugiMdCount)} PUGI.md · ${formatCount(props.mcpServerCount)} MCP · ${formatCount(props.skillCount)} skills · ${formatQuota(props.quotaPct)} quota` }) })] }));
36
+ }
37
+ /**
38
+ * cost-meter sprint — assemble the cost-meter row labels. Pure helper
39
+ * so the snapshot tests assert the formatted shape without standing up
40
+ * an Ink renderer.
41
+ */
42
+ export function renderCostMeterRow(tokensIn, tokensOut, costUsd, sessionStartedAtEpochMs, nowEpochMs) {
43
+ const elapsedMs = typeof sessionStartedAtEpochMs === 'number'
44
+ ? Math.max(0, nowEpochMs - sessionStartedAtEpochMs)
45
+ : 0;
46
+ return {
47
+ tokensInLabel: formatTokens(tokensIn),
48
+ tokensOutLabel: formatTokens(tokensOut),
49
+ costLabel: formatCostUsd(costUsd),
50
+ elapsedLabel: formatElapsedShort(elapsedMs),
51
+ };
52
+ }
53
+ /**
54
+ * cost-meter sprint — render the per-turn delta flash when the most
55
+ * recent turn completed within the flash window. Returns null when no
56
+ * turn has completed yet OR the flash has expired (the elapsed slot
57
+ * gets the slot back). The flash format mirrors the spec:
58
+ *
59
+ * `+200/+1.1k +$0.01`
60
+ *
61
+ * Exported for snapshot tests.
62
+ */
63
+ export function renderTurnDeltaFlash(delta, nowEpochMs) {
64
+ if (!delta)
65
+ return null;
66
+ const elapsedSinceMs = nowEpochMs - delta.completedAtEpochMs;
67
+ if (elapsedSinceMs < 0 || elapsedSinceMs > TURN_DELTA_FLASH_MS)
68
+ return null;
69
+ const inLabel = formatTokens(delta.tokensIn);
70
+ const outLabel = formatTokens(delta.tokensOut);
71
+ const costLabel = delta.costUsd > 0 ? ` +${formatCostUsd(delta.costUsd)}` : '';
72
+ return `+${inLabel}/+${outLabel}${costLabel}`;
73
+ }
74
+ /**
75
+ * cost-meter sprint — local copy of the session elapsed formatter.
76
+ * Mirrors the helper in `core/repl/session.ts` so the status bar stays
77
+ * a pure leaf component without a circular import on session.ts (the
78
+ * model-pricing module's `formatDuration` is similar but ships an
79
+ * `XhYm` ceiling that does not match the CEO spec's `2m44s` shape).
80
+ */
81
+ function formatElapsedShort(elapsedMs) {
82
+ if (!Number.isFinite(elapsedMs) || elapsedMs <= 0)
83
+ return '0s';
84
+ const totalSec = Math.floor(elapsedMs / 1000);
85
+ if (totalSec < 60)
86
+ return `${totalSec}s`;
87
+ const min = Math.floor(totalSec / 60);
88
+ const sec = totalSec % 60;
89
+ if (min < 60)
90
+ return `${min}m${sec.toString().padStart(2, '0')}s`;
91
+ const hr = Math.floor(min / 60);
92
+ const restMin = min % 60;
93
+ return `${hr}h${restMin.toString().padStart(2, '0')}m`;
22
94
  }
23
95
  /**
24
96
  * Render a count badge — number if defined, `—` placeholder otherwise.
@@ -53,29 +125,29 @@ export function connectionLabel(connection) {
53
125
  }
54
126
  }
55
127
  /**
56
- * α6.9: compose the visible status label from connection + FSM state.
128
+ * : compose the visible status label from connection + FSM state.
57
129
  *
58
130
  * Priority order:
59
131
  *
60
- * 1. `offline` / `reconnecting` — transport health wins; the
61
- * operator needs to know about a dropped stream before anything
62
- * about the dispatch.
63
- * 2. `aborting` / `aborted` / `failed` — operator-visible terminal
64
- * states the FSM reached; the colour shifts to amber/red so the
65
- * anomaly stands out vs the calm cyan baseline.
66
- * 3. `tool_running` — surfaces the tool label when available
67
- * (`tool: read`), falls back to `tool` when not.
68
- * 4. `awaiting_response` — `dispatching` (matches Codex CLI's verb
69
- * for the same state).
70
- * 5. `completed` — `shipped` (matches the agent tree status glyph
71
- * so the operator's eye links the two surfaces).
72
- * 6. `idle` / unknown — connection label (`on watch` / `connecting`).
132
+ * 1. `offline` / `reconnecting` — transport health wins; the
133
+ * operator needs to know about a dropped stream before anything
134
+ * about the dispatch.
135
+ * 2. `aborting` / `aborted` / `failed` — operator-visible terminal
136
+ * states the FSM reached; the colour shifts to amber/red so the
137
+ * anomaly stands out vs the calm cyan baseline.
138
+ * 3. `tool_running` — surfaces the tool label when available
139
+ * (`tool: read`), falls back to `tool` when not.
140
+ * 4. `awaiting_response` — `dispatching` (matches peer CLI's verb
141
+ * for the same state).
142
+ * 5. `completed` — `shipped` (matches the agent tree status glyph
143
+ * so the operator's eye links the two surfaces).
144
+ * 6. `idle` / unknown — connection label (`on watch` / `connecting`).
73
145
  *
74
146
  * The dispatch label `dispatchToolLabel` is already shaped as
75
147
  * `tool: <kind>` upstream so we just concatenate; null falls through
76
148
  * to the bare `tool` placeholder.
77
149
  */
78
- function composeStatusLabel(connection, dispatchState, toolLabel) {
150
+ function composeStatusLabel(connection, dispatchState, toolLabel, lastCompletedOutcome) {
79
151
  // Transport health wins.
80
152
  if (connection === 'offline' || connection === 'reconnecting') {
81
153
  return connectionLabel(connection);
@@ -93,6 +165,16 @@ function composeStatusLabel(connection, dispatchState, toolLabel) {
93
165
  case 'awaiting_response':
94
166
  return { label: 'dispatching', color: 'cyan' };
95
167
  case 'completed':
168
+ // Branch on the work-done outcome so the bottom-bar tells the
169
+ // same truth as the agent-tree (2026-05-26 — memory
170
+ // feedback_no_fake_dispatch_promises). `'replied'` = text-only
171
+ // turn, render with the same neutral gray + arrow used in the
172
+ // agent-tree. `'shipped'` = real side-effect (or older server
173
+ // that omits the outcome field). Defaults to `'shipped'` so
174
+ // older callers without the prop wired stay back-compat.
175
+ if (lastCompletedOutcome === 'replied') {
176
+ return { label: 'replied', color: 'gray' };
177
+ }
96
178
  return { label: 'shipped', color: 'green' };
97
179
  case 'idle':
98
180
  case undefined:
@@ -111,18 +193,14 @@ function formatElapsed(startedAt, now) {
111
193
  const seconds = Math.floor((ms % 60_000) / 1000);
112
194
  return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
113
195
  }
114
- /**
115
- * Format the downstream token counter as 1.2k / 12.4k / 1.0m. Anvil
116
- * F1 emits totals in the tens-of-thousands range during a single
117
- * brief, so anything more than three significant figures is noise.
118
- */
119
- function formatTokens(total) {
120
- if (total < 1_000)
121
- return total.toString();
122
- if (total < 1_000_000)
123
- return `${(total / 1_000).toFixed(1)}k`;
124
- return `${(total / 1_000_000).toFixed(1)}m`;
125
- }
196
+ // `formatTokens` for the downstream-throughput slot is imported from
197
+ // `core/repl/model-pricing.ts` single source of truth for token
198
+ // formatting across the cost-meter row, `/cost` slash, and the legacy
199
+ // downstream-tokens slot. The shape is identical to the prior local
200
+ // helper (`<1000` raw, `<1m` one-decimal k, `≥1m` one-decimal m); the
201
+ // only semantic difference is non-finite / negative inputs render as
202
+ // `0` instead of throwing, matching the cost-meter row's defensive
203
+ // posture.
126
204
  function clampPhase(phase) {
127
205
  if (typeof phase !== 'number' || Number.isNaN(phase))
128
206
  return 0;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function StatusTable({ snapshot }) {
4
+ const labelWidth = Math.max('Label'.length, ...snapshot.fields.map((f) => f.label.length));
5
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Pugi status" }) }), snapshot.fields.map((field) => (_jsxs(Box, { children: [_jsxs(Text, { children: [field.label.padEnd(labelWidth, ' '), " "] }), field.available ? (_jsx(Text, { children: field.value })) : (_jsx(Text, { dimColor: true, children: field.value }))] }, field.key))), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["CLI ", snapshot.meta.cliVersion, " Node ", snapshot.meta.nodeVersion, " cwd ", snapshot.meta.cwd] }) })] }));
6
+ }
7
+ //# sourceMappingURL=status-table.js.map