codebakers 1.0.45 ā 2.0.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 +275 -60
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3999 -0
- package/install.bat +9 -0
- package/package.json +71 -115
- package/src/channels/discord.ts +5 -0
- package/src/channels/slack.ts +5 -0
- package/src/channels/sms.ts +4 -0
- package/src/channels/telegram.ts +5 -0
- package/src/channels/whatsapp.ts +7 -0
- package/src/commands/check.ts +365 -0
- package/src/commands/code.ts +684 -0
- package/src/commands/connect.ts +12 -0
- package/src/commands/deploy.ts +414 -0
- package/src/commands/fix.ts +20 -0
- package/src/commands/gateway.ts +604 -0
- package/src/commands/generate.ts +178 -0
- package/src/commands/init.ts +574 -0
- package/src/commands/learn.ts +36 -0
- package/src/commands/security.ts +102 -0
- package/src/commands/setup.ts +448 -0
- package/src/commands/status.ts +56 -0
- package/src/index.ts +268 -0
- package/src/patterns/loader.ts +337 -0
- package/src/services/github.ts +61 -0
- package/src/services/supabase.ts +147 -0
- package/src/services/vercel.ts +61 -0
- package/src/utils/claude-md.ts +287 -0
- package/src/utils/config.ts +282 -0
- package/src/utils/updates.ts +27 -0
- package/tsconfig.json +17 -10
- package/.vscodeignore +0 -18
- package/LICENSE +0 -21
- package/codebakers-1.0.0.vsix +0 -0
- package/codebakers-1.0.10.vsix +0 -0
- package/codebakers-1.0.11.vsix +0 -0
- package/codebakers-1.0.12.vsix +0 -0
- package/codebakers-1.0.13.vsix +0 -0
- package/codebakers-1.0.14.vsix +0 -0
- package/codebakers-1.0.15.vsix +0 -0
- package/codebakers-1.0.16.vsix +0 -0
- package/codebakers-1.0.17.vsix +0 -0
- package/codebakers-1.0.18.vsix +0 -0
- package/codebakers-1.0.19.vsix +0 -0
- package/codebakers-1.0.20.vsix +0 -0
- package/codebakers-1.0.21.vsix +0 -0
- package/codebakers-1.0.22.vsix +0 -0
- package/codebakers-1.0.23.vsix +0 -0
- package/codebakers-1.0.24.vsix +0 -0
- package/codebakers-1.0.25.vsix +0 -0
- package/codebakers-1.0.26.vsix +0 -0
- package/codebakers-1.0.27.vsix +0 -0
- package/codebakers-1.0.28.vsix +0 -0
- package/codebakers-1.0.29.vsix +0 -0
- package/codebakers-1.0.30.vsix +0 -0
- package/codebakers-1.0.31.vsix +0 -0
- package/codebakers-1.0.32.vsix +0 -0
- package/codebakers-1.0.35.vsix +0 -0
- package/codebakers-1.0.36.vsix +0 -0
- package/codebakers-1.0.37.vsix +0 -0
- package/codebakers-1.0.38.vsix +0 -0
- package/codebakers-1.0.39.vsix +0 -0
- package/codebakers-1.0.40.vsix +0 -0
- package/codebakers-1.0.41.vsix +0 -0
- package/codebakers-1.0.42.vsix +0 -0
- package/codebakers-1.0.43.vsix +0 -0
- package/codebakers-1.0.44.vsix +0 -0
- package/codebakers-1.0.45.vsix +0 -0
- package/dist/extension.js +0 -1394
- package/esbuild.js +0 -63
- package/media/icon.png +0 -0
- package/media/icon.svg +0 -7
- package/nul +0 -1
- package/preview.html +0 -547
- package/src/ChatPanelProvider.ts +0 -1815
- package/src/ChatViewProvider.ts +0 -749
- package/src/CodeBakersClient.ts +0 -1146
- package/src/CodeValidator.ts +0 -645
- package/src/FileOperations.ts +0 -410
- package/src/ProjectContext.ts +0 -526
- package/src/extension.ts +0 -332
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import * as fs from 'fs-extra';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
6
|
+
import { execa } from 'execa';
|
|
7
|
+
import { Config } from '../utils/config.js';
|
|
8
|
+
import { loadPatterns } from '../patterns/loader.js';
|
|
9
|
+
import { runPatternCheck } from './check.js';
|
|
10
|
+
|
|
11
|
+
interface CodeOptions {
|
|
12
|
+
watch?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Message {
|
|
16
|
+
role: 'user' | 'assistant';
|
|
17
|
+
content: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ProjectContext {
|
|
21
|
+
name: string;
|
|
22
|
+
framework: string;
|
|
23
|
+
ui: string;
|
|
24
|
+
packages: string[];
|
|
25
|
+
existingFiles: string[];
|
|
26
|
+
recentChanges: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function codeCommand(prompt?: string, options: CodeOptions = {}): Promise<void> {
|
|
30
|
+
const config = new Config();
|
|
31
|
+
|
|
32
|
+
// Check if configured
|
|
33
|
+
if (!config.isConfigured()) {
|
|
34
|
+
p.log.error('Please run `codebakers setup` first.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if in project
|
|
39
|
+
if (!config.isInProject()) {
|
|
40
|
+
p.log.error('Not in a CodeBakers project. Run `codebakers init` first.');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get API key
|
|
45
|
+
const anthropicCreds = config.getCredentials('anthropic');
|
|
46
|
+
if (!anthropicCreds?.apiKey) {
|
|
47
|
+
p.log.error('Anthropic API key not configured. Run `codebakers setup`.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initialize Anthropic
|
|
52
|
+
const anthropic = new Anthropic({
|
|
53
|
+
apiKey: anthropicCreds.apiKey,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Load project context
|
|
57
|
+
const projectContext = await loadProjectContext();
|
|
58
|
+
|
|
59
|
+
// Load patterns
|
|
60
|
+
const patterns = await loadPatterns(config);
|
|
61
|
+
|
|
62
|
+
// Build system prompt
|
|
63
|
+
const systemPrompt = buildSystemPrompt(projectContext, patterns);
|
|
64
|
+
|
|
65
|
+
console.log(chalk.cyan(`
|
|
66
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®
|
|
67
|
+
ā š¤ CodeBakers AI ā
|
|
68
|
+
ā ā
|
|
69
|
+
ā Project: ${projectContext.name.padEnd(38)}ā
|
|
70
|
+
ā Stack: ${`${projectContext.framework} + ${projectContext.ui}`.padEnd(40)}ā
|
|
71
|
+
ā ā
|
|
72
|
+
ā Type your request or "?" for help ā
|
|
73
|
+
ā Type "exit" or Ctrl+C to quit ā
|
|
74
|
+
ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ
|
|
75
|
+
`));
|
|
76
|
+
|
|
77
|
+
// Conversation history
|
|
78
|
+
const messages: Message[] = [];
|
|
79
|
+
|
|
80
|
+
// If prompt provided via command line
|
|
81
|
+
if (prompt) {
|
|
82
|
+
await processUserInput(prompt, messages, anthropic, systemPrompt, projectContext, config);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Interactive loop
|
|
86
|
+
while (true) {
|
|
87
|
+
const input = await p.text({
|
|
88
|
+
message: '',
|
|
89
|
+
placeholder: 'What do you want to build?',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (p.isCancel(input)) {
|
|
93
|
+
p.outro('Goodbye!');
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const userInput = (input as string).trim();
|
|
98
|
+
|
|
99
|
+
if (!userInput) continue;
|
|
100
|
+
|
|
101
|
+
if (userInput.toLowerCase() === 'exit') {
|
|
102
|
+
p.outro('Goodbye!');
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (userInput === '?') {
|
|
107
|
+
showHelp();
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle slash commands
|
|
112
|
+
if (userInput.startsWith('/')) {
|
|
113
|
+
await handleSlashCommand(userInput, projectContext, config);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Process input with AI
|
|
118
|
+
await processUserInput(userInput, messages, anthropic, systemPrompt, projectContext, config);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function processUserInput(
|
|
123
|
+
userInput: string,
|
|
124
|
+
messages: Message[],
|
|
125
|
+
anthropic: Anthropic,
|
|
126
|
+
systemPrompt: string,
|
|
127
|
+
projectContext: ProjectContext,
|
|
128
|
+
config: Config
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
const spinner = p.spinner();
|
|
131
|
+
|
|
132
|
+
// Add user message
|
|
133
|
+
messages.push({ role: 'user', content: userInput });
|
|
134
|
+
|
|
135
|
+
// Check for wizard triggers
|
|
136
|
+
const wizardResult = await checkForWizard(userInput);
|
|
137
|
+
if (wizardResult) {
|
|
138
|
+
// If wizard ran, use its enhanced prompt
|
|
139
|
+
messages[messages.length - 1].content = wizardResult;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
spinner.start('Thinking...');
|
|
144
|
+
|
|
145
|
+
// Call Claude
|
|
146
|
+
const response = await anthropic.messages.create({
|
|
147
|
+
model: 'claude-sonnet-4-20250514',
|
|
148
|
+
max_tokens: 8192,
|
|
149
|
+
system: systemPrompt,
|
|
150
|
+
messages: messages.map(m => ({
|
|
151
|
+
role: m.role,
|
|
152
|
+
content: m.content,
|
|
153
|
+
})),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
spinner.stop('');
|
|
157
|
+
|
|
158
|
+
// Extract response
|
|
159
|
+
const assistantMessage = response.content[0].type === 'text'
|
|
160
|
+
? response.content[0].text
|
|
161
|
+
: '';
|
|
162
|
+
|
|
163
|
+
// Add to history
|
|
164
|
+
messages.push({ role: 'assistant', content: assistantMessage });
|
|
165
|
+
|
|
166
|
+
// Parse and execute any code actions
|
|
167
|
+
const actions = parseActions(assistantMessage);
|
|
168
|
+
|
|
169
|
+
if (actions.length > 0) {
|
|
170
|
+
// Show plan
|
|
171
|
+
console.log(chalk.cyan('\nš Plan:'));
|
|
172
|
+
actions.forEach((action, i) => {
|
|
173
|
+
console.log(chalk.dim(` ${i + 1}. ${action.description}`));
|
|
174
|
+
});
|
|
175
|
+
console.log('');
|
|
176
|
+
|
|
177
|
+
const proceed = await p.confirm({
|
|
178
|
+
message: 'Execute this plan?',
|
|
179
|
+
initialValue: true,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (proceed && !p.isCancel(proceed)) {
|
|
183
|
+
spinner.start('Building...');
|
|
184
|
+
|
|
185
|
+
for (const action of actions) {
|
|
186
|
+
await executeAction(action, spinner);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
spinner.stop('Build complete');
|
|
190
|
+
|
|
191
|
+
// Run pattern check
|
|
192
|
+
console.log(chalk.dim('\nš Running CodeBakers check...'));
|
|
193
|
+
const checkResult = await runPatternCheck(false);
|
|
194
|
+
|
|
195
|
+
if (checkResult.violations.length > 0) {
|
|
196
|
+
console.log(chalk.yellow(`\nā ļø ${checkResult.violations.length} pattern violations found`));
|
|
197
|
+
|
|
198
|
+
const autoFix = await p.confirm({
|
|
199
|
+
message: 'Auto-fix violations?',
|
|
200
|
+
initialValue: true,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (autoFix && !p.isCancel(autoFix)) {
|
|
204
|
+
spinner.start('Auto-fixing...');
|
|
205
|
+
await autoFixViolations(checkResult.violations, anthropic, systemPrompt);
|
|
206
|
+
spinner.stop('Violations fixed');
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
console.log(chalk.green('ā All patterns satisfied'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Update project context
|
|
213
|
+
projectContext.recentChanges.push(...actions.map(a => a.description));
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
// Just print the response
|
|
217
|
+
console.log('\n' + assistantMessage + '\n');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
} catch (error) {
|
|
221
|
+
spinner.stop('Error');
|
|
222
|
+
console.log(chalk.red(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function buildSystemPrompt(context: ProjectContext, patterns: string): string {
|
|
227
|
+
return `You are CodeBakers, an AI coding assistant that ALWAYS follows the user's patterns and rules.
|
|
228
|
+
|
|
229
|
+
PROJECT CONTEXT:
|
|
230
|
+
- Name: ${context.name}
|
|
231
|
+
- Framework: ${context.framework}
|
|
232
|
+
- UI Library: ${context.ui}
|
|
233
|
+
- Packages: ${context.packages.join(', ')}
|
|
234
|
+
|
|
235
|
+
EXISTING FILES:
|
|
236
|
+
${context.existingFiles.slice(0, 50).join('\n')}
|
|
237
|
+
|
|
238
|
+
RECENT CHANGES:
|
|
239
|
+
${context.recentChanges.slice(-10).join('\n')}
|
|
240
|
+
|
|
241
|
+
CODEBAKERS PATTERNS (MUST FOLLOW):
|
|
242
|
+
${patterns}
|
|
243
|
+
|
|
244
|
+
INSTRUCTIONS:
|
|
245
|
+
1. ALWAYS follow the patterns above - they are non-negotiable
|
|
246
|
+
2. When creating code, output it in this format:
|
|
247
|
+
|
|
248
|
+
<<<ACTION:CREATE_FILE>>>
|
|
249
|
+
PATH: src/path/to/file.tsx
|
|
250
|
+
DESCRIPTION: Brief description
|
|
251
|
+
<<<CONTENT>>>
|
|
252
|
+
// file content here
|
|
253
|
+
<<<END_CONTENT>>>
|
|
254
|
+
<<<END_ACTION>>>
|
|
255
|
+
|
|
256
|
+
3. For editing files:
|
|
257
|
+
|
|
258
|
+
<<<ACTION:EDIT_FILE>>>
|
|
259
|
+
PATH: src/path/to/file.tsx
|
|
260
|
+
DESCRIPTION: What you're changing
|
|
261
|
+
<<<FIND>>>
|
|
262
|
+
// code to find
|
|
263
|
+
<<<REPLACE>>>
|
|
264
|
+
// replacement code
|
|
265
|
+
<<<END_ACTION>>>
|
|
266
|
+
|
|
267
|
+
4. For running commands:
|
|
268
|
+
|
|
269
|
+
<<<ACTION:RUN_COMMAND>>>
|
|
270
|
+
COMMAND: pnpm add some-package
|
|
271
|
+
DESCRIPTION: Why running this
|
|
272
|
+
<<<END_ACTION>>>
|
|
273
|
+
|
|
274
|
+
5. Every button MUST have a working onClick handler
|
|
275
|
+
6. Every form MUST have proper validation with Zod
|
|
276
|
+
7. Every async operation MUST have loading, error, and success states
|
|
277
|
+
8. Every list MUST have empty state handling
|
|
278
|
+
9. Use Zustand for state management, not prop drilling
|
|
279
|
+
10. Put files in the correct folders as specified in patterns
|
|
280
|
+
|
|
281
|
+
If you're unsure about requirements, ask clarifying questions before coding.
|
|
282
|
+
`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function loadProjectContext(): Promise<ProjectContext> {
|
|
286
|
+
const cwd = process.cwd();
|
|
287
|
+
const codebakersConfig = path.join(cwd, '.codebakers', 'config.json');
|
|
288
|
+
|
|
289
|
+
let projectConfig: Record<string, unknown> = {};
|
|
290
|
+
if (await fs.pathExists(codebakersConfig)) {
|
|
291
|
+
projectConfig = await fs.readJson(codebakersConfig);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Get list of files
|
|
295
|
+
const glob = (await import('fast-glob')).default;
|
|
296
|
+
const files = await glob(['src/**/*.{ts,tsx,js,jsx}'], { cwd });
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
name: path.basename(cwd),
|
|
300
|
+
framework: (projectConfig.framework as string) || 'nextjs',
|
|
301
|
+
ui: (projectConfig.ui as string) || 'shadcn',
|
|
302
|
+
packages: (projectConfig.packages as string[]) || [],
|
|
303
|
+
existingFiles: files,
|
|
304
|
+
recentChanges: [],
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
interface CodeAction {
|
|
309
|
+
type: 'CREATE_FILE' | 'EDIT_FILE' | 'RUN_COMMAND' | 'DELETE_FILE';
|
|
310
|
+
path?: string;
|
|
311
|
+
content?: string;
|
|
312
|
+
command?: string;
|
|
313
|
+
description: string;
|
|
314
|
+
find?: string;
|
|
315
|
+
replace?: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function parseActions(response: string): CodeAction[] {
|
|
319
|
+
const actions: CodeAction[] = [];
|
|
320
|
+
|
|
321
|
+
// Parse CREATE_FILE actions
|
|
322
|
+
const createFileRegex = /<<<ACTION:CREATE_FILE>>>\s*PATH:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<CONTENT>>>\s*([\s\S]+?)\s*<<<END_CONTENT>>>\s*<<<END_ACTION>>>/g;
|
|
323
|
+
let match;
|
|
324
|
+
|
|
325
|
+
while ((match = createFileRegex.exec(response)) !== null) {
|
|
326
|
+
actions.push({
|
|
327
|
+
type: 'CREATE_FILE',
|
|
328
|
+
path: match[1].trim(),
|
|
329
|
+
description: match[2].trim(),
|
|
330
|
+
content: match[3].trim(),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Parse EDIT_FILE actions
|
|
335
|
+
const editFileRegex = /<<<ACTION:EDIT_FILE>>>\s*PATH:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<FIND>>>\s*([\s\S]+?)\s*<<<REPLACE>>>\s*([\s\S]+?)\s*<<<END_ACTION>>>/g;
|
|
336
|
+
|
|
337
|
+
while ((match = editFileRegex.exec(response)) !== null) {
|
|
338
|
+
actions.push({
|
|
339
|
+
type: 'EDIT_FILE',
|
|
340
|
+
path: match[1].trim(),
|
|
341
|
+
description: match[2].trim(),
|
|
342
|
+
find: match[3].trim(),
|
|
343
|
+
replace: match[4].trim(),
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Parse RUN_COMMAND actions
|
|
348
|
+
const runCommandRegex = /<<<ACTION:RUN_COMMAND>>>\s*COMMAND:\s*(.+?)\s*DESCRIPTION:\s*(.+?)\s*<<<END_ACTION>>>/g;
|
|
349
|
+
|
|
350
|
+
while ((match = runCommandRegex.exec(response)) !== null) {
|
|
351
|
+
actions.push({
|
|
352
|
+
type: 'RUN_COMMAND',
|
|
353
|
+
command: match[1].trim(),
|
|
354
|
+
description: match[2].trim(),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return actions;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function executeAction(action: CodeAction, spinner: ReturnType<typeof p.spinner>): Promise<void> {
|
|
362
|
+
const cwd = process.cwd();
|
|
363
|
+
|
|
364
|
+
switch (action.type) {
|
|
365
|
+
case 'CREATE_FILE': {
|
|
366
|
+
const filePath = path.join(cwd, action.path!);
|
|
367
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
368
|
+
await fs.writeFile(filePath, action.content!);
|
|
369
|
+
spinner.message(`Created ${action.path}`);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
case 'EDIT_FILE': {
|
|
374
|
+
const filePath = path.join(cwd, action.path!);
|
|
375
|
+
if (await fs.pathExists(filePath)) {
|
|
376
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
377
|
+
if (action.find && content.includes(action.find)) {
|
|
378
|
+
content = content.replace(action.find, action.replace || '');
|
|
379
|
+
await fs.writeFile(filePath, content);
|
|
380
|
+
spinner.message(`Edited ${action.path}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
case 'RUN_COMMAND': {
|
|
387
|
+
spinner.message(`Running: ${action.command}`);
|
|
388
|
+
const [cmd, ...args] = action.command!.split(' ');
|
|
389
|
+
await execa(cmd, args, { cwd, stdio: 'pipe' });
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
case 'DELETE_FILE': {
|
|
394
|
+
const filePath = path.join(cwd, action.path!);
|
|
395
|
+
if (await fs.pathExists(filePath)) {
|
|
396
|
+
await fs.remove(filePath);
|
|
397
|
+
spinner.message(`Deleted ${action.path}`);
|
|
398
|
+
}
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function checkForWizard(input: string): Promise<string | null> {
|
|
405
|
+
const lower = input.toLowerCase();
|
|
406
|
+
|
|
407
|
+
// Check for feature-building triggers
|
|
408
|
+
if (lower.includes('add') || lower.includes('create') || lower.includes('build')) {
|
|
409
|
+
// Check for specific feature types
|
|
410
|
+
if (lower.includes('auth') || lower.includes('login') || lower.includes('signup')) {
|
|
411
|
+
return await runAuthWizard(input);
|
|
412
|
+
}
|
|
413
|
+
if (lower.includes('payment') || lower.includes('checkout') || lower.includes('stripe')) {
|
|
414
|
+
return await runPaymentWizard(input);
|
|
415
|
+
}
|
|
416
|
+
if (lower.includes('form')) {
|
|
417
|
+
return await runFormWizard(input);
|
|
418
|
+
}
|
|
419
|
+
if (lower.includes('dashboard')) {
|
|
420
|
+
return await runDashboardWizard(input);
|
|
421
|
+
}
|
|
422
|
+
if (lower.includes('chat') || lower.includes('ai')) {
|
|
423
|
+
return await runAIWizard(input);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function runAuthWizard(originalInput: string): Promise<string> {
|
|
431
|
+
console.log(chalk.cyan('\nš Auth Wizard'));
|
|
432
|
+
|
|
433
|
+
const authType = await p.select({
|
|
434
|
+
message: 'What type of authentication?',
|
|
435
|
+
options: [
|
|
436
|
+
{ value: 'magic-link', label: 'š Magic link (email)' },
|
|
437
|
+
{ value: 'password', label: 'š Password (email + password)' },
|
|
438
|
+
{ value: 'social', label: 'š Social login (Google, GitHub)' },
|
|
439
|
+
{ value: 'phone', label: 'š± Phone (SMS code)' },
|
|
440
|
+
],
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (p.isCancel(authType)) return originalInput;
|
|
444
|
+
|
|
445
|
+
const pages = await p.multiselect({
|
|
446
|
+
message: 'What pages do you need?',
|
|
447
|
+
options: [
|
|
448
|
+
{ value: 'login', label: 'Login page' },
|
|
449
|
+
{ value: 'signup', label: 'Signup page' },
|
|
450
|
+
{ value: 'forgot', label: 'Forgot password' },
|
|
451
|
+
{ value: 'reset', label: 'Reset password' },
|
|
452
|
+
{ value: 'verify', label: 'Email verification' },
|
|
453
|
+
],
|
|
454
|
+
initialValues: ['login', 'signup'],
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
if (p.isCancel(pages)) return originalInput;
|
|
458
|
+
|
|
459
|
+
const redirect = await p.text({
|
|
460
|
+
message: 'Where should users go after login?',
|
|
461
|
+
placeholder: '/dashboard',
|
|
462
|
+
initialValue: '/dashboard',
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
return `Create a complete authentication system with:
|
|
466
|
+
- Auth type: ${authType}
|
|
467
|
+
- Pages: ${(pages as string[]).join(', ')}
|
|
468
|
+
- Redirect after login: ${redirect}
|
|
469
|
+
- Use Supabase Auth
|
|
470
|
+
- Include loading states, error handling, form validation
|
|
471
|
+
- Follow all CodeBakers patterns`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function runPaymentWizard(originalInput: string): Promise<string> {
|
|
475
|
+
console.log(chalk.cyan('\nš³ Payment Wizard'));
|
|
476
|
+
|
|
477
|
+
const paymentType = await p.select({
|
|
478
|
+
message: 'What type of payment flow?',
|
|
479
|
+
options: [
|
|
480
|
+
{ value: 'one-time', label: 'šµ One-time purchase' },
|
|
481
|
+
{ value: 'subscription', label: 'š Subscription (recurring)' },
|
|
482
|
+
{ value: 'both', label: 'š¦ Both' },
|
|
483
|
+
],
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
if (p.isCancel(paymentType)) return originalInput;
|
|
487
|
+
|
|
488
|
+
const features = await p.multiselect({
|
|
489
|
+
message: 'What features?',
|
|
490
|
+
options: [
|
|
491
|
+
{ value: 'checkout', label: 'Checkout page' },
|
|
492
|
+
{ value: 'webhook', label: 'Webhook handler' },
|
|
493
|
+
{ value: 'portal', label: 'Customer portal' },
|
|
494
|
+
{ value: 'invoices', label: 'Invoice history' },
|
|
495
|
+
],
|
|
496
|
+
initialValues: ['checkout', 'webhook'],
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
if (p.isCancel(features)) return originalInput;
|
|
500
|
+
|
|
501
|
+
return `Create a complete Stripe payment system with:
|
|
502
|
+
- Payment type: ${paymentType}
|
|
503
|
+
- Features: ${(features as string[]).join(', ')}
|
|
504
|
+
- Use Stripe SDK
|
|
505
|
+
- Include webhook signature verification
|
|
506
|
+
- Handle all error cases
|
|
507
|
+
- Follow all CodeBakers patterns`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function runFormWizard(originalInput: string): Promise<string> {
|
|
511
|
+
console.log(chalk.cyan('\nš Form Wizard'));
|
|
512
|
+
|
|
513
|
+
const formName = await p.text({
|
|
514
|
+
message: 'What is this form for?',
|
|
515
|
+
placeholder: 'Contact form',
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
if (p.isCancel(formName)) return originalInput;
|
|
519
|
+
|
|
520
|
+
return `Create a ${formName} form with:
|
|
521
|
+
- React Hook Form for form handling
|
|
522
|
+
- Zod validation schema
|
|
523
|
+
- Loading state during submission
|
|
524
|
+
- Error messages for each field
|
|
525
|
+
- Success toast on completion
|
|
526
|
+
- Proper accessibility (aria labels)
|
|
527
|
+
- Follow all CodeBakers patterns`;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async function runDashboardWizard(originalInput: string): Promise<string> {
|
|
531
|
+
console.log(chalk.cyan('\nš Dashboard Wizard'));
|
|
532
|
+
|
|
533
|
+
const components = await p.multiselect({
|
|
534
|
+
message: 'What components?',
|
|
535
|
+
options: [
|
|
536
|
+
{ value: 'stats', label: 'Stats cards' },
|
|
537
|
+
{ value: 'chart', label: 'Charts/graphs' },
|
|
538
|
+
{ value: 'table', label: 'Data table' },
|
|
539
|
+
{ value: 'activity', label: 'Activity feed' },
|
|
540
|
+
{ value: 'sidebar', label: 'Sidebar navigation' },
|
|
541
|
+
],
|
|
542
|
+
initialValues: ['stats', 'sidebar'],
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (p.isCancel(components)) return originalInput;
|
|
546
|
+
|
|
547
|
+
return `Create a dashboard with:
|
|
548
|
+
- Components: ${(components as string[]).join(', ')}
|
|
549
|
+
- Responsive layout
|
|
550
|
+
- Loading skeletons
|
|
551
|
+
- Empty states
|
|
552
|
+
- Error boundaries
|
|
553
|
+
- Follow all CodeBakers patterns`;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function runAIWizard(originalInput: string): Promise<string> {
|
|
557
|
+
console.log(chalk.cyan('\nš¤ AI Feature Wizard'));
|
|
558
|
+
|
|
559
|
+
const aiType = await p.select({
|
|
560
|
+
message: 'What AI feature?',
|
|
561
|
+
options: [
|
|
562
|
+
{ value: 'chat', label: 'š¬ Chat interface' },
|
|
563
|
+
{ value: 'generate', label: 'š Content generation' },
|
|
564
|
+
{ value: 'analyze', label: 'š Data analysis' },
|
|
565
|
+
],
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
if (p.isCancel(aiType)) return originalInput;
|
|
569
|
+
|
|
570
|
+
const streaming = await p.confirm({
|
|
571
|
+
message: 'Use streaming responses?',
|
|
572
|
+
initialValue: true,
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
return `Create an AI ${aiType} feature with:
|
|
576
|
+
- Streaming: ${streaming ? 'Yes' : 'No'}
|
|
577
|
+
- Use Anthropic Claude API
|
|
578
|
+
- Include conversation history
|
|
579
|
+
- Loading indicators
|
|
580
|
+
- Error handling
|
|
581
|
+
- Stop generation button
|
|
582
|
+
- Follow all CodeBakers patterns`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async function handleSlashCommand(
|
|
586
|
+
command: string,
|
|
587
|
+
context: ProjectContext,
|
|
588
|
+
config: Config
|
|
589
|
+
): Promise<void> {
|
|
590
|
+
const [cmd, ...args] = command.slice(1).split(' ');
|
|
591
|
+
|
|
592
|
+
switch (cmd.toLowerCase()) {
|
|
593
|
+
case 'deploy':
|
|
594
|
+
console.log(chalk.dim('Running deploy...'));
|
|
595
|
+
// Import and run deploy command
|
|
596
|
+
break;
|
|
597
|
+
case 'check':
|
|
598
|
+
console.log(chalk.dim('Running pattern check...'));
|
|
599
|
+
await runPatternCheck(false);
|
|
600
|
+
break;
|
|
601
|
+
case 'status':
|
|
602
|
+
console.log(chalk.cyan(`
|
|
603
|
+
Project: ${context.name}
|
|
604
|
+
Framework: ${context.framework}
|
|
605
|
+
UI: ${context.ui}
|
|
606
|
+
Files: ${context.existingFiles.length}
|
|
607
|
+
`));
|
|
608
|
+
break;
|
|
609
|
+
case 'undo':
|
|
610
|
+
console.log(chalk.yellow('Undo not yet implemented'));
|
|
611
|
+
break;
|
|
612
|
+
case 'help':
|
|
613
|
+
showHelp();
|
|
614
|
+
break;
|
|
615
|
+
default:
|
|
616
|
+
console.log(chalk.dim(`Unknown command: ${cmd}`));
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function showHelp(): void {
|
|
621
|
+
console.log(chalk.cyan(`
|
|
622
|
+
Available commands:
|
|
623
|
+
|
|
624
|
+
/deploy Deploy to production
|
|
625
|
+
/check Run pattern check
|
|
626
|
+
/status Show project status
|
|
627
|
+
/undo Undo last change
|
|
628
|
+
/help Show this help
|
|
629
|
+
|
|
630
|
+
Tips:
|
|
631
|
+
⢠Just describe what you want to build
|
|
632
|
+
⢠I'll ask clarifying questions if needed
|
|
633
|
+
⢠All code follows your CodeBakers patterns
|
|
634
|
+
⢠Patterns are auto-checked after every change
|
|
635
|
+
`));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function autoFixViolations(
|
|
639
|
+
violations: Array<{ file: string; rule: string; message: string }>,
|
|
640
|
+
anthropic: Anthropic,
|
|
641
|
+
systemPrompt: string
|
|
642
|
+
): Promise<void> {
|
|
643
|
+
// Group violations by file
|
|
644
|
+
const byFile = new Map<string, typeof violations>();
|
|
645
|
+
|
|
646
|
+
for (const v of violations) {
|
|
647
|
+
if (!byFile.has(v.file)) {
|
|
648
|
+
byFile.set(v.file, []);
|
|
649
|
+
}
|
|
650
|
+
byFile.get(v.file)!.push(v);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
for (const [file, fileViolations] of byFile) {
|
|
654
|
+
const content = await fs.readFile(path.join(process.cwd(), file), 'utf-8');
|
|
655
|
+
|
|
656
|
+
const fixPrompt = `Fix these violations in ${file}:
|
|
657
|
+
|
|
658
|
+
${fileViolations.map(v => `- ${v.rule}: ${v.message}`).join('\n')}
|
|
659
|
+
|
|
660
|
+
Current file content:
|
|
661
|
+
\`\`\`
|
|
662
|
+
${content}
|
|
663
|
+
\`\`\`
|
|
664
|
+
|
|
665
|
+
Output only the fixed file content, no explanation.`;
|
|
666
|
+
|
|
667
|
+
const response = await anthropic.messages.create({
|
|
668
|
+
model: 'claude-sonnet-4-20250514',
|
|
669
|
+
max_tokens: 8192,
|
|
670
|
+
system: systemPrompt,
|
|
671
|
+
messages: [{ role: 'user', content: fixPrompt }],
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
const fixedContent = response.content[0].type === 'text'
|
|
675
|
+
? response.content[0].text
|
|
676
|
+
: '';
|
|
677
|
+
|
|
678
|
+
// Extract code from markdown if present
|
|
679
|
+
const codeMatch = fixedContent.match(/```(?:tsx?|jsx?)?\n([\s\S]+?)\n```/);
|
|
680
|
+
const finalContent = codeMatch ? codeMatch[1] : fixedContent;
|
|
681
|
+
|
|
682
|
+
await fs.writeFile(path.join(process.cwd(), file), finalContent);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/commands/connect.ts
|
|
2
|
+
import * as p from '@clack/prompts';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { Config } from '../utils/config.js';
|
|
5
|
+
import { setupCommand } from './setup.js';
|
|
6
|
+
|
|
7
|
+
export async function connectCommand(service?: string): Promise<void> {
|
|
8
|
+
const config = new Config();
|
|
9
|
+
|
|
10
|
+
// Delegate to setup for now
|
|
11
|
+
await setupCommand();
|
|
12
|
+
}
|