wiggum-cli 0.5.4 → 0.7.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 (89) hide show
  1. package/README.md +88 -22
  2. package/dist/ai/conversation/conversation-manager.d.ts +84 -0
  3. package/dist/ai/conversation/conversation-manager.d.ts.map +1 -0
  4. package/dist/ai/conversation/conversation-manager.js +159 -0
  5. package/dist/ai/conversation/conversation-manager.js.map +1 -0
  6. package/dist/ai/conversation/index.d.ts +8 -0
  7. package/dist/ai/conversation/index.d.ts.map +1 -0
  8. package/dist/ai/conversation/index.js +8 -0
  9. package/dist/ai/conversation/index.js.map +1 -0
  10. package/dist/ai/conversation/spec-generator.d.ts +62 -0
  11. package/dist/ai/conversation/spec-generator.d.ts.map +1 -0
  12. package/dist/ai/conversation/spec-generator.js +267 -0
  13. package/dist/ai/conversation/spec-generator.js.map +1 -0
  14. package/dist/ai/conversation/url-fetcher.d.ts +26 -0
  15. package/dist/ai/conversation/url-fetcher.d.ts.map +1 -0
  16. package/dist/ai/conversation/url-fetcher.js +145 -0
  17. package/dist/ai/conversation/url-fetcher.js.map +1 -0
  18. package/dist/cli.d.ts.map +1 -1
  19. package/dist/cli.js +44 -34
  20. package/dist/cli.js.map +1 -1
  21. package/dist/commands/init.d.ts +19 -0
  22. package/dist/commands/init.d.ts.map +1 -1
  23. package/dist/commands/init.js +61 -21
  24. package/dist/commands/init.js.map +1 -1
  25. package/dist/commands/new.d.ts +11 -1
  26. package/dist/commands/new.d.ts.map +1 -1
  27. package/dist/commands/new.js +102 -43
  28. package/dist/commands/new.js.map +1 -1
  29. package/dist/commands/run.js +3 -3
  30. package/dist/commands/run.js.map +1 -1
  31. package/dist/generator/config.d.ts +3 -3
  32. package/dist/generator/config.d.ts.map +1 -1
  33. package/dist/generator/config.js +5 -3
  34. package/dist/generator/config.js.map +1 -1
  35. package/dist/generator/index.js +1 -1
  36. package/dist/generator/index.js.map +1 -1
  37. package/dist/generator/writer.js +1 -1
  38. package/dist/generator/writer.js.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +34 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/repl/command-parser.d.ts +84 -0
  44. package/dist/repl/command-parser.d.ts.map +1 -0
  45. package/dist/repl/command-parser.js +112 -0
  46. package/dist/repl/command-parser.js.map +1 -0
  47. package/dist/repl/index.d.ts +8 -0
  48. package/dist/repl/index.d.ts.map +1 -0
  49. package/dist/repl/index.js +8 -0
  50. package/dist/repl/index.js.map +1 -0
  51. package/dist/repl/repl-loop.d.ts +30 -0
  52. package/dist/repl/repl-loop.d.ts.map +1 -0
  53. package/dist/repl/repl-loop.js +262 -0
  54. package/dist/repl/repl-loop.js.map +1 -0
  55. package/dist/repl/session-state.d.ts +37 -0
  56. package/dist/repl/session-state.d.ts.map +1 -0
  57. package/dist/repl/session-state.js +26 -0
  58. package/dist/repl/session-state.js.map +1 -0
  59. package/dist/templates/root/README.md.tmpl +1 -1
  60. package/dist/templates/scripts/feature-loop.sh.tmpl +17 -17
  61. package/dist/templates/scripts/loop.sh.tmpl +7 -7
  62. package/dist/templates/scripts/ralph-monitor.sh.tmpl +5 -5
  63. package/dist/utils/config.d.ts +7 -7
  64. package/dist/utils/config.js +4 -4
  65. package/dist/utils/config.js.map +1 -1
  66. package/package.json +1 -1
  67. package/src/ai/conversation/conversation-manager.ts +230 -0
  68. package/src/ai/conversation/index.ts +23 -0
  69. package/src/ai/conversation/spec-generator.ts +327 -0
  70. package/src/ai/conversation/url-fetcher.ts +180 -0
  71. package/src/cli.ts +47 -34
  72. package/src/commands/init.ts +86 -22
  73. package/src/commands/new.ts +121 -44
  74. package/src/commands/run.ts +3 -3
  75. package/src/generator/config.ts +5 -3
  76. package/src/generator/index.ts +1 -1
  77. package/src/generator/writer.ts +1 -1
  78. package/src/index.ts +46 -0
  79. package/src/repl/command-parser.ts +154 -0
  80. package/src/repl/index.ts +23 -0
  81. package/src/repl/repl-loop.ts +339 -0
  82. package/src/repl/session-state.ts +63 -0
  83. package/src/templates/config/ralph.config.cjs.tmpl +38 -0
  84. package/src/templates/root/README.md.tmpl +1 -1
  85. package/src/templates/scripts/feature-loop.sh.tmpl +17 -17
  86. package/src/templates/scripts/loop.sh.tmpl +7 -7
  87. package/src/templates/scripts/ralph-monitor.sh.tmpl +5 -5
  88. package/src/utils/config.ts +9 -9
  89. /package/{src/templates/config/ralph.config.js.tmpl → dist/templates/config/ralph.config.cjs.tmpl} +0 -0
