@pugi/cli 0.1.0-beta.4 → 0.1.0-beta.40

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 (249) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -25
  3. package/bin/run.js +33 -1
  4. package/dist/commands/jobs-watch.js +201 -0
  5. package/dist/commands/jobs.js +15 -0
  6. package/dist/commands/smoke.js +133 -0
  7. package/dist/core/agent-progress/cleanup.js +134 -0
  8. package/dist/core/agent-progress/schema.js +144 -0
  9. package/dist/core/agent-progress/writer.js +101 -0
  10. package/dist/core/artifact-chain/dispatcher.js +148 -0
  11. package/dist/core/artifact-chain/exporter.js +164 -0
  12. package/dist/core/artifact-chain/state.js +243 -0
  13. package/dist/core/artifact-chain/steps.js +169 -0
  14. package/dist/core/auth/ensure-authenticated.js +129 -0
  15. package/dist/core/auth/env-provider.js +238 -0
  16. package/dist/core/auto-update/channels.js +122 -0
  17. package/dist/core/auto-update/checker.js +241 -0
  18. package/dist/core/auto-update/state.js +235 -0
  19. package/dist/core/bare-mode/index.js +107 -0
  20. package/dist/core/bash-classifier.js +108 -1
  21. package/dist/core/checkpoint/resumer.js +149 -0
  22. package/dist/core/checkpoint/rewinder.js +291 -0
  23. package/dist/core/codegraph/decision-store.js +248 -0
  24. package/dist/core/codegraph/detect-repo.js +459 -0
  25. package/dist/core/codegraph/install.js +134 -0
  26. package/dist/core/codegraph/offer-hook.js +220 -0
  27. package/dist/core/compact/auto-trigger.js +96 -0
  28. package/dist/core/compact/buffer-rewriter.js +115 -0
  29. package/dist/core/compact/summarizer.js +208 -0
  30. package/dist/core/compact/token-counter.js +108 -0
  31. package/dist/core/consensus/diff-capture.js +73 -0
  32. package/dist/core/context/index.js +7 -0
  33. package/dist/core/context/markdown-traverse.js +255 -0
  34. package/dist/core/cost/rate-card.js +129 -0
  35. package/dist/core/cost/tracker.js +221 -0
  36. package/dist/core/denial-tracking/index.js +8 -0
  37. package/dist/core/denial-tracking/state.js +264 -0
  38. package/dist/core/diagnostics/probe-runner.js +93 -0
  39. package/dist/core/diagnostics/probes/api.js +46 -0
  40. package/dist/core/diagnostics/probes/auth.js +86 -0
  41. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  42. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  43. package/dist/core/diagnostics/probes/config.js +72 -0
  44. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  45. package/dist/core/diagnostics/probes/disk.js +81 -0
  46. package/dist/core/diagnostics/probes/git.js +65 -0
  47. package/dist/core/diagnostics/probes/mcp.js +75 -0
  48. package/dist/core/diagnostics/probes/node.js +59 -0
  49. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  50. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  51. package/dist/core/diagnostics/probes/session.js +74 -0
  52. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  53. package/dist/core/diagnostics/probes/workspace.js +63 -0
  54. package/dist/core/diagnostics/types.js +70 -0
  55. package/dist/core/dispatch/cache-cleanup.js +197 -0
  56. package/dist/core/dispatch/cache-handoff.js +295 -0
  57. package/dist/core/edits/dispatch.js +218 -2
  58. package/dist/core/edits/journal.js +199 -0
  59. package/dist/core/edits/layer-d-ast.js +557 -14
  60. package/dist/core/edits/verify-hook.js +273 -0
  61. package/dist/core/edits/worktree.js +322 -0
  62. package/dist/core/engine/anvil-client.js +115 -5
  63. package/dist/core/engine/budgets.js +98 -0
  64. package/dist/core/engine/context-prefix.js +155 -0
  65. package/dist/core/engine/intent.js +260 -0
  66. package/dist/core/engine/native-pugi.js +860 -211
  67. package/dist/core/engine/prompts.js +88 -2
  68. package/dist/core/engine/strip-internal-fields.js +124 -0
  69. package/dist/core/engine/tool-bridge.js +992 -36
  70. package/dist/core/feedback/queue.js +177 -0
  71. package/dist/core/feedback/submitter.js +145 -0
  72. package/dist/core/file-cache.js +113 -1
  73. package/dist/core/hooks/events.js +44 -0
  74. package/dist/core/hooks/index.js +15 -0
  75. package/dist/core/hooks/registry.js +213 -0
  76. package/dist/core/hooks/runner.js +236 -0
  77. package/dist/core/hooks/v2/event-emitter.js +115 -0
  78. package/dist/core/hooks/v2/executor.js +282 -0
  79. package/dist/core/hooks/v2/index.js +25 -0
  80. package/dist/core/hooks/v2/lifecycle.js +104 -0
  81. package/dist/core/hooks/v2/loader.js +216 -0
  82. package/dist/core/hooks/v2/matcher.js +125 -0
  83. package/dist/core/hooks/v2/trust.js +143 -0
  84. package/dist/core/hooks/v2/types.js +86 -0
  85. package/dist/core/lsp/cache.js +105 -0
  86. package/dist/core/lsp/client.js +776 -0
  87. package/dist/core/lsp/language-detect.js +66 -0
  88. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  89. package/dist/core/mcp/client.js +75 -6
  90. package/dist/core/mcp/http-server.js +553 -0
  91. package/dist/core/mcp/orchestrator-tools.js +662 -0
  92. package/dist/core/mcp/permission.js +190 -0
  93. package/dist/core/mcp/registry.js +24 -2
  94. package/dist/core/mcp/server-tools.js +219 -0
  95. package/dist/core/mcp/server.js +397 -0
  96. package/dist/core/memory/dual-write.js +416 -0
  97. package/dist/core/memory/phase1-kinds.js +20 -0
  98. package/dist/core/memory-sync/queue.js +158 -0
  99. package/dist/core/onboarding/ensure-initialized.js +133 -0
  100. package/dist/core/onboarding/marker.js +111 -0
  101. package/dist/core/onboarding/telemetry-state.js +108 -0
  102. package/dist/core/output-style/presets.js +176 -0
  103. package/dist/core/output-style/state.js +185 -0
  104. package/dist/core/permissions/auto-classifier.js +124 -0
  105. package/dist/core/permissions/circuit-breaker.js +83 -0
  106. package/dist/core/permissions/gate.js +278 -0
  107. package/dist/core/permissions/index.js +20 -0
  108. package/dist/core/permissions/mode.js +174 -0
  109. package/dist/core/permissions/state.js +241 -0
  110. package/dist/core/permissions/tool-class.js +93 -0
  111. package/dist/core/prd-check/parser.js +215 -0
  112. package/dist/core/prd-check/reporter.js +127 -0
  113. package/dist/core/prd-check/session-review.js +557 -0
  114. package/dist/core/prd-check/verifiers.js +223 -0
  115. package/dist/core/pugi-md/context-injector.js +76 -0
  116. package/dist/core/pugi-md/walk-up.js +207 -0
  117. package/dist/core/release-notes/parser.js +241 -0
  118. package/dist/core/release-notes/state.js +116 -0
  119. package/dist/core/repl/history.js +11 -1
  120. package/dist/core/repl/model-pricing.js +135 -0
  121. package/dist/core/repl/session.js +1899 -38
  122. package/dist/core/repl/slash-commands.js +406 -21
  123. package/dist/core/repl/store/session-store.js +31 -2
  124. package/dist/core/repl/workspace-context.js +22 -0
  125. package/dist/core/repo-map/build.js +125 -0
  126. package/dist/core/repo-map/cache.js +185 -0
  127. package/dist/core/repo-map/extractor.js +254 -0
  128. package/dist/core/repo-map/formatter.js +145 -0
  129. package/dist/core/repo-map/scanner.js +211 -0
  130. package/dist/core/retry-budget/budget.js +284 -0
  131. package/dist/core/retry-budget/index.js +5 -0
  132. package/dist/core/session.js +92 -0
  133. package/dist/core/settings.js +80 -0
  134. package/dist/core/share/formatter.js +271 -0
  135. package/dist/core/share/redactor.js +221 -0
  136. package/dist/core/share/uploader.js +267 -0
  137. package/dist/core/skills/defaults.js +457 -0
  138. package/dist/core/smoke/headless-driver.js +174 -0
  139. package/dist/core/smoke/orchestrator.js +194 -0
  140. package/dist/core/smoke/runner.js +238 -0
  141. package/dist/core/smoke/scenario-parser.js +316 -0
  142. package/dist/core/subagents/dispatcher-real.js +600 -0
  143. package/dist/core/subagents/dispatcher.js +113 -24
  144. package/dist/core/subagents/index.js +18 -5
  145. package/dist/core/subagents/isolation-matrix.js +213 -0
  146. package/dist/core/subagents/spawn.js +19 -4
  147. package/dist/core/telemetry/emitter.js +229 -0
  148. package/dist/core/telemetry/queue.js +251 -0
  149. package/dist/core/theme/context.js +91 -0
  150. package/dist/core/theme/presets.js +228 -0
  151. package/dist/core/theme/state.js +181 -0
  152. package/dist/core/todos/invariant.js +10 -0
  153. package/dist/core/todos/state.js +177 -0
  154. package/dist/core/transport/version-interceptor.js +166 -0
  155. package/dist/core/vim/keymap.js +288 -0
  156. package/dist/core/vim/state.js +92 -0
  157. package/dist/index.js +28 -0
  158. package/dist/runtime/bootstrap.js +190 -0
  159. package/dist/runtime/cli.js +3073 -321
  160. package/dist/runtime/commands/cancel.js +231 -0
  161. package/dist/runtime/commands/chain.js +489 -0
  162. package/dist/runtime/commands/codegraph-status.js +227 -0
  163. package/dist/runtime/commands/compact.js +297 -0
  164. package/dist/runtime/commands/cost.js +199 -0
  165. package/dist/runtime/commands/delegate.js +242 -11
  166. package/dist/runtime/commands/dispatch.js +126 -0
  167. package/dist/runtime/commands/doctor.js +390 -0
  168. package/dist/runtime/commands/feedback.js +184 -0
  169. package/dist/runtime/commands/hooks.js +184 -0
  170. package/dist/runtime/commands/lsp.js +368 -0
  171. package/dist/runtime/commands/mcp.js +879 -0
  172. package/dist/runtime/commands/memory.js +508 -0
  173. package/dist/runtime/commands/model.js +237 -0
  174. package/dist/runtime/commands/onboarding.js +275 -0
  175. package/dist/runtime/commands/patch.js +128 -0
  176. package/dist/runtime/commands/permissions.js +112 -0
  177. package/dist/runtime/commands/plan.js +143 -0
  178. package/dist/runtime/commands/prd-check.js +285 -0
  179. package/dist/runtime/commands/redo-blob-store.js +92 -0
  180. package/dist/runtime/commands/redo.js +361 -0
  181. package/dist/runtime/commands/release-notes.js +229 -0
  182. package/dist/runtime/commands/repo-map.js +95 -0
  183. package/dist/runtime/commands/report.js +299 -0
  184. package/dist/runtime/commands/resume.js +118 -0
  185. package/dist/runtime/commands/review-consensus.js +17 -2
  186. package/dist/runtime/commands/rewind.js +333 -0
  187. package/dist/runtime/commands/sessions.js +163 -0
  188. package/dist/runtime/commands/share.js +316 -0
  189. package/dist/runtime/commands/status.js +186 -0
  190. package/dist/runtime/commands/stickers.js +82 -0
  191. package/dist/runtime/commands/style.js +194 -0
  192. package/dist/runtime/commands/theme.js +196 -0
  193. package/dist/runtime/commands/undo.js +32 -0
  194. package/dist/runtime/commands/update.js +289 -0
  195. package/dist/runtime/commands/vim.js +140 -0
  196. package/dist/runtime/commands/worktree.js +177 -0
  197. package/dist/runtime/headless-repl.js +195 -0
  198. package/dist/runtime/headless.js +543 -0
  199. package/dist/runtime/load-hooks-or-exit.js +71 -0
  200. package/dist/runtime/plan-decompose.js +531 -0
  201. package/dist/runtime/version.js +65 -0
  202. package/dist/tools/agent-tool.js +229 -0
  203. package/dist/tools/apply-patch.js +556 -0
  204. package/dist/tools/ask-user-question.js +213 -0
  205. package/dist/tools/ask-user.js +115 -0
  206. package/dist/tools/file-tools.js +85 -14
  207. package/dist/tools/lsp-tools.js +189 -0
  208. package/dist/tools/mcp-tool.js +260 -0
  209. package/dist/tools/multi-edit.js +361 -0
  210. package/dist/tools/registry.js +46 -0
  211. package/dist/tools/skill-tool.js +96 -0
  212. package/dist/tools/tasks.js +208 -0
  213. package/dist/tools/todo-write.js +184 -0
  214. package/dist/tools/web-fetch.js +147 -2
  215. package/dist/tools/web-search.js +458 -0
  216. package/dist/tui/agent-progress-card.js +111 -0
  217. package/dist/tui/agent-tree.js +10 -0
  218. package/dist/tui/ask-modal.js +2 -2
  219. package/dist/tui/ask-user-question-prompt.js +192 -0
  220. package/dist/tui/compact-banner.js +81 -0
  221. package/dist/tui/conversation-pane.js +82 -8
  222. package/dist/tui/cost-table.js +111 -0
  223. package/dist/tui/doctor-table.js +46 -0
  224. package/dist/tui/feedback-prompt.js +156 -0
  225. package/dist/tui/input-box.js +69 -2
  226. package/dist/tui/markdown-render.js +4 -4
  227. package/dist/tui/onboarding-wizard.js +240 -0
  228. package/dist/tui/permissions-picker.js +86 -0
  229. package/dist/tui/render.js +35 -0
  230. package/dist/tui/repl-render.js +303 -13
  231. package/dist/tui/repl-splash.js +2 -2
  232. package/dist/tui/repl.js +72 -14
  233. package/dist/tui/splash.js +1 -1
  234. package/dist/tui/status-bar.js +94 -16
  235. package/dist/tui/status-table.js +7 -0
  236. package/dist/tui/stickers-art.js +136 -0
  237. package/dist/tui/style-table.js +28 -0
  238. package/dist/tui/theme-table.js +29 -0
  239. package/dist/tui/tool-stream-pane.js +52 -3
  240. package/dist/tui/update-banner.js +20 -2
  241. package/dist/tui/vim-input.js +267 -0
  242. package/docs/examples/codegraph.mcp.json +10 -0
  243. package/package.json +12 -6
  244. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  245. package/test/scenarios/compact-force.scenario.txt +11 -0
  246. package/test/scenarios/identity.scenario.txt +11 -0
  247. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  248. package/test/scenarios/walkback.scenario.txt +12 -0
  249. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,156 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * `/feedback` interactive prompt — Leak L21 (2026-05-27).
