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
@@ -0,0 +1,472 @@
1
+ /**
2
+ * useInit - State management hook for the init workflow
3
+ *
4
+ * Manages the initialization flow phases:
5
+ * 1. scanning - Scan project structure
6
+ * 2. provider-select - Select AI provider (if no key available)
7
+ * 3. key-input - Enter API key
8
+ * 4. key-save - Confirm saving key to .env.local
9
+ * 5. model-select - Select model
10
+ * 6. ai-analysis - Run AI analysis
11
+ * 7. confirm - Confirm file generation
12
+ * 8. generating - Generate configuration files
13
+ * 9. complete - Done
14
+ */
15
+
16
+ import { useState, useCallback, useRef } from 'react';
17
+ import type { AIProvider } from '../../ai/providers.js';
18
+ import type { ScanResult } from '../../scanner/types.js';
19
+ import type { EnhancedScanResult } from '../../ai/index.js';
20
+
21
+ /**
22
+ * Init workflow phases
23
+ */
24
+ export type InitPhase =
25
+ | 'scanning'
26
+ | 'provider-select'
27
+ | 'key-input'
28
+ | 'key-save'
29
+ | 'model-select'
30
+ | 'ai-analysis'
31
+ | 'confirm'
32
+ | 'generating'
33
+ | 'complete'
34
+ | 'error';
35
+
36
+ /**
37
+ * Phase configuration for display
38
+ */
39
+ export interface InitPhaseConfig {
40
+ /** Phase number for progress display */
41
+ number: number;
42
+ /** Human-readable phase name */
43
+ name: string;
44
+ /** Description of what happens in this phase */
45
+ description: string;
46
+ }
47
+
48
+ /**
49
+ * Phase configurations for display
50
+ */
51
+ export const INIT_PHASE_CONFIGS: Record<InitPhase, InitPhaseConfig> = {
52
+ scanning: {
53
+ number: 1,
54
+ name: 'Scanning',
55
+ description: 'Analyzing project structure',
56
+ },
57
+ 'provider-select': {
58
+ number: 2,
59
+ name: 'Provider',
60
+ description: 'Select AI provider',
61
+ },
62
+ 'key-input': {
63
+ number: 2,
64
+ name: 'API Key',
65
+ description: 'Enter API key',
66
+ },
67
+ 'key-save': {
68
+ number: 2,
69
+ name: 'Save Key',
70
+ description: 'Save API key to .env.local',
71
+ },
72
+ 'model-select': {
73
+ number: 3,
74
+ name: 'Model',
75
+ description: 'Select AI model',
76
+ },
77
+ 'ai-analysis': {
78
+ number: 4,
79
+ name: 'Analysis',
80
+ description: 'AI-powered codebase analysis',
81
+ },
82
+ confirm: {
83
+ number: 5,
84
+ name: 'Confirm',
85
+ description: 'Confirm file generation',
86
+ },
87
+ generating: {
88
+ number: 5,
89
+ name: 'Generating',
90
+ description: 'Creating configuration files',
91
+ },
92
+ complete: {
93
+ number: 6,
94
+ name: 'Complete',
95
+ description: 'Initialization complete',
96
+ },
97
+ error: {
98
+ number: 0,
99
+ name: 'Error',
100
+ description: 'An error occurred',
101
+ },
102
+ };
103
+
104
+ /**
105
+ * Total display phases for progress bar
106
+ */
107
+ export const INIT_TOTAL_PHASES = 5;
108
+
109
+ /**
110
+ * State managed by the useInit hook
111
+ */
112
+ export interface InitState {
113
+ /** Current phase of the init workflow */
114
+ phase: InitPhase;
115
+ /** Project root directory */
116
+ projectRoot: string;
117
+ /** Scan result from project analysis */
118
+ scanResult: ScanResult | null;
119
+ /** Enhanced scan result after AI analysis */
120
+ enhancedResult: EnhancedScanResult | null;
121
+ /** Selected AI provider */
122
+ provider: AIProvider | null;
123
+ /** Selected model */
124
+ model: string | null;
125
+ /** API key entered (not persisted in state for security) */
126
+ hasApiKey: boolean;
127
+ /** Whether API key was entered this session (needs save prompt) */
128
+ apiKeyEnteredThisSession: boolean;
129
+ /** Whether user wants to save key to .env.local */
130
+ saveKeyToEnv: boolean;
131
+ /** Whether AI analysis is in progress */
132
+ isWorking: boolean;
133
+ /** Status message for working indicator */
134
+ workingStatus: string;
135
+ /** Error message if something went wrong */
136
+ error: string | null;
137
+ /** Generated files list */
138
+ generatedFiles: string[];
139
+ /** Token usage from AI analysis */
140
+ tokenUsage: { inputTokens: number; outputTokens: number; totalTokens: number } | null;
141
+ }
142
+
143
+ /**
144
+ * Initial state
145
+ */
146
+ const initialState: InitState = {
147
+ phase: 'scanning',
148
+ projectRoot: '',
149
+ scanResult: null,
150
+ enhancedResult: null,
151
+ provider: null,
152
+ model: null,
153
+ hasApiKey: false,
154
+ apiKeyEnteredThisSession: false,
155
+ saveKeyToEnv: false,
156
+ isWorking: false,
157
+ workingStatus: '',
158
+ error: null,
159
+ generatedFiles: [],
160
+ tokenUsage: null,
161
+ };
162
+
163
+ /**
164
+ * Return value from useInit hook
165
+ */
166
+ export interface UseInitReturn {
167
+ /** Current state */
168
+ state: InitState;
169
+
170
+ /** Initialize with project root */
171
+ initialize: (projectRoot: string) => void;
172
+
173
+ /** Set scan result and advance to next phase */
174
+ setScanResult: (result: ScanResult) => void;
175
+
176
+ /** Set that an existing API key is available */
177
+ setExistingProvider: (provider: AIProvider) => void;
178
+
179
+ /** Set selected provider */
180
+ selectProvider: (provider: AIProvider) => void;
181
+
182
+ /** Set API key (marks as entered this session) */
183
+ setApiKey: (key: string) => void;
184
+
185
+ /** Set whether to save key to .env.local */
186
+ setSaveKey: (save: boolean) => void;
187
+
188
+ /** Select model and advance to AI analysis */
189
+ selectModel: (model: string) => void;
190
+
191
+ /** Set AI analysis progress */
192
+ setAiProgress: (status: string) => void;
193
+
194
+ /** Set enhanced result from AI analysis */
195
+ setEnhancedResult: (result: EnhancedScanResult, tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number }) => void;
196
+
197
+ /** Set AI analysis error */
198
+ setAiError: (error: string) => void;
199
+
200
+ /** Confirm file generation */
201
+ confirmGeneration: (confirmed: boolean) => void;
202
+
203
+ /** Set generation in progress */
204
+ setGenerating: (status: string) => void;
205
+
206
+ /** Set generation complete */
207
+ setGenerationComplete: (files: string[]) => void;
208
+
209
+ /** Set error state */
210
+ setError: (error: string) => void;
211
+
212
+ /** Reset to initial state */
213
+ reset: () => void;
214
+
215
+ /** Go back to previous phase */
216
+ goBack: () => void;
217
+ }
218
+
219
+ /**
220
+ * useInit - React hook for managing init workflow state
221
+ */
222
+ export function useInit(): UseInitReturn {
223
+ const [state, setState] = useState<InitState>(initialState);
224
+
225
+ // Store API key in ref (not in state for security)
226
+ const apiKeyRef = useRef<string | null>(null);
227
+
228
+ /**
229
+ * Initialize with project root
230
+ */
231
+ const initialize = useCallback((projectRoot: string) => {
232
+ setState({
233
+ ...initialState,
234
+ projectRoot,
235
+ phase: 'scanning',
236
+ isWorking: true,
237
+ workingStatus: 'Scanning project structure...',
238
+ });
239
+ }, []);
240
+
241
+ /**
242
+ * Set scan result and determine next phase
243
+ */
244
+ const setScanResult = useCallback((result: ScanResult) => {
245
+ setState((prev) => ({
246
+ ...prev,
247
+ scanResult: result,
248
+ isWorking: false,
249
+ workingStatus: '',
250
+ // Will be set to correct phase by setExistingProvider or continue to provider-select
251
+ }));
252
+ }, []);
253
+
254
+ /**
255
+ * Set that an existing API key is available
256
+ */
257
+ const setExistingProvider = useCallback((provider: AIProvider) => {
258
+ setState((prev) => ({
259
+ ...prev,
260
+ provider,
261
+ hasApiKey: true,
262
+ apiKeyEnteredThisSession: false,
263
+ phase: 'model-select',
264
+ }));
265
+ }, []);
266
+
267
+ /**
268
+ * Set selected provider (when no existing key)
269
+ */
270
+ const selectProvider = useCallback((provider: AIProvider) => {
271
+ setState((prev) => ({
272
+ ...prev,
273
+ provider,
274
+ phase: 'key-input',
275
+ }));
276
+ }, []);
277
+
278
+ /**
279
+ * Set API key
280
+ */
281
+ const setApiKey = useCallback((key: string) => {
282
+ apiKeyRef.current = key;
283
+ setState((prev) => ({
284
+ ...prev,
285
+ hasApiKey: true,
286
+ apiKeyEnteredThisSession: true,
287
+ phase: 'key-save',
288
+ }));
289
+ }, []);
290
+
291
+ /**
292
+ * Set whether to save key to .env.local
293
+ */
294
+ const setSaveKey = useCallback((save: boolean) => {
295
+ setState((prev) => ({
296
+ ...prev,
297
+ saveKeyToEnv: save,
298
+ phase: 'model-select',
299
+ }));
300
+ }, []);
301
+
302
+ /**
303
+ * Select model and advance to AI analysis
304
+ */
305
+ const selectModel = useCallback((model: string) => {
306
+ setState((prev) => ({
307
+ ...prev,
308
+ model,
309
+ phase: 'ai-analysis',
310
+ isWorking: true,
311
+ workingStatus: 'Initializing AI analysis...',
312
+ }));
313
+ }, []);
314
+
315
+ /**
316
+ * Set AI analysis progress
317
+ */
318
+ const setAiProgress = useCallback((status: string) => {
319
+ setState((prev) => ({
320
+ ...prev,
321
+ workingStatus: status,
322
+ }));
323
+ }, []);
324
+
325
+ /**
326
+ * Set enhanced result from AI analysis
327
+ */
328
+ const setEnhancedResult = useCallback(
329
+ (result: EnhancedScanResult, tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number }) => {
330
+ setState((prev) => ({
331
+ ...prev,
332
+ enhancedResult: result,
333
+ tokenUsage: tokenUsage || null,
334
+ isWorking: false,
335
+ workingStatus: '',
336
+ phase: 'confirm' as const,
337
+ }));
338
+ },
339
+ []
340
+ );
341
+
342
+ /**
343
+ * Set AI analysis error
344
+ */
345
+ const setAiError = useCallback((error: string) => {
346
+ setState((prev) => ({
347
+ ...prev,
348
+ error,
349
+ isWorking: false,
350
+ workingStatus: '',
351
+ // Continue to confirm even with AI error (will use non-enhanced scan result)
352
+ phase: 'confirm',
353
+ }));
354
+ }, []);
355
+
356
+ /**
357
+ * Confirm file generation
358
+ */
359
+ const confirmGeneration = useCallback((confirmed: boolean) => {
360
+ if (confirmed) {
361
+ setState((prev) => ({
362
+ ...prev,
363
+ phase: 'generating',
364
+ isWorking: true,
365
+ workingStatus: 'Generating configuration files...',
366
+ }));
367
+ } else {
368
+ // User cancelled
369
+ setState((prev) => ({
370
+ ...prev,
371
+ phase: 'error',
372
+ error: 'Cancelled by user',
373
+ }));
374
+ }
375
+ }, []);
376
+
377
+ /**
378
+ * Set generation in progress
379
+ */
380
+ const setGenerating = useCallback((status: string) => {
381
+ setState((prev) => ({
382
+ ...prev,
383
+ workingStatus: status,
384
+ }));
385
+ }, []);
386
+
387
+ /**
388
+ * Set generation complete
389
+ */
390
+ const setGenerationComplete = useCallback((files: string[]) => {
391
+ setState((prev) => ({
392
+ ...prev,
393
+ generatedFiles: files,
394
+ isWorking: false,
395
+ workingStatus: '',
396
+ phase: 'complete',
397
+ }));
398
+ }, []);
399
+
400
+ /**
401
+ * Set error state
402
+ */
403
+ const setError = useCallback((error: string) => {
404
+ setState((prev) => ({
405
+ ...prev,
406
+ error,
407
+ isWorking: false,
408
+ phase: 'error',
409
+ }));
410
+ }, []);
411
+
412
+ /**
413
+ * Reset to initial state
414
+ */
415
+ const reset = useCallback(() => {
416
+ apiKeyRef.current = null;
417
+ setState(initialState);
418
+ }, []);
419
+
420
+ /**
421
+ * Go back to previous phase
422
+ */
423
+ const goBack = useCallback(() => {
424
+ setState((prev) => {
425
+ // Define phase transitions for going back
426
+ const backTransitions: Partial<Record<InitPhase, InitPhase>> = {
427
+ 'provider-select': 'scanning', // Can't really go back from provider select
428
+ 'key-input': 'provider-select',
429
+ 'key-save': 'key-input',
430
+ 'model-select': prev.apiKeyEnteredThisSession ? 'key-save' : 'provider-select',
431
+ 'ai-analysis': 'model-select',
432
+ confirm: 'ai-analysis',
433
+ generating: 'confirm',
434
+ };
435
+
436
+ const prevPhase = backTransitions[prev.phase];
437
+ if (prevPhase) {
438
+ return { ...prev, phase: prevPhase };
439
+ }
440
+ return prev;
441
+ });
442
+ }, []);
443
+
444
+ return {
445
+ state,
446
+ initialize,
447
+ setScanResult,
448
+ setExistingProvider,
449
+ selectProvider,
450
+ setApiKey,
451
+ setSaveKey,
452
+ selectModel,
453
+ setAiProgress,
454
+ setEnhancedResult,
455
+ setAiError,
456
+ confirmGeneration,
457
+ setGenerating,
458
+ setGenerationComplete,
459
+ setError,
460
+ reset,
461
+ goBack,
462
+ };
463
+ }
464
+
465
+ /**
466
+ * Get API key from the ref (for use in the screen component)
467
+ * This is a workaround since we don't store API key in state
468
+ */
469
+ export function getApiKeyFromRef(): string | null {
470
+ // This will be managed by the screen component directly
471
+ return null;
472
+ }
@@ -153,6 +153,11 @@ export interface UseSpecGeneratorReturn {
153
153
  */
154
154
  addMessage: (role: 'user' | 'assistant' | 'system', content: string, toolCalls?: ToolCall[]) => void;
155
155
 
156
+ /**
157
+ * Add a streaming message (assistant) that will be updated
158
+ */
159
+ addStreamingMessage: (initialContent?: string, toolCalls?: ToolCall[]) => string;
160
+
156
161
  /**
157
162
  * Update the streaming message content
158
163
  */
@@ -490,7 +495,17 @@ export function useSpecGenerator(): UseSpecGeneratorReturn {
490
495
  error,
491
496
  };
492
497
  }
493
- return { ...msg, toolCalls: updatedToolCalls };
498
+ // If this message was only for tool calls and has no content, stop streaming
499
+ const allDone = updatedToolCalls.every(tc => tc.status !== 'running');
500
+ const shouldStopStreaming = msg.isStreaming && allDone && msg.content.trim() === '';
501
+ if (shouldStopStreaming && streamingMessageIdRef.current === msg.id) {
502
+ streamingMessageIdRef.current = null;
503
+ }
504
+ return {
505
+ ...msg,
506
+ toolCalls: updatedToolCalls,
507
+ isStreaming: shouldStopStreaming ? false : msg.isStreaming,
508
+ };
494
509
  }
495
510
  return msg;
496
511
  }),
@@ -648,6 +663,7 @@ export function useSpecGenerator(): UseSpecGeneratorReturn {
648
663
  reset,
649
664
  initialize,
650
665
  addMessage,
666
+ addStreamingMessage,
651
667
  updateStreamingMessage,
652
668
  completeStreamingMessage,
653
669
  setReady,