@voltx/cli 0.3.2 → 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 +31 -9
- package/dist/chunk-4T26KROZ.mjs +456 -0
- package/dist/chunk-5RN2FYST.mjs +494 -0
- package/dist/chunk-CFWXMM7Q.mjs +450 -0
- package/dist/chunk-EXMRIKIX.mjs +404 -0
- package/dist/chunk-FN7KZJ6H.mjs +363 -0
- package/dist/chunk-HAFJKS2O.mjs +141 -0
- package/dist/chunk-HM7F67C2.mjs +405 -0
- package/dist/chunk-P5FSO2UH.mjs +497 -0
- package/dist/chunk-UO43CY7Y.mjs +497 -0
- package/dist/cli.js +242 -150
- package/dist/create.js +239 -147
- package/dist/create.mjs +1 -1
- package/dist/generate.js +1 -1
- package/dist/generate.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +241 -149
- package/dist/index.mjs +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,363 @@
|
|
|
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
|
+
var V = "^0.3.0";
|
|
12
|
+
var TEMPLATE_DEPS = {
|
|
13
|
+
blank: { "@voltx/core": V, "@voltx/server": V },
|
|
14
|
+
chatbot: { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/memory": V },
|
|
15
|
+
"rag-app": { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/rag": V, "@voltx/db": V },
|
|
16
|
+
"agent-app": { "@voltx/core": V, "@voltx/ai": V, "@voltx/server": V, "@voltx/agents": V, "@voltx/memory": V }
|
|
17
|
+
};
|
|
18
|
+
async function createProject(options) {
|
|
19
|
+
const { name, template = "blank", auth = "none" } = options;
|
|
20
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
21
|
+
if (fs.existsSync(targetDir)) {
|
|
22
|
+
console.error(`[voltx] Directory "${name}" already exists.`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
26
|
+
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
27
|
+
const model = template === "rag-app" ? "gpt-4o" : "llama3.1-8b";
|
|
28
|
+
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
29
|
+
const deps = { ...TEMPLATE_DEPS[template] ?? TEMPLATE_DEPS["blank"], "@voltx/cli": V };
|
|
30
|
+
if (auth === "better-auth") {
|
|
31
|
+
deps["@voltx/auth"] = V;
|
|
32
|
+
deps["better-auth"] = "^1.5.0";
|
|
33
|
+
} else if (auth === "jwt") {
|
|
34
|
+
deps["@voltx/auth"] = V;
|
|
35
|
+
deps["jose"] = "^6.0.0";
|
|
36
|
+
}
|
|
37
|
+
fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify({
|
|
38
|
+
name,
|
|
39
|
+
version: "0.1.0",
|
|
40
|
+
private: true,
|
|
41
|
+
scripts: { dev: "voltx dev", build: "voltx build", start: "voltx start" },
|
|
42
|
+
dependencies: deps,
|
|
43
|
+
devDependencies: { typescript: "^5.7.0", tsx: "^4.21.0", tsup: "^8.0.0", "@types/node": "^22.0.0" }
|
|
44
|
+
}, null, 2));
|
|
45
|
+
let config = `import { defineConfig } from "@voltx/core";
|
|
46
|
+
|
|
47
|
+
export default defineConfig({
|
|
48
|
+
name: "${name}",
|
|
49
|
+
port: 3000,
|
|
50
|
+
ai: {
|
|
51
|
+
provider: "${provider}",
|
|
52
|
+
model: "${model}",
|
|
53
|
+
},`;
|
|
54
|
+
if (hasDb) config += `
|
|
55
|
+
db: {
|
|
56
|
+
url: process.env.DATABASE_URL,
|
|
57
|
+
},`;
|
|
58
|
+
if (auth !== "none") config += `
|
|
59
|
+
auth: {
|
|
60
|
+
provider: "${auth}",
|
|
61
|
+
},`;
|
|
62
|
+
config += `
|
|
63
|
+
server: {
|
|
64
|
+
routesDir: "src/routes",
|
|
65
|
+
staticDir: "public",
|
|
66
|
+
cors: true,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
`;
|
|
70
|
+
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), config);
|
|
71
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api"), { recursive: true });
|
|
72
|
+
fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
|
|
73
|
+
fs.writeFileSync(path.join(targetDir, "tsconfig.json"), JSON.stringify({
|
|
74
|
+
compilerOptions: { target: "ES2022", module: "ESNext", moduleResolution: "bundler", strict: true, esModuleInterop: true, skipLibCheck: true, outDir: "dist" },
|
|
75
|
+
include: ["src", "voltx.config.ts"]
|
|
76
|
+
}, null, 2));
|
|
77
|
+
fs.writeFileSync(
|
|
78
|
+
path.join(targetDir, "src", "index.ts"),
|
|
79
|
+
`import { createApp } from "@voltx/core";
|
|
80
|
+
import config from "../voltx.config";
|
|
81
|
+
|
|
82
|
+
const app = createApp(config);
|
|
83
|
+
app.start();
|
|
84
|
+
`
|
|
85
|
+
);
|
|
86
|
+
fs.writeFileSync(
|
|
87
|
+
path.join(targetDir, "src", "routes", "index.ts"),
|
|
88
|
+
`// GET / \u2014 Health check
|
|
89
|
+
import type { Context } from "@voltx/server";
|
|
90
|
+
|
|
91
|
+
export function GET(c: Context) {
|
|
92
|
+
return c.json({ name: "${name}", status: "ok" });
|
|
93
|
+
}
|
|
94
|
+
`
|
|
95
|
+
);
|
|
96
|
+
if (template === "chatbot" || template === "agent-app") {
|
|
97
|
+
fs.writeFileSync(
|
|
98
|
+
path.join(targetDir, "src", "routes", "api", "chat.ts"),
|
|
99
|
+
`// POST /api/chat \u2014 Streaming chat with conversation memory
|
|
100
|
+
import type { Context } from "@voltx/server";
|
|
101
|
+
import { streamText } from "@voltx/ai";
|
|
102
|
+
import { createMemory } from "@voltx/memory";
|
|
103
|
+
|
|
104
|
+
const memory = createMemory({ maxMessages: 50 });
|
|
105
|
+
|
|
106
|
+
export async function POST(c: Context) {
|
|
107
|
+
const { messages, conversationId = "default" } = await c.req.json();
|
|
108
|
+
|
|
109
|
+
const lastMessage = messages[messages.length - 1];
|
|
110
|
+
if (lastMessage?.role === "user") {
|
|
111
|
+
await memory.add(conversationId, { role: "user", content: lastMessage.content });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const history = await memory.get(conversationId);
|
|
115
|
+
|
|
116
|
+
const result = await streamText({
|
|
117
|
+
model: "${provider}:${model}",
|
|
118
|
+
system: "You are a helpful AI assistant.",
|
|
119
|
+
messages: history.map((m) => ({ role: m.role, content: m.content })),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
result.text.then(async (text) => {
|
|
123
|
+
await memory.add(conversationId, { role: "assistant", content: text });
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return result.toSSEResponse();
|
|
127
|
+
}
|
|
128
|
+
`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
if (template === "agent-app") {
|
|
132
|
+
fs.mkdirSync(path.join(targetDir, "src", "agents"), { recursive: true });
|
|
133
|
+
fs.mkdirSync(path.join(targetDir, "src", "tools"), { recursive: true });
|
|
134
|
+
fs.writeFileSync(path.join(targetDir, "src", "tools", "calculator.ts"), `// Calculator tool \u2014 evaluates math expressions (no API key needed)
|
|
135
|
+
import type { Tool } from "@voltx/agents";
|
|
136
|
+
|
|
137
|
+
export const calculatorTool: Tool = {
|
|
138
|
+
name: "calculator",
|
|
139
|
+
description: "Evaluate a math expression. Supports +, -, *, /, %, parentheses, and Math functions.",
|
|
140
|
+
parameters: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: { expression: { type: "string", description: "The math expression to evaluate" } },
|
|
143
|
+
required: ["expression"],
|
|
144
|
+
},
|
|
145
|
+
async execute(args: { expression: string }) {
|
|
146
|
+
try {
|
|
147
|
+
const safe = args.expression.replace(/[^0-9+\\-*/.()%\\s,]|(?<!Math)\\.[a-z]/gi, (match) => {
|
|
148
|
+
if (args.expression.includes("Math.")) return match;
|
|
149
|
+
throw new Error("Invalid character: " + match);
|
|
150
|
+
});
|
|
151
|
+
const result = new Function("return " + safe)();
|
|
152
|
+
return \`\${args.expression} = \${result}\`;
|
|
153
|
+
} catch (err) {
|
|
154
|
+
return \`Error: \${err instanceof Error ? err.message : String(err)}\`;
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
`);
|
|
159
|
+
fs.writeFileSync(path.join(targetDir, "src", "tools", "datetime.ts"), `// Date & time tool \u2014 returns current date, time, timezone (no API key needed)
|
|
160
|
+
import type { Tool } from "@voltx/agents";
|
|
161
|
+
|
|
162
|
+
export const datetimeTool: Tool = {
|
|
163
|
+
name: "datetime",
|
|
164
|
+
description: "Get the current date, time, day of week, and timezone.",
|
|
165
|
+
parameters: {
|
|
166
|
+
type: "object",
|
|
167
|
+
properties: { timezone: { type: "string", description: "Optional IANA timezone. Defaults to server timezone." } },
|
|
168
|
+
},
|
|
169
|
+
async execute(args: { timezone?: string }) {
|
|
170
|
+
const now = new Date();
|
|
171
|
+
const tz = args.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
172
|
+
const formatted = now.toLocaleString("en-US", {
|
|
173
|
+
timeZone: tz, weekday: "long", year: "numeric", month: "long", day: "numeric",
|
|
174
|
+
hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true,
|
|
175
|
+
});
|
|
176
|
+
return \`Current date/time (\${tz}): \${formatted}\`;
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
`);
|
|
180
|
+
fs.writeFileSync(path.join(targetDir, "src", "agents", "assistant.ts"), `// AI Agent \u2014 autonomous assistant with tools
|
|
181
|
+
import { createAgent } from "@voltx/agents";
|
|
182
|
+
import { calculatorTool } from "../tools/calculator";
|
|
183
|
+
import { datetimeTool } from "../tools/datetime";
|
|
184
|
+
|
|
185
|
+
export const assistant = createAgent({
|
|
186
|
+
name: "assistant",
|
|
187
|
+
model: "${provider}:${model}",
|
|
188
|
+
instructions: "You are a helpful AI assistant with access to tools: Calculator, Date & Time. Use them when needed to answer questions accurately.",
|
|
189
|
+
tools: [calculatorTool, datetimeTool],
|
|
190
|
+
maxIterations: 5,
|
|
191
|
+
});
|
|
192
|
+
`);
|
|
193
|
+
fs.writeFileSync(
|
|
194
|
+
path.join(targetDir, "src", "routes", "api", "agent.ts"),
|
|
195
|
+
`// POST /api/agent \u2014 Run the AI agent
|
|
196
|
+
import type { Context } from "@voltx/server";
|
|
197
|
+
import { assistant } from "../../agents/assistant";
|
|
198
|
+
|
|
199
|
+
export async function POST(c: Context) {
|
|
200
|
+
const { input } = await c.req.json();
|
|
201
|
+
if (!input) return c.json({ error: "Missing 'input' field" }, 400);
|
|
202
|
+
const result = await assistant.run(input);
|
|
203
|
+
return c.json({ content: result.content, steps: result.steps });
|
|
204
|
+
}
|
|
205
|
+
`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (template === "rag-app") {
|
|
209
|
+
const embedModel = "openai:text-embedding-3-small";
|
|
210
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "rag"), { recursive: true });
|
|
211
|
+
fs.writeFileSync(
|
|
212
|
+
path.join(targetDir, "src", "routes", "api", "rag", "query.ts"),
|
|
213
|
+
`// POST /api/rag/query \u2014 Query documents with RAG
|
|
214
|
+
import type { Context } from "@voltx/server";
|
|
215
|
+
import { streamText } from "@voltx/ai";
|
|
216
|
+
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
217
|
+
import { createVectorStore } from "@voltx/db";
|
|
218
|
+
|
|
219
|
+
const vectorStore = createVectorStore();
|
|
220
|
+
const embedder = createEmbedder({ model: "${embedModel}" });
|
|
221
|
+
const rag = createRAGPipeline({ embedder, vectorStore });
|
|
222
|
+
|
|
223
|
+
export async function POST(c: Context) {
|
|
224
|
+
const { question } = await c.req.json();
|
|
225
|
+
const context = await rag.getContext(question, { topK: 5 });
|
|
226
|
+
const result = await streamText({
|
|
227
|
+
model: "${provider}:${model}",
|
|
228
|
+
system: \`Answer based on context. If not relevant, say so.\\n\\nContext:\\n\${context}\`,
|
|
229
|
+
messages: [{ role: "user", content: question }],
|
|
230
|
+
});
|
|
231
|
+
return result.toSSEResponse();
|
|
232
|
+
}
|
|
233
|
+
`
|
|
234
|
+
);
|
|
235
|
+
fs.writeFileSync(
|
|
236
|
+
path.join(targetDir, "src", "routes", "api", "rag", "ingest.ts"),
|
|
237
|
+
`// POST /api/rag/ingest \u2014 Ingest documents into the vector store
|
|
238
|
+
import type { Context } from "@voltx/server";
|
|
239
|
+
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
240
|
+
import { createVectorStore } from "@voltx/db";
|
|
241
|
+
|
|
242
|
+
const vectorStore = createVectorStore();
|
|
243
|
+
const embedder = createEmbedder({ model: "${embedModel}" });
|
|
244
|
+
const rag = createRAGPipeline({ embedder, vectorStore });
|
|
245
|
+
|
|
246
|
+
export async function POST(c: Context) {
|
|
247
|
+
const { text, idPrefix } = await c.req.json();
|
|
248
|
+
if (!text || typeof text !== "string") return c.json({ error: "Missing 'text' field" }, 400);
|
|
249
|
+
const result = await rag.ingest(text, idPrefix ?? "doc");
|
|
250
|
+
return c.json({ status: "ok", chunks: result.chunks, ids: result.ids });
|
|
251
|
+
}
|
|
252
|
+
`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (auth === "better-auth") {
|
|
256
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "auth"), { recursive: true });
|
|
257
|
+
fs.writeFileSync(
|
|
258
|
+
path.join(targetDir, "src", "routes", "api", "auth", "[...path].ts"),
|
|
259
|
+
`// ALL /api/auth/* \u2014 Better Auth handler
|
|
260
|
+
import type { Context } from "@voltx/server";
|
|
261
|
+
import { auth } from "../../../lib/auth";
|
|
262
|
+
import { createAuthHandler } from "@voltx/auth";
|
|
263
|
+
|
|
264
|
+
const handler = createAuthHandler(auth);
|
|
265
|
+
|
|
266
|
+
export const GET = (c: Context) => handler(c);
|
|
267
|
+
export const POST = (c: Context) => handler(c);
|
|
268
|
+
`
|
|
269
|
+
);
|
|
270
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
271
|
+
fs.writeFileSync(
|
|
272
|
+
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
273
|
+
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
274
|
+
|
|
275
|
+
export const auth = createAuth("better-auth", {
|
|
276
|
+
database: process.env.DATABASE_URL!,
|
|
277
|
+
emailAndPassword: true,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
export const authMiddleware = createAuthMiddleware({
|
|
281
|
+
provider: auth,
|
|
282
|
+
publicPaths: ["/api/auth", "/api/health", "/"],
|
|
283
|
+
});
|
|
284
|
+
`
|
|
285
|
+
);
|
|
286
|
+
} else if (auth === "jwt") {
|
|
287
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
288
|
+
fs.writeFileSync(
|
|
289
|
+
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
290
|
+
`import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
291
|
+
|
|
292
|
+
export const jwt = createAuth("jwt", {
|
|
293
|
+
secret: process.env.JWT_SECRET!,
|
|
294
|
+
expiresIn: "7d",
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
export const authMiddleware = createAuthMiddleware({
|
|
298
|
+
provider: jwt,
|
|
299
|
+
publicPaths: ["/api/auth", "/api/health", "/"],
|
|
300
|
+
});
|
|
301
|
+
`
|
|
302
|
+
);
|
|
303
|
+
fs.writeFileSync(
|
|
304
|
+
path.join(targetDir, "src", "routes", "api", "auth.ts"),
|
|
305
|
+
`import type { Context } from "@voltx/server";
|
|
306
|
+
import { jwt } from "../../lib/auth";
|
|
307
|
+
|
|
308
|
+
export async function POST(c: Context) {
|
|
309
|
+
const { email, password } = await c.req.json();
|
|
310
|
+
if (!email || !password) return c.json({ error: "Email and password are required" }, 400);
|
|
311
|
+
const token = await jwt.sign({ sub: email, email });
|
|
312
|
+
return c.json({ token });
|
|
313
|
+
}
|
|
314
|
+
`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
let envContent = "";
|
|
318
|
+
if (template === "rag-app") {
|
|
319
|
+
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";
|
|
320
|
+
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";
|
|
321
|
+
} else if (template === "chatbot" || template === "agent-app") {
|
|
322
|
+
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";
|
|
323
|
+
if (template === "agent-app") {
|
|
324
|
+
envContent += "# \u2500\u2500\u2500 Database (optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
325
|
+
envContent += "# \u2500\u2500\u2500 Tool API Keys (add keys for tools you use) \u2500\u2500\n";
|
|
326
|
+
envContent += "# TAVILY_API_KEY=tvly-... (Web Search \u2014 https://tavily.com)\n";
|
|
327
|
+
envContent += "# SERPER_API_KEY= (Google Search \u2014 https://serper.dev)\n";
|
|
328
|
+
envContent += "# OPENWEATHER_API_KEY= (Weather \u2014 https://openweathermap.org/api)\n";
|
|
329
|
+
envContent += "# NEWS_API_KEY= (News \u2014 https://newsapi.org)\n\n";
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
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";
|
|
333
|
+
}
|
|
334
|
+
if (auth === "better-auth") {
|
|
335
|
+
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";
|
|
336
|
+
} else if (auth === "jwt") {
|
|
337
|
+
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";
|
|
338
|
+
}
|
|
339
|
+
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";
|
|
340
|
+
fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
|
|
341
|
+
fs.writeFileSync(path.join(targetDir, ".gitignore"), "node_modules\ndist\n.env\n");
|
|
342
|
+
printWelcomeBanner(name);
|
|
343
|
+
}
|
|
344
|
+
var isDirectRun = typeof __require !== "undefined" && __require.main === module && process.argv[1]?.includes("create");
|
|
345
|
+
if (isDirectRun) {
|
|
346
|
+
const projectName = process.argv[2];
|
|
347
|
+
if (!projectName) {
|
|
348
|
+
console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
|
|
349
|
+
process.exit(1);
|
|
350
|
+
}
|
|
351
|
+
const templateFlag = process.argv.indexOf("--template");
|
|
352
|
+
const template = templateFlag !== -1 ? process.argv[templateFlag + 1] : "blank";
|
|
353
|
+
const authFlag = process.argv.indexOf("--auth");
|
|
354
|
+
const auth = authFlag !== -1 ? process.argv[authFlag + 1] : "none";
|
|
355
|
+
createProject({ name: projectName, template, auth }).catch((err) => {
|
|
356
|
+
console.error("[voltx] Error:", err);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export {
|
|
362
|
+
createProject
|
|
363
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// src/generate.ts
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
3
|
+
import { join, dirname } from "path";
|
|
4
|
+
async function runGenerate(options) {
|
|
5
|
+
const cwd = process.cwd();
|
|
6
|
+
const { type, name } = options;
|
|
7
|
+
switch (type) {
|
|
8
|
+
case "route":
|
|
9
|
+
generateRoute(cwd, name, options.method);
|
|
10
|
+
break;
|
|
11
|
+
case "agent":
|
|
12
|
+
generateAgent(cwd, name);
|
|
13
|
+
break;
|
|
14
|
+
case "tool":
|
|
15
|
+
generateTool(cwd, name);
|
|
16
|
+
break;
|
|
17
|
+
case "job":
|
|
18
|
+
generateJob(cwd, name);
|
|
19
|
+
break;
|
|
20
|
+
default:
|
|
21
|
+
console.error(`[voltx] Unknown generator type: ${type}`);
|
|
22
|
+
console.error("[voltx] Available: route, agent, tool, job");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function generateRoute(cwd, name, method = "POST") {
|
|
27
|
+
const routePath = name.startsWith("/") ? name.slice(1) : name;
|
|
28
|
+
const filePath = join(cwd, "src", "routes", `${routePath}.ts`);
|
|
29
|
+
if (existsSync(filePath)) {
|
|
30
|
+
console.error(`[voltx] Route already exists: src/routes/${routePath}.ts`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
const upperMethod = method.toUpperCase();
|
|
34
|
+
const urlPath = "/" + routePath;
|
|
35
|
+
const content = `// ${upperMethod} ${urlPath}
|
|
36
|
+
import type { Context } from "@voltx/server";
|
|
37
|
+
|
|
38
|
+
export async function ${upperMethod}(c: Context) {
|
|
39
|
+
return c.json({ message: "Hello from ${urlPath}" });
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
writeFileSafe(filePath, content);
|
|
43
|
+
console.log(` \u2713 Created route: src/routes/${routePath}.ts`);
|
|
44
|
+
console.log(` ${upperMethod} ${urlPath}`);
|
|
45
|
+
}
|
|
46
|
+
function generateAgent(cwd, name) {
|
|
47
|
+
const filePath = join(cwd, "src", "agents", `${name}.ts`);
|
|
48
|
+
if (existsSync(filePath)) {
|
|
49
|
+
console.error(`[voltx] Agent already exists: src/agents/${name}.ts`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
const content = `// Agent: ${name}
|
|
53
|
+
import { createAgent } from "@voltx/agents";
|
|
54
|
+
|
|
55
|
+
export const ${toCamelCase(name)} = createAgent({
|
|
56
|
+
name: "${name}",
|
|
57
|
+
model: "cerebras:llama3.1-8b",
|
|
58
|
+
instructions: "You are a helpful AI assistant named ${name}.",
|
|
59
|
+
tools: [
|
|
60
|
+
// Add tools here:
|
|
61
|
+
// {
|
|
62
|
+
// name: "example",
|
|
63
|
+
// description: "An example tool",
|
|
64
|
+
// parameters: { type: "object", properties: { input: { type: "string" } }, required: ["input"] },
|
|
65
|
+
// execute: async (params) => \`Processed: \${params.input}\`,
|
|
66
|
+
// },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
`;
|
|
70
|
+
writeFileSafe(filePath, content);
|
|
71
|
+
console.log(` \u2713 Created agent: src/agents/${name}.ts`);
|
|
72
|
+
}
|
|
73
|
+
function generateTool(cwd, name) {
|
|
74
|
+
const filePath = join(cwd, "src", "tools", `${name}.ts`);
|
|
75
|
+
if (existsSync(filePath)) {
|
|
76
|
+
console.error(`[voltx] Tool already exists: src/tools/${name}.ts`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
const content = `// Tool: ${name}
|
|
80
|
+
|
|
81
|
+
export const ${toCamelCase(name)}Tool = {
|
|
82
|
+
name: "${name}",
|
|
83
|
+
description: "TODO: Describe what this tool does",
|
|
84
|
+
parameters: {
|
|
85
|
+
type: "object" as const,
|
|
86
|
+
properties: {
|
|
87
|
+
input: { type: "string", description: "The input to process" },
|
|
88
|
+
},
|
|
89
|
+
required: ["input"],
|
|
90
|
+
},
|
|
91
|
+
execute: async (params: { input: string }): Promise<string> => {
|
|
92
|
+
// TODO: Implement tool logic
|
|
93
|
+
return \`${name} result for: \${params.input}\`;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
`;
|
|
97
|
+
writeFileSafe(filePath, content);
|
|
98
|
+
console.log(` \u2713 Created tool: src/tools/${name}.ts`);
|
|
99
|
+
}
|
|
100
|
+
function generateJob(cwd, name) {
|
|
101
|
+
const filePath = join(cwd, "src", "jobs", `${name}.ts`);
|
|
102
|
+
if (existsSync(filePath)) {
|
|
103
|
+
console.error(`[voltx] Job already exists: src/jobs/${name}.ts`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const content = `// Job: ${name}
|
|
107
|
+
// Runs on a schedule or triggered via ctx.jobs.enqueue("${name}", data)
|
|
108
|
+
|
|
109
|
+
export const config = {
|
|
110
|
+
// Cron schedule (uncomment to enable):
|
|
111
|
+
// schedule: "0 */6 * * *", // every 6 hours
|
|
112
|
+
//
|
|
113
|
+
// Or make it a queue job (triggered on-demand):
|
|
114
|
+
queue: true,
|
|
115
|
+
retries: 3,
|
|
116
|
+
timeout: "5m",
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export async function run(ctx: any, data?: Record<string, unknown>) {
|
|
120
|
+
console.log("[job:${name}] Running...", data);
|
|
121
|
+
|
|
122
|
+
// TODO: Implement job logic
|
|
123
|
+
|
|
124
|
+
console.log("[job:${name}] Done.");
|
|
125
|
+
}
|
|
126
|
+
`;
|
|
127
|
+
writeFileSafe(filePath, content);
|
|
128
|
+
console.log(` \u2713 Created job: src/jobs/${name}.ts`);
|
|
129
|
+
}
|
|
130
|
+
function writeFileSafe(filePath, content) {
|
|
131
|
+
const dir = dirname(filePath);
|
|
132
|
+
mkdirSync(dir, { recursive: true });
|
|
133
|
+
writeFileSync(filePath, content, "utf-8");
|
|
134
|
+
}
|
|
135
|
+
function toCamelCase(str) {
|
|
136
|
+
return str.replace(/[-_](.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toLowerCase());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
runGenerate
|
|
141
|
+
};
|