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

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 (411) 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 +1792 -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/cron.js +433 -0
  351. package/dist/tools/enter-worktree.js +250 -0
  352. package/dist/tools/exit-worktree.js +147 -0
  353. package/dist/tools/file-tools.js +161 -44
  354. package/dist/tools/lsp-tools.js +377 -1
  355. package/dist/tools/mcp-tool.js +260 -0
  356. package/dist/tools/multi-edit.js +361 -0
  357. package/dist/tools/powershell.js +268 -0
  358. package/dist/tools/registry.js +99 -4
  359. package/dist/tools/skill-tool.js +96 -0
  360. package/dist/tools/sleep.js +99 -0
  361. package/dist/tools/synthetic-output.js +133 -0
  362. package/dist/tools/tasks.js +208 -0
  363. package/dist/tools/todo-write.js +184 -0
  364. package/dist/tools/verify-plan-execution.js +295 -0
  365. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  366. package/dist/tools/web-fetch.js +195 -10
  367. package/dist/tools/web-search.js +458 -0
  368. package/dist/tui/agent-progress-card.js +111 -0
  369. package/dist/tui/agent-tree.js +11 -1
  370. package/dist/tui/ask-modal.js +14 -14
  371. package/dist/tui/ask-user-question-chips.js +315 -0
  372. package/dist/tui/ask-user-question-prompt.js +203 -0
  373. package/dist/tui/compact-banner.js +81 -0
  374. package/dist/tui/conversation-pane.js +85 -11
  375. package/dist/tui/cost-table.js +111 -0
  376. package/dist/tui/device-flow.js +2 -2
  377. package/dist/tui/doctor-table.js +46 -0
  378. package/dist/tui/feedback-prompt.js +156 -0
  379. package/dist/tui/input-box.js +247 -32
  380. package/dist/tui/login-picker.js +3 -3
  381. package/dist/tui/markdown-render.js +6 -6
  382. package/dist/tui/multi-file-diff-approval.js +375 -0
  383. package/dist/tui/onboarding-wizard.js +240 -0
  384. package/dist/tui/permissions-picker.js +86 -0
  385. package/dist/tui/render.js +36 -1
  386. package/dist/tui/repl-render.js +176 -25
  387. package/dist/tui/repl-splash-art.js +16 -16
  388. package/dist/tui/repl-splash-mascot.js +48 -24
  389. package/dist/tui/repl-splash.js +22 -22
  390. package/dist/tui/repl.js +125 -45
  391. package/dist/tui/slash-palette.js +6 -6
  392. package/dist/tui/splash.js +2 -2
  393. package/dist/tui/status-bar.js +109 -31
  394. package/dist/tui/status-table.js +7 -0
  395. package/dist/tui/stickers-art.js +136 -0
  396. package/dist/tui/style-table.js +28 -0
  397. package/dist/tui/theme-table.js +29 -0
  398. package/dist/tui/thinking-spinner.js +123 -0
  399. package/dist/tui/tool-stream-pane.js +53 -4
  400. package/dist/tui/update-banner.js +27 -2
  401. package/dist/tui/vim-input.js +267 -0
  402. package/dist/tui/welcome-banner.js +107 -0
  403. package/dist/tui/welcome-data.js +293 -0
  404. package/dist/tui/workspace-context.js +2 -2
  405. package/package.json +31 -16
  406. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  407. package/test/scenarios/compact-force.scenario.txt +12 -0
  408. package/test/scenarios/identity.scenario.txt +12 -0
  409. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  410. package/test/scenarios/walkback.scenario.txt +12 -0
  411. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,375 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * MultiFileDiffApproval — side-by-side multi-file diff approval modal
