@claudetools/cli 0.12.0 → 0.13.1
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 +423 -0
- package/dist/onboard/claude-inference.d.ts +24 -0
- package/dist/onboard/claude-inference.d.ts.map +1 -1
- package/dist/onboard/claude-inference.js +70 -0
- package/dist/onboard/claude-inference.js.map +1 -1
- package/dist/onboard/index.d.ts.map +1 -1
- package/dist/onboard/index.js +420 -279
- package/dist/onboard/index.js.map +1 -1
- package/dist/onboard/questions.d.ts +1 -0
- package/dist/onboard/questions.d.ts.map +1 -1
- package/dist/onboard/questions.js +136 -12
- package/dist/onboard/questions.js.map +1 -1
- package/package.json +1 -1
package/dist/onboard/index.js
CHANGED
|
@@ -6,18 +6,16 @@ import chalk from 'chalk';
|
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
import prompts from 'prompts';
|
|
8
8
|
import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs';
|
|
9
|
+
import { execSync } from 'child_process';
|
|
9
10
|
import { join, basename } from 'path';
|
|
10
11
|
import { detectStack } from './stack-detector.js';
|
|
11
12
|
import { buildDocs } from './docs-builder.js';
|
|
12
13
|
import { buildAgentsMd, writeAgentsMd } from './agents-md-builder.js';
|
|
13
14
|
import { runInteractiveOnboarding } from './questions.js';
|
|
14
|
-
import { isClaudeCliAvailable,
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const empty = width - filled;
|
|
19
|
-
return chalk.cyan('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
|
|
20
|
-
}
|
|
15
|
+
import { isClaudeCliAvailable, runClaudeAnalysis } from './claude-inference.js';
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Main Onboarding Flow
|
|
18
|
+
// =============================================================================
|
|
21
19
|
/**
|
|
22
20
|
* Run the complete onboarding workflow
|
|
23
21
|
*/
|
|
@@ -33,351 +31,494 @@ export async function runOnboard(projectPath, options = {}) {
|
|
|
33
31
|
console.log(chalk.dim(` Project: ${chalk.white(projectName)}`));
|
|
34
32
|
console.log(chalk.dim(` Path: ${projectPath}`));
|
|
35
33
|
console.log('');
|
|
34
|
+
// ==========================================================================
|
|
36
35
|
// Phase 1: Stack Detection
|
|
37
|
-
|
|
36
|
+
// ==========================================================================
|
|
37
|
+
console.log(chalk.cyan(' ▸ Phase 1/4: Stack Detection'));
|
|
38
38
|
const stackSpinner = ora({ text: 'Scanning project files...', indent: 4 }).start();
|
|
39
39
|
const stack = detectStack(projectPath);
|
|
40
|
-
// Handle empty/new projects
|
|
41
40
|
if (stack.isEmpty) {
|
|
42
|
-
stackSpinner.
|
|
43
|
-
console.log('');
|
|
44
|
-
console.log(chalk.dim(' ┌─ New/Empty Project ────────────────────'));
|
|
45
|
-
console.log(chalk.dim(' │'));
|
|
46
|
-
console.log(chalk.dim(' │ ') + chalk.yellow('No package.json, requirements.txt, go.mod,'));
|
|
47
|
-
console.log(chalk.dim(' │ ') + chalk.yellow('or Cargo.toml found.'));
|
|
48
|
-
console.log(chalk.dim(' │'));
|
|
49
|
-
console.log(chalk.dim(' │ ') + chalk.white('This appears to be a new or empty project.'));
|
|
50
|
-
console.log(chalk.dim(' │'));
|
|
51
|
-
console.log(chalk.dim(' └──────────────────────────────────────'));
|
|
52
|
-
console.log('');
|
|
53
|
-
const { continueEmpty } = await prompts({
|
|
54
|
-
type: 'confirm',
|
|
55
|
-
name: 'continueEmpty',
|
|
56
|
-
message: ' Continue with minimal setup for new project?',
|
|
57
|
-
initial: true,
|
|
58
|
-
});
|
|
59
|
-
if (!continueEmpty) {
|
|
60
|
-
console.log('');
|
|
61
|
-
console.log(chalk.dim(' Tip: Initialize your project first, then run onboarding:'));
|
|
62
|
-
console.log(chalk.dim(' • npm init / pnpm init / yarn init'));
|
|
63
|
-
console.log(chalk.dim(' • npx create-next-app'));
|
|
64
|
-
console.log(chalk.dim(' • pip init / poetry init'));
|
|
65
|
-
console.log(chalk.dim(' • go mod init'));
|
|
66
|
-
console.log(chalk.dim(' • cargo init'));
|
|
67
|
-
console.log('');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
console.log(chalk.dim(' Proceeding with minimal setup...'));
|
|
71
|
-
console.log('');
|
|
41
|
+
stackSpinner.info(chalk.cyan('New project - let\'s set it up'));
|
|
72
42
|
}
|
|
73
43
|
else {
|
|
74
44
|
stackSpinner.succeed(chalk.green('Stack detected'));
|
|
75
|
-
console.log('');
|
|
76
|
-
// Show detected stack summary
|
|
77
|
-
console.log(chalk.dim(' ┌─ Detected Stack ─────────────────────'));
|
|
78
|
-
console.log(chalk.dim(' │'));
|
|
79
|
-
console.log(chalk.dim(' │ ') + chalk.white(`Language: ${stack.primaryLanguage}`));
|
|
80
|
-
console.log(chalk.dim(' │ ') + chalk.white(`Package Manager: ${stack.packageManager}`));
|
|
81
|
-
if (stack.frameworks.length > 0) {
|
|
82
|
-
console.log(chalk.dim(' │ ') + chalk.white(`Frameworks: ${stack.frameworks.map(f => f.name).join(', ')}`));
|
|
83
|
-
}
|
|
84
|
-
if (stack.libraries.length > 0) {
|
|
85
|
-
const libCount = stack.libraries.length;
|
|
86
|
-
const topLibs = stack.libraries.slice(0, 5).map(l => l.name).join(', ');
|
|
87
|
-
console.log(chalk.dim(' │ ') + chalk.white(`Libraries: ${topLibs}${libCount > 5 ? ` (+${libCount - 5} more)` : ''}`));
|
|
88
|
-
}
|
|
89
|
-
if (stack.devTools.length > 0) {
|
|
90
|
-
console.log(chalk.dim(' │ ') + chalk.white(`Dev Tools: ${stack.devTools.map(t => t.name).join(', ')}`));
|
|
91
|
-
}
|
|
92
|
-
console.log(chalk.dim(' │'));
|
|
93
|
-
console.log(chalk.dim(' └─────────────────────────────────────'));
|
|
94
|
-
console.log('');
|
|
95
|
-
if (options.verbose) {
|
|
96
|
-
printStackDetails(stack);
|
|
97
|
-
}
|
|
98
45
|
}
|
|
99
|
-
|
|
100
|
-
|
|
46
|
+
console.log('');
|
|
47
|
+
// ==========================================================================
|
|
48
|
+
// Phase 2: Quick Questions
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
console.log(chalk.cyan(' ▸ Phase 2/4: Quick Questions'));
|
|
101
51
|
let answers = {};
|
|
102
52
|
if (!options.skipQuestions && !options.refresh) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
53
|
+
answers = await runInteractiveOnboarding(stack, {
|
|
54
|
+
verbose: options.verbose,
|
|
55
|
+
isNewProject: stack.isEmpty,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(chalk.dim(' Skipping questions'));
|
|
60
|
+
console.log('');
|
|
61
|
+
}
|
|
62
|
+
// ==========================================================================
|
|
63
|
+
// Phase 3: AI Analysis & Proposal
|
|
64
|
+
// ==========================================================================
|
|
65
|
+
console.log(chalk.cyan(' ▸ Phase 3/4: AI Analysis'));
|
|
66
|
+
const hasClaudeCli = isClaudeCliAvailable();
|
|
67
|
+
if (!hasClaudeCli) {
|
|
68
|
+
console.log(chalk.yellow(' Claude CLI not available - using detected stack'));
|
|
69
|
+
console.log('');
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// AI analyzes and proposes
|
|
73
|
+
let proposal = await generateProposal(projectPath, stack, answers);
|
|
74
|
+
let approved = false;
|
|
75
|
+
while (!approved) {
|
|
76
|
+
// Display proposal
|
|
77
|
+
displayProposal(proposal);
|
|
78
|
+
// Ask for approval
|
|
79
|
+
const { decision } = await prompts({
|
|
80
|
+
type: 'select',
|
|
81
|
+
name: 'decision',
|
|
82
|
+
message: ' How does this look?',
|
|
83
|
+
choices: [
|
|
84
|
+
{ title: 'Looks good, proceed', value: 'approve' },
|
|
85
|
+
{ title: 'I have feedback', value: 'feedback' },
|
|
86
|
+
{ title: 'Skip AI analysis', value: 'skip' },
|
|
87
|
+
],
|
|
111
88
|
});
|
|
112
|
-
if (
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
name: 'useClaudeAnalysis',
|
|
127
|
-
message: ' Use AI to analyze codebase? (recommended)',
|
|
128
|
-
initial: true,
|
|
129
|
-
});
|
|
130
|
-
if (useClaudeAnalysis) {
|
|
131
|
-
const analysisSpinner = ora({
|
|
132
|
-
text: 'Claude is analyzing your codebase...',
|
|
133
|
-
indent: 4,
|
|
134
|
-
}).start();
|
|
135
|
-
const analysisSteps = [
|
|
136
|
-
'Reading project structure...',
|
|
137
|
-
'Analyzing architecture patterns...',
|
|
138
|
-
'Detecting conventions...',
|
|
139
|
-
'Identifying key components...',
|
|
140
|
-
'Generating insights...',
|
|
141
|
-
];
|
|
142
|
-
let stepIndex = 0;
|
|
143
|
-
const stepInterval = setInterval(() => {
|
|
144
|
-
stepIndex = (stepIndex + 1) % analysisSteps.length;
|
|
145
|
-
analysisSpinner.text = analysisSteps[stepIndex];
|
|
146
|
-
}, 3000);
|
|
147
|
-
try {
|
|
148
|
-
answers = await analyzeWithClaude(projectPath, stack);
|
|
149
|
-
clearInterval(stepInterval);
|
|
150
|
-
analysisSpinner.succeed(chalk.green('Codebase analysis complete'));
|
|
151
|
-
// Show what was discovered
|
|
152
|
-
if (answers.projectDescription) {
|
|
153
|
-
console.log('');
|
|
154
|
-
console.log(chalk.dim(' ┌─ Analysis Results ────────────────────'));
|
|
155
|
-
console.log(chalk.dim(' │'));
|
|
156
|
-
console.log(chalk.dim(' │ ') + chalk.white(answers.projectDescription.slice(0, 60) + (answers.projectDescription.length > 60 ? '...' : '')));
|
|
157
|
-
if (answers.architectureStyle) {
|
|
158
|
-
console.log(chalk.dim(' │ ') + chalk.dim('Architecture: ') + chalk.white(answers.architectureStyle));
|
|
159
|
-
}
|
|
160
|
-
console.log(chalk.dim(' │'));
|
|
161
|
-
console.log(chalk.dim(' └──────────────────────────────────────'));
|
|
162
|
-
}
|
|
89
|
+
if (decision === 'approve') {
|
|
90
|
+
approved = true;
|
|
91
|
+
console.log(chalk.green(' ✓ Approved'));
|
|
92
|
+
// For empty projects, offer scaffolding
|
|
93
|
+
if (stack.isEmpty) {
|
|
94
|
+
console.log('');
|
|
95
|
+
const { scaffold } = await prompts({
|
|
96
|
+
type: 'confirm',
|
|
97
|
+
name: 'scaffold',
|
|
98
|
+
message: ' Create package.json and install dependencies?',
|
|
99
|
+
initial: true,
|
|
100
|
+
});
|
|
101
|
+
if (scaffold) {
|
|
102
|
+
await scaffoldProject(projectPath, proposal, answers);
|
|
163
103
|
}
|
|
164
|
-
catch (err) {
|
|
165
|
-
clearInterval(stepInterval);
|
|
166
|
-
analysisSpinner.fail(chalk.yellow('Analysis failed, using manual questions'));
|
|
167
|
-
answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
answers = await runInteractiveOnboarding(stack, { verbose: options.verbose });
|
|
172
104
|
}
|
|
173
105
|
}
|
|
174
|
-
else {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
message: ' Answer questions to improve documentation?',
|
|
180
|
-
initial: true,
|
|
106
|
+
else if (decision === 'feedback') {
|
|
107
|
+
const { feedback } = await prompts({
|
|
108
|
+
type: 'text',
|
|
109
|
+
name: 'feedback',
|
|
110
|
+
message: ' What should be different?',
|
|
181
111
|
});
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
112
|
+
if (feedback && typeof feedback === 'string' && feedback.trim()) {
|
|
113
|
+
console.log('');
|
|
114
|
+
const refineSpinner = ora({ text: 'Refining proposal...', indent: 4 }).start();
|
|
115
|
+
proposal = await refineProposal(projectPath, stack, answers, proposal, feedback.trim());
|
|
116
|
+
refineSpinner.succeed('Updated proposal');
|
|
117
|
+
console.log('');
|
|
187
118
|
}
|
|
188
119
|
}
|
|
120
|
+
else {
|
|
121
|
+
// Skip
|
|
122
|
+
console.log(chalk.dim(' Using detected stack only'));
|
|
123
|
+
approved = true;
|
|
124
|
+
}
|
|
189
125
|
}
|
|
190
126
|
}
|
|
191
|
-
else {
|
|
192
|
-
console.log(chalk.dim(' Skipping analysis (refresh mode)'));
|
|
193
|
-
}
|
|
194
127
|
console.log('');
|
|
195
|
-
//
|
|
196
|
-
|
|
128
|
+
// ==========================================================================
|
|
129
|
+
// Phase 4: Build Documentation
|
|
130
|
+
// ==========================================================================
|
|
131
|
+
console.log(chalk.cyan(' ▸ Phase 4/4: Building Documentation'));
|
|
197
132
|
let generatedDocs = [];
|
|
198
133
|
if (!options.skipDocs) {
|
|
199
|
-
|
|
200
|
-
const useClaude = !stack.isEmpty && isClaudeCliAvailable();
|
|
201
|
-
let docsSpinner = null;
|
|
202
|
-
if (stack.isEmpty) {
|
|
203
|
-
console.log(chalk.dim(' Generating documentation templates for new project'));
|
|
204
|
-
console.log('');
|
|
205
|
-
docsSpinner = ora({
|
|
206
|
-
text: 'Creating documentation structure...',
|
|
207
|
-
indent: 4,
|
|
208
|
-
}).start();
|
|
209
|
-
}
|
|
210
|
-
else if (useClaude) {
|
|
211
|
-
console.log(chalk.dim(' Using AI to generate comprehensive documentation'));
|
|
212
|
-
console.log('');
|
|
213
|
-
docsSpinner = ora({
|
|
214
|
-
text: 'Preparing documentation structure...',
|
|
215
|
-
indent: 4,
|
|
216
|
-
}).start();
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
docsSpinner = ora({
|
|
220
|
-
text: 'Generating documentation templates...',
|
|
221
|
-
indent: 4,
|
|
222
|
-
}).start();
|
|
223
|
-
}
|
|
134
|
+
const docsSpinner = ora({ text: 'Fetching documentation...', indent: 4 }).start();
|
|
224
135
|
try {
|
|
225
136
|
generatedDocs = await buildDocs(projectPath, projectName, stack, answers, {
|
|
226
137
|
verbose: options.verbose,
|
|
227
|
-
useClaudeInference:
|
|
138
|
+
useClaudeInference: hasClaudeCli,
|
|
228
139
|
onProgress: (message, current, total) => {
|
|
229
|
-
if (
|
|
230
|
-
const bar = progressBar(current, total);
|
|
140
|
+
if (current !== undefined && total !== undefined) {
|
|
231
141
|
const percent = Math.round((current / total) * 100);
|
|
232
|
-
docsSpinner.text = `${
|
|
142
|
+
docsSpinner.text = `${percent}% ${message}`;
|
|
233
143
|
}
|
|
234
|
-
else
|
|
144
|
+
else {
|
|
235
145
|
docsSpinner.text = message;
|
|
236
146
|
}
|
|
237
147
|
},
|
|
238
148
|
});
|
|
239
|
-
docsSpinner
|
|
240
|
-
// Show
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const categories = {};
|
|
245
|
-
for (const doc of generatedDocs) {
|
|
246
|
-
const cat = doc.category || 'general';
|
|
247
|
-
categories[cat] = (categories[cat] || 0) + 1;
|
|
248
|
-
}
|
|
249
|
-
for (const [category, count] of Object.entries(categories)) {
|
|
250
|
-
const icon = getCategoryIcon(category);
|
|
251
|
-
console.log(chalk.dim(' │ ') + icon + ' ' + chalk.white(`${category}/`) + chalk.dim(` (${count} files)`));
|
|
149
|
+
docsSpinner.succeed(chalk.green(`Generated ${generatedDocs.length} docs`));
|
|
150
|
+
// Show summary
|
|
151
|
+
const categories = new Set(generatedDocs.map(d => d.category).filter(Boolean));
|
|
152
|
+
if (categories.size > 0) {
|
|
153
|
+
console.log(chalk.dim(` Categories: ${[...categories].join(', ')}`));
|
|
252
154
|
}
|
|
253
|
-
console.log(chalk.dim(' │'));
|
|
254
|
-
console.log(chalk.dim(' └──────────────────────────────────────'));
|
|
255
155
|
}
|
|
256
156
|
catch (err) {
|
|
257
|
-
docsSpinner
|
|
258
|
-
console.error(chalk.
|
|
157
|
+
docsSpinner.fail(chalk.red('Documentation failed'));
|
|
158
|
+
console.error(chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
259
159
|
}
|
|
260
160
|
}
|
|
261
161
|
else {
|
|
262
|
-
console.log(chalk.dim(' Skipping documentation
|
|
162
|
+
console.log(chalk.dim(' Skipping documentation'));
|
|
263
163
|
}
|
|
264
164
|
console.log('');
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
const agentsSpinner = ora({ text: 'Building AGENTS.md...', indent: 4 }).start();
|
|
165
|
+
// Build AGENTS.md
|
|
166
|
+
const agentsSpinner = ora({ text: 'Creating AGENTS.md...', indent: 4 }).start();
|
|
268
167
|
const agentsContent = buildAgentsMd(projectPath, stack, generatedDocs, answers);
|
|
269
168
|
writeAgentsMd(projectPath, agentsContent);
|
|
270
169
|
agentsSpinner.succeed(chalk.green('Created AGENTS.md'));
|
|
271
|
-
|
|
272
|
-
console.log('');
|
|
273
|
-
// Phase 5: Finalization
|
|
274
|
-
console.log(chalk.cyan(' ▸ Phase 5/5: Finalization'));
|
|
275
|
-
const finalSpinner = ora({ text: 'Finalizing setup...', indent: 4 }).start();
|
|
170
|
+
// Create CLAUDE.md pointer
|
|
276
171
|
createClaudeMdPointer(projectPath, projectName);
|
|
277
|
-
finalSpinner.succeed(chalk.green('Setup complete'));
|
|
278
172
|
console.log('');
|
|
279
|
-
//
|
|
173
|
+
// ==========================================================================
|
|
174
|
+
// Summary
|
|
175
|
+
// ==========================================================================
|
|
280
176
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
281
177
|
console.log(chalk.bold.green(' ╭─────────────────────────────────────╮'));
|
|
282
|
-
console.log(chalk.bold.green(' │') + chalk.bold.white('
|
|
178
|
+
console.log(chalk.bold.green(' │') + chalk.bold.white(' Setup Complete! ') + chalk.bold.green('│'));
|
|
283
179
|
console.log(chalk.bold.green(' ╰─────────────────────────────────────╯'));
|
|
284
180
|
console.log('');
|
|
285
181
|
console.log(chalk.dim(` Completed in ${elapsed}s`));
|
|
286
182
|
console.log('');
|
|
287
|
-
console.log(chalk.white('
|
|
183
|
+
console.log(chalk.white(' Files created:'));
|
|
184
|
+
console.log(chalk.cyan(' AGENTS.md') + chalk.dim(' ─────── AI context'));
|
|
185
|
+
if (!options.skipDocs && generatedDocs.length > 0) {
|
|
186
|
+
console.log(chalk.cyan(' .claudetools/docs/') + chalk.dim(' ─ documentation'));
|
|
187
|
+
}
|
|
288
188
|
console.log('');
|
|
289
|
-
console.log(chalk.
|
|
290
|
-
if (!options.skipDocs) {
|
|
291
|
-
console.log(chalk.cyan(' .claudetools/'));
|
|
292
|
-
console.log(chalk.dim(' └── docs/') + chalk.dim(' AI context documentation'));
|
|
293
|
-
console.log(chalk.dim(' ├── architecture/') + chalk.dim(' system design, data flow'));
|
|
294
|
-
console.log(chalk.dim(' ├── api/') + chalk.dim(' endpoints, schemas'));
|
|
295
|
-
console.log(chalk.dim(' ├── guides/') + chalk.dim(' getting started, workflow'));
|
|
296
|
-
console.log(chalk.dim(' ├── reference/') + chalk.dim(' config, env, deps'));
|
|
297
|
-
console.log(chalk.dim(' ├── patterns/') + chalk.dim(' coding standards'));
|
|
298
|
-
console.log(chalk.dim(' ├── testing/') + chalk.dim(' test strategy'));
|
|
299
|
-
console.log(chalk.dim(' ├── deployment/') + chalk.dim(' environments, CI/CD'));
|
|
300
|
-
console.log(chalk.dim(' └── decisions/') + chalk.dim(' ADRs'));
|
|
301
|
-
}
|
|
302
|
-
console.log(chalk.cyan(' .claude/CLAUDE.md') + chalk.dim(' ───── config pointer'));
|
|
189
|
+
console.log(chalk.dim(' Refresh: claudetools onboard --refresh'));
|
|
303
190
|
console.log('');
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
191
|
+
}
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// Proposal Generation
|
|
194
|
+
// =============================================================================
|
|
195
|
+
/**
|
|
196
|
+
* Format dynamic answers for inclusion in AI prompts
|
|
197
|
+
*/
|
|
198
|
+
function formatDynamicAnswers(answers) {
|
|
199
|
+
const dynamicAnswers = Object.entries(answers)
|
|
200
|
+
.filter(([key, value]) => key.startsWith('dynamic_') && value)
|
|
201
|
+
.map(([key, value]) => {
|
|
202
|
+
// Convert dynamic_auth-method to "Auth method"
|
|
203
|
+
const label = key
|
|
204
|
+
.replace('dynamic_', '')
|
|
205
|
+
.replace(/-/g, ' ')
|
|
206
|
+
.replace(/\b\w/g, c => c.toUpperCase());
|
|
207
|
+
return `- ${label}: ${value}`;
|
|
208
|
+
});
|
|
209
|
+
if (dynamicAnswers.length === 0)
|
|
210
|
+
return '';
|
|
211
|
+
return `\nAdditional requirements:\n${dynamicAnswers.join('\n')}`;
|
|
212
|
+
}
|
|
213
|
+
async function generateProposal(projectPath, stack, answers) {
|
|
214
|
+
const isNewProject = stack.isEmpty;
|
|
215
|
+
const spinnerText = isNewProject ? 'Generating stack recommendation...' : 'Analyzing codebase...';
|
|
216
|
+
const spinner = ora({ text: spinnerText, indent: 4 }).start();
|
|
217
|
+
try {
|
|
218
|
+
let prompt;
|
|
219
|
+
if (isNewProject) {
|
|
220
|
+
// For new/empty projects: recommend a complete stack
|
|
221
|
+
const projectTypeDescriptions = {
|
|
222
|
+
web: 'a web application (frontend-focused)',
|
|
223
|
+
api: 'an API/backend service',
|
|
224
|
+
cli: 'a command-line tool',
|
|
225
|
+
library: 'a reusable library/package',
|
|
226
|
+
fullstack: 'a full-stack application with frontend and backend',
|
|
227
|
+
};
|
|
228
|
+
const typeDesc = answers.projectType
|
|
229
|
+
? projectTypeDescriptions[answers.projectType] || answers.projectType
|
|
230
|
+
: 'a project';
|
|
231
|
+
prompt = `The user wants to build ${typeDesc}.
|
|
232
|
+
|
|
233
|
+
Project description: "${answers.projectDescription || 'Not specified'}"
|
|
234
|
+
${answers.mainGoal ? `Current focus: ${answers.mainGoal}` : ''}
|
|
235
|
+
${answers.additionalContext ? `Additional context: ${answers.additionalContext}` : ''}
|
|
236
|
+
${formatDynamicAnswers(answers)}
|
|
237
|
+
|
|
238
|
+
Recommend a modern, production-ready tech stack for this project.
|
|
239
|
+
Choose popular, well-maintained tools with good documentation.
|
|
240
|
+
|
|
241
|
+
Respond in this exact JSON format:
|
|
242
|
+
{
|
|
243
|
+
"summary": "One sentence describing what this project will be",
|
|
244
|
+
"frameworks": ["primary framework", "supporting frameworks"],
|
|
245
|
+
"libraries": ["essential libraries for this type of project"],
|
|
246
|
+
"devTools": ["typescript", "eslint", "prettier", "testing framework"],
|
|
247
|
+
"architecture": "Recommended architecture approach",
|
|
248
|
+
"recommendations": ["key suggestion 1", "key suggestion 2", "key suggestion 3"]
|
|
249
|
+
}`;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
// For existing projects: analyze the codebase
|
|
253
|
+
prompt = `Analyze this codebase and provide a brief stack summary.
|
|
254
|
+
|
|
255
|
+
User context:
|
|
256
|
+
${answers.projectDescription ? `- Project: ${answers.projectDescription}` : ''}
|
|
257
|
+
${answers.mainGoal ? `- Current focus: ${answers.mainGoal}` : ''}
|
|
258
|
+
${answers.additionalContext ? `- Notes: ${answers.additionalContext}` : ''}
|
|
259
|
+
${formatDynamicAnswers(answers)}
|
|
260
|
+
|
|
261
|
+
Detected from package.json:
|
|
262
|
+
- Frameworks: ${stack.frameworks.map(f => f.name).join(', ') || 'none detected'}
|
|
263
|
+
- Libraries: ${stack.libraries.slice(0, 10).map(l => l.name).join(', ') || 'none detected'}
|
|
264
|
+
- Dev tools: ${stack.devTools.map(t => t.name).join(', ') || 'none detected'}
|
|
265
|
+
|
|
266
|
+
Respond in this exact JSON format:
|
|
267
|
+
{
|
|
268
|
+
"summary": "One sentence describing what this project is",
|
|
269
|
+
"frameworks": ["list", "of", "key", "frameworks"],
|
|
270
|
+
"libraries": ["list", "of", "important", "libraries"],
|
|
271
|
+
"devTools": ["list", "of", "dev", "tools"],
|
|
272
|
+
"architecture": "Brief architecture description",
|
|
273
|
+
"recommendations": ["suggestion 1", "suggestion 2"]
|
|
274
|
+
}`;
|
|
275
|
+
}
|
|
276
|
+
const result = await runClaudeAnalysis(projectPath, prompt, 60000);
|
|
277
|
+
spinner.stop();
|
|
278
|
+
if (result) {
|
|
279
|
+
// Try to parse JSON from response
|
|
280
|
+
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
|
281
|
+
if (jsonMatch) {
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(jsonMatch[0]);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
// Fall through to default
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
spinner.stop();
|
|
293
|
+
}
|
|
294
|
+
// Default proposal from detected stack
|
|
295
|
+
return {
|
|
296
|
+
summary: answers.projectDescription || `${stack.primaryLanguage} project`,
|
|
297
|
+
frameworks: stack.frameworks.map(f => f.name),
|
|
298
|
+
libraries: stack.libraries.slice(0, 8).map(l => l.name),
|
|
299
|
+
devTools: stack.devTools.map(t => t.name),
|
|
300
|
+
architecture: 'Standard project structure',
|
|
301
|
+
recommendations: [],
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
async function refineProposal(projectPath, _stack, _answers, currentProposal, feedback) {
|
|
305
|
+
try {
|
|
306
|
+
const prompt = `Refine this stack proposal based on user feedback.
|
|
307
|
+
|
|
308
|
+
Current proposal:
|
|
309
|
+
${JSON.stringify(currentProposal, null, 2)}
|
|
310
|
+
|
|
311
|
+
User feedback: "${feedback}"
|
|
312
|
+
|
|
313
|
+
Provide updated proposal in same JSON format:
|
|
314
|
+
{
|
|
315
|
+
"summary": "...",
|
|
316
|
+
"frameworks": [...],
|
|
317
|
+
"libraries": [...],
|
|
318
|
+
"devTools": [...],
|
|
319
|
+
"architecture": "...",
|
|
320
|
+
"recommendations": [...]
|
|
321
|
+
}`;
|
|
322
|
+
const result = await runClaudeAnalysis(projectPath, prompt, 45000);
|
|
323
|
+
if (result) {
|
|
324
|
+
const jsonMatch = result.match(/\{[\s\S]*\}/);
|
|
325
|
+
if (jsonMatch) {
|
|
326
|
+
try {
|
|
327
|
+
return JSON.parse(jsonMatch[0]);
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// Fall through
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
// Fall through
|
|
337
|
+
}
|
|
338
|
+
return currentProposal;
|
|
339
|
+
}
|
|
340
|
+
function displayProposal(proposal) {
|
|
308
341
|
console.log('');
|
|
342
|
+
console.log(chalk.dim(' ┌─ Proposed Configuration ────────────────'));
|
|
343
|
+
console.log(chalk.dim(' │'));
|
|
344
|
+
console.log(chalk.dim(' │ ') + chalk.white(proposal.summary));
|
|
345
|
+
console.log(chalk.dim(' │'));
|
|
346
|
+
if (proposal.frameworks.length > 0) {
|
|
347
|
+
console.log(chalk.dim(' │ ') + chalk.cyan('Frameworks: ') + proposal.frameworks.join(', '));
|
|
348
|
+
}
|
|
349
|
+
if (proposal.libraries.length > 0) {
|
|
350
|
+
console.log(chalk.dim(' │ ') + chalk.cyan('Libraries: ') + proposal.libraries.slice(0, 6).join(', ') +
|
|
351
|
+
(proposal.libraries.length > 6 ? ` (+${proposal.libraries.length - 6})` : ''));
|
|
352
|
+
}
|
|
353
|
+
if (proposal.devTools.length > 0) {
|
|
354
|
+
console.log(chalk.dim(' │ ') + chalk.cyan('Dev Tools: ') + proposal.devTools.join(', '));
|
|
355
|
+
}
|
|
356
|
+
if (proposal.architecture) {
|
|
357
|
+
console.log(chalk.dim(' │ ') + chalk.cyan('Architecture: ') + proposal.architecture);
|
|
358
|
+
}
|
|
359
|
+
if (proposal.recommendations.length > 0) {
|
|
360
|
+
console.log(chalk.dim(' │'));
|
|
361
|
+
console.log(chalk.dim(' │ ') + chalk.yellow('Recommendations:'));
|
|
362
|
+
for (const rec of proposal.recommendations.slice(0, 3)) {
|
|
363
|
+
console.log(chalk.dim(' │ ') + chalk.dim('• ') + rec);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
console.log(chalk.dim(' │'));
|
|
367
|
+
console.log(chalk.dim(' └──────────────────────────────────────────'));
|
|
368
|
+
console.log('');
|
|
369
|
+
}
|
|
370
|
+
// =============================================================================
|
|
371
|
+
// Scaffolding for New Projects
|
|
372
|
+
// =============================================================================
|
|
373
|
+
async function scaffoldProject(projectPath, proposal, answers) {
|
|
374
|
+
const spinner = ora({ text: 'Scaffolding project...', indent: 4 }).start();
|
|
375
|
+
try {
|
|
376
|
+
// Detect package manager
|
|
377
|
+
const packageManager = detectPackageManager();
|
|
378
|
+
// Generate package.json
|
|
379
|
+
const projectName = basename(projectPath).toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
380
|
+
const packageJson = generatePackageJson(projectName, proposal, answers);
|
|
381
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
382
|
+
writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
383
|
+
spinner.text = 'Created package.json';
|
|
384
|
+
// Install dependencies
|
|
385
|
+
spinner.text = `Installing dependencies with ${packageManager}...`;
|
|
386
|
+
const installCmd = packageManager === 'yarn' ? 'yarn' : `${packageManager} install`;
|
|
387
|
+
try {
|
|
388
|
+
execSync(installCmd, {
|
|
389
|
+
cwd: projectPath,
|
|
390
|
+
stdio: 'pipe',
|
|
391
|
+
timeout: 120000, // 2 minute timeout
|
|
392
|
+
});
|
|
393
|
+
spinner.succeed(chalk.green(`Scaffolded with ${packageManager}`));
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
spinner.warn(chalk.yellow('Created package.json (install failed - run manually)'));
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
spinner.fail(chalk.red('Scaffolding failed'));
|
|
401
|
+
console.error(chalk.dim(` ${err instanceof Error ? err.message : String(err)}`));
|
|
402
|
+
}
|
|
309
403
|
}
|
|
310
|
-
function
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
404
|
+
function detectPackageManager() {
|
|
405
|
+
// Check for lock files in current directory or common indicators
|
|
406
|
+
try {
|
|
407
|
+
execSync('pnpm --version', { stdio: 'pipe' });
|
|
408
|
+
return 'pnpm';
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// pnpm not available
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
execSync('bun --version', { stdio: 'pipe' });
|
|
415
|
+
return 'bun';
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// bun not available
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
execSync('yarn --version', { stdio: 'pipe' });
|
|
422
|
+
return 'yarn';
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
// yarn not available
|
|
426
|
+
}
|
|
427
|
+
return 'npm';
|
|
428
|
+
}
|
|
429
|
+
function generatePackageJson(projectName, proposal, answers) {
|
|
430
|
+
const dependencies = {};
|
|
431
|
+
const devDependencies = {};
|
|
432
|
+
// Add frameworks as dependencies
|
|
433
|
+
for (const framework of proposal.frameworks) {
|
|
434
|
+
const dep = frameworkToPackage(framework);
|
|
435
|
+
if (dep)
|
|
436
|
+
dependencies[dep] = 'latest';
|
|
437
|
+
}
|
|
438
|
+
// Add libraries as dependencies
|
|
439
|
+
for (const lib of proposal.libraries) {
|
|
440
|
+
const dep = libraryToPackage(lib);
|
|
441
|
+
if (dep)
|
|
442
|
+
dependencies[dep] = 'latest';
|
|
443
|
+
}
|
|
444
|
+
// Add dev tools as devDependencies
|
|
445
|
+
for (const tool of proposal.devTools) {
|
|
446
|
+
const dep = devToolToPackage(tool);
|
|
447
|
+
if (dep)
|
|
448
|
+
devDependencies[dep] = 'latest';
|
|
449
|
+
}
|
|
450
|
+
// Always include TypeScript for modern projects
|
|
451
|
+
if (!devDependencies['typescript']) {
|
|
452
|
+
devDependencies['typescript'] = 'latest';
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
name: projectName,
|
|
456
|
+
version: '0.1.0',
|
|
457
|
+
description: answers.projectDescription || proposal.summary,
|
|
458
|
+
type: 'module',
|
|
459
|
+
scripts: {
|
|
460
|
+
dev: 'echo "Add your dev script"',
|
|
461
|
+
build: 'echo "Add your build script"',
|
|
462
|
+
test: 'echo "Add your test script"',
|
|
463
|
+
},
|
|
464
|
+
dependencies,
|
|
465
|
+
devDependencies,
|
|
321
466
|
};
|
|
322
|
-
return icons[category] || '📄';
|
|
323
467
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
468
|
+
function frameworkToPackage(framework) {
|
|
469
|
+
const mapping = {
|
|
470
|
+
'next.js': 'next',
|
|
471
|
+
'nextjs': 'next',
|
|
472
|
+
'react': 'react',
|
|
473
|
+
'vue': 'vue',
|
|
474
|
+
'nuxt': 'nuxt',
|
|
475
|
+
'express': 'express',
|
|
476
|
+
'fastify': 'fastify',
|
|
477
|
+
'hono': 'hono',
|
|
478
|
+
'svelte': 'svelte',
|
|
479
|
+
'sveltekit': '@sveltejs/kit',
|
|
480
|
+
'astro': 'astro',
|
|
481
|
+
'remix': '@remix-run/node',
|
|
482
|
+
};
|
|
483
|
+
return mapping[framework.toLowerCase()] || framework.toLowerCase();
|
|
484
|
+
}
|
|
485
|
+
function libraryToPackage(lib) {
|
|
486
|
+
// Most libraries map directly to their package name
|
|
487
|
+
return lib.toLowerCase().replace(/\s+/g, '-');
|
|
488
|
+
}
|
|
489
|
+
function devToolToPackage(tool) {
|
|
490
|
+
const mapping = {
|
|
491
|
+
'typescript': 'typescript',
|
|
492
|
+
'eslint': 'eslint',
|
|
493
|
+
'prettier': 'prettier',
|
|
494
|
+
'vitest': 'vitest',
|
|
495
|
+
'jest': 'jest',
|
|
496
|
+
'biome': '@biomejs/biome',
|
|
497
|
+
};
|
|
498
|
+
return mapping[tool.toLowerCase()] || tool.toLowerCase();
|
|
499
|
+
}
|
|
500
|
+
// =============================================================================
|
|
501
|
+
// Helper Functions
|
|
502
|
+
// =============================================================================
|
|
327
503
|
function createClaudeMdPointer(projectPath, projectName) {
|
|
328
504
|
const claudeDir = join(projectPath, '.claude');
|
|
329
505
|
const claudeMdPath = join(claudeDir, 'CLAUDE.md');
|
|
330
|
-
// Create .claude directory if needed
|
|
331
506
|
if (!existsSync(claudeDir)) {
|
|
332
507
|
mkdirSync(claudeDir, { recursive: true });
|
|
333
508
|
}
|
|
334
|
-
// Check if CLAUDE.md already exists with custom content
|
|
335
509
|
if (existsSync(claudeMdPath)) {
|
|
336
510
|
const existing = readFileSync(claudeMdPath, 'utf-8');
|
|
337
|
-
// If it has substantial custom content, don't overwrite
|
|
338
511
|
if (existing.length > 500 && !existing.includes('See [AGENTS.md]')) {
|
|
339
|
-
|
|
340
|
-
return;
|
|
512
|
+
return; // Don't overwrite custom content
|
|
341
513
|
}
|
|
342
514
|
}
|
|
343
515
|
const content = `# ${projectName}
|
|
344
516
|
|
|
345
|
-
See [AGENTS.md](../AGENTS.md) for
|
|
346
|
-
|
|
347
|
-
## Quick Links
|
|
348
|
-
|
|
349
|
-
- Project docs: \`docs/\`
|
|
350
|
-
- Agent context: \`AGENTS.md\`
|
|
517
|
+
See [AGENTS.md](../AGENTS.md) for AI context.
|
|
351
518
|
|
|
352
519
|
---
|
|
353
|
-
|
|
354
|
-
*Generated by ClaudeTools. Refresh: \`claudetools onboard --refresh\`*
|
|
520
|
+
*Generated by ClaudeTools*
|
|
355
521
|
`;
|
|
356
522
|
writeFileSync(claudeMdPath, content);
|
|
357
523
|
}
|
|
358
|
-
/**
|
|
359
|
-
* Print detailed stack information
|
|
360
|
-
*/
|
|
361
|
-
function printStackDetails(stack) {
|
|
362
|
-
console.log(chalk.dim('\n Stack Details:'));
|
|
363
|
-
if (stack.frameworks.length > 0) {
|
|
364
|
-
console.log(chalk.dim(' Frameworks:'));
|
|
365
|
-
for (const fw of stack.frameworks) {
|
|
366
|
-
console.log(chalk.dim(` - ${fw.name} ${fw.version} (${fw.category})`));
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (stack.libraries.length > 0) {
|
|
370
|
-
console.log(chalk.dim(' Libraries:'));
|
|
371
|
-
for (const lib of stack.libraries) {
|
|
372
|
-
console.log(chalk.dim(` - ${lib.name} ${lib.version} (${lib.category})`));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
if (stack.devTools.length > 0) {
|
|
376
|
-
console.log(chalk.dim(' Dev Tools:'));
|
|
377
|
-
for (const tool of stack.devTools) {
|
|
378
|
-
console.log(chalk.dim(` - ${tool.name} (${tool.category})`));
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
console.log('');
|
|
382
|
-
}
|
|
383
524
|
//# sourceMappingURL=index.js.map
|