@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.
@@ -5,19 +5,25 @@ import { writeFileSync, mkdirSync, existsSync, readdirSync, readFileSync } from
5
5
  import { join } from 'path';
6
6
  import { execSync } from 'child_process';
7
7
  import * as templates from '../templates/nextjs-supabase.js';
8
- import { getApiKey, getApiUrl, setServiceKey, getServiceKey } from '../config.js';
8
+ import {
9
+ getApiKey,
10
+ getApiUrl,
11
+ syncServiceKeys,
12
+ clearAllServiceKeys,
13
+ getConfiguredServiceKeys,
14
+ writeKeysToEnvFile,
15
+ SERVICE_KEYS,
16
+ SERVICE_KEY_LABELS,
17
+ PROVISIONABLE_KEYS,
18
+ type ServiceName,
19
+ type SyncResult,
20
+ } from '../config.js';
9
21
  import { provisionAll, type ProvisionResult } from './provision.js';
10
22
 
11
- interface ServerServiceKeys {
12
- github: string | null;
13
- supabase: string | null;
14
- vercel: string | null;
15
- }
16
-
17
23
  /**
18
- * Fetch service keys from CodeBakers server
24
+ * Fetch ALL service keys from CodeBakers server
19
25
  */
20
- async function fetchServerKeys(): Promise<ServerServiceKeys | null> {
26
+ async function fetchServerKeys(): Promise<Partial<Record<ServiceName, string>> | null> {
21
27
  const apiKey = getApiKey();
22
28
  if (!apiKey) return null;
23
29
 
@@ -30,7 +36,9 @@ async function fetchServerKeys(): Promise<ServerServiceKeys | null> {
30
36
  });
31
37
 
32
38
  if (response.ok) {
33
- return await response.json();
39
+ const data = await response.json();
40
+ // Handle wrapped response { data: {...} } or direct response
41
+ return data.data || data;
34
42
  }
35
43
  } catch {
36
44
  // Server unreachable or error
@@ -38,15 +46,6 @@ async function fetchServerKeys(): Promise<ServerServiceKeys | null> {
38
46
  return null;
39
47
  }
40
48
 
41
- /**
42
- * Sync server keys to local storage
43
- */
44
- function syncKeysToLocal(keys: ServerServiceKeys): void {
45
- if (keys.github) setServiceKey('github', keys.github);
46
- if (keys.supabase) setServiceKey('supabase', keys.supabase);
47
- if (keys.vercel) setServiceKey('vercel', keys.vercel);
48
- }
49
-
50
49
  // Cursor IDE configuration templates
51
50
  const CURSORRULES_TEMPLATE = `# CODEBAKERS CURSOR RULES
52
51
  # Zero-friction AI assistance - everything is automatic
@@ -301,6 +300,58 @@ async function confirm(question: string): Promise<boolean> {
301
300
  return answer.toLowerCase() !== 'n';
302
301
  }
303
302
 
303
+ /**
304
+ * Display configured keys summary
305
+ */
306
+ function displayKeysSummary(_source: 'server' | 'local' | 'both'): void {
307
+ const configuredKeys = getConfiguredServiceKeys();
308
+
309
+ if (configuredKeys.length === 0) {
310
+ console.log(chalk.gray(' No service keys configured\n'));
311
+ return;
312
+ }
313
+
314
+ // Group by provisionable vs other
315
+ const provisionable = configuredKeys.filter(k => PROVISIONABLE_KEYS.includes(k));
316
+ const other = configuredKeys.filter(k => !PROVISIONABLE_KEYS.includes(k));
317
+
318
+ if (provisionable.length > 0) {
319
+ console.log(chalk.gray(' Infrastructure (can auto-provision):'));
320
+ for (const key of provisionable) {
321
+ console.log(chalk.green(` ✓ ${SERVICE_KEY_LABELS[key]}`));
322
+ }
323
+ }
324
+
325
+ if (other.length > 0) {
326
+ console.log(chalk.gray(' Other services:'));
327
+ for (const key of other) {
328
+ console.log(chalk.green(` ✓ ${SERVICE_KEY_LABELS[key]}`));
329
+ }
330
+ }
331
+
332
+ console.log('');
333
+ }
334
+
335
+ /**
336
+ * Display sync results
337
+ */
338
+ function displaySyncResults(result: SyncResult): void {
339
+ if (result.added.length > 0) {
340
+ console.log(chalk.green(` + ${result.added.length} keys synced from server`));
341
+ for (const key of result.added) {
342
+ console.log(chalk.gray(` + ${SERVICE_KEY_LABELS[key]}`));
343
+ }
344
+ }
345
+
346
+ if (result.updated.length > 0) {
347
+ console.log(chalk.yellow(` ~ ${result.updated.length} keys updated from server`));
348
+ }
349
+
350
+ if (result.added.length === 0 && result.updated.length === 0) {
351
+ console.log(chalk.gray(' All keys already in sync'));
352
+ }
353
+ }
354
+
304
355
  /**
305
356
  * Scaffold a new project with full structure
306
357
  */
@@ -482,8 +533,6 @@ export async function scaffold(): Promise<void> {
482
533
  }
483
534
  }
484
535
 
485
- spinner.succeed('Project structure created!');
486
-
487
536
  // Auto-install CodeBakers patterns
488
537
  console.log(chalk.white('\n Installing CodeBakers patterns...\n'));
489
538
 
@@ -573,28 +622,25 @@ export async function scaffold(): Promise<void> {
573
622
  if (wantProvision) {
574
623
  // Check for saved keys in CodeBakers back office
575
624
  const serverKeys = await fetchServerKeys();
576
- const hasServerKeys = serverKeys && (serverKeys.github || serverKeys.supabase || serverKeys.vercel);
577
- const localGithub = getServiceKey('github');
578
- const localSupabase = getServiceKey('supabase');
579
- const localVercel = getServiceKey('vercel');
580
- const hasLocalKeys = localGithub || localSupabase || localVercel;
625
+ const serverKeyCount = serverKeys ? Object.values(serverKeys).filter(v => v).length : 0;
626
+ const localKeyCount = getConfiguredServiceKeys().length;
581
627
 
582
- if (hasServerKeys || hasLocalKeys) {
628
+ if (serverKeyCount > 0 || localKeyCount > 0) {
583
629
  // Show which keys are available
584
630
  console.log(chalk.white('\n Available service keys:\n'));
585
631
 
586
- if (hasServerKeys) {
587
- console.log(chalk.gray(' From CodeBakers account:'));
588
- if (serverKeys?.github) console.log(chalk.green(' ✓ GitHub'));
589
- if (serverKeys?.supabase) console.log(chalk.green(' ✓ Supabase'));
590
- if (serverKeys?.vercel) console.log(chalk.green('Vercel'));
632
+ if (serverKeyCount > 0) {
633
+ console.log(chalk.gray(` From CodeBakers account (${serverKeyCount} keys):`));
634
+ for (const key of SERVICE_KEYS) {
635
+ if (serverKeys && serverKeys[key]) {
636
+ console.log(chalk.green(`${SERVICE_KEY_LABELS[key]}`));
637
+ }
638
+ }
591
639
  }
592
640
 
593
- if (hasLocalKeys) {
594
- console.log(chalk.gray(' Stored locally:'));
595
- if (localGithub) console.log(chalk.green(' ✓ GitHub'));
596
- if (localSupabase) console.log(chalk.green(' ✓ Supabase'));
597
- if (localVercel) console.log(chalk.green(' ✓ Vercel'));
641
+ if (localKeyCount > 0) {
642
+ console.log(chalk.gray(` Stored locally (${localKeyCount} keys):`));
643
+ displayKeysSummary('local');
598
644
  }
599
645
 
600
646
  console.log('');
@@ -602,7 +648,7 @@ export async function scaffold(): Promise<void> {
602
648
  // Ask which keys to use
603
649
  console.log(chalk.white(' Which keys would you like to use?\n'));
604
650
  console.log(chalk.gray(' 1. ') + chalk.cyan('Use saved keys') + chalk.gray(' - Use keys from your account/local storage'));
605
- console.log(chalk.gray(' 2. ') + chalk.cyan('Enter new keys') + chalk.gray(' - For a client project or different account'));
651
+ console.log(chalk.gray(' 2. ') + chalk.cyan('Start fresh') + chalk.gray(' - Clear local keys, enter new ones (for client projects)'));
606
652
  console.log(chalk.gray(' 3. ') + chalk.cyan('Skip') + chalk.gray(' - Don\'t provision, I\'ll do it manually\n'));
607
653
 
608
654
  let keyChoice = '';
@@ -612,23 +658,42 @@ export async function scaffold(): Promise<void> {
612
658
 
613
659
  if (keyChoice === '3') {
614
660
  console.log(chalk.gray('\n Skipping auto-provisioning.\n'));
661
+ } else if (keyChoice === '2') {
662
+ // FIX: Actually clear keys for client projects
663
+ console.log(chalk.yellow('\n Clearing local keys for fresh start...\n'));
664
+ clearAllServiceKeys();
665
+ console.log(chalk.green(' ✓ Local keys cleared'));
666
+ console.log(chalk.gray(' You\'ll be prompted to enter keys for each service.\n'));
667
+
668
+ // Initialize git first
669
+ try {
670
+ execSync('git init', { cwd, stdio: 'pipe' });
671
+ execSync('git add .', { cwd, stdio: 'pipe' });
672
+ execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
673
+ } catch {
674
+ // Git might already be initialized
675
+ }
676
+
677
+ provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
615
678
  } else {
616
- if (keyChoice === '1' && hasServerKeys) {
617
- // Sync server keys to local storage for this session
618
- syncKeysToLocal(serverKeys!);
619
- console.log(chalk.green('\n Using saved keys from CodeBakers account\n'));
620
- } else if (keyChoice === '2') {
621
- // Clear local keys so provision.ts will prompt for new ones
622
- console.log(chalk.gray('\n You\'ll be prompted to enter keys for each service.\n'));
679
+ // Option 1: Use saved keys
680
+ if (serverKeyCount > 0 && serverKeys) {
681
+ // Sync server keys to local storage
682
+ console.log(chalk.white('\n Syncing keys from CodeBakers account...\n'));
683
+ const syncResult = syncServiceKeys(serverKeys);
684
+ displaySyncResults(syncResult);
685
+ console.log(chalk.green('\n Keys synced from CodeBakers account\n'));
686
+ } else {
687
+ console.log(chalk.green('\n ✓ Using locally stored keys\n'));
623
688
  }
624
689
 
625
- // Initialize git first if not already
690
+ // Initialize git first
626
691
  try {
627
692
  execSync('git init', { cwd, stdio: 'pipe' });
628
693
  execSync('git add .', { cwd, stdio: 'pipe' });
629
694
  execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
630
695
  } catch {
631
- // Git might already be initialized or have issues
696
+ // Git might already be initialized
632
697
  }
633
698
 
634
699
  provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
@@ -638,30 +703,41 @@ export async function scaffold(): Promise<void> {
638
703
  console.log(chalk.gray('\n No saved keys found. You\'ll be prompted to enter keys for each service.\n'));
639
704
  console.log(chalk.gray(' Tip: Save keys in your CodeBakers dashboard to auto-provision future projects!\n'));
640
705
 
641
- // Initialize git first if not already
706
+ // Initialize git first
642
707
  try {
643
708
  execSync('git init', { cwd, stdio: 'pipe' });
644
709
  execSync('git add .', { cwd, stdio: 'pipe' });
645
710
  execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
646
711
  } catch {
647
- // Git might already be initialized or have issues
712
+ // Git might already be initialized
648
713
  }
649
714
 
650
715
  provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
651
716
  }
652
717
 
653
- // Update .env.local with Supabase credentials if available
718
+ // Write ALL service keys to .env.local
719
+ const additionalVars: Record<string, string> = {};
720
+
721
+ // Add Supabase project-specific vars if provisioned
654
722
  if (provisionResult.supabase) {
655
- const envPath = join(cwd, '.env.local');
656
- let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
723
+ additionalVars['NEXT_PUBLIC_SUPABASE_URL'] = provisionResult.supabase.apiUrl;
724
+ additionalVars['NEXT_PUBLIC_SUPABASE_ANON_KEY'] = provisionResult.supabase.anonKey;
725
+ additionalVars['SUPABASE_PROJECT_ID'] = provisionResult.supabase.projectId;
726
+ }
657
727
 
658
- // Replace placeholder values with actual credentials
659
- envContent = envContent
660
- .replace('your-project-id.supabase.co', provisionResult.supabase.apiUrl.replace('https://', ''))
661
- .replace('your-anon-key', provisionResult.supabase.anonKey || 'your-anon-key');
728
+ // Add Vercel project-specific vars if provisioned
729
+ if (provisionResult.vercel) {
730
+ additionalVars['VERCEL_PROJECT_ID'] = provisionResult.vercel.projectId;
731
+ }
732
+
733
+ // Write all keys to .env.local
734
+ const { written } = writeKeysToEnvFile(cwd, {
735
+ includeEmpty: false,
736
+ additionalVars,
737
+ });
662
738
 
663
- writeFileSync(envPath, envContent);
664
- console.log(chalk.green('Updated .env.local with Supabase credentials!\n'));
739
+ if (written > 0 || Object.keys(additionalVars).length > 0) {
740
+ console.log(chalk.green(`\nWrote ${written} service keys and ${Object.keys(additionalVars).length} project configs to .env.local\n`));
665
741
  }
666
742
  }
667
743
 
@@ -704,32 +780,63 @@ export async function scaffold(): Promise<void> {
704
780
  console.log('');
705
781
 
706
782
  console.log(chalk.white(' Next steps:\n'));
783
+
784
+ // Show appropriate next steps based on what was provisioned
785
+ let stepNum = 1;
786
+
787
+ if (!provisionResult.supabase) {
788
+ if (isBeginnerMode) {
789
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.white('Set up Supabase (free database + login):'));
790
+ console.log(chalk.gray(' Go to https://supabase.com → Create free account → New Project'));
791
+ console.log('');
792
+ stepNum++;
793
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.white('Connect your project:'));
794
+ console.log(chalk.gray(' Open .env.local file and paste your Supabase credentials'));
795
+ console.log(chalk.gray(' (Found in Supabase: Settings → API)'));
796
+ console.log('');
797
+ stepNum++;
798
+ } else {
799
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.gray('Create Supabase project at https://supabase.com'));
800
+ stepNum++;
801
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.gray('Update .env.local with Supabase credentials'));
802
+ stepNum++;
803
+ }
804
+ }
805
+
806
+ // CRITICAL: Add db:push step
807
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.white('Push database schema:'));
808
+ console.log(chalk.gray(' npx drizzle-kit db:push'));
809
+ console.log('');
810
+ stepNum++;
811
+
812
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.white('Start your app:'));
813
+ console.log(chalk.gray(' npm run dev'));
707
814
  if (isBeginnerMode) {
708
- console.log(chalk.cyan(' 1. ') + chalk.white('Set up Supabase (free database + login):'));
709
- console.log(chalk.gray(' Go to https://supabase.com → Create free account → New Project'));
710
- console.log('');
711
- console.log(chalk.cyan(' 2. ') + chalk.white('Connect your project:'));
712
- console.log(chalk.gray(' Open .env.local file and paste your Supabase credentials'));
713
- console.log(chalk.gray(' (Found in Supabase: Settings → API)'));
714
- console.log('');
715
- console.log(chalk.cyan(' 3. ') + chalk.white('Start your app:'));
716
- console.log(chalk.gray(' Run: npm run dev'));
717
815
  console.log(chalk.gray(' Open: http://localhost:3000 in your browser'));
816
+ }
817
+ console.log('');
818
+ stepNum++;
819
+
820
+ console.log(chalk.cyan(` ${stepNum}. `) + chalk.white('Start building!'));
821
+ console.log(chalk.gray(' Tell your AI: "Build me a [feature]"'));
822
+ if (isBeginnerMode) {
823
+ console.log(chalk.gray(' The AI already has all the patterns loaded!'));
824
+ }
825
+ console.log('');
826
+
827
+ // Show provisioning summary if any services were created
828
+ if (provisionResult.github || provisionResult.supabase || provisionResult.vercel) {
829
+ console.log(chalk.white(' Provisioned services:\n'));
830
+ if (provisionResult.github) {
831
+ console.log(chalk.green(' ✅ GitHub: ') + chalk.gray(provisionResult.github.repoUrl));
832
+ }
833
+ if (provisionResult.supabase) {
834
+ console.log(chalk.green(' ✅ Supabase: ') + chalk.gray(provisionResult.supabase.projectUrl));
835
+ }
836
+ if (provisionResult.vercel) {
837
+ console.log(chalk.green(' ✅ Vercel: ') + chalk.gray(provisionResult.vercel.projectUrl));
838
+ }
718
839
  console.log('');
719
- console.log(chalk.cyan(' 4. ') + chalk.white('Start building!'));
720
- console.log(chalk.gray(' Tell your AI: "Build me a [feature]"'));
721
- console.log(chalk.gray(' The AI already has all the patterns loaded!\n'));
722
- } else {
723
- console.log(chalk.cyan(' 1. ') + chalk.gray('Update .env.local with your Supabase credentials'));
724
- console.log(chalk.cyan(' 2. ') + chalk.gray('Run `npm run dev` to start development'));
725
- console.log(chalk.cyan(' 3. ') + chalk.gray('Tell your AI what to build - patterns are already loaded!\n'));
726
-
727
- console.log(chalk.white(' Supabase setup:\n'));
728
- console.log(chalk.gray(' 1. Create a project at https://supabase.com'));
729
- console.log(chalk.gray(' 2. Go to Settings → API'));
730
- console.log(chalk.gray(' 3. Copy URL and anon key to .env.local'));
731
- console.log(chalk.gray(' 4. Go to Settings → Database → Connection string'));
732
- console.log(chalk.gray(' 5. Copy DATABASE_URL to .env.local\n'));
733
840
  }
734
841
 
735
842
  } catch (error) {
@@ -2,7 +2,8 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { createInterface } from 'readline';
4
4
  import { execSync } from 'child_process';
5
- import { setApiKey, getApiKey, getApiUrl } from '../config.js';
5
+ import { setApiKey, getApiKey, getApiUrl, syncServiceKeys, SERVICE_KEY_LABELS, type ServiceName } from '../config.js';
6
+ import { validateApiKey, formatApiError, checkForUpdates, getCliVersion, type ApiError } from '../lib/api.js';
6
7
 
7
8
  function prompt(question: string): Promise<string> {
8
9
  const rl = createInterface({
@@ -23,6 +24,17 @@ export async function setup(): Promise<void> {
23
24
  console.log(chalk.blue(' ║') + chalk.white(' CodeBakers One-Time Setup ') + chalk.blue('║'));
24
25
  console.log(chalk.blue(' ╚══════════════════════════════════════╝\n'));
25
26
 
27
+ // Check CLI version
28
+ const version = getCliVersion();
29
+ console.log(chalk.gray(` CLI Version: ${version}\n`));
30
+
31
+ // Check for updates
32
+ const updateInfo = await checkForUpdates();
33
+ if (updateInfo?.updateAvailable) {
34
+ console.log(chalk.yellow(` ⚠️ Update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}`));
35
+ console.log(chalk.gray(' Run: npm install -g @codebakers/cli@latest\n'));
36
+ }
37
+
26
38
  // Check if already set up
27
39
  const existingKey = getApiKey();
28
40
  if (existingKey) {
@@ -46,29 +58,22 @@ export async function setup(): Promise<void> {
46
58
  process.exit(1);
47
59
  }
48
60
 
49
- // Validate API key
61
+ // Validate API key using shared validation
50
62
  const spinner = ora('Validating API key...').start();
51
63
 
52
64
  try {
53
- const apiUrl = getApiUrl();
54
- const response = await fetch(`${apiUrl}/api/patterns`, {
55
- method: 'GET',
56
- headers: {
57
- Authorization: `Bearer ${apiKey}`,
58
- },
59
- });
65
+ await validateApiKey(apiKey);
66
+ spinner.succeed('API key validated');
67
+ } catch (error) {
68
+ spinner.fail('Invalid API key');
60
69
 
61
- if (!response.ok) {
62
- spinner.fail('Invalid API key');
63
- const error = await response.json().catch(() => ({}));
64
- console.log(chalk.red(`\n ${error.error || 'API key validation failed'}\n`));
65
- process.exit(1);
70
+ if (error && typeof error === 'object' && 'recoverySteps' in error) {
71
+ console.log(chalk.red(`\n ${formatApiError(error as ApiError)}\n`));
72
+ } else {
73
+ const message = error instanceof Error ? error.message : 'API key validation failed';
74
+ console.log(chalk.red(`\n ${message}\n`));
66
75
  }
67
76
 
68
- spinner.succeed('API key validated');
69
- } catch {
70
- spinner.fail('Could not connect to CodeBakers');
71
- console.log(chalk.red('\n Check your internet connection and try again.\n'));
72
77
  process.exit(1);
73
78
  }
74
79
 
@@ -76,15 +81,55 @@ export async function setup(): Promise<void> {
76
81
  setApiKey(apiKey);
77
82
  console.log(chalk.green(' ✓ API key saved\n'));
78
83
 
84
+ // Step 2: Sync service keys from server
85
+ console.log(chalk.white(' Step 2: Syncing service keys...\n'));
86
+
87
+ const syncSpinner = ora('Fetching service keys from your account...').start();
88
+
89
+ try {
90
+ const apiUrl = getApiUrl();
91
+ const response = await fetch(`${apiUrl}/api/cli/service-keys`, {
92
+ headers: { 'Authorization': `Bearer ${apiKey}` },
93
+ });
94
+
95
+ if (response.ok) {
96
+ const data = await response.json();
97
+ const serverKeys = data.data || data;
98
+
99
+ const result = syncServiceKeys(serverKeys);
100
+ const totalSynced = result.added.length + result.updated.length;
101
+
102
+ if (totalSynced > 0) {
103
+ syncSpinner.succeed(`Synced ${totalSynced} service keys`);
104
+
105
+ // Show which keys were synced
106
+ for (const keyName of [...result.added, ...result.updated]) {
107
+ console.log(chalk.green(` ✓ ${SERVICE_KEY_LABELS[keyName as ServiceName]}`));
108
+ }
109
+ console.log('');
110
+ } else if (result.unchanged.length > 0) {
111
+ syncSpinner.succeed(`${result.unchanged.length} service keys already in sync`);
112
+ } else {
113
+ syncSpinner.succeed('No service keys configured in your account');
114
+ console.log(chalk.gray(' Tip: Add keys at https://codebakers.ai/settings\n'));
115
+ }
116
+ } else {
117
+ syncSpinner.warn('Could not sync service keys');
118
+ console.log(chalk.gray(' You can add keys later in the scaffold wizard.\n'));
119
+ }
120
+ } catch {
121
+ syncSpinner.warn('Could not sync service keys');
122
+ console.log(chalk.gray(' You can add keys later in the scaffold wizard.\n'));
123
+ }
124
+
79
125
  showFinalInstructions();
80
126
  }
81
127
 
82
128
  function showFinalInstructions(): void {
83
129
  const isWindows = process.platform === 'win32';
84
130
 
85
- console.log(chalk.green('\n ✅ API key saved!\n'));
86
131
  console.log(chalk.blue(' ══════════════════════════════════════════════════════════'));
87
- console.log(chalk.white.bold('\n STEP 2: Connecting CodeBakers to Claude...\n'));
132
+ console.log(chalk.white.bold('\n STEP 3: Connecting CodeBakers to Claude...\n'));
88
133
  console.log(chalk.blue(' ══════════════════════════════════════════════════════════\n'));
89
134
 
90
135
  // Auto-install MCP server
@@ -0,0 +1,110 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { getApiKey, getApiUrl } from '../config.js';
6
+ import { checkForUpdates, getCliVersion } from '../lib/api.js';
7
+
8
+ interface ContentResponse {
9
+ version: string;
10
+ router: string;
11
+ modules: Record<string, string>;
12
+ }
13
+
14
+ /**
15
+ * Upgrade CodeBakers patterns to the latest version
16
+ */
17
+ export async function upgrade(): Promise<void> {
18
+ console.log(chalk.blue('\n CodeBakers Upgrade\n'));
19
+
20
+ const cwd = process.cwd();
21
+ const claudeMdPath = join(cwd, 'CLAUDE.md');
22
+ const claudeDir = join(cwd, '.claude');
23
+
24
+ // Check if this is a CodeBakers project
25
+ if (!existsSync(claudeMdPath) && !existsSync(claudeDir)) {
26
+ console.log(chalk.yellow(' No CodeBakers installation found in this directory.\n'));
27
+ console.log(chalk.gray(' Run `codebakers install` to set up patterns first.\n'));
28
+ return;
29
+ }
30
+
31
+ // Check for CLI updates
32
+ console.log(chalk.gray(' Checking for CLI updates...\n'));
33
+ const updateInfo = await checkForUpdates();
34
+
35
+ if (updateInfo?.updateAvailable) {
36
+ console.log(chalk.yellow(` ⚠️ CLI update available: ${updateInfo.currentVersion} → ${updateInfo.latestVersion}`));
37
+ console.log(chalk.gray(' Run: npm install -g @codebakers/cli@latest\n'));
38
+ } else {
39
+ console.log(chalk.green(` ✓ CLI is up to date (v${getCliVersion()})\n`));
40
+ }
41
+
42
+ // Check API key
43
+ const apiKey = getApiKey();
44
+ if (!apiKey) {
45
+ console.log(chalk.yellow(' Not logged in. Run `codebakers setup` first.\n'));
46
+ return;
47
+ }
48
+
49
+ // Fetch latest patterns
50
+ const spinner = ora('Fetching latest patterns...').start();
51
+
52
+ try {
53
+ const apiUrl = getApiUrl();
54
+ const response = await fetch(`${apiUrl}/api/content`, {
55
+ method: 'GET',
56
+ headers: {
57
+ 'Authorization': `Bearer ${apiKey}`,
58
+ },
59
+ });
60
+
61
+ if (!response.ok) {
62
+ const error = await response.json().catch(() => ({}));
63
+ throw new Error(error.error || 'Failed to fetch patterns');
64
+ }
65
+
66
+ const content: ContentResponse = await response.json();
67
+
68
+ spinner.succeed(`Patterns v${content.version} downloaded`);
69
+
70
+ // Count what we're updating
71
+ const moduleCount = Object.keys(content.modules).length;
72
+
73
+ console.log(chalk.gray(` Updating ${moduleCount} modules...\n`));
74
+
75
+ // Update CLAUDE.md
76
+ if (content.router) {
77
+ writeFileSync(claudeMdPath, content.router);
78
+ console.log(chalk.green(' ✓ Updated CLAUDE.md'));
79
+ }
80
+
81
+ // Update pattern modules
82
+ if (content.modules && Object.keys(content.modules).length > 0) {
83
+ if (!existsSync(claudeDir)) {
84
+ mkdirSync(claudeDir, { recursive: true });
85
+ }
86
+
87
+ for (const [name, data] of Object.entries(content.modules)) {
88
+ writeFileSync(join(claudeDir, name), data);
89
+ }
90
+
91
+ console.log(chalk.green(` ✓ Updated ${moduleCount} modules in .claude/`));
92
+ }
93
+
94
+ console.log(chalk.green(`\n ✅ Upgraded to patterns v${content.version}!\n`));
95
+
96
+ // Show what's new if available
97
+ console.log(chalk.gray(' Changes take effect in your next AI session.\n'));
98
+
99
+ } catch (error) {
100
+ spinner.fail('Upgrade failed');
101
+ const message = error instanceof Error ? error.message : 'Unknown error';
102
+ console.log(chalk.red(`\n Error: ${message}\n`));
103
+
104
+ if (message.includes('401') || message.includes('Invalid')) {
105
+ console.log(chalk.gray(' Your API key may have expired. Run `codebakers setup` to reconfigure.\n'));
106
+ }
107
+
108
+ process.exit(1);
109
+ }
110
+ }