create-supyagent-app 0.1.0

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 ADDED
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import * as p2 from "@clack/prompts";
5
+ import pc2 from "picocolors";
6
+
7
+ // src/prompts.ts
8
+ import * as p from "@clack/prompts";
9
+ import pc from "picocolors";
10
+
11
+ // src/utils.ts
12
+ import { existsSync } from "fs";
13
+ import { resolve } from "path";
14
+ function resolveProjectPath(name) {
15
+ return resolve(process.cwd(), name);
16
+ }
17
+ function projectExists(path) {
18
+ return existsSync(path);
19
+ }
20
+ var AI_PROVIDERS = {
21
+ anthropic: {
22
+ label: "Anthropic (Claude)",
23
+ package: "@ai-sdk/anthropic",
24
+ import: `import { anthropic } from '@ai-sdk/anthropic'`,
25
+ model: `anthropic('claude-sonnet-4-20250514')`,
26
+ envKey: "ANTHROPIC_API_KEY"
27
+ },
28
+ openai: {
29
+ label: "OpenAI (GPT)",
30
+ package: "@ai-sdk/openai",
31
+ import: `import { openai } from '@ai-sdk/openai'`,
32
+ model: `openai('gpt-4o')`,
33
+ envKey: "OPENAI_API_KEY"
34
+ },
35
+ openrouter: {
36
+ label: "OpenRouter (any model)",
37
+ package: "@ai-sdk/openrouter",
38
+ import: `import { openrouter } from '@ai-sdk/openrouter'`,
39
+ model: `openrouter('anthropic/claude-sonnet-4-20250514')`,
40
+ envKey: "OPENROUTER_API_KEY"
41
+ }
42
+ };
43
+ var DB_CONFIGS = {
44
+ sqlite: {
45
+ label: "SQLite (local dev)",
46
+ provider: "sqlite",
47
+ url: "file:./dev.db"
48
+ },
49
+ postgres: {
50
+ label: "PostgreSQL (production)",
51
+ provider: "postgresql",
52
+ url: "postgresql://user:password@localhost:5432/mydb"
53
+ }
54
+ };
55
+
56
+ // src/prompts.ts
57
+ async function runPrompts(argName) {
58
+ p.intro(pc.bgCyan(pc.black(" Create Supyagent App ")));
59
+ const projectName = argName || await p.text({
60
+ message: "Project name",
61
+ placeholder: "my-supyagent-app",
62
+ defaultValue: "my-supyagent-app",
63
+ validate(value) {
64
+ if (!value) return "Project name is required";
65
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {
66
+ return "Invalid project name (lowercase, alphanumeric, hyphens, dots)";
67
+ }
68
+ }
69
+ });
70
+ if (p.isCancel(projectName)) {
71
+ p.cancel("Cancelled.");
72
+ return null;
73
+ }
74
+ const projectPath = resolveProjectPath(projectName);
75
+ if (projectExists(projectPath)) {
76
+ p.cancel(`Directory "${projectName}" already exists.`);
77
+ return null;
78
+ }
79
+ const aiProvider = await p.select({
80
+ message: "AI provider",
81
+ options: [
82
+ { value: "anthropic", label: AI_PROVIDERS.anthropic.label },
83
+ { value: "openai", label: AI_PROVIDERS.openai.label },
84
+ { value: "openrouter", label: AI_PROVIDERS.openrouter.label }
85
+ ]
86
+ });
87
+ if (p.isCancel(aiProvider)) {
88
+ p.cancel("Cancelled.");
89
+ return null;
90
+ }
91
+ const database = await p.select({
92
+ message: "Database",
93
+ options: [
94
+ { value: "sqlite", label: DB_CONFIGS.sqlite.label },
95
+ { value: "postgres", label: DB_CONFIGS.postgres.label }
96
+ ]
97
+ });
98
+ if (p.isCancel(database)) {
99
+ p.cancel("Cancelled.");
100
+ return null;
101
+ }
102
+ return { projectName, projectPath, aiProvider, database };
103
+ }
104
+
105
+ // src/scaffold.ts
106
+ import { mkdirSync, writeFileSync, readFileSync } from "fs";
107
+ import { join, dirname } from "path";
108
+ import { fileURLToPath } from "url";
109
+
110
+ // src/template.ts
111
+ function applyTemplate(content, variables) {
112
+ return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
113
+ return key in variables ? variables[key] : match;
114
+ });
115
+ }
116
+
117
+ // src/scaffold.ts
118
+ var __dirname = dirname(fileURLToPath(import.meta.url));
119
+ var TEMPLATES_DIR = join(__dirname, "..", "templates");
120
+ function readTemplate(relativePath) {
121
+ return readFileSync(join(TEMPLATES_DIR, relativePath), "utf-8");
122
+ }
123
+ function writeProject(projectPath, relativePath, content) {
124
+ const fullPath = join(projectPath, relativePath);
125
+ mkdirSync(dirname(fullPath), { recursive: true });
126
+ writeFileSync(fullPath, content, "utf-8");
127
+ }
128
+ function scaffoldProject(config) {
129
+ const { projectPath, projectName, aiProvider, database } = config;
130
+ const ai = AI_PROVIDERS[aiProvider];
131
+ const db = DB_CONFIGS[database];
132
+ const vars = {
133
+ projectName,
134
+ aiProviderPackage: ai.package,
135
+ aiProviderImport: ai.import,
136
+ aiModel: ai.model,
137
+ aiProviderEnvKey: ai.envKey,
138
+ dbProvider: db.provider,
139
+ dbUrl: db.url
140
+ };
141
+ mkdirSync(projectPath, { recursive: true });
142
+ writeProject(projectPath, "next.config.ts", readTemplate("base/next.config.ts"));
143
+ writeProject(projectPath, "tsconfig.json", readTemplate("base/tsconfig.json"));
144
+ writeProject(projectPath, "tailwind.config.ts", readTemplate("base/tailwind.config.ts"));
145
+ writeProject(projectPath, "postcss.config.js", readTemplate("base/postcss.config.js"));
146
+ writeProject(projectPath, ".gitignore", readTemplate("base/.gitignore"));
147
+ writeProject(projectPath, "README.md", applyTemplate(readTemplate("base/README.md.tmpl"), vars));
148
+ writeProject(projectPath, "src/app/layout.tsx", readTemplate("base/src/app/layout.tsx"));
149
+ writeProject(projectPath, "src/app/page.tsx", readTemplate("base/src/app/page.tsx"));
150
+ writeProject(projectPath, "src/app/globals.css", readTemplate("base/src/app/globals.css"));
151
+ writeProject(projectPath, "src/app/chat/page.tsx", readTemplate("base/src/app/chat/page.tsx"));
152
+ writeProject(projectPath, "src/app/chat/[id]/page.tsx", readTemplate("base/src/app/chat/[id]/page.tsx"));
153
+ writeProject(
154
+ projectPath,
155
+ "src/app/api/chat/route.ts",
156
+ applyTemplate(readTemplate("api-route/route.ts.tmpl"), vars)
157
+ );
158
+ writeProject(projectPath, "src/app/api/chats/route.ts", readTemplate("base/src/app/api/chats/route.ts"));
159
+ writeProject(projectPath, "src/app/api/chats/[id]/route.ts", readTemplate("base/src/app/api/chats/[id]/route.ts"));
160
+ writeProject(projectPath, "src/components/chat.tsx", readTemplate("base/src/components/chat.tsx"));
161
+ writeProject(projectPath, "src/components/chat-sidebar.tsx", readTemplate("base/src/components/chat-sidebar.tsx"));
162
+ writeProject(projectPath, "src/components/chat-message.tsx", readTemplate("base/src/components/chat-message.tsx"));
163
+ writeProject(projectPath, "src/components/chat-input.tsx", readTemplate("base/src/components/chat-input.tsx"));
164
+ writeProject(projectPath, "src/lib/utils.ts", readTemplate("base/src/lib/utils.ts"));
165
+ writeProject(projectPath, "src/lib/prisma.ts", readTemplate("base/src/lib/prisma.ts"));
166
+ writeProject(
167
+ projectPath,
168
+ "prisma/schema.prisma",
169
+ applyTemplate(readTemplate("prisma/schema.prisma.tmpl"), vars)
170
+ );
171
+ writeProject(
172
+ projectPath,
173
+ ".env.example",
174
+ applyTemplate(readTemplate("env/.env.example.tmpl"), vars)
175
+ );
176
+ writeProject(
177
+ projectPath,
178
+ "package.json",
179
+ applyTemplate(readTemplate("package-json/package.json.tmpl"), vars)
180
+ );
181
+ }
182
+
183
+ // src/post-install.ts
184
+ import { detectPackageManager, installDependencies } from "nypm";
185
+ async function installDeps(projectPath) {
186
+ const pm = await detectPackageManager(projectPath);
187
+ await installDependencies({
188
+ cwd: projectPath,
189
+ packageManager: pm?.name
190
+ });
191
+ }
192
+
193
+ // src/index.ts
194
+ async function main() {
195
+ const argName = process.argv[2];
196
+ const config = await runPrompts(argName);
197
+ if (!config) {
198
+ process.exit(1);
199
+ }
200
+ const s = p2.spinner();
201
+ s.start("Scaffolding project...");
202
+ scaffoldProject(config);
203
+ s.stop("Scaffolded project");
204
+ s.start("Installing dependencies...");
205
+ try {
206
+ await installDeps(config.projectPath);
207
+ s.stop("Installed dependencies");
208
+ } catch {
209
+ s.stop("Failed to install dependencies \u2014 run install manually");
210
+ }
211
+ p2.note(
212
+ [
213
+ `cd ${config.projectName}`,
214
+ `cp .env.example .env.local ${pc2.dim("# Add your API keys")}`,
215
+ `pnpm db:setup ${pc2.dim("# Initialize database")}`,
216
+ `pnpm dev ${pc2.dim("# Start development server")}`
217
+ ].join("\n"),
218
+ "Next steps"
219
+ );
220
+ p2.outro(pc2.green("Done!"));
221
+ }
222
+ main().catch(console.error);
223
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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\";\n\nasync function main() {\n const argName = process.argv[2];\n const config = await runPrompts(argName);\n\n if (!config) {\n process.exit(1);\n }\n\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\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 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 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: \"@ai-sdk/openrouter\",\n import: `import { openrouter } from '@ai-sdk/openrouter'`,\n model: `openrouter('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 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, \"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":"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","names":["p","pc","pc"]}
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "create-supyagent-app",
3
+ "version": "0.1.0",
4
+ "description": "Create a supyagent-powered chatbot app",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-supyagent-app": "./dist/index.js"
8
+ },
9
+ "files": ["dist", "templates"],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "clean": "rm -rf dist"
13
+ },
14
+ "dependencies": {
15
+ "@clack/prompts": "^0.8.0",
16
+ "nypm": "^0.4.0",
17
+ "picocolors": "^1.1.0"
18
+ },
19
+ "devDependencies": {
20
+ "tsup": "^8.3.0",
21
+ "typescript": "^5.7.0"
22
+ },
23
+ "license": "MIT"
24
+ }
@@ -0,0 +1,29 @@
1
+ import { convertToModelMessages, streamText, type UIMessage } from 'ai';
2
+ {{aiProviderImport}};
3
+ import { supyagent } from '@supyagent/sdk';
4
+ import { createPrismaAdapter } from '@supyagent/sdk/prisma';
5
+ import { prisma } from '@/lib/prisma';
6
+
7
+ const client = supyagent({ apiKey: process.env.SUPYAGENT_API_KEY! });
8
+ const adapter = createPrismaAdapter(prisma);
9
+
10
+ export async function POST(req: Request) {
11
+ const { messages, chatId }: { messages: UIMessage[]; chatId: string } = await req.json();
12
+
13
+ await adapter.saveChat(chatId, messages);
14
+
15
+ const tools = await client.tools({ cache: 300 });
16
+
17
+ const result = streamText({
18
+ model: {{aiModel}},
19
+ system: 'You are a helpful assistant. Use your tools when asked to interact with connected services.',
20
+ messages: convertToModelMessages(messages),
21
+ tools,
22
+ maxSteps: 5,
23
+ onFinish: async ({ response }) => {
24
+ await adapter.saveChat(chatId, [...messages, ...response.messages as unknown as UIMessage[]]);
25
+ },
26
+ });
27
+
28
+ return result.toUIMessageStreamResponse();
29
+ }
@@ -0,0 +1,30 @@
1
+ # {{projectName}}
2
+
3
+ A chatbot powered by [Supyagent](https://supyagent.com) with connected integrations.
4
+
5
+ ## Setup
6
+
7
+ ```bash
8
+ cp .env.example .env.local # Add your API keys
9
+ pnpm db:setup # Initialize database
10
+ pnpm dev # Start development server
11
+ ```
12
+
13
+ ## Getting API Keys
14
+
15
+ 1. **Supyagent API Key**: Sign up at [app.supyagent.com](https://app.supyagent.com) and create an API key
16
+ 2. **AI Provider Key**: Get your key from your AI provider's dashboard
17
+
18
+ ## Features
19
+
20
+ - Chat with an AI assistant that can use your connected integrations
21
+ - Persistent chat history
22
+ - Rich tool result visualization (emails, calendar events, Slack messages, etc.)
23
+
24
+ ## Stack
25
+
26
+ - [Next.js](https://nextjs.org) — React framework
27
+ - [Vercel AI SDK](https://sdk.vercel.ai) — AI integration
28
+ - [Supyagent](https://supyagent.com) — Third-party service integrations
29
+ - [Prisma](https://prisma.io) — Database ORM
30
+ - [Tailwind CSS](https://tailwindcss.com) — Styling
@@ -0,0 +1,5 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {};
4
+
5
+ export default nextConfig;
@@ -0,0 +1,8 @@
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ module.exports = config;
@@ -0,0 +1,23 @@
1
+ import { prisma } from "@/lib/prisma";
2
+ import { createPrismaAdapter } from "@supyagent/sdk/prisma";
3
+ import { NextResponse } from "next/server";
4
+
5
+ const adapter = createPrismaAdapter(prisma);
6
+
7
+ export async function GET(
8
+ _req: Request,
9
+ { params }: { params: Promise<{ id: string }> }
10
+ ) {
11
+ const { id } = await params;
12
+ const messages = await adapter.loadChat(id);
13
+ return NextResponse.json({ messages });
14
+ }
15
+
16
+ export async function DELETE(
17
+ _req: Request,
18
+ { params }: { params: Promise<{ id: string }> }
19
+ ) {
20
+ const { id } = await params;
21
+ await adapter.deleteChat(id);
22
+ return NextResponse.json({ ok: true });
23
+ }
@@ -0,0 +1,10 @@
1
+ import { prisma } from "@/lib/prisma";
2
+ import { createPrismaAdapter } from "@supyagent/sdk/prisma";
3
+ import { NextResponse } from "next/server";
4
+
5
+ const adapter = createPrismaAdapter(prisma);
6
+
7
+ export async function GET() {
8
+ const chats = await adapter.listChats();
9
+ return NextResponse.json({ chats });
10
+ }
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import { use, useEffect, useState } from "react";
4
+ import { Chat } from "@/components/chat";
5
+
6
+ interface ChatMessage {
7
+ id: string;
8
+ role: string;
9
+ parts: Array<{ type: string; [key: string]: unknown }>;
10
+ metadata?: Record<string, unknown>;
11
+ }
12
+
13
+ export default function ChatPage({ params }: { params: Promise<{ id: string }> }) {
14
+ const { id } = use(params);
15
+ const [initialMessages, setInitialMessages] = useState<ChatMessage[] | null>(null);
16
+
17
+ useEffect(() => {
18
+ fetch(`/api/chats/${id}`)
19
+ .then((res) => (res.ok ? res.json() : { messages: [] }))
20
+ .then((data) => setInitialMessages(data.messages || []))
21
+ .catch(() => setInitialMessages([]));
22
+ }, [id]);
23
+
24
+ if (initialMessages === null) {
25
+ return (
26
+ <div className="flex h-screen items-center justify-center">
27
+ <div className="h-6 w-6 animate-spin rounded-full border-2 border-zinc-700 border-t-zinc-300" />
28
+ </div>
29
+ );
30
+ }
31
+
32
+ return <Chat chatId={id} initialMessages={initialMessages} />;
33
+ }
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import { useRouter } from "next/navigation";
4
+ import { useEffect, useId } from "react";
5
+
6
+ export default function NewChatPage() {
7
+ const router = useRouter();
8
+ const id = useId().replace(/:/g, "");
9
+
10
+ useEffect(() => {
11
+ // Generate a unique chat ID and redirect
12
+ const chatId = `chat_${id}_${Date.now().toString(36)}`;
13
+ router.replace(`/chat/${chatId}`);
14
+ }, [router, id]);
15
+
16
+ return (
17
+ <div className="flex h-screen items-center justify-center">
18
+ <div className="h-6 w-6 animate-spin rounded-full border-2 border-zinc-700 border-t-zinc-300" />
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,27 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ body {
7
+ @apply bg-zinc-950 text-zinc-100;
8
+ }
9
+ }
10
+
11
+ /* Scrollbar styling */
12
+ ::-webkit-scrollbar {
13
+ width: 6px;
14
+ }
15
+
16
+ ::-webkit-scrollbar-track {
17
+ background: transparent;
18
+ }
19
+
20
+ ::-webkit-scrollbar-thumb {
21
+ background: rgb(63 63 70);
22
+ border-radius: 3px;
23
+ }
24
+
25
+ ::-webkit-scrollbar-thumb:hover {
26
+ background: rgb(82 82 91);
27
+ }
@@ -0,0 +1,24 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const inter = Inter({ subsets: ["latin"] });
6
+
7
+ export const metadata: Metadata = {
8
+ title: "Supyagent Chat",
9
+ description: "AI chat with connected integrations",
10
+ };
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode;
16
+ }) {
17
+ return (
18
+ <html lang="en" className="dark">
19
+ <body className={`${inter.className} bg-zinc-950 text-zinc-100 antialiased`}>
20
+ {children}
21
+ </body>
22
+ </html>
23
+ );
24
+ }
@@ -0,0 +1,5 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ export default function Home() {
4
+ redirect("/chat");
5
+ }
@@ -0,0 +1,62 @@
1
+ "use client";
2
+
3
+ import { ArrowUp, Square } from "lucide-react";
4
+ import type { ChangeEvent, FormEvent } from "react";
5
+
6
+ interface ChatInputProps {
7
+ input: string;
8
+ handleInputChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
9
+ handleSubmit: (e: FormEvent<HTMLFormElement>) => void;
10
+ isLoading: boolean;
11
+ stop: () => void;
12
+ }
13
+
14
+ export function ChatInput({
15
+ input,
16
+ handleInputChange,
17
+ handleSubmit,
18
+ isLoading,
19
+ stop,
20
+ }: ChatInputProps) {
21
+ return (
22
+ <form
23
+ onSubmit={handleSubmit}
24
+ className="relative flex items-end rounded-xl border border-zinc-800 bg-zinc-900 focus-within:border-zinc-700"
25
+ >
26
+ <textarea
27
+ value={input}
28
+ onChange={handleInputChange}
29
+ placeholder="Send a message..."
30
+ rows={1}
31
+ className="flex-1 resize-none bg-transparent px-4 py-3 text-sm text-zinc-200 placeholder-zinc-500 outline-none"
32
+ onKeyDown={(e) => {
33
+ if (e.key === "Enter" && !e.shiftKey) {
34
+ e.preventDefault();
35
+ if (input.trim()) {
36
+ handleSubmit(e as unknown as FormEvent<HTMLFormElement>);
37
+ }
38
+ }
39
+ }}
40
+ />
41
+ <div className="p-2">
42
+ {isLoading ? (
43
+ <button
44
+ type="button"
45
+ onClick={stop}
46
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-zinc-700 text-zinc-300 hover:bg-zinc-600 transition-colors"
47
+ >
48
+ <Square className="h-3.5 w-3.5" />
49
+ </button>
50
+ ) : (
51
+ <button
52
+ type="submit"
53
+ disabled={!input.trim()}
54
+ 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"
55
+ >
56
+ <ArrowUp className="h-4 w-4" />
57
+ </button>
58
+ )}
59
+ </div>
60
+ </form>
61
+ );
62
+ }
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import type { UIMessage } from "ai";
4
+ import { SupyagentToolCall, SupyagentToolResult } from "@supyagent/sdk/react";
5
+
6
+ interface ChatMessageProps {
7
+ message: UIMessage;
8
+ }
9
+
10
+ export function ChatMessage({ message }: ChatMessageProps) {
11
+ const isUser = message.role === "user";
12
+
13
+ return (
14
+ <div className={`flex ${isUser ? "justify-end" : "justify-start"}`}>
15
+ <div
16
+ className={`max-w-[85%] space-y-2 ${
17
+ isUser
18
+ ? "rounded-2xl rounded-br-md bg-zinc-800 px-4 py-2.5"
19
+ : ""
20
+ }`}
21
+ >
22
+ {message.parts.map((part, i) => {
23
+ 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
+ );
29
+ }
30
+
31
+ if (part.type === "tool-invocation") {
32
+ return (
33
+ <div key={i} className="space-y-2">
34
+ <SupyagentToolCall part={part as Record<string, unknown>} />
35
+ <SupyagentToolResult part={part as Record<string, unknown>} />
36
+ </div>
37
+ );
38
+ }
39
+
40
+ return null;
41
+ })}
42
+ </div>
43
+ </div>
44
+ );
45
+ }
@@ -0,0 +1,76 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { MessageSquare, Plus, Trash2 } from "lucide-react";
6
+
7
+ interface ChatSummary {
8
+ id: string;
9
+ title: string;
10
+ createdAt: string;
11
+ updatedAt: string;
12
+ }
13
+
14
+ interface ChatSidebarProps {
15
+ currentChatId: string;
16
+ }
17
+
18
+ export function ChatSidebar({ currentChatId }: ChatSidebarProps) {
19
+ const router = useRouter();
20
+ const [chats, setChats] = useState<ChatSummary[]>([]);
21
+
22
+ useEffect(() => {
23
+ fetch("/api/chats")
24
+ .then((res) => res.json())
25
+ .then((data) => setChats(data.chats || []))
26
+ .catch(() => {});
27
+ }, [currentChatId]);
28
+
29
+ const deleteChat = async (id: string) => {
30
+ await fetch(`/api/chats/${id}`, { method: "DELETE" });
31
+ setChats((prev) => prev.filter((c) => c.id !== id));
32
+ if (id === currentChatId) {
33
+ router.push("/chat");
34
+ }
35
+ };
36
+
37
+ 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>
41
+ <button
42
+ onClick={() => router.push("/chat")}
43
+ className="rounded-md p-1.5 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200 transition-colors"
44
+ >
45
+ <Plus className="h-4 w-4" />
46
+ </button>
47
+ </div>
48
+
49
+ <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>
71
+ </div>
72
+ ))}
73
+ </div>
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { useChat } from "@ai-sdk/react";
4
+ import { ChatMessage } from "./chat-message";
5
+ import { ChatInput } from "./chat-input";
6
+ import { ChatSidebar } from "./chat-sidebar";
7
+ import { useRef, useEffect } from "react";
8
+
9
+ interface ChatProps {
10
+ chatId: string;
11
+ initialMessages: Parameters<typeof useChat>[0] extends { initialMessages?: infer M } ? M : never;
12
+ }
13
+
14
+ export function Chat({ chatId, initialMessages }: ChatProps) {
15
+ const { messages, input, handleInputChange, handleSubmit, isLoading, stop } =
16
+ useChat({
17
+ api: "/api/chat",
18
+ body: { chatId },
19
+ initialMessages: initialMessages as Parameters<typeof useChat>[0]["initialMessages"],
20
+ });
21
+
22
+ const scrollRef = useRef<HTMLDivElement>(null);
23
+
24
+ useEffect(() => {
25
+ if (scrollRef.current) {
26
+ scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
27
+ }
28
+ }, [messages]);
29
+
30
+ return (
31
+ <div className="flex h-screen">
32
+ <ChatSidebar currentChatId={chatId} />
33
+
34
+ <div className="flex flex-1 flex-col">
35
+ <div
36
+ ref={scrollRef}
37
+ className="flex-1 overflow-y-auto px-4 py-6"
38
+ >
39
+ <div className="mx-auto max-w-3xl space-y-6">
40
+ {messages.length === 0 && (
41
+ <div className="flex h-full min-h-[60vh] items-center justify-center">
42
+ <div className="text-center">
43
+ <h1 className="text-2xl font-semibold text-zinc-200">
44
+ Supyagent Chat
45
+ </h1>
46
+ <p className="mt-2 text-sm text-zinc-500">
47
+ Ask me anything — I can use your connected integrations.
48
+ </p>
49
+ </div>
50
+ </div>
51
+ )}
52
+ {messages.map((message) => (
53
+ <ChatMessage key={message.id} message={message} />
54
+ ))}
55
+ </div>
56
+ </div>
57
+
58
+ <div className="border-t border-zinc-800 px-4 py-4">
59
+ <div className="mx-auto max-w-3xl">
60
+ <ChatInput
61
+ input={input}
62
+ handleInputChange={handleInputChange}
63
+ handleSubmit={handleSubmit}
64
+ isLoading={isLoading}
65
+ stop={stop}
66
+ />
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,11 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ const globalForPrisma = globalThis as unknown as {
4
+ prisma: PrismaClient | undefined;
5
+ };
6
+
7
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient();
8
+
9
+ if (process.env.NODE_ENV !== "production") {
10
+ globalForPrisma.prisma = prisma;
11
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,15 @@
1
+ import type { Config } from "tailwindcss";
2
+
3
+ const config: Config = {
4
+ content: [
5
+ "./src/**/*.{js,ts,jsx,tsx,mdx}",
6
+ "./node_modules/@supyagent/sdk/dist/**/*.{js,mjs}",
7
+ ],
8
+ darkMode: "class",
9
+ theme: {
10
+ extend: {},
11
+ },
12
+ plugins: [],
13
+ };
14
+
15
+ export default config;
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,8 @@
1
+ # Supyagent — Get your API key at https://app.supyagent.com
2
+ SUPYAGENT_API_KEY=
3
+
4
+ # AI Provider
5
+ {{aiProviderEnvKey}}=
6
+
7
+ # Database
8
+ DATABASE_URL="{{dbUrl}}"
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint",
10
+ "db:generate": "prisma generate",
11
+ "db:push": "prisma db push",
12
+ "db:setup": "prisma generate && prisma db push",
13
+ "db:studio": "prisma studio"
14
+ },
15
+ "dependencies": {
16
+ "@ai-sdk/react": "^1.1.0",
17
+ "{{aiProviderPackage}}": "^1.1.0",
18
+ "@prisma/client": "^6.2.0",
19
+ "@supyagent/sdk": "^0.1.0",
20
+ "ai": "^4.1.0",
21
+ "clsx": "^2.1.0",
22
+ "lucide-react": "^0.468.0",
23
+ "next": "^15.1.0",
24
+ "react": "^19.0.0",
25
+ "react-dom": "^19.0.0",
26
+ "tailwind-merge": "^2.6.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.0.0",
30
+ "@types/react": "^19.0.0",
31
+ "@types/react-dom": "^19.0.0",
32
+ "postcss": "^8.4.0",
33
+ "prisma": "^6.2.0",
34
+ "tailwindcss": "^3.4.0",
35
+ "typescript": "^5.7.0"
36
+ }
37
+ }
@@ -0,0 +1,28 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "{{dbProvider}}"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model Chat {
11
+ id String @id @default(cuid())
12
+ title String @default("New Chat")
13
+ createdAt DateTime @default(now())
14
+ updatedAt DateTime @updatedAt
15
+ messages Message[]
16
+ }
17
+
18
+ model Message {
19
+ id String @id @default(cuid())
20
+ chatId String
21
+ chat Chat @relation(fields: [chatId], references: [id], onDelete: Cascade)
22
+ role String
23
+ parts String
24
+ metadata String?
25
+ createdAt DateTime @default(now())
26
+
27
+ @@index([chatId])
28
+ }