create-supyagent-app 0.1.37 → 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
@@ -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"));
@@ -305,8 +310,9 @@ import { join as join2 } from "path";
305
310
  import { execSync, spawn as nodeSpawn } from "child_process";
306
311
  import { detectPackageManager as detectPackageManager2 } from "nypm";
307
312
  function writeEnvLocal(config) {
308
- const { projectPath, aiProvider, apiKeys } = config;
313
+ const { projectPath, aiProvider, apiKeys, database, databaseUrl } = config;
309
314
  const ai = AI_PROVIDERS[aiProvider];
315
+ const dbUrl = databaseUrl ?? DB_CONFIGS[database].url;
310
316
  const lines = [
311
317
  "# Supyagent \u2014 Get your API key at https://app.supyagent.com",
312
318
  `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? ""}`,
@@ -315,18 +321,18 @@ function writeEnvLocal(config) {
315
321
  `${ai.envKey}=${apiKeys?.provider ?? ""}`,
316
322
  "",
317
323
  "# Database",
318
- `DATABASE_URL="file:./dev.db"`,
324
+ `DATABASE_URL="${dbUrl}"`,
319
325
  ""
320
326
  ];
321
327
  writeFileSync2(join2(projectPath, ".env.local"), lines.join("\n"), "utf-8");
322
328
  }
323
- async function runDbSetup(projectPath) {
329
+ async function runDbSetup(projectPath, databaseUrl) {
324
330
  const pm = await detectPackageManager2(projectPath);
325
331
  const cmd = pm?.name ?? "pnpm";
326
332
  execSync(`${cmd} run db:setup`, {
327
333
  cwd: projectPath,
328
334
  stdio: "inherit",
329
- env: { ...process.env, DATABASE_URL: "file:./dev.db" }
335
+ env: { ...process.env, DATABASE_URL: databaseUrl ?? "file:./dev.db" }
330
336
  });
331
337
  }
332
338
  async function runDevServer(projectPath) {
@@ -431,10 +437,6 @@ function parseArgs() {
431
437
  result.agentMode = args[++i];
432
438
  } else if (arg === "--model" && hasValue) {
433
439
  result.model = args[++i];
434
- } else if (arg === "--quickstart") {
435
- result.quickstart = true;
436
- } else if (arg === "--skip-install") {
437
- result.skipInstall = true;
438
440
  } else if (arg === "--supyagent-api-key" && hasValue) {
439
441
  result.supyagentApiKey = args[++i];
440
442
  } else if (arg === "--anthropic-api-key" && hasValue) {
@@ -552,183 +554,78 @@ async function resolveApiKeys(provider, parsed) {
552
554
  }
553
555
  async function main() {
554
556
  const parsed = parseArgs();
555
- if (parsed.quickstart) {
556
- if (parsed.database === "postgres") {
557
- p3.log.warn(
558
- pc3.yellow("--quickstart requires SQLite \u2014 ignoring --db postgres")
559
- );
560
- }
561
- if (parsed.skipInstall) {
562
- p3.log.warn(
563
- pc3.yellow(
564
- "--quickstart needs dependencies installed \u2014 ignoring --skip-install"
565
- )
566
- );
567
- }
568
- p3.intro(pc3.bgCyan(pc3.black(" Create Supyagent App \u2014 Quickstart ")));
569
- let projectName = parsed.projectName;
570
- if (!projectName) {
571
- const name = await p3.text({
572
- message: "Project name",
573
- placeholder: "my-supyagent-app",
574
- defaultValue: "my-supyagent-app",
575
- validate(value) {
576
- if (!value) return "Project name is required";
577
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {
578
- return "Invalid project name (lowercase, alphanumeric, hyphens, dots)";
579
- }
580
- }
581
- });
582
- if (p3.isCancel(name)) {
583
- p3.cancel("Cancelled.");
584
- process.exit(1);
585
- }
586
- projectName = name;
587
- }
588
- const projectPath = resolveProjectPath(projectName);
557
+ let config;
558
+ if (parsed.projectName && parsed.aiProvider && parsed.database) {
559
+ const projectPath = resolveProjectPath(parsed.projectName);
589
560
  if (projectExists(projectPath)) {
590
- p3.cancel(`Directory "${projectName}" already exists.`);
561
+ p3.cancel(`Directory "${parsed.projectName}" already exists.`);
591
562
  process.exit(1);
592
563
  }
593
- let aiProvider = parsed.aiProvider;
594
- if (!aiProvider) {
595
- const selected = await p3.select({
596
- message: "AI provider",
597
- options: [
598
- { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
599
- { value: "openai", label: AI_PROVIDERS.openai.label },
600
- { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
601
- ]
602
- });
603
- if (p3.isCancel(selected)) {
604
- p3.cancel("Cancelled.");
605
- process.exit(1);
606
- }
607
- aiProvider = selected;
608
- }
609
- let agentMode = parsed.agentMode;
610
- if (!agentMode) {
611
- const selected = await p3.select({
612
- message: "Agent mode",
613
- options: [
614
- { value: "skills", label: "Skills (token-efficient)", hint: "recommended" },
615
- { value: "tools", label: "Tools (rich tool definitions)" }
616
- ]
617
- });
618
- if (p3.isCancel(selected)) {
619
- p3.cancel("Cancelled.");
620
- process.exit(1);
621
- }
622
- agentMode = selected;
623
- }
624
- const apiKeys = await resolveApiKeys(aiProvider, parsed);
625
- const config = {
626
- projectName,
564
+ config = {
565
+ projectName: parsed.projectName,
627
566
  projectPath,
628
- aiProvider,
629
- agentMode,
630
- database: "sqlite",
631
- model: parsed.model,
632
- quickstart: true,
633
- apiKeys
567
+ aiProvider: parsed.aiProvider,
568
+ agentMode: parsed.agentMode ?? "skills",
569
+ database: parsed.database,
570
+ model: parsed.model
634
571
  };
635
- const s = p3.spinner();
636
- s.start("Scaffolding project...");
637
- scaffoldProject(config);
638
- writeEnvLocal(config);
639
- s.stop("Scaffolded project");
640
- s.start("Installing dependencies...");
641
- try {
642
- await installDeps(config.projectPath);
643
- s.stop("Installed dependencies");
644
- } catch {
645
- s.stop("Failed to install dependencies \u2014 run install manually");
646
- process.exit(1);
647
- }
648
- s.start("Setting up database...");
649
- try {
650
- await runDbSetup(config.projectPath);
651
- s.stop("Database ready");
652
- } catch (err) {
653
- s.stop("Database setup failed");
654
- p3.log.warn(
655
- `Run ${pc3.cyan(`cd ${projectName} && pnpm db:setup`)} manually`
656
- );
657
- }
658
- p3.log.info(pc3.green("Starting dev server..."));
659
- await runDevServer(config.projectPath);
660
572
  } else {
661
- const isNonInteractive = parsed.projectName && parsed.aiProvider && parsed.database;
662
- let config;
663
- if (isNonInteractive) {
664
- config = {
665
- projectName: parsed.projectName,
666
- projectPath: parsed.projectPath,
667
- aiProvider: parsed.aiProvider,
668
- agentMode: parsed.agentMode ?? "skills",
669
- database: parsed.database,
670
- model: parsed.model
671
- };
672
- console.log(`Creating ${config.projectName}...`);
673
- } else {
674
- config = await runPrompts(parsed.projectName, {
675
- aiProvider: parsed.aiProvider,
676
- agentMode: parsed.agentMode,
677
- database: parsed.database
678
- });
679
- if (config && parsed.model) {
680
- config.model = parsed.model;
681
- }
682
- }
683
- if (!config) {
684
- 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;
685
580
  }
686
- if (isNonInteractive) {
687
- scaffoldProject(config);
688
- console.log("Scaffolded project");
689
- if (!parsed.skipInstall) {
690
- console.log("Installing dependencies...");
691
- try {
692
- await installDeps(config.projectPath);
693
- console.log("Installed dependencies");
694
- } catch {
695
- console.log(
696
- "Failed to install dependencies \u2014 run install manually"
697
- );
698
- }
699
- }
700
- console.log(
701
- `
702
- Next steps:
703
- cd ${config.projectName}
704
- cp .env.example .env.local
705
- pnpm db:setup
706
- pnpm dev`
707
- );
708
- } else {
709
- const s = p3.spinner();
710
- s.start("Scaffolding project...");
711
- scaffoldProject(config);
712
- s.stop("Scaffolded project");
713
- s.start("Installing dependencies...");
714
- try {
715
- await installDeps(config.projectPath);
716
- s.stop("Installed dependencies");
717
- } catch {
718
- 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";
719
592
  }
720
- p3.note(
721
- [
722
- `cd ${config.projectName}`,
723
- `cp .env.example .env.local ${pc3.dim("# Add your API keys")}`,
724
- `pnpm db:setup ${pc3.dim("# Initialize database")}`,
725
- `pnpm dev ${pc3.dim("# Start development server")}`
726
- ].join("\n"),
727
- "Next steps"
728
- );
729
- p3.outro(pc3.green("Done!"));
593
+ });
594
+ if (p3.isCancel(dbUrl)) {
595
+ p3.cancel("Cancelled.");
596
+ process.exit(1);
730
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
+ );
731
626
  }
627
+ p3.log.info(pc3.green("Starting dev server..."));
628
+ await runDevServer(config.projectPath);
732
629
  }
733
630
  main().catch(console.error);
734
631
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/prompts.ts","../src/utils.ts","../src/scaffold.ts","../src/template.ts","../src/post-install.ts","../src/quickstart.ts","../src/device-auth.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { writeEnvLocal, runDbSetup, runDevServer } from \"./quickstart.js\";\nimport { loginViaBrowser } from \"./device-auth.js\";\nimport { AI_PROVIDERS, resolveProjectPath, projectExists } from \"./utils.js\";\nimport type { ProjectConfig, ApiKeys } from \"./utils.js\";\n\ninterface ParsedArgs extends Partial<ProjectConfig> {\n skipInstall?: boolean;\n supyagentApiKey?: string;\n anthropicApiKey?: string;\n openaiApiKey?: string;\n openrouterApiKey?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n const args = process.argv.slice(2);\n const result: ParsedArgs = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n const hasValue = next && !next.startsWith(\"-\");\n\n if (arg === \"--provider\" && hasValue) {\n result.aiProvider = args[++i] as ProjectConfig[\"aiProvider\"];\n } else if (arg === \"--db\" && hasValue) {\n result.database = args[++i] as ProjectConfig[\"database\"];\n } else if (arg === \"--mode\" && hasValue) {\n result.agentMode = args[++i] as ProjectConfig[\"agentMode\"];\n } else if (arg === \"--model\" && hasValue) {\n result.model = args[++i];\n } else if (arg === \"--quickstart\") {\n result.quickstart = true;\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (arg === \"--supyagent-api-key\" && hasValue) {\n result.supyagentApiKey = args[++i];\n } else if (arg === \"--anthropic-api-key\" && hasValue) {\n result.anthropicApiKey = args[++i];\n } else if (arg === \"--openai-api-key\" && hasValue) {\n result.openaiApiKey = args[++i];\n } else if (arg === \"--openrouter-api-key\" && hasValue) {\n result.openrouterApiKey = args[++i];\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n ...result,\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n };\n}\n\nconst ENV_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\n\nconst CLI_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], keyof ParsedArgs> = {\n anthropic: \"anthropicApiKey\",\n openai: \"openaiApiKey\",\n openrouter: \"openrouterApiKey\",\n};\n\n/**\n * Parse a `.env` file into a key-value map. Handles simple KEY=VALUE lines,\n * quoted values, and ignores comments/blank lines.\n */\nfunction parseEnvFile(filePath: string): Record<string, string> {\n const result: Record<string, string> = {};\n if (!existsSync(filePath)) return result;\n const content = readFileSync(filePath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx < 1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n let val = trimmed.slice(eqIdx + 1).trim();\n // Strip surrounding quotes\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1);\n }\n if (val) result[key] = val;\n }\n return result;\n}\n\n/**\n * Walk up from cwd looking for .env files and merge them (closest wins).\n */\nfunction loadEnvFiles(): Record<string, string> {\n const merged: Record<string, string> = {};\n const files: string[] = [];\n let dir = process.cwd();\n const root = dirname(dir) === dir ? dir : \"/\";\n // Collect .env files walking up (stop after 5 levels or root)\n for (let i = 0; i < 5 && dir !== root; i++) {\n const envPath = resolve(dir, \".env\");\n if (existsSync(envPath)) files.push(envPath);\n dir = dirname(dir);\n }\n // Merge in reverse order so closest .env wins\n for (const f of files.reverse()) {\n Object.assign(merged, parseEnvFile(f));\n }\n return merged;\n}\n\nasync function promptForKey(name: string): Promise<string> {\n const value = await p.password({ message: `Enter your ${name}` });\n if (p.isCancel(value)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n return value;\n}\n\nasync function resolveSupyagentKey(parsed: ParsedArgs): Promise<string> {\n // 1. Check CLI flag, env, and .env files first\n const dotenvVars = loadEnvFiles();\n const existing =\n parsed.supyagentApiKey ??\n process.env.SUPYAGENT_API_KEY ??\n dotenvVars.SUPYAGENT_API_KEY;\n\n if (existing) {\n if (!parsed.supyagentApiKey) {\n p.log.info(`Using ${pc.cyan(\"SUPYAGENT_API_KEY\")} from ${process.env.SUPYAGENT_API_KEY ? \"environment\" : \".env file\"}`);\n }\n return existing;\n }\n\n // 2. Ask user how they want to authenticate\n const method = await p.select({\n message: \"How would you like to authenticate with Supyagent?\",\n options: [\n {\n value: \"browser\",\n label: \"Login via browser\",\n hint: \"recommended — opens app.supyagent.com\",\n },\n {\n value: \"manual\",\n label: \"Paste API key\",\n hint: \"enter an existing key manually\",\n },\n ],\n });\n\n if (p.isCancel(method)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n\n if (method === \"browser\") {\n const key = await loginViaBrowser();\n if (key) return key;\n // Browser flow failed — fall back to manual entry\n p.log.warn(\"Browser login failed. Please enter your API key manually.\");\n }\n\n return promptForKey(\"SUPYAGENT_API_KEY\");\n}\n\nasync function resolveApiKeys(\n provider: ProjectConfig[\"aiProvider\"],\n parsed: ParsedArgs,\n): Promise<ApiKeys> {\n const envKey = ENV_KEY_MAP[provider];\n const cliFlag = CLI_KEY_MAP[provider];\n const dotenvVars = loadEnvFiles();\n\n const supyagent = await resolveSupyagentKey(parsed);\n\n const providerKey =\n (parsed[cliFlag] as string | undefined) ??\n process.env[envKey] ??\n dotenvVars[envKey] ??\n (await promptForKey(envKey));\n\n if (!(parsed[cliFlag] as string | undefined) && (process.env[envKey] || dotenvVars[envKey])) {\n p.log.info(`Using ${pc.cyan(envKey)} from ${process.env[envKey] ? \"environment\" : \".env file\"}`);\n }\n\n return { supyagent, provider: providerKey };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n if (parsed.quickstart) {\n // ── Quickstart mode ──\n if (parsed.database === \"postgres\") {\n p.log.warn(\n pc.yellow(\"--quickstart requires SQLite — ignoring --db postgres\"),\n );\n }\n if (parsed.skipInstall) {\n p.log.warn(\n pc.yellow(\n \"--quickstart needs dependencies installed — ignoring --skip-install\",\n ),\n );\n }\n\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App — Quickstart \")));\n\n // Resolve project name\n let projectName = parsed.projectName;\n if (!projectName) {\n const name = await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n });\n if (p.isCancel(name)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n projectName = name as string;\n }\n\n const projectPath = resolveProjectPath(projectName);\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n process.exit(1);\n }\n\n // Resolve provider\n let aiProvider = parsed.aiProvider;\n if (!aiProvider) {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n });\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n aiProvider = selected as ProjectConfig[\"aiProvider\"];\n }\n\n // Resolve agent mode\n let agentMode = parsed.agentMode;\n if (!agentMode) {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n });\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n agentMode = selected as ProjectConfig[\"agentMode\"];\n }\n\n // Resolve API keys\n const apiKeys = await resolveApiKeys(aiProvider, parsed);\n\n const config: ProjectConfig = {\n projectName,\n projectPath,\n aiProvider,\n agentMode,\n database: \"sqlite\",\n model: parsed.model,\n quickstart: true,\n apiKeys,\n };\n\n const s = p.spinner();\n\n // Scaffold\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n writeEnvLocal(config);\n s.stop(\"Scaffolded project\");\n\n // Install deps\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n process.exit(1);\n }\n\n // Database setup\n s.start(\"Setting up database...\");\n try {\n await runDbSetup(config.projectPath);\n s.stop(\"Database ready\");\n } catch (err) {\n s.stop(\"Database setup failed\");\n p.log.warn(\n `Run ${pc.cyan(`cd ${projectName} && pnpm db:setup`)} manually`,\n );\n }\n\n // Dev server\n p.log.info(pc.green(\"Starting dev server...\"));\n await runDevServer(config.projectPath);\n } else {\n // ── Existing non-quickstart flow ──\n const isNonInteractive =\n parsed.projectName && parsed.aiProvider && parsed.database;\n\n let config: ProjectConfig | null;\n\n if (isNonInteractive) {\n config = {\n projectName: parsed.projectName!,\n projectPath: parsed.projectPath!,\n aiProvider: parsed.aiProvider!,\n agentMode: parsed.agentMode ?? \"skills\",\n database: parsed.database!,\n model: parsed.model,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName, {\n aiProvider: parsed.aiProvider,\n agentMode: parsed.agentMode,\n database: parsed.database,\n });\n if (config && parsed.model) {\n config.model = parsed.model;\n }\n }\n\n if (!config) {\n process.exit(1);\n }\n\n if (isNonInteractive) {\n scaffoldProject(config);\n console.log(\"Scaffolded project\");\n\n if (!parsed.skipInstall) {\n console.log(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n console.log(\"Installed dependencies\");\n } catch {\n console.log(\n \"Failed to install dependencies — run install manually\",\n );\n }\n }\n\n console.log(\n `\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`,\n );\n } else {\n const s = p.spinner();\n\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n s.stop(\"Scaffolded project\");\n\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n }\n\n p.note(\n [\n `cd ${config.projectName}`,\n `cp .env.example .env.local ${pc.dim(\"# Add your API keys\")}`,\n `pnpm db:setup ${pc.dim(\"# Initialize database\")}`,\n `pnpm dev ${pc.dim(\"# Start development server\")}`,\n ].join(\"\\n\"),\n \"Next steps\",\n );\n\n p.outro(pc.green(\"Done!\"));\n }\n }\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport interface PromptOptions {\n aiProvider?: ProjectConfig[\"aiProvider\"];\n database?: ProjectConfig[\"database\"];\n agentMode?: ProjectConfig[\"agentMode\"];\n skipDatabase?: boolean;\n}\n\nexport async function runPrompts(\n argName?: string,\n options?: PromptOptions,\n): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n let aiProvider: ProjectConfig[\"aiProvider\"];\n if (options?.aiProvider) {\n aiProvider = options.aiProvider;\n } else {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n aiProvider = selected;\n }\n\n let agentMode: ProjectConfig[\"agentMode\"];\n if (options?.agentMode) {\n agentMode = options.agentMode;\n } else {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n }) as \"skills\" | \"tools\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n agentMode = selected;\n }\n\n let database: ProjectConfig[\"database\"];\n if (options?.database || options?.skipDatabase) {\n database = options?.database ?? \"sqlite\";\n } else {\n const selected = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n database = selected;\n }\n\n return { projectName, projectPath, aiProvider, agentMode, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ApiKeys {\n supyagent?: string;\n provider?: string;\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n agentMode: \"skills\" | \"tools\";\n model?: string;\n quickstart?: boolean;\n apiKeys?: ApiKeys;\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-6-20250620')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n\nexport function buildModelExpression(\n provider: ProjectConfig[\"aiProvider\"],\n customModelId?: string,\n): string {\n const config = AI_PROVIDERS[provider];\n if (!customModelId) return config.model;\n switch (provider) {\n case \"anthropic\":\n return `anthropic('${customModelId}')`;\n case \"openai\":\n return `openai('${customModelId}')`;\n case \"openrouter\":\n return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;\n }\n}\n","import { mkdirSync, writeFileSync, readFileSync, copyFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyTemplate } from \"./template.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, buildModelExpression, type ProjectConfig } from \"./utils.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst TEMPLATES_DIR = join(__dirname, \"..\", \"templates\");\n\nfunction readTemplate(relativePath: string): string {\n return readFileSync(join(TEMPLATES_DIR, relativePath), \"utf-8\");\n}\n\nfunction writeProject(projectPath: string, relativePath: string, content: string): void {\n const fullPath = join(projectPath, relativePath);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, content, \"utf-8\");\n}\n\nfunction copyBinary(projectPath: string, relativePath: string, templateRelativePath: string): void {\n const src = join(TEMPLATES_DIR, templateRelativePath);\n const dest = join(projectPath, relativePath);\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(src, dest);\n}\n\nexport function scaffoldProject(config: ProjectConfig): void {\n const { projectPath, projectName, aiProvider, database } = config;\n const ai = AI_PROVIDERS[aiProvider];\n const db = DB_CONFIGS[database];\n\n const vars: Record<string, string> = {\n projectName,\n aiProviderPackage: ai.package,\n aiProviderVersion: ai.packageVersion,\n aiProviderImport: ai.import,\n aiModel: buildModelExpression(aiProvider, config.model),\n aiProviderEnvKey: ai.envKey,\n dbProvider: db.provider,\n dbUrl: db.url,\n };\n\n mkdirSync(projectPath, { recursive: true });\n\n // ── Base files ──\n writeProject(projectPath, \"next.config.ts\", readTemplate(\"base/next.config.ts\"));\n writeProject(projectPath, \"tsconfig.json\", readTemplate(\"base/tsconfig.json\"));\n writeProject(projectPath, \"tailwind.config.ts\", readTemplate(\"base/tailwind.config.ts\"));\n writeProject(projectPath, \"postcss.config.js\", readTemplate(\"base/postcss.config.js\"));\n writeProject(projectPath, \".gitignore\", readTemplate(\"base/gitignore\"));\n writeProject(projectPath, \".npmrc\", readTemplate(\"base/npmrc\"));\n writeProject(projectPath, \"README.md\", applyTemplate(readTemplate(\"base/README.md.tmpl\"), vars));\n\n // ── Source files ──\n writeProject(projectPath, \"src/app/layout.tsx\", readTemplate(\"base/src/app/layout.tsx\"));\n writeProject(projectPath, \"src/app/page.tsx\", readTemplate(\"base/src/app/page.tsx\"));\n writeProject(projectPath, \"src/app/globals.css\", readTemplate(\"base/src/app/globals.css\"));\n writeProject(projectPath, \"src/app/chat/page.tsx\", readTemplate(\"base/src/app/chat/page.tsx\"));\n writeProject(projectPath, \"src/app/chat/[id]/page.tsx\", readTemplate(\"base/src/app/chat/[id]/page.tsx\"));\n\n // API routes — use skills or tools template based on agentMode\n const routeTemplate = config.agentMode === \"skills\"\n ? \"api-route/route.skills.ts.tmpl\"\n : \"api-route/route.tools.ts.tmpl\";\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(routeTemplate), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n writeProject(projectPath, \"src/app/api/jobs/[id]/route.ts\", readTemplate(\"base/src/app/api/jobs/[id]/route.ts\"));\n writeProject(projectPath, \"src/app/api/me/route.ts\", readTemplate(\"base/src/app/api/me/route.ts\"));\n\n // Hooks\n writeProject(projectPath, \"src/hooks/use-job-polling.ts\", readTemplate(\"base/src/hooks/use-job-polling.ts\"));\n\n // Components\n writeProject(projectPath, \"src/components/chat.tsx\", readTemplate(\"base/src/components/chat.tsx\"));\n writeProject(projectPath, \"src/components/chat-sidebar.tsx\", readTemplate(\"base/src/components/chat-sidebar.tsx\"));\n writeProject(projectPath, \"src/components/chat-message.tsx\", readTemplate(\"base/src/components/chat-message.tsx\"));\n writeProject(projectPath, \"src/components/chat-input.tsx\", readTemplate(\"base/src/components/chat-input.tsx\"));\n writeProject(projectPath, \"src/components/header.tsx\", readTemplate(\"base/src/components/header.tsx\"));\n writeProject(projectPath, \"src/components/user-button.tsx\", readTemplate(\"base/src/components/user-button.tsx\"));\n writeProject(projectPath, \"src/components/theme-provider.tsx\", readTemplate(\"base/src/components/theme-provider.tsx\"));\n writeProject(projectPath, \"src/components/slash-menu.tsx\", readTemplate(\"base/src/components/slash-menu.tsx\"));\n\n // UI primitives (shadcn-style)\n writeProject(projectPath, \"src/components/ui/badge.tsx\", readTemplate(\"base/src/components/ui/badge.tsx\"));\n writeProject(projectPath, \"src/components/ui/collapsible.tsx\", readTemplate(\"base/src/components/ui/collapsible.tsx\"));\n\n // AI Elements (pre-bundled Tool component)\n writeProject(projectPath, \"src/components/ai-elements/tool.tsx\", readTemplate(\"base/src/components/ai-elements/tool.tsx\"));\n\n // Supyagent tool rendering (local, editable)\n writeProject(projectPath, \"src/components/supyagent/tool-message.tsx\", readTemplate(\"base/src/components/supyagent/tool-message.tsx\"));\n writeProject(projectPath, \"src/components/supyagent/tool-renderers.tsx\", readTemplate(\"base/src/components/supyagent/tool-renderers.tsx\"));\n\n // Tool renderers — one per integration\n const toolRenderers = [\n \"gmail\", \"calendar\", \"slack\", \"github\", \"drive\", \"search\", \"docs\",\n \"sheets\", \"slides\", \"hubspot\", \"linear\", \"pipedrive\", \"compute\",\n \"resend\", \"inbox\", \"discord\", \"notion\", \"twitter\", \"telegram\",\n \"stripe\", \"jira\", \"salesforce\", \"brevo\", \"calendly\", \"twilio\",\n \"linkedin\", \"bash\", \"image\", \"audio\", \"video\", \"whatsapp\",\n \"browser\", \"view-image\", \"jobs\", \"generic\",\n ];\n for (const tool of toolRenderers) {\n writeProject(projectPath, `src/components/supyagent/tools/${tool}.tsx`, readTemplate(`base/src/components/supyagent/tools/${tool}.tsx`));\n }\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Branding assets ──\n copyBinary(projectPath, \"public/logo.png\", \"base/public/logo.png\");\n copyBinary(projectPath, \"src/app/icon.png\", \"base/public/logo.png\");\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync, spawn as nodeSpawn } from \"node:child_process\";\nimport { detectPackageManager } from \"nypm\";\nimport { AI_PROVIDERS, type ProjectConfig } from \"./utils.js\";\n\nexport function writeEnvLocal(config: ProjectConfig): void {\n const { projectPath, aiProvider, apiKeys } = config;\n const ai = AI_PROVIDERS[aiProvider];\n\n const lines = [\n \"# Supyagent — Get your API key at https://app.supyagent.com\",\n `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? \"\"}`,\n \"\",\n \"# AI Provider\",\n `${ai.envKey}=${apiKeys?.provider ?? \"\"}`,\n \"\",\n \"# Database\",\n `DATABASE_URL=\"file:./dev.db\"`,\n \"\",\n ];\n\n writeFileSync(join(projectPath, \".env.local\"), lines.join(\"\\n\"), \"utf-8\");\n}\n\nexport async function runDbSetup(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n execSync(`${cmd} run db:setup`, {\n cwd: projectPath,\n stdio: \"inherit\",\n env: { ...process.env, DATABASE_URL: \"file:./dev.db\" },\n });\n}\n\nexport async function runDevServer(projectPath: string): Promise<never> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n\n const child = nodeSpawn(cmd, [\"run\", \"dev\"], {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n\n const forward = (signal: NodeJS.Signals) => {\n child.kill(signal);\n };\n\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n return new Promise((_, reject) => {\n child.on(\"close\", (code) => {\n process.off(\"SIGINT\", forward);\n process.off(\"SIGTERM\", forward);\n process.exit(code ?? 0);\n });\n child.on(\"error\", reject);\n });\n}\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { exec } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\nconst SUPYAGENT_API_URL = \"https://app.supyagent.com\";\n\nfunction openBrowser(url: string): void {\n const cmd =\n platform() === \"darwin\"\n ? \"open\"\n : platform() === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${JSON.stringify(url)}`);\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n}\n\n/**\n * Authenticate via the device code flow (RFC 8628).\n *\n * 1. Request a device code from the Supyagent API\n * 2. Open the browser for the user to approve\n * 3. Poll until approved, denied, or expired\n *\n * Returns the API key on success, or null on failure.\n */\nexport async function loginViaBrowser(): Promise<string | null> {\n const s = p.spinner();\n\n // 1. Request device code\n let deviceCode: DeviceCodeResponse;\n try {\n const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/code`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!res.ok) {\n p.log.error(\"Failed to start browser login. Please enter your API key manually.\");\n return null;\n }\n deviceCode = (await res.json()) as DeviceCodeResponse;\n } catch {\n p.log.error(\"Could not reach Supyagent. Please enter your API key manually.\");\n return null;\n }\n\n // 2. Open browser and show code\n p.log.step(\n `Opening browser to ${pc.cyan(deviceCode.verification_uri)}\\n` +\n ` Enter code: ${pc.bold(pc.cyan(deviceCode.user_code))}`,\n );\n openBrowser(deviceCode.verification_uri);\n\n // 3. Poll for approval\n s.start(\"Waiting for approval in your browser...\");\n\n const interval = (deviceCode.interval || 5) * 1000;\n const deadline = Date.now() + deviceCode.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, interval));\n\n try {\n const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device_code: deviceCode.device_code }),\n });\n\n if (res.ok) {\n const data = (await res.json()) as { api_key: string };\n s.stop(pc.green(\"Logged in successfully!\"));\n return data.api_key;\n }\n\n if (res.status === 403) {\n s.stop(pc.red(\"Authorization denied.\"));\n return null;\n }\n\n if (res.status === 410) {\n s.stop(pc.red(\"Code expired. Please try again.\"));\n return null;\n }\n\n // 428 = still pending, keep polling\n } catch {\n // Network error — keep trying\n }\n }\n\n s.stop(pc.red(\"Timed out waiting for approval.\"));\n return null;\n}\n"],"mappings":";;;AAAA,YAAYA,QAAO;AACnB,OAAOC,SAAQ;AACf,SAAS,gBAAAC,eAAc,cAAAC,mBAAkB;AACzC,SAAS,WAAAC,UAAS,WAAAC,gBAAe;;;ACHjC,YAAY,OAAO;AACnB,OAAO,QAAQ;;;ACDf,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAEjB,SAAS,mBAAmB,MAAsB;AACvD,SAAO,QAAQ,QAAQ,IAAI,GAAG,IAAI;AACpC;AAEO,SAAS,cAAc,MAAuB;AACnD,SAAO,WAAW,IAAI;AACxB;AAkBO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEO,IAAM,aAAa;AAAA,EACxB,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,EACP;AAAA,EACA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,EACP;AACF;AAEO,SAAS,qBACd,UACA,eACQ;AACR,QAAM,SAAS,aAAa,QAAQ;AACpC,MAAI,CAAC,cAAe,QAAO,OAAO;AAClC,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,cAAc,aAAa;AAAA,IACpC,KAAK;AACH,aAAO,WAAW,aAAa;AAAA,IACjC,KAAK;AACH,aAAO,iEAAiE,aAAa;AAAA,EACzF;AACF;;;ADrEA,eAAsB,WACpB,SACA,SAC+B;AAC/B,EAAE,QAAM,GAAG,OAAO,GAAG,MAAM,wBAAwB,CAAC,CAAC;AAErD,QAAM,cAAc,WAAW,MAAQ,OAAK;AAAA,IAC1C,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,SAAS,OAAO;AACd,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,CAAC,yBAAyB,KAAK,KAAK,GAAG;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAM,WAAS,WAAW,GAAG;AAC3B,IAAE,SAAO,YAAY;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,mBAAmB,WAAW;AAElD,MAAI,cAAc,WAAW,GAAG;AAC9B,IAAE,SAAO,cAAc,WAAW,mBAAmB;AACrD,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI,SAAS,YAAY;AACvB,iBAAa,QAAQ;AAAA,EACvB,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,aAAa,OAAO,aAAa,UAAU,MAAM;AAAA,QAC1D,EAAE,OAAO,UAAU,OAAO,aAAa,OAAO,MAAM;AAAA,QACpD,EAAE,OAAO,cAAc,OAAO,aAAa,WAAW,MAAM;AAAA,MAC9D;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,iBAAa;AAAA,EACf;AAEA,MAAI;AACJ,MAAI,SAAS,WAAW;AACtB,gBAAY,QAAQ;AAAA,EACtB,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,4BAA4B,MAAM,cAAc;AAAA,QAC1E,EAAE,OAAO,SAAS,OAAO,gCAAgC;AAAA,MAC3D;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,gBAAY;AAAA,EACd;AAEA,MAAI;AACJ,MAAI,SAAS,YAAY,SAAS,cAAc;AAC9C,eAAW,SAAS,YAAY;AAAA,EAClC,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,WAAW,OAAO,MAAM;AAAA,QAClD,EAAE,OAAO,YAAY,OAAO,WAAW,SAAS,MAAM;AAAA,MACxD;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,eAAW;AAAA,EACb;AAEA,SAAO,EAAE,aAAa,aAAa,YAAY,WAAW,SAAS;AACrE;;;AErGA,SAAS,WAAW,eAAe,cAAc,oBAAoB;AACrE,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;;;ACCvB,SAAS,cACd,SACA,WACQ;AACR,SAAO,QAAQ,QAAQ,kBAAkB,CAAC,OAAO,QAAQ;AACvD,WAAO,OAAO,YAAY,UAAU,GAAG,IAAI;AAAA,EAC7C,CAAC;AACH;;;ADJA,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,gBAAgB,KAAK,WAAW,MAAM,WAAW;AAEvD,SAAS,aAAa,cAA8B;AAClD,SAAO,aAAa,KAAK,eAAe,YAAY,GAAG,OAAO;AAChE;AAEA,SAAS,aAAa,aAAqB,cAAsB,SAAuB;AACtF,QAAM,WAAW,KAAK,aAAa,YAAY;AAC/C,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,SAAS,OAAO;AAC1C;AAEA,SAAS,WAAW,aAAqB,cAAsB,sBAAoC;AACjG,QAAM,MAAM,KAAK,eAAe,oBAAoB;AACpD,QAAM,OAAO,KAAK,aAAa,YAAY;AAC3C,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,eAAa,KAAK,IAAI;AACxB;AAEO,SAAS,gBAAgB,QAA6B;AAC3D,QAAM,EAAE,aAAa,aAAa,YAAY,SAAS,IAAI;AAC3D,QAAM,KAAK,aAAa,UAAU;AAClC,QAAM,KAAK,WAAW,QAAQ;AAE9B,QAAM,OAA+B;AAAA,IACnC;AAAA,IACA,mBAAmB,GAAG;AAAA,IACtB,mBAAmB,GAAG;AAAA,IACtB,kBAAkB,GAAG;AAAA,IACrB,SAAS,qBAAqB,YAAY,OAAO,KAAK;AAAA,IACtD,kBAAkB,GAAG;AAAA,IACrB,YAAY,GAAG;AAAA,IACf,OAAO,GAAG;AAAA,EACZ;AAEA,YAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAG1C,eAAa,aAAa,kBAAkB,aAAa,qBAAqB,CAAC;AAC/E,eAAa,aAAa,iBAAiB,aAAa,oBAAoB,CAAC;AAC7E,eAAa,aAAa,sBAAsB,aAAa,yBAAyB,CAAC;AACvF,eAAa,aAAa,qBAAqB,aAAa,wBAAwB,CAAC;AACrF,eAAa,aAAa,cAAc,aAAa,gBAAgB,CAAC;AACtE,eAAa,aAAa,UAAU,aAAa,YAAY,CAAC;AAC9D,eAAa,aAAa,aAAa,cAAc,aAAa,qBAAqB,GAAG,IAAI,CAAC;AAG/F,eAAa,aAAa,sBAAsB,aAAa,yBAAyB,CAAC;AACvF,eAAa,aAAa,oBAAoB,aAAa,uBAAuB,CAAC;AACnF,eAAa,aAAa,uBAAuB,aAAa,0BAA0B,CAAC;AACzF,eAAa,aAAa,yBAAyB,aAAa,4BAA4B,CAAC;AAC7F,eAAa,aAAa,8BAA8B,aAAa,iCAAiC,CAAC;AAGvG,QAAM,gBAAgB,OAAO,cAAc,WACvC,mCACA;AACJ;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,aAAa,GAAG,IAAI;AAAA,EACjD;AACA,eAAa,aAAa,8BAA8B,aAAa,iCAAiC,CAAC;AACvG,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,kCAAkC,aAAa,qCAAqC,CAAC;AAC/G,eAAa,aAAa,2BAA2B,aAAa,8BAA8B,CAAC;AAGjG,eAAa,aAAa,gCAAgC,aAAa,mCAAmC,CAAC;AAG3G,eAAa,aAAa,2BAA2B,aAAa,8BAA8B,CAAC;AACjG,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,iCAAiC,aAAa,oCAAoC,CAAC;AAC7G,eAAa,aAAa,6BAA6B,aAAa,gCAAgC,CAAC;AACrG,eAAa,aAAa,kCAAkC,aAAa,qCAAqC,CAAC;AAC/G,eAAa,aAAa,qCAAqC,aAAa,wCAAwC,CAAC;AACrH,eAAa,aAAa,iCAAiC,aAAa,oCAAoC,CAAC;AAG7G,eAAa,aAAa,+BAA+B,aAAa,kCAAkC,CAAC;AACzG,eAAa,aAAa,qCAAqC,aAAa,wCAAwC,CAAC;AAGrH,eAAa,aAAa,uCAAuC,aAAa,0CAA0C,CAAC;AAGzH,eAAa,aAAa,6CAA6C,aAAa,gDAAgD,CAAC;AACrI,eAAa,aAAa,+CAA+C,aAAa,kDAAkD,CAAC;AAGzI,QAAM,gBAAgB;AAAA,IACpB;AAAA,IAAS;AAAA,IAAY;AAAA,IAAS;AAAA,IAAU;AAAA,IAAS;AAAA,IAAU;AAAA,IAC3D;AAAA,IAAU;AAAA,IAAU;AAAA,IAAW;AAAA,IAAU;AAAA,IAAa;AAAA,IACtD;AAAA,IAAU;AAAA,IAAS;AAAA,IAAW;AAAA,IAAU;AAAA,IAAW;AAAA,IACnD;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAc;AAAA,IAAS;AAAA,IAAY;AAAA,IACrD;AAAA,IAAY;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAS;AAAA,IAAS;AAAA,IAC/C;AAAA,IAAW;AAAA,IAAc;AAAA,IAAQ;AAAA,EACnC;AACA,aAAW,QAAQ,eAAe;AAChC,iBAAa,aAAa,kCAAkC,IAAI,QAAQ,aAAa,uCAAuC,IAAI,MAAM,CAAC;AAAA,EACzI;AAGA,eAAa,aAAa,oBAAoB,aAAa,uBAAuB,CAAC;AACnF,eAAa,aAAa,qBAAqB,aAAa,wBAAwB,CAAC;AAGrF,aAAW,aAAa,mBAAmB,sBAAsB;AACjE,aAAW,aAAa,oBAAoB,sBAAsB;AAGlE;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,2BAA2B,GAAG,IAAI;AAAA,EAC/D;AAGA;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,uBAAuB,GAAG,IAAI;AAAA,EAC3D;AAGA;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,gCAAgC,GAAG,IAAI;AAAA,EACpE;AACF;;;AE3IA,SAAS,sBAAsB,2BAA2B;AAE1D,eAAsB,YAAY,aAAoC;AACpE,QAAM,KAAK,MAAM,qBAAqB,WAAW;AACjD,QAAM,oBAAoB;AAAA,IACxB,KAAK;AAAA,IACL,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;;;ACRA,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,QAAAC,aAAY;AACrB,SAAS,UAAU,SAAS,iBAAiB;AAC7C,SAAS,wBAAAC,6BAA4B;AAG9B,SAAS,cAAc,QAA6B;AACzD,QAAM,EAAE,aAAa,YAAY,QAAQ,IAAI;AAC7C,QAAM,KAAK,aAAa,UAAU;AAElC,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,qBAAqB,SAAS,aAAa,EAAE;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,GAAG,GAAG,MAAM,IAAI,SAAS,YAAY,EAAE;AAAA,IACvC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,EAAAC,eAAcC,MAAK,aAAa,YAAY,GAAG,MAAM,KAAK,IAAI,GAAG,OAAO;AAC1E;AAEA,eAAsB,WAAW,aAAoC;AACnE,QAAM,KAAK,MAAMC,sBAAqB,WAAW;AACjD,QAAM,MAAM,IAAI,QAAQ;AACxB,WAAS,GAAG,GAAG,iBAAiB;AAAA,IAC9B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK,EAAE,GAAG,QAAQ,KAAK,cAAc,gBAAgB;AAAA,EACvD,CAAC;AACH;AAEA,eAAsB,aAAa,aAAqC;AACtE,QAAM,KAAK,MAAMA,sBAAqB,WAAW;AACjD,QAAM,MAAM,IAAI,QAAQ;AAExB,QAAM,QAAQ,UAAU,KAAK,CAAC,OAAO,KAAK,GAAG;AAAA,IAC3C,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,CAAC,WAA2B;AAC1C,UAAM,KAAK,MAAM;AAAA,EACnB;AAEA,UAAQ,GAAG,UAAU,OAAO;AAC5B,UAAQ,GAAG,WAAW,OAAO;AAE7B,SAAO,IAAI,QAAQ,CAAC,GAAG,WAAW;AAChC,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,cAAQ,IAAI,UAAU,OAAO;AAC7B,cAAQ,IAAI,WAAW,OAAO;AAC9B,cAAQ,KAAK,QAAQ,CAAC;AAAA,IACxB,CAAC;AACD,UAAM,GAAG,SAAS,MAAM;AAAA,EAC1B,CAAC;AACH;;;AC3DA,YAAYC,QAAO;AACnB,OAAOC,SAAQ;AACf,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAEzB,IAAM,oBAAoB;AAE1B,SAAS,YAAY,KAAmB;AACtC,QAAM,MACJ,SAAS,MAAM,WACX,SACA,SAAS,MAAM,UACb,UACA;AACR,OAAK,GAAG,GAAG,IAAI,KAAK,UAAU,GAAG,CAAC,EAAE;AACtC;AAmBA,eAAsB,kBAA0C;AAC9D,QAAM,IAAM,WAAQ;AAGpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,iBAAiB,4BAA4B;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,MAAE,OAAI,MAAM,oEAAoE;AAChF,aAAO;AAAA,IACT;AACA,iBAAc,MAAM,IAAI,KAAK;AAAA,EAC/B,QAAQ;AACN,IAAE,OAAI,MAAM,gEAAgE;AAC5E,WAAO;AAAA,EACT;AAGA,EAAE,OAAI;AAAA,IACJ,sBAAsBA,IAAG,KAAK,WAAW,gBAAgB,CAAC;AAAA,gBACvCA,IAAG,KAAKA,IAAG,KAAK,WAAW,SAAS,CAAC,CAAC;AAAA,EAC3D;AACA,cAAY,WAAW,gBAAgB;AAGvC,IAAE,MAAM,yCAAyC;AAEjD,QAAM,YAAY,WAAW,YAAY,KAAK;AAC9C,QAAM,WAAW,KAAK,IAAI,IAAI,WAAW,aAAa;AAEtD,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEhD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,iBAAiB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,aAAa,WAAW,YAAY,CAAC;AAAA,MAC9D,CAAC;AAED,UAAI,IAAI,IAAI;AACV,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAE,KAAKA,IAAG,MAAM,yBAAyB,CAAC;AAC1C,eAAO,KAAK;AAAA,MACd;AAEA,UAAI,IAAI,WAAW,KAAK;AACtB,UAAE,KAAKA,IAAG,IAAI,uBAAuB,CAAC;AACtC,eAAO;AAAA,MACT;AAEA,UAAI,IAAI,WAAW,KAAK;AACtB,UAAE,KAAKA,IAAG,IAAI,iCAAiC,CAAC;AAChD,eAAO;AAAA,MACT;AAAA,IAGF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,IAAE,KAAKA,IAAG,IAAI,iCAAiC,CAAC;AAChD,SAAO;AACT;;;APjFA,SAAS,YAAwB;AAC/B,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAqB,CAAC;AAC5B,MAAI;AAEJ,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,UAAM,WAAW,QAAQ,CAAC,KAAK,WAAW,GAAG;AAE7C,QAAI,QAAQ,gBAAgB,UAAU;AACpC,aAAO,aAAa,KAAK,EAAE,CAAC;AAAA,IAC9B,WAAW,QAAQ,UAAU,UAAU;AACrC,aAAO,WAAW,KAAK,EAAE,CAAC;AAAA,IAC5B,WAAW,QAAQ,YAAY,UAAU;AACvC,aAAO,YAAY,KAAK,EAAE,CAAC;AAAA,IAC7B,WAAW,QAAQ,aAAa,UAAU;AACxC,aAAO,QAAQ,KAAK,EAAE,CAAC;AAAA,IACzB,WAAW,QAAQ,gBAAgB;AACjC,aAAO,aAAa;AAAA,IACtB,WAAW,QAAQ,kBAAkB;AACnC,aAAO,cAAc;AAAA,IACvB,WAAW,QAAQ,yBAAyB,UAAU;AACpD,aAAO,kBAAkB,KAAK,EAAE,CAAC;AAAA,IACnC,WAAW,QAAQ,yBAAyB,UAAU;AACpD,aAAO,kBAAkB,KAAK,EAAE,CAAC;AAAA,IACnC,WAAW,QAAQ,sBAAsB,UAAU;AACjD,aAAO,eAAe,KAAK,EAAE,CAAC;AAAA,IAChC,WAAW,QAAQ,0BAA0B,UAAU;AACrD,aAAO,mBAAmB,KAAK,EAAE,CAAC;AAAA,IACpC,WAAW,CAAC,IAAI,WAAW,GAAG,GAAG;AAC/B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,aAAa,cAAc,mBAAmB,WAAW,IAAI;AAAA,EAC/D;AACF;AAEA,IAAM,cAA2D;AAAA,EAC/D,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AACd;AAEA,IAAM,cAAqE;AAAA,EACzE,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AACd;AAMA,SAAS,aAAa,UAA0C;AAC9D,QAAM,SAAiC,CAAC;AACxC,MAAI,CAACC,YAAW,QAAQ,EAAG,QAAO;AAClC,QAAM,UAAUC,cAAa,UAAU,OAAO;AAC9C,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,QAAQ,EAAG;AACf,UAAM,MAAM,QAAQ,MAAM,GAAG,KAAK,EAAE,KAAK;AACzC,QAAI,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAE,KAAK;AAExC,QAAK,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,KAAO,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,GAAI;AAC5F,YAAM,IAAI,MAAM,GAAG,EAAE;AAAA,IACvB;AACA,QAAI,IAAK,QAAO,GAAG,IAAI;AAAA,EACzB;AACA,SAAO;AACT;AAKA,SAAS,eAAuC;AAC9C,QAAM,SAAiC,CAAC;AACxC,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM,QAAQ,IAAI;AACtB,QAAM,OAAOC,SAAQ,GAAG,MAAM,MAAM,MAAM;AAE1C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,MAAM,KAAK;AAC1C,UAAM,UAAUC,SAAQ,KAAK,MAAM;AACnC,QAAIH,YAAW,OAAO,EAAG,OAAM,KAAK,OAAO;AAC3C,UAAME,SAAQ,GAAG;AAAA,EACnB;AAEA,aAAW,KAAK,MAAM,QAAQ,GAAG;AAC/B,WAAO,OAAO,QAAQ,aAAa,CAAC,CAAC;AAAA,EACvC;AACA,SAAO;AACT;AAEA,eAAe,aAAa,MAA+B;AACzD,QAAM,QAAQ,MAAQ,YAAS,EAAE,SAAS,cAAc,IAAI,GAAG,CAAC;AAChE,MAAM,YAAS,KAAK,GAAG;AACrB,IAAE,UAAO,YAAY;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,oBAAoB,QAAqC;AAEtE,QAAM,aAAa,aAAa;AAChC,QAAM,WACJ,OAAO,mBACP,QAAQ,IAAI,qBACZ,WAAW;AAEb,MAAI,UAAU;AACZ,QAAI,CAAC,OAAO,iBAAiB;AAC3B,MAAE,OAAI,KAAK,SAASE,IAAG,KAAK,mBAAmB,CAAC,SAAS,QAAQ,IAAI,oBAAoB,gBAAgB,WAAW,EAAE;AAAA,IACxH;AACA,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,MAAQ,UAAO;AAAA,IAC5B,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAM,YAAS,MAAM,GAAG;AACtB,IAAE,UAAO,YAAY;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,WAAW,WAAW;AACxB,UAAM,MAAM,MAAM,gBAAgB;AAClC,QAAI,IAAK,QAAO;AAEhB,IAAE,OAAI,KAAK,2DAA2D;AAAA,EACxE;AAEA,SAAO,aAAa,mBAAmB;AACzC;AAEA,eAAe,eACb,UACA,QACkB;AAClB,QAAM,SAAS,YAAY,QAAQ;AACnC,QAAM,UAAU,YAAY,QAAQ;AACpC,QAAM,aAAa,aAAa;AAEhC,QAAM,YAAY,MAAM,oBAAoB,MAAM;AAElD,QAAM,cACH,OAAO,OAAO,KACf,QAAQ,IAAI,MAAM,KAClB,WAAW,MAAM,KAChB,MAAM,aAAa,MAAM;AAE5B,MAAI,CAAE,OAAO,OAAO,MAA6B,QAAQ,IAAI,MAAM,KAAK,WAAW,MAAM,IAAI;AAC3F,IAAE,OAAI,KAAK,SAASA,IAAG,KAAK,MAAM,CAAC,SAAS,QAAQ,IAAI,MAAM,IAAI,gBAAgB,WAAW,EAAE;AAAA,EACjG;AAEA,SAAO,EAAE,WAAW,UAAU,YAAY;AAC5C;AAEA,eAAe,OAAO;AACpB,QAAM,SAAS,UAAU;AAEzB,MAAI,OAAO,YAAY;AAErB,QAAI,OAAO,aAAa,YAAY;AAClC,MAAE,OAAI;AAAA,QACJA,IAAG,OAAO,4DAAuD;AAAA,MACnE;AAAA,IACF;AACA,QAAI,OAAO,aAAa;AACtB,MAAE,OAAI;AAAA,QACJA,IAAG;AAAA,UACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAE,SAAMA,IAAG,OAAOA,IAAG,MAAM,0CAAqC,CAAC,CAAC;AAGlE,QAAI,cAAc,OAAO;AACzB,QAAI,CAAC,aAAa;AAChB,YAAM,OAAO,MAAQ,QAAK;AAAA,QACxB,SAAS;AAAA,QACT,aAAa;AAAA,QACb,cAAc;AAAA,QACd,SAAS,OAAO;AACd,cAAI,CAAC,MAAO,QAAO;AACnB,cAAI,CAAC,yBAAyB,KAAK,KAAK,GAAG;AACzC,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,MACF,CAAC;AACD,UAAM,YAAS,IAAI,GAAG;AACpB,QAAE,UAAO,YAAY;AACrB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,oBAAc;AAAA,IAChB;AAEA,UAAM,cAAc,mBAAmB,WAAW;AAClD,QAAI,cAAc,WAAW,GAAG;AAC9B,MAAE,UAAO,cAAc,WAAW,mBAAmB;AACrD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,QAAI,aAAa,OAAO;AACxB,QAAI,CAAC,YAAY;AACf,YAAM,WAAW,MAAQ,UAAO;AAAA,QAC9B,SAAS;AAAA,QACT,SAAS;AAAA,UACP,EAAE,OAAO,aAAa,OAAO,aAAa,UAAU,MAAM;AAAA,UAC1D,EAAE,OAAO,UAAU,OAAO,aAAa,OAAO,MAAM;AAAA,UACpD,EAAE,OAAO,cAAc,OAAO,aAAa,WAAW,MAAM;AAAA,QAC9D;AAAA,MACF,CAAC;AACD,UAAM,YAAS,QAAQ,GAAG;AACxB,QAAE,UAAO,YAAY;AACrB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,mBAAa;AAAA,IACf;AAGA,QAAI,YAAY,OAAO;AACvB,QAAI,CAAC,WAAW;AACd,YAAM,WAAW,MAAQ,UAAO;AAAA,QAC9B,SAAS;AAAA,QACT,SAAS;AAAA,UACP,EAAE,OAAO,UAAU,OAAO,4BAA4B,MAAM,cAAc;AAAA,UAC1E,EAAE,OAAO,SAAS,OAAO,gCAAgC;AAAA,QAC3D;AAAA,MACF,CAAC;AACD,UAAM,YAAS,QAAQ,GAAG;AACxB,QAAE,UAAO,YAAY;AACrB,gBAAQ,KAAK,CAAC;AAAA,MAChB;AACA,kBAAY;AAAA,IACd;AAGA,UAAM,UAAU,MAAM,eAAe,YAAY,MAAM;AAEvD,UAAM,SAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,OAAO,OAAO;AAAA,MACd,YAAY;AAAA,MACZ;AAAA,IACF;AAEA,UAAM,IAAM,WAAQ;AAGpB,MAAE,MAAM,wBAAwB;AAChC,oBAAgB,MAAM;AACtB,kBAAc,MAAM;AACpB,MAAE,KAAK,oBAAoB;AAG3B,MAAE,MAAM,4BAA4B;AACpC,QAAI;AACF,YAAM,YAAY,OAAO,WAAW;AACpC,QAAE,KAAK,wBAAwB;AAAA,IACjC,QAAQ;AACN,QAAE,KAAK,4DAAuD;AAC9D,cAAQ,KAAK,CAAC;AAAA,IAChB;AAGA,MAAE,MAAM,wBAAwB;AAChC,QAAI;AACF,YAAM,WAAW,OAAO,WAAW;AACnC,QAAE,KAAK,gBAAgB;AAAA,IACzB,SAAS,KAAK;AACZ,QAAE,KAAK,uBAAuB;AAC9B,MAAE,OAAI;AAAA,QACJ,OAAOA,IAAG,KAAK,MAAM,WAAW,mBAAmB,CAAC;AAAA,MACtD;AAAA,IACF;AAGA,IAAE,OAAI,KAAKA,IAAG,MAAM,wBAAwB,CAAC;AAC7C,UAAM,aAAa,OAAO,WAAW;AAAA,EACvC,OAAO;AAEL,UAAM,mBACJ,OAAO,eAAe,OAAO,cAAc,OAAO;AAEpD,QAAI;AAEJ,QAAI,kBAAkB;AACpB,eAAS;AAAA,QACP,aAAa,OAAO;AAAA,QACpB,aAAa,OAAO;AAAA,QACpB,YAAY,OAAO;AAAA,QACnB,WAAW,OAAO,aAAa;AAAA,QAC/B,UAAU,OAAO;AAAA,QACjB,OAAO,OAAO;AAAA,MAChB;AACA,cAAQ,IAAI,YAAY,OAAO,WAAW,KAAK;AAAA,IACjD,OAAO;AACL,eAAS,MAAM,WAAW,OAAO,aAAa;AAAA,QAC5C,YAAY,OAAO;AAAA,QACnB,WAAW,OAAO;AAAA,QAClB,UAAU,OAAO;AAAA,MACnB,CAAC;AACD,UAAI,UAAU,OAAO,OAAO;AAC1B,eAAO,QAAQ,OAAO;AAAA,MACxB;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ;AACX,cAAQ,KAAK,CAAC;AAAA,IAChB;AAEA,QAAI,kBAAkB;AACpB,sBAAgB,MAAM;AACtB,cAAQ,IAAI,oBAAoB;AAEhC,UAAI,CAAC,OAAO,aAAa;AACvB,gBAAQ,IAAI,4BAA4B;AACxC,YAAI;AACF,gBAAM,YAAY,OAAO,WAAW;AACpC,kBAAQ,IAAI,wBAAwB;AAAA,QACtC,QAAQ;AACN,kBAAQ;AAAA,YACN;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,cAAQ;AAAA,QACN;AAAA;AAAA,OAAuB,OAAO,WAAW;AAAA;AAAA;AAAA;AAAA,MAC3C;AAAA,IACF,OAAO;AACL,YAAM,IAAM,WAAQ;AAEpB,QAAE,MAAM,wBAAwB;AAChC,sBAAgB,MAAM;AACtB,QAAE,KAAK,oBAAoB;AAE3B,QAAE,MAAM,4BAA4B;AACpC,UAAI;AACF,cAAM,YAAY,OAAO,WAAW;AACpC,UAAE,KAAK,wBAAwB;AAAA,MACjC,QAAQ;AACN,UAAE,KAAK,4DAAuD;AAAA,MAChE;AAEA,MAAE;AAAA,QACA;AAAA,UACE,MAAM,OAAO,WAAW;AAAA,UACxB,iCAAiCA,IAAG,IAAI,qBAAqB,CAAC;AAAA,UAC9D,iCAAiCA,IAAG,IAAI,uBAAuB,CAAC;AAAA,UAChE,iCAAiCA,IAAG,IAAI,4BAA4B,CAAC;AAAA,QACvE,EAAE,KAAK,IAAI;AAAA,QACX;AAAA,MACF;AAEA,MAAE,SAAMA,IAAG,MAAM,OAAO,CAAC;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,KAAK,EAAE,MAAM,QAAQ,KAAK;","names":["p","pc","readFileSync","existsSync","resolve","dirname","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","p","pc","existsSync","readFileSync","dirname","resolve","pc"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/prompts.ts","../src/utils.ts","../src/scaffold.ts","../src/template.ts","../src/post-install.ts","../src/quickstart.ts","../src/device-auth.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { writeEnvLocal, runDbSetup, runDevServer } from \"./quickstart.js\";\nimport { loginViaBrowser } from \"./device-auth.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\nimport type { ProjectConfig, ApiKeys } from \"./utils.js\";\n\ninterface ParsedArgs extends Partial<ProjectConfig> {\n supyagentApiKey?: string;\n anthropicApiKey?: string;\n openaiApiKey?: string;\n openrouterApiKey?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n const args = process.argv.slice(2);\n const result: ParsedArgs = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n const hasValue = next && !next.startsWith(\"-\");\n\n if (arg === \"--provider\" && hasValue) {\n result.aiProvider = args[++i] as ProjectConfig[\"aiProvider\"];\n } else if (arg === \"--db\" && hasValue) {\n result.database = args[++i] as ProjectConfig[\"database\"];\n } else if (arg === \"--mode\" && hasValue) {\n result.agentMode = args[++i] as ProjectConfig[\"agentMode\"];\n } else if (arg === \"--model\" && hasValue) {\n result.model = args[++i];\n } else if (arg === \"--supyagent-api-key\" && hasValue) {\n result.supyagentApiKey = args[++i];\n } else if (arg === \"--anthropic-api-key\" && hasValue) {\n result.anthropicApiKey = args[++i];\n } else if (arg === \"--openai-api-key\" && hasValue) {\n result.openaiApiKey = args[++i];\n } else if (arg === \"--openrouter-api-key\" && hasValue) {\n result.openrouterApiKey = args[++i];\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n ...result,\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n };\n}\n\nconst ENV_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\n\nconst CLI_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], keyof ParsedArgs> = {\n anthropic: \"anthropicApiKey\",\n openai: \"openaiApiKey\",\n openrouter: \"openrouterApiKey\",\n};\n\n/**\n * Parse a `.env` file into a key-value map. Handles simple KEY=VALUE lines,\n * quoted values, and ignores comments/blank lines.\n */\nfunction parseEnvFile(filePath: string): Record<string, string> {\n const result: Record<string, string> = {};\n if (!existsSync(filePath)) return result;\n const content = readFileSync(filePath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx < 1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n let val = trimmed.slice(eqIdx + 1).trim();\n // Strip surrounding quotes\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1);\n }\n if (val) result[key] = val;\n }\n return result;\n}\n\n/**\n * Walk up from cwd looking for .env files and merge them (closest wins).\n */\nfunction loadEnvFiles(): Record<string, string> {\n const merged: Record<string, string> = {};\n const files: string[] = [];\n let dir = process.cwd();\n const root = dirname(dir) === dir ? dir : \"/\";\n // Collect .env files walking up (stop after 5 levels or root)\n for (let i = 0; i < 5 && dir !== root; i++) {\n const envPath = resolve(dir, \".env\");\n if (existsSync(envPath)) files.push(envPath);\n dir = dirname(dir);\n }\n // Merge in reverse order so closest .env wins\n for (const f of files.reverse()) {\n Object.assign(merged, parseEnvFile(f));\n }\n return merged;\n}\n\nasync function promptForKey(name: string): Promise<string> {\n const value = await p.password({ message: `Enter your ${name}` });\n if (p.isCancel(value)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n return value;\n}\n\nasync function resolveSupyagentKey(parsed: ParsedArgs): Promise<string> {\n // 1. Check CLI flag, env, and .env files first\n const dotenvVars = loadEnvFiles();\n const existing =\n parsed.supyagentApiKey ??\n process.env.SUPYAGENT_API_KEY ??\n dotenvVars.SUPYAGENT_API_KEY;\n\n if (existing) {\n if (!parsed.supyagentApiKey) {\n p.log.info(`Using ${pc.cyan(\"SUPYAGENT_API_KEY\")} from ${process.env.SUPYAGENT_API_KEY ? \"environment\" : \".env file\"}`);\n }\n return existing;\n }\n\n // 2. Ask user how they want to authenticate\n const method = await p.select({\n message: \"How would you like to authenticate with Supyagent?\",\n options: [\n {\n value: \"browser\",\n label: \"Login via browser\",\n hint: \"recommended — opens app.supyagent.com\",\n },\n {\n value: \"manual\",\n label: \"Paste API key\",\n hint: \"enter an existing key manually\",\n },\n ],\n });\n\n if (p.isCancel(method)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n\n if (method === \"browser\") {\n const key = await loginViaBrowser();\n if (key) return key;\n // Browser flow failed — fall back to manual entry\n p.log.warn(\"Browser login failed. Please enter your API key manually.\");\n }\n\n return promptForKey(\"SUPYAGENT_API_KEY\");\n}\n\nasync function resolveApiKeys(\n provider: ProjectConfig[\"aiProvider\"],\n parsed: ParsedArgs,\n): Promise<ApiKeys> {\n const envKey = ENV_KEY_MAP[provider];\n const cliFlag = CLI_KEY_MAP[provider];\n const dotenvVars = loadEnvFiles();\n\n const supyagent = await resolveSupyagentKey(parsed);\n\n const providerKey =\n (parsed[cliFlag] as string | undefined) ??\n process.env[envKey] ??\n dotenvVars[envKey] ??\n (await promptForKey(envKey));\n\n if (!(parsed[cliFlag] as string | undefined) && (process.env[envKey] || dotenvVars[envKey])) {\n p.log.info(`Using ${pc.cyan(envKey)} from ${process.env[envKey] ? \"environment\" : \".env file\"}`);\n }\n\n return { supyagent, provider: providerKey };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n // Collect project config — prompts or CLI flags\n let config: ProjectConfig | null;\n\n if (parsed.projectName && parsed.aiProvider && parsed.database) {\n // All required args provided — skip interactive prompts\n const projectPath = resolveProjectPath(parsed.projectName);\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${parsed.projectName}\" already exists.`);\n process.exit(1);\n }\n config = {\n projectName: parsed.projectName,\n projectPath,\n aiProvider: parsed.aiProvider,\n agentMode: parsed.agentMode ?? \"skills\",\n database: parsed.database,\n model: parsed.model,\n };\n } else {\n config = await runPrompts(parsed.projectName, {\n aiProvider: parsed.aiProvider,\n agentMode: parsed.agentMode,\n database: parsed.database,\n });\n if (config && parsed.model) {\n config.model = parsed.model;\n }\n }\n\n if (!config) {\n process.exit(1);\n }\n\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n // If PostgreSQL, prompt for DATABASE_URL\n if (config.database === \"postgres\") {\n const dbUrl = await p.text({\n message: \"PostgreSQL connection URL\",\n placeholder: DB_CONFIGS.postgres.url,\n validate(value) {\n if (!value) return \"Connection URL is required\";\n },\n });\n if (p.isCancel(dbUrl)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n config.databaseUrl = dbUrl as string;\n } else {\n config.databaseUrl = DB_CONFIGS.sqlite.url;\n }\n\n // Resolve API keys\n const apiKeys = await resolveApiKeys(config.aiProvider, parsed);\n config.apiKeys = apiKeys;\n\n const s = p.spinner();\n\n // Scaffold\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n writeEnvLocal(config);\n s.stop(\"Scaffolded project\");\n\n // Install deps\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n process.exit(1);\n }\n\n // Database setup\n s.start(\"Setting up database...\");\n try {\n await runDbSetup(config.projectPath, config.databaseUrl);\n s.stop(\"Database ready\");\n } catch {\n s.stop(\"Database setup failed\");\n p.log.warn(\n `Run ${pc.cyan(`cd ${config.projectName} && pnpm db:setup`)} manually`,\n );\n }\n\n // Dev server\n p.log.info(pc.green(\"Starting dev server...\"));\n await runDevServer(config.projectPath);\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport interface PromptOptions {\n aiProvider?: ProjectConfig[\"aiProvider\"];\n database?: ProjectConfig[\"database\"];\n agentMode?: ProjectConfig[\"agentMode\"];\n skipDatabase?: boolean;\n}\n\nexport async function runPrompts(\n argName?: string,\n options?: PromptOptions,\n): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n let aiProvider: ProjectConfig[\"aiProvider\"];\n if (options?.aiProvider) {\n aiProvider = options.aiProvider;\n } else {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n aiProvider = selected;\n }\n\n let agentMode: ProjectConfig[\"agentMode\"];\n if (options?.agentMode) {\n agentMode = options.agentMode;\n } else {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n }) as \"skills\" | \"tools\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n agentMode = selected;\n }\n\n let database: ProjectConfig[\"database\"];\n if (options?.database || options?.skipDatabase) {\n database = options?.database ?? \"sqlite\";\n } else {\n const selected = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n database = selected;\n }\n\n return { projectName, projectPath, aiProvider, agentMode, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ApiKeys {\n supyagent?: string;\n provider?: string;\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n agentMode: \"skills\" | \"tools\";\n model?: string;\n databaseUrl?: string;\n apiKeys?: ApiKeys;\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-6-20250620')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n\nexport function buildModelExpression(\n provider: ProjectConfig[\"aiProvider\"],\n customModelId?: string,\n): string {\n const config = AI_PROVIDERS[provider];\n if (!customModelId) return config.model;\n switch (provider) {\n case \"anthropic\":\n return `anthropic('${customModelId}')`;\n case \"openai\":\n return `openai('${customModelId}')`;\n case \"openrouter\":\n return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;\n }\n}\n","import { mkdirSync, writeFileSync, readFileSync, copyFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyTemplate } from \"./template.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, buildModelExpression, type ProjectConfig } from \"./utils.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst TEMPLATES_DIR = join(__dirname, \"..\", \"templates\");\n\nfunction readTemplate(relativePath: string): string {\n return readFileSync(join(TEMPLATES_DIR, relativePath), \"utf-8\");\n}\n\nfunction writeProject(projectPath: string, relativePath: string, content: string): void {\n const fullPath = join(projectPath, relativePath);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, content, \"utf-8\");\n}\n\nfunction copyBinary(projectPath: string, relativePath: string, templateRelativePath: string): void {\n const src = join(TEMPLATES_DIR, templateRelativePath);\n const dest = join(projectPath, relativePath);\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(src, dest);\n}\n\nexport function scaffoldProject(config: ProjectConfig): void {\n const { projectPath, projectName, aiProvider, database } = config;\n const ai = AI_PROVIDERS[aiProvider];\n const db = DB_CONFIGS[database];\n\n const vars: Record<string, string> = {\n projectName,\n aiProviderPackage: ai.package,\n aiProviderVersion: ai.packageVersion,\n aiProviderImport: ai.import,\n aiModel: buildModelExpression(aiProvider, config.model),\n aiProviderEnvKey: ai.envKey,\n dbProvider: db.provider,\n dbUrl: db.url,\n };\n\n mkdirSync(projectPath, { recursive: true });\n\n // ── Base files ──\n writeProject(projectPath, \"next.config.ts\", readTemplate(\"base/next.config.ts\"));\n writeProject(projectPath, \"tsconfig.json\", readTemplate(\"base/tsconfig.json\"));\n writeProject(projectPath, \"tailwind.config.ts\", readTemplate(\"base/tailwind.config.ts\"));\n writeProject(projectPath, \"postcss.config.js\", readTemplate(\"base/postcss.config.js\"));\n writeProject(projectPath, \".gitignore\", readTemplate(\"base/gitignore\"));\n writeProject(projectPath, \".npmrc\", readTemplate(\"base/npmrc\"));\n writeProject(projectPath, \"README.md\", applyTemplate(readTemplate(\"base/README.md.tmpl\"), vars));\n\n // ── Source files ──\n writeProject(projectPath, \"src/app/layout.tsx\", readTemplate(\"base/src/app/layout.tsx\"));\n writeProject(projectPath, \"src/app/page.tsx\", readTemplate(\"base/src/app/page.tsx\"));\n writeProject(projectPath, \"src/app/globals.css\", readTemplate(\"base/src/app/globals.css\"));\n writeProject(projectPath, \"src/app/chat/page.tsx\", readTemplate(\"base/src/app/chat/page.tsx\"));\n writeProject(projectPath, \"src/app/chat/[id]/page.tsx\", readTemplate(\"base/src/app/chat/[id]/page.tsx\"));\n\n // API routes — use skills or tools template based on agentMode\n const routeTemplate = config.agentMode === \"skills\"\n ? \"api-route/route.skills.ts.tmpl\"\n : \"api-route/route.tools.ts.tmpl\";\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(routeTemplate), vars)\n );\n writeProject(\n projectPath,\n \"src/app/api/chat/compact/route.ts\",\n applyTemplate(readTemplate(\"api-route/compact/route.ts.tmpl\"), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n writeProject(projectPath, \"src/app/api/jobs/[id]/route.ts\", readTemplate(\"base/src/app/api/jobs/[id]/route.ts\"));\n writeProject(projectPath, \"src/app/api/me/route.ts\", readTemplate(\"base/src/app/api/me/route.ts\"));\n\n // Hooks\n writeProject(projectPath, \"src/hooks/use-job-polling.ts\", readTemplate(\"base/src/hooks/use-job-polling.ts\"));\n\n // Components\n writeProject(projectPath, \"src/components/chat.tsx\", readTemplate(\"base/src/components/chat.tsx\"));\n writeProject(projectPath, \"src/components/chat-sidebar.tsx\", readTemplate(\"base/src/components/chat-sidebar.tsx\"));\n writeProject(projectPath, \"src/components/chat-message.tsx\", readTemplate(\"base/src/components/chat-message.tsx\"));\n writeProject(projectPath, \"src/components/chat-input.tsx\", readTemplate(\"base/src/components/chat-input.tsx\"));\n writeProject(projectPath, \"src/components/header.tsx\", readTemplate(\"base/src/components/header.tsx\"));\n writeProject(projectPath, \"src/components/user-button.tsx\", readTemplate(\"base/src/components/user-button.tsx\"));\n writeProject(projectPath, \"src/components/theme-provider.tsx\", readTemplate(\"base/src/components/theme-provider.tsx\"));\n writeProject(projectPath, \"src/components/slash-menu.tsx\", readTemplate(\"base/src/components/slash-menu.tsx\"));\n\n // UI primitives (shadcn-style)\n writeProject(projectPath, \"src/components/ui/badge.tsx\", readTemplate(\"base/src/components/ui/badge.tsx\"));\n writeProject(projectPath, \"src/components/ui/collapsible.tsx\", readTemplate(\"base/src/components/ui/collapsible.tsx\"));\n\n // AI Elements (pre-bundled Tool component)\n writeProject(projectPath, \"src/components/ai-elements/tool.tsx\", readTemplate(\"base/src/components/ai-elements/tool.tsx\"));\n\n // Supyagent tool rendering (local, editable)\n writeProject(projectPath, \"src/components/supyagent/tool-message.tsx\", readTemplate(\"base/src/components/supyagent/tool-message.tsx\"));\n writeProject(projectPath, \"src/components/supyagent/tool-renderers.tsx\", readTemplate(\"base/src/components/supyagent/tool-renderers.tsx\"));\n\n // Tool renderers — one per integration\n const toolRenderers = [\n \"gmail\", \"calendar\", \"slack\", \"github\", \"drive\", \"search\", \"docs\",\n \"sheets\", \"slides\", \"hubspot\", \"linear\", \"pipedrive\", \"compute\",\n \"resend\", \"inbox\", \"discord\", \"notion\", \"twitter\", \"telegram\",\n \"stripe\", \"jira\", \"salesforce\", \"brevo\", \"calendly\", \"twilio\",\n \"linkedin\", \"bash\", \"image\", \"audio\", \"video\", \"whatsapp\",\n \"browser\", \"view-image\", \"jobs\", \"generic\",\n ];\n for (const tool of toolRenderers) {\n writeProject(projectPath, `src/components/supyagent/tools/${tool}.tsx`, readTemplate(`base/src/components/supyagent/tools/${tool}.tsx`));\n }\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Branding assets ──\n copyBinary(projectPath, \"public/logo.png\", \"base/public/logo.png\");\n copyBinary(projectPath, \"src/app/icon.png\", \"base/public/logo.png\");\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync, spawn as nodeSpawn } from \"node:child_process\";\nimport { detectPackageManager } from \"nypm\";\nimport { AI_PROVIDERS, DB_CONFIGS, type ProjectConfig } from \"./utils.js\";\n\nexport function writeEnvLocal(config: ProjectConfig): void {\n const { projectPath, aiProvider, apiKeys, database, databaseUrl } = config;\n const ai = AI_PROVIDERS[aiProvider];\n const dbUrl = databaseUrl ?? DB_CONFIGS[database].url;\n\n const lines = [\n \"# Supyagent — Get your API key at https://app.supyagent.com\",\n `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? \"\"}`,\n \"\",\n \"# AI Provider\",\n `${ai.envKey}=${apiKeys?.provider ?? \"\"}`,\n \"\",\n \"# Database\",\n `DATABASE_URL=\"${dbUrl}\"`,\n \"\",\n ];\n\n writeFileSync(join(projectPath, \".env.local\"), lines.join(\"\\n\"), \"utf-8\");\n}\n\nexport async function runDbSetup(projectPath: string, databaseUrl?: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n execSync(`${cmd} run db:setup`, {\n cwd: projectPath,\n stdio: \"inherit\",\n env: { ...process.env, DATABASE_URL: databaseUrl ?? \"file:./dev.db\" },\n });\n}\n\nexport async function runDevServer(projectPath: string): Promise<never> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n\n const child = nodeSpawn(cmd, [\"run\", \"dev\"], {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n\n const forward = (signal: NodeJS.Signals) => {\n child.kill(signal);\n };\n\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n return new Promise((_, reject) => {\n child.on(\"close\", (code) => {\n process.off(\"SIGINT\", forward);\n process.off(\"SIGTERM\", forward);\n process.exit(code ?? 0);\n });\n child.on(\"error\", reject);\n });\n}\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { exec } from \"node:child_process\";\nimport { platform } from \"node:os\";\n\nconst SUPYAGENT_API_URL = \"https://app.supyagent.com\";\n\nfunction openBrowser(url: string): void {\n const cmd =\n platform() === \"darwin\"\n ? \"open\"\n : platform() === \"win32\"\n ? \"start\"\n : \"xdg-open\";\n exec(`${cmd} ${JSON.stringify(url)}`);\n}\n\ninterface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_uri: string;\n expires_in: number;\n interval: number;\n}\n\n/**\n * Authenticate via the device code flow (RFC 8628).\n *\n * 1. Request a device code from the Supyagent API\n * 2. Open the browser for the user to approve\n * 3. Poll until approved, denied, or expired\n *\n * Returns the API key on success, or null on failure.\n */\nexport async function loginViaBrowser(): Promise<string | null> {\n const s = p.spinner();\n\n // 1. Request device code\n let deviceCode: DeviceCodeResponse;\n try {\n const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/code`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n });\n if (!res.ok) {\n p.log.error(\"Failed to start browser login. Please enter your API key manually.\");\n return null;\n }\n deviceCode = (await res.json()) as DeviceCodeResponse;\n } catch {\n p.log.error(\"Could not reach Supyagent. Please enter your API key manually.\");\n return null;\n }\n\n // 2. Open browser and show code\n p.log.step(\n `Opening browser to ${pc.cyan(deviceCode.verification_uri)}\\n` +\n ` Enter code: ${pc.bold(pc.cyan(deviceCode.user_code))}`,\n );\n openBrowser(deviceCode.verification_uri);\n\n // 3. Poll for approval\n s.start(\"Waiting for approval in your browser...\");\n\n const interval = (deviceCode.interval || 5) * 1000;\n const deadline = Date.now() + deviceCode.expires_in * 1000;\n\n while (Date.now() < deadline) {\n await new Promise((r) => setTimeout(r, interval));\n\n try {\n const res = await fetch(`${SUPYAGENT_API_URL}/api/v1/auth/device/token`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ device_code: deviceCode.device_code }),\n });\n\n if (res.ok) {\n const data = (await res.json()) as { api_key: string };\n s.stop(pc.green(\"Logged in successfully!\"));\n return data.api_key;\n }\n\n if (res.status === 403) {\n s.stop(pc.red(\"Authorization denied.\"));\n return null;\n }\n\n if (res.status === 410) {\n s.stop(pc.red(\"Code expired. Please try again.\"));\n return null;\n }\n\n // 428 = still pending, keep polling\n } catch {\n // Network error — keep trying\n }\n }\n\n s.stop(pc.red(\"Timed out waiting for approval.\"));\n return null;\n}\n"],"mappings":";;;AAAA,YAAYA,QAAO;AACnB,OAAOC,SAAQ;AACf,SAAS,gBAAAC,eAAc,cAAAC,mBAAkB;AACzC,SAAS,WAAAC,UAAS,WAAAC,gBAAe;;;ACHjC,YAAY,OAAO;AACnB,OAAO,QAAQ;;;ACDf,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AAEjB,SAAS,mBAAmB,MAAsB;AACvD,SAAO,QAAQ,QAAQ,IAAI,GAAG,IAAI;AACpC;AAEO,SAAS,cAAc,MAAuB;AACnD,SAAO,WAAW,IAAI;AACxB;AAkBO,IAAM,eAAe;AAAA,EAC1B,WAAW;AAAA,IACT,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,EACV;AACF;AAEO,IAAM,aAAa;AAAA,EACxB,QAAQ;AAAA,IACN,OAAO;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,EACP;AAAA,EACA,UAAU;AAAA,IACR,OAAO;AAAA,IACP,UAAU;AAAA,IACV,KAAK;AAAA,EACP;AACF;AAEO,SAAS,qBACd,UACA,eACQ;AACR,QAAM,SAAS,aAAa,QAAQ;AACpC,MAAI,CAAC,cAAe,QAAO,OAAO;AAClC,UAAQ,UAAU;AAAA,IAChB,KAAK;AACH,aAAO,cAAc,aAAa;AAAA,IACpC,KAAK;AACH,aAAO,WAAW,aAAa;AAAA,IACjC,KAAK;AACH,aAAO,iEAAiE,aAAa;AAAA,EACzF;AACF;;;ADrEA,eAAsB,WACpB,SACA,SAC+B;AAC/B,EAAE,QAAM,GAAG,OAAO,GAAG,MAAM,wBAAwB,CAAC,CAAC;AAErD,QAAM,cAAc,WAAW,MAAQ,OAAK;AAAA,IAC1C,SAAS;AAAA,IACT,aAAa;AAAA,IACb,cAAc;AAAA,IACd,SAAS,OAAO;AACd,UAAI,CAAC,MAAO,QAAO;AACnB,UAAI,CAAC,yBAAyB,KAAK,KAAK,GAAG;AACzC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAM,WAAS,WAAW,GAAG;AAC3B,IAAE,SAAO,YAAY;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,cAAc,mBAAmB,WAAW;AAElD,MAAI,cAAc,WAAW,GAAG;AAC9B,IAAE,SAAO,cAAc,WAAW,mBAAmB;AACrD,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI,SAAS,YAAY;AACvB,iBAAa,QAAQ;AAAA,EACvB,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,aAAa,OAAO,aAAa,UAAU,MAAM;AAAA,QAC1D,EAAE,OAAO,UAAU,OAAO,aAAa,OAAO,MAAM;AAAA,QACpD,EAAE,OAAO,cAAc,OAAO,aAAa,WAAW,MAAM;AAAA,MAC9D;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,iBAAa;AAAA,EACf;AAEA,MAAI;AACJ,MAAI,SAAS,WAAW;AACtB,gBAAY,QAAQ;AAAA,EACtB,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,4BAA4B,MAAM,cAAc;AAAA,QAC1E,EAAE,OAAO,SAAS,OAAO,gCAAgC;AAAA,MAC3D;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,gBAAY;AAAA,EACd;AAEA,MAAI;AACJ,MAAI,SAAS,YAAY,SAAS,cAAc;AAC9C,eAAW,SAAS,YAAY;AAAA,EAClC,OAAO;AACL,UAAM,WAAW,MAAQ,SAAO;AAAA,MAC9B,SAAS;AAAA,MACT,SAAS;AAAA,QACP,EAAE,OAAO,UAAU,OAAO,WAAW,OAAO,MAAM;AAAA,QAClD,EAAE,OAAO,YAAY,OAAO,WAAW,SAAS,MAAM;AAAA,MACxD;AAAA,IACF,CAAC;AAED,QAAM,WAAS,QAAQ,GAAG;AACxB,MAAE,SAAO,YAAY;AACrB,aAAO;AAAA,IACT;AACA,eAAW;AAAA,EACb;AAEA,SAAO,EAAE,aAAa,aAAa,YAAY,WAAW,SAAS;AACrE;;;AErGA,SAAS,WAAW,eAAe,cAAc,oBAAoB;AACrE,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;;;ACCvB,SAAS,cACd,SACA,WACQ;AACR,SAAO,QAAQ,QAAQ,kBAAkB,CAAC,OAAO,QAAQ;AACvD,WAAO,OAAO,YAAY,UAAU,GAAG,IAAI;AAAA,EAC7C,CAAC;AACH;;;ADJA,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,gBAAgB,KAAK,WAAW,MAAM,WAAW;AAEvD,SAAS,aAAa,cAA8B;AAClD,SAAO,aAAa,KAAK,eAAe,YAAY,GAAG,OAAO;AAChE;AAEA,SAAS,aAAa,aAAqB,cAAsB,SAAuB;AACtF,QAAM,WAAW,KAAK,aAAa,YAAY;AAC/C,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,SAAS,OAAO;AAC1C;AAEA,SAAS,WAAW,aAAqB,cAAsB,sBAAoC;AACjG,QAAM,MAAM,KAAK,eAAe,oBAAoB;AACpD,QAAM,OAAO,KAAK,aAAa,YAAY;AAC3C,YAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,eAAa,KAAK,IAAI;AACxB;AAEO,SAAS,gBAAgB,QAA6B;AAC3D,QAAM,EAAE,aAAa,aAAa,YAAY,SAAS,IAAI;AAC3D,QAAM,KAAK,aAAa,UAAU;AAClC,QAAM,KAAK,WAAW,QAAQ;AAE9B,QAAM,OAA+B;AAAA,IACnC;AAAA,IACA,mBAAmB,GAAG;AAAA,IACtB,mBAAmB,GAAG;AAAA,IACtB,kBAAkB,GAAG;AAAA,IACrB,SAAS,qBAAqB,YAAY,OAAO,KAAK;AAAA,IACtD,kBAAkB,GAAG;AAAA,IACrB,YAAY,GAAG;AAAA,IACf,OAAO,GAAG;AAAA,EACZ;AAEA,YAAU,aAAa,EAAE,WAAW,KAAK,CAAC;AAG1C,eAAa,aAAa,kBAAkB,aAAa,qBAAqB,CAAC;AAC/E,eAAa,aAAa,iBAAiB,aAAa,oBAAoB,CAAC;AAC7E,eAAa,aAAa,sBAAsB,aAAa,yBAAyB,CAAC;AACvF,eAAa,aAAa,qBAAqB,aAAa,wBAAwB,CAAC;AACrF,eAAa,aAAa,cAAc,aAAa,gBAAgB,CAAC;AACtE,eAAa,aAAa,UAAU,aAAa,YAAY,CAAC;AAC9D,eAAa,aAAa,aAAa,cAAc,aAAa,qBAAqB,GAAG,IAAI,CAAC;AAG/F,eAAa,aAAa,sBAAsB,aAAa,yBAAyB,CAAC;AACvF,eAAa,aAAa,oBAAoB,aAAa,uBAAuB,CAAC;AACnF,eAAa,aAAa,uBAAuB,aAAa,0BAA0B,CAAC;AACzF,eAAa,aAAa,yBAAyB,aAAa,4BAA4B,CAAC;AAC7F,eAAa,aAAa,8BAA8B,aAAa,iCAAiC,CAAC;AAGvG,QAAM,gBAAgB,OAAO,cAAc,WACvC,mCACA;AACJ;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,aAAa,GAAG,IAAI;AAAA,EACjD;AACA;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,iCAAiC,GAAG,IAAI;AAAA,EACrE;AACA,eAAa,aAAa,8BAA8B,aAAa,iCAAiC,CAAC;AACvG,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,kCAAkC,aAAa,qCAAqC,CAAC;AAC/G,eAAa,aAAa,2BAA2B,aAAa,8BAA8B,CAAC;AAGjG,eAAa,aAAa,gCAAgC,aAAa,mCAAmC,CAAC;AAG3G,eAAa,aAAa,2BAA2B,aAAa,8BAA8B,CAAC;AACjG,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AACjH,eAAa,aAAa,iCAAiC,aAAa,oCAAoC,CAAC;AAC7G,eAAa,aAAa,6BAA6B,aAAa,gCAAgC,CAAC;AACrG,eAAa,aAAa,kCAAkC,aAAa,qCAAqC,CAAC;AAC/G,eAAa,aAAa,qCAAqC,aAAa,wCAAwC,CAAC;AACrH,eAAa,aAAa,iCAAiC,aAAa,oCAAoC,CAAC;AAG7G,eAAa,aAAa,+BAA+B,aAAa,kCAAkC,CAAC;AACzG,eAAa,aAAa,qCAAqC,aAAa,wCAAwC,CAAC;AAGrH,eAAa,aAAa,uCAAuC,aAAa,0CAA0C,CAAC;AAGzH,eAAa,aAAa,6CAA6C,aAAa,gDAAgD,CAAC;AACrI,eAAa,aAAa,+CAA+C,aAAa,kDAAkD,CAAC;AAGzI,QAAM,gBAAgB;AAAA,IACpB;AAAA,IAAS;AAAA,IAAY;AAAA,IAAS;AAAA,IAAU;AAAA,IAAS;AAAA,IAAU;AAAA,IAC3D;AAAA,IAAU;AAAA,IAAU;AAAA,IAAW;AAAA,IAAU;AAAA,IAAa;AAAA,IACtD;AAAA,IAAU;AAAA,IAAS;AAAA,IAAW;AAAA,IAAU;AAAA,IAAW;AAAA,IACnD;AAAA,IAAU;AAAA,IAAQ;AAAA,IAAc;AAAA,IAAS;AAAA,IAAY;AAAA,IACrD;AAAA,IAAY;AAAA,IAAQ;AAAA,IAAS;AAAA,IAAS;AAAA,IAAS;AAAA,IAC/C;AAAA,IAAW;AAAA,IAAc;AAAA,IAAQ;AAAA,EACnC;AACA,aAAW,QAAQ,eAAe;AAChC,iBAAa,aAAa,kCAAkC,IAAI,QAAQ,aAAa,uCAAuC,IAAI,MAAM,CAAC;AAAA,EACzI;AAGA,eAAa,aAAa,oBAAoB,aAAa,uBAAuB,CAAC;AACnF,eAAa,aAAa,qBAAqB,aAAa,wBAAwB,CAAC;AAGrF,aAAW,aAAa,mBAAmB,sBAAsB;AACjE,aAAW,aAAa,oBAAoB,sBAAsB;AAGlE;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,2BAA2B,GAAG,IAAI;AAAA,EAC/D;AAGA;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,uBAAuB,GAAG,IAAI;AAAA,EAC3D;AAGA;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,gCAAgC,GAAG,IAAI;AAAA,EACpE;AACF;;;AEhJA,SAAS,sBAAsB,2BAA2B;AAE1D,eAAsB,YAAY,aAAoC;AACpE,QAAM,KAAK,MAAM,qBAAqB,WAAW;AACjD,QAAM,oBAAoB;AAAA,IACxB,KAAK;AAAA,IACL,gBAAgB,IAAI;AAAA,EACtB,CAAC;AACH;;;ACRA,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,QAAAC,aAAY;AACrB,SAAS,UAAU,SAAS,iBAAiB;AAC7C,SAAS,wBAAAC,6BAA4B;AAG9B,SAAS,cAAc,QAA6B;AACzD,QAAM,EAAE,aAAa,YAAY,SAAS,UAAU,YAAY,IAAI;AACpE,QAAM,KAAK,aAAa,UAAU;AAClC,QAAM,QAAQ,eAAe,WAAW,QAAQ,EAAE;AAElD,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,qBAAqB,SAAS,aAAa,EAAE;AAAA,IAC7C;AAAA,IACA;AAAA,IACA,GAAG,GAAG,MAAM,IAAI,SAAS,YAAY,EAAE;AAAA,IACvC;AAAA,IACA;AAAA,IACA,iBAAiB,KAAK;AAAA,IACtB;AAAA,EACF;AAEA,EAAAC,eAAcC,MAAK,aAAa,YAAY,GAAG,MAAM,KAAK,IAAI,GAAG,OAAO;AAC1E;AAEA,eAAsB,WAAW,aAAqB,aAAqC;AACzF,QAAM,KAAK,MAAMC,sBAAqB,WAAW;AACjD,QAAM,MAAM,IAAI,QAAQ;AACxB,WAAS,GAAG,GAAG,iBAAiB;AAAA,IAC9B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK,EAAE,GAAG,QAAQ,KAAK,cAAc,eAAe,gBAAgB;AAAA,EACtE,CAAC;AACH;AAEA,eAAsB,aAAa,aAAqC;AACtE,QAAM,KAAK,MAAMA,sBAAqB,WAAW;AACjD,QAAM,MAAM,IAAI,QAAQ;AAExB,QAAM,QAAQ,UAAU,KAAK,CAAC,OAAO,KAAK,GAAG;AAAA,IAC3C,KAAK;AAAA,IACL,OAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,CAAC,WAA2B;AAC1C,UAAM,KAAK,MAAM;AAAA,EACnB;AAEA,UAAQ,GAAG,UAAU,OAAO;AAC5B,UAAQ,GAAG,WAAW,OAAO;AAE7B,SAAO,IAAI,QAAQ,CAAC,GAAG,WAAW;AAChC,UAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,cAAQ,IAAI,UAAU,OAAO;AAC7B,cAAQ,IAAI,WAAW,OAAO;AAC9B,cAAQ,KAAK,QAAQ,CAAC;AAAA,IACxB,CAAC;AACD,UAAM,GAAG,SAAS,MAAM;AAAA,EAC1B,CAAC;AACH;;;AC5DA,YAAYC,QAAO;AACnB,OAAOC,SAAQ;AACf,SAAS,YAAY;AACrB,SAAS,gBAAgB;AAEzB,IAAM,oBAAoB;AAE1B,SAAS,YAAY,KAAmB;AACtC,QAAM,MACJ,SAAS,MAAM,WACX,SACA,SAAS,MAAM,UACb,UACA;AACR,OAAK,GAAG,GAAG,IAAI,KAAK,UAAU,GAAG,CAAC,EAAE;AACtC;AAmBA,eAAsB,kBAA0C;AAC9D,QAAM,IAAM,WAAQ;AAGpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,iBAAiB,4BAA4B;AAAA,MACtE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAChD,CAAC;AACD,QAAI,CAAC,IAAI,IAAI;AACX,MAAE,OAAI,MAAM,oEAAoE;AAChF,aAAO;AAAA,IACT;AACA,iBAAc,MAAM,IAAI,KAAK;AAAA,EAC/B,QAAQ;AACN,IAAE,OAAI,MAAM,gEAAgE;AAC5E,WAAO;AAAA,EACT;AAGA,EAAE,OAAI;AAAA,IACJ,sBAAsBA,IAAG,KAAK,WAAW,gBAAgB,CAAC;AAAA,gBACvCA,IAAG,KAAKA,IAAG,KAAK,WAAW,SAAS,CAAC,CAAC;AAAA,EAC3D;AACA,cAAY,WAAW,gBAAgB;AAGvC,IAAE,MAAM,yCAAyC;AAEjD,QAAM,YAAY,WAAW,YAAY,KAAK;AAC9C,QAAM,WAAW,KAAK,IAAI,IAAI,WAAW,aAAa;AAEtD,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,QAAQ,CAAC;AAEhD,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,GAAG,iBAAiB,6BAA6B;AAAA,QACvE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,aAAa,WAAW,YAAY,CAAC;AAAA,MAC9D,CAAC;AAED,UAAI,IAAI,IAAI;AACV,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAE,KAAKA,IAAG,MAAM,yBAAyB,CAAC;AAC1C,eAAO,KAAK;AAAA,MACd;AAEA,UAAI,IAAI,WAAW,KAAK;AACtB,UAAE,KAAKA,IAAG,IAAI,uBAAuB,CAAC;AACtC,eAAO;AAAA,MACT;AAEA,UAAI,IAAI,WAAW,KAAK;AACtB,UAAE,KAAKA,IAAG,IAAI,iCAAiC,CAAC;AAChD,eAAO;AAAA,MACT;AAAA,IAGF,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,IAAE,KAAKA,IAAG,IAAI,iCAAiC,CAAC;AAChD,SAAO;AACT;;;APlFA,SAAS,YAAwB;AAC/B,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAqB,CAAC;AAC5B,MAAI;AAEJ,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,UAAM,OAAO,KAAK,IAAI,CAAC;AACvB,UAAM,WAAW,QAAQ,CAAC,KAAK,WAAW,GAAG;AAE7C,QAAI,QAAQ,gBAAgB,UAAU;AACpC,aAAO,aAAa,KAAK,EAAE,CAAC;AAAA,IAC9B,WAAW,QAAQ,UAAU,UAAU;AACrC,aAAO,WAAW,KAAK,EAAE,CAAC;AAAA,IAC5B,WAAW,QAAQ,YAAY,UAAU;AACvC,aAAO,YAAY,KAAK,EAAE,CAAC;AAAA,IAC7B,WAAW,QAAQ,aAAa,UAAU;AACxC,aAAO,QAAQ,KAAK,EAAE,CAAC;AAAA,IACzB,WAAW,QAAQ,yBAAyB,UAAU;AACpD,aAAO,kBAAkB,KAAK,EAAE,CAAC;AAAA,IACnC,WAAW,QAAQ,yBAAyB,UAAU;AACpD,aAAO,kBAAkB,KAAK,EAAE,CAAC;AAAA,IACnC,WAAW,QAAQ,sBAAsB,UAAU;AACjD,aAAO,eAAe,KAAK,EAAE,CAAC;AAAA,IAChC,WAAW,QAAQ,0BAA0B,UAAU;AACrD,aAAO,mBAAmB,KAAK,EAAE,CAAC;AAAA,IACpC,WAAW,CAAC,IAAI,WAAW,GAAG,GAAG;AAC/B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA,aAAa,cAAc,mBAAmB,WAAW,IAAI;AAAA,EAC/D;AACF;AAEA,IAAM,cAA2D;AAAA,EAC/D,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AACd;AAEA,IAAM,cAAqE;AAAA,EACzE,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,YAAY;AACd;AAMA,SAAS,aAAa,UAA0C;AAC9D,QAAM,SAAiC,CAAC;AACxC,MAAI,CAACC,YAAW,QAAQ,EAAG,QAAO;AAClC,QAAM,UAAUC,cAAa,UAAU,OAAO;AAC9C,aAAW,QAAQ,QAAQ,MAAM,IAAI,GAAG;AACtC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,CAAC,WAAW,QAAQ,WAAW,GAAG,EAAG;AACzC,UAAM,QAAQ,QAAQ,QAAQ,GAAG;AACjC,QAAI,QAAQ,EAAG;AACf,UAAM,MAAM,QAAQ,MAAM,GAAG,KAAK,EAAE,KAAK;AACzC,QAAI,MAAM,QAAQ,MAAM,QAAQ,CAAC,EAAE,KAAK;AAExC,QAAK,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,KAAO,IAAI,WAAW,GAAG,KAAK,IAAI,SAAS,GAAG,GAAI;AAC5F,YAAM,IAAI,MAAM,GAAG,EAAE;AAAA,IACvB;AACA,QAAI,IAAK,QAAO,GAAG,IAAI;AAAA,EACzB;AACA,SAAO;AACT;AAKA,SAAS,eAAuC;AAC9C,QAAM,SAAiC,CAAC;AACxC,QAAM,QAAkB,CAAC;AACzB,MAAI,MAAM,QAAQ,IAAI;AACtB,QAAM,OAAOC,SAAQ,GAAG,MAAM,MAAM,MAAM;AAE1C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,MAAM,KAAK;AAC1C,UAAM,UAAUC,SAAQ,KAAK,MAAM;AACnC,QAAIH,YAAW,OAAO,EAAG,OAAM,KAAK,OAAO;AAC3C,UAAME,SAAQ,GAAG;AAAA,EACnB;AAEA,aAAW,KAAK,MAAM,QAAQ,GAAG;AAC/B,WAAO,OAAO,QAAQ,aAAa,CAAC,CAAC;AAAA,EACvC;AACA,SAAO;AACT;AAEA,eAAe,aAAa,MAA+B;AACzD,QAAM,QAAQ,MAAQ,YAAS,EAAE,SAAS,cAAc,IAAI,GAAG,CAAC;AAChE,MAAM,YAAS,KAAK,GAAG;AACrB,IAAE,UAAO,YAAY;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO;AACT;AAEA,eAAe,oBAAoB,QAAqC;AAEtE,QAAM,aAAa,aAAa;AAChC,QAAM,WACJ,OAAO,mBACP,QAAQ,IAAI,qBACZ,WAAW;AAEb,MAAI,UAAU;AACZ,QAAI,CAAC,OAAO,iBAAiB;AAC3B,MAAE,OAAI,KAAK,SAASE,IAAG,KAAK,mBAAmB,CAAC,SAAS,QAAQ,IAAI,oBAAoB,gBAAgB,WAAW,EAAE;AAAA,IACxH;AACA,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,MAAQ,UAAO;AAAA,IAC5B,SAAS;AAAA,IACT,SAAS;AAAA,MACP;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF,CAAC;AAED,MAAM,YAAS,MAAM,GAAG;AACtB,IAAE,UAAO,YAAY;AACrB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,WAAW,WAAW;AACxB,UAAM,MAAM,MAAM,gBAAgB;AAClC,QAAI,IAAK,QAAO;AAEhB,IAAE,OAAI,KAAK,2DAA2D;AAAA,EACxE;AAEA,SAAO,aAAa,mBAAmB;AACzC;AAEA,eAAe,eACb,UACA,QACkB;AAClB,QAAM,SAAS,YAAY,QAAQ;AACnC,QAAM,UAAU,YAAY,QAAQ;AACpC,QAAM,aAAa,aAAa;AAEhC,QAAM,YAAY,MAAM,oBAAoB,MAAM;AAElD,QAAM,cACH,OAAO,OAAO,KACf,QAAQ,IAAI,MAAM,KAClB,WAAW,MAAM,KAChB,MAAM,aAAa,MAAM;AAE5B,MAAI,CAAE,OAAO,OAAO,MAA6B,QAAQ,IAAI,MAAM,KAAK,WAAW,MAAM,IAAI;AAC3F,IAAE,OAAI,KAAK,SAASA,IAAG,KAAK,MAAM,CAAC,SAAS,QAAQ,IAAI,MAAM,IAAI,gBAAgB,WAAW,EAAE;AAAA,EACjG;AAEA,SAAO,EAAE,WAAW,UAAU,YAAY;AAC5C;AAEA,eAAe,OAAO;AACpB,QAAM,SAAS,UAAU;AAGzB,MAAI;AAEJ,MAAI,OAAO,eAAe,OAAO,cAAc,OAAO,UAAU;AAE9D,UAAM,cAAc,mBAAmB,OAAO,WAAW;AACzD,QAAI,cAAc,WAAW,GAAG;AAC9B,MAAE,UAAO,cAAc,OAAO,WAAW,mBAAmB;AAC5D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,aAAS;AAAA,MACP,aAAa,OAAO;AAAA,MACpB;AAAA,MACA,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO,aAAa;AAAA,MAC/B,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO;AAAA,IAChB;AAAA,EACF,OAAO;AACL,aAAS,MAAM,WAAW,OAAO,aAAa;AAAA,MAC5C,YAAY,OAAO;AAAA,MACnB,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO;AAAA,IACnB,CAAC;AACD,QAAI,UAAU,OAAO,OAAO;AAC1B,aAAO,QAAQ,OAAO;AAAA,IACxB;AAAA,EACF;AAEA,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,EAAE,SAAMA,IAAG,OAAOA,IAAG,MAAM,wBAAwB,CAAC,CAAC;AAGrD,MAAI,OAAO,aAAa,YAAY;AAClC,UAAM,QAAQ,MAAQ,QAAK;AAAA,MACzB,SAAS;AAAA,MACT,aAAa,WAAW,SAAS;AAAA,MACjC,SAAS,OAAO;AACd,YAAI,CAAC,MAAO,QAAO;AAAA,MACrB;AAAA,IACF,CAAC;AACD,QAAM,YAAS,KAAK,GAAG;AACrB,MAAE,UAAO,YAAY;AACrB,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,WAAO,cAAc;AAAA,EACvB,OAAO;AACL,WAAO,cAAc,WAAW,OAAO;AAAA,EACzC;AAGA,QAAM,UAAU,MAAM,eAAe,OAAO,YAAY,MAAM;AAC9D,SAAO,UAAU;AAEjB,QAAM,IAAM,WAAQ;AAGpB,IAAE,MAAM,wBAAwB;AAChC,kBAAgB,MAAM;AACtB,gBAAc,MAAM;AACpB,IAAE,KAAK,oBAAoB;AAG3B,IAAE,MAAM,4BAA4B;AACpC,MAAI;AACF,UAAM,YAAY,OAAO,WAAW;AACpC,MAAE,KAAK,wBAAwB;AAAA,EACjC,QAAQ;AACN,MAAE,KAAK,4DAAuD;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,IAAE,MAAM,wBAAwB;AAChC,MAAI;AACF,UAAM,WAAW,OAAO,aAAa,OAAO,WAAW;AACvD,MAAE,KAAK,gBAAgB;AAAA,EACzB,QAAQ;AACN,MAAE,KAAK,uBAAuB;AAC9B,IAAE,OAAI;AAAA,MACJ,OAAOA,IAAG,KAAK,MAAM,OAAO,WAAW,mBAAmB,CAAC;AAAA,IAC7D;AAAA,EACF;AAGA,EAAE,OAAI,KAAKA,IAAG,MAAM,wBAAwB,CAAC;AAC7C,QAAM,aAAa,OAAO,WAAW;AACvC;AAEA,KAAK,EAAE,MAAM,QAAQ,KAAK;","names":["p","pc","readFileSync","existsSync","resolve","dirname","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","p","pc","existsSync","readFileSync","dirname","resolve","pc"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-supyagent-app",
3
- "version": "0.1.37",
3
+ "version": "0.1.38",
4
4
  "description": "Create a supyagent-powered chatbot app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,30 @@
1
+ import { type UIMessage } from 'ai';
2
+ {{aiProviderImport}};
3
+ import { createContextManager } from '@supyagent/sdk/context';
4
+ import { createPrismaAdapter } from '@supyagent/sdk/prisma';
5
+ import { prisma } from '@/lib/prisma';
6
+
7
+ const adapter = createPrismaAdapter(prisma);
8
+
9
+ export async function POST(req: Request) {
10
+ const { chatId, instruction }: { chatId: string; instruction?: string } = await req.json();
11
+
12
+ const messages = (await adapter.loadChat(chatId)) as UIMessage[];
13
+ if (messages.length < 4) {
14
+ return Response.json({ error: 'Not enough messages to compact' }, { status: 400 });
15
+ }
16
+
17
+ const ctx = createContextManager({
18
+ maxTokens: 128_000,
19
+ summaryModel: {{aiModel}},
20
+ ...(instruction ? { summaryPrompt: instruction } : {}),
21
+ });
22
+
23
+ const compacted = await ctx.compactify(messages);
24
+ await adapter.saveChat(chatId, compacted as any);
25
+
26
+ return Response.json({
27
+ messages: compacted,
28
+ state: ctx.getState(),
29
+ });
30
+ }
@@ -1,6 +1,7 @@
1
1
  import { convertToModelMessages, streamText, stepCountIs, type UIMessage } from 'ai';
2
2
  {{aiProviderImport}};
3
3
  import { supyagent, createBashTool, createViewImageTool } from '@supyagent/sdk';
4
+ import { createContextManager } from '@supyagent/sdk/context';
4
5
  import { createPrismaAdapter } from '@supyagent/sdk/prisma';
5
6
  import { prisma } from '@/lib/prisma';
6
7
 
@@ -15,12 +16,36 @@ export async function POST(req: Request) {
15
16
  const { systemPrompt: skillsPrompt, tools: skillTools } = await client.skills({ cache: 300 });
16
17
  const tools = { ...skillTools, bash: createBashTool(), viewImage: createViewImageTool() };
17
18
 
19
+ // Context management — tracks token usage and compactifies when thresholds are exceeded
20
+ const ctx = createContextManager({
21
+ maxTokens: 128_000,
22
+ summaryModel: {{aiModel}},
23
+ });
24
+
25
+ ctx.updateEstimate(messages);
26
+
27
+ // Hard threshold: compactify before the LLM call if context is too large
28
+ let activeMessages = messages;
29
+ if (ctx.shouldCompactify(messages)) {
30
+ activeMessages = await ctx.compactify(messages);
31
+ await adapter.saveChat(chatId, activeMessages as any);
32
+ }
33
+
34
+ const systemBase = `You are a helpful assistant.\n\n${skillsPrompt}\n\nUse the loadSkill tool to get detailed instructions before using any skill. Use the apiCall tool to make authenticated API requests. You can also run bash commands for general tasks. When a tool execution is not approved by the user, do not retry it — explain what you were trying to do and ask how to proceed.`;
35
+ const { messages: preparedMessages, systemPrompt } = await ctx.prepareMessages(activeMessages, systemBase);
36
+
18
37
  const result = streamText({
19
38
  model: {{aiModel}},
20
- system: `You are a helpful assistant.\n\n${skillsPrompt}\n\nUse the loadSkill tool to get detailed instructions before using any skill. Use the apiCall tool to make authenticated API requests. You can also run bash commands for general tasks. When a tool execution is not approved by the user, do not retry it — explain what you were trying to do and ask how to proceed.`,
21
- messages: await convertToModelMessages(messages),
39
+ system: systemPrompt,
40
+ messages: await convertToModelMessages(preparedMessages),
22
41
  tools,
23
42
  stopWhen: stepCountIs(5),
43
+ onStepFinish: ({ usage }) => {
44
+ ctx.recordUsage({
45
+ inputTokens: usage.inputTokens ?? undefined,
46
+ outputTokens: usage.outputTokens ?? undefined,
47
+ });
48
+ },
24
49
  prepareStep: async ({ steps, messages: stepMessages }) => {
25
50
  const imageUrls: string[] = [];
26
51
  for (const step of steps) {
@@ -30,6 +55,19 @@ export async function POST(req: Request) {
30
55
  }
31
56
  }
32
57
  }
58
+
59
+ // Mid-chain hard threshold check
60
+ if (ctx.shouldCompactify(activeMessages)) {
61
+ activeMessages = await ctx.compactify(activeMessages);
62
+ await adapter.saveChat(chatId, activeMessages as any);
63
+ const { messages: trimmed, systemPrompt: newSystem } =
64
+ await ctx.prepareMessages(activeMessages, systemBase);
65
+ return {
66
+ system: newSystem,
67
+ messages: await convertToModelMessages(trimmed),
68
+ };
69
+ }
70
+
33
71
  if (imageUrls.length === 0) return undefined;
34
72
 
35
73
  return {
@@ -50,11 +88,25 @@ export async function POST(req: Request) {
50
88
 
51
89
  return result.toUIMessageStreamResponse({
52
90
  originalMessages: messages,
91
+ messageMetadata: ({ part }) => {
92
+ if (part.type === 'finish-step') {
93
+ return ctx.getMessageMetadata();
94
+ }
95
+ return undefined;
96
+ },
53
97
  onStepFinish: async ({ messages: updatedMessages }) => {
54
98
  await adapter.saveChat(chatId, updatedMessages as any);
55
99
  },
56
100
  onFinish: async ({ messages: updatedMessages }) => {
57
101
  await adapter.saveChat(chatId, updatedMessages as any);
102
+
103
+ // Soft threshold: background summarization after the response
104
+ ctx.updateEstimate(updatedMessages);
105
+ if (ctx.shouldSummarize(updatedMessages)) {
106
+ ctx.compactify(updatedMessages)
107
+ .then(compacted => adapter.saveChat(chatId, compacted as any))
108
+ .catch(() => {});
109
+ }
58
110
  },
59
111
  });
60
112
  }
@@ -1,6 +1,7 @@
1
1
  import { convertToModelMessages, streamText, stepCountIs, type UIMessage } from 'ai';
2
2
  {{aiProviderImport}};
3
3
  import { supyagent, createBashTool, createViewImageTool } from '@supyagent/sdk';
4
+ import { createContextManager } from '@supyagent/sdk/context';
4
5
  import { createPrismaAdapter } from '@supyagent/sdk/prisma';
5
6
  import { prisma } from '@/lib/prisma';
6
7
 
@@ -15,12 +16,36 @@ export async function POST(req: Request) {
15
16
  const supyagentTools = await client.tools({ cache: 300 });
16
17
  const tools = { ...supyagentTools, bash: createBashTool(), viewImage: createViewImageTool() };
17
18
 
19
+ // Context management — tracks token usage and compactifies when thresholds are exceeded
20
+ const ctx = createContextManager({
21
+ maxTokens: 128_000,
22
+ summaryModel: {{aiModel}},
23
+ });
24
+
25
+ ctx.updateEstimate(messages);
26
+
27
+ // Hard threshold: compactify before the LLM call if context is too large
28
+ let activeMessages = messages;
29
+ if (ctx.shouldCompactify(messages)) {
30
+ activeMessages = await ctx.compactify(messages);
31
+ await adapter.saveChat(chatId, activeMessages as any);
32
+ }
33
+
34
+ const systemBase = 'You are a helpful assistant. Use your tools when asked to interact with connected services. You can also run bash commands to help with tasks. When a tool execution is not approved by the user, do not retry it — explain what you were trying to do and ask how to proceed.';
35
+ const { messages: preparedMessages, systemPrompt } = await ctx.prepareMessages(activeMessages, systemBase);
36
+
18
37
  const result = streamText({
19
38
  model: {{aiModel}},
20
- system: 'You are a helpful assistant. Use your tools when asked to interact with connected services. You can also run bash commands to help with tasks. When a tool execution is not approved by the user, do not retry it — explain what you were trying to do and ask how to proceed.',
21
- messages: await convertToModelMessages(messages),
39
+ system: systemPrompt,
40
+ messages: await convertToModelMessages(preparedMessages),
22
41
  tools,
23
42
  stopWhen: stepCountIs(5),
43
+ onStepFinish: ({ usage }) => {
44
+ ctx.recordUsage({
45
+ inputTokens: usage.inputTokens ?? undefined,
46
+ outputTokens: usage.outputTokens ?? undefined,
47
+ });
48
+ },
24
49
  prepareStep: async ({ steps, messages: stepMessages }) => {
25
50
  const imageUrls: string[] = [];
26
51
  for (const step of steps) {
@@ -30,6 +55,19 @@ export async function POST(req: Request) {
30
55
  }
31
56
  }
32
57
  }
58
+
59
+ // Mid-chain hard threshold check
60
+ if (ctx.shouldCompactify(activeMessages)) {
61
+ activeMessages = await ctx.compactify(activeMessages);
62
+ await adapter.saveChat(chatId, activeMessages as any);
63
+ const { messages: trimmed, systemPrompt: newSystem } =
64
+ await ctx.prepareMessages(activeMessages, systemBase);
65
+ return {
66
+ system: newSystem,
67
+ messages: await convertToModelMessages(trimmed),
68
+ };
69
+ }
70
+
33
71
  if (imageUrls.length === 0) return undefined;
34
72
 
35
73
  return {
@@ -50,11 +88,25 @@ export async function POST(req: Request) {
50
88
 
51
89
  return result.toUIMessageStreamResponse({
52
90
  originalMessages: messages,
91
+ messageMetadata: ({ part }) => {
92
+ if (part.type === 'finish-step') {
93
+ return ctx.getMessageMetadata();
94
+ }
95
+ return undefined;
96
+ },
53
97
  onStepFinish: async ({ messages: updatedMessages }) => {
54
98
  await adapter.saveChat(chatId, updatedMessages as any);
55
99
  },
56
100
  onFinish: async ({ messages: updatedMessages }) => {
57
101
  await adapter.saveChat(chatId, updatedMessages as any);
102
+
103
+ // Soft threshold: background summarization after the response
104
+ ctx.updateEstimate(updatedMessages);
105
+ if (ctx.shouldSummarize(updatedMessages)) {
106
+ ctx.compactify(updatedMessages)
107
+ .then(compacted => adapter.saveChat(chatId, compacted as any))
108
+ .catch(() => {});
109
+ }
58
110
  },
59
111
  });
60
112
  }
@@ -12,6 +12,7 @@ interface ChatInputProps {
12
12
  }) => Promise<void>;
13
13
  isLoading: boolean;
14
14
  stop: () => void;
15
+ onCompact?: () => void;
15
16
  }
16
17
 
17
18
  const MAX_BASE64_BYTES = 4.5 * 1024 * 1024; // stay well under provider 5MB limit
@@ -48,7 +49,7 @@ function compressImage(file: File): Promise<{ url: string; mediaType: string }>
48
49
  });
49
50
  }
50
51
 
51
- export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
52
+ export function ChatInput({ sendMessage, isLoading, stop, onCompact }: ChatInputProps) {
52
53
  const [input, setInput] = useState("");
53
54
  const [files, setFiles] = useState<File[]>([]);
54
55
  const [isDragging, setIsDragging] = useState(false);
@@ -95,6 +96,14 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
95
96
  (command: SlashCommand) => {
96
97
  setSlashMenuOpen(false);
97
98
  const prompt = command.prompt;
99
+
100
+ // Intercept special commands
101
+ if (prompt === "__compact__") {
102
+ setInput("");
103
+ onCompact?.();
104
+ return;
105
+ }
106
+
98
107
  // If prompt ends with space (open-ended), just set the input
99
108
  if (prompt.endsWith(" ")) {
100
109
  setInput(prompt);
@@ -105,7 +114,7 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
105
114
  sendMessage({ text: prompt });
106
115
  }
107
116
  },
108
- [sendMessage]
117
+ [sendMessage, onCompact]
109
118
  );
110
119
 
111
120
  const handleSubmit = async (e: FormEvent) => {
@@ -2,6 +2,7 @@
2
2
 
3
3
  import type { UIMessage } from "ai";
4
4
  import { isToolUIPart } from "ai";
5
+ import { SummaryMessage, isContextSummary } from "@supyagent/sdk/react";
5
6
  import { ToolMessage } from "@/components/supyagent/tool-message";
6
7
  import { useState } from "react";
7
8
  import { Streamdown } from "streamdown";
@@ -44,6 +45,15 @@ function CopyButton({ text }: { text: string }) {
44
45
  }
45
46
 
46
47
  export function ChatMessage({ message, addToolApprovalResponse }: ChatMessageProps) {
48
+ // Render context-summary messages with a distinct card
49
+ if (isContextSummary(message)) {
50
+ return (
51
+ <div className="flex justify-center">
52
+ <SummaryMessage message={message} className="max-w-[85%]" />
53
+ </div>
54
+ );
55
+ }
56
+
47
57
  const isUser = message.role === "user";
48
58
  const textContent = message.parts
49
59
  .filter((part) => part.type === "text")
@@ -7,6 +7,7 @@ import { ChatInput } from "./chat-input";
7
7
  import { ChatSidebar } from "./chat-sidebar";
8
8
  import { useRef, useEffect, useMemo, useState, useCallback } from "react";
9
9
  import { ArrowDown, Mail, MessageSquare, Github, ExternalLink } from "lucide-react";
10
+ import { ContextIndicator } from "@supyagent/sdk/react";
10
11
  import { Header } from "./header";
11
12
  import Image from "next/image";
12
13
 
@@ -50,7 +51,7 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
50
51
  [chatId]
51
52
  );
52
53
 
53
- const { messages, sendMessage, status, stop, addToolApprovalResponse } = useChat({
54
+ const { messages, sendMessage, status, stop, addToolApprovalResponse, setMessages } = useChat({
54
55
  id: chatId,
55
56
  transport,
56
57
  messages: initialMessages,
@@ -58,6 +59,27 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
58
59
  });
59
60
 
60
61
  const isLoading = status === "submitted" || status === "streaming";
62
+ const [isCompacting, setIsCompacting] = useState(false);
63
+
64
+ const handleCompact = useCallback(async () => {
65
+ if (isCompacting || isLoading) return;
66
+ setIsCompacting(true);
67
+ try {
68
+ const res = await fetch("/api/chat/compact", {
69
+ method: "POST",
70
+ headers: { "Content-Type": "application/json" },
71
+ body: JSON.stringify({ chatId }),
72
+ });
73
+ if (res.ok) {
74
+ const { messages: compactedMessages } = await res.json();
75
+ setMessages(compactedMessages);
76
+ }
77
+ } catch {
78
+ // Compact failed silently
79
+ } finally {
80
+ setIsCompacting(false);
81
+ }
82
+ }, [chatId, isCompacting, isLoading, setMessages]);
61
83
 
62
84
  const scrollRef = useRef<HTMLDivElement>(null);
63
85
  const bottomRef = useRef<HTMLDivElement>(null);
@@ -243,10 +265,14 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
243
265
 
244
266
  <div className="border-t border-border px-4 py-4">
245
267
  <div className="mx-auto max-w-3xl">
268
+ <div className="flex items-center justify-end mb-1.5 min-h-[20px]">
269
+ <ContextIndicator messages={messages} />
270
+ </div>
246
271
  <ChatInput
247
272
  sendMessage={sendMessage}
248
273
  isLoading={isLoading}
249
274
  stop={stop}
275
+ onCompact={handleCompact}
250
276
  />
251
277
  </div>
252
278
  </div>
@@ -10,6 +10,7 @@ import {
10
10
  Search,
11
11
  Send,
12
12
  CalendarClock,
13
+ Minimize2,
13
14
  } from "lucide-react";
14
15
 
15
16
  interface SlashCommand {
@@ -29,6 +30,7 @@ const COMMANDS: SlashCommand[] = [
29
30
  { name: "search", label: "/search", description: "Search the web for...", prompt: "Search the web for ", icon: <Search className="h-4 w-4" /> },
30
31
  { name: "compose", label: "/compose", description: "Draft an email to...", prompt: "Draft an email to ", icon: <Send className="h-4 w-4" /> },
31
32
  { name: "schedule", label: "/schedule", description: "Schedule a meeting for...", prompt: "Schedule a meeting for ", icon: <CalendarClock className="h-4 w-4" /> },
33
+ { name: "compact", label: "/compact", description: "Compact conversation history", prompt: "__compact__", icon: <Minimize2 className="h-4 w-4" /> },
32
34
  ];
33
35
 
34
36
  interface SlashMenuProps {