@recursiv/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/bin/create-recursiv-app.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { existsSync as existsSync2 } from "fs";
|
|
8
|
+
import { mkdir } from "fs/promises";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import { execSync } from "child_process";
|
|
11
|
+
import prompts2 from "prompts";
|
|
12
|
+
import pc3 from "picocolors";
|
|
13
|
+
import ora2 from "ora";
|
|
14
|
+
|
|
15
|
+
// src/lib/logger.ts
|
|
16
|
+
import pc from "picocolors";
|
|
17
|
+
var log = {
|
|
18
|
+
info(msg) {
|
|
19
|
+
console.log(pc.cyan("info") + " " + msg);
|
|
20
|
+
},
|
|
21
|
+
success(msg) {
|
|
22
|
+
console.log(pc.green("ok") + " " + msg);
|
|
23
|
+
},
|
|
24
|
+
warn(msg) {
|
|
25
|
+
console.log(pc.yellow("warn") + " " + msg);
|
|
26
|
+
},
|
|
27
|
+
error(msg) {
|
|
28
|
+
console.error(pc.red("error") + " " + msg);
|
|
29
|
+
},
|
|
30
|
+
step(n, total, msg) {
|
|
31
|
+
console.log(pc.dim(`[${n}/${total}]`) + " " + msg);
|
|
32
|
+
},
|
|
33
|
+
blank() {
|
|
34
|
+
console.log();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
function banner() {
|
|
38
|
+
console.log();
|
|
39
|
+
console.log(pc.bold("Recursiv") + pc.dim(" \u2014 build and ship apps with AI"));
|
|
40
|
+
console.log();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/lib/config.ts
|
|
44
|
+
import { readFile, writeFile } from "fs/promises";
|
|
45
|
+
import { join } from "path";
|
|
46
|
+
var CONFIG_FILE = ".recursiv.json";
|
|
47
|
+
function configPath(dir) {
|
|
48
|
+
return join(dir, CONFIG_FILE);
|
|
49
|
+
}
|
|
50
|
+
async function writeConfig(dir, config) {
|
|
51
|
+
await writeFile(configPath(dir), JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
52
|
+
}
|
|
53
|
+
function createConfig(opts) {
|
|
54
|
+
return {
|
|
55
|
+
version: 1,
|
|
56
|
+
project: {
|
|
57
|
+
name: opts.name,
|
|
58
|
+
template: opts.template,
|
|
59
|
+
framework: opts.framework
|
|
60
|
+
},
|
|
61
|
+
api: {
|
|
62
|
+
baseUrl: "https://recursiv.io/api/v1"
|
|
63
|
+
},
|
|
64
|
+
dev: {
|
|
65
|
+
port: opts.port ?? 3e3,
|
|
66
|
+
command: opts.devCommand ?? "npm run dev"
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/lib/env.ts
|
|
72
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
73
|
+
import { join as join2 } from "path";
|
|
74
|
+
async function writeEnvFile(dir, apiKey) {
|
|
75
|
+
const envPath = join2(dir, ".env");
|
|
76
|
+
const content = [
|
|
77
|
+
"# Recursiv API key",
|
|
78
|
+
`RECURSIV_API_KEY=${apiKey}`,
|
|
79
|
+
"",
|
|
80
|
+
"# Framework-specific public keys (uncomment as needed)",
|
|
81
|
+
`# NEXT_PUBLIC_RECURSIV_API_KEY=${apiKey}`,
|
|
82
|
+
`# VITE_RECURSIV_API_KEY=${apiKey}`,
|
|
83
|
+
""
|
|
84
|
+
].join("\n");
|
|
85
|
+
await writeFile2(envPath, content, "utf-8");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/lib/templates.ts
|
|
89
|
+
import degit from "degit";
|
|
90
|
+
import ora from "ora";
|
|
91
|
+
async function cloneTemplate(template, dest) {
|
|
92
|
+
const spinner = ora(`Cloning ${template.name} template...`).start();
|
|
93
|
+
try {
|
|
94
|
+
const emitter = degit(template.repo, {
|
|
95
|
+
cache: false,
|
|
96
|
+
force: true,
|
|
97
|
+
verbose: false
|
|
98
|
+
});
|
|
99
|
+
await emitter.clone(dest);
|
|
100
|
+
spinner.succeed(`Template cloned successfully`);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
spinner.fail("Failed to clone template");
|
|
103
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Could not clone template from ${template.repo}.
|
|
106
|
+
Make sure you have internet access and the repo exists.
|
|
107
|
+
Details: ${message}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/lib/auth-flow.ts
|
|
113
|
+
import prompts from "prompts";
|
|
114
|
+
import pc2 from "picocolors";
|
|
115
|
+
var API_KEY_PATTERN = /^sk_(live|test)_[a-zA-Z0-9]{20,}$/;
|
|
116
|
+
var DASHBOARD_URL = "https://recursiv.io/dashboard/api-keys";
|
|
117
|
+
var DEFAULT_SCOPES = [
|
|
118
|
+
"posts:read",
|
|
119
|
+
"posts:write",
|
|
120
|
+
"chat:read",
|
|
121
|
+
"chat:write",
|
|
122
|
+
"agents:read",
|
|
123
|
+
"agents:write",
|
|
124
|
+
"projects:read",
|
|
125
|
+
"projects:write",
|
|
126
|
+
"communities:read",
|
|
127
|
+
"communities:write",
|
|
128
|
+
"users:read"
|
|
129
|
+
];
|
|
130
|
+
function isValidKeyFormat(key) {
|
|
131
|
+
return API_KEY_PATTERN.test(key);
|
|
132
|
+
}
|
|
133
|
+
async function validateApiKey(apiKey, baseUrl) {
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(`${baseUrl}/users/me`, {
|
|
136
|
+
headers: {
|
|
137
|
+
Authorization: `Bearer ${apiKey}`,
|
|
138
|
+
Accept: "application/json"
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return res.ok;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
async function terminalSignup(baseUrl) {
|
|
147
|
+
const rootUrl = baseUrl.replace(/\/api\/v1\/?$/, "");
|
|
148
|
+
console.log();
|
|
149
|
+
console.log(pc2.bold("Create a free Recursiv account"));
|
|
150
|
+
console.log(pc2.dim("Free tier: 1,000 API calls/day, 1 agent, 3 projects"));
|
|
151
|
+
console.log();
|
|
152
|
+
const { email } = await prompts(
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
name: "email",
|
|
156
|
+
message: "Email",
|
|
157
|
+
validate: (v) => {
|
|
158
|
+
if (!v || !v.includes("@")) return "Valid email required";
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{ onCancel: () => process.exit(1) }
|
|
163
|
+
);
|
|
164
|
+
const { password } = await prompts(
|
|
165
|
+
{
|
|
166
|
+
type: "password",
|
|
167
|
+
name: "password",
|
|
168
|
+
message: "Password",
|
|
169
|
+
validate: (v) => {
|
|
170
|
+
if (!v || v.length < 8) return "Password must be at least 8 characters";
|
|
171
|
+
if (!/[a-zA-Z]/.test(v) || !/[0-9]/.test(v))
|
|
172
|
+
return "Password must contain both letters and numbers";
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
{ onCancel: () => process.exit(1) }
|
|
177
|
+
);
|
|
178
|
+
let sessionCookie = null;
|
|
179
|
+
let isNewAccount = false;
|
|
180
|
+
try {
|
|
181
|
+
const signupRes = await fetch(`${rootUrl}/api/auth/sign-up/email`, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
headers: { "Content-Type": "application/json" },
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
email,
|
|
186
|
+
password,
|
|
187
|
+
name: email.split("@")[0]
|
|
188
|
+
}),
|
|
189
|
+
redirect: "manual"
|
|
190
|
+
});
|
|
191
|
+
if (signupRes.ok || signupRes.status === 302) {
|
|
192
|
+
sessionCookie = extractSessionCookie(signupRes);
|
|
193
|
+
isNewAccount = true;
|
|
194
|
+
} else if (signupRes.status === 409 || signupRes.status === 422) {
|
|
195
|
+
const signinRes = await fetch(`${rootUrl}/api/auth/sign-in/email`, {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "Content-Type": "application/json" },
|
|
198
|
+
body: JSON.stringify({ email, password }),
|
|
199
|
+
redirect: "manual"
|
|
200
|
+
});
|
|
201
|
+
if (signinRes.ok || signinRes.status === 302) {
|
|
202
|
+
sessionCookie = extractSessionCookie(signinRes);
|
|
203
|
+
} else {
|
|
204
|
+
const errBody = await signinRes.text().catch(() => "");
|
|
205
|
+
log.error(`Login failed (${signinRes.status}): ${errBody || "Invalid credentials"}`);
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
const errBody = await signupRes.text().catch(() => "");
|
|
210
|
+
log.error(`Signup failed (${signupRes.status}): ${errBody || "Unknown error"}`);
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
log.error(
|
|
215
|
+
`Could not reach ${rootUrl} \u2014 ${err instanceof Error ? err.message : "network error"}`
|
|
216
|
+
);
|
|
217
|
+
log.info(`You can create an API key manually at ${pc2.underline(DASHBOARD_URL)}`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
if (!sessionCookie) {
|
|
221
|
+
log.error("No session returned from server");
|
|
222
|
+
log.info(`Create an API key manually at ${pc2.underline(DASHBOARD_URL)}`);
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
if (isNewAccount) {
|
|
226
|
+
log.success("Account created");
|
|
227
|
+
} else {
|
|
228
|
+
log.success("Signed in");
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const keyRes = await fetch(`${baseUrl}/api-keys`, {
|
|
232
|
+
method: "POST",
|
|
233
|
+
headers: {
|
|
234
|
+
"Content-Type": "application/json",
|
|
235
|
+
Cookie: sessionCookie
|
|
236
|
+
},
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
name: "CLI (auto-created)",
|
|
239
|
+
scopes: DEFAULT_SCOPES
|
|
240
|
+
})
|
|
241
|
+
});
|
|
242
|
+
if (!keyRes.ok) {
|
|
243
|
+
const errBody = await keyRes.text().catch(() => "");
|
|
244
|
+
log.error(`Failed to create API key (${keyRes.status}): ${errBody}`);
|
|
245
|
+
log.info(`Create one manually at ${pc2.underline(DASHBOARD_URL)}`);
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
const { data } = await keyRes.json();
|
|
249
|
+
log.success(`API key created (free tier: 1,000 calls/day)`);
|
|
250
|
+
return data.key;
|
|
251
|
+
} catch (err) {
|
|
252
|
+
log.error(
|
|
253
|
+
`Failed to create API key: ${err instanceof Error ? err.message : "unknown error"}`
|
|
254
|
+
);
|
|
255
|
+
log.info(`Create one manually at ${pc2.underline(DASHBOARD_URL)}`);
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function extractSessionCookie(res) {
|
|
260
|
+
const setCookieHeaders = "getSetCookie" in res.headers ? res.headers.getSetCookie() : [];
|
|
261
|
+
if (setCookieHeaders.length === 0) {
|
|
262
|
+
const raw = res.headers.get("set-cookie");
|
|
263
|
+
if (raw) {
|
|
264
|
+
return raw.split(",").map((c) => c.split(";")[0].trim()).join("; ");
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return setCookieHeaders.map((c) => c.split(";")[0].trim()).join("; ");
|
|
269
|
+
}
|
|
270
|
+
async function promptApiKey() {
|
|
271
|
+
console.log(
|
|
272
|
+
pc2.dim(
|
|
273
|
+
`Get your API key from ${pc2.underline(DASHBOARD_URL)}
|
|
274
|
+
Keys start with sk_live_ or sk_test_`
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
console.log();
|
|
278
|
+
const { apiKey } = await prompts(
|
|
279
|
+
{
|
|
280
|
+
type: "text",
|
|
281
|
+
name: "apiKey",
|
|
282
|
+
message: "API key (or press Enter to skip)",
|
|
283
|
+
validate: (value) => {
|
|
284
|
+
if (!value) return true;
|
|
285
|
+
if (!isValidKeyFormat(value)) {
|
|
286
|
+
return "Invalid key format. Keys start with sk_live_ or sk_test_";
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
{ onCancel: () => process.exit(1) }
|
|
292
|
+
);
|
|
293
|
+
if (!apiKey) {
|
|
294
|
+
log.warn("No API key provided \u2014 you can add one later in .env");
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
return apiKey;
|
|
298
|
+
}
|
|
299
|
+
async function promptAndValidateApiKey(baseUrl) {
|
|
300
|
+
const apiKey = await promptApiKey();
|
|
301
|
+
if (!apiKey) return null;
|
|
302
|
+
const valid = await validateApiKey(apiKey, baseUrl);
|
|
303
|
+
if (!valid) {
|
|
304
|
+
log.warn("Could not validate API key (server unreachable or invalid key)");
|
|
305
|
+
log.info("Key saved to .env \u2014 you can update it later");
|
|
306
|
+
} else {
|
|
307
|
+
log.success("API key validated");
|
|
308
|
+
}
|
|
309
|
+
return apiKey;
|
|
310
|
+
}
|
|
311
|
+
async function getOrCreateApiKey(baseUrl) {
|
|
312
|
+
console.log();
|
|
313
|
+
const { method } = await prompts(
|
|
314
|
+
{
|
|
315
|
+
type: "select",
|
|
316
|
+
name: "method",
|
|
317
|
+
message: "How would you like to authenticate?",
|
|
318
|
+
choices: [
|
|
319
|
+
{
|
|
320
|
+
title: "Create a free account",
|
|
321
|
+
description: "Sign up with email + password (no browser needed)",
|
|
322
|
+
value: "signup"
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
title: "Enter an existing API key",
|
|
326
|
+
description: "Paste a key from your dashboard",
|
|
327
|
+
value: "paste"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
title: "Skip for now",
|
|
331
|
+
description: "Add an API key later in .env",
|
|
332
|
+
value: "skip"
|
|
333
|
+
}
|
|
334
|
+
]
|
|
335
|
+
},
|
|
336
|
+
{ onCancel: () => process.exit(1) }
|
|
337
|
+
);
|
|
338
|
+
if (method === "signup") {
|
|
339
|
+
return terminalSignup(baseUrl);
|
|
340
|
+
}
|
|
341
|
+
if (method === "paste") {
|
|
342
|
+
return promptAndValidateApiKey(baseUrl);
|
|
343
|
+
}
|
|
344
|
+
log.warn("No API key \u2014 you can add one later in .env");
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/lib/package-manager.ts
|
|
349
|
+
import { existsSync } from "fs";
|
|
350
|
+
import { join as join3 } from "path";
|
|
351
|
+
function detectFromUserAgent() {
|
|
352
|
+
const ua = process.env.npm_config_user_agent ?? "";
|
|
353
|
+
if (ua.startsWith("bun")) return "bun";
|
|
354
|
+
if (ua.startsWith("pnpm")) return "pnpm";
|
|
355
|
+
if (ua.startsWith("yarn")) return "yarn";
|
|
356
|
+
return "npm";
|
|
357
|
+
}
|
|
358
|
+
function installCommand(pm) {
|
|
359
|
+
return pm === "yarn" ? "yarn" : `${pm} install`;
|
|
360
|
+
}
|
|
361
|
+
function runCommand(pm, script) {
|
|
362
|
+
if (pm === "npm") return `npm run ${script}`;
|
|
363
|
+
return `${pm} ${script}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/templates/registry.ts
|
|
367
|
+
var templates = [
|
|
368
|
+
{
|
|
369
|
+
id: "nextjs",
|
|
370
|
+
name: "Next.js + Recursiv",
|
|
371
|
+
description: "Full-stack Next.js app with feeds, chat, and agents",
|
|
372
|
+
repo: "socialdotdev/template-nextjs",
|
|
373
|
+
framework: "nextjs",
|
|
374
|
+
devCommand: "next dev",
|
|
375
|
+
devPort: 3e3,
|
|
376
|
+
recommended: true
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
id: "node",
|
|
380
|
+
name: "Node.js + Express",
|
|
381
|
+
description: "Express API server with Recursiv SDK",
|
|
382
|
+
repo: "socialdotdev/template-node",
|
|
383
|
+
framework: "express",
|
|
384
|
+
devCommand: "node --watch src/index.js",
|
|
385
|
+
devPort: 4e3
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "vite",
|
|
389
|
+
name: "Vite + React",
|
|
390
|
+
description: "React SPA with social feed components",
|
|
391
|
+
repo: "socialdotdev/template-vite",
|
|
392
|
+
framework: "vite",
|
|
393
|
+
devCommand: "vite",
|
|
394
|
+
devPort: 5173
|
|
395
|
+
}
|
|
396
|
+
];
|
|
397
|
+
|
|
398
|
+
// src/commands/init.ts
|
|
399
|
+
import { writeFile as writeFile3 } from "fs/promises";
|
|
400
|
+
async function initCommand(nameArg) {
|
|
401
|
+
banner();
|
|
402
|
+
const totalSteps = 7;
|
|
403
|
+
log.step(1, totalSteps, "Project setup");
|
|
404
|
+
let projectName;
|
|
405
|
+
if (nameArg) {
|
|
406
|
+
projectName = nameArg;
|
|
407
|
+
} else {
|
|
408
|
+
const { name } = await prompts2(
|
|
409
|
+
{
|
|
410
|
+
type: "text",
|
|
411
|
+
name: "name",
|
|
412
|
+
message: "Project name",
|
|
413
|
+
initial: "my-recursiv-app",
|
|
414
|
+
validate: (v) => v.trim() ? true : "Name is required"
|
|
415
|
+
},
|
|
416
|
+
{ onCancel: () => process.exit(1) }
|
|
417
|
+
);
|
|
418
|
+
projectName = name;
|
|
419
|
+
}
|
|
420
|
+
const projectDir = resolve(process.cwd(), projectName);
|
|
421
|
+
if (existsSync2(projectDir)) {
|
|
422
|
+
log.error(`Directory ${pc3.bold(projectName)} already exists`);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
log.step(2, totalSteps, "Choose a template");
|
|
426
|
+
const templateChoices = templates.map((t) => ({
|
|
427
|
+
title: t.recommended ? `${t.name} ${pc3.dim("(recommended)")}` : t.name,
|
|
428
|
+
description: t.description,
|
|
429
|
+
value: t.id
|
|
430
|
+
}));
|
|
431
|
+
const { templateId } = await prompts2(
|
|
432
|
+
{
|
|
433
|
+
type: "select",
|
|
434
|
+
name: "templateId",
|
|
435
|
+
message: "Template",
|
|
436
|
+
choices: templateChoices,
|
|
437
|
+
initial: 0
|
|
438
|
+
},
|
|
439
|
+
{ onCancel: () => process.exit(1) }
|
|
440
|
+
);
|
|
441
|
+
const template = templates.find((t) => t.id === templateId);
|
|
442
|
+
log.step(3, totalSteps, "Authentication");
|
|
443
|
+
const apiKey = await getOrCreateApiKey("https://recursiv.io/api/v1");
|
|
444
|
+
log.step(4, totalSteps, "Creating project");
|
|
445
|
+
await mkdir(projectDir, { recursive: true });
|
|
446
|
+
await cloneTemplate(template, projectDir);
|
|
447
|
+
log.step(5, totalSteps, "Writing config");
|
|
448
|
+
const pm = detectFromUserAgent();
|
|
449
|
+
const config = createConfig({
|
|
450
|
+
name: projectName,
|
|
451
|
+
template: template.id,
|
|
452
|
+
framework: template.framework,
|
|
453
|
+
port: template.devPort,
|
|
454
|
+
devCommand: runCommand(pm, "dev")
|
|
455
|
+
});
|
|
456
|
+
await writeConfig(projectDir, config);
|
|
457
|
+
if (apiKey) {
|
|
458
|
+
await writeEnvFile(projectDir, apiKey);
|
|
459
|
+
}
|
|
460
|
+
const gitignorePath = resolve(projectDir, ".gitignore");
|
|
461
|
+
if (!existsSync2(gitignorePath)) {
|
|
462
|
+
await writeFile3(
|
|
463
|
+
gitignorePath,
|
|
464
|
+
["node_modules", "dist", ".env", ".env.local", ".next", ".turbo", ""].join("\n"),
|
|
465
|
+
"utf-8"
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
log.step(6, totalSteps, "Installing dependencies");
|
|
469
|
+
const installSpinner = ora2(`Running ${pc3.bold(installCommand(pm))}...`).start();
|
|
470
|
+
try {
|
|
471
|
+
execSync(installCommand(pm), {
|
|
472
|
+
cwd: projectDir,
|
|
473
|
+
stdio: "pipe",
|
|
474
|
+
timeout: 12e4
|
|
475
|
+
});
|
|
476
|
+
installSpinner.succeed("Dependencies installed");
|
|
477
|
+
} catch {
|
|
478
|
+
installSpinner.warn("Could not install dependencies \u2014 run install manually");
|
|
479
|
+
}
|
|
480
|
+
log.step(7, totalSteps, "Initializing git");
|
|
481
|
+
try {
|
|
482
|
+
execSync("git init", { cwd: projectDir, stdio: "pipe" });
|
|
483
|
+
execSync("git add -A", { cwd: projectDir, stdio: "pipe" });
|
|
484
|
+
execSync('git commit -m "Initial commit from create-recursiv-app"', {
|
|
485
|
+
cwd: projectDir,
|
|
486
|
+
stdio: "pipe"
|
|
487
|
+
});
|
|
488
|
+
log.success("Git repository initialized");
|
|
489
|
+
} catch {
|
|
490
|
+
log.warn("Could not initialize git repository");
|
|
491
|
+
}
|
|
492
|
+
log.blank();
|
|
493
|
+
console.log(pc3.bold(pc3.green("Your Recursiv app is ready!")));
|
|
494
|
+
log.blank();
|
|
495
|
+
console.log(" Next steps:");
|
|
496
|
+
console.log();
|
|
497
|
+
console.log(` ${pc3.dim("$")} cd ${projectName}`);
|
|
498
|
+
if (!apiKey) {
|
|
499
|
+
console.log(` ${pc3.dim("$")} ${pc3.dim("# Add your API key to .env")}`);
|
|
500
|
+
}
|
|
501
|
+
console.log(` ${pc3.dim("$")} ${runCommand(pm, "dev")}`);
|
|
502
|
+
log.blank();
|
|
503
|
+
console.log(pc3.dim("Docs: https://docs.recursiv.io"));
|
|
504
|
+
console.log(pc3.dim("Dashboard: https://recursiv.io/dashboard"));
|
|
505
|
+
log.blank();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/bin/create-recursiv-app.ts
|
|
509
|
+
var program = new Command();
|
|
510
|
+
program.name("create-recursiv-app").description("Create a new Recursiv application").version("0.1.0").argument("[name]", "project name").action(async (name) => {
|
|
511
|
+
try {
|
|
512
|
+
await initCommand(name);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
console.error(err instanceof Error ? err.message : err);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
program.parse();
|
|
519
|
+
//# sourceMappingURL=create-recursiv-app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/bin/create-recursiv-app.ts","../../src/commands/init.ts","../../src/lib/logger.ts","../../src/lib/config.ts","../../src/lib/env.ts","../../src/lib/templates.ts","../../src/lib/auth-flow.ts","../../src/lib/package-manager.ts","../../src/templates/registry.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { initCommand } from '../commands/init.js';\n\nconst program = new Command();\n\nprogram\n .name('create-recursiv-app')\n .description('Create a new Recursiv application')\n .version('0.1.0')\n .argument('[name]', 'project name')\n .action(async (name?: string) => {\n try {\n await initCommand(name);\n } catch (err) {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n }\n });\n\nprogram.parse();\n","import { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { resolve, basename } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport prompts from 'prompts';\nimport pc from 'picocolors';\nimport ora from 'ora';\nimport { log, banner } from '../lib/logger.js';\nimport { createConfig, writeConfig } from '../lib/config.js';\nimport { writeEnvFile } from '../lib/env.js';\nimport { cloneTemplate } from '../lib/templates.js';\nimport { getOrCreateApiKey } from '../lib/auth-flow.js';\nimport { detectFromUserAgent, installCommand, runCommand } from '../lib/package-manager.js';\nimport { templates, type Template } from '../templates/registry.js';\nimport { writeFile } from 'node:fs/promises';\n\nexport async function initCommand(nameArg?: string): Promise<void> {\n banner();\n\n const totalSteps = 7;\n\n // 1. Project name\n log.step(1, totalSteps, 'Project setup');\n let projectName: string;\n if (nameArg) {\n projectName = nameArg;\n } else {\n const { name } = await prompts(\n {\n type: 'text',\n name: 'name',\n message: 'Project name',\n initial: 'my-recursiv-app',\n validate: (v: string) => (v.trim() ? true : 'Name is required'),\n },\n { onCancel: () => process.exit(1) }\n );\n projectName = name;\n }\n\n const projectDir = resolve(process.cwd(), projectName);\n\n if (existsSync(projectDir)) {\n log.error(`Directory ${pc.bold(projectName)} already exists`);\n process.exit(1);\n }\n\n // 2. Template selection\n log.step(2, totalSteps, 'Choose a template');\n const templateChoices = templates.map((t) => ({\n title: t.recommended ? `${t.name} ${pc.dim('(recommended)')}` : t.name,\n description: t.description,\n value: t.id,\n }));\n\n const { templateId } = await prompts(\n {\n type: 'select',\n name: 'templateId',\n message: 'Template',\n choices: templateChoices,\n initial: 0,\n },\n { onCancel: () => process.exit(1) }\n );\n\n const template = templates.find((t) => t.id === templateId) as Template;\n\n // 3. API key\n log.step(3, totalSteps, 'Authentication');\n const apiKey = await getOrCreateApiKey('https://recursiv.io/api/v1');\n\n // 4. Clone template\n log.step(4, totalSteps, 'Creating project');\n await mkdir(projectDir, { recursive: true });\n await cloneTemplate(template, projectDir);\n\n // 5. Write config files\n log.step(5, totalSteps, 'Writing config');\n const pm = detectFromUserAgent();\n\n const config = createConfig({\n name: projectName,\n template: template.id,\n framework: template.framework,\n port: template.devPort,\n devCommand: runCommand(pm, 'dev'),\n });\n await writeConfig(projectDir, config);\n\n if (apiKey) {\n await writeEnvFile(projectDir, apiKey);\n }\n\n // Write .gitignore if it doesn't exist\n const gitignorePath = resolve(projectDir, '.gitignore');\n if (!existsSync(gitignorePath)) {\n await writeFile(\n gitignorePath,\n ['node_modules', 'dist', '.env', '.env.local', '.next', '.turbo', ''].join('\\n'),\n 'utf-8'\n );\n }\n\n // 6. Install dependencies\n log.step(6, totalSteps, 'Installing dependencies');\n const installSpinner = ora(`Running ${pc.bold(installCommand(pm))}...`).start();\n try {\n execSync(installCommand(pm), {\n cwd: projectDir,\n stdio: 'pipe',\n timeout: 120_000,\n });\n installSpinner.succeed('Dependencies installed');\n } catch {\n installSpinner.warn('Could not install dependencies — run install manually');\n }\n\n // 7. Init git\n log.step(7, totalSteps, 'Initializing git');\n try {\n execSync('git init', { cwd: projectDir, stdio: 'pipe' });\n execSync('git add -A', { cwd: projectDir, stdio: 'pipe' });\n execSync('git commit -m \"Initial commit from create-recursiv-app\"', {\n cwd: projectDir,\n stdio: 'pipe',\n });\n log.success('Git repository initialized');\n } catch {\n log.warn('Could not initialize git repository');\n }\n\n // Done!\n log.blank();\n console.log(pc.bold(pc.green('Your Recursiv app is ready!')));\n log.blank();\n console.log(' Next steps:');\n console.log();\n console.log(` ${pc.dim('$')} cd ${projectName}`);\n if (!apiKey) {\n console.log(` ${pc.dim('$')} ${pc.dim('# Add your API key to .env')}`);\n }\n console.log(` ${pc.dim('$')} ${runCommand(pm, 'dev')}`);\n log.blank();\n console.log(pc.dim('Docs: https://docs.recursiv.io'));\n console.log(pc.dim('Dashboard: https://recursiv.io/dashboard'));\n log.blank();\n}\n","import pc from 'picocolors';\n\nexport const log = {\n info(msg: string) {\n console.log(pc.cyan('info') + ' ' + msg);\n },\n success(msg: string) {\n console.log(pc.green('ok') + ' ' + msg);\n },\n warn(msg: string) {\n console.log(pc.yellow('warn') + ' ' + msg);\n },\n error(msg: string) {\n console.error(pc.red('error') + ' ' + msg);\n },\n step(n: number, total: number, msg: string) {\n console.log(pc.dim(`[${n}/${total}]`) + ' ' + msg);\n },\n blank() {\n console.log();\n },\n};\n\nexport function banner() {\n console.log();\n console.log(pc.bold('Recursiv') + pc.dim(' — build and ship apps with AI'));\n console.log();\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface RecursivConfig {\n version: number;\n project: {\n name: string;\n template: string;\n framework: string;\n };\n api: {\n baseUrl: string;\n };\n dev: {\n port: number;\n command: string;\n };\n}\n\nconst CONFIG_FILE = '.recursiv.json';\n\nexport function configPath(dir: string): string {\n return join(dir, CONFIG_FILE);\n}\n\nexport async function readConfig(dir: string): Promise<RecursivConfig | null> {\n try {\n const raw = await readFile(configPath(dir), 'utf-8');\n return JSON.parse(raw) as RecursivConfig;\n } catch {\n return null;\n }\n}\n\nexport async function writeConfig(dir: string, config: RecursivConfig): Promise<void> {\n await writeFile(configPath(dir), JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\nexport function createConfig(opts: {\n name: string;\n template: string;\n framework: string;\n port?: number;\n devCommand?: string;\n}): RecursivConfig {\n return {\n version: 1,\n project: {\n name: opts.name,\n template: opts.template,\n framework: opts.framework,\n },\n api: {\n baseUrl: 'https://recursiv.io/api/v1',\n },\n dev: {\n port: opts.port ?? 3000,\n command: opts.devCommand ?? 'npm run dev',\n },\n };\n}\n","import { readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport async function writeEnvFile(dir: string, apiKey: string): Promise<void> {\n const envPath = join(dir, '.env');\n const content = [\n '# Recursiv API key',\n `RECURSIV_API_KEY=${apiKey}`,\n '',\n '# Framework-specific public keys (uncomment as needed)',\n `# NEXT_PUBLIC_RECURSIV_API_KEY=${apiKey}`,\n `# VITE_RECURSIV_API_KEY=${apiKey}`,\n '',\n ].join('\\n');\n await writeFile(envPath, content, 'utf-8');\n}\n\nexport async function readApiKeyFromEnv(dir: string): Promise<string | null> {\n try {\n const raw = await readFile(join(dir, '.env'), 'utf-8');\n const match = raw.match(/^RECURSIV_API_KEY=(.+)$/m);\n return match?.[1]?.trim() ?? null;\n } catch {\n return null;\n }\n}\n\nexport async function updateApiKeyInEnv(dir: string, apiKey: string): Promise<void> {\n const envPath = join(dir, '.env');\n let content: string;\n try {\n content = await readFile(envPath, 'utf-8');\n if (content.includes('RECURSIV_API_KEY=')) {\n content = content.replace(/^RECURSIV_API_KEY=.+$/m, `RECURSIV_API_KEY=${apiKey}`);\n } else {\n content += `\\nRECURSIV_API_KEY=${apiKey}\\n`;\n }\n } catch {\n content = `RECURSIV_API_KEY=${apiKey}\\n`;\n }\n await writeFile(envPath, content, 'utf-8');\n}\n","import degit from 'degit';\nimport ora from 'ora';\nimport type { Template } from '../templates/registry.js';\n\nexport async function cloneTemplate(template: Template, dest: string): Promise<void> {\n const spinner = ora(`Cloning ${template.name} template...`).start();\n try {\n const emitter = degit(template.repo, {\n cache: false,\n force: true,\n verbose: false,\n });\n\n await emitter.clone(dest);\n spinner.succeed(`Template cloned successfully`);\n } catch (err) {\n spinner.fail('Failed to clone template');\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `Could not clone template from ${template.repo}.\\n` +\n `Make sure you have internet access and the repo exists.\\n` +\n `Details: ${message}`\n );\n }\n}\n","import prompts from 'prompts';\nimport pc from 'picocolors';\nimport { log } from './logger.js';\n\nconst API_KEY_PATTERN = /^sk_(live|test)_[a-zA-Z0-9]{20,}$/;\nconst DASHBOARD_URL = 'https://recursiv.io/dashboard/api-keys';\n\nconst DEFAULT_SCOPES = [\n 'posts:read',\n 'posts:write',\n 'chat:read',\n 'chat:write',\n 'agents:read',\n 'agents:write',\n 'projects:read',\n 'projects:write',\n 'communities:read',\n 'communities:write',\n 'users:read',\n];\n\nexport function isValidKeyFormat(key: string): boolean {\n return API_KEY_PATTERN.test(key);\n}\n\nexport async function validateApiKey(apiKey: string, baseUrl: string): Promise<boolean> {\n try {\n const res = await fetch(`${baseUrl}/users/me`, {\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: 'application/json',\n },\n });\n return res.ok;\n } catch {\n return false;\n }\n}\n\n/**\n * Terminal signup flow — create a Recursiv account and API key without a browser.\n *\n * Flow:\n * 1. Prompt for email + password\n * 2. Try signup (or signin if account exists)\n * 3. Use session cookie to create an API key via REST endpoint\n * 4. Return the API key string\n */\nexport async function terminalSignup(baseUrl: string): Promise<string | null> {\n // baseUrl is like https://recursiv.io/api/v1 — we need the root for auth endpoints\n const rootUrl = baseUrl.replace(/\\/api\\/v1\\/?$/, '');\n\n console.log();\n console.log(pc.bold('Create a free Recursiv account'));\n console.log(pc.dim('Free tier: 1,000 API calls/day, 1 agent, 3 projects'));\n console.log();\n\n const { email } = await prompts(\n {\n type: 'text',\n name: 'email',\n message: 'Email',\n validate: (v: string) => {\n if (!v || !v.includes('@')) return 'Valid email required';\n return true;\n },\n },\n { onCancel: () => process.exit(1) },\n );\n\n const { password } = await prompts(\n {\n type: 'password',\n name: 'password',\n message: 'Password',\n validate: (v: string) => {\n if (!v || v.length < 8) return 'Password must be at least 8 characters';\n if (!/[a-zA-Z]/.test(v) || !/[0-9]/.test(v))\n return 'Password must contain both letters and numbers';\n return true;\n },\n },\n { onCancel: () => process.exit(1) },\n );\n\n // Try signup first\n let sessionCookie: string | null = null;\n let isNewAccount = false;\n\n try {\n const signupRes = await fetch(`${rootUrl}/api/auth/sign-up/email`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n email,\n password,\n name: email.split('@')[0],\n }),\n redirect: 'manual',\n });\n\n if (signupRes.ok || signupRes.status === 302) {\n sessionCookie = extractSessionCookie(signupRes);\n isNewAccount = true;\n } else if (signupRes.status === 409 || signupRes.status === 422) {\n // Account already exists — try signing in\n const signinRes = await fetch(`${rootUrl}/api/auth/sign-in/email`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n redirect: 'manual',\n });\n\n if (signinRes.ok || signinRes.status === 302) {\n sessionCookie = extractSessionCookie(signinRes);\n } else {\n const errBody = await signinRes.text().catch(() => '');\n log.error(`Login failed (${signinRes.status}): ${errBody || 'Invalid credentials'}`);\n return null;\n }\n } else {\n const errBody = await signupRes.text().catch(() => '');\n log.error(`Signup failed (${signupRes.status}): ${errBody || 'Unknown error'}`);\n return null;\n }\n } catch (err) {\n log.error(\n `Could not reach ${rootUrl} — ${err instanceof Error ? err.message : 'network error'}`,\n );\n log.info(`You can create an API key manually at ${pc.underline(DASHBOARD_URL)}`);\n return null;\n }\n\n if (!sessionCookie) {\n log.error('No session returned from server');\n log.info(`Create an API key manually at ${pc.underline(DASHBOARD_URL)}`);\n return null;\n }\n\n if (isNewAccount) {\n log.success('Account created');\n } else {\n log.success('Signed in');\n }\n\n // Create an API key using the session\n try {\n const keyRes = await fetch(`${baseUrl}/api-keys`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Cookie: sessionCookie,\n },\n body: JSON.stringify({\n name: 'CLI (auto-created)',\n scopes: DEFAULT_SCOPES,\n }),\n });\n\n if (!keyRes.ok) {\n const errBody = await keyRes.text().catch(() => '');\n log.error(`Failed to create API key (${keyRes.status}): ${errBody}`);\n log.info(`Create one manually at ${pc.underline(DASHBOARD_URL)}`);\n return null;\n }\n\n const { data } = (await keyRes.json()) as { data: { key: string } };\n log.success(`API key created (free tier: 1,000 calls/day)`);\n return data.key;\n } catch (err) {\n log.error(\n `Failed to create API key: ${err instanceof Error ? err.message : 'unknown error'}`,\n );\n log.info(`Create one manually at ${pc.underline(DASHBOARD_URL)}`);\n return null;\n }\n}\n\n/**\n * Extract session cookie from a Better Auth response.\n * Better Auth sets cookies via set-cookie header.\n */\nfunction extractSessionCookie(res: Response): string | null {\n // In Node.js, getSetCookie() returns individual cookie strings\n const setCookieHeaders =\n 'getSetCookie' in res.headers\n ? (res.headers as { getSetCookie(): string[] }).getSetCookie()\n : [];\n\n // Fall back to raw header if getSetCookie not available\n if (setCookieHeaders.length === 0) {\n const raw = res.headers.get('set-cookie');\n if (raw) {\n // Multiple cookies may be comma-separated (though non-standard)\n return raw\n .split(',')\n .map((c) => c.split(';')[0].trim())\n .join('; ');\n }\n return null;\n }\n\n // Extract cookie name=value pairs (strip attributes)\n return setCookieHeaders.map((c) => c.split(';')[0].trim()).join('; ');\n}\n\nexport async function promptApiKey(): Promise<string | null> {\n console.log(\n pc.dim(\n `Get your API key from ${pc.underline(DASHBOARD_URL)}\\n` +\n `Keys start with sk_live_ or sk_test_`,\n ),\n );\n console.log();\n\n const { apiKey } = await prompts(\n {\n type: 'text',\n name: 'apiKey',\n message: 'API key (or press Enter to skip)',\n validate: (value: string) => {\n if (!value) return true; // allow skip\n if (!isValidKeyFormat(value)) {\n return 'Invalid key format. Keys start with sk_live_ or sk_test_';\n }\n return true;\n },\n },\n { onCancel: () => process.exit(1) },\n );\n\n if (!apiKey) {\n log.warn('No API key provided — you can add one later in .env');\n return null;\n }\n\n return apiKey;\n}\n\nexport async function promptAndValidateApiKey(baseUrl: string): Promise<string | null> {\n const apiKey = await promptApiKey();\n if (!apiKey) return null;\n\n const valid = await validateApiKey(apiKey, baseUrl);\n if (!valid) {\n log.warn('Could not validate API key (server unreachable or invalid key)');\n log.info('Key saved to .env — you can update it later');\n } else {\n log.success('API key validated');\n }\n\n return apiKey;\n}\n\n/**\n * Get an API key — either from env, by prompting for an existing key,\n * or by creating a new account via terminal signup.\n */\nexport async function getOrCreateApiKey(baseUrl: string): Promise<string | null> {\n console.log();\n\n const { method } = await prompts(\n {\n type: 'select',\n name: 'method',\n message: 'How would you like to authenticate?',\n choices: [\n {\n title: 'Create a free account',\n description: 'Sign up with email + password (no browser needed)',\n value: 'signup',\n },\n {\n title: 'Enter an existing API key',\n description: 'Paste a key from your dashboard',\n value: 'paste',\n },\n {\n title: 'Skip for now',\n description: 'Add an API key later in .env',\n value: 'skip',\n },\n ],\n },\n { onCancel: () => process.exit(1) },\n );\n\n if (method === 'signup') {\n return terminalSignup(baseUrl);\n }\n\n if (method === 'paste') {\n return promptAndValidateApiKey(baseUrl);\n }\n\n log.warn('No API key — you can add one later in .env');\n return null;\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun';\n\nexport function detectPackageManager(dir: string): PackageManager {\n if (existsSync(join(dir, 'bun.lockb')) || existsSync(join(dir, 'bun.lock'))) return 'bun';\n if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(dir, 'yarn.lock'))) return 'yarn';\n return 'npm';\n}\n\nexport function detectFromUserAgent(): PackageManager {\n const ua = process.env.npm_config_user_agent ?? '';\n if (ua.startsWith('bun')) return 'bun';\n if (ua.startsWith('pnpm')) return 'pnpm';\n if (ua.startsWith('yarn')) return 'yarn';\n return 'npm';\n}\n\nexport function installCommand(pm: PackageManager): string {\n return pm === 'yarn' ? 'yarn' : `${pm} install`;\n}\n\nexport function runCommand(pm: PackageManager, script: string): string {\n if (pm === 'npm') return `npm run ${script}`;\n return `${pm} ${script}`;\n}\n","export interface Template {\n id: string;\n name: string;\n description: string;\n repo: string;\n framework: string;\n devCommand: string;\n devPort: number;\n recommended?: boolean;\n}\n\nexport const templates: Template[] = [\n {\n id: 'nextjs',\n name: 'Next.js + Recursiv',\n description: 'Full-stack Next.js app with feeds, chat, and agents',\n repo: 'socialdotdev/template-nextjs',\n framework: 'nextjs',\n devCommand: 'next dev',\n devPort: 3000,\n recommended: true,\n },\n {\n id: 'node',\n name: 'Node.js + Express',\n description: 'Express API server with Recursiv SDK',\n repo: 'socialdotdev/template-node',\n framework: 'express',\n devCommand: 'node --watch src/index.js',\n devPort: 4000,\n },\n {\n id: 'vite',\n name: 'Vite + React',\n description: 'React SPA with social feed components',\n repo: 'socialdotdev/template-vite',\n framework: 'vite',\n devCommand: 'vite',\n devPort: 5173,\n },\n];\n\nexport function getTemplate(id: string): Template | undefined {\n return templates.find((t) => t.id === id);\n}\n\nexport function getDefaultTemplate(): Template {\n return templates.find((t) => t.recommended) ?? templates[0]!;\n}\n"],"mappings":"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","names":["existsSync","prompts","pc","ora","readFile","writeFile","join","pc","pc","join","writeFile","prompts","existsSync","pc","ora"]}
|