codebakers 2.0.4 → 2.0.10

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.
@@ -0,0 +1,319 @@
1
+ // src/utils/config.ts
2
+ import Conf from "conf";
3
+ import * as fs from "fs-extra";
4
+ import * as path from "path";
5
+ import os from "os";
6
+ import { z } from "zod";
7
+ var ConfigSchema = z.object({
8
+ version: z.string().default("1.0.0"),
9
+ credentials: z.object({
10
+ github: z.object({
11
+ token: z.string().optional(),
12
+ username: z.string().optional()
13
+ }).optional(),
14
+ vercel: z.object({
15
+ token: z.string().optional(),
16
+ teamId: z.string().optional()
17
+ }).optional(),
18
+ supabase: z.object({
19
+ accessToken: z.string().optional(),
20
+ orgId: z.string().optional()
21
+ }).optional(),
22
+ anthropic: z.object({
23
+ apiKey: z.string().optional()
24
+ }).optional(),
25
+ openai: z.object({
26
+ apiKey: z.string().optional()
27
+ }).optional(),
28
+ stripe: z.object({
29
+ secretKey: z.string().optional(),
30
+ publishableKey: z.string().optional(),
31
+ webhookSecret: z.string().optional()
32
+ }).optional(),
33
+ twilio: z.object({
34
+ accountSid: z.string().optional(),
35
+ authToken: z.string().optional(),
36
+ phoneNumber: z.string().optional()
37
+ }).optional(),
38
+ vapi: z.object({
39
+ apiKey: z.string().optional()
40
+ }).optional(),
41
+ resend: z.object({
42
+ apiKey: z.string().optional()
43
+ }).optional(),
44
+ elevenLabs: z.object({
45
+ apiKey: z.string().optional()
46
+ }).optional(),
47
+ microsoft: z.object({
48
+ clientId: z.string().optional(),
49
+ clientSecret: z.string().optional(),
50
+ tenantId: z.string().optional()
51
+ }).optional(),
52
+ google: z.object({
53
+ clientId: z.string().optional(),
54
+ clientSecret: z.string().optional()
55
+ }).optional()
56
+ }).default({}),
57
+ preferences: z.object({
58
+ defaultFramework: z.string().optional(),
59
+ defaultUI: z.string().optional(),
60
+ defaultPackages: z.array(z.string()).optional(),
61
+ deployToPreviewFirst: z.boolean().default(true)
62
+ }).default({}),
63
+ learning: z.object({
64
+ enabled: z.boolean().default(true),
65
+ shortcuts: z.record(z.string()).default({}),
66
+ preferences: z.record(z.any()).default({}),
67
+ rejections: z.array(z.string()).default([]),
68
+ workflows: z.record(z.array(z.string())).default({})
69
+ }).default({}),
70
+ projects: z.array(z.object({
71
+ name: z.string(),
72
+ path: z.string(),
73
+ github: z.string().optional(),
74
+ vercel: z.string().optional(),
75
+ supabase: z.string().optional(),
76
+ createdAt: z.string()
77
+ })).default([]),
78
+ channels: z.object({
79
+ whatsapp: z.object({
80
+ enabled: z.boolean().default(false),
81
+ phoneNumber: z.string().optional()
82
+ }).optional(),
83
+ telegram: z.object({
84
+ enabled: z.boolean().default(false),
85
+ botToken: z.string().optional()
86
+ }).optional(),
87
+ discord: z.object({
88
+ enabled: z.boolean().default(false),
89
+ botToken: z.string().optional()
90
+ }).optional(),
91
+ slack: z.object({
92
+ enabled: z.boolean().default(false),
93
+ botToken: z.string().optional()
94
+ }).optional()
95
+ }).default({})
96
+ });
97
+ var Config = class {
98
+ conf;
99
+ configDir;
100
+ constructor() {
101
+ this.configDir = path.join(os.homedir(), ".codebakers");
102
+ fs.ensureDirSync(this.configDir);
103
+ fs.ensureDirSync(path.join(this.configDir, "patterns"));
104
+ fs.ensureDirSync(path.join(this.configDir, "templates"));
105
+ fs.ensureDirSync(path.join(this.configDir, "learning"));
106
+ this.conf = new Conf({
107
+ projectName: "codebakers",
108
+ cwd: this.configDir,
109
+ schema: {
110
+ version: { type: "string", default: "1.0.0" },
111
+ credentials: { type: "object", default: {} },
112
+ preferences: { type: "object", default: {} },
113
+ learning: { type: "object", default: { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} } },
114
+ projects: { type: "array", default: [] },
115
+ channels: { type: "object", default: {} }
116
+ }
117
+ });
118
+ }
119
+ // Check if initial setup is complete
120
+ isConfigured() {
121
+ const creds = this.conf.get("credentials") || {};
122
+ return !!(creds.github?.token && creds.vercel?.token && creds.supabase?.accessToken && creds.anthropic?.apiKey);
123
+ }
124
+ // Check if current directory is a CodeBakers project
125
+ isInProject() {
126
+ const cwd = process.cwd();
127
+ const codebakersDir = path.join(cwd, ".codebakers");
128
+ const claudeFile = path.join(cwd, "CLAUDE.md");
129
+ if (fs.existsSync(codebakersDir) || fs.existsSync(claudeFile)) {
130
+ return true;
131
+ }
132
+ const projectIndicators = [
133
+ "package.json",
134
+ "next.config.js",
135
+ "next.config.mjs",
136
+ "next.config.ts",
137
+ "vite.config.ts",
138
+ "vite.config.js",
139
+ "remix.config.js",
140
+ "astro.config.mjs"
141
+ ];
142
+ const hasProjectFile = projectIndicators.some(
143
+ (file) => fs.existsSync(path.join(cwd, file))
144
+ );
145
+ if (hasProjectFile) {
146
+ this.autoInitExisting(cwd);
147
+ return true;
148
+ }
149
+ return false;
150
+ }
151
+ // Auto-initialize CodeBakers in an existing project
152
+ autoInitExisting(cwd) {
153
+ try {
154
+ let framework = "unknown";
155
+ let ui = "unknown";
156
+ if (fs.existsSync(path.join(cwd, "next.config.js")) || fs.existsSync(path.join(cwd, "next.config.mjs")) || fs.existsSync(path.join(cwd, "next.config.ts"))) {
157
+ framework = "nextjs";
158
+ } else if (fs.existsSync(path.join(cwd, "vite.config.ts")) || fs.existsSync(path.join(cwd, "vite.config.js"))) {
159
+ framework = "vite";
160
+ } else if (fs.existsSync(path.join(cwd, "remix.config.js"))) {
161
+ framework = "remix";
162
+ } else if (fs.existsSync(path.join(cwd, "astro.config.mjs"))) {
163
+ framework = "astro";
164
+ }
165
+ const pkgPath = path.join(cwd, "package.json");
166
+ if (fs.existsSync(pkgPath)) {
167
+ const pkg = fs.readJsonSync(pkgPath);
168
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
169
+ if (deps["@shadcn/ui"] || deps["class-variance-authority"]) {
170
+ ui = "shadcn";
171
+ } else if (deps["@chakra-ui/react"]) {
172
+ ui = "chakra";
173
+ } else if (deps["@mantine/core"]) {
174
+ ui = "mantine";
175
+ } else if (deps["@mui/material"]) {
176
+ ui = "mui";
177
+ }
178
+ }
179
+ const configDir = path.join(cwd, ".codebakers");
180
+ fs.ensureDirSync(configDir);
181
+ const config = {
182
+ name: path.basename(cwd),
183
+ framework,
184
+ ui,
185
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
186
+ autoDetected: true
187
+ };
188
+ fs.writeJsonSync(path.join(configDir, "config.json"), config, { spaces: 2 });
189
+ const gitignorePath = path.join(cwd, ".gitignore");
190
+ if (fs.existsSync(gitignorePath)) {
191
+ const gitignore = fs.readFileSync(gitignorePath, "utf-8");
192
+ if (!gitignore.includes(".codebakers")) {
193
+ fs.appendFileSync(gitignorePath, "\n# CodeBakers\n.codebakers/\n");
194
+ }
195
+ }
196
+ } catch {
197
+ }
198
+ }
199
+ // Get current project config
200
+ getProjectConfig() {
201
+ const cwd = process.cwd();
202
+ const configPath = path.join(cwd, ".codebakers", "config.json");
203
+ if (fs.existsSync(configPath)) {
204
+ return fs.readJsonSync(configPath);
205
+ }
206
+ return null;
207
+ }
208
+ // Credentials
209
+ getCredentials(service) {
210
+ const creds = this.conf.get("credentials") || {};
211
+ return creds[service];
212
+ }
213
+ setCredentials(service, credentials) {
214
+ const current = this.conf.get("credentials") || {};
215
+ this.conf.set("credentials", {
216
+ ...current,
217
+ [service]: { ...current[service], ...credentials }
218
+ });
219
+ }
220
+ // Preferences
221
+ getPreference(key) {
222
+ const prefs = this.conf.get("preferences") || {};
223
+ return prefs[key];
224
+ }
225
+ setPreference(key, value) {
226
+ const current = this.conf.get("preferences") || {};
227
+ this.conf.set("preferences", { ...current, [key]: value });
228
+ }
229
+ // Learning
230
+ getLearning() {
231
+ return this.conf.get("learning") || { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} };
232
+ }
233
+ addShortcut(shortcut, expansion) {
234
+ const learning = this.getLearning();
235
+ learning.shortcuts[shortcut] = expansion;
236
+ this.conf.set("learning", learning);
237
+ }
238
+ addRejection(item) {
239
+ const learning = this.getLearning();
240
+ if (!learning.rejections.includes(item)) {
241
+ learning.rejections.push(item);
242
+ this.conf.set("learning", learning);
243
+ }
244
+ }
245
+ addWorkflow(name, steps) {
246
+ const learning = this.getLearning();
247
+ learning.workflows[name] = steps;
248
+ this.conf.set("learning", learning);
249
+ }
250
+ learnPreference(key, value) {
251
+ const learning = this.getLearning();
252
+ learning.preferences[key] = value;
253
+ this.conf.set("learning", learning);
254
+ }
255
+ forgetPreference(key) {
256
+ const learning = this.getLearning();
257
+ delete learning.preferences[key];
258
+ this.conf.set("learning", learning);
259
+ }
260
+ resetLearning() {
261
+ this.conf.set("learning", { enabled: true, shortcuts: {}, preferences: {}, rejections: [], workflows: {} });
262
+ }
263
+ // Projects
264
+ getProjects() {
265
+ return this.conf.get("projects") || [];
266
+ }
267
+ addProject(project) {
268
+ const projects = this.getProjects();
269
+ projects.push(project);
270
+ this.conf.set("projects", projects);
271
+ }
272
+ // Channels
273
+ getChannelConfig(channel) {
274
+ const channels = this.conf.get("channels") || {};
275
+ return channels[channel];
276
+ }
277
+ setChannelConfig(channel, config) {
278
+ const current = this.conf.get("channels") || {};
279
+ this.conf.set("channels", { ...current, [channel]: config });
280
+ }
281
+ // Config directory access
282
+ getConfigDir() {
283
+ return this.configDir;
284
+ }
285
+ getPatternsDir() {
286
+ return path.join(this.configDir, "patterns");
287
+ }
288
+ getTemplatesDir() {
289
+ return path.join(this.configDir, "templates");
290
+ }
291
+ getLearningDir() {
292
+ return path.join(this.configDir, "learning");
293
+ }
294
+ // Export/Import
295
+ exportConfig() {
296
+ return {
297
+ version: this.conf.get("version") || "1.0.0",
298
+ credentials: {},
299
+ // Don't export credentials
300
+ preferences: this.conf.get("preferences") || {},
301
+ learning: this.getLearning(),
302
+ projects: this.getProjects(),
303
+ channels: {}
304
+ // Don't export channel tokens
305
+ };
306
+ }
307
+ importConfig(config) {
308
+ if (config.preferences) {
309
+ this.conf.set("preferences", config.preferences);
310
+ }
311
+ if (config.learning) {
312
+ this.conf.set("learning", config.learning);
313
+ }
314
+ }
315
+ };
316
+
317
+ export {
318
+ Config
319
+ };
@@ -0,0 +1,326 @@
1
+ import {
2
+ Config
3
+ } from "./chunk-RCC7FYEU.js";
4
+
5
+ // src/commands/prd.ts
6
+ import * as p from "@clack/prompts";
7
+ import chalk from "chalk";
8
+ import * as fs from "fs-extra";
9
+ import * as path from "path";
10
+ import Anthropic from "@anthropic-ai/sdk";
11
+ async function prdCommand(filePath) {
12
+ const config = new Config();
13
+ if (!config.isConfigured()) {
14
+ p.log.error("Please run `codebakers setup` first.");
15
+ return;
16
+ }
17
+ p.intro(chalk.bgCyan.black(" Build from PRD "));
18
+ let prdPath = filePath;
19
+ if (!prdPath) {
20
+ const file = await p.text({
21
+ message: "Path to PRD file:",
22
+ placeholder: "./PRD.md or paste URL",
23
+ validate: (v) => !v ? "File path required" : void 0
24
+ });
25
+ if (p.isCancel(file)) return;
26
+ prdPath = file;
27
+ }
28
+ const spinner2 = p.spinner();
29
+ spinner2.start("Reading PRD...");
30
+ let prdContent;
31
+ try {
32
+ if (prdPath.startsWith("http")) {
33
+ const response = await fetch(prdPath);
34
+ prdContent = await response.text();
35
+ } else {
36
+ prdContent = await fs.readFile(prdPath, "utf-8");
37
+ }
38
+ } catch (error) {
39
+ spinner2.stop("Error");
40
+ p.log.error(`Could not read PRD: ${error instanceof Error ? error.message : "Unknown error"}`);
41
+ return;
42
+ }
43
+ spinner2.stop("PRD loaded");
44
+ spinner2.start("Analyzing PRD...");
45
+ const anthropicCreds = config.getCredentials("anthropic");
46
+ if (!anthropicCreds?.apiKey) {
47
+ spinner2.stop("Error");
48
+ p.log.error("Anthropic API key not configured.");
49
+ return;
50
+ }
51
+ const anthropic = new Anthropic({ apiKey: anthropicCreds.apiKey });
52
+ const parsed = await parsePRD(anthropic, prdContent);
53
+ spinner2.stop("PRD analyzed");
54
+ console.log(chalk.bold("\n\u{1F4CB} Extracted from PRD:\n"));
55
+ console.log(` ${chalk.cyan("Name:")} ${parsed.name}`);
56
+ console.log(` ${chalk.cyan("Description:")} ${parsed.description}`);
57
+ console.log(` ${chalk.cyan("Features:")} ${parsed.features.length} features`);
58
+ console.log(` ${chalk.cyan("Pages:")} ${parsed.pages.join(", ") || "Auto-detect"}`);
59
+ console.log(` ${chalk.cyan("Database:")} ${parsed.database.length} tables`);
60
+ console.log(` ${chalk.cyan("Integrations:")} ${parsed.integrations.join(", ") || "None"}`);
61
+ console.log("");
62
+ if (parsed.features.length > 0) {
63
+ console.log(chalk.bold("Features to build:"));
64
+ parsed.features.slice(0, 10).forEach((f, i) => {
65
+ console.log(chalk.dim(` ${i + 1}. ${f}`));
66
+ });
67
+ if (parsed.features.length > 10) {
68
+ console.log(chalk.dim(` ... and ${parsed.features.length - 10} more`));
69
+ }
70
+ console.log("");
71
+ }
72
+ const proceed = await p.confirm({
73
+ message: "Build this project?",
74
+ initialValue: true
75
+ });
76
+ if (!proceed || p.isCancel(proceed)) {
77
+ p.cancel("Cancelled");
78
+ return;
79
+ }
80
+ const options = await p.group({
81
+ createInfra: () => p.confirm({
82
+ message: "Create GitHub + Vercel + Supabase?",
83
+ initialValue: true
84
+ }),
85
+ designProfile: () => p.select({
86
+ message: "Design profile:",
87
+ options: [
88
+ { value: "minimal", label: "Minimal (Linear, Notion)" },
89
+ { value: "bold", label: "Bold (Stripe, Ramp)" },
90
+ { value: "editorial", label: "Editorial (Medium, Substack)" },
91
+ { value: "playful", label: "Playful (Figma, Slack)" },
92
+ { value: "premium", label: "Premium (Apple, Porsche)" },
93
+ { value: "dashboard", label: "Dashboard (Datadog, Linear)" }
94
+ ],
95
+ initialValue: parsed.design.profile || "minimal"
96
+ })
97
+ });
98
+ if (p.isCancel(options)) return;
99
+ await buildFromPRD(parsed, options, anthropic, config);
100
+ }
101
+ async function parsePRD(anthropic, content) {
102
+ const response = await anthropic.messages.create({
103
+ model: "claude-sonnet-4-20250514",
104
+ max_tokens: 4096,
105
+ messages: [{
106
+ role: "user",
107
+ content: `Analyze this PRD and extract structured information. Return JSON only, no explanation.
108
+
109
+ PRD:
110
+ ${content}
111
+
112
+ Return this exact JSON structure:
113
+ {
114
+ "name": "project name (lowercase, hyphenated)",
115
+ "description": "one sentence description",
116
+ "features": ["feature 1", "feature 2", ...],
117
+ "pages": ["page1", "page2", ...],
118
+ "database": ["table1", "table2", ...],
119
+ "integrations": ["stripe", "supabase", ...],
120
+ "design": {
121
+ "profile": "minimal|bold|editorial|playful|premium|dashboard or null",
122
+ "brandColor": "#hexcolor or null"
123
+ }
124
+ }
125
+
126
+ Extract ALL features mentioned. Include auth, payments, dashboards, etc.
127
+ For pages, list actual pages/routes needed.
128
+ For database, list tables/entities needed.
129
+ For integrations, list third-party services mentioned.`
130
+ }]
131
+ });
132
+ const text2 = response.content[0].type === "text" ? response.content[0].text : "";
133
+ const jsonMatch = text2.match(/\{[\s\S]*\}/);
134
+ if (!jsonMatch) {
135
+ throw new Error("Could not parse PRD");
136
+ }
137
+ return JSON.parse(jsonMatch[0]);
138
+ }
139
+ async function buildFromPRD(prd, options, anthropic, config) {
140
+ const spinner2 = p.spinner();
141
+ const projectPath = path.join(process.cwd(), prd.name);
142
+ spinner2.start("Creating project structure...");
143
+ await fs.ensureDir(projectPath);
144
+ await fs.ensureDir(path.join(projectPath, ".codebakers"));
145
+ await fs.ensureDir(path.join(projectPath, "src", "app"));
146
+ await fs.ensureDir(path.join(projectPath, "src", "components"));
147
+ await fs.ensureDir(path.join(projectPath, "src", "lib"));
148
+ spinner2.stop("Project structure created");
149
+ spinner2.start("Saving PRD...");
150
+ await fs.writeFile(path.join(projectPath, "PRD.md"), await fs.readFile(process.cwd(), "utf-8").catch(() => JSON.stringify(prd, null, 2)));
151
+ await fs.writeJson(path.join(projectPath, ".codebakers", "prd.json"), prd, { spaces: 2 });
152
+ await fs.writeJson(path.join(projectPath, ".codebakers", "design.json"), {
153
+ profile: options.designProfile,
154
+ colors: prd.design.brandColor ? { brand: prd.design.brandColor } : void 0
155
+ }, { spaces: 2 });
156
+ spinner2.stop("PRD saved");
157
+ spinner2.start("Generating build plan...");
158
+ const buildPlan = await generateBuildPlan(anthropic, prd, options.designProfile);
159
+ await fs.writeJson(path.join(projectPath, ".codebakers", "build-plan.json"), buildPlan, { spaces: 2 });
160
+ spinner2.stop("Build plan generated");
161
+ console.log(chalk.bold("\n\u{1F3D7}\uFE0F Build Plan:\n"));
162
+ buildPlan.phases.forEach((phase, i) => {
163
+ console.log(chalk.cyan(`Phase ${i + 1}: ${phase.name}`));
164
+ phase.tasks.forEach((task) => {
165
+ console.log(chalk.dim(` \u2022 ${task}`));
166
+ });
167
+ });
168
+ console.log("");
169
+ const startBuild = await p.confirm({
170
+ message: "Start building?",
171
+ initialValue: true
172
+ });
173
+ if (!startBuild || p.isCancel(startBuild)) {
174
+ p.log.info(`Build plan saved to ${prd.name}/.codebakers/build-plan.json`);
175
+ p.log.info(`Run \`cd ${prd.name} && codebakers code\` to continue building.`);
176
+ return;
177
+ }
178
+ for (let i = 0; i < buildPlan.phases.length; i++) {
179
+ const phase = buildPlan.phases[i];
180
+ console.log(chalk.bold(`
181
+ \u{1F4E6} Phase ${i + 1}: ${phase.name}
182
+ `));
183
+ for (const task of phase.tasks) {
184
+ spinner2.start(task);
185
+ try {
186
+ await executeTask(anthropic, projectPath, task, prd, options.designProfile);
187
+ spinner2.stop(`\u2713 ${task}`);
188
+ } catch (error) {
189
+ spinner2.stop(`\u2717 ${task}`);
190
+ p.log.error(error instanceof Error ? error.message : "Task failed");
191
+ const continueBuilding = await p.confirm({
192
+ message: "Continue with next task?",
193
+ initialValue: true
194
+ });
195
+ if (!continueBuilding || p.isCancel(continueBuilding)) {
196
+ p.log.info("Build paused. Run `codebakers code` to continue.");
197
+ return;
198
+ }
199
+ }
200
+ }
201
+ }
202
+ if (options.createInfra) {
203
+ console.log(chalk.bold("\n\u{1F680} Setting up infrastructure...\n"));
204
+ spinner2.start("Creating GitHub repository...");
205
+ spinner2.stop("GitHub repository created");
206
+ spinner2.start("Creating Supabase project...");
207
+ spinner2.stop("Supabase project created");
208
+ spinner2.start("Creating Vercel project...");
209
+ spinner2.stop("Vercel project created");
210
+ spinner2.start("Deploying...");
211
+ spinner2.stop("Deployed!");
212
+ }
213
+ p.outro(chalk.green(`
214
+ \u2713 Project built from PRD!
215
+
216
+ ${chalk.bold("Your project:")}
217
+ ${chalk.cyan(`cd ${prd.name}`)}
218
+ ${chalk.cyan("npm run dev")}
219
+
220
+ ${chalk.bold("Continue building:")}
221
+ ${chalk.cyan("codebakers code")} \u2014 AI agent
222
+ ${chalk.cyan("codebakers check")} \u2014 Verify patterns
223
+ ${chalk.cyan("codebakers deploy")} \u2014 Deploy changes
224
+ `));
225
+ }
226
+ async function generateBuildPlan(anthropic, prd, designProfile) {
227
+ const response = await anthropic.messages.create({
228
+ model: "claude-sonnet-4-20250514",
229
+ max_tokens: 4096,
230
+ messages: [{
231
+ role: "user",
232
+ content: `Create a build plan for this project. Return JSON only.
233
+
234
+ Project: ${prd.name}
235
+ Description: ${prd.description}
236
+ Features: ${prd.features.join(", ")}
237
+ Pages: ${prd.pages.join(", ")}
238
+ Database tables: ${prd.database.join(", ")}
239
+ Integrations: ${prd.integrations.join(", ")}
240
+ Design: ${designProfile}
241
+
242
+ Return this structure:
243
+ {
244
+ "phases": [
245
+ {
246
+ "name": "Phase name",
247
+ "tasks": ["task 1", "task 2", ...]
248
+ }
249
+ ]
250
+ }
251
+
252
+ Phases should be:
253
+ 1. Setup (package.json, config, base files)
254
+ 2. Database (schema, migrations, types)
255
+ 3. Auth (if needed)
256
+ 4. Core Features (main functionality)
257
+ 5. UI/Pages (frontend)
258
+ 6. Integrations (third-party services)
259
+ 7. Polish (loading states, error handling, empty states)
260
+
261
+ Keep tasks specific and actionable.`
262
+ }]
263
+ });
264
+ const text2 = response.content[0].type === "text" ? response.content[0].text : "";
265
+ const jsonMatch = text2.match(/\{[\s\S]*\}/);
266
+ if (!jsonMatch) {
267
+ throw new Error("Could not generate build plan");
268
+ }
269
+ return JSON.parse(jsonMatch[0]);
270
+ }
271
+ async function executeTask(anthropic, projectPath, task, prd, designProfile) {
272
+ const response = await anthropic.messages.create({
273
+ model: "claude-sonnet-4-20250514",
274
+ max_tokens: 8192,
275
+ messages: [{
276
+ role: "user",
277
+ content: `Execute this task for the project.
278
+
279
+ Project: ${prd.name}
280
+ Task: ${task}
281
+ Design Profile: ${designProfile}
282
+
283
+ Context:
284
+ - Features: ${prd.features.join(", ")}
285
+ - Database: ${prd.database.join(", ")}
286
+
287
+ Output files in this format:
288
+
289
+ <<<FILE: path/to/file.ts>>>
290
+ file content here
291
+ <<<END_FILE>>>
292
+
293
+ <<<FILE: another/file.tsx>>>
294
+ file content here
295
+ <<<END_FILE>>>
296
+
297
+ Follow these rules:
298
+ - Use TypeScript
299
+ - Use Next.js App Router
300
+ - Use Tailwind CSS
301
+ - Use shadcn/ui components
302
+ - Every button needs onClick handler
303
+ - Every form needs Zod validation
304
+ - Every async operation needs loading/error states
305
+ - Every list needs empty state
306
+ - No generic gradient heroes
307
+ - No icon spam
308
+ - Generous spacing (py-16 or larger for sections)
309
+
310
+ Generate ALL files needed for this task.`
311
+ }]
312
+ });
313
+ const text2 = response.content[0].type === "text" ? response.content[0].text : "";
314
+ const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
315
+ let match;
316
+ while ((match = fileRegex.exec(text2)) !== null) {
317
+ const filePath = path.join(projectPath, match[1].trim());
318
+ const content = match[2].trim();
319
+ await fs.ensureDir(path.dirname(filePath));
320
+ await fs.writeFile(filePath, content);
321
+ }
322
+ }
323
+
324
+ export {
325
+ prdCommand
326
+ };