@voltx/cli 0.3.4 → 0.3.5

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