@codebakers/cli 1.6.0 → 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/dist/commands/audit.d.ts +19 -0
- package/dist/commands/audit.js +730 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +176 -0
- package/dist/commands/doctor.js +59 -4
- package/dist/commands/heal.d.ts +41 -0
- package/dist/commands/heal.js +734 -0
- package/dist/commands/login.js +12 -16
- package/dist/commands/provision.d.ts +55 -3
- package/dist/commands/provision.js +243 -74
- package/dist/commands/scaffold.js +158 -80
- package/dist/commands/setup.js +60 -19
- package/dist/commands/upgrade.d.ts +4 -0
- package/dist/commands/upgrade.js +90 -0
- package/dist/config.d.ts +61 -5
- package/dist/config.js +268 -5
- package/dist/index.js +44 -3
- package/dist/lib/api.d.ts +45 -0
- package/dist/lib/api.js +159 -0
- package/dist/mcp/server.js +146 -0
- package/package.json +1 -1
- package/src/commands/audit.ts +827 -0
- package/src/commands/config.ts +216 -0
- package/src/commands/doctor.ts +69 -4
- package/src/commands/heal.ts +889 -0
- package/src/commands/login.ts +14 -18
- package/src/commands/provision.ts +323 -101
- package/src/commands/scaffold.ts +188 -81
- package/src/commands/setup.ts +65 -20
- package/src/commands/upgrade.ts +110 -0
- package/src/config.ts +320 -11
- package/src/index.ts +48 -3
- package/src/lib/api.ts +183 -0
- package/src/mcp/server.ts +160 -0
package/dist/config.js
CHANGED
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PROVISIONABLE_KEYS = exports.SERVICE_KEY_LABELS = exports.SERVICE_KEY_CATEGORIES = exports.ENV_VAR_NAMES = exports.SERVICE_KEYS = void 0;
|
|
6
7
|
exports.getApiKey = getApiKey;
|
|
7
8
|
exports.setApiKey = setApiKey;
|
|
8
9
|
exports.clearApiKey = clearApiKey;
|
|
@@ -13,21 +14,128 @@ exports.setExperienceLevel = setExperienceLevel;
|
|
|
13
14
|
exports.getServiceKey = getServiceKey;
|
|
14
15
|
exports.setServiceKey = setServiceKey;
|
|
15
16
|
exports.clearServiceKey = clearServiceKey;
|
|
17
|
+
exports.clearAllServiceKeys = clearAllServiceKeys;
|
|
16
18
|
exports.getAllServiceKeys = getAllServiceKeys;
|
|
19
|
+
exports.getConfiguredServiceKeys = getConfiguredServiceKeys;
|
|
20
|
+
exports.getLastKeySync = getLastKeySync;
|
|
21
|
+
exports.setLastKeySync = setLastKeySync;
|
|
22
|
+
exports.syncServiceKeys = syncServiceKeys;
|
|
23
|
+
exports.writeKeysToEnvFile = writeKeysToEnvFile;
|
|
24
|
+
exports.validateKeyFormat = validateKeyFormat;
|
|
25
|
+
exports.getConfigPath = getConfigPath;
|
|
26
|
+
exports.getConfigStore = getConfigStore;
|
|
17
27
|
const conf_1 = __importDefault(require("conf"));
|
|
28
|
+
const fs_1 = require("fs");
|
|
29
|
+
const path_1 = require("path");
|
|
30
|
+
/**
|
|
31
|
+
* Canonical list of ALL supported service keys
|
|
32
|
+
* This must match the server contract at src/lib/contracts/service-keys.ts
|
|
33
|
+
*/
|
|
34
|
+
exports.SERVICE_KEYS = [
|
|
35
|
+
// Infrastructure (provisioning-capable)
|
|
36
|
+
'github',
|
|
37
|
+
'supabase',
|
|
38
|
+
'vercel',
|
|
39
|
+
// AI
|
|
40
|
+
'openai',
|
|
41
|
+
'anthropic',
|
|
42
|
+
// Payments
|
|
43
|
+
'stripe',
|
|
44
|
+
// Communication
|
|
45
|
+
'twilio_sid',
|
|
46
|
+
'twilio_auth',
|
|
47
|
+
'resend',
|
|
48
|
+
'vapi',
|
|
49
|
+
// Monitoring
|
|
50
|
+
'sentry',
|
|
51
|
+
// Media
|
|
52
|
+
'cloudinary',
|
|
53
|
+
'pexels',
|
|
54
|
+
'midjourney',
|
|
55
|
+
];
|
|
56
|
+
/**
|
|
57
|
+
* Map service key names to environment variable names
|
|
58
|
+
*/
|
|
59
|
+
exports.ENV_VAR_NAMES = {
|
|
60
|
+
github: 'GITHUB_TOKEN',
|
|
61
|
+
supabase: 'SUPABASE_ACCESS_TOKEN',
|
|
62
|
+
vercel: 'VERCEL_TOKEN',
|
|
63
|
+
openai: 'OPENAI_API_KEY',
|
|
64
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
65
|
+
stripe: 'STRIPE_SECRET_KEY',
|
|
66
|
+
twilio_sid: 'TWILIO_ACCOUNT_SID',
|
|
67
|
+
twilio_auth: 'TWILIO_AUTH_TOKEN',
|
|
68
|
+
resend: 'RESEND_API_KEY',
|
|
69
|
+
vapi: 'VAPI_API_KEY',
|
|
70
|
+
sentry: 'SENTRY_DSN',
|
|
71
|
+
cloudinary: 'CLOUDINARY_API_SECRET',
|
|
72
|
+
pexels: 'PEXELS_API_KEY',
|
|
73
|
+
midjourney: 'MIDJOURNEY_API_KEY',
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Service key categories for display grouping
|
|
77
|
+
*/
|
|
78
|
+
exports.SERVICE_KEY_CATEGORIES = {
|
|
79
|
+
infrastructure: ['github', 'supabase', 'vercel'],
|
|
80
|
+
ai: ['openai', 'anthropic'],
|
|
81
|
+
payments: ['stripe'],
|
|
82
|
+
communication: ['twilio_sid', 'twilio_auth', 'resend', 'vapi'],
|
|
83
|
+
monitoring: ['sentry'],
|
|
84
|
+
media: ['cloudinary', 'pexels', 'midjourney'],
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Service key labels for display
|
|
88
|
+
*/
|
|
89
|
+
exports.SERVICE_KEY_LABELS = {
|
|
90
|
+
github: 'GitHub',
|
|
91
|
+
supabase: 'Supabase',
|
|
92
|
+
vercel: 'Vercel',
|
|
93
|
+
openai: 'OpenAI',
|
|
94
|
+
anthropic: 'Anthropic',
|
|
95
|
+
stripe: 'Stripe',
|
|
96
|
+
twilio_sid: 'Twilio SID',
|
|
97
|
+
twilio_auth: 'Twilio Auth',
|
|
98
|
+
resend: 'Resend',
|
|
99
|
+
vapi: 'VAPI',
|
|
100
|
+
sentry: 'Sentry',
|
|
101
|
+
cloudinary: 'Cloudinary',
|
|
102
|
+
pexels: 'Pexels',
|
|
103
|
+
midjourney: 'Midjourney',
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Keys that can be used for auto-provisioning
|
|
107
|
+
*/
|
|
108
|
+
exports.PROVISIONABLE_KEYS = ['github', 'supabase', 'vercel'];
|
|
109
|
+
// Create default service keys object with all keys set to null
|
|
110
|
+
const defaultServiceKeys = Object.fromEntries(exports.SERVICE_KEYS.map(key => [key, null]));
|
|
18
111
|
const config = new conf_1.default({
|
|
19
112
|
projectName: 'codebakers',
|
|
113
|
+
projectVersion: '1.7.0',
|
|
20
114
|
defaults: {
|
|
21
115
|
apiKey: null,
|
|
22
116
|
apiUrl: 'https://codebakers.ai',
|
|
23
117
|
experienceLevel: 'intermediate',
|
|
24
|
-
serviceKeys:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
118
|
+
serviceKeys: defaultServiceKeys,
|
|
119
|
+
lastKeySync: null,
|
|
120
|
+
},
|
|
121
|
+
// Migration to add new keys when upgrading from old version
|
|
122
|
+
migrations: {
|
|
123
|
+
'1.7.0': (store) => {
|
|
124
|
+
const oldKeys = store.get('serviceKeys');
|
|
125
|
+
const newKeys = { ...defaultServiceKeys };
|
|
126
|
+
// Preserve existing keys
|
|
127
|
+
for (const key of exports.SERVICE_KEYS) {
|
|
128
|
+
if (oldKeys && key in oldKeys && oldKeys[key]) {
|
|
129
|
+
newKeys[key] = oldKeys[key];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
store.set('serviceKeys', newKeys);
|
|
28
133
|
},
|
|
29
134
|
},
|
|
30
135
|
});
|
|
136
|
+
// ============================================================
|
|
137
|
+
// API Key Management
|
|
138
|
+
// ============================================================
|
|
31
139
|
function getApiKey() {
|
|
32
140
|
return config.get('apiKey');
|
|
33
141
|
}
|
|
@@ -43,15 +151,21 @@ function getApiUrl() {
|
|
|
43
151
|
function setApiUrl(url) {
|
|
44
152
|
config.set('apiUrl', url);
|
|
45
153
|
}
|
|
154
|
+
// ============================================================
|
|
155
|
+
// Experience Level
|
|
156
|
+
// ============================================================
|
|
46
157
|
function getExperienceLevel() {
|
|
47
158
|
return config.get('experienceLevel');
|
|
48
159
|
}
|
|
49
160
|
function setExperienceLevel(level) {
|
|
50
161
|
config.set('experienceLevel', level);
|
|
51
162
|
}
|
|
163
|
+
// ============================================================
|
|
164
|
+
// Service API Keys
|
|
165
|
+
// ============================================================
|
|
52
166
|
function getServiceKey(service) {
|
|
53
167
|
const keys = config.get('serviceKeys');
|
|
54
|
-
return keys[service];
|
|
168
|
+
return keys[service] ?? null;
|
|
55
169
|
}
|
|
56
170
|
function setServiceKey(service, key) {
|
|
57
171
|
const keys = config.get('serviceKeys');
|
|
@@ -63,6 +177,155 @@ function clearServiceKey(service) {
|
|
|
63
177
|
keys[service] = null;
|
|
64
178
|
config.set('serviceKeys', keys);
|
|
65
179
|
}
|
|
180
|
+
function clearAllServiceKeys() {
|
|
181
|
+
config.set('serviceKeys', { ...defaultServiceKeys });
|
|
182
|
+
config.set('lastKeySync', null);
|
|
183
|
+
}
|
|
66
184
|
function getAllServiceKeys() {
|
|
67
185
|
return config.get('serviceKeys');
|
|
68
186
|
}
|
|
187
|
+
function getConfiguredServiceKeys() {
|
|
188
|
+
const keys = config.get('serviceKeys');
|
|
189
|
+
return exports.SERVICE_KEYS.filter(name => keys[name] !== null && keys[name] !== '');
|
|
190
|
+
}
|
|
191
|
+
function getLastKeySync() {
|
|
192
|
+
const lastSync = config.get('lastKeySync');
|
|
193
|
+
return lastSync ? new Date(lastSync) : null;
|
|
194
|
+
}
|
|
195
|
+
function setLastKeySync(date) {
|
|
196
|
+
config.set('lastKeySync', date.toISOString());
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Sync keys from server response to local storage
|
|
200
|
+
*/
|
|
201
|
+
function syncServiceKeys(serverKeys) {
|
|
202
|
+
const localKeys = config.get('serviceKeys');
|
|
203
|
+
const result = {
|
|
204
|
+
added: [],
|
|
205
|
+
updated: [],
|
|
206
|
+
unchanged: [],
|
|
207
|
+
total: 0,
|
|
208
|
+
};
|
|
209
|
+
for (const keyName of exports.SERVICE_KEYS) {
|
|
210
|
+
const serverValue = serverKeys[keyName];
|
|
211
|
+
const localValue = localKeys[keyName];
|
|
212
|
+
if (serverValue) {
|
|
213
|
+
result.total++;
|
|
214
|
+
if (!localValue) {
|
|
215
|
+
result.added.push(keyName);
|
|
216
|
+
localKeys[keyName] = serverValue;
|
|
217
|
+
}
|
|
218
|
+
else if (localValue !== serverValue) {
|
|
219
|
+
result.updated.push(keyName);
|
|
220
|
+
localKeys[keyName] = serverValue;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
result.unchanged.push(keyName);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
config.set('serviceKeys', localKeys);
|
|
228
|
+
config.set('lastKeySync', new Date().toISOString());
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
// ============================================================
|
|
232
|
+
// Environment File Operations
|
|
233
|
+
// ============================================================
|
|
234
|
+
/**
|
|
235
|
+
* Write service keys to a .env.local file
|
|
236
|
+
*/
|
|
237
|
+
function writeKeysToEnvFile(projectPath, options) {
|
|
238
|
+
const envPath = (0, path_1.join)(projectPath, '.env.local');
|
|
239
|
+
const keys = config.get('serviceKeys');
|
|
240
|
+
const lines = [];
|
|
241
|
+
// Header
|
|
242
|
+
lines.push('# Service Keys - Generated by CodeBakers CLI');
|
|
243
|
+
lines.push(`# Generated: ${new Date().toISOString()}`);
|
|
244
|
+
lines.push('');
|
|
245
|
+
// Add additional vars first (like Supabase URL, etc.)
|
|
246
|
+
if (options?.additionalVars) {
|
|
247
|
+
lines.push('# Project Configuration');
|
|
248
|
+
for (const [name, value] of Object.entries(options.additionalVars)) {
|
|
249
|
+
lines.push(`${name}=${value}`);
|
|
250
|
+
}
|
|
251
|
+
lines.push('');
|
|
252
|
+
}
|
|
253
|
+
// Group keys by category
|
|
254
|
+
for (const [category, keyNames] of Object.entries(exports.SERVICE_KEY_CATEGORIES)) {
|
|
255
|
+
const categoryLines = [];
|
|
256
|
+
for (const keyName of keyNames) {
|
|
257
|
+
const value = keys[keyName];
|
|
258
|
+
const envVarName = exports.ENV_VAR_NAMES[keyName];
|
|
259
|
+
if (value) {
|
|
260
|
+
categoryLines.push(`${envVarName}=${value}`);
|
|
261
|
+
}
|
|
262
|
+
else if (options?.includeEmpty) {
|
|
263
|
+
categoryLines.push(`# ${envVarName}=`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (categoryLines.length > 0) {
|
|
267
|
+
lines.push(`# ${category.charAt(0).toUpperCase() + category.slice(1)}`);
|
|
268
|
+
lines.push(...categoryLines);
|
|
269
|
+
lines.push('');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Read existing .env.local and preserve non-CodeBakers variables
|
|
273
|
+
let existingContent = '';
|
|
274
|
+
if ((0, fs_1.existsSync)(envPath)) {
|
|
275
|
+
existingContent = (0, fs_1.readFileSync)(envPath, 'utf-8');
|
|
276
|
+
// Extract lines that aren't CodeBakers-managed
|
|
277
|
+
const existingLines = existingContent.split('\n');
|
|
278
|
+
const preservedLines = [];
|
|
279
|
+
let inCodeBakersSection = false;
|
|
280
|
+
for (const line of existingLines) {
|
|
281
|
+
if (line.includes('Generated by CodeBakers CLI')) {
|
|
282
|
+
inCodeBakersSection = true;
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
// Check if line is a CodeBakers-managed env var
|
|
286
|
+
const isCodeBakersVar = Object.values(exports.ENV_VAR_NAMES).some(envName => line.startsWith(`${envName}=`) || line.startsWith(`# ${envName}=`));
|
|
287
|
+
if (!isCodeBakersVar && !inCodeBakersSection && line.trim()) {
|
|
288
|
+
preservedLines.push(line);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Add preserved lines at the end
|
|
292
|
+
if (preservedLines.length > 0) {
|
|
293
|
+
lines.push('# Existing Configuration');
|
|
294
|
+
lines.push(...preservedLines);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const content = lines.join('\n');
|
|
298
|
+
(0, fs_1.writeFileSync)(envPath, content);
|
|
299
|
+
const written = Object.values(keys).filter(v => v !== null).length;
|
|
300
|
+
return { written, path: envPath };
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Check if a key value appears valid (basic format check)
|
|
304
|
+
*/
|
|
305
|
+
function validateKeyFormat(name, value) {
|
|
306
|
+
if (!value || value.length < 8)
|
|
307
|
+
return false;
|
|
308
|
+
const prefixes = {
|
|
309
|
+
github: ['ghp_', 'gho_', 'ghu_', 'ghs_', 'ghr_'],
|
|
310
|
+
openai: ['sk-'],
|
|
311
|
+
anthropic: ['sk-ant-'],
|
|
312
|
+
stripe: ['sk_live_', 'sk_test_', 'rk_live_', 'rk_test_'],
|
|
313
|
+
twilio_sid: ['AC'],
|
|
314
|
+
resend: ['re_'],
|
|
315
|
+
supabase: ['sbp_'],
|
|
316
|
+
};
|
|
317
|
+
const validPrefixes = prefixes[name];
|
|
318
|
+
if (validPrefixes) {
|
|
319
|
+
return validPrefixes.some(prefix => value.startsWith(prefix));
|
|
320
|
+
}
|
|
321
|
+
return true; // No specific format required
|
|
322
|
+
}
|
|
323
|
+
// ============================================================
|
|
324
|
+
// Config Path (for debugging)
|
|
325
|
+
// ============================================================
|
|
326
|
+
function getConfigPath() {
|
|
327
|
+
return config.path;
|
|
328
|
+
}
|
|
329
|
+
function getConfigStore() {
|
|
330
|
+
return config.store;
|
|
331
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,10 @@ const mcp_config_js_1 = require("./commands/mcp-config.js");
|
|
|
18
18
|
const setup_js_1 = require("./commands/setup.js");
|
|
19
19
|
const scaffold_js_1 = require("./commands/scaffold.js");
|
|
20
20
|
const generate_js_1 = require("./commands/generate.js");
|
|
21
|
+
const upgrade_js_1 = require("./commands/upgrade.js");
|
|
22
|
+
const config_js_1 = require("./commands/config.js");
|
|
23
|
+
const audit_js_1 = require("./commands/audit.js");
|
|
24
|
+
const heal_js_1 = require("./commands/heal.js");
|
|
21
25
|
// Show welcome message when no command is provided
|
|
22
26
|
function showWelcome() {
|
|
23
27
|
console.log(chalk_1.default.blue(`
|
|
@@ -35,7 +39,9 @@ function showWelcome() {
|
|
|
35
39
|
console.log(chalk_1.default.cyan(' codebakers init') + chalk_1.default.gray(' Add patterns to existing project\n'));
|
|
36
40
|
console.log(chalk_1.default.white(' Development:\n'));
|
|
37
41
|
console.log(chalk_1.default.cyan(' codebakers generate') + chalk_1.default.gray(' Generate components, APIs, services'));
|
|
38
|
-
console.log(chalk_1.default.cyan(' codebakers
|
|
42
|
+
console.log(chalk_1.default.cyan(' codebakers upgrade') + chalk_1.default.gray(' Update patterns to latest version'));
|
|
43
|
+
console.log(chalk_1.default.cyan(' codebakers status') + chalk_1.default.gray(' Check what\'s installed'));
|
|
44
|
+
console.log(chalk_1.default.cyan(' codebakers config') + chalk_1.default.gray(' View or modify configuration\n'));
|
|
39
45
|
console.log(chalk_1.default.white(' Examples:\n'));
|
|
40
46
|
console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers scaffold'));
|
|
41
47
|
console.log(chalk_1.default.gray(' Create a new Next.js + Supabase + Drizzle project\n'));
|
|
@@ -43,8 +49,12 @@ function showWelcome() {
|
|
|
43
49
|
console.log(chalk_1.default.gray(' Generate a React component with TypeScript\n'));
|
|
44
50
|
console.log(chalk_1.default.gray(' $ ') + chalk_1.default.cyan('codebakers g api users'));
|
|
45
51
|
console.log(chalk_1.default.gray(' Generate a Next.js API route with validation\n'));
|
|
52
|
+
console.log(chalk_1.default.white(' Quality:\n'));
|
|
53
|
+
console.log(chalk_1.default.cyan(' codebakers audit') + chalk_1.default.gray(' Run automated code quality checks'));
|
|
54
|
+
console.log(chalk_1.default.cyan(' codebakers heal') + chalk_1.default.gray(' Auto-detect and fix common issues'));
|
|
55
|
+
console.log(chalk_1.default.cyan(' codebakers doctor') + chalk_1.default.gray(' Check CodeBakers setup\n'));
|
|
46
56
|
console.log(chalk_1.default.white(' All Commands:\n'));
|
|
47
|
-
console.log(chalk_1.default.gray(' setup, scaffold, init, generate, status, doctor, login'));
|
|
57
|
+
console.log(chalk_1.default.gray(' setup, scaffold, init, generate, upgrade, status, audit, heal, doctor, config, login'));
|
|
48
58
|
console.log(chalk_1.default.gray(' install, uninstall, install-hook, uninstall-hook'));
|
|
49
59
|
console.log(chalk_1.default.gray(' serve, mcp-config, mcp-uninstall\n'));
|
|
50
60
|
console.log(chalk_1.default.gray(' Run ') + chalk_1.default.cyan('codebakers <command> --help') + chalk_1.default.gray(' for more info\n'));
|
|
@@ -53,7 +63,7 @@ const program = new commander_1.Command();
|
|
|
53
63
|
program
|
|
54
64
|
.name('codebakers')
|
|
55
65
|
.description('CodeBakers CLI - Production patterns for AI-assisted development')
|
|
56
|
-
.version('1.
|
|
66
|
+
.version('1.7.0');
|
|
57
67
|
// Primary command - one-time setup
|
|
58
68
|
program
|
|
59
69
|
.command('setup')
|
|
@@ -73,6 +83,14 @@ program
|
|
|
73
83
|
.alias('g')
|
|
74
84
|
.description('Generate code from templates (component, api, service, hook, page, schema, form)')
|
|
75
85
|
.action((type, name) => (0, generate_js_1.generate)({ type, name }));
|
|
86
|
+
program
|
|
87
|
+
.command('upgrade')
|
|
88
|
+
.description('Update patterns to the latest version')
|
|
89
|
+
.action(upgrade_js_1.upgrade);
|
|
90
|
+
program
|
|
91
|
+
.command('config [action]')
|
|
92
|
+
.description('View or modify CLI configuration (show, path, keys, clear-keys, set-url, reset)')
|
|
93
|
+
.action((action) => (0, config_js_1.config)(action));
|
|
76
94
|
program
|
|
77
95
|
.command('login')
|
|
78
96
|
.description('Login with your API key')
|
|
@@ -101,6 +119,29 @@ program
|
|
|
101
119
|
.command('doctor')
|
|
102
120
|
.description('Check if CodeBakers is set up correctly')
|
|
103
121
|
.action(doctor_js_1.doctor);
|
|
122
|
+
program
|
|
123
|
+
.command('audit')
|
|
124
|
+
.description('Run automated code quality and security checks')
|
|
125
|
+
.action(async () => { await (0, audit_js_1.audit)(); });
|
|
126
|
+
program
|
|
127
|
+
.command('heal')
|
|
128
|
+
.description('Auto-detect and fix common issues (TypeScript, deps, security)')
|
|
129
|
+
.option('--auto', 'Automatically apply safe fixes')
|
|
130
|
+
.option('--watch', 'Watch mode - continuously monitor and fix')
|
|
131
|
+
.option('--dry-run', 'Show what would be fixed without applying')
|
|
132
|
+
.option('--severity <level>', 'Filter by severity (critical, high, medium, low)')
|
|
133
|
+
.action(async (options) => {
|
|
134
|
+
if (options.watch) {
|
|
135
|
+
await (0, heal_js_1.healWatch)();
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await (0, heal_js_1.heal)({
|
|
139
|
+
auto: options.auto,
|
|
140
|
+
dryRun: options.dryRun,
|
|
141
|
+
severity: options.severity
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
104
145
|
// MCP Server commands
|
|
105
146
|
program
|
|
106
147
|
.command('serve')
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API client utilities for CodeBakers CLI
|
|
3
|
+
* This is the single source of truth for API validation and error handling
|
|
4
|
+
*/
|
|
5
|
+
export interface ApiError {
|
|
6
|
+
error: string;
|
|
7
|
+
code?: string;
|
|
8
|
+
recoverySteps?: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validate an API key format
|
|
12
|
+
*/
|
|
13
|
+
export declare function isValidApiKeyFormat(apiKey: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Validate an API key against the server
|
|
16
|
+
* Returns true if valid, throws an ApiError if not
|
|
17
|
+
*/
|
|
18
|
+
export declare function validateApiKey(apiKey: string): Promise<boolean>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a structured API error
|
|
21
|
+
*/
|
|
22
|
+
export declare function createApiError(message: string, code: string, recoverySteps: string[]): ApiError;
|
|
23
|
+
/**
|
|
24
|
+
* Format an API error for display
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatApiError(error: ApiError): string;
|
|
27
|
+
/**
|
|
28
|
+
* Check if the current API key is valid (for doctor command)
|
|
29
|
+
*/
|
|
30
|
+
export declare function checkApiKeyValidity(): Promise<{
|
|
31
|
+
valid: boolean;
|
|
32
|
+
error?: ApiError;
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* Get the current CLI version
|
|
36
|
+
*/
|
|
37
|
+
export declare function getCliVersion(): string;
|
|
38
|
+
/**
|
|
39
|
+
* Check if there's a newer version of the CLI available
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkForUpdates(): Promise<{
|
|
42
|
+
currentVersion: string;
|
|
43
|
+
latestVersion: string;
|
|
44
|
+
updateAvailable: boolean;
|
|
45
|
+
} | null>;
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidApiKeyFormat = isValidApiKeyFormat;
|
|
4
|
+
exports.validateApiKey = validateApiKey;
|
|
5
|
+
exports.createApiError = createApiError;
|
|
6
|
+
exports.formatApiError = formatApiError;
|
|
7
|
+
exports.checkApiKeyValidity = checkApiKeyValidity;
|
|
8
|
+
exports.getCliVersion = getCliVersion;
|
|
9
|
+
exports.checkForUpdates = checkForUpdates;
|
|
10
|
+
const config_js_1 = require("../config.js");
|
|
11
|
+
/**
|
|
12
|
+
* Validate an API key format
|
|
13
|
+
*/
|
|
14
|
+
function isValidApiKeyFormat(apiKey) {
|
|
15
|
+
if (!apiKey || typeof apiKey !== 'string')
|
|
16
|
+
return false;
|
|
17
|
+
// Keys should start with cb_ and be at least 20 characters
|
|
18
|
+
return apiKey.startsWith('cb_') && apiKey.length >= 20;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Validate an API key against the server
|
|
22
|
+
* Returns true if valid, throws an ApiError if not
|
|
23
|
+
*/
|
|
24
|
+
async function validateApiKey(apiKey) {
|
|
25
|
+
// First check format
|
|
26
|
+
if (!isValidApiKeyFormat(apiKey)) {
|
|
27
|
+
throw createApiError('Invalid API key format. Keys start with "cb_"', 'INVALID_FORMAT', [
|
|
28
|
+
'Check that you copied the full API key',
|
|
29
|
+
'Get your API key from: https://codebakers.ai/dashboard',
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
const apiUrl = (0, config_js_1.getApiUrl)();
|
|
34
|
+
const response = await fetch(`${apiUrl}/api/content`, {
|
|
35
|
+
method: 'GET',
|
|
36
|
+
headers: {
|
|
37
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (response.ok) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
// Parse error response
|
|
44
|
+
const errorBody = await response.json().catch(() => ({}));
|
|
45
|
+
const errorMessage = errorBody.error || errorBody.message || 'API key validation failed';
|
|
46
|
+
if (response.status === 401) {
|
|
47
|
+
throw createApiError(errorMessage, 'UNAUTHORIZED', [
|
|
48
|
+
'Your API key may have been revoked or expired',
|
|
49
|
+
'Generate a new key at: https://codebakers.ai/dashboard',
|
|
50
|
+
]);
|
|
51
|
+
}
|
|
52
|
+
if (response.status === 403) {
|
|
53
|
+
throw createApiError(errorMessage, 'FORBIDDEN', [
|
|
54
|
+
'Your subscription may have expired',
|
|
55
|
+
'Check your account status at: https://codebakers.ai/settings',
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
throw createApiError(errorMessage, 'UNKNOWN', [
|
|
59
|
+
'Try again in a few moments',
|
|
60
|
+
'If the problem persists, contact support',
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// If it's already an ApiError, rethrow it
|
|
65
|
+
if (error && typeof error === 'object' && 'recoverySteps' in error) {
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
// Network error
|
|
69
|
+
throw createApiError('Could not connect to CodeBakers server', 'NETWORK_ERROR', [
|
|
70
|
+
'Check your internet connection',
|
|
71
|
+
'Try again in a few moments',
|
|
72
|
+
'If using a VPN or proxy, try disabling it',
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Create a structured API error
|
|
78
|
+
*/
|
|
79
|
+
function createApiError(message, code, recoverySteps) {
|
|
80
|
+
return {
|
|
81
|
+
error: message,
|
|
82
|
+
code,
|
|
83
|
+
recoverySteps,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Format an API error for display
|
|
88
|
+
*/
|
|
89
|
+
function formatApiError(error) {
|
|
90
|
+
let output = error.error;
|
|
91
|
+
if (error.recoverySteps && error.recoverySteps.length > 0) {
|
|
92
|
+
output += '\n\n Try:';
|
|
93
|
+
for (const step of error.recoverySteps) {
|
|
94
|
+
output += `\n • ${step}`;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return output;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if the current API key is valid (for doctor command)
|
|
101
|
+
*/
|
|
102
|
+
async function checkApiKeyValidity() {
|
|
103
|
+
const { getApiKey } = await import('../config.js');
|
|
104
|
+
const apiKey = getApiKey();
|
|
105
|
+
if (!apiKey) {
|
|
106
|
+
return {
|
|
107
|
+
valid: false,
|
|
108
|
+
error: createApiError('No API key configured', 'NOT_CONFIGURED', [
|
|
109
|
+
'Run: codebakers setup',
|
|
110
|
+
]),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
await validateApiKey(apiKey);
|
|
115
|
+
return { valid: true };
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
valid: false,
|
|
120
|
+
error: error,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Get the current CLI version
|
|
126
|
+
*/
|
|
127
|
+
function getCliVersion() {
|
|
128
|
+
try {
|
|
129
|
+
// Try to read from package.json
|
|
130
|
+
const packageJson = require('../../package.json');
|
|
131
|
+
return packageJson.version || 'unknown';
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return 'unknown';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Check if there's a newer version of the CLI available
|
|
139
|
+
*/
|
|
140
|
+
async function checkForUpdates() {
|
|
141
|
+
try {
|
|
142
|
+
const currentVersion = getCliVersion();
|
|
143
|
+
const response = await fetch('https://registry.npmjs.org/@codebakers/cli/latest', {
|
|
144
|
+
headers: { 'Accept': 'application/json' },
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok)
|
|
147
|
+
return null;
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
const latestVersion = data.version;
|
|
150
|
+
return {
|
|
151
|
+
currentVersion,
|
|
152
|
+
latestVersion,
|
|
153
|
+
updateAvailable: currentVersion !== latestVersion,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|