@codebakers/cli 1.4.6 → 1.5.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.
@@ -0,0 +1,51 @@
1
+ export interface ProvisionResult {
2
+ github?: {
3
+ repoUrl: string;
4
+ cloneUrl: string;
5
+ };
6
+ supabase?: {
7
+ projectId: string;
8
+ projectUrl: string;
9
+ apiUrl: string;
10
+ anonKey: string;
11
+ };
12
+ vercel?: {
13
+ projectId: string;
14
+ projectUrl: string;
15
+ };
16
+ }
17
+ /**
18
+ * Create a GitHub repository
19
+ */
20
+ export declare function createGitHubRepo(projectName: string, description?: string): Promise<{
21
+ repoUrl: string;
22
+ cloneUrl: string;
23
+ } | null>;
24
+ /**
25
+ * Create a Supabase project
26
+ */
27
+ export declare function createSupabaseProject(projectName: string, organizationId?: string): Promise<{
28
+ projectId: string;
29
+ projectUrl: string;
30
+ apiUrl: string;
31
+ anonKey: string;
32
+ } | null>;
33
+ /**
34
+ * Create a Vercel project
35
+ */
36
+ export declare function createVercelProject(projectName: string, gitRepoUrl?: string): Promise<{
37
+ projectId: string;
38
+ projectUrl: string;
39
+ } | null>;
40
+ /**
41
+ * Full provisioning flow - create all services
42
+ */
43
+ export declare function provisionAll(projectName: string, description?: string): Promise<ProvisionResult>;
44
+ /**
45
+ * Check which services have keys configured
46
+ */
47
+ export declare function getConfiguredServices(): {
48
+ github: boolean;
49
+ supabase: boolean;
50
+ vercel: boolean;
51
+ };
@@ -0,0 +1,309 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createGitHubRepo = createGitHubRepo;
7
+ exports.createSupabaseProject = createSupabaseProject;
8
+ exports.createVercelProject = createVercelProject;
9
+ exports.provisionAll = provisionAll;
10
+ exports.getConfiguredServices = getConfiguredServices;
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const ora_1 = __importDefault(require("ora"));
13
+ const child_process_1 = require("child_process");
14
+ const readline_1 = require("readline");
15
+ const config_js_1 = require("../config.js");
16
+ function prompt(question) {
17
+ const rl = (0, readline_1.createInterface)({
18
+ input: process.stdin,
19
+ output: process.stdout,
20
+ });
21
+ return new Promise((resolve) => {
22
+ rl.question(question, (answer) => {
23
+ rl.close();
24
+ resolve(answer.trim());
25
+ });
26
+ });
27
+ }
28
+ /**
29
+ * Check if a service key is configured, prompt if not
30
+ */
31
+ async function ensureServiceKey(service, instructions) {
32
+ let key = (0, config_js_1.getServiceKey)(service);
33
+ if (!key) {
34
+ console.log(chalk_1.default.yellow(`\n No ${service} API key configured.\n`));
35
+ console.log(chalk_1.default.gray(instructions));
36
+ const inputKey = await prompt(`\n ${service} API key (or press Enter to skip): `);
37
+ if (inputKey) {
38
+ (0, config_js_1.setServiceKey)(service, inputKey);
39
+ key = inputKey;
40
+ console.log(chalk_1.default.green(` ✓ ${service} key saved\n`));
41
+ }
42
+ else {
43
+ console.log(chalk_1.default.gray(` Skipping ${service} provisioning.\n`));
44
+ return null;
45
+ }
46
+ }
47
+ return key;
48
+ }
49
+ /**
50
+ * Create a GitHub repository
51
+ */
52
+ async function createGitHubRepo(projectName, description = '') {
53
+ // Check if gh CLI is available
54
+ try {
55
+ (0, child_process_1.execSync)('gh --version', { stdio: 'pipe' });
56
+ }
57
+ catch {
58
+ console.log(chalk_1.default.yellow(' GitHub CLI (gh) not found. Install it from: https://cli.github.com\n'));
59
+ return null;
60
+ }
61
+ // Check if authenticated
62
+ try {
63
+ (0, child_process_1.execSync)('gh auth status', { stdio: 'pipe' });
64
+ }
65
+ catch {
66
+ console.log(chalk_1.default.yellow(' Not logged into GitHub CLI.\n'));
67
+ console.log(chalk_1.default.gray(' Run: gh auth login\n'));
68
+ return null;
69
+ }
70
+ const spinner = (0, ora_1.default)('Creating GitHub repository...').start();
71
+ try {
72
+ // Create the repo
73
+ const result = (0, child_process_1.execSync)(`gh repo create ${projectName} --public --description "${description}" --source . --remote origin --push`, { encoding: 'utf-8', stdio: 'pipe' });
74
+ // Get the repo URL
75
+ const repoUrl = (0, child_process_1.execSync)('gh repo view --json url -q .url', { encoding: 'utf-8' }).trim();
76
+ const cloneUrl = `${repoUrl}.git`;
77
+ spinner.succeed('GitHub repository created!');
78
+ console.log(chalk_1.default.gray(` ${repoUrl}\n`));
79
+ return { repoUrl, cloneUrl };
80
+ }
81
+ catch (error) {
82
+ spinner.fail('Failed to create GitHub repository');
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ if (message.includes('already exists')) {
85
+ console.log(chalk_1.default.yellow(' Repository already exists. Skipping.\n'));
86
+ }
87
+ else {
88
+ console.log(chalk_1.default.red(` Error: ${message}\n`));
89
+ }
90
+ return null;
91
+ }
92
+ }
93
+ /**
94
+ * Create a Supabase project
95
+ */
96
+ async function createSupabaseProject(projectName, organizationId) {
97
+ const accessToken = await ensureServiceKey('supabase', ' Get your access token from: https://supabase.com/dashboard/account/tokens');
98
+ if (!accessToken)
99
+ return null;
100
+ const spinner = (0, ora_1.default)('Creating Supabase project...').start();
101
+ try {
102
+ // Get organization if not provided
103
+ if (!organizationId) {
104
+ const orgsResponse = await fetch('https://api.supabase.com/v1/organizations', {
105
+ headers: {
106
+ 'Authorization': `Bearer ${accessToken}`,
107
+ },
108
+ });
109
+ if (!orgsResponse.ok) {
110
+ throw new Error('Failed to fetch organizations. Check your access token.');
111
+ }
112
+ const orgs = await orgsResponse.json();
113
+ if (orgs.length === 0) {
114
+ throw new Error('No organizations found. Create one at supabase.com first.');
115
+ }
116
+ organizationId = orgs[0].id;
117
+ }
118
+ // Generate a random password for the database
119
+ const dbPassword = Math.random().toString(36).slice(-16) +
120
+ Math.random().toString(36).slice(-16).toUpperCase() +
121
+ '!1';
122
+ // Create the project
123
+ const createResponse = await fetch('https://api.supabase.com/v1/projects', {
124
+ method: 'POST',
125
+ headers: {
126
+ 'Authorization': `Bearer ${accessToken}`,
127
+ 'Content-Type': 'application/json',
128
+ },
129
+ body: JSON.stringify({
130
+ name: projectName,
131
+ organization_id: organizationId,
132
+ region: 'us-east-1',
133
+ plan: 'free',
134
+ db_pass: dbPassword,
135
+ }),
136
+ });
137
+ if (!createResponse.ok) {
138
+ const error = await createResponse.json();
139
+ throw new Error(error.message || 'Failed to create project');
140
+ }
141
+ const project = await createResponse.json();
142
+ spinner.succeed('Supabase project created!');
143
+ // Wait for project to be ready (it takes a moment)
144
+ const waitSpinner = (0, ora_1.default)('Waiting for project to be ready...').start();
145
+ let projectReady = false;
146
+ let attempts = 0;
147
+ let projectDetails = {};
148
+ while (!projectReady && attempts < 30) {
149
+ await new Promise(resolve => setTimeout(resolve, 2000));
150
+ const statusResponse = await fetch(`https://api.supabase.com/v1/projects/${project.id}`, {
151
+ headers: {
152
+ 'Authorization': `Bearer ${accessToken}`,
153
+ },
154
+ });
155
+ if (statusResponse.ok) {
156
+ projectDetails = await statusResponse.json();
157
+ if (projectDetails.api_url) {
158
+ projectReady = true;
159
+ }
160
+ }
161
+ attempts++;
162
+ }
163
+ if (!projectReady) {
164
+ waitSpinner.warn('Project created but may not be fully ready yet.');
165
+ }
166
+ else {
167
+ waitSpinner.succeed('Project ready!');
168
+ }
169
+ const projectUrl = `https://supabase.com/dashboard/project/${project.id}`;
170
+ console.log(chalk_1.default.gray(` ${projectUrl}\n`));
171
+ // Get the anon key
172
+ const keysResponse = await fetch(`https://api.supabase.com/v1/projects/${project.id}/api-keys`, {
173
+ headers: {
174
+ 'Authorization': `Bearer ${accessToken}`,
175
+ },
176
+ });
177
+ let anonKey = '';
178
+ if (keysResponse.ok) {
179
+ const keys = await keysResponse.json();
180
+ const anonKeyObj = keys.find((k) => k.name === 'anon');
181
+ anonKey = anonKeyObj?.api_key || '';
182
+ }
183
+ return {
184
+ projectId: project.id,
185
+ projectUrl,
186
+ apiUrl: projectDetails.api_url || `https://${project.id}.supabase.co`,
187
+ anonKey,
188
+ };
189
+ }
190
+ catch (error) {
191
+ spinner.fail('Failed to create Supabase project');
192
+ const message = error instanceof Error ? error.message : String(error);
193
+ console.log(chalk_1.default.red(` Error: ${message}\n`));
194
+ return null;
195
+ }
196
+ }
197
+ /**
198
+ * Create a Vercel project
199
+ */
200
+ async function createVercelProject(projectName, gitRepoUrl) {
201
+ const accessToken = await ensureServiceKey('vercel', ' Get your token from: https://vercel.com/account/tokens');
202
+ if (!accessToken)
203
+ return null;
204
+ const spinner = (0, ora_1.default)('Creating Vercel project...').start();
205
+ try {
206
+ // Create the project
207
+ const createResponse = await fetch('https://api.vercel.com/v10/projects', {
208
+ method: 'POST',
209
+ headers: {
210
+ 'Authorization': `Bearer ${accessToken}`,
211
+ 'Content-Type': 'application/json',
212
+ },
213
+ body: JSON.stringify({
214
+ name: projectName,
215
+ framework: 'nextjs',
216
+ ...(gitRepoUrl && {
217
+ gitRepository: {
218
+ type: 'github',
219
+ repo: gitRepoUrl.replace('https://github.com/', ''),
220
+ },
221
+ }),
222
+ }),
223
+ });
224
+ if (!createResponse.ok) {
225
+ const error = await createResponse.json();
226
+ throw new Error(error.error?.message || 'Failed to create project');
227
+ }
228
+ const project = await createResponse.json();
229
+ spinner.succeed('Vercel project created!');
230
+ const projectUrl = `https://vercel.com/${project.accountId}/${project.name}`;
231
+ console.log(chalk_1.default.gray(` ${projectUrl}\n`));
232
+ return {
233
+ projectId: project.id,
234
+ projectUrl,
235
+ };
236
+ }
237
+ catch (error) {
238
+ spinner.fail('Failed to create Vercel project');
239
+ const message = error instanceof Error ? error.message : String(error);
240
+ if (message.includes('already exists')) {
241
+ console.log(chalk_1.default.yellow(' Project already exists. Skipping.\n'));
242
+ }
243
+ else {
244
+ console.log(chalk_1.default.red(` Error: ${message}\n`));
245
+ }
246
+ return null;
247
+ }
248
+ }
249
+ /**
250
+ * Full provisioning flow - create all services
251
+ */
252
+ async function provisionAll(projectName, description = '') {
253
+ console.log(chalk_1.default.blue('\n ══════════════════════════════════════════════════════════'));
254
+ console.log(chalk_1.default.white.bold(' 🚀 Auto-Provisioning Services\n'));
255
+ console.log(chalk_1.default.blue(' ══════════════════════════════════════════════════════════\n'));
256
+ const result = {};
257
+ // 1. GitHub
258
+ console.log(chalk_1.default.white(' Step 1: GitHub Repository\n'));
259
+ const github = await createGitHubRepo(projectName, description);
260
+ if (github) {
261
+ result.github = github;
262
+ }
263
+ // 2. Supabase
264
+ console.log(chalk_1.default.white(' Step 2: Supabase Project\n'));
265
+ const supabase = await createSupabaseProject(projectName);
266
+ if (supabase) {
267
+ result.supabase = supabase;
268
+ }
269
+ // 3. Vercel
270
+ console.log(chalk_1.default.white(' Step 3: Vercel Project\n'));
271
+ const vercel = await createVercelProject(projectName, result.github?.repoUrl);
272
+ if (vercel) {
273
+ result.vercel = vercel;
274
+ }
275
+ // Summary
276
+ console.log(chalk_1.default.blue(' ══════════════════════════════════════════════════════════'));
277
+ console.log(chalk_1.default.white.bold(' 📋 Provisioning Summary\n'));
278
+ console.log(chalk_1.default.blue(' ══════════════════════════════════════════════════════════\n'));
279
+ if (result.github) {
280
+ console.log(chalk_1.default.green(' ✅ GitHub: ') + chalk_1.default.gray(result.github.repoUrl));
281
+ }
282
+ else {
283
+ console.log(chalk_1.default.yellow(' ⏭️ GitHub: Skipped'));
284
+ }
285
+ if (result.supabase) {
286
+ console.log(chalk_1.default.green(' ✅ Supabase: ') + chalk_1.default.gray(result.supabase.projectUrl));
287
+ }
288
+ else {
289
+ console.log(chalk_1.default.yellow(' ⏭️ Supabase: Skipped'));
290
+ }
291
+ if (result.vercel) {
292
+ console.log(chalk_1.default.green(' ✅ Vercel: ') + chalk_1.default.gray(result.vercel.projectUrl));
293
+ }
294
+ else {
295
+ console.log(chalk_1.default.yellow(' ⏭️ Vercel: Skipped'));
296
+ }
297
+ console.log('');
298
+ return result;
299
+ }
300
+ /**
301
+ * Check which services have keys configured
302
+ */
303
+ function getConfiguredServices() {
304
+ return {
305
+ github: false, // GitHub uses gh CLI auth, not stored key
306
+ supabase: !!(0, config_js_1.getServiceKey)('supabase'),
307
+ vercel: !!(0, config_js_1.getServiceKey)('vercel'),
308
+ };
309
+ }
@@ -45,6 +45,7 @@ const path_1 = require("path");
45
45
  const child_process_1 = require("child_process");
