wiggum-cli 0.9.7 → 0.10.0
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.
- package/dist/ai/providers.d.ts +8 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +25 -1
- package/dist/ai/providers.js.map +1 -1
- package/dist/commands/init.d.ts +8 -19
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -347
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts +21 -13
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +10 -267
- package/dist/commands/new.js.map +1 -1
- package/dist/generator/config.d.ts.map +1 -1
- package/dist/generator/config.js +4 -2
- package/dist/generator/config.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -88
- package/dist/index.js.map +1 -1
- package/dist/repl/index.d.ts +3 -3
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +3 -3
- package/dist/repl/index.js.map +1 -1
- package/dist/tui/app.d.ts +1 -5
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +7 -12
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/Confirm.d.ts +39 -0
- package/dist/tui/components/Confirm.d.ts.map +1 -0
- package/dist/tui/components/Confirm.js +60 -0
- package/dist/tui/components/Confirm.js.map +1 -0
- package/dist/tui/components/PasswordInput.d.ts +39 -0
- package/dist/tui/components/PasswordInput.d.ts.map +1 -0
- package/dist/tui/components/PasswordInput.js +56 -0
- package/dist/tui/components/PasswordInput.js.map +1 -0
- package/dist/tui/components/Select.d.ts +55 -0
- package/dist/tui/components/Select.d.ts.map +1 -0
- package/dist/tui/components/Select.js +63 -0
- package/dist/tui/components/Select.js.map +1 -0
- package/dist/tui/components/index.d.ts +6 -0
- package/dist/tui/components/index.d.ts.map +1 -1
- package/dist/tui/components/index.js +3 -0
- package/dist/tui/components/index.js.map +1 -1
- package/dist/tui/hooks/index.d.ts +3 -0
- package/dist/tui/hooks/index.d.ts.map +1 -1
- package/dist/tui/hooks/index.js +2 -0
- package/dist/tui/hooks/index.js.map +1 -1
- package/dist/tui/hooks/useInit.d.ts +130 -0
- package/dist/tui/hooks/useInit.d.ts.map +1 -0
- package/dist/tui/hooks/useInit.js +326 -0
- package/dist/tui/hooks/useInit.js.map +1 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts +4 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -1
- package/dist/tui/hooks/useSpecGenerator.js +12 -1
- package/dist/tui/hooks/useSpecGenerator.js.map +1 -1
- package/dist/tui/screens/InitScreen.d.ts +17 -10
- package/dist/tui/screens/InitScreen.d.ts.map +1 -1
- package/dist/tui/screens/InitScreen.js +317 -18
- package/dist/tui/screens/InitScreen.js.map +1 -1
- package/dist/tui/screens/InterviewScreen.d.ts.map +1 -1
- package/dist/tui/screens/InterviewScreen.js +2 -2
- package/dist/tui/screens/InterviewScreen.js.map +1 -1
- package/dist/tui/screens/index.d.ts +6 -0
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +3 -0
- package/dist/tui/screens/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/providers.ts +28 -1
- package/src/commands/init.ts +8 -428
- package/src/commands/new.ts +13 -319
- package/src/generator/config.ts +4 -2
- package/src/index.ts +104 -96
- package/src/repl/index.ts +3 -3
- package/src/tui/app.tsx +7 -15
- package/src/tui/components/Confirm.tsx +109 -0
- package/src/tui/components/PasswordInput.tsx +106 -0
- package/src/tui/components/Select.tsx +132 -0
- package/src/tui/components/index.ts +9 -0
- package/src/tui/hooks/index.ts +9 -0
- package/src/tui/hooks/useInit.ts +472 -0
- package/src/tui/hooks/useSpecGenerator.ts +17 -1
- package/src/tui/screens/InitScreen.tsx +562 -29
- package/src/tui/screens/InterviewScreen.tsx +2 -1
- package/src/tui/screens/index.ts +9 -0
- package/src/cli.ts +0 -274
- package/src/repl/repl-loop.ts +0 -389
- package/src/utils/repl-prompts.ts +0 -381
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* REPL-Friendly Prompts
|
|
3
|
-
* Simple prompts using readline that work properly in REPL context
|
|
4
|
-
* (Clack prompts conflict with REPL readline management)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import readline from 'node:readline';
|
|
8
|
-
import pc from 'picocolors';
|
|
9
|
-
import { simpson } from './colors.js';
|
|
10
|
-
|
|
11
|
-
export interface SelectOption<T> {
|
|
12
|
-
value: T;
|
|
13
|
-
label: string;
|
|
14
|
-
hint?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Interactive select prompt with arrow key navigation
|
|
19
|
-
* Uses readline's emitKeypressEvents for robust keypress handling
|
|
20
|
-
*/
|
|
21
|
-
export async function select<T>(options: {
|
|
22
|
-
message: string;
|
|
23
|
-
options: SelectOption<T>[];
|
|
24
|
-
}): Promise<T | null> {
|
|
25
|
-
const { message, options: choices } = options;
|
|
26
|
-
|
|
27
|
-
let selectedIndex = 0;
|
|
28
|
-
|
|
29
|
-
// Render the options with current selection highlighted
|
|
30
|
-
const render = () => {
|
|
31
|
-
const output: string[] = [];
|
|
32
|
-
|
|
33
|
-
choices.forEach((choice, index) => {
|
|
34
|
-
const hint = choice.hint ? pc.dim(` (${choice.hint})`) : '';
|
|
35
|
-
if (index === selectedIndex) {
|
|
36
|
-
output.push(` ${pc.cyan('❯')} ${pc.cyan(choice.label)}${hint}`);
|
|
37
|
-
} else {
|
|
38
|
-
output.push(` ${pc.dim(choice.label)}${hint}`);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
return output.join('\n');
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Initial render
|
|
46
|
-
console.log('');
|
|
47
|
-
console.log(`${simpson.yellow('?')} ${message}`);
|
|
48
|
-
console.log('');
|
|
49
|
-
process.stdout.write(render());
|
|
50
|
-
console.log('');
|
|
51
|
-
console.log(pc.dim(' (Use arrow keys, Enter to select, Ctrl+C to cancel)'));
|
|
52
|
-
|
|
53
|
-
return new Promise((resolve) => {
|
|
54
|
-
// Track if we've already resolved
|
|
55
|
-
let resolved = false;
|
|
56
|
-
// Track when keypress handling becomes active (to ignore stale buffered input)
|
|
57
|
-
let acceptingInput = false;
|
|
58
|
-
|
|
59
|
-
const cleanup = () => {
|
|
60
|
-
if (resolved) return;
|
|
61
|
-
resolved = true;
|
|
62
|
-
process.stdin.removeListener('keypress', onKeypress);
|
|
63
|
-
if (process.stdin.isTTY && process.stdin.isRaw) {
|
|
64
|
-
process.stdin.setRawMode(false);
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
const finishWithSelection = () => {
|
|
69
|
-
cleanup();
|
|
70
|
-
const selected = choices[selectedIndex];
|
|
71
|
-
// Move up past hint + options + blank line + question
|
|
72
|
-
process.stdout.write(`\x1b[${choices.length + 3}A`);
|
|
73
|
-
// Clear from cursor to end of screen
|
|
74
|
-
process.stdout.write('\x1b[J');
|
|
75
|
-
// Rewrite the question with the answer
|
|
76
|
-
console.log(`${pc.green('✓')} ${message}`);
|
|
77
|
-
console.log(` ${pc.cyan(selected.label)}`);
|
|
78
|
-
console.log('');
|
|
79
|
-
resolve(selected.value);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const finishWithCancel = () => {
|
|
83
|
-
cleanup();
|
|
84
|
-
// Move up past hint + options + blank line + question
|
|
85
|
-
process.stdout.write(`\x1b[${choices.length + 3}A`);
|
|
86
|
-
// Clear from cursor to end of screen
|
|
87
|
-
process.stdout.write('\x1b[J');
|
|
88
|
-
console.log(`${pc.red('✗')} ${message}`);
|
|
89
|
-
console.log(pc.dim(' Cancelled'));
|
|
90
|
-
console.log('');
|
|
91
|
-
resolve(null);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const redraw = () => {
|
|
95
|
-
// Move up past hint + options
|
|
96
|
-
process.stdout.write(`\x1b[${choices.length + 1}A`);
|
|
97
|
-
// Clear from cursor to end of screen
|
|
98
|
-
process.stdout.write('\x1b[J');
|
|
99
|
-
// Write new options
|
|
100
|
-
process.stdout.write(render());
|
|
101
|
-
console.log('');
|
|
102
|
-
console.log(pc.dim(' (Use arrow keys, Enter to select, Ctrl+C to cancel)'));
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const onKeypress = (_str: string | undefined, key: readline.Key | undefined) => {
|
|
106
|
-
if (resolved || !key) return;
|
|
107
|
-
|
|
108
|
-
// Ignore enter/return until we're ready to accept input.
|
|
109
|
-
// This prevents buffered newlines from REPL transitions from
|
|
110
|
-
// immediately triggering selection.
|
|
111
|
-
if (!acceptingInput && (key.name === 'return' || key.name === 'enter')) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (key.ctrl && key.name === 'c') {
|
|
116
|
-
finishWithCancel();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
121
|
-
finishWithSelection();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (key.name === 'up' || key.name === 'k') {
|
|
126
|
-
selectedIndex = (selectedIndex - 1 + choices.length) % choices.length;
|
|
127
|
-
redraw();
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (key.name === 'down' || key.name === 'j') {
|
|
132
|
-
selectedIndex = (selectedIndex + 1) % choices.length;
|
|
133
|
-
redraw();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// Enable keypress events on stdin
|
|
139
|
-
readline.emitKeypressEvents(process.stdin);
|
|
140
|
-
|
|
141
|
-
// Set up keypress listener
|
|
142
|
-
process.stdin.on('keypress', onKeypress);
|
|
143
|
-
|
|
144
|
-
// Enable raw mode for keypress detection
|
|
145
|
-
if (process.stdin.isTTY) {
|
|
146
|
-
process.stdin.setRawMode(true);
|
|
147
|
-
process.stdin.resume();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Delay accepting enter/return to allow buffered input to be discarded.
|
|
151
|
-
// Any enter keypress arriving before this timeout is from buffered REPL input.
|
|
152
|
-
setTimeout(() => {
|
|
153
|
-
acceptingInput = true;
|
|
154
|
-
}, 100);
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Simple password prompt for REPL
|
|
160
|
-
* Masks input with asterisks
|
|
161
|
-
*/
|
|
162
|
-
export async function password(options: {
|
|
163
|
-
message: string;
|
|
164
|
-
}): Promise<string | null> {
|
|
165
|
-
const { message } = options;
|
|
166
|
-
|
|
167
|
-
console.log('');
|
|
168
|
-
console.log(`${simpson.yellow('?')} ${message}`);
|
|
169
|
-
|
|
170
|
-
return new Promise((resolve) => {
|
|
171
|
-
let input = '';
|
|
172
|
-
|
|
173
|
-
// Show initial cursor position
|
|
174
|
-
process.stdout.write(`${pc.dim('>')} `);
|
|
175
|
-
|
|
176
|
-
const onData = (char: string) => {
|
|
177
|
-
// Ctrl+C
|
|
178
|
-
if (char === '\u0003') {
|
|
179
|
-
cleanup();
|
|
180
|
-
console.log('');
|
|
181
|
-
resolve(null);
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Enter
|
|
186
|
-
if (char === '\r' || char === '\n') {
|
|
187
|
-
cleanup();
|
|
188
|
-
// Clear the line and show fixed mask
|
|
189
|
-
process.stdout.write('\r' + pc.dim('>') + ' ' + '*'.repeat(32) + '\n');
|
|
190
|
-
console.log(`${pc.green('✓')} API key entered`);
|
|
191
|
-
resolve(input.trim() || null);
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Backspace
|
|
196
|
-
if (char === '\u007F' || char === '\b') {
|
|
197
|
-
if (input.length > 0) {
|
|
198
|
-
input = input.slice(0, -1);
|
|
199
|
-
// Clear and rewrite asterisks
|
|
200
|
-
process.stdout.write('\r' + pc.dim('>') + ' ' + '*'.repeat(input.length) + ' \b');
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Regular character
|
|
206
|
-
if (char.charCodeAt(0) >= 32 && char.charCodeAt(0) <= 126) {
|
|
207
|
-
input += char;
|
|
208
|
-
process.stdout.write('*');
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
const cleanup = () => {
|
|
213
|
-
if (process.stdin.isTTY) {
|
|
214
|
-
process.stdin.setRawMode(false);
|
|
215
|
-
}
|
|
216
|
-
process.stdin.removeListener('data', onData);
|
|
217
|
-
process.stdin.pause();
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
if (process.stdin.isTTY) {
|
|
221
|
-
process.stdin.setRawMode(true);
|
|
222
|
-
}
|
|
223
|
-
process.stdin.resume();
|
|
224
|
-
process.stdin.setEncoding('utf8');
|
|
225
|
-
process.stdin.on('data', onData);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Simple confirm prompt for REPL
|
|
231
|
-
*/
|
|
232
|
-
export async function confirm(options: {
|
|
233
|
-
message: string;
|
|
234
|
-
initialValue?: boolean;
|
|
235
|
-
}): Promise<boolean | null> {
|
|
236
|
-
const { message, initialValue = true } = options;
|
|
237
|
-
|
|
238
|
-
console.log('');
|
|
239
|
-
const defaultHint = initialValue ? 'Y/n' : 'y/N';
|
|
240
|
-
|
|
241
|
-
const rl = readline.createInterface({
|
|
242
|
-
input: process.stdin,
|
|
243
|
-
output: process.stdout,
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
return new Promise((resolve) => {
|
|
247
|
-
rl.question(`${simpson.yellow('?')} ${message} ${pc.dim(`(${defaultHint}):`)} `, (answer) => {
|
|
248
|
-
rl.close();
|
|
249
|
-
const trimmed = answer.trim().toLowerCase();
|
|
250
|
-
|
|
251
|
-
if (trimmed === '') {
|
|
252
|
-
console.log(`${pc.green('✓')} ${initialValue ? 'Yes' : 'No'}`);
|
|
253
|
-
resolve(initialValue);
|
|
254
|
-
} else if (trimmed === 'y' || trimmed === 'yes') {
|
|
255
|
-
console.log(`${pc.green('✓')} Yes`);
|
|
256
|
-
resolve(true);
|
|
257
|
-
} else if (trimmed === 'n' || trimmed === 'no') {
|
|
258
|
-
console.log(`${pc.green('✓')} No`);
|
|
259
|
-
resolve(false);
|
|
260
|
-
} else {
|
|
261
|
-
console.log(pc.red('Invalid input, using default'));
|
|
262
|
-
resolve(initialValue);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
rl.on('SIGINT', () => {
|
|
267
|
-
rl.close();
|
|
268
|
-
resolve(null);
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Check if user cancelled (similar to clack's isCancel)
|
|
275
|
-
*/
|
|
276
|
-
export function isCancel(value: unknown): value is null {
|
|
277
|
-
return value === null;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Multi-line input for paste support
|
|
282
|
-
* Reads input until an empty line (after content) or Ctrl+D
|
|
283
|
-
*
|
|
284
|
-
* @param prompt - The prompt to display
|
|
285
|
-
* @returns The collected input or null if cancelled
|
|
286
|
-
*/
|
|
287
|
-
export async function multilineInput(options: {
|
|
288
|
-
prompt?: string;
|
|
289
|
-
/** Hint to show for how to end input */
|
|
290
|
-
endHint?: string;
|
|
291
|
-
}): Promise<string | null> {
|
|
292
|
-
const { prompt = '>', endHint = 'Press Enter twice when done' } = options;
|
|
293
|
-
|
|
294
|
-
console.log(pc.dim(` (${endHint})`));
|
|
295
|
-
|
|
296
|
-
return new Promise((resolve) => {
|
|
297
|
-
const lines: string[] = [];
|
|
298
|
-
let lastLineEmpty = false;
|
|
299
|
-
|
|
300
|
-
const rl = readline.createInterface({
|
|
301
|
-
input: process.stdin,
|
|
302
|
-
output: process.stdout,
|
|
303
|
-
prompt: `${pc.dim(prompt)} `,
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
rl.prompt();
|
|
307
|
-
|
|
308
|
-
rl.on('line', (line) => {
|
|
309
|
-
const trimmed = line.trim();
|
|
310
|
-
|
|
311
|
-
// If we get an empty line after having content, we're done
|
|
312
|
-
if (trimmed === '' && lines.length > 0 && lastLineEmpty) {
|
|
313
|
-
rl.close();
|
|
314
|
-
// Remove the trailing empty line we added
|
|
315
|
-
const result = lines.slice(0, -1).join('\n').trim();
|
|
316
|
-
if (result) {
|
|
317
|
-
console.log(pc.green('✓') + pc.dim(` Received ${result.split('\n').length} line(s)`));
|
|
318
|
-
}
|
|
319
|
-
resolve(result || null);
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
lastLineEmpty = trimmed === '';
|
|
324
|
-
lines.push(line);
|
|
325
|
-
rl.prompt();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
rl.on('close', () => {
|
|
329
|
-
// Ctrl+D or EOF
|
|
330
|
-
const result = lines.join('\n').trim();
|
|
331
|
-
if (result) {
|
|
332
|
-
console.log(pc.green('✓') + pc.dim(` Received ${result.split('\n').length} line(s)`));
|
|
333
|
-
}
|
|
334
|
-
resolve(result || null);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
rl.on('SIGINT', () => {
|
|
338
|
-
rl.close();
|
|
339
|
-
console.log('');
|
|
340
|
-
resolve(null);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Simple text input prompt
|
|
347
|
-
*
|
|
348
|
-
* @param prompt - The prompt to display
|
|
349
|
-
* @returns The input text or null if cancelled
|
|
350
|
-
*/
|
|
351
|
-
export async function textInput(options: {
|
|
352
|
-
message: string;
|
|
353
|
-
placeholder?: string;
|
|
354
|
-
}): Promise<string | null> {
|
|
355
|
-
const { message, placeholder } = options;
|
|
356
|
-
|
|
357
|
-
const hint = placeholder ? pc.dim(` (${placeholder})`) : '';
|
|
358
|
-
|
|
359
|
-
const rl = readline.createInterface({
|
|
360
|
-
input: process.stdin,
|
|
361
|
-
output: process.stdout,
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
return new Promise((resolve) => {
|
|
365
|
-
rl.question(`${simpson.yellow('?')} ${message}${hint}: `, (answer) => {
|
|
366
|
-
rl.close();
|
|
367
|
-
const trimmed = answer.trim();
|
|
368
|
-
if (trimmed) {
|
|
369
|
-
resolve(trimmed);
|
|
370
|
-
} else {
|
|
371
|
-
resolve(null);
|
|
372
|
-
}
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
rl.on('SIGINT', () => {
|
|
376
|
-
rl.close();
|
|
377
|
-
console.log('');
|
|
378
|
-
resolve(null);
|
|
379
|
-
});
|
|
380
|
-
});
|
|
381
|
-
}
|