4
+ *
5
+ * Five-step wizard mounted from both the top-level `pugi feedback`
6
+ * shell handler and the in-REPL `/feedback` slash. Steps:
7
+ *
8
+ * 1. category — bug / feature / general / praise (numeric pick 1-4)
9
+ * 2. rating — 1-5 stars (numeric pick 1-5)
10
+ * 3. comment — free-text, multi-line, Ctrl-D submits, Esc cancels
11
+ * 4. context — y/n include last 5 turns (redacted)? default no
12
+ * 5. confirm — y/n submit?
13
+ *
14
+ * Brand voice gate: ASCII glyphs only, no em-dashes, no banned brand
15
+ * words. Copy is intentionally power-word neutral so the future i18n
16
+ * landing localises cleanly.
17
+ *
18
+ * The component is PURE in the Ink sense — props in, one terminal
19
+ * `onResolve` event out. The caller owns the submit + queue logic.
20
+ *
21
+ * The verdict the operator submits is:
22
+ * - `{ cancelled: true }` when the operator presses Esc at any step
23
+ * - `{ cancelled: false, draft }` when the wizard completes
24
+ *
25
+ * `draft` is the assembled `FeedbackDraft` — NOT yet a full envelope
26
+ * (the caller still injects `ts` + `cliVersion` + maybe `tier`).
27
+ */
28
+ import { useState } from 'react';
29
+ import { Box, Text, render, useApp, useInput } from 'ink';
30
+ const CATEGORIES = [
31
+ { value: 'bug', label: 'bug', gloss: 'something broke or behaves unexpectedly' },
32
+ { value: 'feature', label: 'feature', gloss: 'request a new capability' },
33
+ { value: 'general', label: 'general', gloss: 'observation, idea, comment' },
34
+ { value: 'praise', label: 'praise', gloss: 'positive note for the team' },
35
+ ];
36
+ export function FeedbackPrompt(props) {
37
+ const [step, setStep] = useState('category');
38
+ const [category, setCategory] = useState(null);
39
+ const [rating, setRating] = useState(null);
40
+ const [commentBuffer, setCommentBuffer] = useState('');
41
+ const [includeContext, setIncludeContext] = useState(false);
42
+ useInput((input, key) => {
43
+ if (key.escape) {
44
+ props.onResolve({ cancelled: true });
45
+ return;
46
+ }
47
+ if (step === 'category') {
48
+ const n = Number.parseInt(input, 10);
49
+ if (!Number.isNaN(n) && n >= 1 && n <= CATEGORIES.length) {
50
+ const picked = CATEGORIES[n - 1];
51
+ if (picked) {
52
+ setCategory(picked.value);
53
+ setStep('rating');
54
+ }
55
+ }
56
+ return;
57
+ }
58
+ if (step === 'rating') {
59
+ const n = Number.parseInt(input, 10);
60
+ if (!Number.isNaN(n) && n >= 1 && n <= 5) {
61
+ setRating(n);
62
+ setStep('comment');
63
+ }
64
+ return;
65
+ }
66
+ if (step === 'comment') {
67
+ // Ctrl-D submits the comment (even when empty — rating-only
68
+ // feedback is allowed). Enter inserts a newline so the
69
+ // operator can write multi-line bug repros without the wizard
70
+ // ending the buffer prematurely.
71
+ if (key.ctrl && (input === 'd' || input === '')) {
72
+ setStep('context');
73
+ return;
74
+ }
75
+ if (key.return) {
76
+ setCommentBuffer((prev) => prev + '\n');
77
+ return;
78
+ }
79
+ if (key.backspace || key.delete) {
80
+ setCommentBuffer((prev) => prev.slice(0, -1));
81
+ return;
82
+ }
83
+ // Printable characters land in the buffer. We accept anything
84
+ // non-empty + non-control. Ink's useInput sometimes delivers
85
+ // multi-char paste bursts as one `input` — we append the whole
86
+ // burst so paste lands intact.
87
+ if (input && !key.ctrl && !key.meta) {
88
+ setCommentBuffer((prev) => prev + input);
89
+ }
90
+ return;
91
+ }
92
+ if (step === 'context') {
93
+ if (input === 'y' || input === 'Y') {
94
+ setIncludeContext(true);
95
+ setStep('confirm');
96
+ return;
97
+ }
98
+ if (input === 'n' || input === 'N' || key.return) {
99
+ // Default no — Enter at the context prompt picks "no" so
100
+ // the operator can blast through with all defaults.
101
+ setIncludeContext(false);
102
+ setStep('confirm');
103
+ return;
104
+ }
105
+ return;
106
+ }
107
+ if (step === 'confirm') {
108
+ if (input === 'y' || input === 'Y' || key.return) {
109
+ if (category != null && rating != null) {
110
+ props.onResolve({
111
+ cancelled: false,
112
+ draft: {
113
+ category,
114
+ rating,
115
+ comment: commentBuffer,
116
+ includeSessionContext: includeContext,
117
+ },
118
+ });
119
+ }
120
+ return;
121
+ }
122
+ if (input === 'n' || input === 'N') {
123
+ props.onResolve({ cancelled: true });
124
+ return;
125
+ }
126
+ return;
127
+ }
128
+ }, { isActive: !props.inert });
129
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Pugi /feedback" }), _jsx(Text, { children: " \u2014 share what you saw. Esc cancels at any step." })] }), step === 'category' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "1. Category" }), CATEGORIES.map((c, idx) => (_jsx(Text, { children: ` ${idx + 1}. ${c.label.padEnd(8)} ${c.gloss}` }, c.value))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Pick 1-4." }) })] })), step === 'rating' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "2. Rating" }), _jsx(Text, { children: ` 1=poor, 5=excellent. Picked category: ${category ?? '?'}` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Pick 1-5." }) })] })), step === 'comment' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "3. Comment" }), _jsx(Text, { dimColor: true, children: "Multi-line. Enter inserts a newline. Ctrl-D submits." }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: commentBuffer.length === 0 ? (_jsx(Text, { dimColor: true, children: "(empty \u2014 Ctrl-D submits without a comment)" })) : (commentBuffer.split('\n').map((line, idx) => (_jsx(Text, { children: `> ${line}` }, idx)))) })] })), step === 'context' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "4. Include session context?" }), _jsx(Text, { children: ' Last 5 turns, redacted (tokens / *_KEY=* values stripped).' }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "y / n \u2014 default n (Enter)." }) })] })), step === 'confirm' && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "5. Submit?" }), _jsx(Text, { children: ` category=${category ?? '?'} rating=${rating ?? '?'} context=${includeContext ? 'yes' : 'no'}` }), _jsx(Text, { children: ` comment=${commentBuffer.length} chars` }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "y / n \u2014 default y (Enter)." }) })] }))] }));
130
+ }
131
+ export async function renderFeedbackPrompt() {
132
+ let resolveOuter;
133
+ const outerPromise = new Promise((resolve) => {
134
+ resolveOuter = resolve;
135
+ });
136
+ function App() {
137
+ const { exit } = useApp();
138
+ return (_jsx(FeedbackPrompt, { onResolve: (verdict) => {
139
+ resolveOuter(verdict);
140
+ // Mirror renderAskCli: tiny delay so Ink flushes unmount
141
+ // before the caller prints the toast line.
142
+ setTimeout(() => exit(), 16);
143
+ } }));
144
+ }
145
+ const instance = render(_jsx(App, {}));
146
+ const verdict = await outerPromise;
147
+ try {
148
+ await instance.waitUntilExit();
149
+ }
150
+ catch {
151
+ // Ink can throw when exit() races with a re-render — the verdict
152
+ // is already captured so we ignore.
153
+ }
154
+ return verdict;
155
+ }
156
+ //# sourceMappingURL=feedback-prompt.js.map
@@ -33,6 +33,14 @@ import { SlashPalette, completePalette, filterPalette, } from './slash-palette.j
33
33
  import { EMPTY_KILL_RING, killToLineEnd, killToLineStart, killWordBackward, yankAtCursor, } from '../core/repl/kill-ring.js';
34
34
  import { readClipboard } from '../core/repl/clipboard-read.js';
35
35
  const CTRL_C_DOUBLE_TAP_MS = 1_000;
36
+ /**
37
+ * Wave 6 BT 8 (Claude Code parity): Esc-Esc walks the conversation back
38
+ * one turn. 500ms is tight enough that an operator clearing the buffer +
39
+ * later changing their mind does NOT accidentally pop a turn, while
40
+ * still feeling like one motion. Matches Claude Code's documented
41
+ * double-Esc window.
42
+ */
43
+ const ESCAPE_DOUBLE_TAP_MS = 500;
36
44
  /** Width subtracted from the terminal width so the border + padding fit. */
37
45
  const FRAME_OVERHEAD_COLUMNS = 4;
38
46
  /** Fallback width when ink cannot read stdout (e.g. test harness). */
@@ -75,6 +83,11 @@ export function InputBox(props) {
75
83
  const [history, setHistory] = useState(seededHistory);
76
84
  const [historyIndex, setHistoryIndex] = useState(-1);
77
85
  const [lastCtrlCAt, setLastCtrlCAt] = useState(undefined);
86
+ // Wave 6 BT 8: Esc-Esc walkback double-tap window. Tracks the epoch
87
+ // ms of the most recent Esc press so the next Esc within
88
+ // ESCAPE_DOUBLE_TAP_MS triggers the walkback handler instead of
89
+ // re-clearing the buffer.
90
+ const [lastEscapeAt, setLastEscapeAt] = useState(undefined);
78
91
  const [cursorVisible, setCursorVisible] = useState(true);
79
92
  // Ctrl+R / Ctrl+S reverse-search mode. Undefined when idle, a
80
93
  // HistorySearchState while the operator is searching.
@@ -94,6 +107,11 @@ export function InputBox(props) {
94
107
  // panes when Ctrl+L wipes the terminal (the parent React tree is
95
108
  // otherwise stable and would not redraw on a stdout.write alone).
96
109
  const [, setRedrawTick] = useState(0);
110
+ // Wave 7 Shift+Tab toast — flashed for 2s after a mode cycle so the
111
+ // operator sees `Mode → acceptEdits` под the input divider. Cleared
112
+ // by a setTimeout so a quick second Shift+Tab refreshes the toast.
113
+ const [modeCycleToast, setModeCycleToast] = useState(null);
114
+ const modeCycleTimerRef = useRef(null);
97
115
  const now = props.now ?? Date.now;
98
116
  const { stdout } = useStdout();
99
117
  const columns = stdout?.columns ?? FALLBACK_COLUMNS;
@@ -184,6 +202,24 @@ export function InputBox(props) {
184
202
  }
185
203
  return;
186
204
  }
205
+ // Wave 7 — Claude Code parity: Shift+Tab cycles permission mode.
206
+ // The host owns the cycle logic + persistence; we just intercept
207
+ // the chord and surface a one-line toast on success. Place this
208
+ // BEFORE the search-mode and palette branches so a Shift+Tab fires
209
+ // even while reverse-search is active (operator habit-driven).
210
+ if (key.shift && key.tab && props.onCyclePermissionMode) {
211
+ const nextMode = props.onCyclePermissionMode();
212
+ if (nextMode) {
213
+ setModeCycleToast(`Mode → ${nextMode}`);
214
+ if (modeCycleTimerRef.current)
215
+ clearTimeout(modeCycleTimerRef.current);
216
+ modeCycleTimerRef.current = setTimeout(() => {
217
+ setModeCycleToast(null);
218
+ modeCycleTimerRef.current = null;
219
+ }, 2_000);
220
+ }
221
+ return;
222
+ }
187
223
  // Search-mode key handling. Ctrl+R / Ctrl+S cycle, Enter accepts,
188
224
  // Esc cancels (restoring the pre-search draft), backspace shortens
189
225
  // the query, typed characters extend it.
@@ -375,10 +411,41 @@ export function InputBox(props) {
375
411
  if (key.escape) {
376
412
  if (paletteOpen) {
377
413
  // Close the palette without clearing the buffer so the operator
378
- // can still send `/help` as plain text if they want.
414
+ // can still send `/help` as plain text if they want. Palette
415
+ // takes precedence over walkback because the operator's mental
416
+ // model is "Esc closes the visible overlay first".
379
417
  setPaletteSuppressed(true);
418
+ setLastEscapeAt(undefined);
419
+ return;
420
+ }
421
+ // Wave 6 BT 8: Esc-Esc walkback. Two presses within
422
+ // ESCAPE_DOUBLE_TAP_MS step the conversation back by one turn.
423
+ // First press still clears the buffer (legacy behaviour for the
424
+ // single-Esc cancel UX); the second press calls the host's
425
+ // walkback handler. Buffer-clear on the first press is what makes
426
+ // the double-tap feel "free" - the operator did not have to
427
+ // memorise a new chord; they just have to keep pressing.
428
+ const tEsc = now();
429
+ const withinEscapeWindow = typeof lastEscapeAt === 'number'
430
+ && tEsc - lastEscapeAt <= ESCAPE_DOUBLE_TAP_MS;
431
+ if (withinEscapeWindow && props.onWalkback) {
432
+ // Second tap inside the window. Buffer was already cleared on
433
+ // the first press, so the host sees a clean input box AND the
434
+ // walkback result. We clear the window so a third tap restarts
435
+ // the cycle (no run-on walkbacks from a stuck Esc key).
436
+ const verdict = props.onWalkback();
437
+ setLastEscapeAt(undefined);
438
+ if (verdict !== 'walked-back') {
439
+ // Host refused (dispatch in flight, no turns to pop). The
440
+ // host owns the refusal copy via its own writeOutput path;
441
+ // we do not double-message here.
442
+ return;
443
+ }
380
444
  return;
381
445
  }
446
+ // First Esc (or no walkback wired). Arm the window + clear the
447
+ // buffer per the long-standing single-Esc cancel contract.
448
+ setLastEscapeAt(tEsc);
382
449
  setLine('');
383
450
  setCursor(0);
384
451
  setHistoryIndex(-1);
@@ -499,7 +566,7 @@ export function InputBox(props) {
499
566
  : Math.min(paletteIndex, paletteView.rows.length - 1);
500
567
  const divider = '─'.repeat(innerWidth);
501
568
  const focusedMatch = search ? search.matches[search.focusedIndex] : undefined;
502
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", dimColor: true, children: divider }), _jsx(Box, { paddingX: 1, flexDirection: "column", children: search ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: '(reverse-i-search) ' }), _jsx(Text, { children: `\`${search.query}\`: ` }), _jsx(Text, { color: "yellow", children: focusedMatch ? focusedMatch.brief : '(no match)' })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `Ctrl+R next · Ctrl+S prev · Enter accept · Esc cancel · ${search.matches.length} match${search.matches.length === 1 ? '' : 'es'}` }) })] })) : (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: '› ' }), _jsx(Text, { children: renderLineWithCursor(line, cursor, cursorVisible) })] })) }), _jsx(Text, { color: "cyan", dimColor: true, children: divider }), line.length > innerWidth - 4 ? (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: '┊ ' }), _jsx(Text, { dimColor: true, children: 'line wraps - Enter still submits' })] })) : null, _jsx(SlashPalette, { rows: paletteView.rows, focusedIndex: clampedPaletteIndex, totalBeforeLimit: paletteView.totalBeforeLimit }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '↑/↓ history · Ctrl+R search · / commands · Enter brief · Esc cancel · Ctrl+C abort / ×2 exit' }) })] }));
569
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), _jsx(Box, { paddingX: 1, flexDirection: "column", children: search ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '(reverse-i-search) ' }), _jsx(Text, { children: `\`${search.query}\`: ` }), _jsx(Text, { color: "yellow", children: focusedMatch ? focusedMatch.brief : '(no match)' })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `Ctrl+R next · Ctrl+S prev · Enter accept · Esc cancel · ${search.matches.length} match${search.matches.length === 1 ? '' : 'es'}` }) })] })) : (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '› ' }), _jsx(Text, { children: renderLineWithCursor(line, cursor, cursorVisible) })] })) }), _jsx(Text, { color: "#3da9fc", dimColor: true, children: divider }), modeCycleToast ? (_jsx(Box, { children: _jsx(Text, { color: "#3da9fc", bold: true, children: ` ${modeCycleToast}` }) })) : null, line.length > innerWidth - 4 ? (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: '┊ ' }), _jsx(Text, { dimColor: true, children: 'line wraps - Enter still submits' })] })) : null, _jsx(SlashPalette, { rows: paletteView.rows, focusedIndex: clampedPaletteIndex, totalBeforeLimit: paletteView.totalBeforeLimit }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '↑/↓ history · Ctrl+R search · / commands · Shift+Tab mode · Enter brief · Esc cancel · Ctrl+C abort / ×2 exit' }) })] }));
503
570
  }
