@codebakers/cli 1.6.0 → 2.1.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/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
- github: null,
26
- supabase: null,
27
- vercel: null,
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 status') + chalk_1.default.gray(' Check what\'s installed\n'));
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.6.0');
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>;
@@ -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
+ }