@voltx/cli 0.3.4 → 0.3.6

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.
Files changed (50) hide show
  1. package/README.md +54 -22
  2. package/dist/build.d.mts +4 -3
  3. package/dist/build.d.ts +4 -3
  4. package/dist/build.js +56 -27
  5. package/dist/build.mjs +1 -2
  6. package/dist/chunk-2LHDOMF2.mjs +680 -0
  7. package/dist/chunk-5DVBIJXU.mjs +84 -0
  8. package/dist/chunk-65PVXS4D.mjs +894 -0
  9. package/dist/chunk-7JVIEGSA.mjs +141 -0
  10. package/dist/chunk-A4NA4AJN.mjs +786 -0
  11. package/dist/chunk-AAAHANST.mjs +839 -0
  12. package/dist/chunk-AD3WMFZF.mjs +205 -0
  13. package/dist/chunk-CSSHLVYS.mjs +83 -0
  14. package/dist/chunk-CWOSNV5O.mjs +150 -0
  15. package/dist/chunk-EI6XBYKB.mjs +84 -0
  16. package/dist/chunk-FI2W4L4S.mjs +205 -0
  17. package/dist/chunk-G2INQCCJ.mjs +907 -0
  18. package/dist/chunk-H2DTIOEO.mjs +150 -0
  19. package/dist/chunk-IS2WTE3C.mjs +138 -0
  20. package/dist/chunk-JECCDBYI.mjs +730 -0
  21. package/dist/chunk-KX2MRJUO.mjs +795 -0
  22. package/dist/chunk-LTGMHAZS.mjs +147 -0
  23. package/dist/chunk-OPO6RUFP.mjs +698 -0
  24. package/dist/chunk-PWQSKYAM.mjs +682 -0
  25. package/dist/chunk-Q5XCFN7L.mjs +1026 -0
  26. package/dist/chunk-QSU6FZC7.mjs +497 -0
  27. package/dist/chunk-RYWRFHEC.mjs +83 -0
  28. package/dist/chunk-SU4Q3PTH.mjs +201 -0
  29. package/dist/chunk-TFVNHM7S.mjs +1028 -0
  30. package/dist/chunk-UXI3QSDN.mjs +121 -0
  31. package/dist/chunk-VD3CNPNP.mjs +123 -0
  32. package/dist/chunk-X6VOAPRJ.mjs +756 -0
  33. package/dist/cli.js +935 -308
  34. package/dist/cli.mjs +7 -6
  35. package/dist/create.d.mts +1 -0
  36. package/dist/create.d.ts +1 -0
  37. package/dist/create.js +726 -192
  38. package/dist/create.mjs +1 -2
  39. package/dist/dev.d.mts +6 -4
  40. package/dist/dev.d.ts +6 -4
  41. package/dist/dev.js +119 -46
  42. package/dist/dev.mjs +1 -2
  43. package/dist/generate.js +13 -13
  44. package/dist/generate.mjs +1 -2
  45. package/dist/index.js +922 -296
  46. package/dist/index.mjs +5 -6
  47. package/dist/start.js +7 -17
  48. package/dist/start.mjs +1 -2
  49. package/dist/welcome.mjs +0 -1
  50. package/package.json +11 -3
package/dist/cli.js CHANGED
@@ -145,8 +145,11 @@ var create_exports = {};
145
145
  __export(create_exports, {
146
146
  createProject: () => createProject
147
147
  });