504
571
  /**
505
572
  * Render the line with the cursor glyph inserted at `cursor`. The cursor
@@ -107,9 +107,9 @@ function renderBlock(block, key) {
107
107
  case 'paragraph':
108
108
  return (_jsx(Text, { children: renderInline(block.text) }, key));
109
109
  case 'bullet':
110
- return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: '• ' }), _jsx(Text, { children: renderInline(block.text) })] }, key));
110
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: '• ' }), _jsx(Text, { children: renderInline(block.text) })] }, key));
111
111
  case 'ordered':
112
- return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: `${block.index}. ` }), _jsx(Text, { children: renderInline(block.text) })] }, key));
112
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", children: `${block.index}. ` }), _jsx(Text, { children: renderInline(block.text) })] }, key));
113
113
  case 'code':
114
114
  return renderCodeBlock(block.lang, block.body, key);
115
115
  case 'blank':
@@ -148,7 +148,7 @@ function renderCodeLine(line, keywords) {
148
148
  spans.push(_jsx(Text, { color: "green", children: tok }, key));
149
149
  }
150
150
  else if (keywords.includes(tok)) {
151
- spans.push(_jsx(Text, { color: "cyan", bold: true, children: tok }, key));
151
+ spans.push(_jsx(Text, { color: "#3da9fc", bold: true, children: tok }, key));
152
152
  }
153
153
  else {
154
154
  spans.push(_jsx(Text, { children: tok }, key));
@@ -260,7 +260,7 @@ function renderSpan(span, key) {
260
260
  case 'code':
261
261
  return _jsx(Text, { color: "green", children: span.text }, key);
262
262
  case 'link':
263
- return (_jsxs(Text, { children: [_jsx(Text, { color: "cyan", underline: true, children: span.text }), _jsx(Text, { dimColor: true, children: ` (${span.url})` })] }, key));
263
+ return (_jsxs(Text, { children: [_jsx(Text, { color: "#3da9fc", underline: true, children: span.text }), _jsx(Text, { dimColor: true, children: ` (${span.url})` })] }, key));
264
264
  }
265
265
  }
266
266
  //# sourceMappingURL=markdown-render.js.map
@@ -0,0 +1,240 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Leak L25 (2026-05-27) — 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
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { PERMISSION_MODES, PERMISSION_MODE_GLOSS, } from '../core/permissions/index.js';
5
+ /**
6
+ * Build the rendered rows from the canonical mode list. We map the
7
+ * 1-line gloss → row hint so `/permissions` and the picker stay in
8
+ * sync — a change to `PERMISSION_MODE_GLOSS` shows up here too.
9
+ */
10
+ const ITEMS = PERMISSION_MODES.map((mode) => ({
11
+ mode,
12
+ title: titleCase(mode),
13
+ hint: PERMISSION_MODE_GLOSS[mode],
14
+ }));
15
+ function titleCase(mode) {
16
+ return mode.charAt(0).toUpperCase() + mode.slice(1);
17
+ }
18
+ /**
19
+ * Resolve the initial cursor — prefer the explicit `initialIndex`
20
+ * (spec / test) when provided, otherwise highlight the row matching
21
+ * the active mode so the operator opens the picker with their
22
+ * current selection focused.
23
+ */
24
+ function resolveInitialIndex(currentMode, override) {
25
+ if (typeof override === 'number') {
26
+ return Math.min(Math.max(override, 0), ITEMS.length - 1);
27
+ }
28
+ const idx = ITEMS.findIndex((item) => item.mode === currentMode);
29
+ return idx >= 0 ? idx : 0;
30
+ }
31
+ export function PermissionsPicker(props) {
32
+ const [index, setIndex] = useState(resolveInitialIndex(props.currentMode, props.initialIndex));
33
+ useInput((input, key) => {
34
+ if (key.upArrow || input === 'k') {
35
+ setIndex((current) => (current === 0 ? ITEMS.length - 1 : current - 1));
36
+ return;
37
+ }
38
+ if (key.downArrow || input === 'j') {
39
+ setIndex((current) => (current === ITEMS.length - 1 ? 0 : current + 1));
40
+ return;
41
+ }
42
+ if (key.return) {
43
+ const selected = ITEMS[index];
44
+ if (selected)
45
+ props.onSelect(selected.mode);
46
+ return;
47
+ }
48
+ if (key.escape || input === 'q') {
49
+ props.onCancel();
50
+ return;
51
+ }
52
+ // Wave 7: number shortcuts mirror the canonical 6-mode order.
53
+ // Operators coming from Claude Code reach the same mode with the
54
+ // same digit (1=default, 2=acceptEdits, …, 6=bypassPermissions).
55
+ if (input === '1')
56
+ props.onSelect('default');
57
+ if (input === '2')
58
+ props.onSelect('acceptEdits');
59
+ if (input === '3')
60
+ props.onSelect('plan');
61
+ if (input === '4')
62
+ props.onSelect('auto');
63
+ if (input === '5')
64
+ props.onSelect('dontAsk');
65
+ if (input === '6')
66
+ props.onSelect('bypassPermissions');
67
+ });
68
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, children: "Permission mode" }), _jsx(Text, { dimColor: true, children: ` (current: ${props.currentMode} — ${props.sourceLabel})` })] }), props.firstRun ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "First time? Mode = Ask \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E. Use /permissions \u043A change later." }) })) : null, _jsx(Box, { marginTop: 1, flexDirection: "column", children: ITEMS.map((item, itemIndex) => {
69
+ const isSelected = itemIndex === index;
70
+ const isCurrent = item.mode === props.currentMode;
71
+ return (_jsx(PickerRow, { isSelected: isSelected, isCurrent: isCurrent, title: item.title, hint: item.hint }, item.mode));
72
+ }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: '↑/↓ select Enter confirm Esc cancel' }) })] }));
73
+ }
74
+ function PickerRow({ isSelected, isCurrent, title, hint, }) {
75
+ // Arrow glyph + padded title so highlighted and dim rows share
76
+ // column alignment. A trailing ` ●` marks the currently-effective
77
+ // mode (separate from cursor focus) so an operator instantly sees
78
+ // which row is "what I have now" vs "what I'm hovering".
79
+ const indicator = isSelected ? '▸ ' : ' ';
80
+ // Wave 7: longest title is `BypassPermissions` (17 chars). Bump the
81
+ // pad column так the gloss column stays aligned across all 6 rows.
82
+ const padded = title.padEnd(18, ' ');
83
+ const currentMarker = isCurrent ? ' ●' : ' ';
84
+ return (_jsxs(Text, { children: [_jsxs(Text, { color: isSelected ? 'cyan' : undefined, bold: isSelected, children: [indicator, padded] }), _jsx(Text, { color: isCurrent ? 'green' : undefined, children: currentMarker }), _jsx(Text, { dimColor: true, children: ` ${hint}` })] }));
85
+ }
86
+ //# sourceMappingURL=permissions-picker.js.map