@recursiv/cli 0.1.5

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,4172 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/lib/logger.ts
13
+ import pc from "picocolors";
14
+ function banner() {
15
+ console.log();
16
+ console.log(pc.bold("Recursiv") + pc.dim(" \u2014 build and ship apps with AI"));
17
+ console.log();
18
+ }
19
+ var log;
20
+ var init_logger = __esm({
21
+ "src/lib/logger.ts"() {
22
+ "use strict";
23
+ log = {
24
+ info(msg) {
25
+ console.log(pc.cyan("info") + " " + msg);
26
+ },
27
+ success(msg) {
28
+ console.log(pc.green("ok") + " " + msg);
29
+ },
30
+ warn(msg) {
31
+ console.log(pc.yellow("warn") + " " + msg);
32
+ },
33
+ error(msg) {
34
+ console.error(pc.red("error") + " " + msg);
35
+ },
36
+ step(n, total, msg) {
37
+ console.log(pc.dim(`[${n}/${total}]`) + " " + msg);
38
+ },
39
+ blank() {
40
+ console.log();
41
+ }
42
+ };
43
+ }
44
+ });
45
+
46
+ // src/lib/config.ts
47
+ var config_exports = {};
48
+ __export(config_exports, {
49
+ configPath: () => configPath,
50
+ createConfig: () => createConfig,
51
+ readConfig: () => readConfig,
52
+ writeConfig: () => writeConfig
53
+ });
54
+ import { readFile, writeFile } from "fs/promises";
55
+ import { join } from "path";
56
+ function configPath(dir) {
57
+ return join(dir, CONFIG_FILE);
58
+ }
59
+ async function readConfig(dir) {
60
+ try {
61
+ const raw = await readFile(configPath(dir), "utf-8");
62
+ return JSON.parse(raw);
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+ async function writeConfig(dir, config) {
68
+ await writeFile(configPath(dir), JSON.stringify(config, null, 2) + "\n", "utf-8");
69
+ }
70
+ function createConfig(opts) {
71
+ return {
72
+ version: 1,
73
+ project: {
74
+ name: opts.name,
75
+ template: opts.template,
76
+ framework: opts.framework
77
+ },
78
+ api: {
79
+ baseUrl: "https://api.recursiv.io/api/v1"
80
+ },
81
+ dev: {
82
+ port: opts.port ?? 3e3,
83
+ command: opts.devCommand ?? "npm run dev"
84
+ }
85
+ };
86
+ }
87
+ var CONFIG_FILE;
88
+ var init_config = __esm({
89
+ "src/lib/config.ts"() {
90
+ "use strict";
91
+ CONFIG_FILE = ".recursiv.json";
92
+ }
93
+ });
94
+
95
+ // src/lib/env.ts
96
+ var env_exports = {};
97
+ __export(env_exports, {
98
+ readApiKeyFromEnv: () => readApiKeyFromEnv,
99
+ updateApiKeyInEnv: () => updateApiKeyInEnv,
100
+ writeEnvFile: () => writeEnvFile
101
+ });
102
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
103
+ import { join as join2 } from "path";
104
+ async function writeEnvFile(dir, apiKey) {
105
+ const envPath = join2(dir, ".env");
106
+ const content = [
107
+ "# Recursiv API key",
108
+ `RECURSIV_API_KEY=${apiKey}`,
109
+ "",
110
+ "# Framework-specific public keys (uncomment as needed)",
111
+ `# NEXT_PUBLIC_RECURSIV_API_KEY=${apiKey}`,
112
+ `# VITE_RECURSIV_API_KEY=${apiKey}`,
113
+ ""
114
+ ].join("\n");
115
+ await writeFile2(envPath, content, "utf-8");
116
+ }
117
+ async function readApiKeyFromEnv(dir) {
118
+ try {
119
+ const raw = await readFile2(join2(dir, ".env"), "utf-8");
120
+ const match = raw.match(/^RECURSIV_API_KEY=(.+)$/m);
121
+ return match?.[1]?.trim() ?? null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+ async function updateApiKeyInEnv(dir, apiKey) {
127
+ const envPath = join2(dir, ".env");
128
+ let content;
129
+ try {
130
+ content = await readFile2(envPath, "utf-8");
131
+ if (content.includes("RECURSIV_API_KEY=")) {
132
+ content = content.replace(/^RECURSIV_API_KEY=.+$/m, `RECURSIV_API_KEY=${apiKey}`);
133
+ } else {
134
+ content += `
135
+ RECURSIV_API_KEY=${apiKey}
136
+ `;
137
+ }
138
+ } catch {
139
+ content = `RECURSIV_API_KEY=${apiKey}
140
+ `;
141
+ }
142
+ await writeFile2(envPath, content, "utf-8");
143
+ }
144
+ var init_env = __esm({
145
+ "src/lib/env.ts"() {
146
+ "use strict";
147
+ }
148
+ });
149
+
150
+ // src/lib/auth-flow.ts
151
+ var auth_flow_exports = {};
152
+ __export(auth_flow_exports, {
153
+ getOrCreateApiKey: () => getOrCreateApiKey,
154
+ isValidKeyFormat: () => isValidKeyFormat,
155
+ promptAndValidateApiKey: () => promptAndValidateApiKey,
156
+ promptApiKey: () => promptApiKey,
157
+ terminalSignup: () => terminalSignup,
158
+ validateApiKey: () => validateApiKey
159
+ });
160
+ import prompts from "prompts";
161
+ import pc2 from "picocolors";
162
+ function isValidKeyFormat(key) {
163
+ return API_KEY_PATTERN.test(key);
164
+ }
165
+ async function validateApiKey(apiKey, baseUrl) {
166
+ try {
167
+ const res = await fetch(`${baseUrl}/users/me`, {
168
+ headers: {
169
+ Authorization: `Bearer ${apiKey}`,
170
+ Accept: "application/json"
171
+ }
172
+ });
173
+ return res.ok;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+ async function terminalSignup(baseUrl) {
179
+ const rootUrl = baseUrl.replace(/\/api\/v1\/?$/, "");
180
+ console.log();
181
+ console.log(pc2.bold("Create a free Recursiv account"));
182
+ console.log(pc2.dim("Free tier: 1,000 API calls/day, 1 agent, 3 projects"));
183
+ console.log(pc2.dim("We'll send a 6-digit code to your email \u2014 no password needed."));
184
+ console.log();
185
+ const { email } = await prompts(
186
+ {
187
+ type: "text",
188
+ name: "email",
189
+ message: "Email",
190
+ validate: (v) => {
191
+ if (!v || !v.includes("@")) return "Valid email required";
192
+ return true;
193
+ }
194
+ },
195
+ { onCancel: () => process.exit(1) }
196
+ );
197
+ const origin = rootUrl || "https://api.recursiv.io";
198
+ const authHeaders = { "Content-Type": "application/json", Origin: origin };
199
+ try {
200
+ const sendRes = await fetch(`${rootUrl}/api/auth/email-otp/send-verification-otp`, {
201
+ method: "POST",
202
+ headers: authHeaders,
203
+ body: JSON.stringify({ email, type: "sign-in" })
204
+ });
205
+ if (!sendRes.ok) {
206
+ const errBody = await sendRes.text().catch(() => "");
207
+ log.error(`Failed to send code (${sendRes.status}): ${errBody || "Unknown error"}`);
208
+ return null;
209
+ }
210
+ } catch (err) {
211
+ log.error(
212
+ `Could not reach ${rootUrl} \u2014 ${err instanceof Error ? err.message : "network error"}`
213
+ );
214
+ log.info(`You can create an API key manually at ${pc2.underline(DASHBOARD_URL)}`);
215
+ return null;
216
+ }
217
+ log.success(`Verification code sent to ${email}`);
218
+ console.log();
219
+ const { code } = await prompts(
220
+ {
221
+ type: "text",
222
+ name: "code",
223
+ message: "Enter the 6-digit code",
224
+ validate: (v) => {
225
+ if (!v || !/^\d{6}$/.test(v.trim())) return "Enter the 6-digit code from your email";
226
+ return true;
227
+ }
228
+ },
229
+ { onCancel: () => process.exit(1) }
230
+ );
231
+ let sessionCookie = null;
232
+ try {
233
+ const verifyRes = await fetch(`${rootUrl}/api/auth/email-otp/verify-email`, {
234
+ method: "POST",
235
+ headers: authHeaders,
236
+ body: JSON.stringify({ email, otp: code.trim() }),
237
+ redirect: "manual"
238
+ });
239
+ if (verifyRes.ok || verifyRes.status === 302) {
240
+ sessionCookie = extractSessionCookie(verifyRes);
241
+ } else {
242
+ const errBody = await verifyRes.text().catch(() => "");
243
+ log.error(`Verification failed (${verifyRes.status}): ${errBody || "Invalid or expired code"}`);
244
+ return null;
245
+ }
246
+ } catch (err) {
247
+ log.error(
248
+ `Verification failed \u2014 ${err instanceof Error ? err.message : "network error"}`
249
+ );
250
+ return null;
251
+ }
252
+ if (!sessionCookie) {
253
+ log.error("No session returned from server");
254
+ log.info(`Create an API key manually at ${pc2.underline(DASHBOARD_URL)}`);
255
+ return null;
256
+ }
257
+ log.success("Authenticated");
258
+ try {
259
+ const keyRes = await fetch(`${baseUrl}/api-keys`, {
260
+ method: "POST",
261
+ headers: {
262
+ "Content-Type": "application/json",
263
+ Cookie: sessionCookie
264
+ },
265
+ body: JSON.stringify({
266
+ name: "CLI (auto-created)",
267
+ scopes: DEFAULT_SCOPES
268
+ })
269
+ });
270
+ if (!keyRes.ok) {
271
+ const errBody = await keyRes.text().catch(() => "");
272
+ log.error(`Failed to create API key (${keyRes.status}): ${errBody}`);
273
+ log.info(`Create one manually at ${pc2.underline(DASHBOARD_URL)}`);
274
+ return null;
275
+ }
276
+ const { data } = await keyRes.json();
277
+ log.success(`API key created (free tier: 1,000 calls/day)`);
278
+ return data.key;
279
+ } catch (err) {
280
+ log.error(
281
+ `Failed to create API key: ${err instanceof Error ? err.message : "unknown error"}`
282
+ );
283
+ log.info(`Create one manually at ${pc2.underline(DASHBOARD_URL)}`);
284
+ return null;
285
+ }
286
+ }
287
+ function extractSessionCookie(res) {
288
+ const setCookieHeaders = "getSetCookie" in res.headers ? res.headers.getSetCookie() : [];
289
+ if (setCookieHeaders.length === 0) {
290
+ const raw = res.headers.get("set-cookie");
291
+ if (raw) {
292
+ return raw.split(",").map((c) => c.split(";")[0].trim()).join("; ");
293
+ }
294
+ return null;
295
+ }
296
+ return setCookieHeaders.map((c) => c.split(";")[0].trim()).join("; ");
297
+ }
298
+ async function promptApiKey() {
299
+ console.log(
300
+ pc2.dim(
301
+ `Get your API key from ${pc2.underline(DASHBOARD_URL)}
302
+ Keys start with sk_live_ or sk_test_`
303
+ )
304
+ );
305
+ console.log();
306
+ const { apiKey } = await prompts(
307
+ {
308
+ type: "text",
309
+ name: "apiKey",
310
+ message: "API key (or press Enter to skip)",
311
+ validate: (value) => {
312
+ if (!value) return true;
313
+ if (!isValidKeyFormat(value)) {
314
+ return "Invalid key format. Keys start with sk_live_ or sk_test_";
315
+ }
316
+ return true;
317
+ }
318
+ },
319
+ { onCancel: () => process.exit(1) }
320
+ );
321
+ if (!apiKey) {
322
+ log.warn("No API key provided \u2014 you can add one later in .env");
323
+ return null;
324
+ }
325
+ return apiKey;
326
+ }
327
+ async function promptAndValidateApiKey(baseUrl) {
328
+ const apiKey = await promptApiKey();
329
+ if (!apiKey) return null;
330
+ const valid = await validateApiKey(apiKey, baseUrl);
331
+ if (!valid) {
332
+ log.warn("Could not validate API key (server unreachable or invalid key)");
333
+ log.info("Key saved to .env \u2014 you can update it later");
334
+ } else {
335
+ log.success("API key validated");
336
+ }
337
+ return apiKey;
338
+ }
339
+ async function getOrCreateApiKey(baseUrl) {
340
+ console.log();
341
+ const { method } = await prompts(
342
+ {
343
+ type: "select",
344
+ name: "method",
345
+ message: "How would you like to authenticate?",
346
+ choices: [
347
+ {
348
+ title: "Create a free account",
349
+ description: "Sign up with email (we'll send a 6-digit code)",
350
+ value: "signup"
351
+ },
352
+ {
353
+ title: "Enter an existing API key",
354
+ description: "Paste a key from your dashboard",
355
+ value: "paste"
356
+ },
357
+ {
358
+ title: "Skip for now",
359
+ description: "Add an API key later in .env",
360
+ value: "skip"
361
+ }
362
+ ]
363
+ },
364
+ { onCancel: () => process.exit(1) }
365
+ );
366
+ if (method === "signup") {
367
+ return terminalSignup(baseUrl);
368
+ }
369
+ if (method === "paste") {
370
+ return promptAndValidateApiKey(baseUrl);
371
+ }
372
+ log.warn("No API key \u2014 you can add one later in .env");
373
+ return null;
374
+ }
375
+ var API_KEY_PATTERN, DASHBOARD_URL, DEFAULT_SCOPES;
376
+ var init_auth_flow = __esm({
377
+ "src/lib/auth-flow.ts"() {
378
+ "use strict";
379
+ init_logger();
380
+ API_KEY_PATTERN = /^sk_(live|test)_[a-zA-Z0-9]{20,}$/;
381
+ DASHBOARD_URL = "https://recursiv.io/dashboard/api-keys";
382
+ DEFAULT_SCOPES = [
383
+ "posts:read",
384
+ "posts:write",
385
+ "chat:read",
386
+ "chat:write",
387
+ "agents:read",
388
+ "agents:write",
389
+ "projects:read",
390
+ "projects:write",
391
+ "communities:read",
392
+ "communities:write",
393
+ "users:read"
394
+ ];
395
+ }
396
+ });
397
+
398
+ // src/lib/credentials.ts
399
+ var credentials_exports = {};
400
+ __export(credentials_exports, {
401
+ readCredentials: () => readCredentials,
402
+ resolveAnthropicKey: () => resolveAnthropicKey,
403
+ resolveRecursivKey: () => resolveRecursivKey,
404
+ saveCredentials: () => saveCredentials
405
+ });
406
+ import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir2 } from "fs/promises";
407
+ import { join as join4 } from "path";
408
+ import { homedir } from "os";
409
+ async function readCredentials() {
410
+ try {
411
+ const raw = await readFile4(CREDENTIALS_PATH, "utf-8");
412
+ return JSON.parse(raw);
413
+ } catch {
414
+ return {};
415
+ }
416
+ }
417
+ async function saveCredentials(creds) {
418
+ await mkdir2(RECURSIV_DIR, { recursive: true });
419
+ const existing = await readCredentials();
420
+ const merged = { ...existing, ...creds };
421
+ await writeFile5(CREDENTIALS_PATH, JSON.stringify(merged, null, 2) + "\n", {
422
+ encoding: "utf-8",
423
+ mode: 384
424
+ // Owner-only read/write
425
+ });
426
+ }
427
+ async function resolveRecursivKey(projectDir) {
428
+ const envKey = process.env.RECURSIV_API_KEY || process.env.SOCIAL_DEV_API_KEY;
429
+ if (envKey) return envKey;
430
+ if (projectDir) {
431
+ try {
432
+ const { readApiKeyFromEnv: readApiKeyFromEnv2 } = await Promise.resolve().then(() => (init_env(), env_exports));
433
+ const projKey = await readApiKeyFromEnv2(projectDir);
434
+ if (projKey) return projKey;
435
+ } catch {
436
+ }
437
+ }
438
+ const creds = await readCredentials();
439
+ return creds.recursivApiKey || null;
440
+ }
441
+ async function resolveAnthropicKey() {
442
+ if (process.env.ANTHROPIC_API_KEY) return process.env.ANTHROPIC_API_KEY;
443
+ const creds = await readCredentials();
444
+ return creds.anthropicApiKey || null;
445
+ }
446
+ var RECURSIV_DIR, CREDENTIALS_PATH;
447
+ var init_credentials = __esm({
448
+ "src/lib/credentials.ts"() {
449
+ "use strict";
450
+ RECURSIV_DIR = join4(homedir(), ".recursiv");
451
+ CREDENTIALS_PATH = join4(RECURSIV_DIR, "credentials");
452
+ }
453
+ });
454
+
455
+ // src/commands/mcp-install.ts
456
+ var mcp_install_exports = {};
457
+ __export(mcp_install_exports, {
458
+ mcpInstallCommand: () => mcpInstallCommand,
459
+ mcpVerifyCommand: () => mcpVerifyCommand
460
+ });
461
+ import { readFile as readFile5, writeFile as writeFile6, mkdir as mkdir3 } from "fs/promises";
462
+ import { join as join5, resolve as resolve3 } from "path";
463
+ import { homedir as homedir2, platform } from "os";
464
+ import pc11 from "picocolors";
465
+ async function mcpInstallCommand(opts) {
466
+ const target = opts.target;
467
+ if (!VALID_TARGETS.includes(target)) {
468
+ console.error(
469
+ pc11.red("error") + ` Unknown target "${opts.target}". Valid targets: ${VALID_TARGETS.join(", ")}`
470
+ );
471
+ process.exit(1);
472
+ }
473
+ const { resolveRecursivKey: resolveRecursivKey2, saveCredentials: saveCredentials2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
474
+ let apiKey = opts.apiKey || await resolveRecursivKey2(process.cwd());
475
+ if (!apiKey) {
476
+ console.log();
477
+ console.log(pc11.dim(" No Recursiv API key found. Let's authenticate."));
478
+ console.log();
479
+ const { getOrCreateApiKey: getOrCreateApiKey2 } = await Promise.resolve().then(() => (init_auth_flow(), auth_flow_exports));
480
+ const baseUrl = process.env.RECURSIV_BASE_URL || "https://api.recursiv.io/api/v1";
481
+ apiKey = await getOrCreateApiKey2(baseUrl);
482
+ if (!apiKey) {
483
+ console.error(pc11.red(" error") + " Authentication required to continue.");
484
+ process.exit(1);
485
+ }
486
+ await saveCredentials2({ recursivApiKey: apiKey });
487
+ console.log(pc11.dim(" Credentials saved to ~/.recursiv/credentials"));
488
+ console.log();
489
+ }
490
+ switch (target) {
491
+ case "claude-code":
492
+ await installClaudeCode(apiKey, opts.global);
493
+ break;
494
+ case "claude-desktop":
495
+ await installClaudeDesktop(apiKey);
496
+ break;
497
+ case "codex":
498
+ await installCodex(apiKey);
499
+ break;
500
+ case "opencode":
501
+ await installOpenCode(apiKey, opts.global);
502
+ break;
503
+ }
504
+ }
505
+ async function installClaudeCode(apiKey, global) {
506
+ if (global) {
507
+ const claudeDir = join5(homedir2(), ".claude");
508
+ const settingsPath = join5(claudeDir, "settings.json");
509
+ await mkdir3(claudeDir, { recursive: true });
510
+ const settings = await readJsonSafe(settingsPath);
511
+ if (!settings.mcpServers) settings.mcpServers = {};
512
+ settings.mcpServers.recursiv = mcpServerEntry(apiKey);
513
+ await writeFile6(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
514
+ console.log(pc11.green("\u2713") + ` Recursiv MCP added to ${pc11.dim(settingsPath)}`);
515
+ } else {
516
+ const mcpPath = join5(process.cwd(), ".mcp.json");
517
+ const config = await readJsonSafe(mcpPath);
518
+ if (!config.mcpServers) config.mcpServers = {};
519
+ config.mcpServers.recursiv = mcpServerEntry(apiKey);
520
+ await writeFile6(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
521
+ console.log(pc11.green("\u2713") + ` Recursiv MCP added to ${pc11.dim(mcpPath)}`);
522
+ }
523
+ }
524
+ function claudeDesktopConfigPath() {
525
+ const p = platform();
526
+ if (p === "darwin") {
527
+ return join5(homedir2(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
528
+ }
529
+ if (p === "win32") {
530
+ return join5(process.env.APPDATA || join5(homedir2(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
531
+ }
532
+ return join5(homedir2(), ".config", "claude", "claude_desktop_config.json");
533
+ }
534
+ async function installClaudeDesktop(apiKey) {
535
+ const configPath2 = claudeDesktopConfigPath();
536
+ const dir = resolve3(configPath2, "..");
537
+ await mkdir3(dir, { recursive: true });
538
+ const config = await readJsonSafe(configPath2);
539
+ if (!config.mcpServers) config.mcpServers = {};
540
+ config.mcpServers.recursiv = mcpServerEntry(apiKey);
541
+ await writeFile6(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
542
+ console.log(pc11.green("\u2713") + ` Recursiv MCP added to ${pc11.dim(configPath2)}`);
543
+ console.log(pc11.dim(" Restart Claude Desktop to pick up the change."));
544
+ }
545
+ async function installCodex(apiKey) {
546
+ const codexDir = join5(homedir2(), ".codex");
547
+ const configPath2 = join5(codexDir, "config.toml");
548
+ await mkdir3(codexDir, { recursive: true });
549
+ let existing = "";
550
+ try {
551
+ existing = await readFile5(configPath2, "utf-8");
552
+ } catch {
553
+ }
554
+ existing = removeTomlSection(existing, "mcp_servers.recursiv");
555
+ const tomlBlock = [
556
+ "",
557
+ "[mcp_servers.recursiv]",
558
+ 'command = "npx"',
559
+ 'args = ["-y", "@recursiv/mcp"]',
560
+ "",
561
+ "[mcp_servers.recursiv.env]",
562
+ `RECURSIV_API_KEY = "${apiKey}"`
563
+ ].join("\n");
564
+ const final = existing.trimEnd() + "\n" + tomlBlock + "\n";
565
+ await writeFile6(configPath2, final, "utf-8");
566
+ console.log(pc11.green("\u2713") + ` Recursiv MCP added to ${pc11.dim(configPath2)}`);
567
+ }
568
+ async function installOpenCode(apiKey, global) {
569
+ let configPath2;
570
+ if (global) {
571
+ const configDir = join5(homedir2(), ".config", "opencode");
572
+ await mkdir3(configDir, { recursive: true });
573
+ configPath2 = join5(configDir, "opencode.json");
574
+ } else {
575
+ configPath2 = join5(process.cwd(), "opencode.json");
576
+ }
577
+ const config = await readJsonSafe(configPath2);
578
+ if (!config.mcp) config.mcp = {};
579
+ config.mcp.recursiv = {
580
+ type: "local",
581
+ command: ["npx", "-y", "@recursiv/mcp"],
582
+ enabled: true,
583
+ environment: {
584
+ RECURSIV_API_KEY: apiKey
585
+ }
586
+ };
587
+ await writeFile6(configPath2, JSON.stringify(config, null, 2) + "\n", "utf-8");
588
+ console.log(pc11.green("\u2713") + ` Recursiv MCP added to ${pc11.dim(configPath2)}`);
589
+ }
590
+ async function mcpVerifyCommand() {
591
+ const { spawn: spawn2 } = await import("child_process");
592
+ const apiKey = process.env.RECURSIV_API_KEY || process.env.SOCIAL_DEV_API_KEY;
593
+ if (!apiKey) {
594
+ console.error(pc11.red(" error") + " RECURSIV_API_KEY not set in environment.");
595
+ process.exit(1);
596
+ }
597
+ console.log(pc11.dim(" Verifying Recursiv MCP server..."));
598
+ const child = spawn2("npx", ["-y", "@recursiv/mcp"], {
599
+ env: { ...process.env, RECURSIV_API_KEY: apiKey },
600
+ stdio: ["pipe", "pipe", "pipe"]
601
+ });
602
+ const initRequest = JSON.stringify({
603
+ jsonrpc: "2.0",
604
+ id: 1,
605
+ method: "initialize",
606
+ params: {
607
+ protocolVersion: "2024-11-05",
608
+ capabilities: {},
609
+ clientInfo: { name: "recursiv-cli", version: "0.1.4" }
610
+ }
611
+ });
612
+ let stdout = "";
613
+ let stderr = "";
614
+ child.stdout.on("data", (chunk) => {
615
+ stdout += chunk.toString();
616
+ });
617
+ child.stderr.on("data", (chunk) => {
618
+ stderr += chunk.toString();
619
+ });
620
+ child.stdin.write(initRequest + "\n");
621
+ const result = await Promise.race([
622
+ new Promise((resolve4) => {
623
+ const check = setInterval(() => {
624
+ if (stdout.includes('"result"')) {
625
+ clearInterval(check);
626
+ resolve4("ok");
627
+ }
628
+ if (stdout.includes('"error"') || stderr.includes("Missing")) {
629
+ clearInterval(check);
630
+ resolve4("error");
631
+ }
632
+ }, 100);
633
+ }),
634
+ new Promise((resolve4) => setTimeout(() => resolve4("timeout"), 1e4))
635
+ ]);
636
+ child.kill();
637
+ switch (result) {
638
+ case "ok":
639
+ console.log(pc11.green(" \u2713") + " MCP server responded successfully.");
640
+ try {
641
+ const parsed = JSON.parse(stdout.trim().split("\n").pop() || "{}");
642
+ const serverName = parsed?.result?.serverInfo?.name;
643
+ const version = parsed?.result?.serverInfo?.version;
644
+ if (serverName) {
645
+ console.log(pc11.dim(` Server: ${serverName} v${version || "?"}`));
646
+ }
647
+ } catch {
648
+ }
649
+ break;
650
+ case "error":
651
+ console.error(pc11.red(" \u2717") + " MCP server returned an error.");
652
+ if (stderr) console.error(pc11.dim(` ${stderr.trim()}`));
653
+ process.exit(1);
654
+ break;
655
+ case "timeout":
656
+ console.error(pc11.red(" \u2717") + " MCP server did not respond within 10s.");
657
+ process.exit(1);
658
+ break;
659
+ }
660
+ }
661
+ function mcpServerEntry(apiKey) {
662
+ return {
663
+ command: "npx",
664
+ args: ["-y", "@recursiv/mcp"],
665
+ env: {
666
+ RECURSIV_API_KEY: apiKey
667
+ }
668
+ };
669
+ }
670
+ async function readJsonSafe(path) {
671
+ try {
672
+ const raw = await readFile5(path, "utf-8");
673
+ return JSON.parse(raw);
674
+ } catch {
675
+ return {};
676
+ }
677
+ }
678
+ function removeTomlSection(content, section) {
679
+ const lines = content.split("\n");
680
+ const result = [];
681
+ let skipping = false;
682
+ const sectionHeader = `[${section}]`;
683
+ for (const line of lines) {
684
+ const trimmed = line.trim();
685
+ if (trimmed === sectionHeader || trimmed.startsWith(`[${section}.`)) {
686
+ skipping = true;
687
+ continue;
688
+ }
689
+ if (skipping && trimmed.startsWith("[")) {
690
+ skipping = false;
691
+ }
692
+ if (!skipping) {
693
+ result.push(line);
694
+ }
695
+ }
696
+ return result.join("\n");
697
+ }
698
+ var VALID_TARGETS;
699
+ var init_mcp_install = __esm({
700
+ "src/commands/mcp-install.ts"() {
701
+ "use strict";
702
+ VALID_TARGETS = ["claude-code", "claude-desktop", "codex", "opencode"];
703
+ }
704
+ });
705
+
706
+ // src/chat/renderer.ts
707
+ import pc12 from "picocolors";
708
+ function getWidth() {
709
+ return Math.max((process.stdout.columns || 80) - INDENT.length, 40);
710
+ }
711
+ function wordWrap(text, width, indent = INDENT) {
712
+ const lines = [];
713
+ for (const paragraph of text.split("\n")) {
714
+ if (paragraph.length <= width) {
715
+ lines.push(indent + paragraph);
716
+ continue;
717
+ }
718
+ const words = paragraph.split(/(\s+)/);
719
+ let current = "";
720
+ for (const word of words) {
721
+ if (current.length + word.length > width && current.length > 0) {
722
+ lines.push(indent + current.trimEnd());
723
+ current = word.trimStart();
724
+ } else {
725
+ current += word;
726
+ }
727
+ }
728
+ if (current.trimEnd()) lines.push(indent + current.trimEnd());
729
+ }
730
+ return lines.join("\n");
731
+ }
732
+ function colorize(text) {
733
+ text = text.replace(/(https?:\/\/[^\s)\]>]+)/g, pc12.cyan(pc12.underline("$1")));
734
+ text = text.replace(/`([^`]+)`/g, pc12.yellow("`$1`"));
735
+ text = text.replace(/\*\*([^*]+)\*\*/g, pc12.bold("$1"));
736
+ text = text.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, pc12.italic("$1"));
737
+ return text;
738
+ }
739
+ function printBanner(opts) {
740
+ const v = opts?.version ?? "0.1.4";
741
+ const logo = [
742
+ pc12.cyan(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"),
743
+ pc12.cyan(" \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502"),
744
+ pc12.cyan(" \u2502 \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502") + " " + pc12.bold(pc12.cyan("recursiv")) + pc12.dim(` v${v}`),
745
+ pc12.cyan(" \u2502 \u2502 \u2502 \u250C\u2500\u2510 \u2502 \u2502 \u2502") + " " + pc12.dim("https://recursiv.io"),
746
+ pc12.cyan(" \u2502 \u2502 \u2502 \u2514\u2500\u2518 \u2502 \u2502 \u2502"),
747
+ pc12.cyan(" \u2502 \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502"),
748
+ pc12.cyan(" \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502"),
749
+ pc12.cyan(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518")
750
+ ];
751
+ console.log();
752
+ for (const line of logo) {
753
+ console.log(line);
754
+ }
755
+ console.log();
756
+ if (opts?.email || opts?.username) {
757
+ const identity = opts.email ? `${opts.username ? pc12.bold(`@${opts.username}`) : "signed in"} ${pc12.dim(`(${opts.email})`)}` : pc12.bold(`@${opts.username}`);
758
+ console.log(pc12.dim(" ") + identity);
759
+ }
760
+ if (opts?.resumed) {
761
+ console.log(
762
+ pc12.dim(" ") + pc12.dim(`resumed session ${opts.resumed.id} (${opts.resumed.messageCount} messages)`)
763
+ );
764
+ }
765
+ console.log();
766
+ console.log(pc12.dim(" Type a message to get started. /help for commands. Ctrl-D to exit."));
767
+ }
768
+ function printPrompt() {
769
+ process.stdout.write(pc12.bold(pc12.green("> ")));
770
+ }
771
+ function printTips(context = "welcome") {
772
+ const tips = HINT_MAP[context] || HINT_MAP.default;
773
+ console.log();
774
+ for (const [cmd, desc] of tips) {
775
+ console.log(
776
+ INDENT + pc12.dim(" ") + pc12.cyan(cmd) + pc12.dim(` \u2014 ${desc}`)
777
+ );
778
+ }
779
+ console.log();
780
+ }
781
+ function detectHintContext(responseText) {
782
+ const lower = responseText.toLowerCase();
783
+ if (lower.includes("created") && lower.includes("project")) return "project_created";
784
+ if (lower.includes("deployed") || lower.includes("deployment")) return "deployed";
785
+ if (lower.includes("created") && lower.includes("agent")) return "agent_created";
786
+ if (lower.includes("sandbox") && lower.includes("start")) return "sandbox_started";
787
+ if (lower.includes("executed") || lower.includes("output:")) return "code_executed";
788
+ if (lower.includes("projects") && (lower.includes("list") || lower.includes("|"))) return "listed_projects";
789
+ return "default";
790
+ }
791
+ function printAssistantLabel() {
792
+ console.log();
793
+ process.stdout.write(pc12.bold(pc12.cyan("recursiv ")) + "\n");
794
+ }
795
+ function printToolCall(name) {
796
+ console.log(pc12.yellow(" \u25B8 ") + pc12.dim(name));
797
+ }
798
+ function printToolResult(name, success) {
799
+ if (success) {
800
+ console.log(pc12.green(" \u2713 ") + pc12.dim(name));
801
+ } else {
802
+ console.log(pc12.red(" \u2717 ") + pc12.dim(name));
803
+ }
804
+ }
805
+ function startThinking() {
806
+ const frames = [" \xB7", " \xB7\xB7", " \xB7\xB7\xB7"];
807
+ let i = 0;
808
+ const interval = setInterval(() => {
809
+ process.stdout.write(`\r${pc12.dim(frames[i % frames.length])} `);
810
+ i++;
811
+ }, 250);
812
+ return {
813
+ stop() {
814
+ clearInterval(interval);
815
+ process.stdout.write("\r\x1B[0K");
816
+ }
817
+ };
818
+ }
819
+ async function confirmAction(description) {
820
+ const { createInterface: createInterface2 } = await import("readline");
821
+ const rl = createInterface2({ input: process.stdin, output: process.stdout, terminal: true });
822
+ return new Promise((resolve4) => {
823
+ process.stdout.write(
824
+ pc12.yellow(" ? ") + description + pc12.dim(" (y/n) ")
825
+ );
826
+ rl.once("line", (line) => {
827
+ rl.close();
828
+ const answer = line.trim().toLowerCase();
829
+ resolve4(answer === "y" || answer === "yes");
830
+ });
831
+ rl.once("close", () => resolve4(false));
832
+ });
833
+ }
834
+ function printError(message) {
835
+ console.log(pc12.red(" \u2717 ") + message);
836
+ }
837
+ function printInfo(message) {
838
+ console.log(pc12.dim(message));
839
+ }
840
+ var INDENT, StreamWriter, HINT_MAP;
841
+ var init_renderer = __esm({
842
+ "src/chat/renderer.ts"() {
843
+ "use strict";
844
+ INDENT = " ";
845
+ StreamWriter = class {
846
+ buffer = "";
847
+ lineBuffer = "";
848
+ inCodeBlock = false;
849
+ write(chunk) {
850
+ this.buffer += chunk;
851
+ for (const char of chunk) {
852
+ this.lineBuffer += char;
853
+ if (char === "\n") {
854
+ this.flushLine();
855
+ }
856
+ }
857
+ }
858
+ flushLine() {
859
+ const line = this.lineBuffer;
860
+ this.lineBuffer = "";
861
+ const width = getWidth();
862
+ if (line.trimStart().startsWith("```")) {
863
+ this.inCodeBlock = !this.inCodeBlock;
864
+ process.stdout.write(INDENT + pc12.dim(line));
865
+ return;
866
+ }
867
+ if (this.inCodeBlock) {
868
+ process.stdout.write(INDENT + pc12.cyan(line));
869
+ return;
870
+ }
871
+ if (line.trim() === "") {
872
+ process.stdout.write("\n");
873
+ return;
874
+ }
875
+ const headingMatch = line.match(/^(#{1,3})\s+(.+)\n?$/);
876
+ if (headingMatch) {
877
+ process.stdout.write(INDENT + pc12.bold(pc12.cyan(headingMatch[2])) + "\n");
878
+ return;
879
+ }
880
+ if (/^---+\s*\n?$/.test(line)) {
881
+ process.stdout.write(INDENT + pc12.dim("\u2500".repeat(Math.min(width, 60))) + "\n");
882
+ return;
883
+ }
884
+ const bulletMatch = line.match(/^(\s*)([-*])\s+(.*)$/);
885
+ if (bulletMatch) {
886
+ const extraIndent = bulletMatch[1];
887
+ const content = colorize(bulletMatch[3]);
888
+ const prefix = INDENT + extraIndent + pc12.cyan("\u2022") + " ";
889
+ process.stdout.write(prefix + content + "\n");
890
+ return;
891
+ }
892
+ const numMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
893
+ if (numMatch) {
894
+ const extraIndent = numMatch[1];
895
+ const num = numMatch[2];
896
+ const content = colorize(numMatch[3]);
897
+ const prefix = INDENT + extraIndent + pc12.cyan(num + ".") + " ";
898
+ process.stdout.write(prefix + content + "\n");
899
+ return;
900
+ }
901
+ const trimmed = line.replace(/\n$/, "");
902
+ const wrapped = wordWrap(trimmed, width);
903
+ process.stdout.write(colorize(wrapped) + "\n");
904
+ }
905
+ getBuffer() {
906
+ return this.buffer;
907
+ }
908
+ finishAndRender() {
909
+ if (this.lineBuffer) {
910
+ const remaining = this.lineBuffer;
911
+ this.lineBuffer = "";
912
+ if (this.inCodeBlock) {
913
+ process.stdout.write(pc12.cyan(remaining));
914
+ } else {
915
+ process.stdout.write(colorize(remaining));
916
+ }
917
+ }
918
+ }
919
+ };
920
+ HINT_MAP = {
921
+ welcome: [
922
+ ["build an app", "create & deploy a project"],
923
+ ["create an agent", "spin up an AI agent"],
924
+ ["run some code", "execute in a sandbox"],
925
+ ["show my projects", "list everything you've built"]
926
+ ],
927
+ project_created: [
928
+ ["deploy it", "push to production"],
929
+ ["add an agent", "attach AI to this project"],
930
+ ["start a sandbox", "spin up a dev environment"],
931
+ ["show my projects", "see all projects"]
932
+ ],
933
+ deployed: [
934
+ ["check deploy status", "view deployment state"],
935
+ ["view logs", "see deployment logs"],
936
+ ["create another project", "start something new"]
937
+ ],
938
+ agent_created: [
939
+ ["chat with it", "talk to your agent"],
940
+ ["schedule a task", "set up recurring work"],
941
+ ["create a swarm", "coordinate multiple agents"]
942
+ ],
943
+ sandbox_started: [
944
+ ["run some code", "execute in the sandbox"],
945
+ ["deploy it", "push to production"],
946
+ ["stop sandbox", "tear down environment"]
947
+ ],
948
+ code_executed: [
949
+ ["run more code", "iterate in the sandbox"],
950
+ ["deploy it", "push to production"],
951
+ ["show my projects", "see all projects"]
952
+ ],
953
+ listed_projects: [
954
+ ["deploy <name>", "deploy a specific project"],
955
+ ["build something new", "create a new project"],
956
+ ["create an agent", "add AI to a project"]
957
+ ],
958
+ default: [
959
+ ["build an app", "create & deploy a project"],
960
+ ["show my projects", "see what you've built"],
961
+ ["create an agent", "spin up an AI agent"]
962
+ ]
963
+ };
964
+ }
965
+ });
966
+
967
+ // src/chat/session.ts
968
+ import { readFile as readFile6, writeFile as writeFile7, mkdir as mkdir4 } from "fs/promises";
969
+ import { join as join6 } from "path";
970
+ import { homedir as homedir3 } from "os";
971
+ import { randomUUID } from "crypto";
972
+ function createSession() {
973
+ return {
974
+ id: randomUUID(),
975
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
976
+ messages: []
977
+ };
978
+ }
979
+ async function saveSession(session) {
980
+ await mkdir4(SESSIONS_DIR, { recursive: true });
981
+ const path = join6(SESSIONS_DIR, `${session.id}.json`);
982
+ const trimmed = {
983
+ ...session,
984
+ messages: session.messages.slice(-MAX_HISTORY_MESSAGES)
985
+ };
986
+ await writeFile7(path, JSON.stringify(trimmed, null, 2), "utf-8");
987
+ }
988
+ async function loadLatestSession() {
989
+ try {
990
+ const { readdir, stat } = await import("fs/promises");
991
+ const files = await readdir(SESSIONS_DIR);
992
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
993
+ if (jsonFiles.length === 0) return null;
994
+ let latest = "";
995
+ let latestTime = 0;
996
+ for (const file of jsonFiles) {
997
+ const info = await stat(join6(SESSIONS_DIR, file));
998
+ if (info.mtimeMs > latestTime) {
999
+ latestTime = info.mtimeMs;
1000
+ latest = file;
1001
+ }
1002
+ }
1003
+ if (!latest) return null;
1004
+ const raw = await readFile6(join6(SESSIONS_DIR, latest), "utf-8");
1005
+ return JSON.parse(raw);
1006
+ } catch {
1007
+ return null;
1008
+ }
1009
+ }
1010
+ async function loadSession(id) {
1011
+ try {
1012
+ const raw = await readFile6(join6(SESSIONS_DIR, `${id}.json`), "utf-8");
1013
+ return JSON.parse(raw);
1014
+ } catch {
1015
+ return null;
1016
+ }
1017
+ }
1018
+ async function listSessions(limit = 10) {
1019
+ try {
1020
+ const { readdir, stat } = await import("fs/promises");
1021
+ const files = await readdir(SESSIONS_DIR);
1022
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
1023
+ const entries = [];
1024
+ for (const file of jsonFiles) {
1025
+ try {
1026
+ const filePath = join6(SESSIONS_DIR, file);
1027
+ const info = await stat(filePath);
1028
+ const raw = await readFile6(filePath, "utf-8");
1029
+ const session = JSON.parse(raw);
1030
+ entries.push({
1031
+ id: session.id,
1032
+ createdAt: session.createdAt,
1033
+ messageCount: session.messages.length,
1034
+ updatedAt: info.mtime
1035
+ });
1036
+ } catch {
1037
+ }
1038
+ }
1039
+ entries.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
1040
+ return entries.slice(0, limit);
1041
+ } catch {
1042
+ return [];
1043
+ }
1044
+ }
1045
+ function addUserMessage(session, content) {
1046
+ session.messages.push({ role: "user", content });
1047
+ }
1048
+ function addAssistantMessage(session, content) {
1049
+ session.messages.push({ role: "assistant", content });
1050
+ }
1051
+ function addToolResult(session, toolUseId, result, isError = false) {
1052
+ session.messages.push({
1053
+ role: "user",
1054
+ content: [
1055
+ {
1056
+ type: "tool_result",
1057
+ tool_use_id: toolUseId,
1058
+ content: result,
1059
+ is_error: isError
1060
+ }
1061
+ ]
1062
+ });
1063
+ }
1064
+ var SESSIONS_DIR, MAX_HISTORY_MESSAGES;
1065
+ var init_session = __esm({
1066
+ "src/chat/session.ts"() {
1067
+ "use strict";
1068
+ SESSIONS_DIR = join6(homedir3(), ".recursiv", "sessions");
1069
+ MAX_HISTORY_MESSAGES = 100;
1070
+ }
1071
+ });
1072
+
1073
+ // src/chat/system-prompt.ts
1074
+ var system_prompt_exports = {};
1075
+ __export(system_prompt_exports, {
1076
+ buildSystemPrompt: () => buildSystemPrompt
1077
+ });
1078
+ function buildSystemPrompt(context) {
1079
+ return `You are Recursiv \u2014 a CLI agent that builds, deploys, and runs software on the Recursiv platform.
1080
+
1081
+ ## Current user
1082
+ ${context.username ? `- Username: @${context.username}` : "- Not yet identified (call get_my_profile)"}
1083
+ ${context.email ? `- Email: ${context.email}` : ""}
1084
+ - API: ${context.apiBaseUrl}
1085
+
1086
+ ## Your capabilities
1087
+
1088
+ You have the full Recursiv toolchain. Use it:
1089
+
1090
+ - **Projects**: create_project, list_projects, get_project, deploy_project
1091
+ - **Deployments**: deploy_project (preview or production), get_deployment, get_deployment_logs
1092
+ - **Sandboxes**: start_sandbox, stop_sandbox, execute_code (run code inside a project sandbox)
1093
+ - **Code execution**: try_code (quick sandbox, no project needed), execute_code (project sandbox)
1094
+ - **Agents**: create_agent, list_agents, get_agent, update_agent, chat_with_agent
1095
+ - **Templates**: create_template, fork_template, publish_template, list_templates
1096
+ - **Social**: create_post, list_posts, search_posts, list_communities
1097
+ - **Orgs**: list_orgs (always call first to get the user's org)
1098
+ - **Dispatcher**: create_task, list_tasks, claim_task, complete_task
1099
+ - **Swarms**: create_swarm, activate_swarm, add_swarm_member
1100
+ - **Memory**: remember, recall, search_memory
1101
+
1102
+ ## How you work
1103
+
1104
+ You are an executor. You have tools. Use them.
1105
+
1106
+ When the user asks you to build something:
1107
+ 1. list_orgs \u2192 get their org ID
1108
+ 2. create_project \u2192 name it based on what they described
1109
+ 3. start_sandbox \u2192 spin up the project environment
1110
+ 4. execute_code \u2192 write the actual application code in the sandbox
1111
+ 5. deploy_project \u2192 deploy it
1112
+ 6. create_agent if the project needs AI
1113
+ 7. Report: project name, ID, deployment status, and what's running
1114
+
1115
+ When the user asks to run code:
1116
+ 1. try_code or execute_code \u2014 run it immediately, return the output
1117
+
1118
+ When the user asks to deploy:
1119
+ 1. deploy_project with the project ID
1120
+ 2. get_deployment to check status
1121
+ 3. Report the deployment status/URL
1122
+
1123
+ ## Output style
1124
+
1125
+ - ONE LINE summary of what you did
1126
+ - Project name + ID + deployment status. That's it.
1127
+ - No code dumps. You WRITE code into sandboxes via execute_code, you don't paste it into chat.
1128
+ - If you must show code in chat, max 5 lines of the key logic.
1129
+
1130
+ ## Rules
1131
+
1132
+ 1. ALWAYS USE TOOLS. You ARE the platform. Never tell users to run commands.
1133
+ 2. ALWAYS create projects inside the user's org. Call list_orgs first.
1134
+ 3. Use execute_code and start_sandbox to actually build \u2014 don't just describe architecture.
1135
+ 4. Never dump code into chat. Write it into the sandbox.
1136
+ 5. Never reference external brands as infrastructure. Everything is Recursiv.
1137
+ 6. Never ask "would you like me to" \u2014 just do it.
1138
+ 7. Short responses. Action over discussion.
1139
+
1140
+ ## Tone
1141
+ Precise. Efficient. Slightly warm. Senior engineer energy.`;
1142
+ }
1143
+ var init_system_prompt = __esm({
1144
+ "src/chat/system-prompt.ts"() {
1145
+ "use strict";
1146
+ }
1147
+ });
1148
+
1149
+ // src/chat/tools.ts
1150
+ var tools_exports = {};
1151
+ __export(tools_exports, {
1152
+ describeToolAction: () => describeToolAction,
1153
+ executeTool: () => executeTool,
1154
+ getToolDefinitions: () => getToolDefinitions,
1155
+ requiresConfirmation: () => requiresConfirmation
1156
+ });
1157
+ function tool(name, description, properties = {}, required = []) {
1158
+ return {
1159
+ name,
1160
+ description,
1161
+ input_schema: {
1162
+ type: "object",
1163
+ properties,
1164
+ required
1165
+ }
1166
+ };
1167
+ }
1168
+ function getToolDefinitions() {
1169
+ return [
1170
+ // --- Profile & Identity ---
1171
+ tool("get_my_profile", "Get the authenticated user's profile"),
1172
+ tool("get_profile", "Get a user profile by ID", {
1173
+ user_id: { type: "string", description: "User ID" }
1174
+ }, ["user_id"]),
1175
+ tool("get_profile_by_username", "Get a user profile by username", {
1176
+ username: { type: "string", description: "Username (without @)" }
1177
+ }, ["username"]),
1178
+ tool("search_profiles", "Search for users", {
1179
+ query: { type: "string", description: "Search query" },
1180
+ limit: { type: "number", description: "Max results (default 20)" }
1181
+ }, ["query"]),
1182
+ tool("update_profile", "Update the current user's profile", {
1183
+ name: { type: "string" },
1184
+ bio: { type: "string" },
1185
+ location: { type: "string" },
1186
+ website: { type: "string" }
1187
+ }),
1188
+ tool("follow_user", "Follow a user", {
1189
+ user_id: { type: "string", description: "User ID to follow" }
1190
+ }, ["user_id"]),
1191
+ tool("unfollow_user", "Unfollow a user", {
1192
+ user_id: { type: "string", description: "User ID to unfollow" }
1193
+ }, ["user_id"]),
1194
+ tool("get_followers", "Get followers of a user", {
1195
+ user_id: { type: "string" },
1196
+ limit: { type: "number" },
1197
+ offset: { type: "number" }
1198
+ }, ["user_id"]),
1199
+ tool("get_following", "Get users that a user follows", {
1200
+ user_id: { type: "string" },
1201
+ limit: { type: "number" },
1202
+ offset: { type: "number" }
1203
+ }, ["user_id"]),
1204
+ // --- Posts ---
1205
+ tool("list_posts", "List posts from the feed", {
1206
+ community_id: { type: "string", description: "Filter by community" },
1207
+ author_id: { type: "string", description: "Filter by author" },
1208
+ limit: { type: "number" },
1209
+ offset: { type: "number" }
1210
+ }),
1211
+ tool("get_post", "Get a specific post", {
1212
+ post_id: { type: "string" }
1213
+ }, ["post_id"]),
1214
+ tool("create_post", "Create a new post", {
1215
+ content: { type: "string", description: "Post content" },
1216
+ content_format: { type: "string", enum: ["plain", "markdown"], description: "Content format (default: plain)" },
1217
+ community_id: { type: "string", description: "Post to a specific community" },
1218
+ organization_id: { type: "string" },
1219
+ reply_to_id: { type: "string", description: "Reply to a post" }
1220
+ }, ["content"]),
1221
+ tool("update_post", "Update a post", {
1222
+ post_id: { type: "string" },
1223
+ content: { type: "string" }
1224
+ }, ["post_id", "content"]),
1225
+ tool("delete_post", "Delete a post", {
1226
+ post_id: { type: "string" }
1227
+ }, ["post_id"]),
1228
+ tool("search_posts", "Search posts", {
1229
+ query: { type: "string" },
1230
+ limit: { type: "number" }
1231
+ }, ["query"]),
1232
+ tool("react_to_post", "React to a post with one of the allowed reaction types", {
1233
+ post_id: { type: "string" },
1234
+ reaction: { type: "string", enum: ["like", "heart", "fire", "laugh", "sad", "angry"], description: "Reaction type" }
1235
+ }, ["post_id", "reaction"]),
1236
+ // --- Communities ---
1237
+ tool("list_communities", "List communities", {
1238
+ limit: { type: "number" },
1239
+ offset: { type: "number" }
1240
+ }),
1241
+ tool("get_community", "Get a community by ID", {
1242
+ community_id: { type: "string" }
1243
+ }, ["community_id"]),
1244
+ tool("create_community", "Create a new community", {
1245
+ name: { type: "string" },
1246
+ slug: { type: "string" },
1247
+ description: { type: "string" },
1248
+ privacy: { type: "string", enum: ["public", "private", "hidden"] }
1249
+ }, ["name", "slug"]),
1250
+ tool("join_community", "Join a community", {
1251
+ community_id: { type: "string" }
1252
+ }, ["community_id"]),
1253
+ tool("leave_community", "Leave a community", {
1254
+ community_id: { type: "string" }
1255
+ }, ["community_id"]),
1256
+ // --- Chat ---
1257
+ tool("list_conversations", "List chat conversations", {
1258
+ limit: { type: "number" },
1259
+ offset: { type: "number" }
1260
+ }),
1261
+ tool("list_messages", "List messages in a conversation", {
1262
+ conversation_id: { type: "string" },
1263
+ limit: { type: "number" },
1264
+ before: { type: "string", description: "Cursor for pagination" }
1265
+ }, ["conversation_id"]),
1266
+ tool("send_message", "Send a chat message", {
1267
+ conversation_id: { type: "string" },
1268
+ content: { type: "string" }
1269
+ }, ["conversation_id", "content"]),
1270
+ tool("get_or_create_dm", "Get or create a DM conversation with a user", {
1271
+ user_id: { type: "string" }
1272
+ }, ["user_id"]),
1273
+ tool("create_group_chat", "Create a group chat", {
1274
+ name: { type: "string", description: "Group name" },
1275
+ member_ids: { type: "array", items: { type: "string" }, description: "User IDs to add" }
1276
+ }, ["name", "member_ids"]),
1277
+ tool("get_unread_count", "Get unread message count for a conversation", {
1278
+ conversation_id: { type: "string", description: "Conversation ID" }
1279
+ }, ["conversation_id"]),
1280
+ // --- Organizations ---
1281
+ tool("list_orgs", "List organizations the user belongs to", {
1282
+ limit: { type: "number" },
1283
+ offset: { type: "number" }
1284
+ }),
1285
+ tool("get_org", "Get organization details", {
1286
+ org_id: { type: "string" }
1287
+ }, ["org_id"]),
1288
+ tool("create_org", "Create a new organization", {
1289
+ name: { type: "string" },
1290
+ slug: { type: "string" },
1291
+ description: { type: "string" }
1292
+ }, ["name", "slug"]),
1293
+ tool("list_org_members", "List members of an organization", {
1294
+ org_id: { type: "string" },
1295
+ limit: { type: "number" },
1296
+ offset: { type: "number" }
1297
+ }, ["org_id"]),
1298
+ tool("invite_org_member", "Invite a user to an organization", {
1299
+ org_id: { type: "string" },
1300
+ email: { type: "string" },
1301
+ role: { type: "string", enum: ["member", "admin", "owner"] }
1302
+ }, ["org_id", "email"]),
1303
+ // --- Projects ---
1304
+ tool("list_projects", "List projects", {
1305
+ organization_id: { type: "string" },
1306
+ limit: { type: "number" },
1307
+ offset: { type: "number" }
1308
+ }),
1309
+ tool("get_project", "Get a project by ID", {
1310
+ project_id: { type: "string" }
1311
+ }, ["project_id"]),
1312
+ tool("create_project", "Create a new project", {
1313
+ organization_id: { type: "string" },
1314
+ name: { type: "string" },
1315
+ slug: { type: "string" },
1316
+ repo_url: { type: "string" }
1317
+ }, ["organization_id", "name"]),
1318
+ // --- Agents ---
1319
+ tool("list_agents", "List AI agents", {
1320
+ limit: { type: "number" },
1321
+ offset: { type: "number" }
1322
+ }),
1323
+ tool("get_agent", "Get an agent by ID", {
1324
+ agent_id: { type: "string" }
1325
+ }, ["agent_id"]),
1326
+ tool("create_agent", "Create an AI agent", {
1327
+ name: { type: "string" },
1328
+ username: { type: "string" },
1329
+ bio: { type: "string" },
1330
+ model: { type: "string" },
1331
+ system_prompt: { type: "string" },
1332
+ social_mode: { type: "string", enum: ["chat_only", "chat_post"] },
1333
+ post_frequency: { type: "string", enum: ["never", "light", "medium", "heavy"] },
1334
+ tool_mode: { type: "string", enum: ["chat_only", "permission", "autonomous"] },
1335
+ organization_id: { type: "string" }
1336
+ }, ["name", "username"]),
1337
+ tool("update_agent", "Update an agent", {
1338
+ agent_id: { type: "string" },
1339
+ name: { type: "string" },
1340
+ bio: { type: "string" },
1341
+ system_prompt: { type: "string" },
1342
+ social_mode: { type: "string" },
1343
+ post_frequency: { type: "string" },
1344
+ tool_mode: { type: "string" }
1345
+ }, ["agent_id"]),
1346
+ tool("delete_agent", "Delete an agent", {
1347
+ agent_id: { type: "string" }
1348
+ }, ["agent_id"]),
1349
+ tool("chat_with_agent", "Send a message to an agent and get a response", {
1350
+ agent_id: { type: "string" },
1351
+ message: { type: "string" },
1352
+ conversation_id: { type: "string", description: "Optional \u2014 continue existing conversation" }
1353
+ }, ["agent_id", "message"]),
1354
+ // --- Dispatcher ---
1355
+ tool("list_tasks", "List dispatcher tasks", {
1356
+ status: { type: "string", enum: ["pending", "in_progress", "claimed", "done"] },
1357
+ owner: { type: "string" },
1358
+ limit: { type: "number" }
1359
+ }),
1360
+ tool("create_task", "Create a dispatcher task", {
1361
+ title: { type: "string" },
1362
+ description: { type: "string" },
1363
+ layer: { type: "string", enum: ["core", "plugin", "ops"] },
1364
+ effort: { type: "number", description: "1-10 effort score" },
1365
+ priority: { type: "number", description: "1-10 priority" }
1366
+ }, ["title"]),
1367
+ tool("claim_task", "Claim a task", {
1368
+ task_id: { type: "string" },
1369
+ agent: { type: "string", description: "Agent/person identifier" }
1370
+ }, ["task_id", "agent"]),
1371
+ tool("complete_task", "Mark a task as done", {
1372
+ task_id: { type: "string" },
1373
+ notes: { type: "string" }
1374
+ }, ["task_id"]),
1375
+ // --- Storage ---
1376
+ tool("list_storage_buckets", "List storage buckets for a project", {
1377
+ project_id: { type: "string" }
1378
+ }, ["project_id"]),
1379
+ tool("list_storage_items", "List items in a storage bucket", {
1380
+ project_id: { type: "string" },
1381
+ bucket_name: { type: "string" },
1382
+ prefix: { type: "string" }
1383
+ }, ["project_id", "bucket_name"]),
1384
+ // --- Databases ---
1385
+ tool("list_databases", "List databases for a project", {
1386
+ project_id: { type: "string" }
1387
+ }, ["project_id"]),
1388
+ tool("run_sql_query", "Execute a SQL query against a project database", {
1389
+ project_id: { type: "string" },
1390
+ database_id: { type: "string" },
1391
+ query: { type: "string", description: "SQL query to execute" }
1392
+ }, ["project_id", "database_id", "query"]),
1393
+ // --- Deployments ---
1394
+ tool("list_deployments", "List deployments for a project", {
1395
+ project_id: { type: "string" }
1396
+ }, ["project_id"]),
1397
+ tool("trigger_deployment", "Trigger a new deployment", {
1398
+ project_id: { type: "string" },
1399
+ environment: { type: "string", description: "Target environment" }
1400
+ }, ["project_id"]),
1401
+ // --- Memory ---
1402
+ tool("remember", "Store a fact in agent memory", {
1403
+ content: { type: "string", description: "What to remember" },
1404
+ category: { type: "string", description: "Category tag" }
1405
+ }, ["content"]),
1406
+ tool("recall", "Recall memories matching a query", {
1407
+ query: { type: "string" },
1408
+ limit: { type: "number" }
1409
+ }, ["query"]),
1410
+ tool("search_memory", "Search memory with filters", {
1411
+ query: { type: "string" },
1412
+ category: { type: "string" },
1413
+ limit: { type: "number" }
1414
+ }, ["query"]),
1415
+ // --- Sandbox ---
1416
+ tool("execute_code", "Execute code in a sandboxed environment", {
1417
+ code: { type: "string", description: "Code to execute" },
1418
+ language: { type: "string", enum: ["python", "javascript", "typescript"], description: "Language (default: python)" }
1419
+ }, ["code"]),
1420
+ // --- Wallet ---
1421
+ tool("get_my_wallet", "Get the user's wallet info"),
1422
+ tool("get_wallet_balance", "Get wallet balance"),
1423
+ // --- Network & Context ---
1424
+ tool("get_network_config", "Get the current network configuration"),
1425
+ tool("list_inbox", "List inbox notifications", {
1426
+ limit: { type: "number" },
1427
+ offset: { type: "number" }
1428
+ }),
1429
+ // --- Swarms ---
1430
+ tool("list_swarms", "List agent swarms", {
1431
+ limit: { type: "number" }
1432
+ }),
1433
+ tool("create_swarm", "Create a new agent swarm", {
1434
+ name: { type: "string" },
1435
+ description: { type: "string" },
1436
+ member_agent_ids: { type: "array", items: { type: "string" } }
1437
+ }, ["name", "member_agent_ids"]),
1438
+ // --- Templates ---
1439
+ tool("list_templates", "List available templates", {
1440
+ limit: { type: "number" }
1441
+ }),
1442
+ // --- Billing ---
1443
+ tool("get_billing_status", "Get billing status for the current user"),
1444
+ tool("get_tier", "Get the user's current tier and limits"),
1445
+ tool("get_tier_usage", "Get current tier usage statistics"),
1446
+ // --- Admin (requires network admin role) ---
1447
+ tool("admin_get_stats", "Get network-wide statistics. Requires: network admin."),
1448
+ tool("admin_list_users", "List all users. Requires: network admin.", {
1449
+ limit: { type: "number" },
1450
+ offset: { type: "number" }
1451
+ })
1452
+ ];
1453
+ }
1454
+ function requiresConfirmation(name) {
1455
+ return DESTRUCTIVE_TOOLS.has(name);
1456
+ }
1457
+ function describeToolAction(name, input) {
1458
+ switch (name) {
1459
+ case "delete_post":
1460
+ return `Delete post ${input.post_id}`;
1461
+ case "delete_agent":
1462
+ return `Delete agent ${input.agent_id}`;
1463
+ case "create_post": {
1464
+ const content = input.content || "";
1465
+ const preview = content.length > 60 ? content.slice(0, 57) + "..." : content;
1466
+ return `Create post: "${preview}"`;
1467
+ }
1468
+ case "create_agent":
1469
+ return `Create agent @${input.username}`;
1470
+ case "create_org":
1471
+ return `Create organization "${input.name}"`;
1472
+ case "create_community":
1473
+ return `Create community "${input.name}"`;
1474
+ case "update_profile":
1475
+ return "Update your profile";
1476
+ case "send_message": {
1477
+ const content = input.content || "";
1478
+ const preview = content.length > 60 ? content.slice(0, 57) + "..." : content;
1479
+ return `Send message: "${preview}"`;
1480
+ }
1481
+ case "invite_org_member":
1482
+ return `Invite ${input.email} to organization`;
1483
+ case "trigger_deployment":
1484
+ return `Deploy project ${input.project_id}`;
1485
+ case "run_sql_query": {
1486
+ const sql = input.query || "";
1487
+ const preview = sql.length > 60 ? sql.slice(0, 57) + "..." : sql;
1488
+ return `Run SQL: ${preview}`;
1489
+ }
1490
+ case "execute_code":
1491
+ return `Execute ${input.language || "python"} code in sandbox`;
1492
+ case "create_swarm":
1493
+ return `Create swarm "${input.name}"`;
1494
+ case "create_task":
1495
+ return `Create task "${input.title}"`;
1496
+ case "complete_task":
1497
+ return `Complete task ${input.task_id}`;
1498
+ case "claim_task":
1499
+ return `Claim task ${input.task_id}`;
1500
+ case "follow_user":
1501
+ return `Follow user ${input.user_id}`;
1502
+ case "unfollow_user":
1503
+ return `Unfollow user ${input.user_id}`;
1504
+ case "join_community":
1505
+ return `Join community ${input.community_id}`;
1506
+ case "leave_community":
1507
+ return `Leave community ${input.community_id}`;
1508
+ case "react_to_post":
1509
+ return `React ${input.reaction} to post ${input.post_id}`;
1510
+ default:
1511
+ return `${name}(${Object.keys(input).join(", ")})`;
1512
+ }
1513
+ }
1514
+ async function executeTool(client, name, input) {
1515
+ switch (name) {
1516
+ // --- Profile ---
1517
+ case "get_my_profile":
1518
+ return client.profiles.me();
1519
+ case "get_profile":
1520
+ return client.profiles.get(input.user_id);
1521
+ case "get_profile_by_username":
1522
+ return client.profiles.getByUsername(input.username);
1523
+ case "search_profiles":
1524
+ return client.profiles.search({
1525
+ q: input.query,
1526
+ limit: input.limit
1527
+ });
1528
+ case "update_profile":
1529
+ return client.profiles.update(input);
1530
+ case "follow_user":
1531
+ return client.profiles.follow(input.user_id);
1532
+ case "unfollow_user":
1533
+ return client.profiles.unfollow(input.user_id);
1534
+ case "get_followers":
1535
+ return client.profiles.followers(input.user_id, {
1536
+ limit: input.limit,
1537
+ offset: input.offset
1538
+ });
1539
+ case "get_following":
1540
+ return client.profiles.following(input.user_id, {
1541
+ limit: input.limit,
1542
+ offset: input.offset
1543
+ });
1544
+ // --- Posts ---
1545
+ case "list_posts":
1546
+ return client.posts.list(input);
1547
+ case "get_post":
1548
+ return client.posts.get(input.post_id);
1549
+ case "create_post":
1550
+ return client.posts.create(input);
1551
+ case "update_post":
1552
+ return client.posts.update(input.post_id, { content: input.content });
1553
+ case "delete_post":
1554
+ return client.posts.delete(input.post_id);
1555
+ case "search_posts":
1556
+ return client.posts.search(input);
1557
+ case "react_to_post":
1558
+ return client.posts.react(
1559
+ input.post_id,
1560
+ input.reaction
1561
+ );
1562
+ // --- Communities ---
1563
+ case "list_communities":
1564
+ return client.communities.list(input);
1565
+ case "get_community":
1566
+ return client.communities.get(input.community_id);
1567
+ case "create_community":
1568
+ return client.communities.create(input);
1569
+ case "join_community":
1570
+ return client.communities.join(input.community_id);
1571
+ case "leave_community":
1572
+ return client.communities.leave(input.community_id);
1573
+ // --- Chat ---
1574
+ case "list_conversations":
1575
+ return client.chat.conversations(input);
1576
+ case "list_messages":
1577
+ return client.chat.messages(input.conversation_id, {
1578
+ limit: input.limit
1579
+ });
1580
+ case "send_message":
1581
+ return client.chat.send({
1582
+ conversation_id: input.conversation_id,
1583
+ content: input.content
1584
+ });
1585
+ case "get_or_create_dm":
1586
+ return client.chat.dm({ user_id: input.user_id });
1587
+ case "create_group_chat":
1588
+ return client.chat.createGroup({
1589
+ name: input.name,
1590
+ member_ids: input.member_ids
1591
+ });
1592
+ case "get_unread_count":
1593
+ return client.chat.unreadCount(input.conversation_id);
1594
+ // --- Organizations ---
1595
+ case "list_orgs":
1596
+ return client.organizations.list(input);
1597
+ case "get_org":
1598
+ return client.organizations.get(input.org_id);
1599
+ case "create_org":
1600
+ return client.organizations.create(input);
1601
+ case "list_org_members":
1602
+ return client.organizations.members(input.org_id, {
1603
+ limit: input.limit,
1604
+ offset: input.offset
1605
+ });
1606
+ case "invite_org_member":
1607
+ return client.organizations.invite(input.org_id, {
1608
+ email: input.email,
1609
+ role: input.role
1610
+ });
1611
+ // --- Projects ---
1612
+ case "list_projects":
1613
+ return client.projects.list(input);
1614
+ case "get_project":
1615
+ return client.projects.get(input.project_id);
1616
+ case "create_project":
1617
+ return client.projects.create(input);
1618
+ // --- Agents ---
1619
+ case "list_agents":
1620
+ return client.agents.list(input);
1621
+ case "get_agent":
1622
+ return client.agents.get(input.agent_id);
1623
+ case "create_agent":
1624
+ return client.agents.create(input);
1625
+ case "update_agent":
1626
+ return client.agents.update(input.agent_id, input);
1627
+ case "delete_agent":
1628
+ return client.agents.delete(input.agent_id);
1629
+ case "chat_with_agent":
1630
+ return client.agents.chat(input.agent_id, {
1631
+ message: input.message,
1632
+ conversation_id: input.conversation_id
1633
+ });
1634
+ // --- Dispatcher ---
1635
+ case "list_tasks":
1636
+ return client.dispatcher.tasks(input);
1637
+ case "create_task":
1638
+ return client.dispatcher.create(input);
1639
+ case "claim_task":
1640
+ return client.dispatcher.claim(input.task_id, {
1641
+ agent: input.agent
1642
+ });
1643
+ case "complete_task":
1644
+ return client.dispatcher.complete(input.task_id, {
1645
+ notes: input.notes
1646
+ });
1647
+ // --- Storage ---
1648
+ case "list_storage_buckets":
1649
+ return client.storage.listBuckets({ project_id: input.project_id });
1650
+ case "list_storage_items":
1651
+ return client.storage.listItems({
1652
+ project_id: input.project_id,
1653
+ bucket_name: input.bucket_name,
1654
+ prefix: input.prefix
1655
+ });
1656
+ // --- Databases ---
1657
+ case "list_databases":
1658
+ return client.databases.list({ project_id: input.project_id });
1659
+ case "run_sql_query":
1660
+ return client.databases.query({
1661
+ project_id: input.project_id,
1662
+ sql: input.query,
1663
+ database_name: input.database_id
1664
+ });
1665
+ // --- Deployments ---
1666
+ case "list_deployments":
1667
+ return client.deployments.list({ project_id: input.project_id });
1668
+ case "trigger_deployment":
1669
+ return client.deployments.deploy({
1670
+ project_id: input.project_id,
1671
+ branch: input.environment
1672
+ });
1673
+ // --- Memory ---
1674
+ case "remember":
1675
+ return client.memory.facts.add({
1676
+ fact: input.content
1677
+ });
1678
+ case "recall":
1679
+ return client.memory.search({
1680
+ query: input.query,
1681
+ limit: input.limit
1682
+ });
1683
+ case "search_memory":
1684
+ return client.memory.search({
1685
+ query: input.query,
1686
+ limit: input.limit
1687
+ });
1688
+ // --- Sandbox ---
1689
+ case "execute_code":
1690
+ return client.sandbox.execute({
1691
+ code: input.code,
1692
+ language: input.language || "python"
1693
+ });
1694
+ // --- Wallet ---
1695
+ case "get_my_wallet":
1696
+ return client.wallet.getMyWallet();
1697
+ case "get_wallet_balance":
1698
+ return client.wallet.getBalance();
1699
+ // --- Network & Context ---
1700
+ case "get_network_config":
1701
+ return client.network.getConfig();
1702
+ case "list_inbox":
1703
+ return client.inbox.list(input);
1704
+ // --- Swarms ---
1705
+ case "list_swarms":
1706
+ return client.swarms.list(input);
1707
+ case "create_swarm":
1708
+ return client.swarms.create(input);
1709
+ // --- Templates ---
1710
+ case "list_templates":
1711
+ return client.templates.list(input);
1712
+ // --- Billing ---
1713
+ case "get_billing_status":
1714
+ return client.billing.getStatus({});
1715
+ case "get_tier":
1716
+ return client.freeTier.getTier();
1717
+ case "get_tier_usage":
1718
+ return client.freeTier.getUsage();
1719
+ // --- Admin ---
1720
+ case "admin_get_stats":
1721
+ return client.admin.getStats();
1722
+ case "admin_list_users":
1723
+ return client.admin.listUsers(input);
1724
+ default:
1725
+ throw new Error(`Unknown tool: ${name}`);
1726
+ }
1727
+ }
1728
+ var DESTRUCTIVE_TOOLS;
1729
+ var init_tools = __esm({
1730
+ "src/chat/tools.ts"() {
1731
+ "use strict";
1732
+ DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
1733
+ "delete_post",
1734
+ "delete_agent",
1735
+ "create_post",
1736
+ "create_agent",
1737
+ "create_org",
1738
+ "create_community",
1739
+ "update_profile",
1740
+ "send_message",
1741
+ "invite_org_member",
1742
+ "trigger_deployment",
1743
+ "run_sql_query",
1744
+ "execute_code",
1745
+ "create_swarm",
1746
+ "create_task",
1747
+ "complete_task",
1748
+ "claim_task",
1749
+ "follow_user",
1750
+ "unfollow_user",
1751
+ "join_community",
1752
+ "leave_community",
1753
+ "react_to_post"
1754
+ ]);
1755
+ }
1756
+ });
1757
+
1758
+ // src/chat/agent.ts
1759
+ var agent_exports = {};
1760
+ __export(agent_exports, {
1761
+ startAgent: () => startAgent
1762
+ });
1763
+ import { createInterface } from "readline";
1764
+ import { existsSync as existsSync3, readFileSync } from "fs";
1765
+ import { basename as basename2, join as join7 } from "path";
1766
+ import { execSync as execSync2 } from "child_process";
1767
+ import { Recursiv } from "@recursiv/sdk";
1768
+ import pc13 from "picocolors";
1769
+ async function startAgent(config) {
1770
+ const client = new Recursiv({
1771
+ apiKey: config.recursivApiKey,
1772
+ baseUrl: config.recursivBaseUrl
1773
+ });
1774
+ let username;
1775
+ let email;
1776
+ try {
1777
+ const profile = await client.profiles.me();
1778
+ const data = profile.data;
1779
+ username = data?.username;
1780
+ email = data?.email;
1781
+ } catch {
1782
+ }
1783
+ let session;
1784
+ if (config.resume) {
1785
+ const prev = await loadLatestSession();
1786
+ if (prev) {
1787
+ session = prev;
1788
+ } else {
1789
+ session = createSession();
1790
+ }
1791
+ } else {
1792
+ session = createSession();
1793
+ }
1794
+ printBanner({
1795
+ username,
1796
+ email,
1797
+ resumed: config.resume && session.messages.length > 0 ? { id: session.id.slice(0, 8), messageCount: session.messages.length } : void 0
1798
+ });
1799
+ console.log();
1800
+ const rl = createInterface({
1801
+ input: process.stdin,
1802
+ output: process.stdout,
1803
+ terminal: true
1804
+ });
1805
+ rl.on("close", async () => {
1806
+ console.log();
1807
+ printInfo("Session saved. Goodbye.");
1808
+ await saveSession(session);
1809
+ process.exit(0);
1810
+ });
1811
+ if (config.anthropicApiKey) {
1812
+ await replDirect(rl, config, client, session);
1813
+ } else {
1814
+ await replProxy(rl, client, session);
1815
+ }
1816
+ }
1817
+ async function replProxy(rl, client, session) {
1818
+ const { readCredentials: readCredentials2, saveCredentials: saveCredentials2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
1819
+ const creds = await readCredentials2();
1820
+ let agentId = creds.cliAgentId;
1821
+ if (!agentId) {
1822
+ try {
1823
+ let orgId;
1824
+ try {
1825
+ const orgs = await client.organizations.list({ limit: 1 });
1826
+ const orgData = orgs.data;
1827
+ if (orgData?.[0]?.id) orgId = orgData[0].id;
1828
+ } catch {
1829
+ }
1830
+ const result = await client.agents.create({
1831
+ name: "CLI Assistant",
1832
+ username: `cli_${Date.now().toString(36)}`,
1833
+ bio: "Personal CLI assistant created by the Recursiv CLI.",
1834
+ system_prompt: CLI_AGENT_SYSTEM_PROMPT,
1835
+ social_mode: "chat_only",
1836
+ tool_mode: "autonomous",
1837
+ post_frequency: "never",
1838
+ ...orgId && { organization_id: orgId }
1839
+ });
1840
+ agentId = result.data.id;
1841
+ await saveCredentials2({ cliAgentId: agentId });
1842
+ printInfo(` Created CLI agent (${agentId.slice(0, 8)})`);
1843
+ } catch (err) {
1844
+ printError(`Could not create CLI agent: ${err instanceof Error ? err.message : String(err)}`);
1845
+ printInfo(" Falling back to direct mode. Set ANTHROPIC_API_KEY to use.");
1846
+ return;
1847
+ }
1848
+ }
1849
+ let conversationId;
1850
+ const isFirstRun = session.messages.length === 0;
1851
+ if (isFirstRun) {
1852
+ const ctx = detectProjectContext();
1853
+ printFirstRunGreeting(ctx);
1854
+ }
1855
+ while (true) {
1856
+ const input = await prompt(rl);
1857
+ if (input === null) break;
1858
+ const trimmed = input.trim();
1859
+ if (!trimmed) continue;
1860
+ if (trimmed.startsWith("/")) {
1861
+ const handled = await handleSlashCommand(trimmed, session, client);
1862
+ if (handled === "exit") break;
1863
+ if (handled) continue;
1864
+ }
1865
+ addUserMessage(session, trimmed);
1866
+ const messageToSend = planMode ? `[PLAN MODE] Do NOT execute anything yet. Instead, outline the steps you would take to accomplish this, what tools you'd call and in what order. Wait for my approval before executing.
1867
+
1868
+ ${trimmed}` : trimmed;
1869
+ try {
1870
+ if (planMode) {
1871
+ console.log(pc13.dim(" \u25C6 plan mode"));
1872
+ }
1873
+ printAssistantLabel();
1874
+ const thinking = startThinking();
1875
+ const writer = new StreamWriter();
1876
+ let hasText = false;
1877
+ let thinkingStopped = false;
1878
+ for await (const chunk of client.agents.chatStream(agentId, {
1879
+ message: messageToSend,
1880
+ conversation_id: conversationId
1881
+ })) {
1882
+ if (chunk.type === "text_delta" && chunk.delta) {
1883
+ if (!thinkingStopped) {
1884
+ thinking.stop();
1885
+ thinkingStopped = true;
1886
+ }
1887
+ hasText = true;
1888
+ writer.write(chunk.delta);
1889
+ } else if (chunk.type === "tool_use" && chunk.tool_name) {
1890
+ if (!thinkingStopped) {
1891
+ thinking.stop();
1892
+ thinkingStopped = true;
1893
+ }
1894
+ printToolCall(chunk.tool_name);
1895
+ } else if (chunk.type === "tool_result") {
1896
+ printToolResult(chunk.tool_name || "tool", true);
1897
+ } else if (chunk.type === "error") {
1898
+ if (!thinkingStopped) {
1899
+ thinking.stop();
1900
+ thinkingStopped = true;
1901
+ }
1902
+ printError(chunk.error || "Unknown error");
1903
+ }
1904
+ if (chunk.conversation_id) {
1905
+ conversationId = chunk.conversation_id;
1906
+ }
1907
+ }
1908
+ if (!thinkingStopped) thinking.stop();
1909
+ if (hasText) {
1910
+ writer.finishAndRender();
1911
+ }
1912
+ console.log();
1913
+ const fullText = writer.getBuffer() || "";
1914
+ if (fullText) {
1915
+ addAssistantMessage(session, [{ type: "text", text: fullText }]);
1916
+ const hintCtx = detectHintContext(fullText);
1917
+ printTips(hintCtx);
1918
+ }
1919
+ } catch (err) {
1920
+ const message = err instanceof Error ? err.message : String(err);
1921
+ if (message.includes("rate_limit") || message.includes("429")) {
1922
+ printError("Rate limited \u2014 wait a moment and try again.");
1923
+ } else if (message.includes("401") || message.includes("authentication")) {
1924
+ printError("API key is invalid. Run `recursiv auth login` to re-authenticate.");
1925
+ } else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED")) {
1926
+ printError("Network error \u2014 check your internet connection.");
1927
+ } else {
1928
+ printError(message);
1929
+ }
1930
+ console.log();
1931
+ }
1932
+ await saveSession(session);
1933
+ }
1934
+ }
1935
+ async function replDirect(rl, config, client, session) {
1936
+ const Anthropic = (await import("@anthropic-ai/sdk")).default;
1937
+ const { buildSystemPrompt: buildSystemPrompt2 } = await Promise.resolve().then(() => (init_system_prompt(), system_prompt_exports));
1938
+ const { getToolDefinitions: getToolDefinitions2, executeTool: executeTool2, requiresConfirmation: requiresConfirmation2, describeToolAction: describeToolAction2 } = await Promise.resolve().then(() => (init_tools(), tools_exports));
1939
+ const anthropic = new Anthropic({ apiKey: config.anthropicApiKey });
1940
+ const tools = getToolDefinitions2();
1941
+ const model = config.model || DEFAULT_MODEL;
1942
+ let username;
1943
+ let email;
1944
+ try {
1945
+ const profile = await client.profiles.me();
1946
+ const data = profile.data;
1947
+ username = data?.username;
1948
+ email = data?.email;
1949
+ } catch {
1950
+ }
1951
+ const systemPrompt = buildSystemPrompt2({
1952
+ username,
1953
+ email,
1954
+ apiBaseUrl: config.recursivBaseUrl
1955
+ });
1956
+ while (true) {
1957
+ const input = await prompt(rl);
1958
+ if (input === null) break;
1959
+ const trimmed = input.trim();
1960
+ if (!trimmed) continue;
1961
+ if (trimmed.startsWith("/")) {
1962
+ const handled = await handleSlashCommand(trimmed, session, client);
1963
+ if (handled === "exit") break;
1964
+ if (handled) continue;
1965
+ }
1966
+ addUserMessage(session, trimmed);
1967
+ await runDirectTurn(anthropic, client, tools, model, systemPrompt, session);
1968
+ await saveSession(session);
1969
+ }
1970
+ }
1971
+ async function runDirectTurn(anthropic, client, tools, model, systemPrompt, session) {
1972
+ const { requiresConfirmation: requiresConfirmation2, describeToolAction: describeToolAction2, executeTool: executeTool2 } = await Promise.resolve().then(() => (init_tools(), tools_exports));
1973
+ let rounds = 0;
1974
+ while (rounds < MAX_TOOL_ROUNDS) {
1975
+ rounds++;
1976
+ try {
1977
+ printAssistantLabel();
1978
+ const thinking = startThinking();
1979
+ const stream = anthropic.messages.stream({
1980
+ model,
1981
+ max_tokens: MAX_TOKENS,
1982
+ system: systemPrompt,
1983
+ tools,
1984
+ messages: session.messages
1985
+ });
1986
+ const writer = new StreamWriter();
1987
+ let hasText = false;
1988
+ let thinkingStopped = false;
1989
+ for await (const event of stream) {
1990
+ if (event.type === "content_block_delta") {
1991
+ const delta = event.delta;
1992
+ if ("text" in delta && delta.text) {
1993
+ if (!thinkingStopped) {
1994
+ thinking.stop();
1995
+ thinkingStopped = true;
1996
+ }
1997
+ hasText = true;
1998
+ writer.write(delta.text);
1999
+ }
2000
+ }
2001
+ }
2002
+ if (!thinkingStopped) thinking.stop();
2003
+ const finalMessage = await stream.finalMessage();
2004
+ if (hasText) {
2005
+ writer.finishAndRender();
2006
+ }
2007
+ console.log();
2008
+ const contentBlocks = [];
2009
+ let hasToolUse = false;
2010
+ for (const block of finalMessage.content) {
2011
+ if (block.type === "text") {
2012
+ contentBlocks.push({ type: "text", text: block.text });
2013
+ } else if (block.type === "tool_use") {
2014
+ hasToolUse = true;
2015
+ contentBlocks.push({
2016
+ type: "tool_use",
2017
+ id: block.id,
2018
+ name: block.name,
2019
+ input: block.input
2020
+ });
2021
+ }
2022
+ }
2023
+ addAssistantMessage(session, contentBlocks);
2024
+ if (!hasToolUse) break;
2025
+ for (const block of finalMessage.content) {
2026
+ if (block.type !== "tool_use") continue;
2027
+ const toolInput = block.input;
2028
+ if (requiresConfirmation2(block.name)) {
2029
+ const desc = describeToolAction2(block.name, toolInput);
2030
+ const approved = await confirmAction(desc);
2031
+ if (!approved) {
2032
+ printToolResult(block.name, false);
2033
+ addToolResult(session, block.id, "User declined this action.", true);
2034
+ continue;
2035
+ }
2036
+ }
2037
+ printToolCall(block.name);
2038
+ try {
2039
+ const result = await executeTool2(client, block.name, toolInput);
2040
+ const resultStr = JSON.stringify(result, null, 2);
2041
+ printToolResult(block.name, true);
2042
+ addToolResult(session, block.id, resultStr);
2043
+ } catch (err) {
2044
+ const errMsg = err instanceof Error ? err.message : String(err);
2045
+ printToolResult(block.name, false);
2046
+ addToolResult(session, block.id, errMsg, true);
2047
+ }
2048
+ }
2049
+ } catch (err) {
2050
+ const message = err instanceof Error ? err.message : String(err);
2051
+ if (message.includes("rate_limit") || message.includes("429")) {
2052
+ printError("Rate limited \u2014 wait a moment and try again.");
2053
+ } else if (message.includes("overloaded") || message.includes("529")) {
2054
+ printError("API overloaded \u2014 try again in a few seconds.");
2055
+ } else if (message.includes("authentication") || message.includes("401")) {
2056
+ printError("Anthropic API key is invalid. Set ANTHROPIC_API_KEY in your environment.");
2057
+ } else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED")) {
2058
+ printError("Network error \u2014 check your internet connection.");
2059
+ } else {
2060
+ printError(message);
2061
+ }
2062
+ console.log();
2063
+ break;
2064
+ }
2065
+ }
2066
+ if (rounds >= MAX_TOOL_ROUNDS) {
2067
+ printError(`Tool loop limit reached (${MAX_TOOL_ROUNDS} rounds). Stopping to prevent runaway.`);
2068
+ console.log();
2069
+ }
2070
+ }
2071
+ function prompt(rl) {
2072
+ return new Promise((resolve4) => {
2073
+ printPrompt();
2074
+ rl.once("line", (line) => resolve4(line));
2075
+ rl.once("close", () => resolve4(null));
2076
+ });
2077
+ }
2078
+ async function handleSlashCommand(input, session, client) {
2079
+ const cmd = input.toLowerCase().split(/\s+/)[0];
2080
+ switch (cmd) {
2081
+ case "/exit":
2082
+ case "/quit":
2083
+ case "/q":
2084
+ printInfo("Session saved. Goodbye.");
2085
+ await saveSession(session);
2086
+ return "exit";
2087
+ case "/plan":
2088
+ planMode = !planMode;
2089
+ if (planMode) {
2090
+ console.log(pc13.cyan(" \u25C6 Plan mode ON") + pc13.dim(" \u2014 agent will outline steps before executing"));
2091
+ } else {
2092
+ console.log(pc13.green(" \u25C6 Plan mode OFF") + pc13.dim(" \u2014 agent will execute immediately"));
2093
+ }
2094
+ console.log();
2095
+ return true;
2096
+ case "/clear":
2097
+ session.messages = [];
2098
+ console.log(pc13.dim(" Conversation cleared."));
2099
+ console.log();
2100
+ return true;
2101
+ case "/help":
2102
+ console.log();
2103
+ console.log(pc13.bold(" Commands"));
2104
+ console.log(pc13.dim(" /help ") + "Show this help");
2105
+ console.log(pc13.dim(" /plan ") + "Toggle plan mode (outline before executing)");
2106
+ console.log(pc13.dim(" /clear ") + "Clear conversation history");
2107
+ console.log(pc13.dim(" /status ") + "Show connection status");
2108
+ console.log(pc13.dim(" /context ") + "Show full user context (profile, orgs, tier)");
2109
+ console.log(pc13.dim(" /sessions ") + "List recent sessions");
2110
+ console.log(pc13.dim(" /load <id> ") + "Load a previous session by ID");
2111
+ console.log(pc13.dim(" /exit ") + "Exit the CLI");
2112
+ console.log();
2113
+ console.log(pc13.bold(" Usage"));
2114
+ console.log(pc13.dim(" Just type naturally. Examples:"));
2115
+ console.log(pc13.dim(' "show my posts"'));
2116
+ console.log(pc13.dim(' "create a post saying hello world"'));
2117
+ console.log(pc13.dim(' "list my agents"'));
2118
+ console.log(pc13.dim(' "what communities can I join?"'));
2119
+ console.log(pc13.dim(' "check my unread messages"'));
2120
+ console.log(pc13.dim(' "show dispatcher tasks"'));
2121
+ console.log();
2122
+ console.log(pc13.dim(" Destructive actions (delete, deploy, send) require confirmation."));
2123
+ console.log();
2124
+ return true;
2125
+ case "/status":
2126
+ try {
2127
+ const profile = await client.profiles.me();
2128
+ const data = profile.data;
2129
+ console.log();
2130
+ console.log(pc13.bold(" Status"));
2131
+ console.log(pc13.dim(" User: ") + pc13.bold(`@${data.username || "unknown"}`));
2132
+ if (data.email) console.log(pc13.dim(" Email: ") + data.email);
2133
+ console.log(pc13.dim(" Session: ") + session.id.slice(0, 8));
2134
+ console.log(pc13.dim(" History: ") + `${session.messages.length} messages`);
2135
+ console.log();
2136
+ } catch {
2137
+ printError("Could not fetch status \u2014 check your API key");
2138
+ console.log();
2139
+ }
2140
+ return true;
2141
+ case "/context":
2142
+ try {
2143
+ const [profile, orgs, tier] = await Promise.allSettled([
2144
+ client.profiles.me(),
2145
+ client.organizations.list({ limit: 50 }),
2146
+ client.freeTier.getTier()
2147
+ ]);
2148
+ console.log();
2149
+ console.log(pc13.bold(" User Context"));
2150
+ if (profile.status === "fulfilled") {
2151
+ const p = profile.value.data;
2152
+ console.log(pc13.dim(" User: ") + pc13.bold(`@${p.username || "unknown"}`));
2153
+ console.log(pc13.dim(" Name: ") + (p.name || "-"));
2154
+ console.log(pc13.dim(" Email: ") + (p.email || "-"));
2155
+ console.log(pc13.dim(" Followers: ") + (p.followers_count ?? 0));
2156
+ console.log(pc13.dim(" Following: ") + (p.following_count ?? 0));
2157
+ }
2158
+ if (orgs.status === "fulfilled") {
2159
+ const orgList = orgs.value.data;
2160
+ console.log();
2161
+ console.log(pc13.bold(" Organizations"));
2162
+ if (orgList.length === 0) {
2163
+ console.log(pc13.dim(" None"));
2164
+ } else {
2165
+ for (const org of orgList) {
2166
+ const role = org.role ? pc13.dim(` (${org.role})`) : "";
2167
+ console.log(` - ${pc13.bold(org.name)}${role}`);
2168
+ }
2169
+ }
2170
+ }
2171
+ if (tier.status === "fulfilled") {
2172
+ const t = tier.value.data;
2173
+ console.log();
2174
+ console.log(pc13.bold(" Tier"));
2175
+ console.log(pc13.dim(" Plan: ") + (t.tier || t.plan || "free"));
2176
+ if (t.api_calls_remaining != null) {
2177
+ console.log(pc13.dim(" Calls: ") + `${t.api_calls_remaining} remaining`);
2178
+ }
2179
+ }
2180
+ console.log();
2181
+ } catch {
2182
+ printError("Could not fetch context");
2183
+ console.log();
2184
+ }
2185
+ return true;
2186
+ case "/sessions": {
2187
+ const sessions = await listSessions();
2188
+ console.log();
2189
+ if (sessions.length === 0) {
2190
+ console.log(pc13.dim(" No saved sessions."));
2191
+ } else {
2192
+ console.log(pc13.bold(" Recent Sessions"));
2193
+ for (const s of sessions) {
2194
+ const current = s.id === session.id ? pc13.green(" (current)") : "";
2195
+ const date = new Date(s.createdAt).toLocaleDateString();
2196
+ console.log(
2197
+ pc13.dim(" ") + pc13.bold(s.id.slice(0, 8)) + pc13.dim(` \u2014 ${date}, ${s.messageCount} messages`) + current
2198
+ );
2199
+ }
2200
+ }
2201
+ console.log();
2202
+ return true;
2203
+ }
2204
+ case "/load": {
2205
+ const parts = input.split(/\s+/);
2206
+ const targetId = parts[1];
2207
+ if (!targetId) {
2208
+ console.log(pc13.dim(" Usage: /load <session-id>"));
2209
+ console.log();
2210
+ return true;
2211
+ }
2212
+ const sessions = await listSessions(50);
2213
+ const match = sessions.find((s) => s.id.startsWith(targetId));
2214
+ if (!match) {
2215
+ printError(`No session found matching "${targetId}"`);
2216
+ console.log();
2217
+ return true;
2218
+ }
2219
+ const loaded = await loadSession(match.id);
2220
+ if (!loaded) {
2221
+ printError("Could not load session file");
2222
+ console.log();
2223
+ return true;
2224
+ }
2225
+ session.id = loaded.id;
2226
+ session.createdAt = loaded.createdAt;
2227
+ session.messages = loaded.messages;
2228
+ console.log(
2229
+ pc13.dim(" ") + `Loaded session ${pc13.bold(loaded.id.slice(0, 8))} (${loaded.messages.length} messages)`
2230
+ );
2231
+ console.log();
2232
+ return true;
2233
+ }
2234
+ default:
2235
+ console.log(pc13.dim(` Unknown command: ${cmd}. Type /help for available commands.`));
2236
+ console.log();
2237
+ return true;
2238
+ }
2239
+ }
2240
+ function detectProjectContext() {
2241
+ const cwd = process.cwd();
2242
+ const dirName = basename2(cwd);
2243
+ const hasPackageJson = existsSync3(join7(cwd, "package.json"));
2244
+ const hasReadme = existsSync3(join7(cwd, "README.md"));
2245
+ let isGitRepo = false;
2246
+ try {
2247
+ execSync2("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
2248
+ isGitRepo = true;
2249
+ } catch {
2250
+ }
2251
+ let isEmpty = true;
2252
+ try {
2253
+ const files = execSync2("ls -A", { cwd, encoding: "utf-8" }).trim().split("\n");
2254
+ isEmpty = files.length <= 2 && files.every((f) => f.startsWith(".") || f === "node_modules");
2255
+ } catch {
2256
+ }
2257
+ let packageName;
2258
+ let packageDescription;
2259
+ let framework;
2260
+ let language;
2261
+ if (hasPackageJson) {
2262
+ try {
2263
+ const pkg = JSON.parse(readFileSync(join7(cwd, "package.json"), "utf-8"));
2264
+ packageName = pkg.name;
2265
+ packageDescription = pkg.description;
2266
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2267
+ if (deps.next) framework = "Next.js";
2268
+ else if (deps.nuxt) framework = "Nuxt";
2269
+ else if (deps.svelte || deps["@sveltejs/kit"]) framework = "SvelteKit";
2270
+ else if (deps.react) framework = "React";
2271
+ else if (deps.vue) framework = "Vue";
2272
+ else if (deps.express) framework = "Express";
2273
+ else if (deps.hono) framework = "Hono";
2274
+ else if (deps.fastify) framework = "Fastify";
2275
+ } catch {
2276
+ }
2277
+ }
2278
+ if (existsSync3(join7(cwd, "tsconfig.json"))) language = "TypeScript";
2279
+ else if (existsSync3(join7(cwd, "pyproject.toml")) || existsSync3(join7(cwd, "requirements.txt"))) language = "Python";
2280
+ else if (existsSync3(join7(cwd, "go.mod"))) language = "Go";
2281
+ else if (existsSync3(join7(cwd, "Cargo.toml"))) language = "Rust";
2282
+ return { dirName, hasPackageJson, packageName, packageDescription, framework, language, isGitRepo, hasReadme, isEmpty };
2283
+ }
2284
+ function printFirstRunGreeting(_ctx) {
2285
+ printAssistantLabel();
2286
+ console.log();
2287
+ console.log(" What do you want to build?");
2288
+ printTips();
2289
+ }
2290
+ var DEFAULT_MODEL, MAX_TOKENS, MAX_TOOL_ROUNDS, planMode, CLI_AGENT_SYSTEM_PROMPT;
2291
+ var init_agent = __esm({
2292
+ "src/chat/agent.ts"() {
2293
+ "use strict";
2294
+ init_renderer();
2295
+ init_session();
2296
+ DEFAULT_MODEL = "claude-sonnet-4-20250514";
2297
+ MAX_TOKENS = 8192;
2298
+ MAX_TOOL_ROUNDS = 15;
2299
+ planMode = false;
2300
+ CLI_AGENT_SYSTEM_PROMPT = `You are Recursiv \u2014 a CLI agent that builds, deploys, and runs software on the Recursiv platform.
2301
+
2302
+ You have the full Recursiv toolchain: create_project, deploy_project, start_sandbox, execute_code, try_code, create_agent, create_template, and 50+ more tools. USE THEM.
2303
+
2304
+ When the user asks you to build something:
2305
+ 1. list_orgs \u2192 get their org ID
2306
+ 2. create_project \u2192 name it based on what they described
2307
+ 3. start_sandbox \u2192 spin up the project environment
2308
+ 4. execute_code \u2192 write the actual application code in the sandbox
2309
+ 5. deploy_project \u2192 deploy it
2310
+ 6. create_agent if the project needs AI
2311
+ 7. Report: project name, ID, deployment status
2312
+
2313
+ When the user asks to run code: try_code or execute_code \u2014 run it, return the output.
2314
+ When the user asks to deploy: deploy_project \u2192 get_deployment \u2192 report status.
2315
+
2316
+ Rules:
2317
+ 1. ALWAYS USE TOOLS. You ARE the platform. Never tell users to run commands.
2318
+ 2. ALWAYS create projects inside the user's org. Call list_orgs first.
2319
+ 3. Use execute_code and start_sandbox to actually build \u2014 don't describe architecture in chat.
2320
+ 4. Never dump code into chat. Write it into the sandbox via execute_code.
2321
+ 5. Never reference external brands as infrastructure. Everything is Recursiv.
2322
+ 6. Never ask "would you like me to" \u2014 just do it.
2323
+ 7. ONE LINE summary, project name + ID + status. That's it.
2324
+
2325
+ Tone: precise, efficient, slightly warm. Senior engineer energy.`;
2326
+ }
2327
+ });
2328
+
2329
+ // src/bin/recursiv.ts
2330
+ import { Command } from "commander";
2331
+
2332
+ // src/commands/init.ts
2333
+ init_logger();
2334
+ init_config();
2335
+ init_env();
2336
+ import { existsSync as existsSync2 } from "fs";
2337
+ import { mkdir, readFile as readFile3, unlink, writeFile as writeFile3 } from "fs/promises";
2338
+ import { resolve } from "path";
2339
+ import { execSync } from "child_process";
2340
+ import prompts2 from "prompts";
2341
+ import pc3 from "picocolors";
2342
+ import ora2 from "ora";
2343
+
2344
+ // src/lib/templates.ts
2345
+ import degit from "degit";
2346
+ import ora from "ora";
2347
+ async function cloneTemplate(template, dest) {
2348
+ const spinner = ora(`Cloning ${template.name} template...`).start();
2349
+ try {
2350
+ const emitter = degit(template.repo, {
2351
+ cache: false,
2352
+ force: true,
2353
+ verbose: false
2354
+ });
2355
+ await emitter.clone(dest);
2356
+ spinner.succeed(`Template cloned successfully`);
2357
+ } catch (err) {
2358
+ spinner.fail("Failed to clone template");
2359
+ const message = err instanceof Error ? err.message : String(err);
2360
+ throw new Error(
2361
+ `Could not clone template from ${template.repo}.
2362
+ Make sure you have internet access and the repo exists.
2363
+ Details: ${message}`
2364
+ );
2365
+ }
2366
+ }
2367
+
2368
+ // src/commands/init.ts
2369
+ init_auth_flow();
2370
+
2371
+ // src/lib/package-manager.ts
2372
+ import { existsSync } from "fs";
2373
+ import { join as join3 } from "path";
2374
+ function detectPackageManager(dir) {
2375
+ if (existsSync(join3(dir, "bun.lockb")) || existsSync(join3(dir, "bun.lock"))) return "bun";
2376
+ if (existsSync(join3(dir, "pnpm-lock.yaml"))) return "pnpm";
2377
+ if (existsSync(join3(dir, "yarn.lock"))) return "yarn";
2378
+ return "npm";
2379
+ }
2380
+ function detectFromUserAgent() {
2381
+ const ua = process.env.npm_config_user_agent ?? "";
2382
+ if (ua.startsWith("bun")) return "bun";
2383
+ if (ua.startsWith("pnpm")) return "pnpm";
2384
+ if (ua.startsWith("yarn")) return "yarn";
2385
+ return "npm";
2386
+ }
2387
+ function installCommand(pm) {
2388
+ return pm === "yarn" ? "yarn" : `${pm} install`;
2389
+ }
2390
+ function runCommand(pm, script) {
2391
+ if (pm === "npm") return `npm run ${script}`;
2392
+ return `${pm} ${script}`;
2393
+ }
2394
+
2395
+ // src/templates/registry.ts
2396
+ var templates = [
2397
+ {
2398
+ id: "nextjs",
2399
+ name: "Next.js + Recursiv",
2400
+ description: "Full-stack Next.js app with feeds, chat, and agents",
2401
+ repo: "recursivlabs/template-nextjs",
2402
+ framework: "nextjs",
2403
+ devCommand: "next dev",
2404
+ devPort: 3e3,
2405
+ recommended: true
2406
+ },
2407
+ {
2408
+ id: "node",
2409
+ name: "Node.js + Express",
2410
+ description: "Express API server with Recursiv SDK",
2411
+ repo: "recursivlabs/template-node",
2412
+ framework: "express",
2413
+ devCommand: "node --watch src/index.js",
2414
+ devPort: 4e3
2415
+ },
2416
+ {
2417
+ id: "vite",
2418
+ name: "Vite + React",
2419
+ description: "React SPA with social feed components",
2420
+ repo: "recursivlabs/template-vite",
2421
+ framework: "vite",
2422
+ devCommand: "vite",
2423
+ devPort: 5173
2424
+ }
2425
+ ];
2426
+ function getTemplate(id) {
2427
+ return templates.find((t) => t.id === id);
2428
+ }
2429
+
2430
+ // src/commands/init.ts
2431
+ async function initCommand(nameArg) {
2432
+ banner();
2433
+ const totalSteps = 7;
2434
+ log.step(1, totalSteps, "Project setup");
2435
+ let projectName;
2436
+ if (nameArg) {
2437
+ projectName = nameArg;
2438
+ } else {
2439
+ const { name } = await prompts2(
2440
+ {
2441
+ type: "text",
2442
+ name: "name",
2443
+ message: "Project name",
2444
+ initial: "my-recursiv-app",
2445
+ validate: (v) => v.trim() ? true : "Name is required"
2446
+ },
2447
+ { onCancel: () => process.exit(1) }
2448
+ );
2449
+ projectName = name;
2450
+ }
2451
+ const projectDir = resolve(process.cwd(), projectName);
2452
+ if (existsSync2(projectDir)) {
2453
+ log.error(`Directory ${pc3.bold(projectName)} already exists`);
2454
+ process.exit(1);
2455
+ }
2456
+ log.step(2, totalSteps, "Choose a template");
2457
+ const templateChoices = templates.map((t) => ({
2458
+ title: t.recommended ? `${t.name} ${pc3.dim("(recommended)")}` : t.name,
2459
+ description: t.description,
2460
+ value: t.id
2461
+ }));
2462
+ const { templateId } = await prompts2(
2463
+ {
2464
+ type: "select",
2465
+ name: "templateId",
2466
+ message: "Template",
2467
+ choices: templateChoices,
2468
+ initial: 0
2469
+ },
2470
+ { onCancel: () => process.exit(1) }
2471
+ );
2472
+ const template = templates.find((t) => t.id === templateId);
2473
+ log.step(3, totalSteps, "Authentication");
2474
+ const apiKey = await getOrCreateApiKey("https://api.recursiv.io/api/v1");
2475
+ log.step(4, totalSteps, "Creating project");
2476
+ await mkdir(projectDir, { recursive: true });
2477
+ await cloneTemplate(template, projectDir);
2478
+ const nextConfigTs = resolve(projectDir, "next.config.ts");
2479
+ const nextConfigMjs = resolve(projectDir, "next.config.mjs");
2480
+ const cleanNextConfig = '/** @type {import("next").NextConfig} */\nconst nextConfig = {};\nexport default nextConfig;\n';
2481
+ if (existsSync2(nextConfigTs)) {
2482
+ await unlink(nextConfigTs);
2483
+ if (existsSync2(nextConfigMjs)) await unlink(nextConfigMjs);
2484
+ await writeFile3(nextConfigMjs, cleanNextConfig, "utf-8");
2485
+ } else if (existsSync2(nextConfigMjs)) {
2486
+ const content = await readFile3(nextConfigMjs, "utf-8");
2487
+ if (content.includes("import type") || content.includes(": NextConfig")) {
2488
+ await writeFile3(nextConfigMjs, cleanNextConfig, "utf-8");
2489
+ }
2490
+ }
2491
+ log.step(5, totalSteps, "Writing config");
2492
+ const pm = detectFromUserAgent();
2493
+ const config = createConfig({
2494
+ name: projectName,
2495
+ template: template.id,
2496
+ framework: template.framework,
2497
+ port: template.devPort,
2498
+ devCommand: runCommand(pm, "dev")
2499
+ });
2500
+ await writeConfig(projectDir, config);
2501
+ if (apiKey) {
2502
+ await writeEnvFile(projectDir, apiKey);
2503
+ }
2504
+ const gitignorePath = resolve(projectDir, ".gitignore");
2505
+ if (!existsSync2(gitignorePath)) {
2506
+ await writeFile3(
2507
+ gitignorePath,
2508
+ ["node_modules", "dist", ".env", ".env.local", ".next", ".turbo", ""].join("\n"),
2509
+ "utf-8"
2510
+ );
2511
+ }
2512
+ log.step(6, totalSteps, "Installing dependencies");
2513
+ const installSpinner = ora2(`Running ${pc3.bold(installCommand(pm))}...`).start();
2514
+ try {
2515
+ execSync(installCommand(pm), {
2516
+ cwd: projectDir,
2517
+ stdio: "pipe",
2518
+ timeout: 12e4
2519
+ });
2520
+ installSpinner.succeed("Dependencies installed");
2521
+ } catch {
2522
+ installSpinner.warn("Could not install dependencies \u2014 run install manually");
2523
+ }
2524
+ log.step(7, totalSteps, "Initializing git");
2525
+ try {
2526
+ execSync("git init", { cwd: projectDir, stdio: "pipe" });
2527
+ execSync("git add -A", { cwd: projectDir, stdio: "pipe" });
2528
+ execSync('git commit -m "Initial commit from create-recursiv-app"', {
2529
+ cwd: projectDir,
2530
+ stdio: "pipe"
2531
+ });
2532
+ log.success("Git repository initialized");
2533
+ } catch {
2534
+ log.warn("Could not initialize git repository");
2535
+ }
2536
+ log.blank();
2537
+ console.log(pc3.bold(pc3.green("Your Recursiv app is ready!")));
2538
+ log.blank();
2539
+ console.log(" Next steps:");
2540
+ console.log();
2541
+ console.log(` ${pc3.dim("$")} cd ${projectName}`);
2542
+ if (!apiKey) {
2543
+ console.log(` ${pc3.dim("$")} ${pc3.dim("# Add your API key to .env")}`);
2544
+ }
2545
+ console.log(` ${pc3.dim("$")} ${runCommand(pm, "dev")}`);
2546
+ log.blank();
2547
+ console.log(pc3.dim("Docs: https://docs.recursiv.io"));
2548
+ console.log(pc3.dim("Dashboard: https://recursiv.io/dashboard"));
2549
+ log.blank();
2550
+ }
2551
+
2552
+ // src/commands/dev.ts
2553
+ init_logger();
2554
+ init_config();
2555
+ init_env();
2556
+ import { spawn } from "child_process";
2557
+ import pc4 from "picocolors";
2558
+ async function devCommand(opts) {
2559
+ const cwd = process.cwd();
2560
+ const config = await readConfig(cwd);
2561
+ if (!config) {
2562
+ log.error("No .recursiv.json found \u2014 are you in a Recursiv project?");
2563
+ log.info(`Run ${pc4.bold("create-recursiv-app")} to create a new project`);
2564
+ process.exit(1);
2565
+ }
2566
+ const apiKey = await readApiKeyFromEnv(cwd);
2567
+ if (!apiKey) {
2568
+ log.warn("No RECURSIV_API_KEY found in .env \u2014 API calls will fail");
2569
+ }
2570
+ const pm = detectPackageManager(cwd);
2571
+ const port = opts.port ?? String(config.dev.port);
2572
+ const devCmd = config.dev.command || runCommand(pm, "dev");
2573
+ const env = {
2574
+ ...process.env,
2575
+ PORT: port
2576
+ };
2577
+ if (apiKey) {
2578
+ env.RECURSIV_API_KEY = apiKey;
2579
+ if (config.project.framework === "nextjs") {
2580
+ env.NEXT_PUBLIC_RECURSIV_API_KEY = apiKey;
2581
+ } else if (config.project.framework === "vite") {
2582
+ env.VITE_RECURSIV_API_KEY = apiKey;
2583
+ }
2584
+ }
2585
+ log.info(`Starting dev server on port ${pc4.bold(port)}`);
2586
+ log.info(`Running: ${pc4.dim(devCmd)}`);
2587
+ console.log();
2588
+ const [cmd, ...args] = devCmd.split(" ");
2589
+ const child = spawn(cmd, args, {
2590
+ cwd,
2591
+ env,
2592
+ stdio: "inherit",
2593
+ shell: true
2594
+ });
2595
+ child.on("close", (code) => {
2596
+ process.exit(code ?? 0);
2597
+ });
2598
+ const cleanup = () => {
2599
+ child.kill("SIGTERM");
2600
+ };
2601
+ process.on("SIGINT", cleanup);
2602
+ process.on("SIGTERM", cleanup);
2603
+ }
2604
+
2605
+ // src/commands/deploy.ts
2606
+ init_logger();
2607
+ init_config();
2608
+ import { writeFile as writeFile4 } from "fs/promises";
2609
+ import { resolve as resolve2 } from "path";
2610
+ import pc5 from "picocolors";
2611
+ import ora3 from "ora";
2612
+ var targets = [
2613
+ {
2614
+ id: "vercel",
2615
+ name: "Vercel",
2616
+ generate: (name, framework) => ({
2617
+ file: "vercel.json",
2618
+ content: JSON.stringify(
2619
+ {
2620
+ $schema: "https://openapi.vercel.sh/vercel.json",
2621
+ projectSettings: {
2622
+ framework: framework === "nextjs" ? "nextjs" : framework === "vite" ? "vite" : null
2623
+ },
2624
+ env: {
2625
+ RECURSIV_API_KEY: "@recursiv-api-key"
2626
+ }
2627
+ },
2628
+ null,
2629
+ 2
2630
+ )
2631
+ })
2632
+ },
2633
+ {
2634
+ id: "docker",
2635
+ name: "Docker",
2636
+ generate: (name, framework) => ({
2637
+ file: "Dockerfile",
2638
+ content: [
2639
+ "FROM node:20-slim AS base",
2640
+ "WORKDIR /app",
2641
+ "COPY package*.json ./",
2642
+ "RUN npm ci --omit=dev",
2643
+ "COPY . .",
2644
+ framework === "nextjs" ? "RUN npm run build" : "",
2645
+ framework === "nextjs" ? "EXPOSE 3000" : "EXPOSE 4000",
2646
+ framework === "nextjs" ? 'CMD ["npm", "start"]' : 'CMD ["node", "src/index.js"]',
2647
+ ""
2648
+ ].filter(Boolean).join("\n")
2649
+ })
2650
+ },
2651
+ {
2652
+ id: "render",
2653
+ name: "Render",
2654
+ generate: (name, framework) => ({
2655
+ file: "render.yaml",
2656
+ content: [
2657
+ "services:",
2658
+ ` - type: web`,
2659
+ ` name: ${name}`,
2660
+ ` runtime: node`,
2661
+ ` buildCommand: npm install && npm run build`,
2662
+ framework === "nextjs" ? " startCommand: npm start" : " startCommand: node src/index.js",
2663
+ " envVars:",
2664
+ " - key: RECURSIV_API_KEY",
2665
+ " sync: false",
2666
+ ""
2667
+ ].join("\n")
2668
+ })
2669
+ },
2670
+ {
2671
+ id: "railway",
2672
+ name: "Railway",
2673
+ generate: (name, framework) => ({
2674
+ file: "railway.toml",
2675
+ content: [
2676
+ "[build]",
2677
+ 'builder = "nixpacks"',
2678
+ "",
2679
+ "[deploy]",
2680
+ framework === "nextjs" ? 'startCommand = "npm start"' : 'startCommand = "node src/index.js"',
2681
+ 'healthcheckPath = "/"',
2682
+ ""
2683
+ ].join("\n")
2684
+ })
2685
+ }
2686
+ ];
2687
+ async function deployCommand(target) {
2688
+ const cwd = process.cwd();
2689
+ const config = await readConfig(cwd);
2690
+ if (!config) {
2691
+ log.error("No .recursiv.json found \u2014 are you in a Recursiv project?");
2692
+ process.exit(1);
2693
+ }
2694
+ if (target === "cloud") {
2695
+ log.info("Cloud deployment via Recursiv is coming soon");
2696
+ log.info(`For now, deploy to Vercel, Render, or Railway with ${pc5.bold("recursiv deploy <target>")}`);
2697
+ return;
2698
+ }
2699
+ const deployTarget = targets.find((t) => t.id === target);
2700
+ if (!deployTarget) {
2701
+ log.error(`Unknown target: ${pc5.bold(target)}`);
2702
+ log.info(`Available targets: ${targets.map((t) => t.id).join(", ")}, cloud`);
2703
+ process.exit(1);
2704
+ }
2705
+ const spinner = ora3(`Generating ${deployTarget.name} config...`).start();
2706
+ const { file, content } = deployTarget.generate(
2707
+ config.project.name,
2708
+ config.project.framework
2709
+ );
2710
+ await writeFile4(resolve2(cwd, file), content + "\n", "utf-8");
2711
+ spinner.succeed(`Created ${pc5.bold(file)}`);
2712
+ log.blank();
2713
+ log.info(`Remember to set ${pc5.bold("RECURSIV_API_KEY")} in your ${deployTarget.name} environment`);
2714
+ }
2715
+
2716
+ // src/commands/auth.ts
2717
+ init_logger();
2718
+ init_env();
2719
+ init_auth_flow();
2720
+ init_config();
2721
+ init_credentials();
2722
+ import pc6 from "picocolors";
2723
+ async function loginCommand() {
2724
+ const cwd = process.cwd();
2725
+ const config = await readConfig(cwd);
2726
+ const baseUrl = config?.api.baseUrl ?? "https://api.recursiv.io/api/v1";
2727
+ const existing = await resolveRecursivKey(cwd);
2728
+ if (existing) {
2729
+ log.info(`Existing key found: ${pc6.dim(maskKey(existing))}`);
2730
+ log.info("Enter a new key to replace it, or press Enter to keep it");
2731
+ }
2732
+ const apiKey = await getOrCreateApiKey(baseUrl);
2733
+ if (apiKey) {
2734
+ await saveCredentials({ recursivApiKey: apiKey });
2735
+ log.success("API key saved to ~/.recursiv/credentials");
2736
+ try {
2737
+ await updateApiKeyInEnv(cwd, apiKey);
2738
+ log.success("Also saved to .env");
2739
+ } catch {
2740
+ }
2741
+ }
2742
+ }
2743
+ async function logoutCommand() {
2744
+ await saveCredentials({ recursivApiKey: void 0 });
2745
+ log.success("API key removed from ~/.recursiv/credentials");
2746
+ const cwd = process.cwd();
2747
+ const existing = await readApiKeyFromEnv(cwd);
2748
+ if (existing) {
2749
+ await updateApiKeyInEnv(cwd, "");
2750
+ log.success("Also removed from .env");
2751
+ }
2752
+ }
2753
+ async function whoamiCommand() {
2754
+ const cwd = process.cwd();
2755
+ const config = await readConfig(cwd);
2756
+ const baseUrl = config?.api.baseUrl ?? "https://api.recursiv.io/api/v1";
2757
+ const apiKey = await resolveRecursivKey(cwd);
2758
+ if (!apiKey) {
2759
+ log.error("No API key found in .env");
2760
+ log.info(`Run ${pc6.bold("recursiv auth login")} to set one`);
2761
+ return;
2762
+ }
2763
+ log.info(`Key: ${pc6.dim(maskKey(apiKey))}`);
2764
+ try {
2765
+ const res = await fetch(`${baseUrl}/users/me`, {
2766
+ headers: {
2767
+ Authorization: `Bearer ${apiKey}`,
2768
+ Accept: "application/json"
2769
+ }
2770
+ });
2771
+ if (!res.ok) {
2772
+ log.error("API key is invalid or expired");
2773
+ return;
2774
+ }
2775
+ const body = await res.json();
2776
+ if (body.data.username) log.info(`User: ${pc6.bold(body.data.username)}`);
2777
+ if (body.data.email) log.info(`Email: ${body.data.email}`);
2778
+ } catch {
2779
+ log.warn("Could not reach API \u2014 check your network");
2780
+ }
2781
+ }
2782
+ function maskKey(key) {
2783
+ if (key.length <= 12) return "****";
2784
+ return key.slice(0, 8) + "..." + key.slice(-4);
2785
+ }
2786
+
2787
+ // src/commands/info.ts
2788
+ init_logger();
2789
+ init_config();
2790
+ init_env();
2791
+ import pc7 from "picocolors";
2792
+ async function infoCommand() {
2793
+ const cwd = process.cwd();
2794
+ const config = await readConfig(cwd);
2795
+ if (!config) {
2796
+ log.error("No .recursiv.json found \u2014 are you in a Recursiv project?");
2797
+ log.info(`Run ${pc7.bold("create-recursiv-app")} to create a new project`);
2798
+ process.exit(1);
2799
+ }
2800
+ const apiKey = await readApiKeyFromEnv(cwd);
2801
+ const pm = detectPackageManager(cwd);
2802
+ const template = getTemplate(config.project.template);
2803
+ console.log();
2804
+ console.log(pc7.bold("Project Info"));
2805
+ console.log();
2806
+ console.log(` Name: ${pc7.bold(config.project.name)}`);
2807
+ console.log(` Template: ${template?.name ?? config.project.template}`);
2808
+ console.log(` Framework: ${config.project.framework}`);
2809
+ console.log(` Dev port: ${config.dev.port}`);
2810
+ console.log(` Dev cmd: ${pc7.dim(config.dev.command)}`);
2811
+ console.log(` Pkg mgr: ${pm}`);
2812
+ console.log(` API URL: ${pc7.dim(config.api.baseUrl)}`);
2813
+ console.log(` API key: ${apiKey ? pc7.green("configured") : pc7.yellow("not set")}`);
2814
+ console.log(` Config: ${pc7.dim(".recursiv.json v" + config.version)}`);
2815
+ console.log();
2816
+ if (apiKey) {
2817
+ try {
2818
+ const res = await fetch(`${config.api.baseUrl}/users/me`, {
2819
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" }
2820
+ });
2821
+ if (res.ok) {
2822
+ log.success("API connection OK");
2823
+ } else {
2824
+ log.warn(`API returned ${res.status} \u2014 key may be invalid`);
2825
+ }
2826
+ } catch {
2827
+ log.warn("Could not reach API");
2828
+ }
2829
+ }
2830
+ }
2831
+
2832
+ // src/commands/brain.tsx
2833
+ init_config();
2834
+ init_env();
2835
+ import { render } from "ink";
2836
+
2837
+ // src/ui/app.tsx
2838
+ import { useEffect, useMemo, useState } from "react";
2839
+ import { Box, Text, useApp, useInput, useStdout } from "ink";
2840
+ import { jsx, jsxs } from "react/jsx-runtime";
2841
+ var TABS = [
2842
+ { key: "overview", label: "Overview" },
2843
+ { key: "commands", label: "Commands" },
2844
+ { key: "stack", label: "App" }
2845
+ ];
2846
+ var THEME = {
2847
+ accent: "#1FE4C6",
2848
+ accentWarm: "#F6D47A",
2849
+ accentCool: "#4DB6FF",
2850
+ muted: "#6F7C8A",
2851
+ bright: "#E9FFFA",
2852
+ danger: "#FF6B6B"
2853
+ };
2854
+ var WORDMARK_COLORS = ["cyan", "blue", "yellow"];
2855
+ var WORDMARK_LINES = [
2856
+ "#### #### ### ##### # #",
2857
+ "# # # # # # # ## #",
2858
+ "#### #### ##### # # # #",
2859
+ "# # # # # # # # ##",
2860
+ "#### # # # # ##### # #"
2861
+ ];
2862
+ var HERO_LINES = [
2863
+ "Neurons, particles, waves.",
2864
+ "The brain behind Recursiv.",
2865
+ "Signals in motion, products alive.",
2866
+ "A living map of your platform.",
2867
+ "Bioluminescent systems in sync."
2868
+ ];
2869
+ var COPY = {
2870
+ overview: `# Social.dev Brain
2871
+ A neural CLI reflection of your product surface with neurons, particles, and waves.
2872
+
2873
+ ## What it shows
2874
+ - Branded networks, communities, posts, and reactions
2875
+ - Real-time chat with human + AI agent threads
2876
+ - Tool-enabled agents with sandboxes and integrations
2877
+ - Deployments, storage buckets, and databases
2878
+
2879
+ ## Flow
2880
+ 1. Create a project
2881
+ 2. Run dev locally
2882
+ 3. Deploy anywhere`,
2883
+ commands: `# Command Deck
2884
+ - \`recursiv\` runs Brain when interactive
2885
+ - \`recursiv brain\`
2886
+ - \`RECURSIV_BRAIN=1 recursiv\`
2887
+ - \`RECURSIV_BRAIN=0 recursiv\`
2888
+ - \`recursiv create my-app\`
2889
+ - \`recursiv dev --port 3000\`
2890
+ - \`recursiv deploy <target>\`
2891
+ - \`recursiv auth login | logout | whoami\`
2892
+ - \`recursiv info\`
2893
+ - \`recursiv users me\`
2894
+ - \`recursiv communities list\`
2895
+ - \`recursiv posts list\`
2896
+ - \`recursiv agents list\``,
2897
+ stack: `# App Reflection
2898
+ ## Client
2899
+ - React Native + Expo
2900
+ - NativeWind + Tailwind
2901
+
2902
+ ## Backend
2903
+ - Hono + tRPC
2904
+ - Better Auth
2905
+
2906
+ ## Data + Realtime
2907
+ - PostgreSQL + Drizzle ORM
2908
+ - Kafka + Socket.IO
2909
+
2910
+ ## Agents
2911
+ - Composio + E2B + OpenRouter`
2912
+ };
2913
+ var PULSE_FRAMES = ["spark", "pulse", "sync", "bloom"];
2914
+ var DOT_FRAMES = [".", "..", "..."];
2915
+ var BAR_FRAMES = ["[~ ]", "[~~ ]", "[~~~ ]", "[ ~~~~ ]", "[ ~~~~]", "[ ~~~]", "[ ~~]", "[ ~]"];
2916
+ var AURORA_COLORS = ["#1FE4C6", "#4DB6FF", "#8AF0FF", "#F6D47A"];
2917
+ var AURORA_COLORS_DEEP = ["#0C2236", "#12314C", "#1E3A5F"];
2918
+ var QUICK_ACTIONS = [
2919
+ "recursiv brain",
2920
+ "create-recursiv-app my-app",
2921
+ "recursiv dev --port 3000",
2922
+ "recursiv users me",
2923
+ "recursiv deploy vercel"
2924
+ ];
2925
+ var HINTS = ["Tab or arrows to switch tabs.", "1/2/3 to jump instantly.", "q or Esc to exit."];
2926
+ var INTEGRATIONS = ["Composio", "E2B", "OpenRouter"];
2927
+ var INTRO_DURATION_MS = 5200;
2928
+ var INTRO_STEPS = [
2929
+ "Igniting cortex",
2930
+ "Mapping synapses",
2931
+ "Charging particles",
2932
+ "Calibrating agents",
2933
+ "Locking signal"
2934
+ ];
2935
+ var INTRO_HINTS = ["Press Space to skip", "Press q to exit"];
2936
+ function App(props) {
2937
+ const { snapshot, animate } = props;
2938
+ const { exit } = useApp();
2939
+ const { stdout } = useStdout();
2940
+ const [tab, setTab] = useState("overview");
2941
+ const [introActive, setIntroActive] = useState(animate);
2942
+ const columns = stdout?.columns ?? process.stdout.columns ?? 100;
2943
+ const isNarrow = columns < 100;
2944
+ const fieldWidth = isNarrow ? Math.max(12, columns - 6) : 26;
2945
+ const fieldHeight = isNarrow ? 6 : 10;
2946
+ const navWidth = isNarrow ? void 0 : 26;
2947
+ const infoWidth = isNarrow ? void 0 : 34;
2948
+ const pulseIndex = useTicker(animate, 700, PULSE_FRAMES.length);
2949
+ const dotIndex = useTicker(animate, 450, DOT_FRAMES.length);
2950
+ const heroIndex = useTicker(animate, 2200, HERO_LINES.length);
2951
+ const barIndex = useTicker(animate, 140, BAR_FRAMES.length);
2952
+ const sweepIndex = useTicker(animate, 60, Math.max(10, Math.min(columns - 4, 120)));
2953
+ const scanIndex = useTicker(animate, 320, 9);
2954
+ const introProgress = useProgress(introActive && animate, INTRO_DURATION_MS, 80);
2955
+ const introFrame = useTicker(introActive && animate, 60, Math.max(10, columns - 6));
2956
+ const pulse = PULSE_FRAMES[pulseIndex];
2957
+ const dots = DOT_FRAMES[dotIndex];
2958
+ const hero = HERO_LINES[heroIndex];
2959
+ const bar = BAR_FRAMES[barIndex];
2960
+ useEffect(() => {
2961
+ if (!animate) {
2962
+ setIntroActive(false);
2963
+ return;
2964
+ }
2965
+ setIntroActive(true);
2966
+ const id = setTimeout(() => setIntroActive(false), INTRO_DURATION_MS);
2967
+ return () => clearTimeout(id);
2968
+ }, [animate]);
2969
+ useInput((input, key) => {
2970
+ if (input === "q" || key.escape) {
2971
+ exit();
2972
+ return;
2973
+ }
2974
+ if (introActive) {
2975
+ if (input === " " || key.return || input === "s") {
2976
+ setIntroActive(false);
2977
+ }
2978
+ return;
2979
+ }
2980
+ if (key.tab || key.rightArrow) {
2981
+ const next = (tabIndex(tab) + 1) % TABS.length;
2982
+ setTab(TABS[next].key);
2983
+ return;
2984
+ }
2985
+ if (key.leftArrow) {
2986
+ const prev = (tabIndex(tab) - 1 + TABS.length) % TABS.length;
2987
+ setTab(TABS[prev].key);
2988
+ return;
2989
+ }
2990
+ if (input === "1") setTab("overview");
2991
+ if (input === "2") setTab("commands");
2992
+ if (input === "3") setTab("stack");
2993
+ });
2994
+ if (introActive) {
2995
+ return /* @__PURE__ */ jsx(
2996
+ IntroScreen,
2997
+ {
2998
+ width: columns,
2999
+ height: Math.max(14, fieldHeight + 6),
3000
+ progress: introProgress,
3001
+ frame: introFrame
3002
+ }
3003
+ );
3004
+ }
3005
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
3006
+ /* @__PURE__ */ jsx(
3007
+ Header,
3008
+ {
3009
+ hero,
3010
+ pulse,
3011
+ dots,
3012
+ bar,
3013
+ sweep: sweepIndex,
3014
+ width: columns,
3015
+ animate
3016
+ }
3017
+ ),
3018
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: isNarrow ? "column" : "row", children: [
3019
+ /* @__PURE__ */ jsx(
3020
+ Box,
3021
+ {
3022
+ flexDirection: "column",
3023
+ width: navWidth,
3024
+ marginRight: isNarrow ? 0 : 2,
3025
+ marginBottom: isNarrow ? 1 : 0,
3026
+ children: /* @__PURE__ */ jsxs(Panel, { title: "Brain Map", accent: THEME.accent, borderStyle: "round", children: [
3027
+ /* @__PURE__ */ jsx(NeuralField, { width: fieldWidth, height: fieldHeight, frame: sweepIndex, animate }),
3028
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "* neuron o synapse + particle ~ wave" }),
3029
+ /* @__PURE__ */ jsx(Divider, { width: fieldWidth }),
3030
+ /* @__PURE__ */ jsx(SectionTitle, { label: "Navigator" }),
3031
+ /* @__PURE__ */ jsx(Navigation, { active: tab }),
3032
+ /* @__PURE__ */ jsx(Divider, {}),
3033
+ /* @__PURE__ */ jsx(SectionTitle, { label: "Quick Actions" }),
3034
+ QUICK_ACTIONS.map((item) => /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3035
+ "- ",
3036
+ item
3037
+ ] }, item)),
3038
+ /* @__PURE__ */ jsx(Divider, {}),
3039
+ /* @__PURE__ */ jsx(SectionTitle, { label: "Hints" }),
3040
+ HINTS.map((item) => /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3041
+ "- ",
3042
+ item
3043
+ ] }, item))
3044
+ ] })
3045
+ }
3046
+ ),
3047
+ /* @__PURE__ */ jsx(
3048
+ Box,
3049
+ {
3050
+ flexDirection: "column",
3051
+ flexGrow: 1,
3052
+ marginRight: isNarrow ? 0 : 2,
3053
+ marginBottom: isNarrow ? 1 : 0,
3054
+ children: /* @__PURE__ */ jsx(Panel, { title: tabTitle(tab), accent: THEME.accentWarm, borderStyle: "double", children: /* @__PURE__ */ jsx(AnimatedMarkdown, { content: COPY[tab], animate }) })
3055
+ }
3056
+ ),
3057
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: infoWidth, children: /* @__PURE__ */ jsx(Panel, { title: "Synapses", accent: THEME.accentCool, borderStyle: "round", children: /* @__PURE__ */ jsx(ProjectPanel, { snapshot, animate, dots, frame: sweepIndex, scanIndex }) }) })
3058
+ ] }),
3059
+ /* @__PURE__ */ jsx(Footer, { snapshot, animate })
3060
+ ] });
3061
+ }
3062
+ function Header(props) {
3063
+ const { hero, pulse, dots, bar, sweep, width, animate } = props;
3064
+ const lineWidth = Math.max(32, Math.min(width - 4, 120));
3065
+ const pulseLabel = animate ? `${pulse}${dots}` : "ready";
3066
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit" });
3067
+ const wordmarkPalette = rotatePalette(WORDMARK_COLORS, sweep);
3068
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", children: [
3069
+ /* @__PURE__ */ jsx(Wordmark, { lines: WORDMARK_LINES, palette: wordmarkPalette }),
3070
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: hero }),
3071
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "by Recursiv" }),
3072
+ /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", children: [
3073
+ /* @__PURE__ */ jsx(Box, { marginRight: 2, children: /* @__PURE__ */ jsx(StatusPill, { label: "brain", value: pulseLabel, tone: THEME.accent }) }),
3074
+ /* @__PURE__ */ jsx(Box, { marginRight: 2, children: /* @__PURE__ */ jsx(StatusPill, { label: "clock", value: time, tone: THEME.accentWarm }) }),
3075
+ /* @__PURE__ */ jsx(StatusPill, { label: "waves", value: bar, tone: THEME.accentCool })
3076
+ ] }),
3077
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx(Aurora, { width: lineWidth, frame: sweep }) }),
3078
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(AccentLine, { width: lineWidth, frame: sweep }) })
3079
+ ] });
3080
+ }
3081
+ function Footer(props) {
3082
+ const { snapshot, animate } = props;
3083
+ const projectHint = snapshot ? `${snapshot.name}` : "no project loaded";
3084
+ const status = animate ? "brain live" : "brain ready";
3085
+ return /* @__PURE__ */ jsxs(Box, { marginTop: 1, flexDirection: "row", justifyContent: "space-between", children: [
3086
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "Tab to switch | 1/2/3 to jump | q to quit" }),
3087
+ /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3088
+ status,
3089
+ " | ",
3090
+ projectHint
3091
+ ] })
3092
+ ] });
3093
+ }
3094
+ function Panel(props) {
3095
+ const { title, accent, borderStyle = "round", children } = props;
3096
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle, borderColor: accent, paddingX: 1, paddingY: 1, children: [
3097
+ /* @__PURE__ */ jsx(Text, { color: accent, children: title }),
3098
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children })
3099
+ ] });
3100
+ }
3101
+ function SectionTitle(props) {
3102
+ return /* @__PURE__ */ jsx(Text, { color: THEME.accentWarm, children: props.label });
3103
+ }
3104
+ function Navigation(props) {
3105
+ const { active } = props;
3106
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: TABS.map((tab, index) => {
3107
+ const isActive = tab.key === active;
3108
+ const marker = isActive ? ">" : " ";
3109
+ const color = isActive ? THEME.accent : THEME.muted;
3110
+ return /* @__PURE__ */ jsxs(Text, { color, children: [
3111
+ marker,
3112
+ " ",
3113
+ index + 1,
3114
+ ". ",
3115
+ tab.label
3116
+ ] }, tab.key);
3117
+ }) });
3118
+ }
3119
+ function NeuralField(props) {
3120
+ const { width, height, frame, animate } = props;
3121
+ const lines = useMemo(() => buildNeuralField(width, height, animate ? frame : 0), [width, height, frame, animate]);
3122
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, index) => {
3123
+ const tone = line.includes("~") || line.includes("*") || line.includes("+") || line.includes("o") || line.includes("O") ? THEME.accentCool : THEME.muted;
3124
+ return /* @__PURE__ */ jsx(Text, { color: tone, children: line }, index);
3125
+ }) });
3126
+ }
3127
+ function ProjectPanel(props) {
3128
+ const { snapshot, animate, dots, frame, scanIndex } = props;
3129
+ if (!snapshot) {
3130
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3131
+ /* @__PURE__ */ jsx(Text, { color: THEME.accentWarm, children: "Not in a Recursiv project." }),
3132
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "Run create-recursiv-app to start." })
3133
+ ] });
3134
+ }
3135
+ const status = animate ? `firing${dots}` : "quiet";
3136
+ const apiStatus = snapshot.apiKeyConfigured ? "linked" : "missing";
3137
+ const apiTone = snapshot.apiKeyConfigured ? THEME.accentCool : THEME.danger;
3138
+ const meter = buildPulseMeter(animate ? frame : 0, 16);
3139
+ const fields = [
3140
+ { label: "Name", value: snapshot.name },
3141
+ { label: "Template", value: snapshot.template },
3142
+ { label: "Framework", value: snapshot.framework },
3143
+ { label: "Dev port", value: String(snapshot.port) },
3144
+ { label: "Pkg mgr", value: snapshot.packageManager },
3145
+ { label: "API base", value: snapshot.apiBaseUrl, dim: true },
3146
+ { label: "API key", value: apiStatus, tone: apiTone },
3147
+ { label: "Status", value: status, tone: THEME.accent },
3148
+ { label: "Pulse", value: meter, tone: THEME.accentWarm }
3149
+ ];
3150
+ const activeIndex = fields.length > 0 ? scanIndex % fields.length : 0;
3151
+ const scanBar = buildScanBar(activeIndex, 18);
3152
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3153
+ fields.map((field, index) => {
3154
+ const distance = Math.min(
3155
+ Math.abs(index - activeIndex),
3156
+ fields.length - Math.abs(index - activeIndex)
3157
+ );
3158
+ const glow = animate && distance === 0 ? "strong" : animate && distance === 1 ? "soft" : "none";
3159
+ return /* @__PURE__ */ jsx(
3160
+ Field,
3161
+ {
3162
+ label: field.label,
3163
+ value: field.value,
3164
+ dim: field.dim,
3165
+ tone: field.tone,
3166
+ glow
3167
+ },
3168
+ field.label
3169
+ );
3170
+ }),
3171
+ /* @__PURE__ */ jsxs(Text, { color: THEME.accentCool, children: [
3172
+ "Neural scan ",
3173
+ scanBar
3174
+ ] }),
3175
+ /* @__PURE__ */ jsx(Divider, {}),
3176
+ /* @__PURE__ */ jsx(SectionTitle, { label: "Integrations" }),
3177
+ INTEGRATIONS.map((item) => /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3178
+ "- ",
3179
+ item
3180
+ ] }, item)),
3181
+ /* @__PURE__ */ jsx(Divider, {}),
3182
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "Dev cmd:" }),
3183
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: snapshot.devCommand })
3184
+ ] });
3185
+ }
3186
+ function Field(props) {
3187
+ const { label, value, dim, tone, glow = "none" } = props;
3188
+ const marker = glow === "strong" ? ">" : glow === "soft" ? "-" : " ";
3189
+ const paddedLabel = `${marker} ${label.padEnd(9, " ")}`;
3190
+ const labelColor = glow === "strong" ? THEME.accentWarm : glow === "soft" ? THEME.accent : THEME.muted;
3191
+ const valueColor = glow === "strong" ? THEME.bright : glow === "soft" ? THEME.accentCool : tone ?? (dim ? THEME.muted : void 0);
3192
+ return /* @__PURE__ */ jsxs(Text, { children: [
3193
+ /* @__PURE__ */ jsx(Text, { color: labelColor, children: paddedLabel }),
3194
+ /* @__PURE__ */ jsx(Text, { color: valueColor, children: value })
3195
+ ] });
3196
+ }
3197
+ function StatusPill(props) {
3198
+ const { label, value, tone } = props;
3199
+ return /* @__PURE__ */ jsxs(Text, { color: tone, children: [
3200
+ "[",
3201
+ label,
3202
+ ": ",
3203
+ value,
3204
+ "]"
3205
+ ] });
3206
+ }
3207
+ function Wordmark(props) {
3208
+ const { lines, palette } = props;
3209
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", children: lines.map((line, index) => /* @__PURE__ */ jsx(Text, { color: palette[index % palette.length] ?? THEME.accent, children: line }, index)) });
3210
+ }
3211
+ function ColorLine(props) {
3212
+ return /* @__PURE__ */ jsx(Text, { color: props.color, children: props.text });
3213
+ }
3214
+ function Aurora(props) {
3215
+ const { width, frame } = props;
3216
+ const lineWidth = Math.max(18, width);
3217
+ const lineA = buildAuroraLine(lineWidth, frame, 0);
3218
+ const lineB = buildInterferenceLine(lineWidth, frame + 7, 1);
3219
+ const lineC = buildAuroraLine(lineWidth, frame + 13, 2);
3220
+ const lineD = buildNeuronHalo(lineWidth, frame);
3221
+ const palette = rotatePalette(AURORA_COLORS, frame);
3222
+ const deepPalette = rotatePalette(AURORA_COLORS_DEEP, frame);
3223
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
3224
+ /* @__PURE__ */ jsx(ColorLine, { text: lineA, color: palette[0] }),
3225
+ /* @__PURE__ */ jsx(ColorLine, { text: lineB, color: deepPalette[0] }),
3226
+ /* @__PURE__ */ jsx(ColorLine, { text: lineC, color: palette[1] ?? palette[0] }),
3227
+ /* @__PURE__ */ jsx(ColorLine, { text: lineD, color: palette[2] ?? palette[0] })
3228
+ ] });
3229
+ }
3230
+ function Divider(props = {}) {
3231
+ const { width } = props;
3232
+ const lineWidth = Math.max(12, Math.min(32, width ?? 22));
3233
+ return /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "-".repeat(lineWidth) });
3234
+ }
3235
+ function AccentLine(props) {
3236
+ const { width, frame } = props;
3237
+ const lineWidth = Math.max(10, width);
3238
+ const head = Math.min(frame % lineWidth, lineWidth - 1);
3239
+ const left = "-".repeat(head);
3240
+ const right = "-".repeat(Math.max(0, lineWidth - head - 1));
3241
+ return /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3242
+ left,
3243
+ /* @__PURE__ */ jsx(Text, { color: THEME.accent, children: "*" }),
3244
+ right
3245
+ ] });
3246
+ }
3247
+ function AnimatedMarkdown(props) {
3248
+ const { content, animate } = props;
3249
+ const safe = useMemo(() => content.replace(/\r\n/g, "\n"), [content]);
3250
+ const lines = useMemo(() => safe.split("\n"), [safe]);
3251
+ const [visibleLines, setVisibleLines] = useState(animate ? 1 : lines.length);
3252
+ useEffect(() => {
3253
+ if (!animate) {
3254
+ setVisibleLines(lines.length);
3255
+ return;
3256
+ }
3257
+ setVisibleLines(1);
3258
+ const id = setInterval(() => {
3259
+ setVisibleLines((current) => current >= lines.length ? current : current + 1);
3260
+ }, 55);
3261
+ return () => clearInterval(id);
3262
+ }, [animate, lines.length, safe]);
3263
+ const sliceCount = Math.max(1, visibleLines);
3264
+ const text = animate ? lines.slice(0, sliceCount).join("\n") : safe;
3265
+ return /* @__PURE__ */ jsx(MarkdownBlock, { content: text });
3266
+ }
3267
+ function MarkdownBlock(props) {
3268
+ const lines = props.content.split("\n");
3269
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: lines.map((line, index) => {
3270
+ if (line.startsWith("### ")) {
3271
+ return /* @__PURE__ */ jsx(Text, { color: THEME.accentCool, children: line.slice(4) }, index);
3272
+ }
3273
+ if (line.startsWith("## ")) {
3274
+ return /* @__PURE__ */ jsx(Text, { color: THEME.accentWarm, children: line.slice(3) }, index);
3275
+ }
3276
+ if (line.startsWith("# ")) {
3277
+ return /* @__PURE__ */ jsx(Text, { color: THEME.accent, children: line.slice(2) }, index);
3278
+ }
3279
+ if (line.startsWith("- ")) {
3280
+ return /* @__PURE__ */ jsxs(Text, { color: THEME.muted, children: [
3281
+ "- ",
3282
+ line.slice(2)
3283
+ ] }, index);
3284
+ }
3285
+ if (/^\d+\.\s/.test(line)) {
3286
+ return /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: line }, index);
3287
+ }
3288
+ if (line.trim().length === 0) {
3289
+ return /* @__PURE__ */ jsx(Text, { children: " " }, index);
3290
+ }
3291
+ return /* @__PURE__ */ jsx(Text, { color: THEME.bright, children: line }, index);
3292
+ }) });
3293
+ }
3294
+ function useTicker(animate, intervalMs, length) {
3295
+ const [tick, setTick] = useState(0);
3296
+ useEffect(() => {
3297
+ if (!animate || length <= 0) {
3298
+ setTick(0);
3299
+ return;
3300
+ }
3301
+ const id = setInterval(() => {
3302
+ setTick((current) => (current + 1) % length);
3303
+ }, intervalMs);
3304
+ return () => clearInterval(id);
3305
+ }, [animate, intervalMs, length]);
3306
+ return tick;
3307
+ }
3308
+ function tabIndex(tab) {
3309
+ return TABS.findIndex((item) => item.key === tab);
3310
+ }
3311
+ function tabTitle(tab) {
3312
+ const active = TABS.find((item) => item.key === tab);
3313
+ return active ? active.label : "Overview";
3314
+ }
3315
+ function buildNeuralField(width, height, frame) {
3316
+ const safeWidth = Math.max(8, width);
3317
+ const safeHeight = Math.max(4, height);
3318
+ const grid = Array.from({ length: safeHeight }, () => Array(safeWidth).fill(" "));
3319
+ const phase = Math.floor(frame / 6);
3320
+ const rng = makeRng(phase * 109 + 97);
3321
+ const nodes = [];
3322
+ const place = (x, y, char, priority = 0) => {
3323
+ if (x < 0 || x >= safeWidth || y < 0 || y >= safeHeight) return;
3324
+ const current = grid[y][x];
3325
+ const currentPriority = current === "*" || current === "O" ? 3 : current === "o" ? 2 : current === "~" ? 1 : 0;
3326
+ if (priority >= currentPriority || current === " ") {
3327
+ grid[y][x] = char;
3328
+ }
3329
+ };
3330
+ for (let y = 0; y < safeHeight; y += 1) {
3331
+ for (let x = 0; x < safeWidth; x += 1) {
3332
+ const roll = rng();
3333
+ if (roll < 0.018) {
3334
+ grid[y][x] = "*";
3335
+ nodes.push({ x, y });
3336
+ } else if (roll < 0.04) {
3337
+ grid[y][x] = "o";
3338
+ nodes.push({ x, y });
3339
+ } else if (roll < 0.12) {
3340
+ grid[y][x] = ".";
3341
+ }
3342
+ }
3343
+ }
3344
+ const mid = Math.floor(safeHeight / 2);
3345
+ const amp = Math.max(1, Math.floor(safeHeight / 3));
3346
+ for (let x = 0; x < safeWidth; x += 1) {
3347
+ const y = Math.round(mid + Math.sin((x + frame * 0.6) / 3) * amp);
3348
+ place(x, y, "~", 1);
3349
+ if ((x + frame) % 11 === 0) place(x, y, "*", 3);
3350
+ }
3351
+ const mid2 = Math.floor(safeHeight / 3);
3352
+ const amp2 = Math.max(1, Math.floor(safeHeight / 4));
3353
+ for (let x = 0; x < safeWidth; x += 1) {
3354
+ const y = Math.round(mid2 + Math.sin((x + frame * 0.4) / 2.6) * amp2);
3355
+ place(x, y, "~", 1);
3356
+ if ((x + frame * 2) % 17 === 0) place(x, y, "o", 2);
3357
+ }
3358
+ const particleCount = Math.max(2, Math.floor(safeWidth / 12));
3359
+ for (let i = 0; i < particleCount; i += 1) {
3360
+ const particleX = (frame * (i + 2) + i * 7) % safeWidth;
3361
+ const particleY = (frame * (i + 3) + i * 5) % safeHeight;
3362
+ place(particleX, particleY, "+", 2);
3363
+ }
3364
+ nodes.forEach((node) => {
3365
+ if (rng() < 0.35) {
3366
+ const length = 2 + Math.floor(rng() * 6);
3367
+ const drift = rng() < 0.5 ? 1 : -1;
3368
+ for (let step = 1; step <= length; step += 1) {
3369
+ const x = node.x + step;
3370
+ const y = node.y + (rng() < 0.3 ? drift : 0);
3371
+ place(x, y, rng() < 0.5 ? "-" : "=", 0);
3372
+ }
3373
+ }
3374
+ if ((node.x + frame) % 14 === 0) {
3375
+ place(node.x, node.y, "O", 3);
3376
+ }
3377
+ });
3378
+ return grid.map((row) => row.join(""));
3379
+ }
3380
+ function buildAuroraLine(width, frame, offset) {
3381
+ const safeWidth = Math.max(10, width);
3382
+ const chars = [" ", " ", ".", ":", "-", "=", "+", "*"];
3383
+ let line = "";
3384
+ for (let x = 0; x < safeWidth; x += 1) {
3385
+ const phase = (x + frame * 0.9 + offset * 7) / 4;
3386
+ const wave = Math.sin(phase) * 0.6 + Math.sin(phase / 2 + offset) * 0.4 + 1;
3387
+ const normalized = Math.max(0, Math.min(1, wave / 2));
3388
+ const idx = Math.min(chars.length - 1, Math.floor(normalized * (chars.length - 1)));
3389
+ line += chars[idx];
3390
+ }
3391
+ return line;
3392
+ }
3393
+ function buildInterferenceLine(width, frame, offset) {
3394
+ const safeWidth = Math.max(10, width);
3395
+ const chars = [" ", ".", ":", "-", "=", "+", "*", "#"];
3396
+ let line = "";
3397
+ for (let x = 0; x < safeWidth; x += 1) {
3398
+ const waveA = Math.sin((x + frame * 0.6 + offset * 5) / 3);
3399
+ const waveB = Math.sin((x * 0.6 + frame * 0.3) / 2.1);
3400
+ const waveC = Math.sin((x * 1.2 + frame * 0.9) / 4.6);
3401
+ const mix = (waveA + waveB + waveC + 3) / 6;
3402
+ const idx = Math.min(chars.length - 1, Math.floor(mix * (chars.length - 1)));
3403
+ line += chars[idx];
3404
+ }
3405
+ return line;
3406
+ }
3407
+ function buildNeuronHalo(width, frame) {
3408
+ const safeWidth = Math.max(10, width);
3409
+ const rng = makeRng((Math.floor(frame / 4) + 11) * 233);
3410
+ let line = "";
3411
+ for (let x = 0; x < safeWidth; x += 1) {
3412
+ const roll = rng();
3413
+ if (roll < 0.04) line += "*";
3414
+ else if (roll < 0.08) line += "o";
3415
+ else if (roll < 0.11) line += ".";
3416
+ else line += " ";
3417
+ }
3418
+ return line;
3419
+ }
3420
+ function buildPulseMeter(frame, width) {
3421
+ const safeWidth = Math.max(8, width);
3422
+ const head = frame % safeWidth;
3423
+ let line = "";
3424
+ for (let i = 0; i < safeWidth; i += 1) {
3425
+ if (i === head) {
3426
+ line += "*";
3427
+ } else if (Math.abs(i - head) === 1) {
3428
+ line += "+";
3429
+ } else if ((i + frame) % 5 === 0) {
3430
+ line += "-";
3431
+ } else {
3432
+ line += ".";
3433
+ }
3434
+ }
3435
+ return line;
3436
+ }
3437
+ function buildScanBar(position, width) {
3438
+ const safeWidth = Math.max(8, width);
3439
+ if (position < 0) return "[" + ".".repeat(safeWidth) + "]";
3440
+ const head = position % safeWidth;
3441
+ let line = "";
3442
+ for (let i = 0; i < safeWidth; i += 1) {
3443
+ if (i === head) line += ">";
3444
+ else if (Math.abs(i - head) === 1) line += "=";
3445
+ else line += ".";
3446
+ }
3447
+ return `[${line}]`;
3448
+ }
3449
+ function makeRng(seed) {
3450
+ let state = seed >>> 0;
3451
+ return () => {
3452
+ state = state * 1664525 + 1013904223 >>> 0;
3453
+ return state / 4294967295;
3454
+ };
3455
+ }
3456
+ function rotatePalette(palette, frame) {
3457
+ if (palette.length === 0) return palette;
3458
+ const offset = frame % palette.length;
3459
+ return [...palette.slice(offset), ...palette.slice(0, offset)];
3460
+ }
3461
+ function useProgress(active, durationMs, stepMs) {
3462
+ const [progress, setProgress] = useState(active ? 0 : 1);
3463
+ useEffect(() => {
3464
+ if (!active) {
3465
+ setProgress(1);
3466
+ return;
3467
+ }
3468
+ setProgress(0);
3469
+ const start = Date.now();
3470
+ const id = setInterval(() => {
3471
+ const value = Math.min(1, (Date.now() - start) / durationMs);
3472
+ setProgress(value);
3473
+ if (value >= 1) clearInterval(id);
3474
+ }, stepMs);
3475
+ return () => clearInterval(id);
3476
+ }, [active, durationMs, stepMs]);
3477
+ return progress;
3478
+ }
3479
+ function IntroScreen(props) {
3480
+ const { width, height, progress, frame } = props;
3481
+ const fieldWidth = Math.max(24, width - 6);
3482
+ const fieldHeight = Math.max(10, Math.min(14, height));
3483
+ const steps = INTRO_STEPS.map((label, index) => ({
3484
+ label,
3485
+ done: progress >= (index + 1) / INTRO_STEPS.length
3486
+ }));
3487
+ const bar = buildProgressBar(progress, Math.max(16, Math.min(34, Math.floor(width / 3))));
3488
+ const palette = rotatePalette(WORDMARK_COLORS, frame);
3489
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
3490
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", children: [
3491
+ /* @__PURE__ */ jsx(Wordmark, { lines: WORDMARK_LINES, palette }),
3492
+ /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: "Neural ignition sequence" })
3493
+ ] }),
3494
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", alignItems: "center", children: /* @__PURE__ */ jsx(ColorLine, { text: bar, color: THEME.accentWarm }) }),
3495
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, flexDirection: "column", children: steps.map((step, index) => /* @__PURE__ */ jsxs(Text, { color: step.done ? THEME.accent : THEME.muted, children: [
3496
+ "[",
3497
+ step.done ? "x" : " ",
3498
+ "] ",
3499
+ step.label
3500
+ ] }, index)) }),
3501
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(NeuralField, { width: fieldWidth, height: fieldHeight, frame, animate: true }) }),
3502
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: INTRO_HINTS.map((hint) => /* @__PURE__ */ jsx(Text, { color: THEME.muted, children: hint }, hint)) })
3503
+ ] });
3504
+ }
3505
+ function buildProgressBar(progress, width) {
3506
+ const safeWidth = Math.max(10, width);
3507
+ const filled = Math.round(progress * safeWidth);
3508
+ const left = "=".repeat(filled);
3509
+ const right = ".".repeat(Math.max(0, safeWidth - filled));
3510
+ return `[${left}${right}]`;
3511
+ }
3512
+
3513
+ // src/commands/brain.tsx
3514
+ import { jsx as jsx2 } from "react/jsx-runtime";
3515
+ async function brainCommand(opts) {
3516
+ const cwd = process.cwd();
3517
+ const config = await readConfig(cwd);
3518
+ const apiKey = await readApiKeyFromEnv(cwd);
3519
+ const packageManager = detectPackageManager(cwd);
3520
+ const snapshot = config ? {
3521
+ name: config.project.name,
3522
+ template: config.project.template,
3523
+ framework: config.project.framework,
3524
+ port: config.dev.port,
3525
+ devCommand: config.dev.command,
3526
+ apiBaseUrl: config.api.baseUrl,
3527
+ apiKeyConfigured: Boolean(apiKey),
3528
+ packageManager
3529
+ } : null;
3530
+ const app = render(/* @__PURE__ */ jsx2(App, { snapshot, animate: opts.anim !== false }));
3531
+ await app.waitUntilExit();
3532
+ }
3533
+
3534
+ // src/commands/studio.tsx
3535
+ async function studioCommand(opts) {
3536
+ await brainCommand(opts);
3537
+ }
3538
+
3539
+ // src/commands/browser-use.ts
3540
+ import pc8 from "picocolors";
3541
+ import { BrowserUse } from "browser-use-sdk";
3542
+ function getClient() {
3543
+ const apiKey = process.env.BROWSER_USE_API_KEY;
3544
+ if (!apiKey) {
3545
+ throw new Error("BROWSER_USE_API_KEY is required");
3546
+ }
3547
+ return new BrowserUse({
3548
+ apiKey,
3549
+ baseUrl: process.env.BROWSER_USE_BASE_URL || void 0
3550
+ });
3551
+ }
3552
+ async function browserUseRunCommand(opts) {
3553
+ if (!opts.task) {
3554
+ throw new Error("--task is required");
3555
+ }
3556
+ const client = getClient();
3557
+ const result = await client.run(opts.task, {
3558
+ startUrl: opts.startUrl,
3559
+ maxSteps: opts.maxSteps ? Number.parseInt(opts.maxSteps, 10) : void 0,
3560
+ timeout: opts.wait === false ? 1e4 : 3e5
3561
+ });
3562
+ if (opts.json) {
3563
+ console.log(JSON.stringify(result, null, 2));
3564
+ return;
3565
+ }
3566
+ console.log(pc8.green("Browser Use task complete"));
3567
+ console.log(`${pc8.dim("Task:")} ${opts.task}`);
3568
+ console.log(`${pc8.dim("Status:")} ${result.status}`);
3569
+ if (result.output) {
3570
+ console.log(`${pc8.dim("Output:")} ${String(result.output)}`);
3571
+ }
3572
+ }
3573
+ async function browserUseMcpSnippetCommand(opts) {
3574
+ const target = opts.target || "generic";
3575
+ if (target === "generic") {
3576
+ console.log(JSON.stringify({
3577
+ name: "browser-use",
3578
+ transport: "streamable-http",
3579
+ url: "https://api.browser-use.com/mcp",
3580
+ headers: {
3581
+ Authorization: "Bearer ${BROWSER_USE_API_KEY}"
3582
+ }
3583
+ }, null, 2));
3584
+ return;
3585
+ }
3586
+ if (target === "codex") {
3587
+ console.log([
3588
+ "[mcp_servers.browser_use]",
3589
+ 'url = "https://api.browser-use.com/mcp"',
3590
+ "",
3591
+ "[mcp_servers.browser_use.http_headers]",
3592
+ 'Authorization = "Bearer ${BROWSER_USE_API_KEY}"'
3593
+ ].join("\n"));
3594
+ return;
3595
+ }
3596
+ if (target === "claude-code") {
3597
+ console.log(JSON.stringify({
3598
+ mcpServers: {
3599
+ "browser-use": {
3600
+ type: "http",
3601
+ url: "https://api.browser-use.com/mcp",
3602
+ headers: {
3603
+ Authorization: "Bearer ${BROWSER_USE_API_KEY}"
3604
+ }
3605
+ }
3606
+ }
3607
+ }, null, 2));
3608
+ return;
3609
+ }
3610
+ throw new Error(`Unsupported target "${target}". Use generic, codex, or claude-code.`);
3611
+ }
3612
+
3613
+ // src/commands/api.ts
3614
+ import pc10 from "picocolors";
3615
+
3616
+ // src/lib/api.ts
3617
+ init_config();
3618
+ init_env();
3619
+ init_logger();
3620
+ import pc9 from "picocolors";
3621
+ async function getApiContext(cwd) {
3622
+ const config = await readConfig(cwd);
3623
+ const apiKey = await readApiKeyFromEnv(cwd);
3624
+ if (!apiKey) {
3625
+ log.error("No API key configured.");
3626
+ log.info(`Run ${pc9.bold("recursiv auth login")} to set your key.`);
3627
+ process.exit(1);
3628
+ }
3629
+ return {
3630
+ baseUrl: config?.api.baseUrl ?? "https://api.recursiv.io/api/v1",
3631
+ apiKey
3632
+ };
3633
+ }
3634
+ async function apiRequest(ctx, opts) {
3635
+ const url = new URL(opts.path, ctx.baseUrl.endsWith("/") ? ctx.baseUrl : ctx.baseUrl + "/");
3636
+ if (opts.query) {
3637
+ Object.entries(opts.query).forEach(([key, value]) => {
3638
+ if (value === void 0 || value === null || value === "") return;
3639
+ url.searchParams.set(key, String(value));
3640
+ });
3641
+ }
3642
+ const res = await fetch(url, {
3643
+ method: opts.method,
3644
+ headers: {
3645
+ Authorization: `Bearer ${ctx.apiKey}`,
3646
+ Accept: "application/json",
3647
+ ...opts.body ? { "Content-Type": "application/json" } : {}
3648
+ },
3649
+ body: opts.body ? JSON.stringify(opts.body) : void 0
3650
+ });
3651
+ const text = await res.text();
3652
+ const payload = safeJson(text);
3653
+ if (!res.ok) {
3654
+ const message = payload?.error?.message || payload?.message || `Request failed (${res.status} ${res.statusText})`;
3655
+ throw new Error(message);
3656
+ }
3657
+ return payload ?? {};
3658
+ }
3659
+ function safeJson(text) {
3660
+ if (!text) return null;
3661
+ try {
3662
+ return JSON.parse(text);
3663
+ } catch {
3664
+ return null;
3665
+ }
3666
+ }
3667
+
3668
+ // src/commands/api.ts
3669
+ init_logger();
3670
+ async function usersMeCommand(opts) {
3671
+ const ctx = await getApiContext(process.cwd());
3672
+ const res = await apiRequest(ctx, { method: "GET", path: "users/me" });
3673
+ output(res, opts.json, (data) => {
3674
+ log.blank();
3675
+ console.log(pc10.bold("User"));
3676
+ console.log(` ${pc10.dim("id")}: ${data.id}`);
3677
+ console.log(` ${pc10.dim("name")}: ${data.name ?? "-"}`);
3678
+ console.log(` ${pc10.dim("username")}: ${data.username ?? "-"}`);
3679
+ console.log(` ${pc10.dim("email")}: ${data.email ?? "-"}`);
3680
+ console.log(` ${pc10.dim("followers")}: ${data.followers_count ?? 0}`);
3681
+ console.log(` ${pc10.dim("following")}: ${data.following_count ?? 0}`);
3682
+ console.log(` ${pc10.dim("posts")}: ${data.posts_count ?? 0}`);
3683
+ log.blank();
3684
+ });
3685
+ }
3686
+ async function communitiesListCommand(opts) {
3687
+ const ctx = await getApiContext(process.cwd());
3688
+ const res = await apiRequest(ctx, {
3689
+ method: "GET",
3690
+ path: "communities",
3691
+ query: {
3692
+ limit: opts.limit,
3693
+ offset: opts.offset
3694
+ }
3695
+ });
3696
+ output(res, opts.json, (data, meta) => {
3697
+ log.blank();
3698
+ console.log(pc10.bold("Communities"));
3699
+ data.forEach((comm) => {
3700
+ const name = pc10.bold(comm.name);
3701
+ const slug = comm.slug ? pc10.dim(`/${comm.slug}`) : "";
3702
+ const members = pc10.dim(`members ${comm.member_count ?? 0}`);
3703
+ console.log(` ${name} ${slug} ${members}`);
3704
+ if (comm.description) console.log(` ${pc10.dim(comm.description)}`);
3705
+ });
3706
+ printMeta(meta);
3707
+ });
3708
+ }
3709
+ async function communitiesCreateCommand(opts) {
3710
+ if (!opts.name || !opts.slug) {
3711
+ log.error("Missing required flags: --name and --slug");
3712
+ process.exit(1);
3713
+ }
3714
+ const ctx = await getApiContext(process.cwd());
3715
+ const res = await apiRequest(ctx, {
3716
+ method: "POST",
3717
+ path: "communities",
3718
+ body: {
3719
+ name: opts.name,
3720
+ slug: opts.slug,
3721
+ description: opts.description,
3722
+ privacy: opts.privacy ?? "public"
3723
+ }
3724
+ });
3725
+ output(res, opts.json, (data) => {
3726
+ log.success(`Community created: ${pc10.bold(data.name)} (${data.id})`);
3727
+ });
3728
+ }
3729
+ async function postsListCommand(opts) {
3730
+ const ctx = await getApiContext(process.cwd());
3731
+ const res = await apiRequest(ctx, {
3732
+ method: "GET",
3733
+ path: "posts",
3734
+ query: {
3735
+ limit: opts.limit,
3736
+ offset: opts.offset,
3737
+ community_id: opts.communityId,
3738
+ author_id: opts.authorId
3739
+ }
3740
+ });
3741
+ output(res, opts.json, (data, meta) => {
3742
+ log.blank();
3743
+ console.log(pc10.bold("Posts"));
3744
+ data.forEach((post) => {
3745
+ const author = post.author?.username ? `@${post.author.username}` : "unknown";
3746
+ const content = truncate(post.content ?? "", 100);
3747
+ console.log(` ${pc10.bold(author)} ${pc10.dim(post.id)}`);
3748
+ console.log(` ${content}`);
3749
+ });
3750
+ printMeta(meta);
3751
+ });
3752
+ }
3753
+ async function postsCreateCommand(opts) {
3754
+ if (!opts.content) {
3755
+ log.error("Missing required flag: --content");
3756
+ process.exit(1);
3757
+ }
3758
+ const ctx = await getApiContext(process.cwd());
3759
+ const tagIds = opts.tagIds ? opts.tagIds.split(",").map((id) => id.trim()).filter(Boolean) : void 0;
3760
+ const res = await apiRequest(ctx, {
3761
+ method: "POST",
3762
+ path: "posts",
3763
+ body: {
3764
+ content: opts.content,
3765
+ content_format: opts.markdown ? "markdown" : "plain",
3766
+ community_id: opts.communityId,
3767
+ organization_id: opts.organizationId,
3768
+ reply_to_id: opts.replyToId,
3769
+ tag_ids: tagIds
3770
+ }
3771
+ });
3772
+ output(res, opts.json, (data) => {
3773
+ log.success(`Post created: ${pc10.dim(data.id)}`);
3774
+ });
3775
+ }
3776
+ async function projectsListCommand(opts) {
3777
+ const ctx = await getApiContext(process.cwd());
3778
+ const res = await apiRequest(ctx, {
3779
+ method: "GET",
3780
+ path: "projects",
3781
+ query: {
3782
+ limit: opts.limit,
3783
+ offset: opts.offset,
3784
+ organization_id: opts.organizationId
3785
+ }
3786
+ });
3787
+ output(res, opts.json, (data, meta) => {
3788
+ log.blank();
3789
+ console.log(pc10.bold("Projects"));
3790
+ data.forEach((proj) => {
3791
+ const name = pc10.bold(proj.name);
3792
+ const slug = proj.slug ? pc10.dim(`/${proj.slug}`) : "";
3793
+ const org = proj.organization?.slug ? pc10.dim(`org ${proj.organization.slug}`) : "";
3794
+ console.log(` ${name} ${slug} ${org}`);
3795
+ });
3796
+ printMeta(meta);
3797
+ });
3798
+ }
3799
+ async function projectsCreateCommand(opts) {
3800
+ if (!opts.organizationId || !opts.name) {
3801
+ log.error("Missing required flags: --org and --name");
3802
+ process.exit(1);
3803
+ }
3804
+ const ctx = await getApiContext(process.cwd());
3805
+ const res = await apiRequest(ctx, {
3806
+ method: "POST",
3807
+ path: "projects",
3808
+ body: {
3809
+ organization_id: opts.organizationId,
3810
+ name: opts.name,
3811
+ slug: opts.slug,
3812
+ repo_url: opts.repoUrl,
3813
+ template_source: opts.templateSource ?? "default",
3814
+ custom_template_url: opts.templateUrl
3815
+ }
3816
+ });
3817
+ output(res, opts.json, (data) => {
3818
+ log.success(`Project created: ${pc10.bold(data.name)} (${data.id})`);
3819
+ });
3820
+ }
3821
+ async function agentsListCommand(opts) {
3822
+ const ctx = await getApiContext(process.cwd());
3823
+ const res = await apiRequest(ctx, {
3824
+ method: "GET",
3825
+ path: "agents",
3826
+ query: {
3827
+ limit: opts.limit,
3828
+ offset: opts.offset
3829
+ }
3830
+ });
3831
+ output(res, opts.json, (data, meta) => {
3832
+ log.blank();
3833
+ console.log(pc10.bold("Agents"));
3834
+ data.forEach((agent) => {
3835
+ const name = pc10.bold(agent.name);
3836
+ const handle = agent.username ? pc10.dim(`@${agent.username}`) : "";
3837
+ const model = agent.model ? pc10.dim(agent.model) : "";
3838
+ console.log(` ${name} ${handle} ${model}`);
3839
+ });
3840
+ printMeta(meta);
3841
+ });
3842
+ }
3843
+ async function agentsCreateCommand(opts) {
3844
+ if (!opts.name || !opts.username) {
3845
+ log.error("Missing required flags: --name and --username");
3846
+ process.exit(1);
3847
+ }
3848
+ const ctx = await getApiContext(process.cwd());
3849
+ const res = await apiRequest(ctx, {
3850
+ method: "POST",
3851
+ path: "agents",
3852
+ body: {
3853
+ name: opts.name,
3854
+ username: opts.username,
3855
+ bio: opts.bio,
3856
+ model: opts.model,
3857
+ system_prompt: opts.systemPrompt,
3858
+ social_mode: opts.socialMode,
3859
+ post_frequency: opts.postFrequency,
3860
+ tool_mode: opts.toolMode,
3861
+ daily_request_limit: opts.dailyRequestLimit ? Number(opts.dailyRequestLimit) : void 0,
3862
+ organization_id: opts.organizationId
3863
+ }
3864
+ });
3865
+ output(res, opts.json, (data) => {
3866
+ log.success(`Agent created: ${pc10.bold(data.name)} (${data.id})`);
3867
+ });
3868
+ }
3869
+ function output(res, json, render2) {
3870
+ if (json) {
3871
+ console.log(JSON.stringify(res, null, 2));
3872
+ return;
3873
+ }
3874
+ render2(res.data, res.meta);
3875
+ }
3876
+ function printMeta(meta) {
3877
+ if (!meta) return;
3878
+ log.blank();
3879
+ console.log(pc10.dim(`limit ${meta.limit ?? 0} offset ${meta.offset ?? 0} has_more ${meta.has_more ?? false}`));
3880
+ log.blank();
3881
+ }
3882
+ function truncate(value, max) {
3883
+ if (value.length <= max) return value;
3884
+ return value.slice(0, max - 1) + "\u2026";
3885
+ }
3886
+
3887
+ // src/bin/recursiv.ts
3888
+ var program = new Command();
3889
+ program.name("recursiv").description("Recursiv CLI \u2014 build and ship apps with AI").version("0.1.0");
3890
+ program.command("chat").description("Start the chat-first AI agent (default when no command is given)").option("--model <model>", "Claude model to use (default: claude-sonnet-4-20250514)").option("--resume", "Resume the most recent session").action(async (opts) => {
3891
+ try {
3892
+ await launchChatAgent({ model: opts.model, resume: opts.resume });
3893
+ } catch (err) {
3894
+ console.error(err instanceof Error ? err.message : err);
3895
+ process.exit(1);
3896
+ }
3897
+ });
3898
+ program.command("create [name]").description("Create a new Recursiv application").action(async (name) => {
3899
+ try {
3900
+ await initCommand(name);
3901
+ } catch (err) {
3902
+ console.error(err instanceof Error ? err.message : err);
3903
+ process.exit(1);
3904
+ }
3905
+ });
3906
+ program.command("dev").description("Start the development server").option("-p, --port <port>", "port to run on").action(async (opts) => {
3907
+ try {
3908
+ await devCommand(opts);
3909
+ } catch (err) {
3910
+ console.error(err instanceof Error ? err.message : err);
3911
+ process.exit(1);
3912
+ }
3913
+ });
3914
+ program.command("deploy <target>").description("Generate deployment config (vercel, docker, render, railway, cloud)").action(async (target) => {
3915
+ try {
3916
+ await deployCommand(target);
3917
+ } catch (err) {
3918
+ console.error(err instanceof Error ? err.message : err);
3919
+ process.exit(1);
3920
+ }
3921
+ });
3922
+ var auth = program.command("auth").description("Manage API authentication");
3923
+ auth.command("login").description("Set or update your API key").action(async () => {
3924
+ try {
3925
+ await loginCommand();
3926
+ } catch (err) {
3927
+ console.error(err instanceof Error ? err.message : err);
3928
+ process.exit(1);
3929
+ }
3930
+ });
3931
+ auth.command("logout").description("Remove API key from .env").action(async () => {
3932
+ try {
3933
+ await logoutCommand();
3934
+ } catch (err) {
3935
+ console.error(err instanceof Error ? err.message : err);
3936
+ process.exit(1);
3937
+ }
3938
+ });
3939
+ auth.command("whoami").description("Show current authenticated user").action(async () => {
3940
+ try {
3941
+ await whoamiCommand();
3942
+ } catch (err) {
3943
+ console.error(err instanceof Error ? err.message : err);
3944
+ process.exit(1);
3945
+ }
3946
+ });
3947
+ program.command("info").description("Display project information").action(async () => {
3948
+ try {
3949
+ await infoCommand();
3950
+ } catch (err) {
3951
+ console.error(err instanceof Error ? err.message : err);
3952
+ process.exit(1);
3953
+ }
3954
+ });
3955
+ var browserUse = program.command("browser-use").description("Browser Use helpers");
3956
+ browserUse.command("run").description("Run a Browser Use task with the official SDK").requiredOption("--task <task>", "task to run").option("--start-url <url>", "initial URL").option("--max-steps <n>", "maximum Browser Use steps").option("--no-wait", "return quickly instead of waiting for the full result").option("--json", "Output JSON").action(async (opts) => {
3957
+ try {
3958
+ await browserUseRunCommand(opts);
3959
+ } catch (err) {
3960
+ console.error(err instanceof Error ? err.message : err);
3961
+ process.exit(1);
3962
+ }
3963
+ });
3964
+ browserUse.command("mcp-snippet").description("Print Browser Use MCP configuration snippets").option("--target <target>", "generic | codex | claude-code", "generic").action(async (opts) => {
3965
+ try {
3966
+ await browserUseMcpSnippetCommand(opts);
3967
+ } catch (err) {
3968
+ console.error(err instanceof Error ? err.message : err);
3969
+ process.exit(1);
3970
+ }
3971
+ });
3972
+ var users = program.command("users").description("Users API");
3973
+ users.command("me").description("Show current authenticated user").option("--json", "Output JSON").action(async (opts) => {
3974
+ try {
3975
+ await usersMeCommand(opts);
3976
+ } catch (err) {
3977
+ console.error(err instanceof Error ? err.message : err);
3978
+ process.exit(1);
3979
+ }
3980
+ });
3981
+ var communities = program.command("communities").description("Communities API");
3982
+ communities.command("list").description("List communities").option("--limit <limit>", "number of results").option("--offset <offset>", "pagination offset").option("--json", "Output JSON").action(async (opts) => {
3983
+ try {
3984
+ await communitiesListCommand(opts);
3985
+ } catch (err) {
3986
+ console.error(err instanceof Error ? err.message : err);
3987
+ process.exit(1);
3988
+ }
3989
+ });
3990
+ communities.command("create").description("Create a community").requiredOption("--name <name>", "community name").requiredOption("--slug <slug>", "community slug").option("--description <description>", "description").option("--privacy <privacy>", "public | private | hidden", "public").option("--json", "Output JSON").action(async (opts) => {
3991
+ try {
3992
+ await communitiesCreateCommand(opts);
3993
+ } catch (err) {
3994
+ console.error(err instanceof Error ? err.message : err);
3995
+ process.exit(1);
3996
+ }
3997
+ });
3998
+ var posts = program.command("posts").description("Posts API");
3999
+ posts.command("list").description("List posts").option("--community-id <id>", "filter by community").option("--author-id <id>", "filter by author").option("--limit <limit>", "number of results").option("--offset <offset>", "pagination offset").option("--json", "Output JSON").action(async (opts) => {
4000
+ try {
4001
+ await postsListCommand(opts);
4002
+ } catch (err) {
4003
+ console.error(err instanceof Error ? err.message : err);
4004
+ process.exit(1);
4005
+ }
4006
+ });
4007
+ posts.command("create").description("Create a post").requiredOption("--content <content>", "post content").option("--markdown", "content is markdown").option("--community-id <id>", "community id").option("--organization-id <id>", "organization id").option("--reply-to-id <id>", "reply to post id").option("--tag-ids <ids>", "comma-separated tag ids").option("--json", "Output JSON").action(async (opts) => {
4008
+ try {
4009
+ await postsCreateCommand(opts);
4010
+ } catch (err) {
4011
+ console.error(err instanceof Error ? err.message : err);
4012
+ process.exit(1);
4013
+ }
4014
+ });
4015
+ var projects = program.command("projects").description("Projects API");
4016
+ projects.command("list").description("List projects").option("--org <id>", "organization id").option("--limit <limit>", "number of results").option("--offset <offset>", "pagination offset").option("--json", "Output JSON").action(async (opts) => {
4017
+ try {
4018
+ await projectsListCommand({
4019
+ organizationId: opts.org,
4020
+ limit: opts.limit,
4021
+ offset: opts.offset,
4022
+ json: opts.json
4023
+ });
4024
+ } catch (err) {
4025
+ console.error(err instanceof Error ? err.message : err);
4026
+ process.exit(1);
4027
+ }
4028
+ });
4029
+ projects.command("create").description("Create a project").requiredOption("--org <id>", "organization id").requiredOption("--name <name>", "project name").option("--slug <slug>", "project slug").option("--repo <url>", "repo URL").option("--template <source>", "default | custom", "default").option("--template-url <url>", "custom template URL").option("--json", "Output JSON").action(async (opts) => {
4030
+ try {
4031
+ await projectsCreateCommand({
4032
+ organizationId: opts.org,
4033
+ name: opts.name,
4034
+ slug: opts.slug,
4035
+ repoUrl: opts.repo,
4036
+ templateSource: opts.template,
4037
+ templateUrl: opts.templateUrl,
4038
+ json: opts.json
4039
+ });
4040
+ } catch (err) {
4041
+ console.error(err instanceof Error ? err.message : err);
4042
+ process.exit(1);
4043
+ }
4044
+ });
4045
+ var agents = program.command("agents").description("Agents API");
4046
+ agents.command("list").description("List agents").option("--limit <limit>", "number of results").option("--offset <offset>", "pagination offset").option("--json", "Output JSON").action(async (opts) => {
4047
+ try {
4048
+ await agentsListCommand(opts);
4049
+ } catch (err) {
4050
+ console.error(err instanceof Error ? err.message : err);
4051
+ process.exit(1);
4052
+ }
4053
+ });
4054
+ agents.command("create").description("Create an AI agent").requiredOption("--name <name>", "agent name").requiredOption("--username <username>", "agent username").option("--bio <bio>", "agent bio").option("--model <model>", "model id").option("--system-prompt <prompt>", "system prompt").option("--social-mode <mode>", "chat_only | chat_post").option("--post-frequency <freq>", "never | light | medium | heavy").option("--tool-mode <mode>", "chat_only | permission | autonomous").option("--daily-request-limit <n>", "daily request limit").option("--org <id>", "organization id").option("--json", "Output JSON").action(async (opts) => {
4055
+ try {
4056
+ await agentsCreateCommand({
4057
+ name: opts.name,
4058
+ username: opts.username,
4059
+ bio: opts.bio,
4060
+ model: opts.model,
4061
+ systemPrompt: opts.systemPrompt,
4062
+ socialMode: opts.socialMode,
4063
+ postFrequency: opts.postFrequency,
4064
+ toolMode: opts.toolMode,
4065
+ dailyRequestLimit: opts.dailyRequestLimit,
4066
+ organizationId: opts.org,
4067
+ json: opts.json
4068
+ });
4069
+ } catch (err) {
4070
+ console.error(err instanceof Error ? err.message : err);
4071
+ process.exit(1);
4072
+ }
4073
+ });
4074
+ program.command("brain").description("Launch the visual Recursiv brain TUI").option("--no-anim", "Disable animations").action(async (opts) => {
4075
+ try {
4076
+ await brainCommand(opts);
4077
+ } catch (err) {
4078
+ console.error(err instanceof Error ? err.message : err);
4079
+ process.exit(1);
4080
+ }
4081
+ });
4082
+ program.command("studio").description("Legacy alias for brain").option("--no-anim", "Disable animations").action(async (opts) => {
4083
+ try {
4084
+ await studioCommand(opts);
4085
+ } catch (err) {
4086
+ console.error(err instanceof Error ? err.message : err);
4087
+ process.exit(1);
4088
+ }
4089
+ });
4090
+ var mcp = program.command("mcp").description("Manage MCP server integrations");
4091
+ mcp.command("install <target>").description("Configure Recursiv MCP server for a host (claude-code, claude-desktop, codex, opencode)").option("--api-key <key>", "Recursiv API key (defaults to RECURSIV_API_KEY env var)").option("--global", "Install globally instead of project-level (claude-code, opencode)").action(async (target, opts) => {
4092
+ try {
4093
+ const { mcpInstallCommand: mcpInstallCommand2 } = await Promise.resolve().then(() => (init_mcp_install(), mcp_install_exports));
4094
+ await mcpInstallCommand2({ target, apiKey: opts.apiKey, global: opts.global });
4095
+ } catch (err) {
4096
+ console.error(err instanceof Error ? err.message : err);
4097
+ process.exit(1);
4098
+ }
4099
+ });
4100
+ mcp.command("verify").description("Verify that the Recursiv MCP server starts and responds").action(async () => {
4101
+ try {
4102
+ const { mcpVerifyCommand: mcpVerifyCommand2 } = await Promise.resolve().then(() => (init_mcp_install(), mcp_install_exports));
4103
+ await mcpVerifyCommand2();
4104
+ } catch (err) {
4105
+ console.error(err instanceof Error ? err.message : err);
4106
+ process.exit(1);
4107
+ }
4108
+ });
4109
+ async function launchChatAgent(opts) {
4110
+ const { startAgent: startAgent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
4111
+ const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
4112
+ const { resolveRecursivKey: resolveRecursivKey2, resolveAnthropicKey: resolveAnthropicKey2, saveCredentials: saveCredentials2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
4113
+ const { getOrCreateApiKey: getOrCreateApiKey2 } = await Promise.resolve().then(() => (init_auth_flow(), auth_flow_exports));
4114
+ const pc14 = (await import("picocolors")).default;
4115
+ const prompts3 = (await import("prompts")).default;
4116
+ const cwd = process.cwd();
4117
+ const config = await readConfig2(cwd);
4118
+ const baseUrl = config?.api.baseUrl ?? "https://api.recursiv.io/api/v1";
4119
+ let recursivApiKey = await resolveRecursivKey2(cwd);
4120
+ if (!recursivApiKey) {
4121
+ console.log();
4122
+ console.log(pc14.bold(" Welcome to Recursiv"));
4123
+ console.log(pc14.dim(" Let's get you authenticated."));
4124
+ console.log();
4125
+ recursivApiKey = await getOrCreateApiKey2(baseUrl);
4126
+ if (!recursivApiKey) {
4127
+ console.error(pc14.red(" error") + " Authentication required to continue.");
4128
+ process.exit(1);
4129
+ }
4130
+ await saveCredentials2({ recursivApiKey });
4131
+ console.log(pc14.dim(" Credentials saved to ~/.recursiv/credentials"));
4132
+ console.log();
4133
+ }
4134
+ const anthropicApiKey = await resolveAnthropicKey2() ?? void 0;
4135
+ await startAgent2({
4136
+ recursivApiKey,
4137
+ recursivBaseUrl: baseUrl,
4138
+ model: opts?.model,
4139
+ resume: opts?.resume,
4140
+ anthropicApiKey
4141
+ });
4142
+ }
4143
+ function shouldAutoLaunchChat(args) {
4144
+ if (process.env.RECURSIV_BRAIN === "1") return false;
4145
+ if (process.env.RECURSIV_STUDIO === "1") return false;
4146
+ if (args.length > 0) return false;
4147
+ if (!process.stdout.isTTY) return false;
4148
+ if (process.env.CI) return false;
4149
+ return true;
4150
+ }
4151
+ async function main() {
4152
+ const args = process.argv.slice(2);
4153
+ if (args.includes("--brain") || args.includes("--studio")) {
4154
+ const filtered = args.filter((arg) => arg !== "--brain" && arg !== "--studio");
4155
+ process.argv = [process.argv[0], process.argv[1], ...filtered];
4156
+ const noAnim = filtered.includes("--no-anim");
4157
+ await brainCommand({ anim: !noAnim });
4158
+ return;
4159
+ }
4160
+ if (shouldAutoLaunchChat(args)) {
4161
+ try {
4162
+ await launchChatAgent();
4163
+ } catch (err) {
4164
+ console.error(err instanceof Error ? err.message : err);
4165
+ process.exit(1);
4166
+ }
4167
+ return;
4168
+ }
4169
+ program.parse();
4170
+ }
4171
+ void main();
4172
+ //# sourceMappingURL=recursiv.js.map