@@ -0,0 +1,180 @@
1
+ /**
2
+ * URL Fetcher
3
+ * Fetches content from URLs and local files for context gathering
4
+ */
5
+
6
+ import { readFileSync, existsSync } from 'node:fs';
7
+ import { resolve, isAbsolute } from 'node:path';
8
+
9
+ const MAX_CONTENT_LENGTH = 10000;
10
+ const FETCH_TIMEOUT = 10000;
11
+
12
+ /**
13
+ * Fetched content result
14
+ */
15
+ export interface FetchedContent {
16
+ source: string;
17
+ content: string;
18
+ truncated: boolean;
19
+ error?: string;
20
+ }
21
+
22
+ /**
23
+ * Check if a string is a URL
24
+ */
25
+ export function isUrl(input: string): boolean {
26
+ try {
27
+ const url = new URL(input);
28
+ return url.protocol === 'http:' || url.protocol === 'https:';
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Extract text content from HTML
36
+ * Simple extraction that removes scripts, styles, and HTML tags
37
+ */
38
+ function extractTextFromHtml(html: string): string {
39
+ // Remove script and style tags with their content
40
+ let text = html
41
+ .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
42
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
43
+ .replace(/<noscript[^>]*>[\s\S]*?<\/noscript>/gi, '');
44
+
45
+ // Remove HTML tags but keep content
46
+ text = text.replace(/<[^>]+>/g, ' ');
47
+
48
+ // Decode common HTML entities
49
+ text = text
50
+ .replace(/&nbsp;/g, ' ')
51
+ .replace(/&amp;/g, '&')
52
+ .replace(/&lt;/g, '<')
53
+ .replace(/&gt;/g, '>')
54
+ .replace(/&quot;/g, '"')
55
+ .replace(/&#39;/g, "'");
56
+
57
+ // Clean up whitespace
58
+ text = text.replace(/\s+/g, ' ').trim();
59
+
60
+ return text;
61
+ }
62
+
63
+ /**
64
+ * Fetch content from a URL
65
+ */
66
+ async function fetchFromUrl(url: string): Promise<FetchedContent> {
67
+ try {
68
+ const controller = new AbortController();
69
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
70
+
71
+ const response = await fetch(url, {
72
+ signal: controller.signal,
73
+ headers: {
74
+ 'User-Agent': 'Wiggum-CLI/1.0 (Feature Spec Generator)',
75
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,text/plain;q=0.8,*/*;q=0.7',
76
+ },
77
+ });
78
+
79
+ clearTimeout(timeoutId);
80
+
81
+ if (!response.ok) {
82
+ return {
83
+ source: url,
84
+ content: '',
85
+ truncated: false,
86
+ error: `HTTP ${response.status}: ${response.statusText}`,
87
+ };
88
+ }
89
+
90
+ const contentType = response.headers.get('content-type') || '';
91
+ let text = await response.text();
92
+
93
+ // If HTML, extract text content
94
+ if (contentType.includes('text/html')) {
95
+ text = extractTextFromHtml(text);
96
+ }
97
+
98
+ // Truncate if too long
99
+ const truncated = text.length > MAX_CONTENT_LENGTH;
100
+ if (truncated) {
101
+ text = text.slice(0, MAX_CONTENT_LENGTH) + '\n\n[Content truncated...]';
102
+ }
103
+
104
+ return {
105
+ source: url,
106
+ content: text,
107
+ truncated,
108
+ };
109
+ } catch (error) {
110
+ const errorMessage = error instanceof Error ? error.message : String(error);
111
+ return {
112
+ source: url,
113
+ content: '',
114
+ truncated: false,
115
+ error: errorMessage.includes('abort') ? 'Request timed out' : errorMessage,
116
+ };
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Read content from a local file
122
+ */
123
+ function readFromFile(filePath: string, projectRoot: string): FetchedContent {
124
+ try {
125
+ const absolutePath = isAbsolute(filePath) ? filePath : resolve(projectRoot, filePath);
126
+
127
+ if (!existsSync(absolutePath)) {
128
+ return {
129
+ source: filePath,
130
+ content: '',
131
+ truncated: false,
132
+ error: 'File not found',
133
+ };
134
+ }
135
+
136
+ let content = readFileSync(absolutePath, 'utf-8');
137
+
138
+ // Truncate if too long
139
+ const truncated = content.length > MAX_CONTENT_LENGTH;
140
+ if (truncated) {
141
+ content = content.slice(0, MAX_CONTENT_LENGTH) + '\n\n[Content truncated...]';
142
+ }
143
+
144
+ return {
145
+ source: filePath,
146
+ content,
147
+ truncated,
148
+ };
149
+ } catch (error) {
150
+ return {
151
+ source: filePath,
152
+ content: '',
153
+ truncated: false,
154
+ error: error instanceof Error ? error.message : String(error),
155
+ };
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Fetch content from a URL or local file path
161
+ */
162
+ export async function fetchContent(
163
+ input: string,
164
+ projectRoot: string
165
+ ): Promise<FetchedContent> {
166
+ if (isUrl(input)) {
167
+ return fetchFromUrl(input);
168
+ }
169
+ return readFromFile(input, projectRoot);
170
+ }
171
+
172
+ /**
173
+ * Fetch multiple sources in parallel
174
+ */
175
+ export async function fetchMultipleSources(
176
+ inputs: string[],
177
+ projectRoot: string
178
+ ): Promise<FetchedContent[]> {
179
+ return Promise.all(inputs.map(input => fetchContent(input, projectRoot)));
180
+ }
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ export function createCli(): Command {
13
13
  const program = new Command();
14
14
 
15
15
  program
16
- .name('ralph')
16
+ .name('wiggum')
17
17
  .description(
18
18
  'AI-powered feature development loop CLI.\n\n' +
19
19
  'Ralph auto-detects your tech stack and generates an intelligent\n' +
@@ -27,17 +27,17 @@ export function createCli(): Command {
27
27
  'after',
28
28
  `
29
29
  Examples:
30
- $ ralph init Initialize Ralph with AI analysis
31
- $ ralph new my-feature Create a new feature specification
32
- $ ralph run my-feature Run the feature development loop
33
- $ ralph monitor my-feature Monitor progress in real-time
30
+ $ wiggum init Initialize Wiggum with AI analysis
31
+ $ wiggum new my-feature Create a new feature specification
32
+ $ wiggum run my-feature Run the feature development loop
33
+ $ wiggum monitor my-feature Monitor progress in real-time
34
34
 
35
35
  Documentation:
36
- https://github.com/your-org/ralph-cli#readme
36
+ https://github.com/your-org/wiggum-cli#readme
37
37
  `
38
38
  );
39
39
 
40
- // ralph init
40
+ // wiggum init
41
41
  program
42
42
  .command('init')
43
43
  .description(
@@ -51,13 +51,15 @@ Documentation:
51
51
  'anthropic'
52
52
  )
53
53
  .option('-y, --yes', 'Accept defaults and skip all confirmation prompts')
54
+ .option('-i, --interactive', 'Stay in interactive REPL mode after initialization')
54
55
  .addHelpText(
55
56
  'after',
56
57
  `
57
58
  Examples:
58
- $ ralph init Initialize with AI analysis
59
- $ ralph init --provider openai Use OpenAI provider
60
- $ ralph init --yes Non-interactive mode
59
+ $ wiggum init Initialize with AI analysis
60
+ $ wiggum init --provider openai Use OpenAI provider
61
+ $ wiggum init --yes Non-interactive mode
62
+ $ wiggum init -i Initialize and enter interactive mode
61
63
 
62
64
  API Keys (BYOK - Bring Your Own Keys):
63
65
  Required (one of):
@@ -78,7 +80,7 @@ API Keys (BYOK - Bring Your Own Keys):
78
80
  }
79
81
  });
80
82
 
81
- // ralph run <feature>
83
+ // wiggum run <feature>
82
84
  program
83
85
  .command('run <feature>')
84
86
  .description(
@@ -112,14 +114,14 @@ API Keys (BYOK - Bring Your Own Keys):
112
114
  'after',
113
115
  `
114
116
  Examples:
115
- $ ralph run user-auth Run the user-auth feature
116
- $ ralph run payment --worktree Run in isolated worktree
117
- $ ralph run payment --resume Resume interrupted session
118
- $ ralph run my-feature --model opus Use Claude Opus model
119
- $ ralph run my-feature --max-iterations 30 --max-e2e-attempts 5
117
+ $ wiggum run user-auth Run the user-auth feature
118
+ $ wiggum run payment --worktree Run in isolated worktree
119
+ $ wiggum run payment --resume Resume interrupted session
120
+ $ wiggum run my-feature --model opus Use Claude Opus model
121
+ $ wiggum run my-feature --max-iterations 30 --max-e2e-attempts 5
120
122
 
121
123
  Notes:
122
- - Create a feature spec first with: ralph new <feature>
124
+ - Create a feature spec first with: wiggum new <feature>
123
125
  - The spec file should be at: .ralph/specs/<feature>.md
124
126
  - Use --worktree to run multiple features in parallel
125
127
  `
@@ -139,7 +141,7 @@ Notes:
139
141
  }
140
142
  });
141
143
 
142
- // ralph monitor <feature>
144
+ // wiggum monitor <feature>
143
145
  program
144
146
  .command('monitor <feature>')
145
147
  .description(
@@ -162,9 +164,9 @@ Notes:
162
164
  'after',
163
165
  `
164
166
  Examples:
165
- $ ralph monitor my-feature Monitor with built-in dashboard
166
- $ ralph monitor my-feature --interval 2 Refresh every 2 seconds
167
- $ ralph monitor my-feature --bash Use bash script monitor
167
+ $ wiggum monitor my-feature Monitor with built-in dashboard
168
+ $ wiggum monitor my-feature --interval 2 Refresh every 2 seconds
169
+ $ wiggum monitor my-feature --bash Use bash script monitor
168
170
 
169
171
  Dashboard Shows:
170
172
  - Current phase (Planning, Implementation, E2E Testing, etc.)
@@ -187,7 +189,7 @@ Dashboard Shows:
187
189
  }
188
190
  });
189
191
 
190
- // ralph new <feature>
192
+ // wiggum new <feature>
191
193
  program
192
194
  .command('new <feature>')
193
195
  .description(
@@ -202,26 +204,34 @@ Dashboard Shows:
202
204
  )
203
205
  .option('-y, --yes', 'Skip confirmation prompts')
204
206
  .option('-f, --force', 'Overwrite existing spec file without prompting')
207
+ .option('--ai', 'Use AI interview to generate the spec')
208
+ .option(
209
+ '--provider <name>',
210
+ 'AI provider for spec generation (anthropic, openai, openrouter)'
211
+ )
212
+ .option('--model <model>', 'Model to use for AI spec generation')
205
213
  .addHelpText(
206
214
  'after',
207
215
  `
208
216
  Examples:
209
- $ ralph new user-dashboard Create spec with prompts
210
- $ ralph new user-dashboard --edit Create and open in editor
211
- $ ralph new user-dashboard -e --editor vim Open in vim
212
- $ ralph new user-dashboard --yes Skip confirmations
213
- $ ralph new user-dashboard --force Overwrite if exists
217
+ $ wiggum new user-dashboard Create spec from template
218
+ $ wiggum new user-dashboard --ai Use AI interview to generate spec
219
+ $ wiggum new user-dashboard --edit Create and open in editor
220
+ $ wiggum new user-dashboard -e --editor vim Open in vim
221
+ $ wiggum new user-dashboard --yes Skip confirmations
222
+ $ wiggum new user-dashboard --force Overwrite if exists
214
223
 
215
224
  Output:
216
225
  Creates: .ralph/specs/<feature>.md
217
226
 
218
- Template includes sections for:
219
- - Purpose and user stories
220
- - Functional and non-functional requirements
221
- - Technical notes and dependencies
222
- - Visual requirements (for UI features)
223
- - API endpoints
224
- - Acceptance criteria
227
+ AI Mode (--ai):
228
+ - Gathers context from URLs/files you provide
229
+ - Conducts an interview to understand your requirements
230
+ - Generates a detailed, project-specific specification
231
+
232
+ Template Mode (default):
233
+ - Uses a standard template with sections for:
234
+ Purpose, user stories, requirements, technical notes, etc.
225
235
  `
226
236
  )
227
237
  .action(async (feature: string, options) => {
@@ -231,6 +241,9 @@ Template includes sections for:
231
241
  editor: options.editor,
232
242
  yes: options.yes,
233
243
  force: options.force,
244
+ ai: options.ai,
245
+ provider: options.provider,
246
+ model: options.model,
234
247
  };
235
248
  await newCommand(feature, newOptions);
236
249
  } catch (error) {
@@ -31,6 +31,8 @@ import {
31
31
  } from '../utils/colors.js';
32
32
  import { flushTracing } from '../utils/tracing.js';
33
33
  import { createShimmerSpinner, type ShimmerSpinner } from '../utils/spinner.js';
34
+ import { startRepl, createSessionState } from '../repl/index.js';
35
+ import { loadConfigWithDefaults } from '../utils/config.js';
34
36
 
35
37
  const FIXED_MASK = '*'.repeat(32);
36
38
 
@@ -116,6 +118,18 @@ async function securePasswordInput(message: string): Promise<string | null> {
116
118
  export interface InitOptions {
117
119
  provider?: AIProvider;
118
120
  yes?: boolean;
121
+ interactive?: boolean;
122
+ }
123
+
124
+ /**
125
+ * Result of the init workflow
126
+ */
127
+ export interface InitResult {
128
+ success: boolean;
129
+ provider: AIProvider;
130
+ model: string;
131
+ scanResult: ScanResult;
132
+ config: import('../utils/config.js').RalphConfig | null;
119
133
  }
120
134
 
121
135
  /**
@@ -275,12 +289,15 @@ async function collectApiKeys(
275
289
  }
276
290
 
277
291
  /**
278
- * Initialize Ralph in the current project
279
- * Uses BYOK (Bring Your Own Keys) model with multi-agent AI analysis
292
+ * Run the init workflow
293
+ * Reusable core logic for both CLI and REPL usage
294
+ * Returns InitResult on success, null on cancellation
295
+ * Throws on hard errors
280
296
  */
281
- export async function initCommand(options: InitOptions): Promise<void> {
282
- const projectRoot = process.cwd();
283
-
297
+ export async function runInitWorkflow(
298
+ projectRoot: string,
299
+ options: InitOptions
300
+ ): Promise<InitResult | null> {
284
301
  logger.info('Initializing Ralph...');
285
302
  logger.info(`Project: ${projectRoot}`);
286
303
  console.log('');
@@ -297,9 +314,8 @@ export async function initCommand(options: InitOptions): Promise<void> {
297
314
  scanSpinner.stop('Project scanned');
298
315
  } catch (error) {
299
316
  scanSpinner.fail('Scan failed');
300
- logger.error(`Failed to scan project: ${error instanceof Error ? error.message : String(error)}`);
301
317
  await flushTracing();
302
- process.exit(1);
318
+ throw new Error(`Failed to scan project: ${error instanceof Error ? error.message : String(error)}`);
303
319
  }
304
320
 
305
321
  // Step 2: Show detected stack
@@ -317,14 +333,9 @@ export async function initCommand(options: InitOptions): Promise<void> {
317
333
  const apiKeys = await collectApiKeys(projectRoot, options);
318
334
 
319
335
  if (!apiKeys) {
320
- // In --yes mode, null means missing API key (hard failure)
321
- // In interactive mode, null means user cancelled
336
+ // User cancelled or missing API key in --yes mode
322
337
  await flushTracing();
323
- if (options.yes) {
324
- process.exit(1);
325
- }
326
- logger.info('Initialization cancelled');
327
- return;
338
+ return null;
328
339
  }
329
340
 
330
341
  // Step 4: Run AI analysis
@@ -387,8 +398,7 @@ export async function initCommand(options: InitOptions): Promise<void> {
387
398
 
388
399
  if (prompts.isCancel(shouldContinue) || !shouldContinue) {
389
400
  await flushTracing();
390
- logger.info('Initialization cancelled');
391
- return;
401
+ return null;
392
402
  }
393
403
  }
394
404
 
@@ -424,23 +434,77 @@ export async function initCommand(options: InitOptions): Promise<void> {
424
434
  await flushTracing();
425
435
 
426
436
  if (generationResult.success) {
427
- // Show next steps
437
+ // Show next steps (REPL-aware)
428
438
  console.log(nextStepsBox([
429
- { command: 'ralph new my-feature', description: 'Create a feature specification' },
430
- { command: 'ralph run my-feature', description: 'Start the development loop' },
431
- { command: 'ralph monitor my-feature', description: 'Watch progress in real-time' },
439
+ { command: '/new my-feature', description: 'Create a feature specification' },
440
+ { command: '/run my-feature', description: 'Start the development loop' },
441
+ { command: '/help', description: 'Show all available commands' },
432
442
  ]));
433
443
 
434
444
  console.log(` ${simpson.brown('Documentation:')} .ralph/guides/AGENTS.md`);
435
445
  console.log('');
436
- logger.success('Ralph initialized successfully!');
446
+ logger.success('Wiggum initialized successfully!');
447
+
448
+ // Load config and return result
449
+ const config = await loadConfigWithDefaults(projectRoot);
450
+ return {
451
+ success: true,
452
+ provider: apiKeys.provider,
453
+ model: apiKeys.model,
454
+ scanResult,
455
+ config,
456
+ };
437
457
  } else {
438
458
  logger.warn('Initialization completed with some errors');
459
+ const config = await loadConfigWithDefaults(projectRoot);
460
+ return {
461
+ success: true, // Still return success to continue
462
+ provider: apiKeys.provider,
463
+ model: apiKeys.model,
464
+ scanResult,
465
+ config,
466
+ };
439
467
  }
440
468
  } catch (error) {
441
469
  genSpinner.fail('Generation failed');
442
- logger.error(`Failed to generate files: ${error instanceof Error ? error.message : String(error)}`);
443
470
  await flushTracing();
471
+ throw new Error(`Failed to generate files: ${error instanceof Error ? error.message : String(error)}`);
472
+ }
473
+ }
474
+
475
+ /**
476
+ * Initialize Ralph in the current project
477
+ * Uses BYOK (Bring Your Own Keys) model with multi-agent AI analysis
478
+ */
479
+ export async function initCommand(options: InitOptions): Promise<void> {
480
+ const projectRoot = process.cwd();
481
+
482
+ try {
483
+ const result = await runInitWorkflow(projectRoot, options);
484
+
485
+ if (!result) {
486
+ // Cancelled by user or missing API key
487
+ if (options.yes) {
488
+ process.exit(1);
489
+ }
490
+ logger.info('Initialization cancelled');
491
+ return;
492
+ }
493
+
494
+ // Start interactive REPL if requested
495
+ if (options.interactive) {
496
+ const sessionState = createSessionState(
497
+ projectRoot,
498
+ result.provider,
499
+ result.model,
500
+ result.scanResult,
501
+ result.config,
502
+ true // initialized
503
+ );
504
+ await startRepl(sessionState);
505
+ }
506
+ } catch (error) {
507
+ logger.error(error instanceof Error ? error.message : String(error));
444
508
  process.exit(1);
445
509
  }
446
510
  }