create-supyagent-app 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import * as p2 from "@clack/prompts";
5
- import pc2 from "picocolors";
4
+ import * as p3 from "@clack/prompts";
5
+ import pc3 from "picocolors";
6
6
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
7
7
  import { resolve as resolve2, dirname as dirname2 } from "path";
8
8
 
@@ -210,6 +210,11 @@ function scaffoldProject(config) {
210
210
  "src/app/api/chat/route.ts",
211
211
  applyTemplate(readTemplate(routeTemplate), vars)
212
212
  );
213
+ writeProject(
214
+ projectPath,
215
+ "src/app/api/chat/compact/route.ts",
216
+ applyTemplate(readTemplate("api-route/compact/route.ts.tmpl"), vars)
217
+ );
213
218
  writeProject(projectPath, "src/app/api/chats/route.ts", readTemplate("base/src/app/api/chats/route.ts"));
214
219
  writeProject(projectPath, "src/app/api/chats/[id]/route.ts", readTemplate("base/src/app/api/chats/[id]/route.ts"));
215
220
  writeProject(projectPath, "src/app/api/jobs/[id]/route.ts", readTemplate("base/src/app/api/jobs/[id]/route.ts"));
@@ -221,6 +226,8 @@ function scaffoldProject(config) {
221
226
  writeProject(projectPath, "src/components/chat-input.tsx", readTemplate("base/src/components/chat-input.tsx"));
222
227
  writeProject(projectPath, "src/components/header.tsx", readTemplate("base/src/components/header.tsx"));
223
228
  writeProject(projectPath, "src/components/user-button.tsx", readTemplate("base/src/components/user-button.tsx"));
229
+ writeProject(projectPath, "src/components/theme-provider.tsx", readTemplate("base/src/components/theme-provider.tsx"));
230
+ writeProject(projectPath, "src/components/slash-menu.tsx", readTemplate("base/src/components/slash-menu.tsx"));
224
231
  writeProject(projectPath, "src/components/ui/badge.tsx", readTemplate("base/src/components/ui/badge.tsx"));
225
232
  writeProject(projectPath, "src/components/ui/collapsible.tsx", readTemplate("base/src/components/ui/collapsible.tsx"));
226
233
  writeProject(projectPath, "src/components/ai-elements/tool.tsx", readTemplate("base/src/components/ai-elements/tool.tsx"));
@@ -303,8 +310,9 @@ import { join as join2 } from "path";
303
310
  import { execSync, spawn as nodeSpawn } from "child_process";
304
311
  import { detectPackageManager as detectPackageManager2 } from "nypm";
305
312
  function writeEnvLocal(config) {
306
- const { projectPath, aiProvider, apiKeys } = config;
313
+ const { projectPath, aiProvider, apiKeys, database, databaseUrl } = config;
307
314
  const ai = AI_PROVIDERS[aiProvider];
315
+ const dbUrl = databaseUrl ?? DB_CONFIGS[database].url;
308
316
  const lines = [
309
317
  "# Supyagent \u2014 Get your API key at https://app.supyagent.com",
310
318
  `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? ""}`,
@@ -313,18 +321,18 @@ function writeEnvLocal(config) {
313
321
  `${ai.envKey}=${apiKeys?.provider ?? ""}`,
314
322
  "",
315
323
  "# Database",
316
- `DATABASE_URL="file:./dev.db"`,
324
+ `DATABASE_URL="${dbUrl}"`,
317
325
  ""
318
326
  ];
319
327
  writeFileSync2(join2(projectPath, ".env.local"), lines.join("\n"), "utf-8");
320
328
  }
321
- async function runDbSetup(projectPath) {
329
+ async function runDbSetup(projectPath, databaseUrl) {
322
330
  const pm = await detectPackageManager2(projectPath);
323
331
  const cmd = pm?.name ?? "pnpm";
324
332
  execSync(`${cmd} run db:setup`, {
325
333
  cwd: projectPath,
326
334
  stdio: "inherit",
327
- env: { ...process.env, DATABASE_URL: "file:./dev.db" }
335
+ env: { ...process.env, DATABASE_URL: databaseUrl ?? "file:./dev.db" }
328
336
  });
329
337
  }
330
338
  async function runDevServer(projectPath) {
@@ -349,6 +357,69 @@ async function runDevServer(projectPath) {
349
357
  });
350
358
  }
351
359
 
360
+ // src/device-auth.ts
361
+ import * as p2 from "@clack/prompts";
362
+ import pc2 from "picocolors";
363
+ import { exec } from "child_process";
364
+ import { platform } from "os";
365
+ var SUPYAGENT_API_URL = "https://app.supyagent.com";
366
+ function openBrowser(url) {
367
+ const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
368
+ exec(`${cmd} ${JSON.stringify(url)}`);
369
+ }
370
+ async function loginViaBrowser() {
371
+ const s = p2.spinner();
372
+ let deviceCode;
373
+ try {
374
+ const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/code`, {
375
+ method: "POST",
376
+ headers: { "Content-Type": "application/json" }
377
+ });
378
+ if (!res.ok) {
379
+ p2.log.error("Failed to start browser login. Please enter your API key manually.");
380
+ return null;
381
+ }
382
+ deviceCode = await res.json();
383
+ } catch {
384
+ p2.log.error("Could not reach Supyagent. Please enter your API key manually.");
385
+ return null;
386
+ }
387
+ p2.log.step(
388
+ `Opening browser to ${pc2.cyan(deviceCode.verification_uri)}
389
+ Enter code: ${pc2.bold(pc2.cyan(deviceCode.user_code))}`
390
+ );
391
+ openBrowser(deviceCode.verification_uri);
392
+ s.start("Waiting for approval in your browser...");
393
+ const interval = (deviceCode.interval || 5) * 1e3;
394
+ const deadline = Date.now() + deviceCode.expires_in * 1e3;
395
+ while (Date.now() < deadline) {
396
+ await new Promise((r) => setTimeout(r, interval));
397
+ try {
398
+ const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/token`, {
399
+ method: "POST",
400
+ headers: { "Content-Type": "application/json" },
401
+ body: JSON.stringify({ device_code: deviceCode.device_code })
402
+ });
403
+ if (res.ok) {
404
+ const data = await res.json();
405
+ s.stop(pc2.green("Logged in successfully!"));
406
+ return data.api_key;
407
+ }
408
+ if (res.status === 403) {
409
+ s.stop(pc2.red("Authorization denied."));
410
+ return null;
411
+ }
412
+ if (res.status === 410) {
413
+ s.stop(pc2.red("Code expired. Please try again."));
414
+ return null;
415
+ }
416
+ } catch {
417
+ }
418
+ }
419
+ s.stop(pc2.red("Timed out waiting for approval."));
420
+ return null;
421
+ }
422
+
352
423
  // src/index.ts
