vibeman 0.0.6 → 0.0.7

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/dist/api.js CHANGED
@@ -1,35 +1,32 @@
1
- // @bun
2
1
  // ../api/src/index.ts
3
- import { readdir } from "fs/promises";
4
- import { resolve, normalize } from "path";
5
- var port = Number(Bun.env.API_PORT ?? 6970);
2
+ import { readdir } from "node:fs/promises";
3
+ import { createServer } from "node:http";
4
+ import { resolve, normalize } from "node:path";
5
+ var port = Number(process.env.API_PORT ?? 6970);
6
6
  var startedAt = Date.now();
7
7
  var root = process.cwd();
8
- Bun.serve({
9
- port,
10
- async fetch(request) {
11
- const { pathname, searchParams } = new URL(request.url);
12
- if (pathname === "/api/health") {
13
- return json({ ok: true, uptimeMs: Date.now() - startedAt, root });
14
- }
15
- if (pathname === "/api/files") {
16
- const rawPath = searchParams.get("path") ?? ".";
17
- try {
18
- const entries = await listFiles(rawPath);
19
- return json({ path: rawPath, entries });
20
- } catch (error) {
21
- const message = error instanceof Error ? error.message : "Unable to read directory";
22
- return json({ error: message }, 400);
23
- }
8
+ var server = createServer(async (req, res) => {
9
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
10
+ if (url.pathname === "/api/health") {
11
+ return sendJson(res, { ok: true, uptimeMs: Date.now() - startedAt, root });
12
+ }
13
+ if (url.pathname === "/api/files") {
14
+ const rawPath = url.searchParams.get("path") ?? ".";
15
+ try {
16
+ const entries = await listFiles(rawPath);
17
+ return sendJson(res, { path: rawPath, entries });
18
+ } catch (error) {
19
+ const message = error instanceof Error ? error.message : "Unable to read directory";
20
+ return sendJson(res, { error: message }, 400);
24
21
  }
25
- return new Response("Not found", { status: 404 });
26
- },
27
- development: {
28
- hmr: true,
29
- console: true
30
22
  }
23
+ res.statusCode = 404;
24
+ res.setHeader("content-type", "text/plain; charset=utf-8");
25
+ res.end("Not found");
26
+ });
27
+ server.listen(port, () => {
28
+ console.log(`API ready on http://localhost:${port}`);
31
29
  });
32
- console.log(`API ready on http://localhost:${port}`);
33
30
  async function listFiles(pathInput) {
34
31
  const target = normalize(resolve(root, pathInput));
35
32
  const entries = await readdir(target, { withFileTypes: true });
@@ -38,12 +35,9 @@ async function listFiles(pathInput) {
38
35
  kind: entry.isDirectory() ? "dir" : entry.isFile() ? "file" : "other"
39
36
  }));
40
37
  }
41
- function json(payload, status = 200) {
42
- return new Response(JSON.stringify(payload), {
43
- status,
44
- headers: {
45
- "content-type": "application/json",
46
- "cache-control": "no-store"
47
- }
48
- });
38
+ function sendJson(res, payload, status = 200) {
39
+ res.statusCode = status;
40
+ res.setHeader("content-type", "application/json");
41
+ res.setHeader("cache-control", "no-store");
42
+ res.end(JSON.stringify(payload));
49
43
  }
package/dist/index.js ADDED
@@ -0,0 +1,237 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { spawn } from "node:child_process";
5
+ import { createReadStream, existsSync } from "node:fs";
6
+ import { stat } from "node:fs/promises";
7
+ import { createServer, request } from "node:http";
8
+ import { request as requestSecure } from "node:https";
9
+ import { basename, dirname, extname, normalize, resolve } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ var args = process.argv.slice(2);
12
+ var command = args[0];
13
+ if (!command || command === "--help" || command === "-h" || command === "help") {
14
+ printHelp();
15
+ process.exit(0);
16
+ }
17
+ if (command !== "start") {
18
+ console.error(`Unknown command: ${command}`);
19
+ printHelp();
20
+ process.exit(1);
21
+ }
22
+ var options = parseStartArgs(args.slice(1));
23
+ await startApps(options);
24
+ function parseStartArgs(argv) {
25
+ const output = {
26
+ path: ".",
27
+ apiPort: 6970,
28
+ uiPort: 6969,
29
+ open: true
30
+ };
31
+ for (let i = 0;i < argv.length; i += 1) {
32
+ const arg = argv[i];
33
+ if (!arg)
34
+ continue;
35
+ if (arg === "--port") {
36
+ const mainPort = Number(argv[i + 1]);
37
+ output.uiPort = mainPort;
38
+ output.apiPort = mainPort + 1;
39
+ i += 1;
40
+ continue;
41
+ }
42
+ if (arg === "--no-open") {
43
+ output.open = false;
44
+ continue;
45
+ }
46
+ if (!arg.startsWith("-") && output.path === ".") {
47
+ output.path = arg;
48
+ }
49
+ }
50
+ return output;
51
+ }
52
+ async function startApps(options2) {
53
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
54
+ const distRoot = resolveDistRoot(moduleDir);
55
+ const distApi = distRoot ? resolve(distRoot, "api.js") : "";
56
+ const distUiRoot = distRoot ? resolve(distRoot, "ui") : "";
57
+ const nodePath = resolveNodePath();
58
+ if (!distRoot || !existsSync(distApi) || !existsSync(resolve(distUiRoot, "index.html"))) {
59
+ console.error("Missing dist runtime files. Run the build first.");
60
+ process.exit(1);
61
+ }
62
+ const apiProcess = spawn(nodePath, [distApi], {
63
+ cwd: process.cwd(),
64
+ env: {
65
+ ...process.env,
66
+ API_PORT: String(options2.apiPort)
67
+ },
68
+ stdio: "inherit"
69
+ });
70
+ const uiServer = createUiServer({
71
+ apiBase: `http://localhost:${options2.apiPort}`,
72
+ uiRoot: distUiRoot
73
+ });
74
+ uiServer.listen(options2.uiPort, () => {
75
+ console.log(`UI ready on http://localhost:${options2.uiPort}`);
76
+ });
77
+ const url = new URL(`http://localhost:${options2.uiPort}/`);
78
+ url.searchParams.set("path", options2.path);
79
+ if (options2.open) {
80
+ await openBrowser(url.toString());
81
+ }
82
+ console.log(`
83
+ Vibeman running:`);
84
+ console.log(` API: http://localhost:${options2.apiPort}`);
85
+ console.log(` UI: ${url.toString()}`);
86
+ console.log("Press Ctrl+C to stop.");
87
+ const stop = () => {
88
+ apiProcess.kill();
89
+ uiServer.close();
90
+ };
91
+ process.on("SIGINT", () => {
92
+ stop();
93
+ process.exit(0);
94
+ });
95
+ process.on("SIGTERM", () => {
96
+ stop();
97
+ process.exit(0);
98
+ });
99
+ const exitCode = await waitForExit(apiProcess);
100
+ stop();
101
+ process.exit(exitCode ?? 1);
102
+ }
103
+ async function openBrowser(target) {
104
+ const platform = process.platform;
105
+ if (platform === "darwin") {
106
+ spawn("open", [target], { stdio: "ignore", detached: true }).unref();
107
+ return;
108
+ }
109
+ if (platform === "win32") {
110
+ spawn("cmd", ["/c", "start", target], { stdio: "ignore", detached: true }).unref();
111
+ return;
112
+ }
113
+ spawn("xdg-open", [target], { stdio: "ignore", detached: true }).unref();
114
+ }
115
+ function printHelp() {
116
+ console.log(`vibeman CLI
117
+
118
+ Usage:
119
+ vibeman start [path] [--port <port>] [--no-open]
120
+
121
+ Examples:
122
+ vibeman start .
123
+ vibeman start ./notes --port 7010
124
+ `);
125
+ }
126
+ function waitForExit(child) {
127
+ return new Promise((resolvePromise) => {
128
+ child.once("exit", (code) => resolvePromise(code));
129
+ });
130
+ }
131
+ function resolveDistRoot(selfDir) {
132
+ const execDir = dirname(process.execPath);
133
+ const candidates = [
134
+ selfDir,
135
+ resolve(execDir, ".."),
136
+ resolve(execDir, "../.."),
137
+ resolve(selfDir, ".."),
138
+ resolve(selfDir, "../..")
139
+ ];
140
+ for (const candidate of candidates) {
141
+ if (existsSync(resolve(candidate, "api.js")) && existsSync(resolve(candidate, "ui", "index.html"))) {
142
+ return candidate;
143
+ }
144
+ }
145
+ return "";
146
+ }
147
+ function resolveNodePath() {
148
+ const execBase = basename(process.execPath);
149
+ return execBase === "node" || execBase === "node.exe" ? process.execPath : "node";
150
+ }
151
+ function createUiServer({ apiBase, uiRoot }) {
152
+ return createServer(async (req, res) => {
153
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
154
+ if (url.pathname.startsWith("/api")) {
155
+ proxyApi(req, res, url, apiBase);
156
+ return;
157
+ }
158
+ try {
159
+ const filePath = resolveStaticPath(uiRoot, url.pathname);
160
+ const fileStat = await stat(filePath);
161
+ if (fileStat.isDirectory()) {
162
+ return serveFile(resolve(filePath, "index.html"), res);
163
+ }
164
+ return serveFile(filePath, res);
165
+ } catch {
166
+ return serveFile(resolve(uiRoot, "index.html"), res);
167
+ }
168
+ });
169
+ }
170
+ function resolveStaticPath(uiRoot, pathname) {
171
+ const normalized = normalize(pathname).replace(/^(\.\.(\/|\\|$))+/, "");
172
+ const target = resolve(uiRoot, normalized.slice(1));
173
+ if (!target.startsWith(uiRoot)) {
174
+ throw new Error("Invalid path");
175
+ }
176
+ return target;
177
+ }
178
+ function serveFile(filePath, res) {
179
+ if (!existsSync(filePath)) {
180
+ res.statusCode = 404;
181
+ res.setHeader("content-type", "text/plain; charset=utf-8");
182
+ res.end("Not found");
183
+ return;
184
+ }
185
+ res.statusCode = 200;
186
+ res.setHeader("content-type", contentType(filePath));
187
+ createReadStream(filePath).pipe(res);
188
+ }
189
+ function contentType(filePath) {
190
+ switch (extname(filePath)) {
191
+ case ".html":
192
+ return "text/html; charset=utf-8";
193
+ case ".js":
194
+ return "text/javascript; charset=utf-8";
195
+ case ".css":
196
+ return "text/css; charset=utf-8";
197
+ case ".json":
198
+ return "application/json; charset=utf-8";
199
+ case ".svg":
200
+ return "image/svg+xml";
201
+ case ".png":
202
+ return "image/png";
203
+ case ".jpg":
204
+ case ".jpeg":
205
+ return "image/jpeg";
206
+ case ".ico":
207
+ return "image/x-icon";
208
+ default:
209
+ return "application/octet-stream";
210
+ }
211
+ }
212
+ function proxyApi(req, res, url, apiBase) {
213
+ const target = new URL(apiBase);
214
+ target.pathname = url.pathname;
215
+ target.search = url.search;
216
+ const isSecure = target.protocol === "https:";
217
+ const proxyRequest = (isSecure ? requestSecure : request)({
218
+ protocol: target.protocol,
219
+ hostname: target.hostname,
220
+ port: target.port,
221
+ method: req.method,
222
+ path: target.pathname + target.search,
223
+ headers: {
224
+ ...req.headers,
225
+ host: target.host
226
+ }
227
+ }, (proxyRes) => {
228
+ res.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers);
229
+ proxyRes.pipe(res);
230
+ });
231
+ proxyRequest.on("error", (error) => {
232
+ res.statusCode = 502;
233
+ res.setHeader("content-type", "text/plain; charset=utf-8");
234
+ res.end(`Proxy error: ${error instanceof Error ? error.message : "unknown"}`);
235
+ });
236
+ req.pipe(proxyRequest);
237
+ }