46
46
  const templates = __importStar(require("../templates/nextjs-supabase.js"));
47
47
  const config_js_1 = require("../config.js");
48
+ const provision_js_1 = require("./provision.js");
48
49
  // Cursor IDE configuration templates
49
50
  const CURSORRULES_TEMPLATE = `# CODEBAKERS CURSOR RULES
50
51
  # Zero-friction AI assistance - everything is automatic
@@ -507,6 +508,35 @@ async function scaffold() {
507
508
  else {
508
509
  console.log(chalk_1.default.yellow(' ⚠️ Not logged in - run `codebakers setup` first to get patterns\n'));
509
510
  }
511
+ // Ask about auto-provisioning
512
+ console.log(chalk_1.default.white('\n Would you like to auto-provision your infrastructure?\n'));
513
+ console.log(chalk_1.default.gray(' This can create GitHub repo, Supabase database, and Vercel project automatically.'));
514
+ console.log(chalk_1.default.gray(' You\'ll need API keys for each service (one-time setup).\n'));
515
+ const wantProvision = await confirm(' Auto-provision services?');
516
+ let provisionResult = {};
517
+ if (wantProvision) {
518
+ // Initialize git first if not already
519
+ try {
520
+ (0, child_process_1.execSync)('git init', { cwd, stdio: 'pipe' });
521
+ (0, child_process_1.execSync)('git add .', { cwd, stdio: 'pipe' });
522
+ (0, child_process_1.execSync)('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
523
+ }
524
+ catch {
525
+ // Git might already be initialized or have issues
526
+ }
527
+ provisionResult = await (0, provision_js_1.provisionAll)(projectName, `${projectName} - Built with CodeBakers`);
528
+ // Update .env.local with Supabase credentials if available
529
+ if (provisionResult.supabase) {
530
+ const envPath = (0, path_1.join)(cwd, '.env.local');
531
+ let envContent = (0, fs_1.existsSync)(envPath) ? (0, fs_1.readFileSync)(envPath, 'utf-8') : '';
532
+ // Replace placeholder values with actual credentials
533
+ envContent = envContent
534
+ .replace('your-project-id.supabase.co', provisionResult.supabase.apiUrl.replace('https://', ''))
535
+ .replace('your-anon-key', provisionResult.supabase.anonKey || 'your-anon-key');
536
+ (0, fs_1.writeFileSync)(envPath, envContent);
537
+ console.log(chalk_1.default.green(' ✅ Updated .env.local with Supabase credentials!\n'));
538
+ }
539
+ }
510
540
  // Success message
511
541
  console.log(chalk_1.default.green(`
