@codebakers/cli 2.8.0 → 3.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/billing.d.ts +4 -0
- package/dist/commands/billing.js +91 -0
- package/dist/commands/extend.d.ts +4 -0
- package/dist/commands/extend.js +141 -0
- package/dist/commands/go.d.ts +4 -0
- package/dist/commands/go.js +173 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.js +83 -1
- package/dist/index.js +24 -6
- package/dist/lib/fingerprint.d.ts +23 -0
- package/dist/lib/fingerprint.js +136 -0
- package/dist/mcp/server.js +343 -26
- package/package.json +1 -1
- package/src/commands/billing.ts +99 -0
- package/src/commands/extend.ts +157 -0
- package/src/commands/go.ts +197 -0
- package/src/config.ts +101 -1
- package/src/index.ts +27 -6
- package/src/lib/fingerprint.ts +122 -0
- package/src/mcp/server.ts +372 -29
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getDeviceFingerprint = getDeviceFingerprint;
|
|
37
|
+
exports.canCreateFingerprint = canCreateFingerprint;
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
/**
|
|
42
|
+
* Get a stable machine identifier based on OS
|
|
43
|
+
* - Windows: MachineGuid from registry
|
|
44
|
+
* - macOS: IOPlatformUUID from system
|
|
45
|
+
* - Linux: /etc/machine-id
|
|
46
|
+
*/
|
|
47
|
+
function getMachineId() {
|
|
48
|
+
try {
|
|
49
|
+
const platform = os.platform();
|
|
50
|
+
if (platform === 'win32') {
|
|
51
|
+
// Windows: Use MachineGuid from registry
|
|
52
|
+
const output = (0, child_process_1.execSync)('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
53
|
+
const match = output.match(/MachineGuid\s+REG_SZ\s+(.+)/);
|
|
54
|
+
if (match && match[1]) {
|
|
55
|
+
return match[1].trim();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (platform === 'darwin') {
|
|
59
|
+
// macOS: Use hardware UUID
|
|
60
|
+
const output = (0, child_process_1.execSync)('ioreg -rd1 -c IOPlatformExpertDevice | grep IOPlatformUUID', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
61
|
+
const match = output.match(/"IOPlatformUUID"\s*=\s*"(.+)"/);
|
|
62
|
+
if (match && match[1]) {
|
|
63
|
+
return match[1];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Linux: Use machine-id
|
|
68
|
+
const output = (0, child_process_1.execSync)('cat /etc/machine-id', {
|
|
69
|
+
encoding: 'utf-8',
|
|
70
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
71
|
+
});
|
|
72
|
+
return output.trim();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Fallback handled below
|
|
77
|
+
}
|
|
78
|
+
// Fallback: Create a stable hash from hostname + username + home directory
|
|
79
|
+
// This is less reliable but works when we can't access system IDs
|
|
80
|
+
const fallbackData = [
|
|
81
|
+
os.hostname(),
|
|
82
|
+
os.userInfo().username,
|
|
83
|
+
os.homedir(),
|
|
84
|
+
os.platform(),
|
|
85
|
+
os.arch(),
|
|
86
|
+
].join('|');
|
|
87
|
+
return crypto.createHash('sha256').update(fallbackData).digest('hex').slice(0, 36);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get a complete device fingerprint
|
|
91
|
+
* The deviceHash is the primary identifier used for trial tracking
|
|
92
|
+
*/
|
|
93
|
+
function getDeviceFingerprint() {
|
|
94
|
+
const machineId = getMachineId();
|
|
95
|
+
// Collect stable machine characteristics
|
|
96
|
+
const fingerprintData = {
|
|
97
|
+
machineId,
|
|
98
|
+
hostname: os.hostname(),
|
|
99
|
+
username: os.userInfo().username,
|
|
100
|
+
platform: os.platform(),
|
|
101
|
+
arch: os.arch(),
|
|
102
|
+
cpuModel: os.cpus()[0]?.model || 'unknown',
|
|
103
|
+
totalMemory: Math.floor(os.totalmem() / (1024 * 1024 * 1024)), // GB rounded
|
|
104
|
+
homeDir: os.homedir(),
|
|
105
|
+
};
|
|
106
|
+
// Create a stable hash from all characteristics
|
|
107
|
+
const deviceHash = crypto
|
|
108
|
+
.createHash('sha256')
|
|
109
|
+
.update(JSON.stringify(fingerprintData))
|
|
110
|
+
.digest('hex');
|
|
111
|
+
return {
|
|
112
|
+
machineId,
|
|
113
|
+
deviceHash,
|
|
114
|
+
platform: os.platform(),
|
|
115
|
+
hostname: os.hostname(),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Validate that we can create a fingerprint
|
|
120
|
+
* Used for diagnostics
|
|
121
|
+
*/
|
|
122
|
+
function canCreateFingerprint() {
|
|
123
|
+
try {
|
|
124
|
+
const fp = getDeviceFingerprint();
|
|
125
|
+
if (fp.deviceHash && fp.deviceHash.length === 64) {
|
|
126
|
+
return { success: true };
|
|
127
|
+
}
|
|
128
|
+
return { success: false, error: 'Invalid fingerprint generated' };
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
package/dist/mcp/server.js
CHANGED
|
@@ -53,11 +53,15 @@ class CodeBakersServer {
|
|
|
53
53
|
server;
|
|
54
54
|
apiKey;
|
|
55
55
|
apiUrl;
|
|
56
|
+
trialState;
|
|
57
|
+
authMode;
|
|
56
58
|
autoUpdateChecked = false;
|
|
57
59
|
autoUpdateInProgress = false;
|
|
58
60
|
constructor() {
|
|
59
61
|
this.apiKey = (0, config_js_1.getApiKey)();
|
|
60
62
|
this.apiUrl = (0, config_js_1.getApiUrl)();
|
|
63
|
+
this.trialState = (0, config_js_1.getTrialState)();
|
|
64
|
+
this.authMode = (0, config_js_1.getAuthMode)();
|
|
61
65
|
this.server = new index_js_1.Server({
|
|
62
66
|
name: 'codebakers',
|
|
63
67
|
version: '1.0.0',
|
|
@@ -72,12 +76,25 @@ class CodeBakersServer {
|
|
|
72
76
|
// Silently ignore errors - don't interrupt user
|
|
73
77
|
});
|
|
74
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Get authorization headers for API requests
|
|
81
|
+
* Supports both API key (paid users) and trial ID (free users)
|
|
82
|
+
*/
|
|
83
|
+
getAuthHeaders() {
|
|
84
|
+
if (this.apiKey) {
|
|
85
|
+
return { 'Authorization': `Bearer ${this.apiKey}` };
|
|
86
|
+
}
|
|
87
|
+
if (this.trialState?.trialId) {
|
|
88
|
+
return { 'X-Trial-Id': this.trialState.trialId };
|
|
89
|
+
}
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
75
92
|
/**
|
|
76
93
|
* Automatically check for and apply pattern updates
|
|
77
94
|
* Runs silently in background - no user intervention needed
|
|
78
95
|
*/
|
|
79
96
|
async checkAndAutoUpdate() {
|
|
80
|
-
if (this.autoUpdateChecked || this.autoUpdateInProgress ||
|
|
97
|
+
if (this.autoUpdateChecked || this.autoUpdateInProgress || this.authMode === 'none') {
|
|
81
98
|
return;
|
|
82
99
|
}
|
|
83
100
|
this.autoUpdateInProgress = true;
|
|
@@ -110,7 +127,7 @@ class CodeBakersServer {
|
|
|
110
127
|
}
|
|
111
128
|
// Fetch latest version
|
|
112
129
|
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
113
|
-
headers:
|
|
130
|
+
headers: this.getAuthHeaders(),
|
|
114
131
|
});
|
|
115
132
|
if (!response.ok) {
|
|
116
133
|
this.autoUpdateInProgress = false;
|
|
@@ -128,7 +145,7 @@ class CodeBakersServer {
|
|
|
128
145
|
}
|
|
129
146
|
// Fetch full content and update
|
|
130
147
|
const contentResponse = await fetch(`${this.apiUrl}/api/content`, {
|
|
131
|
-
headers:
|
|
148
|
+
headers: this.getAuthHeaders(),
|
|
132
149
|
});
|
|
133
150
|
if (!contentResponse.ok) {
|
|
134
151
|
this.autoUpdateInProgress = false;
|
|
@@ -362,7 +379,7 @@ class CodeBakersServer {
|
|
|
362
379
|
let latest = null;
|
|
363
380
|
try {
|
|
364
381
|
const response = await fetch(`${this.apiUrl}/api/content/version`, {
|
|
365
|
-
headers: this.
|
|
382
|
+
headers: this.getAuthHeaders(),
|
|
366
383
|
});
|
|
367
384
|
if (response.ok) {
|
|
368
385
|
latest = await response.json();
|
|
@@ -509,7 +526,7 @@ class CodeBakersServer {
|
|
|
509
526
|
},
|
|
510
527
|
{
|
|
511
528
|
name: 'scaffold_project',
|
|
512
|
-
description: 'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically.',
|
|
529
|
+
description: 'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically. Set fullDeploy=true for seamless idea-to-deployment (creates GitHub repo, Supabase project, and deploys to Vercel). When fullDeploy=true, first call returns explanation - then call again with deployConfirmed=true after user confirms.',
|
|
513
530
|
inputSchema: {
|
|
514
531
|
type: 'object',
|
|
515
532
|
properties: {
|
|
@@ -521,6 +538,14 @@ class CodeBakersServer {
|
|
|
521
538
|
type: 'string',
|
|
522
539
|
description: 'Brief description of what the project is for (used in PRD.md)',
|
|
523
540
|
},
|
|
541
|
+
fullDeploy: {
|
|
542
|
+
type: 'boolean',
|
|
543
|
+
description: 'If true, enables full deployment flow (GitHub + Supabase + Vercel). First call returns explanation for user confirmation.',
|
|
544
|
+
},
|
|
545
|
+
deployConfirmed: {
|
|
546
|
+
type: 'boolean',
|
|
547
|
+
description: 'Set to true AFTER user confirms they want full deployment. Only set this after showing user the explanation and getting their approval.',
|
|
548
|
+
},
|
|
524
549
|
},
|
|
525
550
|
required: ['projectName'],
|
|
526
551
|
},
|
|
@@ -797,8 +822,26 @@ class CodeBakersServer {
|
|
|
797
822
|
}));
|
|
798
823
|
// Handle tool calls
|
|
799
824
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
800
|
-
|
|
801
|
-
|
|
825
|
+
// Check access: API key OR valid trial
|
|
826
|
+
if (this.authMode === 'none') {
|
|
827
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Not logged in. Run `codebakers go` to start a free trial, or `codebakers setup` if you have an account.');
|
|
828
|
+
}
|
|
829
|
+
// Check if trial expired
|
|
830
|
+
if (this.authMode === 'trial' && (0, config_js_1.isTrialExpired)()) {
|
|
831
|
+
const trialState = (0, config_js_1.getTrialState)();
|
|
832
|
+
if (trialState?.stage === 'anonymous') {
|
|
833
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Trial expired. Run `codebakers extend` to add 7 more days with GitHub, or `codebakers billing` to upgrade.');
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Trial expired. Run `codebakers billing` to upgrade to a paid plan.');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
// Show warning if trial expiring soon
|
|
840
|
+
if (this.authMode === 'trial') {
|
|
841
|
+
const daysRemaining = (0, config_js_1.getTrialDaysRemaining)();
|
|
842
|
+
if (daysRemaining <= 2) {
|
|
843
|
+
console.error(`[CodeBakers] Trial expires in ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''}. Run 'codebakers extend' or 'codebakers billing'.`);
|
|
844
|
+
}
|
|
802
845
|
}
|
|
803
846
|
const { name, arguments: args } = request.params;
|
|
804
847
|
switch (name) {
|
|
@@ -864,7 +907,7 @@ class CodeBakersServer {
|
|
|
864
907
|
method: 'POST',
|
|
865
908
|
headers: {
|
|
866
909
|
'Content-Type': 'application/json',
|
|
867
|
-
|
|
910
|
+
...this.getAuthHeaders(),
|
|
868
911
|
},
|
|
869
912
|
body: JSON.stringify({
|
|
870
913
|
prompt: userRequest,
|
|
@@ -973,9 +1016,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
973
1016
|
async handleListPatterns() {
|
|
974
1017
|
const response = await fetch(`${this.apiUrl}/api/patterns`, {
|
|
975
1018
|
method: 'GET',
|
|
976
|
-
headers:
|
|
977
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
978
|
-
},
|
|
1019
|
+
headers: this.getAuthHeaders(),
|
|
979
1020
|
});
|
|
980
1021
|
if (!response.ok) {
|
|
981
1022
|
const error = await response.json().catch(() => ({}));
|
|
@@ -1018,7 +1059,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1018
1059
|
method: 'POST',
|
|
1019
1060
|
headers: {
|
|
1020
1061
|
'Content-Type': 'application/json',
|
|
1021
|
-
|
|
1062
|
+
...this.getAuthHeaders(),
|
|
1022
1063
|
},
|
|
1023
1064
|
body: JSON.stringify({ patterns }),
|
|
1024
1065
|
});
|
|
@@ -1035,7 +1076,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1035
1076
|
method: 'POST',
|
|
1036
1077
|
headers: {
|
|
1037
1078
|
'Content-Type': 'application/json',
|
|
1038
|
-
|
|
1079
|
+
...this.getAuthHeaders(),
|
|
1039
1080
|
},
|
|
1040
1081
|
body: JSON.stringify({ query }),
|
|
1041
1082
|
});
|
|
@@ -1204,8 +1245,12 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1204
1245
|
};
|
|
1205
1246
|
}
|
|
1206
1247
|
async handleScaffoldProject(args) {
|
|
1207
|
-
const { projectName, description } = args;
|
|
1248
|
+
const { projectName, description, fullDeploy, deployConfirmed } = args;
|
|
1208
1249
|
const cwd = process.cwd();
|
|
1250
|
+
// If fullDeploy requested but not confirmed, show explanation and ask for confirmation
|
|
1251
|
+
if (fullDeploy && !deployConfirmed) {
|
|
1252
|
+
return this.showFullDeployExplanation(projectName, description);
|
|
1253
|
+
}
|
|
1209
1254
|
// Check if directory has files
|
|
1210
1255
|
const files = fs.readdirSync(cwd);
|
|
1211
1256
|
const hasFiles = files.filter(f => !f.startsWith('.')).length > 0;
|
|
@@ -1279,7 +1324,7 @@ Show the user what their simple request was expanded into, then proceed with the
|
|
|
1279
1324
|
results.push('\n## Installing CodeBakers Patterns...\n');
|
|
1280
1325
|
const response = await fetch(`${this.apiUrl}/api/content`, {
|
|
1281
1326
|
method: 'GET',
|
|
1282
|
-
headers:
|
|
1327
|
+
headers: this.getAuthHeaders(),
|
|
1283
1328
|
});
|
|
1284
1329
|
if (response.ok) {
|
|
1285
1330
|
const content = await response.json();
|
|
@@ -1344,14 +1389,22 @@ phase: setup
|
|
|
1344
1389
|
}
|
|
1345
1390
|
results.push('\n---\n');
|
|
1346
1391
|
results.push('## ✅ Project Created Successfully!\n');
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1392
|
+
// If fullDeploy is enabled and confirmed, proceed with cloud deployment
|
|
1393
|
+
if (fullDeploy && deployConfirmed) {
|
|
1394
|
+
results.push('## 🚀 Starting Full Deployment...\n');
|
|
1395
|
+
const deployResults = await this.executeFullDeploy(projectName, cwd, description);
|
|
1396
|
+
results.push(...deployResults);
|
|
1397
|
+
}
|
|
1398
|
+
else {
|
|
1399
|
+
results.push('### Next Steps:\n');
|
|
1400
|
+
results.push('1. **Set up Supabase:** Go to https://supabase.com and create a free project');
|
|
1401
|
+
results.push('2. **Add credentials:** Copy your Supabase URL and anon key to `.env.local`');
|
|
1402
|
+
results.push('3. **Start building:** Just tell me what features you want!\n');
|
|
1403
|
+
results.push('### Example:\n');
|
|
1404
|
+
results.push('> "Add user authentication with email/password"');
|
|
1405
|
+
results.push('> "Create a dashboard with stats cards"');
|
|
1406
|
+
results.push('> "Build a todo list with CRUD operations"');
|
|
1407
|
+
}
|
|
1355
1408
|
}
|
|
1356
1409
|
catch (error) {
|
|
1357
1410
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -1364,6 +1417,270 @@ phase: setup
|
|
|
1364
1417
|
}],
|
|
1365
1418
|
};
|
|
1366
1419
|
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Show explanation of what fullDeploy will do and ask for confirmation
|
|
1422
|
+
*/
|
|
1423
|
+
showFullDeployExplanation(projectName, description) {
|
|
1424
|
+
const explanation = `# 🚀 Full Deployment: ${projectName}
|
|
1425
|
+
|
|
1426
|
+
## What This Will Do
|
|
1427
|
+
|
|
1428
|
+
Full deployment creates a complete production-ready environment automatically:
|
|
1429
|
+
|
|
1430
|
+
### 1. 📁 Local Project
|
|
1431
|
+
- Create Next.js + Supabase + Drizzle project
|
|
1432
|
+
- Install all dependencies
|
|
1433
|
+
- Set up CodeBakers patterns
|
|
1434
|
+
|
|
1435
|
+
### 2. 🐙 GitHub Repository
|
|
1436
|
+
- Create a new private repository: \`${projectName}\`
|
|
1437
|
+
- Initialize git and push code
|
|
1438
|
+
- Set up .gitignore properly
|
|
1439
|
+
|
|
1440
|
+
### 3. 🗄️ Supabase Project
|
|
1441
|
+
- Create a new Supabase project
|
|
1442
|
+
- Get database connection string
|
|
1443
|
+
- Get API keys (anon + service role)
|
|
1444
|
+
- Auto-configure .env.local
|
|
1445
|
+
|
|
1446
|
+
### 4. 🔺 Vercel Deployment
|
|
1447
|
+
- Deploy to Vercel
|
|
1448
|
+
- Connect to GitHub for auto-deploys
|
|
1449
|
+
- Set all environment variables
|
|
1450
|
+
- Get your live URL
|
|
1451
|
+
|
|
1452
|
+
---
|
|
1453
|
+
|
|
1454
|
+
## Requirements
|
|
1455
|
+
|
|
1456
|
+
Make sure you have these CLIs installed and authenticated:
|
|
1457
|
+
- \`gh\` - GitHub CLI (run: \`gh auth login\`)
|
|
1458
|
+
- \`supabase\` - Supabase CLI (run: \`supabase login\`)
|
|
1459
|
+
- \`vercel\` - Vercel CLI (run: \`vercel login\`)
|
|
1460
|
+
|
|
1461
|
+
---
|
|
1462
|
+
|
|
1463
|
+
## 🎯 Result
|
|
1464
|
+
|
|
1465
|
+
After completion, you'll have:
|
|
1466
|
+
- ✅ GitHub repo with your code
|
|
1467
|
+
- ✅ Supabase project with database ready
|
|
1468
|
+
- ✅ Live URL on Vercel
|
|
1469
|
+
- ✅ Auto-deploys on every push
|
|
1470
|
+
|
|
1471
|
+
---
|
|
1472
|
+
|
|
1473
|
+
**⚠️ IMPORTANT: Ask the user to confirm before proceeding.**
|
|
1474
|
+
|
|
1475
|
+
To proceed, call \`scaffold_project\` again with:
|
|
1476
|
+
\`\`\`json
|
|
1477
|
+
{
|
|
1478
|
+
"projectName": "${projectName}",
|
|
1479
|
+
"description": "${description || ''}",
|
|
1480
|
+
"fullDeploy": true,
|
|
1481
|
+
"deployConfirmed": true
|
|
1482
|
+
}
|
|
1483
|
+
\`\`\`
|
|
1484
|
+
|
|
1485
|
+
Or if user declines, call without fullDeploy:
|
|
1486
|
+
\`\`\`json
|
|
1487
|
+
{
|
|
1488
|
+
"projectName": "${projectName}",
|
|
1489
|
+
"description": "${description || ''}"
|
|
1490
|
+
}
|
|
1491
|
+
\`\`\`
|
|
1492
|
+
`;
|
|
1493
|
+
return {
|
|
1494
|
+
content: [{
|
|
1495
|
+
type: 'text',
|
|
1496
|
+
text: explanation,
|
|
1497
|
+
}],
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Execute full cloud deployment (GitHub + Supabase + Vercel)
|
|
1502
|
+
*/
|
|
1503
|
+
async executeFullDeploy(projectName, cwd, description) {
|
|
1504
|
+
const results = [];
|
|
1505
|
+
// Check for required CLIs
|
|
1506
|
+
const cliChecks = this.checkRequiredCLIs();
|
|
1507
|
+
if (cliChecks.missing.length > 0) {
|
|
1508
|
+
results.push('### ❌ Missing Required CLIs\n');
|
|
1509
|
+
results.push('The following CLIs are required for full deployment:\n');
|
|
1510
|
+
for (const cli of cliChecks.missing) {
|
|
1511
|
+
results.push(`- **${cli.name}**: ${cli.installCmd}`);
|
|
1512
|
+
}
|
|
1513
|
+
results.push('\nInstall the missing CLIs and try again.');
|
|
1514
|
+
return results;
|
|
1515
|
+
}
|
|
1516
|
+
results.push('✓ All required CLIs found\n');
|
|
1517
|
+
// Step 1: Initialize Git and create GitHub repo
|
|
1518
|
+
results.push('### Step 1: GitHub Repository\n');
|
|
1519
|
+
try {
|
|
1520
|
+
// Initialize git
|
|
1521
|
+
(0, child_process_1.execSync)('git init', { cwd, stdio: 'pipe' });
|
|
1522
|
+
(0, child_process_1.execSync)('git add .', { cwd, stdio: 'pipe' });
|
|
1523
|
+
(0, child_process_1.execSync)('git commit -m "Initial commit from CodeBakers"', { cwd, stdio: 'pipe' });
|
|
1524
|
+
results.push('✓ Initialized git repository');
|
|
1525
|
+
// Create GitHub repo
|
|
1526
|
+
const ghDescription = description || `${projectName} - Created with CodeBakers`;
|
|
1527
|
+
(0, child_process_1.execSync)(`gh repo create ${projectName} --private --source=. --push --description "${ghDescription}"`, { cwd, stdio: 'pipe' });
|
|
1528
|
+
results.push(`✓ Created GitHub repo: ${projectName}`);
|
|
1529
|
+
results.push(` → https://github.com/${this.getGitHubUsername()}/${projectName}\n`);
|
|
1530
|
+
}
|
|
1531
|
+
catch (error) {
|
|
1532
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1533
|
+
results.push(`⚠️ GitHub setup failed: ${msg}`);
|
|
1534
|
+
results.push(' You can create the repo manually: gh repo create\n');
|
|
1535
|
+
}
|
|
1536
|
+
// Step 2: Create Supabase project
|
|
1537
|
+
results.push('### Step 2: Supabase Project\n');
|
|
1538
|
+
try {
|
|
1539
|
+
// Create Supabase project (this may take a while)
|
|
1540
|
+
const orgId = this.getSupabaseOrgId();
|
|
1541
|
+
if (orgId) {
|
|
1542
|
+
(0, child_process_1.execSync)(`supabase projects create ${projectName} --org-id ${orgId} --region us-east-1 --db-password "${this.generatePassword()}"`, { cwd, stdio: 'pipe', timeout: 120000 });
|
|
1543
|
+
results.push(`✓ Created Supabase project: ${projectName}`);
|
|
1544
|
+
// Get project credentials
|
|
1545
|
+
const projectsOutput = (0, child_process_1.execSync)('supabase projects list --output json', { cwd, encoding: 'utf-8' });
|
|
1546
|
+
const projects = JSON.parse(projectsOutput);
|
|
1547
|
+
const newProject = projects.find((p) => p.name === projectName);
|
|
1548
|
+
if (newProject) {
|
|
1549
|
+
// Update .env.local with Supabase credentials
|
|
1550
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1551
|
+
let envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1552
|
+
envContent = envContent.replace('your-supabase-url', `https://${newProject.id}.supabase.co`);
|
|
1553
|
+
envContent = envContent.replace('your-anon-key', newProject.anon_key || 'YOUR_ANON_KEY');
|
|
1554
|
+
fs.writeFileSync(envPath, envContent);
|
|
1555
|
+
results.push('✓ Updated .env.local with Supabase credentials\n');
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
else {
|
|
1559
|
+
results.push('⚠️ Could not detect Supabase organization');
|
|
1560
|
+
results.push(' Run: supabase orgs list\n');
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
catch (error) {
|
|
1564
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1565
|
+
results.push(`⚠️ Supabase setup failed: ${msg}`);
|
|
1566
|
+
results.push(' Create project manually at: https://supabase.com/dashboard\n');
|
|
1567
|
+
}
|
|
1568
|
+
// Step 3: Deploy to Vercel
|
|
1569
|
+
results.push('### Step 3: Vercel Deployment\n');
|
|
1570
|
+
try {
|
|
1571
|
+
// Link to Vercel (creates new project)
|
|
1572
|
+
(0, child_process_1.execSync)('vercel link --yes', { cwd, stdio: 'pipe' });
|
|
1573
|
+
results.push('✓ Linked to Vercel');
|
|
1574
|
+
// Set environment variables from .env.local
|
|
1575
|
+
const envPath = path.join(cwd, '.env.local');
|
|
1576
|
+
if (fs.existsSync(envPath)) {
|
|
1577
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
1578
|
+
const envVars = envContent.split('\n')
|
|
1579
|
+
.filter(line => line.includes('=') && !line.startsWith('#'))
|
|
1580
|
+
.map(line => {
|
|
1581
|
+
const [key, ...valueParts] = line.split('=');
|
|
1582
|
+
return { key: key.trim(), value: valueParts.join('=').trim() };
|
|
1583
|
+
});
|
|
1584
|
+
for (const { key, value } of envVars) {
|
|
1585
|
+
if (value && !value.includes('your-')) {
|
|
1586
|
+
try {
|
|
1587
|
+
(0, child_process_1.execSync)(`vercel env add ${key} production <<< "${value}"`, { cwd, stdio: 'pipe', shell: 'bash' });
|
|
1588
|
+
}
|
|
1589
|
+
catch {
|
|
1590
|
+
// Env var might already exist, try to update
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
results.push('✓ Set environment variables');
|
|
1595
|
+
}
|
|
1596
|
+
// Deploy to production
|
|
1597
|
+
const deployOutput = (0, child_process_1.execSync)('vercel --prod --yes', { cwd, encoding: 'utf-8' });
|
|
1598
|
+
const urlMatch = deployOutput.match(/https:\/\/[^\s]+\.vercel\.app/);
|
|
1599
|
+
const deployUrl = urlMatch ? urlMatch[0] : 'Check Vercel dashboard';
|
|
1600
|
+
results.push(`✓ Deployed to Vercel`);
|
|
1601
|
+
results.push(` → ${deployUrl}\n`);
|
|
1602
|
+
// Connect to GitHub for auto-deploys
|
|
1603
|
+
try {
|
|
1604
|
+
(0, child_process_1.execSync)('vercel git connect --yes', { cwd, stdio: 'pipe' });
|
|
1605
|
+
results.push('✓ Connected to GitHub for auto-deploys\n');
|
|
1606
|
+
}
|
|
1607
|
+
catch {
|
|
1608
|
+
results.push('⚠️ Could not auto-connect to GitHub\n');
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
catch (error) {
|
|
1612
|
+
const msg = error instanceof Error ? error.message : 'Unknown error';
|
|
1613
|
+
results.push(`⚠️ Vercel deployment failed: ${msg}`);
|
|
1614
|
+
results.push(' Deploy manually: vercel --prod\n');
|
|
1615
|
+
}
|
|
1616
|
+
// Summary
|
|
1617
|
+
results.push('---\n');
|
|
1618
|
+
results.push('## 🎉 Full Deployment Complete!\n');
|
|
1619
|
+
results.push('Your project is now live with:');
|
|
1620
|
+
results.push('- GitHub repo with CI/CD ready');
|
|
1621
|
+
results.push('- Supabase database configured');
|
|
1622
|
+
results.push('- Vercel hosting with auto-deploys\n');
|
|
1623
|
+
results.push('**Start building features - every push auto-deploys!**');
|
|
1624
|
+
return results;
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Check if required CLIs are installed
|
|
1628
|
+
*/
|
|
1629
|
+
checkRequiredCLIs() {
|
|
1630
|
+
const clis = [
|
|
1631
|
+
{ name: 'gh', cmd: 'gh --version', installCmd: 'npm install -g gh' },
|
|
1632
|
+
{ name: 'supabase', cmd: 'supabase --version', installCmd: 'npm install -g supabase' },
|
|
1633
|
+
{ name: 'vercel', cmd: 'vercel --version', installCmd: 'npm install -g vercel' },
|
|
1634
|
+
];
|
|
1635
|
+
const installed = [];
|
|
1636
|
+
const missing = [];
|
|
1637
|
+
for (const cli of clis) {
|
|
1638
|
+
try {
|
|
1639
|
+
(0, child_process_1.execSync)(cli.cmd, { stdio: 'pipe' });
|
|
1640
|
+
installed.push(cli.name);
|
|
1641
|
+
}
|
|
1642
|
+
catch {
|
|
1643
|
+
missing.push({ name: cli.name, installCmd: cli.installCmd });
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return { installed, missing };
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Get GitHub username from gh CLI
|
|
1650
|
+
*/
|
|
1651
|
+
getGitHubUsername() {
|
|
1652
|
+
try {
|
|
1653
|
+
const output = (0, child_process_1.execSync)('gh api user --jq .login', { encoding: 'utf-8' });
|
|
1654
|
+
return output.trim();
|
|
1655
|
+
}
|
|
1656
|
+
catch {
|
|
1657
|
+
return 'YOUR_USERNAME';
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* Get Supabase organization ID
|
|
1662
|
+
*/
|
|
1663
|
+
getSupabaseOrgId() {
|
|
1664
|
+
try {
|
|
1665
|
+
const output = (0, child_process_1.execSync)('supabase orgs list --output json', { encoding: 'utf-8' });
|
|
1666
|
+
const orgs = JSON.parse(output);
|
|
1667
|
+
return orgs[0]?.id || null;
|
|
1668
|
+
}
|
|
1669
|
+
catch {
|
|
1670
|
+
return null;
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Generate a secure random password for Supabase
|
|
1675
|
+
*/
|
|
1676
|
+
generatePassword() {
|
|
1677
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
1678
|
+
let password = '';
|
|
1679
|
+
for (let i = 0; i < 24; i++) {
|
|
1680
|
+
password += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
1681
|
+
}
|
|
1682
|
+
return password;
|
|
1683
|
+
}
|
|
1367
1684
|
async handleInitProject(args) {
|
|
1368
1685
|
const cwd = process.cwd();
|
|
1369
1686
|
const results = [];
|
|
@@ -1387,7 +1704,7 @@ phase: setup
|
|
|
1387
1704
|
try {
|
|
1388
1705
|
const response = await fetch(`${this.apiUrl}/api/content`, {
|
|
1389
1706
|
method: 'GET',
|
|
1390
|
-
headers:
|
|
1707
|
+
headers: this.getAuthHeaders(),
|
|
1391
1708
|
});
|
|
1392
1709
|
if (!response.ok) {
|
|
1393
1710
|
throw new Error('Failed to fetch patterns from API');
|
|
@@ -2394,7 +2711,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
2394
2711
|
method: 'POST',
|
|
2395
2712
|
headers: {
|
|
2396
2713
|
'Content-Type': 'application/json',
|
|
2397
|
-
|
|
2714
|
+
...this.getAuthHeaders(),
|
|
2398
2715
|
},
|
|
2399
2716
|
body: JSON.stringify({
|
|
2400
2717
|
category,
|
|
@@ -2441,7 +2758,7 @@ Just describe what you want to build! I'll automatically:
|
|
|
2441
2758
|
method: 'POST',
|
|
2442
2759
|
headers: {
|
|
2443
2760
|
'Content-Type': 'application/json',
|
|
2444
|
-
|
|
2761
|
+
...this.getAuthHeaders(),
|
|
2445
2762
|
},
|
|
2446
2763
|
body: JSON.stringify({
|
|
2447
2764
|
eventType,
|