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
package/src/cli.ts DELETED
@@ -1,274 +0,0 @@
1
- import { Command } from 'commander';
2
- import { displayHeader } from './utils/header.js';
3
- import { initCommand } from './commands/init.js';
4
- import { runCommand, type RunOptions } from './commands/run.js';
5
- import { monitorCommand, type MonitorOptions } from './commands/monitor.js';
6
- import { newCommand, type NewOptions } from './commands/new.js';
7
- import { logger } from './utils/logger.js';
8
-
9
- /**
10
- * Set up and configure the CLI
11
- */
12
- export function createCli(): Command {
13
- const program = new Command();
14
-
15
- program
16
- .name('wiggum')
17
- .description(
18
- 'AI-powered feature development loop CLI.\n\n' +
19
- 'Ralph auto-detects your tech stack and generates an intelligent\n' +
20
- 'development environment for AI-driven feature implementation.'
21
- )
22
- .version('0.1.0')
23
- .hook('preAction', () => {
24
- displayHeader();
25
- })
26
- .addHelpText(
27
- 'after',
28
- `
29
- Examples:
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
-
35
- Documentation:
36
- https://github.com/your-org/wiggum-cli#readme
37
- `
38
- );
39
-
40
- // wiggum init
41
- program
42
- .command('init')
43
- .description(
44
- 'Initialize Ralph in the current project.\n\n' +
45
- 'Uses AI to analyze your codebase, detect the tech stack, and generate\n' +
46
- 'intelligent configuration files in .ralph/'
47
- )
48
- .option(
49
- '--provider <name>',
50
- 'AI provider to use (anthropic, openai, openrouter)',
51
- 'anthropic'
52
- )
53
- .option('-y, --yes', 'Accept defaults and skip all confirmation prompts')
54
- .option('-i, --interactive', 'Stay in interactive REPL mode after initialization')
55
- .addHelpText(
56
- 'after',
57
- `
58
- Examples:
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
63
-
64
- API Keys (BYOK - Bring Your Own Keys):
65
- Required (one of):
66
- ANTHROPIC_API_KEY For Anthropic (Claude) provider
67
- OPENAI_API_KEY For OpenAI provider
68
- OPENROUTER_API_KEY For OpenRouter provider
69
-
70
- Optional (for enhanced research):
71
- TAVILY_API_KEY Enable web search for best practices
72
- CONTEXT7_API_KEY Enable documentation lookup
73
- `
74
- )
75
- .action(async (options) => {
76
- try {
77
- await initCommand(options);
78
- } catch (error) {
79
- handleCommandError(error);
80
- }
81
- });
82
-
83
- // wiggum run <feature>
84
- program
85
- .command('run <feature>')
86
- .description(
87
- 'Run the feature development loop for a specific feature.\n\n' +
88
- 'Executes the AI-driven implementation workflow using the feature\n' +
89
- 'spec in .ralph/specs/<feature>.md'
90
- )
91
- .option(
92
- '--worktree',
93
- 'Use git worktree for isolation (enables parallel execution of multiple features)'
94
- )
95
- .option(
96
- '--resume',
97
- 'Resume an interrupted loop (reuses existing branch and worktree)'
98
- )
99
- .option(
100
- '--model <model>',
101
- 'Claude model to use for implementation (opus, sonnet)'
102
- )
103
- .option(
104
- '--max-iterations <n>',
105
- 'Maximum number of implementation iterations (default: 50)',
106
- parseInt
107
- )
108
- .option(
109
- '--max-e2e-attempts <n>',
110
- 'Maximum E2E test retry attempts before giving up (default: 3)',
111
- parseInt
112
- )
113
- .addHelpText(
114
- 'after',
115
- `
116
- Examples:
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
122
-
123
- Notes:
124
- - Create a feature spec first with: wiggum new <feature>
125
- - The spec file should be at: .ralph/specs/<feature>.md
126
- - Use --worktree to run multiple features in parallel
127
- `
128
- )
129
- .action(async (feature: string, options) => {
130
- try {
131
- const runOptions: RunOptions = {
132
- worktree: options.worktree,
133
- resume: options.resume,
134
- model: options.model,
135
- maxIterations: options.maxIterations,
136
- maxE2eAttempts: options.maxE2eAttempts,
137
- };
138
- await runCommand(feature, runOptions);
139
- } catch (error) {
140
- handleCommandError(error);
141
- }
142
- });
143
-
144
- // wiggum monitor <feature>
145
- program
146
- .command('monitor <feature>')
147
- .description(
148
- 'Launch the monitoring dashboard for a feature.\n\n' +
149
- 'Displays real-time progress including iteration count, phase,\n' +
150
- 'task completion, token usage, and E2E test status.'
151
- )
152
- .option(
153
- '--bash',
154
- 'Use the bash script monitor instead of the built-in dashboard'
155
- )
156
- .option('--python', 'Use the Python TUI monitor (if available)')
157
- .option(
158
- '--interval <seconds>',
159
- 'Dashboard refresh interval in seconds (default: 5)',
160
- parseInt,
161
- 5
162
- )
163
- .addHelpText(
164
- 'after',
165
- `
166
- Examples:
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
170
-
171
- Dashboard Shows:
172
- - Current phase (Planning, Implementation, E2E Testing, etc.)
173
- - Iteration progress
174
- - Task completion status
175
- - Token usage (input/output)
176
- - Git branch information
177
- `
178
- )
179
- .action(async (feature: string, options) => {
180
- try {
181
- const monitorOptions: MonitorOptions = {
182
- bash: options.bash,
183
- python: options.python,
184
- interval: options.interval,
185
- };
186
- await monitorCommand(feature, monitorOptions);
187
- } catch (error) {
188
- handleCommandError(error);
189
- }
190
- });
191
-
192
- // wiggum new <feature>
193
- program
194
- .command('new <feature>')
195
- .description(
196
- 'Create a new feature specification from template.\n\n' +
197
- 'Generates a markdown spec file with sections for requirements,\n' +
198
- 'acceptance criteria, technical notes, and more.'
199
- )
200
- .option('-e, --edit', 'Open the spec in your editor after creation')
201
- .option(
202
- '--editor <editor>',
203
- 'Editor command to use (defaults to $EDITOR or "code")'
204
- )
205
- .option('-y, --yes', 'Skip confirmation prompts')
206
- .option('-f, --force', 'Overwrite existing spec file without prompting')
207
- .option('--ai', 'Use AI interview to generate the spec')
208
- .option('--tui', 'Use Ink TUI for AI interview (with --ai)')
209
- .option(
210
- '--provider <name>',
211
- 'AI provider for spec generation (anthropic, openai, openrouter)'
212
- )
213
- .option('--model <model>', 'Model to use for AI spec generation')
214
- .addHelpText(
215
- 'after',
216
- `
217
- Examples:
218
- $ wiggum new user-dashboard Create spec from template
219
- $ wiggum new user-dashboard --ai Use AI interview to generate spec
220
- $ wiggum new user-dashboard --ai --tui Use AI with Ink TUI interface
221
- $ wiggum new user-dashboard --edit Create and open in editor
222
- $ wiggum new user-dashboard -e --editor vim Open in vim
223
- $ wiggum new user-dashboard --yes Skip confirmations
224
- $ wiggum new user-dashboard --force Overwrite if exists
225
-
226
- Output:
227
- Creates: .ralph/specs/<feature>.md
228
-
229
- AI Mode (--ai):
230
- - Gathers context from URLs/files you provide
231
- - Conducts an interview to understand your requirements
232
- - Generates a detailed, project-specific specification
233
- - Add --tui for a beautiful Ink-based terminal interface
234
-
235
- Template Mode (default):
236
- - Uses a standard template with sections for:
237
- Purpose, user stories, requirements, technical notes, etc.
238
- `
239
- )
240
- .action(async (feature: string, options) => {
241
- try {
242
- const newOptions: NewOptions = {
243
- edit: options.edit,
244
- editor: options.editor,
245
- yes: options.yes,
246
- force: options.force,
247
- ai: options.ai,
248
- tui: options.tui,
249
- provider: options.provider,
250
- model: options.model,
251
- };
252
- await newCommand(feature, newOptions);
253
- } catch (error) {
254
- handleCommandError(error);
255
- }
256
- });
257
-
258
- return program;
259
- }
260
-
261
- /**
262
- * Handle command errors with user-friendly output
263
- */
264
- function handleCommandError(error: unknown): void {
265
- if (error instanceof Error) {
266
- logger.error(error.message);
267
- if (process.env.DEBUG) {
268
- console.error(error.stack);
269
- }
270
- } else {
271
- logger.error(String(error));
272
- }
273
- process.exit(1);
274
- }
@@ -1,389 +0,0 @@
1
- /**
2
- * REPL Loop
3
- * Main interactive loop for the Wiggum CLI
4
- */
5
-
6
- import readline from 'node:readline';
7
- import pc from 'picocolors';
8
- import { logger } from '../utils/logger.js';
9
- import { simpson } from '../utils/colors.js';
10
- import { runCommand } from '../commands/run.js';
11
- import { monitorCommand } from '../commands/monitor.js';
12
- import { runInitWorkflow } from '../commands/init.js';
13
- import { handleConfigCommand } from '../commands/config.js';
14
- import { hasConfig } from '../utils/config.js';
15
- import type { SessionState } from './session-state.js';
16
- import { updateSessionState } from './session-state.js';
17
- import {
18
- parseInput,
19
- resolveCommandAlias,
20
- formatHelpText,
21
- type ReplCommandName,
22
- } from './command-parser.js';
23
-
24
- const PROMPT = `${simpson.yellow('wiggum')}${simpson.brown('>')} `;
25
-
26
- /**
27
- * Clear any buffered stdin data
28
- * Prevents leaked input after subcommands that use their own stdin handling
29
- */
30
- async function clearStdinBuffer(): Promise<void> {
31
- return new Promise((resolve) => {
32
- if (process.stdin.isTTY) {
33
- // Set raw mode temporarily to drain buffer
34
- const wasRaw = process.stdin.isRaw;
35
- process.stdin.setRawMode(true);
36
- process.stdin.once('readable', () => {
37
- // Drain any buffered data
38
- while (process.stdin.read() !== null) {
39
- // discard
40
- }
41
- process.stdin.setRawMode(wasRaw);
42
- resolve();
43
- });
44
- // Trigger readable if nothing buffered
45
- setTimeout(resolve, 10);
46
- } else {
47
- resolve();
48
- }
49
- });
50
- }
51
-
52
- /**
53
- * Handler for the /init command
54
- */
55
- async function handleInitCommand(
56
- _args: string[],
57
- state: SessionState,
58
- rl: readline.Interface
59
- ): Promise<SessionState> {
60
- // Check if already initialized
61
- if (state.initialized && hasConfig(state.projectRoot)) {
62
- logger.warn('Project is already initialized. Re-running init will update configuration.');
63
- console.log('');
64
- }
65
-
66
- // Close REPL readline to avoid conflicts with subcommand's stdin usage
67
- // We'll signal to recreate it after the command completes
68
- rl.close();
69
-
70
- try {
71
- const result = await runInitWorkflow(state.projectRoot, {
72
- yes: false, // Always interactive in REPL
73
- });
74
-
75
- if (result) {
76
- // Update state with init result
77
- return updateSessionState(state, {
78
- provider: result.provider,
79
- model: result.model,
80
- scanResult: result.scanResult,
81
- config: result.config,
82
- initialized: true,
83
- });
84
- }
85
-
86
- // User cancelled
87
- return state;
88
- } catch (error) {
89
- logger.error(`Init failed: ${error instanceof Error ? error.message : String(error)}`);
90
- return state;
91
- }
92
- }
93
-
94
- /**
95
- * Handler for the /new command
96
- * Always uses AI interview mode in REPL (falls back to template if no API key)
97
- */
98
- async function handleNewCommand(
99
- args: string[],
100
- state: SessionState,
101
- rl: readline.Interface
102
- ): Promise<SessionState> {
103
- // Check if initialized
104
- if (!state.initialized && !hasConfig(state.projectRoot)) {
105
- logger.warn('Project not initialized. Run /init first.');
106
- return state;
107
- }
108
-
109
- if (args.length === 0) {
110
- logger.error('Feature name required. Usage: /new <feature-name>');
111
- return state;
112
- }
113
-
114
- const featureName = args[0];
115
-
116
- // Close REPL readline to avoid stdin conflicts with subcommand prompts
117
- rl.close();
118
-
119
- try {
120
- // Delegate to the existing new command behavior
121
- // Always use AI mode in REPL (the command handles fallback to template if no API key)
122
- const { newCommand } = await import('../commands/new.js');
123
- await newCommand(featureName, {
124
- yes: false,
125
- scanResult: state.scanResult,
126
- provider: state.provider ?? undefined,
127
- model: state.model,
128
- ai: true, // Always use AI interview in REPL
129
- });
130
- } catch (error) {
131
- logger.error(`New command failed: ${error instanceof Error ? error.message : String(error)}`);
132
- }
133
-
134
- return state;
135
- }
136
-
137
- /**
138
- * Handler for the /run command
139
- */
140
- async function handleRunCommand(
141
- args: string[],
142
- state: SessionState,
143
- rl: readline.Interface
144
- ): Promise<SessionState> {
145
- // Check if initialized
146
- if (!state.initialized && !hasConfig(state.projectRoot)) {
147
- logger.warn('Project not initialized. Run /init first.');
148
- return state;
149
- }
150
-
151
- if (args.length === 0) {
152
- logger.error('Feature name required. Usage: /run <feature-name>');
153
- return state;
154
- }
155
-
156
- const featureName = args[0];
157
-
158
- // Close REPL readline to avoid stdin conflicts with subcommand
159
- rl.close();
160
-
161
- try {
162
- await runCommand(featureName, {});
163
- } catch (error) {
164
- logger.error(`Run command failed: ${error instanceof Error ? error.message : String(error)}`);
165
- }
166
-
167
- return state;
168
- }
169
-
170
- /**
171
- * Handler for the /monitor command
172
- */
173
- async function handleMonitorCommand(
174
- args: string[],
175
- state: SessionState,
176
- rl: readline.Interface
177
- ): Promise<SessionState> {
178
- if (args.length === 0) {
179
- logger.error('Feature name required. Usage: /monitor <feature-name>');
180
- return state;
181
- }
182
-
183
- const featureName = args[0];
184
-
185
- // Close REPL readline to avoid stdin conflicts with subcommand
186
- rl.close();
187
-
188
- try {
189
- await monitorCommand(featureName, {});
190
- } catch (error) {
191
- logger.error(`Monitor command failed: ${error instanceof Error ? error.message : String(error)}`);
192
- }
193
-
194
- return state;
195
- }
196
-
197
- /**
198
- * Handler for the /help command
199
- */
200
- function handleHelpCommand(): void {
201
- console.log('');
202
- console.log(formatHelpText());
203
- console.log('');
204
- }
205
-
206
- /**
207
- * Execute a REPL command
208
- */
209
- async function executeCommand(
210
- commandName: ReplCommandName,
211
- args: string[],
212
- state: SessionState,
213
- rl: readline.Interface
214
- ): Promise<{ state: SessionState; shouldExit: boolean; needsRlRecreate: boolean }> {
215
- switch (commandName) {
216
- case 'init':
217
- // Init closes the readline to avoid stdin conflicts
218
- return { state: await handleInitCommand(args, state, rl), shouldExit: false, needsRlRecreate: true };
219
-
220
- case 'new':
221
- return { state: await handleNewCommand(args, state, rl), shouldExit: false, needsRlRecreate: true };
222
-
223
- case 'run':
224
- return { state: await handleRunCommand(args, state, rl), shouldExit: false, needsRlRecreate: true };
225
-
226
- case 'monitor':
227
- return { state: await handleMonitorCommand(args, state, rl), shouldExit: false, needsRlRecreate: true };
228
-
229
- case 'config':
230
- return { state: await handleConfigCommand(args, state), shouldExit: false, needsRlRecreate: false };
231
-
232
- case 'help':
233
- handleHelpCommand();
234
- return { state, shouldExit: false, needsRlRecreate: false };
235
-
236
- case 'exit':
237
- return { state, shouldExit: true, needsRlRecreate: false };
238
-
239
- default:
240
- logger.warn(`Unknown command: ${commandName}`);
241
- return { state, shouldExit: false, needsRlRecreate: false };
242
- }
243
- }
244
-
245
- /**
246
- * Handle natural language input
247
- * For now, just shows a message. Will be enhanced in Phase 3.
248
- */
249
- async function handleNaturalLanguage(
250
- text: string,
251
- state: SessionState
252
- ): Promise<SessionState> {
253
- if (state.conversationMode) {
254
- // In conversation mode, pass to the conversation handler
255
- // This will be implemented in Phase 3
256
- console.log(pc.dim('(Conversation mode not yet implemented)'));
257
- } else {
258
- console.log('');
259
- console.log(pc.dim('Tip: Use /help to see available commands, or /new <feature> to create a spec.'));
260
- console.log('');
261
- }
262
-
263
- return state;
264
- }
265
-
266
- /**
267
- * Process a single line of input
268
- */
269
- async function processInput(
270
- input: string,
271
- state: SessionState,
272
- rl: readline.Interface
273
- ): Promise<{ state: SessionState; shouldExit: boolean; needsRlRecreate: boolean }> {
274
- const parsed = parseInput(input);
275
-
276
- switch (parsed.type) {
277
- case 'empty':
278
- return { state, shouldExit: false, needsRlRecreate: false };
279
-
280
- case 'slash-command': {
281
- const { command } = parsed;
282
- if (!command) {
283
- return { state, shouldExit: false, needsRlRecreate: false };
284
- }
285
-
286
- const resolvedName = resolveCommandAlias(command.name);
287
- if (!resolvedName) {
288
- logger.warn(`Unknown command: /${command.name}. Type /help for available commands.`);
289
- return { state, shouldExit: false, needsRlRecreate: false };
290
- }
291
-
292
- return executeCommand(resolvedName, command.args, state, rl);
293
- }
294
-
295
- case 'natural-language': {
296
- const newState = await handleNaturalLanguage(parsed.text!, state);
297
- return { state: newState, shouldExit: false, needsRlRecreate: false };
298
- }
299
-
300
- default:
301
- return { state, shouldExit: false, needsRlRecreate: false };
302
- }
303
- }
304
-
305
- /**
306
- * Create a new readline interface
307
- */
308
- function createReadline(): readline.Interface {
309
- return readline.createInterface({
310
- input: process.stdin,
311
- output: process.stdout,
312
- prompt: PROMPT,
313
- terminal: true,
314
- });
315
- }
316
-
317
- /**
318
- * Read a single line from readline
319
- */
320
- function readLine(rl: readline.Interface): Promise<string | null> {
321
- return new Promise((resolve) => {
322
- rl.once('line', (line) => resolve(line));
323
- rl.once('close', () => resolve(null));
324
- rl.once('SIGINT', () => {
325
- console.log('');
326
- logger.info('Use /exit to quit');
327
- rl.prompt();
328
- });
329
- });
330
- }
331
-
332
- /**
333
- * Start the REPL loop
334
- */
335
- export async function startRepl(initialState: SessionState): Promise<void> {
336
- let state = initialState;
337
-
338
- console.log('');
339
- console.log(simpson.yellow('Wiggum Interactive Mode'));
340
-
341
- // Show context-aware welcome message
342
- if (!state.initialized && !hasConfig(state.projectRoot)) {
343
- console.log(pc.dim('Not initialized. Run /init to set up this project.'));
344
- } else {
345
- console.log(pc.dim('Type /help for commands, /exit to quit'));
346
- }
347
- console.log('');
348
-
349
- let rl = createReadline();
350
- let running = true;
351
-
352
- while (running) {
353
- rl.prompt();
354
- const line = await readLine(rl);
355
-
356
- // Handle EOF (Ctrl+D)
357
- if (line === null) {
358
- console.log('');
359
- logger.info('Goodbye!');
360
- running = false;
361
- break;
362
- }
363
-
364
- try {
365
- const result = await processInput(line, state, rl);
366
- state = result.state;
367
-
368
- if (result.shouldExit) {
369
- console.log('');
370
- logger.info('Goodbye!');
371
- rl.close();
372
- running = false;
373
- break;
374
- }
375
-
376
- // Recreate readline if needed (after commands that closed it)
377
- if (result.needsRlRecreate) {
378
- rl = createReadline();
379
- }
380
- } catch (error) {
381
- logger.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
382
- }
383
- }
384
- }
385
-
386
- /**
387
- * Export for testing
388
- */
389
- export { processInput, executeCommand };