gsdd-cli 0.2.0 → 0.16.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 (43) hide show
  1. package/README.md +67 -34
  2. package/agents/DISTILLATION.md +15 -13
  3. package/agents/planner.md +2 -0
  4. package/bin/adapters/agents.mjs +1 -0
  5. package/bin/adapters/claude.mjs +12 -3
  6. package/bin/adapters/codex.mjs +4 -0
  7. package/bin/adapters/opencode.mjs +13 -4
  8. package/bin/gsdd.mjs +64 -39
  9. package/bin/lib/cli-utils.mjs +1 -1
  10. package/bin/lib/file-ops.mjs +161 -0
  11. package/bin/lib/health-truth.mjs +188 -0
  12. package/bin/lib/health.mjs +65 -12
  13. package/bin/lib/init-flow.mjs +267 -0
  14. package/bin/lib/init-prompts.mjs +247 -0
  15. package/bin/lib/init-runtime.mjs +226 -0
  16. package/bin/lib/init.mjs +19 -378
  17. package/bin/lib/models.mjs +19 -4
  18. package/bin/lib/phase.mjs +100 -14
  19. package/bin/lib/plan-constants.mjs +30 -0
  20. package/bin/lib/provenance.mjs +146 -0
  21. package/bin/lib/templates.mjs +17 -0
  22. package/distilled/DESIGN.md +625 -41
  23. package/distilled/EVIDENCE-INDEX.md +297 -0
  24. package/distilled/README.md +49 -21
  25. package/distilled/SKILL.md +89 -85
  26. package/distilled/templates/agents.block.md +13 -82
  27. package/distilled/templates/agents.md +0 -7
  28. package/distilled/templates/delegates/plan-checker.md +11 -4
  29. package/distilled/workflows/audit-milestone.md +7 -5
  30. package/distilled/workflows/complete-milestone.md +297 -0
  31. package/distilled/workflows/execute.md +188 -19
  32. package/distilled/workflows/map-codebase.md +14 -7
  33. package/distilled/workflows/new-milestone.md +249 -0
  34. package/distilled/workflows/new-project.md +28 -24
  35. package/distilled/workflows/pause.md +42 -6
  36. package/distilled/workflows/plan-milestone-gaps.md +183 -0
  37. package/distilled/workflows/plan.md +78 -13
  38. package/distilled/workflows/progress.md +42 -19
  39. package/distilled/workflows/quick.md +171 -8
  40. package/distilled/workflows/resume.md +121 -11
  41. package/distilled/workflows/verify-work.md +260 -0
  42. package/distilled/workflows/verify.md +124 -33
  43. package/package.json +9 -7
