create-supyagent-app 0.1.6 → 0.1.8

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
@@ -23,7 +23,7 @@ var AI_PROVIDERS = {
23
23
  package: "@ai-sdk/anthropic",
24
24
  packageVersion: "^3.0.0",
25
25
  import: `import { anthropic } from '@ai-sdk/anthropic'`,
26
- model: `anthropic('claude-sonnet-4-20250514')`,
26
+ model: `anthropic('claude-sonnet-4-6-20250620')`,
27
27
  envKey: "ANTHROPIC_API_KEY"
28
28
  },
29
29
  openai: {
@@ -39,7 +39,7 @@ var AI_PROVIDERS = {
39
39
  package: "@openrouter/ai-sdk-provider",
40
40
  packageVersion: "^2.2.3",
41
41
  import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,
42
- model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4-20250514')`,
42
+ model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,
43
43
  envKey: "OPENROUTER_API_KEY"
44
44
  }
45
45
  };
@@ -55,9 +55,21 @@ var DB_CONFIGS = {
55
55
  url: "postgresql://user:password@localhost:5432/mydb"
56
56
  }
57
57
  };
58
+ function buildModelExpression(provider, customModelId) {
59
+ const config = AI_PROVIDERS[provider];
60
+ if (!customModelId) return config.model;
61
+ switch (provider) {
62
+ case "anthropic":
63
+ return `anthropic('${customModelId}')`;
64
+ case "openai":
65
+ return `openai('${customModelId}')`;
66
+ case "openrouter":
67
+ return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;
68
+ }
69
+ }
58
70
 
59
71
  // src/prompts.ts
