@codebakers/cli 1.1.5 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.js +218 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +772 -0
- package/dist/commands/install-hook.d.ts +12 -0
- package/dist/commands/install-hook.js +193 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +81 -0
- package/dist/commands/login.d.ts +1 -0
- package/dist/commands/login.js +54 -0
- package/dist/commands/mcp-config.d.ts +6 -0
- package/dist/commands/mcp-config.js +209 -0
- package/dist/commands/serve.d.ts +1 -0
- package/dist/commands/serve.js +26 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +92 -0
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +49 -0
- package/dist/commands/uninstall.d.ts +1 -0
- package/dist/commands/uninstall.js +50 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +33 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +71 -1075
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.js +544 -0
- package/package.json +17 -38
- package/src/commands/doctor.ts +231 -0
- package/src/commands/init.ts +827 -0
- package/src/commands/install-hook.ts +207 -0
- package/src/commands/install.ts +94 -0
- package/src/commands/login.ts +56 -0
- package/src/commands/mcp-config.ts +235 -0
- package/src/commands/serve.ts +23 -0
- package/src/commands/setup.ts +104 -0
- package/src/commands/status.ts +48 -0
- package/src/commands/uninstall.ts +49 -0
- package/src/config.ts +34 -0
- package/src/index.ts +87 -0
- package/src/mcp/server.ts +617 -0
- package/tsconfig.json +16 -0
- package/README.md +0 -89
- package/dist/chunk-7CKLRE2H.js +0 -36
- package/dist/config-R2H6JKGW.js +0 -16
package/dist/index.js
CHANGED
|
@@ -1,1077 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const data = await response.json();
|
|
76
|
-
return { valid: false, error: data.error || `HTTP ${response.status}` };
|
|
77
|
-
} catch {
|
|
78
|
-
return { valid: false, error: `HTTP ${response.status}` };
|
|
79
|
-
}
|
|
80
|
-
} catch (error) {
|
|
81
|
-
const message = error instanceof Error ? error.message : "Network error";
|
|
82
|
-
return { valid: false, error: message };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function listPatterns() {
|
|
86
|
-
return makeRequest("/api/patterns");
|
|
87
|
-
}
|
|
88
|
-
async function fetchPatterns(patterns) {
|
|
89
|
-
return makeRequest("/api/patterns", {
|
|
90
|
-
method: "POST",
|
|
91
|
-
body: JSON.stringify({ patterns })
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
async function checkCliVersion() {
|
|
95
|
-
try {
|
|
96
|
-
const baseUrl = getApiUrl();
|
|
97
|
-
const response = await fetchFn(`${baseUrl}/api/cli/version`);
|
|
98
|
-
if (!response.ok) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
return response.json();
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/commands/setup.ts
|
|
108
|
-
import { createRequire } from "module";
|
|
109
|
-
var require2 = createRequire(import.meta.url);
|
|
110
|
-
var packageJson = require2("../../package.json");
|
|
111
|
-
var CURRENT_VERSION = packageJson.version;
|
|
112
|
-
var CODEBAKERS_MARKER = "# CODEBAKERS SMART ROUTER";
|
|
113
|
-
var USER_CONTENT_SEPARATOR = `
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
# USER CUSTOM INSTRUCTIONS
|
|
118
|
-
|
|
119
|
-
The following instructions were preserved from your original CLAUDE.md file.
|
|
120
|
-
CodeBakers patterns will be applied first, then these instructions.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
`;
|
|
125
|
-
var IDE_CONFIGS = {
|
|
126
|
-
cursor: {
|
|
127
|
-
file: ".cursor/mcp.json",
|
|
128
|
-
description: "Cursor AI IDE"
|
|
129
|
-
},
|
|
130
|
-
"claude-code": {
|
|
131
|
-
file: ".mcp.json",
|
|
132
|
-
description: "Claude Code"
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
function setupCommand(program2) {
|
|
136
|
-
program2.command("setup").description("Configure CodeBakers with your API key").option("--ide <type>", "IDE to configure (cursor, windsurf, aider, claude-code)").option("--key <apiKey>", "API key (or enter interactively)").option("--force", "Overwrite existing configuration").action(async (options) => {
|
|
137
|
-
console.log(chalk.bold("\n\u{1F36A} CodeBakers Setup\n"));
|
|
138
|
-
let apiKey = options.key;
|
|
139
|
-
const existingKey = getApiKey();
|
|
140
|
-
if (existingKey && !options.key) {
|
|
141
|
-
const { useExisting } = await inquirer.prompt([
|
|
142
|
-
{
|
|
143
|
-
type: "confirm",
|
|
144
|
-
name: "useExisting",
|
|
145
|
-
message: "API key already configured. Use existing key?",
|
|
146
|
-
default: true
|
|
147
|
-
}
|
|
148
|
-
]);
|
|
149
|
-
if (useExisting) {
|
|
150
|
-
apiKey = existingKey;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
if (!apiKey) {
|
|
154
|
-
const answers = await inquirer.prompt([
|
|
155
|
-
{
|
|
156
|
-
type: "password",
|
|
157
|
-
name: "apiKey",
|
|
158
|
-
message: "Enter your CodeBakers API key:",
|
|
159
|
-
mask: "*",
|
|
160
|
-
validate: (input) => {
|
|
161
|
-
if (!input.startsWith("cb_")) {
|
|
162
|
-
return "Invalid key format. API keys start with cb_";
|
|
163
|
-
}
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
]);
|
|
168
|
-
apiKey = answers.apiKey;
|
|
169
|
-
}
|
|
170
|
-
const spinner = ora("Validating API key...").start();
|
|
171
|
-
const validation = await validateApiKey(apiKey);
|
|
172
|
-
if (!validation.valid) {
|
|
173
|
-
spinner.fail("API key validation failed");
|
|
174
|
-
const errLower = validation.error?.toLowerCase() || "";
|
|
175
|
-
if (errLower.includes("invalid api key") || validation.error === "HTTP 401") {
|
|
176
|
-
console.log(chalk.red("\nInvalid API key."));
|
|
177
|
-
console.log(chalk.dim("Get your key at: https://codebakers.ai/dashboard"));
|
|
178
|
-
} else if (errLower.includes("enotfound") || errLower.includes("econnrefused") || errLower.includes("unable to connect")) {
|
|
179
|
-
console.log(chalk.red("\nCould not connect to CodeBakers API."));
|
|
180
|
-
console.log(chalk.dim("Check your internet connection and try again."));
|
|
181
|
-
console.log(chalk.dim(`Details: ${validation.error}`));
|
|
182
|
-
} else {
|
|
183
|
-
console.log(chalk.red(`
|
|
184
|
-
Validation failed: ${validation.error || "Unknown error"}`));
|
|
185
|
-
console.log(chalk.dim("Get your key at: https://codebakers.ai/dashboard"));
|
|
186
|
-
}
|
|
187
|
-
process.exit(1);
|
|
188
|
-
}
|
|
189
|
-
const userName = validation.userName;
|
|
190
|
-
spinner.succeed("API key validated");
|
|
191
|
-
setApiKey(apiKey);
|
|
192
|
-
console.log(chalk.green("\n\u2713 API key saved\n"));
|
|
193
|
-
let ide = options.ide;
|
|
194
|
-
if (!ide) {
|
|
195
|
-
const { selectedIde } = await inquirer.prompt([
|
|
196
|
-
{
|
|
197
|
-
type: "list",
|
|
198
|
-
name: "selectedIde",
|
|
199
|
-
message: "Which AI coding tool are you using?",
|
|
200
|
-
choices: [
|
|
201
|
-
{ name: "Cursor", value: "cursor" },
|
|
202
|
-
{ name: "Claude Code (VS Code extension or CLI)", value: "claude-code" },
|
|
203
|
-
{ name: "Skip for now", value: null }
|
|
204
|
-
]
|
|
205
|
-
}
|
|
206
|
-
]);
|
|
207
|
-
ide = selectedIde;
|
|
208
|
-
}
|
|
209
|
-
if (ide) {
|
|
210
|
-
await configureIDE(ide, options.force);
|
|
211
|
-
}
|
|
212
|
-
await installClaudeMd(apiKey, options.force);
|
|
213
|
-
const isExistingProject = detectExistingProject();
|
|
214
|
-
const firstName = userName?.split(" ")[0];
|
|
215
|
-
const greeting = firstName ? `${firstName}, you're` : "You're";
|
|
216
|
-
console.log(chalk.bold.green("\n\u{1F389} Setup complete!\n"));
|
|
217
|
-
if (isExistingProject) {
|
|
218
|
-
console.log(chalk.green(`\u2713 ${greeting} ready to upgrade this project!
|
|
219
|
-
`));
|
|
220
|
-
console.log(chalk.bold.yellow("\u{1F4CB} Existing Project Detected\n"));
|
|
221
|
-
console.log(chalk.white("We recommend starting with an audit to see how your code"));
|
|
222
|
-
console.log(chalk.white("compares to CodeBakers production standards.\n"));
|
|
223
|
-
console.log(chalk.bold("Recommended First Step:"));
|
|
224
|
-
console.log(chalk.cyan(" /audit\n"));
|
|
225
|
-
console.log(chalk.dim(" This will scan your codebase and generate a report showing:"));
|
|
226
|
-
console.log(chalk.dim(" \u2022 Security issues (SQL injection, XSS, etc.)"));
|
|
227
|
-
console.log(chalk.dim(" \u2022 Missing validation and error handling"));
|
|
228
|
-
console.log(chalk.dim(" \u2022 Performance opportunities"));
|
|
229
|
-
console.log(chalk.dim(" \u2022 Test coverage gaps"));
|
|
230
|
-
console.log(chalk.dim(" \u2022 Overall score out of 100\n"));
|
|
231
|
-
console.log(chalk.dim("After reviewing the report, you decide what to fix."));
|
|
232
|
-
console.log(chalk.dim("The AI will use CodeBakers patterns for any changes.\n"));
|
|
233
|
-
console.log(chalk.bold("Other Commands:\n"));
|
|
234
|
-
console.log(chalk.cyan(" /feature [idea]") + chalk.dim(" - Add new functionality"));
|
|
235
|
-
console.log(chalk.cyan(" /design [path]") + chalk.dim(" - Clone design from mockups"));
|
|
236
|
-
console.log(chalk.cyan(" /status") + chalk.dim(" - View project progress"));
|
|
237
|
-
console.log(chalk.cyan(" /commands") + chalk.dim(" - List all commands\n"));
|
|
238
|
-
} else {
|
|
239
|
-
console.log(chalk.green(`\u2713 ${greeting} ready to build!
|
|
240
|
-
`));
|
|
241
|
-
console.log(chalk.bold("6 Commands to Know:\n"));
|
|
242
|
-
console.log(chalk.cyan(" /build [idea]"));
|
|
243
|
-
console.log(chalk.dim(" Start a new project. AI asks questions, plans everything,"));
|
|
244
|
-
console.log(chalk.dim(" then builds phase by phase with tests.\n"));
|
|
245
|
-
console.log(chalk.cyan(" /feature [idea]"));
|
|
246
|
-
console.log(chalk.dim(" Add to an existing project. AI analyzes your codebase"));
|
|
247
|
-
console.log(chalk.dim(" and integrates the new feature properly.\n"));
|
|
248
|
-
console.log(chalk.cyan(" /design [path]"));
|
|
249
|
-
console.log(chalk.dim(" Clone a design pixel-perfect from mockups or websites."));
|
|
250
|
-
console.log(chalk.dim(' Example: /design ./mockups or /design "like Linear"\n'));
|
|
251
|
-
console.log(chalk.cyan(" /status"));
|
|
252
|
-
console.log(chalk.dim(" See project progress, what's built, what's next.\n"));
|
|
253
|
-
console.log(chalk.cyan(" /audit"));
|
|
254
|
-
console.log(chalk.dim(" Review code quality, security, and get a score.\n"));
|
|
255
|
-
console.log(chalk.cyan(" /commands"));
|
|
256
|
-
console.log(chalk.dim(" List all available commands.\n"));
|
|
257
|
-
console.log(chalk.bold("Examples:"));
|
|
258
|
-
console.log(chalk.white(" /build a project management tool for remote teams"));
|
|
259
|
-
console.log(chalk.white(" /design ./mockups\n"));
|
|
260
|
-
console.log(chalk.dim("The AI will ask discovery questions, create a PRD, and"));
|
|
261
|
-
console.log(chalk.dim("build your app with production patterns + tests.\n"));
|
|
262
|
-
}
|
|
263
|
-
checkForUpdates();
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
async function checkForUpdates() {
|
|
267
|
-
try {
|
|
268
|
-
const versionInfo = await checkCliVersion();
|
|
269
|
-
if (!versionInfo) return;
|
|
270
|
-
const currentParts = CURRENT_VERSION.split(".").map(Number);
|
|
271
|
-
const latestParts = versionInfo.latest.split(".").map(Number);
|
|
272
|
-
let needsUpdate = false;
|
|
273
|
-
for (let i = 0; i < 3; i++) {
|
|
274
|
-
if ((latestParts[i] || 0) > (currentParts[i] || 0)) {
|
|
275
|
-
needsUpdate = true;
|
|
276
|
-
break;
|
|
277
|
-
} else if ((latestParts[i] || 0) < (currentParts[i] || 0)) {
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
if (needsUpdate) {
|
|
282
|
-
console.log(chalk.yellow(`
|
|
283
|
-
\u{1F4E6} Update available: ${CURRENT_VERSION} \u2192 ${versionInfo.latest}`));
|
|
284
|
-
console.log(chalk.dim(` Run: ${versionInfo.updateCommand}
|
|
285
|
-
`));
|
|
286
|
-
}
|
|
287
|
-
} catch {
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
function detectExistingProject() {
|
|
291
|
-
const cwd = process.cwd();
|
|
292
|
-
const indicators = [
|
|
293
|
-
"package.json",
|
|
294
|
-
"src",
|
|
295
|
-
"app",
|
|
296
|
-
"pages",
|
|
297
|
-
"components",
|
|
298
|
-
"lib",
|
|
299
|
-
"tsconfig.json",
|
|
300
|
-
"next.config.js",
|
|
301
|
-
"next.config.mjs",
|
|
302
|
-
"vite.config.ts",
|
|
303
|
-
"tailwind.config.js",
|
|
304
|
-
"tailwind.config.ts"
|
|
305
|
-
];
|
|
306
|
-
let score = 0;
|
|
307
|
-
for (const indicator of indicators) {
|
|
308
|
-
if (existsSync(join(cwd, indicator))) {
|
|
309
|
-
score++;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return score >= 2;
|
|
313
|
-
}
|
|
314
|
-
async function configureIDE(ide, force = false) {
|
|
315
|
-
const config = IDE_CONFIGS[ide];
|
|
316
|
-
const spinner = ora(`Configuring ${config.description}...`).start();
|
|
317
|
-
try {
|
|
318
|
-
await configureMCP(ide, force, spinner);
|
|
319
|
-
} catch (error) {
|
|
320
|
-
spinner.fail("Failed to configure IDE");
|
|
321
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
322
|
-
console.log(chalk.red(`
|
|
323
|
-
Error: ${message}`));
|
|
324
|
-
process.exit(1);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
async function configureMCP(ide, force, spinner) {
|
|
328
|
-
const config = IDE_CONFIGS[ide];
|
|
329
|
-
const targetPath = ide === "cursor" ? join(process.cwd(), ".cursor", "mcp.json") : join(process.cwd(), ".mcp.json");
|
|
330
|
-
if (ide === "cursor") {
|
|
331
|
-
const targetDir = join(process.cwd(), ".cursor");
|
|
332
|
-
if (!existsSync(targetDir)) {
|
|
333
|
-
await mkdir(targetDir, { recursive: true });
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
let existingConfig = {};
|
|
337
|
-
if (existsSync(targetPath)) {
|
|
338
|
-
if (!force) {
|
|
339
|
-
spinner.stop();
|
|
340
|
-
const { merge } = await inquirer.prompt([
|
|
341
|
-
{
|
|
342
|
-
type: "confirm",
|
|
343
|
-
name: "merge",
|
|
344
|
-
message: `${config.file} exists. Add CodeBakers to existing config?`,
|
|
345
|
-
default: true
|
|
346
|
-
}
|
|
347
|
-
]);
|
|
348
|
-
if (!merge) {
|
|
349
|
-
console.log(chalk.yellow("\nSetup cancelled."));
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
spinner.start();
|
|
353
|
-
}
|
|
354
|
-
try {
|
|
355
|
-
const existing = await readFile(targetPath, "utf-8");
|
|
356
|
-
existingConfig = JSON.parse(existing);
|
|
357
|
-
} catch {
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
spinner.text = "Configuring MCP server...";
|
|
361
|
-
const mcpConfig = {
|
|
362
|
-
...existingConfig,
|
|
363
|
-
mcpServers: {
|
|
364
|
-
...existingConfig.mcpServers || {},
|
|
365
|
-
codebakers: {
|
|
366
|
-
command: "npx",
|
|
367
|
-
args: ["@codebakers/cli", "serve"]
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
await writeFile(targetPath, JSON.stringify(mcpConfig, null, 2), "utf-8");
|
|
372
|
-
spinner.succeed(`Configured MCP server in ${config.file}`);
|
|
373
|
-
console.log(chalk.green(`
|
|
374
|
-
\u2713 ${config.description} configured with MCP`));
|
|
375
|
-
console.log(chalk.dim("\nPatterns are fetched securely from the API on-demand."));
|
|
376
|
-
console.log(chalk.dim("No pattern files are stored locally.\n"));
|
|
377
|
-
if (ide === "cursor") {
|
|
378
|
-
console.log(chalk.yellow("Restart Cursor to activate the MCP server.\n"));
|
|
379
|
-
} else {
|
|
380
|
-
console.log(chalk.yellow("Restart your IDE to activate the MCP server.\n"));
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
async function fetchClaudeMdFromApi(apiKey) {
|
|
384
|
-
try {
|
|
385
|
-
const apiUrl = getApiUrl();
|
|
386
|
-
const response = await fetch(`${apiUrl}/api/cli/claude-md`, {
|
|
387
|
-
headers: {
|
|
388
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
389
|
-
"Content-Type": "application/json"
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
if (!response.ok) {
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
const data = await response.json();
|
|
396
|
-
return data.content || null;
|
|
397
|
-
} catch {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
async function installClaudeMd(apiKey, force) {
|
|
402
|
-
const spinner = ora("Setting up CLAUDE.md...").start();
|
|
403
|
-
const claudeMdPath = join(process.cwd(), "CLAUDE.md");
|
|
404
|
-
try {
|
|
405
|
-
spinner.text = "Fetching CLAUDE.md from CodeBakers...";
|
|
406
|
-
const codebakersContent = await fetchClaudeMdFromApi(apiKey);
|
|
407
|
-
if (!codebakersContent) {
|
|
408
|
-
spinner.warn("Could not fetch CLAUDE.md from API. Skipping...");
|
|
409
|
-
console.log(chalk.dim(" You can manually add CLAUDE.md later.\n"));
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (existsSync(claudeMdPath)) {
|
|
413
|
-
const existingContent = await readFile(claudeMdPath, "utf-8");
|
|
414
|
-
const isCodeBakersFile = existingContent.trim().startsWith(CODEBAKERS_MARKER);
|
|
415
|
-
if (isCodeBakersFile) {
|
|
416
|
-
await writeFile(claudeMdPath, codebakersContent, "utf-8");
|
|
417
|
-
spinner.succeed("Updated CLAUDE.md to latest version");
|
|
418
|
-
} else {
|
|
419
|
-
if (!force) {
|
|
420
|
-
spinner.stop();
|
|
421
|
-
console.log(chalk.cyan("\n \u2139\uFE0F Your custom instructions will be preserved at the end of the file."));
|
|
422
|
-
console.log(chalk.dim(" CodeBakers patterns load first, then your rules apply on top."));
|
|
423
|
-
console.log(chalk.dim(" Your original content remains intact and can still override patterns.\n"));
|
|
424
|
-
const { shouldMerge } = await inquirer.prompt([
|
|
425
|
-
{
|
|
426
|
-
type: "confirm",
|
|
427
|
-
name: "shouldMerge",
|
|
428
|
-
message: "Merge with CodeBakers?",
|
|
429
|
-
default: true
|
|
430
|
-
}
|
|
431
|
-
]);
|
|
432
|
-
if (!shouldMerge) {
|
|
433
|
-
console.log(chalk.yellow(" Skipping CLAUDE.md installation.\n"));
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
spinner.start("Merging CLAUDE.md...");
|
|
437
|
-
}
|
|
438
|
-
const mergedContent = codebakersContent + USER_CONTENT_SEPARATOR + existingContent;
|
|
439
|
-
await writeFile(claudeMdPath, mergedContent, "utf-8");
|
|
440
|
-
spinner.succeed("Merged CLAUDE.md (your custom instructions preserved at the end)");
|
|
441
|
-
}
|
|
442
|
-
} else {
|
|
443
|
-
await writeFile(claudeMdPath, codebakersContent, "utf-8");
|
|
444
|
-
spinner.succeed("Created CLAUDE.md");
|
|
445
|
-
}
|
|
446
|
-
console.log(chalk.green("\u2713 CLAUDE.md installed"));
|
|
447
|
-
console.log(chalk.dim(" This file tells your AI to use CodeBakers patterns.\n"));
|
|
448
|
-
} catch (error) {
|
|
449
|
-
spinner.fail("Failed to install CLAUDE.md");
|
|
450
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
451
|
-
console.log(chalk.dim(` Error: ${message}
|
|
452
|
-
`));
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// src/commands/serve.ts
|
|
457
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
458
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
459
|
-
import {
|
|
460
|
-
CallToolRequestSchema,
|
|
461
|
-
ListToolsRequestSchema,
|
|
462
|
-
ListResourcesRequestSchema,
|
|
463
|
-
ReadResourceRequestSchema
|
|
464
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
465
|
-
function serveCommand(program2) {
|
|
466
|
-
program2.command("serve").description("Run CodeBakers as an MCP server for Claude Code").action(async () => {
|
|
467
|
-
const apiKey = getApiKey();
|
|
468
|
-
if (!apiKey) {
|
|
469
|
-
console.error("No API key configured. Run: npx @codebakers/cli setup");
|
|
470
|
-
process.exit(1);
|
|
471
|
-
}
|
|
472
|
-
const server = new Server(
|
|
473
|
-
{
|
|
474
|
-
name: "codebakers",
|
|
475
|
-
version: "1.0.0"
|
|
476
|
-
},
|
|
477
|
-
{
|
|
478
|
-
capabilities: {
|
|
479
|
-
tools: {},
|
|
480
|
-
resources: {}
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
);
|
|
484
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
485
|
-
return {
|
|
486
|
-
tools: [
|
|
487
|
-
{
|
|
488
|
-
name: "get_pattern",
|
|
489
|
-
description: "Fetch a CodeBakers pattern module for production-ready code patterns. Available patterns: 00-core, 01-database, 02-auth, 03-api, 04-frontend, 05-payments, 06-integrations, 07-performance, 08-testing, 09-design, 10-generators, 11-realtime, 12-saas, 14-ai, and more.",
|
|
490
|
-
inputSchema: {
|
|
491
|
-
type: "object",
|
|
492
|
-
properties: {
|
|
493
|
-
patterns: {
|
|
494
|
-
type: "array",
|
|
495
|
-
items: { type: "string" },
|
|
496
|
-
description: 'Pattern names to fetch (e.g., ["00-core", "02-auth"]). Max 5 per request.'
|
|
497
|
-
}
|
|
498
|
-
},
|
|
499
|
-
required: ["patterns"]
|
|
500
|
-
}
|
|
501
|
-
},
|
|
502
|
-
{
|
|
503
|
-
name: "list_patterns",
|
|
504
|
-
description: "List all available CodeBakers pattern modules",
|
|
505
|
-
inputSchema: {
|
|
506
|
-
type: "object",
|
|
507
|
-
properties: {}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
]
|
|
511
|
-
};
|
|
512
|
-
});
|
|
513
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
514
|
-
try {
|
|
515
|
-
const result = await listPatterns();
|
|
516
|
-
return {
|
|
517
|
-
resources: result.patterns.map((p) => ({
|
|
518
|
-
uri: `codebakers://pattern/${p.name}`,
|
|
519
|
-
name: p.name,
|
|
520
|
-
description: `CodeBakers ${p.name} pattern module`,
|
|
521
|
-
mimeType: "text/markdown"
|
|
522
|
-
}))
|
|
523
|
-
};
|
|
524
|
-
} catch (error) {
|
|
525
|
-
return { resources: [] };
|
|
526
|
-
}
|
|
527
|
-
});
|
|
528
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
529
|
-
const uri = request.params.uri;
|
|
530
|
-
const match = uri.match(/^codebakers:\/\/pattern\/(.+)$/);
|
|
531
|
-
if (!match) {
|
|
532
|
-
throw new Error(`Invalid resource URI: ${uri}`);
|
|
533
|
-
}
|
|
534
|
-
const patternName = match[1];
|
|
535
|
-
try {
|
|
536
|
-
const result = await fetchPatterns([patternName]);
|
|
537
|
-
const content = result.patterns[patternName];
|
|
538
|
-
if (!content) {
|
|
539
|
-
throw new Error(`Pattern not found: ${patternName}`);
|
|
540
|
-
}
|
|
541
|
-
return {
|
|
542
|
-
contents: [
|
|
543
|
-
{
|
|
544
|
-
uri,
|
|
545
|
-
mimeType: "text/markdown",
|
|
546
|
-
text: content
|
|
547
|
-
}
|
|
548
|
-
]
|
|
549
|
-
};
|
|
550
|
-
} catch (error) {
|
|
551
|
-
if (error instanceof ApiError) {
|
|
552
|
-
throw new Error(`API Error: ${error.message}`);
|
|
553
|
-
}
|
|
554
|
-
throw error;
|
|
555
|
-
}
|
|
556
|
-
});
|
|
557
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
558
|
-
const { name, arguments: args } = request.params;
|
|
559
|
-
if (name === "list_patterns") {
|
|
560
|
-
try {
|
|
561
|
-
const result = await listPatterns();
|
|
562
|
-
return {
|
|
563
|
-
content: [
|
|
564
|
-
{
|
|
565
|
-
type: "text",
|
|
566
|
-
text: `Available CodeBakers patterns (${result.total}):
|
|
567
|
-
|
|
568
|
-
${result.patterns.map((p) => `- ${p.name}`).join("\n")}
|
|
569
|
-
|
|
570
|
-
Use get_pattern to fetch specific patterns.`
|
|
571
|
-
}
|
|
572
|
-
]
|
|
573
|
-
};
|
|
574
|
-
} catch (error) {
|
|
575
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
576
|
-
return {
|
|
577
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
578
|
-
isError: true
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
if (name === "get_pattern") {
|
|
583
|
-
const patterns = args?.patterns || [];
|
|
584
|
-
if (patterns.length === 0) {
|
|
585
|
-
return {
|
|
586
|
-
content: [
|
|
587
|
-
{ type: "text", text: "Error: No patterns specified" }
|
|
588
|
-
],
|
|
589
|
-
isError: true
|
|
590
|
-
};
|
|
591
|
-
}
|
|
592
|
-
if (patterns.length > 5) {
|
|
593
|
-
return {
|
|
594
|
-
content: [
|
|
595
|
-
{ type: "text", text: "Error: Maximum 5 patterns per request" }
|
|
596
|
-
],
|
|
597
|
-
isError: true
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
try {
|
|
601
|
-
const result = await fetchPatterns(patterns);
|
|
602
|
-
const content = Object.entries(result.patterns).map(([name2, text]) => `# Pattern: ${name2}
|
|
603
|
-
|
|
604
|
-
${text}`).join("\n\n---\n\n");
|
|
605
|
-
return {
|
|
606
|
-
content: [
|
|
607
|
-
{
|
|
608
|
-
type: "text",
|
|
609
|
-
text: content || `No patterns found for: ${patterns.join(", ")}`
|
|
610
|
-
}
|
|
611
|
-
]
|
|
612
|
-
};
|
|
613
|
-
} catch (error) {
|
|
614
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
615
|
-
if (error instanceof ApiError) {
|
|
616
|
-
if (error.statusCode === 401) {
|
|
617
|
-
return {
|
|
618
|
-
content: [
|
|
619
|
-
{
|
|
620
|
-
type: "text",
|
|
621
|
-
text: "Error: Invalid API key. Run: npx @codebakers/cli setup"
|
|
622
|
-
}
|
|
623
|
-
],
|
|
624
|
-
isError: true
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
if (error.statusCode === 402) {
|
|
628
|
-
return {
|
|
629
|
-
content: [
|
|
630
|
-
{
|
|
631
|
-
type: "text",
|
|
632
|
-
text: `Error: ${message}. Upgrade at: https://codebakers.ai/billing`
|
|
633
|
-
}
|
|
634
|
-
],
|
|
635
|
-
isError: true
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return {
|
|
640
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
641
|
-
isError: true
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return {
|
|
646
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
647
|
-
isError: true
|
|
648
|
-
};
|
|
649
|
-
});
|
|
650
|
-
const transport = new StdioServerTransport();
|
|
651
|
-
await server.connect(transport);
|
|
652
|
-
});
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// src/commands/validate.ts
|
|
656
|
-
import chalk2 from "chalk";
|
|
657
|
-
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
658
|
-
import { join as join2 } from "path";
|
|
659
|
-
function validateStateFile(projectRoot) {
|
|
660
|
-
const result = { valid: true, errors: [], warnings: [] };
|
|
661
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
662
|
-
if (!existsSync2(statePath)) {
|
|
663
|
-
result.warnings.push("No .codebakers.json found");
|
|
664
|
-
return result;
|
|
665
|
-
}
|
|
666
|
-
try {
|
|
667
|
-
const content = readFileSync(statePath, "utf-8");
|
|
668
|
-
JSON.parse(content);
|
|
669
|
-
} catch (error) {
|
|
670
|
-
result.valid = false;
|
|
671
|
-
result.errors.push(`Invalid JSON in .codebakers.json: ${error}`);
|
|
672
|
-
}
|
|
673
|
-
return result;
|
|
674
|
-
}
|
|
675
|
-
function validateContextSummaries(projectRoot) {
|
|
676
|
-
const result = { valid: true, errors: [], warnings: [] };
|
|
677
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
678
|
-
if (!existsSync2(statePath)) return result;
|
|
679
|
-
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
680
|
-
if (!state.build?.fileToTaskMap) return result;
|
|
681
|
-
const trackedFiles = Object.keys(state.build.fileToTaskMap);
|
|
682
|
-
const contextFiles = state.context ? Object.keys(state.context) : [];
|
|
683
|
-
const missingContext = [];
|
|
684
|
-
for (const file of trackedFiles) {
|
|
685
|
-
if (file.endsWith(".json") || file.endsWith(".config.ts") || file.endsWith(".config.js")) {
|
|
686
|
-
continue;
|
|
687
|
-
}
|
|
688
|
-
if (file.startsWith("src/") && !contextFiles.includes(file)) {
|
|
689
|
-
missingContext.push(file);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
if (missingContext.length > 0) {
|
|
693
|
-
result.warnings.push(`Missing context summaries: ${missingContext.length} files`);
|
|
694
|
-
}
|
|
695
|
-
return result;
|
|
696
|
-
}
|
|
697
|
-
function validateContracts(projectRoot) {
|
|
698
|
-
const result = { valid: true, errors: [], warnings: [] };
|
|
699
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
700
|
-
if (!existsSync2(statePath)) return result;
|
|
701
|
-
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
702
|
-
if (!state.contracts) return result;
|
|
703
|
-
for (const [featureName, contract] of Object.entries(state.contracts)) {
|
|
704
|
-
if (!contract.expects) continue;
|
|
705
|
-
for (const depName of Object.keys(contract.expects)) {
|
|
706
|
-
if (depName === "environment" || depName === "database") continue;
|
|
707
|
-
if (typeof contract.expects[depName] === "object" && !state.contracts[depName]) {
|
|
708
|
-
result.warnings.push(`'${featureName}' expects '${depName}' but no contract found`);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return result;
|
|
713
|
-
}
|
|
714
|
-
function validateBuildState(projectRoot) {
|
|
715
|
-
const result = { valid: true, errors: [], warnings: [] };
|
|
716
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
717
|
-
if (!existsSync2(statePath)) return result;
|
|
718
|
-
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
719
|
-
if (!state.build) return result;
|
|
720
|
-
let inProgressCount = 0;
|
|
721
|
-
for (const phase of state.build.phases || []) {
|
|
722
|
-
for (const task of phase.tasks || []) {
|
|
723
|
-
if (task.status === "in_progress") {
|
|
724
|
-
inProgressCount++;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (inProgressCount > 1) {
|
|
729
|
-
result.warnings.push(`${inProgressCount} tasks marked in_progress (should be 1)`);
|
|
730
|
-
}
|
|
731
|
-
return result;
|
|
732
|
-
}
|
|
733
|
-
function validateFileExistence(projectRoot) {
|
|
734
|
-
const result = { valid: true, errors: [], warnings: [] };
|
|
735
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
736
|
-
if (!existsSync2(statePath)) return result;
|
|
737
|
-
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
738
|
-
if (!state.build?.fileToTaskMap) return result;
|
|
739
|
-
const missingFiles = [];
|
|
740
|
-
for (const file of Object.keys(state.build.fileToTaskMap)) {
|
|
741
|
-
const filePath = join2(projectRoot, file);
|
|
742
|
-
if (!existsSync2(filePath)) {
|
|
743
|
-
missingFiles.push(file);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
if (missingFiles.length > 0) {
|
|
747
|
-
result.errors.push(`${missingFiles.length} tracked files don't exist`);
|
|
748
|
-
result.valid = false;
|
|
749
|
-
}
|
|
750
|
-
return result;
|
|
751
|
-
}
|
|
752
|
-
function showBuildStatus(projectRoot) {
|
|
753
|
-
const statePath = join2(projectRoot, ".codebakers.json");
|
|
754
|
-
if (!existsSync2(statePath)) {
|
|
755
|
-
console.log(chalk2.dim("\nNo active build found."));
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
const state = JSON.parse(readFileSync(statePath, "utf-8"));
|
|
759
|
-
if (!state.build) {
|
|
760
|
-
console.log(chalk2.dim("\nNo active build found."));
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
console.log(chalk2.bold(`
|
|
764
|
-
\u{1F4CA} Build: ${state.build.projectName || "Unnamed Project"}`));
|
|
765
|
-
console.log("");
|
|
766
|
-
for (const phase of state.build.phases || []) {
|
|
767
|
-
const tasks = phase.tasks || [];
|
|
768
|
-
const completed = tasks.filter((t) => t.status === "completed").length;
|
|
769
|
-
const total = tasks.length;
|
|
770
|
-
const pct = total > 0 ? Math.round(completed / total * 100) : 0;
|
|
771
|
-
const bar = "\u2588".repeat(Math.round(pct / 10)) + "\u2591".repeat(10 - Math.round(pct / 10));
|
|
772
|
-
let status = "";
|
|
773
|
-
if (phase.status === "complete") {
|
|
774
|
-
status = chalk2.green("\u2705");
|
|
775
|
-
} else if (phase.status === "in_progress") {
|
|
776
|
-
status = chalk2.yellow("\u{1F504}");
|
|
777
|
-
} else {
|
|
778
|
-
status = chalk2.dim("\u23F3");
|
|
779
|
-
}
|
|
780
|
-
console.log(`${status} Phase ${phase.id}: ${phase.name} [${bar}] ${pct}%`);
|
|
781
|
-
}
|
|
782
|
-
if (state.build.stats) {
|
|
783
|
-
console.log("");
|
|
784
|
-
console.log(chalk2.dim(`Tasks: ${state.build.stats.completedTasks}/${state.build.stats.totalTasks} completed`));
|
|
785
|
-
}
|
|
786
|
-
if (state.build.currentTaskId) {
|
|
787
|
-
for (const phase of state.build.phases || []) {
|
|
788
|
-
const task = phase.tasks?.find((t) => t.id === state.build.currentTaskId);
|
|
789
|
-
if (task) {
|
|
790
|
-
console.log("");
|
|
791
|
-
console.log(chalk2.bold("Current Task:"));
|
|
792
|
-
console.log(` ${task.id}: ${task.title}`);
|
|
793
|
-
break;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
if (state.context) {
|
|
798
|
-
const contextCount = Object.keys(state.context).length;
|
|
799
|
-
console.log("");
|
|
800
|
-
console.log(chalk2.dim(`Context summaries: ${contextCount} files`));
|
|
801
|
-
}
|
|
802
|
-
if (state.contracts) {
|
|
803
|
-
const contractCount = Object.keys(state.contracts).length;
|
|
804
|
-
console.log(chalk2.dim(`Feature contracts: ${contractCount} defined`));
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
function validateCommand(program2) {
|
|
808
|
-
const cmd = program2.command("validate").description("Validate architectural coherence of the project").option("-v, --verbose", "Show detailed output").option("--status", "Show build status summary").action(async (options) => {
|
|
809
|
-
const projectRoot = process.cwd();
|
|
810
|
-
console.log(chalk2.bold("\n\u{1F50D} CodeBakers Coherence Validator\n"));
|
|
811
|
-
if (options.status) {
|
|
812
|
-
showBuildStatus(projectRoot);
|
|
813
|
-
console.log("");
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
const validators = [
|
|
817
|
-
{ name: "State File", fn: validateStateFile },
|
|
818
|
-
{ name: "Context Summaries", fn: validateContextSummaries },
|
|
819
|
-
{ name: "Contracts", fn: validateContracts },
|
|
820
|
-
{ name: "Build State", fn: validateBuildState },
|
|
821
|
-
{ name: "File Existence", fn: validateFileExistence }
|
|
822
|
-
];
|
|
823
|
-
let hasErrors = false;
|
|
824
|
-
let hasWarnings = false;
|
|
825
|
-
for (const { name, fn } of validators) {
|
|
826
|
-
const result = fn(projectRoot);
|
|
827
|
-
if (result.errors.length > 0) {
|
|
828
|
-
hasErrors = true;
|
|
829
|
-
console.log(chalk2.red(`\u274C ${name}:`));
|
|
830
|
-
for (const error of result.errors) {
|
|
831
|
-
console.log(chalk2.red(` ${error}`));
|
|
832
|
-
}
|
|
833
|
-
} else if (result.warnings.length > 0) {
|
|
834
|
-
hasWarnings = true;
|
|
835
|
-
console.log(chalk2.yellow(`\u26A0\uFE0F ${name}:`));
|
|
836
|
-
for (const warning of result.warnings) {
|
|
837
|
-
console.log(chalk2.yellow(` ${warning}`));
|
|
838
|
-
}
|
|
839
|
-
} else {
|
|
840
|
-
console.log(chalk2.green(`\u2705 ${name}`));
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
console.log("");
|
|
844
|
-
if (hasErrors) {
|
|
845
|
-
console.log(chalk2.red("Validation FAILED"));
|
|
846
|
-
process.exit(1);
|
|
847
|
-
} else if (hasWarnings) {
|
|
848
|
-
console.log(chalk2.yellow("Validation passed with warnings"));
|
|
849
|
-
} else {
|
|
850
|
-
console.log(chalk2.green("All validations passed"));
|
|
851
|
-
}
|
|
852
|
-
showBuildStatus(projectRoot);
|
|
853
|
-
console.log("");
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
// src/commands/summarize.ts
|
|
858
|
-
import chalk3 from "chalk";
|
|
859
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
860
|
-
import { join as join3, relative, extname } from "path";
|
|
861
|
-
function detectFileType(filePath) {
|
|
862
|
-
const ext = extname(filePath);
|
|
863
|
-
const name = filePath.toLowerCase();
|
|
864
|
-
if (name.includes("schema") || name.includes("/db/")) return "schema";
|
|
865
|
-
if (name.includes("/api/") || name.includes("route.ts")) return "api";
|
|
866
|
-
if (name.includes("middleware")) return "middleware";
|
|
867
|
-
if (name.includes("/components/")) return "component";
|
|
868
|
-
if (name.includes("/hooks/") || name.startsWith("use")) return "hook";
|
|
869
|
-
if (name.includes("/lib/") || name.includes("/utils/")) return "utility";
|
|
870
|
-
if (name.includes(".test.") || name.includes(".spec.")) return "test";
|
|
871
|
-
return "unknown";
|
|
872
|
-
}
|
|
873
|
-
function generateSummaryTemplate(filePath, content) {
|
|
874
|
-
const fileType = detectFileType(filePath);
|
|
875
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
876
|
-
const exportMatches = content.match(/export\s+(const|function|class|type|interface|enum)\s+(\w+)/g) || [];
|
|
877
|
-
const exports = exportMatches.map((m) => {
|
|
878
|
-
const match = m.match(/export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/);
|
|
879
|
-
return match ? match[1] : "";
|
|
880
|
-
}).filter(Boolean);
|
|
881
|
-
const base = {
|
|
882
|
-
purpose: `[TODO: Describe what this ${fileType} does]`,
|
|
883
|
-
exports: exports.length > 0 ? exports : void 0,
|
|
884
|
-
createdAt: now
|
|
885
|
-
};
|
|
886
|
-
switch (fileType) {
|
|
887
|
-
case "schema":
|
|
888
|
-
return {
|
|
889
|
-
...base,
|
|
890
|
-
purpose: "[TODO: Describe this database schema]",
|
|
891
|
-
schema: {},
|
|
892
|
-
relations: []
|
|
893
|
-
};
|
|
894
|
-
case "api":
|
|
895
|
-
return {
|
|
896
|
-
...base,
|
|
897
|
-
purpose: "[TODO: Describe this API endpoint]",
|
|
898
|
-
method: content.includes("POST") ? "POST" : content.includes("PUT") ? "PUT" : content.includes("DELETE") ? "DELETE" : "GET",
|
|
899
|
-
input: {},
|
|
900
|
-
output: {},
|
|
901
|
-
sideEffects: []
|
|
902
|
-
};
|
|
903
|
-
case "middleware":
|
|
904
|
-
return {
|
|
905
|
-
...base,
|
|
906
|
-
purpose: "[TODO: Describe this middleware]",
|
|
907
|
-
behavior: {},
|
|
908
|
-
protectedRoutes: [],
|
|
909
|
-
publicRoutes: []
|
|
910
|
-
};
|
|
911
|
-
case "component":
|
|
912
|
-
return {
|
|
913
|
-
...base,
|
|
914
|
-
purpose: "[TODO: Describe this component]",
|
|
915
|
-
props: [],
|
|
916
|
-
state: [],
|
|
917
|
-
events: []
|
|
918
|
-
};
|
|
919
|
-
case "hook":
|
|
920
|
-
return {
|
|
921
|
-
...base,
|
|
922
|
-
purpose: "[TODO: Describe this hook]",
|
|
923
|
-
params: [],
|
|
924
|
-
returns: "[TODO]",
|
|
925
|
-
sideEffects: []
|
|
926
|
-
};
|
|
927
|
-
case "utility":
|
|
928
|
-
return {
|
|
929
|
-
...base,
|
|
930
|
-
purpose: "[TODO: Describe this utility]",
|
|
931
|
-
dependencies: [],
|
|
932
|
-
patterns: {}
|
|
933
|
-
};
|
|
934
|
-
default:
|
|
935
|
-
return base;
|
|
936
|
-
}
|
|
937
|
-
}
|
|
938
|
-
function ensureStateFile(projectRoot) {
|
|
939
|
-
const statePath = join3(projectRoot, ".codebakers.json");
|
|
940
|
-
if (existsSync3(statePath)) {
|
|
941
|
-
return JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
942
|
-
}
|
|
943
|
-
const initial = {
|
|
944
|
-
version: "2.0",
|
|
945
|
-
context: {}
|
|
946
|
-
};
|
|
947
|
-
writeFileSync(statePath, JSON.stringify(initial, null, 2));
|
|
948
|
-
return initial;
|
|
949
|
-
}
|
|
950
|
-
function summarizeCommand(program2) {
|
|
951
|
-
program2.command("summarize [file]").description("Generate or update context summary for a file").option("-a, --all", "Generate summaries for all tracked files").option("--show", "Show existing summary without modifying").action(async (file, options) => {
|
|
952
|
-
const projectRoot = process.cwd();
|
|
953
|
-
const statePath = join3(projectRoot, ".codebakers.json");
|
|
954
|
-
console.log(chalk3.bold("\n\u{1F4DD} CodeBakers Context Summarizer\n"));
|
|
955
|
-
const state = ensureStateFile(projectRoot);
|
|
956
|
-
if (!state.context) {
|
|
957
|
-
state.context = {};
|
|
958
|
-
}
|
|
959
|
-
if (options.all) {
|
|
960
|
-
if (!state.build?.fileToTaskMap) {
|
|
961
|
-
console.log(chalk3.yellow("No tracked files found in build."));
|
|
962
|
-
console.log(chalk3.dim("Run /build command first to start tracking files."));
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
const files = Object.keys(state.build.fileToTaskMap);
|
|
966
|
-
let generated = 0;
|
|
967
|
-
let skipped = 0;
|
|
968
|
-
for (const f of files) {
|
|
969
|
-
if (f.endsWith(".json") || f.endsWith(".config.ts")) {
|
|
970
|
-
skipped++;
|
|
971
|
-
continue;
|
|
972
|
-
}
|
|
973
|
-
const filePath = join3(projectRoot, f);
|
|
974
|
-
if (!existsSync3(filePath)) {
|
|
975
|
-
console.log(chalk3.yellow(`\u26A0\uFE0F ${f} - file not found`));
|
|
976
|
-
continue;
|
|
977
|
-
}
|
|
978
|
-
if (state.context[f]) {
|
|
979
|
-
console.log(chalk3.dim(` ${f} - already has summary`));
|
|
980
|
-
skipped++;
|
|
981
|
-
continue;
|
|
982
|
-
}
|
|
983
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
984
|
-
const summary = generateSummaryTemplate(f, content);
|
|
985
|
-
state.context[f] = summary;
|
|
986
|
-
generated++;
|
|
987
|
-
console.log(chalk3.green(`\u2705 ${f}`));
|
|
988
|
-
}
|
|
989
|
-
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
990
|
-
console.log("");
|
|
991
|
-
console.log(chalk3.bold(`Generated: ${generated} | Skipped: ${skipped}`));
|
|
992
|
-
console.log(chalk3.dim("\nEdit .codebakers.json to fill in [TODO] placeholders."));
|
|
993
|
-
} else if (file) {
|
|
994
|
-
const relativePath = relative(projectRoot, join3(projectRoot, file));
|
|
995
|
-
const filePath = join3(projectRoot, file);
|
|
996
|
-
if (!existsSync3(filePath)) {
|
|
997
|
-
console.log(chalk3.red(`File not found: ${file}`));
|
|
998
|
-
process.exit(1);
|
|
999
|
-
}
|
|
1000
|
-
if (options.show && state.context[relativePath]) {
|
|
1001
|
-
console.log(chalk3.bold(`Summary for ${relativePath}:
|
|
1002
|
-
`));
|
|
1003
|
-
console.log(JSON.stringify(state.context[relativePath], null, 2));
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
const content = readFileSync2(filePath, "utf-8");
|
|
1007
|
-
const summary = generateSummaryTemplate(relativePath, content);
|
|
1008
|
-
if (state.context[relativePath]) {
|
|
1009
|
-
console.log(chalk3.yellow(`Updating existing summary for ${relativePath}`));
|
|
1010
|
-
state.context[relativePath] = {
|
|
1011
|
-
...summary,
|
|
1012
|
-
...state.context[relativePath],
|
|
1013
|
-
exports: summary.exports
|
|
1014
|
-
// Always update exports
|
|
1015
|
-
};
|
|
1016
|
-
} else {
|
|
1017
|
-
state.context[relativePath] = summary;
|
|
1018
|
-
console.log(chalk3.green(`Created summary for ${relativePath}`));
|
|
1019
|
-
}
|
|
1020
|
-
writeFileSync(statePath, JSON.stringify(state, null, 2));
|
|
1021
|
-
console.log("");
|
|
1022
|
-
console.log(chalk3.bold("Summary:"));
|
|
1023
|
-
console.log(JSON.stringify(state.context[relativePath], null, 2));
|
|
1024
|
-
console.log("");
|
|
1025
|
-
console.log(chalk3.dim("Edit .codebakers.json to fill in [TODO] placeholders."));
|
|
1026
|
-
} else {
|
|
1027
|
-
console.log("Usage:");
|
|
1028
|
-
console.log(" codebakers summarize <file> Generate summary for a specific file");
|
|
1029
|
-
console.log(" codebakers summarize --all Generate summaries for all tracked files");
|
|
1030
|
-
console.log(" codebakers summarize <file> --show Show existing summary");
|
|
1031
|
-
console.log("");
|
|
1032
|
-
const contextCount = Object.keys(state.context).length;
|
|
1033
|
-
const todoCount = Object.values(state.context).filter(
|
|
1034
|
-
(c) => JSON.stringify(c).includes("[TODO]")
|
|
1035
|
-
).length;
|
|
1036
|
-
console.log(chalk3.bold("Current Status:"));
|
|
1037
|
-
console.log(` Summaries: ${contextCount}`);
|
|
1038
|
-
if (todoCount > 0) {
|
|
1039
|
-
console.log(chalk3.yellow(` Need attention: ${todoCount} with [TODO] placeholders`));
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
// src/index.ts
|
|
1046
|
-
var program = new Command();
|
|
1047
|
-
program.name("codebakers").description("CodeBakers CLI - AI prompt patterns for production-ready code").version("1.0.0");
|
|
1048
|
-
setupCommand(program);
|
|
1049
|
-
serveCommand(program);
|
|
1050
|
-
validateCommand(program);
|
|
1051
|
-
summarizeCommand(program);
|
|
1052
|
-
program.command("status").description("Check CodeBakers configuration status").action(() => {
|
|
1053
|
-
console.log(chalk4.bold("\n\u{1F36A} CodeBakers Status\n"));
|
|
1054
|
-
const apiKey = getApiKey();
|
|
1055
|
-
if (apiKey) {
|
|
1056
|
-
const maskedKey = apiKey.substring(0, 10) + "..." + apiKey.slice(-4);
|
|
1057
|
-
console.log(chalk4.green("\u2713 API key configured"));
|
|
1058
|
-
console.log(chalk4.dim(` Key: ${maskedKey}`));
|
|
1059
|
-
} else {
|
|
1060
|
-
console.log(chalk4.red("\u2717 No API key configured"));
|
|
1061
|
-
console.log(chalk4.dim(" Run: codebakers setup"));
|
|
1062
|
-
}
|
|
1063
|
-
const apiUrl = getApiUrl();
|
|
1064
|
-
console.log(chalk4.dim(`
|
|
1065
|
-
API URL: ${apiUrl}`));
|
|
1066
|
-
if (apiUrl !== "https://codebakers.ai") {
|
|
1067
|
-
console.log(chalk4.yellow("\u26A0 Warning: API URL is not the default. Run `codebakers logout` to reset."));
|
|
1068
|
-
}
|
|
1069
|
-
console.log(chalk4.dim(`Config path: ${getConfigPath()}
|
|
1070
|
-
`));
|
|
1071
|
-
});
|
|
1072
|
-
program.command("logout").description("Remove stored API key").action(async () => {
|
|
1073
|
-
const { clearConfig } = await import("./config-R2H6JKGW.js");
|
|
1074
|
-
clearConfig();
|
|
1075
|
-
console.log(chalk4.green("\n\u2713 API key removed\n"));
|
|
1076
|
-
});
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const login_js_1 = require("./commands/login.js");
|
|
6
|
+
const install_js_1 = require("./commands/install.js");
|
|
7
|
+
const status_js_1 = require("./commands/status.js");
|
|
8
|
+
const uninstall_js_1 = require("./commands/uninstall.js");
|
|
9
|
+
const install_hook_js_1 = require("./commands/install-hook.js");
|
|
10
|
+
const doctor_js_1 = require("./commands/doctor.js");
|
|
11
|
+
const init_js_1 = require("./commands/init.js");
|
|
12
|
+
const serve_js_1 = require("./commands/serve.js");
|
|
13
|
+
const mcp_config_js_1 = require("./commands/mcp-config.js");
|
|
14
|
+
const setup_js_1 = require("./commands/setup.js");
|
|
15
|
+
const program = new commander_1.Command();
|
|
16
|
+
program
|
|
17
|
+
.name('codebakers')
|
|
18
|
+
.description('CodeBakers CLI - Production patterns for AI-assisted development')
|
|
19
|
+
.version('1.0.0');
|
|
20
|
+
// Primary command - one-time setup
|
|
21
|
+
program
|
|
22
|
+
.command('setup')
|
|
23
|
+
.description('One-time setup: login + configure Claude Code (recommended)')
|
|
24
|
+
.action(setup_js_1.setup);
|
|
25
|
+
program
|
|
26
|
+
.command('init')
|
|
27
|
+
.description('Interactive project setup wizard')
|
|
28
|
+
.action(init_js_1.init);
|
|
29
|
+
program
|
|
30
|
+
.command('login')
|
|
31
|
+
.description('Login with your API key')
|
|
32
|
+
.action(login_js_1.login);
|
|
33
|
+
program
|
|
34
|
+
.command('install')
|
|
35
|
+
.description('Install patterns in the current project')
|
|
36
|
+
.action(install_js_1.install);
|
|
37
|
+
program
|
|
38
|
+
.command('status')
|
|
39
|
+
.description('Check installation status')
|
|
40
|
+
.action(status_js_1.status);
|
|
41
|
+
program
|
|
42
|
+
.command('uninstall')
|
|
43
|
+
.description('Remove patterns from the current project')
|
|
44
|
+
.action(uninstall_js_1.uninstall);
|
|
45
|
+
program
|
|
46
|
+
.command('install-hook')
|
|
47
|
+
.description('Install the CodeBakers hook into Claude Code')
|
|
48
|
+
.action(install_hook_js_1.installHook);
|
|
49
|
+
program
|
|
50
|
+
.command('uninstall-hook')
|
|
51
|
+
.description('Remove the CodeBakers hook from Claude Code')
|
|
52
|
+
.action(install_hook_js_1.uninstallHook);
|
|
53
|
+
program
|
|
54
|
+
.command('doctor')
|
|
55
|
+
.description('Check if CodeBakers is set up correctly')
|
|
56
|
+
.action(doctor_js_1.doctor);
|
|
57
|
+
// MCP Server commands
|
|
58
|
+
program
|
|
59
|
+
.command('serve')
|
|
60
|
+
.description('Start the MCP server for Claude Code integration')
|
|
61
|
+
.action(serve_js_1.serve);
|
|
62
|
+
program
|
|
63
|
+
.command('mcp-config')
|
|
64
|
+
.description('Show or install MCP configuration for Claude Code')
|
|
65
|
+
.option('--install', 'Install to global Claude Code config')
|
|
66
|
+
.option('--project', 'Create .mcp.json in current directory (for boilerplates)')
|
|
67
|
+
.option('--show', 'Show configuration without installing')
|
|
68
|
+
.action(mcp_config_js_1.mcpConfig);
|
|
69
|
+
program
|
|
70
|
+
.command('mcp-uninstall')
|
|
71
|
+
.description('Remove MCP configuration from Claude Code')
|
|
72
|
+
.action(mcp_config_js_1.mcpUninstall);
|
|
1077
73
|
program.parse();
|