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.
- package/README.md +88 -22
- package/dist/ai/conversation/conversation-manager.d.ts +84 -0
- package/dist/ai/conversation/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation/conversation-manager.js +159 -0
- package/dist/ai/conversation/conversation-manager.js.map +1 -0
- package/dist/ai/conversation/index.d.ts +8 -0
- package/dist/ai/conversation/index.d.ts.map +1 -0
- package/dist/ai/conversation/index.js +8 -0
- package/dist/ai/conversation/index.js.map +1 -0
- package/dist/ai/conversation/spec-generator.d.ts +62 -0
- package/dist/ai/conversation/spec-generator.d.ts.map +1 -0
- package/dist/ai/conversation/spec-generator.js +267 -0
- package/dist/ai/conversation/spec-generator.js.map +1 -0
- package/dist/ai/conversation/url-fetcher.d.ts +26 -0
- package/dist/ai/conversation/url-fetcher.d.ts.map +1 -0
- package/dist/ai/conversation/url-fetcher.js +145 -0
- package/dist/ai/conversation/url-fetcher.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +44 -34
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +19 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +61 -21
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts +11 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +102 -43
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/run.js +3 -3
- package/dist/commands/run.js.map +1 -1
- package/dist/generator/config.d.ts +3 -3
- package/dist/generator/config.d.ts.map +1 -1
- package/dist/generator/config.js +5 -3
- package/dist/generator/config.js.map +1 -1
- package/dist/generator/index.js +1 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/writer.js +1 -1
- package/dist/generator/writer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -1
- package/dist/repl/command-parser.d.ts +84 -0
- package/dist/repl/command-parser.d.ts.map +1 -0
- package/dist/repl/command-parser.js +112 -0
- package/dist/repl/command-parser.js.map +1 -0
- package/dist/repl/index.d.ts +8 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +8 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/repl-loop.d.ts +30 -0
- package/dist/repl/repl-loop.d.ts.map +1 -0
- package/dist/repl/repl-loop.js +262 -0
- package/dist/repl/repl-loop.js.map +1 -0
- package/dist/repl/session-state.d.ts +37 -0
- package/dist/repl/session-state.d.ts.map +1 -0
- package/dist/repl/session-state.js +26 -0
- package/dist/repl/session-state.js.map +1 -0
- package/dist/templates/root/README.md.tmpl +1 -1
- package/dist/templates/scripts/feature-loop.sh.tmpl +17 -17
- package/dist/templates/scripts/loop.sh.tmpl +7 -7
- package/dist/templates/scripts/ralph-monitor.sh.tmpl +5 -5
- package/dist/utils/config.d.ts +7 -7
- package/dist/utils/config.js +4 -4
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/conversation/conversation-manager.ts +230 -0
- package/src/ai/conversation/index.ts +23 -0
- package/src/ai/conversation/spec-generator.ts +327 -0
- package/src/ai/conversation/url-fetcher.ts +180 -0
- package/src/cli.ts +47 -34
- package/src/commands/init.ts +86 -22
- package/src/commands/new.ts +121 -44
- package/src/commands/run.ts +3 -3
- package/src/generator/config.ts +5 -3
- package/src/generator/index.ts +1 -1
- package/src/generator/writer.ts +1 -1
- package/src/index.ts +46 -0
- package/src/repl/command-parser.ts +154 -0
- package/src/repl/index.ts +23 -0
- package/src/repl/repl-loop.ts +339 -0
- package/src/repl/session-state.ts +63 -0
- package/src/templates/config/ralph.config.cjs.tmpl +38 -0
- package/src/templates/root/README.md.tmpl +1 -1
- package/src/templates/scripts/feature-loop.sh.tmpl +17 -17
- package/src/templates/scripts/loop.sh.tmpl +7 -7
- package/src/templates/scripts/ralph-monitor.sh.tmpl +5 -5
- package/src/utils/config.ts +9 -9
- /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(/ /g, ' ')
|
|
51
|
+
.replace(/&/g, '&')
|
|
52
|
+
.replace(/</g, '<')
|
|
53
|
+
.replace(/>/g, '>')
|
|
54
|
+
.replace(/"/g, '"')
|
|
55
|
+
.replace(/'/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('
|
|
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
|
-
$
|
|
31
|
-
$
|
|
32
|
-
$
|
|
33
|
-
$
|
|
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/
|
|
36
|
+
https://github.com/your-org/wiggum-cli#readme
|
|
37
37
|
`
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
//
|
|
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
|
-
$
|
|
59
|
-
$
|
|
60
|
-
$
|
|
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
|
-
//
|
|
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
|
-
$
|
|
116
|
-
$
|
|
117
|
-
$
|
|
118
|
-
$
|
|
119
|
-
$
|
|
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:
|
|
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
|
-
//
|
|
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
|
-
$
|
|
166
|
-
$
|
|
167
|
-
$
|
|
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
|
-
//
|
|
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
|
-
$
|
|
210
|
-
$
|
|
211
|
-
$
|
|
212
|
-
$
|
|
213
|
-
$
|
|
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
|
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
-
|
|
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) {
|
package/src/commands/init.ts
CHANGED
|
@@ -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
|
-
*
|
|
279
|
-
*
|
|
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
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
321
|
-
// In interactive mode, null means user cancelled
|
|
336
|
+
// User cancelled or missing API key in --yes mode
|
|
322
337
|
await flushTracing();
|
|
323
|
-
|
|
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
|
-
|
|
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: '
|
|
430
|
-
{ command: '
|
|
431
|
-
{ command: '
|
|
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('
|
|
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
|
}
|