60
- async function runPrompts(argName) {
72
+ async function runPrompts(argName, options) {
61
73
  p.intro(pc.bgCyan(pc.black(" Create Supyagent App ")));
62
74
  const projectName = argName || await p.text({
63
75
  message: "Project name",
@@ -79,28 +91,40 @@ async function runPrompts(argName) {
79
91
  p.cancel(`Directory "${projectName}" already exists.`);
80
92
  return null;
81
93
  }
82
- const aiProvider = await p.select({
83
- message: "AI provider",
84
- options: [
85
- { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
86
- { value: "openai", label: AI_PROVIDERS.openai.label },
87
- { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
88
- ]
89
- });
90
- if (p.isCancel(aiProvider)) {
91
- p.cancel("Cancelled.");
92
- return null;
94
+ let aiProvider;
95
+ if (options?.aiProvider) {
96
+ aiProvider = options.aiProvider;
97
+ } else {
98
+ const selected = await p.select({
99
+ message: "AI provider",
100
+ options: [
101
+ { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
102
+ { value: "openai", label: AI_PROVIDERS.openai.label },
103
+ { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
104
+ ]
105
+ });
106
+ if (p.isCancel(selected)) {
107
+ p.cancel("Cancelled.");
108
+ return null;
109
+ }
110
+ aiProvider = selected;
93
111
  }
94
- const database = await p.select({
95
- message: "Database",
96
- options: [
97
- { value: "sqlite", label: DB_CONFIGS.sqlite.label },
98
- { value: "postgres", label: DB_CONFIGS.postgres.label }
99
- ]
100
- });
101
- if (p.isCancel(database)) {
102
- p.cancel("Cancelled.");
103
- return null;
112
+ let database;
113
+ if (options?.database || options?.skipDatabase) {
114
+ database = options?.database ?? "sqlite";
115
+ } else {
116
+ const selected = await p.select({
117
+ message: "Database",
118
+ options: [
119
+ { value: "sqlite", label: DB_CONFIGS.sqlite.label },
120
+ { value: "postgres", label: DB_CONFIGS.postgres.label }
121
+ ]
122
+ });
123
+ if (p.isCancel(selected)) {
124
+ p.cancel("Cancelled.");
125
+ return null;
126
+ }
127
+ database = selected;
104
128
  }
105
129
  return { projectName, projectPath, aiProvider, database };
106
130
  }
@@ -137,7 +161,7 @@ function scaffoldProject(config) {
137
161
  aiProviderPackage: ai.package,
138
162
  aiProviderVersion: ai.packageVersion,
139
163
  aiProviderImport: ai.import,
140
- aiModel: ai.model,
164
+ aiModel: buildModelExpression(aiProvider, config.model),
141
165
  aiProviderEnvKey: ai.envKey,
142
166
  dbProvider: db.provider,
143
167
  dbUrl: db.url
@@ -147,7 +171,7 @@ function scaffoldProject(config) {
147
171
  writeProject(projectPath, "tsconfig.json", readTemplate("base/tsconfig.json"));
148
172
  writeProject(projectPath, "tailwind.config.ts", readTemplate("base/tailwind.config.ts"));
149
173
  writeProject(projectPath, "postcss.config.js", readTemplate("base/postcss.config.js"));
150
- writeProject(projectPath, ".gitignore", readTemplate("base/.gitignore"));
174
+ writeProject(projectPath, ".gitignore", readTemplate("base/gitignore"));
151
175
  writeProject(projectPath, ".npmrc", readTemplate("base/.npmrc"));
152
176
  writeProject(projectPath, "README.md", applyTemplate(readTemplate("base/README.md.tmpl"), vars));
153
177
  writeProject(projectPath, "src/app/layout.tsx", readTemplate("base/src/app/layout.tsx"));
@@ -195,6 +219,58 @@ async function installDeps(projectPath) {
195
219
  });
196
220
  }
197
221
 
222
+ // src/quickstart.ts
223
+ import { writeFileSync as writeFileSync2 } from "fs";
224
+ import { join as join2 } from "path";
225
+ import { execSync, spawn as nodeSpawn } from "child_process";
226
+ import { detectPackageManager as detectPackageManager2 } from "nypm";
227
+ function writeEnvLocal(config) {
228
+ const { projectPath, aiProvider, apiKeys } = config;
229
+ const ai = AI_PROVIDERS[aiProvider];
230
+ const lines = [
231
+ "# Supyagent \u2014 Get your API key at https://app.supyagent.com",
232
+ `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? ""}`,
233
+ "",
234
+ "# AI Provider",
235
+ `${ai.envKey}=${apiKeys?.provider ?? ""}`,
236
+ "",
237
+ "# Database",
238
+ `DATABASE_URL="file:./dev.db"`,
239
+ ""
240
+ ];
241
+ writeFileSync2(join2(projectPath, ".env.local"), lines.join("\n"), "utf-8");
242
+ }
243
+ async function runDbSetup(projectPath) {
244
+ const pm = await detectPackageManager2(projectPath);
245
+ const cmd = pm?.name ?? "pnpm";
246
+ execSync(`${cmd} run db:setup`, {
247
+ cwd: projectPath,
248
+ stdio: "inherit",
249
+ env: { ...process.env, DATABASE_URL: "file:./dev.db" }
250
+ });
251
+ }
252
+ async function runDevServer(projectPath) {
253
+ const pm = await detectPackageManager2(projectPath);
254
+ const cmd = pm?.name ?? "pnpm";
255
+ const child = nodeSpawn(cmd, ["dev"], {
256
+ cwd: projectPath,
257
+ stdio: "inherit"
258
+ });
259
+ const forward = (signal) => {
260
+ child.kill(signal);
261
+ };
262
+ process.on("SIGINT", forward);
263
+ process.on("SIGTERM", forward);
264
+ return new Promise((_, reject) => {
265
+ child.on("close", (code) => {
266
+ process.off("SIGINT", forward);
267
+ process.off("SIGTERM", forward);
268
+ process.exit(code ?? 0);
269
+ });
270
+ child.on("error", reject);
271
+ });
272
+ }
273
+
198
274
  // src/index.ts
199
275
  function parseArgs() {
200
276
  const args = process.argv.slice(2);
@@ -202,64 +278,131 @@ function parseArgs() {
202
278
  let projectName;
203
279
  for (let i = 0; i < args.length; i++) {
204
280
  const arg = args[i];
205
- if (arg === "--provider" && args[i + 1]) {
281
+ const next = args[i + 1];
282
+ const hasValue = next && !next.startsWith("-");
283
+ if (arg === "--provider" && hasValue) {
206
284
  result.aiProvider = args[++i];
207
- } else if (arg === "--db" && args[i + 1]) {
285
+ } else if (arg === "--db" && hasValue) {
208
286
  result.database = args[++i];
287
+ } else if (arg === "--model" && hasValue) {
288
+ result.model = args[++i];
289
+ } else if (arg === "--quickstart") {
290
+ result.quickstart = true;
209
291
  } else if (arg === "--skip-install") {
210
292
  result.skipInstall = true;
293
+ } else if (arg === "--supyagent-api-key" && hasValue) {
294
+ result.supyagentApiKey = args[++i];
295
+ } else if (arg === "--anthropic-api-key" && hasValue) {
296
+ result.anthropicApiKey = args[++i];
297
+ } else if (arg === "--openai-api-key" && hasValue) {
298
+ result.openaiApiKey = args[++i];
299
+ } else if (arg === "--openrouter-api-key" && hasValue) {
300
+ result.openrouterApiKey = args[++i];
211
301
  } else if (!arg.startsWith("-")) {
212
302
  projectName = arg;
213
303
  }
214
304
  }
215
305
  return {
306
+ ...result,
216
307
  projectName,
217
- projectPath: projectName ? resolveProjectPath(projectName) : void 0,
218
- aiProvider: result.aiProvider,
219
- database: result.database,
220
- skipInstall: result.skipInstall
308
+ projectPath: projectName ? resolveProjectPath(projectName) : void 0
221
309
  };
222
310
  }
223
- async function main() {
224
- const parsed = parseArgs();
225
- const isNonInteractive = parsed.projectName && parsed.aiProvider && parsed.database;
226
- let config;
227
- if (isNonInteractive) {
228
- config = {
229
- projectName: parsed.projectName,
230
- projectPath: parsed.projectPath,
231
- aiProvider: parsed.aiProvider,
232
- database: parsed.database
233
- };
234
- console.log(`Creating ${config.projectName}...`);
235
- } else {
236
- config = await runPrompts(parsed.projectName);
237
- }
238
- if (!config) {
311
+ var ENV_KEY_MAP = {
312
+ anthropic: "ANTHROPIC_API_KEY",
313
+ openai: "OPENAI_API_KEY",
314
+ openrouter: "OPENROUTER_API_KEY"
315
+ };
316
+ var CLI_KEY_MAP = {
317
+ anthropic: "anthropicApiKey",
318
+ openai: "openaiApiKey",
319
+ openrouter: "openrouterApiKey"
320
+ };
321
+ async function promptForKey(name) {
322
+ const value = await p2.password({ message: `Enter your ${name}` });
323
+ if (p2.isCancel(value)) {
324
+ p2.cancel("Cancelled.");
239
325
  process.exit(1);
240
326
  }
241
- if (isNonInteractive) {
242
- scaffoldProject(config);
243
- console.log("Scaffolded project");
244
- if (!parsed.skipInstall) {
245
- console.log("Installing dependencies...");
246
- try {
247
- await installDeps(config.projectPath);
248
- console.log("Installed dependencies");
249
- } catch {
250
- console.log("Failed to install dependencies \u2014 run install manually");
327
+ return value;
328
+ }
329
+ async function resolveApiKeys(provider, parsed) {
330
+ const envKey = ENV_KEY_MAP[provider];
331
+ const cliFlag = CLI_KEY_MAP[provider];
332
+ const supyagent = parsed.supyagentApiKey ?? process.env.SUPYAGENT_API_KEY ?? await promptForKey("SUPYAGENT_API_KEY");
333
+ const providerKey = parsed[cliFlag] ?? process.env[envKey] ?? await promptForKey(envKey);
334
+ return { supyagent, provider: providerKey };
335
+ }
336
+ async function main() {
337
+ const parsed = parseArgs();
338
+ if (parsed.quickstart) {
339
+ if (parsed.database === "postgres") {
340
+ p2.log.warn(
341
+ pc2.yellow("--quickstart requires SQLite \u2014 ignoring --db postgres")
342
+ );
343
+ }
344
+ if (parsed.skipInstall) {
345
+ p2.log.warn(
346
+ pc2.yellow(
347
+ "--quickstart needs dependencies installed \u2014 ignoring --skip-install"
348
+ )
349
+ );
350
+ }
351
+ p2.intro(pc2.bgCyan(pc2.black(" Create Supyagent App \u2014 Quickstart ")));
352
+ let projectName = parsed.projectName;
353
+ if (!projectName) {
354
+ const name = await p2.text({
355
+ message: "Project name",
356
+ placeholder: "my-supyagent-app",
357
+ defaultValue: "my-supyagent-app",
358
+ validate(value) {
359
+ if (!value) return "Project name is required";
360
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {
361
+ return "Invalid project name (lowercase, alphanumeric, hyphens, dots)";
362
+ }
363
+ }
364
+ });
365
+ if (p2.isCancel(name)) {
366
+ p2.cancel("Cancelled.");
367
+ process.exit(1);
251
368
  }
369
+ projectName = name;
252
370
  }
253
- console.log(`
254
- Next steps:
255
- cd ${config.projectName}
256
- cp .env.example .env.local
257
- pnpm db:setup
258
- pnpm dev`);
259
- } else {
371
+ const projectPath = resolveProjectPath(projectName);
372
+ if (projectExists(projectPath)) {
373
+ p2.cancel(`Directory "${projectName}" already exists.`);
374
+ process.exit(1);
375
+ }
376
+ let aiProvider = parsed.aiProvider;
377
+ if (!aiProvider) {
378
+ const selected = await p2.select({
379
+ message: "AI provider",
380
+ options: [
381
+ { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
382
+ { value: "openai", label: AI_PROVIDERS.openai.label },
383
+ { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
384
+ ]
385
+ });
386
+ if (p2.isCancel(selected)) {
387
+ p2.cancel("Cancelled.");
388
+ process.exit(1);
389
+ }
390
+ aiProvider = selected;
391
+ }
392
+ const apiKeys = await resolveApiKeys(aiProvider, parsed);
393
+ const config = {
394
+ projectName,
395
+ projectPath,
396
+ aiProvider,
397
+ database: "sqlite",
398
+ model: parsed.model,
399
+ quickstart: true,
400
+ apiKeys
401
+ };
260
402
  const s = p2.spinner();
261
403
  s.start("Scaffolding project...");
262
404
  scaffoldProject(config);
405
+ writeEnvLocal(config);
263
406
  s.stop("Scaffolded project");
264
407
  s.start("Installing dependencies...");
265
408
  try {
@@ -267,17 +410,89 @@ Next steps:
267
410
  s.stop("Installed dependencies");
268
411
  } catch {
269
412
  s.stop("Failed to install dependencies \u2014 run install manually");
413
+ process.exit(1);
414
+ }
415
+ s.start("Setting up database...");
416
+ try {
417
+ await runDbSetup(config.projectPath);
418
+ s.stop("Database ready");
419
+ } catch (err) {
420
+ s.stop("Database setup failed");
421
+ p2.log.warn(
422
+ `Run ${pc2.cyan(`cd ${projectName} && pnpm db:setup`)} manually`
423
+ );
424
+ }
425
+ p2.log.info(pc2.green("Starting dev server..."));
426
+ await runDevServer(config.projectPath);
427
+ } else {
428
+ const isNonInteractive = parsed.projectName && parsed.aiProvider && parsed.database;
429
+ let config;
430
+ if (isNonInteractive) {
431
+ config = {
432
+ projectName: parsed.projectName,
433
+ projectPath: parsed.projectPath,
434
+ aiProvider: parsed.aiProvider,
435
+ database: parsed.database,
436
+ model: parsed.model
437
+ };
438
+ console.log(`Creating ${config.projectName}...`);
439
+ } else {
440
+ config = await runPrompts(parsed.projectName, {
441
+ aiProvider: parsed.aiProvider,
442
+ database: parsed.database
443
+ });
444
+ if (config && parsed.model) {
445
+ config.model = parsed.model;
446
+ }
447
+ }
448
+ if (!config) {
449
+ process.exit(1);
450
+ }
451
+ if (isNonInteractive) {
452
+ scaffoldProject(config);
453
+ console.log("Scaffolded project");
454
+ if (!parsed.skipInstall) {
455
+ console.log("Installing dependencies...");
456
+ try {
457
+ await installDeps(config.projectPath);
458
+ console.log("Installed dependencies");
459
+ } catch {
460
+ console.log(
461
+ "Failed to install dependencies \u2014 run install manually"
462
+ );
463
+ }
464
+ }
465
+ console.log(
466
+ `
467
+ Next steps:
468
+ cd ${config.projectName}
469
+ cp .env.example .env.local
470
+ pnpm db:setup
471
+ pnpm dev`
472
+ );
473
+ } else {
474
+ const s = p2.spinner();
475
+ s.start("Scaffolding project...");
476
+ scaffoldProject(config);
477
+ s.stop("Scaffolded project");
478
+ s.start("Installing dependencies...");
479
+ try {
480
+ await installDeps(config.projectPath);
481
+ s.stop("Installed dependencies");
482
+ } catch {
483
+ s.stop("Failed to install dependencies \u2014 run install manually");
484
+ }
485
+ p2.note(
486
+ [
487
+ `cd ${config.projectName}`,
488
+ `cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
489
+ `pnpm db:setup ${pc2.dim("# Initialize database")}`,
490
+ `pnpm dev ${pc2.dim("# Start development server")}`
491
+ ].join("\n"),
492
+ "Next steps"
493
+ );
494
+ p2.outro(pc2.green("Done!"));
270
495
  }
271
- p2.note(
272
- [
273
- `cd ${config.projectName}`,
274
- `cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
275
- `pnpm db:setup ${pc2.dim("# Initialize database")}`,
276
- `pnpm dev ${pc2.dim("# Start development server")}`
277
- ].join("\n"),
278
- "Next steps"
279
- );
280
- p2.outro(pc2.green("Done!"));
281
496
  }
282
497
  }
283
498
  main().catch(console.error);
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"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { resolveProjectPath } from \"./utils.js\";\nimport type { ProjectConfig } from \"./utils.js\";\n\nfunction parseArgs(): Partial<ProjectConfig> & { skipInstall?: boolean } {\n const args = process.argv.slice(2);\n const result: Record<string, string | boolean> = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--provider\" && args[i + 1]) {\n result.aiProvider = args[++i];\n } else if (arg === \"--db\" && args[i + 1]) {\n result.database = args[++i];\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n aiProvider: result.aiProvider as ProjectConfig[\"aiProvider\"],\n database: result.database as ProjectConfig[\"database\"],\n skipInstall: result.skipInstall as boolean | undefined,\n };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n // If all required args are provided, skip interactive prompts\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 database: parsed.database!,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName);\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(\"Failed to install dependencies — run install manually\");\n }\n }\n\n console.log(`\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`);\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\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 async function runPrompts(argName?: string): 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 const aiProvider = 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(aiProvider)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const database = 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(database)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n return { projectName, projectPath, aiProvider, 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 ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\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-20250514')`,\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-20250514')`,\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","import { mkdirSync, writeFileSync, readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { applyTemplate } from \"./template.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, 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\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: ai.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\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(\"api-route/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\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\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 // ── 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"],"mappings":";;;AAAA,YAAYA,QAAO;AACnB,OAAOC,SAAQ;;;ACDf,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;AASO,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;;;ADnDA,eAAsB,WAAW,SAAiD;AAChF,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,QAAM,aAAa,MAAQ,SAAO;AAAA,IAChC,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,aAAa,OAAO,aAAa,UAAU,MAAM;AAAA,MAC1D,EAAE,OAAO,UAAU,OAAO,aAAa,OAAO,MAAM;AAAA,MACpD,EAAE,OAAO,cAAc,OAAO,aAAa,WAAW,MAAM;AAAA,IAC9D;AAAA,EACF,CAAC;AAED,MAAM,WAAS,UAAU,GAAG;AAC1B,IAAE,SAAO,YAAY;AACrB,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,MAAQ,SAAO;AAAA,IAC9B,SAAS;AAAA,IACT,SAAS;AAAA,MACP,EAAE,OAAO,UAAU,OAAO,WAAW,OAAO,MAAM;AAAA,MAClD,EAAE,OAAO,YAAY,OAAO,WAAW,SAAS,MAAM;AAAA,IACxD;AAAA,EACF,CAAC;AAED,MAAM,WAAS,QAAQ,GAAG;AACxB,IAAE,SAAO,YAAY;AACrB,WAAO;AAAA,EACT;AAEA,SAAO,EAAE,aAAa,aAAa,YAAY,SAAS;AAC1D;;;AE5DA,SAAS,WAAW,eAAe,oBAAoB;AACvD,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;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,GAAG;AAAA,IACZ,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,iBAAiB,CAAC;AACvE,eAAa,aAAa,UAAU,aAAa,aAAa,CAAC;AAC/D,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;AAAA,IACE;AAAA,IACA;AAAA,IACA,cAAc,aAAa,yBAAyB,GAAG,IAAI;AAAA,EAC7D;AACA,eAAa,aAAa,8BAA8B,aAAa,iCAAiC,CAAC;AACvG,eAAa,aAAa,mCAAmC,aAAa,sCAAsC,CAAC;AAGjH,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;AAG7G,eAAa,aAAa,oBAAoB,aAAa,uBAAuB,CAAC;AACnF,eAAa,aAAa,qBAAqB,aAAa,wBAAwB,CAAC;AAGrF;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;;;AE5FA,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;;;ALAA,SAAS,YAAgE;AACvE,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAA2C,CAAC;AAClD,MAAI;AAEJ,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,gBAAgB,KAAK,IAAI,CAAC,GAAG;AACvC,aAAO,aAAa,KAAK,EAAE,CAAC;AAAA,IAC9B,WAAW,QAAQ,UAAU,KAAK,IAAI,CAAC,GAAG;AACxC,aAAO,WAAW,KAAK,EAAE,CAAC;AAAA,IAC5B,WAAW,QAAQ,kBAAkB;AACnC,aAAO,cAAc;AAAA,IACvB,WAAW,CAAC,IAAI,WAAW,GAAG,GAAG;AAC/B,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,aAAa,cAAc,mBAAmB,WAAW,IAAI;AAAA,IAC7D,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA,IACjB,aAAa,OAAO;AAAA,EACtB;AACF;AAEA,eAAe,OAAO;AACpB,QAAM,SAAS,UAAU;AAGzB,QAAM,mBACJ,OAAO,eAAe,OAAO,cAAc,OAAO;AAEpD,MAAI;AAEJ,MAAI,kBAAkB;AACpB,aAAS;AAAA,MACP,aAAa,OAAO;AAAA,MACpB,aAAa,OAAO;AAAA,MACpB,YAAY,OAAO;AAAA,MACnB,UAAU,OAAO;AAAA,IACnB;AACA,YAAQ,IAAI,YAAY,OAAO,WAAW,KAAK;AAAA,EACjD,OAAO;AACL,aAAS,MAAM,WAAW,OAAO,WAAW;AAAA,EAC9C;AAEA,MAAI,CAAC,QAAQ;AACX,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,kBAAkB;AACpB,oBAAgB,MAAM;AACtB,YAAQ,IAAI,oBAAoB;AAEhC,QAAI,CAAC,OAAO,aAAa;AACvB,cAAQ,IAAI,4BAA4B;AACxC,UAAI;AACF,cAAM,YAAY,OAAO,WAAW;AACpC,gBAAQ,IAAI,wBAAwB;AAAA,MACtC,QAAQ;AACN,gBAAQ,IAAI,4DAAuD;AAAA,MACrE;AAAA,IACF;AAEA,YAAQ,IAAI;AAAA;AAAA,OAAuB,OAAO,WAAW;AAAA;AAAA;AAAA,WAA6D;AAAA,EACpH,OAAO;AACL,UAAM,IAAM,WAAQ;AAEpB,MAAE,MAAM,wBAAwB;AAChC,oBAAgB,MAAM;AACtB,MAAE,KAAK,oBAAoB;AAE3B,MAAE,MAAM,4BAA4B;AACpC,QAAI;AACF,YAAM,YAAY,OAAO,WAAW;AACpC,QAAE,KAAK,wBAAwB;AAAA,IACjC,QAAQ;AACN,QAAE,KAAK,4DAAuD;AAAA,IAChE;AAEA,IAAE;AAAA,MACA;AAAA,QACE,MAAM,OAAO,WAAW;AAAA,QACxB,iCAAiCC,IAAG,IAAI,qBAAqB,CAAC;AAAA,QAC9D,iCAAiCA,IAAG,IAAI,uBAAuB,CAAC;AAAA,QAChE,iCAAiCA,IAAG,IAAI,4BAA4B,CAAC;AAAA,MACvE,EAAE,KAAK,IAAI;AAAA,MACX;AAAA,IACF;AAEA,IAAE,SAAMA,IAAG,MAAM,OAAO,CAAC;AAAA,EAC3B;AACF;AAEA,KAAK,EAAE,MAAM,QAAQ,KAAK;","names":["p","pc","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"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { writeEnvLocal, runDbSetup, runDevServer } from \"./quickstart.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 === \"--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\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 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\n const supyagent =\n parsed.supyagentApiKey ??\n process.env.SUPYAGENT_API_KEY ??\n (await promptForKey(\"SUPYAGENT_API_KEY\"));\n\n const providerKey =\n (parsed[cliFlag] as string | undefined) ??\n process.env[envKey] ??\n (await promptForKey(envKey));\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 API keys\n const apiKeys = await resolveApiKeys(aiProvider, parsed);\n\n const config: ProjectConfig = {\n projectName,\n projectPath,\n aiProvider,\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 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 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 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 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, 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 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 } 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\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\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(\"api-route/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\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\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 // ── 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, [\"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"],"mappings":"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","names":["p","pc","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","pc"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-supyagent-app",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Create a supyagent-powered chatbot app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { convertToModelMessages, streamText, type UIMessage } from 'ai';
1
+ import { convertToModelMessages, streamText, stepCountIs, type UIMessage } from 'ai';
2
2
  {{aiProviderImport}};
3
3
  import { supyagent } from '@supyagent/sdk';
4
4
  import { createPrismaAdapter } from '@supyagent/sdk/prisma';
@@ -10,7 +10,7 @@ const adapter = createPrismaAdapter(prisma);
10
10
  export async function POST(req: Request) {
11
11
  const { messages, chatId }: { messages: UIMessage[]; chatId: string } = await req.json();
12
12
 
13
- await adapter.saveChat(chatId, messages);
13
+ await adapter.saveChat(chatId, messages as any);
14
14
 
15
15
  const tools = await client.tools({ cache: 300 });
16
16
 
@@ -19,13 +19,13 @@ export async function POST(req: Request) {
19
19
  system: 'You are a helpful assistant. Use your tools when asked to interact with connected services.',
20
20
  messages: await convertToModelMessages(messages),
21
21
  tools,
22
- maxSteps: 5,
22
+ stopWhen: stepCountIs(5),
23
23
  });
24
24
 
25
25
  return result.toUIMessageStreamResponse({
26
26
  originalMessages: messages,
27
27
  onFinish: async ({ messages: updatedMessages }) => {
28
- await adapter.saveChat(chatId, updatedMessages);
28
+ await adapter.saveChat(chatId, updatedMessages as any);
29
29
  },
30
30
  });
31
31
  }
@@ -0,0 +1,33 @@
1
+ # dependencies
2
+ /node_modules
3
+ /.pnp
4
+ .pnp.*
5
+ .yarn/install-state.gz
6
+
7
+ # next.js
8
+ /.next/
9
+ /out/
10
+
11
+ # production
12
+ /build
13
+
14
+ # misc
15
+ .DS_Store
16
+ *.pem
17
+
18
+ # debug
19
+ npm-debug.log*
20
+ yarn-debug.log*
21
+ yarn-error.log*
22
+ .pnpm-debug.log*
23
+
24
+ # env files
25
+ .env*.local
26
+
27
+ # typescript
28
+ *.tsbuildinfo
29
+ next-env.d.ts
30
+
31
+ # prisma
32
+ /prisma/dev.db
33
+ /prisma/dev.db-journal
@@ -1,7 +1,7 @@
1
1
  /** @type {import('postcss-load-config').Config} */
2
2
  const config = {
3
3
  plugins: {
4
- tailwindcss: {},
4
+ "@tailwindcss/postcss": {},
5
5
  },
6
6
  };
7
7
 
@@ -23,5 +23,5 @@ export default function ChatPage({ params }: { params: Promise<{ id: string }> }
23
23
  );
24
24
  }
25
25
 
26
- return <Chat chatId={id} initialMessages={initialMessages} />;
26
+ return <Chat key={id} chatId={id} initialMessages={initialMessages} />;
27
27
  }
@@ -1,10 +1,86 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import "tailwindcss";
2
+ @source "../node_modules/@supyagent/sdk/dist/*.js";
3
+ @source "../node_modules/streamdown/dist/*.js";
4
4
 
5
5
  @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 240 10% 3.9%;
9
+ --card: 0 0% 100%;
10
+ --card-foreground: 240 10% 3.9%;
11
+ --popover: 0 0% 100%;
12
+ --popover-foreground: 240 10% 3.9%;
13
+ --primary: 240 5.9% 10%;
14
+ --primary-foreground: 0 0% 98%;
15
+ --secondary: 240 4.8% 95.9%;
16
+ --secondary-foreground: 240 5.9% 10%;
17
+ --muted: 240 4.8% 95.9%;
18
+ --muted-foreground: 240 3.8% 46.1%;
19
+ --accent: 240 4.8% 95.9%;
20
+ --accent-foreground: 240 5.9% 10%;
21
+ --destructive: 0 84.2% 60.2%;
22
+ --destructive-foreground: 0 0% 98%;
23
+ --border: 240 5.9% 90%;
24
+ --input: 240 5.9% 90%;
25
+ --ring: 240 5.9% 10%;
26
+ --radius: 0.5rem;
27
+ }
28
+
29
+ .dark {
30
+ --background: 240 10% 3.9%;
31
+ --foreground: 0 0% 98%;
32
+ --card: 240 10% 5.9%;
33
+ --card-foreground: 0 0% 98%;
34
+ --popover: 240 10% 5.9%;
35
+ --popover-foreground: 0 0% 98%;
36
+ --primary: 0 0% 98%;
37
+ --primary-foreground: 240 5.9% 10%;
38
+ --secondary: 240 3.7% 15.9%;
39
+ --secondary-foreground: 0 0% 98%;
40
+ --muted: 240 3.7% 15.9%;
41
+ --muted-foreground: 240 5% 64.9%;
42
+ --accent: 240 3.7% 15.9%;
43
+ --accent-foreground: 0 0% 98%;
44
+ --destructive: 0 62.8% 30.6%;
45
+ --destructive-foreground: 0 0% 98%;
46
+ --border: 240 3.7% 15.9%;
47
+ --input: 240 3.7% 15.9%;
48
+ --ring: 240 4.9% 83.9%;
49
+ }
50
+ }
51
+
52
+ @theme inline {
53
+ --color-background: hsl(var(--background));
54
+ --color-foreground: hsl(var(--foreground));
55
+ --color-card: hsl(var(--card));
56
+ --color-card-foreground: hsl(var(--card-foreground));
57
+ --color-popover: hsl(var(--popover));
58
+ --color-popover-foreground: hsl(var(--popover-foreground));
59
+ --color-primary: hsl(var(--primary));
60
+ --color-primary-foreground: hsl(var(--primary-foreground));
61
+ --color-secondary: hsl(var(--secondary));
62
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
63
+ --color-muted: hsl(var(--muted));
64
+ --color-muted-foreground: hsl(var(--muted-foreground));
65
+ --color-accent: hsl(var(--accent));
66
+ --color-accent-foreground: hsl(var(--accent-foreground));
67
+ --color-destructive: hsl(var(--destructive));
68
+ --color-destructive-foreground: hsl(var(--destructive-foreground));
69
+ --color-border: hsl(var(--border));
70
+ --color-input: hsl(var(--input));
71
+ --color-ring: hsl(var(--ring));
72
+ --radius-sm: calc(var(--radius) - 4px);
73
+ --radius-md: calc(var(--radius) - 2px);
74
+ --radius-lg: var(--radius);
75
+ --radius-xl: calc(var(--radius) + 4px);
76
+ }
77
+
78
+ @layer base {
79
+ * {
80
+ @apply border-border;
81
+ }
6
82
  body {
7
- @apply bg-zinc-950 text-zinc-100;
83
+ @apply bg-background text-foreground;
8
84
  }
9
85
  }
10
86
 
@@ -18,10 +94,11 @@
18
94
  }
19
95
 
20
96
  ::-webkit-scrollbar-thumb {
21
- background: rgb(63 63 70);
97
+ background: hsl(var(--border));
22
98
  border-radius: 3px;
23
99
  }
24
100
 
25
101
  ::-webkit-scrollbar-thumb:hover {
26
- background: rgb(82 82 91);
102
+ background: hsl(var(--muted-foreground) / 0.3);
27
103
  }
104
+
@@ -16,7 +16,7 @@ export default function RootLayout({
16
16
  }) {
17
17
  return (
18
18
  <html lang="en" className="dark">
19
- <body className={`${inter.className} bg-zinc-950 text-zinc-100 antialiased`}>
19
+ <body className={`${inter.className} bg-background text-foreground antialiased`}>
20
20
  {children}
21
21
  </body>
22
22
  </html>
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
 
3
3
  import { ArrowUp, Square } from "lucide-react";
4
- import { useState } from "react";
5
- import type { FormEvent } from "react";
4
+ import { useState, useRef, useEffect, useCallback } from "react";
5
+ import type { FormEvent, KeyboardEvent } from "react";
6
6
 
7
7
  interface ChatInputProps {
8
8
  sendMessage: (message: { text: string }) => Promise<void>;
@@ -12,40 +12,59 @@ interface ChatInputProps {
12
12
 
13
13
  export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
14
14
  const [input, setInput] = useState("");
15
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
16
+
17
+ const adjustHeight = useCallback(() => {
18
+ const textarea = textareaRef.current;
19
+ if (!textarea) return;
20
+ textarea.style.height = "auto";
21
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
22
+ }, []);
23
+
24
+ useEffect(() => {
25
+ adjustHeight();
26
+ }, [input, adjustHeight]);
15
27
 
16
28
  const handleSubmit = (e: FormEvent) => {
17
29
  e.preventDefault();
18
- if (!input.trim()) return;
30
+ if (!input.trim() || isLoading) return;
19
31
  sendMessage({ text: input });
20
32
  setInput("");
33
+ // Reset height
34
+ if (textareaRef.current) {
35
+ textareaRef.current.style.height = "auto";
36
+ }
37
+ };
38
+
39
+ const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
40
+ if (e.key === "Enter" && !e.shiftKey) {
41
+ e.preventDefault();
42
+ if (input.trim() && !isLoading) {
43
+ handleSubmit(e as unknown as FormEvent);
44
+ }
45
+ }
21
46
  };
22
47
 
23
48
  return (
24
49
  <form
25
50
  onSubmit={handleSubmit}
26
- className="relative flex items-end rounded-xl border border-zinc-800 bg-zinc-900 focus-within:border-zinc-700"
51
+ className="relative flex items-end rounded-xl border border-border bg-card focus-within:border-ring focus-within:ring-1 focus-within:ring-ring transition-colors"
27
52
  >
28
53
  <textarea
54
+ ref={textareaRef}
29
55
  value={input}
30
56
  onChange={(e) => setInput(e.target.value)}
57
+ onKeyDown={handleKeyDown}
31
58
  placeholder="Send a message..."
32
59
  rows={1}
33
- className="flex-1 resize-none bg-transparent px-4 py-3 text-sm text-zinc-200 placeholder-zinc-500 outline-none"
34
- onKeyDown={(e) => {
35
- if (e.key === "Enter" && !e.shiftKey) {
36
- e.preventDefault();
37
- if (input.trim()) {
38
- handleSubmit(e as unknown as FormEvent);
39
- }
40
- }
41
- }}
60
+ className="flex-1 resize-none bg-transparent px-4 py-3 text-sm text-foreground placeholder-muted-foreground outline-none max-h-[200px]"
42
61
  />
43
62
  <div className="p-2">
44
63
  {isLoading ? (
45
64
  <button
46
65
  type="button"
47
66
  onClick={stop}
48
- className="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-700 text-zinc-300 hover:bg-zinc-600 transition-colors"
67
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-muted text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
49
68
  >
50
69
  <Square className="h-3.5 w-3.5" />
51
70
  </button>
@@ -53,7 +72,7 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
53
72
  <button
54
73
  type="submit"
55
74
  disabled={!input.trim()}
56
- className="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-200 text-zinc-900 disabled:opacity-30 disabled:cursor-not-allowed hover:bg-white transition-colors"
75
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary text-primary-foreground disabled:opacity-30 disabled:cursor-not-allowed hover:bg-primary/90 transition-colors"
57
76
  >
58
77
  <ArrowUp className="h-4 w-4" />
59
78
  </button>
@@ -1,44 +1,89 @@
1
1
  "use client";
2
2
 
3
3
  import type { UIMessage } from "ai";
4
- import { SupyagentToolCall, SupyagentToolResult } from "@supyagent/sdk/react";
4
+ import { isToolUIPart } from "ai";
5
+ import { SupyagentToolAction } from "@supyagent/sdk/react";
6
+ import { useState } from "react";
7
+ import { Streamdown } from "streamdown";
8
+ import { Copy, Check } from "lucide-react";
9
+ import { cn } from "@/lib/utils";
5
10
 
6
11
  interface ChatMessageProps {
7
12
  message: UIMessage;
8
13
  }
9
14
 
15
+ function MarkdownContent({ text }: { text: string }) {
16
+ return (
17
+ <div className="text-sm text-foreground">
18
+ <Streamdown>{text}</Streamdown>
19
+ </div>
20
+ );
21
+ }
22
+
23
+ function CopyButton({ text }: { text: string }) {
24
+ const [copied, setCopied] = useState(false);
25
+
26
+ const handleCopy = () => {
27
+ navigator.clipboard.writeText(text).then(() => {
28
+ setCopied(true);
29
+ setTimeout(() => setCopied(false), 2000);
30
+ });
31
+ };
32
+
33
+ return (
34
+ <button
35
+ type="button"
36
+ onClick={handleCopy}
37
+ className="rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
38
+ title="Copy message"
39
+ >
40
+ {copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
41
+ </button>
42
+ );
43
+ }
44
+
10
45
  export function ChatMessage({ message }: ChatMessageProps) {
11
46
  const isUser = message.role === "user";
47
+ const textContent = message.parts
48
+ .filter((part) => part.type === "text")
49
+ .map((part) => (part as any).text)
50
+ .join("\n");
12
51
 
13
52
  return (
14
- <div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
53
+ <div className={cn("group flex", isUser ? "justify-end" : "justify-start")}>
15
54
  <div
16
- className={`max-w-[85%] space-y-2 ${
55
+ className={cn(
56
+ "max-w-[85%] space-y-2",
17
57
  isUser
18
- ? "rounded-2xl rounded-br-md bg-zinc-800 px-4 py-2.5"
19
- : ""
20
- }`}
58
+ ? "rounded-2xl rounded-br-md bg-secondary px-4 py-2.5"
59
+ : "w-full max-w-none"
60
+ )}
21
61
  >
22
62
  {message.parts.map((part, i) => {
23
63
  if (part.type === "text") {
24
- return (
25
- <p key={i} className="text-sm text-zinc-200 whitespace-pre-wrap">
26
- {part.text}
27
- </p>
28
- );
64
+ if (isUser) {
65
+ return (
66
+ <p key={i} className="text-sm text-foreground whitespace-pre-wrap">
67
+ {(part as any).text}
68
+ </p>
69
+ );
70
+ }
71
+ return <MarkdownContent key={i} text={(part as any).text} />;
29
72
  }
30
73
 
31
- if (part.type === "tool-invocation") {
32
- return (
33
- <div key={i} className="space-y-2">
34
- <SupyagentToolCall part={part as any} />
35
- <SupyagentToolResult part={part as any} />
36
- </div>
37
- );
74
+ if (isToolUIPart(part)) {
75
+ return <SupyagentToolAction key={i} part={part as any} />;
38
76
  }
39
77
 
40
78
  return null;
41
79
  })}
80
+
81
+ {/* Message actions */}
82
+ {!isUser && textContent && (
83
+ <div className="opacity-0 group-hover:opacity-100 transition-opacity pt-1">
84
+ <CopyButton text={textContent} />
85
+ </div>
86
+ )}
42
87
  </div>
43
88
  </div>
44
89
  );
@@ -1,8 +1,9 @@
1
1
  "use client";
2
2
 
3
- import { useEffect, useState } from "react";
3
+ import { useEffect, useState, useMemo } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- import { MessageSquare, Plus, Trash2 } from "lucide-react";
5
+ import { MessageSquare, Plus, Trash2, Search, PanelLeftClose, PanelLeft } from "lucide-react";
6
+ import { cn } from "@/lib/utils";
6
7
 
7
8
  interface ChatSummary {
8
9
  id: string;
@@ -15,9 +16,24 @@ interface ChatSidebarProps {
15
16
  currentChatId: string;
16
17
  }
17
18
 
19
+ function getDateGroup(dateStr: string): string {
20
+ const date = new Date(dateStr);
21
+ const now = new Date();
22
+ const diffMs = now.getTime() - date.getTime();
23
+ const diffDays = Math.floor(diffMs / 86400000);
24
+
25
+ if (diffDays === 0) return "Today";
26
+ if (diffDays === 1) return "Yesterday";
27
+ if (diffDays < 7) return "Previous 7 days";
28
+ if (diffDays < 30) return "Previous 30 days";
29
+ return "Older";
30
+ }
31
+
18
32
  export function ChatSidebar({ currentChatId }: ChatSidebarProps) {
19
33
  const router = useRouter();
20
34
  const [chats, setChats] = useState<ChatSummary[]>([]);
35
+ const [search, setSearch] = useState("");
36
+ const [collapsed, setCollapsed] = useState(false);
21
37
 
22
38
  useEffect(() => {
23
39
  fetch("/api/chats")
@@ -34,40 +50,108 @@ export function ChatSidebar({ currentChatId }: ChatSidebarProps) {
34
50
  }
35
51
  };
36
52
 
53
+ const filteredChats = useMemo(() => {
54
+ if (!search.trim()) return chats;
55
+ const q = search.toLowerCase();
56
+ return chats.filter((c) => c.title.toLowerCase().includes(q));
57
+ }, [chats, search]);
58
+
59
+ const groupedChats = useMemo(() => {
60
+ const groups: Record<string, ChatSummary[]> = {};
61
+ const order = ["Today", "Yesterday", "Previous 7 days", "Previous 30 days", "Older"];
62
+
63
+ for (const chat of filteredChats) {
64
+ const group = getDateGroup(chat.updatedAt || chat.createdAt);
65
+ if (!groups[group]) groups[group] = [];
66
+ groups[group].push(chat);
67
+ }
68
+
69
+ return order
70
+ .filter((g) => groups[g]?.length)
71
+ .map((g) => ({ label: g, chats: groups[g] }));
72
+ }, [filteredChats]);
73
+
74
+ if (collapsed) {
75
+ return (
76
+ <div className="flex h-full w-12 flex-col items-center border-r border-border bg-card py-3 gap-2">
77
+ <button
78
+ onClick={() => setCollapsed(false)}
79
+ className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
80
+ >
81
+ <PanelLeft className="h-4 w-4" />
82
+ </button>
83
+ <button
84
+ onClick={() => router.push("/chat")}
85
+ className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
86
+ >
87
+ <Plus className="h-4 w-4" />
88
+ </button>
89
+ </div>
90
+ );
91
+ }
92
+
37
93
  return (
38
- <div className="flex h-full w-64 flex-col border-r border-zinc-800 bg-zinc-900/50">
39
- <div className="flex items-center justify-between p-4">
40
- <span className="text-sm font-medium text-zinc-300">Chats</span>
94
+ <div className="flex h-full w-64 flex-col border-r border-border bg-card">
95
+ <div className="flex items-center justify-between p-3">
96
+ <button
97
+ onClick={() => setCollapsed(true)}
98
+ className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
99
+ >
100
+ <PanelLeftClose className="h-4 w-4" />
101
+ </button>
102
+ <span className="text-sm font-medium text-foreground">Chats</span>
41
103
  <button
42
104
  onClick={() => router.push("/chat")}
43
- className="rounded-md p-1.5 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200 transition-colors"
105
+ className="rounded-md p-1.5 text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
44
106
  >
45
107
  <Plus className="h-4 w-4" />
46
108
  </button>
47
109
  </div>
48
110
 
111
+ {/* Search */}
112
+ <div className="px-3 pb-2">
113
+ <div className="flex items-center gap-2 rounded-lg border border-border bg-background px-2.5 py-1.5">
114
+ <Search className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
115
+ <input
116
+ type="text"
117
+ value={search}
118
+ onChange={(e) => setSearch(e.target.value)}
119
+ placeholder="Search chats..."
120
+ className="flex-1 bg-transparent text-xs text-foreground placeholder-muted-foreground outline-none"
121
+ />
122
+ </div>
123
+ </div>
124
+
49
125
  <div className="flex-1 overflow-y-auto px-2">
50
- {chats.map((chat) => (
51
- <div
52
- key={chat.id}
53
- className={`group mb-1 flex items-center rounded-lg px-3 py-2 cursor-pointer transition-colors ${
54
- chat.id === currentChatId
55
- ? "bg-zinc-800 text-zinc-200"
56
- : "text-zinc-400 hover:bg-zinc-800/50 hover:text-zinc-300"
57
- }`}
58
- onClick={() => router.push(`/chat/${chat.id}`)}
59
- >
60
- <MessageSquare className="mr-2 h-3.5 w-3.5 shrink-0" />
61
- <span className="flex-1 truncate text-sm">{chat.title}</span>
62
- <button
63
- onClick={(e) => {
64
- e.stopPropagation();
65
- deleteChat(chat.id);
66
- }}
67
- className="ml-1 hidden rounded p-1 text-zinc-500 hover:bg-zinc-700 hover:text-zinc-300 group-hover:block"
68
- >
69
- <Trash2 className="h-3 w-3" />
70
- </button>
126
+ {groupedChats.map((group) => (
127
+ <div key={group.label} className="mb-3">
128
+ <p className="px-3 py-1 text-xs font-medium text-muted-foreground">
129
+ {group.label}
130
+ </p>
131
+ {group.chats.map((chat) => (
132
+ <div
133
+ key={chat.id}
134
+ className={cn(
135
+ "group mb-0.5 flex items-center rounded-lg px-3 py-2 cursor-pointer transition-colors",
136
+ chat.id === currentChatId
137
+ ? "bg-muted text-foreground"
138
+ : "text-muted-foreground hover:bg-muted hover:text-foreground"
139
+ )}
140
+ onClick={() => router.push(`/chat/${chat.id}`)}
141
+ >
142
+ <MessageSquare className="mr-2 h-3.5 w-3.5 shrink-0" />
143
+ <span className="flex-1 truncate text-sm">{chat.title}</span>
144
+ <button
145
+ onClick={(e) => {
146
+ e.stopPropagation();
147
+ deleteChat(chat.id);
148
+ }}
149
+ className="ml-1 hidden rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground group-hover:block"
150
+ >
151
+ <Trash2 className="h-3 w-3" />
152
+ </button>
153
+ </div>
154
+ ))}
71
155
  </div>
72
156
  ))}
73
157
  </div>
@@ -5,7 +5,8 @@ import { DefaultChatTransport, type UIMessage } from "ai";
5
5
  import { ChatMessage } from "./chat-message";
6
6
  import { ChatInput } from "./chat-input";
7
7
  import { ChatSidebar } from "./chat-sidebar";
8
- import { useRef, useEffect, useMemo } from "react";
8
+ import { useRef, useEffect, useMemo, useState, useCallback } from "react";
9
+ import { MessageSquare, ArrowDown } from "lucide-react";
9
10
 
10
11
  interface ChatProps {
11
12
  chatId: string;
@@ -25,23 +26,49 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
25
26
  const { messages, sendMessage, status, stop } = useChat({
26
27
  id: chatId,
27
28
  transport,
28
- initialMessages,
29
+ messages: initialMessages,
29
30
  });
30
31
 
31
32
  const isLoading = status === "submitted" || status === "streaming";
32
33
  const scrollRef = useRef<HTMLDivElement>(null);
34
+ const bottomRef = useRef<HTMLDivElement>(null);
35
+ const [isAtBottom, setIsAtBottom] = useState(true);
36
+
37
+ const scrollToBottom = useCallback(() => {
38
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
39
+ }, []);
33
40
 
34
41
  useEffect(() => {
35
- if (scrollRef.current) {
36
- scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
42
+ const el = scrollRef.current;
43
+ if (!el) return;
44
+
45
+ const handleScroll = () => {
46
+ const threshold = 100;
47
+ const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < threshold;
48
+ setIsAtBottom(atBottom);
49
+ };
50
+
51
+ el.addEventListener("scroll", handleScroll, { passive: true });
52
+ return () => el.removeEventListener("scroll", handleScroll);
53
+ }, []);
54
+
55
+ useEffect(() => {
56
+ if (isAtBottom) {
57
+ bottomRef.current?.scrollIntoView({ behavior: "smooth" });
37
58
  }
38
- }, [messages]);
59
+ }, [messages, isAtBottom]);
60
+
61
+ const suggestions = [
62
+ "What integrations are connected?",
63
+ "Search my recent emails",
64
+ "What's on my calendar today?",
65
+ ];
39
66
 
40
67
  return (
41
68
  <div className="flex h-screen">
42
69
  <ChatSidebar currentChatId={chatId} />
43
70
 
44
- <div className="flex flex-1 flex-col">
71
+ <div className="flex flex-1 flex-col relative">
45
72
  <div
46
73
  ref={scrollRef}
47
74
  className="flex-1 overflow-y-auto px-4 py-6"
@@ -49,23 +76,51 @@ export function Chat({ chatId, initialMessages }: ChatProps) {
49
76
  <div className="mx-auto max-w-3xl space-y-6">
50
77
  {messages.length === 0 && (
51
78
  <div className="flex h-full min-h-[60vh] items-center justify-center">
52
- <div className="text-center">
53
- <h1 className="text-2xl font-semibold text-zinc-200">
79
+ <div className="text-center max-w-md">
80
+ <div className="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-muted">
81
+ <MessageSquare className="h-6 w-6 text-muted-foreground" />
82
+ </div>
83
+ <h1 className="text-xl font-semibold text-foreground">
54
84
  Supyagent Chat
55
85
  </h1>
56
- <p className="mt-2 text-sm text-zinc-500">
86
+ <p className="mt-2 text-sm text-muted-foreground">
57
87
  Ask me anything — I can use your connected integrations.
58
88
  </p>
89
+ <div className="mt-6 flex flex-wrap justify-center gap-2">
90
+ {suggestions.map((suggestion) => (
91
+ <button
92
+ key={suggestion}
93
+ type="button"
94
+ onClick={() => sendMessage({ text: suggestion })}
95
+ className="rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
96
+ >
97
+ {suggestion}
98
+ </button>
99
+ ))}
100
+ </div>
59
101
  </div>
60
102
  </div>
61
103
  )}
62
104
  {messages.map((message) => (
63
105
  <ChatMessage key={message.id} message={message} />
64
106
  ))}
107
+ <div ref={bottomRef} />
65
108
  </div>
66
109
  </div>
67
110
 
68
- <div className="border-t border-zinc-800 px-4 py-4">
111
+ {/* Scroll to bottom button */}
112
+ {!isAtBottom && messages.length > 0 && (
113
+ <button
114
+ type="button"
115
+ onClick={scrollToBottom}
116
+ className="absolute bottom-24 left-1/2 -translate-x-1/2 flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted-foreground shadow-lg hover:bg-muted transition-colors"
117
+ >
118
+ <ArrowDown className="h-3 w-3" />
119
+ Scroll to bottom
120
+ </button>
121
+ )}
122
+
123
+ <div className="border-t border-border px-4 py-4">
69
124
  <div className="mx-auto max-w-3xl">
70
125
  <ChatInput
71
126
  sendMessage={sendMessage}
@@ -16,22 +16,24 @@
16
16
  "@ai-sdk/react": "^3.0.0",
17
17
  "{{aiProviderPackage}}": "{{aiProviderVersion}}",
18
18
  "@prisma/client": "^6.2.0",
19
- "@supyagent/sdk": "^0.1.4",
19
+ "@supyagent/sdk": "^0.1.7",
20
20
  "ai": "^6.0.0",
21
21
  "clsx": "^2.1.0",
22
22
  "lucide-react": "^0.468.0",
23
23
  "next": "^15.1.0",
24
24
  "react": "^19.0.0",
25
25
  "react-dom": "^19.0.0",
26
- "tailwind-merge": "^2.6.0"
26
+ "streamdown": "^2.0.0",
27
+ "tailwind-merge": "^2.6.0",
28
+ "use-stick-to-bottom": "^1.0.0"
27
29
  },
28
30
  "devDependencies": {
31
+ "@tailwindcss/postcss": "^4.0.0",
29
32
  "@types/node": "^22.0.0",
30
33
  "@types/react": "^19.0.0",
31
34
  "@types/react-dom": "^19.0.0",
32
- "postcss": "^8.4.0",
33
35
  "prisma": "^6.2.0",
34
- "tailwindcss": "^3.4.0",
36
+ "tailwindcss": "^4.0.0",
35
37
  "typescript": "^5.7.0"
36
38
  }
37
39
  }