@voltx/cli 0.3.3 → 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.
- package/README.md +68 -14
- package/dist/build.d.mts +4 -3
- package/dist/build.d.ts +4 -3
- package/dist/build.js +56 -27
- package/dist/build.mjs +1 -2
- package/dist/chunk-2LHDOMF2.mjs +680 -0
- package/dist/chunk-4T26KROZ.mjs +456 -0
- package/dist/chunk-5DVBIJXU.mjs +84 -0
- package/dist/chunk-5RN2FYST.mjs +494 -0
- package/dist/chunk-65PVXS4D.mjs +894 -0
- package/dist/chunk-7JVIEGSA.mjs +141 -0
- package/dist/chunk-A4NA4AJN.mjs +786 -0
- package/dist/chunk-AAAHANST.mjs +839 -0
- package/dist/chunk-AD3WMFZF.mjs +205 -0
- package/dist/chunk-CFWXMM7Q.mjs +450 -0
- package/dist/chunk-CSSHLVYS.mjs +83 -0
- package/dist/chunk-CWOSNV5O.mjs +150 -0
- package/dist/chunk-EI6XBYKB.mjs +84 -0
- package/dist/chunk-EXMRIKIX.mjs +404 -0
- package/dist/chunk-FI2W4L4S.mjs +205 -0
- package/dist/chunk-FN7KZJ6H.mjs +363 -0
- package/dist/chunk-G2INQCCJ.mjs +907 -0
- package/dist/chunk-H2DTIOEO.mjs +150 -0
- package/dist/chunk-IS2WTE3C.mjs +138 -0
- package/dist/chunk-JECCDBYI.mjs +730 -0
- package/dist/chunk-KX2MRJUO.mjs +795 -0
- package/dist/chunk-LTGMHAZS.mjs +147 -0
- package/dist/chunk-OPO6RUFP.mjs +698 -0
- package/dist/chunk-P5FSO2UH.mjs +497 -0
- package/dist/chunk-PWQSKYAM.mjs +682 -0
- package/dist/chunk-Q5XCFN7L.mjs +1026 -0
- package/dist/chunk-QSU6FZC7.mjs +497 -0
- package/dist/chunk-RYWRFHEC.mjs +83 -0
- package/dist/chunk-SU4Q3PTH.mjs +201 -0
- package/dist/chunk-UO43CY7Y.mjs +497 -0
- package/dist/chunk-UXI3QSDN.mjs +121 -0
- package/dist/chunk-VD3CNPNP.mjs +123 -0
- package/dist/chunk-X6VOAPRJ.mjs +756 -0
- package/dist/cli.js +1023 -306
- package/dist/cli.mjs +7 -6
- package/dist/create.d.mts +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +813 -189
- package/dist/create.mjs +1 -2
- package/dist/dev.d.mts +6 -4
- package/dist/dev.d.ts +6 -4
- package/dist/dev.js +119 -46
- package/dist/dev.mjs +1 -2
- package/dist/generate.js +13 -13
- package/dist/generate.mjs +1 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1010 -294
- package/dist/index.mjs +6 -7
- package/dist/start.js +7 -17
- package/dist/start.mjs +1 -2
- package/dist/welcome.mjs +0 -1
- package/package.json +11 -3
package/dist/cli.js
CHANGED
|
@@ -145,70 +145,55 @@ 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.`);
|
|
153
156
|
process.exit(1);
|
|
154
157
|
}
|
|
155
158
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
159
|
+
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
160
|
+
const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
|
|
161
|
+
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
162
|
+
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": v("@voltx/cli") };
|
|
163
|
+
if (auth === "better-auth") {
|
|
164
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
165
|
+
deps["better-auth"] = "^1.5.0";
|
|
166
|
+
} else if (auth === "jwt") {
|
|
167
|
+
deps["@voltx/auth"] = v("@voltx/auth");
|
|
168
|
+
deps["jose"] = "^6.0.0";
|
|
169
|
+
}
|
|
170
|
+
const devDeps = { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" };
|
|
171
|
+
deps["hono"] = "^4.7.0";
|
|
172
|
+
deps["@hono/node-server"] = "^1.14.0";
|
|
173
|
+
deps["react"] = "^19.0.0";
|
|
174
|
+
deps["react-dom"] = "^19.0.0";
|
|
175
|
+
deps["tailwindcss"] = "^4.0.0";
|
|
176
|
+
devDeps["vite"] = "^6.0.0";
|
|
177
|
+
devDeps["@hono/vite-dev-server"] = "^0.7.0";
|
|
178
|
+
devDeps["@vitejs/plugin-react"] = "^4.3.0";
|
|
179
|
+
devDeps["@tailwindcss/vite"] = "^4.0.0";
|
|
180
|
+
devDeps["@types/react"] = "^19.0.0";
|
|
181
|
+
devDeps["@types/react-dom"] = "^19.0.0";
|
|
182
|
+
if (shadcn) {
|
|
183
|
+
deps["class-variance-authority"] = "^0.7.0";
|
|
184
|
+
deps["clsx"] = "^2.1.0";
|
|
185
|
+
deps["tailwind-merge"] = "^3.0.0";
|
|
186
|
+
deps["lucide-react"] = "^0.468.0";
|
|
187
|
+
}
|
|
188
|
+
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify({
|
|
183
189
|
name,
|
|
184
190
|
version: "0.1.0",
|
|
185
191
|
private: true,
|
|
186
|
-
scripts: {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
dependencies: {
|
|
192
|
-
...templateDeps[template] ?? templateDeps["blank"],
|
|
193
|
-
"@voltx/cli": "^0.3.0",
|
|
194
|
-
...auth === "better-auth" ? { "@voltx/auth": "^0.3.0", "better-auth": "^1.5.0" } : {},
|
|
195
|
-
...auth === "jwt" ? { "@voltx/auth": "^0.3.0", "jose": "^6.0.0" } : {}
|
|
196
|
-
},
|
|
197
|
-
devDependencies: {
|
|
198
|
-
typescript: "^5.7.0",
|
|
199
|
-
tsx: "^4.21.0",
|
|
200
|
-
tsup: "^8.0.0",
|
|
201
|
-
"@types/node": "^22.0.0"
|
|
202
|
-
}
|
|
203
|
-
};
|
|
204
|
-
fs.writeFileSync(
|
|
205
|
-
path.join(targetDir, "package.json"),
|
|
206
|
-
JSON.stringify(packageJson, null, 2)
|
|
207
|
-
);
|
|
208
|
-
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
209
|
-
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
210
|
-
const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
|
|
211
|
-
let configContent = `import { defineConfig } from "@voltx/core";
|
|
192
|
+
scripts: { dev: "voltx dev", build: "voltx build", start: "voltx start" },
|
|
193
|
+
dependencies: deps,
|
|
194
|
+
devDependencies: devDeps
|
|
195
|
+
}, null, 2));
|
|
196
|
+
let config = `import { defineConfig } from "@voltx/core";
|
|
212
197
|
|
|
213
198
|
export default defineConfig({
|
|
214
199
|
name: "${name}",
|
|
@@ -217,41 +202,63 @@ export default defineConfig({
|
|
|
217
202
|
provider: "${provider}",
|
|
218
203
|
model: "${model}",
|
|
219
204
|
},`;
|
|
220
|
-
if (hasDb)
|
|
221
|
-
configContent += `
|
|
205
|
+
if (hasDb) config += `
|
|
222
206
|
db: {
|
|
223
207
|
url: process.env.DATABASE_URL,
|
|
224
208
|
},`;
|
|
225
|
-
|
|
226
|
-
if (auth !== "none") {
|
|
227
|
-
configContent += `
|
|
209
|
+
if (auth !== "none") config += `
|
|
228
210
|
auth: {
|
|
229
211
|
provider: "${auth}",
|
|
230
212
|
},`;
|
|
231
|
-
|
|
232
|
-
configContent += `
|
|
213
|
+
config += `
|
|
233
214
|
server: {
|
|
234
|
-
routesDir: "
|
|
215
|
+
routesDir: "api",
|
|
235
216
|
staticDir: "public",
|
|
236
217
|
cors: true,
|
|
237
218
|
},
|
|
238
219
|
});
|
|
239
220
|
`;
|
|
240
|
-
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"),
|
|
241
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
221
|
+
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), config);
|
|
222
|
+
fs.mkdirSync(path.join(targetDir, "api"), { recursive: true });
|
|
242
223
|
fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
|
|
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));
|
|
243
259
|
fs.writeFileSync(
|
|
244
|
-
path.join(targetDir, "
|
|
245
|
-
|
|
246
|
-
import config from "../voltx.config";
|
|
247
|
-
|
|
248
|
-
const app = createApp(config);
|
|
249
|
-
app.start();
|
|
250
|
-
`
|
|
251
|
-
);
|
|
252
|
-
fs.writeFileSync(
|
|
253
|
-
path.join(targetDir, "src", "routes", "index.ts"),
|
|
254
|
-
`// GET / \u2014 Health check
|
|
260
|
+
path.join(targetDir, "api", "index.ts"),
|
|
261
|
+
`// GET /api \u2014 Health check
|
|
255
262
|
import type { Context } from "@voltx/server";
|
|
256
263
|
|
|
257
264
|
export function GET(c: Context) {
|
|
@@ -261,25 +268,22 @@ export function GET(c: Context) {
|
|
|
261
268
|
);
|
|
262
269
|
if (template === "chatbot" || template === "agent-app") {
|
|
263
270
|
fs.writeFileSync(
|
|
264
|
-
path.join(targetDir, "
|
|
271
|
+
path.join(targetDir, "api", "chat.ts"),
|
|
265
272
|
`// POST /api/chat \u2014 Streaming chat with conversation memory
|
|
266
273
|
import type { Context } from "@voltx/server";
|
|
267
274
|
import { streamText } from "@voltx/ai";
|
|
268
275
|
import { createMemory } from "@voltx/memory";
|
|
269
276
|
|
|
270
|
-
// In-memory for dev; swap to createMemory("postgres", { url }) for production
|
|
271
277
|
const memory = createMemory({ maxMessages: 50 });
|
|
272
278
|
|
|
273
279
|
export async function POST(c: Context) {
|
|
274
280
|
const { messages, conversationId = "default" } = await c.req.json();
|
|
275
281
|
|
|
276
|
-
// Store the latest user message
|
|
277
282
|
const lastMessage = messages[messages.length - 1];
|
|
278
283
|
if (lastMessage?.role === "user") {
|
|
279
284
|
await memory.add(conversationId, { role: "user", content: lastMessage.content });
|
|
280
285
|
}
|
|
281
286
|
|
|
282
|
-
// Get conversation history from memory
|
|
283
287
|
const history = await memory.get(conversationId);
|
|
284
288
|
|
|
285
289
|
const result = await streamText({
|
|
@@ -288,7 +292,6 @@ export async function POST(c: Context) {
|
|
|
288
292
|
messages: history.map((m) => ({ role: m.role, content: m.content })),
|
|
289
293
|
});
|
|
290
294
|
|
|
291
|
-
// Store assistant response after stream completes
|
|
292
295
|
result.text.then(async (text) => {
|
|
293
296
|
await memory.add(conversationId, { role: "assistant", content: text });
|
|
294
297
|
});
|
|
@@ -299,44 +302,72 @@ export async function POST(c: Context) {
|
|
|
299
302
|
);
|
|
300
303
|
}
|
|
301
304
|
if (template === "agent-app") {
|
|
302
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
303
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
304
|
-
fs.writeFileSync(
|
|
305
|
-
|
|
306
|
-
`import { createAgent } from "@voltx/agents";
|
|
307
|
-
import { searchTool } from "../tools/search";
|
|
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)
|
|
308
|
+
import type { Tool } from "@voltx/agents";
|
|
308
309
|
|
|
309
|
-
export const
|
|
310
|
-
name: "
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
310
|
+
export const calculatorTool: Tool = {
|
|
311
|
+
name: "calculator",
|
|
312
|
+
description: "Evaluate a math expression. Supports +, -, *, /, %, parentheses, and Math functions.",
|
|
313
|
+
parameters: {
|
|
314
|
+
type: "object",
|
|
315
|
+
properties: { expression: { type: "string", description: "The math expression to evaluate" } },
|
|
316
|
+
required: ["expression"],
|
|
317
|
+
},
|
|
318
|
+
async execute(args: { expression: string }) {
|
|
319
|
+
try {
|
|
320
|
+
const safe = args.expression.replace(/[^0-9+\\-*/.()%\\s,]|(?<!Math)\\.[a-z]/gi, (match) => {
|
|
321
|
+
if (args.expression.includes("Math.")) return match;
|
|
322
|
+
throw new Error("Invalid character: " + match);
|
|
323
|
+
});
|
|
324
|
+
const result = new Function("return " + safe)();
|
|
325
|
+
return \`\${args.expression} = \${result}\`;
|
|
326
|
+
} catch (err) {
|
|
327
|
+
return \`Error: \${err instanceof Error ? err.message : String(err)}\`;
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
`);
|
|
332
|
+
fs.writeFileSync(path.join(targetDir, "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
|
|
333
|
+
import type { Tool } from "@voltx/agents";
|
|
321
334
|
|
|
322
|
-
export const
|
|
323
|
-
name: "
|
|
324
|
-
description: "
|
|
335
|
+
export const datetimeTool: Tool = {
|
|
336
|
+
name: "datetime",
|
|
337
|
+
description: "Get the current date, time, day of week, and timezone.",
|
|
325
338
|
parameters: {
|
|
326
339
|
type: "object",
|
|
327
|
-
properties: {
|
|
328
|
-
required: ["query"],
|
|
340
|
+
properties: { timezone: { type: "string", description: "Optional IANA timezone. Defaults to server timezone." } },
|
|
329
341
|
},
|
|
330
|
-
async execute(args: {
|
|
331
|
-
|
|
342
|
+
async execute(args: { timezone?: string }) {
|
|
343
|
+
const now = new Date();
|
|
344
|
+
const tz = args.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
345
|
+
const formatted = now.toLocaleString("en-US", {
|
|
346
|
+
timeZone: tz, weekday: "long", year: "numeric", month: "long", day: "numeric",
|
|
347
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true,
|
|
348
|
+
});
|
|
349
|
+
return \`Current date/time (\${tz}): \${formatted}\`;
|
|
332
350
|
},
|
|
333
351
|
};
|
|
334
|
-
`
|
|
335
|
-
)
|
|
352
|
+
`);
|
|
353
|
+
fs.writeFileSync(path.join(targetDir, "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
|
|
354
|
+
import { createAgent } from "@voltx/agents";
|
|
355
|
+
import { calculatorTool } from "../tools/calculator";
|
|
356
|
+
import { datetimeTool } from "../tools/datetime";
|
|
357
|
+
|
|
358
|
+
export const assistant = createAgent({
|
|
359
|
+
name: "assistant",
|
|
360
|
+
model: "${provider}:${model}",
|
|
361
|
+
instructions: "You are a helpful AI assistant with access to tools: Calculator, Date & Time. Use them when needed to answer questions accurately.",
|
|
362
|
+
tools: [calculatorTool, datetimeTool],
|
|
363
|
+
maxIterations: 5,
|
|
364
|
+
});
|
|
365
|
+
`);
|
|
336
366
|
fs.writeFileSync(
|
|
337
|
-
path.join(targetDir, "
|
|
338
|
-
|
|
339
|
-
import {
|
|
367
|
+
path.join(targetDir, "api", "agent.ts"),
|
|
368
|
+
`// POST /api/agent \u2014 Run the AI agent
|
|
369
|
+
import type { Context } from "@voltx/server";
|
|
370
|
+
import { assistant } from "../agents/assistant";
|
|
340
371
|
|
|
341
372
|
export async function POST(c: Context) {
|
|
342
373
|
const { input } = await c.req.json();
|
|
@@ -349,36 +380,33 @@ export async function POST(c: Context) {
|
|
|
349
380
|
}
|
|
350
381
|
if (template === "rag-app") {
|
|
351
382
|
const embedModel = "openai:text-embedding-3-small";
|
|
352
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
383
|
+
fs.mkdirSync(path.join(targetDir, "api", "rag"), { recursive: true });
|
|
353
384
|
fs.writeFileSync(
|
|
354
|
-
path.join(targetDir, "
|
|
385
|
+
path.join(targetDir, "api", "rag", "query.ts"),
|
|
355
386
|
`// POST /api/rag/query \u2014 Query documents with RAG
|
|
356
387
|
import type { Context } from "@voltx/server";
|
|
357
388
|
import { streamText } from "@voltx/ai";
|
|
358
389
|
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
359
390
|
import { createVectorStore } from "@voltx/db";
|
|
360
391
|
|
|
361
|
-
const vectorStore = createVectorStore();
|
|
392
|
+
const vectorStore = createVectorStore();
|
|
362
393
|
const embedder = createEmbedder({ model: "${embedModel}" });
|
|
363
394
|
const rag = createRAGPipeline({ embedder, vectorStore });
|
|
364
395
|
|
|
365
396
|
export async function POST(c: Context) {
|
|
366
397
|
const { question } = await c.req.json();
|
|
367
|
-
|
|
368
398
|
const context = await rag.getContext(question, { topK: 5 });
|
|
369
|
-
|
|
370
399
|
const result = await streamText({
|
|
371
400
|
model: "${provider}:${model}",
|
|
372
|
-
system: \`Answer
|
|
401
|
+
system: \`Answer based on context. If not relevant, say so.\\n\\nContext:\\n\${context}\`,
|
|
373
402
|
messages: [{ role: "user", content: question }],
|
|
374
403
|
});
|
|
375
|
-
|
|
376
404
|
return result.toSSEResponse();
|
|
377
405
|
}
|
|
378
406
|
`
|
|
379
407
|
);
|
|
380
408
|
fs.writeFileSync(
|
|
381
|
-
path.join(targetDir, "
|
|
409
|
+
path.join(targetDir, "api", "rag", "ingest.ts"),
|
|
382
410
|
`// POST /api/rag/ingest \u2014 Ingest documents into the vector store
|
|
383
411
|
import type { Context } from "@voltx/server";
|
|
384
412
|
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
@@ -390,11 +418,7 @@ const rag = createRAGPipeline({ embedder, vectorStore });
|
|
|
390
418
|
|
|
391
419
|
export async function POST(c: Context) {
|
|
392
420
|
const { text, idPrefix } = await c.req.json();
|
|
393
|
-
|
|
394
|
-
if (!text || typeof text !== "string") {
|
|
395
|
-
return c.json({ error: "Missing 'text' field" }, 400);
|
|
396
|
-
}
|
|
397
|
-
|
|
421
|
+
if (!text || typeof text !== "string") return c.json({ error: "Missing 'text' field" }, 400);
|
|
398
422
|
const result = await rag.ingest(text, idPrefix ?? "doc");
|
|
399
423
|
return c.json({ status: "ok", chunks: result.chunks, ids: result.ids });
|
|
400
424
|
}
|
|
@@ -402,12 +426,12 @@ export async function POST(c: Context) {
|
|
|
402
426
|
);
|
|
403
427
|
}
|
|
404
428
|
if (auth === "better-auth") {
|
|
405
|
-
fs.mkdirSync(path.join(targetDir, "
|
|
429
|
+
fs.mkdirSync(path.join(targetDir, "api", "auth"), { recursive: true });
|
|
406
430
|
fs.writeFileSync(
|
|
407
|
-
path.join(targetDir, "
|
|
431
|
+
path.join(targetDir, "api", "auth", "[...path].ts"),
|
|
408
432
|
`// ALL /api/auth/* \u2014 Better Auth handler
|
|
409
433
|
import type { Context } from "@voltx/server";
|
|
410
|
-
import { auth } from "
|
|
434
|
+
import { auth } from "../../src/lib/auth";
|
|
411
435
|
import { createAuthHandler } from "@voltx/auth";
|
|
412
436
|
|
|
413
437
|
const handler = createAuthHandler(auth);
|
|
@@ -416,11 +440,9 @@ export const GET = (c: Context) => handler(c);
|
|
|
416
440
|
export const POST = (c: Context) => handler(c);
|
|
417
441
|
`
|
|
418
442
|
);
|
|
419
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
420
443
|
fs.writeFileSync(
|
|
421
444
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
422
|
-
|
|
423
|
-
import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
445
|
+
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
424
446
|
|
|
425
447
|
export const auth = createAuth("better-auth", {
|
|
426
448
|
database: process.env.DATABASE_URL!,
|
|
@@ -434,11 +456,9 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
434
456
|
`
|
|
435
457
|
);
|
|
436
458
|
} else if (auth === "jwt") {
|
|
437
|
-
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
438
459
|
fs.writeFileSync(
|
|
439
460
|
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
440
|
-
|
|
441
|
-
import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
461
|
+
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
442
462
|
|
|
443
463
|
export const jwt = createAuth("jwt", {
|
|
444
464
|
secret: process.env.JWT_SECRET!,
|
|
@@ -452,18 +472,13 @@ export const authMiddleware = createAuthMiddleware({
|
|
|
452
472
|
`
|
|
453
473
|
);
|
|
454
474
|
fs.writeFileSync(
|
|
455
|
-
path.join(targetDir, "
|
|
456
|
-
|
|
457
|
-
import
|
|
458
|
-
import { jwt } from "../../lib/auth";
|
|
475
|
+
path.join(targetDir, "api", "auth.ts"),
|
|
476
|
+
`import type { Context } from "@voltx/server";
|
|
477
|
+
import { jwt } from "../src/lib/auth";
|
|
459
478
|
|
|
460
479
|
export async function POST(c: Context) {
|
|
461
480
|
const { email, password } = await c.req.json();
|
|
462
|
-
|
|
463
|
-
if (!email || !password) {
|
|
464
|
-
return c.json({ error: "Email and password are required" }, 400);
|
|
465
|
-
}
|
|
466
|
-
|
|
481
|
+
if (!email || !password) return c.json({ error: "Email and password are required" }, 400);
|
|
467
482
|
const token = await jwt.sign({ sub: email, email });
|
|
468
483
|
return c.json({ token });
|
|
469
484
|
}
|
|
@@ -473,76 +488,685 @@ export async function POST(c: Context) {
|
|
|
473
488
|
let envContent = "";
|
|
474
489
|
if (template === "rag-app") {
|
|
475
490
|
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";
|
|
476
|
-
envContent += "# \u2500\u2500\u2500 Database
|
|
477
|
-
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";
|
|
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";
|
|
478
492
|
} else if (template === "chatbot" || template === "agent-app") {
|
|
479
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";
|
|
480
|
-
if (template === "agent-app") {
|
|
481
|
-
envContent += "# \u2500\u2500\u2500 Database (Neon Postgres \u2014 optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
482
|
-
}
|
|
483
494
|
} else {
|
|
484
|
-
envContent += "# \u2500\u2500\u2500 LLM Provider
|
|
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";
|
|
485
496
|
}
|
|
486
497
|
if (auth === "better-auth") {
|
|
487
|
-
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";
|
|
488
|
-
if (template !== "rag-app" && template !== "agent-app") {
|
|
489
|
-
envContent += "DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require\n";
|
|
490
|
-
}
|
|
491
|
-
envContent += "# GITHUB_CLIENT_ID=\n# GITHUB_CLIENT_SECRET=\n\n";
|
|
498
|
+
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\n";
|
|
492
499
|
} else if (auth === "jwt") {
|
|
493
500
|
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";
|
|
494
501
|
}
|
|
495
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";
|
|
496
503
|
fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
|
|
497
|
-
fs.writeFileSync(
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
504
|
+
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n.env.local\n.env.*.local\nvite.config.voltx.ts\n");
|
|
505
|
+
printWelcomeBanner(name);
|
|
506
|
+
}
|
|
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);
|
|
516
631
|
},
|
|
517
|
-
|
|
518
|
-
2
|
|
519
|
-
)
|
|
632
|
+
}
|
|
520
633
|
);
|
|
521
|
-
|
|
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>
|
|
648
|
+
</div>
|
|
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%;
|
|
522
733
|
}
|
|
523
|
-
|
|
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>
|
|
852
|
+
</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>
|
|
866
|
+
</div>
|
|
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
|
|
872
|
+
</div>
|
|
873
|
+
</div>
|
|
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>
|
|
877
|
+
</div>
|
|
878
|
+
</div>
|
|
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>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
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>
|
|
916
|
+
</div>
|
|
917
|
+
</div>
|
|
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;
|
|
524
1147
|
var init_create = __esm({
|
|
525
1148
|
"src/create.ts"() {
|
|
526
1149
|
"use strict";
|
|
527
1150
|
fs = __toESM(require("fs"));
|
|
528
1151
|
path = __toESM(require("path"));
|
|
529
1152
|
init_welcome();
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
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
|
+
};
|
|
1164
|
+
TEMPLATE_DEPS = {
|
|
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") }
|
|
1169
|
+
};
|
|
546
1170
|
}
|
|
547
1171
|
});
|
|
548
1172
|
|
|
@@ -559,7 +1183,7 @@ async function runDev(options = {}) {
|
|
|
559
1183
|
clearScreen = true
|
|
560
1184
|
} = options;
|
|
561
1185
|
if (!entry) {
|
|
562
|
-
console.error("[voltx] Could not find entry point. Expected
|
|
1186
|
+
console.error("[voltx] Could not find entry point. Expected server.ts or src/index.ts");
|
|
563
1187
|
process.exit(1);
|
|
564
1188
|
}
|
|
565
1189
|
const entryPath = (0, import_node_path.resolve)(cwd, entry);
|
|
@@ -567,56 +1191,124 @@ async function runDev(options = {}) {
|
|
|
567
1191
|
console.error(`[voltx] Entry file not found: ${entry}`);
|
|
568
1192
|
process.exit(1);
|
|
569
1193
|
}
|
|
570
|
-
|
|
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
|
+
}
|
|
571
1215
|
const env = {
|
|
572
1216
|
...process.env,
|
|
573
1217
|
NODE_ENV: "development"
|
|
574
1218
|
};
|
|
575
|
-
if (
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
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
|
+
}
|
|
585
1240
|
}
|
|
1241
|
+
};
|
|
1242
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
1243
|
+
for (const signal of signals) {
|
|
1244
|
+
process.on(signal, () => {
|
|
1245
|
+
cleanup();
|
|
1246
|
+
child.kill(signal);
|
|
1247
|
+
});
|
|
586
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
|
+
};
|
|
587
1297
|
if (port) {
|
|
588
1298
|
env.PORT = String(port);
|
|
589
1299
|
}
|
|
590
|
-
printDevBanner(entry, port);
|
|
591
1300
|
const tsxArgs = ["watch"];
|
|
592
|
-
if (clearScreen) {
|
|
1301
|
+
if (options.clearScreen ?? true) {
|
|
593
1302
|
tsxArgs.push("--clear-screen=false");
|
|
594
1303
|
}
|
|
595
|
-
const watchDirs = [
|
|
596
|
-
"src/routes",
|
|
597
|
-
"src/agents",
|
|
598
|
-
"src/tools",
|
|
599
|
-
"src/jobs",
|
|
600
|
-
"src/lib",
|
|
601
|
-
"voltx.config.ts",
|
|
602
|
-
...options.watch ?? []
|
|
603
|
-
];
|
|
604
1304
|
tsxArgs.push("--ignore=node_modules", "--ignore=dist", "--ignore=.turbo");
|
|
605
1305
|
tsxArgs.push(entry);
|
|
606
|
-
const tsxBin =
|
|
1306
|
+
const tsxBin = findBin(cwd, "tsx");
|
|
607
1307
|
let child;
|
|
608
1308
|
if (tsxBin) {
|
|
609
|
-
child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, {
|
|
610
|
-
cwd,
|
|
611
|
-
env,
|
|
612
|
-
stdio: "inherit"
|
|
613
|
-
});
|
|
1309
|
+
child = (0, import_node_child_process.spawn)(tsxBin, tsxArgs, { cwd, env, stdio: "inherit" });
|
|
614
1310
|
} else {
|
|
615
|
-
child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], {
|
|
616
|
-
cwd,
|
|
617
|
-
env,
|
|
618
|
-
stdio: "inherit"
|
|
619
|
-
});
|
|
1311
|
+
child = (0, import_node_child_process.spawn)("npx", ["tsx", ...tsxArgs], { cwd, env, stdio: "inherit" });
|
|
620
1312
|
}
|
|
621
1313
|
const signals = ["SIGINT", "SIGTERM"];
|
|
622
1314
|
for (const signal of signals) {
|
|
@@ -627,7 +1319,6 @@ async function runDev(options = {}) {
|
|
|
627
1319
|
child.on("error", (err) => {
|
|
628
1320
|
if (err.code === "ENOENT") {
|
|
629
1321
|
console.error("[voltx] tsx not found. Install it with: npm install -D tsx");
|
|
630
|
-
console.error("[voltx] Or run your app directly: npx tsx watch src/index.ts");
|
|
631
1322
|
} else {
|
|
632
1323
|
console.error("[voltx] Dev server error:", err.message);
|
|
633
1324
|
}
|
|
@@ -639,6 +1330,8 @@ async function runDev(options = {}) {
|
|
|
639
1330
|
}
|
|
640
1331
|
function findEntryPoint(cwd) {
|
|
641
1332
|
const candidates = [
|
|
1333
|
+
"server.ts",
|
|
1334
|
+
"server.js",
|
|
642
1335
|
"src/index.ts",
|
|
643
1336
|
"src/index.js",
|
|
644
1337
|
"src/index.mts",
|
|
@@ -654,34 +1347,38 @@ function findEntryPoint(cwd) {
|
|
|
654
1347
|
}
|
|
655
1348
|
return null;
|
|
656
1349
|
}
|
|
657
|
-
function
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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
|
+
}
|
|
664
1359
|
return null;
|
|
665
1360
|
}
|
|
666
|
-
function printDevBanner(entry, port) {
|
|
1361
|
+
function printDevBanner(entry, port, fullStack) {
|
|
667
1362
|
console.log("");
|
|
668
1363
|
console.log(" \u26A1 VoltX Dev Server");
|
|
669
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");
|
|
670
1365
|
console.log(` Entry: ${entry}`);
|
|
671
|
-
|
|
672
|
-
|
|
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)`);
|
|
673
1370
|
}
|
|
674
|
-
console.log(` Mode: development (hot reload)`);
|
|
675
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");
|
|
676
1372
|
console.log("");
|
|
677
1373
|
}
|
|
678
|
-
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;
|
|
679
1375
|
var init_dev = __esm({
|
|
680
1376
|
"src/dev.ts"() {
|
|
681
1377
|
"use strict";
|
|
682
1378
|
import_node_child_process = require("child_process");
|
|
683
1379
|
import_node_path = require("path");
|
|
684
1380
|
import_node_fs = require("fs");
|
|
1381
|
+
import_core = require("@voltx/core");
|
|
685
1382
|
}
|
|
686
1383
|
});
|
|
687
1384
|
|
|
@@ -699,7 +1396,7 @@ async function runBuild(options = {}) {
|
|
|
699
1396
|
sourcemap = false
|
|
700
1397
|
} = options;
|
|
701
1398
|
if (!entry) {
|
|
702
|
-
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");
|
|
703
1400
|
process.exit(1);
|
|
704
1401
|
}
|
|
705
1402
|
const entryPath = (0, import_node_path2.resolve)(cwd, entry);
|
|
@@ -707,59 +1404,87 @@ async function runBuild(options = {}) {
|
|
|
707
1404
|
console.error(`[voltx] Entry file not found: ${entry}`);
|
|
708
1405
|
process.exit(1);
|
|
709
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;
|
|
710
1411
|
console.log("");
|
|
711
1412
|
console.log(" \u26A1 VoltX Build");
|
|
712
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");
|
|
713
1414
|
console.log(` Entry: ${entry}`);
|
|
714
1415
|
console.log(` Output: ${outDir}/`);
|
|
1416
|
+
console.log(` Mode: ${isFullStack ? "full-stack" : "API-only"}`);
|
|
715
1417
|
console.log(` Minify: ${minify}`);
|
|
716
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");
|
|
717
1419
|
console.log("");
|
|
718
1420
|
(0, import_node_fs2.mkdirSync)((0, import_node_path2.resolve)(cwd, outDir), { recursive: true });
|
|
719
|
-
|
|
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");
|
|
720
1465
|
const tsupArgs = [
|
|
721
1466
|
entry,
|
|
722
1467
|
"--format",
|
|
723
1468
|
"esm",
|
|
724
1469
|
"--out-dir",
|
|
725
1470
|
outDir,
|
|
726
|
-
"--clean",
|
|
727
1471
|
"--target",
|
|
728
|
-
"node20"
|
|
1472
|
+
"node20",
|
|
1473
|
+
"--no-splitting"
|
|
729
1474
|
];
|
|
1475
|
+
if (clean) tsupArgs.push("--clean");
|
|
730
1476
|
if (minify) tsupArgs.push("--minify");
|
|
731
1477
|
if (sourcemap) tsupArgs.push("--sourcemap");
|
|
732
|
-
tsupArgs.push("--no-splitting");
|
|
733
|
-
const tsupBin = findBin(cwd, "tsup");
|
|
734
1478
|
await runCommand(
|
|
735
1479
|
tsupBin ?? "npx",
|
|
736
1480
|
tsupBin ? tsupArgs : ["tsup", ...tsupArgs],
|
|
737
1481
|
cwd
|
|
738
1482
|
);
|
|
739
|
-
console.log(" \u2713 Server built successfully");
|
|
740
|
-
const frontendDir = (0, import_node_path2.resolve)(cwd, "src", "frontend");
|
|
741
|
-
const viteConfig = (0, import_node_path2.resolve)(cwd, "vite.config.ts");
|
|
742
|
-
const hasFrontend = (0, import_node_fs2.existsSync)(frontendDir) || (0, import_node_fs2.existsSync)(viteConfig);
|
|
743
|
-
if (hasFrontend) {
|
|
744
|
-
console.log(" [2/2] Building frontend...");
|
|
745
|
-
const viteBin = findBin(cwd, "vite");
|
|
746
|
-
const viteArgs = ["build", "--outDir", (0, import_node_path2.join)(outDir, "public")];
|
|
747
|
-
await runCommand(
|
|
748
|
-
viteBin ?? "npx",
|
|
749
|
-
viteBin ? viteArgs : ["vite", ...viteArgs],
|
|
750
|
-
cwd
|
|
751
|
-
);
|
|
752
|
-
console.log(" \u2713 Frontend built successfully");
|
|
753
|
-
} else {
|
|
754
|
-
console.log(" [2/2] No frontend found, skipping...");
|
|
755
|
-
}
|
|
756
|
-
console.log("");
|
|
757
|
-
console.log(" \u26A1 Build complete!");
|
|
758
|
-
console.log(` Run \`voltx start\` to start the production server.`);
|
|
759
|
-
console.log("");
|
|
760
1483
|
}
|
|
761
1484
|
function findEntryPoint2(cwd) {
|
|
762
1485
|
const candidates = [
|
|
1486
|
+
"server.ts",
|
|
1487
|
+
"server.js",
|
|
763
1488
|
"src/index.ts",
|
|
764
1489
|
"src/index.js",
|
|
765
1490
|
"src/index.mts",
|
|
@@ -773,7 +1498,7 @@ function findEntryPoint2(cwd) {
|
|
|
773
1498
|
}
|
|
774
1499
|
return null;
|
|
775
1500
|
}
|
|
776
|
-
function
|
|
1501
|
+
function findBin2(cwd, name) {
|
|
777
1502
|
const paths = [
|
|
778
1503
|
(0, import_node_path2.join)(cwd, "node_modules", ".bin", name),
|
|
779
1504
|
(0, import_node_path2.join)(cwd, "..", "node_modules", ".bin", name),
|
|
@@ -803,13 +1528,14 @@ function runCommand(cmd, args2, cwd) {
|
|
|
803
1528
|
});
|
|
804
1529
|
});
|
|
805
1530
|
}
|
|
806
|
-
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;
|
|
807
1532
|
var init_build = __esm({
|
|
808
1533
|
"src/build.ts"() {
|
|
809
1534
|
"use strict";
|
|
810
1535
|
import_node_child_process2 = require("child_process");
|
|
811
1536
|
import_node_path2 = require("path");
|
|
812
1537
|
import_node_fs2 = require("fs");
|
|
1538
|
+
import_core2 = require("@voltx/core");
|
|
813
1539
|
}
|
|
814
1540
|
});
|
|
815
1541
|
|
|
@@ -838,26 +1564,15 @@ async function runStart(options = {}) {
|
|
|
838
1564
|
console.error(`[voltx] Entry file not found: ${outDir}/${entry}`);
|
|
839
1565
|
process.exit(1);
|
|
840
1566
|
}
|
|
1567
|
+
(0, import_core3.loadEnv)("production", cwd);
|
|
841
1568
|
const env = {
|
|
842
1569
|
...process.env,
|
|
843
1570
|
NODE_ENV: "production"
|
|
844
1571
|
};
|
|
845
|
-
const envFile = (0, import_node_path3.resolve)(cwd, ".env");
|
|
846
|
-
if ((0, import_node_fs3.existsSync)(envFile)) {
|
|
847
|
-
const envContent = (0, import_node_fs3.readFileSync)(envFile, "utf-8");
|
|
848
|
-
for (const line of envContent.split("\n")) {
|
|
849
|
-
const trimmed = line.trim();
|
|
850
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
851
|
-
const eqIdx = trimmed.indexOf("=");
|
|
852
|
-
if (eqIdx === -1) continue;
|
|
853
|
-
const key = trimmed.slice(0, eqIdx).trim();
|
|
854
|
-
const value = trimmed.slice(eqIdx + 1).trim();
|
|
855
|
-
env[key] = value;
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
1572
|
if (port) {
|
|
859
1573
|
env.PORT = String(port);
|
|
860
1574
|
}
|
|
1575
|
+
const hasClientBuild = (0, import_node_fs3.existsSync)((0, import_node_path3.resolve)(distDir, "client"));
|
|
861
1576
|
console.log("");
|
|
862
1577
|
console.log(" \u26A1 VoltX Production Server");
|
|
863
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");
|
|
@@ -865,7 +1580,7 @@ async function runStart(options = {}) {
|
|
|
865
1580
|
if (port) {
|
|
866
1581
|
console.log(` Port: ${port}`);
|
|
867
1582
|
}
|
|
868
|
-
console.log(` Mode:
|
|
1583
|
+
console.log(` Mode: ${hasClientBuild ? "full-stack" : "API-only"}`);
|
|
869
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");
|
|
870
1585
|
console.log("");
|
|
871
1586
|
const child = (0, import_node_child_process3.spawn)("node", [entryPath], {
|
|
@@ -889,13 +1604,13 @@ async function runStart(options = {}) {
|
|
|
889
1604
|
}
|
|
890
1605
|
function findDistEntry(distDir) {
|
|
891
1606
|
const candidates = [
|
|
1607
|
+
"server.mjs",
|
|
1608
|
+
"server.js",
|
|
892
1609
|
"index.mjs",
|
|
893
1610
|
"index.js",
|
|
894
1611
|
"index.cjs",
|
|
895
1612
|
"main.mjs",
|
|
896
|
-
"main.js"
|
|
897
|
-
"src/index.mjs",
|
|
898
|
-
"src/index.js"
|
|
1613
|
+
"main.js"
|
|
899
1614
|
];
|
|
900
1615
|
for (const candidate of candidates) {
|
|
901
1616
|
if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(distDir, candidate))) {
|
|
@@ -904,13 +1619,14 @@ function findDistEntry(distDir) {
|
|
|
904
1619
|
}
|
|
905
1620
|
return null;
|
|
906
1621
|
}
|
|
907
|
-
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;
|
|
908
1623
|
var init_start = __esm({
|
|
909
1624
|
"src/start.ts"() {
|
|
910
1625
|
"use strict";
|
|
911
1626
|
import_node_child_process3 = require("child_process");
|
|
912
1627
|
import_node_path3 = require("path");
|
|
913
1628
|
import_node_fs3 = require("fs");
|
|
1629
|
+
import_core3 = require("@voltx/core");
|
|
914
1630
|
}
|
|
915
1631
|
});
|
|
916
1632
|
|
|
@@ -943,13 +1659,13 @@ async function runGenerate(options) {
|
|
|
943
1659
|
}
|
|
944
1660
|
function generateRoute(cwd, name, method = "POST") {
|
|
945
1661
|
const routePath = name.startsWith("/") ? name.slice(1) : name;
|
|
946
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1662
|
+
const filePath = (0, import_node_path4.join)(cwd, "api", `${routePath}.ts`);
|
|
947
1663
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
948
|
-
console.error(`[voltx] Route already exists:
|
|
1664
|
+
console.error(`[voltx] Route already exists: api/${routePath}.ts`);
|
|
949
1665
|
process.exit(1);
|
|
950
1666
|
}
|
|
951
1667
|
const upperMethod = method.toUpperCase();
|
|
952
|
-
const urlPath = "/" + routePath;
|
|
1668
|
+
const urlPath = "/api/" + routePath;
|
|
953
1669
|
const content = `// ${upperMethod} ${urlPath}
|
|
954
1670
|
import type { Context } from "@voltx/server";
|
|
955
1671
|
|
|
@@ -958,13 +1674,13 @@ export async function ${upperMethod}(c: Context) {
|
|
|
958
1674
|
}
|
|
959
1675
|
`;
|
|
960
1676
|
writeFileSafe(filePath, content);
|
|
961
|
-
console.log(` \u2713 Created route:
|
|
1677
|
+
console.log(` \u2713 Created route: api/${routePath}.ts`);
|
|
962
1678
|
console.log(` ${upperMethod} ${urlPath}`);
|
|
963
1679
|
}
|
|
964
1680
|
function generateAgent(cwd, name) {
|
|
965
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1681
|
+
const filePath = (0, import_node_path4.join)(cwd, "agents", `${name}.ts`);
|
|
966
1682
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
967
|
-
console.error(`[voltx] Agent already exists:
|
|
1683
|
+
console.error(`[voltx] Agent already exists: agents/${name}.ts`);
|
|
968
1684
|
process.exit(1);
|
|
969
1685
|
}
|
|
970
1686
|
const content = `// Agent: ${name}
|
|
@@ -986,12 +1702,12 @@ export const ${toCamelCase(name)} = createAgent({
|
|
|
986
1702
|
});
|
|
987
1703
|
`;
|
|
988
1704
|
writeFileSafe(filePath, content);
|
|
989
|
-
console.log(` \u2713 Created agent:
|
|
1705
|
+
console.log(` \u2713 Created agent: agents/${name}.ts`);
|
|
990
1706
|
}
|
|
991
1707
|
function generateTool(cwd, name) {
|
|
992
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1708
|
+
const filePath = (0, import_node_path4.join)(cwd, "tools", `${name}.ts`);
|
|
993
1709
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
994
|
-
console.error(`[voltx] Tool already exists:
|
|
1710
|
+
console.error(`[voltx] Tool already exists: tools/${name}.ts`);
|
|
995
1711
|
process.exit(1);
|
|
996
1712
|
}
|
|
997
1713
|
const content = `// Tool: ${name}
|
|
@@ -1013,12 +1729,12 @@ export const ${toCamelCase(name)}Tool = {
|
|
|
1013
1729
|
};
|
|
1014
1730
|
`;
|
|
1015
1731
|
writeFileSafe(filePath, content);
|
|
1016
|
-
console.log(` \u2713 Created tool:
|
|
1732
|
+
console.log(` \u2713 Created tool: tools/${name}.ts`);
|
|
1017
1733
|
}
|
|
1018
1734
|
function generateJob(cwd, name) {
|
|
1019
|
-
const filePath = (0, import_node_path4.join)(cwd, "
|
|
1735
|
+
const filePath = (0, import_node_path4.join)(cwd, "jobs", `${name}.ts`);
|
|
1020
1736
|
if ((0, import_node_fs4.existsSync)(filePath)) {
|
|
1021
|
-
console.error(`[voltx] Job already exists:
|
|
1737
|
+
console.error(`[voltx] Job already exists: jobs/${name}.ts`);
|
|
1022
1738
|
process.exit(1);
|
|
1023
1739
|
}
|
|
1024
1740
|
const content = `// Job: ${name}
|
|
@@ -1043,7 +1759,7 @@ export async function run(ctx: any, data?: Record<string, unknown>) {
|
|
|
1043
1759
|
}
|
|
1044
1760
|
`;
|
|
1045
1761
|
writeFileSafe(filePath, content);
|
|
1046
|
-
console.log(` \u2713 Created job:
|
|
1762
|
+
console.log(` \u2713 Created job: jobs/${name}.ts`);
|
|
1047
1763
|
}
|
|
1048
1764
|
function writeFileSafe(filePath, content) {
|
|
1049
1765
|
const dir = (0, import_node_path4.dirname)(filePath);
|
|
@@ -1081,7 +1797,7 @@ var init_index = __esm({
|
|
|
1081
1797
|
init_build();
|
|
1082
1798
|
init_start();
|
|
1083
1799
|
init_generate();
|
|
1084
|
-
CLI_VERSION = "0.3.
|
|
1800
|
+
CLI_VERSION = "0.3.5";
|
|
1085
1801
|
}
|
|
1086
1802
|
});
|
|
1087
1803
|
|
|
@@ -1105,13 +1821,14 @@ async function main() {
|
|
|
1105
1821
|
case "create": {
|
|
1106
1822
|
const projectName = args[1];
|
|
1107
1823
|
if (!projectName || projectName.startsWith("-")) {
|
|
1108
|
-
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]");
|
|
1109
1825
|
process.exit(1);
|
|
1110
1826
|
}
|
|
1111
1827
|
const template = parseFlag("--template");
|
|
1112
1828
|
const auth = parseFlag("--auth");
|
|
1829
|
+
const useShadcn = args.includes("--shadcn");
|
|
1113
1830
|
const { createProject: createProject2 } = await Promise.resolve().then(() => (init_create(), create_exports));
|
|
1114
|
-
await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none" });
|
|
1831
|
+
await createProject2({ name: projectName, template: template ?? "blank", auth: auth ?? "none", shadcn: useShadcn });
|
|
1115
1832
|
break;
|
|
1116
1833
|
}
|
|
1117
1834
|
// ─── voltx dev ─────────────────────────────────────────────────────
|
|
@@ -1154,7 +1871,7 @@ async function main() {
|
|
|
1154
1871
|
console.error("[voltx] Usage: voltx generate <type> <name>");
|
|
1155
1872
|
console.error("");
|
|
1156
1873
|
console.error(" Types:");
|
|
1157
|
-
console.error(" route <path> Generate a new API route (e.g.,
|
|
1874
|
+
console.error(" route <path> Generate a new API route (e.g., users)");
|
|
1158
1875
|
console.error(" agent <name> Generate a new agent (e.g., assistant)");
|
|
1159
1876
|
console.error(" tool <name> Generate a new tool (e.g., search)");
|
|
1160
1877
|
console.error(" job <name> Generate a new background job (e.g., cleanup)");
|
|
@@ -1207,7 +1924,7 @@ function printHelp() {
|
|
|
1207
1924
|
|
|
1208
1925
|
Dev Options:
|
|
1209
1926
|
--port, -p <number> Port override
|
|
1210
|
-
--entry <file> Custom entry file (default:
|
|
1927
|
+
--entry <file> Custom entry file (default: server.ts)
|
|
1211
1928
|
--no-clear Don't clear screen on restart
|
|
1212
1929
|
|
|
1213
1930
|
Build Options:
|
|
@@ -1222,7 +1939,7 @@ function printHelp() {
|
|
|
1222
1939
|
--entry <file> Entry file within output dir
|
|
1223
1940
|
|
|
1224
1941
|
Generate Types:
|
|
1225
|
-
route <path> API route (e.g., voltx generate route
|
|
1942
|
+
route <path> API route (e.g., voltx generate route users)
|
|
1226
1943
|
agent <name> Agent (e.g., voltx generate agent assistant)
|
|
1227
1944
|
tool <name> Tool (e.g., voltx generate tool search)
|
|
1228
1945
|
job <name> Background job (e.g., voltx generate job cleanup)
|
|
@@ -1232,7 +1949,7 @@ function printHelp() {
|
|
|
1232
1949
|
voltx dev --port 4000
|
|
1233
1950
|
voltx build --sourcemap
|
|
1234
1951
|
voltx start
|
|
1235
|
-
voltx generate route
|
|
1952
|
+
voltx generate route users --method GET
|
|
1236
1953
|
voltx generate agent assistant
|
|
1237
1954
|
`);
|
|
1238
1955
|
}
|