512
542
  ╔═══════════════════════════════════════════════════════════╗
package/dist/config.d.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  export type ExperienceLevel = 'beginner' | 'intermediate' | 'advanced';
2
+ interface ServiceKeys {
3
+ github: string | null;
4
+ supabase: string | null;
5
+ vercel: string | null;
6
+ }
2
7
  export declare function getApiKey(): string | null;
3
8
  export declare function setApiKey(key: string): void;
4
9
  export declare function clearApiKey(): void;
@@ -6,3 +11,9 @@ export declare function getApiUrl(): string;
6
11
  export declare function setApiUrl(url: string): void;
7
12
  export declare function getExperienceLevel(): ExperienceLevel;
8
13
  export declare function setExperienceLevel(level: ExperienceLevel): void;
14
+ export type ServiceName = 'github' | 'supabase' | 'vercel';
15
+ export declare function getServiceKey(service: ServiceName): string | null;
16
+ export declare function setServiceKey(service: ServiceName, key: string): void;
17
+ export declare function clearServiceKey(service: ServiceName): void;
18
+ export declare function getAllServiceKeys(): ServiceKeys;
19
+ export {};
package/dist/config.js CHANGED
@@ -10,6 +10,10 @@ exports.getApiUrl = getApiUrl;
10
10
  exports.setApiUrl = setApiUrl;
