@pugi/cli 0.1.0-beta.3 → 0.1.0-beta.31

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 (219) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -40
  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/core/agent-progress/cleanup.js +134 -0
  7. package/dist/core/agent-progress/schema.js +144 -0
  8. package/dist/core/agent-progress/writer.js +101 -0
  9. package/dist/core/artifact-chain/dispatcher.js +148 -0
  10. package/dist/core/artifact-chain/exporter.js +164 -0
  11. package/dist/core/artifact-chain/state.js +243 -0
  12. package/dist/core/artifact-chain/steps.js +169 -0
  13. package/dist/core/auth/env-provider.js +238 -0
  14. package/dist/core/auto-update/channels.js +122 -0
  15. package/dist/core/auto-update/checker.js +241 -0
  16. package/dist/core/auto-update/state.js +235 -0
  17. package/dist/core/bare-mode/index.js +107 -0
  18. package/dist/core/checkpoint/resumer.js +149 -0
  19. package/dist/core/checkpoint/rewinder.js +291 -0
  20. package/dist/core/compact/auto-trigger.js +96 -0
  21. package/dist/core/compact/buffer-rewriter.js +115 -0
  22. package/dist/core/compact/summarizer.js +208 -0
  23. package/dist/core/compact/token-counter.js +108 -0
  24. package/dist/core/consensus/diff-capture.js +73 -0
  25. package/dist/core/context/index.js +7 -0
  26. package/dist/core/context/markdown-traverse.js +255 -0
  27. package/dist/core/cost/rate-card.js +129 -0
  28. package/dist/core/cost/tracker.js +221 -0
  29. package/dist/core/denial-tracking/index.js +8 -0
  30. package/dist/core/denial-tracking/state.js +264 -0
  31. package/dist/core/diagnostics/probe-runner.js +93 -0
  32. package/dist/core/diagnostics/probes/api.js +46 -0
  33. package/dist/core/diagnostics/probes/auth.js +86 -0
  34. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  35. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  36. package/dist/core/diagnostics/probes/config.js +72 -0
  37. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  38. package/dist/core/diagnostics/probes/disk.js +81 -0
  39. package/dist/core/diagnostics/probes/git.js +65 -0
  40. package/dist/core/diagnostics/probes/mcp.js +75 -0
  41. package/dist/core/diagnostics/probes/node.js +59 -0
  42. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  43. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  44. package/dist/core/diagnostics/probes/session.js +74 -0
  45. package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
  46. package/dist/core/diagnostics/probes/workspace.js +63 -0
  47. package/dist/core/diagnostics/types.js +70 -0
  48. package/dist/core/dispatch/cache-cleanup.js +197 -0
  49. package/dist/core/dispatch/cache-handoff.js +295 -0
  50. package/dist/core/edits/dispatch.js +218 -2
  51. package/dist/core/edits/journal.js +199 -0
  52. package/dist/core/edits/layer-d-ast.js +557 -14
  53. package/dist/core/edits/verify-hook.js +273 -0
  54. package/dist/core/edits/worktree.js +111 -18
  55. package/dist/core/engine/anvil-client.js +115 -5
  56. package/dist/core/engine/budgets.js +89 -0
  57. package/dist/core/engine/context-prefix.js +155 -0
  58. package/dist/core/engine/intent.js +260 -0
  59. package/dist/core/engine/native-pugi.js +852 -210
  60. package/dist/core/engine/prompts.js +89 -6
  61. package/dist/core/engine/strip-internal-fields.js +124 -0
  62. package/dist/core/engine/tool-bridge.js +972 -33
  63. package/dist/core/feedback/queue.js +177 -0
  64. package/dist/core/feedback/submitter.js +145 -0
  65. package/dist/core/file-cache.js +113 -1
  66. package/dist/core/hooks/events.js +44 -0
  67. package/dist/core/hooks/index.js +15 -0
  68. package/dist/core/hooks/registry.js +213 -0
  69. package/dist/core/hooks/runner.js +236 -0
  70. package/dist/core/init/scaffold.js +195 -0
  71. package/dist/core/lsp/cache.js +105 -0
  72. package/dist/core/lsp/client.js +174 -29
  73. package/dist/core/lsp/language-detect.js +66 -0
  74. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  75. package/dist/core/mcp/client.js +75 -6
  76. package/dist/core/mcp/http-server.js +553 -0
  77. package/dist/core/mcp/permission.js +190 -0
  78. package/dist/core/mcp/registry.js +24 -2
  79. package/dist/core/mcp/server-tools.js +219 -0
  80. package/dist/core/mcp/server.js +397 -0
  81. package/dist/core/memory/dual-write.js +416 -0
  82. package/dist/core/memory/dual-write.spec.js +297 -0
  83. package/dist/core/memory/phase1-kinds.js +20 -0
  84. package/dist/core/memory-sync/queue.js +158 -0
  85. package/dist/core/memory-sync/queue.spec.js +105 -0
  86. package/dist/core/onboarding/marker.js +111 -0
  87. package/dist/core/onboarding/telemetry-state.js +108 -0
  88. package/dist/core/output-style/presets.js +176 -0
  89. package/dist/core/output-style/state.js +185 -0
  90. package/dist/core/permissions/gate.js +187 -0
  91. package/dist/core/permissions/index.js +18 -0
  92. package/dist/core/permissions/mode.js +102 -0
  93. package/dist/core/permissions/state.js +215 -0
  94. package/dist/core/permissions/tool-class.js +93 -0
  95. package/dist/core/prd-check/parser.js +215 -0
  96. package/dist/core/prd-check/reporter.js +127 -0
  97. package/dist/core/prd-check/session-review.js +557 -0
  98. package/dist/core/prd-check/verifiers.js +223 -0
  99. package/dist/core/pugi-md/context-injector.js +76 -0
  100. package/dist/core/pugi-md/walk-up.js +207 -0
  101. package/dist/core/release-notes/parser.js +241 -0
  102. package/dist/core/release-notes/state.js +116 -0
  103. package/dist/core/repl/codebase-survey.js +308 -0
  104. package/dist/core/repl/history.js +11 -1
  105. package/dist/core/repl/init-interview.js +457 -0
  106. package/dist/core/repl/model-pricing.js +135 -0
  107. package/dist/core/repl/onboarding-state.js +297 -0
  108. package/dist/core/repl/session.js +1529 -30
  109. package/dist/core/repl/slash-commands.js +361 -13
  110. package/dist/core/repl/store/session-store.js +31 -2
  111. package/dist/core/repl/workspace-context.js +22 -0
  112. package/dist/core/repo-map/build.js +125 -0
  113. package/dist/core/repo-map/cache.js +185 -0
  114. package/dist/core/repo-map/extractor.js +254 -0
  115. package/dist/core/repo-map/formatter.js +145 -0
  116. package/dist/core/repo-map/scanner.js +211 -0
  117. package/dist/core/retry-budget/budget.js +284 -0
  118. package/dist/core/retry-budget/index.js +5 -0
  119. package/dist/core/session.js +44 -0
  120. package/dist/core/settings.js +80 -0
  121. package/dist/core/share/formatter.js +271 -0
  122. package/dist/core/share/redactor.js +221 -0
  123. package/dist/core/share/uploader.js +267 -0
  124. package/dist/core/skills/defaults.js +457 -0
  125. package/dist/core/subagents/dispatcher-real.js +600 -0
  126. package/dist/core/subagents/dispatcher.js +113 -24
  127. package/dist/core/subagents/index.js +18 -5
  128. package/dist/core/subagents/isolation-matrix.js +213 -0
  129. package/dist/core/subagents/spawn.js +19 -4
  130. package/dist/core/telemetry/emitter.js +229 -0
  131. package/dist/core/telemetry/queue.js +251 -0
  132. package/dist/core/theme/context.js +91 -0
  133. package/dist/core/theme/presets.js +228 -0
  134. package/dist/core/theme/state.js +181 -0
  135. package/dist/core/todos/invariant.js +10 -0
  136. package/dist/core/todos/state.js +177 -0
  137. package/dist/core/transport/version-interceptor.js +166 -0
  138. package/dist/core/vim/keymap.js +288 -0
  139. package/dist/core/vim/state.js +92 -0
  140. package/dist/index.js +28 -0
  141. package/dist/runtime/bootstrap.js +190 -0
  142. package/dist/runtime/cli.js +2603 -278
  143. package/dist/runtime/commands/chain.js +489 -0
  144. package/dist/runtime/commands/compact.js +297 -0
  145. package/dist/runtime/commands/cost.js +199 -0
  146. package/dist/runtime/commands/delegate.js +312 -0
  147. package/dist/runtime/commands/dispatch.js +126 -0
  148. package/dist/runtime/commands/doctor.js +390 -0
  149. package/dist/runtime/commands/feedback.js +184 -0
  150. package/dist/runtime/commands/hooks.js +184 -0
  151. package/dist/runtime/commands/lsp.js +212 -28
  152. package/dist/runtime/commands/mcp.js +824 -0
  153. package/dist/runtime/commands/memory.js +508 -0
  154. package/dist/runtime/commands/memory.spec.js +174 -0
  155. package/dist/runtime/commands/model.js +237 -0
  156. package/dist/runtime/commands/onboarding.js +275 -0
  157. package/dist/runtime/commands/patch.js +17 -0
  158. package/dist/runtime/commands/permissions.js +87 -0
  159. package/dist/runtime/commands/plan.js +143 -0
  160. package/dist/runtime/commands/prd-check.js +285 -0
  161. package/dist/runtime/commands/release-notes.js +229 -0
  162. package/dist/runtime/commands/repo-map.js +95 -0
  163. package/dist/runtime/commands/report.js +299 -0
  164. package/dist/runtime/commands/resume.js +118 -0
  165. package/dist/runtime/commands/review-consensus.js +17 -2
  166. package/dist/runtime/commands/rewind.js +333 -0
  167. package/dist/runtime/commands/roster.js +117 -0
  168. package/dist/runtime/commands/sessions.js +163 -0
  169. package/dist/runtime/commands/share.js +316 -0
  170. package/dist/runtime/commands/status.js +178 -0
  171. package/dist/runtime/commands/stickers.js +82 -0
  172. package/dist/runtime/commands/style.js +194 -0
  173. package/dist/runtime/commands/theme.js +196 -0
  174. package/dist/runtime/commands/update.js +289 -0
  175. package/dist/runtime/commands/vim.js +140 -0
  176. package/dist/runtime/commands/worktree.js +50 -6
  177. package/dist/runtime/headless.js +543 -0
  178. package/dist/runtime/load-hooks-or-exit.js +71 -0
  179. package/dist/runtime/plan-decompose.js +531 -0
  180. package/dist/runtime/version.js +65 -0
  181. package/dist/tools/agent-tool.js +229 -0
  182. package/dist/tools/apply-patch.js +281 -39
  183. package/dist/tools/ask-user-question.js +213 -0
  184. package/dist/tools/ask-user.js +115 -0
  185. package/dist/tools/file-tools.js +85 -14
  186. package/dist/tools/mcp-tool.js +260 -0
  187. package/dist/tools/multi-edit.js +361 -0
  188. package/dist/tools/registry.js +30 -2
  189. package/dist/tools/skill-tool.js +96 -0
  190. package/dist/tools/tasks.js +208 -0
  191. package/dist/tools/todo-write.js +184 -0
  192. package/dist/tools/web-fetch.js +147 -2
  193. package/dist/tools/web-search.js +458 -0
  194. package/dist/tui/agent-progress-card.js +111 -0
  195. package/dist/tui/agent-tree.js +10 -0
  196. package/dist/tui/ask-modal.js +2 -2
  197. package/dist/tui/ask-user-question-prompt.js +192 -0
  198. package/dist/tui/compact-banner.js +81 -0
  199. package/dist/tui/conversation-pane.js +82 -8
  200. package/dist/tui/cost-table.js +111 -0
  201. package/dist/tui/doctor-table.js +46 -0
  202. package/dist/tui/feedback-prompt.js +156 -0
  203. package/dist/tui/input-box.js +46 -2
  204. package/dist/tui/markdown-render.js +4 -4
  205. package/dist/tui/onboarding-wizard.js +240 -0
  206. package/dist/tui/repl-render.js +293 -35
  207. package/dist/tui/repl-splash.js +2 -2
  208. package/dist/tui/repl.js +45 -13
  209. package/dist/tui/splash.js +1 -1
  210. package/dist/tui/status-bar.js +94 -16
  211. package/dist/tui/status-table.js +7 -0
  212. package/dist/tui/stickers-art.js +136 -0
  213. package/dist/tui/style-table.js +28 -0
  214. package/dist/tui/theme-table.js +29 -0
  215. package/dist/tui/tool-stream-pane.js +7 -0
  216. package/dist/tui/update-banner.js +20 -2
  217. package/dist/tui/vim-input.js +267 -0
  218. package/docs/examples/codegraph.mcp.json +10 -0
  219. package/package.json +9 -6
