codebakers 2.1.0 → 2.2.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/index.js +1518 -960
- package/package.json +1 -1
- package/src/commands/build.ts +34 -3
- package/src/commands/code.ts +28 -3
- package/src/commands/deploy.ts +36 -2
- package/src/commands/init.ts +11 -1
- package/src/commands/integrate.ts +464 -565
- package/src/commands/setup.ts +375 -357
- package/src/commands/website.ts +658 -0
- package/src/index.ts +42 -23
package/dist/index.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// src/index.ts
|
|
13
13
|
import { Command } from "commander";
|
|
14
|
-
import * as
|
|
15
|
-
import
|
|
14
|
+
import * as p21 from "@clack/prompts";
|
|
15
|
+
import chalk21 from "chalk";
|
|
16
16
|
import boxen from "boxen";
|
|
17
17
|
import gradient from "gradient-string";
|
|
18
18
|
|
|
@@ -43,6 +43,159 @@ async function checkForUpdates() {
|
|
|
43
43
|
import * as p from "@clack/prompts";
|
|
44
44
|
import chalk2 from "chalk";
|
|
45
45
|
import open from "open";
|
|
46
|
+
var SERVICES = {
|
|
47
|
+
anthropic: {
|
|
48
|
+
key: "anthropic",
|
|
49
|
+
name: "Anthropic (Claude)",
|
|
50
|
+
description: "Powers the AI coding agent",
|
|
51
|
+
whyNeeded: "Without this, CodeBakers cannot generate any code. This is the brain of the system.",
|
|
52
|
+
url: "https://console.anthropic.com/settings/keys",
|
|
53
|
+
urlDescription: "Create an API key",
|
|
54
|
+
fields: [{
|
|
55
|
+
key: "apiKey",
|
|
56
|
+
label: "Anthropic API Key",
|
|
57
|
+
placeholder: "sk-ant-...",
|
|
58
|
+
validate: (v) => {
|
|
59
|
+
if (!v) return "API key is required";
|
|
60
|
+
if (!v.startsWith("sk-ant-")) return "Should start with sk-ant-";
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
}]
|
|
64
|
+
},
|
|
65
|
+
github: {
|
|
66
|
+
key: "github",
|
|
67
|
+
name: "GitHub",
|
|
68
|
+
description: "Create repos and push code",
|
|
69
|
+
whyNeeded: "Without this, CodeBakers cannot create repositories or save your code to GitHub.",
|
|
70
|
+
url: "https://github.com/settings/tokens/new?scopes=repo,user&description=CodeBakers",
|
|
71
|
+
urlDescription: 'Create a token with "repo" and "user" scopes checked',
|
|
72
|
+
fields: [{
|
|
73
|
+
key: "token",
|
|
74
|
+
label: "GitHub Token",
|
|
75
|
+
placeholder: "ghp_... or github_pat_...",
|
|
76
|
+
validate: (v) => {
|
|
77
|
+
if (!v) return "Token is required";
|
|
78
|
+
if (!v.startsWith("ghp_") && !v.startsWith("github_pat_")) {
|
|
79
|
+
return "Should start with ghp_ or github_pat_";
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
}]
|
|
84
|
+
},
|
|
85
|
+
vercel: {
|
|
86
|
+
key: "vercel",
|
|
87
|
+
name: "Vercel",
|
|
88
|
+
description: "Deploy your apps to production",
|
|
89
|
+
whyNeeded: "Without this, CodeBakers cannot deploy your apps. You'll have to deploy manually.",
|
|
90
|
+
url: "https://vercel.com/account/tokens",
|
|
91
|
+
urlDescription: 'Click "Create" to generate a new token',
|
|
92
|
+
fields: [{
|
|
93
|
+
key: "token",
|
|
94
|
+
label: "Vercel Token",
|
|
95
|
+
placeholder: "vercel_..."
|
|
96
|
+
}]
|
|
97
|
+
},
|
|
98
|
+
supabase: {
|
|
99
|
+
key: "supabase",
|
|
100
|
+
name: "Supabase",
|
|
101
|
+
description: "Database and authentication",
|
|
102
|
+
whyNeeded: "Without this, CodeBakers cannot create databases for your apps. You'll need to set up databases manually.",
|
|
103
|
+
url: "https://supabase.com/dashboard/account/tokens",
|
|
104
|
+
urlDescription: "Generate a new access token",
|
|
105
|
+
fields: [{
|
|
106
|
+
key: "accessToken",
|
|
107
|
+
label: "Supabase Access Token",
|
|
108
|
+
placeholder: "sbp_..."
|
|
109
|
+
}]
|
|
110
|
+
},
|
|
111
|
+
openai: {
|
|
112
|
+
key: "openai",
|
|
113
|
+
name: "OpenAI",
|
|
114
|
+
description: "GPT models, embeddings, DALL-E",
|
|
115
|
+
whyNeeded: "Optional. Only needed if you want to use OpenAI models instead of or alongside Claude.",
|
|
116
|
+
url: "https://platform.openai.com/api-keys",
|
|
117
|
+
urlDescription: "Create a new API key",
|
|
118
|
+
fields: [{
|
|
119
|
+
key: "apiKey",
|
|
120
|
+
label: "OpenAI API Key",
|
|
121
|
+
placeholder: "sk-..."
|
|
122
|
+
}]
|
|
123
|
+
},
|
|
124
|
+
stripe: {
|
|
125
|
+
key: "stripe",
|
|
126
|
+
name: "Stripe",
|
|
127
|
+
description: "Payment processing",
|
|
128
|
+
whyNeeded: "Optional. Only needed if your app accepts payments.",
|
|
129
|
+
url: "https://dashboard.stripe.com/apikeys",
|
|
130
|
+
urlDescription: "Copy your Secret key (sk_live or sk_test)",
|
|
131
|
+
fields: [{
|
|
132
|
+
key: "secretKey",
|
|
133
|
+
label: "Stripe Secret Key",
|
|
134
|
+
placeholder: "sk_live_... or sk_test_..."
|
|
135
|
+
}]
|
|
136
|
+
},
|
|
137
|
+
twilio: {
|
|
138
|
+
key: "twilio",
|
|
139
|
+
name: "Twilio",
|
|
140
|
+
description: "SMS, voice calls, WhatsApp",
|
|
141
|
+
whyNeeded: "Optional. Only needed if your app sends SMS or makes calls.",
|
|
142
|
+
url: "https://console.twilio.com/",
|
|
143
|
+
urlDescription: "Find Account SID and Auth Token on the dashboard",
|
|
144
|
+
fields: [
|
|
145
|
+
{
|
|
146
|
+
key: "accountSid",
|
|
147
|
+
label: "Account SID",
|
|
148
|
+
placeholder: "AC..."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
key: "authToken",
|
|
152
|
+
label: "Auth Token",
|
|
153
|
+
placeholder: "..."
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
},
|
|
157
|
+
resend: {
|
|
158
|
+
key: "resend",
|
|
159
|
+
name: "Resend",
|
|
160
|
+
description: "Email sending",
|
|
161
|
+
whyNeeded: "Optional. Only needed if your app sends emails.",
|
|
162
|
+
url: "https://resend.com/api-keys",
|
|
163
|
+
urlDescription: "Create an API key",
|
|
164
|
+
fields: [{
|
|
165
|
+
key: "apiKey",
|
|
166
|
+
label: "Resend API Key",
|
|
167
|
+
placeholder: "re_..."
|
|
168
|
+
}]
|
|
169
|
+
},
|
|
170
|
+
vapi: {
|
|
171
|
+
key: "vapi",
|
|
172
|
+
name: "VAPI",
|
|
173
|
+
description: "Voice AI agents",
|
|
174
|
+
whyNeeded: "Optional. Only needed if you're building voice agents.",
|
|
175
|
+
url: "https://dashboard.vapi.ai/",
|
|
176
|
+
urlDescription: "Copy your API key from the dashboard",
|
|
177
|
+
fields: [{
|
|
178
|
+
key: "apiKey",
|
|
179
|
+
label: "VAPI API Key",
|
|
180
|
+
placeholder: "..."
|
|
181
|
+
}]
|
|
182
|
+
},
|
|
183
|
+
elevenlabs: {
|
|
184
|
+
key: "elevenlabs",
|
|
185
|
+
name: "ElevenLabs",
|
|
186
|
+
description: "AI voice generation",
|
|
187
|
+
whyNeeded: "Optional. Only needed if you want AI-generated voices.",
|
|
188
|
+
url: "https://elevenlabs.io/app/settings/api-keys",
|
|
189
|
+
urlDescription: "Create an API key",
|
|
190
|
+
fields: [{
|
|
191
|
+
key: "apiKey",
|
|
192
|
+
label: "ElevenLabs API Key",
|
|
193
|
+
placeholder: "..."
|
|
194
|
+
}]
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
var CORE_SERVICES = ["anthropic", "github", "vercel", "supabase"];
|
|
198
|
+
var OPTIONAL_SERVICES = ["openai", "stripe", "twilio", "resend", "vapi", "elevenlabs"];
|
|
46
199
|
async function setupCommand() {
|
|
47
200
|
const config = new Config();
|
|
48
201
|
p.intro(chalk2.bgCyan.black(" CodeBakers Setup "));
|
|
@@ -52,7 +205,7 @@ async function setupCommand() {
|
|
|
52
205
|
options: [
|
|
53
206
|
{ value: "view", label: "\u{1F440} View connected services" },
|
|
54
207
|
{ value: "add", label: "\u2795 Add another service" },
|
|
55
|
-
{ value: "update", label: "\u{1F504} Update
|
|
208
|
+
{ value: "update", label: "\u{1F504} Update a service" },
|
|
56
209
|
{ value: "reset", label: "\u{1F5D1}\uFE0F Reset all configuration" },
|
|
57
210
|
{ value: "back", label: "\u2190 Back" }
|
|
58
211
|
]
|
|
@@ -60,341 +213,198 @@ async function setupCommand() {
|
|
|
60
213
|
if (p.isCancel(action) || action === "back") {
|
|
61
214
|
return;
|
|
62
215
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
216
|
+
switch (action) {
|
|
217
|
+
case "view":
|
|
218
|
+
showConnectedServices(config);
|
|
219
|
+
return;
|
|
220
|
+
case "add":
|
|
221
|
+
await addService(config);
|
|
222
|
+
return;
|
|
223
|
+
case "update":
|
|
224
|
+
await updateService(config);
|
|
225
|
+
return;
|
|
226
|
+
case "reset":
|
|
227
|
+
await resetConfig(config);
|
|
228
|
+
return;
|
|
75
229
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
230
|
+
}
|
|
231
|
+
console.log(chalk2.cyan(`
|
|
232
|
+
Welcome to CodeBakers! Let's connect your services.
|
|
233
|
+
|
|
234
|
+
${chalk2.dim("Your credentials are stored locally in ~/.codebakers/")}
|
|
235
|
+
${chalk2.dim("and are never sent to our servers.")}
|
|
236
|
+
|
|
237
|
+
${chalk2.bold("Core Services (Recommended):")}
|
|
238
|
+
${chalk2.dim("These power the main CodeBakers features.")}
|
|
239
|
+
|
|
240
|
+
`));
|
|
241
|
+
let skippedCore = [];
|
|
242
|
+
for (const serviceKey of CORE_SERVICES) {
|
|
243
|
+
const service = SERVICES[serviceKey];
|
|
244
|
+
const connected = await connectService(config, service);
|
|
245
|
+
if (!connected) {
|
|
246
|
+
skippedCore.push(service.name);
|
|
79
247
|
}
|
|
80
248
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const requiredServices = [
|
|
89
|
-
{ name: "GitHub", key: "github", required: true },
|
|
90
|
-
{ name: "Vercel", key: "vercel", required: true },
|
|
91
|
-
{ name: "Supabase", key: "supabase", required: true },
|
|
92
|
-
{ name: "Anthropic (Claude)", key: "anthropic", required: true }
|
|
93
|
-
];
|
|
94
|
-
const optionalServices = [
|
|
95
|
-
{ name: "OpenAI", key: "openai" },
|
|
96
|
-
{ name: "Stripe", key: "stripe" },
|
|
97
|
-
{ name: "Twilio", key: "twilio" },
|
|
98
|
-
{ name: "VAPI", key: "vapi" },
|
|
99
|
-
{ name: "Resend", key: "resend" },
|
|
100
|
-
{ name: "ElevenLabs", key: "elevenLabs" },
|
|
101
|
-
{ name: "Microsoft Graph", key: "microsoft" },
|
|
102
|
-
{ name: "Google APIs", key: "google" }
|
|
103
|
-
];
|
|
104
|
-
p.log.step("Connecting required services...");
|
|
105
|
-
for (const service of requiredServices) {
|
|
106
|
-
await connectService(config, service.key, service.name, true);
|
|
249
|
+
if (skippedCore.length > 0) {
|
|
250
|
+
console.log(chalk2.yellow(`
|
|
251
|
+
\u26A0\uFE0F You skipped: ${skippedCore.join(", ")}
|
|
252
|
+
|
|
253
|
+
Some features won't work without these services.
|
|
254
|
+
Run ${chalk2.bold("codebakers setup")} anytime to add them.
|
|
255
|
+
`));
|
|
107
256
|
}
|
|
108
257
|
const addOptional = await p.confirm({
|
|
109
|
-
message: "
|
|
258
|
+
message: "Add optional services? (Stripe, Twilio, Resend, etc.)",
|
|
110
259
|
initialValue: false
|
|
111
260
|
});
|
|
112
261
|
if (addOptional && !p.isCancel(addOptional)) {
|
|
113
262
|
const selected = await p.multiselect({
|
|
114
|
-
message: "Select services to
|
|
115
|
-
options:
|
|
116
|
-
value:
|
|
117
|
-
label:
|
|
263
|
+
message: "Select services to add:",
|
|
264
|
+
options: OPTIONAL_SERVICES.map((key) => ({
|
|
265
|
+
value: key,
|
|
266
|
+
label: `${SERVICES[key].name} - ${SERVICES[key].description}`
|
|
118
267
|
})),
|
|
119
268
|
required: false
|
|
120
269
|
});
|
|
121
|
-
if (!p.isCancel(selected)) {
|
|
270
|
+
if (!p.isCancel(selected) && Array.isArray(selected)) {
|
|
122
271
|
for (const serviceKey of selected) {
|
|
123
|
-
|
|
124
|
-
if (service) {
|
|
125
|
-
await connectService(config, service.key, service.name, false);
|
|
126
|
-
}
|
|
272
|
+
await connectService(config, SERVICES[serviceKey]);
|
|
127
273
|
}
|
|
128
274
|
}
|
|
129
275
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
276
|
+
const connectedCount = [...CORE_SERVICES, ...OPTIONAL_SERVICES].filter(
|
|
277
|
+
(key) => config.getCredentials(key)
|
|
278
|
+
).length;
|
|
279
|
+
p.outro(chalk2.green(`
|
|
280
|
+
\u2713 Setup complete! ${connectedCount} services connected.
|
|
281
|
+
|
|
282
|
+
Run ${chalk2.bold("codebakers")} to get started.
|
|
283
|
+
`));
|
|
133
284
|
}
|
|
134
|
-
async function connectService(config,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
case "vercel": {
|
|
163
|
-
p.log.info(`${chalk2.bold("Vercel")} - Opens browser for OAuth authorization`);
|
|
164
|
-
const proceed = await p.confirm({
|
|
165
|
-
message: "Open browser to authorize Vercel?",
|
|
166
|
-
initialValue: true
|
|
167
|
-
});
|
|
168
|
-
if (p.isCancel(proceed) || !proceed) {
|
|
169
|
-
if (required) {
|
|
170
|
-
p.log.warn("Vercel is required. Skipping for now.");
|
|
171
|
-
}
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
p.log.info(chalk2.dim("Opening browser..."));
|
|
175
|
-
await open("https://vercel.com/account/tokens");
|
|
176
|
-
const token = await p.text({
|
|
177
|
-
message: "Paste your Vercel token:",
|
|
178
|
-
placeholder: "vercel_..."
|
|
179
|
-
});
|
|
180
|
-
if (!p.isCancel(token) && token) {
|
|
181
|
-
config.setCredentials("vercel", { token });
|
|
182
|
-
p.log.success("Vercel connected!");
|
|
183
|
-
return true;
|
|
184
|
-
}
|
|
185
|
-
break;
|
|
186
|
-
}
|
|
187
|
-
case "supabase": {
|
|
188
|
-
p.log.info(`${chalk2.bold("Supabase")} - Opens browser for OAuth authorization`);
|
|
189
|
-
const proceed = await p.confirm({
|
|
190
|
-
message: "Open browser to authorize Supabase?",
|
|
191
|
-
initialValue: true
|
|
192
|
-
});
|
|
193
|
-
if (p.isCancel(proceed) || !proceed) {
|
|
194
|
-
if (required) {
|
|
195
|
-
p.log.warn("Supabase is required. Skipping for now.");
|
|
196
|
-
}
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
p.log.info(chalk2.dim("Opening browser..."));
|
|
200
|
-
await open("https://supabase.com/dashboard/account/tokens");
|
|
201
|
-
const token = await p.text({
|
|
202
|
-
message: "Paste your Supabase access token:",
|
|
203
|
-
placeholder: "sbp_..."
|
|
204
|
-
});
|
|
205
|
-
if (!p.isCancel(token) && token) {
|
|
206
|
-
config.setCredentials("supabase", { accessToken: token });
|
|
207
|
-
p.log.success("Supabase connected!");
|
|
208
|
-
return true;
|
|
209
|
-
}
|
|
210
|
-
break;
|
|
211
|
-
}
|
|
212
|
-
case "anthropic": {
|
|
213
|
-
p.log.info(`${chalk2.bold("Anthropic (Claude)")} - Powers the AI coding agent`);
|
|
214
|
-
const openBrowser = await p.confirm({
|
|
215
|
-
message: "Open browser to get API key?",
|
|
216
|
-
initialValue: true
|
|
217
|
-
});
|
|
218
|
-
if (openBrowser && !p.isCancel(openBrowser)) {
|
|
219
|
-
await open("https://console.anthropic.com/settings/keys");
|
|
220
|
-
}
|
|
221
|
-
const apiKey = await p.text({
|
|
222
|
-
message: "Paste your Anthropic API key:",
|
|
223
|
-
placeholder: "sk-ant-...",
|
|
224
|
-
validate: (value) => {
|
|
225
|
-
if (!value && required) return "API key is required";
|
|
226
|
-
if (value && !value.startsWith("sk-ant-")) return "Invalid API key format";
|
|
227
|
-
return void 0;
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
if (!p.isCancel(apiKey) && apiKey) {
|
|
231
|
-
config.setCredentials("anthropic", { apiKey });
|
|
232
|
-
p.log.success("Anthropic connected!");
|
|
233
|
-
return true;
|
|
234
|
-
}
|
|
235
|
-
break;
|
|
236
|
-
}
|
|
237
|
-
case "openai": {
|
|
238
|
-
const openBrowser = await p.confirm({
|
|
239
|
-
message: "Open browser to get OpenAI API key?",
|
|
240
|
-
initialValue: true
|
|
241
|
-
});
|
|
242
|
-
if (openBrowser && !p.isCancel(openBrowser)) {
|
|
243
|
-
await open("https://platform.openai.com/api-keys");
|
|
244
|
-
}
|
|
245
|
-
const apiKey = await p.text({
|
|
246
|
-
message: "Paste your OpenAI API key:",
|
|
247
|
-
placeholder: "sk-..."
|
|
248
|
-
});
|
|
249
|
-
if (!p.isCancel(apiKey) && apiKey) {
|
|
250
|
-
config.setCredentials("openai", { apiKey });
|
|
251
|
-
p.log.success("OpenAI connected!");
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
256
|
-
case "stripe": {
|
|
257
|
-
const openBrowser = await p.confirm({
|
|
258
|
-
message: "Open browser to get Stripe API keys?",
|
|
259
|
-
initialValue: true
|
|
260
|
-
});
|
|
261
|
-
if (openBrowser && !p.isCancel(openBrowser)) {
|
|
262
|
-
await open("https://dashboard.stripe.com/apikeys");
|
|
263
|
-
}
|
|
264
|
-
const secretKey = await p.text({
|
|
265
|
-
message: "Paste your Stripe secret key:",
|
|
266
|
-
placeholder: "sk_live_... or sk_test_..."
|
|
267
|
-
});
|
|
268
|
-
if (!p.isCancel(secretKey) && secretKey) {
|
|
269
|
-
config.setCredentials("stripe", { secretKey });
|
|
270
|
-
p.log.success("Stripe connected!");
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
case "twilio": {
|
|
276
|
-
const openBrowser = await p.confirm({
|
|
277
|
-
message: "Open browser to get Twilio credentials?",
|
|
278
|
-
initialValue: true
|
|
279
|
-
});
|
|
280
|
-
if (openBrowser && !p.isCancel(openBrowser)) {
|
|
281
|
-
await open("https://console.twilio.com/");
|
|
282
|
-
}
|
|
283
|
-
const accountSid = await p.text({
|
|
284
|
-
message: "Paste your Twilio Account SID:",
|
|
285
|
-
placeholder: "AC..."
|
|
286
|
-
});
|
|
287
|
-
const authToken = await p.text({
|
|
288
|
-
message: "Paste your Twilio Auth Token:",
|
|
289
|
-
placeholder: "..."
|
|
290
|
-
});
|
|
291
|
-
if (!p.isCancel(accountSid) && !p.isCancel(authToken) && accountSid && authToken) {
|
|
292
|
-
config.setCredentials("twilio", {
|
|
293
|
-
accountSid,
|
|
294
|
-
authToken
|
|
295
|
-
});
|
|
296
|
-
p.log.success("Twilio connected!");
|
|
297
|
-
return true;
|
|
298
|
-
}
|
|
299
|
-
break;
|
|
285
|
+
async function connectService(config, service) {
|
|
286
|
+
console.log("");
|
|
287
|
+
console.log(chalk2.bold(` ${service.name}`));
|
|
288
|
+
console.log(chalk2.dim(` ${service.description}`));
|
|
289
|
+
console.log("");
|
|
290
|
+
const action = await p.select({
|
|
291
|
+
message: `Connect ${service.name}?`,
|
|
292
|
+
options: [
|
|
293
|
+
{ value: "connect", label: "\u2713 Yes, connect now" },
|
|
294
|
+
{ value: "skip", label: "\u2192 Skip for now" },
|
|
295
|
+
{ value: "why", label: "? Why do I need this?" }
|
|
296
|
+
]
|
|
297
|
+
});
|
|
298
|
+
if (p.isCancel(action)) return false;
|
|
299
|
+
if (action === "why") {
|
|
300
|
+
console.log(chalk2.yellow(`
|
|
301
|
+
${service.whyNeeded}
|
|
302
|
+
`));
|
|
303
|
+
const proceed = await p.confirm({
|
|
304
|
+
message: `Connect ${service.name}?`,
|
|
305
|
+
initialValue: true
|
|
306
|
+
});
|
|
307
|
+
if (!proceed || p.isCancel(proceed)) {
|
|
308
|
+
console.log(chalk2.dim(` Skipped ${service.name}`));
|
|
309
|
+
return false;
|
|
300
310
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
311
|
+
} else if (action === "skip") {
|
|
312
|
+
console.log(chalk2.dim(` Skipped ${service.name}`));
|
|
313
|
+
console.log(chalk2.dim(` ${service.whyNeeded}`));
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
const openBrowser = await p.confirm({
|
|
317
|
+
message: "Open browser to get credentials?",
|
|
318
|
+
initialValue: true
|
|
319
|
+
});
|
|
320
|
+
if (openBrowser && !p.isCancel(openBrowser)) {
|
|
321
|
+
console.log(chalk2.dim(`
|
|
322
|
+
Opening: ${service.url}`));
|
|
323
|
+
console.log(chalk2.dim(` ${service.urlDescription}
|
|
324
|
+
`));
|
|
325
|
+
await open(service.url);
|
|
326
|
+
}
|
|
327
|
+
const credentials = {};
|
|
328
|
+
for (const field of service.fields) {
|
|
329
|
+
const value = await p.text({
|
|
330
|
+
message: `${field.label}:`,
|
|
331
|
+
placeholder: field.placeholder,
|
|
332
|
+
validate: field.validate
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(value)) return false;
|
|
335
|
+
if (!value) {
|
|
336
|
+
console.log(chalk2.dim(` Skipped ${service.name} (no ${field.label} provided)`));
|
|
337
|
+
return false;
|
|
319
338
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
339
|
+
credentials[field.key] = value;
|
|
340
|
+
}
|
|
341
|
+
config.setCredentials(service.key, credentials);
|
|
342
|
+
console.log(chalk2.green(` \u2713 ${service.name} connected!`));
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
function showConnectedServices(config) {
|
|
346
|
+
console.log(chalk2.bold("\n Connected Services:\n"));
|
|
347
|
+
const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
|
|
348
|
+
let connectedCount = 0;
|
|
349
|
+
for (const key of allServices) {
|
|
350
|
+
const service = SERVICES[key];
|
|
351
|
+
const creds = config.getCredentials(key);
|
|
352
|
+
if (creds) {
|
|
353
|
+
console.log(chalk2.green(` \u2713 ${service.name}`));
|
|
354
|
+
connectedCount++;
|
|
355
|
+
} else {
|
|
356
|
+
console.log(chalk2.dim(` \u25CB ${service.name} (not connected)`));
|
|
338
357
|
}
|
|
339
|
-
default:
|
|
340
|
-
p.log.warn(`Service ${serviceName} not yet implemented`);
|
|
341
|
-
return false;
|
|
342
358
|
}
|
|
343
|
-
|
|
359
|
+
console.log(chalk2.dim(`
|
|
360
|
+
${connectedCount}/${allServices.length} services connected
|
|
361
|
+
`));
|
|
344
362
|
}
|
|
345
363
|
async function addService(config) {
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
{ value: "google", label: "Google APIs" }
|
|
359
|
-
];
|
|
360
|
-
const service = await p.select({
|
|
361
|
-
message: "Which service do you want to connect?",
|
|
362
|
-
options: services
|
|
364
|
+
const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
|
|
365
|
+
const unconnected = allServices.filter((key) => !config.getCredentials(key));
|
|
366
|
+
if (unconnected.length === 0) {
|
|
367
|
+
p.log.info("All services are already connected!");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const selected = await p.select({
|
|
371
|
+
message: "Select service to add:",
|
|
372
|
+
options: unconnected.map((key) => ({
|
|
373
|
+
value: key,
|
|
374
|
+
label: `${SERVICES[key].name} - ${SERVICES[key].description}`
|
|
375
|
+
}))
|
|
363
376
|
});
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
377
|
+
if (p.isCancel(selected)) return;
|
|
378
|
+
await connectService(config, SERVICES[selected]);
|
|
379
|
+
}
|
|
380
|
+
async function updateService(config) {
|
|
381
|
+
const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
|
|
382
|
+
const connected = allServices.filter((key) => config.getCredentials(key));
|
|
383
|
+
if (connected.length === 0) {
|
|
384
|
+
p.log.info("No services connected yet. Run setup first.");
|
|
385
|
+
return;
|
|
369
386
|
}
|
|
387
|
+
const selected = await p.select({
|
|
388
|
+
message: "Select service to update:",
|
|
389
|
+
options: connected.map((key) => ({
|
|
390
|
+
value: key,
|
|
391
|
+
label: SERVICES[key].name
|
|
392
|
+
}))
|
|
393
|
+
});
|
|
394
|
+
if (p.isCancel(selected)) return;
|
|
395
|
+
await connectService(config, SERVICES[selected]);
|
|
370
396
|
}
|
|
371
|
-
function
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
{ key: "vapi", name: "VAPI" },
|
|
381
|
-
{ key: "resend", name: "Resend" },
|
|
382
|
-
{ key: "elevenLabs", name: "ElevenLabs" },
|
|
383
|
-
{ key: "microsoft", name: "Microsoft" },
|
|
384
|
-
{ key: "google", name: "Google" }
|
|
385
|
-
];
|
|
386
|
-
console.log("\n" + chalk2.bold("Connected Services:") + "\n");
|
|
387
|
-
for (const service of services) {
|
|
388
|
-
const creds = config.getCredentials(service.key);
|
|
389
|
-
const isConnected = creds && Object.values(creds).some((v) => v);
|
|
390
|
-
const status = isConnected ? chalk2.green("\u2713 Connected") : chalk2.dim("\u25CB Not connected");
|
|
391
|
-
console.log(` ${service.name.padEnd(15)} ${status}`);
|
|
397
|
+
async function resetConfig(config) {
|
|
398
|
+
const confirm13 = await p.confirm({
|
|
399
|
+
message: "Are you sure? This will remove ALL credentials.",
|
|
400
|
+
initialValue: false
|
|
401
|
+
});
|
|
402
|
+
if (!confirm13 || p.isCancel(confirm13)) return;
|
|
403
|
+
const allServices = [...CORE_SERVICES, ...OPTIONAL_SERVICES];
|
|
404
|
+
for (const key of allServices) {
|
|
405
|
+
config.setCredentials(key, null);
|
|
392
406
|
}
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
async function installPatterns(config) {
|
|
396
|
-
const patternsDir = config.getPatternsDir();
|
|
397
|
-
p.log.info(chalk2.dim(`Patterns installed to ${patternsDir}`));
|
|
407
|
+
p.log.success("Configuration reset. Run `codebakers setup` to reconfigure.");
|
|
398
408
|
}
|
|
399
409
|
|
|
400
410
|
// src/commands/init.ts
|
|
@@ -606,10 +616,10 @@ var SupabaseService = class {
|
|
|
606
616
|
throw new Error("Failed to list projects");
|
|
607
617
|
}
|
|
608
618
|
const projects = await response.json();
|
|
609
|
-
return projects.map((
|
|
610
|
-
id:
|
|
611
|
-
name:
|
|
612
|
-
region:
|
|
619
|
+
return projects.map((p22) => ({
|
|
620
|
+
id: p22.id,
|
|
621
|
+
name: p22.name,
|
|
622
|
+
region: p22.region
|
|
613
623
|
}));
|
|
614
624
|
}
|
|
615
625
|
};
|
|
@@ -1036,7 +1046,15 @@ Domain: ${domain || "Vercel default"}`,
|
|
|
1036
1046
|
await createLocalProject(projectPath, projectConfig);
|
|
1037
1047
|
spinner16.stop("Local project created");
|
|
1038
1048
|
spinner16.start("Installing dependencies...");
|
|
1039
|
-
|
|
1049
|
+
try {
|
|
1050
|
+
await execa3("npm", ["install"], { cwd: projectPath });
|
|
1051
|
+
} catch {
|
|
1052
|
+
try {
|
|
1053
|
+
await execa3("pnpm", ["install"], { cwd: projectPath });
|
|
1054
|
+
} catch {
|
|
1055
|
+
await execa3("yarn", ["install"], { cwd: projectPath });
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1040
1058
|
spinner16.stop("Dependencies installed");
|
|
1041
1059
|
if (services.includes("github")) {
|
|
1042
1060
|
spinner16.start("Creating GitHub repository...");
|
|
@@ -2056,11 +2074,11 @@ async function getVoiceInput(prompt) {
|
|
|
2056
2074
|
console.log(chalk5.green(`
|
|
2057
2075
|
\u2713 Heard: "${transcription}"
|
|
2058
2076
|
`));
|
|
2059
|
-
const
|
|
2077
|
+
const confirm13 = await p4.confirm({
|
|
2060
2078
|
message: "Is this correct?",
|
|
2061
2079
|
initialValue: true
|
|
2062
2080
|
});
|
|
2063
|
-
if (
|
|
2081
|
+
if (confirm13 && !p4.isCancel(confirm13)) {
|
|
2064
2082
|
return transcription;
|
|
2065
2083
|
} else {
|
|
2066
2084
|
const action = await p4.select({
|
|
@@ -2075,20 +2093,20 @@ async function getVoiceInput(prompt) {
|
|
|
2075
2093
|
if (action === "retry") {
|
|
2076
2094
|
return await getVoiceInput(prompt);
|
|
2077
2095
|
} else {
|
|
2078
|
-
const
|
|
2079
|
-
return p4.isCancel(
|
|
2096
|
+
const text16 = await p4.text({ message: "Type your response:" });
|
|
2097
|
+
return p4.isCancel(text16) ? null : text16;
|
|
2080
2098
|
}
|
|
2081
2099
|
}
|
|
2082
2100
|
} else {
|
|
2083
2101
|
console.log(chalk5.yellow("\n No speech detected. Try again or type your response.\n"));
|
|
2084
|
-
const
|
|
2085
|
-
return p4.isCancel(
|
|
2102
|
+
const text16 = await p4.text({ message: "Type instead:" });
|
|
2103
|
+
return p4.isCancel(text16) ? null : text16;
|
|
2086
2104
|
}
|
|
2087
2105
|
} catch (error) {
|
|
2088
2106
|
spinner16.stop("Recording failed");
|
|
2089
2107
|
console.log(chalk5.yellow("Voice input failed. Please type instead."));
|
|
2090
|
-
const
|
|
2091
|
-
return p4.isCancel(
|
|
2108
|
+
const text16 = await p4.text({ message: prompt });
|
|
2109
|
+
return p4.isCancel(text16) ? null : text16;
|
|
2092
2110
|
}
|
|
2093
2111
|
}
|
|
2094
2112
|
async function playBeep() {
|
|
@@ -2223,10 +2241,10 @@ async function transcribeWithWhisper(audioFile) {
|
|
|
2223
2241
|
], { timeout: 6e4 });
|
|
2224
2242
|
const txtFile = outputBase + ".txt";
|
|
2225
2243
|
if (await fs4.pathExists(txtFile)) {
|
|
2226
|
-
const
|
|
2244
|
+
const text16 = await fs4.readFile(txtFile, "utf-8");
|
|
2227
2245
|
await fs4.remove(txtFile).catch(() => {
|
|
2228
2246
|
});
|
|
2229
|
-
return
|
|
2247
|
+
return text16.trim();
|
|
2230
2248
|
}
|
|
2231
2249
|
} catch {
|
|
2232
2250
|
}
|
|
@@ -2333,9 +2351,9 @@ async function readFile5(filePath) {
|
|
|
2333
2351
|
name
|
|
2334
2352
|
};
|
|
2335
2353
|
} else if (pdfExtensions.includes(ext)) {
|
|
2336
|
-
const
|
|
2337
|
-
if (
|
|
2338
|
-
return { content:
|
|
2354
|
+
const text16 = await extractPdfText(cleanPath);
|
|
2355
|
+
if (text16) {
|
|
2356
|
+
return { content: text16, type: "pdf", name };
|
|
2339
2357
|
}
|
|
2340
2358
|
return {
|
|
2341
2359
|
content: `[PDF file: ${name} - text extraction not available]`,
|
|
@@ -2407,19 +2425,19 @@ function formatFilesForContext(files) {
|
|
|
2407
2425
|
context += "--- END FILES ---\n\n";
|
|
2408
2426
|
return context;
|
|
2409
2427
|
}
|
|
2410
|
-
function looksLikePaste(
|
|
2411
|
-
if (
|
|
2412
|
-
if (
|
|
2413
|
-
if (
|
|
2414
|
-
if (
|
|
2428
|
+
function looksLikePaste(text16) {
|
|
2429
|
+
if (text16.includes("\n") && text16.split("\n").length > 3) return true;
|
|
2430
|
+
if (text16.includes("function ") || text16.includes("const ") || text16.includes("import ") || text16.includes("export ") || text16.includes("class ") || text16.includes("def ") || text16.includes("public ") || text16.includes("private ")) return true;
|
|
2431
|
+
if (text16.startsWith("{") && text16.endsWith("}") || text16.startsWith("[") && text16.endsWith("]")) return true;
|
|
2432
|
+
if (text16.length > 200 && !text16.includes("\n")) return true;
|
|
2415
2433
|
return false;
|
|
2416
2434
|
}
|
|
2417
|
-
async function handlePastedContent(
|
|
2418
|
-
if (!looksLikePaste(
|
|
2435
|
+
async function handlePastedContent(text16) {
|
|
2436
|
+
if (!looksLikePaste(text16)) {
|
|
2419
2437
|
return null;
|
|
2420
2438
|
}
|
|
2421
2439
|
console.log(chalk6.cyan("\n\u{1F4CB} Detected pasted content!\n"));
|
|
2422
|
-
const preview =
|
|
2440
|
+
const preview = text16.length > 200 ? text16.slice(0, 200) + "..." : text16;
|
|
2423
2441
|
console.log(chalk6.dim(preview));
|
|
2424
2442
|
console.log("");
|
|
2425
2443
|
const action = await p5.select({
|
|
@@ -2435,7 +2453,7 @@ async function handlePastedContent(text15) {
|
|
|
2435
2453
|
});
|
|
2436
2454
|
if (p5.isCancel(action)) return null;
|
|
2437
2455
|
if (action === "literal") {
|
|
2438
|
-
return { prompt:
|
|
2456
|
+
return { prompt: text16, context: "" };
|
|
2439
2457
|
}
|
|
2440
2458
|
if (action === "custom") {
|
|
2441
2459
|
const instruction = await p5.text({
|
|
@@ -2449,7 +2467,7 @@ async function handlePastedContent(text15) {
|
|
|
2449
2467
|
|
|
2450
2468
|
--- PASTED CODE ---
|
|
2451
2469
|
\`\`\`
|
|
2452
|
-
${
|
|
2470
|
+
${text16}
|
|
2453
2471
|
\`\`\`
|
|
2454
2472
|
--- END ---
|
|
2455
2473
|
|
|
@@ -2468,7 +2486,7 @@ ${text15}
|
|
|
2468
2486
|
|
|
2469
2487
|
--- PASTED CODE ---
|
|
2470
2488
|
\`\`\`
|
|
2471
|
-
${
|
|
2489
|
+
${text16}
|
|
2472
2490
|
\`\`\`
|
|
2473
2491
|
--- END ---
|
|
2474
2492
|
|
|
@@ -2480,16 +2498,41 @@ ${text15}
|
|
|
2480
2498
|
async function codeCommand(prompt, options = {}) {
|
|
2481
2499
|
const config = new Config();
|
|
2482
2500
|
if (!config.isConfigured()) {
|
|
2483
|
-
|
|
2501
|
+
console.log(chalk7.yellow(`
|
|
2502
|
+
\u26A0\uFE0F CodeBakers isn't set up yet.
|
|
2503
|
+
|
|
2504
|
+
Run this first:
|
|
2505
|
+
${chalk7.cyan("codebakers setup")}
|
|
2506
|
+
|
|
2507
|
+
This connects your API keys (Anthropic, GitHub, etc.)
|
|
2508
|
+
so CodeBakers can generate code and deploy for you.
|
|
2509
|
+
`));
|
|
2484
2510
|
return;
|
|
2485
2511
|
}
|
|
2486
2512
|
if (!config.isInProject()) {
|
|
2487
|
-
|
|
2513
|
+
console.log(chalk7.yellow(`
|
|
2514
|
+
\u26A0\uFE0F You're not in a CodeBakers project.
|
|
2515
|
+
|
|
2516
|
+
Options:
|
|
2517
|
+
${chalk7.cyan("codebakers init")} Create a new project here
|
|
2518
|
+
${chalk7.cyan("codebakers website")} Build a website by describing it
|
|
2519
|
+
${chalk7.cyan("cd my-project")} Navigate to an existing project
|
|
2520
|
+
`));
|
|
2488
2521
|
return;
|
|
2489
2522
|
}
|
|
2490
2523
|
const anthropicCreds = config.getCredentials("anthropic");
|
|
2491
2524
|
if (!anthropicCreds?.apiKey) {
|
|
2492
|
-
|
|
2525
|
+
console.log(chalk7.yellow(`
|
|
2526
|
+
\u26A0\uFE0F Anthropic API key not configured.
|
|
2527
|
+
|
|
2528
|
+
The AI coding agent needs Claude to work.
|
|
2529
|
+
|
|
2530
|
+
Run this to add your API key:
|
|
2531
|
+
${chalk7.cyan("codebakers setup")}
|
|
2532
|
+
|
|
2533
|
+
Get an API key at:
|
|
2534
|
+
${chalk7.dim("https://console.anthropic.com/settings/keys")}
|
|
2535
|
+
`));
|
|
2493
2536
|
return;
|
|
2494
2537
|
}
|
|
2495
2538
|
const anthropic = new Anthropic({
|
|
@@ -3077,7 +3120,30 @@ import Anthropic2 from "@anthropic-ai/sdk";
|
|
|
3077
3120
|
async function deployCommand(options = {}) {
|
|
3078
3121
|
const config = new Config();
|
|
3079
3122
|
if (!config.isInProject()) {
|
|
3080
|
-
|
|
3123
|
+
console.log(chalk8.yellow(`
|
|
3124
|
+
\u26A0\uFE0F You're not in a CodeBakers project.
|
|
3125
|
+
|
|
3126
|
+
Navigate to your project first:
|
|
3127
|
+
${chalk8.cyan("cd my-project")}
|
|
3128
|
+
|
|
3129
|
+
Or create a new one:
|
|
3130
|
+
${chalk8.cyan("codebakers init")}
|
|
3131
|
+
`));
|
|
3132
|
+
return;
|
|
3133
|
+
}
|
|
3134
|
+
const vercelCreds = config.getCredentials("vercel");
|
|
3135
|
+
if (!vercelCreds?.token) {
|
|
3136
|
+
console.log(chalk8.yellow(`
|
|
3137
|
+
\u26A0\uFE0F Vercel isn't connected.
|
|
3138
|
+
|
|
3139
|
+
CodeBakers deploys to Vercel by default.
|
|
3140
|
+
|
|
3141
|
+
Run this to connect:
|
|
3142
|
+
${chalk8.cyan("codebakers setup")}
|
|
3143
|
+
|
|
3144
|
+
Or deploy manually:
|
|
3145
|
+
${chalk8.cyan("npx vercel")}
|
|
3146
|
+
`));
|
|
3081
3147
|
return;
|
|
3082
3148
|
}
|
|
3083
3149
|
p7.intro(chalk8.bgCyan.black(" Deploy to Production "));
|
|
@@ -3146,7 +3212,15 @@ async function deployCommand(options = {}) {
|
|
|
3146
3212
|
}
|
|
3147
3213
|
spinner16.start("Building project...");
|
|
3148
3214
|
try {
|
|
3149
|
-
|
|
3215
|
+
try {
|
|
3216
|
+
await execa7("npm", ["run", "build"], { cwd: process.cwd() });
|
|
3217
|
+
} catch {
|
|
3218
|
+
try {
|
|
3219
|
+
await execa7("pnpm", ["build"], { cwd: process.cwd() });
|
|
3220
|
+
} catch {
|
|
3221
|
+
await execa7("yarn", ["build"], { cwd: process.cwd() });
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3150
3224
|
spinner16.stop("Build successful");
|
|
3151
3225
|
} catch (error) {
|
|
3152
3226
|
spinner16.stop("");
|
|
@@ -4770,21 +4844,21 @@ async function extractMigrationSQL(tool, errorOutput) {
|
|
|
4770
4844
|
}
|
|
4771
4845
|
return null;
|
|
4772
4846
|
}
|
|
4773
|
-
async function copyToClipboard(
|
|
4847
|
+
async function copyToClipboard(text16) {
|
|
4774
4848
|
try {
|
|
4775
4849
|
const platform = process.platform;
|
|
4776
4850
|
if (platform === "win32") {
|
|
4777
|
-
const proc = await execa8("clip", { input:
|
|
4851
|
+
const proc = await execa8("clip", { input: text16, reject: false });
|
|
4778
4852
|
return proc.exitCode === 0;
|
|
4779
4853
|
} else if (platform === "darwin") {
|
|
4780
|
-
const proc = await execa8("pbcopy", { input:
|
|
4854
|
+
const proc = await execa8("pbcopy", { input: text16, reject: false });
|
|
4781
4855
|
return proc.exitCode === 0;
|
|
4782
4856
|
} else {
|
|
4783
4857
|
try {
|
|
4784
|
-
const proc = await execa8("xclip", ["-selection", "clipboard"], { input:
|
|
4858
|
+
const proc = await execa8("xclip", ["-selection", "clipboard"], { input: text16, reject: false });
|
|
4785
4859
|
return proc.exitCode === 0;
|
|
4786
4860
|
} catch {
|
|
4787
|
-
const proc = await execa8("xsel", ["--clipboard", "--input"], { input:
|
|
4861
|
+
const proc = await execa8("xsel", ["--clipboard", "--input"], { input: text16, reject: false });
|
|
4788
4862
|
return proc.exitCode === 0;
|
|
4789
4863
|
}
|
|
4790
4864
|
}
|
|
@@ -5053,8 +5127,8 @@ async function getVoiceInput2(prompt) {
|
|
|
5053
5127
|
initialValue: true
|
|
5054
5128
|
});
|
|
5055
5129
|
if (!ready || p16.isCancel(ready)) {
|
|
5056
|
-
const
|
|
5057
|
-
return p16.isCancel(
|
|
5130
|
+
const text17 = await p16.text({ message: "Type instead:" });
|
|
5131
|
+
return p16.isCancel(text17) ? null : text17;
|
|
5058
5132
|
}
|
|
5059
5133
|
const spinner16 = p16.spinner();
|
|
5060
5134
|
spinner16.start("\u{1F534} Recording... (press Ctrl+C to stop)");
|
|
@@ -5072,11 +5146,11 @@ async function getVoiceInput2(prompt) {
|
|
|
5072
5146
|
console.log(chalk17.green(`
|
|
5073
5147
|
Heard: "${transcription}"
|
|
5074
5148
|
`));
|
|
5075
|
-
const
|
|
5149
|
+
const confirm13 = await p16.confirm({
|
|
5076
5150
|
message: "Is this correct?",
|
|
5077
5151
|
initialValue: true
|
|
5078
5152
|
});
|
|
5079
|
-
if (
|
|
5153
|
+
if (confirm13 && !p16.isCancel(confirm13)) {
|
|
5080
5154
|
return transcription;
|
|
5081
5155
|
} else {
|
|
5082
5156
|
const retry = await p16.select({
|
|
@@ -5090,8 +5164,8 @@ async function getVoiceInput2(prompt) {
|
|
|
5090
5164
|
if (retry === "retry") {
|
|
5091
5165
|
return await getVoiceInput2(prompt);
|
|
5092
5166
|
} else {
|
|
5093
|
-
const
|
|
5094
|
-
return p16.isCancel(
|
|
5167
|
+
const text17 = await p16.text({ message: "Type your response:" });
|
|
5168
|
+
return p16.isCancel(text17) ? null : text17;
|
|
5095
5169
|
}
|
|
5096
5170
|
}
|
|
5097
5171
|
}
|
|
@@ -5099,8 +5173,8 @@ async function getVoiceInput2(prompt) {
|
|
|
5099
5173
|
spinner16.stop("Recording failed");
|
|
5100
5174
|
console.log(chalk17.yellow("Voice input failed. Falling back to text."));
|
|
5101
5175
|
}
|
|
5102
|
-
const
|
|
5103
|
-
return p16.isCancel(
|
|
5176
|
+
const text16 = await p16.text({ message: prompt });
|
|
5177
|
+
return p16.isCancel(text16) ? null : text16;
|
|
5104
5178
|
}
|
|
5105
5179
|
async function recordWithWindowsSpeech2() {
|
|
5106
5180
|
const psScript = `
|
|
@@ -5155,9 +5229,9 @@ async function recordWithMacOS2() {
|
|
|
5155
5229
|
});
|
|
5156
5230
|
const txtFile = tempFile.replace(".wav", ".txt");
|
|
5157
5231
|
if (await fs13.pathExists(txtFile)) {
|
|
5158
|
-
const
|
|
5232
|
+
const text16 = await fs13.readFile(txtFile, "utf-8");
|
|
5159
5233
|
await fs13.remove(txtFile);
|
|
5160
|
-
return
|
|
5234
|
+
return text16.trim();
|
|
5161
5235
|
}
|
|
5162
5236
|
} catch {
|
|
5163
5237
|
}
|
|
@@ -5205,9 +5279,9 @@ async function recordWithLinux2() {
|
|
|
5205
5279
|
});
|
|
5206
5280
|
const txtFile = tempFile.replace(".wav", ".txt");
|
|
5207
5281
|
if (await fs13.pathExists(txtFile)) {
|
|
5208
|
-
const
|
|
5282
|
+
const text16 = await fs13.readFile(txtFile, "utf-8");
|
|
5209
5283
|
await fs13.remove(txtFile);
|
|
5210
|
-
return
|
|
5284
|
+
return text16.trim();
|
|
5211
5285
|
}
|
|
5212
5286
|
} catch {
|
|
5213
5287
|
}
|
|
@@ -5308,16 +5382,34 @@ var displayPaused = false;
|
|
|
5308
5382
|
async function buildCommand(prdPath, options = {}) {
|
|
5309
5383
|
const config = new Config();
|
|
5310
5384
|
if (!config.isConfigured()) {
|
|
5311
|
-
|
|
5385
|
+
console.log(chalk18.yellow(`
|
|
5386
|
+
\u26A0\uFE0F CodeBakers isn't set up yet.
|
|
5387
|
+
|
|
5388
|
+
Run this first:
|
|
5389
|
+
${chalk18.cyan("codebakers setup")}
|
|
5390
|
+
`));
|
|
5312
5391
|
return;
|
|
5313
5392
|
}
|
|
5314
5393
|
const anthropicCreds = config.getCredentials("anthropic");
|
|
5315
5394
|
if (!anthropicCreds?.apiKey) {
|
|
5316
|
-
|
|
5395
|
+
console.log(chalk18.yellow(`
|
|
5396
|
+
\u26A0\uFE0F Anthropic API key not configured.
|
|
5397
|
+
|
|
5398
|
+
The parallel build needs Claude AI to work.
|
|
5399
|
+
|
|
5400
|
+
Run this to add your API key:
|
|
5401
|
+
${chalk18.cyan("codebakers setup")}
|
|
5402
|
+
`));
|
|
5317
5403
|
return;
|
|
5318
5404
|
}
|
|
5319
5405
|
let prdFile = prdPath;
|
|
5320
5406
|
if (!prdFile) {
|
|
5407
|
+
console.log(chalk18.dim(`
|
|
5408
|
+
A PRD (Product Requirements Document) describes what you want to build.
|
|
5409
|
+
|
|
5410
|
+
Don't have one? Create one with:
|
|
5411
|
+
${chalk18.cyan("codebakers prd-maker")}
|
|
5412
|
+
`));
|
|
5321
5413
|
const file = await p17.text({
|
|
5322
5414
|
message: "Path to PRD file:",
|
|
5323
5415
|
placeholder: "./my-app-prd.md",
|
|
@@ -5327,13 +5419,25 @@ async function buildCommand(prdPath, options = {}) {
|
|
|
5327
5419
|
prdFile = file;
|
|
5328
5420
|
}
|
|
5329
5421
|
if (!await fs14.pathExists(prdFile)) {
|
|
5330
|
-
|
|
5422
|
+
console.log(chalk18.red(`
|
|
5423
|
+
\u274C PRD file not found: ${prdFile}
|
|
5424
|
+
|
|
5425
|
+
Make sure the file exists, or create one:
|
|
5426
|
+
${chalk18.cyan("codebakers prd-maker")}
|
|
5427
|
+
`));
|
|
5331
5428
|
return;
|
|
5332
5429
|
}
|
|
5333
5430
|
const prdContent = await fs14.readFile(prdFile, "utf-8");
|
|
5334
5431
|
console.log(chalk18.cyan(`
|
|
5335
5432
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
5336
5433
|
\u2551 \u{1F680} CODEBAKERS PARALLEL BUILD \u2551
|
|
5434
|
+
\u2551 \u2551
|
|
5435
|
+
\u2551 This will: \u2551
|
|
5436
|
+
\u2551 1. Analyze your PRD \u2551
|
|
5437
|
+
\u2551 2. Break it into features \u2551
|
|
5438
|
+
\u2551 3. Run up to 3 AI agents in parallel \u2551
|
|
5439
|
+
\u2551 4. Auto-fix any errors \u2551
|
|
5440
|
+
\u2551 5. Merge everything together \u2551
|
|
5337
5441
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
5338
5442
|
`));
|
|
5339
5443
|
const anthropic = new Anthropic4({ apiKey: anthropicCreds.apiKey });
|
|
@@ -5450,8 +5554,8 @@ Think about:
|
|
|
5450
5554
|
- What needs data from other features (dashboards, reports)?`
|
|
5451
5555
|
}]
|
|
5452
5556
|
});
|
|
5453
|
-
const
|
|
5454
|
-
const jsonMatch =
|
|
5557
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
5558
|
+
const jsonMatch = text16.match(/\{[\s\S]*\}/);
|
|
5455
5559
|
if (!jsonMatch) {
|
|
5456
5560
|
throw new Error("Failed to parse PRD analysis");
|
|
5457
5561
|
}
|
|
@@ -5695,9 +5799,9 @@ Start with the index.ts that exports everything.`
|
|
|
5695
5799
|
max_tokens: 8192,
|
|
5696
5800
|
messages
|
|
5697
5801
|
});
|
|
5698
|
-
const
|
|
5699
|
-
messages.push({ role: "assistant", content:
|
|
5700
|
-
const questionMatch =
|
|
5802
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
5803
|
+
messages.push({ role: "assistant", content: text16 });
|
|
5804
|
+
const questionMatch = text16.match(/<<<ASK_USER>>>([\s\S]*?)<<<END_ASK>>>/);
|
|
5701
5805
|
if (questionMatch) {
|
|
5702
5806
|
const questionBlock = questionMatch[1];
|
|
5703
5807
|
const questionLine = questionBlock.match(/question:\s*(.+)/);
|
|
@@ -5726,7 +5830,7 @@ Now continue building the feature with this choice. Generate the code files.`
|
|
|
5726
5830
|
}
|
|
5727
5831
|
}
|
|
5728
5832
|
if (onProgress) onProgress(60, "Writing files...");
|
|
5729
|
-
const files = await writeFilesFromResponse(
|
|
5833
|
+
const files = await writeFilesFromResponse(text16, projectPath);
|
|
5730
5834
|
agent.files = files;
|
|
5731
5835
|
if (onProgress) onProgress(80, "Validating...");
|
|
5732
5836
|
if (files.length === 0) {
|
|
@@ -5809,8 +5913,8 @@ Common fixes:
|
|
|
5809
5913
|
- Timeout \u2192 retry with simpler approach`
|
|
5810
5914
|
}]
|
|
5811
5915
|
});
|
|
5812
|
-
const
|
|
5813
|
-
const jsonMatch =
|
|
5916
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
5917
|
+
const jsonMatch = text16.match(/\{[\s\S]*\}/);
|
|
5814
5918
|
if (!jsonMatch) {
|
|
5815
5919
|
return {
|
|
5816
5920
|
canFix: false,
|
|
@@ -5850,8 +5954,8 @@ content
|
|
|
5850
5954
|
Make sure all features are accessible and properly connected.`
|
|
5851
5955
|
}]
|
|
5852
5956
|
});
|
|
5853
|
-
const
|
|
5854
|
-
await writeFilesFromResponse(
|
|
5957
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
5958
|
+
await writeFilesFromResponse(text16, projectPath);
|
|
5855
5959
|
await execa9("git", ["add", "."], { cwd: projectPath });
|
|
5856
5960
|
await execa9("git", ["commit", "-m", "Integration: wire up all features"], { cwd: projectPath, reject: false });
|
|
5857
5961
|
spinner16.stop("Integration complete");
|
|
@@ -5943,11 +6047,11 @@ var ProgressDisplay = class {
|
|
|
5943
6047
|
return chalk18.green("\u2588".repeat(filled)) + chalk18.gray("\u2591".repeat(empty));
|
|
5944
6048
|
}
|
|
5945
6049
|
};
|
|
5946
|
-
async function writeFilesFromResponse(
|
|
6050
|
+
async function writeFilesFromResponse(text16, projectPath) {
|
|
5947
6051
|
const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
|
|
5948
6052
|
const files = [];
|
|
5949
6053
|
let match;
|
|
5950
|
-
while ((match = fileRegex.exec(
|
|
6054
|
+
while ((match = fileRegex.exec(text16)) !== null) {
|
|
5951
6055
|
const filePath = path13.join(projectPath, match[1].trim());
|
|
5952
6056
|
const content = match[2].trim();
|
|
5953
6057
|
await fs14.ensureDir(path13.dirname(filePath));
|
|
@@ -5969,93 +6073,94 @@ import open2 from "open";
|
|
|
5969
6073
|
import { execa as execa10 } from "execa";
|
|
5970
6074
|
var INTEGRATIONS = [
|
|
5971
6075
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
5972
|
-
// AUTH
|
|
6076
|
+
// AUTH
|
|
5973
6077
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
5974
6078
|
{
|
|
5975
6079
|
id: "clerk",
|
|
5976
6080
|
name: "Clerk",
|
|
5977
|
-
description: "
|
|
6081
|
+
description: "User management & authentication",
|
|
5978
6082
|
category: "auth",
|
|
5979
6083
|
icon: "\u{1F510}",
|
|
5980
|
-
|
|
5981
|
-
oauthUrl: "https://dashboard.clerk.com/apps/new",
|
|
6084
|
+
dashboardUrl: "https://dashboard.clerk.com",
|
|
5982
6085
|
envVars: ["NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", "CLERK_SECRET_KEY"],
|
|
6086
|
+
envVarHints: {
|
|
6087
|
+
"NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY": "pk_test_... or pk_live_...",
|
|
6088
|
+
"CLERK_SECRET_KEY": "sk_test_... or sk_live_..."
|
|
6089
|
+
},
|
|
5983
6090
|
packages: ["@clerk/nextjs"],
|
|
5984
|
-
docs: "https://clerk.com/docs"
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
icon: "\u{1F512}",
|
|
5992
|
-
authType: "oauth",
|
|
5993
|
-
oauthUrl: "https://manage.auth0.com/dashboard",
|
|
5994
|
-
envVars: ["AUTH0_SECRET", "AUTH0_BASE_URL", "AUTH0_ISSUER_BASE_URL", "AUTH0_CLIENT_ID", "AUTH0_CLIENT_SECRET"],
|
|
5995
|
-
packages: ["@auth0/nextjs-auth0"],
|
|
5996
|
-
docs: "https://auth0.com/docs"
|
|
5997
|
-
},
|
|
5998
|
-
{
|
|
5999
|
-
id: "nextauth",
|
|
6000
|
-
name: "NextAuth.js",
|
|
6001
|
-
description: "Open source authentication for Next.js",
|
|
6002
|
-
category: "auth",
|
|
6003
|
-
icon: "\u{1F511}",
|
|
6004
|
-
authType: "npm",
|
|
6005
|
-
envVars: ["NEXTAUTH_SECRET", "NEXTAUTH_URL"],
|
|
6006
|
-
packages: ["next-auth"],
|
|
6007
|
-
docs: "https://next-auth.js.org"
|
|
6091
|
+
docs: "https://clerk.com/docs",
|
|
6092
|
+
steps: [
|
|
6093
|
+
"Sign up or log in at dashboard.clerk.com",
|
|
6094
|
+
"Create a new application",
|
|
6095
|
+
"Go to API Keys in the sidebar",
|
|
6096
|
+
"Copy Publishable Key and Secret Key"
|
|
6097
|
+
]
|
|
6008
6098
|
},
|
|
6009
6099
|
{
|
|
6010
6100
|
id: "supabase-auth",
|
|
6011
6101
|
name: "Supabase Auth",
|
|
6012
|
-
description: "
|
|
6102
|
+
description: "Auth with email, social, magic links",
|
|
6013
6103
|
category: "auth",
|
|
6014
6104
|
icon: "\u26A1",
|
|
6015
|
-
|
|
6016
|
-
oauthUrl: "https://supabase.com/dashboard/projects",
|
|
6105
|
+
dashboardUrl: "https://supabase.com/dashboard",
|
|
6017
6106
|
envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY"],
|
|
6107
|
+
envVarHints: {
|
|
6108
|
+
"NEXT_PUBLIC_SUPABASE_URL": "https://xxx.supabase.co",
|
|
6109
|
+
"NEXT_PUBLIC_SUPABASE_ANON_KEY": "eyJ... (anon public key)"
|
|
6110
|
+
},
|
|
6018
6111
|
packages: ["@supabase/supabase-js", "@supabase/auth-helpers-nextjs"],
|
|
6019
|
-
docs: "https://supabase.com/docs/guides/auth"
|
|
6112
|
+
docs: "https://supabase.com/docs/guides/auth",
|
|
6113
|
+
steps: [
|
|
6114
|
+
"Sign in at supabase.com/dashboard",
|
|
6115
|
+
"Create or select a project",
|
|
6116
|
+
"Go to Settings > API",
|
|
6117
|
+
"Copy Project URL and anon/public key"
|
|
6118
|
+
]
|
|
6020
6119
|
},
|
|
6021
6120
|
{
|
|
6022
|
-
id: "
|
|
6023
|
-
name: "
|
|
6024
|
-
description: "
|
|
6121
|
+
id: "nextauth",
|
|
6122
|
+
name: "NextAuth.js",
|
|
6123
|
+
description: "Open source auth for Next.js",
|
|
6025
6124
|
category: "auth",
|
|
6026
|
-
icon: "\u{
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6125
|
+
icon: "\u{1F511}",
|
|
6126
|
+
dashboardUrl: "https://next-auth.js.org",
|
|
6127
|
+
envVars: ["NEXTAUTH_SECRET", "NEXTAUTH_URL"],
|
|
6128
|
+
envVarHints: {
|
|
6129
|
+
"NEXTAUTH_SECRET": "Run: openssl rand -base64 32",
|
|
6130
|
+
"NEXTAUTH_URL": "http://localhost:3000"
|
|
6131
|
+
},
|
|
6132
|
+
packages: ["next-auth"],
|
|
6133
|
+
docs: "https://next-auth.js.org",
|
|
6134
|
+
steps: [
|
|
6135
|
+
"No signup needed - open source!",
|
|
6136
|
+
"Generate secret: openssl rand -base64 32",
|
|
6137
|
+
"Set URL to your app (localhost for dev)"
|
|
6138
|
+
]
|
|
6032
6139
|
},
|
|
6033
6140
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6034
|
-
//
|
|
6141
|
+
// DATABASE
|
|
6035
6142
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6036
6143
|
{
|
|
6037
6144
|
id: "supabase",
|
|
6038
6145
|
name: "Supabase",
|
|
6039
|
-
description: "Postgres
|
|
6146
|
+
description: "Postgres + Realtime + Auth + Storage",
|
|
6040
6147
|
category: "database",
|
|
6041
6148
|
icon: "\u26A1",
|
|
6042
|
-
|
|
6043
|
-
oauthUrl: "https://supabase.com/dashboard/projects",
|
|
6149
|
+
dashboardUrl: "https://supabase.com/dashboard",
|
|
6044
6150
|
envVars: ["NEXT_PUBLIC_SUPABASE_URL", "NEXT_PUBLIC_SUPABASE_ANON_KEY", "SUPABASE_SERVICE_ROLE_KEY"],
|
|
6151
|
+
envVarHints: {
|
|
6152
|
+
"NEXT_PUBLIC_SUPABASE_URL": "https://xxx.supabase.co",
|
|
6153
|
+
"NEXT_PUBLIC_SUPABASE_ANON_KEY": "eyJ... (anon key)",
|
|
6154
|
+
"SUPABASE_SERVICE_ROLE_KEY": "eyJ... (service_role key - keep secret!)"
|
|
6155
|
+
},
|
|
6045
6156
|
packages: ["@supabase/supabase-js"],
|
|
6046
|
-
docs: "https://supabase.com/docs"
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
icon: "\u{1FA90}",
|
|
6054
|
-
authType: "oauth",
|
|
6055
|
-
oauthUrl: "https://app.planetscale.com",
|
|
6056
|
-
envVars: ["DATABASE_URL"],
|
|
6057
|
-
packages: ["@planetscale/database"],
|
|
6058
|
-
docs: "https://planetscale.com/docs"
|
|
6157
|
+
docs: "https://supabase.com/docs",
|
|
6158
|
+
steps: [
|
|
6159
|
+
"Sign in at supabase.com/dashboard",
|
|
6160
|
+
"Create a new project",
|
|
6161
|
+
"Go to Settings > API",
|
|
6162
|
+
"Copy URL, anon key, and service_role key"
|
|
6163
|
+
]
|
|
6059
6164
|
},
|
|
6060
6165
|
{
|
|
6061
6166
|
id: "neon",
|
|
@@ -6063,57 +6168,58 @@ var INTEGRATIONS = [
|
|
|
6063
6168
|
description: "Serverless Postgres",
|
|
6064
6169
|
category: "database",
|
|
6065
6170
|
icon: "\u{1F418}",
|
|
6066
|
-
|
|
6067
|
-
oauthUrl: "https://console.neon.tech",
|
|
6171
|
+
dashboardUrl: "https://console.neon.tech",
|
|
6068
6172
|
envVars: ["DATABASE_URL"],
|
|
6173
|
+
envVarHints: {
|
|
6174
|
+
"DATABASE_URL": "postgres://user:pass@xxx.neon.tech/db?sslmode=require"
|
|
6175
|
+
},
|
|
6069
6176
|
packages: ["@neondatabase/serverless"],
|
|
6070
|
-
docs: "https://neon.tech/docs"
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
icon: "\u{1F422}",
|
|
6078
|
-
authType: "oauth",
|
|
6079
|
-
oauthUrl: "https://turso.tech/app",
|
|
6080
|
-
envVars: ["TURSO_DATABASE_URL", "TURSO_AUTH_TOKEN"],
|
|
6081
|
-
packages: ["@libsql/client"],
|
|
6082
|
-
docs: "https://docs.turso.tech"
|
|
6177
|
+
docs: "https://neon.tech/docs",
|
|
6178
|
+
steps: [
|
|
6179
|
+
"Sign in at console.neon.tech",
|
|
6180
|
+
"Create a project",
|
|
6181
|
+
"Go to Dashboard > Connection Details",
|
|
6182
|
+
"Copy the connection string"
|
|
6183
|
+
]
|
|
6083
6184
|
},
|
|
6084
6185
|
{
|
|
6085
|
-
id: "
|
|
6086
|
-
name: "
|
|
6087
|
-
description: "
|
|
6186
|
+
id: "planetscale",
|
|
6187
|
+
name: "PlanetScale",
|
|
6188
|
+
description: "Serverless MySQL",
|
|
6088
6189
|
category: "database",
|
|
6089
|
-
icon: "\u{
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6190
|
+
icon: "\u{1FA90}",
|
|
6191
|
+
dashboardUrl: "https://app.planetscale.com",
|
|
6192
|
+
envVars: ["DATABASE_URL"],
|
|
6193
|
+
envVarHints: {
|
|
6194
|
+
"DATABASE_URL": "mysql://user:pass@xxx.psdb.cloud/db?sslaccept=strict"
|
|
6195
|
+
},
|
|
6196
|
+
packages: ["@planetscale/database"],
|
|
6197
|
+
docs: "https://planetscale.com/docs",
|
|
6198
|
+
steps: [
|
|
6199
|
+
"Sign in at app.planetscale.com",
|
|
6200
|
+
"Create a database",
|
|
6201
|
+
"Go to Connect > Create password",
|
|
6202
|
+
"Copy the connection string"
|
|
6203
|
+
]
|
|
6095
6204
|
},
|
|
6096
6205
|
{
|
|
6097
6206
|
id: "prisma",
|
|
6098
6207
|
name: "Prisma",
|
|
6099
|
-
description: "Type-safe ORM
|
|
6208
|
+
description: "Type-safe ORM",
|
|
6100
6209
|
category: "database",
|
|
6101
6210
|
icon: "\u{1F537}",
|
|
6102
|
-
|
|
6211
|
+
dashboardUrl: "https://prisma.io",
|
|
6103
6212
|
envVars: ["DATABASE_URL"],
|
|
6213
|
+
envVarHints: {
|
|
6214
|
+
"DATABASE_URL": "Your database connection string"
|
|
6215
|
+
},
|
|
6104
6216
|
packages: ["prisma", "@prisma/client"],
|
|
6105
|
-
docs: "https://
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
category: "database",
|
|
6112
|
-
icon: "\u{1F4A7}",
|
|
6113
|
-
authType: "npm",
|
|
6114
|
-
envVars: ["DATABASE_URL"],
|
|
6115
|
-
packages: ["drizzle-orm", "drizzle-kit"],
|
|
6116
|
-
docs: "https://orm.drizzle.team"
|
|
6217
|
+
docs: "https://prisma.io/docs",
|
|
6218
|
+
steps: [
|
|
6219
|
+
"No account needed - Prisma is open source!",
|
|
6220
|
+
"Use your existing database URL",
|
|
6221
|
+
"Run: npx prisma init"
|
|
6222
|
+
]
|
|
6117
6223
|
},
|
|
6118
6224
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6119
6225
|
// PAYMENTS
|
|
@@ -6121,38 +6227,46 @@ var INTEGRATIONS = [
|
|
|
6121
6227
|
{
|
|
6122
6228
|
id: "stripe",
|
|
6123
6229
|
name: "Stripe",
|
|
6124
|
-
description: "
|
|
6230
|
+
description: "Payments & subscriptions",
|
|
6125
6231
|
category: "payments",
|
|
6126
6232
|
icon: "\u{1F4B3}",
|
|
6127
|
-
|
|
6128
|
-
oauthUrl: "https://dashboard.stripe.com/apikeys",
|
|
6233
|
+
dashboardUrl: "https://dashboard.stripe.com/apikeys",
|
|
6129
6234
|
envVars: ["STRIPE_SECRET_KEY", "NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY", "STRIPE_WEBHOOK_SECRET"],
|
|
6235
|
+
envVarHints: {
|
|
6236
|
+
"STRIPE_SECRET_KEY": "sk_test_... or sk_live_...",
|
|
6237
|
+
"NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY": "pk_test_... or pk_live_...",
|
|
6238
|
+
"STRIPE_WEBHOOK_SECRET": "whsec_... (from Webhooks page)"
|
|
6239
|
+
},
|
|
6130
6240
|
packages: ["stripe", "@stripe/stripe-js"],
|
|
6131
|
-
docs: "https://stripe.com/docs"
|
|
6241
|
+
docs: "https://stripe.com/docs",
|
|
6242
|
+
steps: [
|
|
6243
|
+
"Sign in at dashboard.stripe.com",
|
|
6244
|
+
"Go to Developers > API keys",
|
|
6245
|
+
"Copy Publishable and Secret keys",
|
|
6246
|
+
"For webhooks: Developers > Webhooks > Add endpoint"
|
|
6247
|
+
]
|
|
6132
6248
|
},
|
|
6133
6249
|
{
|
|
6134
6250
|
id: "lemonsqueezy",
|
|
6135
6251
|
name: "Lemon Squeezy",
|
|
6136
|
-
description: "
|
|
6252
|
+
description: "Payments with tax handling (MoR)",
|
|
6137
6253
|
category: "payments",
|
|
6138
6254
|
icon: "\u{1F34B}",
|
|
6139
|
-
|
|
6140
|
-
oauthUrl: "https://app.lemonsqueezy.com/settings/api",
|
|
6255
|
+
dashboardUrl: "https://app.lemonsqueezy.com/settings/api",
|
|
6141
6256
|
envVars: ["LEMONSQUEEZY_API_KEY", "LEMONSQUEEZY_STORE_ID", "LEMONSQUEEZY_WEBHOOK_SECRET"],
|
|
6257
|
+
envVarHints: {
|
|
6258
|
+
"LEMONSQUEEZY_API_KEY": "From Settings > API",
|
|
6259
|
+
"LEMONSQUEEZY_STORE_ID": "Your store ID number",
|
|
6260
|
+
"LEMONSQUEEZY_WEBHOOK_SECRET": "From Settings > Webhooks"
|
|
6261
|
+
},
|
|
6142
6262
|
packages: ["@lemonsqueezy/lemonsqueezy.js"],
|
|
6143
|
-
docs: "https://docs.lemonsqueezy.com"
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
icon: "\u{1F3D3}",
|
|
6151
|
-
authType: "oauth",
|
|
6152
|
-
oauthUrl: "https://vendors.paddle.com/authentication",
|
|
6153
|
-
envVars: ["PADDLE_VENDOR_ID", "PADDLE_API_KEY", "PADDLE_PUBLIC_KEY"],
|
|
6154
|
-
packages: ["@paddle/paddle-js"],
|
|
6155
|
-
docs: "https://developer.paddle.com"
|
|
6263
|
+
docs: "https://docs.lemonsqueezy.com",
|
|
6264
|
+
steps: [
|
|
6265
|
+
"Sign in at app.lemonsqueezy.com",
|
|
6266
|
+
"Go to Settings > API",
|
|
6267
|
+
"Create an API key",
|
|
6268
|
+
"Note your Store ID from the URL"
|
|
6269
|
+
]
|
|
6156
6270
|
},
|
|
6157
6271
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6158
6272
|
// EMAIL
|
|
@@ -6160,14 +6274,22 @@ var INTEGRATIONS = [
|
|
|
6160
6274
|
{
|
|
6161
6275
|
id: "resend",
|
|
6162
6276
|
name: "Resend",
|
|
6163
|
-
description: "Modern email API
|
|
6277
|
+
description: "Modern email API",
|
|
6164
6278
|
category: "email",
|
|
6165
6279
|
icon: "\u{1F4E7}",
|
|
6166
|
-
|
|
6167
|
-
oauthUrl: "https://resend.com/api-keys",
|
|
6280
|
+
dashboardUrl: "https://resend.com/api-keys",
|
|
6168
6281
|
envVars: ["RESEND_API_KEY"],
|
|
6282
|
+
envVarHints: {
|
|
6283
|
+
"RESEND_API_KEY": "re_..."
|
|
6284
|
+
},
|
|
6169
6285
|
packages: ["resend"],
|
|
6170
|
-
docs: "https://resend.com/docs"
|
|
6286
|
+
docs: "https://resend.com/docs",
|
|
6287
|
+
steps: [
|
|
6288
|
+
"Sign up at resend.com",
|
|
6289
|
+
"Go to API Keys",
|
|
6290
|
+
"Create a new API key",
|
|
6291
|
+
"Copy the key (only shown once!)"
|
|
6292
|
+
]
|
|
6171
6293
|
},
|
|
6172
6294
|
{
|
|
6173
6295
|
id: "sendgrid",
|
|
@@ -6175,46 +6297,19 @@ var INTEGRATIONS = [
|
|
|
6175
6297
|
description: "Email delivery service",
|
|
6176
6298
|
category: "email",
|
|
6177
6299
|
icon: "\u{1F4E8}",
|
|
6178
|
-
|
|
6179
|
-
oauthUrl: "https://app.sendgrid.com/settings/api_keys",
|
|
6300
|
+
dashboardUrl: "https://app.sendgrid.com/settings/api_keys",
|
|
6180
6301
|
envVars: ["SENDGRID_API_KEY"],
|
|
6302
|
+
envVarHints: {
|
|
6303
|
+
"SENDGRID_API_KEY": "SG.xxx..."
|
|
6304
|
+
},
|
|
6181
6305
|
packages: ["@sendgrid/mail"],
|
|
6182
|
-
docs: "https://docs.sendgrid.com"
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
icon: "\u{1F4EC}",
|
|
6190
|
-
authType: "oauth",
|
|
6191
|
-
oauthUrl: "https://account.postmarkapp.com/servers",
|
|
6192
|
-
envVars: ["POSTMARK_API_KEY"],
|
|
6193
|
-
packages: ["postmark"],
|
|
6194
|
-
docs: "https://postmarkapp.com/developer"
|
|
6195
|
-
},
|
|
6196
|
-
{
|
|
6197
|
-
id: "mailgun",
|
|
6198
|
-
name: "Mailgun",
|
|
6199
|
-
description: "Email API service",
|
|
6200
|
-
category: "email",
|
|
6201
|
-
icon: "\u{1F4EE}",
|
|
6202
|
-
authType: "oauth",
|
|
6203
|
-
oauthUrl: "https://app.mailgun.com/app/account/security/api_keys",
|
|
6204
|
-
envVars: ["MAILGUN_API_KEY", "MAILGUN_DOMAIN"],
|
|
6205
|
-
packages: ["mailgun.js"],
|
|
6206
|
-
docs: "https://documentation.mailgun.com"
|
|
6207
|
-
},
|
|
6208
|
-
{
|
|
6209
|
-
id: "react-email",
|
|
6210
|
-
name: "React Email",
|
|
6211
|
-
description: "Build emails with React components",
|
|
6212
|
-
category: "email",
|
|
6213
|
-
icon: "\u269B\uFE0F",
|
|
6214
|
-
authType: "npm",
|
|
6215
|
-
envVars: [],
|
|
6216
|
-
packages: ["react-email", "@react-email/components"],
|
|
6217
|
-
docs: "https://react.email/docs"
|
|
6306
|
+
docs: "https://docs.sendgrid.com",
|
|
6307
|
+
steps: [
|
|
6308
|
+
"Sign in at app.sendgrid.com",
|
|
6309
|
+
"Go to Settings > API Keys",
|
|
6310
|
+
"Create an API key with Mail Send permission",
|
|
6311
|
+
"Copy the key (only shown once!)"
|
|
6312
|
+
]
|
|
6218
6313
|
},
|
|
6219
6314
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6220
6315
|
// STORAGE
|
|
@@ -6225,11 +6320,20 @@ var INTEGRATIONS = [
|
|
|
6225
6320
|
description: "File uploads for Next.js",
|
|
6226
6321
|
category: "storage",
|
|
6227
6322
|
icon: "\u{1F4E4}",
|
|
6228
|
-
|
|
6229
|
-
oauthUrl: "https://uploadthing.com/dashboard",
|
|
6323
|
+
dashboardUrl: "https://uploadthing.com/dashboard",
|
|
6230
6324
|
envVars: ["UPLOADTHING_SECRET", "UPLOADTHING_APP_ID"],
|
|
6325
|
+
envVarHints: {
|
|
6326
|
+
"UPLOADTHING_SECRET": "sk_live_...",
|
|
6327
|
+
"UPLOADTHING_APP_ID": "Your app ID"
|
|
6328
|
+
},
|
|
6231
6329
|
packages: ["uploadthing", "@uploadthing/react"],
|
|
6232
|
-
docs: "https://docs.uploadthing.com"
|
|
6330
|
+
docs: "https://docs.uploadthing.com",
|
|
6331
|
+
steps: [
|
|
6332
|
+
"Sign in at uploadthing.com",
|
|
6333
|
+
"Create or select an app",
|
|
6334
|
+
"Go to API Keys",
|
|
6335
|
+
"Copy Secret and App ID"
|
|
6336
|
+
]
|
|
6233
6337
|
},
|
|
6234
6338
|
{
|
|
6235
6339
|
id: "cloudinary",
|
|
@@ -6237,85 +6341,20 @@ var INTEGRATIONS = [
|
|
|
6237
6341
|
description: "Image & video management",
|
|
6238
6342
|
category: "storage",
|
|
6239
6343
|
icon: "\u2601\uFE0F",
|
|
6240
|
-
|
|
6241
|
-
oauthUrl: "https://console.cloudinary.com/settings/api-keys",
|
|
6344
|
+
dashboardUrl: "https://console.cloudinary.com/settings/api-keys",
|
|
6242
6345
|
envVars: ["CLOUDINARY_CLOUD_NAME", "CLOUDINARY_API_KEY", "CLOUDINARY_API_SECRET"],
|
|
6346
|
+
envVarHints: {
|
|
6347
|
+
"CLOUDINARY_CLOUD_NAME": "Your cloud name",
|
|
6348
|
+
"CLOUDINARY_API_KEY": "Numeric API key",
|
|
6349
|
+
"CLOUDINARY_API_SECRET": "API Secret"
|
|
6350
|
+
},
|
|
6243
6351
|
packages: ["cloudinary"],
|
|
6244
|
-
docs: "https://cloudinary.com/documentation"
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
category: "storage",
|
|
6251
|
-
icon: "\u{1FAA3}",
|
|
6252
|
-
authType: "oauth",
|
|
6253
|
-
oauthUrl: "https://console.aws.amazon.com/iam/home#/security_credentials",
|
|
6254
|
-
envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION", "AWS_S3_BUCKET"],
|
|
6255
|
-
packages: ["@aws-sdk/client-s3"],
|
|
6256
|
-
docs: "https://docs.aws.amazon.com/s3"
|
|
6257
|
-
},
|
|
6258
|
-
{
|
|
6259
|
-
id: "vercel-blob",
|
|
6260
|
-
name: "Vercel Blob",
|
|
6261
|
-
description: "File storage by Vercel",
|
|
6262
|
-
category: "storage",
|
|
6263
|
-
icon: "\u25B2",
|
|
6264
|
-
authType: "oauth",
|
|
6265
|
-
oauthUrl: "https://vercel.com/dashboard/stores",
|
|
6266
|
-
envVars: ["BLOB_READ_WRITE_TOKEN"],
|
|
6267
|
-
packages: ["@vercel/blob"],
|
|
6268
|
-
docs: "https://vercel.com/docs/storage/vercel-blob"
|
|
6269
|
-
},
|
|
6270
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
6271
|
-
// ANALYTICS
|
|
6272
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
6273
|
-
{
|
|
6274
|
-
id: "vercel-analytics",
|
|
6275
|
-
name: "Vercel Analytics",
|
|
6276
|
-
description: "Web analytics by Vercel",
|
|
6277
|
-
category: "analytics",
|
|
6278
|
-
icon: "\u{1F4CA}",
|
|
6279
|
-
authType: "npm",
|
|
6280
|
-
envVars: [],
|
|
6281
|
-
packages: ["@vercel/analytics"],
|
|
6282
|
-
docs: "https://vercel.com/docs/analytics"
|
|
6283
|
-
},
|
|
6284
|
-
{
|
|
6285
|
-
id: "posthog",
|
|
6286
|
-
name: "PostHog",
|
|
6287
|
-
description: "Product analytics & feature flags",
|
|
6288
|
-
category: "analytics",
|
|
6289
|
-
icon: "\u{1F994}",
|
|
6290
|
-
authType: "oauth",
|
|
6291
|
-
oauthUrl: "https://app.posthog.com/project/settings",
|
|
6292
|
-
envVars: ["NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST"],
|
|
6293
|
-
packages: ["posthog-js"],
|
|
6294
|
-
docs: "https://posthog.com/docs"
|
|
6295
|
-
},
|
|
6296
|
-
{
|
|
6297
|
-
id: "mixpanel",
|
|
6298
|
-
name: "Mixpanel",
|
|
6299
|
-
description: "Event-based analytics",
|
|
6300
|
-
category: "analytics",
|
|
6301
|
-
icon: "\u{1F4C8}",
|
|
6302
|
-
authType: "oauth",
|
|
6303
|
-
oauthUrl: "https://mixpanel.com/settings/project",
|
|
6304
|
-
envVars: ["NEXT_PUBLIC_MIXPANEL_TOKEN"],
|
|
6305
|
-
packages: ["mixpanel-browser"],
|
|
6306
|
-
docs: "https://docs.mixpanel.com"
|
|
6307
|
-
},
|
|
6308
|
-
{
|
|
6309
|
-
id: "plausible",
|
|
6310
|
-
name: "Plausible",
|
|
6311
|
-
description: "Privacy-friendly analytics",
|
|
6312
|
-
category: "analytics",
|
|
6313
|
-
icon: "\u{1F4C9}",
|
|
6314
|
-
authType: "oauth",
|
|
6315
|
-
oauthUrl: "https://plausible.io/sites",
|
|
6316
|
-
envVars: ["NEXT_PUBLIC_PLAUSIBLE_DOMAIN"],
|
|
6317
|
-
packages: ["next-plausible"],
|
|
6318
|
-
docs: "https://plausible.io/docs"
|
|
6352
|
+
docs: "https://cloudinary.com/documentation",
|
|
6353
|
+
steps: [
|
|
6354
|
+
"Sign in at cloudinary.com",
|
|
6355
|
+
"Go to Settings > API Keys",
|
|
6356
|
+
"Copy Cloud Name, API Key, API Secret"
|
|
6357
|
+
]
|
|
6319
6358
|
},
|
|
6320
6359
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6321
6360
|
// AI
|
|
@@ -6323,14 +6362,22 @@ var INTEGRATIONS = [
|
|
|
6323
6362
|
{
|
|
6324
6363
|
id: "openai",
|
|
6325
6364
|
name: "OpenAI",
|
|
6326
|
-
description: "GPT
|
|
6365
|
+
description: "GPT-4, DALL-E, Whisper",
|
|
6327
6366
|
category: "ai",
|
|
6328
6367
|
icon: "\u{1F916}",
|
|
6329
|
-
|
|
6330
|
-
oauthUrl: "https://platform.openai.com/api-keys",
|
|
6368
|
+
dashboardUrl: "https://platform.openai.com/api-keys",
|
|
6331
6369
|
envVars: ["OPENAI_API_KEY"],
|
|
6370
|
+
envVarHints: {
|
|
6371
|
+
"OPENAI_API_KEY": "sk-..."
|
|
6372
|
+
},
|
|
6332
6373
|
packages: ["openai"],
|
|
6333
|
-
docs: "https://platform.openai.com/docs"
|
|
6374
|
+
docs: "https://platform.openai.com/docs",
|
|
6375
|
+
steps: [
|
|
6376
|
+
"Sign in at platform.openai.com",
|
|
6377
|
+
"Go to API Keys",
|
|
6378
|
+
"Create new secret key",
|
|
6379
|
+
"Copy it (only shown once!)"
|
|
6380
|
+
]
|
|
6334
6381
|
},
|
|
6335
6382
|
{
|
|
6336
6383
|
id: "anthropic",
|
|
@@ -6338,11 +6385,19 @@ var INTEGRATIONS = [
|
|
|
6338
6385
|
description: "Claude AI models",
|
|
6339
6386
|
category: "ai",
|
|
6340
6387
|
icon: "\u{1F9E0}",
|
|
6341
|
-
|
|
6342
|
-
oauthUrl: "https://console.anthropic.com/settings/keys",
|
|
6388
|
+
dashboardUrl: "https://console.anthropic.com/settings/keys",
|
|
6343
6389
|
envVars: ["ANTHROPIC_API_KEY"],
|
|
6390
|
+
envVarHints: {
|
|
6391
|
+
"ANTHROPIC_API_KEY": "sk-ant-..."
|
|
6392
|
+
},
|
|
6344
6393
|
packages: ["@anthropic-ai/sdk"],
|
|
6345
|
-
docs: "https://docs.anthropic.com"
|
|
6394
|
+
docs: "https://docs.anthropic.com",
|
|
6395
|
+
steps: [
|
|
6396
|
+
"Sign in at console.anthropic.com",
|
|
6397
|
+
"Go to API Keys",
|
|
6398
|
+
"Create a new key",
|
|
6399
|
+
"Copy it (only shown once!)"
|
|
6400
|
+
]
|
|
6346
6401
|
},
|
|
6347
6402
|
{
|
|
6348
6403
|
id: "replicate",
|
|
@@ -6350,72 +6405,83 @@ var INTEGRATIONS = [
|
|
|
6350
6405
|
description: "Run ML models in the cloud",
|
|
6351
6406
|
category: "ai",
|
|
6352
6407
|
icon: "\u{1F504}",
|
|
6353
|
-
|
|
6354
|
-
oauthUrl: "https://replicate.com/account/api-tokens",
|
|
6408
|
+
dashboardUrl: "https://replicate.com/account/api-tokens",
|
|
6355
6409
|
envVars: ["REPLICATE_API_TOKEN"],
|
|
6410
|
+
envVarHints: {
|
|
6411
|
+
"REPLICATE_API_TOKEN": "r8_..."
|
|
6412
|
+
},
|
|
6356
6413
|
packages: ["replicate"],
|
|
6357
|
-
docs: "https://replicate.com/docs"
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
category: "ai",
|
|
6364
|
-
icon: "\u2728",
|
|
6365
|
-
authType: "npm",
|
|
6366
|
-
envVars: [],
|
|
6367
|
-
packages: ["ai"],
|
|
6368
|
-
docs: "https://sdk.vercel.ai/docs"
|
|
6369
|
-
},
|
|
6370
|
-
{
|
|
6371
|
-
id: "elevenlabs",
|
|
6372
|
-
name: "ElevenLabs",
|
|
6373
|
-
description: "AI voice generation",
|
|
6374
|
-
category: "ai",
|
|
6375
|
-
icon: "\u{1F399}\uFE0F",
|
|
6376
|
-
authType: "oauth",
|
|
6377
|
-
oauthUrl: "https://elevenlabs.io/app/settings/api-keys",
|
|
6378
|
-
envVars: ["ELEVENLABS_API_KEY"],
|
|
6379
|
-
packages: ["elevenlabs"],
|
|
6380
|
-
docs: "https://elevenlabs.io/docs"
|
|
6414
|
+
docs: "https://replicate.com/docs",
|
|
6415
|
+
steps: [
|
|
6416
|
+
"Sign in at replicate.com",
|
|
6417
|
+
"Go to Account > API Tokens",
|
|
6418
|
+
"Create a token"
|
|
6419
|
+
]
|
|
6381
6420
|
},
|
|
6382
6421
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6383
|
-
//
|
|
6422
|
+
// ANALYTICS
|
|
6384
6423
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6385
6424
|
{
|
|
6386
|
-
id: "
|
|
6387
|
-
name: "
|
|
6388
|
-
description: "
|
|
6389
|
-
category: "
|
|
6390
|
-
icon: "\u{
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6425
|
+
id: "posthog",
|
|
6426
|
+
name: "PostHog",
|
|
6427
|
+
description: "Product analytics & feature flags",
|
|
6428
|
+
category: "analytics",
|
|
6429
|
+
icon: "\u{1F994}",
|
|
6430
|
+
dashboardUrl: "https://app.posthog.com/project/settings",
|
|
6431
|
+
envVars: ["NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST"],
|
|
6432
|
+
envVarHints: {
|
|
6433
|
+
"NEXT_PUBLIC_POSTHOG_KEY": "phc_...",
|
|
6434
|
+
"NEXT_PUBLIC_POSTHOG_HOST": "https://app.posthog.com (or your self-hosted URL)"
|
|
6435
|
+
},
|
|
6436
|
+
packages: ["posthog-js"],
|
|
6437
|
+
docs: "https://posthog.com/docs",
|
|
6438
|
+
steps: [
|
|
6439
|
+
"Sign up at posthog.com",
|
|
6440
|
+
"Create or select a project",
|
|
6441
|
+
"Go to Project Settings",
|
|
6442
|
+
"Copy Project API Key"
|
|
6443
|
+
]
|
|
6396
6444
|
},
|
|
6397
6445
|
{
|
|
6398
|
-
id: "
|
|
6399
|
-
name: "
|
|
6400
|
-
description: "
|
|
6401
|
-
category: "
|
|
6402
|
-
icon: "\
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6446
|
+
id: "vercel-analytics",
|
|
6447
|
+
name: "Vercel Analytics",
|
|
6448
|
+
description: "Web analytics by Vercel",
|
|
6449
|
+
category: "analytics",
|
|
6450
|
+
icon: "\u25B2",
|
|
6451
|
+
dashboardUrl: "https://vercel.com/dashboard",
|
|
6452
|
+
envVars: [],
|
|
6453
|
+
packages: ["@vercel/analytics"],
|
|
6454
|
+
docs: "https://vercel.com/docs/analytics",
|
|
6455
|
+
steps: [
|
|
6456
|
+
"No API key needed!",
|
|
6457
|
+
"Just install the package",
|
|
6458
|
+
"Add <Analytics /> to your layout",
|
|
6459
|
+
"Deploy to Vercel to see data"
|
|
6460
|
+
]
|
|
6408
6461
|
},
|
|
6462
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6463
|
+
// MONITORING
|
|
6464
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
6409
6465
|
{
|
|
6410
|
-
id: "
|
|
6411
|
-
name: "
|
|
6412
|
-
description: "
|
|
6413
|
-
category: "
|
|
6414
|
-
icon: "\u{
|
|
6415
|
-
|
|
6416
|
-
envVars: ["
|
|
6417
|
-
|
|
6418
|
-
|
|
6466
|
+
id: "sentry",
|
|
6467
|
+
name: "Sentry",
|
|
6468
|
+
description: "Error tracking & performance",
|
|
6469
|
+
category: "monitoring",
|
|
6470
|
+
icon: "\u{1F41B}",
|
|
6471
|
+
dashboardUrl: "https://sentry.io/settings/account/api/auth-tokens/",
|
|
6472
|
+
envVars: ["SENTRY_DSN", "SENTRY_AUTH_TOKEN"],
|
|
6473
|
+
envVarHints: {
|
|
6474
|
+
"SENTRY_DSN": "https://xxx@xxx.ingest.sentry.io/xxx",
|
|
6475
|
+
"SENTRY_AUTH_TOKEN": "From Organization Auth Tokens"
|
|
6476
|
+
},
|
|
6477
|
+
packages: ["@sentry/nextjs"],
|
|
6478
|
+
docs: "https://docs.sentry.io",
|
|
6479
|
+
steps: [
|
|
6480
|
+
"Sign up at sentry.io",
|
|
6481
|
+
"Create a project (Next.js)",
|
|
6482
|
+
"Copy DSN from Project Settings > Client Keys",
|
|
6483
|
+
"Create auth token in Settings > Auth Tokens"
|
|
6484
|
+
]
|
|
6419
6485
|
},
|
|
6420
6486
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6421
6487
|
// MESSAGING
|
|
@@ -6426,11 +6492,21 @@ var INTEGRATIONS = [
|
|
|
6426
6492
|
description: "SMS, voice & WhatsApp",
|
|
6427
6493
|
category: "messaging",
|
|
6428
6494
|
icon: "\u{1F4F1}",
|
|
6429
|
-
|
|
6430
|
-
oauthUrl: "https://console.twilio.com/us1/account/keys-credentials/api-keys",
|
|
6495
|
+
dashboardUrl: "https://console.twilio.com",
|
|
6431
6496
|
envVars: ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN", "TWILIO_PHONE_NUMBER"],
|
|
6497
|
+
envVarHints: {
|
|
6498
|
+
"TWILIO_ACCOUNT_SID": "AC... (from Console Dashboard)",
|
|
6499
|
+
"TWILIO_AUTH_TOKEN": "From Console Dashboard (click to reveal)",
|
|
6500
|
+
"TWILIO_PHONE_NUMBER": "+1234567890 (buy in Phone Numbers)"
|
|
6501
|
+
},
|
|
6432
6502
|
packages: ["twilio"],
|
|
6433
|
-
docs: "https://www.twilio.com/docs"
|
|
6503
|
+
docs: "https://www.twilio.com/docs",
|
|
6504
|
+
steps: [
|
|
6505
|
+
"Sign up at twilio.com",
|
|
6506
|
+
"Go to Console Dashboard",
|
|
6507
|
+
"Copy Account SID and Auth Token",
|
|
6508
|
+
"Buy a phone number in Phone Numbers > Manage"
|
|
6509
|
+
]
|
|
6434
6510
|
},
|
|
6435
6511
|
{
|
|
6436
6512
|
id: "pusher",
|
|
@@ -6438,62 +6514,23 @@ var INTEGRATIONS = [
|
|
|
6438
6514
|
description: "Realtime websockets",
|
|
6439
6515
|
category: "messaging",
|
|
6440
6516
|
icon: "\u{1F514}",
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6517
|
+
dashboardUrl: "https://dashboard.pusher.com",
|
|
6518
|
+
envVars: ["PUSHER_APP_ID", "PUSHER_KEY", "PUSHER_SECRET", "NEXT_PUBLIC_PUSHER_KEY", "PUSHER_CLUSTER"],
|
|
6519
|
+
envVarHints: {
|
|
6520
|
+
"PUSHER_APP_ID": "App ID from App Keys",
|
|
6521
|
+
"PUSHER_KEY": "Key from App Keys",
|
|
6522
|
+
"PUSHER_SECRET": "Secret from App Keys",
|
|
6523
|
+
"NEXT_PUBLIC_PUSHER_KEY": "Same as PUSHER_KEY",
|
|
6524
|
+
"PUSHER_CLUSTER": "e.g., us2, eu, ap1"
|
|
6525
|
+
},
|
|
6444
6526
|
packages: ["pusher", "pusher-js"],
|
|
6445
|
-
docs: "https://pusher.com/docs"
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
icon: "\u{1F514}",
|
|
6453
|
-
authType: "oauth",
|
|
6454
|
-
oauthUrl: "https://dashboard.knock.app",
|
|
6455
|
-
envVars: ["KNOCK_API_KEY", "NEXT_PUBLIC_KNOCK_PUBLIC_API_KEY"],
|
|
6456
|
-
packages: ["@knocklabs/node", "@knocklabs/react"],
|
|
6457
|
-
docs: "https://docs.knock.app"
|
|
6458
|
-
},
|
|
6459
|
-
{
|
|
6460
|
-
id: "stream",
|
|
6461
|
-
name: "Stream",
|
|
6462
|
-
description: "Chat & activity feeds",
|
|
6463
|
-
category: "messaging",
|
|
6464
|
-
icon: "\u{1F4AC}",
|
|
6465
|
-
authType: "oauth",
|
|
6466
|
-
oauthUrl: "https://dashboard.getstream.io",
|
|
6467
|
-
envVars: ["STREAM_API_KEY", "STREAM_API_SECRET"],
|
|
6468
|
-
packages: ["stream-chat", "stream-chat-react"],
|
|
6469
|
-
docs: "https://getstream.io/docs"
|
|
6470
|
-
},
|
|
6471
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
6472
|
-
// MONITORING
|
|
6473
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
6474
|
-
{
|
|
6475
|
-
id: "sentry",
|
|
6476
|
-
name: "Sentry",
|
|
6477
|
-
description: "Error tracking & performance",
|
|
6478
|
-
category: "monitoring",
|
|
6479
|
-
icon: "\u{1F41B}",
|
|
6480
|
-
authType: "oauth",
|
|
6481
|
-
oauthUrl: "https://sentry.io/settings/account/api/auth-tokens/",
|
|
6482
|
-
envVars: ["SENTRY_DSN", "SENTRY_AUTH_TOKEN"],
|
|
6483
|
-
packages: ["@sentry/nextjs"],
|
|
6484
|
-
docs: "https://docs.sentry.io"
|
|
6485
|
-
},
|
|
6486
|
-
{
|
|
6487
|
-
id: "logrocket",
|
|
6488
|
-
name: "LogRocket",
|
|
6489
|
-
description: "Session replay & monitoring",
|
|
6490
|
-
category: "monitoring",
|
|
6491
|
-
icon: "\u{1F680}",
|
|
6492
|
-
authType: "oauth",
|
|
6493
|
-
oauthUrl: "https://app.logrocket.com/settings/setup",
|
|
6494
|
-
envVars: ["NEXT_PUBLIC_LOGROCKET_APP_ID"],
|
|
6495
|
-
packages: ["logrocket"],
|
|
6496
|
-
docs: "https://docs.logrocket.com"
|
|
6527
|
+
docs: "https://pusher.com/docs",
|
|
6528
|
+
steps: [
|
|
6529
|
+
"Sign up at pusher.com",
|
|
6530
|
+
"Create a Channels app",
|
|
6531
|
+
"Go to App Keys",
|
|
6532
|
+
"Copy all credentials"
|
|
6533
|
+
]
|
|
6497
6534
|
},
|
|
6498
6535
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6499
6536
|
// DEPLOYMENT
|
|
@@ -6504,11 +6541,19 @@ var INTEGRATIONS = [
|
|
|
6504
6541
|
description: "Deploy Next.js apps",
|
|
6505
6542
|
category: "deployment",
|
|
6506
6543
|
icon: "\u25B2",
|
|
6507
|
-
|
|
6508
|
-
oauthUrl: "https://vercel.com/account/tokens",
|
|
6544
|
+
dashboardUrl: "https://vercel.com/account/tokens",
|
|
6509
6545
|
envVars: ["VERCEL_TOKEN"],
|
|
6546
|
+
envVarHints: {
|
|
6547
|
+
"VERCEL_TOKEN": "From Account Settings > Tokens"
|
|
6548
|
+
},
|
|
6510
6549
|
packages: ["vercel"],
|
|
6511
|
-
docs: "https://vercel.com/docs"
|
|
6550
|
+
docs: "https://vercel.com/docs",
|
|
6551
|
+
steps: [
|
|
6552
|
+
"Sign in at vercel.com",
|
|
6553
|
+
"Go to Account Settings > Tokens",
|
|
6554
|
+
"Create a new token",
|
|
6555
|
+
"Copy it"
|
|
6556
|
+
]
|
|
6512
6557
|
},
|
|
6513
6558
|
{
|
|
6514
6559
|
id: "github",
|
|
@@ -6516,73 +6561,80 @@ var INTEGRATIONS = [
|
|
|
6516
6561
|
description: "Code hosting & CI/CD",
|
|
6517
6562
|
category: "deployment",
|
|
6518
6563
|
icon: "\u{1F419}",
|
|
6519
|
-
|
|
6520
|
-
oauthUrl: "https://github.com/settings/tokens",
|
|
6564
|
+
dashboardUrl: "https://github.com/settings/tokens",
|
|
6521
6565
|
envVars: ["GITHUB_TOKEN"],
|
|
6566
|
+
envVarHints: {
|
|
6567
|
+
"GITHUB_TOKEN": "ghp_... or github_pat_..."
|
|
6568
|
+
},
|
|
6522
6569
|
packages: ["octokit"],
|
|
6523
|
-
docs: "https://docs.github.com"
|
|
6570
|
+
docs: "https://docs.github.com",
|
|
6571
|
+
steps: [
|
|
6572
|
+
"Sign in at github.com",
|
|
6573
|
+
"Go to Settings > Developer settings > Personal access tokens",
|
|
6574
|
+
"Generate new token (classic)",
|
|
6575
|
+
"Select scopes: repo, user",
|
|
6576
|
+
"Copy the token"
|
|
6577
|
+
]
|
|
6524
6578
|
},
|
|
6525
6579
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6526
|
-
//
|
|
6580
|
+
// CMS
|
|
6527
6581
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
6528
6582
|
{
|
|
6529
|
-
id: "
|
|
6530
|
-
name: "
|
|
6531
|
-
description: "
|
|
6532
|
-
category: "
|
|
6533
|
-
icon: "\u{
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
packages: ["@intercom/messenger-js-sdk"],
|
|
6550
|
-
docs: "https://developers.intercom.com"
|
|
6551
|
-
},
|
|
6552
|
-
{
|
|
6553
|
-
id: "cal",
|
|
6554
|
-
name: "Cal.com",
|
|
6555
|
-
description: "Scheduling infrastructure",
|
|
6556
|
-
category: "other",
|
|
6557
|
-
icon: "\u{1F4C5}",
|
|
6558
|
-
authType: "oauth",
|
|
6559
|
-
oauthUrl: "https://app.cal.com/settings/developer/api-keys",
|
|
6560
|
-
envVars: ["CAL_API_KEY"],
|
|
6561
|
-
packages: ["@calcom/embed-react"],
|
|
6562
|
-
docs: "https://cal.com/docs"
|
|
6583
|
+
id: "sanity",
|
|
6584
|
+
name: "Sanity",
|
|
6585
|
+
description: "Headless CMS",
|
|
6586
|
+
category: "cms",
|
|
6587
|
+
icon: "\u{1F4DD}",
|
|
6588
|
+
dashboardUrl: "https://www.sanity.io/manage",
|
|
6589
|
+
envVars: ["NEXT_PUBLIC_SANITY_PROJECT_ID", "NEXT_PUBLIC_SANITY_DATASET", "SANITY_API_TOKEN"],
|
|
6590
|
+
envVarHints: {
|
|
6591
|
+
"NEXT_PUBLIC_SANITY_PROJECT_ID": "From project settings",
|
|
6592
|
+
"NEXT_PUBLIC_SANITY_DATASET": 'Usually "production"',
|
|
6593
|
+
"SANITY_API_TOKEN": "From API > Tokens"
|
|
6594
|
+
},
|
|
6595
|
+
packages: ["@sanity/client", "next-sanity"],
|
|
6596
|
+
docs: "https://www.sanity.io/docs",
|
|
6597
|
+
steps: [
|
|
6598
|
+
"Sign in at sanity.io/manage",
|
|
6599
|
+
"Create or select a project",
|
|
6600
|
+
"Copy Project ID from settings",
|
|
6601
|
+
"Create API token in API > Tokens"
|
|
6602
|
+
]
|
|
6563
6603
|
}
|
|
6564
6604
|
];
|
|
6565
6605
|
async function integrateCommand(integrationId) {
|
|
6566
|
-
const config = new Config();
|
|
6567
6606
|
console.log(chalk19.cyan(`
|
|
6568
6607
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
6569
6608
|
\u2551 \u{1F50C} ONE-CLICK INTEGRATIONS \u2551
|
|
6570
6609
|
\u2551 \u2551
|
|
6571
|
-
\u2551 ${INTEGRATIONS.length}
|
|
6572
|
-
\u2551 Browser-based auth \u2022 Auto-install \u2022 Ready in seconds \u2551
|
|
6610
|
+
\u2551 ${INTEGRATIONS.length} services \u2022 Step-by-step guidance \u2022 Auto-setup \u2551
|
|
6573
6611
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
6574
6612
|
`));
|
|
6575
6613
|
if (integrationId) {
|
|
6576
6614
|
const integration2 = INTEGRATIONS.find((i) => i.id === integrationId);
|
|
6577
6615
|
if (!integration2) {
|
|
6578
6616
|
p18.log.error(`Unknown integration: ${integrationId}`);
|
|
6579
|
-
console.log(chalk19.dim(`
|
|
6617
|
+
console.log(chalk19.dim(`
|
|
6618
|
+
Available: ${INTEGRATIONS.map((i) => i.id).join(", ")}`));
|
|
6580
6619
|
return;
|
|
6581
6620
|
}
|
|
6582
|
-
await installIntegration(integration2
|
|
6621
|
+
await installIntegration(integration2);
|
|
6583
6622
|
return;
|
|
6584
6623
|
}
|
|
6585
6624
|
const categories = [...new Set(INTEGRATIONS.map((i) => i.category))];
|
|
6625
|
+
const categoryLabels = {
|
|
6626
|
+
auth: "\u{1F510} Authentication",
|
|
6627
|
+
database: "\u{1F5C4}\uFE0F Database",
|
|
6628
|
+
payments: "\u{1F4B3} Payments",
|
|
6629
|
+
email: "\u{1F4E7} Email",
|
|
6630
|
+
storage: "\u{1F4E6} Storage",
|
|
6631
|
+
ai: "\u{1F916} AI & ML",
|
|
6632
|
+
analytics: "\u{1F4CA} Analytics",
|
|
6633
|
+
monitoring: "\u{1F41B} Monitoring",
|
|
6634
|
+
messaging: "\u{1F4AC} Messaging",
|
|
6635
|
+
deployment: "\u{1F680} Deployment",
|
|
6636
|
+
cms: "\u{1F4DD} CMS"
|
|
6637
|
+
};
|
|
6586
6638
|
const category = await p18.select({
|
|
6587
6639
|
message: "Choose a category:",
|
|
6588
6640
|
options: [
|
|
@@ -6590,33 +6642,33 @@ async function integrateCommand(integrationId) {
|
|
|
6590
6642
|
{ value: "search", label: "\u{1F50D} Search by name" },
|
|
6591
6643
|
...categories.map((c) => ({
|
|
6592
6644
|
value: c,
|
|
6593
|
-
label:
|
|
6594
|
-
hint: `${INTEGRATIONS.filter((i) => i.category === c).length}
|
|
6645
|
+
label: categoryLabels[c] || c,
|
|
6646
|
+
hint: `${INTEGRATIONS.filter((i) => i.category === c).length} services`
|
|
6595
6647
|
}))
|
|
6596
6648
|
]
|
|
6597
6649
|
});
|
|
6598
6650
|
if (p18.isCancel(category)) return;
|
|
6599
|
-
let
|
|
6651
|
+
let filtered = INTEGRATIONS;
|
|
6600
6652
|
if (category === "search") {
|
|
6601
6653
|
const query = await p18.text({
|
|
6602
|
-
message: "Search
|
|
6603
|
-
placeholder: "stripe,
|
|
6654
|
+
message: "Search:",
|
|
6655
|
+
placeholder: "stripe, supabase, openai..."
|
|
6604
6656
|
});
|
|
6605
6657
|
if (p18.isCancel(query)) return;
|
|
6606
6658
|
const q = query.toLowerCase();
|
|
6607
|
-
|
|
6608
|
-
(i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q) || i.id.
|
|
6659
|
+
filtered = INTEGRATIONS.filter(
|
|
6660
|
+
(i) => i.name.toLowerCase().includes(q) || i.description.toLowerCase().includes(q) || i.id.includes(q)
|
|
6609
6661
|
);
|
|
6662
|
+
if (filtered.length === 0) {
|
|
6663
|
+
p18.log.warn("No integrations found");
|
|
6664
|
+
return;
|
|
6665
|
+
}
|
|
6610
6666
|
} else if (category !== "all") {
|
|
6611
|
-
|
|
6612
|
-
}
|
|
6613
|
-
if (filteredIntegrations.length === 0) {
|
|
6614
|
-
p18.log.warn("No integrations found");
|
|
6615
|
-
return;
|
|
6667
|
+
filtered = INTEGRATIONS.filter((i) => i.category === category);
|
|
6616
6668
|
}
|
|
6617
6669
|
const selected = await p18.select({
|
|
6618
|
-
message: "Select
|
|
6619
|
-
options:
|
|
6670
|
+
message: "Select integration:",
|
|
6671
|
+
options: filtered.map((i) => ({
|
|
6620
6672
|
value: i.id,
|
|
6621
6673
|
label: `${i.icon} ${i.name}`,
|
|
6622
6674
|
hint: i.description
|
|
@@ -6624,112 +6676,97 @@ async function integrateCommand(integrationId) {
|
|
|
6624
6676
|
});
|
|
6625
6677
|
if (p18.isCancel(selected)) return;
|
|
6626
6678
|
const integration = INTEGRATIONS.find((i) => i.id === selected);
|
|
6627
|
-
await installIntegration(integration
|
|
6679
|
+
await installIntegration(integration);
|
|
6628
6680
|
}
|
|
6629
|
-
async function installIntegration(integration
|
|
6681
|
+
async function installIntegration(integration) {
|
|
6630
6682
|
console.log(chalk19.cyan(`
|
|
6631
|
-
|
|
6683
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6684
|
+
\u2502 ${integration.icon} Installing ${integration.name}
|
|
6685
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6632
6686
|
`));
|
|
6633
|
-
const steps = [];
|
|
6634
6687
|
if (integration.packages && integration.packages.length > 0) {
|
|
6635
|
-
|
|
6636
|
-
|
|
6637
|
-
if (integration.envVars.length > 0) {
|
|
6638
|
-
steps.push({ name: "Configure credentials", done: false });
|
|
6639
|
-
}
|
|
6640
|
-
steps.push({ name: "Setup integration", done: false });
|
|
6641
|
-
const spinner16 = p18.spinner();
|
|
6642
|
-
if (integration.packages && integration.packages.length > 0) {
|
|
6643
|
-
spinner16.start(`Installing ${integration.packages.join(", ")}...`);
|
|
6688
|
+
const spinner17 = p18.spinner();
|
|
6689
|
+
spinner17.start(`Installing ${integration.packages.join(", ")}...`);
|
|
6644
6690
|
try {
|
|
6645
6691
|
await execa10("npm", ["install", ...integration.packages], {
|
|
6646
6692
|
cwd: process.cwd(),
|
|
6647
6693
|
reject: false
|
|
6648
6694
|
});
|
|
6649
|
-
|
|
6695
|
+
spinner17.stop(chalk19.green("\u2713 Packages installed"));
|
|
6650
6696
|
} catch {
|
|
6651
|
-
|
|
6697
|
+
spinner17.stop(chalk19.yellow("\u26A0 Package install had issues (may already be installed)"));
|
|
6652
6698
|
}
|
|
6653
6699
|
}
|
|
6654
6700
|
if (integration.envVars.length > 0) {
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6701
|
+
console.log(chalk19.cyan(`
|
|
6702
|
+
\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6703
|
+
\u2502 \u{1F4CB} HOW TO GET YOUR API KEYS
|
|
6704
|
+
\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6659
6705
|
`));
|
|
6660
|
-
|
|
6661
|
-
|
|
6706
|
+
integration.steps.forEach((step, i) => {
|
|
6707
|
+
console.log(chalk19.white(` ${i + 1}. ${step}`));
|
|
6708
|
+
});
|
|
6709
|
+
console.log(chalk19.dim(`
|
|
6710
|
+
Dashboard: ${integration.dashboardUrl}
|
|
6711
|
+
Docs: ${integration.docs}
|
|
6712
|
+
`));
|
|
6713
|
+
const openDashboard = await p18.confirm({
|
|
6714
|
+
message: `Open ${integration.name} dashboard in browser?`,
|
|
6715
|
+
initialValue: true
|
|
6716
|
+
});
|
|
6717
|
+
if (openDashboard && !p18.isCancel(openDashboard)) {
|
|
6718
|
+
await open2(integration.dashboardUrl);
|
|
6719
|
+
console.log(chalk19.dim(`
|
|
6720
|
+
\u2713 Browser opened! Get your keys and come back.
|
|
6721
|
+
`));
|
|
6722
|
+
await sleep2(2e3);
|
|
6662
6723
|
}
|
|
6724
|
+
console.log(chalk19.cyan(`
|
|
6725
|
+
Enter your credentials:
|
|
6726
|
+
`));
|
|
6663
6727
|
const credentials = {};
|
|
6664
6728
|
for (const envVar of integration.envVars) {
|
|
6729
|
+
const hint = integration.envVarHints?.[envVar] || "Paste here...";
|
|
6665
6730
|
const isPublic = envVar.startsWith("NEXT_PUBLIC_");
|
|
6666
|
-
const
|
|
6731
|
+
const label = isPublic ? chalk19.yellow("(public)") : chalk19.green("(secret)");
|
|
6667
6732
|
const value = await p18.text({
|
|
6668
|
-
message: `${envVar} ${
|
|
6669
|
-
placeholder:
|
|
6733
|
+
message: `${envVar} ${label}`,
|
|
6734
|
+
placeholder: hint,
|
|
6670
6735
|
validate: (v) => !v ? "Required" : void 0
|
|
6671
6736
|
});
|
|
6672
6737
|
if (p18.isCancel(value)) return;
|
|
6673
6738
|
credentials[envVar] = value;
|
|
6674
6739
|
}
|
|
6675
6740
|
await saveEnvVars(credentials);
|
|
6676
|
-
console.log(chalk19.green(`
|
|
6741
|
+
console.log(chalk19.green(`
|
|
6742
|
+
\u2713 Saved to .env.local
|
|
6677
6743
|
`));
|
|
6678
6744
|
}
|
|
6679
|
-
spinner16.
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6683
|
-
await fs15.ensureDir(path14.dirname(file.path));
|
|
6684
|
-
await fs15.writeFile(file.path, file.content);
|
|
6685
|
-
}
|
|
6686
|
-
}
|
|
6687
|
-
spinner16.stop("\u2713 Setup complete");
|
|
6745
|
+
const spinner16 = p18.spinner();
|
|
6746
|
+
spinner16.start("Creating setup file...");
|
|
6747
|
+
await generateSetupCode(integration);
|
|
6748
|
+
spinner16.stop(chalk19.green("\u2713 Setup complete"));
|
|
6688
6749
|
console.log(chalk19.green(`
|
|
6689
6750
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
6690
|
-
\u2551 \
|
|
6751
|
+
\u2551 \u2705 ${integration.name} installed!
|
|
6691
6752
|
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
6692
|
-
|
|
6693
|
-
|
|
6694
|
-
|
|
6695
|
-
|
|
6696
|
-
if (integration.envVars.length > 0) {
|
|
6697
|
-
console.log(chalk19.dim(` Env vars: ${integration.envVars.join(", ")}`));
|
|
6698
|
-
}
|
|
6699
|
-
console.log(`
|
|
6700
|
-
\u2551 \u{1F4DA} Docs: ${integration.docs}
|
|
6753
|
+
\u2551 \u2551
|
|
6754
|
+
\u2551 Import: import { ... } from '@/lib/${integration.id}'
|
|
6755
|
+
\u2551 Docs: ${integration.docs.substring(0, 45).padEnd(45)}\u2551
|
|
6756
|
+
\u2551 \u2551
|
|
6701
6757
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
6702
|
-
`);
|
|
6703
|
-
}
|
|
6704
|
-
function getCategoryLabel(category) {
|
|
6705
|
-
const labels = {
|
|
6706
|
-
auth: "\u{1F510} Authentication",
|
|
6707
|
-
database: "\u{1F5C4}\uFE0F Databases",
|
|
6708
|
-
payments: "\u{1F4B3} Payments",
|
|
6709
|
-
email: "\u{1F4E7} Email",
|
|
6710
|
-
storage: "\u{1F4E6} Storage",
|
|
6711
|
-
analytics: "\u{1F4CA} Analytics",
|
|
6712
|
-
ai: "\u{1F916} AI & ML",
|
|
6713
|
-
cms: "\u{1F4DD} CMS",
|
|
6714
|
-
messaging: "\u{1F4AC} Messaging",
|
|
6715
|
-
monitoring: "\u{1F50D} Monitoring",
|
|
6716
|
-
deployment: "\u{1F680} Deployment",
|
|
6717
|
-
other: "\u{1F527} Other"
|
|
6718
|
-
};
|
|
6719
|
-
return labels[category] || category;
|
|
6758
|
+
`));
|
|
6720
6759
|
}
|
|
6721
6760
|
async function saveEnvVars(vars) {
|
|
6722
6761
|
const envPath = path14.join(process.cwd(), ".env.local");
|
|
6723
6762
|
let content = "";
|
|
6724
6763
|
if (await fs15.pathExists(envPath)) {
|
|
6725
6764
|
content = await fs15.readFile(envPath, "utf-8");
|
|
6726
|
-
if (!content.endsWith("\n"))
|
|
6727
|
-
content += "\n";
|
|
6728
|
-
}
|
|
6729
|
-
content += "\n# Added by CodeBakers\n";
|
|
6765
|
+
if (!content.endsWith("\n")) content += "\n";
|
|
6730
6766
|
}
|
|
6767
|
+
content += "\n# Added by CodeBakers\n";
|
|
6731
6768
|
for (const [key, value] of Object.entries(vars)) {
|
|
6732
|
-
const regex = new RegExp(`^${key}
|
|
6769
|
+
const regex = new RegExp(`^${key}=.*$`, "m");
|
|
6733
6770
|
if (regex.test(content)) {
|
|
6734
6771
|
content = content.replace(regex, `${key}=${value}`);
|
|
6735
6772
|
} else {
|
|
@@ -6740,98 +6777,604 @@ async function saveEnvVars(vars) {
|
|
|
6740
6777
|
await fs15.writeFile(envPath, content);
|
|
6741
6778
|
}
|
|
6742
6779
|
async function generateSetupCode(integration) {
|
|
6743
|
-
const
|
|
6780
|
+
const libDir = path14.join(process.cwd(), "src", "lib");
|
|
6781
|
+
await fs15.ensureDir(libDir);
|
|
6782
|
+
let code = "";
|
|
6744
6783
|
switch (integration.id) {
|
|
6745
|
-
case "
|
|
6746
|
-
|
|
6747
|
-
|
|
6748
|
-
content: `import { clerkMiddleware } from '@clerk/nextjs/server';
|
|
6784
|
+
case "supabase":
|
|
6785
|
+
case "supabase-auth":
|
|
6786
|
+
code = `import { createClient } from '@supabase/supabase-js';
|
|
6749
6787
|
|
|
6750
|
-
|
|
6788
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
6789
|
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
6751
6790
|
|
|
6752
|
-
export const
|
|
6753
|
-
|
|
6754
|
-
};
|
|
6755
|
-
`
|
|
6756
|
-
});
|
|
6791
|
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
|
6792
|
+
`;
|
|
6757
6793
|
break;
|
|
6758
6794
|
case "stripe":
|
|
6759
|
-
|
|
6760
|
-
path: "src/lib/stripe.ts",
|
|
6761
|
-
content: `import Stripe from 'stripe';
|
|
6795
|
+
code = `import Stripe from 'stripe';
|
|
6762
6796
|
|
|
6763
6797
|
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
|
|
6764
6798
|
apiVersion: '2023-10-16',
|
|
6765
|
-
typescript: true,
|
|
6766
6799
|
});
|
|
6767
|
-
|
|
6768
|
-
});
|
|
6769
|
-
break;
|
|
6770
|
-
case "supabase":
|
|
6771
|
-
files.push({
|
|
6772
|
-
path: "src/lib/supabase.ts",
|
|
6773
|
-
content: `import { createClient } from '@supabase/supabase-js';
|
|
6774
|
-
|
|
6775
|
-
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
6776
|
-
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
6777
|
-
|
|
6778
|
-
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
|
6779
|
-
`
|
|
6780
|
-
});
|
|
6800
|
+
`;
|
|
6781
6801
|
break;
|
|
6782
6802
|
case "resend":
|
|
6783
|
-
|
|
6784
|
-
path: "src/lib/resend.ts",
|
|
6785
|
-
content: `import { Resend } from 'resend';
|
|
6803
|
+
code = `import { Resend } from 'resend';
|
|
6786
6804
|
|
|
6787
6805
|
export const resend = new Resend(process.env.RESEND_API_KEY);
|
|
6788
|
-
|
|
6789
|
-
});
|
|
6806
|
+
`;
|
|
6790
6807
|
break;
|
|
6791
6808
|
case "openai":
|
|
6792
|
-
|
|
6793
|
-
path: "src/lib/openai.ts",
|
|
6794
|
-
content: `import OpenAI from 'openai';
|
|
6809
|
+
code = `import OpenAI from 'openai';
|
|
6795
6810
|
|
|
6796
6811
|
export const openai = new OpenAI({
|
|
6797
6812
|
apiKey: process.env.OPENAI_API_KEY,
|
|
6798
6813
|
});
|
|
6799
|
-
|
|
6800
|
-
});
|
|
6814
|
+
`;
|
|
6801
6815
|
break;
|
|
6802
6816
|
case "anthropic":
|
|
6803
|
-
|
|
6804
|
-
path: "src/lib/anthropic.ts",
|
|
6805
|
-
content: `import Anthropic from '@anthropic-ai/sdk';
|
|
6817
|
+
code = `import Anthropic from '@anthropic-ai/sdk';
|
|
6806
6818
|
|
|
6807
6819
|
export const anthropic = new Anthropic({
|
|
6808
6820
|
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
6809
6821
|
});
|
|
6810
|
-
|
|
6811
|
-
});
|
|
6822
|
+
`;
|
|
6812
6823
|
break;
|
|
6813
|
-
case "
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
+
case "clerk":
|
|
6825
|
+
code = `// Clerk is configured via middleware
|
|
6826
|
+
// See: https://clerk.com/docs/quickstarts/nextjs
|
|
6827
|
+
|
|
6828
|
+
// In middleware.ts:
|
|
6829
|
+
// import { clerkMiddleware } from '@clerk/nextjs/server';
|
|
6830
|
+
// export default clerkMiddleware();
|
|
6831
|
+
|
|
6832
|
+
// In layout.tsx:
|
|
6833
|
+
// import { ClerkProvider } from '@clerk/nextjs';
|
|
6834
|
+
// <ClerkProvider>{children}</ClerkProvider>
|
|
6835
|
+
|
|
6836
|
+
export {};
|
|
6837
|
+
`;
|
|
6824
6838
|
break;
|
|
6839
|
+
default:
|
|
6840
|
+
code = `// ${integration.name}
|
|
6841
|
+
// Docs: ${integration.docs}
|
|
6842
|
+
//
|
|
6843
|
+
// Environment variables:
|
|
6844
|
+
${integration.envVars.map((v) => `// - ${v}`).join("\n")}
|
|
6845
|
+
|
|
6846
|
+
export {};
|
|
6847
|
+
`;
|
|
6825
6848
|
}
|
|
6826
|
-
|
|
6849
|
+
await fs15.writeFile(path14.join(libDir, `${integration.id}.ts`), code);
|
|
6827
6850
|
}
|
|
6828
6851
|
function sleep2(ms) {
|
|
6829
6852
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6830
6853
|
}
|
|
6831
6854
|
|
|
6832
|
-
// src/
|
|
6855
|
+
// src/commands/website.ts
|
|
6833
6856
|
import * as p19 from "@clack/prompts";
|
|
6857
|
+
import chalk20 from "chalk";
|
|
6858
|
+
import * as fs16 from "fs-extra";
|
|
6859
|
+
import * as path15 from "path";
|
|
6834
6860
|
import Anthropic5 from "@anthropic-ai/sdk";
|
|
6861
|
+
import { execa as execa11 } from "execa";
|
|
6862
|
+
var TEMPLATES = [
|
|
6863
|
+
{
|
|
6864
|
+
id: "landing",
|
|
6865
|
+
name: "Landing Page",
|
|
6866
|
+
description: "Convert visitors into customers",
|
|
6867
|
+
icon: "\u{1F680}",
|
|
6868
|
+
sections: ["hero", "features", "testimonials", "pricing", "cta", "footer"],
|
|
6869
|
+
style: "bold"
|
|
6870
|
+
},
|
|
6871
|
+
{
|
|
6872
|
+
id: "saas",
|
|
6873
|
+
name: "SaaS Website",
|
|
6874
|
+
description: "Software product marketing site",
|
|
6875
|
+
icon: "\u{1F4BB}",
|
|
6876
|
+
sections: ["hero", "features", "how-it-works", "pricing", "faq", "testimonials", "cta", "footer"],
|
|
6877
|
+
style: "minimal"
|
|
6878
|
+
},
|
|
6879
|
+
{
|
|
6880
|
+
id: "portfolio",
|
|
6881
|
+
name: "Portfolio",
|
|
6882
|
+
description: "Showcase your work",
|
|
6883
|
+
icon: "\u{1F3A8}",
|
|
6884
|
+
sections: ["hero", "about", "projects", "skills", "contact", "footer"],
|
|
6885
|
+
style: "elegant"
|
|
6886
|
+
},
|
|
6887
|
+
{
|
|
6888
|
+
id: "agency",
|
|
6889
|
+
name: "Agency Website",
|
|
6890
|
+
description: "Professional services company",
|
|
6891
|
+
icon: "\u{1F3E2}",
|
|
6892
|
+
sections: ["hero", "services", "portfolio", "team", "testimonials", "contact", "footer"],
|
|
6893
|
+
style: "corporate"
|
|
6894
|
+
},
|
|
6895
|
+
{
|
|
6896
|
+
id: "startup",
|
|
6897
|
+
name: "Startup Page",
|
|
6898
|
+
description: "Early stage company",
|
|
6899
|
+
icon: "\u26A1",
|
|
6900
|
+
sections: ["hero", "problem", "solution", "features", "team", "waitlist", "footer"],
|
|
6901
|
+
style: "bold"
|
|
6902
|
+
},
|
|
6903
|
+
{
|
|
6904
|
+
id: "restaurant",
|
|
6905
|
+
name: "Restaurant",
|
|
6906
|
+
description: "Food & dining establishment",
|
|
6907
|
+
icon: "\u{1F37D}\uFE0F",
|
|
6908
|
+
sections: ["hero", "menu", "about", "gallery", "reservations", "location", "footer"],
|
|
6909
|
+
style: "elegant"
|
|
6910
|
+
},
|
|
6911
|
+
{
|
|
6912
|
+
id: "ecommerce",
|
|
6913
|
+
name: "E-commerce",
|
|
6914
|
+
description: "Online store",
|
|
6915
|
+
icon: "\u{1F6D2}",
|
|
6916
|
+
sections: ["hero", "featured-products", "categories", "bestsellers", "newsletter", "footer"],
|
|
6917
|
+
style: "minimal"
|
|
6918
|
+
},
|
|
6919
|
+
{
|
|
6920
|
+
id: "blog",
|
|
6921
|
+
name: "Blog",
|
|
6922
|
+
description: "Content & articles",
|
|
6923
|
+
icon: "\u{1F4DD}",
|
|
6924
|
+
sections: ["hero", "featured-posts", "categories", "newsletter", "about", "footer"],
|
|
6925
|
+
style: "minimal"
|
|
6926
|
+
},
|
|
6927
|
+
{
|
|
6928
|
+
id: "event",
|
|
6929
|
+
name: "Event Page",
|
|
6930
|
+
description: "Conference or meetup",
|
|
6931
|
+
icon: "\u{1F389}",
|
|
6932
|
+
sections: ["hero", "speakers", "schedule", "venue", "sponsors", "tickets", "footer"],
|
|
6933
|
+
style: "bold"
|
|
6934
|
+
},
|
|
6935
|
+
{
|
|
6936
|
+
id: "nonprofit",
|
|
6937
|
+
name: "Nonprofit",
|
|
6938
|
+
description: "Charity or cause",
|
|
6939
|
+
icon: "\u{1F49A}",
|
|
6940
|
+
sections: ["hero", "mission", "impact", "programs", "donate", "volunteer", "footer"],
|
|
6941
|
+
style: "elegant"
|
|
6942
|
+
},
|
|
6943
|
+
{
|
|
6944
|
+
id: "personal",
|
|
6945
|
+
name: "Personal Website",
|
|
6946
|
+
description: "Your online presence",
|
|
6947
|
+
icon: "\u{1F464}",
|
|
6948
|
+
sections: ["hero", "about", "experience", "projects", "blog", "contact", "footer"],
|
|
6949
|
+
style: "minimal"
|
|
6950
|
+
},
|
|
6951
|
+
{
|
|
6952
|
+
id: "coming-soon",
|
|
6953
|
+
name: "Coming Soon",
|
|
6954
|
+
description: "Pre-launch teaser",
|
|
6955
|
+
icon: "\u23F3",
|
|
6956
|
+
sections: ["hero", "countdown", "features-preview", "waitlist", "footer"],
|
|
6957
|
+
style: "bold"
|
|
6958
|
+
},
|
|
6959
|
+
{
|
|
6960
|
+
id: "custom",
|
|
6961
|
+
name: "Custom",
|
|
6962
|
+
description: "Build from scratch with AI",
|
|
6963
|
+
icon: "\u2728",
|
|
6964
|
+
sections: [],
|
|
6965
|
+
style: "minimal"
|
|
6966
|
+
}
|
|
6967
|
+
];
|
|
6968
|
+
async function websiteCommand() {
|
|
6969
|
+
const config = new Config();
|
|
6970
|
+
if (!config.isConfigured()) {
|
|
6971
|
+
console.log(chalk20.yellow(`
|
|
6972
|
+
\u26A0\uFE0F CodeBakers isn't set up yet.
|
|
6973
|
+
|
|
6974
|
+
Run this first:
|
|
6975
|
+
${chalk20.cyan("codebakers setup")}
|
|
6976
|
+
`));
|
|
6977
|
+
return;
|
|
6978
|
+
}
|
|
6979
|
+
const anthropicCreds = config.getCredentials("anthropic");
|
|
6980
|
+
if (!anthropicCreds?.apiKey) {
|
|
6981
|
+
console.log(chalk20.yellow(`
|
|
6982
|
+
\u26A0\uFE0F Anthropic API key not configured.
|
|
6983
|
+
|
|
6984
|
+
The website builder needs Claude AI to generate code.
|
|
6985
|
+
|
|
6986
|
+
Run this to add your API key:
|
|
6987
|
+
${chalk20.cyan("codebakers setup")}
|
|
6988
|
+
|
|
6989
|
+
Get an API key at:
|
|
6990
|
+
${chalk20.dim("https://console.anthropic.com/settings/keys")}
|
|
6991
|
+
`));
|
|
6992
|
+
return;
|
|
6993
|
+
}
|
|
6994
|
+
console.log(chalk20.cyan(`
|
|
6995
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
6996
|
+
\u2551 \u{1F310} WEBSITE BUILDER \u2551
|
|
6997
|
+
\u2551 \u2551
|
|
6998
|
+
\u2551 Describe your website in plain English. \u2551
|
|
6999
|
+
\u2551 AI builds it in minutes. \u2551
|
|
7000
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
7001
|
+
`));
|
|
7002
|
+
const approach = await p19.select({
|
|
7003
|
+
message: "How would you like to build your website?",
|
|
7004
|
+
options: [
|
|
7005
|
+
{ value: "describe", label: "\u{1F4AC} Describe it", hint: "Tell me what you want in plain English" },
|
|
7006
|
+
{ value: "template", label: "\u{1F4CB} Start from template", hint: "Choose a pre-made structure" },
|
|
7007
|
+
{ value: "url", label: "\u{1F517} Clone a design", hint: "Describe a site you like" }
|
|
7008
|
+
]
|
|
7009
|
+
});
|
|
7010
|
+
if (p19.isCancel(approach)) return;
|
|
7011
|
+
const anthropic = new Anthropic5({ apiKey: anthropicCreds.apiKey });
|
|
7012
|
+
let websiteSpec;
|
|
7013
|
+
if (approach === "describe") {
|
|
7014
|
+
websiteSpec = await describeWebsite(anthropic);
|
|
7015
|
+
} else if (approach === "template") {
|
|
7016
|
+
websiteSpec = await templateWebsite(anthropic);
|
|
7017
|
+
} else {
|
|
7018
|
+
websiteSpec = await cloneDesign(anthropic);
|
|
7019
|
+
}
|
|
7020
|
+
if (!websiteSpec) return;
|
|
7021
|
+
console.log(chalk20.cyan(`
|
|
7022
|
+
\u{1F4CB} Website Plan:
|
|
7023
|
+
`));
|
|
7024
|
+
console.log(chalk20.bold(` ${websiteSpec.name}`));
|
|
7025
|
+
console.log(chalk20.dim(` ${websiteSpec.description}
|
|
7026
|
+
`));
|
|
7027
|
+
console.log(chalk20.dim(` Style: ${websiteSpec.style}`));
|
|
7028
|
+
console.log(chalk20.dim(` Sections: ${websiteSpec.sections.join(", ")}
|
|
7029
|
+
`));
|
|
7030
|
+
const confirm13 = await p19.confirm({
|
|
7031
|
+
message: "Build this website?",
|
|
7032
|
+
initialValue: true
|
|
7033
|
+
});
|
|
7034
|
+
if (!confirm13 || p19.isCancel(confirm13)) return;
|
|
7035
|
+
await buildWebsite(anthropic, websiteSpec, config);
|
|
7036
|
+
}
|
|
7037
|
+
async function describeWebsite(anthropic) {
|
|
7038
|
+
console.log(chalk20.dim("\n Describe your website. Be as detailed as you want.\n"));
|
|
7039
|
+
console.log(chalk20.dim(" Examples:"));
|
|
7040
|
+
console.log(chalk20.dim(' \u2022 "A landing page for my AI writing tool called WriteBot"'));
|
|
7041
|
+
console.log(chalk20.dim(' \u2022 "Portfolio site for a photographer, dark theme, minimal"'));
|
|
7042
|
+
console.log(chalk20.dim(' \u2022 "Coffee shop website with menu, location, and online ordering"\n'));
|
|
7043
|
+
const description = await textWithVoice({
|
|
7044
|
+
message: "Describe your website:",
|
|
7045
|
+
placeholder: "A landing page for..."
|
|
7046
|
+
});
|
|
7047
|
+
if (p19.isCancel(description)) return null;
|
|
7048
|
+
const spinner16 = p19.spinner();
|
|
7049
|
+
spinner16.start("Understanding your vision...");
|
|
7050
|
+
const response = await anthropic.messages.create({
|
|
7051
|
+
model: "claude-sonnet-4-20250514",
|
|
7052
|
+
max_tokens: 2048,
|
|
7053
|
+
messages: [{
|
|
7054
|
+
role: "user",
|
|
7055
|
+
content: `Based on this website description, create a detailed specification.
|
|
7056
|
+
|
|
7057
|
+
Description: "${description}"
|
|
7058
|
+
|
|
7059
|
+
Return JSON only:
|
|
7060
|
+
{
|
|
7061
|
+
"name": "Project name (kebab-case)",
|
|
7062
|
+
"description": "One line description",
|
|
7063
|
+
"style": "minimal | bold | elegant | playful | corporate",
|
|
7064
|
+
"colorScheme": {
|
|
7065
|
+
"primary": "#hex",
|
|
7066
|
+
"secondary": "#hex",
|
|
7067
|
+
"accent": "#hex",
|
|
7068
|
+
"background": "#hex",
|
|
7069
|
+
"text": "#hex"
|
|
7070
|
+
},
|
|
7071
|
+
"sections": ["hero", "features", ...],
|
|
7072
|
+
"content": {
|
|
7073
|
+
"hero": {
|
|
7074
|
+
"headline": "...",
|
|
7075
|
+
"subheadline": "...",
|
|
7076
|
+
"cta": "..."
|
|
7077
|
+
},
|
|
7078
|
+
"features": [
|
|
7079
|
+
{ "title": "...", "description": "...", "icon": "..." }
|
|
7080
|
+
]
|
|
7081
|
+
// etc for each section
|
|
7082
|
+
},
|
|
7083
|
+
"features": ["responsive", "dark-mode", "animations", etc]
|
|
7084
|
+
}
|
|
7085
|
+
|
|
7086
|
+
Make the content compelling and professional. Use appropriate sections for the type of website.`
|
|
7087
|
+
}]
|
|
7088
|
+
});
|
|
7089
|
+
spinner16.stop("Got it!");
|
|
7090
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
7091
|
+
const jsonMatch = text16.match(/\{[\s\S]*\}/);
|
|
7092
|
+
if (!jsonMatch) {
|
|
7093
|
+
p19.log.error("Failed to understand website description");
|
|
7094
|
+
return null;
|
|
7095
|
+
}
|
|
7096
|
+
return JSON.parse(jsonMatch[0]);
|
|
7097
|
+
}
|
|
7098
|
+
async function templateWebsite(anthropic) {
|
|
7099
|
+
const template = await p19.select({
|
|
7100
|
+
message: "Choose a template:",
|
|
7101
|
+
options: TEMPLATES.map((t) => ({
|
|
7102
|
+
value: t.id,
|
|
7103
|
+
label: `${t.icon} ${t.name}`,
|
|
7104
|
+
hint: t.description
|
|
7105
|
+
}))
|
|
7106
|
+
});
|
|
7107
|
+
if (p19.isCancel(template)) return null;
|
|
7108
|
+
const selectedTemplate = TEMPLATES.find((t) => t.id === template);
|
|
7109
|
+
const name = await p19.text({
|
|
7110
|
+
message: "What is your business/project name?",
|
|
7111
|
+
placeholder: "My Awesome Project"
|
|
7112
|
+
});
|
|
7113
|
+
if (p19.isCancel(name)) return null;
|
|
7114
|
+
const tagline = await p19.text({
|
|
7115
|
+
message: "Tagline or one-line description:",
|
|
7116
|
+
placeholder: "The best solution for..."
|
|
7117
|
+
});
|
|
7118
|
+
if (p19.isCancel(tagline)) return null;
|
|
7119
|
+
const details = await textWithVoice({
|
|
7120
|
+
message: "Any other details? (or press enter to skip)",
|
|
7121
|
+
placeholder: "We help startups with..., Our colors are blue and white..."
|
|
7122
|
+
});
|
|
7123
|
+
if (p19.isCancel(details)) return null;
|
|
7124
|
+
const spinner16 = p19.spinner();
|
|
7125
|
+
spinner16.start("Customizing template...");
|
|
7126
|
+
const response = await anthropic.messages.create({
|
|
7127
|
+
model: "claude-sonnet-4-20250514",
|
|
7128
|
+
max_tokens: 2048,
|
|
7129
|
+
messages: [{
|
|
7130
|
+
role: "user",
|
|
7131
|
+
content: `Create a website spec based on this template and customization.
|
|
7132
|
+
|
|
7133
|
+
Template: ${selectedTemplate.name}
|
|
7134
|
+
Sections: ${selectedTemplate.sections.join(", ")}
|
|
7135
|
+
Default Style: ${selectedTemplate.style}
|
|
7136
|
+
|
|
7137
|
+
Business Name: ${name}
|
|
7138
|
+
Tagline: ${tagline}
|
|
7139
|
+
Additional Details: ${details || "None provided"}
|
|
7140
|
+
|
|
7141
|
+
Return JSON only:
|
|
7142
|
+
{
|
|
7143
|
+
"name": "project-name-kebab",
|
|
7144
|
+
"description": "${tagline}",
|
|
7145
|
+
"style": "${selectedTemplate.style}",
|
|
7146
|
+
"colorScheme": {
|
|
7147
|
+
"primary": "#hex",
|
|
7148
|
+
"secondary": "#hex",
|
|
7149
|
+
"accent": "#hex",
|
|
7150
|
+
"background": "#hex",
|
|
7151
|
+
"text": "#hex"
|
|
7152
|
+
},
|
|
7153
|
+
"sections": ${JSON.stringify(selectedTemplate.sections)},
|
|
7154
|
+
"content": {
|
|
7155
|
+
// Content for each section, tailored to the business
|
|
7156
|
+
},
|
|
7157
|
+
"features": ["responsive", "dark-mode", etc]
|
|
7158
|
+
}
|
|
7159
|
+
|
|
7160
|
+
Make the content specific and compelling for this business.`
|
|
7161
|
+
}]
|
|
7162
|
+
});
|
|
7163
|
+
spinner16.stop("Template customized!");
|
|
7164
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
7165
|
+
const jsonMatch = text16.match(/\{[\s\S]*\}/);
|
|
7166
|
+
if (!jsonMatch) {
|
|
7167
|
+
p19.log.error("Failed to customize template");
|
|
7168
|
+
return null;
|
|
7169
|
+
}
|
|
7170
|
+
return JSON.parse(jsonMatch[0]);
|
|
7171
|
+
}
|
|
7172
|
+
async function cloneDesign(anthropic) {
|
|
7173
|
+
console.log(chalk20.dim("\n Describe a website design you like.\n"));
|
|
7174
|
+
console.log(chalk20.dim(" Examples:"));
|
|
7175
|
+
console.log(chalk20.dim(' \u2022 "Like Linear.app - minimal, clean, dark mode"'));
|
|
7176
|
+
console.log(chalk20.dim(' \u2022 "Like Stripe - professional, lots of gradients"'));
|
|
7177
|
+
console.log(chalk20.dim(' \u2022 "Like Notion - simple, friendly, illustrated"\n'));
|
|
7178
|
+
const inspiration = await textWithVoice({
|
|
7179
|
+
message: "What site do you want to be inspired by?",
|
|
7180
|
+
placeholder: "Like Linear.app but for..."
|
|
7181
|
+
});
|
|
7182
|
+
if (p19.isCancel(inspiration)) return null;
|
|
7183
|
+
const purpose = await p19.text({
|
|
7184
|
+
message: "What is YOUR website for?",
|
|
7185
|
+
placeholder: "A project management tool for designers"
|
|
7186
|
+
});
|
|
7187
|
+
if (p19.isCancel(purpose)) return null;
|
|
7188
|
+
const spinner16 = p19.spinner();
|
|
7189
|
+
spinner16.start("Analyzing design inspiration...");
|
|
7190
|
+
const response = await anthropic.messages.create({
|
|
7191
|
+
model: "claude-sonnet-4-20250514",
|
|
7192
|
+
max_tokens: 2048,
|
|
7193
|
+
messages: [{
|
|
7194
|
+
role: "user",
|
|
7195
|
+
content: `Create a website spec inspired by another site's design.
|
|
7196
|
+
|
|
7197
|
+
Inspiration: "${inspiration}"
|
|
7198
|
+
Purpose: "${purpose}"
|
|
7199
|
+
|
|
7200
|
+
Analyze the design style of the inspiration (based on your knowledge of popular websites) and create a spec that captures that aesthetic for this new purpose.
|
|
7201
|
+
|
|
7202
|
+
Return JSON only:
|
|
7203
|
+
{
|
|
7204
|
+
"name": "project-name-kebab",
|
|
7205
|
+
"description": "One line description",
|
|
7206
|
+
"style": "minimal | bold | elegant | playful | corporate",
|
|
7207
|
+
"colorScheme": {
|
|
7208
|
+
"primary": "#hex - inspired by the reference",
|
|
7209
|
+
"secondary": "#hex",
|
|
7210
|
+
"accent": "#hex",
|
|
7211
|
+
"background": "#hex",
|
|
7212
|
+
"text": "#hex"
|
|
7213
|
+
},
|
|
7214
|
+
"sections": ["appropriate sections for the purpose"],
|
|
7215
|
+
"content": {
|
|
7216
|
+
// Compelling content for each section
|
|
7217
|
+
},
|
|
7218
|
+
"features": ["responsive", "dark-mode", "animations", "gradients", etc - based on inspiration]
|
|
7219
|
+
}
|
|
7220
|
+
|
|
7221
|
+
Capture the FEEL of the inspiration but make it original.`
|
|
7222
|
+
}]
|
|
7223
|
+
});
|
|
7224
|
+
spinner16.stop("Design analyzed!");
|
|
7225
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
7226
|
+
const jsonMatch = text16.match(/\{[\s\S]*\}/);
|
|
7227
|
+
if (!jsonMatch) {
|
|
7228
|
+
p19.log.error("Failed to analyze design");
|
|
7229
|
+
return null;
|
|
7230
|
+
}
|
|
7231
|
+
return JSON.parse(jsonMatch[0]);
|
|
7232
|
+
}
|
|
7233
|
+
async function buildWebsite(anthropic, spec, config) {
|
|
7234
|
+
const projectPath = path15.join(process.cwd(), spec.name);
|
|
7235
|
+
if (await fs16.pathExists(projectPath)) {
|
|
7236
|
+
const overwrite = await p19.confirm({
|
|
7237
|
+
message: `${spec.name} already exists. Overwrite?`,
|
|
7238
|
+
initialValue: false
|
|
7239
|
+
});
|
|
7240
|
+
if (!overwrite || p19.isCancel(overwrite)) return;
|
|
7241
|
+
await fs16.remove(projectPath);
|
|
7242
|
+
}
|
|
7243
|
+
console.log(chalk20.cyan(`
|
|
7244
|
+
\u{1F3D7}\uFE0F Building ${spec.name}...
|
|
7245
|
+
`));
|
|
7246
|
+
const spinner16 = p19.spinner();
|
|
7247
|
+
spinner16.start("Creating Next.js project...");
|
|
7248
|
+
await execa11("npx", [
|
|
7249
|
+
"create-next-app@latest",
|
|
7250
|
+
spec.name,
|
|
7251
|
+
"--typescript",
|
|
7252
|
+
"--tailwind",
|
|
7253
|
+
"--eslint",
|
|
7254
|
+
"--app",
|
|
7255
|
+
"--src-dir",
|
|
7256
|
+
"--import-alias",
|
|
7257
|
+
"@/*",
|
|
7258
|
+
"--no-git"
|
|
7259
|
+
], { cwd: process.cwd(), reject: false });
|
|
7260
|
+
spinner16.stop("Project created");
|
|
7261
|
+
spinner16.start("Setting up shadcn/ui...");
|
|
7262
|
+
await execa11("npx", ["shadcn@latest", "init", "-y", "-d"], {
|
|
7263
|
+
cwd: projectPath,
|
|
7264
|
+
reject: false
|
|
7265
|
+
});
|
|
7266
|
+
await execa11("npx", ["shadcn@latest", "add", "button", "card", "input", "badge", "-y"], {
|
|
7267
|
+
cwd: projectPath,
|
|
7268
|
+
reject: false
|
|
7269
|
+
});
|
|
7270
|
+
spinner16.stop("UI components ready");
|
|
7271
|
+
spinner16.start("Generating website code...");
|
|
7272
|
+
const response = await anthropic.messages.create({
|
|
7273
|
+
model: "claude-sonnet-4-20250514",
|
|
7274
|
+
max_tokens: 16e3,
|
|
7275
|
+
messages: [{
|
|
7276
|
+
role: "user",
|
|
7277
|
+
content: `Generate a complete Next.js website based on this specification.
|
|
7278
|
+
|
|
7279
|
+
Website Spec:
|
|
7280
|
+
${JSON.stringify(spec, null, 2)}
|
|
7281
|
+
|
|
7282
|
+
Generate these files:
|
|
7283
|
+
|
|
7284
|
+
1. src/app/page.tsx - Main landing page with ALL sections
|
|
7285
|
+
2. src/app/layout.tsx - Root layout with fonts, metadata
|
|
7286
|
+
3. src/app/globals.css - Tailwind config with custom colors
|
|
7287
|
+
4. src/components/sections/Hero.tsx
|
|
7288
|
+
5. src/components/sections/[OtherSections].tsx - One for each section
|
|
7289
|
+
6. src/components/ui/Navbar.tsx
|
|
7290
|
+
7. src/components/ui/Footer.tsx
|
|
7291
|
+
8. tailwind.config.ts - With custom colors from colorScheme
|
|
7292
|
+
|
|
7293
|
+
Requirements:
|
|
7294
|
+
- Use TypeScript
|
|
7295
|
+
- Use Tailwind CSS for styling
|
|
7296
|
+
- Use shadcn/ui components (Button, Card, Input, Badge)
|
|
7297
|
+
- Make it responsive (mobile-first)
|
|
7298
|
+
- Add smooth scroll behavior
|
|
7299
|
+
- Use modern design patterns
|
|
7300
|
+
- Include hover effects and transitions
|
|
7301
|
+
- Use Lucide icons where appropriate
|
|
7302
|
+
|
|
7303
|
+
Color scheme to use:
|
|
7304
|
+
- Primary: ${spec.colorScheme.primary}
|
|
7305
|
+
- Secondary: ${spec.colorScheme.secondary}
|
|
7306
|
+
- Accent: ${spec.colorScheme.accent}
|
|
7307
|
+
- Background: ${spec.colorScheme.background}
|
|
7308
|
+
- Text: ${spec.colorScheme.text}
|
|
7309
|
+
|
|
7310
|
+
Style: ${spec.style}
|
|
7311
|
+
${spec.style === "minimal" ? "Clean, lots of whitespace, simple" : ""}
|
|
7312
|
+
${spec.style === "bold" ? "Strong colors, big typography, impactful" : ""}
|
|
7313
|
+
${spec.style === "elegant" ? "Refined, sophisticated, subtle animations" : ""}
|
|
7314
|
+
${spec.style === "playful" ? "Fun, colorful, friendly illustrations" : ""}
|
|
7315
|
+
${spec.style === "corporate" ? "Professional, trustworthy, structured" : ""}
|
|
7316
|
+
|
|
7317
|
+
Output format:
|
|
7318
|
+
<<<FILE: path/to/file.tsx>>>
|
|
7319
|
+
content
|
|
7320
|
+
<<<END_FILE>>>
|
|
7321
|
+
|
|
7322
|
+
Make it production-quality and visually impressive.`
|
|
7323
|
+
}]
|
|
7324
|
+
});
|
|
7325
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
7326
|
+
const fileRegex = /<<<FILE:\s*(.+?)>>>([\s\S]*?)<<<END_FILE>>>/g;
|
|
7327
|
+
let match;
|
|
7328
|
+
let fileCount = 0;
|
|
7329
|
+
while ((match = fileRegex.exec(text16)) !== null) {
|
|
7330
|
+
const filePath = path15.join(projectPath, match[1].trim());
|
|
7331
|
+
const content = match[2].trim();
|
|
7332
|
+
await fs16.ensureDir(path15.dirname(filePath));
|
|
7333
|
+
await fs16.writeFile(filePath, content);
|
|
7334
|
+
fileCount++;
|
|
7335
|
+
}
|
|
7336
|
+
spinner16.stop(`Generated ${fileCount} files`);
|
|
7337
|
+
spinner16.start("Initializing git...");
|
|
7338
|
+
await execa11("git", ["init"], { cwd: projectPath, reject: false });
|
|
7339
|
+
await execa11("git", ["add", "."], { cwd: projectPath, reject: false });
|
|
7340
|
+
await execa11("git", ["commit", "-m", "Initial website build by CodeBakers"], { cwd: projectPath, reject: false });
|
|
7341
|
+
spinner16.stop("Git initialized");
|
|
7342
|
+
console.log(chalk20.green(`
|
|
7343
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
7344
|
+
\u2551 \u2705 Website built successfully! \u2551
|
|
7345
|
+
\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
|
|
7346
|
+
\u2551 \u2551
|
|
7347
|
+
\u2551 ${spec.name.padEnd(55)}\u2551
|
|
7348
|
+
\u2551 ${spec.description.substring(0, 55).padEnd(55)}\u2551
|
|
7349
|
+
\u2551 \u2551
|
|
7350
|
+
\u2551 Next steps: \u2551
|
|
7351
|
+
\u2551 cd ${spec.name.padEnd(52)}\u2551
|
|
7352
|
+
\u2551 npm run dev \u2551
|
|
7353
|
+
\u2551 \u2551
|
|
7354
|
+
\u2551 Then open http://localhost:3000 \u2551
|
|
7355
|
+
\u2551 \u2551
|
|
7356
|
+
\u2551 Ready to deploy? \u2551
|
|
7357
|
+
\u2551 codebakers deploy \u2551
|
|
7358
|
+
\u2551 \u2551
|
|
7359
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
7360
|
+
`));
|
|
7361
|
+
const openDev = await p19.confirm({
|
|
7362
|
+
message: "Start development server now?",
|
|
7363
|
+
initialValue: true
|
|
7364
|
+
});
|
|
7365
|
+
if (openDev && !p19.isCancel(openDev)) {
|
|
7366
|
+
console.log(chalk20.dim("\n Starting dev server...\n"));
|
|
7367
|
+
process.chdir(projectPath);
|
|
7368
|
+
await execa11("npm", ["run", "dev"], {
|
|
7369
|
+
stdio: "inherit",
|
|
7370
|
+
reject: false
|
|
7371
|
+
});
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
|
|
7375
|
+
// src/utils/nlp.ts
|
|
7376
|
+
import * as p20 from "@clack/prompts";
|
|
7377
|
+
import Anthropic6 from "@anthropic-ai/sdk";
|
|
6835
7378
|
var COMMAND_PATTERNS = {
|
|
6836
7379
|
init: {
|
|
6837
7380
|
patterns: ["new project", "create app", "start project", "initialize", "new app", "start building", "create new"],
|
|
@@ -6900,7 +7443,7 @@ async function parseNaturalLanguage(input, config) {
|
|
|
6900
7443
|
if (!anthropicCreds?.apiKey) {
|
|
6901
7444
|
return quickMatch;
|
|
6902
7445
|
}
|
|
6903
|
-
const anthropic = new
|
|
7446
|
+
const anthropic = new Anthropic6({ apiKey: anthropicCreds.apiKey });
|
|
6904
7447
|
try {
|
|
6905
7448
|
const result = await parseWithAI(anthropic, input);
|
|
6906
7449
|
return result;
|
|
@@ -6952,7 +7495,7 @@ function extractArgs(input, pattern) {
|
|
|
6952
7495
|
const patternWords = pattern.toLowerCase().split(/\s+/);
|
|
6953
7496
|
const inputWords = input.split(/\s+/);
|
|
6954
7497
|
const args2 = inputWords.filter(
|
|
6955
|
-
(word) => !patternWords.some((
|
|
7498
|
+
(word) => !patternWords.some((p22) => word.toLowerCase().includes(p22) || p22.includes(word.toLowerCase()))
|
|
6956
7499
|
);
|
|
6957
7500
|
return args2.filter((a) => a.length > 2);
|
|
6958
7501
|
}
|
|
@@ -6983,8 +7526,8 @@ If the input is asking to build/create/add a specific feature, use "code" and pu
|
|
|
6983
7526
|
If unclear between multiple commands, use the most likely one with lower confidence.`
|
|
6984
7527
|
}]
|
|
6985
7528
|
});
|
|
6986
|
-
const
|
|
6987
|
-
const jsonMatch =
|
|
7529
|
+
const text16 = response.content[0].type === "text" ? response.content[0].text : "";
|
|
7530
|
+
const jsonMatch = text16.match(/\{[\s\S]*?\}/);
|
|
6988
7531
|
if (!jsonMatch) {
|
|
6989
7532
|
return {
|
|
6990
7533
|
command: "code",
|
|
@@ -7000,11 +7543,11 @@ async function clarifyCommand(parsed) {
|
|
|
7000
7543
|
return parsed;
|
|
7001
7544
|
}
|
|
7002
7545
|
if (parsed.confidence >= 0.5) {
|
|
7003
|
-
const
|
|
7546
|
+
const confirm13 = await p20.confirm({
|
|
7004
7547
|
message: `Did you mean: ${parsed.interpretation}?`,
|
|
7005
7548
|
initialValue: true
|
|
7006
7549
|
});
|
|
7007
|
-
if (
|
|
7550
|
+
if (confirm13 && !p20.isCancel(confirm13)) {
|
|
7008
7551
|
return { ...parsed, confidence: 1 };
|
|
7009
7552
|
}
|
|
7010
7553
|
}
|
|
@@ -7019,19 +7562,19 @@ async function clarifyCommand(parsed) {
|
|
|
7019
7562
|
{ value: "prd-maker", label: "\u{1F4DD} Create PRD", description: "Document your idea" },
|
|
7020
7563
|
{ value: "other", label: "\u2753 Something else", description: "Describe what you need" }
|
|
7021
7564
|
];
|
|
7022
|
-
const selection = await
|
|
7565
|
+
const selection = await p20.select({
|
|
7023
7566
|
message: "What would you like to do?",
|
|
7024
7567
|
options: options.map((o) => ({ value: o.value, label: o.label, hint: o.description }))
|
|
7025
7568
|
});
|
|
7026
|
-
if (
|
|
7569
|
+
if (p20.isCancel(selection)) {
|
|
7027
7570
|
return { ...parsed, command: "cancel", confidence: 1 };
|
|
7028
7571
|
}
|
|
7029
7572
|
if (selection === "other") {
|
|
7030
|
-
const description = await
|
|
7573
|
+
const description = await p20.text({
|
|
7031
7574
|
message: "Describe what you want to do:",
|
|
7032
7575
|
placeholder: "I want to..."
|
|
7033
7576
|
});
|
|
7034
|
-
if (
|
|
7577
|
+
if (p20.isCancel(description)) {
|
|
7035
7578
|
return { ...parsed, command: "cancel", confidence: 1 };
|
|
7036
7579
|
}
|
|
7037
7580
|
return {
|
|
@@ -7049,19 +7592,19 @@ async function clarifyCommand(parsed) {
|
|
|
7049
7592
|
};
|
|
7050
7593
|
}
|
|
7051
7594
|
async function clarifyDeployTarget() {
|
|
7052
|
-
const target = await
|
|
7595
|
+
const target = await p20.select({
|
|
7053
7596
|
message: "Where do you want to deploy?",
|
|
7054
7597
|
options: [
|
|
7055
7598
|
{ value: "preview", label: "\u{1F50D} Preview", hint: "Test URL to review changes" },
|
|
7056
7599
|
{ value: "production", label: "\u{1F680} Production", hint: "Live site for users" }
|
|
7057
7600
|
]
|
|
7058
7601
|
});
|
|
7059
|
-
if (
|
|
7602
|
+
if (p20.isCancel(target)) return null;
|
|
7060
7603
|
return target;
|
|
7061
7604
|
}
|
|
7062
7605
|
|
|
7063
7606
|
// src/index.ts
|
|
7064
|
-
var VERSION2 = "2.
|
|
7607
|
+
var VERSION2 = "2.2.0";
|
|
7065
7608
|
var logo = `
|
|
7066
7609
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
7067
7610
|
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
@@ -7074,18 +7617,18 @@ async function showMainMenu() {
|
|
|
7074
7617
|
const config = new Config();
|
|
7075
7618
|
const isSetup = config.isConfigured();
|
|
7076
7619
|
console.log(gradient.pastel.multiline(logo));
|
|
7077
|
-
console.log(
|
|
7620
|
+
console.log(chalk21.dim(` v${VERSION2} \u2014 AI dev team that follows the rules
|
|
7078
7621
|
`));
|
|
7079
7622
|
if (!isSetup) {
|
|
7080
7623
|
console.log(boxen(
|
|
7081
|
-
|
|
7624
|
+
chalk21.yellow("Welcome to CodeBakers! Let's get you set up."),
|
|
7082
7625
|
{ padding: 1, borderColor: "yellow", borderStyle: "round" }
|
|
7083
7626
|
));
|
|
7084
7627
|
await setupCommand();
|
|
7085
7628
|
return;
|
|
7086
7629
|
}
|
|
7087
7630
|
const inProject = config.isInProject();
|
|
7088
|
-
const action = await
|
|
7631
|
+
const action = await p21.select({
|
|
7089
7632
|
message: "What do you want to do? (or type naturally)",
|
|
7090
7633
|
options: inProject ? [
|
|
7091
7634
|
{ value: "code", label: "\u{1F4AC} Code with AI", hint: "build features, fix bugs" },
|
|
@@ -7104,6 +7647,7 @@ async function showMainMenu() {
|
|
|
7104
7647
|
{ value: "design", label: "\u{1F3A8} Design system", hint: "set profile, colors" },
|
|
7105
7648
|
{ value: "separator2", label: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" },
|
|
7106
7649
|
{ value: "new", label: "\u{1F195} Create new project" },
|
|
7650
|
+
{ value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
|
|
7107
7651
|
{ value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
|
|
7108
7652
|
{ value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
|
|
7109
7653
|
{ value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
|
|
@@ -7112,6 +7656,7 @@ async function showMainMenu() {
|
|
|
7112
7656
|
{ value: "help", label: "\u2753 Help" }
|
|
7113
7657
|
] : [
|
|
7114
7658
|
{ value: "new", label: "\u{1F195} Create new project" },
|
|
7659
|
+
{ value: "website", label: "\u{1F310} Website Builder", hint: "Describe \u2192 AI builds it" },
|
|
7115
7660
|
{ value: "build", label: "\u{1F3D7}\uFE0F Parallel Build", hint: "3 agents from PRD (swarm)" },
|
|
7116
7661
|
{ value: "prd", label: "\u{1F4C4} Build from PRD", hint: "sequential build" },
|
|
7117
7662
|
{ value: "prd-maker", label: "\u270F\uFE0F Create PRD", hint: "interview \u2192 generate PRD" },
|
|
@@ -7126,8 +7671,8 @@ async function showMainMenu() {
|
|
|
7126
7671
|
{ value: "help", label: "\u2753 Help" }
|
|
7127
7672
|
]
|
|
7128
7673
|
});
|
|
7129
|
-
if (
|
|
7130
|
-
|
|
7674
|
+
if (p21.isCancel(action)) {
|
|
7675
|
+
p21.cancel("Goodbye!");
|
|
7131
7676
|
process.exit(0);
|
|
7132
7677
|
}
|
|
7133
7678
|
switch (action) {
|
|
@@ -7173,6 +7718,9 @@ async function showMainMenu() {
|
|
|
7173
7718
|
case "new":
|
|
7174
7719
|
await initCommand();
|
|
7175
7720
|
break;
|
|
7721
|
+
case "website":
|
|
7722
|
+
await websiteCommand();
|
|
7723
|
+
break;
|
|
7176
7724
|
case "build":
|
|
7177
7725
|
await buildCommand();
|
|
7178
7726
|
break;
|
|
@@ -7197,27 +7745,34 @@ async function showMainMenu() {
|
|
|
7197
7745
|
}
|
|
7198
7746
|
function showHelp2() {
|
|
7199
7747
|
console.log(boxen(`
|
|
7200
|
-
${
|
|
7201
|
-
|
|
7202
|
-
${
|
|
7203
|
-
${
|
|
7204
|
-
${
|
|
7205
|
-
${
|
|
7206
|
-
${
|
|
7207
|
-
|
|
7208
|
-
|
|
7209
|
-
${
|
|
7210
|
-
${
|
|
7211
|
-
${
|
|
7212
|
-
${
|
|
7213
|
-
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
${
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
${
|
|
7220
|
-
${
|
|
7748
|
+
${chalk21.bold("CodeBakers CLI v" + VERSION2)} \u2014 AI dev team that follows the rules
|
|
7749
|
+
|
|
7750
|
+
${chalk21.bold.cyan("Getting Started:")}
|
|
7751
|
+
${chalk21.cyan("codebakers setup")} Connect your accounts (GitHub, Vercel, etc.)
|
|
7752
|
+
${chalk21.cyan("codebakers init")} Create a new project from scratch
|
|
7753
|
+
${chalk21.cyan("codebakers website")} Build a website by describing it
|
|
7754
|
+
${chalk21.cyan("codebakers build")} Build from PRD with parallel AI agents
|
|
7755
|
+
|
|
7756
|
+
${chalk21.bold.cyan("Daily Development:")}
|
|
7757
|
+
${chalk21.cyan("codebakers code")} Chat with AI to build features
|
|
7758
|
+
${chalk21.cyan("codebakers check")} Check code quality & patterns
|
|
7759
|
+
${chalk21.cyan("codebakers fix")} Auto-fix errors with AI
|
|
7760
|
+
${chalk21.cyan("codebakers deploy")} Deploy to Vercel
|
|
7761
|
+
|
|
7762
|
+
${chalk21.bold.cyan("Integrations:")}
|
|
7763
|
+
${chalk21.cyan("codebakers integrate")} 50+ one-click integrations (Stripe, Supabase, etc.)
|
|
7764
|
+
${chalk21.cyan("codebakers gateway")} Connect WhatsApp, Telegram, Discord, etc.
|
|
7765
|
+
|
|
7766
|
+
${chalk21.bold.cyan("Planning:")}
|
|
7767
|
+
${chalk21.cyan("codebakers prd-maker")} Create PRD through interview (voice supported)
|
|
7768
|
+
${chalk21.cyan("codebakers advisors")} Consult AI experts (CEO, CTO, Designer, etc.)
|
|
7769
|
+
|
|
7770
|
+
${chalk21.bold.cyan("Tips:")}
|
|
7771
|
+
\u2022 Type naturally: ${chalk21.dim('codebakers "add a contact form"')}
|
|
7772
|
+
\u2022 Voice input: Type ${chalk21.yellow('"v"')} at any prompt
|
|
7773
|
+
\u2022 Clipboard: Type ${chalk21.yellow('"clip"')} to paste from clipboard
|
|
7774
|
+
|
|
7775
|
+
${chalk21.bold("Docs:")} ${chalk21.dim("https://codebakers.dev/docs")}
|
|
7221
7776
|
`, { padding: 1, borderColor: "cyan", borderStyle: "round" }));
|
|
7222
7777
|
}
|
|
7223
7778
|
var program = new Command();
|
|
@@ -7241,9 +7796,10 @@ program.command("migrate").alias("db").description("Database migrations (push, g
|
|
|
7241
7796
|
program.command("prd-maker").alias("create-prd").description("Create a PRD through guided interview (supports voice input)").action(prdMakerCommand);
|
|
7242
7797
|
program.command("build [prd-file]").alias("swarm").description("Parallel build with 3 AI agents (self-healing)").option("--sequential", "Disable parallel execution").action(buildCommand);
|
|
7243
7798
|
program.command("integrate [integration]").alias("add").description("One-click integrations (50+ services with browser auth)").action(integrateCommand);
|
|
7799
|
+
program.command("website").alias("site").description("Build a website by describing it in plain English").action(websiteCommand);
|
|
7244
7800
|
async function handleNaturalLanguage(input) {
|
|
7245
7801
|
const config = new Config();
|
|
7246
|
-
console.log(
|
|
7802
|
+
console.log(chalk21.dim("\n Parsing your request...\n"));
|
|
7247
7803
|
const parsed = await parseNaturalLanguage(input, config);
|
|
7248
7804
|
if (!parsed) {
|
|
7249
7805
|
await codeCommand(input);
|
|
@@ -7331,6 +7887,8 @@ if (args.length === 0) {
|
|
|
7331
7887
|
"swarm",
|
|
7332
7888
|
"integrate",
|
|
7333
7889
|
"add",
|
|
7890
|
+
"website",
|
|
7891
|
+
"site",
|
|
7334
7892
|
"help"
|
|
7335
7893
|
];
|
|
7336
7894
|
const firstArg = args[0];
|