@codebakers/mcp 5.2.0 → 5.4.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,285 @@
1
+ /**
2
+ * codebakers_setup_github
3
+ *
4
+ * GitHub Repository Setup with OAuth
5
+ *
6
+ * Prompts user:
7
+ * - Have existing GitHub repo? → Configure remote
8
+ * - Create new repo? → OAuth → Create → Configure git
9
+ * - Skip? → User will configure manually later
10
+ *
11
+ * For new repos:
12
+ * - Opens browser for GitHub OAuth
13
+ * - Creates repository via GitHub API
14
+ * - Configures git remote
15
+ * - Makes initial commit and push
16
+ */
17
+ import * as fs from 'fs/promises';
18
+ import * as path from 'path';
19
+ import { execSync } from 'child_process';
20
+ import * as http from 'http';
21
+ import * as url from 'url';
22
+ const GITHUB_CLIENT_ID = 'Ov23liPmfN1K2T4M3mBr'; // CodeBakers public client
23
+ const GITHUB_SCOPES = 'repo,user';
24
+ export async function setupGitHub(args = {}) {
25
+ const cwd = process.cwd();
26
+ const { mode, repo_name, repo_url, is_private = true } = args;
27
+ console.error('🍞 CodeBakers: GitHub Setup');
28
+ try {
29
+ // Check if git is initialized
30
+ const isGitRepo = await checkGitRepo(cwd);
31
+ if (!isGitRepo) {
32
+ return `🍞 CodeBakers: ❌ Git Not Initialized\n\nThis directory is not a git repository.\n\nRun first:\ngit init\n\nThen run this tool again.`;
33
+ }
34
+ // Mode selection
35
+ if (!mode) {
36
+ return `🍞 CodeBakers: GitHub Repository Setup\n\n**Choose an option:**\n\n1. **Have existing GitHub repo** → \`mode: "existing"\`\n2. **Create new GitHub repo** → \`mode: "create"\`\n3. **Skip (configure manually later)** → \`mode: "skip"\`\n\n**Example:**\ncodebakers_setup_github({ mode: "create", repo_name: "my-app", is_private: true })`;
37
+ }
38
+ if (mode === 'skip') {
39
+ return `🍞 CodeBakers: GitHub setup skipped.\n\nYou can configure GitHub manually later or run this tool again.`;
40
+ }
41
+ if (mode === 'existing') {
42
+ if (!repo_url) {
43
+ return `🍞 CodeBakers: Missing Repository URL\n\nProvide your GitHub repository URL:\n\ncodebakers_setup_github({ mode: "existing", repo_url: "https://github.com/username/repo.git" })`;
44
+ }
45
+ return await configureExistingRepo(cwd, repo_url);
46
+ }
47
+ if (mode === 'create') {
48
+ if (!repo_name) {
49
+ return `🍞 CodeBakers: Missing Repository Name\n\nProvide a name for your new repository:\n\ncodebakers_setup_github({ mode: "create", repo_name: "my-app", is_private: true })`;
50
+ }
51
+ return await createNewRepo(cwd, repo_name, is_private);
52
+ }
53
+ return `🍞 CodeBakers: Invalid mode. Use: "existing", "create", or "skip"`;
54
+ }
55
+ catch (error) {
56
+ return `🍞 CodeBakers: GitHub Setup Failed\n\nError: ${error instanceof Error ? error.message : String(error)}`;
57
+ }
58
+ }
59
+ async function checkGitRepo(cwd) {
60
+ try {
61
+ execSync('git rev-parse --git-dir', { cwd, stdio: 'ignore' });
62
+ return true;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ async function configureExistingRepo(cwd, repoUrl) {
69
+ try {
70
+ // Check if remote 'origin' already exists
71
+ let currentRemote = '';
72
+ try {
73
+ currentRemote = execSync('git remote get-url origin', { cwd, encoding: 'utf-8' }).trim();
74
+ }
75
+ catch {
76
+ // No remote exists
77
+ }
78
+ if (currentRemote) {
79
+ if (currentRemote === repoUrl) {
80
+ return `🍞 CodeBakers: ✅ GitHub Already Configured\n\nRepository: ${repoUrl}\nRemote 'origin' is already set correctly.`;
81
+ }
82
+ else {
83
+ // Different remote - update it
84
+ execSync(`git remote set-url origin "${repoUrl}"`, { cwd });
85
+ return `🍞 CodeBakers: ✅ GitHub Remote Updated\n\nOld: ${currentRemote}\nNew: ${repoUrl}\n\nRun: git push -u origin main`;
86
+ }
87
+ }
88
+ // Add new remote
89
+ execSync(`git remote add origin "${repoUrl}"`, { cwd });
90
+ return `🍞 CodeBakers: ✅ GitHub Remote Configured\n\nRepository: ${repoUrl}\n\n**Next steps:**\n1. Ensure you have committed your code: \`git add . && git commit -m "initial commit"\`\n2. Push to GitHub: \`git push -u origin main\``;
91
+ }
92
+ catch (error) {
93
+ throw new Error(`Failed to configure remote: ${error instanceof Error ? error.message : String(error)}`);
94
+ }
95
+ }
96
+ async function createNewRepo(cwd, repoName, isPrivate) {
97
+ console.error('\n🔐 Starting GitHub OAuth flow...\n');
98
+ console.error('A browser window will open for GitHub authentication.\n');
99
+ // Start OAuth flow
100
+ const credentials = await startOAuthFlow();
101
+ console.error('\n✅ Authentication successful!\n');
102
+ console.error(`Creating repository: ${repoName}...\n`);
103
+ // Create repository via GitHub API
104
+ const repoData = await createGitHubRepo(credentials, repoName, isPrivate);
105
+ console.error(`✅ Repository created: ${repoData.html_url}\n`);
106
+ // Configure git remote
107
+ const cloneUrl = repoData.clone_url;
108
+ await configureExistingRepo(cwd, cloneUrl);
109
+ // Save credentials for future use
110
+ await saveCredentials(cwd, credentials);
111
+ // Make initial commit if needed
112
+ const hasCommits = await checkHasCommits(cwd);
113
+ if (!hasCommits) {
114
+ console.error('Making initial commit...\n');
115
+ execSync('git add .', { cwd });
116
+ execSync('git commit -m "chore: initial commit via CodeBakers"', { cwd, stdio: 'ignore' });
117
+ }
118
+ // Push to GitHub
119
+ console.error('Pushing to GitHub...\n');
120
+ try {
121
+ execSync('git push -u origin main', { cwd, stdio: 'inherit' });
122
+ }
123
+ catch {
124
+ // Try master if main fails
125
+ try {
126
+ execSync('git push -u origin master', { cwd, stdio: 'inherit' });
127
+ }
128
+ catch (error) {
129
+ console.error('⚠️ Push failed. You may need to push manually.\n');
130
+ }
131
+ }
132
+ return `🍞 CodeBakers: ✅ GitHub Repository Created\n\n**Repository:** ${repoData.html_url}\n**Clone URL:** ${cloneUrl}\n**Visibility:** ${isPrivate ? 'Private' : 'Public'}\n\n✅ Git remote configured\n✅ Initial commit pushed\n\n**GitHub credentials saved** to .codebakers/credentials.json`;
133
+ }
134
+ async function startOAuthFlow() {
135
+ return new Promise((resolve, reject) => {
136
+ const server = http.createServer(async (req, res) => {
137
+ const parsedUrl = url.parse(req.url || '', true);
138
+ if (parsedUrl.pathname === '/callback') {
139
+ const code = parsedUrl.query.code;
140
+ if (!code) {
141
+ res.writeHead(400, { 'Content-Type': 'text/html' });
142
+ res.end('<h1>Error: No authorization code received</h1>');
143
+ server.close();
144
+ reject(new Error('No authorization code received'));
145
+ return;
146
+ }
147
+ try {
148
+ // Exchange code for access token
149
+ const credentials = await exchangeCodeForToken(code);
150
+ res.writeHead(200, { 'Content-Type': 'text/html' });
151
+ res.end(`
152
+ <html>
153
+ <head><title>GitHub Authentication</title></head>
154
+ <body style="font-family: system-ui; max-width: 600px; margin: 100px auto; text-align: center;">
155
+ <h1 style="color: #28a745;">✅ Authentication Successful!</h1>
156
+ <p>You can close this window and return to your terminal.</p>
157
+ </body>
158
+ </html>
159
+ `);
160
+ server.close();
161
+ resolve(credentials);
162
+ }
163
+ catch (error) {
164
+ res.writeHead(500, { 'Content-Type': 'text/html' });
165
+ res.end('<h1>Error: Failed to authenticate</h1>');
166
+ server.close();
167
+ reject(error);
168
+ }
169
+ }
170
+ });
171
+ server.listen(3000, () => {
172
+ const authUrl = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&scope=${GITHUB_SCOPES}&redirect_uri=http://localhost:3000/callback`;
173
+ console.error(`Opening browser to: ${authUrl}\n`);
174
+ // Open browser
175
+ const open = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
176
+ try {
177
+ execSync(`${open} "${authUrl}"`, { stdio: 'ignore' });
178
+ }
179
+ catch {
180
+ console.error(`\nCouldn't open browser automatically.\nPlease visit: ${authUrl}\n`);
181
+ }
182
+ });
183
+ // Timeout after 5 minutes
184
+ setTimeout(() => {
185
+ server.close();
186
+ reject(new Error('OAuth flow timed out after 5 minutes'));
187
+ }, 5 * 60 * 1000);
188
+ });
189
+ }
190
+ async function exchangeCodeForToken(code) {
191
+ // Note: In production, this should go through a backend server
192
+ // For now, using GitHub Device Flow would be more secure
193
+ // This is a simplified implementation
194
+ const response = await fetch('https://github.com/login/oauth/access_token', {
195
+ method: 'POST',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ 'Accept': 'application/json'
199
+ },
200
+ body: JSON.stringify({
201
+ client_id: GITHUB_CLIENT_ID,
202
+ code,
203
+ redirect_uri: 'http://localhost:3000/callback'
204
+ })
205
+ });
206
+ const data = await response.json();
207
+ if (data.error) {
208
+ throw new Error(`GitHub OAuth error: ${data.error_description || data.error}`);
209
+ }
210
+ const accessToken = data.access_token;
211
+ // Get username
212
+ const userResponse = await fetch('https://api.github.com/user', {
213
+ headers: {
214
+ 'Authorization': `token ${accessToken}`,
215
+ 'Accept': 'application/vnd.github.v3+json'
216
+ }
217
+ });
218
+ const userData = await userResponse.json();
219
+ return {
220
+ access_token: accessToken,
221
+ username: userData.login
222
+ };
223
+ }
224
+ async function createGitHubRepo(credentials, repoName, isPrivate) {
225
+ const response = await fetch('https://api.github.com/user/repos', {
226
+ method: 'POST',
227
+ headers: {
228
+ 'Authorization': `token ${credentials.access_token}`,
229
+ 'Accept': 'application/vnd.github.v3+json',
230
+ 'Content-Type': 'application/json'
231
+ },
232
+ body: JSON.stringify({
233
+ name: repoName,
234
+ private: isPrivate,
235
+ auto_init: false
236
+ })
237
+ });
238
+ if (!response.ok) {
239
+ const error = await response.json();
240
+ throw new Error(`Failed to create repository: ${error.message || response.statusText}`);
241
+ }
242
+ return await response.json();
243
+ }
244
+ async function saveCredentials(cwd, credentials) {
245
+ const credsDir = path.join(cwd, '.codebakers');
246
+ const credsFile = path.join(credsDir, 'credentials.json');
247
+ await fs.mkdir(credsDir, { recursive: true });
248
+ let existing = {};
249
+ try {
250
+ const content = await fs.readFile(credsFile, 'utf-8');
251
+ existing = JSON.parse(content);
252
+ }
253
+ catch {
254
+ // File doesn't exist yet
255
+ }
256
+ existing.github = {
257
+ username: credentials.username,
258
+ access_token: credentials.access_token,
259
+ created_at: new Date().toISOString()
260
+ };
261
+ await fs.writeFile(credsFile, JSON.stringify(existing, null, 2), 'utf-8');
262
+ // Add to .gitignore
263
+ const gitignorePath = path.join(cwd, '.gitignore');
264
+ try {
265
+ let gitignore = await fs.readFile(gitignorePath, 'utf-8');
266
+ if (!gitignore.includes('.codebakers/credentials.json')) {
267
+ gitignore += '\n# CodeBakers credentials (DO NOT COMMIT)\n.codebakers/credentials.json\n';
268
+ await fs.writeFile(gitignorePath, gitignore, 'utf-8');
269
+ }
270
+ }
271
+ catch {
272
+ // .gitignore doesn't exist - create it
273
+ await fs.writeFile(gitignorePath, '.codebakers/credentials.json\n', 'utf-8');
274
+ }
275
+ }
276
+ async function checkHasCommits(cwd) {
277
+ try {
278
+ execSync('git log -1', { cwd, stdio: 'ignore' });
279
+ return true;
280
+ }
281
+ catch {
282
+ return false;
283
+ }
284
+ }
285
+ //# sourceMappingURL=setup-github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-github.js","sourceRoot":"","sources":["../../src/tools/setup-github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * codebakers_setup_supabase
3
+ *
4
+ * Supabase Project Setup with OAuth
5
+ *
6
+ * Prompts user:
7
+ * - Have existing Supabase project? → Get credentials → Write to .env
8
+ * - Create new project? → OAuth → Create → Get credentials → Write to .env
9
+ * - Skip? → User will configure manually later
10
+ *
11
+ * For new projects:
12
+ * - Opens browser for Supabase OAuth
13
+ * - Creates project via Supabase Management API
14
+ * - Retrieves database credentials automatically
15
+ * - Writes to .env file
16
+ */
17
+ interface SupabaseSetupArgs {
18
+ mode?: 'existing' | 'create' | 'skip';
19
+ project_name?: string;
20
+ region?: string;
21
+ supabase_url?: string;
22
+ supabase_anon_key?: string;
23
+ supabase_service_key?: string;
24
+ database_url?: string;
25
+ }
26
+ export declare function setupSupabase(args?: SupabaseSetupArgs): Promise<string>;
27
+ export {};
28
+ //# sourceMappingURL=setup-supabase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-supabase.d.ts","sourceRoot":"","sources":["../../src/tools/setup-supabase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH,UAAU,iBAAiB;IACzB,IAAI,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,MAAM,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAuBD,wBAAsB,aAAa,CAAC,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyCjF"}
@@ -0,0 +1,320 @@
1
+ /**
2
+ * codebakers_setup_supabase
3
+ *
4
+ * Supabase Project Setup with OAuth
5
+ *
6
+ * Prompts user:
7
+ * - Have existing Supabase project? → Get credentials → Write to .env
8
+ * - Create new project? → OAuth → Create → Get credentials → Write to .env
9
+ * - Skip? → User will configure manually later
10
+ *
11
+ * For new projects:
12
+ * - Opens browser for Supabase OAuth
13
+ * - Creates project via Supabase Management API
14
+ * - Retrieves database credentials automatically
15
+ * - Writes to .env file
16
+ */
17
+ import * as fs from 'fs/promises';
18
+ import * as path from 'path';
19
+ import { execSync } from 'child_process';
20
+ import * as http from 'http';
21
+ import * as url from 'url';
22
+ const SUPABASE_CLIENT_ID = 'supabase-cli'; // Using CLI OAuth flow
23
+ const SUPABASE_AUTH_URL = 'https://api.supabase.com';
24
+ export async function setupSupabase(args = {}) {
25
+ const cwd = process.cwd();
26
+ const { mode, project_name, region = 'us-east-1', supabase_url, supabase_anon_key, supabase_service_key, database_url } = args;
27
+ console.error('🍞 CodeBakers: Supabase Setup');
28
+ try {
29
+ // Mode selection
30
+ if (!mode) {
31
+ return `🍞 CodeBakers: Supabase Project Setup\n\n**Choose an option:**\n\n1. **Have existing Supabase project** → \`mode: "existing"\`\n2. **Create new Supabase project** → \`mode: "create"\`\n3. **Skip (configure manually later)** → \`mode: "skip"\`\n\n**Example (Create):**\ncodebakers_setup_supabase({ mode: "create", project_name: "my-app", region: "us-east-1" })\n\n**Example (Existing):**\ncodebakers_setup_supabase({ \n mode: "existing", \n supabase_url: "https://xxx.supabase.co",\n supabase_anon_key: "eyJhbG...",\n database_url: "postgresql://..."\n})`;
32
+ }
33
+ if (mode === 'skip') {
34
+ return `🍞 CodeBakers: Supabase setup skipped.\n\nYou can configure Supabase manually later or run this tool again.`;
35
+ }
36
+ if (mode === 'existing') {
37
+ if (!supabase_url || !supabase_anon_key) {
38
+ return `🍞 CodeBakers: Missing Credentials\n\nProvide your Supabase project credentials:\n\ncodebakers_setup_supabase({\n mode: "existing",\n supabase_url: "https://xxx.supabase.co",\n supabase_anon_key: "your-anon-key",\n supabase_service_key: "your-service-role-key" (optional),\n database_url: "postgresql://..." (optional)\n})\n\n**Find your credentials:**\n1. Go to: https://app.supabase.com/project/_/settings/api\n2. Copy: Project URL, anon key\n3. Database URL: Settings → Database → Connection string (direct)`;
39
+ }
40
+ return await configureExistingProject(cwd, {
41
+ supabase_url,
42
+ supabase_anon_key,
43
+ supabase_service_key,
44
+ database_url
45
+ });
46
+ }
47
+ if (mode === 'create') {
48
+ if (!project_name) {
49
+ return `🍞 CodeBakers: Missing Project Name\n\nProvide a name for your new Supabase project:\n\ncodebakers_setup_supabase({ mode: "create", project_name: "my-app", region: "us-east-1" })\n\n**Available regions:**\n- us-east-1 (North Virginia)\n- us-west-1 (North California)\n- eu-west-1 (Ireland)\n- eu-central-1 (Frankfurt)\n- ap-southeast-1 (Singapore)\n- ap-northeast-1 (Tokyo)`;
50
+ }
51
+ return await createNewProject(cwd, project_name, region);
52
+ }
53
+ return `🍞 CodeBakers: Invalid mode. Use: "existing", "create", or "skip"`;
54
+ }
55
+ catch (error) {
56
+ return `🍞 CodeBakers: Supabase Setup Failed\n\nError: ${error instanceof Error ? error.message : String(error)}`;
57
+ }
58
+ }
59
+ async function configureExistingProject(cwd, config) {
60
+ try {
61
+ const envPath = path.join(cwd, '.env');
62
+ let envContent = '';
63
+ // Read existing .env
64
+ try {
65
+ envContent = await fs.readFile(envPath, 'utf-8');
66
+ }
67
+ catch {
68
+ // .env doesn't exist - create new
69
+ }
70
+ // Update or add Supabase variables
71
+ envContent = updateEnvVar(envContent, 'NEXT_PUBLIC_SUPABASE_URL', config.supabase_url);
72
+ envContent = updateEnvVar(envContent, 'NEXT_PUBLIC_SUPABASE_ANON_KEY', config.supabase_anon_key);
73
+ if (config.supabase_service_key) {
74
+ envContent = updateEnvVar(envContent, 'SUPABASE_SERVICE_ROLE_KEY', config.supabase_service_key);
75
+ }
76
+ if (config.database_url) {
77
+ envContent = updateEnvVar(envContent, 'DATABASE_URL', config.database_url);
78
+ // Extract project ref for DIRECT_URL (port 5432)
79
+ const directUrl = config.database_url.replace(':6543/', ':5432/');
80
+ envContent = updateEnvVar(envContent, 'DIRECT_URL', directUrl);
81
+ }
82
+ // Write .env
83
+ await fs.writeFile(envPath, envContent, 'utf-8');
84
+ // Ensure .env is in .gitignore
85
+ await ensureGitignore(cwd, '.env');
86
+ return `🍞 CodeBakers: ✅ Supabase Configured\n\n**Project URL:** ${config.supabase_url}\n\n**Environment variables written to .env:**\n- NEXT_PUBLIC_SUPABASE_URL\n- NEXT_PUBLIC_SUPABASE_ANON_KEY${config.supabase_service_key ? '\n- SUPABASE_SERVICE_ROLE_KEY' : ''}${config.database_url ? '\n- DATABASE_URL\n- DIRECT_URL' : ''}\n\n✅ Ready to use Supabase in your app`;
87
+ }
88
+ catch (error) {
89
+ throw new Error(`Failed to configure Supabase: ${error instanceof Error ? error.message : String(error)}`);
90
+ }
91
+ }
92
+ async function createNewProject(cwd, projectName, region) {
93
+ console.error('\n🔐 Starting Supabase OAuth flow...\n');
94
+ console.error('A browser window will open for Supabase authentication.\n');
95
+ // Start OAuth flow
96
+ const credentials = await startOAuthFlow();
97
+ console.error('\n✅ Authentication successful!\n');
98
+ console.error(`Creating Supabase project: ${projectName} in ${region}...\n`);
99
+ // Create project via Supabase Management API
100
+ const project = await createSupabaseProject(credentials, projectName, region);
101
+ console.error(`✅ Project created: ${project.name}\n`);
102
+ console.error('⏳ Waiting for project to be ready (this may take 1-2 minutes)...\n');
103
+ // Wait for project to be ready
104
+ await waitForProjectReady(credentials, project.id);
105
+ console.error('✅ Project is ready!\n');
106
+ // Get project credentials
107
+ const projectDetails = await getProjectDetails(credentials, project.id);
108
+ console.error('Configuring environment variables...\n');
109
+ // Write to .env
110
+ await configureExistingProject(cwd, {
111
+ supabase_url: projectDetails.api_url,
112
+ supabase_anon_key: projectDetails.anon_key,
113
+ supabase_service_key: projectDetails.service_role_key,
114
+ database_url: `postgresql://postgres:[YOUR-PASSWORD]@${projectDetails.database.host}:6543/postgres?pgbouncer=true`
115
+ });
116
+ // Save credentials for future use
117
+ await saveCredentials(cwd, credentials);
118
+ return `🍞 CodeBakers: ✅ Supabase Project Created\n\n**Project:** ${project.name}\n**Region:** ${region}\n**Dashboard:** https://app.supabase.com/project/${project.id}\n\n✅ Environment variables configured in .env\n\n**Important:** \nYour database password was set during project creation.\nUpdate DATABASE_URL in .env with your password:\n\nDATABASE_URL=postgresql://postgres:[YOUR-PASSWORD]@${projectDetails.database.host}:6543/postgres?pgbouncer=true\nDIRECT_URL=postgresql://postgres:[YOUR-PASSWORD]@${projectDetails.database.host}:5432/postgres\n\n**Supabase credentials saved** to .codebakers/credentials.json`;
119
+ }
120
+ async function startOAuthFlow() {
121
+ return new Promise((resolve, reject) => {
122
+ const server = http.createServer(async (req, res) => {
123
+ const parsedUrl = url.parse(req.url || '', true);
124
+ if (parsedUrl.pathname === '/callback') {
125
+ const code = parsedUrl.query.code;
126
+ if (!code) {
127
+ res.writeHead(400, { 'Content-Type': 'text/html' });
128
+ res.end('<h1>Error: No authorization code received</h1>');
129
+ server.close();
130
+ reject(new Error('No authorization code received'));
131
+ return;
132
+ }
133
+ try {
134
+ // Exchange code for access token
135
+ const credentials = await exchangeCodeForToken(code);
136
+ res.writeHead(200, { 'Content-Type': 'text/html' });
137
+ res.end(`
138
+ <html>
139
+ <head><title>Supabase Authentication</title></head>
140
+ <body style="font-family: system-ui; max-width: 600px; margin: 100px auto; text-align: center;">
141
+ <h1 style="color: #3ECF8E;">✅ Authentication Successful!</h1>
142
+ <p>You can close this window and return to your terminal.</p>
143
+ </body>
144
+ </html>
145
+ `);
146
+ server.close();
147
+ resolve(credentials);
148
+ }
149
+ catch (error) {
150
+ res.writeHead(500, { 'Content-Type': 'text/html' });
151
+ res.end('<h1>Error: Failed to authenticate</h1>');
152
+ server.close();
153
+ reject(error);
154
+ }
155
+ }
156
+ });
157
+ server.listen(3001, () => {
158
+ const authUrl = `https://api.supabase.com/v1/oauth/authorize?client_id=${SUPABASE_CLIENT_ID}&redirect_uri=http://localhost:3001/callback&response_type=code`;
159
+ console.error(`Opening browser to Supabase login...\n`);
160
+ // Open browser
161
+ const open = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
162
+ try {
163
+ execSync(`${open} "${authUrl}"`, { stdio: 'ignore' });
164
+ }
165
+ catch {
166
+ console.error(`\nCouldn't open browser automatically.\nPlease visit: ${authUrl}\n`);
167
+ }
168
+ });
169
+ // Timeout after 5 minutes
170
+ setTimeout(() => {
171
+ server.close();
172
+ reject(new Error('OAuth flow timed out after 5 minutes'));
173
+ }, 5 * 60 * 1000);
174
+ });
175
+ }
176
+ async function exchangeCodeForToken(code) {
177
+ const response = await fetch(`${SUPABASE_AUTH_URL}/v1/oauth/token`, {
178
+ method: 'POST',
179
+ headers: {
180
+ 'Content-Type': 'application/json'
181
+ },
182
+ body: JSON.stringify({
183
+ grant_type: 'authorization_code',
184
+ code,
185
+ redirect_uri: 'http://localhost:3001/callback'
186
+ })
187
+ });
188
+ const data = await response.json();
189
+ if (data.error) {
190
+ throw new Error(`Supabase OAuth error: ${data.error_description || data.error}`);
191
+ }
192
+ return {
193
+ access_token: data.access_token,
194
+ refresh_token: data.refresh_token
195
+ };
196
+ }
197
+ async function createSupabaseProject(credentials, name, region) {
198
+ const response = await fetch(`${SUPABASE_AUTH_URL}/v1/projects`, {
199
+ method: 'POST',
200
+ headers: {
201
+ 'Authorization': `Bearer ${credentials.access_token}`,
202
+ 'Content-Type': 'application/json'
203
+ },
204
+ body: JSON.stringify({
205
+ name,
206
+ organization_id: await getOrganizationId(credentials),
207
+ region,
208
+ plan: 'free'
209
+ })
210
+ });
211
+ if (!response.ok) {
212
+ const error = await response.json();
213
+ throw new Error(`Failed to create project: ${error.message || response.statusText}`);
214
+ }
215
+ return await response.json();
216
+ }
217
+ async function getOrganizationId(credentials) {
218
+ const response = await fetch(`${SUPABASE_AUTH_URL}/v1/organizations`, {
219
+ headers: {
220
+ 'Authorization': `Bearer ${credentials.access_token}`
221
+ }
222
+ });
223
+ const orgs = await response.json();
224
+ if (orgs.length === 0) {
225
+ throw new Error('No organizations found. Please create one at https://app.supabase.com');
226
+ }
227
+ return orgs[0].id;
228
+ }
229
+ async function waitForProjectReady(credentials, projectId) {
230
+ const maxAttempts = 60; // 2 minutes
231
+ const delayMs = 2000;
232
+ for (let i = 0; i < maxAttempts; i++) {
233
+ const response = await fetch(`${SUPABASE_AUTH_URL}/v1/projects/${projectId}`, {
234
+ headers: {
235
+ 'Authorization': `Bearer ${credentials.access_token}`
236
+ }
237
+ });
238
+ const project = await response.json();
239
+ if (project.status === 'ACTIVE_HEALTHY') {
240
+ return;
241
+ }
242
+ await new Promise(resolve => setTimeout(resolve, delayMs));
243
+ }
244
+ throw new Error('Project creation timed out. Check Supabase dashboard for status.');
245
+ }
246
+ async function getProjectDetails(credentials, projectId) {
247
+ const response = await fetch(`${SUPABASE_AUTH_URL}/v1/projects/${projectId}`, {
248
+ headers: {
249
+ 'Authorization': `Bearer ${credentials.access_token}`
250
+ }
251
+ });
252
+ const project = await response.json();
253
+ // Get API keys
254
+ const keysResponse = await fetch(`${SUPABASE_AUTH_URL}/v1/projects/${projectId}/api-keys`, {
255
+ headers: {
256
+ 'Authorization': `Bearer ${credentials.access_token}`
257
+ }
258
+ });
259
+ const keys = await keysResponse.json();
260
+ const anonKey = keys.find(k => k.name === 'anon')?.api_key;
261
+ const serviceKey = keys.find(k => k.name === 'service_role')?.api_key;
262
+ return {
263
+ id: project.id,
264
+ name: project.name,
265
+ region: project.region,
266
+ database: {
267
+ host: project.database.host,
268
+ port: project.database.port
269
+ },
270
+ api_url: `https://${project.ref}.supabase.co`,
271
+ anon_key: anonKey,
272
+ service_role_key: serviceKey
273
+ };
274
+ }
275
+ function updateEnvVar(envContent, key, value) {
276
+ const regex = new RegExp(`^${key}=.*$`, 'm');
277
+ const newLine = `${key}=${value}`;
278
+ if (regex.test(envContent)) {
279
+ return envContent.replace(regex, newLine);
280
+ }
281
+ else {
282
+ return envContent + (envContent.endsWith('\n') ? '' : '\n') + newLine + '\n';
283
+ }
284
+ }
285
+ async function ensureGitignore(cwd, entry) {
286
+ const gitignorePath = path.join(cwd, '.gitignore');
287
+ try {
288
+ let gitignore = await fs.readFile(gitignorePath, 'utf-8');
289
+ if (!gitignore.includes(entry)) {
290
+ gitignore += `\n${entry}\n`;
291
+ await fs.writeFile(gitignorePath, gitignore, 'utf-8');
292
+ }
293
+ }
294
+ catch {
295
+ // .gitignore doesn't exist - create it
296
+ await fs.writeFile(gitignorePath, `${entry}\n`, 'utf-8');
297
+ }
298
+ }
299
+ async function saveCredentials(cwd, credentials) {
300
+ const credsDir = path.join(cwd, '.codebakers');
301
+ const credsFile = path.join(credsDir, 'credentials.json');
302
+ await fs.mkdir(credsDir, { recursive: true });
303
+ let existing = {};
304
+ try {
305
+ const content = await fs.readFile(credsFile, 'utf-8');
306
+ existing = JSON.parse(content);
307
+ }
308
+ catch {
309
+ // File doesn't exist yet
310
+ }
311
+ existing.supabase = {
312
+ access_token: credentials.access_token,
313
+ refresh_token: credentials.refresh_token,
314
+ created_at: new Date().toISOString()
315
+ };
316
+ await fs.writeFile(credsFile, JSON.stringify(existing, null, 2), 'utf-8');
317
+ // Add to .gitignore
318
+ await ensureGitignore(cwd, '.codebakers/credentials.json');
319
+ }
320
+ //# sourceMappingURL=setup-supabase.js.map