gsdd-cli 0.18.5 → 0.19.1

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 (101) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +610 -608
  3. package/agents/DISTILLATION.md +421 -421
  4. package/agents/README.md +62 -62
  5. package/agents/approach-explorer.md +370 -361
  6. package/agents/debugger.md +82 -82
  7. package/agents/executor.md +473 -394
  8. package/agents/integration-checker.md +318 -318
  9. package/agents/mapper.md +103 -103
  10. package/agents/planner.md +342 -313
  11. package/agents/researcher.md +84 -84
  12. package/agents/roadmapper.md +296 -296
  13. package/agents/synthesizer.md +236 -236
  14. package/agents/verifier.md +337 -337
  15. package/bin/adapters/agents.mjs +34 -34
  16. package/bin/adapters/claude.mjs +193 -191
  17. package/bin/adapters/codex.mjs +85 -85
  18. package/bin/adapters/index.mjs +20 -20
  19. package/bin/adapters/opencode.mjs +280 -278
  20. package/bin/gsdd.mjs +123 -116
  21. package/bin/lib/cli-utils.mjs +28 -28
  22. package/bin/lib/evidence-contract.mjs +325 -112
  23. package/bin/lib/file-ops.mjs +186 -144
  24. package/bin/lib/health-truth.mjs +196 -178
  25. package/bin/lib/health.mjs +246 -226
  26. package/bin/lib/init-flow.mjs +247 -231
  27. package/bin/lib/init-prompts.mjs +248 -247
  28. package/bin/lib/init-runtime.mjs +193 -190
  29. package/bin/lib/init.mjs +17 -17
  30. package/bin/lib/lifecycle-preflight.mjs +760 -326
  31. package/bin/lib/lifecycle-state.mjs +356 -267
  32. package/bin/lib/manifest.mjs +116 -114
  33. package/bin/lib/models.mjs +411 -411
  34. package/bin/lib/phase.mjs +365 -358
  35. package/bin/lib/plan-constants.mjs +35 -30
  36. package/bin/lib/provenance.mjs +109 -106
  37. package/bin/lib/rendering.mjs +119 -83
  38. package/bin/lib/runtime-freshness.mjs +214 -214
  39. package/bin/lib/session-fingerprint.mjs +91 -14
  40. package/bin/lib/templates.mjs +225 -224
  41. package/bin/lib/workspace-root.mjs +2 -1
  42. package/distilled/DESIGN.md +2461 -2323
  43. package/distilled/EVIDENCE-INDEX.md +418 -392
  44. package/distilled/README.md +196 -193
  45. package/distilled/SKILL.md +86 -85
  46. package/distilled/templates/agents.block.md +21 -21
  47. package/distilled/templates/agents.md +6 -6
  48. package/distilled/templates/approach.md +272 -232
  49. package/distilled/templates/auth-matrix.md +78 -78
  50. package/distilled/templates/brownfield-change/CHANGE.md +99 -0
  51. package/distilled/templates/brownfield-change/HANDOFF.md +38 -0
  52. package/distilled/templates/brownfield-change/VERIFICATION.md +56 -0
  53. package/distilled/templates/codebase/architecture.md +110 -110
  54. package/distilled/templates/codebase/concerns.md +95 -95
  55. package/distilled/templates/codebase/conventions.md +193 -193
  56. package/distilled/templates/codebase/stack.md +96 -96
  57. package/distilled/templates/delegates/approach-explorer.md +28 -25
  58. package/distilled/templates/delegates/mapper-arch.md +26 -26
  59. package/distilled/templates/delegates/mapper-concerns.md +27 -27
  60. package/distilled/templates/delegates/mapper-quality.md +28 -28
  61. package/distilled/templates/delegates/mapper-tech.md +25 -25
  62. package/distilled/templates/delegates/plan-checker.md +78 -68
  63. package/distilled/templates/delegates/researcher-architecture.md +30 -30
  64. package/distilled/templates/delegates/researcher-features.md +30 -30
  65. package/distilled/templates/delegates/researcher-pitfalls.md +30 -30
  66. package/distilled/templates/delegates/researcher-stack.md +30 -30
  67. package/distilled/templates/delegates/researcher-synthesizer.md +31 -31
  68. package/distilled/templates/research/architecture.md +57 -57
  69. package/distilled/templates/research/features.md +23 -23
  70. package/distilled/templates/research/pitfalls.md +46 -46
  71. package/distilled/templates/research/stack.md +45 -45
  72. package/distilled/templates/research/summary.md +67 -67
  73. package/distilled/templates/roadmap.md +74 -62
  74. package/distilled/templates/spec.md +110 -110
  75. package/distilled/workflows/audit-milestone.md +303 -271
  76. package/distilled/workflows/complete-milestone.md +349 -332
  77. package/distilled/workflows/execute.md +457 -450
  78. package/distilled/workflows/map-codebase.md +253 -253
  79. package/distilled/workflows/new-milestone.md +242 -238
  80. package/distilled/workflows/new-project.md +398 -398
  81. package/distilled/workflows/pause.md +160 -156
  82. package/distilled/workflows/plan-milestone-gaps.md +183 -183
  83. package/distilled/workflows/plan.md +454 -448
  84. package/distilled/workflows/progress.md +227 -223
  85. package/distilled/workflows/quick.md +351 -347
  86. package/distilled/workflows/resume.md +220 -212
  87. package/distilled/workflows/verify-work.md +260 -260
  88. package/distilled/workflows/verify.md +431 -429
  89. package/docs/BROWNFIELD-PROOF.md +95 -95
  90. package/docs/RUNTIME-SUPPORT.md +80 -69
  91. package/docs/USER-GUIDE.md +394 -386
  92. package/docs/VERIFICATION-DISCIPLINE.md +59 -59
  93. package/docs/claude/context-monitor.md +98 -98
  94. package/docs/proof/consumer-node-cli/README.md +37 -37
  95. package/docs/proof/consumer-node-cli/ROADMAP.md +14 -14
  96. package/docs/proof/consumer-node-cli/SPEC.md +17 -17
  97. package/docs/proof/consumer-node-cli/brief.md +9 -9
  98. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-PLAN.md +34 -34
  99. package/docs/proof/consumer-node-cli/phases/01-foundation/01-01-SUMMARY.md +10 -10
  100. package/docs/proof/consumer-node-cli/phases/01-foundation/01-VERIFICATION.md +30 -30
  101. package/package.json +62 -61