@@ -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.
@@ -375,10 +388,41 @@ export function InputBox(props) {
375
388
  if (key.escape) {
376
389
  if (paletteOpen) {
377
390
  // Close the palette without clearing the buffer so the operator
378
- // can still send `/help` as plain text if they want.
391
+ // can still send `/help` as plain text if they want. Palette
392
+ // takes precedence over walkback because the operator's mental
393
+ // model is "Esc closes the visible overlay first".
379
394
  setPaletteSuppressed(true);
395
+ setLastEscapeAt(undefined);
396
+ return;
397
+ }
398
+ // Wave 6 BT 8: Esc-Esc walkback. Two presses within
399
+ // ESCAPE_DOUBLE_TAP_MS step the conversation back by one turn.
400
+ // First press still clears the buffer (legacy behaviour for the
401
+ // single-Esc cancel UX); the second press calls the host's
402
+ // walkback handler. Buffer-clear on the first press is what makes
403
+ // the double-tap feel "free" - the operator did not have to
404
+ // memorise a new chord; they just have to keep pressing.
405
+ const tEsc = now();
406
+ const withinEscapeWindow = typeof lastEscapeAt === 'number'
407
+ && tEsc - lastEscapeAt <= ESCAPE_DOUBLE_TAP_MS;
408
+ if (withinEscapeWindow && props.onWalkback) {
409
+ // Second tap inside the window. Buffer was already cleared on
410
+ // the first press, so the host sees a clean input box AND the
411
+ // walkback result. We clear the window so a third tap restarts
412
+ // the cycle (no run-on walkbacks from a stuck Esc key).
413
+ const verdict = props.onWalkback();
414
+ setLastEscapeAt(undefined);
415
+ if (verdict !== 'walked-back') {
416
+ // Host refused (dispatch in flight, no turns to pop). The
417
+ // host owns the refusal copy via its own writeOutput path;
418
+ // we do not double-message here.
419
+ return;
420
+ }
380
421
  return;
381
422
  }
423
+ // First Esc (or no walkback wired). Arm the window + clear the
424
+ // buffer per the long-standing single-Esc cancel contract.
425
+ setLastEscapeAt(tEsc);
382
426
  setLine('');
383
427
  setCursor(0);
384
428
  setHistoryIndex(-1);
@@ -499,7 +543,7 @@ export function InputBox(props) {
499
543
  : Math.min(paletteIndex, paletteView.rows.length - 1);
500
544
  const divider = '─'.repeat(innerWidth);
501
545
  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' }) })] }));
546
+ 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 }), 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' }) })] }));
503
547
  }
504
548
  /**
505
549
  * 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