@@ -0,0 +1,267 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync } from 'fs';
2
+ import { join, isAbsolute } from 'path';
3
+ import { renderSkillContent } from './rendering.mjs';
4
+ import { buildManifest, writeManifest } from './manifest.mjs';
5
+ import { parseFlagValue, parseToolsFlag, parseAutoFlag } from './cli-utils.mjs';
6
+ import { buildDefaultConfig, COST_PROFILES, RIGOR_PROFILES } from './models.mjs';
7
+ import { installProjectTemplates, refreshTemplates } from './templates.mjs';
8
+ import {
9
+ detectPlatforms,
10
+ getAdaptersToUpdate,
11
+ getPostInitRoutingLines,
12
+ normalizeRequestedTools,
13
+ resolveAdapters,
14
+ resolveInteractiveInitSession,
15
+ } from './init-runtime.mjs';
16
+ import { createInitPromptApi } from './init-prompts.mjs';
17
+
18
+ function validateKindContract(adapter, cwd) {
19
+ if (!adapter.subagentFiles) return;
20
+ if (adapter.kind === 'native_capable') {
21
+ const missing = adapter.subagentFiles
22
+ .map(f => join(cwd, f))
23
+ .filter(p => !existsSync(p));
24
+ if (missing.length > 0) {
25
+ console.warn(
26
+ `[WARN] ${adapter.name} adapter (kind=native_capable) missing expected subagent files:\n` +
27
+ missing.map(p => ` - ${p}`).join('\n')
28
+ );
29
+ }
30
+ } else if (adapter.kind === 'governance_only') {
31
+ const unexpected = adapter.subagentFiles
32
+ .map(f => join(cwd, f))
33
+ .filter(p => existsSync(p));
34
+ if (unexpected.length > 0) {
35
+ console.warn(
36
+ `[WARN] ${adapter.name} adapter (kind=governance_only) unexpectedly generated subagent files:\n` +
37
+ unexpected.map(p => ` - ${p}`).join('\n')
38
+ );
39
+ }
40
+ }
41
+ }
42
+
43
+ export function createCmdInit(ctx) {
44
+ return async function cmdInit(...initArgs) {
45
+ console.log('gsdd init - setting up GSDD workflow\n');
46
+
47
+ const isAuto = parseAutoFlag(initArgs);
48
+ const toolsFlag = parseFlagValue(initArgs, '--tools');
49
+ const briefFlag = parseFlagValue(initArgs, '--brief');
50
+ let briefSource = null;
51
+
52
+ if (toolsFlag.invalid) {
53
+ console.error('ERROR: --tools requires a value. Example: gsdd init --tools claude');
54
+ process.exitCode = 1;
55
+ return;
56
+ }
57
+
58
+ if (briefFlag.invalid) {
59
+ console.error('ERROR: --brief requires a file path. Example: gsdd init --brief project-idea.md');
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+
64
+ if (briefFlag.value) {
65
+ briefSource = isAbsolute(briefFlag.value) ? briefFlag.value : join(ctx.cwd, briefFlag.value);
66
+ if (!existsSync(briefSource)) {
67
+ console.error(`ERROR: Brief file not found: ${briefFlag.value}`);
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+ }
72
+
73
+ const parsedTools = parseToolsFlag(initArgs);
74
+ if (isAuto && parsedTools.length === 0) {
75
+ console.error('ERROR: --auto requires --tools <platform>. Example: gsdd init --auto --tools claude');
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+
80
+ const promptApi = ctx.initPromptApi || createInitPromptApi();
81
+ const interactiveSession = await resolveInteractiveInitSession({
82
+ ctx,
83
+ promptApi,
84
+ parsedTools,
85
+ isAuto,
86
+ });
87
+
88
+ const existed = existsSync(ctx.planningDir);
89
+ mkdirSync(join(ctx.planningDir, 'phases'), { recursive: true });
90
+ mkdirSync(join(ctx.planningDir, 'research'), { recursive: true });
91
+ console.log(existed
92
+ ? ' - .planning/ already exists (ensured subdirectories)'
93
+ : ' - created .planning/ directory structure');
94
+
95
+ installProjectTemplates(ctx);
96
+ await ensureConfig({
97
+ cwd: ctx.cwd,
98
+ planningDir: ctx.planningDir,
99
+ isAuto,
100
+ promptApi,
101
+ preselectedConfig: interactiveSession.config,
102
+ });
103
+
104
+ if (briefSource) {
105
+ cpSync(briefSource, join(ctx.planningDir, 'PROJECT_BRIEF.md'));
106
+ console.log(' - copied project brief to .planning/PROJECT_BRIEF.md');
107
+ }
108
+
109
+ generateOpenStandardSkills(ctx.cwd, ctx.workflows);
110
+ console.log(' - generated open-standard skills (.agents/skills/gsdd-*)');
111
+
112
+ for (const adapter of resolveAdapters(ctx.adapters, interactiveSession.adapterTargets)) {
113
+ adapter.generate();
114
+ validateKindContract(adapter, ctx.cwd);
115
+ console.log(` - ${adapter.summary('generated')}`);
116
+ }
117
+
118
+ const manifest = buildManifest({ planningDir: ctx.planningDir, frameworkVersion: ctx.frameworkVersion });
119
+ writeManifest(ctx.planningDir, manifest);
120
+ console.log(' - wrote generation manifest');
121
+
122
+ console.log('\nGSDD initialized.');
123
+ printInitSummary(interactiveSession.config ?? buildDefaultConfig({ autoAdvance: isAuto }));
124
+ console.log('Next: run the new-project workflow to produce SPEC.md and ROADMAP.md:\n');
125
+ printPostInitRouting(interactiveSession.selectedRuntimes);
126
+ };
127
+ }
128
+
129
+ export function createCmdUpdate(ctx) {
130
+ return function cmdUpdate(...updateArgs) {
131
+ const isDry = updateArgs.includes('--dry');
132
+ const doTemplates = updateArgs.includes('--templates');
133
+
134
+ console.log(`gsdd update - regenerating adapter files${isDry ? ' (dry run)' : ''}\n`);
135
+
136
+ const parsedTools = parseToolsFlag(updateArgs);
137
+ const requested = normalizeRequestedTools(parsedTools);
138
+ const platforms = parsedTools.length > 0 ? requested.adapterTargets : detectPlatforms(ctx.adapters);
139
+
140
+ let updated = false;
141
+
142
+ if (doTemplates) {
143
+ refreshTemplates({ ...ctx, isDry });
144
+ updated = true;
145
+ }
146
+
147
+ if (platforms.length > 0 || existsSync(join(ctx.cwd, '.agents', 'skills'))) {
148
+ if (isDry) {
149
+ console.log(' - would update open-standard skills (.agents/skills/gsdd-*)');
150
+ } else {
151
+ generateOpenStandardSkills(ctx.cwd, ctx.workflows);
152
+ console.log(' - updated open-standard skills (.agents/skills/gsdd-*)');
153
+ }
154
+ updated = true;
155
+ }
156
+
157
+ for (const adapter of getAdaptersToUpdate(ctx.adapters, platforms)) {
158
+ if (isDry) {
159
+ console.log(` - would update ${adapter.name} adapter`);
160
+ } else {
161
+ adapter.generate();
162
+ validateKindContract(adapter, ctx.cwd);
163
+ console.log(` - ${adapter.summary('updated')}`);
164
+ }
165
+ updated = true;
166
+ }
167
+
168
+ if (!updated) {
169
+ console.log(' - no adapters found to update (run `gsdd init` first)');
170
+ } else if (isDry) {
171
+ console.log('\nDry run complete. No files were written.\n');
172
+ } else {
173
+ if (doTemplates && existsSync(ctx.planningDir)) {
174
+ const manifest = buildManifest({ planningDir: ctx.planningDir, frameworkVersion: ctx.frameworkVersion });
175
+ writeManifest(ctx.planningDir, manifest);
176
+ console.log(' - updated generation manifest');
177
+ }
178
+ console.log('\nAdapters updated.\n');
179
+ }
180
+ };
181
+ }
182
+
183
+ function generateOpenStandardSkills(cwd, workflows) {
184
+ for (const workflow of workflows) {
185
+ const dir = join(cwd, '.agents', 'skills', workflow.name);
186
+ mkdirSync(dir, { recursive: true });
187
+ writeFileSync(join(dir, 'SKILL.md'), renderSkillContent(workflow));
188
+ }
189
+ }
190
+
191
+ async function ensureConfig({ cwd, planningDir, isAuto, promptApi, preselectedConfig = null }) {
192
+ const configFile = join(planningDir, 'config.json');
193
+ if (existsSync(configFile)) {
194
+ console.log(' - .planning/config.json already exists');
195
+ return;
196
+ }
197
+
198
+ if (preselectedConfig) {
199
+ writeFileSync(configFile, JSON.stringify(preselectedConfig, null, 2));
200
+ console.log(' - saved .planning/config.json (guided wizard)\n');
201
+ if (!preselectedConfig.commitDocs) ensureGitignoreEntry(cwd);
202
+ return;
203
+ }
204
+
205
+ if (isAuto) {
206
+ console.log(' - auto mode: writing config.json with defaults');
207
+ writeFileSync(configFile, JSON.stringify(buildDefaultConfig({ autoAdvance: true }), null, 2));
208
+ console.log(' - saved .planning/config.json (auto mode - autoAdvance enabled)\n');
209
+ return;
210
+ }
211
+
212
+ if (!process.stdin.isTTY) {
213
+ console.log(' - non-interactive mode detected: writing config.json with defaults');
214
+ writeFileSync(configFile, JSON.stringify(buildDefaultConfig(), null, 2));
215
+ console.log(' - saved .planning/config.json (defaults - re-run gsdd init in a terminal to customize)\n');
216
+ return;
217
+ }
218
+
219
+ const config = await promptApi.promptForConfig(cwd);
220
+ writeFileSync(configFile, JSON.stringify(config, null, 2));
221
+ console.log(' - saved .planning/config.json (guided wizard)\n');
222
+
223
+ if (!config.commitDocs) ensureGitignoreEntry(cwd);
224
+ }
225
+
226
+ function ensureGitignoreEntry(cwd) {
227
+ const gitignorePath = join(cwd, '.gitignore');
228
+ const ignoreEntry = '\n# GSDD planning docs (local only)\n.planning/\n';
229
+
230
+ if (existsSync(gitignorePath)) {
231
+ const existing = readFileSync(gitignorePath, 'utf-8');
232
+ if (!existing.includes('.planning/')) {
233
+ writeFileSync(gitignorePath, existing + ignoreEntry);
234
+ console.log(' - added .planning/ to .gitignore');
235
+ }
236
+ return;
237
+ }
238
+
239
+ writeFileSync(gitignorePath, ignoreEntry.trimStart());
240
+ console.log(' - created .gitignore with .planning/ entry');
241
+ }
242
+
243
+ function printPostInitRouting(selectedRuntimes) {
244
+ for (const line of getPostInitRoutingLines(selectedRuntimes)) {
245
+ console.log(line);
246
+ }
247
+ console.log('');
248
+ }
249
+
250
+ function printInitSummary(config) {
251
+ const rigor = Object.entries(RIGOR_PROFILES).find(([, profile]) => (
252
+ profile.researchDepth === config.researchDepth
253
+ && profile.workflow.research === config.workflow?.research
254
+ && profile.workflow.discuss === config.workflow?.discuss
255
+ && profile.workflow.planCheck === config.workflow?.planCheck
256
+ && profile.workflow.verifier === config.workflow?.verifier
257
+ ))?.[0] ?? 'balanced';
258
+ const cost = Object.entries(COST_PROFILES).find(([, profile]) => (
259
+ profile.modelProfile === config.modelProfile
260
+ && profile.parallelization === config.parallelization
261
+ ))?.[0] ?? 'balanced';
262
+
263
+ console.log(`Rigor: ${rigor} Cost: ${cost} Track .planning/ in git: ${config.commitDocs ? 'yes' : 'no'}`);
264
+ console.log('Workflows: new-project → plan → execute → verify → progress');
265
+ console.log('Edit .planning/config.json to fine-tune (verifier, gitProtocol, individual workflow flags).');
266
+ console.log('');
267
+ }
@@ -0,0 +1,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
+ };
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
+ }