@codebakers/cli 1.5.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 +221 -41
- 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 +257 -43
- 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/src/commands/scaffold.ts
CHANGED
|
@@ -5,9 +5,47 @@ 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 {
|
|
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
|
|
|
23
|
+
/**
|
|
24
|
+
* Fetch ALL service keys from CodeBakers server
|
|
25
|
+
*/
|
|
26
|
+
async function fetchServerKeys(): Promise<Partial<Record<ServiceName, string>> | null> {
|
|
27
|
+
const apiKey = getApiKey();
|
|
28
|
+
if (!apiKey) return null;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const apiUrl = getApiUrl();
|
|
32
|
+
const response = await fetch(`${apiUrl}/api/cli/service-keys`, {
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${apiKey}`,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (response.ok) {
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
// Handle wrapped response { data: {...} } or direct response
|
|
41
|
+
return data.data || data;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// Server unreachable or error
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
11
49
|
// Cursor IDE configuration templates
|
|
12
50
|
const CURSORRULES_TEMPLATE = `# CODEBAKERS CURSOR RULES
|
|
13
51
|
# Zero-friction AI assistance - everything is automatic
|
|
@@ -262,6 +300,58 @@ async function confirm(question: string): Promise<boolean> {
|
|
|
262
300
|
return answer.toLowerCase() !== 'n';
|
|
263
301
|
}
|
|
264
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
|
+
|
|
265
355
|
/**
|
|
266
356
|
* Scaffold a new project with full structure
|
|
267
357
|
*/
|
|
@@ -443,8 +533,6 @@ export async function scaffold(): Promise<void> {
|
|
|
443
533
|
}
|
|
444
534
|
}
|
|
445
535
|
|
|
446
|
-
spinner.succeed('Project structure created!');
|
|
447
|
-
|
|
448
536
|
// Auto-install CodeBakers patterns
|
|
449
537
|
console.log(chalk.white('\n Installing CodeBakers patterns...\n'));
|
|
450
538
|
|
|
@@ -532,29 +620,124 @@ export async function scaffold(): Promise<void> {
|
|
|
532
620
|
let provisionResult: ProvisionResult = {};
|
|
533
621
|
|
|
534
622
|
if (wantProvision) {
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
//
|
|
623
|
+
// Check for saved keys in CodeBakers back office
|
|
624
|
+
const serverKeys = await fetchServerKeys();
|
|
625
|
+
const serverKeyCount = serverKeys ? Object.values(serverKeys).filter(v => v).length : 0;
|
|
626
|
+
const localKeyCount = getConfiguredServiceKeys().length;
|
|
627
|
+
|
|
628
|
+
if (serverKeyCount > 0 || localKeyCount > 0) {
|
|
629
|
+
// Show which keys are available
|
|
630
|
+
console.log(chalk.white('\n Available service keys:\n'));
|
|
631
|
+
|
|
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
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (localKeyCount > 0) {
|
|
642
|
+
console.log(chalk.gray(` Stored locally (${localKeyCount} keys):`));
|
|
643
|
+
displayKeysSummary('local');
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
console.log('');
|
|
647
|
+
|
|
648
|
+
// Ask which keys to use
|
|
649
|
+
console.log(chalk.white(' Which keys would you like to use?\n'));
|
|
650
|
+
console.log(chalk.gray(' 1. ') + chalk.cyan('Use saved keys') + chalk.gray(' - Use keys from your account/local storage'));
|
|
651
|
+
console.log(chalk.gray(' 2. ') + chalk.cyan('Start fresh') + chalk.gray(' - Clear local keys, enter new ones (for client projects)'));
|
|
652
|
+
console.log(chalk.gray(' 3. ') + chalk.cyan('Skip') + chalk.gray(' - Don\'t provision, I\'ll do it manually\n'));
|
|
653
|
+
|
|
654
|
+
let keyChoice = '';
|
|
655
|
+
while (!['1', '2', '3'].includes(keyChoice)) {
|
|
656
|
+
keyChoice = await prompt(' Enter 1, 2, or 3: ');
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (keyChoice === '3') {
|
|
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`);
|
|
678
|
+
} else {
|
|
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'));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Initialize git first
|
|
691
|
+
try {
|
|
692
|
+
execSync('git init', { cwd, stdio: 'pipe' });
|
|
693
|
+
execSync('git add .', { cwd, stdio: 'pipe' });
|
|
694
|
+
execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
|
|
695
|
+
} catch {
|
|
696
|
+
// Git might already be initialized
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
|
|
700
|
+
}
|
|
701
|
+
} else {
|
|
702
|
+
// No saved keys - proceed with provisioning (will prompt for keys)
|
|
703
|
+
console.log(chalk.gray('\n No saved keys found. You\'ll be prompted to enter keys for each service.\n'));
|
|
704
|
+
console.log(chalk.gray(' Tip: Save keys in your CodeBakers dashboard to auto-provision future projects!\n'));
|
|
705
|
+
|
|
706
|
+
// Initialize git first
|
|
707
|
+
try {
|
|
708
|
+
execSync('git init', { cwd, stdio: 'pipe' });
|
|
709
|
+
execSync('git add .', { cwd, stdio: 'pipe' });
|
|
710
|
+
execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
|
|
711
|
+
} catch {
|
|
712
|
+
// Git might already be initialized
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
|
|
542
716
|
}
|
|
543
717
|
|
|
544
|
-
|
|
718
|
+
// Write ALL service keys to .env.local
|
|
719
|
+
const additionalVars: Record<string, string> = {};
|
|
545
720
|
|
|
546
|
-
//
|
|
721
|
+
// Add Supabase project-specific vars if provisioned
|
|
547
722
|
if (provisionResult.supabase) {
|
|
548
|
-
|
|
549
|
-
|
|
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
|
+
}
|
|
727
|
+
|
|
728
|
+
// Add Vercel project-specific vars if provisioned
|
|
729
|
+
if (provisionResult.vercel) {
|
|
730
|
+
additionalVars['VERCEL_PROJECT_ID'] = provisionResult.vercel.projectId;
|
|
731
|
+
}
|
|
550
732
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
733
|
+
// Write all keys to .env.local
|
|
734
|
+
const { written } = writeKeysToEnvFile(cwd, {
|
|
735
|
+
includeEmpty: false,
|
|
736
|
+
additionalVars,
|
|
737
|
+
});
|
|
555
738
|
|
|
556
|
-
|
|
557
|
-
console.log(chalk.green(
|
|
739
|
+
if (written > 0 || Object.keys(additionalVars).length > 0) {
|
|
740
|
+
console.log(chalk.green(`\n ✅ Wrote ${written} service keys and ${Object.keys(additionalVars).length} project configs to .env.local\n`));
|
|
558
741
|
}
|
|
559
742
|
}
|
|
560
743
|
|
|
@@ -597,32 +780,63 @@ export async function scaffold(): Promise<void> {
|
|
|
597
780
|
console.log('');
|
|
598
781
|
|
|
599
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'));
|
|
600
814
|
if (isBeginnerMode) {
|
|
601
|
-
console.log(chalk.cyan(' 1. ') + chalk.white('Set up Supabase (free database + login):'));
|
|
602
|
-
console.log(chalk.gray(' Go to https://supabase.com → Create free account → New Project'));
|
|
603
|
-
console.log('');
|
|
604
|
-
console.log(chalk.cyan(' 2. ') + chalk.white('Connect your project:'));
|
|
605
|
-
console.log(chalk.gray(' Open .env.local file and paste your Supabase credentials'));
|
|
606
|
-
console.log(chalk.gray(' (Found in Supabase: Settings → API)'));
|
|
607
|
-
console.log('');
|
|
608
|
-
console.log(chalk.cyan(' 3. ') + chalk.white('Start your app:'));
|
|
609
|
-
console.log(chalk.gray(' Run: npm run dev'));
|
|
610
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
|
+
}
|
|
611
839
|
console.log('');
|
|
612
|
-
console.log(chalk.cyan(' 4. ') + chalk.white('Start building!'));
|
|
613
|
-
console.log(chalk.gray(' Tell your AI: "Build me a [feature]"'));
|
|
614
|
-
console.log(chalk.gray(' The AI already has all the patterns loaded!\n'));
|
|
615
|
-
} else {
|
|
616
|
-
console.log(chalk.cyan(' 1. ') + chalk.gray('Update .env.local with your Supabase credentials'));
|
|
617
|
-
console.log(chalk.cyan(' 2. ') + chalk.gray('Run `npm run dev` to start development'));
|
|
618
|
-
console.log(chalk.cyan(' 3. ') + chalk.gray('Tell your AI what to build - patterns are already loaded!\n'));
|
|
619
|
-
|
|
620
|
-
console.log(chalk.white(' Supabase setup:\n'));
|
|
621
|
-
console.log(chalk.gray(' 1. Create a project at https://supabase.com'));
|
|
622
|
-
console.log(chalk.gray(' 2. Go to Settings → API'));
|
|
623
|
-
console.log(chalk.gray(' 3. Copy URL and anon key to .env.local'));
|
|
624
|
-
console.log(chalk.gray(' 4. Go to Settings → Database → Connection string'));
|
|
625
|
-
console.log(chalk.gray(' 5. Copy DATABASE_URL to .env.local\n'));
|
|
626
840
|
}
|
|
627
841
|
|
|
628
842
|
} catch (error) {
|
package/src/commands/setup.ts
CHANGED
|
@@ -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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
+
}
|