@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/src/commands/scaffold.ts
CHANGED
|
@@ -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 {
|
|
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<
|
|
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
|
-
|
|
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
|
|
577
|
-
const
|
|
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 (
|
|
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 (
|
|
587
|
-
console.log(chalk.gray(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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 (
|
|
594
|
-
console.log(chalk.gray(
|
|
595
|
-
|
|
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('
|
|
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
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
console.log(chalk.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
console.log(chalk.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
656
|
-
|
|
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
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
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
|
-
|
|
664
|
-
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`));
|
|
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) {
|
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
|
+
}
|