11
11
  exports.getExperienceLevel = getExperienceLevel;
12
12
  exports.setExperienceLevel = setExperienceLevel;
13
+ exports.getServiceKey = getServiceKey;
14
+ exports.setServiceKey = setServiceKey;
15
+ exports.clearServiceKey = clearServiceKey;
16
+ exports.getAllServiceKeys = getAllServiceKeys;
13
17
  const conf_1 = __importDefault(require("conf"));
14
18
  const config = new conf_1.default({
15
19
  projectName: 'codebakers',
@@ -17,6 +21,11 @@ const config = new conf_1.default({
17
21
  apiKey: null,
18
22
  apiUrl: 'https://codebakers.ai',
19
23
  experienceLevel: 'intermediate',
24
+ serviceKeys: {
25
+ github: null,
26
+ supabase: null,
27
+ vercel: null,
28
+ },
20
29
  },
21
30
  });
22
31
  function getApiKey() {
@@ -40,3 +49,20 @@ function getExperienceLevel() {
40
49
  function setExperienceLevel(level) {
41
50
  config.set('experienceLevel', level);
42
51
  }
52
+ function getServiceKey(service) {
53
+ const keys = config.get('serviceKeys');
54
+ return keys[service];
55
+ }
56
+ function setServiceKey(service, key) {
57
+ const keys = config.get('serviceKeys');
58
+ keys[service] = key;
59
+ config.set('serviceKeys', keys);
60
+ }
61
+ function clearServiceKey(service) {
62
+ const keys = config.get('serviceKeys');
63
+ keys[service] = null;
64
+ config.set('serviceKeys', keys);
65
+ }
66
+ function getAllServiceKeys() {
67
+ return config.get('serviceKeys');
68
+ }
package/dist/index.js CHANGED
@@ -53,7 +53,7 @@ const program = new commander_1.Command();
53
53
  program
54
54
  .name('codebakers')
55
55
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
56
- .version('1.4.6');
56
+ .version('1.5.0');
57
57
  // Primary command - one-time setup
58
58
  program
59
59
  .command('setup')
@@ -1131,7 +1131,7 @@ phase: development
1131
1131
  ## Connection Status
1132
1132
  - **MCP Server:** Running
1133
1133
  - **API Connected:** Yes
1134
- - **Version:** 1.4.6
1134
+ - **Version:** 1.5.0
1135
1135
 
1136
1136
  ## Current Settings
1137
1137
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -0,0 +1,380 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { execSync, exec } from 'child_process';
4
+ import { createInterface } from 'readline';
5
+ import { getServiceKey, setServiceKey, type ServiceName } from '../config.js';
6
+
7
+ function prompt(question: string): Promise<string> {
8
+ const rl = createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout,
11
+ });
12
+
13
+ return new Promise((resolve) => {
14
+ rl.question(question, (answer) => {
15
+ rl.close();
16
+ resolve(answer.trim());
17
+ });
18
+ });
19
+ }
20
+
21
+ export interface ProvisionResult {
22
+ github?: {
23
+ repoUrl: string;
24
+ cloneUrl: string;
25
+ };
26
+ supabase?: {
27
+ projectId: string;
28
+ projectUrl: string;
29
+ apiUrl: string;
30
+ anonKey: string;
31
+ };
32
+ vercel?: {
33
+ projectId: string;
34
+ projectUrl: string;
35
+ };
36
+ }
37
+
38
+ /**
39
+ * Check if a service key is configured, prompt if not
40
+ */
41
+ async function ensureServiceKey(service: ServiceName, instructions: string): Promise<string | null> {
42
+ let key = getServiceKey(service);
43
+
44
+ if (!key) {
45
+ console.log(chalk.yellow(`\n No ${service} API key configured.\n`));
46
+ console.log(chalk.gray(instructions));
47
+
48
+ const inputKey = await prompt(`\n ${service} API key (or press Enter to skip): `);
49
+
50
+ if (inputKey) {
51
+ setServiceKey(service, inputKey);
52
+ key = inputKey;
53
+ console.log(chalk.green(` ✓ ${service} key saved\n`));
54
+ } else {
55
+ console.log(chalk.gray(` Skipping ${service} provisioning.\n`));
56
+ return null;
57
+ }
58
+ }
59
+
60
+ return key;
61
+ }
62
+
63
+ /**
64
+ * Create a GitHub repository
65
+ */
66
+ export async function createGitHubRepo(
67
+ projectName: string,
68
+ description: string = ''
69
+ ): Promise<{ repoUrl: string; cloneUrl: string } | null> {
70
+ // Check if gh CLI is available
71
+ try {
72
+ execSync('gh --version', { stdio: 'pipe' });
73
+ } catch {
74
+ console.log(chalk.yellow(' GitHub CLI (gh) not found. Install it from: https://cli.github.com\n'));
75
+ return null;
76
+ }
77
+
78
+ // Check if authenticated
79
+ try {
80
+ execSync('gh auth status', { stdio: 'pipe' });
81
+ } catch {
82
+ console.log(chalk.yellow(' Not logged into GitHub CLI.\n'));
83
+ console.log(chalk.gray(' Run: gh auth login\n'));
84
+ return null;
85
+ }
86
+
87
+ const spinner = ora('Creating GitHub repository...').start();
88
+
89
+ try {
90
+ // Create the repo
91
+ const result = execSync(
92
+ `gh repo create ${projectName} --public --description "${description}" --source . --remote origin --push`,
93
+ { encoding: 'utf-8', stdio: 'pipe' }
94
+ );
95
+
96
+ // Get the repo URL
97
+ const repoUrl = execSync('gh repo view --json url -q .url', { encoding: 'utf-8' }).trim();
98
+ const cloneUrl = `${repoUrl}.git`;
99
+
100
+ spinner.succeed('GitHub repository created!');
101
+ console.log(chalk.gray(` ${repoUrl}\n`));
102
+
103
+ return { repoUrl, cloneUrl };
104
+ } catch (error) {
105
+ spinner.fail('Failed to create GitHub repository');
106
+ const message = error instanceof Error ? error.message : String(error);
107
+
108
+ if (message.includes('already exists')) {
109
+ console.log(chalk.yellow(' Repository already exists. Skipping.\n'));
110
+ } else {
111
+ console.log(chalk.red(` Error: ${message}\n`));
112
+ }
113
+ return null;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Create a Supabase project
119
+ */
120
+ export async function createSupabaseProject(
121
+ projectName: string,
122
+ organizationId?: string
123
+ ): Promise<{ projectId: string; projectUrl: string; apiUrl: string; anonKey: string } | null> {
124
+ const accessToken = await ensureServiceKey('supabase',
125
+ ' Get your access token from: https://supabase.com/dashboard/account/tokens'
126
+ );
127
+
128
+ if (!accessToken) return null;
129
+
130
+ const spinner = ora('Creating Supabase project...').start();
131
+
132
+ try {
133
+ // Get organization if not provided
134
+ if (!organizationId) {
135
+ const orgsResponse = await fetch('https://api.supabase.com/v1/organizations', {
136
+ headers: {
137
+ 'Authorization': `Bearer ${accessToken}`,
138
+ },
139
+ });
140
+
141
+ if (!orgsResponse.ok) {
142
+ throw new Error('Failed to fetch organizations. Check your access token.');
143
+ }
144
+
145
+ const orgs = await orgsResponse.json();
146
+ if (orgs.length === 0) {
147
+ throw new Error('No organizations found. Create one at supabase.com first.');
148
+ }
149
+
150
+ organizationId = orgs[0].id;
151
+ }
152
+
153
+ // Generate a random password for the database
154
+ const dbPassword = Math.random().toString(36).slice(-16) +
155
+ Math.random().toString(36).slice(-16).toUpperCase() +
156
+ '!1';
157
+
158
+ // Create the project
159
+ const createResponse = await fetch('https://api.supabase.com/v1/projects', {
160
+ method: 'POST',
161
+ headers: {
162
+ 'Authorization': `Bearer ${accessToken}`,
163
+ 'Content-Type': 'application/json',
164
+ },
165
+ body: JSON.stringify({
166
+ name: projectName,
167
+ organization_id: organizationId,
168
+ region: 'us-east-1',
169
+ plan: 'free',
170
+ db_pass: dbPassword,
171
+ }),
172
+ });
173
+
174
+ if (!createResponse.ok) {
175
+ const error = await createResponse.json();
176
+ throw new Error(error.message || 'Failed to create project');
177
+ }
178
+
179
+ const project = await createResponse.json();
180
+
181
+ spinner.succeed('Supabase project created!');
182
+
183
+ // Wait for project to be ready (it takes a moment)
184
+ const waitSpinner = ora('Waiting for project to be ready...').start();
185
+
186
+ let projectReady = false;
187
+ let attempts = 0;
188
+ let projectDetails: { api_url?: string; anon_key?: string } = {};
189
+
190
+ while (!projectReady && attempts < 30) {
191
+ await new Promise(resolve => setTimeout(resolve, 2000));
192
+
193
+ const statusResponse = await fetch(`https://api.supabase.com/v1/projects/${project.id}`, {
194
+ headers: {
195
+ 'Authorization': `Bearer ${accessToken}`,
196
+ },
197
+ });
198
+
199
+ if (statusResponse.ok) {
200
+ projectDetails = await statusResponse.json();
201
+ if (projectDetails.api_url) {
202
+ projectReady = true;
203
+ }
204
+ }
205
+ attempts++;
206
+ }
207
+
208
+ if (!projectReady) {
209
+ waitSpinner.warn('Project created but may not be fully ready yet.');
210
+ } else {
211
+ waitSpinner.succeed('Project ready!');
212
+ }
213
+
214
+ const projectUrl = `https://supabase.com/dashboard/project/${project.id}`;
215
+ console.log(chalk.gray(` ${projectUrl}\n`));
216
+
217
+ // Get the anon key
218
+ const keysResponse = await fetch(`https://api.supabase.com/v1/projects/${project.id}/api-keys`, {
219
+ headers: {
220
+ 'Authorization': `Bearer ${accessToken}`,
221
+ },
222
+ });
223
+
224
+ let anonKey = '';
225
+ if (keysResponse.ok) {
226
+ const keys = await keysResponse.json();
227
+ const anonKeyObj = keys.find((k: { name: string }) => k.name === 'anon');
228
+ anonKey = anonKeyObj?.api_key || '';
229
+ }
230
+
231
+ return {
232
+ projectId: project.id,
233
+ projectUrl,
234
+ apiUrl: projectDetails.api_url || `https://${project.id}.supabase.co`,
235
+ anonKey,
236
+ };
237
+ } catch (error) {
238
+ spinner.fail('Failed to create Supabase project');
239
+ const message = error instanceof Error ? error.message : String(error);
240
+ console.log(chalk.red(` Error: ${message}\n`));
241
+ return null;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Create a Vercel project
247
+ */
248
+ export async function createVercelProject(
249
+ projectName: string,
250
+ gitRepoUrl?: string
251
+ ): Promise<{ projectId: string; projectUrl: string } | null> {
252
+ const accessToken = await ensureServiceKey('vercel',
253
+ ' Get your token from: https://vercel.com/account/tokens'
254
+ );
255
+
256
+ if (!accessToken) return null;
257
+
258
+ const spinner = ora('Creating Vercel project...').start();
259
+
260
+ try {
261
+ // Create the project
262
+ const createResponse = await fetch('https://api.vercel.com/v10/projects', {
263
+ method: 'POST',
264
+ headers: {
265
+ 'Authorization': `Bearer ${accessToken}`,
266
+ 'Content-Type': 'application/json',
267
+ },
268
+ body: JSON.stringify({
269
+ name: projectName,
270
+ framework: 'nextjs',
271
+ ...(gitRepoUrl && {
272
+ gitRepository: {
273
+ type: 'github',
274
+ repo: gitRepoUrl.replace('https://github.com/', ''),
275
+ },
276
+ }),
277
+ }),
278
+ });
279
+
280
+ if (!createResponse.ok) {
281
+ const error = await createResponse.json();
282
+ throw new Error(error.error?.message || 'Failed to create project');
283
+ }
284
+
285
+ const project = await createResponse.json();
286
+
287
+ spinner.succeed('Vercel project created!');
288
+
289
+ const projectUrl = `https://vercel.com/${project.accountId}/${project.name}`;
290
+ console.log(chalk.gray(` ${projectUrl}\n`));
291
+
292
+ return {
293
+ projectId: project.id,
294
+ projectUrl,
295
+ };
296
+ } catch (error) {
297
+ spinner.fail('Failed to create Vercel project');
298
+ const message = error instanceof Error ? error.message : String(error);
299
+
300
+ if (message.includes('already exists')) {
301
+ console.log(chalk.yellow(' Project already exists. Skipping.\n'));
302
+ } else {
303
+ console.log(chalk.red(` Error: ${message}\n`));
304
+ }
305
+ return null;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Full provisioning flow - create all services
311
+ */
312
+ export async function provisionAll(
313
+ projectName: string,
314
+ description: string = ''
315
+ ): Promise<ProvisionResult> {
316
+ console.log(chalk.blue('\n ══════════════════════════════════════════════════════════'));
317
+ console.log(chalk.white.bold(' 🚀 Auto-Provisioning Services\n'));
318
+ console.log(chalk.blue(' ══════════════════════════════════════════════════════════\n'));
319
+
320
+ const result: ProvisionResult = {};
321
+
322
+ // 1. GitHub
323
+ console.log(chalk.white(' Step 1: GitHub Repository\n'));
324
+ const github = await createGitHubRepo(projectName, description);
325
+ if (github) {
326
+ result.github = github;
327
+ }
328
+
329
+ // 2. Supabase
330
+ console.log(chalk.white(' Step 2: Supabase Project\n'));
331
+ const supabase = await createSupabaseProject(projectName);
332
+ if (supabase) {
333
+ result.supabase = supabase;
334
+ }
335
+
336
+ // 3. Vercel
337
+ console.log(chalk.white(' Step 3: Vercel Project\n'));
338
+ const vercel = await createVercelProject(projectName, result.github?.repoUrl);
339
+ if (vercel) {
340
+ result.vercel = vercel;
341
+ }
342
+
343
+ // Summary
344
+ console.log(chalk.blue(' ══════════════════════════════════════════════════════════'));
345
+ console.log(chalk.white.bold(' 📋 Provisioning Summary\n'));
346
+ console.log(chalk.blue(' ══════════════════════════════════════════════════════════\n'));
347
+
348
+ if (result.github) {
349
+ console.log(chalk.green(' ✅ GitHub: ') + chalk.gray(result.github.repoUrl));
350
+ } else {
351
+ console.log(chalk.yellow(' ⏭️ GitHub: Skipped'));
352
+ }
353
+
354
+ if (result.supabase) {
355
+ console.log(chalk.green(' ✅ Supabase: ') + chalk.gray(result.supabase.projectUrl));
356
+ } else {
357
+ console.log(chalk.yellow(' ⏭️ Supabase: Skipped'));
358
+ }
359
+
360
+ if (result.vercel) {
361
+ console.log(chalk.green(' ✅ Vercel: ') + chalk.gray(result.vercel.projectUrl));
362
+ } else {
363
+ console.log(chalk.yellow(' ⏭️ Vercel: Skipped'));
364
+ }
365
+
366
+ console.log('');
367
+
368
+ return result;
369
+ }
370
+
371
+ /**
372
+ * Check which services have keys configured
373
+ */
374
+ export function getConfiguredServices(): { github: boolean; supabase: boolean; vercel: boolean } {
375
+ return {
376
+ github: false, // GitHub uses gh CLI auth, not stored key
377
+ supabase: !!getServiceKey('supabase'),
378
+ vercel: !!getServiceKey('vercel'),
379
+ };
380
+ }
@@ -6,6 +6,7 @@ import { join } from 'path';
6
6
  import { execSync } from 'child_process';
7
7
  import * as templates from '../templates/nextjs-supabase.js';
8
8
  import { getApiKey, getApiUrl } from '../config.js';
9
+ import { provisionAll, type ProvisionResult } from './provision.js';
9
10
 
10
11
  // Cursor IDE configuration templates
11
12
  const CURSORRULES_TEMPLATE = `# CODEBAKERS CURSOR RULES
@@ -522,6 +523,41 @@ export async function scaffold(): Promise<void> {
522
523
  console.log(chalk.yellow(' ⚠️ Not logged in - run `codebakers setup` first to get patterns\n'));
523
524
  }
524
525
 
526
+ // Ask about auto-provisioning
527
+ console.log(chalk.white('\n Would you like to auto-provision your infrastructure?\n'));
528
+ console.log(chalk.gray(' This can create GitHub repo, Supabase database, and Vercel project automatically.'));
529
+ console.log(chalk.gray(' You\'ll need API keys for each service (one-time setup).\n'));
530
+
531
+ const wantProvision = await confirm(' Auto-provision services?');
532
+ let provisionResult: ProvisionResult = {};
533
+
534
+ if (wantProvision) {
535
+ // Initialize git first if not already
536
+ try {
537
+ execSync('git init', { cwd, stdio: 'pipe' });
538
+ execSync('git add .', { cwd, stdio: 'pipe' });
539
+ execSync('git commit -m "Initial commit from CodeBakers scaffold"', { cwd, stdio: 'pipe' });
540
+ } catch {
541
+ // Git might already be initialized or have issues
542
+ }
543
+
544
+ provisionResult = await provisionAll(projectName, `${projectName} - Built with CodeBakers`);
545
+
546
+ // Update .env.local with Supabase credentials if available
547
+ if (provisionResult.supabase) {
548
+ const envPath = join(cwd, '.env.local');
549
+ let envContent = existsSync(envPath) ? readFileSync(envPath, 'utf-8') : '';
550
+
551
+ // Replace placeholder values with actual credentials
552
+ envContent = envContent
553
+ .replace('your-project-id.supabase.co', provisionResult.supabase.apiUrl.replace('https://', ''))
554
+ .replace('your-anon-key', provisionResult.supabase.anonKey || 'your-anon-key');
555
+
556
+ writeFileSync(envPath, envContent);
557
+ console.log(chalk.green(' ✅ Updated .env.local with Supabase credentials!\n'));
558
+ }
559
+ }
560
+
525
561
  // Success message
526
562
  console.log(chalk.green(`
527
563
  ╔═══════════════════════════════════════════════════════════╗
package/src/config.ts CHANGED
@@ -2,10 +2,17 @@ import Conf from 'conf';
2
2
 
3
3
  export type ExperienceLevel = 'beginner' | 'intermediate' | 'advanced';
4
4
 
5
+ interface ServiceKeys {
6
+ github: string | null;
7
+ supabase: string | null;
8
+ vercel: string | null;
9
+ }
10
+
5
11
  interface ConfigSchema {
6
12
  apiKey: string | null;
7
13
  apiUrl: string;
8
14
  experienceLevel: ExperienceLevel;
15
+ serviceKeys: ServiceKeys;
9
16
  }
10
17
 
11
18
  const config = new Conf<ConfigSchema>({
@@ -14,6 +21,11 @@ const config = new Conf<ConfigSchema>({
14
21
  apiKey: null,
15
22
  apiUrl: 'https://codebakers.ai',
16
23
  experienceLevel: 'intermediate',
24
+ serviceKeys: {
25
+ github: null,
26
+ supabase: null,
27
+ vercel: null,
28
+ },
17
29
  },
18
30
  });
19
31
 
@@ -44,3 +56,27 @@ export function getExperienceLevel(): ExperienceLevel {
44
56
  export function setExperienceLevel(level: ExperienceLevel): void {
45
57
  config.set('experienceLevel', level);
46
58
  }
59
+
60
+ // Service API Keys
61
+ export type ServiceName = 'github' | 'supabase' | 'vercel';
62
+
63
+ export function getServiceKey(service: ServiceName): string | null {
64
+ const keys = config.get('serviceKeys');
65
+ return keys[service];
66
+ }
67
+
68
+ export function setServiceKey(service: ServiceName, key: string): void {
69
+ const keys = config.get('serviceKeys');
70
+ keys[service] = key;
71
+ config.set('serviceKeys', keys);
72
+ }
73
+
74
+ export function clearServiceKey(service: ServiceName): void {
75
+ const keys = config.get('serviceKeys');
76
+ keys[service] = null;
77
+ config.set('serviceKeys', keys);
78
+ }
79
+
80
+ export function getAllServiceKeys(): ServiceKeys {
81
+ return config.get('serviceKeys');
82
+ }
package/src/index.ts CHANGED
@@ -57,7 +57,7 @@ const program = new Command();
57
57
  program
58
58
  .name('codebakers')
59
59
  .description('CodeBakers CLI - Production patterns for AI-assisted development')
60
- .version('1.4.6');
60
+ .version('1.5.0');
61
61
 
62
62
  // Primary command - one-time setup
63
63
  program
package/src/mcp/server.ts CHANGED
@@ -1285,7 +1285,7 @@ phase: development
1285
1285
  ## Connection Status
1286
1286
  - **MCP Server:** Running
1287
1287
  - **API Connected:** Yes
1288
- - **Version:** 1.4.6
1288
+ - **Version:** 1.5.0
1289
1289
 
1290
1290
  ## Current Settings
1291
1291
  - **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}