create-supyagent-app 0.1.37 → 0.1.39
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 +72 -175
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/api-route/compact/route.ts.tmpl +30 -0
- package/templates/api-route/route.skills.ts.tmpl +55 -3
- package/templates/api-route/route.tools.ts.tmpl +55 -3
- package/templates/base/src/components/chat-input.tsx +11 -2
- package/templates/base/src/components/chat-message.tsx +10 -0
- package/templates/base/src/components/chat.tsx +27 -1
- package/templates/base/src/components/slash-menu.tsx +2 -0
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="
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
-
|
|
594
|
-
|
|
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:
|
|
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
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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":"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","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":"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","names":["p","pc","readFileSync","existsSync","resolve","dirname","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","p","pc","existsSync","readFileSync","dirname","resolve","pc"]}
|
package/package.json
CHANGED
|
@@ -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:
|
|
21
|
-
messages: await convertToModelMessages(
|
|
39
|
+
system: systemPrompt,
|
|
40
|
+
messages: await convertToModelMessages(preparedMessages),
|
|
22
41
|
tools,
|
|
23
|
-
stopWhen: stepCountIs(
|
|
42
|
+
stopWhen: stepCountIs(50),
|
|
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:
|
|
21
|
-
messages: await convertToModelMessages(
|
|
39
|
+
system: systemPrompt,
|
|
40
|
+
messages: await convertToModelMessages(preparedMessages),
|
|
22
41
|
tools,
|
|
23
|
-
stopWhen: stepCountIs(
|
|
42
|
+
stopWhen: stepCountIs(50),
|
|
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 {
|