@lovenyberg/ove 0.4.0 → 0.5.1
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/package.json +1 -1
- package/src/adapters/http.ts +5 -1
- package/src/index.ts +2 -1
- package/src/setup.ts +116 -1
- package/deploy/ove.service +0 -15
package/package.json
CHANGED
package/src/adapters/http.ts
CHANGED
|
@@ -31,9 +31,12 @@ export class HttpApiAdapter implements EventAdapter {
|
|
|
31
31
|
private eventAdapters: EventAdapter[] = [];
|
|
32
32
|
private startedAt?: string;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
private hostname: string;
|
|
35
|
+
|
|
36
|
+
constructor(port: number, apiKey: string, trace: TraceStore, queue?: TaskQueue, sessions?: SessionStore, hostname?: string) {
|
|
35
37
|
this.port = port;
|
|
36
38
|
this.apiKey = apiKey;
|
|
39
|
+
this.hostname = hostname || "0.0.0.0";
|
|
37
40
|
this.trace = trace;
|
|
38
41
|
this.queue = queue || null;
|
|
39
42
|
this.sessions = sessions || null;
|
|
@@ -83,6 +86,7 @@ export class HttpApiAdapter implements EventAdapter {
|
|
|
83
86
|
|
|
84
87
|
this.server = Bun.serve({
|
|
85
88
|
port: this.port,
|
|
89
|
+
hostname: this.hostname,
|
|
86
90
|
idleTimeout: 255, // SSE connections need to stay open for long-running tasks
|
|
87
91
|
async fetch(req) {
|
|
88
92
|
const url = new URL(req.url);
|
package/src/index.ts
CHANGED
package/src/setup.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { userInfo } from "node:os";
|
|
2
5
|
import { createInterface } from "node:readline/promises";
|
|
3
6
|
import type { Config } from "./config";
|
|
4
7
|
|
|
@@ -206,6 +209,8 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
206
209
|
process.stdout.write("\n");
|
|
207
210
|
const port = (await ask(rl, "HTTP API port [3000]")) || "3000";
|
|
208
211
|
envValues.HTTP_API_PORT = port;
|
|
212
|
+
const host = await ask(rl, "Bind address [127.0.0.1] (0.0.0.0 for all interfaces)");
|
|
213
|
+
envValues.HTTP_API_HOST = host || "127.0.0.1";
|
|
209
214
|
const key = await ask(rl, "API key (leave empty to generate)");
|
|
210
215
|
envValues.HTTP_API_KEY = key || crypto.randomUUID();
|
|
211
216
|
}
|
|
@@ -222,6 +227,15 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
222
227
|
envValues.CLI_MODE = "true";
|
|
223
228
|
}
|
|
224
229
|
|
|
230
|
+
// Tracing
|
|
231
|
+
if (!fixing) {
|
|
232
|
+
process.stdout.write("\n");
|
|
233
|
+
const enableTrace = await ask(rl, "Enable task tracing? (y/n)");
|
|
234
|
+
if (enableTrace.toLowerCase() === "y") {
|
|
235
|
+
envValues.OVE_TRACE = "true";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
225
239
|
// Collect repos
|
|
226
240
|
const repos: Record<string, { url: string; defaultBranch: string }> = existingConfig.repos
|
|
227
241
|
? { ...existingConfig.repos }
|
|
@@ -322,6 +336,7 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
322
336
|
if (has("HTTP API")) {
|
|
323
337
|
envLines.push("# HTTP API + Web UI");
|
|
324
338
|
envLines.push(`HTTP_API_PORT=${envValues.HTTP_API_PORT || "3000"}`);
|
|
339
|
+
envLines.push(`HTTP_API_HOST=${envValues.HTTP_API_HOST || "127.0.0.1"}`);
|
|
325
340
|
envLines.push(`HTTP_API_KEY=${envValues.HTTP_API_KEY || ""}`);
|
|
326
341
|
envLines.push("");
|
|
327
342
|
}
|
|
@@ -341,6 +356,13 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
341
356
|
envLines.push("");
|
|
342
357
|
}
|
|
343
358
|
|
|
359
|
+
// Tracing
|
|
360
|
+
if (envValues.OVE_TRACE) {
|
|
361
|
+
envLines.push("# Tracing");
|
|
362
|
+
envLines.push(`OVE_TRACE=${envValues.OVE_TRACE}`);
|
|
363
|
+
envLines.push("");
|
|
364
|
+
}
|
|
365
|
+
|
|
344
366
|
// Always include repos dir
|
|
345
367
|
envLines.push("# Repos directory");
|
|
346
368
|
envLines.push(`REPOS_DIR=${envValues.REPOS_DIR || "./repos"}`);
|
|
@@ -363,8 +385,39 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
363
385
|
process.stdout.write(" Wrote config.json\n");
|
|
364
386
|
}
|
|
365
387
|
|
|
388
|
+
// Systemd service setup
|
|
389
|
+
let installedSystemd = false;
|
|
366
390
|
if (!fixing) {
|
|
367
|
-
|
|
391
|
+
const installService = await ask(rl, "Install as systemd service? (y/n)");
|
|
392
|
+
if (installService.toLowerCase() === "y") {
|
|
393
|
+
installedSystemd = await installSystemdService(rl);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!fixing) {
|
|
398
|
+
process.stdout.write("\n Nåväl.\n");
|
|
399
|
+
if (installedSystemd) {
|
|
400
|
+
process.stdout.write("\n Useful commands:\n");
|
|
401
|
+
process.stdout.write(" sudo systemctl status ove # check status\n");
|
|
402
|
+
process.stdout.write(" sudo journalctl -u ove -f # follow logs\n");
|
|
403
|
+
process.stdout.write(" sudo systemctl restart ove # restart\n");
|
|
404
|
+
process.stdout.write(" sudo systemctl stop ove # stop\n");
|
|
405
|
+
if (has("HTTP API")) {
|
|
406
|
+
const port = envValues.HTTP_API_PORT || "3000";
|
|
407
|
+
const host = envValues.HTTP_API_HOST || "127.0.0.1";
|
|
408
|
+
const displayHost = host === "0.0.0.0" ? "<your-ip>" : host;
|
|
409
|
+
process.stdout.write(`\n Web UI: http://${displayHost}:${port}\n`);
|
|
410
|
+
if (envValues.OVE_TRACE === "true") {
|
|
411
|
+
process.stdout.write(` Traces: http://${displayHost}:${port}/trace\n`);
|
|
412
|
+
}
|
|
413
|
+
if (host === "0.0.0.0") {
|
|
414
|
+
process.stdout.write(" (bound to all interfaces)\n");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
process.stdout.write("\n");
|
|
418
|
+
} else {
|
|
419
|
+
process.stdout.write(" Run 'ove start' when you're ready.\n\n");
|
|
420
|
+
}
|
|
368
421
|
} else {
|
|
369
422
|
process.stdout.write("\n");
|
|
370
423
|
}
|
|
@@ -372,3 +425,65 @@ export async function runSetup(opts?: { fixOnly?: string[] }): Promise<void> {
|
|
|
372
425
|
rl.close();
|
|
373
426
|
}
|
|
374
427
|
}
|
|
428
|
+
|
|
429
|
+
async function installSystemdService(rl: ReturnType<typeof createInterface>): Promise<boolean> {
|
|
430
|
+
const detectedUser = userInfo().username;
|
|
431
|
+
const detectedDir = resolve(".");
|
|
432
|
+
let detectedBun = "";
|
|
433
|
+
try {
|
|
434
|
+
detectedBun = execFileSync("which", ["bun"]).toString().trim();
|
|
435
|
+
} catch {
|
|
436
|
+
// bun not in PATH
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const user = (await ask(rl, `User [${detectedUser}]`)) || detectedUser;
|
|
440
|
+
const workDir = (await ask(rl, `Working directory [${detectedDir}]`)) || detectedDir;
|
|
441
|
+
const bunPath = (await ask(rl, `Bun path [${detectedBun}]`)) || detectedBun;
|
|
442
|
+
|
|
443
|
+
if (!bunPath) {
|
|
444
|
+
process.stdout.write(" Could not find bun. Skipping service install.\n");
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const envPath = resolve(workDir, ".env");
|
|
449
|
+
const service = `[Unit]
|
|
450
|
+
Description=Ove - Personal AI coding assistant
|
|
451
|
+
After=network.target
|
|
452
|
+
|
|
453
|
+
[Service]
|
|
454
|
+
Type=simple
|
|
455
|
+
User=${user}
|
|
456
|
+
WorkingDirectory=${workDir}
|
|
457
|
+
ExecStart=${bunPath} run src/index.ts
|
|
458
|
+
Restart=always
|
|
459
|
+
RestartSec=5
|
|
460
|
+
EnvironmentFile=${envPath}
|
|
461
|
+
|
|
462
|
+
[Install]
|
|
463
|
+
WantedBy=multi-user.target
|
|
464
|
+
`;
|
|
465
|
+
|
|
466
|
+
const servicePath = resolve(workDir, "ove.service");
|
|
467
|
+
writeFileSync(servicePath, service);
|
|
468
|
+
process.stdout.write(` Wrote ${servicePath}\n`);
|
|
469
|
+
|
|
470
|
+
const install = await ask(rl, "Install and enable now? Requires sudo (y/n)");
|
|
471
|
+
if (install.toLowerCase() === "y") {
|
|
472
|
+
try {
|
|
473
|
+
execFileSync("sudo", ["cp", servicePath, "/etc/systemd/system/ove.service"]);
|
|
474
|
+
execFileSync("sudo", ["systemctl", "daemon-reload"]);
|
|
475
|
+
execFileSync("sudo", ["systemctl", "enable", "ove"]);
|
|
476
|
+
process.stdout.write(" Service installed and enabled.\n");
|
|
477
|
+
|
|
478
|
+
const startNow = await ask(rl, "Start service now? (y/n)");
|
|
479
|
+
if (startNow.toLowerCase() === "y") {
|
|
480
|
+
execFileSync("sudo", ["systemctl", "start", "ove"]);
|
|
481
|
+
process.stdout.write(" Service started.\n");
|
|
482
|
+
}
|
|
483
|
+
return true;
|
|
484
|
+
} catch (err) {
|
|
485
|
+
process.stdout.write(` Failed to install service: ${err}\n`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
}
|
package/deploy/ove.service
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
[Unit]
|
|
2
|
-
Description=Ove - Personal AI coding assistant
|
|
3
|
-
After=network.target
|
|
4
|
-
|
|
5
|
-
[Service]
|
|
6
|
-
Type=simple
|
|
7
|
-
User=YOUR_USER
|
|
8
|
-
WorkingDirectory=/path/to/ove
|
|
9
|
-
ExecStart=/path/to/bun run src/index.ts
|
|
10
|
-
Restart=always
|
|
11
|
-
RestartSec=5
|
|
12
|
-
EnvironmentFile=/path/to/ove/.env
|
|
13
|
-
|
|
14
|
-
[Install]
|
|
15
|
-
WantedBy=multi-user.target
|