@@ -1,247 +1,248 @@
1
- import * as readline from 'readline';
2
- import { DEFAULT_GIT_PROTOCOL, resolveCost, resolveRigor } from './models.mjs';
3
- import { buildRuntimeChoices, INIT_VERSION, resolveWizardAdapterTargets } from './init-runtime.mjs';
4
-
5
- const ANSI = {
6
- reset: '\x1b[0m',
7
- dim: '\x1b[2m',
8
- bold: '\x1b[1m',
9
- cyan: '\x1b[36m',
10
- green: '\x1b[32m',
11
- };
12
-
13
- export function createInitPromptApi({ input = process.stdin, output = process.stdout } = {}) {
14
- return {
15
- async runInitWizard({ cwd, adapters }) {
16
- output.write(`${ANSI.bold}${ANSI.cyan}GSDD Install Wizard${ANSI.reset}\n`);
17
- output.write(`${ANSI.dim}Portable skills are always installed. Select the runtimes you want GSDD to feel native in.${ANSI.reset}\n\n`);
18
-
19
- const runtimeChoices = buildRuntimeChoices(adapters);
20
- const selectedRuntimes = await promptMultiSelect({
21
- input,
22
- output,
23
- title: 'Step 1 of 3 - Select runtimes',
24
- hint: 'Space toggles, Enter confirms.',
25
- choices: runtimeChoices,
26
- });
27
- const installGovernance = await promptConfirm({
28
- input,
29
- output,
30
- title: 'Step 2 of 3 - Repo-wide AGENTS.md governance',
31
- prompt: 'Install repo-wide AGENTS.md rules?',
32
- defaultValue: false,
33
- details: [
34
- 'Why care: consistent behavioral discipline across sessions and mixed-runtime teams.',
35
- 'Why skip it: it writes to the repo root, and your selected runtimes already discover .agents/skills/ natively.',
36
- ],
37
- });
38
- const config = await promptForConfig(cwd, { input, output });
39
- return {
40
- selectedRuntimes,
41
- adapterTargets: resolveWizardAdapterTargets(selectedRuntimes, installGovernance),
42
- config,
43
- };
44
- },
45
- promptForConfig(cwd) {
46
- return promptForConfig(cwd, { input, output });
47
- },
48
- };
49
- }
50
-
51
- export async function promptForConfig(cwd, { input = process.stdin, output = process.stdout } = {}) {
52
- output.write(`\n${ANSI.bold}Step 3 of 3 - Configure planning defaults${ANSI.reset}\n`);
53
-
54
- const rigor = await promptSingleSelect({
55
- input,
56
- output,
57
- title: 'Rigor',
58
- choices: [
59
- { value: 'quick', label: 'quick', description: 'Faster setup with lighter planning rigor' },
60
- { value: 'balanced', label: 'balanced', description: 'Recommended default for most projects' },
61
- { value: 'thorough', label: 'thorough', description: 'Maximum research and plan review rigor' },
62
- ],
63
- defaultIndex: 1,
64
- });
65
- const cost = await promptSingleSelect({
66
- input,
67
- output,
68
- title: 'Cost',
69
- choices: [
70
- { value: 'budget', label: 'budget', description: 'Lower-cost model profile and no parallelization' },
71
- { value: 'balanced', label: 'balanced', description: 'Recommended cost/quality tradeoff' },
72
- { value: 'quality', label: 'quality', description: 'Maximum quality with parallel work enabled' },
73
- ],
74
- defaultIndex: 1,
75
- });
76
- const commitDocs = await promptSingleSelect({
77
- input,
78
- output,
79
- title: 'Planning docs in git',
80
- choices: [
81
- { value: true, label: 'yes', description: 'Track .planning/ in git.' },
82
- { value: false, label: 'no', description: 'Keep .planning/ local only.' },
83
- ],
84
- defaultIndex: 0,
85
- });
86
-
87
- const rigorConfig = resolveRigor(rigor);
88
- const costConfig = resolveCost(cost);
89
-
90
- return {
91
- ...rigorConfig,
92
- ...costConfig,
93
- commitDocs,
94
- gitProtocol: { ...DEFAULT_GIT_PROTOCOL },
95
- initVersion: INIT_VERSION,
96
- };
97
- }
98
-
99
- export function yesNoChoices(prompt, defaultYes) {
100
- return [
101
- { value: true, label: 'yes', description: `${prompt} Yes.` },
102
- { value: false, label: 'no', description: `${prompt} No.` },
103
- ].sort((a, b) => {
104
- if (defaultYes) return a.value === true ? -1 : 1;
105
- return a.value === false ? -1 : 1;
106
- });
107
- }
108
-
109
- export async function promptConfirm({ input, output, title, prompt, defaultValue, details = [] }) {
110
- if (typeof input.resume === 'function') input.resume();
111
- output.write(`\n${ANSI.bold}${title}${ANSI.reset}\n`);
112
- for (const detail of details) {
113
- output.write(` ${ANSI.dim}${detail}${ANSI.reset}\n`);
114
- }
115
- const rl = readline.createInterface({ input, output });
116
- const suffix = defaultValue ? '[Y/n]' : '[y/N]';
117
- try {
118
- const answer = await new Promise((resolve) => rl.question(` ${prompt} ${suffix}: `, resolve));
119
- const normalized = answer.trim().toLowerCase();
120
- if (!normalized) return defaultValue;
121
- return normalized === 'y' || normalized === 'yes';
122
- } finally {
123
- rl.close();
124
- }
125
- }
126
-
127
- export async function promptSingleSelect({ input, output, title, choices, defaultIndex = 0 }) {
128
- const selected = await promptChoiceList({
129
- input,
130
- output,
131
- title,
132
- choices: choices.map((choice, index) => ({
133
- ...choice,
134
- selected: index === defaultIndex,
135
- detected: false,
136
- })),
137
- multi: false,
138
- });
139
- return selected[0];
140
- }
141
-
142
- export async function promptMultiSelect({ input, output, title, hint, choices }) {
143
- return promptChoiceList({ input, output, title, hint, choices, multi: true });
144
- }
145
-
146
- export async function promptChoiceList({ input, output, title, hint = 'Use arrows to move. Enter confirms.', choices, multi }) {
147
- if (typeof input.resume === 'function') input.resume();
148
- readline.emitKeypressEvents(input);
149
- const previousRawMode = typeof input.isRaw === 'boolean' ? input.isRaw : false;
150
- if (typeof input.setRawMode === 'function') input.setRawMode(true);
151
-
152
- let cursor = Math.max(0, choices.findIndex((choice) => choice.selected));
153
- let renderedLines = 0;
154
-
155
- const selectCursor = () => {
156
- if (multi) return;
157
- for (const choice of choices) choice.selected = false;
158
- choices[cursor].selected = true;
159
- };
160
-
161
- const measureLines = (text) => {
162
- const columns = Math.max(20, Number(output.columns) || 80);
163
- const visible = stripAnsi(text);
164
- return visible.split('\n').reduce((total, line) => {
165
- const width = Math.max(1, line.length);
166
- return total + Math.ceil(width / columns);
167
- }, 0);
168
- };
169
-
170
- const render = () => {
171
- if (renderedLines > 0) {
172
- readline.moveCursor(output, 0, -renderedLines);
173
- }
174
- readline.cursorTo(output, 0);
175
- readline.clearScreenDown(output);
176
- const lines = [`${ANSI.bold}${title}${ANSI.reset}`];
177
- if (hint) lines.push(`${ANSI.dim}${hint}${ANSI.reset}`);
178
- lines.push('');
179
- for (let i = 0; i < choices.length; i++) {
180
- const choice = choices[i];
181
- const pointer = i === cursor ? `${ANSI.green}>${ANSI.reset}` : ' ';
182
- const mark = choice.selected ? `${ANSI.green}[x]${ANSI.reset}` : '[ ]';
183
- const detected = choice.detected ? ` ${ANSI.dim}(detected)${ANSI.reset}` : '';
184
- lines.push(`${pointer} ${mark} ${choice.label}${detected}`);
185
- lines.push(` ${ANSI.dim}${choice.description}${ANSI.reset}`);
186
- }
187
-
188
- renderedLines = 0;
189
- for (const line of lines) {
190
- output.write(`${line}\n`);
191
- renderedLines += measureLines(line);
192
- }
193
- };
194
-
195
- render();
196
-
197
- const values = await new Promise((resolve, reject) => {
198
- const cleanup = () => {
199
- input.off('keypress', onKeypress);
200
- if (typeof input.setRawMode === 'function') input.setRawMode(previousRawMode);
201
- };
202
-
203
- const onKeypress = (_, key = {}) => {
204
- if (key.ctrl && key.name === 'c') {
205
- cleanup();
206
- reject(new Error('Prompt cancelled by user'));
207
- return;
208
- }
209
- if (key.name === 'up') {
210
- cursor = cursor === 0 ? choices.length - 1 : cursor - 1;
211
- selectCursor();
212
- render();
213
- return;
214
- }
215
- if (key.name === 'down') {
216
- cursor = cursor === choices.length - 1 ? 0 : cursor + 1;
217
- selectCursor();
218
- render();
219
- return;
220
- }
221
- if (key.name === 'space') {
222
- if (multi) {
223
- choices[cursor].selected = !choices[cursor].selected;
224
- } else {
225
- for (const choice of choices) choice.selected = false;
226
- choices[cursor].selected = true;
227
- }
228
- render();
229
- return;
230
- }
231
- if (key.name === 'return') {
232
- selectCursor();
233
- cleanup();
234
- resolve(choices.filter((choice) => choice.selected).map((choice) => choice.value ?? choice.id));
235
- }
236
- };
237
-
238
- input.on('keypress', onKeypress);
239
- });
240
-
241
- output.write('\n');
242
- return values;
243
- }
244
-
245
- export function stripAnsi(text) {
246
- return String(text).replace(/\x1B\[[0-9;]*[A-Za-z]/g, '');
247
- }
1
+ import * as readline from 'readline';
2
+ import { DEFAULT_GIT_PROTOCOL, resolveCost, resolveRigor } from './models.mjs';
3
+ import { buildRuntimeChoices, INIT_VERSION, resolveWizardAdapterTargets } from './init-runtime.mjs';
4
+
5
+ const ANSI = {
6
+ reset: '\x1b[0m',
7
+ dim: '\x1b[2m',
8
+ bold: '\x1b[1m',
9
+ cyan: '\x1b[36m',
10
+ green: '\x1b[32m',
11
+ };
12
+
13
+ export function createInitPromptApi({ input = process.stdin, output = process.stdout } = {}) {
14
+ return {
15
+ async runInitWizard({ cwd, adapters }) {
16
+ output.write(`${ANSI.bold}${ANSI.cyan}GSDD Install Wizard${ANSI.reset}\n`);
17
+ output.write(`${ANSI.dim}Portable skills are always installed. Select the runtimes you want GSDD to feel native in.${ANSI.reset}\n\n`);
18
+
19
+ const runtimeChoices = buildRuntimeChoices(adapters);
20
+ const selectedRuntimes = await promptMultiSelect({
21
+ input,
22
+ output,
23
+ title: 'Step 1 of 3 - Select runtimes',
24
+ hint: 'Space toggles, Enter confirms.',
25
+ choices: runtimeChoices,
26
+ });
27
+ const installGovernance = await promptConfirm({
28
+ input,
29
+ output,
30
+ title: 'Step 2 of 3 - Repo-wide AGENTS.md governance',
31
+ prompt: 'Install repo-wide AGENTS.md rules?',
32
+ defaultValue: false,
33
+ details: [
34
+ 'Why care: consistent behavioral discipline across sessions and mixed-runtime teams.',
35
+ 'Why skip it: it writes to the repo root, and your selected runtimes already discover .agents/skills/ natively.',
36
+ ],
37
+ });
38
+ const config = await promptForConfig(cwd, { input, output });
39
+ return {
40
+ selectedRuntimes,
41
+ adapterTargets: resolveWizardAdapterTargets(selectedRuntimes, installGovernance),
42
+ config,
43
+ };
44
+ },
45
+ promptForConfig(cwd) {
46
+ return promptForConfig(cwd, { input, output });
47
+ },
48
+ };
49
+ }
50
+
51
+ export async function promptForConfig(cwd, { input = process.stdin, output = process.stdout } = {}) {
52
+ output.write(`\n${ANSI.bold}Step 3 of 3 - Configure planning defaults${ANSI.reset}\n`);
53
+
54
+ const rigor = await promptSingleSelect({
55
+ input,
56
+ output,
57
+ title: 'Rigor',
58
+ choices: [
59
+ { value: 'quick', label: 'quick', description: 'Faster setup with lighter planning rigor' },
60
+ { value: 'balanced', label: 'balanced', description: 'Recommended default for most projects' },
61
+ { value: 'thorough', label: 'thorough', description: 'Maximum research and plan review rigor' },
62
+ ],
63
+ defaultIndex: 1,
64
+ });
65
+ const cost = await promptSingleSelect({
66
+ input,
67
+ output,
68
+ title: 'Cost',
69
+ choices: [
70
+ { value: 'budget', label: 'budget', description: 'Lower-cost model profile and no parallelization' },
71
+ { value: 'balanced', label: 'balanced', description: 'Recommended cost/quality tradeoff' },
72
+ { value: 'quality', label: 'quality', description: 'Maximum quality with parallel work enabled' },
73
+ ],
74
+ defaultIndex: 1,
75
+ });
76
+ const commitDocs = await promptSingleSelect({
77
+ input,
78
+ output,
79
+ title: 'Planning docs in git',
80
+ choices: [
81
+ { value: true, label: 'yes', description: 'Track .planning/ in git.' },
82
+ { value: false, label: 'no', description: 'Keep .planning/ local only.' },
83
+ ],
84
+ defaultIndex: 0,
85
+ });
86
+
87
+ const rigorConfig = resolveRigor(rigor);
88
+ const costConfig = resolveCost(cost);
89
+
90
+ return {
91
+ ...rigorConfig,
92
+ ...costConfig,
93
+ commitDocs,
94
+ gitProtocol: { ...DEFAULT_GIT_PROTOCOL },
95
+ initVersion: INIT_VERSION,
96
+ };
97
+ }
98
+
99
+ export function yesNoChoices(prompt, defaultYes) {
100
+ return [
101
+ { value: true, label: 'yes', description: `${prompt} Yes.` },
102
+ { value: false, label: 'no', description: `${prompt} No.` },
103
+ ].sort((a, b) => {
104
+ if (defaultYes) return a.value === true ? -1 : 1;
105
+ return a.value === false ? -1 : 1;
106
+ });
107
+ }
108
+
109
+ export async function promptConfirm({ input, output, title, prompt, defaultValue, details = [] }) {
110
+ if (typeof input.resume === 'function') input.resume();
111
+ output.write(`\n${ANSI.bold}${title}${ANSI.reset}\n`);
112
+ for (const detail of details) {
113
+ output.write(` ${ANSI.dim}${detail}${ANSI.reset}\n`);
114
+ }
115
+ const rl = readline.createInterface({ input, output });
116
+ const suffix = defaultValue ? '[Y/n]' : '[y/N]';
117
+ try {
118
+ const answer = await new Promise((resolve) => rl.question(` ${prompt} ${suffix}: `, resolve));
119
+ const normalized = answer.trim().toLowerCase();
120
+ if (!normalized) return defaultValue;
121
+ return normalized === 'y' || normalized === 'yes';
122
+ } finally {
123
+ rl.close();
124
+ }
125
+ }
126
+
127
+ export async function promptSingleSelect({ input, output, title, choices, defaultIndex = 0 }) {
128
+ const selected = await promptChoiceList({
129
+ input,
130
+ output,
131
+ title,
132
+ choices: choices.map((choice, index) => ({
133
+ ...choice,
134
+ selected: index === defaultIndex,
135
+ detected: false,
136
+ })),
137
+ multi: false,
138
+ });
139
+ return selected[0];
140
+ }
141
+
142
+ export async function promptMultiSelect({ input, output, title, hint, choices }) {
143
+ return promptChoiceList({ input, output, title, hint, choices, multi: true });
144
+ }
145
+
146
+ export async function promptChoiceList({ input, output, title, hint = 'Use arrows to move. Enter confirms.', choices, multi }) {
147
+ if (typeof input.resume === 'function') input.resume();
148
+ readline.emitKeypressEvents(input);
149
+ const previousRawMode = typeof input.isRaw === 'boolean' ? input.isRaw : false;
150
+ if (typeof input.setRawMode === 'function') input.setRawMode(true);
151
+
152
+ let cursor = Math.max(0, choices.findIndex((choice) => choice.selected));
153
+ let renderedLines = 0;
154
+
155
+ const selectCursor = () => {
156
+ if (multi) return;
157
+ for (const choice of choices) choice.selected = false;
158
+ choices[cursor].selected = true;
159
+ };
160
+
161
+ const measureLines = (text) => {
162
+ const columns = Math.max(20, Number(output.columns) || 80);
163
+ const visible = stripAnsi(text);
164
+ return visible.split('\n').reduce((total, line) => {
165
+ const width = Math.max(1, line.length);
166
+ return total + Math.ceil(width / columns);
167
+ }, 0);
168
+ };
169
+
170
+ const render = () => {
171
+ if (renderedLines > 0) {
172
+ readline.moveCursor(output, 0, -renderedLines);
173
+ }
174
+ readline.cursorTo(output, 0);
175
+ readline.clearScreenDown(output);
176
+ const lines = [`${ANSI.bold}${title}${ANSI.reset}`];
177
+ if (hint) lines.push(`${ANSI.dim}${hint}${ANSI.reset}`);
178
+ lines.push('');
179
+ for (let i = 0; i < choices.length; i++) {
180
+ const choice = choices[i];
181
+ const pointer = i === cursor ? `${ANSI.green}>${ANSI.reset}` : ' ';
182
+ const mark = choice.selected ? `${ANSI.green}[x]${ANSI.reset}` : '[ ]';
183
+ const detected = choice.detected ? ` ${ANSI.dim}(detected)${ANSI.reset}` : '';
184
+ lines.push(`${pointer} ${mark} ${choice.label}${detected}`);
185
+ lines.push(` ${ANSI.dim}${choice.description}${ANSI.reset}`);
186
+ }
187
+
188
+ renderedLines = 0;
189
+ for (const line of lines) {
190
+ output.write(`${line}\n`);
191
+ renderedLines += measureLines(line);
192
+ }
193
+ };
194
+
195
+ render();
196
+
197
+ const values = await new Promise((resolve, reject) => {
198
+ const cleanup = () => {
199
+ input.off('keypress', onKeypress);
200
+ if (typeof input.setRawMode === 'function') input.setRawMode(previousRawMode);
201
+ if (typeof input.pause === 'function') input.pause();
202
+ };
203
+
204
+ const onKeypress = (_, key = {}) => {
205
+ if (key.ctrl && key.name === 'c') {
206
+ cleanup();
207
+ reject(new Error('Prompt cancelled by user'));
208
+ return;
209
+ }
210
+ if (key.name === 'up') {
211
+ cursor = cursor === 0 ? choices.length - 1 : cursor - 1;
212
+ selectCursor();
213
+ render();
214
+ return;
215
+ }
216
+ if (key.name === 'down') {
217
+ cursor = cursor === choices.length - 1 ? 0 : cursor + 1;
218
+ selectCursor();
219
+ render();
220
+ return;
221
+ }
222
+ if (key.name === 'space') {
223
+ if (multi) {
224
+ choices[cursor].selected = !choices[cursor].selected;
225
+ } else {
226
+ for (const choice of choices) choice.selected = false;
227
+ choices[cursor].selected = true;
228
+ }
229
+ render();
230
+ return;
231
+ }
232
+ if (key.name === 'return') {
233
+ selectCursor();
234
+ cleanup();
235
+ resolve(choices.filter((choice) => choice.selected).map((choice) => choice.value ?? choice.id));
236
+ }
237
+ };
238
+
239
+ input.on('keypress', onKeypress);
240
+ });
241
+
242
+ output.write('\n');
243
+ return values;
244
+ }
245
+
246
+ export function stripAnsi(text) {
247
+ return String(text).replace(/\x1B\[[0-9;]*[A-Za-z]/g, '');
248
+ }