353
424
  function parseArgs() {
354
425
  const args = process.argv.slice(2);
@@ -366,10 +437,6 @@ function parseArgs() {
366
437
  result.agentMode = args[++i];
367
438
  } else if (arg === "--model" && hasValue) {
368
439
  result.model = args[++i];
369
- } else if (arg === "--quickstart") {
370
- result.quickstart = true;
371
- } else if (arg === "--skip-install") {
372
- result.skipInstall = true;
373
440
  } else if (arg === "--supyagent-api-key" && hasValue) {
374
441
  result.supyagentApiKey = args[++i];
375
442
  } else if (arg === "--anthropic-api-key" && hasValue) {
@@ -432,206 +499,133 @@ function loadEnvFiles() {
432
499
  return merged;
433
500
  }
434
501
  async function promptForKey(name) {
435
- const value = await p2.password({ message: `Enter your ${name}` });
436
- if (p2.isCancel(value)) {
437
- p2.cancel("Cancelled.");
502
+ const value = await p3.password({ message: `Enter your ${name}` });
503
+ if (p3.isCancel(value)) {
504
+ p3.cancel("Cancelled.");
438
505
  process.exit(1);
439
506
  }
440
507
  return value;
441
508
  }
509
+ async function resolveSupyagentKey(parsed) {
510
+ const dotenvVars = loadEnvFiles();
511
+ const existing = parsed.supyagentApiKey ?? process.env.SUPYAGENT_API_KEY ?? dotenvVars.SUPYAGENT_API_KEY;
512
+ if (existing) {
513
+ if (!parsed.supyagentApiKey) {
514
+ p3.log.info(`Using ${pc3.cyan("SUPYAGENT_API_KEY")} from ${process.env.SUPYAGENT_API_KEY ? "environment" : ".env file"}`);
515
+ }
516
+ return existing;
517
+ }
518
+ const method = await p3.select({
519
+ message: "How would you like to authenticate with Supyagent?",
520
+ options: [
521
+ {
522
+ value: "browser",
523
+ label: "Login via browser",
524
+ hint: "recommended \u2014 opens app.supyagent.com"
525
+ },
526
+ {
527
+ value: "manual",
528
+ label: "Paste API key",
529
+ hint: "enter an existing key manually"
530
+ }
531
+ ]
532
+ });
533
+ if (p3.isCancel(method)) {
534
+ p3.cancel("Cancelled.");
535
+ process.exit(1);
536
+ }
537
+ if (method === "browser") {
538
+ const key = await loginViaBrowser();
539
+ if (key) return key;
540
+ p3.log.warn("Browser login failed. Please enter your API key manually.");
541
+ }
542
+ return promptForKey("SUPYAGENT_API_KEY");
543
+ }
442
544
  async function resolveApiKeys(provider, parsed) {
443
545
  const envKey = ENV_KEY_MAP[provider];
444
546
  const cliFlag = CLI_KEY_MAP[provider];
445
547
  const dotenvVars = loadEnvFiles();
446
- const supyagent = parsed.supyagentApiKey ?? process.env.SUPYAGENT_API_KEY ?? dotenvVars.SUPYAGENT_API_KEY ?? await promptForKey("SUPYAGENT_API_KEY");
447
- if (!parsed.supyagentApiKey && (process.env.SUPYAGENT_API_KEY || dotenvVars.SUPYAGENT_API_KEY)) {
448
- p2.log.info(`Using ${pc2.cyan("SUPYAGENT_API_KEY")} from ${process.env.SUPYAGENT_API_KEY ? "environment" : ".env file"}`);
449
- }
548
+ const supyagent = await resolveSupyagentKey(parsed);
450
549
  const providerKey = parsed[cliFlag] ?? process.env[envKey] ?? dotenvVars[envKey] ?? await promptForKey(envKey);
451
550
  if (!parsed[cliFlag] && (process.env[envKey] || dotenvVars[envKey])) {
452
- p2.log.info(`Using ${pc2.cyan(envKey)} from ${process.env[envKey] ? "environment" : ".env file"}`);
551
+ p3.log.info(`Using ${pc3.cyan(envKey)} from ${process.env[envKey] ? "environment" : ".env file"}`);
453
552
  }
454
553
  return { supyagent, provider: providerKey };
455
554
  }
456
555
  async function main() {
457
556
  const parsed = parseArgs();
458
- if (parsed.quickstart) {
459
- if (parsed.database === "postgres") {
460
- p2.log.warn(
461
- pc2.yellow("--quickstart requires SQLite \u2014 ignoring --db postgres")
462
- );
463
- }
464
- if (parsed.skipInstall) {
465
- p2.log.warn(
466
- pc2.yellow(
467
- "--quickstart needs dependencies installed \u2014 ignoring --skip-install"
468
- )
469
- );
470
- }
471
- p2.intro(pc2.bgCyan(pc2.black(" Create Supyagent App \u2014 Quickstart ")));
472
- let projectName = parsed.projectName;
473
- if (!projectName) {
474
- const name = await p2.text({
475
- message: "Project name",
476
- placeholder: "my-supyagent-app",
477
- defaultValue: "my-supyagent-app",
478
- validate(value) {
479
- if (!value) return "Project name is required";
480
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {
481
- return "Invalid project name (lowercase, alphanumeric, hyphens, dots)";
482
- }
483
- }
484
- });
485
- if (p2.isCancel(name)) {
486
- p2.cancel("Cancelled.");
487
- process.exit(1);
488
- }
489
- projectName = name;
490
- }
491
- const projectPath = resolveProjectPath(projectName);
557
+ let config;
558
+ if (parsed.projectName && parsed.aiProvider && parsed.database) {
559
+ const projectPath = resolveProjectPath(parsed.projectName);
492
560
  if (projectExists(projectPath)) {
493
- p2.cancel(`Directory "${projectName}" already exists.`);
561
+ p3.cancel(`Directory "${parsed.projectName}" already exists.`);
494
562
  process.exit(1);
495
563
  }
496
- let aiProvider = parsed.aiProvider;
497
- if (!aiProvider) {
498
- const selected = await p2.select({
499
- message: "AI provider",
500
- options: [
501
- { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
502
- { value: "openai", label: AI_PROVIDERS.openai.label },
503
- { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
504
- ]
505
- });
506
- if (p2.isCancel(selected)) {
507
- p2.cancel("Cancelled.");
508
- process.exit(1);
509
- }
510
- aiProvider = selected;
511
- }
512
- let agentMode = parsed.agentMode;
513
- if (!agentMode) {
514
- const selected = await p2.select({
515
- message: "Agent mode",
516
- options: [
517
- { value: "skills", label: "Skills (token-efficient)", hint: "recommended" },
518
- { value: "tools", label: "Tools (rich tool definitions)" }
519
- ]
520
- });
521
- if (p2.isCancel(selected)) {
522
- p2.cancel("Cancelled.");
523
- process.exit(1);
524
- }
525
- agentMode = selected;
526
- }
527
- const apiKeys = await resolveApiKeys(aiProvider, parsed);
528
- const config = {
529
- projectName,
564
+ config = {
565
+ projectName: parsed.projectName,
530
566
  projectPath,
531
- aiProvider,
532
- agentMode,
533
- database: "sqlite",
534
- model: parsed.model,
535
- quickstart: true,
536
- apiKeys
567
+ aiProvider: parsed.aiProvider,
568
+ agentMode: parsed.agentMode ?? "skills",
569
+ database: parsed.database,
570
+ model: parsed.model
537
571
  };
538
- const s = p2.spinner();
539
- s.start("Scaffolding project...");
540
- scaffoldProject(config);
541
- writeEnvLocal(config);
542
- s.stop("Scaffolded project");
543
- s.start("Installing dependencies...");
544
- try {
545
- await installDeps(config.projectPath);
546
- s.stop("Installed dependencies");
547
- } catch {
548
- s.stop("Failed to install dependencies \u2014 run install manually");
549
- process.exit(1);
550
- }
551
- s.start("Setting up database...");
552
- try {
553
- await runDbSetup(config.projectPath);
554
- s.stop("Database ready");
555
- } catch (err) {
556
- s.stop("Database setup failed");
557
- p2.log.warn(
558
- `Run ${pc2.cyan(`cd ${projectName} && pnpm db:setup`)} manually`
559
- );
560
- }
561
- p2.log.info(pc2.green("Starting dev server..."));
562
- await runDevServer(config.projectPath);
563
572
  } else {
564
- const isNonInteractive = parsed.projectName && parsed.aiProvider && parsed.database;
565
- let config;
566
- if (isNonInteractive) {
567
- config = {
568
- projectName: parsed.projectName,
569
- projectPath: parsed.projectPath,
570
- aiProvider: parsed.aiProvider,
571
- agentMode: parsed.agentMode ?? "skills",
572
- database: parsed.database,
573
- model: parsed.model
574
- };
575
- console.log(`Creating ${config.projectName}...`);
576
- } else {
577
- config = await runPrompts(parsed.projectName, {
578
- aiProvider: parsed.aiProvider,
579
- agentMode: parsed.agentMode,
580
- database: parsed.database
581
- });
582
- if (config && parsed.model) {
583
- config.model = parsed.model;
584
- }
585
- }
586
- if (!config) {
587
- process.exit(1);
573
+ config = await runPrompts(parsed.projectName, {
574
+ aiProvider: parsed.aiProvider,
575
+ agentMode: parsed.agentMode,
576
+ database: parsed.database
577
+ });
578
+ if (config && parsed.model) {
579
+ config.model = parsed.model;
588
580
  }
589
- if (isNonInteractive) {
590
- scaffoldProject(config);
591
- console.log("Scaffolded project");
592
- if (!parsed.skipInstall) {
593
- console.log("Installing dependencies...");
594
- try {
595
- await installDeps(config.projectPath);
596
- console.log("Installed dependencies");
597
- } catch {
598
- console.log(
599
- "Failed to install dependencies \u2014 run install manually"
600
- );
601
- }
602
- }
603
- console.log(
604
- `
605
- Next steps:
606
- cd ${config.projectName}
607
- cp .env.example .env.local
608
- pnpm db:setup
609
- pnpm dev`
610
- );
611
- } else {
612
- const s = p2.spinner();
613
- s.start("Scaffolding project...");
614
- scaffoldProject(config);
615
- s.stop("Scaffolded project");
616
- s.start("Installing dependencies...");
617
- try {
618
- await installDeps(config.projectPath);
619
- s.stop("Installed dependencies");
620
- } catch {
621
- s.stop("Failed to install dependencies \u2014 run install manually");
581
+ }
582
+ if (!config) {
583
+ process.exit(1);
584
+ }
585
+ p3.intro(pc3.bgCyan(pc3.black(" Create Supyagent App ")));
586
+ if (config.database === "postgres") {
587
+ const dbUrl = await p3.text({
588
+ message: "PostgreSQL connection URL",
589
+ placeholder: DB_CONFIGS.postgres.url,
590
+ validate(value) {
591
+ if (!value) return "Connection URL is required";
622
592
  }
623
- p2.note(
624
- [
625
- `cd ${config.projectName}`,
626
- `cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
627
- `pnpm db:setup ${pc2.dim("# Initialize database")}`,
628
- `pnpm dev ${pc2.dim("# Start development server")}`
629
- ].join("\n"),
630
- "Next steps"
631
- );
632
- p2.outro(pc2.green("Done!"));
593
+ });
594
+ if (p3.isCancel(dbUrl)) {
595
+ p3.cancel("Cancelled.");
596
+ process.exit(1);
633
597
  }
598
+ config.databaseUrl = dbUrl;
599
+ } else {
600
+ config.databaseUrl = DB_CONFIGS.sqlite.url;
601
+ }
602
+ const apiKeys = await resolveApiKeys(config.aiProvider, parsed);
603
+ config.apiKeys = apiKeys;
604
+ const s = p3.spinner();
605
+ s.start("Scaffolding project...");
606
+ scaffoldProject(config);
607
+ writeEnvLocal(config);
608
+ s.stop("Scaffolded project");
609
+ s.start("Installing dependencies...");
610
+ try {
611
+ await installDeps(config.projectPath);
612
+ s.stop("Installed dependencies");
613
+ } catch {
614
+ s.stop("Failed to install dependencies \u2014 run install manually");
615
+ process.exit(1);
616
+ }
617
+ s.start("Setting up database...");
618
+ try {
619
+ await runDbSetup(config.projectPath, config.databaseUrl);
620
+ s.stop("Database ready");
621
+ } catch {
622
+ s.stop("Database setup failed");
623
+ p3.log.warn(
624
+ `Run ${pc3.cyan(`cd ${config.projectName} && pnpm db:setup`)} manually`
625
+ );
634
626
  }
627
+ p3.log.info(pc3.green("Starting dev server..."));
628
+ await runDevServer(config.projectPath);
635
629
  }
636
630
  main().catch(console.error);
637
631
  //# sourceMappingURL=index.js.map