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.
Files changed (87) hide show
  1. package/dist/ai/providers.d.ts +8 -0
  2. package/dist/ai/providers.d.ts.map +1 -1
  3. package/dist/ai/providers.js +25 -1
  4. package/dist/ai/providers.js.map +1 -1
  5. package/dist/commands/init.d.ts +8 -19
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +5 -347
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/new.d.ts +21 -13
  10. package/dist/commands/new.d.ts.map +1 -1
  11. package/dist/commands/new.js +10 -267
  12. package/dist/commands/new.js.map +1 -1
  13. package/dist/generator/config.d.ts.map +1 -1
  14. package/dist/generator/config.js +4 -2
  15. package/dist/generator/config.js.map +1 -1
  16. package/dist/index.d.ts +1 -2
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +92 -88
  19. package/dist/index.js.map +1 -1
  20. package/dist/repl/index.d.ts +3 -3
  21. package/dist/repl/index.d.ts.map +1 -1
  22. package/dist/repl/index.js +3 -3
  23. package/dist/repl/index.js.map +1 -1
  24. package/dist/tui/app.d.ts +1 -5
  25. package/dist/tui/app.d.ts.map +1 -1
  26. package/dist/tui/app.js +7 -12
  27. package/dist/tui/app.js.map +1 -1
  28. package/dist/tui/components/Confirm.d.ts +39 -0
  29. package/dist/tui/components/Confirm.d.ts.map +1 -0
  30. package/dist/tui/components/Confirm.js +60 -0
  31. package/dist/tui/components/Confirm.js.map +1 -0
  32. package/dist/tui/components/PasswordInput.d.ts +39 -0
  33. package/dist/tui/components/PasswordInput.d.ts.map +1 -0
  34. package/dist/tui/components/PasswordInput.js +56 -0
  35. package/dist/tui/components/PasswordInput.js.map +1 -0
  36. package/dist/tui/components/Select.d.ts +55 -0
  37. package/dist/tui/components/Select.d.ts.map +1 -0
  38. package/dist/tui/components/Select.js +63 -0
  39. package/dist/tui/components/Select.js.map +1 -0
  40. package/dist/tui/components/index.d.ts +6 -0
  41. package/dist/tui/components/index.d.ts.map +1 -1
  42. package/dist/tui/components/index.js +3 -0
  43. package/dist/tui/components/index.js.map +1 -1
  44. package/dist/tui/hooks/index.d.ts +3 -0
  45. package/dist/tui/hooks/index.d.ts.map +1 -1
  46. package/dist/tui/hooks/index.js +2 -0
  47. package/dist/tui/hooks/index.js.map +1 -1
  48. package/dist/tui/hooks/useInit.d.ts +130 -0
  49. package/dist/tui/hooks/useInit.d.ts.map +1 -0
  50. package/dist/tui/hooks/useInit.js +326 -0
  51. package/dist/tui/hooks/useInit.js.map +1 -0
  52. package/dist/tui/hooks/useSpecGenerator.d.ts +4 -0
  53. package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -1
  54. package/dist/tui/hooks/useSpecGenerator.js +12 -1
  55. package/dist/tui/hooks/useSpecGenerator.js.map +1 -1
  56. package/dist/tui/screens/InitScreen.d.ts +17 -10
  57. package/dist/tui/screens/InitScreen.d.ts.map +1 -1
  58. package/dist/tui/screens/InitScreen.js +317 -18
  59. package/dist/tui/screens/InitScreen.js.map +1 -1
  60. package/dist/tui/screens/InterviewScreen.d.ts.map +1 -1
  61. package/dist/tui/screens/InterviewScreen.js +2 -2
  62. package/dist/tui/screens/InterviewScreen.js.map +1 -1
  63. package/dist/tui/screens/index.d.ts +6 -0
  64. package/dist/tui/screens/index.d.ts.map +1 -1
  65. package/dist/tui/screens/index.js +3 -0
  66. package/dist/tui/screens/index.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/ai/providers.ts +28 -1
  69. package/src/commands/init.ts +8 -428
  70. package/src/commands/new.ts +13 -319
  71. package/src/generator/config.ts +4 -2
  72. package/src/index.ts +104 -96
  73. package/src/repl/index.ts +3 -3
  74. package/src/tui/app.tsx +7 -15
  75. package/src/tui/components/Confirm.tsx +109 -0
  76. package/src/tui/components/PasswordInput.tsx +106 -0
  77. package/src/tui/components/Select.tsx +132 -0
  78. package/src/tui/components/index.ts +9 -0
  79. package/src/tui/hooks/index.ts +9 -0
  80. package/src/tui/hooks/useInit.ts +472 -0
  81. package/src/tui/hooks/useSpecGenerator.ts +17 -1
  82. package/src/tui/screens/InitScreen.tsx +562 -29
  83. package/src/tui/screens/InterviewScreen.tsx +2 -1
  84. package/src/tui/screens/index.ts +9 -0
  85. package/src/cli.ts +0 -274
  86. package/src/repl/repl-loop.ts +0 -389
  87. 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
- }