4
+ * (PUGI-68).
5
+ *
6
+ * When the engine proposes a batch of edits that touches more than one
7
+ * file, the operator needs to review every diff in a single coherent
8
+ * view, then accept-all / reject-all / per-file approve before the
9
+ * dispatcher applies anything. The single-file approval surfaces in
10
+ * the AskModal / PlanReviewModal family are insufficient — a per-file
11
+ * back-to-back prompt loses the cross-file context the operator needs
12
+ * to spot a regression that spans modules.
13
+ *
14
+ * Layout (matches the AskUserQuestionChips PUGI-130 side-by-side
15
+ * precedent, see `ask-user-question-chips.tsx`):
16
+ *
17
+ * ┌─ Multi-file diff review ───────────────────────────────────────┐
18
+ * │ ┌─ Files ─────────────┐ ┌─ Diff: src/foo/bar.ts ─────────────┐ │
19
+ * │ │ ▸ • src/foo/bar.ts │ │ --- src/foo/bar.ts │ │
20
+ * │ │ ✓ src/baz.ts │ │ +++ src/foo/bar.ts │ │
21
+ * │ │ ✗ src/qux.ts │ │ @@ -1,4 +1,4 @@ │ │
22
+ * │ │ • src/x/y.ts │ │ -const x = 1; │ │
23
+ * │ │ │ │ +const x = 2; │ │
24
+ * │ └─────────────────────┘ └────────────────────────────────────┘ │
25
+ * │ 1/4 approved · 1/4 rejected · 2 remaining · Enter when done │
26
+ * └────────────────────────────────────────────────────────────────┘
27
+ *
28
+ * Key bindings:
29
+ * - ↑ / ↓ ............ navigate the file list
30
+ * - a ................ approve the highlighted file
31
+ * - r ................ reject the highlighted file
32
+ * - A (shift+a) ...... approve every file in the batch
33
+ * - R (shift+r) ...... reject every file in the batch
34
+ * - PgUp / PgDn ...... scroll the diff pane
35
+ * - Enter ............ finalise — emit per-file verdicts
36
+ * - Esc .............. cancel the whole batch (every file → rejected)
37
+ *
38
+ * Contract notes:
39
+ * - The component is PURE (Ink-style). It mounts useState for cursor,
40
+ * per-file verdict map, and diff-pane scroll offset. Emits exactly
41
+ * one `onResolve` callback when the operator presses Enter (or Esc
42
+ * for a cancel).
43
+ * - Per-file diff bodies are rendered verbatim from the provided
44
+ * unified-diff text — the component does NOT compute diffs from raw
45
+ * file contents. Callers (typically the dispatcher result transcript)
46
+ * own the diff text generation.
47
+ * - Long file paths are truncated middle-style so prefix + suffix stay
48
+ * visible (the parts the operator scans for module scope + filename).
49
+ * - Long diff bodies scroll via a single-axis offset; horizontal
50
+ * overflow is left to Ink's text wrapping per line. No mouse support.
51
+ *
52
+ * Brand voice gate: ASCII glyphs only (✓ / ✗ kept for status — already
53
+ * shipped в other TUI components like agent-progress-card). No banned
54
+ * brand words, no em-dashes, no attribution to external AIs.
55
+ */
56
+ import { useEffect, useMemo, useRef, useState } from 'react';
57
+ import { Box, Text, useInput } from 'ink';
58
+ /* ------------------------------------------------------------------ */
59
+ /* Defensive caps */
60
+ /* ------------------------------------------------------------------ */
61
+ /** Maximum chars rendered per file path in the left pane. */
62
+ export const MULTI_FILE_DIFF_PATH_CAP = 48;
63
+ /** Visible rows in the diff pane viewport. */
64
+ export const MULTI_FILE_DIFF_PANE_HEIGHT = 18;
65
+ /** Width hint for the left file-list column (used for path truncation). */
66
+ export const MULTI_FILE_DIFF_LEFT_WIDTH = 32;
67
+ /** Hard cap on diff body length (chars) — defends against runaway model output. */
68
+ export const MULTI_FILE_DIFF_BODY_CHAR_CAP = 200_000;
69
+ /* ------------------------------------------------------------------ */
70
+ /* Helpers */
71
+ /* ------------------------------------------------------------------ */
72
+ /**
73
+ * Middle-truncate a long file path so both prefix (module scope) and
74
+ * suffix (filename) stay visible. Mirrors the macOS `truncate=middle`
75
+ * convention. Appends `…` in the middle when truncation happens.
76
+ *
77
+ * Examples (cap = 24):
78
+ * "src/foo/bar/baz/qux.ts" → "src/foo/bar/baz/qux.ts" (fits)
79
+ * "src/a-long-module/b/c/d.ts" → "src/a-long-mo…/b/c/d.ts" (cut middle)
80
+ *
81
+ * Exported so the spec can assert the contract directly.
82
+ */
83
+ export function truncateMiddle(raw, cap) {
84
+ if (cap <= 1)
85
+ return '…';
86
+ if (raw.length <= cap)
87
+ return raw;
88
+ // Reserve one slot for the ellipsis; balance prefix/suffix around it.
89
+ // The suffix gets one extra char on odd budgets so the filename stays
90
+ // longer (operators scan filenames more than the path prefix).
91
+ const remaining = cap - 1;
92
+ const prefixLen = Math.floor(remaining / 2);
93
+ const suffixLen = remaining - prefixLen;
94
+ return `${raw.slice(0, prefixLen)}…${raw.slice(raw.length - suffixLen)}`;
95
+ }
96
+ /**
97
+ * Defensive char cap on the diff body. Schema-level caps live upstream;
98
+ * this is belt + braces against a malformed dispatcher transcript that
99
+ * could otherwise exhaust the terminal scrollback.
100
+ */
101
+ export function clampDiffBody(raw) {
102
+ if (raw.length <= MULTI_FILE_DIFF_BODY_CHAR_CAP)
103
+ return raw;
104
+ return `${raw.slice(0, MULTI_FILE_DIFF_BODY_CHAR_CAP - 1)}…`;
105
+ }
106
+ export function classifyDiffLine(line) {
107
+ if (line.startsWith('+++') || line.startsWith('---'))
108
+ return 'file-header';
109
+ if (line.startsWith('@@'))
110
+ return 'hunk-header';
111
+ if (line.startsWith('+'))
112
+ return 'addition';
113
+ if (line.startsWith('-'))
114
+ return 'deletion';
115
+ return 'context';
116
+ }
117
+ /** Inline helper — return per-line render props for a diff line. */
118
+ function renderProps(kind) {
119
+ switch (kind) {
120
+ case 'file-header':
121
+ return { bold: true };
122
+ case 'hunk-header':
123
+ return { color: 'blue', bold: true };
124
+ case 'addition':
125
+ return { color: 'green' };
126
+ case 'deletion':
127
+ return { color: 'red' };
128
+ case 'context':
129
+ return { dimColor: true };
130
+ }
131
+ }
132
+ /**
133
+ * Public verdict-encoder mirror of the AskModal / PlanReviewModal
134
+ * encoders. Surfaces a single human-readable summary string the caller
135
+ * can inject as the next operator turn or log to a session journal.
136
+ *
137
+ * - `cancelled` → "[MULTI-DIFF-VERDICT:cancelled]"
138
+ * - all approved → "[MULTI-DIFF-VERDICT:all-approved] file1; file2"
139
+ * - all rejected → "[MULTI-DIFF-VERDICT:all-rejected] file1; file2"
140
+ * - mixed → "[MULTI-DIFF-VERDICT:mixed] +file1; -file2; ?file3"
141
+ *
142
+ * Each per-file token in mixed mode is prefixed `+` (approved), `-`
143
+ * (rejected), or `?` (still pending — operator hit Enter без deciding).
144
+ */
145
+ export function encodeMultiFileDiffVerdict(result) {
146
+ if (result.cancelled)
147
+ return '[MULTI-DIFF-VERDICT:cancelled]';
148
+ const total = result.verdicts.length;
149
+ if (total === 0)
150
+ return '[MULTI-DIFF-VERDICT:empty]';
151
+ const allApproved = result.verdicts.every((v) => v.verdict === 'approved');
152
+ const allRejected = result.verdicts.every((v) => v.verdict === 'rejected');
153
+ if (allApproved) {
154
+ return `[MULTI-DIFF-VERDICT:all-approved] ${result.verdicts
155
+ .map((v) => v.path)
156
+ .join('; ')}`;
157
+ }
158
+ if (allRejected) {
159
+ return `[MULTI-DIFF-VERDICT:all-rejected] ${result.verdicts
160
+ .map((v) => v.path)
161
+ .join('; ')}`;
162
+ }
163
+ const tokens = result.verdicts.map((v) => {
164
+ const prefix = v.verdict === 'approved' ? '+' : v.verdict === 'rejected' ? '-' : '?';
165
+ return `${prefix}${v.path}`;
166
+ });
167
+ return `[MULTI-DIFF-VERDICT:mixed] ${tokens.join('; ')}`;
168
+ }
169
+ /* ------------------------------------------------------------------ */
170
+ /* Component */
171
+ /* ------------------------------------------------------------------ */
172
+ /**
173
+ * Build the result object for a given verdict map + entries vector.
174
+ * Hoisted because both the commit (Enter) and cancel (Esc) paths need
175
+ * to project the same shape — cancel just overrides every verdict to
176
+ * `rejected` and stamps `cancelled: true`.
177
+ */
178
+ function buildResult(entries, verdicts, cancelled) {
179
+ const projected = entries.map((entry, idx) => ({
180
+ path: entry.path,
181
+ verdict: verdicts[idx] ?? 'pending',
182
+ }));
183
+ return {
184
+ verdicts: projected,
185
+ approvedPaths: projected
186
+ .filter((v) => v.verdict === 'approved')
187
+ .map((v) => v.path),
188
+ rejectedPaths: projected
189
+ .filter((v) => v.verdict === 'rejected')
190
+ .map((v) => v.path),
191
+ cancelled,
192
+ };
193
+ }
194
+ export function MultiFileDiffApproval(props) {
195
+ // FIX (PR #876 triple-review): the previous `useMemo(() => props.entries,
196
+ // [props.entries])` was a no-op — it returned the same reference it
197
+ // depended on, so the memo never produced a stable identity gain. We
198
+ // reference `props.entries` directly now; downstream useEffect /
199
+ // useMemo hooks key on the real prop, not a redundant wrapper.
200
+ const entries = props.entries;
201
+ const paneHeight = props.paneHeight ?? MULTI_FILE_DIFF_PANE_HEIGHT;
202
+ const [cursor, setCursor] = useState(0);
203
+ const [verdicts, setVerdicts] = useState(() => entries.map(() => 'pending'));
204
+ const [scrollOffset, setScrollOffset] = useState(0);
205
+ // FIX (PR #876 triple-review, P1): the lazy useState initialiser
206
+ // captures `entries.length` ONCE at mount. If the caller swaps in
207
+ // a different entries array (length mismatch), the verdicts vector
208
+ // would drift — `setVerdictAt` could index out of range or
209
+ // `buildResult` could project a `pending` for a real entry whose
210
+ // verdict the operator already set. Re-sync on length change while
211
+ // preserving existing per-index verdicts (forgiving variant A).
212
+ useEffect(() => {
213
+ setVerdicts((prev) => {
214
+ if (prev.length === entries.length)
215
+ return prev;
216
+ return entries.map((_, i) => prev[i] ?? 'pending');
217
+ });
218
+ // Also clamp the cursor so it never points past the new end.
219
+ setCursor((c) => {
220
+ if (entries.length === 0)
221
+ return 0;
222
+ return Math.min(c, entries.length - 1);
223
+ });
224
+ }, [entries.length, entries]);
225
+ // FIX (PR #876 triple-review, P1): `commit()` is called from inside
226
+ // the useInput closure, which captures the `verdicts` value at the
227
+ // render time the closure was created. With rapid keypresses (e.g.
228
+ // `a` then Enter on the same React tick), React has scheduled the
229
+ // setVerdicts update but the closure still sees the previous array.
230
+ // The emitted MultiFileDiffResult would drop the latest verdict.
231
+ // Fix: track verdicts via a ref synced in a useEffect — the commit
232
+ // path reads `verdictsRef.current` so it always sees the latest map.
233
+ const verdictsRef = useRef(verdicts);
234
+ useEffect(() => {
235
+ verdictsRef.current = verdicts;
236
+ }, [verdicts]);
237
+ // Pre-split the active diff body into lines for the right pane. The
238
+ // body is clamped defensively before splitting so a runaway model
239
+ // payload cannot exhaust the terminal scrollback. useMemo here keeps
240
+ // the line array stable across navigation re-renders on the SAME
241
+ // file — only the cursor changes when the operator presses ↑/↓ on
242
+ // the same diff body.
243
+ const activeEntry = entries[cursor];
244
+ const activeLines = useMemo(() => {
245
+ const body = clampDiffBody(activeEntry?.diff ?? '');
246
+ if (body.length === 0)
247
+ return [];
248
+ return body.split('\n');
249
+ }, [activeEntry?.diff]);
250
+ function commit(cancelled = false) {
251
+ // Read the freshest verdict map via ref — the closure-captured
252
+ // `verdicts` state could be one render behind on rapid keypresses.
253
+ const latest = verdictsRef.current;
254
+ const finalVerdicts = cancelled
255
+ ? entries.map(() => 'rejected')
256
+ : latest;
257
+ props.onResolve(buildResult(entries, finalVerdicts, cancelled));
258
+ }
259
+ function setVerdictAt(idx, verdict) {
260
+ setVerdicts((prev) => {
261
+ const next = prev.slice();
262
+ next[idx] = verdict;
263
+ return next;
264
+ });
265
+ }
266
+ function setAllVerdicts(verdict) {
267
+ setVerdicts(() => entries.map(() => verdict));
268
+ }
269
+ useInput((input, key) => {
270
+ // Esc cancels the whole batch (every file → rejected). Mirrors the
271
+ // AskModal cancel contract so operator muscle memory transfers.
272
+ if (key.escape) {
273
+ commit(true);
274
+ return;
275
+ }
276
+ // Enter commits the current verdict map. Pending files stay
277
+ // pending — the caller decides whether `pending` is treated as
278
+ // approve or reject (dispatcher policy lives upstream).
279
+ if (key.return) {
280
+ commit(false);
281
+ return;
282
+ }
283
+ // ↑ / ↓ navigate file list. Reset scroll on file change so the
284
+ // operator sees the diff header again, not a stale offset from
285
+ // the previous file.
286
+ if (key.upArrow) {
287
+ if (entries.length === 0)
288
+ return;
289
+ setCursor((c) => (c - 1 + entries.length) % entries.length);
290
+ setScrollOffset(0);
291
+ return;
292
+ }
293
+ if (key.downArrow) {
294
+ if (entries.length === 0)
295
+ return;
296
+ setCursor((c) => (c + 1) % entries.length);
297
+ setScrollOffset(0);
298
+ return;
299
+ }
300
+ // PgUp / PgDn scroll the diff pane by a near-full viewport. We
301
+ // keep one row of overlap so the operator's eye anchors across
302
+ // pages. Clamped to [0, max] so over-scroll stops at the bottom
303
+ // line instead of producing an empty pane.
304
+ if (key.pageUp) {
305
+ setScrollOffset((o) => Math.max(0, o - Math.max(1, paneHeight - 1)));
306
+ return;
307
+ }
308
+ if (key.pageDown) {
309
+ setScrollOffset((o) => {
310
+ const maxOffset = Math.max(0, activeLines.length - paneHeight);
311
+ return Math.min(maxOffset, o + Math.max(1, paneHeight - 1));
312
+ });
313
+ return;
314
+ }
315
+ // Capital A / R = bulk verdict. Lowercase a / r = per-file. The
316
+ // order matters: Ink delivers SHIFTed keys as the upper-case
317
+ // glyph in `input`, so we can match с a direct equality.
318
+ if (input === 'A') {
319
+ setAllVerdicts('approved');
320
+ return;
321
+ }
322
+ if (input === 'R') {
323
+ setAllVerdicts('rejected');
324
+ return;
325
+ }
326
+ if (input === 'a') {
327
+ setVerdictAt(cursor, 'approved');
328
+ return;
329
+ }
330
+ if (input === 'r') {
331
+ setVerdictAt(cursor, 'rejected');
332
+ return;
333
+ }
334
+ }, { isActive: props.inert !== true });
335
+ const approvedCount = verdicts.filter((v) => v === 'approved').length;
336
+ const rejectedCount = verdicts.filter((v) => v === 'rejected').length;
337
+ const pendingCount = verdicts.filter((v) => v === 'pending').length;
338
+ const total = entries.length;
339
+ // Diff pane viewport: a window of `paneHeight` lines starting at
340
+ // scrollOffset. Empty entries render с a placeholder so the operator
341
+ // sees the file is intentionally empty (vs the modal being broken).
342
+ const visibleLines = activeLines.slice(scrollOffset, scrollOffset + paneHeight);
343
+ const diffPaneTitle = activeEntry
344
+ ? `Diff: ${truncateMiddle(activeEntry.path, MULTI_FILE_DIFF_PATH_CAP)}`
345
+ : 'Diff';
346
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: '? ' }), _jsx(Text, { bold: true, children: `Multi-file diff review (${total} ${total === 1 ? 'file' : 'files'})` })] }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, marginRight: 1, minWidth: MULTI_FILE_DIFF_LEFT_WIDTH, children: [_jsx(Box, { children: _jsx(Text, { bold: true, dimColor: true, children: 'Files' }) }), entries.map((entry, idx) => {
347
+ const isHighlighted = idx === cursor;
348
+ const verdict = verdicts[idx] ?? 'pending';
349
+ // Badge: ✓ approved (green), ✗ rejected (red), • pending (dim).
350
+ // The badge sits BEFORE the cursor arrow so the operator can
351
+ // skim status without scanning the cursor column.
352
+ let badgeGlyph = '•';
353
+ let badgeColor;
354
+ let badgeDim = true;
355
+ if (verdict === 'approved') {
356
+ badgeGlyph = '✓';
357
+ badgeColor = 'green';
358
+ badgeDim = false;
359
+ }
360
+ else if (verdict === 'rejected') {
361
+ badgeGlyph = '✗';
362
+ badgeColor = 'red';
363
+ badgeDim = false;
364
+ }
365
+ // Path budget = left width minus borders, cursor arrow (2),
366
+ // badge + space (2), padding (2). Defensive floor at 8 chars.
367
+ const pathBudget = Math.max(8, MULTI_FILE_DIFF_LEFT_WIDTH - 4 - 2 - 2);
368
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? 'cyan' : undefined, bold: isHighlighted, children: isHighlighted ? '▸ ' : ' ' }), _jsx(Text, { color: badgeColor, dimColor: badgeDim, bold: !badgeDim, children: `${badgeGlyph} ` }), _jsx(Text, { color: isHighlighted ? 'cyan' : undefined, bold: isHighlighted, children: truncateMiddle(entry.path, pathBudget) }), entry.hint !== undefined && entry.hint.length > 0 ? (_jsx(Text, { dimColor: true, italic: true, children: ` (${entry.hint})` })) : null] }, `file-${idx}-${entry.path}`));
369
+ })] }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: true, children: diffPaneTitle }), activeLines.length > paneHeight ? (_jsx(Text, { dimColor: true, children: ` · lines ${scrollOffset + 1}-${Math.min(scrollOffset + paneHeight, activeLines.length)}/${activeLines.length}` })) : null] }), visibleLines.length === 0 ? (_jsx(Text, { dimColor: true, italic: true, children: '(empty diff)' })) : (visibleLines.map((line, idx) => {
370
+ const kind = classifyDiffLine(line);
371
+ const rp = renderProps(kind);
372
+ return (_jsx(Text, { color: rp.color, bold: rp.bold, dimColor: rp.dimColor, children: line.length > 0 ? line : ' ' }, `diff-${cursor}-${scrollOffset + idx}`));
373
+ }))] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "green", children: `${approvedCount}/${total} approved` }), _jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { color: "red", children: `${rejectedCount}/${total} rejected` }), _jsx(Text, { dimColor: true, children: ' · ' }), _jsx(Text, { children: `${pendingCount} remaining` }), _jsx(Text, { dimColor: true, children: ' · Enter when done' })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '[↑↓] navigate · [a] approve · [r] reject · [A] all · [R] none · [PgUp/PgDn] scroll · [Esc] cancel' }) })] })] }));
374
+ }
375
+ //# sourceMappingURL=multi-file-diff-approval.js.map
@@ -0,0 +1,240 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * — Onboarding Ink wizard.
4
+ *
5
+ * Six-screen interactive walk:
6
+ *
7
+ * 1. Welcome + auth status (single Enter to continue)
8
+ * 2. Default permission mode (4-row picker)
9
+ * 3. Output style preset (5-row picker)
10
+ * 4. MCP server pointer (informational, Enter to continue)
11
+ * 5. Telemetry consent (3-row picker)
12
+ * 6. Recap card (Enter to commit + exit)
13
+ *
14
+ * Driven entirely by Ink's `useInput`. The component does NOT perform
15
+ * any fs writes — it resolves the verdict back to the caller
16
+ * (`runOnboardingCommand`), which translates verdicts into L6 / L18 /
17
+ * telemetry-state mutations. Single source of truth: the runner.
18
+ *
19
+ * Each picker step pre-selects the CURRENT persisted value (from the
20
+ * snapshot passed in props) so pressing Enter on Step 2/3/5 keeps the
21
+ * current value — that is the idempotency contract.
22
+ *
23
+ * Cancellation:
24
+ * - Esc / Ctrl-C at any step → verdict.cancelled = true, the runner
25
+ * skips ALL writes (including the marker touch) so the next bare
26
+ * `pugi` invocation still surfaces the first-run hint.
27
+ *
28
+ * Keystrokes:
29
+ * - ↑/↓ or j/k — move selection in pickers.
30
+ * - Enter — confirm step (keep current = pass through; new pick
31
+ * = update verdict for that tier).
32
+ * - 's' — skip current step explicitly (verdict slot stays null).
33
+ * - Esc / 'q' — cancel the wizard.
34
+ */
35
+ import { useState } from 'react';
36
+ import { Box, Text, render, useApp, useInput } from 'ink';
37
+ import { PERMISSION_MODES, PERMISSION_MODE_GLOSS, } from '../core/permissions/index.js';
38
+ import { OUTPUT_STYLES, OUTPUT_STYLE_SLUGS, } from '../core/output-style/presets.js';
39
+ import { TELEMETRY_CHOICES, } from '../core/onboarding/telemetry-state.js';
40
+ const EMPTY_DRAFT = {
41
+ permissionMode: null,
42
+ outputStyle: null,
43
+ telemetry: null,
44
+ };
45
+ /**
46
+ * Lookup the snapshot value's index within the picker rows so the
47
+ * initial cursor sits on the current value. Returns 0 (safe default)
48
+ * when the snapshot value is outside the closed list — should never
49
+ * happen given the type guards in the state modules, but the index
50
+ * fallback keeps Ink from crashing on a malformed config.
51
+ */
52
+ function indexOf(rows, value) {
53
+ const idx = rows.indexOf(value);
54
+ return idx === -1 ? 0 : idx;
55
+ }
56
+ /**
57
+ * The wizard component. Pure: no fs, no env, no network. Verdicts
58
+ * flow up via `onComplete`; the caller owns the writes.
59
+ */
60
+ export function OnboardingWizard(props) {
61
+ const { snapshot, onComplete } = props;
62
+ const [step, setStep] = useState(1);
63
+ const [permissionIdx, setPermissionIdx] = useState(indexOf(PERMISSION_MODES, snapshot.permissionMode));
64
+ const [styleIdx, setStyleIdx] = useState(indexOf(OUTPUT_STYLE_SLUGS, snapshot.outputStyle));
65
+ const [telemetryIdx, setTelemetryIdx] = useState(indexOf(TELEMETRY_CHOICES, snapshot.telemetry));
66
+ const [draft, setDraft] = useState(EMPTY_DRAFT);
67
+ const finish = (final, cancelled) => {
68
+ onComplete({
69
+ permissionMode: final.permissionMode,
70
+ outputStyle: final.outputStyle,
71
+ telemetry: final.telemetry,
72
+ cancelled,
73
+ });
74
+ };
75
+ useInput((input, key) => {
76
+ // Universal cancel.
77
+ if (key.escape || (key.ctrl && input === 'c')) {
78
+ finish(EMPTY_DRAFT, true);
79
+ return;
80
+ }
81
+ // Universal skip — Enter on a picker means "keep current"; explicit
82
+ // 's' makes the skip intent obvious in the recap.
83
+ const isAdvance = key.return;
84
+ const moveUp = key.upArrow || input === 'k';
85
+ const moveDown = key.downArrow || input === 'j';
86
+ const explicitSkip = input === 's';
87
+ switch (step) {
88
+ case 1: {
89
+ if (isAdvance)
90
+ setStep(2);
91
+ return;
92
+ }
93
+ case 2: {
94
+ if (moveUp) {
95
+ setPermissionIdx((i) => (i === 0 ? PERMISSION_MODES.length - 1 : i - 1));
96
+ return;
97
+ }
98
+ if (moveDown) {
99
+ setPermissionIdx((i) => (i === PERMISSION_MODES.length - 1 ? 0 : i + 1));
100
+ return;
101
+ }
102
+ if (isAdvance || explicitSkip) {
103
+ const picked = PERMISSION_MODES[permissionIdx];
104
+ const verdict = explicitSkip || picked === snapshot.permissionMode ? null : picked ?? null;
105
+ setDraft((d) => ({ ...d, permissionMode: verdict }));
106
+ setStep(3);
107
+ return;
108
+ }
109
+ return;
110
+ }
111
+ case 3: {
112
+ if (moveUp) {
113
+ setStyleIdx((i) => (i === 0 ? OUTPUT_STYLE_SLUGS.length - 1 : i - 1));
114
+ return;
115
+ }
116
+ if (moveDown) {
117
+ setStyleIdx((i) => (i === OUTPUT_STYLE_SLUGS.length - 1 ? 0 : i + 1));
118
+ return;
119
+ }
120
+ if (isAdvance || explicitSkip) {
121
+ const picked = OUTPUT_STYLE_SLUGS[styleIdx];
122
+ const verdict = explicitSkip || picked === snapshot.outputStyle ? null : picked ?? null;
123
+ setDraft((d) => ({ ...d, outputStyle: verdict }));
124
+ setStep(4);
125
+ return;
126
+ }
127
+ return;
128
+ }
129
+ case 4: {
130
+ if (isAdvance)
131
+ setStep(5);
132
+ return;
133
+ }
134
+ case 5: {
135
+ if (moveUp) {
136
+ setTelemetryIdx((i) => (i === 0 ? TELEMETRY_CHOICES.length - 1 : i - 1));
137
+ return;
138
+ }
139
+ if (moveDown) {
140
+ setTelemetryIdx((i) => (i === TELEMETRY_CHOICES.length - 1 ? 0 : i + 1));
141
+ return;
142
+ }
143
+ if (isAdvance || explicitSkip) {
144
+ const picked = TELEMETRY_CHOICES[telemetryIdx];
145
+ const verdict = explicitSkip || picked === snapshot.telemetry ? null : picked ?? null;
146
+ setDraft((d) => ({ ...d, telemetry: verdict }));
147
+ setStep(6);
148
+ return;
149
+ }
150
+ return;
151
+ }
152
+ case 6: {
153
+ if (isAdvance)
154
+ finish(draft, false);
155
+ return;
156
+ }
157
+ }
158
+ });
159
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsx(StepHeader, { step: step }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [step === 1 && _jsx(WelcomeStep, { snapshot: snapshot }), step === 2 && (_jsx(ModeStep, { current: snapshot.permissionMode, selectedIdx: permissionIdx })), step === 3 && (_jsx(StyleStep, { current: snapshot.outputStyle, currentSource: snapshot.outputStyleSource, selectedIdx: styleIdx })), step === 4 && _jsx(McpStep, {}), step === 5 && (_jsx(TelemetryStep, { current: snapshot.telemetry, selectedIdx: telemetryIdx })), step === 6 && _jsx(RecapStep, { snapshot: snapshot, draft: draft })] }), _jsx(FooterHints, { step: step })] }));
160
+ }
161
+ function StepHeader({ step }) {
162
+ const titles = {
163
+ 1: 'Welcome to Pugi',
164
+ 2: 'Step 2 / 5 — Default permission mode',
165
+ 3: 'Step 3 / 5 — Output style',
166
+ 4: 'Step 4 / 5 — MCP servers',
167
+ 5: 'Step 5 / 5 — Telemetry consent',
168
+ 6: 'Setup complete',
169
+ };
170
+ return (_jsx(Text, { bold: true, color: "cyan", children: titles[step] }));
171
+ }
172
+ function WelcomeStep({ snapshot }) {
173
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Brief it. It ships." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: snapshot.authPresent
174
+ ? 'You are signed in. The wizard configures local defaults; values persist to ~/.pugi/config.json.'
175
+ : 'You are NOT signed in. The wizard still configures local defaults, but you should run `pugi login` after.' }) })] }));
176
+ }
177
+ function ModeStep({ current, selectedIdx, }) {
178
+ return (_jsx(Box, { flexDirection: "column", children: PERMISSION_MODES.map((mode, idx) => (_jsx(PickerRow, { isSelected: idx === selectedIdx, isCurrent: mode === current, title: mode, gloss: PERMISSION_MODE_GLOSS[mode] }, mode))) }));
179
+ }
180
+ function StyleStep({ current, currentSource, selectedIdx, }) {
181
+ return (_jsxs(Box, { flexDirection: "column", children: [OUTPUT_STYLE_SLUGS.map((slug, idx) => (_jsx(PickerRow, { isSelected: idx === selectedIdx, isCurrent: slug === current, title: slug, gloss: OUTPUT_STYLES[slug].gloss }, slug))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: currentSource === 'workspace'
182
+ ? 'Active style is currently a workspace override. The wizard writes the user-tier default.'
183
+ : `Active source: ${currentSource}.` }) })] }));
184
+ }
185
+ function McpStep() {
186
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "MCP servers extend Pugi with extra tools (filesystem, browser, custom APIs)." }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Add one with:" }) }), _jsx(Text, { children: ' pugi mcp add <name> <command>' }), _jsx(Text, { children: ' pugi mcp list' }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "You can skip for now and add servers later." }) })] }));
187
+ }
188
+ function TelemetryStep({ current, selectedIdx, }) {
189
+ const gloss = {
190
+ off: 'No telemetry of any kind.',
191
+ anonymous: 'Counts + error categories only; no payloads.',
192
+ community: 'Anonymous + opt-in usage panels.',
193
+ };
194
+ return (_jsx(Box, { flexDirection: "column", children: TELEMETRY_CHOICES.map((choice, idx) => (_jsx(PickerRow, { isSelected: idx === selectedIdx, isCurrent: choice === current, title: choice, gloss: gloss[choice] }, choice))) }));
195
+ }
196
+ function RecapStep({ snapshot, draft, }) {
197
+ const finalMode = draft.permissionMode ?? snapshot.permissionMode;
198
+ const finalStyle = draft.outputStyle ?? snapshot.outputStyle;
199
+ const finalTelemetry = draft.telemetry ?? snapshot.telemetry;
200
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: ` Permission mode: ${finalMode}${draft.permissionMode === null ? ' (unchanged)' : ''}` }), _jsx(Text, { children: ` Output style: ${finalStyle}${draft.outputStyle === null ? ' (unchanged)' : ''}` }), _jsx(Text, { children: ` Telemetry: ${finalTelemetry}${draft.telemetry === null ? ' (unchanged)' : ''}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Press Enter to write defaults + exit. Esc to cancel without saving." }) })] }));
201
+ }
202
+ function PickerRow({ isSelected, isCurrent, title, gloss, }) {
203
+ const indicator = isSelected ? '▸ ' : ' ';
204
+ const currentTag = isCurrent ? ' [current]' : '';
205
+ return (_jsxs(Text, { children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, bold: isSelected, children: [indicator, title.padEnd(18, ' ')] }), _jsx(Text, { dimColor: true, children: `${gloss}${currentTag}` })] }));
206
+ }
207
+ function FooterHints({ step }) {
208
+ const hint = step === 1 || step === 4
209
+ ? 'Enter continue Esc cancel'
210
+ : step === 6
211
+ ? 'Enter commit + exit Esc cancel'
212
+ : '↑/↓ select Enter confirm s skip Esc cancel';
213
+ return (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: hint }) }));
214
+ }
215
+ /**
216
+ * Mount the wizard, await the operator's verdict, unmount Ink, return
217
+ * the verdict to the runner. Wrapped in a `useApp` consumer so we can
218
+ * call `exit()` and let `waitUntilExit()` resolve cleanly.
219
+ */
220
+ export async function renderOnboardingWizard(opts) {
221
+ return new Promise((resolvePromise) => {
222
+ let resolved = false;
223
+ const handleComplete = (verdict) => {
224
+ if (resolved)
225
+ return;
226
+ resolved = true;
227
+ app.unmount();
228
+ resolvePromise(verdict);
229
+ };
230
+ const Wrapper = () => {
231
+ const { exit } = useApp();
232
+ return (_jsx(OnboardingWizard, { snapshot: opts.snapshot, onComplete: (verdict) => {
233
+ handleComplete(verdict);
234
+ exit();
235
+ } }));
236
+ };
237
+ const app = render(_jsx(Wrapper, {}));
238
+ });
239
+ }
240
+ //# sourceMappingURL=onboarding-wizard.js.map