create-supyagent-app 0.1.21 → 0.1.23
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 +60 -3
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/templates/base/src/components/chat-input.tsx +160 -32
- package/templates/base/src/components/chat-message.tsx +28 -0
- package/templates/base/src/components/supyagent/tool-renderers.tsx +6 -0
- package/templates/base/src/components/supyagent/tools/audio.tsx +92 -0
- package/templates/base/src/components/supyagent/tools/image.tsx +67 -0
- package/templates/base/src/components/supyagent/tools/video.tsx +85 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import * as p2 from "@clack/prompts";
|
|
5
5
|
import pc2 from "picocolors";
|
|
6
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
7
|
+
import { resolve as resolve2, dirname as dirname2 } from "path";
|
|
6
8
|
|
|
7
9
|
// src/prompts.ts
|
|
8
10
|
import * as p from "@clack/prompts";
|
|
@@ -376,6 +378,39 @@ var CLI_KEY_MAP = {
|
|
|
376
378
|
openai: "openaiApiKey",
|
|
377
379
|
openrouter: "openrouterApiKey"
|
|
378
380
|
};
|
|
381
|
+
function parseEnvFile(filePath) {
|
|
382
|
+
const result = {};
|
|
383
|
+
if (!existsSync2(filePath)) return result;
|
|
384
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
385
|
+
for (const line of content.split("\n")) {
|
|
386
|
+
const trimmed = line.trim();
|
|
387
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
388
|
+
const eqIdx = trimmed.indexOf("=");
|
|
389
|
+
if (eqIdx < 1) continue;
|
|
390
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
391
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
392
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
393
|
+
val = val.slice(1, -1);
|
|
394
|
+
}
|
|
395
|
+
if (val) result[key] = val;
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
function loadEnvFiles() {
|
|
400
|
+
const merged = {};
|
|
401
|
+
const files = [];
|
|
402
|
+
let dir = process.cwd();
|
|
403
|
+
const root = dirname2(dir) === dir ? dir : "/";
|
|
404
|
+
for (let i = 0; i < 5 && dir !== root; i++) {
|
|
405
|
+
const envPath = resolve2(dir, ".env");
|
|
406
|
+
if (existsSync2(envPath)) files.push(envPath);
|
|
407
|
+
dir = dirname2(dir);
|
|
408
|
+
}
|
|
409
|
+
for (const f of files.reverse()) {
|
|
410
|
+
Object.assign(merged, parseEnvFile(f));
|
|
411
|
+
}
|
|
412
|
+
return merged;
|
|
413
|
+
}
|
|
379
414
|
async function promptForKey(name) {
|
|
380
415
|
const value = await p2.password({ message: `Enter your ${name}` });
|
|
381
416
|
if (p2.isCancel(value)) {
|
|
@@ -387,8 +422,15 @@ async function promptForKey(name) {
|
|
|
387
422
|
async function resolveApiKeys(provider, parsed) {
|
|
388
423
|
const envKey = ENV_KEY_MAP[provider];
|
|
389
424
|
const cliFlag = CLI_KEY_MAP[provider];
|
|
390
|
-
const
|
|
391
|
-
const
|
|
425
|
+
const dotenvVars = loadEnvFiles();
|
|
426
|
+
const supyagent = parsed.supyagentApiKey ?? process.env.SUPYAGENT_API_KEY ?? dotenvVars.SUPYAGENT_API_KEY ?? await promptForKey("SUPYAGENT_API_KEY");
|
|
427
|
+
if (!parsed.supyagentApiKey && (process.env.SUPYAGENT_API_KEY || dotenvVars.SUPYAGENT_API_KEY)) {
|
|
428
|
+
p2.log.info(`Using ${pc2.cyan("SUPYAGENT_API_KEY")} from ${process.env.SUPYAGENT_API_KEY ? "environment" : ".env file"}`);
|
|
429
|
+
}
|
|
430
|
+
const providerKey = parsed[cliFlag] ?? process.env[envKey] ?? dotenvVars[envKey] ?? await promptForKey(envKey);
|
|
431
|
+
if (!parsed[cliFlag] && (process.env[envKey] || dotenvVars[envKey])) {
|
|
432
|
+
p2.log.info(`Using ${pc2.cyan(envKey)} from ${process.env[envKey] ? "environment" : ".env file"}`);
|
|
433
|
+
}
|
|
392
434
|
return { supyagent, provider: providerKey };
|
|
393
435
|
}
|
|
394
436
|
async function main() {
|
|
@@ -447,12 +489,27 @@ async function main() {
|
|
|
447
489
|
}
|
|
448
490
|
aiProvider = selected;
|
|
449
491
|
}
|
|
492
|
+
let agentMode = parsed.agentMode;
|
|
493
|
+
if (!agentMode) {
|
|
494
|
+
const selected = await p2.select({
|
|
495
|
+
message: "Agent mode",
|
|
496
|
+
options: [
|
|
497
|
+
{ value: "skills", label: "Skills (token-efficient)", hint: "recommended" },
|
|
498
|
+
{ value: "tools", label: "Tools (rich tool definitions)" }
|
|
499
|
+
]
|
|
500
|
+
});
|
|
501
|
+
if (p2.isCancel(selected)) {
|
|
502
|
+
p2.cancel("Cancelled.");
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
agentMode = selected;
|
|
506
|
+
}
|
|
450
507
|
const apiKeys = await resolveApiKeys(aiProvider, parsed);
|
|
451
508
|
const config = {
|
|
452
509
|
projectName,
|
|
453
510
|
projectPath,
|
|
454
511
|
aiProvider,
|
|
455
|
-
agentMode
|
|
512
|
+
agentMode,
|
|
456
513
|
database: "sqlite",
|
|
457
514
|
model: parsed.model,
|
|
458
515
|
quickstart: true,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/prompts.ts","../src/utils.ts","../src/scaffold.ts","../src/template.ts","../src/post-install.ts","../src/quickstart.ts"],"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 === \"--mode\" && hasValue) {\n result.agentMode = args[++i] as ProjectConfig[\"agentMode\"];\n } else if (arg === \"--model\" && hasValue) {\n result.model = args[++i];\n } else if (arg === \"--quickstart\") {\n result.quickstart = true;\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (arg === \"--supyagent-api-key\" && hasValue) {\n result.supyagentApiKey = args[++i];\n } else if (arg === \"--anthropic-api-key\" && hasValue) {\n result.anthropicApiKey = args[++i];\n } else if (arg === \"--openai-api-key\" && hasValue) {\n result.openaiApiKey = args[++i];\n } else if (arg === \"--openrouter-api-key\" && hasValue) {\n result.openrouterApiKey = args[++i];\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n ...result,\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n };\n}\n\nconst ENV_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\n\nconst CLI_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], keyof ParsedArgs> = {\n anthropic: \"anthropicApiKey\",\n openai: \"openaiApiKey\",\n openrouter: \"openrouterApiKey\",\n};\n\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 agentMode: parsed.agentMode ?? \"skills\",\n database: \"sqlite\",\n model: parsed.model,\n quickstart: true,\n apiKeys,\n };\n\n const s = p.spinner();\n\n // Scaffold\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n writeEnvLocal(config);\n s.stop(\"Scaffolded project\");\n\n // Install deps\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n process.exit(1);\n }\n\n // Database setup\n s.start(\"Setting up database...\");\n try {\n await runDbSetup(config.projectPath);\n s.stop(\"Database ready\");\n } catch (err) {\n s.stop(\"Database setup failed\");\n p.log.warn(\n `Run ${pc.cyan(`cd ${projectName} && pnpm db:setup`)} manually`,\n );\n }\n\n // Dev server\n p.log.info(pc.green(\"Starting dev server...\"));\n await runDevServer(config.projectPath);\n } else {\n // ── Existing non-quickstart flow ──\n const isNonInteractive =\n parsed.projectName && parsed.aiProvider && parsed.database;\n\n let config: ProjectConfig | null;\n\n if (isNonInteractive) {\n config = {\n projectName: parsed.projectName!,\n projectPath: parsed.projectPath!,\n aiProvider: parsed.aiProvider!,\n agentMode: parsed.agentMode ?? \"skills\",\n database: parsed.database!,\n model: parsed.model,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName, {\n aiProvider: parsed.aiProvider,\n agentMode: parsed.agentMode,\n database: parsed.database,\n });\n if (config && parsed.model) {\n config.model = parsed.model;\n }\n }\n\n if (!config) {\n process.exit(1);\n }\n\n if (isNonInteractive) {\n scaffoldProject(config);\n console.log(\"Scaffolded project\");\n\n if (!parsed.skipInstall) {\n console.log(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n console.log(\"Installed dependencies\");\n } catch {\n console.log(\n \"Failed to install dependencies — run install manually\",\n );\n }\n }\n\n console.log(\n `\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`,\n );\n } else {\n const s = p.spinner();\n\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n s.stop(\"Scaffolded project\");\n\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n }\n\n p.note(\n [\n `cd ${config.projectName}`,\n `cp .env.example .env.local ${pc.dim(\"# Add your API keys\")}`,\n `pnpm db:setup ${pc.dim(\"# Initialize database\")}`,\n `pnpm dev ${pc.dim(\"# Start development server\")}`,\n ].join(\"\\n\"),\n \"Next steps\",\n );\n\n p.outro(pc.green(\"Done!\"));\n }\n }\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport interface PromptOptions {\n aiProvider?: ProjectConfig[\"aiProvider\"];\n database?: ProjectConfig[\"database\"];\n agentMode?: ProjectConfig[\"agentMode\"];\n skipDatabase?: boolean;\n}\n\nexport async function runPrompts(\n argName?: string,\n options?: PromptOptions,\n): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n let aiProvider: ProjectConfig[\"aiProvider\"];\n if (options?.aiProvider) {\n aiProvider = options.aiProvider;\n } else {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n aiProvider = selected;\n }\n\n let agentMode: ProjectConfig[\"agentMode\"];\n if (options?.agentMode) {\n agentMode = options.agentMode;\n } else {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n }) as \"skills\" | \"tools\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n agentMode = selected;\n }\n\n let database: ProjectConfig[\"database\"];\n if (options?.database || options?.skipDatabase) {\n database = options?.database ?? \"sqlite\";\n } else {\n const selected = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n database = selected;\n }\n\n return { projectName, projectPath, aiProvider, agentMode, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ApiKeys {\n supyagent?: string;\n provider?: string;\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n agentMode: \"skills\" | \"tools\";\n model?: string;\n quickstart?: boolean;\n apiKeys?: ApiKeys;\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-6-20250620')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n\nexport function buildModelExpression(\n provider: ProjectConfig[\"aiProvider\"],\n customModelId?: string,\n): string {\n const config = AI_PROVIDERS[provider];\n if (!customModelId) return config.model;\n switch (provider) {\n case \"anthropic\":\n return `anthropic('${customModelId}')`;\n case \"openai\":\n return `openai('${customModelId}')`;\n case \"openrouter\":\n return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;\n }\n}\n","import { mkdirSync, writeFileSync, readFileSync } 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 — use skills or tools template based on agentMode\n const routeTemplate = config.agentMode === \"skills\"\n ? \"api-route/route.skills.ts.tmpl\"\n : \"api-route/route.tools.ts.tmpl\";\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(routeTemplate), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n\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 // UI primitives (shadcn-style)\n writeProject(projectPath, \"src/components/ui/badge.tsx\", readTemplate(\"base/src/components/ui/badge.tsx\"));\n writeProject(projectPath, \"src/components/ui/collapsible.tsx\", readTemplate(\"base/src/components/ui/collapsible.tsx\"));\n\n // AI Elements (pre-bundled Tool component)\n writeProject(projectPath, \"src/components/ai-elements/tool.tsx\", readTemplate(\"base/src/components/ai-elements/tool.tsx\"));\n\n // Supyagent tool rendering (local, editable)\n writeProject(projectPath, \"src/components/supyagent/tool-message.tsx\", readTemplate(\"base/src/components/supyagent/tool-message.tsx\"));\n writeProject(projectPath, \"src/components/supyagent/tool-renderers.tsx\", readTemplate(\"base/src/components/supyagent/tool-renderers.tsx\"));\n\n // Tool renderers — one per integration\n const toolRenderers = [\n \"gmail\", \"calendar\", \"slack\", \"github\", \"drive\", \"search\", \"docs\",\n \"sheets\", \"slides\", \"hubspot\", \"linear\", \"pipedrive\", \"compute\",\n \"resend\", \"inbox\", \"discord\", \"notion\", \"twitter\", \"telegram\",\n \"stripe\", \"jira\", \"salesforce\", \"brevo\", \"calendly\", \"twilio\",\n \"linkedin\", \"bash\", \"generic\",\n ];\n for (const tool of toolRenderers) {\n writeProject(projectPath, `src/components/supyagent/tools/${tool}.tsx`, readTemplate(`base/src/components/supyagent/tools/${tool}.tsx`));\n }\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync, spawn as nodeSpawn } from \"node:child_process\";\nimport { detectPackageManager } from \"nypm\";\nimport { AI_PROVIDERS, type ProjectConfig } from \"./utils.js\";\n\nexport function writeEnvLocal(config: ProjectConfig): void {\n const { projectPath, aiProvider, apiKeys } = config;\n const ai = AI_PROVIDERS[aiProvider];\n\n const lines = [\n \"# Supyagent — Get your API key at https://app.supyagent.com\",\n `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? \"\"}`,\n \"\",\n \"# AI Provider\",\n `${ai.envKey}=${apiKeys?.provider ?? \"\"}`,\n \"\",\n \"# Database\",\n `DATABASE_URL=\"file:./dev.db\"`,\n \"\",\n ];\n\n writeFileSync(join(projectPath, \".env.local\"), lines.join(\"\\n\"), \"utf-8\");\n}\n\nexport async function runDbSetup(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n execSync(`${cmd} run db:setup`, {\n cwd: projectPath,\n stdio: \"inherit\",\n env: { ...process.env, DATABASE_URL: \"file:./dev.db\" },\n });\n}\n\nexport async function runDevServer(projectPath: string): Promise<never> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n\n const child = nodeSpawn(cmd, [\"run\", \"dev\"], {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n\n const forward = (signal: NodeJS.Signals) => {\n child.kill(signal);\n };\n\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n return new Promise((_, reject) => {\n child.on(\"close\", (code) => {\n process.off(\"SIGINT\", forward);\n process.off(\"SIGTERM\", forward);\n process.exit(code ?? 0);\n });\n child.on(\"error\", reject);\n });\n}\n"],"mappings":"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","names":["p","pc","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","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 { readFileSync, existsSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffoldProject } from \"./scaffold.js\";\nimport { installDeps } from \"./post-install.js\";\nimport { writeEnvLocal, runDbSetup, runDevServer } from \"./quickstart.js\";\nimport { AI_PROVIDERS, resolveProjectPath, projectExists } from \"./utils.js\";\nimport type { ProjectConfig, ApiKeys } from \"./utils.js\";\n\ninterface ParsedArgs extends Partial<ProjectConfig> {\n skipInstall?: boolean;\n supyagentApiKey?: string;\n anthropicApiKey?: string;\n openaiApiKey?: string;\n openrouterApiKey?: string;\n}\n\nfunction parseArgs(): ParsedArgs {\n const args = process.argv.slice(2);\n const result: ParsedArgs = {};\n let projectName: string | undefined;\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n const next = args[i + 1];\n const hasValue = next && !next.startsWith(\"-\");\n\n if (arg === \"--provider\" && hasValue) {\n result.aiProvider = args[++i] as ProjectConfig[\"aiProvider\"];\n } else if (arg === \"--db\" && hasValue) {\n result.database = args[++i] as ProjectConfig[\"database\"];\n } else if (arg === \"--mode\" && hasValue) {\n result.agentMode = args[++i] as ProjectConfig[\"agentMode\"];\n } else if (arg === \"--model\" && hasValue) {\n result.model = args[++i];\n } else if (arg === \"--quickstart\") {\n result.quickstart = true;\n } else if (arg === \"--skip-install\") {\n result.skipInstall = true;\n } else if (arg === \"--supyagent-api-key\" && hasValue) {\n result.supyagentApiKey = args[++i];\n } else if (arg === \"--anthropic-api-key\" && hasValue) {\n result.anthropicApiKey = args[++i];\n } else if (arg === \"--openai-api-key\" && hasValue) {\n result.openaiApiKey = args[++i];\n } else if (arg === \"--openrouter-api-key\" && hasValue) {\n result.openrouterApiKey = args[++i];\n } else if (!arg.startsWith(\"-\")) {\n projectName = arg;\n }\n }\n\n return {\n ...result,\n projectName,\n projectPath: projectName ? resolveProjectPath(projectName) : undefined,\n };\n}\n\nconst ENV_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], string> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n openrouter: \"OPENROUTER_API_KEY\",\n};\n\nconst CLI_KEY_MAP: Record<ProjectConfig[\"aiProvider\"], keyof ParsedArgs> = {\n anthropic: \"anthropicApiKey\",\n openai: \"openaiApiKey\",\n openrouter: \"openrouterApiKey\",\n};\n\n/**\n * Parse a `.env` file into a key-value map. Handles simple KEY=VALUE lines,\n * quoted values, and ignores comments/blank lines.\n */\nfunction parseEnvFile(filePath: string): Record<string, string> {\n const result: Record<string, string> = {};\n if (!existsSync(filePath)) return result;\n const content = readFileSync(filePath, \"utf-8\");\n for (const line of content.split(\"\\n\")) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n const eqIdx = trimmed.indexOf(\"=\");\n if (eqIdx < 1) continue;\n const key = trimmed.slice(0, eqIdx).trim();\n let val = trimmed.slice(eqIdx + 1).trim();\n // Strip surrounding quotes\n if ((val.startsWith('\"') && val.endsWith('\"')) || (val.startsWith(\"'\") && val.endsWith(\"'\"))) {\n val = val.slice(1, -1);\n }\n if (val) result[key] = val;\n }\n return result;\n}\n\n/**\n * Walk up from cwd looking for .env files and merge them (closest wins).\n */\nfunction loadEnvFiles(): Record<string, string> {\n const merged: Record<string, string> = {};\n const files: string[] = [];\n let dir = process.cwd();\n const root = dirname(dir) === dir ? dir : \"/\";\n // Collect .env files walking up (stop after 5 levels or root)\n for (let i = 0; i < 5 && dir !== root; i++) {\n const envPath = resolve(dir, \".env\");\n if (existsSync(envPath)) files.push(envPath);\n dir = dirname(dir);\n }\n // Merge in reverse order so closest .env wins\n for (const f of files.reverse()) {\n Object.assign(merged, parseEnvFile(f));\n }\n return merged;\n}\n\nasync function promptForKey(name: string): Promise<string> {\n const value = await p.password({ message: `Enter your ${name}` });\n if (p.isCancel(value)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n return value;\n}\n\nasync function resolveApiKeys(\n provider: ProjectConfig[\"aiProvider\"],\n parsed: ParsedArgs,\n): Promise<ApiKeys> {\n const envKey = ENV_KEY_MAP[provider];\n const cliFlag = CLI_KEY_MAP[provider];\n const dotenvVars = loadEnvFiles();\n\n const supyagent =\n parsed.supyagentApiKey ??\n process.env.SUPYAGENT_API_KEY ??\n dotenvVars.SUPYAGENT_API_KEY ??\n (await promptForKey(\"SUPYAGENT_API_KEY\"));\n\n if (!parsed.supyagentApiKey && (process.env.SUPYAGENT_API_KEY || dotenvVars.SUPYAGENT_API_KEY)) {\n p.log.info(`Using ${pc.cyan(\"SUPYAGENT_API_KEY\")} from ${process.env.SUPYAGENT_API_KEY ? \"environment\" : \".env file\"}`);\n }\n\n const providerKey =\n (parsed[cliFlag] as string | undefined) ??\n process.env[envKey] ??\n dotenvVars[envKey] ??\n (await promptForKey(envKey));\n\n if (!(parsed[cliFlag] as string | undefined) && (process.env[envKey] || dotenvVars[envKey])) {\n p.log.info(`Using ${pc.cyan(envKey)} from ${process.env[envKey] ? \"environment\" : \".env file\"}`);\n }\n\n return { supyagent, provider: providerKey };\n}\n\nasync function main() {\n const parsed = parseArgs();\n\n if (parsed.quickstart) {\n // ── Quickstart mode ──\n if (parsed.database === \"postgres\") {\n p.log.warn(\n pc.yellow(\"--quickstart requires SQLite — ignoring --db postgres\"),\n );\n }\n if (parsed.skipInstall) {\n p.log.warn(\n pc.yellow(\n \"--quickstart needs dependencies installed — ignoring --skip-install\",\n ),\n );\n }\n\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App — Quickstart \")));\n\n // Resolve project name\n let projectName = parsed.projectName;\n if (!projectName) {\n const name = await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n });\n if (p.isCancel(name)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n projectName = name as string;\n }\n\n const projectPath = resolveProjectPath(projectName);\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n process.exit(1);\n }\n\n // Resolve provider\n let aiProvider = parsed.aiProvider;\n if (!aiProvider) {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n });\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n aiProvider = selected as ProjectConfig[\"aiProvider\"];\n }\n\n // Resolve agent mode\n let agentMode = parsed.agentMode;\n if (!agentMode) {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n });\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n process.exit(1);\n }\n agentMode = selected as ProjectConfig[\"agentMode\"];\n }\n\n // Resolve API keys\n const apiKeys = await resolveApiKeys(aiProvider, parsed);\n\n const config: ProjectConfig = {\n projectName,\n projectPath,\n aiProvider,\n agentMode,\n database: \"sqlite\",\n model: parsed.model,\n quickstart: true,\n apiKeys,\n };\n\n const s = p.spinner();\n\n // Scaffold\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n writeEnvLocal(config);\n s.stop(\"Scaffolded project\");\n\n // Install deps\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n process.exit(1);\n }\n\n // Database setup\n s.start(\"Setting up database...\");\n try {\n await runDbSetup(config.projectPath);\n s.stop(\"Database ready\");\n } catch (err) {\n s.stop(\"Database setup failed\");\n p.log.warn(\n `Run ${pc.cyan(`cd ${projectName} && pnpm db:setup`)} manually`,\n );\n }\n\n // Dev server\n p.log.info(pc.green(\"Starting dev server...\"));\n await runDevServer(config.projectPath);\n } else {\n // ── Existing non-quickstart flow ──\n const isNonInteractive =\n parsed.projectName && parsed.aiProvider && parsed.database;\n\n let config: ProjectConfig | null;\n\n if (isNonInteractive) {\n config = {\n projectName: parsed.projectName!,\n projectPath: parsed.projectPath!,\n aiProvider: parsed.aiProvider!,\n agentMode: parsed.agentMode ?? \"skills\",\n database: parsed.database!,\n model: parsed.model,\n };\n console.log(`Creating ${config.projectName}...`);\n } else {\n config = await runPrompts(parsed.projectName, {\n aiProvider: parsed.aiProvider,\n agentMode: parsed.agentMode,\n database: parsed.database,\n });\n if (config && parsed.model) {\n config.model = parsed.model;\n }\n }\n\n if (!config) {\n process.exit(1);\n }\n\n if (isNonInteractive) {\n scaffoldProject(config);\n console.log(\"Scaffolded project\");\n\n if (!parsed.skipInstall) {\n console.log(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n console.log(\"Installed dependencies\");\n } catch {\n console.log(\n \"Failed to install dependencies — run install manually\",\n );\n }\n }\n\n console.log(\n `\\nNext steps:\\n cd ${config.projectName}\\n cp .env.example .env.local\\n pnpm db:setup\\n pnpm dev`,\n );\n } else {\n const s = p.spinner();\n\n s.start(\"Scaffolding project...\");\n scaffoldProject(config);\n s.stop(\"Scaffolded project\");\n\n s.start(\"Installing dependencies...\");\n try {\n await installDeps(config.projectPath);\n s.stop(\"Installed dependencies\");\n } catch {\n s.stop(\"Failed to install dependencies — run install manually\");\n }\n\n p.note(\n [\n `cd ${config.projectName}`,\n `cp .env.example .env.local ${pc.dim(\"# Add your API keys\")}`,\n `pnpm db:setup ${pc.dim(\"# Initialize database\")}`,\n `pnpm dev ${pc.dim(\"# Start development server\")}`,\n ].join(\"\\n\"),\n \"Next steps\",\n );\n\n p.outro(pc.green(\"Done!\"));\n }\n }\n}\n\nmain().catch(console.error);\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport type { ProjectConfig } from \"./utils.js\";\nimport { AI_PROVIDERS, DB_CONFIGS, resolveProjectPath, projectExists } from \"./utils.js\";\n\nexport interface PromptOptions {\n aiProvider?: ProjectConfig[\"aiProvider\"];\n database?: ProjectConfig[\"database\"];\n agentMode?: ProjectConfig[\"agentMode\"];\n skipDatabase?: boolean;\n}\n\nexport async function runPrompts(\n argName?: string,\n options?: PromptOptions,\n): Promise<ProjectConfig | null> {\n p.intro(pc.bgCyan(pc.black(\" Create Supyagent App \")));\n\n const projectName = argName || await p.text({\n message: \"Project name\",\n placeholder: \"my-supyagent-app\",\n defaultValue: \"my-supyagent-app\",\n validate(value) {\n if (!value) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(value)) {\n return \"Invalid project name (lowercase, alphanumeric, hyphens, dots)\";\n }\n },\n }) as string;\n\n if (p.isCancel(projectName)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n\n const projectPath = resolveProjectPath(projectName);\n\n if (projectExists(projectPath)) {\n p.cancel(`Directory \"${projectName}\" already exists.`);\n return null;\n }\n\n let aiProvider: ProjectConfig[\"aiProvider\"];\n if (options?.aiProvider) {\n aiProvider = options.aiProvider;\n } else {\n const selected = await p.select({\n message: \"AI provider\",\n options: [\n { value: \"anthropic\", label: AI_PROVIDERS.anthropic.label },\n { value: \"openai\", label: AI_PROVIDERS.openai.label },\n { value: \"openrouter\", label: AI_PROVIDERS.openrouter.label },\n ],\n }) as \"anthropic\" | \"openai\" | \"openrouter\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n aiProvider = selected;\n }\n\n let agentMode: ProjectConfig[\"agentMode\"];\n if (options?.agentMode) {\n agentMode = options.agentMode;\n } else {\n const selected = await p.select({\n message: \"Agent mode\",\n options: [\n { value: \"skills\", label: \"Skills (token-efficient)\", hint: \"recommended\" },\n { value: \"tools\", label: \"Tools (rich tool definitions)\" },\n ],\n }) as \"skills\" | \"tools\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n agentMode = selected;\n }\n\n let database: ProjectConfig[\"database\"];\n if (options?.database || options?.skipDatabase) {\n database = options?.database ?? \"sqlite\";\n } else {\n const selected = await p.select({\n message: \"Database\",\n options: [\n { value: \"sqlite\", label: DB_CONFIGS.sqlite.label },\n { value: \"postgres\", label: DB_CONFIGS.postgres.label },\n ],\n }) as \"sqlite\" | \"postgres\";\n\n if (p.isCancel(selected)) {\n p.cancel(\"Cancelled.\");\n return null;\n }\n database = selected;\n }\n\n return { projectName, projectPath, aiProvider, agentMode, database };\n}\n","import { existsSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\n\nexport function resolveProjectPath(name: string): string {\n return resolve(process.cwd(), name);\n}\n\nexport function projectExists(path: string): boolean {\n return existsSync(path);\n}\n\nexport interface ApiKeys {\n supyagent?: string;\n provider?: string;\n}\n\nexport interface ProjectConfig {\n projectName: string;\n projectPath: string;\n aiProvider: \"anthropic\" | \"openai\" | \"openrouter\";\n database: \"sqlite\" | \"postgres\";\n agentMode: \"skills\" | \"tools\";\n model?: string;\n quickstart?: boolean;\n apiKeys?: ApiKeys;\n}\n\nexport const AI_PROVIDERS = {\n anthropic: {\n label: \"Anthropic (Claude)\",\n package: \"@ai-sdk/anthropic\",\n packageVersion: \"^3.0.0\",\n import: `import { anthropic } from '@ai-sdk/anthropic'`,\n model: `anthropic('claude-sonnet-4-6-20250620')`,\n envKey: \"ANTHROPIC_API_KEY\",\n },\n openai: {\n label: \"OpenAI (GPT)\",\n package: \"@ai-sdk/openai\",\n packageVersion: \"^3.0.0\",\n import: `import { openai } from '@ai-sdk/openai'`,\n model: `openai('gpt-4o')`,\n envKey: \"OPENAI_API_KEY\",\n },\n openrouter: {\n label: \"OpenRouter (any model)\",\n package: \"@openrouter/ai-sdk-provider\",\n packageVersion: \"^2.2.3\",\n import: `import { createOpenRouter } from '@openrouter/ai-sdk-provider'`,\n model: `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('anthropic/claude-sonnet-4.6')`,\n envKey: \"OPENROUTER_API_KEY\",\n },\n} as const;\n\nexport const DB_CONFIGS = {\n sqlite: {\n label: \"SQLite (local dev)\",\n provider: \"sqlite\",\n url: \"file:./dev.db\",\n },\n postgres: {\n label: \"PostgreSQL (production)\",\n provider: \"postgresql\",\n url: \"postgresql://user:password@localhost:5432/mydb\",\n },\n} as const;\n\nexport function buildModelExpression(\n provider: ProjectConfig[\"aiProvider\"],\n customModelId?: string,\n): string {\n const config = AI_PROVIDERS[provider];\n if (!customModelId) return config.model;\n switch (provider) {\n case \"anthropic\":\n return `anthropic('${customModelId}')`;\n case \"openai\":\n return `openai('${customModelId}')`;\n case \"openrouter\":\n return `createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY })('${customModelId}')`;\n }\n}\n","import { mkdirSync, writeFileSync, readFileSync } 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 — use skills or tools template based on agentMode\n const routeTemplate = config.agentMode === \"skills\"\n ? \"api-route/route.skills.ts.tmpl\"\n : \"api-route/route.tools.ts.tmpl\";\n writeProject(\n projectPath,\n \"src/app/api/chat/route.ts\",\n applyTemplate(readTemplate(routeTemplate), vars)\n );\n writeProject(projectPath, \"src/app/api/chats/route.ts\", readTemplate(\"base/src/app/api/chats/route.ts\"));\n writeProject(projectPath, \"src/app/api/chats/[id]/route.ts\", readTemplate(\"base/src/app/api/chats/[id]/route.ts\"));\n\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 // UI primitives (shadcn-style)\n writeProject(projectPath, \"src/components/ui/badge.tsx\", readTemplate(\"base/src/components/ui/badge.tsx\"));\n writeProject(projectPath, \"src/components/ui/collapsible.tsx\", readTemplate(\"base/src/components/ui/collapsible.tsx\"));\n\n // AI Elements (pre-bundled Tool component)\n writeProject(projectPath, \"src/components/ai-elements/tool.tsx\", readTemplate(\"base/src/components/ai-elements/tool.tsx\"));\n\n // Supyagent tool rendering (local, editable)\n writeProject(projectPath, \"src/components/supyagent/tool-message.tsx\", readTemplate(\"base/src/components/supyagent/tool-message.tsx\"));\n writeProject(projectPath, \"src/components/supyagent/tool-renderers.tsx\", readTemplate(\"base/src/components/supyagent/tool-renderers.tsx\"));\n\n // Tool renderers — one per integration\n const toolRenderers = [\n \"gmail\", \"calendar\", \"slack\", \"github\", \"drive\", \"search\", \"docs\",\n \"sheets\", \"slides\", \"hubspot\", \"linear\", \"pipedrive\", \"compute\",\n \"resend\", \"inbox\", \"discord\", \"notion\", \"twitter\", \"telegram\",\n \"stripe\", \"jira\", \"salesforce\", \"brevo\", \"calendly\", \"twilio\",\n \"linkedin\", \"bash\", \"generic\",\n ];\n for (const tool of toolRenderers) {\n writeProject(projectPath, `src/components/supyagent/tools/${tool}.tsx`, readTemplate(`base/src/components/supyagent/tools/${tool}.tsx`));\n }\n\n // Lib\n writeProject(projectPath, \"src/lib/utils.ts\", readTemplate(\"base/src/lib/utils.ts\"));\n writeProject(projectPath, \"src/lib/prisma.ts\", readTemplate(\"base/src/lib/prisma.ts\"));\n\n // ── Prisma schema ──\n writeProject(\n projectPath,\n \"prisma/schema.prisma\",\n applyTemplate(readTemplate(\"prisma/schema.prisma.tmpl\"), vars)\n );\n\n // ── Env example ──\n writeProject(\n projectPath,\n \".env.example\",\n applyTemplate(readTemplate(\"env/.env.example.tmpl\"), vars)\n );\n\n // ── package.json ──\n writeProject(\n projectPath,\n \"package.json\",\n applyTemplate(readTemplate(\"package-json/package.json.tmpl\"), vars)\n );\n}\n","/**\n * Replace {{variable}} placeholders in template content.\n */\nexport function applyTemplate(\n content: string,\n variables: Record<string, string>\n): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key) => {\n return key in variables ? variables[key] : match;\n });\n}\n","import { detectPackageManager, installDependencies } from \"nypm\";\n\nexport async function installDeps(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n await installDependencies({\n cwd: projectPath,\n packageManager: pm?.name,\n });\n}\n","import { writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { execSync, spawn as nodeSpawn } from \"node:child_process\";\nimport { detectPackageManager } from \"nypm\";\nimport { AI_PROVIDERS, type ProjectConfig } from \"./utils.js\";\n\nexport function writeEnvLocal(config: ProjectConfig): void {\n const { projectPath, aiProvider, apiKeys } = config;\n const ai = AI_PROVIDERS[aiProvider];\n\n const lines = [\n \"# Supyagent — Get your API key at https://app.supyagent.com\",\n `SUPYAGENT_API_KEY=${apiKeys?.supyagent ?? \"\"}`,\n \"\",\n \"# AI Provider\",\n `${ai.envKey}=${apiKeys?.provider ?? \"\"}`,\n \"\",\n \"# Database\",\n `DATABASE_URL=\"file:./dev.db\"`,\n \"\",\n ];\n\n writeFileSync(join(projectPath, \".env.local\"), lines.join(\"\\n\"), \"utf-8\");\n}\n\nexport async function runDbSetup(projectPath: string): Promise<void> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n execSync(`${cmd} run db:setup`, {\n cwd: projectPath,\n stdio: \"inherit\",\n env: { ...process.env, DATABASE_URL: \"file:./dev.db\" },\n });\n}\n\nexport async function runDevServer(projectPath: string): Promise<never> {\n const pm = await detectPackageManager(projectPath);\n const cmd = pm?.name ?? \"pnpm\";\n\n const child = nodeSpawn(cmd, [\"run\", \"dev\"], {\n cwd: projectPath,\n stdio: \"inherit\",\n });\n\n const forward = (signal: NodeJS.Signals) => {\n child.kill(signal);\n };\n\n process.on(\"SIGINT\", forward);\n process.on(\"SIGTERM\", forward);\n\n return new Promise((_, reject) => {\n child.on(\"close\", (code) => {\n process.off(\"SIGINT\", forward);\n process.off(\"SIGTERM\", forward);\n process.exit(code ?? 0);\n });\n child.on(\"error\", reject);\n });\n}\n"],"mappings":"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","names":["p","pc","readFileSync","existsSync","resolve","dirname","writeFileSync","join","detectPackageManager","writeFileSync","join","detectPackageManager","existsSync","readFileSync","dirname","resolve","pc"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-supyagent-app",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Create a supyagent-powered chatbot app",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"dist",
|
|
11
11
|
"templates"
|
|
12
12
|
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsup",
|
|
15
|
-
"clean": "rm -rf dist"
|
|
16
|
-
},
|
|
17
13
|
"dependencies": {
|
|
18
14
|
"@clack/prompts": "^0.8.0",
|
|
19
15
|
"nypm": "^0.4.0",
|
|
@@ -23,5 +19,9 @@
|
|
|
23
19
|
"tsup": "^8.3.0",
|
|
24
20
|
"typescript": "^5.7.0"
|
|
25
21
|
},
|
|
26
|
-
"license": "MIT"
|
|
27
|
-
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"clean": "rm -rf dist"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,18 +1,33 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { ArrowUp, Square } from "lucide-react";
|
|
3
|
+
import { ArrowUp, Square, Paperclip, X } from "lucide-react";
|
|
4
4
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
5
|
-
import type { FormEvent, KeyboardEvent } from "react";
|
|
5
|
+
import type { FormEvent, KeyboardEvent, DragEvent, ClipboardEvent } from "react";
|
|
6
6
|
|
|
7
7
|
interface ChatInputProps {
|
|
8
|
-
sendMessage: (message: {
|
|
8
|
+
sendMessage: (message: {
|
|
9
|
+
text: string;
|
|
10
|
+
files?: Array<{ type: "file"; url: string; mediaType: string; filename?: string }>;
|
|
11
|
+
}) => Promise<void>;
|
|
9
12
|
isLoading: boolean;
|
|
10
13
|
stop: () => void;
|
|
11
14
|
}
|
|
12
15
|
|
|
16
|
+
function readFileAsDataURL(file: File): Promise<string> {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const reader = new FileReader();
|
|
19
|
+
reader.onload = () => resolve(reader.result as string);
|
|
20
|
+
reader.onerror = reject;
|
|
21
|
+
reader.readAsDataURL(file);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
|
|
14
26
|
const [input, setInput] = useState("");
|
|
27
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
28
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
15
29
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
30
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
16
31
|
|
|
17
32
|
const adjustHeight = useCallback(() => {
|
|
18
33
|
const textarea = textareaRef.current;
|
|
@@ -25,12 +40,43 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
|
|
|
25
40
|
adjustHeight();
|
|
26
41
|
}, [input, adjustHeight]);
|
|
27
42
|
|
|
28
|
-
const
|
|
43
|
+
const addFiles = useCallback((newFiles: FileList | File[]) => {
|
|
44
|
+
const imageFiles = Array.from(newFiles).filter((f) =>
|
|
45
|
+
f.type.startsWith("image/")
|
|
46
|
+
);
|
|
47
|
+
if (imageFiles.length > 0) {
|
|
48
|
+
setFiles((prev) => [...prev, ...imageFiles]);
|
|
49
|
+
}
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const removeFile = useCallback((index: number) => {
|
|
53
|
+
setFiles((prev) => prev.filter((_, i) => i !== index));
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const handleSubmit = async (e: FormEvent) => {
|
|
29
57
|
e.preventDefault();
|
|
30
|
-
if (!input.trim() || isLoading) return;
|
|
31
|
-
|
|
58
|
+
if ((!input.trim() && files.length === 0) || isLoading) return;
|
|
59
|
+
|
|
60
|
+
let fileUIParts:
|
|
61
|
+
| Array<{ type: "file"; url: string; mediaType: string; filename?: string }>
|
|
62
|
+
| undefined;
|
|
63
|
+
if (files.length > 0) {
|
|
64
|
+
fileUIParts = await Promise.all(
|
|
65
|
+
files.map(async (f) => ({
|
|
66
|
+
type: "file" as const,
|
|
67
|
+
url: await readFileAsDataURL(f),
|
|
68
|
+
mediaType: f.type,
|
|
69
|
+
filename: f.name,
|
|
70
|
+
}))
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
sendMessage({
|
|
75
|
+
text: input,
|
|
76
|
+
...(fileUIParts ? { files: fileUIParts } : {}),
|
|
77
|
+
});
|
|
32
78
|
setInput("");
|
|
33
|
-
|
|
79
|
+
setFiles([]);
|
|
34
80
|
if (textareaRef.current) {
|
|
35
81
|
textareaRef.current.style.height = "auto";
|
|
36
82
|
}
|
|
@@ -39,44 +85,126 @@ export function ChatInput({ sendMessage, isLoading, stop }: ChatInputProps) {
|
|
|
39
85
|
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
40
86
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
41
87
|
e.preventDefault();
|
|
42
|
-
if (input.trim() && !isLoading) {
|
|
88
|
+
if ((input.trim() || files.length > 0) && !isLoading) {
|
|
43
89
|
handleSubmit(e as unknown as FormEvent);
|
|
44
90
|
}
|
|
45
91
|
}
|
|
46
92
|
};
|
|
47
93
|
|
|
94
|
+
const handlePaste = (e: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
95
|
+
const items = e.clipboardData?.files;
|
|
96
|
+
if (items && items.length > 0) {
|
|
97
|
+
addFiles(items);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleDragOver = (e: DragEvent<HTMLFormElement>) => {
|
|
102
|
+
e.preventDefault();
|
|
103
|
+
setIsDragging(true);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleDragLeave = (e: DragEvent<HTMLFormElement>) => {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
setIsDragging(false);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleDrop = (e: DragEvent<HTMLFormElement>) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
setIsDragging(false);
|
|
114
|
+
if (e.dataTransfer.files.length > 0) {
|
|
115
|
+
addFiles(e.dataTransfer.files);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const hasContent = input.trim() || files.length > 0;
|
|
120
|
+
|
|
48
121
|
return (
|
|
49
122
|
<form
|
|
50
123
|
onSubmit={handleSubmit}
|
|
51
|
-
|
|
124
|
+
onDragOver={handleDragOver}
|
|
125
|
+
onDragLeave={handleDragLeave}
|
|
126
|
+
onDrop={handleDrop}
|
|
127
|
+
className={`relative flex flex-col rounded-xl border bg-card transition-colors ${
|
|
128
|
+
isDragging
|
|
129
|
+
? "border-ring ring-1 ring-ring"
|
|
130
|
+
: "border-border focus-within:border-ring focus-within:ring-1 focus-within:ring-ring"
|
|
131
|
+
}`}
|
|
52
132
|
>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
133
|
+
{/* File previews */}
|
|
134
|
+
{files.length > 0 && (
|
|
135
|
+
<div className="flex gap-2 px-3 pt-3 pb-0 flex-wrap">
|
|
136
|
+
{files.map((file, i) => (
|
|
137
|
+
<div key={`${file.name}-${i}`} className="relative group">
|
|
138
|
+
<img
|
|
139
|
+
src={URL.createObjectURL(file)}
|
|
140
|
+
alt={file.name}
|
|
141
|
+
className="h-16 w-16 rounded-lg object-cover border border-border"
|
|
142
|
+
/>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
onClick={() => removeFile(i)}
|
|
146
|
+
className="absolute -top-1.5 -right-1.5 h-5 w-5 rounded-full bg-muted border border-border flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-accent"
|
|
147
|
+
>
|
|
148
|
+
<X className="h-3 w-3 text-muted-foreground" />
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
151
|
+
))}
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<div className="flex items-end">
|
|
156
|
+
{/* Paperclip button */}
|
|
157
|
+
<div className="p-2">
|
|
64
158
|
<button
|
|
65
159
|
type="button"
|
|
66
|
-
onClick={
|
|
67
|
-
className="flex h-8 w-8 items-center justify-center rounded-lg
|
|
160
|
+
onClick={() => fileInputRef.current?.click()}
|
|
161
|
+
className="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
|
|
162
|
+
title="Attach image"
|
|
68
163
|
>
|
|
69
|
-
<
|
|
164
|
+
<Paperclip className="h-4 w-4" />
|
|
70
165
|
</button>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
type="
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
166
|
+
<input
|
|
167
|
+
ref={fileInputRef}
|
|
168
|
+
type="file"
|
|
169
|
+
accept="image/*"
|
|
170
|
+
multiple
|
|
171
|
+
className="hidden"
|
|
172
|
+
onChange={(e) => {
|
|
173
|
+
if (e.target.files) addFiles(e.target.files);
|
|
174
|
+
e.target.value = "";
|
|
175
|
+
}}
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
|
|
179
|
+
<textarea
|
|
180
|
+
ref={textareaRef}
|
|
181
|
+
value={input}
|
|
182
|
+
onChange={(e) => setInput(e.target.value)}
|
|
183
|
+
onKeyDown={handleKeyDown}
|
|
184
|
+
onPaste={handlePaste}
|
|
185
|
+
placeholder="Send a message..."
|
|
186
|
+
rows={1}
|
|
187
|
+
className="flex-1 resize-none bg-transparent py-3 text-sm text-foreground placeholder-muted-foreground outline-none max-h-[200px]"
|
|
188
|
+
/>
|
|
189
|
+
<div className="p-2">
|
|
190
|
+
{isLoading ? (
|
|
191
|
+
<button
|
|
192
|
+
type="button"
|
|
193
|
+
onClick={stop}
|
|
194
|
+
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"
|
|
195
|
+
>
|
|
196
|
+
<Square className="h-3.5 w-3.5" />
|
|
197
|
+
</button>
|
|
198
|
+
) : (
|
|
199
|
+
<button
|
|
200
|
+
type="submit"
|
|
201
|
+
disabled={!hasContent}
|
|
202
|
+
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"
|
|
203
|
+
>
|
|
204
|
+
<ArrowUp className="h-4 w-4" />
|
|
205
|
+
</button>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
80
208
|
</div>
|
|
81
209
|
</form>
|
|
82
210
|
);
|
|
@@ -72,6 +72,34 @@ export function ChatMessage({ message, addToolApprovalResponse }: ChatMessagePro
|
|
|
72
72
|
return <MarkdownContent key={i} text={(part as any).text} />;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
if (part.type === "file") {
|
|
76
|
+
const filePart = part as { type: "file"; mediaType?: string; url: string; filename?: string };
|
|
77
|
+
if (filePart.mediaType?.startsWith("image/")) {
|
|
78
|
+
return (
|
|
79
|
+
<img
|
|
80
|
+
key={i}
|
|
81
|
+
src={filePart.url}
|
|
82
|
+
alt={filePart.filename || "Image"}
|
|
83
|
+
className="rounded-lg max-w-full max-h-96 object-contain"
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
if (filePart.mediaType?.startsWith("audio/")) {
|
|
88
|
+
return <audio key={i} controls src={filePart.url} className="w-full max-w-md" />;
|
|
89
|
+
}
|
|
90
|
+
return (
|
|
91
|
+
<a
|
|
92
|
+
key={i}
|
|
93
|
+
href={filePart.url}
|
|
94
|
+
target="_blank"
|
|
95
|
+
rel="noopener noreferrer"
|
|
96
|
+
className="text-xs text-primary hover:underline"
|
|
97
|
+
>
|
|
98
|
+
{filePart.filename || "Download file"}
|
|
99
|
+
</a>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
if (isToolUIPart(part)) {
|
|
76
104
|
return <ToolMessage key={i} part={part} addToolApprovalResponse={addToolApprovalResponse} />;
|
|
77
105
|
}
|
|
@@ -26,6 +26,9 @@ import { CalendlyRenderer } from "./tools/calendly";
|
|
|
26
26
|
import { TwilioRenderer } from "./tools/twilio";
|
|
27
27
|
import { LinkedInRenderer } from "./tools/linkedin";
|
|
28
28
|
import { BashRenderer } from "./tools/bash";
|
|
29
|
+
import { ImageRenderer } from "./tools/image";
|
|
30
|
+
import { AudioRenderer } from "./tools/audio";
|
|
31
|
+
import { VideoRenderer } from "./tools/video";
|
|
29
32
|
import { GenericRenderer } from "./tools/generic";
|
|
30
33
|
|
|
31
34
|
export interface ToolRendererProps {
|
|
@@ -64,6 +67,9 @@ const renderers: Record<string, ComponentType<ToolRendererProps>> = {
|
|
|
64
67
|
twilio: TwilioRenderer,
|
|
65
68
|
linkedin: LinkedInRenderer,
|
|
66
69
|
bash: BashRenderer,
|
|
70
|
+
image: ImageRenderer,
|
|
71
|
+
audio: AudioRenderer,
|
|
72
|
+
video: VideoRenderer,
|
|
67
73
|
};
|
|
68
74
|
|
|
69
75
|
export function getToolRenderer(formatterType: string): ComponentType<ToolRendererProps> {
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ExternalLink, Loader2 } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface AudioRendererProps {
|
|
5
|
+
data: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function AudioRenderer({ data }: AudioRendererProps) {
|
|
9
|
+
if (typeof data !== "object" || data === null) {
|
|
10
|
+
return (
|
|
11
|
+
<p className="text-sm text-muted-foreground italic">No data returned</p>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const d = data as Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
if (d.error) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-3">
|
|
20
|
+
<p className="text-sm text-destructive">{String(d.error)}</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (d.status === "processing" || (d.poll_url && !d.audio_url)) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-card p-4">
|
|
28
|
+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
29
|
+
<span className="text-sm text-muted-foreground">Processing audio...</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// TTS result — audio player
|
|
35
|
+
if (typeof d.audio_url === "string") {
|
|
36
|
+
return (
|
|
37
|
+
<div className="space-y-2">
|
|
38
|
+
<audio controls className="w-full max-w-md">
|
|
39
|
+
<source src={d.audio_url} />
|
|
40
|
+
</audio>
|
|
41
|
+
<a
|
|
42
|
+
href={d.audio_url}
|
|
43
|
+
target="_blank"
|
|
44
|
+
rel="noopener noreferrer"
|
|
45
|
+
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
|
46
|
+
>
|
|
47
|
+
<ExternalLink className="h-3 w-3" />
|
|
48
|
+
Download audio
|
|
49
|
+
</a>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// STT result — transcription text
|
|
55
|
+
if (d.result && typeof d.result === "object") {
|
|
56
|
+
const result = d.result as Record<string, unknown>;
|
|
57
|
+
if (typeof result.text === "string") {
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<div className="rounded-lg border border-border bg-card p-3">
|
|
61
|
+
<p className="text-sm text-foreground whitespace-pre-wrap">{result.text}</p>
|
|
62
|
+
</div>
|
|
63
|
+
{result.tokens_used != null && (
|
|
64
|
+
<p className="text-xs text-muted-foreground">
|
|
65
|
+
Tokens used: {String(result.tokens_used)}
|
|
66
|
+
</p>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// OCR-like text result at top level
|
|
74
|
+
if (typeof d.text === "string") {
|
|
75
|
+
return (
|
|
76
|
+
<div className="rounded-lg border border-border bg-card p-3">
|
|
77
|
+
<p className="text-sm text-foreground whitespace-pre-wrap">{d.text}</p>
|
|
78
|
+
{d.tokens_used != null && (
|
|
79
|
+
<p className="text-xs text-muted-foreground mt-2">
|
|
80
|
+
Tokens used: {String(d.tokens_used)}
|
|
81
|
+
</p>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<pre className="rounded-lg border border-border bg-background p-3 text-xs text-foreground overflow-x-auto max-h-96 overflow-y-auto">
|
|
89
|
+
{JSON.stringify(data, null, 2)}
|
|
90
|
+
</pre>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ExternalLink, Loader2 } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface ImageRendererProps {
|
|
5
|
+
data: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ImageRenderer({ data }: ImageRendererProps) {
|
|
9
|
+
if (typeof data !== "object" || data === null) {
|
|
10
|
+
return (
|
|
11
|
+
<p className="text-sm text-muted-foreground italic">No data returned</p>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const d = data as Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
if (d.error) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-3">
|
|
20
|
+
<p className="text-sm text-destructive">{String(d.error)}</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (d.status === "processing" || (d.poll_url && !d.image_url)) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-card p-4">
|
|
28
|
+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
29
|
+
<span className="text-sm text-muted-foreground">Generating image...</span>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (typeof d.image_url === "string") {
|
|
35
|
+
return (
|
|
36
|
+
<div className="space-y-2">
|
|
37
|
+
<a
|
|
38
|
+
href={d.image_url}
|
|
39
|
+
target="_blank"
|
|
40
|
+
rel="noopener noreferrer"
|
|
41
|
+
className="block"
|
|
42
|
+
>
|
|
43
|
+
<img
|
|
44
|
+
src={d.image_url}
|
|
45
|
+
alt="Generated image"
|
|
46
|
+
className="rounded-lg max-w-full max-h-96 object-contain border border-border"
|
|
47
|
+
/>
|
|
48
|
+
</a>
|
|
49
|
+
<a
|
|
50
|
+
href={d.image_url}
|
|
51
|
+
target="_blank"
|
|
52
|
+
rel="noopener noreferrer"
|
|
53
|
+
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
|
54
|
+
>
|
|
55
|
+
<ExternalLink className="h-3 w-3" />
|
|
56
|
+
Open full size
|
|
57
|
+
</a>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<pre className="rounded-lg border border-border bg-background p-3 text-xs text-foreground overflow-x-auto max-h-96 overflow-y-auto">
|
|
64
|
+
{JSON.stringify(data, null, 2)}
|
|
65
|
+
</pre>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { ExternalLink, Loader2 } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
interface VideoRendererProps {
|
|
5
|
+
data: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function VideoRenderer({ data }: VideoRendererProps) {
|
|
9
|
+
if (typeof data !== "object" || data === null) {
|
|
10
|
+
return (
|
|
11
|
+
<p className="text-sm text-muted-foreground italic">No data returned</p>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const d = data as Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
if (d.error) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-3">
|
|
20
|
+
<p className="text-sm text-destructive">{String(d.error)}</p>
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (d.status === "processing" || (d.poll_url && !d.answer && !d.result)) {
|
|
26
|
+
const eta = typeof d.estimated_time_seconds === "number"
|
|
27
|
+
? ` (~${d.estimated_time_seconds}s)`
|
|
28
|
+
: "";
|
|
29
|
+
return (
|
|
30
|
+
<div className="flex items-center gap-2 rounded-lg border border-border bg-card p-4">
|
|
31
|
+
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
|
|
32
|
+
<span className="text-sm text-muted-foreground">Processing video...{eta}</span>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Video understanding — text answer
|
|
38
|
+
if (typeof d.answer === "string") {
|
|
39
|
+
return (
|
|
40
|
+
<div className="rounded-lg border border-border bg-card p-3">
|
|
41
|
+
<p className="text-sm text-foreground whitespace-pre-wrap">{d.answer}</p>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Video generation — playable result
|
|
47
|
+
if (d.result && typeof d.result === "object") {
|
|
48
|
+
const result = d.result as Record<string, unknown>;
|
|
49
|
+
const videoUrl = typeof result.output === "string"
|
|
50
|
+
? result.output
|
|
51
|
+
: typeof result.url === "string"
|
|
52
|
+
? result.url
|
|
53
|
+
: typeof result.video_url === "string"
|
|
54
|
+
? result.video_url
|
|
55
|
+
: null;
|
|
56
|
+
|
|
57
|
+
if (videoUrl) {
|
|
58
|
+
return (
|
|
59
|
+
<div className="space-y-2">
|
|
60
|
+
<video
|
|
61
|
+
controls
|
|
62
|
+
className="rounded-lg max-w-full max-h-96 border border-border"
|
|
63
|
+
>
|
|
64
|
+
<source src={videoUrl} />
|
|
65
|
+
</video>
|
|
66
|
+
<a
|
|
67
|
+
href={videoUrl}
|
|
68
|
+
target="_blank"
|
|
69
|
+
rel="noopener noreferrer"
|
|
70
|
+
className="inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground"
|
|
71
|
+
>
|
|
72
|
+
<ExternalLink className="h-3 w-3" />
|
|
73
|
+
Open video
|
|
74
|
+
</a>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<pre className="rounded-lg border border-border bg-background p-3 text-xs text-foreground overflow-x-auto max-h-96 overflow-y-auto">
|
|
82
|
+
{JSON.stringify(data, null, 2)}
|
|
83
|
+
</pre>
|
|
84
|
+
);
|
|
85
|
+
}
|