148
+ function v(pkg) {
149
+ return VV[pkg] ?? "^0.3.0";
150
+ }
148
151
  async function createProject(options) {
149
- const { name, template = "blank", auth = "none" } = options;
152
+ const { name, template = "blank", auth = "none", shadcn = false } = options;
150
153
  const targetDir = path.resolve(process.cwd(), name);
151
154
  if (fs.existsSync(targetDir)) {
152
155
  console.error(`[voltx] Directory "${name}" already exists.`);
@@ -156,21 +159,41 @@ async function createProject(options) {
156
159
  const provider = template === "rag-app" ? "openai" : "cerebras";
157
160
  const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
158
161
  const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
159
- const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": V };
162
+ const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": v("@voltx/cli") };
160
163
  if (auth === "better-auth") {
161
- deps["@voltx/auth"] = V;
164
+ deps["@voltx/auth"] = v("@voltx/auth");
162
165
  deps["better-auth"] = "^1.5.0";
163
166
  } else if (auth === "jwt") {
164
- deps["@voltx/auth"] = V;
167
+ deps["@voltx/auth"] = v("@voltx/auth");
165
168
  deps["jose"] = "^6.0.0";
166
169
  }
170
+ const devDeps = { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" };
171
+ deps["hono"] = "^4.7.0";
172
+ deps["@hono/node-server"] = "^1.14.0";
173
+ deps["react"] = "^19.0.0";
174
+ deps["react-dom"] = "^19.0.0";
175
+ deps["tailwindcss"] = "^4.0.0";
176
+ devDeps["vite"] = "^6.0.0";
177
+ devDeps["@hono/vite-dev-server"] = "^0.7.0";
178
+ devDeps["@vitejs/plugin-react"] = "^4.3.0";
179
+ devDeps["@tailwindcss/vite"] = "^4.0.0";
180
+ devDeps["@types/react"] = "^19.0.0";
181
+ devDeps["@types/react-dom"] = "^19.0.0";
182
+ if (shadcn) {
183
+ deps["class-variance-authority"] = "^0.7.0";
184
+ deps["clsx"] = "^2.1.0";
185
+ deps["tailwind-merge"] = "^3.0.0";
186
+ deps["lucide-react"] = "^0.468.0";
187
+ }
188
+ devDeps["@voltx/cli"] = deps["@voltx/cli"] ?? v("@voltx/cli");
189
+ delete deps["@voltx/cli"];
167
190
  fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify({
168
191
  name,
169
192
  version: "0.1.0",
170
193
  private: true,
171
- scripts: { dev: "voltx dev", build: "voltx build", start: "voltx start" },
194
+ scripts: { dev: "npx voltx dev", build: "npx voltx build", start: "npx voltx start" },
172
195
  dependencies: deps,
173
- devDependencies: { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" }
196
+ devDependencies: devDeps
174
197
  }, null, 2));
175
198
  let config = `import { defineConfig } from "@voltx/core";
176
199
 
@@ -191,31 +214,53 @@ export default defineConfig({
191
214
  },`;
192
215
  config += `
193
216
  server: {
194
- routesDir: "src/routes",
217
+ routesDir: "api",
195
218
  staticDir: "public",
196
219
  cors: true,
197
220
  },
198
221
  });
199
222
  `;
200
223
  fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), config);
201
- fs.mkdirSync(path.join(targetDir, "src", "routes", "api"), { recursive: true });
224
+ fs.mkdirSync(path.join(targetDir, "api"), { recursive: true });
202
225
  fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
203
- fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify({
204
- compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, esModuleInterop: true, skipLibCheck: true, outDir: "dist" },
205
- include: ["src", "voltx.config.ts"]
206
- }, null, 2));
207
- fs.writeFileSync(
208
- path.join(targetDir, "src", "index.ts"),
209
- `import { createApp } from "@voltx/core";
210
- import config from "../voltx.config";
211
-
212
- const app = createApp(config);
213
- app.start();
214
- `
215
- );
226
+ fs.mkdirSync(path.join(targetDir, "src"), { recursive: true });
227
+ fs.mkdirSync(path.join(targetDir, "src", "components"), { recursive: true });
228
+ fs.mkdirSync(path.join(targetDir, "src", "hooks"), { recursive: true });
229
+ fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
230
+ fs.writeFileSync(path.join(targetDir, "public", "favicon.svg"), generateFaviconSVG());
231
+ fs.writeFileSync(path.join(targetDir, "public", "robots.txt"), generateRobotsTxt());
232
+ fs.writeFileSync(path.join(targetDir, "public", "site.webmanifest"), generateWebManifest(name));
233
+ const tsconfig = {
234
+ compilerOptions: {
235
+ target: "ES2022",
236
+ module: "ESNext",
237
+ moduleResolution: "bundler",
238
+ strict: true,
239
+ esModuleInterop: true,
240
+ skipLibCheck: true,
241
+ outDir: "dist",
242
+ baseUrl: ".",
243
+ paths: { "@/*": ["./src/*"] },
244
+ jsx: "react-jsx"
245
+ },
246
+ include: ["src", "api", "server.ts", "voltx.config.ts"]
247
+ };
248
+ if (template === "agent-app") tsconfig.include.push("agents", "tools");
249
+ fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
250
+ fs.writeFileSync(path.join(targetDir, "server.ts"), generateServerEntry(name, template));
251
+ fs.writeFileSync(path.join(targetDir, "vite.config.ts"), generateViteConfigFile("server.ts"));
252
+ fs.writeFileSync(path.join(targetDir, "src", "entry-client.tsx"), generateEntryClient());
253
+ fs.writeFileSync(path.join(targetDir, "src", "entry-server.tsx"), generateEntryServer());
254
+ fs.writeFileSync(path.join(targetDir, "src", "layout.tsx"), generateLayoutComponent(name));
255
+ fs.writeFileSync(path.join(targetDir, "src", "globals.css"), generateGlobalCSS(shadcn));
256
+ if (shadcn) {
257
+ fs.writeFileSync(path.join(targetDir, "src", "lib", "utils.ts"), generateCnUtil());
258
+ fs.writeFileSync(path.join(targetDir, "components.json"), generateComponentsJson());
259
+ }
260
+ fs.writeFileSync(path.join(targetDir, "src", "app.tsx"), generateAppComponent(name, template));
216
261
  fs.writeFileSync(
217
- path.join(targetDir, "src", "routes", "index.ts"),
218
- `// GET / \u2014 Health check
262
+ path.join(targetDir, "api", "index.ts"),
263
+ `// GET /api \u2014 Health check
219
264
  import type { Context } from "@voltx/server";
220
265
 
221
266
  export function GET(c: Context) {
@@ -225,7 +270,7 @@ export function GET(c: Context) {
225
270
  );
226
271
  if (template === "chatbot" || template === "agent-app") {
227
272
  fs.writeFileSync(
228
- path.join(targetDir, "src", "routes", "api", "chat.ts"),
273
+ path.join(targetDir, "api", "chat.ts"),
229
274
  `// POST /api/chat \u2014 Streaming chat with conversation memory
230
275
  import type { Context } from "@voltx/server";
231
276
  import { streamText } from "@voltx/ai";
@@ -259,9 +304,9 @@ export async function POST(c: Context) {
259
304
  );
260
305
  }
261
306
  if (template === "agent-app") {
262
- fs.mkdirSync(path.join(targetDir, "src", "agents"), { recursive: true });
263
- fs.mkdirSync(path.join(targetDir, "src", "tools"), { recursive: true });
264
- fs.writeFileSync(path.join(targetDir, "src", "tools", "calculator.ts"), `// Calculator tool \u2014 evaluates math expressions (no API key needed)
307
+ fs.mkdirSync(path.join(targetDir, "agents"), { recursive: true });
308
+ fs.mkdirSync(path.join(targetDir, "tools"), { recursive: true });
309
+ fs.writeFileSync(path.join(targetDir, "tools", "calculator.ts"), `// Calculator tool \u2014 evaluates math expressions (no API key needed)
265
310
  import type { Tool } from "@voltx/agents";
266
311
 
267
312
  export const calculatorTool: Tool = {
@@ -286,7 +331,7 @@ export const calculatorTool: Tool = {
286
331
  },
287
332
  };
288
333
  `);
289
- fs.writeFileSync(path.join(targetDir, "src", "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
334
+ fs.writeFileSync(path.join(targetDir, "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
290
335
  import type { Tool } from "@voltx/agents";
291
336
 
292
337
  export const datetimeTool: Tool = {
@@ -307,7 +352,7 @@ export const datetimeTool: Tool = {
307
352
  },
308
353
  };
309
354
  `);
310
- fs.writeFileSync(path.join(targetDir, "src", "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
355
+ fs.writeFileSync(path.join(targetDir, "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
311
356
  import { createAgent } from "@voltx/agents";
312
357
  import { calculatorTool } from "../tools/calculator";
313
358
  import { datetimeTool } from "../tools/datetime";
@@ -321,10 +366,10 @@ export const assistant = createAgent({
321
366
  });
322
367
  `);
323
368
  fs.writeFileSync(
324
- path.join(targetDir, "src", "routes", "api", "agent.ts"),
369
+ path.join(targetDir, "api", "agent.ts"),
325
370
  `// POST /api/agent \u2014 Run the AI agent
326
371
  import type { Context } from "@voltx/server";
327
- import { assistant } from "../../agents/assistant";
372
+ import { assistant } from "../agents/assistant";
328
373
 
329
374
  export async function POST(c: Context) {
330
375
  const { input } = await c.req.json();
@@ -337,9 +382,9 @@ export async function POST(c: Context) {
337
382
  }
338
383
  if (template === "rag-app") {
339
384
  const embedModel = "openai:text-embedding-3-small";
340
- fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "rag"), { recursive: true });
385
+ fs.mkdirSync(path.join(targetDir, "api", "rag"), { recursive: true });
341
386
  fs.writeFileSync(
342
- path.join(targetDir, "src", "routes", "api", "rag", "query.ts"),
387
+ path.join(targetDir, "api", "rag", "query.ts"),
343
388
  `// POST /api/rag/query \u2014 Query documents with RAG
344
389
  import type { Context } from "@voltx/server";
345
390
  import { streamText } from "@voltx/ai";
@@ -363,7 +408,7 @@ export async function POST(c: Context) {
363
408
  `
364
409
  );
365
410
  fs.writeFileSync(
366
- path.join(targetDir, "src", "routes", "api", "rag", "ingest.ts"),
411
+ path.join(targetDir, "api", "rag", "ingest.ts"),
367
412
  `// POST /api/rag/ingest \u2014 Ingest documents into the vector store
368
413
  import type { Context } from "@voltx/server";
369
414
  import { createRAGPipeline, createEmbedder } from "@voltx/rag";
@@ -383,12 +428,12 @@ export async function POST(c: Context) {
383
428
  );
384
429
  }
385
430
  if (auth === "better-auth") {
386
- fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "auth"), { recursive: true });
431
+ fs.mkdirSync(path.join(targetDir, "api", "auth"), { recursive: true });
387
432
  fs.writeFileSync(
388
- path.join(targetDir, "src", "routes", "api", "auth", "[...path].ts"),
433
+ path.join(targetDir, "api", "auth", "[...path].ts"),
389
434
  `// ALL /api/auth/* \u2014 Better Auth handler
390
435
  import type { Context } from "@voltx/server";
391
- import { auth } from "../../../lib/auth";
436
+ import { auth } from "../../src/lib/auth";
392
437
  import { createAuthHandler } from "@voltx/auth";
393
438
 
394
439
  const handler = createAuthHandler(auth);
@@ -397,7 +442,6 @@ export const GET = (c: Context) => handler(c);
397
442
  export const POST = (c: Context) => handler(c);
398
443
  `
399
444
  );
400
- fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
401
445
  fs.writeFileSync(
402
446
  path.join(targetDir, "src", "lib", "auth.ts"),
403
447
  `import { createAuth, createAuthMiddleware } from "@voltx/auth";
@@ -414,7 +458,6 @@ export const authMiddleware = createAuthMiddleware({
414
458
  `
415
459
  );
416
460
  } else if (auth === "jwt") {
417
- fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
418
461
  fs.writeFileSync(
419
462
  path.join(targetDir, "src", "lib", "auth.ts"),
420
463
  `import { createAuth, createAuthMiddleware } from "@voltx/auth";
@@ -431,9 +474,9 @@ export const authMiddleware = createAuthMiddleware({
431
474
  `
432
475
  );
433
476
  fs.writeFileSync(
434
- path.join(targetDir, "src", "routes", "api", "auth.ts"),
477
+ path.join(targetDir, "api", "auth.ts"),
435
478
  `import type { Context } from "@voltx/server";
436
- import { jwt } from "../../lib/auth";
479
+ import { jwt } from "../src/lib/auth";
437
480
 
438
481
  export async function POST(c: Context) {
439
482
  const { email, password } = await c.req.json();
@@ -450,14 +493,6 @@ export async function POST(c: Context) {
450
493
  envContent += "# \u2500\u2500\u2500 Database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
451
494
  } else if (template === "chatbot" || template === "agent-app") {
452
495
  envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nCEREBRAS_API_KEY=csk-...\n\n";
453
- if (template === "agent-app") {
454
- envContent += "# \u2500\u2500\u2500 Database (optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
455
- envContent += "# \u2500\u2500\u2500 Tool API Keys (add keys for tools you use) \u2500\u2500\n";
456
- envContent += "# TAVILY_API_KEY=tvly-... (Web Search \u2014 https://tavily.com)\n";
457
- envContent += "# SERPER_API_KEY= (Google Search \u2014 https://serper.dev)\n";
458
- envContent += "# OPENWEATHER_API_KEY= (Weather \u2014 https://openweathermap.org/api)\n";
459
- envContent += "# NEWS_API_KEY= (News \u2014 https://newsapi.org)\n\n";
460
- }
461
496
  } else {
462
497
  envContent += "# \u2500\u2500\u2500 LLM Provider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# OPENAI_API_KEY=sk-...\n# CEREBRAS_API_KEY=csk-...\n\n";
463
498
  }
@@ -468,173 +503,672 @@ export async function POST(c: Context) {
468
503
  }
469
504
  envContent += "# \u2500\u2500\u2500 App \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPORT=3000\nNODE_ENV=development\n";
470
505
  fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
471
- if (template !== "blank") {
472
- fs.writeFileSync(path.join(targetDir, "public", "index.html"), generateFrontendHTML(name, template));
473
- }
474
- fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n");
506
+ fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n.env.local\n.env.*.local\nvite.config.voltx.ts\n");
475
507
  printWelcomeBanner(name);
476
508
  }
477
- function generateFrontendHTML(projectName, template) {
478
- const badge = template === "chatbot" ? "Chatbot" : template === "rag-app" ? "RAG App" : "Agent App";
479
- const accentClass = template === "rag-app" ? "emerald" : template === "agent-app" ? "purple" : "blue";
480
- const shell = (body) => `<!DOCTYPE html>
481
- <html lang="en" class="h-full">
482
- <head>
483
- <meta charset="UTF-8" />
484
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
485
- <title>${projectName}</title>
486
- <script src="https://cdn.tailwindcss.com/3.4.17"></script>
487
- <style>
488
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
489
- body { font-family: 'Inter', system-ui, sans-serif; }
490
- .msg-enter { animation: msgIn 0.25s ease-out; }
491
- @keyframes msgIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
492
- .typing-dot { animation: blink 1.4s infinite both; }
493
- .typing-dot:nth-child(2) { animation-delay: 0.2s; }
494
- .typing-dot:nth-child(3) { animation-delay: 0.4s; }
495
- @keyframes blink { 0%, 80%, 100% { opacity: 0.2; } 40% { opacity: 1; } }
496
- ::-webkit-scrollbar { width: 6px; }
497
- ::-webkit-scrollbar-track { background: transparent; }
498
- ::-webkit-scrollbar-thumb { background: #334155; border-radius: 3px; }
499
- </style>
500
- </head>
501
- <body class="h-full bg-gray-950 text-gray-100">
502
- <div id="app" class="h-full flex flex-col">
503
- <header class="flex-shrink-0 border-b border-gray-800 bg-gray-950/80 backdrop-blur-sm px-4 py-3">
504
- <div class="max-w-4xl mx-auto flex items-center justify-between">
505
- <div class="flex items-center gap-3">
506
- <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-500 to-purple-600 flex items-center justify-center text-sm font-bold">V</div>
507
- <h1 class="text-lg font-semibold text-white">${projectName}</h1>
508
- <span class="text-xs px-2 py-0.5 rounded-full bg-gray-800 text-gray-400">${badge}</span>
509
- </div>
510
- <a href="https://github.com/codewithshail/voltx" target="_blank" class="text-gray-500 hover:text-gray-300 transition-colors text-sm">Built with VoltX</a>
511
- </div>
512
- </header>
513
- <main class="flex-1 overflow-hidden"><div class="h-full max-w-4xl mx-auto">
514
- ${body}
515
- </div></main>
516
- </div>
517
- </body>
518
- </html>`;
519
- if (template === "chatbot") return shell(chatbotBody());
520
- if (template === "rag-app") return shell(ragAppBody());
521
- if (template === "agent-app") return shell(agentAppBody());
522
- return "";
523
- }
524
- function chatbotBody() {
525
- return ` <div class="h-full flex flex-col">
526
- <div id="messages" class="flex-1 overflow-y-auto px-4 py-6 space-y-4">
527
- <div class="text-center py-12">
528
- <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-blue-500/20 to-purple-600/20 border border-blue-500/20 flex items-center justify-center">
529
- <svg class="w-8 h-8 text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/></svg>
530
- </div>
531
- <h2 class="text-xl font-semibold text-white mb-2">Start a conversation</h2>
532
- <p class="text-gray-500 text-sm">Type a message below to chat with your AI assistant.</p>
533
- </div>
509
+ function generateServerEntry(projectName, template) {
510
+ const imports = [];
511
+ const mounts = [];
512
+ imports.push('import { GET as healthGET } from "./api/index";');
513
+ mounts.push('app.get("/api", healthGET);');
514
+ if (template === "chatbot" || template === "agent-app") {
515
+ imports.push('import { POST as chatPOST } from "./api/chat";');
516
+ mounts.push('app.post("/api/chat", chatPOST);');
517
+ }
518
+ if (template === "agent-app") {
519
+ imports.push('import { POST as agentPOST } from "./api/agent";');
520
+ mounts.push('app.post("/api/agent", agentPOST);');
521
+ }
522
+ if (template === "rag-app") {
523
+ imports.push('import { POST as ragQueryPOST } from "./api/rag/query";');
524
+ imports.push('import { POST as ragIngestPOST } from "./api/rag/ingest";');
525
+ mounts.push('app.post("/api/rag/query", ragQueryPOST);');
526
+ mounts.push('app.post("/api/rag/ingest", ragIngestPOST);');
527
+ }
528
+ return `import { Hono } from "hono";
529
+ import { serve } from "@hono/node-server";
530
+ import { serveStatic } from "@hono/node-server/serve-static";
531
+ import { registerSSR } from "@voltx/server";
532
+ import { loadEnv } from "@voltx/core";
533
+ ${imports.join("\n")}
534
+
535
+ loadEnv(process.env.NODE_ENV ?? "development");
536
+
537
+ const isProd = process.env.NODE_ENV === "production";
538
+ const app = new Hono();
539
+
540
+ // \u2500\u2500 API Routes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
541
+ ${mounts.join("\n")}
542
+
543
+ // \u2500\u2500 Static assets (production) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
544
+ if (isProd) {
545
+ app.use("/assets/*", serveStatic({ root: "./dist/client/" }));
546
+ app.use("/favicon.svg", serveStatic({ root: "./public/" }));
547
+ app.use("/robots.txt", serveStatic({ root: "./public/" }));
548
+ app.use("/site.webmanifest", serveStatic({ root: "./public/" }));
549
+ }
550
+
551
+ // \u2500\u2500 SSR catch-all \u2014 renders React on the server \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
552
+ registerSSR(app, null, {
553
+ title: "${projectName}",
554
+ entryServer: "src/entry-server.tsx",
555
+ entryClient: "src/entry-client.tsx",
556
+ });
557
+
558
+ export default app;
559
+
560
+ if (isProd) {
561
+ const port = Number(process.env.PORT) || 3000;
562
+ serve({ fetch: app.fetch, port }, (info) => {
563
+ console.log(\`\\n \u26A1 ${projectName} running at http://localhost:\${info.port}\\n\`);
564
+ });
565
+ }
566
+ `;
567
+ }
568
+ function generateViteConfigFile(entry) {
569
+ return `import { defineConfig } from "vite";
570
+ import devServer from "@hono/vite-dev-server";
571
+ import react from "@vitejs/plugin-react";
572
+ import tailwindcss from "@tailwindcss/vite";
573
+
574
+ export default defineConfig({
575
+ resolve: {
576
+ alias: {
577
+ "@": "/src",
578
+ },
579
+ },
580
+ plugins: [
581
+ react(),
582
+ tailwindcss(),
583
+ devServer({
584
+ entry: "${entry}",
585
+ exclude: [
586
+ /.*\\.tsx?($|\\?)/,
587
+ /.*\\.(s?css|less)($|\\?)/,
588
+ /.*\\.(svg|png|jpg|jpeg|gif|webp|ico)($|\\?)/,
589
+ /^\\/@.+$/,
590
+ /^\\/favicon\\.svg$/,
591
+ /^\\/node_modules\\/.*/,
592
+ /^\\/src\\/.*/,
593
+ ],
594
+ injectClientScript: false,
595
+ }),
596
+ ],
597
+ });
598
+ `;
599
+ }
600
+ function generateEntryClient() {
601
+ return `import React from "react";
602
+ import { hydrateRoot } from "react-dom/client";
603
+ import Layout from "./layout";
604
+ import App from "./app";
605
+ import "./globals.css";
606
+
607
+ hydrateRoot(
608
+ document.getElementById("root")!,
609
+ <React.StrictMode>
610
+ <Layout>
611
+ <App />
612
+ </Layout>
613
+ </React.StrictMode>
614
+ );
615
+ `;
616
+ }
617
+ function generateEntryServer() {
618
+ return `import React from "react";
619
+ import { renderToReadableStream } from "react-dom/server";
620
+ import Layout from "./layout";
621
+ import App from "./app";
622
+
623
+ export async function render(_url: string): Promise<ReadableStream> {
624
+ const stream = await renderToReadableStream(
625
+ <React.StrictMode>
626
+ <Layout>
627
+ <App />
628
+ </Layout>
629
+ </React.StrictMode>,
630
+ {
631
+ onError(error: unknown) {
632
+ console.error("[voltx] SSR render error:", error);
633
+ },
634
+ }
635
+ );
636
+ return stream;
637
+ }
638
+ `;
639
+ }
640
+ function generateLayoutComponent(projectName) {
641
+ return `import React from "react";
642
+
643
+ export default function Layout({ children }: { children: React.ReactNode }) {
644
+ return (
645
+ <div className="min-h-screen bg-background text-foreground font-sans">
646
+ <header className="border-b border-border px-6 py-3 flex items-center justify-between">
647
+ <div className="flex items-center gap-2">
648
+ <span className="text-xl">\u26A1</span>
649
+ <span className="font-semibold">${projectName}</span>
534
650
  </div>
535
- <div class="flex-shrink-0 border-t border-gray-800 px-4 py-4">
536
- <form id="chatForm" class="flex gap-3">
537
- <input id="chatInput" type="text" placeholder="Type your message..." autocomplete="off" class="flex-1 bg-gray-900 border border-gray-700 rounded-xl px-4 py-3 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 transition-colors" />
538
- <button type="submit" id="sendBtn" class="px-5 py-3 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-xl transition-colors">Send</button>
539
- </form>
651
+ <a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-muted text-sm hover:text-foreground transition-colors">
652
+ Built with VoltX
653
+ </a>
654
+ </header>
655
+ <main>{children}</main>
656
+ </div>
657
+ );
658
+ }
659
+ `;
660
+ }
661
+ function generateGlobalCSS(useShadcn = false) {
662
+ if (useShadcn) {
663
+ return `@import "tailwindcss";
664
+
665
+ @theme inline {
666
+ --radius-sm: 0.25rem;
667
+ --radius-md: 0.375rem;
668
+ --radius-lg: 0.5rem;
669
+ --radius-xl: 0.75rem;
670
+ --color-background: hsl(var(--background));
671
+ --color-foreground: hsl(var(--foreground));
672
+ --color-card: hsl(var(--card));
673
+ --color-card-foreground: hsl(var(--card-foreground));
674
+ --color-popover: hsl(var(--popover));
675
+ --color-popover-foreground: hsl(var(--popover-foreground));
676
+ --color-primary: hsl(var(--primary));
677
+ --color-primary-foreground: hsl(var(--primary-foreground));
678
+ --color-secondary: hsl(var(--secondary));
679
+ --color-secondary-foreground: hsl(var(--secondary-foreground));
680
+ --color-muted: hsl(var(--muted));
681
+ --color-muted-foreground: hsl(var(--muted-foreground));
682
+ --color-accent: hsl(var(--accent));
683
+ --color-accent-foreground: hsl(var(--accent-foreground));
684
+ --color-destructive: hsl(var(--destructive));
685
+ --color-border: hsl(var(--border));
686
+ --color-input: hsl(var(--input));
687
+ --color-ring: hsl(var(--ring));
688
+ --color-chart-1: hsl(var(--chart-1));
689
+ --color-chart-2: hsl(var(--chart-2));
690
+ --color-chart-3: hsl(var(--chart-3));
691
+ --color-chart-4: hsl(var(--chart-4));
692
+ --color-chart-5: hsl(var(--chart-5));
693
+ --color-sidebar: hsl(var(--sidebar));
694
+ --color-sidebar-foreground: hsl(var(--sidebar-foreground));
695
+ --color-sidebar-primary: hsl(var(--sidebar-primary));
696
+ --color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
697
+ --color-sidebar-accent: hsl(var(--sidebar-accent));
698
+ --color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
699
+ --color-sidebar-border: hsl(var(--sidebar-border));
700
+ --color-sidebar-ring: hsl(var(--sidebar-ring));
701
+ }
702
+
703
+ :root {
704
+ --background: 0 0% 4%;
705
+ --foreground: 0 0% 93%;
706
+ --card: 0 0% 6%;
707
+ --card-foreground: 0 0% 93%;
708
+ --popover: 0 0% 6%;
709
+ --popover-foreground: 0 0% 93%;
710
+ --primary: 0 0% 93%;
711
+ --primary-foreground: 0 0% 6%;
712
+ --secondary: 0 0% 12%;
713
+ --secondary-foreground: 0 0% 93%;
714
+ --muted: 0 0% 12%;
715
+ --muted-foreground: 0 0% 55%;
716
+ --accent: 0 0% 12%;
717
+ --accent-foreground: 0 0% 93%;
718
+ --destructive: 0 62% 50%;
719
+ --border: 0 0% 14%;
720
+ --input: 0 0% 14%;
721
+ --ring: 0 0% 83%;
722
+ --chart-1: 220 70% 50%;
723
+ --chart-2: 160 60% 45%;
724
+ --chart-3: 30 80% 55%;
725
+ --chart-4: 280 65% 60%;
726
+ --chart-5: 340 75% 55%;
727
+ --sidebar: 0 0% 5%;
728
+ --sidebar-foreground: 0 0% 93%;
729
+ --sidebar-primary: 0 0% 93%;
730
+ --sidebar-primary-foreground: 0 0% 6%;
731
+ --sidebar-accent: 0 0% 12%;
732
+ --sidebar-accent-foreground: 0 0% 93%;
733
+ --sidebar-border: 0 0% 14%;
734
+ --sidebar-ring: 0 0% 83%;
735
+ }
736
+
737
+ *,
738
+ *::before,
739
+ *::after {
740
+ box-sizing: border-box;
741
+ margin: 0;
742
+ padding: 0;
743
+ border-color: var(--color-border);
744
+ }
745
+
746
+ html,
747
+ body {
748
+ height: 100%;
749
+ background: var(--color-background);
750
+ color: var(--color-foreground);
751
+ font-family: system-ui, -apple-system, sans-serif;
752
+ -webkit-font-smoothing: antialiased;
753
+ }
754
+
755
+ #root {
756
+ height: 100%;
757
+ }
758
+ `;
759
+ }
760
+ return `@import "tailwindcss";
761
+
762
+ @theme {
763
+ --color-background: #0a0a0a;
764
+ --color-foreground: #ededed;
765
+ --color-muted: #888888;
766
+ --color-border: #222222;
767
+ --color-primary: #2563eb;
768
+ --color-accent: #a78bfa;
769
+ --font-sans: system-ui, -apple-system, sans-serif;
770
+ }
771
+
772
+ *,
773
+ *::before,
774
+ *::after {
775
+ box-sizing: border-box;
776
+ margin: 0;
777
+ padding: 0;
778
+ }
779
+
780
+ html,
781
+ body {
782
+ height: 100%;
783
+ background: var(--color-background);
784
+ color: var(--color-foreground);
785
+ font-family: var(--font-sans);
786
+ -webkit-font-smoothing: antialiased;
787
+ }
788
+
789
+ #root {
790
+ height: 100%;
791
+ }
792
+
793
+ a {
794
+ color: inherit;
795
+ text-decoration: none;
796
+ }
797
+
798
+ a:hover {
799
+ text-decoration: underline;
800
+ }
801
+ `;
802
+ }
803
+ function generateCnUtil() {
804
+ return `import { type ClassValue, clsx } from "clsx";
805
+ import { twMerge } from "tailwind-merge";
806
+
807
+ export function cn(...inputs: ClassValue[]) {
808
+ return twMerge(clsx(inputs));
809
+ }
810
+ `;
811
+ }
812
+ function generateComponentsJson() {
813
+ return JSON.stringify({
814
+ "$schema": "https://ui.shadcn.com/schema.json",
815
+ style: "new-york",
816
+ rsc: false,
817
+ tsx: true,
818
+ tailwind: {
819
+ config: "",
820
+ css: "src/globals.css",
821
+ baseColor: "neutral",
822
+ cssVariables: true
823
+ },
824
+ aliases: {
825
+ components: "@/components",
826
+ utils: "@/lib/utils",
827
+ ui: "@/components/ui",
828
+ lib: "@/lib",
829
+ hooks: "@/hooks"
830
+ }
831
+ }, null, 2) + "\n";
832
+ }
833
+ function generateAppComponent(projectName, template) {
834
+ if (template === "blank") {
835
+ return `import React, { useState, useEffect } from "react";
836
+
837
+ export default function App() {
838
+ const [status, setStatus] = useState<string>("checking...");
839
+
840
+ useEffect(() => {
841
+ fetch("/api")
842
+ .then((res) => res.json())
843
+ .then((data) => setStatus(data.status || "ok"))
844
+ .catch(() => setStatus("error"));
845
+ }, []);
846
+
847
+ return (
848
+ <div className="flex flex-col items-center justify-center min-h-[calc(100vh-60px)] px-6 py-12">
849
+ <div className="text-center max-w-2xl w-full">
850
+ {/* Hero */}
851
+ <div className="relative mb-8">
852
+ <div className="absolute inset-0 blur-3xl opacity-20 bg-gradient-to-r from-blue-500 via-purple-500 to-blue-500 rounded-full" />
853
+ <div className="relative text-7xl mb-4">\u26A1</div>
540
854
  </div>
541
- </div>
542
- <script>
543
- const messages=[], messagesEl=document.getElementById("messages"), form=document.getElementById("chatForm"), input=document.getElementById("chatInput"), sendBtn=document.getElementById("sendBtn");
544
- function addMsg(role,content){const d=document.createElement("div");d.className="msg-enter flex "+(role==="user"?"justify-end":"justify-start");const b=document.createElement("div");b.className=role==="user"?"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-br-md bg-blue-600 text-white text-sm leading-relaxed":"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-bl-md bg-gray-800 text-gray-200 text-sm leading-relaxed";b.textContent=content;d.appendChild(b);const w=messagesEl.querySelector(".text-center.py-12");if(w)w.remove();messagesEl.appendChild(d);messagesEl.scrollTop=messagesEl.scrollHeight;return b}
545
- form.addEventListener("submit",async e=>{e.preventDefault();const text=input.value.trim();if(!text)return;messages.push({role:"user",content:text});addMsg("user",text);input.value="";sendBtn.disabled=true;try{const res=await fetch("/api/chat",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({messages,conversationId:"default"})});if(!res.ok){addMsg("assistant","Error: "+res.status);sendBtn.disabled=false;return}const bubble=addMsg("assistant","");const reader=res.body.getReader();const dec=new TextDecoder();let full="";while(true){const{done,value}=await reader.read();if(done)break;const chunk=dec.decode(value,{stream:true});for(const line of chunk.split("\\n")){if(line.startsWith("data: ")){const d=line.slice(6);if(d==="[DONE]")continue;try{const p=JSON.parse(d);const t=p.textDelta||p.choices?.[0]?.delta?.content||p.content||p.text||"";full+=t;bubble.textContent=full;messagesEl.scrollTop=messagesEl.scrollHeight}catch{}}}}messages.push({role:"assistant",content:full})}catch(err){addMsg("assistant","Error: "+err.message)}sendBtn.disabled=false;input.focus()});
546
- input.focus();
547
- </script>`;
548
- }
549
- function ragAppBody() {
550
- return ` <div class="h-full flex flex-col md:flex-row">
551
- <div class="md:w-80 flex-shrink-0 border-b md:border-b-0 md:border-r border-gray-800 flex flex-col">
552
- <div class="px-4 py-3 border-b border-gray-800"><h2 class="text-sm font-semibold text-white flex items-center gap-2"><svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/></svg>Ingest Documents</h2></div>
553
- <div class="flex-1 p-4 flex flex-col gap-3">
554
- <textarea id="ingestText" rows="6" placeholder="Paste text to ingest..." class="w-full bg-gray-900 border border-gray-700 rounded-lg px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-emerald-500 resize-none"></textarea>
555
- <button id="ingestBtn" onclick="ingestDoc()" class="w-full py-2.5 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-lg transition-colors">Ingest</button>
556
- <div id="ingestStatus" class="text-xs text-gray-500 hidden"></div>
557
- <div class="mt-auto pt-3 border-t border-gray-800"><p class="text-xs text-gray-600">Documents are chunked, embedded, and stored for retrieval.</p></div>
855
+ <h1 className="text-5xl font-bold tracking-tight mb-3 bg-gradient-to-r from-white to-white/60 bg-clip-text text-transparent">
856
+ \${"\${projectName}"}
857
+ </h1>
858
+ <p className="text-muted text-lg mb-10">The AI-first full-stack framework</p>
859
+
860
+ {/* Status cards */}
861
+ <div className="flex gap-4 justify-center mb-10">
862
+ <div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
863
+ <div className="text-xs text-muted mb-1 uppercase tracking-wider">Server</div>
864
+ <div className={\`text-sm font-medium \${status === "ok" ? "text-emerald-400" : "text-red-400"}\`}>
865
+ <span className={\`inline-block w-2 h-2 rounded-full mr-2 \${status === "ok" ? "bg-emerald-400 animate-pulse" : "bg-red-400"}\`} />
866
+ {status}
867
+ </div>
558
868
  </div>
559
- </div>
560
- <div class="flex-1 flex flex-col min-w-0">
561
- <div id="ragMessages" class="flex-1 overflow-y-auto px-4 py-6 space-y-4">
562
- <div class="text-center py-12">
563
- <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-emerald-500/20 to-blue-600/20 border border-emerald-500/20 flex items-center justify-center"><svg class="w-8 h-8 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg></div>
564
- <h2 class="text-xl font-semibold text-white mb-2">Ask your documents</h2>
565
- <p class="text-gray-500 text-sm">Ingest documents on the left, then ask questions here.</p>
869
+ <div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
870
+ <div className="text-xs text-muted mb-1 uppercase tracking-wider">Frontend</div>
871
+ <div className="text-sm font-medium text-emerald-400">
872
+ <span className="inline-block w-2 h-2 rounded-full mr-2 bg-emerald-400 animate-pulse" />
873
+ React + Vite
566
874
  </div>
567
875
  </div>
568
- <div class="flex-shrink-0 border-t border-gray-800 px-4 py-4">
569
- <form id="ragForm" class="flex gap-3">
570
- <input id="ragInput" type="text" placeholder="Ask about your documents..." autocomplete="off" class="flex-1 bg-gray-900 border border-gray-700 rounded-xl px-4 py-3 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-emerald-500 focus:ring-1 focus:ring-emerald-500 transition-colors" />
571
- <button type="submit" id="ragSendBtn" class="px-5 py-3 bg-emerald-600 hover:bg-emerald-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-xl transition-colors">Ask</button>
572
- </form>
876
+ <div className="px-6 py-4 rounded-xl bg-white/5 border border-border backdrop-blur-sm">
877
+ <div className="text-xs text-muted mb-1 uppercase tracking-wider">CSS</div>
878
+ <div className="text-sm font-medium text-sky-400">Tailwind v4</div>
573
879
  </div>
574
880
  </div>
575
- </div>
576
- <script>
577
- const ragEl=document.getElementById("ragMessages");
578
- function addRagMsg(role,c){const d=document.createElement("div");d.className="msg-enter flex "+(role==="user"?"justify-end":"justify-start");const b=document.createElement("div");b.className=role==="user"?"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-br-md bg-emerald-600 text-white text-sm leading-relaxed":"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-bl-md bg-gray-800 text-gray-200 text-sm leading-relaxed";b.textContent=c;d.appendChild(b);const w=ragEl.querySelector(".text-center.py-12");if(w)w.remove();ragEl.appendChild(d);ragEl.scrollTop=ragEl.scrollHeight;return b}
579
- async function ingestDoc(){const text=document.getElementById("ingestText").value.trim();if(!text)return;const btn=document.getElementById("ingestBtn"),st=document.getElementById("ingestStatus");btn.disabled=true;btn.textContent="Ingesting...";st.className="text-xs text-yellow-400";st.textContent="Processing...";st.classList.remove("hidden");try{const res=await fetch("/api/rag/ingest",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({text})});const data=await res.json();if(res.ok){st.className="text-xs text-emerald-400";st.textContent="Ingested "+(data.chunks||0)+" chunks.";document.getElementById("ingestText").value=""}else{st.className="text-xs text-red-400";st.textContent="Error: "+(data.error||res.statusText)}}catch(e){st.className="text-xs text-red-400";st.textContent="Error: "+e.message}btn.disabled=false;btn.textContent="Ingest"}
580
- document.getElementById("ragForm").addEventListener("submit",async e=>{e.preventDefault();const input=document.getElementById("ragInput"),text=input.value.trim();if(!text)return;addRagMsg("user",text);input.value="";document.getElementById("ragSendBtn").disabled=true;try{const res=await fetch("/api/rag/query",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({question:text})});if(!res.ok){addRagMsg("assistant","Error: "+res.status);document.getElementById("ragSendBtn").disabled=false;return}const bubble=addRagMsg("assistant","");const reader=res.body.getReader();const dec=new TextDecoder();let full="";while(true){const{done,value}=await reader.read();if(done)break;const chunk=dec.decode(value,{stream:true});for(const line of chunk.split("\\n")){if(line.startsWith("data: ")){const d=line.slice(6);if(d==="[DONE]")continue;try{const p=JSON.parse(d);const t=p.textDelta||p.choices?.[0]?.delta?.content||p.content||p.text||"";full+=t;bubble.textContent=full;ragEl.scrollTop=ragEl.scrollHeight}catch{}}}};}catch(err){addRagMsg("assistant","Error: "+err.message)}document.getElementById("ragSendBtn").disabled=false;document.getElementById("ragInput").focus()});
581
- document.getElementById("ragInput").focus();
582
- </script>`;
583
- }
584
- function agentAppBody() {
585
- return ` <div class="h-full flex flex-col">
586
- <div id="agentMessages" class="flex-1 overflow-y-auto px-4 py-6 space-y-4">
587
- <div class="text-center py-12">
588
- <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-gradient-to-br from-purple-500/20 to-orange-500/20 border border-purple-500/20 flex items-center justify-center"><svg class="w-8 h-8 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg></div>
589
- <h2 class="text-xl font-semibold text-white mb-2">AI Agent</h2>
590
- <p class="text-gray-500 text-sm">Your agent can use tools to answer questions accurately.</p>
881
+
882
+ {/* Get started */}
883
+ <div className="bg-white/[0.03] border border-border rounded-2xl p-8 text-left backdrop-blur-sm">
884
+ <h2 className="text-sm font-medium text-muted mb-6 uppercase tracking-wider">Get started</h2>
885
+ <div className="space-y-4">
886
+ <div className="flex items-start gap-4">
887
+ <div className="w-8 h-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400 text-sm shrink-0">1</div>
888
+ <div>
889
+ <code className="text-purple-400 text-sm">src/app.tsx</code>
890
+ <p className="text-muted text-sm mt-1">Edit this file to build your UI</p>
891
+ </div>
892
+ </div>
893
+ <div className="flex items-start gap-4">
894
+ <div className="w-8 h-8 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400 text-sm shrink-0">2</div>
895
+ <div>
896
+ <code className="text-blue-400 text-sm">api/</code>
897
+ <p className="text-muted text-sm mt-1">Add API routes here (file-based routing)</p>
898
+ </div>
899
+ </div>
900
+ <div className="flex items-start gap-4">
901
+ <div className="w-8 h-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 text-sm shrink-0">3</div>
902
+ <div>
903
+ <code className="text-emerald-400 text-sm">src/components/</code>
904
+ <p className="text-muted text-sm mt-1">Create React components with Tailwind CSS</p>
905
+ </div>
906
+ </div>
591
907
  </div>
592
908
  </div>
593
- <div class="flex-shrink-0 border-t border-gray-800 px-4 py-4">
594
- <form id="agentForm" class="flex gap-3">
595
- <input id="agentInput" type="text" placeholder="Ask the agent anything..." autocomplete="off" class="flex-1 bg-gray-900 border border-gray-700 rounded-xl px-4 py-3 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 transition-colors" />
596
- <button type="submit" id="agentSendBtn" class="px-5 py-3 bg-purple-600 hover:bg-purple-500 disabled:bg-gray-700 disabled:text-gray-500 text-white text-sm font-medium rounded-xl transition-colors">Run</button>
597
- </form>
909
+
910
+ {/* Links */}
911
+ <div className="flex gap-6 justify-center mt-8">
912
+ <a href="https://github.com/codewithshail/voltx" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
913
+ GitHub \u2192
914
+ </a>
915
+ <a href="https://voltx.co.in" target="_blank" rel="noopener noreferrer" className="text-sm text-muted hover:text-foreground transition-colors">
916
+ Docs \u2192
917
+ </a>
598
918
  </div>
599
919
  </div>
600
- <script>
601
- const agentEl=document.getElementById("agentMessages");
602
- function addAgentMsg(role,c,isStep){const d=document.createElement("div");d.className="msg-enter flex "+(role==="user"?"justify-end":"justify-start");const b=document.createElement("div");if(isStep){b.className="max-w-[85%] px-3 py-2 rounded-lg bg-gray-900 border border-gray-700 text-xs text-gray-400 font-mono"}else{b.className=role==="user"?"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-br-md bg-purple-600 text-white text-sm leading-relaxed":"max-w-[75%] px-4 py-2.5 rounded-2xl rounded-bl-md bg-gray-800 text-gray-200 text-sm leading-relaxed whitespace-pre-wrap"}b.textContent=c;d.appendChild(b);const w=agentEl.querySelector(".text-center.py-12");if(w)w.remove();agentEl.appendChild(d);agentEl.scrollTop=agentEl.scrollHeight;return b}
603
- function addThinking(){const d=document.createElement("div");d.id="thinking";d.className="msg-enter flex justify-start";d.innerHTML='<div class="px-4 py-2.5 rounded-2xl rounded-bl-md bg-gray-800 flex items-center gap-2 text-sm text-gray-400"><svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path></svg>Agent is thinking...</div>';agentEl.appendChild(d);agentEl.scrollTop=agentEl.scrollHeight}
604
- function removeThinking(){const t=document.getElementById("thinking");if(t)t.remove()}
605
- document.getElementById("agentForm").addEventListener("submit",async e=>{e.preventDefault();const input=document.getElementById("agentInput"),text=input.value.trim();if(!text)return;addAgentMsg("user",text);input.value="";document.getElementById("agentSendBtn").disabled=true;addThinking();try{const res=await fetch("/api/agent",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({input:text})});removeThinking();if(!res.ok){const err=await res.json().catch(()=>({}));addAgentMsg("assistant","Error: "+(err.error||res.statusText));document.getElementById("agentSendBtn").disabled=false;return}const data=await res.json();if(data.steps&&data.steps.length>0){for(const s of data.steps){const name=s.tool||s.name||"tool";const inp=typeof s.input==="string"?s.input:JSON.stringify(s.input||{});const out=typeof s.output==="string"?s.output:JSON.stringify(s.output||{});addAgentMsg("assistant","\u{1F527} "+name+"("+inp+")\\n\u2192 "+out.slice(0,300),true)}}addAgentMsg("assistant",data.content||"No response.")}catch(err){removeThinking();addAgentMsg("assistant","Error: "+err.message)}document.getElementById("agentSendBtn").disabled=false;input.focus()});
606
- document.getElementById("agentInput").focus();
607
- </script>`;
608
- }
609
- var fs, path, V, TEMPLATE_DEPS;
920
+ </div>
921
+ );
922
+ }
923
+ `;
924
+ }
925
+ const apiEndpoint = template === "agent-app" ? "/api/agent" : template === "rag-app" ? "/api/rag/query" : "/api/chat";
926
+ const isAgent = template === "agent-app";
927
+ const isRag = template === "rag-app";
928
+ let emptyStateTitle = "Start a conversation";
929
+ let emptyStateHint = "Type a message below to chat with AI";
930
+ let accentColor = "blue";
931
+ let inputPlaceholder = "Type a message...";
932
+ if (isAgent) {
933
+ emptyStateTitle = "Talk to your AI agent";
934
+ emptyStateHint = "The agent can use tools like Calculator and Date/Time";
935
+ accentColor = "purple";
936
+ inputPlaceholder = "Ask the agent anything...";
937
+ } else if (isRag) {
938
+ emptyStateTitle = "Ask your documents";
939
+ emptyStateHint = "Query your knowledge base \u2014 ingest docs via POST /api/rag/ingest";
940
+ accentColor = "emerald";
941
+ inputPlaceholder = "Ask a question about your documents...";
942
+ }
943
+ return `import React, { useState, useRef, useEffect, useCallback } from "react";
944
+
945
+ interface Message {
946
+ id: string;
947
+ role: "user" | "assistant";
948
+ content: string;
949
+ }
950
+
951
+ export default function App() {
952
+ const [messages, setMessages] = useState<Message[]>([]);
953
+ const [input, setInput] = useState("");
954
+ const [isLoading, setIsLoading] = useState(false);
955
+ const messagesEndRef = useRef<HTMLDivElement>(null);
956
+ const inputRef = useRef<HTMLInputElement>(null);
957
+
958
+ useEffect(() => {
959
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
960
+ }, [messages]);
961
+
962
+ useEffect(() => {
963
+ inputRef.current?.focus();
964
+ }, []);
965
+
966
+ const sendMessage = useCallback(async () => {
967
+ const text = input.trim();
968
+ if (!text || isLoading) return;
969
+
970
+ const userMsg: Message = { id: crypto.randomUUID(), role: "user", content: text };
971
+ setMessages((prev) => [...prev, userMsg]);
972
+ setInput("");
973
+ setIsLoading(true);
974
+
975
+ const assistantMsg: Message = { id: crypto.randomUUID(), role: "assistant", content: "" };
976
+ setMessages((prev) => [...prev, assistantMsg]);
977
+
978
+ try {${isAgent ? `
979
+ const res = await fetch("${apiEndpoint}", {
980
+ method: "POST",
981
+ headers: { "Content-Type": "application/json" },
982
+ body: JSON.stringify({ input: text }),
983
+ });
984
+ const data = await res.json();
985
+ setMessages((prev) =>
986
+ prev.map((m) => m.id === assistantMsg.id ? { ...m, content: data.content || "No response" } : m)
987
+ );` : `
988
+ const res = await fetch("${apiEndpoint}", {
989
+ method: "POST",
990
+ headers: { "Content-Type": "application/json" },
991
+ body: JSON.stringify({${isRag ? ` question: text ` : ` messages: [...messages, { role: "user", content: text }] `}}),
992
+ });
993
+
994
+ if (!res.body) throw new Error("No response body");
995
+
996
+ const reader = res.body.getReader();
997
+ const decoder = new TextDecoder();
998
+ let buffer = "";
999
+ let fullContent = "";
1000
+
1001
+ while (true) {
1002
+ const { done, value } = await reader.read();
1003
+ if (done) break;
1004
+ buffer += decoder.decode(value, { stream: true });
1005
+ const lines = buffer.split("\\n");
1006
+ buffer = lines.pop() ?? "";
1007
+
1008
+ for (const line of lines) {
1009
+ if (!line.startsWith("data: ")) continue;
1010
+ const data = line.slice(6);
1011
+ if (data === "[DONE]") break;
1012
+ try {
1013
+ const parsed = JSON.parse(data);
1014
+ const chunk = parsed.textDelta ?? parsed.content ?? parsed.choices?.[0]?.delta?.content ?? "";
1015
+ if (chunk) {
1016
+ fullContent += chunk;
1017
+ setMessages((prev) =>
1018
+ prev.map((m) => m.id === assistantMsg.id ? { ...m, content: fullContent } : m)
1019
+ );
1020
+ }
1021
+ } catch {}
1022
+ }
1023
+ }`}
1024
+ } catch (err) {
1025
+ setMessages((prev) =>
1026
+ prev.map((m) => m.id === assistantMsg.id ? { ...m, content: "Error: " + (err instanceof Error ? err.message : String(err)) } : m)
1027
+ );
1028
+ } finally {
1029
+ setIsLoading(false);
1030
+ }
1031
+ }, [input, isLoading, messages]);
1032
+
1033
+ return (
1034
+ <div className="h-[calc(100vh-60px)] flex flex-col">
1035
+ <main className="flex-1 overflow-y-auto px-4 py-6">
1036
+ <div className="max-w-3xl mx-auto">
1037
+ {messages.length === 0 && (
1038
+ <div className="flex flex-col items-center justify-center h-[60vh] text-center">
1039
+ <div className="relative mb-6">
1040
+ <div className="absolute inset-0 blur-2xl opacity-20 bg-${accentColor}-500 rounded-full" />
1041
+ <div className="relative text-5xl">\u26A1</div>
1042
+ </div>
1043
+ <h2 className="text-2xl font-semibold mb-2">${emptyStateTitle}</h2>
1044
+ <p className="text-muted text-sm max-w-md">${emptyStateHint}</p>
1045
+ <div className="flex gap-2 mt-6">
1046
+ ${isAgent ? `<button onClick={() => { setInput("What is 42 * 17?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1047
+ What is 42 \xD7 17?
1048
+ </button>
1049
+ <button onClick={() => { setInput("What day is it today?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1050
+ What day is it?
1051
+ </button>` : isRag ? `<button onClick={() => { setInput("Summarize the main topics"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1052
+ Summarize main topics
1053
+ </button>
1054
+ <button onClick={() => { setInput("What are the key findings?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1055
+ Key findings
1056
+ </button>` : `<button onClick={() => { setInput("Hello! What can you do?"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1057
+ Hello! What can you do?
1058
+ </button>
1059
+ <button onClick={() => { setInput("Tell me a fun fact"); }} className="px-3 py-1.5 text-xs rounded-full bg-white/5 border border-border text-muted hover:text-foreground hover:border-${accentColor}-500/50 transition-all cursor-pointer">
1060
+ Tell me a fun fact
1061
+ </button>`}
1062
+ </div>
1063
+ </div>
1064
+ )}
1065
+ {messages.map((msg) => (
1066
+ <div key={msg.id} className={\`mb-6 flex \${msg.role === "user" ? "justify-end" : "justify-start"}\`}>
1067
+ <div className={\`flex items-start gap-3 max-w-[80%] \${msg.role === "user" ? "flex-row-reverse" : ""}\`}>
1068
+ <div className={\`w-8 h-8 rounded-full flex items-center justify-center text-sm shrink-0 \${
1069
+ msg.role === "user" ? "bg-${accentColor}-500 text-white" : "bg-white/10 text-muted"
1070
+ }\`}>
1071
+ {msg.role === "user" ? "Y" : "\u26A1"}
1072
+ </div>
1073
+ <div className={\`px-4 py-3 rounded-2xl whitespace-pre-wrap leading-relaxed text-sm \${
1074
+ msg.role === "user"
1075
+ ? "bg-${accentColor}-500 text-white rounded-br-md"
1076
+ : "bg-white/[0.05] border border-border rounded-bl-md"
1077
+ }\`}>
1078
+ {msg.content || (isLoading && msg.role === "assistant" ? (
1079
+ <span className="flex gap-1">
1080
+ <span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:0ms]" />
1081
+ <span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:150ms]" />
1082
+ <span className="w-2 h-2 bg-muted rounded-full animate-bounce [animation-delay:300ms]" />
1083
+ </span>
1084
+ ) : "")}
1085
+ </div>
1086
+ </div>
1087
+ </div>
1088
+ ))}
1089
+ <div ref={messagesEndRef} />
1090
+ </div>
1091
+ </main>
1092
+ <footer className="border-t border-border px-4 py-4">
1093
+ <form onSubmit={(e) => { e.preventDefault(); sendMessage(); }} className="max-w-3xl mx-auto flex gap-3">
1094
+ <input
1095
+ ref={inputRef}
1096
+ value={input}
1097
+ onChange={(e) => setInput(e.target.value)}
1098
+ placeholder="${inputPlaceholder}"
1099
+ disabled={isLoading}
1100
+ className="flex-1 px-4 py-3 rounded-xl bg-white/[0.05] border border-border text-foreground placeholder:text-muted/50 outline-none focus:border-${accentColor}-500/50 focus:ring-1 focus:ring-${accentColor}-500/25 transition-all disabled:opacity-50"
1101
+ />
1102
+ <button
1103
+ type="submit"
1104
+ disabled={isLoading || !input.trim()}
1105
+ className="px-6 py-3 rounded-xl font-medium text-sm transition-all disabled:opacity-30 disabled:cursor-not-allowed bg-${accentColor}-500 hover:bg-${accentColor}-400 text-white cursor-pointer"
1106
+ >
1107
+ {isLoading ? (
1108
+ <svg className="w-5 h-5 animate-spin" viewBox="0 0 24 24" fill="none">
1109
+ <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
1110
+ <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
1111
+ </svg>
1112
+ ) : "Send"}
1113
+ </button>
1114
+ </form>
1115
+ </footer>
1116
+ </div>
1117
+ );
1118
+ }
1119
+ `;
1120
+ }
1121
+ function generateFaviconSVG() {
1122
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
1123
+ <rect width="32" height="32" rx="6" fill="#0a0a0a"/>
1124
+ <path d="M18.5 4L8 18h7l-1.5 10L24 14h-7l1.5-10z" fill="#facc15" stroke="#facc15" stroke-width="0.5" stroke-linejoin="round"/>
1125
+ </svg>
1126
+ `;
1127
+ }
1128
+ function generateRobotsTxt() {
1129
+ return `# https://www.robotstxt.org/robotstxt.html
1130
+ User-agent: *
1131
+ Allow: /
1132
+
1133
+ Sitemap: /sitemap.xml
1134
+ `;
1135
+ }
1136
+ function generateWebManifest(projectName) {
1137
+ return JSON.stringify({
1138
+ name: projectName,
1139
+ short_name: projectName,
1140
+ icons: [
1141
+ { src: "/favicon.svg", sizes: "any", type: "image/svg+xml" }
1142
+ ],
1143
+ theme_color: "#0a0a0a",
1144
+ background_color: "#0a0a0a",
1145
+ display: "standalone"
1146
+ }, null, 2) + "\n";
1147
+ }
1148
+ var fs, path, VV, TEMPLATE_DEPS;
610
1149
  var init_create = __esm({
611
1150
  "src/create.ts"() {
612
1151
  "use strict";
613
1152
  fs = __toESM(require("fs"));
614
1153
  path = __toESM(require("path"));
615
1154
  init_welcome();
616
- V = "^0.3.0";
1155
+ VV = {
1156
+ "@voltx/core": "^0.3.1",
1157
+ "@voltx/server": "^0.3.1",
1158
+ "@voltx/cli": "^0.3.5",
1159
+ "@voltx/ai": "^0.3.0",
1160
+ "@voltx/agents": "^0.3.1",
1161
+ "@voltx/memory": "^0.3.0",
1162
+ "@voltx/db": "^0.3.0",
1163
+ "@voltx/rag": "^0.3.1",
1164
+ "@voltx/auth": "^0.3.0"
1165
+ };
617
1166
  TEMPLATE_DEPS = {
618
- blank: { "@voltx/core": V, "@voltx/server": V },
619
- chatbot: { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/memory": V },
620
- "rag-app": { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/rag": V, "@voltx/db": V },
621
- "agent-app": { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/agents": V, "@voltx/memory": V }
1167
+ blank: { "@voltx/core": v("@voltx/core"), "@voltx/server": v("@voltx/server") },
1168
+ chatbot: { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/memory": v("@voltx/memory") },
1169
+ "rag-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/rag": v("@voltx/rag"), "@voltx/db": v("@voltx/db") },
1170
+ "agent-app": { "@voltx/core": v("@voltx/core"), "@voltx/ai": v("@voltx/ai"), "@voltx/server": v("@voltx/server"), "@voltx/agents": v("@voltx/agents"), "@voltx/memory": v("@voltx/memory") }
622
1171
  };
623
- if (typeof require !== "undefined" && require.main === module && process.argv[1]?.includes("create")) {
624
- const projectName = process.argv[2];
625
- if (!projectName) {
626
- console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
627
- process.exit(1);
628
- }
629
- const templateFlag = process.argv.indexOf("--template");
630
- const template = templateFlag !== -1 ? process.argv[templateFlag + 1] : "blank";
631
- const authFlag = process.argv.indexOf("--auth");
632
- const auth = authFlag !== -1 ? process.argv[authFlag + 1] : "none";
633
- createProject({ name: projectName, template, auth }).catch((err) => {
634
- console.error("[voltx] Error:", err);
635
- process.exit(1);
636
- });
637
- }
638
1172
  }
639
1173
  });
640
1174
 
@@ -651,7 +1185,7 @@ async function runDev(options = {}) {
651
1185
  clearScreen = true
652
1186
  } = options;
653
1187
  if (!entry) {
654
- console.error("[voltx] Could not find entry point. Expected src/index.ts or src/index.js");
1188
+ console.error("[voltx] Could not find entry point. Expected server.ts or src/index.ts");
655
1189
  process.exit(1);
656
1190
  }
657
1191
  const entryPath = (0, import_node_path.resolve)(cwd, entry);
@@ -659,56 +1193,124 @@ async function runDev(options = {}) {
659
1193
  console.error(`[voltx] Entry file not found: ${entry}`);
660
1194
  process.exit(1);
661
1195
  }
662
- const envFile = (0, import_node_path.resolve)(cwd, ".env");
1196
+ (0, import_core.loadEnv)("development", cwd);
1197
+ const hasViteConfig = (0, import_node_fs.existsSync)((0, import_node_path.resolve)(cwd, "vite.config.ts"));
1198
+ const hasServerEntry = (0, import_node_fs.existsSync)((0, import_node_path.resolve)(cwd, "server.ts"));
1199
+ const isFullStack = hasViteConfig || hasServerEntry;
1200
+ if (isFullStack) {
1201
+ await startViteDevServer(cwd, port, entry, options);
1202
+ } else {
1203
+ await startTsxWatch(cwd, port, entry, options);
1204
+ }
1205
+ }
1206
+ async function startViteDevServer(cwd, port, entry, options) {
1207
+ const resolvedPort = port ?? (Number(process.env.PORT) || 3e3);
1208
+ printDevBanner(entry, resolvedPort, true);
1209
+ const userViteConfig = (0, import_node_path.resolve)(cwd, "vite.config.ts");
1210
+ const hasUserViteConfig = (0, import_node_fs.existsSync)(userViteConfig);
1211
+ let tempConfigPath = null;
1212
+ if (!hasUserViteConfig) {
1213
+ tempConfigPath = (0, import_node_path.resolve)(cwd, "vite.config.voltx.ts");
1214
+ const viteConfigContent = generateViteConfig(entry, resolvedPort);
1215
+ (0, import_node_fs.writeFileSync)(tempConfigPath, viteConfigContent, "utf-8");
1216
+ }
663
1217
  const env = {
664
1218
  ...process.env,
665
1219
  NODE_ENV: "development"
666
1220
  };
667
- if ((0, import_node_fs.existsSync)(envFile)) {
668
- const envContent = (0, import_node_fs.readFileSync)(envFile, "utf-8");
669
- for (const line of envContent.split("\n")) {
670
- const trimmed = line.trim();
671
- if (!trimmed || trimmed.startsWith("#")) continue;
672
- const eqIdx = trimmed.indexOf("=");
673
- if (eqIdx === -1) continue;
674
- const key = trimmed.slice(0, eqIdx).trim();
675
- const value = trimmed.slice(eqIdx + 1).trim();
676
- env[key] = value;
1221
+ if (port) {
1222
+ env.PORT = String(port);
1223
+ }
1224
+ const viteBin = findBin(cwd, "vite");
1225
+ const viteArgs = ["dev"];
1226
+ if (tempConfigPath) {
1227
+ viteArgs.push("--config", tempConfigPath);
1228
+ }
1229
+ viteArgs.push("--port", String(resolvedPort));
1230
+ let child;
1231
+ if (viteBin) {
1232
+ child = (0, import_node_child_process.spawn)(viteBin, viteArgs, { cwd, env, stdio: "inherit" });
1233
+ } else {
1234
+ child = (0, import_node_child_process.spawn)("npx", ["vite", ...viteArgs], { cwd, env, stdio: "inherit" });
1235
+ }
1236
+ const cleanup = () => {
1237
+ if (tempConfigPath && (0, import_node_fs.existsSync)(tempConfigPath)) {
1238
+ try {
1239
+ (0, import_node_fs.unlinkSync)(tempConfigPath);
1240
+ } catch {
1241
+ }
677
1242
  }
1243
+ };
1244
+ const signals = ["SIGINT", "SIGTERM"];
1245
+ for (const signal of signals) {
1246
+ process.on(signal, () => {
1247
+ cleanup();
1248
+ child.kill(signal);
1249
+ });
678
1250
  }
1251
+ child.on("error", (err) => {
1252
+ cleanup();
1253
+ if (err.code === "ENOENT") {
1254
+ console.error("[voltx] vite not found. Install it with: npm install -D vite @hono/vite-dev-server");
1255
+ } else {
1256
+ console.error("[voltx] Dev server error:", err.message);
1257
+ }
1258
+ process.exit(1);
1259
+ });
1260
+ child.on("exit", (code) => {
1261
+ cleanup();
1262
+ process.exit(code ?? 0);
1263
+ });
1264
+ }
1265
+ function generateViteConfig(entry, port) {
1266
+ return `// Auto-generated by VoltX \u2014 do not commit this file
1267
+ import { defineConfig } from "vite";
1268
+ import devServer from "@hono/vite-dev-server";
1269
+
1270
+ export default defineConfig({
1271
+ server: {
1272
+ port: ${port},
1273
+ },
1274
+ plugins: [
1275
+ devServer({
1276
+ entry: "${entry}",
1277
+ exclude: [
1278
+ /.*\\.tsx?($|\\?)/,
1279
+ /.*\\.(s?css|less)($|\\?)/,
1280
+ /.*\\.(svg|png|jpg|jpeg|gif|webp|ico)($|\\?)/,
1281
+ /^\\/@.+$/,
1282
+ /^\\/favicon\\.svg$/,
1283
+ /^\\/node_modules\\/.*/,
1284
+ /^\\/src\\/.*/,
1285
+ ],
1286
+ injectClientScript: false,
1287
+ }),
1288
+ ],
1289
+ });
1290
+ `;
1291
+ }
1292
+ async function startTsxWatch(cwd, port, entry, options) {
1293
+ const resolvedPort = port ?? (Number(process.env.PORT) || 3e3);
1294
+ printDevBanner(entry, resolvedPort, false);
1295
+ const env = {
1296
+ ...process.env,
1297
+ NODE_ENV: "development"
1298
+ };
679
1299
  if (port) {
680
1300
  env.PORT = String(port);
681
1301
  }
682
- printDevBanner(entry, port);
683
1302
  const tsxArgs = ["watch"];
684
- if (clearScreen) {
1303
+ if (options.clearScreen ?? true) {
685
1304
  tsxArgs.push("--clear-screen=false");
686
1305
  }
687
- const watchDirs = [
688
- "src/routes",
689
- "src/agents",
690
- "src/tools",
691
- "src/jobs",
692
- "src/lib",
693
- "voltx.config.ts",
694
- ...options.watch ?? []
695
- ];
696
1306
  tsxArgs.push("--ignore=node_modules", "--ignore=dist", "--ignore=.turbo");
697
1307
  tsxArgs.push(entry);
698
- const tsxBin = findTsxBin(cwd);
1308
+ const tsxBin = findBin(cwd, "tsx");
699
1309
  let child;
700
1310
  if (tsxBin) {
701
- child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, {
702
- cwd,
703
- env,
704
- stdio: "inherit"
705
- });
1311
+ child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, { cwd, env, stdio: "inherit" });
706
1312
  } else {
707
- child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], {
708
- cwd,
709
- env,
710
- stdio: "inherit"
711
- });
1313
+ child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], { cwd, env, stdio: "inherit" });
712
1314
  }
713
1315
  const signals = ["SIGINT", "SIGTERM"];
714
1316
  for (const signal of signals) {
@@ -719,7 +1321,6 @@ async function runDev(options = {}) {
719
1321
  child.on("error", (err) => {
720
1322
  if (err.code === "ENOENT") {
721
1323
  console.error("[voltx] tsx not found. Install it with: npm install -D tsx");
722
- console.error("[voltx] Or run your app directly: npx tsx watch src/index.ts");
723
1324
  } else {
724
1325
  console.error("[voltx] Dev server error:", err.message);
725
1326
  }
@@ -731,6 +1332,8 @@ async function runDev(options = {}) {
731
1332
  }
732
1333
  function findEntryPoint(cwd) {
733
1334
  const candidates = [
1335
+ "server.ts",
1336
+ "server.js",
734
1337
  "src/index.ts",
735
1338
  "src/index.js",
736
1339
  "src/index.mts",
@@ -746,34 +1349,38 @@ function findEntryPoint(cwd) {
746
1349
  }
747
1350
  return null;
748
1351
  }
749
- function findTsxBin(cwd) {
750
- const localBin = (0, import_node_path.join)(cwd, "node_modules", ".bin", "tsx");
751
- if ((0, import_node_fs.existsSync)(localBin)) return localBin;
752
- const parentBin = (0, import_node_path.join)(cwd, "..", "node_modules", ".bin", "tsx");
753
- if ((0, import_node_fs.existsSync)(parentBin)) return parentBin;
754
- const rootBin = (0, import_node_path.join)(cwd, "..", "..", "node_modules", ".bin", "tsx");
755
- if ((0, import_node_fs.existsSync)(rootBin)) return rootBin;
1352
+ function findBin(cwd, name) {
1353
+ const paths = [
1354
+ (0, import_node_path.join)(cwd, "node_modules", ".bin", name),
1355
+ (0, import_node_path.join)(cwd, "..", "node_modules", ".bin", name),
1356
+ (0, import_node_path.join)(cwd, "..", "..", "node_modules", ".bin", name)
1357
+ ];
1358
+ for (const p of paths) {
1359
+ if ((0, import_node_fs.existsSync)(p)) return p;
1360
+ }
756
1361
  return null;
757
1362
  }
758
- function printDevBanner(entry, port) {
1363
+ function printDevBanner(entry, port, fullStack) {
759
1364
  console.log("");
760
1365
  console.log(" \u26A1 VoltX Dev Server");
761
1366
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
762
1367
  console.log(` Entry: ${entry}`);
763
- if (port) {
764
- console.log(` Port: ${port}`);
1368
+ console.log(` Port: ${port}`);
1369
+ console.log(` Mode: ${fullStack ? "full-stack (Vite + Hono)" : "API-only (tsx watch)"}`);
1370
+ if (fullStack) {
1371
+ console.log(` HMR: enabled (frontend + backend)`);
765
1372
  }
766
- console.log(` Mode: development (hot reload)`);
767
1373
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
768
1374
  console.log("");
769
1375
  }
770
- var import_node_child_process, import_node_path, import_node_fs;
1376
+ var import_node_child_process, import_node_path, import_node_fs, import_core;
771
1377
  var init_dev = __esm({
772
1378
  "src/dev.ts"() {
773
1379
  "use strict";
774
1380
  import_node_child_process = require("child_process");
775
1381
  import_node_path = require("path");
776
1382
  import_node_fs = require("fs");
1383
+ import_core = require("@voltx/core");
777
1384
  }
778
1385
  });
779
1386
 
@@ -791,7 +1398,7 @@ async function runBuild(options = {}) {
791
1398
  sourcemap = false
792
1399
  } = options;
793
1400
  if (!entry) {
794
- console.error("[voltx] Could not find entry point. Expected src/index.ts");
1401
+ console.error("[voltx] Could not find entry point. Expected server.ts or src/index.ts");
795
1402
  process.exit(1);
796
1403
  }
797
1404
  const entryPath = (0, import_node_path2.resolve)(cwd, entry);
@@ -799,59 +1406,87 @@ async function runBuild(options = {}) {
799
1406
  console.error(`[voltx] Entry file not found: ${entry}`);
800
1407
  process.exit(1);
801
1408
  }
1409
+ (0, import_core2.loadEnv)("production", cwd);
1410
+ const hasViteConfig = (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(cwd, "vite.config.ts"));
1411
+ const hasServerEntry = (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(cwd, "server.ts"));
1412
+ const isFullStack = hasViteConfig || hasServerEntry;
802
1413
  console.log("");
803
1414
  console.log(" \u26A1 VoltX Build");
804
1415
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
805
1416
  console.log(` Entry: ${entry}`);
806
1417
  console.log(` Output: ${outDir}/`);
1418
+ console.log(` Mode: ${isFullStack ? "full-stack" : "API-only"}`);
807
1419
  console.log(` Minify: ${minify}`);
808
1420
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
809
1421
  console.log("");
810
1422
  (0, import_node_fs2.mkdirSync)((0, import_node_path2.resolve)(cwd, outDir), { recursive: true });
811
- console.log(" [1/2] Building server...");
1423
+ if (isFullStack) {
1424
+ await buildFullStack(cwd, entry, outDir, minify, sourcemap);
1425
+ } else {
1426
+ await buildApiOnly(cwd, entry, outDir, minify, sourcemap);
1427
+ }
1428
+ console.log("");
1429
+ console.log(" \u26A1 Build complete!");
1430
+ console.log(` Run \`voltx start\` to start the production server.`);
1431
+ console.log("");
1432
+ }
1433
+ async function buildFullStack(cwd, entry, outDir, minify, sourcemap) {
1434
+ const ssrEntry = (0, import_node_fs2.existsSync)((0, import_node_path2.join)(cwd, "src", "entry-server.tsx")) ? "src/entry-server.tsx" : null;
1435
+ const totalSteps = ssrEntry ? 3 : 2;
1436
+ console.log(` [1/${totalSteps}] Building client...`);
1437
+ const viteBin = findBin2(cwd, "vite");
1438
+ const clientOutDir = (0, import_node_path2.join)(outDir, "client");
1439
+ await runCommand(
1440
+ viteBin ?? "npx",
1441
+ viteBin ? ["build", "--outDir", clientOutDir] : ["vite", "build", "--outDir", clientOutDir],
1442
+ cwd
1443
+ );
1444
+ console.log(" \u2713 Client built");
1445
+ if (ssrEntry) {
1446
+ console.log(` [2/${totalSteps}] Building SSR bundle...`);
1447
+ const serverOutDir = (0, import_node_path2.join)(outDir, "server");
1448
+ await runCommand(
1449
+ viteBin ?? "npx",
1450
+ viteBin ? ["build", "--ssr", ssrEntry, "--outDir", serverOutDir] : ["vite", "build", "--ssr", ssrEntry, "--outDir", serverOutDir],
1451
+ cwd
1452
+ );
1453
+ console.log(" \u2713 SSR bundle built");
1454
+ }
1455
+ const serverStep = ssrEntry ? 3 : 2;
1456
+ console.log(` [${serverStep}/${totalSteps}] Building server...`);
1457
+ await buildServer(cwd, entry, outDir, minify, sourcemap, false);
1458
+ console.log(" \u2713 Server built");
1459
+ }
1460
+ async function buildApiOnly(cwd, entry, outDir, minify, sourcemap) {
1461
+ console.log(" [1/1] Building server...");
1462
+ await buildServer(cwd, entry, outDir, minify, sourcemap);
1463
+ console.log(" \u2713 Server built");
1464
+ }
1465
+ async function buildServer(cwd, entry, outDir, minify, sourcemap, clean = true) {
1466
+ const tsupBin = findBin2(cwd, "tsup");
812
1467
  const tsupArgs = [
813
1468
  entry,
814
1469
  "--format",
815
1470
  "esm",
816
1471
  "--out-dir",
817
1472
  outDir,
818
- "--clean",
819
1473
  "--target",
820
- "node20"
1474
+ "node20",
1475
+ "--no-splitting"
821
1476
  ];
1477
+ if (clean) tsupArgs.push("--clean");
822
1478
  if (minify) tsupArgs.push("--minify");
823
1479
  if (sourcemap) tsupArgs.push("--sourcemap");
824
- tsupArgs.push("--no-splitting");
825
- const tsupBin = findBin(cwd, "tsup");
826
1480
  await runCommand(
827
1481
  tsupBin ?? "npx",
828
1482
  tsupBin ? tsupArgs : ["tsup", ...tsupArgs],
829
1483
  cwd
830
1484
  );
831
- console.log(" \u2713 Server built successfully");
832
- const frontendDir = (0, import_node_path2.resolve)(cwd, "src", "frontend");
833
- const viteConfig = (0, import_node_path2.resolve)(cwd, "vite.config.ts");
834
- const hasFrontend = (0, import_node_fs2.existsSync)(frontendDir) || (0, import_node_fs2.existsSync)(viteConfig);
835
- if (hasFrontend) {
836
- console.log(" [2/2] Building frontend...");
837
- const viteBin = findBin(cwd, "vite");
838
- const viteArgs = ["build", "--outDir", (0, import_node_path2.join)(outDir, "public")];
839
- await runCommand(
840
- viteBin ?? "npx",
841
- viteBin ? viteArgs : ["vite", ...viteArgs],
842
- cwd
843
- );
844
- console.log(" \u2713 Frontend built successfully");
845
- } else {
846
- console.log(" [2/2] No frontend found, skipping...");
847
- }
848
- console.log("");
849
- console.log(" \u26A1 Build complete!");
850
- console.log(` Run \`voltx start\` to start the production server.`);
851
- console.log("");
852
1485
  }
853
1486
  function findEntryPoint2(cwd) {
854
1487
  const candidates = [
1488
+ "server.ts",
1489
+ "server.js",
855
1490
  "src/index.ts",
856
1491
  "src/index.js",
857
1492
  "src/index.mts",
@@ -865,7 +1500,7 @@ function findEntryPoint2(cwd) {
865
1500
  }
866
1501
  return null;
867
1502
  }
868
- function findBin(cwd, name) {
1503
+ function findBin2(cwd, name) {
869
1504
  const paths = [
870
1505
  (0, import_node_path2.join)(cwd, "node_modules", ".bin", name),
871
1506
  (0, import_node_path2.join)(cwd, "..", "node_modules", ".bin", name),
@@ -895,13 +1530,14 @@ function runCommand(cmd, args2, cwd) {
895
1530
  });
896
1531
  });
897
1532
  }
898
- var import_node_child_process2, import_node_path2, import_node_fs2;
1533
+ var import_node_child_process2, import_node_path2, import_node_fs2, import_core2;
899
1534
  var init_build = __esm({
900
1535
  "src/build.ts"() {
901
1536
  "use strict";
902
1537
  import_node_child_process2 = require("child_process");
903
1538
  import_node_path2 = require("path");
904
1539
  import_node_fs2 = require("fs");
1540
+ import_core2 = require("@voltx/core");
905
1541
  }
906
1542
  });
907
1543
 
@@ -930,26 +1566,15 @@ async function runStart(options = {}) {
930
1566
  console.error(`[voltx] Entry file not found: ${outDir}/${entry}`);
931
1567
  process.exit(1);
932
1568
  }
1569
+ (0, import_core3.loadEnv)("production", cwd);
933
1570
  const env = {
934
1571
  ...process.env,
935
1572
  NODE_ENV: "production"
936
1573
  };
937
- const envFile = (0, import_node_path3.resolve)(cwd, ".env");
938
- if ((0, import_node_fs3.existsSync)(envFile)) {
939
- const envContent = (0, import_node_fs3.readFileSync)(envFile, "utf-8");
940
- for (const line of envContent.split("\n")) {
941
- const trimmed = line.trim();
942
- if (!trimmed || trimmed.startsWith("#")) continue;
943
- const eqIdx = trimmed.indexOf("=");
944
- if (eqIdx === -1) continue;
945
- const key = trimmed.slice(0, eqIdx).trim();
946
- const value = trimmed.slice(eqIdx + 1).trim();
947
- env[key] = value;
948
- }
949
- }
950
1574
  if (port) {
951
1575
  env.PORT = String(port);
952
1576
  }
1577
+ const hasClientBuild = (0, import_node_fs3.existsSync)((0, import_node_path3.resolve)(distDir, "client"));
953
1578
  console.log("");
954
1579
  console.log(" \u26A1 VoltX Production Server");
955
1580
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
@@ -957,7 +1582,7 @@ async function runStart(options = {}) {
957
1582
  if (port) {
958
1583
  console.log(` Port: ${port}`);
959
1584
  }
960
- console.log(` Mode: production`);
1585
+ console.log(` Mode: ${hasClientBuild ? "full-stack" : "API-only"}`);
961
1586
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
962
1587
  console.log("");
963
1588
  const child = (0, import_node_child_process3.spawn)("node", [entryPath], {
@@ -981,13 +1606,13 @@ async function runStart(options = {}) {
981
1606
  }
982
1607
  function findDistEntry(distDir) {
983
1608
  const candidates = [
1609
+ "server.mjs",
1610
+ "server.js",
984
1611
  "index.mjs",
985
1612
  "index.js",
986
1613
  "index.cjs",
987
1614
  "main.mjs",
988
- "main.js",
989
- "src/index.mjs",
990
- "src/index.js"
1615
+ "main.js"
991
1616
  ];
992
1617
  for (const candidate of candidates) {
993
1618
  if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(distDir, candidate))) {
@@ -996,13 +1621,14 @@ function findDistEntry(distDir) {
996
1621
  }
997
1622
  return null;
998
1623
  }
999
- var import_node_child_process3, import_node_path3, import_node_fs3;
1624
+ var import_node_child_process3, import_node_path3, import_node_fs3, import_core3;
1000
1625
  var init_start = __esm({
1001
1626
  "src/start.ts"() {
1002
1627
  "use strict";
1003
1628
  import_node_child_process3 = require("child_process");
1004
1629
  import_node_path3 = require("path");
1005
1630
  import_node_fs3 = require("fs");
1631
+ import_core3 = require("@voltx/core");
1006
1632
  }
1007
1633
  });
1008
1634
 
@@ -1035,13 +1661,13 @@ async function runGenerate(options) {
1035
1661
  }
1036
1662
  function generateRoute(cwd, name, method = "POST") {
1037
1663
  const routePath = name.startsWith("/") ? name.slice(1) : name;
1038
- const filePath = (0, import_node_path4.join)(cwd, "src", "routes", `${routePath}.ts`);
1664
+ const filePath = (0, import_node_path4.join)(cwd, "api", `${routePath}.ts`);
1039
1665
  if ((0, import_node_fs4.existsSync)(filePath)) {
1040
- console.error(`[voltx] Route already exists: src/routes/${routePath}.ts`);
1666
+ console.error(`[voltx] Route already exists: api/${routePath}.ts`);
1041
1667
  process.exit(1);
1042
1668
  }
1043
1669
  const upperMethod = method.toUpperCase();
1044
- const urlPath = "/" + routePath;
1670
+ const urlPath = "/api/" + routePath;
1045
1671
  const content = `// ${upperMethod} ${urlPath}
1046
1672
  import type { Context } from "@voltx/server";
1047
1673
 
@@ -1050,13 +1676,13 @@ export async function ${upperMethod}(c: Context) {
1050
1676
  }
1051
1677
  `;
1052
1678
  writeFileSafe(filePath, content);
1053
- console.log(` \u2713 Created route: src/routes/${routePath}.ts`);
1679
+ console.log(` \u2713 Created route: api/${routePath}.ts`);
1054
1680
  console.log(` ${upperMethod} ${urlPath}`);
1055
1681
  }
1056
1682
  function generateAgent(cwd, name) {
1057
- const filePath = (0, import_node_path4.join)(cwd, "src", "agents", `${name}.ts`);
1683
+ const filePath = (0, import_node_path4.join)(cwd, "agents", `${name}.ts`);
1058
1684
  if ((0, import_node_fs4.existsSync)(filePath)) {
1059
- console.error(`[voltx] Agent already exists: src/agents/${name}.ts`);
1685
+ console.error(`[voltx] Agent already exists: agents/${name}.ts`);
1060
1686
  process.exit(1);
1061
1687
  }
1062
1688
  const content = `// Agent: ${name}
@@ -1078,12 +1704,12 @@ export const ${toCamelCase(name)} = createAgent({
1078
1704
  });
1079
1705
  `;
1080
1706
  writeFileSafe(filePath, content);
1081
- console.log(` \u2713 Created agent: src/agents/${name}.ts`);
1707
+ console.log(` \u2713 Created agent: agents/${name}.ts`);
1082
1708
  }
1083
1709
  function generateTool(cwd, name) {
1084
- const filePath = (0, import_node_path4.join)(cwd, "src", "tools", `${name}.ts`);
1710
+ const filePath = (0, import_node_path4.join)(cwd, "tools", `${name}.ts`);
1085
1711
  if ((0, import_node_fs4.existsSync)(filePath)) {
1086
- console.error(`[voltx] Tool already exists: src/tools/${name}.ts`);
1712
+ console.error(`[voltx] Tool already exists: tools/${name}.ts`);
1087
1713
  process.exit(1);
1088
1714
  }
1089
1715
  const content = `// Tool: ${name}
@@ -1105,12 +1731,12 @@ export const ${toCamelCase(name)}Tool = {
1105
1731
  };
1106
1732
  `;
1107
1733
  writeFileSafe(filePath, content);
1108
- console.log(` \u2713 Created tool: src/tools/${name}.ts`);
1734
+ console.log(` \u2713 Created tool: tools/${name}.ts`);
1109
1735
  }
1110
1736
  function generateJob(cwd, name) {
1111
- const filePath = (0, import_node_path4.join)(cwd, "src", "jobs", `${name}.ts`);
1737
+ const filePath = (0, import_node_path4.join)(cwd, "jobs", `${name}.ts`);
1112
1738
  if ((0, import_node_fs4.existsSync)(filePath)) {
1113
- console.error(`[voltx] Job already exists: src/jobs/${name}.ts`);
1739
+ console.error(`[voltx] Job already exists: jobs/${name}.ts`);
1114
1740
  process.exit(1);
1115
1741
  }
1116
1742
  const content = `// Job: ${name}
@@ -1135,7 +1761,7 @@ export async function run(ctx: any, data?: Record<string, unknown>) {
1135
1761
  }
1136
1762
  `;
1137
1763
  writeFileSafe(filePath, content);
1138
- console.log(` \u2713 Created job: src/jobs/${name}.ts`);
1764
+ console.log(` \u2713 Created job: jobs/${name}.ts`);
1139
1765
  }
1140
1766
  function writeFileSafe(filePath, content) {
1141
1767
  const dir = (0, import_node_path4.dirname)(filePath);
@@ -1197,13 +1823,14 @@ async function main() {
1197
1823
  case "create": {
1198
1824
  const projectName = args[1];
1199
1825
  if (!projectName || projectName.startsWith("-")) {
1200
- console.error("[voltx] Usage: voltx create <project-name> [--template chatbot|rag-app|agent-app] [--auth better-auth|jwt|none]");
1826
+ console.error("[voltx] Usage: voltx create <project-name> [--template chatbot|rag-app|agent-app] [--auth better-auth|jwt|none] [--shadcn]");
1201
1827
  process.exit(1);
1202
1828
  }
1203
1829
  const template = parseFlag("--template");
1204
1830
  const auth = parseFlag("--auth");
1831
+ const useShadcn = args.includes("--shadcn");
1205
1832
  const { createProject: createProject2 } = await Promise.resolve().then(() => (init_create(), create_exports));
1206
- await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none" });
1833
+ await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none", shadcn: useShadcn });
1207
1834
  break;
1208
1835
  }
1209
1836
  // ─── voltx dev ─────────────────────────────────────────────────────
@@ -1246,7 +1873,7 @@ async function main() {
1246
1873
  console.error("[voltx] Usage: voltx generate <type> <name>");
1247
1874
  console.error("");
1248
1875
  console.error(" Types:");
1249
- console.error(" route <path> Generate a new API route (e.g., api/users)");
1876
+ console.error(" route <path> Generate a new API route (e.g., users)");
1250
1877
  console.error(" agent <name> Generate a new agent (e.g., assistant)");
1251
1878
  console.error(" tool <name> Generate a new tool (e.g., search)");
1252
1879
  console.error(" job <name> Generate a new background job (e.g., cleanup)");
@@ -1299,7 +1926,7 @@ function printHelp() {
1299
1926
 
1300
1927
  Dev Options:
1301
1928
  --port, -p <number> Port override
1302
- --entry <file> Custom entry file (default: src/index.ts)
1929
+ --entry <file> Custom entry file (default: server.ts)
1303
1930
  --no-clear Don't clear screen on restart
1304
1931
 
1305
1932
  Build Options:
@@ -1314,7 +1941,7 @@ function printHelp() {
1314
1941
  --entry <file> Entry file within output dir
1315
1942
 
1316
1943
  Generate Types:
1317
- route <path> API route (e.g., voltx generate route api/users)
1944
+ route <path> API route (e.g., voltx generate route users)
1318
1945
  agent <name> Agent (e.g., voltx generate agent assistant)
1319
1946
  tool <name> Tool (e.g., voltx generate tool search)
1320
1947
  job <name> Background job (e.g., voltx generate job cleanup)
@@ -1324,7 +1951,7 @@ function printHelp() {
1324
1951
  voltx dev --port 4000
1325
1952
  voltx build --sourcemap
1326
1953
  voltx start
1327
- voltx generate route api/users --method GET
1954
+ voltx generate route users --method GET
1328
1955
  voltx generate agent assistant
1329
1956
  `);
1330
1957
  }