@voltx/cli 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ---
13
13
 
14
- Command-line tools for the [VoltX](https://github.com/codewithshail/voltx) framework. Scaffold new projects, run the dev server, and build for production.
14
+ Command-line tools for the [VoltX](https://github.com/codewithshail/voltx) framework. Scaffold projects, run the dev server, build for production, and generate code.
15
15
 
16
16
  ## Installation
17
17
 
@@ -22,11 +22,19 @@ npm install -g @voltx/cli
22
22
  Or use directly with npx:
23
23
 
24
24
  ```bash
25
- npx @voltx/cli create my-app
25
+ npx @voltx/cli dev
26
26
  ```
27
27
 
28
28
  ## Commands
29
29
 
30
+ ### Development
31
+
32
+ ```bash
33
+ voltx dev # Start dev server with hot reload
34
+ voltx build # Build for production
35
+ voltx start # Start production server
36
+ ```
37
+
30
38
  ### Create a New Project
31
39
 
32
40
  ```bash
@@ -36,14 +44,27 @@ voltx create my-app --template rag-app
36
44
  voltx create my-app --template agent-app
37
45
  ```
38
46
 
39
- ### Available Templates
47
+ > For the full interactive experience with tool selection, RAG toggle, and API key management, use `npx create-voltx-app` instead.
48
+
49
+ ### Code Generation
50
+
51
+ ```bash
52
+ voltx generate route api/users # New API route
53
+ voltx generate agent assistant # New agent definition
54
+ voltx generate tool search # New tool for agents
55
+ voltx generate job cleanup # New background job
56
+ ```
57
+
58
+ ## Templates
59
+
60
+ | Template | What you get | Frontend UI |
61
+ |----------|-------------|-------------|
62
+ | `blank` | Minimal server with file-based routing | — |
63
+ | `chatbot` | Streaming chat + memory | Chat interface |
64
+ | `rag-app` | Document Q&A + vector search | Ingest + query split view |
65
+ | `agent-app` | AI agent with calculator + datetime tools | Chat with tool steps |
40
66
 
41
- | Template | Description |
42
- |----------|-------------|
43
- | `blank` | Minimal server with file-based routing |
44
- | `chatbot` | Streaming chat with AI + conversation memory |
45
- | `rag-app` | Document Q&A with embeddings + vector search |
46
- | `agent-app` | Autonomous agent with tools + memory |
67
+ All non-blank templates include a `public/index.html` with a dark-theme Tailwind CSS UI that connects to the backend API routes.
47
68
 
48
69
  ## Programmatic Usage
49
70
 
@@ -53,6 +74,7 @@ import { createProject } from "@voltx/cli";
53
74
  await createProject({
54
75
  name: "my-app",
55
76
  template: "chatbot",
77
+ auth: "jwt",
56
78
  });
57
79
  ```
58
80
 
@@ -0,0 +1,456 @@
1
+ import {
2
+ printWelcomeBanner
3
+ } from "./chunk-IV352HZA.mjs";
4
+ import {
5
+ __require
6
+ } from "./chunk-Y6FXYEAI.mjs";
7
+
8
+ // src/create.ts
9
+ import * as fs from "fs";
10
+ import * as path from "path";
11
+ async function createProject(options) {
12
+ const { name, template = "blank", auth = "none" } = options;
13
+ const targetDir = path.resolve(process.cwd(), name);
14
+ if (fs.existsSync(targetDir)) {
15
+ console.error(`[voltx] Directory "${name}" already exists.`);
16
+ process.exit(1);
17
+ }
18
+ fs.mkdirSync(targetDir, { recursive: true });
19
+ const templateDeps = {
20
+ blank: {
21
+ "@voltx/core": "^0.3.0",
22
+ "@voltx/server": "^0.3.0"
23
+ },
24
+ chatbot: {
25
+ "@voltx/core": "^0.3.0",
26
+ "@voltx/ai": "^0.3.0",
27
+ "@voltx/server": "^0.3.0",
28
+ "@voltx/memory": "^0.3.0"
29
+ },
30
+ "rag-app": {
31
+ "@voltx/core": "^0.3.0",
32
+ "@voltx/ai": "^0.3.0",
33
+ "@voltx/server": "^0.3.0",
34
+ "@voltx/rag": "^0.3.0",
35
+ "@voltx/db": "^0.3.0"
36
+ },
37
+ "agent-app": {
38
+ "@voltx/core": "^0.3.0",
39
+ "@voltx/ai": "^0.3.0",
40
+ "@voltx/server": "^0.3.0",
41
+ "@voltx/agents": "^0.3.0",
42
+ "@voltx/memory": "^0.3.0"
43
+ }
44
+ };
45
+ const packageJson = {
46
+ name,
47
+ version: "0.1.0",
48
+ private: true,
49
+ scripts: {
50
+ dev: "voltx dev",
51
+ build: "voltx build",
52
+ start: "voltx start"
53
+ },
54
+ dependencies: {
55
+ ...templateDeps[template] ?? templateDeps["blank"],
56
+ "@voltx/cli": "^0.3.0",
57
+ ...auth === "better-auth" ? { "@voltx/auth": "^0.3.0", "better-auth": "^1.5.0" } : {},
58
+ ...auth === "jwt" ? { "@voltx/auth": "^0.3.0", "jose": "^6.0.0" } : {}
59
+ },
60
+ devDependencies: {
61
+ typescript: "^5.7.0",
62
+ tsx: "^4.21.0",
63
+ tsup: "^8.0.0",
64
+ "@types/node": "^22.0.0"
65
+ }
66
+ };
67
+ fs.writeFileSync(
68
+ path.join(targetDir, "package.json"),
69
+ JSON.stringify(packageJson, null, 2)
70
+ );
71
+ const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
72
+ const provider = template === "rag-app" ? "openai" : "cerebras";
73
+ const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
74
+ let configContent = `import { defineConfig } from "@voltx/core";
75
+
76
+ export default defineConfig({
77
+ name: "${name}",
78
+ port: 3000,
79
+ ai: {
80
+ provider: "${provider}",
81
+ model: "${model}",
82
+ },`;
83
+ if (hasDb) {
84
+ configContent += `
85
+ db: {
86
+ url: process.env.DATABASE_URL,
87
+ },`;
88
+ }
89
+ if (auth !== "none") {
90
+ configContent += `
91
+ auth: {
92
+ provider: "${auth}",
93
+ },`;
94
+ }
95
+ configContent += `
96
+ server: {
97
+ routesDir: "src/routes",
98
+ staticDir: "public",
99
+ cors: true,
100
+ },
101
+ });
102
+ `;
103
+ fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), configContent);
104
+ fs.mkdirSync(path.join(targetDir, "src", "routes", "api"), { recursive: true });
105
+ fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
106
+ fs.writeFileSync(
107
+ path.join(targetDir, "src", "index.ts"),
108
+ `import { createApp } from "@voltx/core";
109
+ import config from "../voltx.config";
110
+
111
+ const app = createApp(config);
112
+ app.start();
113
+ `
114
+ );
115
+ fs.writeFileSync(
116
+ path.join(targetDir, "src", "routes", "index.ts"),
117
+ `// GET / \u2014 Health check
118
+ import type { Context } from "@voltx/server";
119
+
120
+ export function GET(c: Context) {
121
+ return c.json({ name: "${name}", status: "ok" });
122
+ }
123
+ `
124
+ );
125
+ if (template === "chatbot" || template === "agent-app") {
126
+ fs.writeFileSync(
127
+ path.join(targetDir, "src", "routes", "api", "chat.ts"),
128
+ `// POST /api/chat \u2014 Streaming chat with conversation memory
129
+ import type { Context } from "@voltx/server";
130
+ import { streamText } from "@voltx/ai";
131
+ import { createMemory } from "@voltx/memory";
132
+
133
+ // In-memory for dev; swap to createMemory("postgres", { url }) for production
134
+ const memory = createMemory({ maxMessages: 50 });
135
+
136
+ export async function POST(c: Context) {
137
+ const { messages, conversationId = "default" } = await c.req.json();
138
+
139
+ // Store the latest user message
140
+ const lastMessage = messages[messages.length - 1];
141
+ if (lastMessage?.role === "user") {
142
+ await memory.add(conversationId, { role: "user", content: lastMessage.content });
143
+ }
144
+
145
+ // Get conversation history from memory
146
+ const history = await memory.get(conversationId);
147
+
148
+ const result = await streamText({
149
+ model: "${provider}:${model}",
150
+ system: "You are a helpful AI assistant.",
151
+ messages: history.map((m) => ({ role: m.role, content: m.content })),
152
+ });
153
+
154
+ // Store assistant response after stream completes
155
+ result.text.then(async (text) => {
156
+ await memory.add(conversationId, { role: "assistant", content: text });
157
+ });
158
+
159
+ return result.toSSEResponse();
160
+ }
161
+ `
162
+ );
163
+ }
164
+ if (template === "agent-app") {
165
+ fs.mkdirSync(path.join(targetDir, "src", "agents"), { recursive: true });
166
+ fs.mkdirSync(path.join(targetDir, "src", "tools"), { recursive: true });
167
+ fs.writeFileSync(
168
+ path.join(targetDir, "src", "tools", "calculator.ts"),
169
+ `// Calculator tool \u2014 evaluates math expressions (no API key needed)
170
+ import type { Tool } from "@voltx/agents";
171
+
172
+ export const calculatorTool: Tool = {
173
+ name: "calculator",
174
+ description: "Evaluate a math expression. Supports +, -, *, /, %, parentheses, and Math functions like Math.sqrt(), Math.pow(), Math.round().",
175
+ parameters: {
176
+ type: "object",
177
+ properties: {
178
+ expression: { type: "string", description: "The math expression to evaluate, e.g. '(15 * 85) / 100'" },
179
+ },
180
+ required: ["expression"],
181
+ },
182
+ async execute(args: { expression: string }) {
183
+ try {
184
+ const safe = args.expression.replace(/[^0-9+\\-*/.()%\\s,]|(?<!Math)\\.[a-z]/gi, (match) => {
185
+ if (args.expression.includes("Math.")) return match;
186
+ throw new Error("Invalid character: " + match);
187
+ });
188
+ const result = new Function("return " + safe)();
189
+ return \`\${args.expression} = \${result}\`;
190
+ } catch (err) {
191
+ return \`Error evaluating "\${args.expression}": \${err instanceof Error ? err.message : String(err)}\`;
192
+ }
193
+ },
194
+ };
195
+ `
196
+ );
197
+ fs.writeFileSync(
198
+ path.join(targetDir, "src", "tools", "datetime.ts"),
199
+ `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
200
+ import type { Tool } from "@voltx/agents";
201
+
202
+ export const datetimeTool: Tool = {
203
+ name: "datetime",
204
+ description: "Get the current date, time, day of week, and timezone. Use this when the user asks about the current date or time.",
205
+ parameters: {
206
+ type: "object",
207
+ properties: {
208
+ timezone: { type: "string", description: "Optional IANA timezone like 'America/New_York' or 'Asia/Kolkata'. Defaults to server timezone." },
209
+ },
210
+ },
211
+ async execute(args: { timezone?: string }) {
212
+ const now = new Date();
213
+ const tz = args.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
214
+ const formatted = now.toLocaleString("en-US", {
215
+ timeZone: tz,
216
+ weekday: "long",
217
+ year: "numeric",
218
+ month: "long",
219
+ day: "numeric",
220
+ hour: "2-digit",
221
+ minute: "2-digit",
222
+ second: "2-digit",
223
+ hour12: true,
224
+ });
225
+ return \`Current date/time (\${tz}): \${formatted}\`;
226
+ },
227
+ };
228
+ `
229
+ );
230
+ fs.writeFileSync(
231
+ path.join(targetDir, "src", "agents", "assistant.ts"),
232
+ `// AI Agent \u2014 autonomous assistant with tools
233
+ import { createAgent } from "@voltx/agents";
234
+ import { calculatorTool } from "../tools/calculator";
235
+ import { datetimeTool } from "../tools/datetime";
236
+
237
+ export const assistant = createAgent({
238
+ name: "assistant",
239
+ model: "${provider}:${model}",
240
+ instructions: "You are a helpful AI assistant with access to tools: Calculator, Date & Time. Use them when needed to answer questions accurately.",
241
+ tools: [calculatorTool, datetimeTool],
242
+ maxIterations: 5,
243
+ });
244
+ `
245
+ );
246
+ fs.writeFileSync(
247
+ path.join(targetDir, "src", "routes", "api", "agent.ts"),
248
+ `import type { Context } from "@voltx/server";
249
+ import { assistant } from "../../agents/assistant";
250
+
251
+ export async function POST(c: Context) {
252
+ const { input } = await c.req.json();
253
+ if (!input) return c.json({ error: "Missing 'input' field" }, 400);
254
+ const result = await assistant.run(input);
255
+ return c.json({ content: result.content, steps: result.steps });
256
+ }
257
+ `
258
+ );
259
+ }
260
+ if (template === "rag-app") {
261
+ const embedModel = "openai:text-embedding-3-small";
262
+ fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "rag"), { recursive: true });
263
+ fs.writeFileSync(
264
+ path.join(targetDir, "src", "routes", "api", "rag", "query.ts"),
265
+ `// POST /api/rag/query \u2014 Query documents with RAG
266
+ import type { Context } from "@voltx/server";
267
+ import { streamText } from "@voltx/ai";
268
+ import { createRAGPipeline, createEmbedder } from "@voltx/rag";
269
+ import { createVectorStore } from "@voltx/db";
270
+
271
+ const vectorStore = createVectorStore(); // swap to "pinecone" or "pgvector" for production
272
+ const embedder = createEmbedder({ model: "${embedModel}" });
273
+ const rag = createRAGPipeline({ embedder, vectorStore });
274
+
275
+ export async function POST(c: Context) {
276
+ const { question } = await c.req.json();
277
+
278
+ const context = await rag.getContext(question, { topK: 5 });
279
+
280
+ const result = await streamText({
281
+ model: "${provider}:${model}",
282
+ system: \`Answer the user's question based on the following context. If the context doesn't contain relevant information, say so.\\n\\nContext:\\n\${context}\`,
283
+ messages: [{ role: "user", content: question }],
284
+ });
285
+
286
+ return result.toSSEResponse();
287
+ }
288
+ `
289
+ );
290
+ fs.writeFileSync(
291
+ path.join(targetDir, "src", "routes", "api", "rag", "ingest.ts"),
292
+ `// POST /api/rag/ingest \u2014 Ingest documents into the vector store
293
+ import type { Context } from "@voltx/server";
294
+ import { createRAGPipeline, createEmbedder } from "@voltx/rag";
295
+ import { createVectorStore } from "@voltx/db";
296
+
297
+ const vectorStore = createVectorStore();
298
+ const embedder = createEmbedder({ model: "${embedModel}" });
299
+ const rag = createRAGPipeline({ embedder, vectorStore });
300
+
301
+ export async function POST(c: Context) {
302
+ const { text, idPrefix } = await c.req.json();
303
+
304
+ if (!text || typeof text !== "string") {
305
+ return c.json({ error: "Missing 'text' field" }, 400);
306
+ }
307
+
308
+ const result = await rag.ingest(text, idPrefix ?? "doc");
309
+ return c.json({ status: "ok", chunks: result.chunks, ids: result.ids });
310
+ }
311
+ `
312
+ );
313
+ }
314
+ if (auth === "better-auth") {
315
+ fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "auth"), { recursive: true });
316
+ fs.writeFileSync(
317
+ path.join(targetDir, "src", "routes", "api", "auth", "[...path].ts"),
318
+ `// ALL /api/auth/* \u2014 Better Auth handler
319
+ import type { Context } from "@voltx/server";
320
+ import { auth } from "../../../lib/auth";
321
+ import { createAuthHandler } from "@voltx/auth";
322
+
323
+ const handler = createAuthHandler(auth);
324
+
325
+ export const GET = (c: Context) => handler(c);
326
+ export const POST = (c: Context) => handler(c);
327
+ `
328
+ );
329
+ fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
330
+ fs.writeFileSync(
331
+ path.join(targetDir, "src", "lib", "auth.ts"),
332
+ `// Auth configuration \u2014 Better Auth with DB-backed sessions
333
+ import { createAuth, createAuthMiddleware } from "@voltx/auth";
334
+
335
+ export const auth = createAuth("better-auth", {
336
+ database: process.env.DATABASE_URL!,
337
+ emailAndPassword: true,
338
+ });
339
+
340
+ export const authMiddleware = createAuthMiddleware({
341
+ provider: auth,
342
+ publicPaths: ["/api/auth", "/api/health", "/"],
343
+ });
344
+ `
345
+ );
346
+ } else if (auth === "jwt") {
347
+ fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
348
+ fs.writeFileSync(
349
+ path.join(targetDir, "src", "lib", "auth.ts"),
350
+ `// Auth configuration \u2014 JWT (stateless)
351
+ import { createAuth, createAuthMiddleware } from "@voltx/auth";
352
+
353
+ export const jwt = createAuth("jwt", {
354
+ secret: process.env.JWT_SECRET!,
355
+ expiresIn: "7d",
356
+ });
357
+
358
+ export const authMiddleware = createAuthMiddleware({
359
+ provider: jwt,
360
+ publicPaths: ["/api/auth", "/api/health", "/"],
361
+ });
362
+ `
363
+ );
364
+ fs.writeFileSync(
365
+ path.join(targetDir, "src", "routes", "api", "auth.ts"),
366
+ `// POST /api/auth/login \u2014 Example JWT login route
367
+ import type { Context } from "@voltx/server";
368
+ import { jwt } from "../../lib/auth";
369
+
370
+ export async function POST(c: Context) {
371
+ const { email, password } = await c.req.json();
372
+
373
+ if (!email || !password) {
374
+ return c.json({ error: "Email and password are required" }, 400);
375
+ }
376
+
377
+ const token = await jwt.sign({ sub: email, email });
378
+ return c.json({ token });
379
+ }
380
+ `
381
+ );
382
+ }
383
+ let envContent = "";
384
+ if (template === "rag-app") {
385
+ 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\nOPENAI_API_KEY=sk-...\n\n";
386
+ envContent += "# \u2500\u2500\u2500 Database (Neon Postgres) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require\n\n";
387
+ envContent += "# \u2500\u2500\u2500 Vector Database (Pinecone) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nPINECONE_API_KEY=pc-...\nPINECONE_INDEX=voltx-embeddings\n\n";
388
+ } else if (template === "chatbot" || template === "agent-app") {
389
+ 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";
390
+ if (template === "agent-app") {
391
+ envContent += "# \u2500\u2500\u2500 Database (Neon Postgres \u2014 optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
392
+ envContent += "# \u2500\u2500\u2500 Tool API Keys (add keys for tools you use) \u2500\u2500\n";
393
+ envContent += "# TAVILY_API_KEY=tvly-... (Web Search \u2014 https://tavily.com)\n";
394
+ envContent += "# SERPER_API_KEY= (Google Search \u2014 https://serper.dev)\n";
395
+ envContent += "# OPENWEATHER_API_KEY= (Weather \u2014 https://openweathermap.org/api)\n";
396
+ envContent += "# NEWS_API_KEY= (News \u2014 https://newsapi.org)\n\n";
397
+ }
398
+ } else {
399
+ envContent += "# \u2500\u2500\u2500 LLM Provider (add your key) \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";
400
+ }
401
+ if (auth === "better-auth") {
402
+ envContent += "# \u2500\u2500\u2500 Auth (Better Auth) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nBETTER_AUTH_SECRET=your-secret-key-min-32-chars-here\nBETTER_AUTH_URL=http://localhost:3000\n";
403
+ if (template !== "rag-app" && template !== "agent-app") {
404
+ envContent += "DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require\n";
405
+ }
406
+ envContent += "# GITHUB_CLIENT_ID=\n# GITHUB_CLIENT_SECRET=\n\n";
407
+ } else if (auth === "jwt") {
408
+ envContent += "# \u2500\u2500\u2500 Auth (JWT) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nJWT_SECRET=your-jwt-secret-key\n\n";
409
+ }
410
+ 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";
411
+ fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
412
+ fs.writeFileSync(
413
+ path.join(targetDir, ".gitignore"),
414
+ "node_modules\ndist\n.env\n"
415
+ );
416
+ fs.writeFileSync(
417
+ path.join(targetDir, "tsconfig.json"),
418
+ JSON.stringify(
419
+ {
420
+ compilerOptions: {
421
+ target: "ES2022",
422
+ module: "ESNext",
423
+ moduleResolution: "bundler",
424
+ strict: true,
425
+ esModuleInterop: true,
426
+ skipLibCheck: true,
427
+ outDir: "dist"
428
+ },
429
+ include: ["src", "voltx.config.ts"]
430
+ },
431
+ null,
432
+ 2
433
+ )
434
+ );
435
+ printWelcomeBanner(name);
436
+ }
437
+ var isDirectRun = typeof __require !== "undefined" && __require.main === module && process.argv[1]?.includes("create");
438
+ if (isDirectRun) {
439
+ const projectName = process.argv[2];
440
+ if (!projectName) {
441
+ console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
442
+ process.exit(1);
443
+ }
444
+ const templateFlag = process.argv.indexOf("--template");
445
+ const template = templateFlag !== -1 ? process.argv[templateFlag + 1] : "blank";
446
+ const authFlag = process.argv.indexOf("--auth");
447
+ const auth = authFlag !== -1 ? process.argv[authFlag + 1] : "none";
448
+ createProject({ name: projectName, template, auth }).catch((err) => {
449
+ console.error("[voltx] Error:", err);
450
+ process.exit(1);
451
+ });
452
+ }
453
+
454
+ export {
455
+ createProject
456
+ };