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
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
|
-
}
|
package/src/repl/repl-loop.ts
DELETED
|
@@ -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 };
|