@voltx/cli 0.2.0 → 0.3.0
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 +65 -0
- package/dist/build.d.mts +19 -0
- package/dist/build.d.ts +19 -0
- package/dist/build.js +145 -0
- package/dist/build.mjs +7 -0
- package/dist/chunk-AONHLE42.mjs +141 -0
- package/dist/chunk-BIE3F5AW.mjs +261 -0
- package/dist/chunk-BLBAHJXR.mjs +239 -0
- package/dist/chunk-IGBR4TFT.mjs +351 -0
- package/dist/chunk-IV352HZA.mjs +107 -0
- package/dist/chunk-KFHPTRKZ.mjs +405 -0
- package/dist/chunk-L3247M3A.mjs +81 -0
- package/dist/chunk-RN7BUALR.mjs +120 -0
- package/dist/chunk-SGL7RBD5.mjs +355 -0
- package/dist/chunk-TUZ3MHD4.mjs +355 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/chunk-ZA7EJWJI.mjs +221 -0
- package/dist/chunk-ZB2F3WTS.mjs +121 -0
- package/dist/chunk-ZLIPYI22.mjs +350 -0
- package/dist/cli.js +945 -59
- package/dist/cli.mjs +123 -23
- package/dist/create.d.mts +1 -0
- package/dist/create.d.ts +1 -0
- package/dist/create.js +308 -36
- package/dist/create.mjs +3 -2
- package/dist/dev.d.mts +19 -0
- package/dist/dev.d.ts +19 -0
- package/dist/dev.js +144 -0
- package/dist/dev.mjs +7 -0
- package/dist/generate.d.mts +13 -0
- package/dist/generate.d.ts +13 -0
- package/dist/generate.js +165 -0
- package/dist/generate.mjs +7 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +770 -39
- package/dist/index.mjs +21 -4
- package/dist/start.d.mts +16 -0
- package/dist/start.d.ts +16 -0
- package/dist/start.js +105 -0
- package/dist/start.mjs +7 -0
- package/dist/welcome.js +1 -1
- package/dist/welcome.mjs +2 -1
- package/package.json +24 -22
- package/LICENSE +0 -21
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// src/welcome.ts
|
|
2
|
+
var ESC = "\x1B[";
|
|
3
|
+
var RESET = `${ESC}0m`;
|
|
4
|
+
var ITALIC = `${ESC}3m`;
|
|
5
|
+
function rgb(r, g, b, text) {
|
|
6
|
+
return `${ESC}38;2;${r};${g};${b}m${text}${RESET}`;
|
|
7
|
+
}
|
|
8
|
+
function lerp(a, b, t) {
|
|
9
|
+
return Math.round(a + (b - a) * t);
|
|
10
|
+
}
|
|
11
|
+
function gradientLine(text, from, to) {
|
|
12
|
+
const len = text.length;
|
|
13
|
+
if (len === 0) return "";
|
|
14
|
+
return text.split("").map((char, i) => {
|
|
15
|
+
const t = len === 1 ? 0 : i / (len - 1);
|
|
16
|
+
const r = lerp(from.r, to.r, t);
|
|
17
|
+
const g = lerp(from.g, to.g, t);
|
|
18
|
+
const b = lerp(from.b, to.b, t);
|
|
19
|
+
return rgb(r, g, b, char);
|
|
20
|
+
}).join("");
|
|
21
|
+
}
|
|
22
|
+
function gradientBlock(lines, colors) {
|
|
23
|
+
return lines.map((line, i) => {
|
|
24
|
+
const t = lines.length === 1 ? 0 : i / (lines.length - 1);
|
|
25
|
+
const segIndex = t * (colors.length - 1);
|
|
26
|
+
const fromIdx = Math.floor(segIndex);
|
|
27
|
+
const toIdx = Math.min(fromIdx + 1, colors.length - 1);
|
|
28
|
+
const localT = segIndex - fromIdx;
|
|
29
|
+
const from = {
|
|
30
|
+
r: lerp(colors[fromIdx].r, colors[toIdx].r, localT),
|
|
31
|
+
g: lerp(colors[fromIdx].g, colors[toIdx].g, localT),
|
|
32
|
+
b: lerp(colors[fromIdx].b, colors[toIdx].b, localT)
|
|
33
|
+
};
|
|
34
|
+
const to = {
|
|
35
|
+
r: lerp(colors[Math.min(fromIdx + 1, colors.length - 1)].r, colors[Math.min(toIdx + 1, colors.length - 1)].r, localT),
|
|
36
|
+
g: lerp(colors[Math.min(fromIdx + 1, colors.length - 1)].g, colors[Math.min(toIdx + 1, colors.length - 1)].g, localT),
|
|
37
|
+
b: lerp(colors[Math.min(fromIdx + 1, colors.length - 1)].b, colors[Math.min(toIdx + 1, colors.length - 1)].b, localT)
|
|
38
|
+
};
|
|
39
|
+
return gradientLine(line, from, to);
|
|
40
|
+
}).join("\n");
|
|
41
|
+
}
|
|
42
|
+
var VOLTX_BANNER = [
|
|
43
|
+
" \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557",
|
|
44
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D",
|
|
45
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D ",
|
|
46
|
+
" \u255A\u2588\u2588\u2557 \u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 ",
|
|
47
|
+
" \u255A\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2557",
|
|
48
|
+
" \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
|
|
49
|
+
];
|
|
50
|
+
var VOLTX_COLORS = [
|
|
51
|
+
{ r: 0, g: 180, b: 255 },
|
|
52
|
+
// electric blue
|
|
53
|
+
{ r: 120, g: 80, b: 255 },
|
|
54
|
+
// purple
|
|
55
|
+
{ r: 255, g: 50, b: 180 },
|
|
56
|
+
// hot pink
|
|
57
|
+
{ r: 255, g: 120, b: 50 }
|
|
58
|
+
// orange
|
|
59
|
+
];
|
|
60
|
+
function dimRule(width = 48) {
|
|
61
|
+
return ` ${rgb(60, 60, 80, "\u2500".repeat(width))}`;
|
|
62
|
+
}
|
|
63
|
+
function printWelcomeBanner(projectName) {
|
|
64
|
+
console.log("");
|
|
65
|
+
console.log(gradientBlock(VOLTX_BANNER, VOLTX_COLORS));
|
|
66
|
+
console.log("");
|
|
67
|
+
console.log(
|
|
68
|
+
` ${ITALIC}${rgb(180, 180, 220, "The AI-first full-stack framework")}${RESET}`
|
|
69
|
+
);
|
|
70
|
+
console.log("");
|
|
71
|
+
if (projectName) {
|
|
72
|
+
console.log(
|
|
73
|
+
` ${ITALIC}${rgb(120, 220, 180, `Thank you for choosing VoltX! \u26A1`)}${RESET}`
|
|
74
|
+
);
|
|
75
|
+
console.log(
|
|
76
|
+
` ${ITALIC}${rgb(160, 160, 200, `Your project "${projectName}" is ready to go.`)}${RESET}`
|
|
77
|
+
);
|
|
78
|
+
console.log("");
|
|
79
|
+
console.log(` ${rgb(100, 180, 255, "Next steps:")}`);
|
|
80
|
+
console.log(` ${rgb(200, 200, 220, ` cd ${projectName}`)}`);
|
|
81
|
+
console.log(` ${rgb(200, 200, 220, " pnpm install")}`);
|
|
82
|
+
console.log(` ${rgb(200, 200, 220, " pnpm dev # or: npx voltx dev")}`);
|
|
83
|
+
console.log("");
|
|
84
|
+
console.log(dimRule());
|
|
85
|
+
console.log("");
|
|
86
|
+
console.log(
|
|
87
|
+
` ${rgb(255, 200, 80, "\u2615")} ${ITALIC}${rgb(200, 180, 140, "Love VoltX? Support us and fuel the next update:")}${RESET}`
|
|
88
|
+
);
|
|
89
|
+
console.log(
|
|
90
|
+
` ${rgb(255, 180, 100, "https://buymeacoffee.com/promptlyai")}`
|
|
91
|
+
);
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(dimRule());
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log(
|
|
96
|
+
` ${ITALIC}${rgb(140, 140, 170, "Docs: https://voltx.co.in \u2022 GitHub: github.com/codewithshail/voltx")}${RESET}`
|
|
97
|
+
);
|
|
98
|
+
console.log(
|
|
99
|
+
` ${ITALIC}${rgb(100, 100, 130, "Made with \u2665 by the Promptly AI Team")}${RESET}`
|
|
100
|
+
);
|
|
101
|
+
console.log("");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
printWelcomeBanner
|
|
107
|
+
};
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import {
|
|
2
|
+
printWelcomeBanner
|
|
3
|
+
} from "./chunk-IV352HZA.mjs";
|
|
4
|
+
import {
|
|
5
|
+
__require
|
|
6
|
+
} from "./chunk-Y6FXYEAI.mjs";
|
|
7
|
+
|
|
8
|
+
// src/create.ts
|
|
9
|
+
import * as fs from "fs";
|
|
10
|
+
import * as path from "path";
|
|
11
|
+
async function createProject(options) {
|
|
12
|
+
const { name, template = "blank", auth = "none" } = options;
|
|
13
|
+
const targetDir = path.resolve(process.cwd(), name);
|
|
14
|
+
if (fs.existsSync(targetDir)) {
|
|
15
|
+
console.error(`[voltx] Directory "${name}" already exists.`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
19
|
+
const templateDeps = {
|
|
20
|
+
blank: {
|
|
21
|
+
"@voltx/core": "^0.3.0",
|
|
22
|
+
"@voltx/server": "^0.3.0"
|
|
23
|
+
},
|
|
24
|
+
chatbot: {
|
|
25
|
+
"@voltx/core": "^0.3.0",
|
|
26
|
+
"@voltx/ai": "^0.3.0",
|
|
27
|
+
"@voltx/server": "^0.3.0",
|
|
28
|
+
"@voltx/memory": "^0.3.0"
|
|
29
|
+
},
|
|
30
|
+
"rag-app": {
|
|
31
|
+
"@voltx/core": "^0.3.0",
|
|
32
|
+
"@voltx/ai": "^0.3.0",
|
|
33
|
+
"@voltx/server": "^0.3.0",
|
|
34
|
+
"@voltx/rag": "^0.3.0",
|
|
35
|
+
"@voltx/db": "^0.3.0"
|
|
36
|
+
},
|
|
37
|
+
"agent-app": {
|
|
38
|
+
"@voltx/core": "^0.3.0",
|
|
39
|
+
"@voltx/ai": "^0.3.0",
|
|
40
|
+
"@voltx/server": "^0.3.0",
|
|
41
|
+
"@voltx/agents": "^0.3.0",
|
|
42
|
+
"@voltx/memory": "^0.3.0"
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const packageJson = {
|
|
46
|
+
name,
|
|
47
|
+
version: "0.1.0",
|
|
48
|
+
private: true,
|
|
49
|
+
scripts: {
|
|
50
|
+
dev: "voltx dev",
|
|
51
|
+
build: "voltx build",
|
|
52
|
+
start: "voltx start"
|
|
53
|
+
},
|
|
54
|
+
dependencies: {
|
|
55
|
+
...templateDeps[template] ?? templateDeps["blank"],
|
|
56
|
+
"@voltx/cli": "^0.3.0",
|
|
57
|
+
...auth === "better-auth" ? { "@voltx/auth": "^0.3.0", "better-auth": "^1.5.0" } : {},
|
|
58
|
+
...auth === "jwt" ? { "@voltx/auth": "^0.3.0", "jose": "^6.0.0" } : {}
|
|
59
|
+
},
|
|
60
|
+
devDependencies: {
|
|
61
|
+
typescript: "^5.7.0",
|
|
62
|
+
tsx: "^4.21.0",
|
|
63
|
+
tsup: "^8.0.0",
|
|
64
|
+
"@types/node": "^22.0.0"
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
path.join(targetDir, "package.json"),
|
|
69
|
+
JSON.stringify(packageJson, null, 2)
|
|
70
|
+
);
|
|
71
|
+
const hasDb = template === "rag-app" || template === "agent-app" || auth === "better-auth";
|
|
72
|
+
const provider = template === "rag-app" ? "openai" : "cerebras";
|
|
73
|
+
const model = template === "rag-app" ? "gpt-4o" : "llama-4-scout-17b-16e";
|
|
74
|
+
let configContent = `import { defineConfig } from "@voltx/core";
|
|
75
|
+
|
|
76
|
+
export default defineConfig({
|
|
77
|
+
name: "${name}",
|
|
78
|
+
port: 3000,
|
|
79
|
+
ai: {
|
|
80
|
+
provider: "${provider}",
|
|
81
|
+
model: "${model}",
|
|
82
|
+
},`;
|
|
83
|
+
if (hasDb) {
|
|
84
|
+
configContent += `
|
|
85
|
+
db: {
|
|
86
|
+
url: process.env.DATABASE_URL,
|
|
87
|
+
},`;
|
|
88
|
+
}
|
|
89
|
+
if (auth !== "none") {
|
|
90
|
+
configContent += `
|
|
91
|
+
auth: {
|
|
92
|
+
provider: "${auth}",
|
|
93
|
+
},`;
|
|
94
|
+
}
|
|
95
|
+
configContent += `
|
|
96
|
+
server: {
|
|
97
|
+
routesDir: "src/routes",
|
|
98
|
+
staticDir: "public",
|
|
99
|
+
cors: true,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
`;
|
|
103
|
+
fs.writeFileSync(path.join(targetDir, "voltx.config.ts"), configContent);
|
|
104
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api"), { recursive: true });
|
|
105
|
+
fs.mkdirSync(path.join(targetDir, "public"), { recursive: true });
|
|
106
|
+
fs.writeFileSync(
|
|
107
|
+
path.join(targetDir, "src", "index.ts"),
|
|
108
|
+
`import { createApp } from "@voltx/core";
|
|
109
|
+
import config from "../voltx.config";
|
|
110
|
+
|
|
111
|
+
const app = createApp(config);
|
|
112
|
+
app.start();
|
|
113
|
+
`
|
|
114
|
+
);
|
|
115
|
+
fs.writeFileSync(
|
|
116
|
+
path.join(targetDir, "src", "routes", "index.ts"),
|
|
117
|
+
`// GET / \u2014 Health check
|
|
118
|
+
import type { Context } from "@voltx/server";
|
|
119
|
+
|
|
120
|
+
export function GET(c: Context) {
|
|
121
|
+
return c.json({ name: "${name}", status: "ok" });
|
|
122
|
+
}
|
|
123
|
+
`
|
|
124
|
+
);
|
|
125
|
+
if (template === "chatbot" || template === "agent-app") {
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
path.join(targetDir, "src", "routes", "api", "chat.ts"),
|
|
128
|
+
`// POST /api/chat \u2014 Streaming chat with conversation memory
|
|
129
|
+
import type { Context } from "@voltx/server";
|
|
130
|
+
import { streamText } from "@voltx/ai";
|
|
131
|
+
import { createMemory } from "@voltx/memory";
|
|
132
|
+
|
|
133
|
+
// In-memory for dev; swap to createMemory("postgres", { url }) for production
|
|
134
|
+
const memory = createMemory({ maxMessages: 50 });
|
|
135
|
+
|
|
136
|
+
export async function POST(c: Context) {
|
|
137
|
+
const { messages, conversationId = "default" } = await c.req.json();
|
|
138
|
+
|
|
139
|
+
// Store the latest user message
|
|
140
|
+
const lastMessage = messages[messages.length - 1];
|
|
141
|
+
if (lastMessage?.role === "user") {
|
|
142
|
+
await memory.add(conversationId, { role: "user", content: lastMessage.content });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Get conversation history from memory
|
|
146
|
+
const history = await memory.get(conversationId);
|
|
147
|
+
|
|
148
|
+
const result = await streamText({
|
|
149
|
+
model: "${provider}:${model}",
|
|
150
|
+
system: "You are a helpful AI assistant.",
|
|
151
|
+
messages: history.map((m) => ({ role: m.role, content: m.content })),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Store assistant response after stream completes
|
|
155
|
+
result.text.then(async (text) => {
|
|
156
|
+
await memory.add(conversationId, { role: "assistant", content: text });
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return result.toSSEResponse();
|
|
160
|
+
}
|
|
161
|
+
`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (template === "agent-app") {
|
|
165
|
+
fs.mkdirSync(path.join(targetDir, "src", "agents"), { recursive: true });
|
|
166
|
+
fs.mkdirSync(path.join(targetDir, "src", "tools"), { recursive: true });
|
|
167
|
+
fs.writeFileSync(
|
|
168
|
+
path.join(targetDir, "src", "agents", "assistant.ts"),
|
|
169
|
+
`import { createAgent } from "@voltx/agents";
|
|
170
|
+
import { searchTool } from "../tools/search";
|
|
171
|
+
|
|
172
|
+
export const assistant = createAgent({
|
|
173
|
+
name: "assistant",
|
|
174
|
+
model: "${provider}:${model}",
|
|
175
|
+
instructions: "You are a helpful AI assistant. Use your tools when needed.",
|
|
176
|
+
tools: [searchTool],
|
|
177
|
+
maxIterations: 5,
|
|
178
|
+
});
|
|
179
|
+
`
|
|
180
|
+
);
|
|
181
|
+
fs.writeFileSync(
|
|
182
|
+
path.join(targetDir, "src", "tools", "search.ts"),
|
|
183
|
+
`import type { Tool } from "@voltx/agents";
|
|
184
|
+
|
|
185
|
+
export const searchTool: Tool = {
|
|
186
|
+
name: "search",
|
|
187
|
+
description: "Search for information on a topic.",
|
|
188
|
+
parameters: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: { query: { type: "string", description: "The search query" } },
|
|
191
|
+
required: ["query"],
|
|
192
|
+
},
|
|
193
|
+
async execute(args: { query: string }) {
|
|
194
|
+
return \`Search results for "\${args.query}": Placeholder \u2014 connect a real search API.\`;
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
`
|
|
198
|
+
);
|
|
199
|
+
fs.writeFileSync(
|
|
200
|
+
path.join(targetDir, "src", "routes", "api", "agent.ts"),
|
|
201
|
+
`import type { Context } from "@voltx/server";
|
|
202
|
+
import { assistant } from "../../agents/assistant";
|
|
203
|
+
|
|
204
|
+
export async function POST(c: Context) {
|
|
205
|
+
const { input } = await c.req.json();
|
|
206
|
+
if (!input) return c.json({ error: "Missing 'input' field" }, 400);
|
|
207
|
+
const result = await assistant.run(input);
|
|
208
|
+
return c.json({ content: result.content, steps: result.steps });
|
|
209
|
+
}
|
|
210
|
+
`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
if (template === "rag-app") {
|
|
214
|
+
const embedModel = "openai:text-embedding-3-small";
|
|
215
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "rag"), { recursive: true });
|
|
216
|
+
fs.writeFileSync(
|
|
217
|
+
path.join(targetDir, "src", "routes", "api", "rag", "query.ts"),
|
|
218
|
+
`// POST /api/rag/query \u2014 Query documents with RAG
|
|
219
|
+
import type { Context } from "@voltx/server";
|
|
220
|
+
import { streamText } from "@voltx/ai";
|
|
221
|
+
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
222
|
+
import { createVectorStore } from "@voltx/db";
|
|
223
|
+
|
|
224
|
+
const vectorStore = createVectorStore(); // swap to "pinecone" or "pgvector" for production
|
|
225
|
+
const embedder = createEmbedder({ model: "${embedModel}" });
|
|
226
|
+
const rag = createRAGPipeline({ embedder, vectorStore });
|
|
227
|
+
|
|
228
|
+
export async function POST(c: Context) {
|
|
229
|
+
const { question } = await c.req.json();
|
|
230
|
+
|
|
231
|
+
const context = await rag.getContext(question, { topK: 5 });
|
|
232
|
+
|
|
233
|
+
const result = await streamText({
|
|
234
|
+
model: "${provider}:${model}",
|
|
235
|
+
system: \`Answer the user's question based on the following context. If the context doesn't contain relevant information, say so.\\n\\nContext:\\n\${context}\`,
|
|
236
|
+
messages: [{ role: "user", content: question }],
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
return result.toSSEResponse();
|
|
240
|
+
}
|
|
241
|
+
`
|
|
242
|
+
);
|
|
243
|
+
fs.writeFileSync(
|
|
244
|
+
path.join(targetDir, "src", "routes", "api", "rag", "ingest.ts"),
|
|
245
|
+
`// POST /api/rag/ingest \u2014 Ingest documents into the vector store
|
|
246
|
+
import type { Context } from "@voltx/server";
|
|
247
|
+
import { createRAGPipeline, createEmbedder } from "@voltx/rag";
|
|
248
|
+
import { createVectorStore } from "@voltx/db";
|
|
249
|
+
|
|
250
|
+
const vectorStore = createVectorStore();
|
|
251
|
+
const embedder = createEmbedder({ model: "${embedModel}" });
|
|
252
|
+
const rag = createRAGPipeline({ embedder, vectorStore });
|
|
253
|
+
|
|
254
|
+
export async function POST(c: Context) {
|
|
255
|
+
const { text, idPrefix } = await c.req.json();
|
|
256
|
+
|
|
257
|
+
if (!text || typeof text !== "string") {
|
|
258
|
+
return c.json({ error: "Missing 'text' field" }, 400);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const result = await rag.ingest(text, idPrefix ?? "doc");
|
|
262
|
+
return c.json({ status: "ok", chunks: result.chunks, ids: result.ids });
|
|
263
|
+
}
|
|
264
|
+
`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
if (auth === "better-auth") {
|
|
268
|
+
fs.mkdirSync(path.join(targetDir, "src", "routes", "api", "auth"), { recursive: true });
|
|
269
|
+
fs.writeFileSync(
|
|
270
|
+
path.join(targetDir, "src", "routes", "api", "auth", "[...path].ts"),
|
|
271
|
+
`// ALL /api/auth/* \u2014 Better Auth handler
|
|
272
|
+
import type { Context } from "@voltx/server";
|
|
273
|
+
import { auth } from "../../../lib/auth";
|
|
274
|
+
import { createAuthHandler } from "@voltx/auth";
|
|
275
|
+
|
|
276
|
+
const handler = createAuthHandler(auth);
|
|
277
|
+
|
|
278
|
+
export const GET = (c: Context) => handler(c);
|
|
279
|
+
export const POST = (c: Context) => handler(c);
|
|
280
|
+
`
|
|
281
|
+
);
|
|
282
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
283
|
+
fs.writeFileSync(
|
|
284
|
+
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
285
|
+
`// Auth configuration \u2014 Better Auth with DB-backed sessions
|
|
286
|
+
import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
287
|
+
|
|
288
|
+
export const auth = createAuth("better-auth", {
|
|
289
|
+
database: process.env.DATABASE_URL!,
|
|
290
|
+
emailAndPassword: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
export const authMiddleware = createAuthMiddleware({
|
|
294
|
+
provider: auth,
|
|
295
|
+
publicPaths: ["/api/auth", "/api/health", "/"],
|
|
296
|
+
});
|
|
297
|
+
`
|
|
298
|
+
);
|
|
299
|
+
} else if (auth === "jwt") {
|
|
300
|
+
fs.mkdirSync(path.join(targetDir, "src", "lib"), { recursive: true });
|
|
301
|
+
fs.writeFileSync(
|
|
302
|
+
path.join(targetDir, "src", "lib", "auth.ts"),
|
|
303
|
+
`// Auth configuration \u2014 JWT (stateless)
|
|
304
|
+
import { createAuth, createAuthMiddleware } from "@voltx/auth";
|
|
305
|
+
|
|
306
|
+
export const jwt = createAuth("jwt", {
|
|
307
|
+
secret: process.env.JWT_SECRET!,
|
|
308
|
+
expiresIn: "7d",
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
export const authMiddleware = createAuthMiddleware({
|
|
312
|
+
provider: jwt,
|
|
313
|
+
publicPaths: ["/api/auth", "/api/health", "/"],
|
|
314
|
+
});
|
|
315
|
+
`
|
|
316
|
+
);
|
|
317
|
+
fs.writeFileSync(
|
|
318
|
+
path.join(targetDir, "src", "routes", "api", "auth.ts"),
|
|
319
|
+
`// POST /api/auth/login \u2014 Example JWT login route
|
|
320
|
+
import type { Context } from "@voltx/server";
|
|
321
|
+
import { jwt } from "../../lib/auth";
|
|
322
|
+
|
|
323
|
+
export async function POST(c: Context) {
|
|
324
|
+
const { email, password } = await c.req.json();
|
|
325
|
+
|
|
326
|
+
if (!email || !password) {
|
|
327
|
+
return c.json({ error: "Email and password are required" }, 400);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const token = await jwt.sign({ sub: email, email });
|
|
331
|
+
return c.json({ token });
|
|
332
|
+
}
|
|
333
|
+
`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
let envContent = "";
|
|
337
|
+
if (template === "rag-app") {
|
|
338
|
+
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";
|
|
339
|
+
envContent += "# \u2500\u2500\u2500 Database (Neon Postgres) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require\n\n";
|
|
340
|
+
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";
|
|
341
|
+
} else if (template === "chatbot" || template === "agent-app") {
|
|
342
|
+
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";
|
|
343
|
+
if (template === "agent-app") {
|
|
344
|
+
envContent += "# \u2500\u2500\u2500 Database (Neon Postgres \u2014 optional) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nDATABASE_URL=\n\n";
|
|
345
|
+
}
|
|
346
|
+
} else {
|
|
347
|
+
envContent += "# \u2500\u2500\u2500 LLM Provider (add your key) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# OPENAI_API_KEY=sk-...\n# CEREBRAS_API_KEY=csk-...\n\n";
|
|
348
|
+
}
|
|
349
|
+
if (auth === "better-auth") {
|
|
350
|
+
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";
|
|
351
|
+
if (template !== "rag-app" && template !== "agent-app") {
|
|
352
|
+
envContent += "DATABASE_URL=postgresql://user:pass@ep-xxx.us-east-2.aws.neon.tech/dbname?sslmode=require\n";
|
|
353
|
+
}
|
|
354
|
+
envContent += "# GITHUB_CLIENT_ID=\n# GITHUB_CLIENT_SECRET=\n\n";
|
|
355
|
+
} else if (auth === "jwt") {
|
|
356
|
+
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";
|
|
357
|
+
}
|
|
358
|
+
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";
|
|
359
|
+
fs.writeFileSync(path.join(targetDir, ".env.example"), envContent);
|
|
360
|
+
fs.writeFileSync(
|
|
361
|
+
path.join(targetDir, ".gitignore"),
|
|
362
|
+
"node_modules\ndist\n.env\n"
|
|
363
|
+
);
|
|
364
|
+
fs.writeFileSync(
|
|
365
|
+
path.join(targetDir, "tsconfig.json"),
|
|
366
|
+
JSON.stringify(
|
|
367
|
+
{
|
|
368
|
+
compilerOptions: {
|
|
369
|
+
target: "ES2022",
|
|
370
|
+
module: "ESNext",
|
|
371
|
+
moduleResolution: "bundler",
|
|
372
|
+
strict: true,
|
|
373
|
+
esModuleInterop: true,
|
|
374
|
+
skipLibCheck: true,
|
|
375
|
+
outDir: "dist",
|
|
376
|
+
rootDir: "src"
|
|
377
|
+
},
|
|
378
|
+
include: ["src"]
|
|
379
|
+
},
|
|
380
|
+
null,
|
|
381
|
+
2
|
|
382
|
+
)
|
|
383
|
+
);
|
|
384
|
+
printWelcomeBanner(name);
|
|
385
|
+
}
|
|
386
|
+
var isDirectRun = typeof __require !== "undefined" && __require.main === module && process.argv[1]?.includes("create");
|
|
387
|
+
if (isDirectRun) {
|
|
388
|
+
const projectName = process.argv[2];
|
|
389
|
+
if (!projectName) {
|
|
390
|
+
console.log("Usage: create-voltx-app <project-name> [--template chatbot] [--auth jwt]");
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
const templateFlag = process.argv.indexOf("--template");
|
|
394
|
+
const template = templateFlag !== -1 ? process.argv[templateFlag + 1] : "blank";
|
|
395
|
+
const authFlag = process.argv.indexOf("--auth");
|
|
396
|
+
const auth = authFlag !== -1 ? process.argv[authFlag + 1] : "none";
|
|
397
|
+
createProject({ name: projectName, template, auth }).catch((err) => {
|
|
398
|
+
console.error("[voltx] Error:", err);
|
|
399
|
+
process.exit(1);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export {
|
|
404
|
+
createProject
|
|
405
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// src/start.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import { resolve, join } from "path";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
async function runStart(options = {}) {
|
|
6
|
+
const cwd = process.cwd();
|
|
7
|
+
const { port, outDir = "dist" } = options;
|
|
8
|
+
const distDir = resolve(cwd, outDir);
|
|
9
|
+
if (!existsSync(distDir)) {
|
|
10
|
+
console.error(`[voltx] Build output not found at ${outDir}/`);
|
|
11
|
+
console.error("[voltx] Run `voltx build` first to create a production build.");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const entry = options.entry ?? findDistEntry(distDir);
|
|
15
|
+
if (!entry) {
|
|
16
|
+
console.error(`[voltx] No entry file found in ${outDir}/`);
|
|
17
|
+
console.error("[voltx] Expected index.js, index.mjs, or main.js");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const entryPath = resolve(distDir, entry);
|
|
21
|
+
if (!existsSync(entryPath)) {
|
|
22
|
+
console.error(`[voltx] Entry file not found: ${outDir}/${entry}`);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const env = {
|
|
26
|
+
...process.env,
|
|
27
|
+
NODE_ENV: "production"
|
|
28
|
+
};
|
|
29
|
+
if (port) {
|
|
30
|
+
env.PORT = String(port);
|
|
31
|
+
}
|
|
32
|
+
console.log("");
|
|
33
|
+
console.log(" \u26A1 VoltX Production Server");
|
|
34
|
+
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");
|
|
35
|
+
console.log(` Entry: ${outDir}/${entry}`);
|
|
36
|
+
if (port) {
|
|
37
|
+
console.log(` Port: ${port}`);
|
|
38
|
+
}
|
|
39
|
+
console.log(` Mode: production`);
|
|
40
|
+
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");
|
|
41
|
+
console.log("");
|
|
42
|
+
const child = spawn("node", [entryPath], {
|
|
43
|
+
cwd,
|
|
44
|
+
env,
|
|
45
|
+
stdio: "inherit"
|
|
46
|
+
});
|
|
47
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
48
|
+
for (const signal of signals) {
|
|
49
|
+
process.on(signal, () => {
|
|
50
|
+
child.kill(signal);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
child.on("error", (err) => {
|
|
54
|
+
console.error("[voltx] Failed to start production server:", err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
57
|
+
child.on("exit", (code) => {
|
|
58
|
+
process.exit(code ?? 0);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function findDistEntry(distDir) {
|
|
62
|
+
const candidates = [
|
|
63
|
+
"index.mjs",
|
|
64
|
+
"index.js",
|
|
65
|
+
"index.cjs",
|
|
66
|
+
"main.mjs",
|
|
67
|
+
"main.js",
|
|
68
|
+
"src/index.mjs",
|
|
69
|
+
"src/index.js"
|
|
70
|
+
];
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (existsSync(join(distDir, candidate))) {
|
|
73
|
+
return candidate;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export {
|
|
80
|
+
runStart
|
|
81
|
+
};
|