nextclaw 0.2.2 → 0.2.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/dist/cli/index.js
CHANGED
|
@@ -22,7 +22,16 @@ import {
|
|
|
22
22
|
|
|
23
23
|
// src/cli/index.ts
|
|
24
24
|
import { Command } from "commander";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
existsSync as existsSync6,
|
|
27
|
+
mkdirSync as mkdirSync5,
|
|
28
|
+
readFileSync as readFileSync5,
|
|
29
|
+
writeFileSync as writeFileSync4,
|
|
30
|
+
cpSync,
|
|
31
|
+
rmSync,
|
|
32
|
+
openSync,
|
|
33
|
+
closeSync
|
|
34
|
+
} from "fs";
|
|
26
35
|
import { join as join6, resolve } from "path";
|
|
27
36
|
import { spawn, spawnSync } from "child_process";
|
|
28
37
|
import { createInterface } from "readline";
|
|
@@ -3101,7 +3110,25 @@ program.command("ui").description(`Start the ${APP_NAME} UI with gateway`).optio
|
|
|
3101
3110
|
}
|
|
3102
3111
|
await startGateway({ uiOverrides, allowMissingProvider: true });
|
|
3103
3112
|
});
|
|
3104
|
-
program.command("start").description(`Start the ${APP_NAME} gateway + UI
|
|
3113
|
+
program.command("start").description(`Start the ${APP_NAME} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server", false).option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
|
|
3114
|
+
const uiOverrides = {
|
|
3115
|
+
enabled: true,
|
|
3116
|
+
open: false
|
|
3117
|
+
};
|
|
3118
|
+
if (opts.uiHost) {
|
|
3119
|
+
uiOverrides.host = String(opts.uiHost);
|
|
3120
|
+
}
|
|
3121
|
+
if (opts.uiPort) {
|
|
3122
|
+
uiOverrides.port = Number(opts.uiPort);
|
|
3123
|
+
}
|
|
3124
|
+
await startService({
|
|
3125
|
+
uiOverrides,
|
|
3126
|
+
frontend: Boolean(opts.frontend),
|
|
3127
|
+
frontendPort: Number(opts.frontendPort),
|
|
3128
|
+
open: Boolean(opts.open)
|
|
3129
|
+
});
|
|
3130
|
+
});
|
|
3131
|
+
program.command("serve").description(`Run the ${APP_NAME} gateway + UI in the foreground`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server", false).option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => {
|
|
3105
3132
|
const uiOverrides = {
|
|
3106
3133
|
enabled: true,
|
|
3107
3134
|
open: false
|
|
@@ -3126,15 +3153,12 @@ program.command("start").description(`Start the ${APP_NAME} gateway + UI (backen
|
|
|
3126
3153
|
dir: frontendDir
|
|
3127
3154
|
});
|
|
3128
3155
|
frontendUrl = frontend?.url ?? null;
|
|
3129
|
-
} else if (shouldStartFrontend && !frontendDir
|
|
3156
|
+
} else if (shouldStartFrontend && !frontendDir) {
|
|
3130
3157
|
console.log("Warning: UI frontend not found. Start it separately.");
|
|
3131
3158
|
}
|
|
3132
3159
|
if (!frontendUrl && staticDir) {
|
|
3133
3160
|
frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
3134
3161
|
}
|
|
3135
|
-
if (!frontendUrl && staticDir) {
|
|
3136
|
-
frontendUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
3137
|
-
}
|
|
3138
3162
|
if (opts.open && frontendUrl) {
|
|
3139
3163
|
openBrowser(frontendUrl);
|
|
3140
3164
|
} else if (opts.open && !frontendUrl) {
|
|
@@ -3142,6 +3166,9 @@ program.command("start").description(`Start the ${APP_NAME} gateway + UI (backen
|
|
|
3142
3166
|
}
|
|
3143
3167
|
await startGateway({ uiOverrides, allowMissingProvider: true, uiStaticDir: staticDir ?? void 0 });
|
|
3144
3168
|
});
|
|
3169
|
+
program.command("stop").description(`Stop the ${APP_NAME} background service`).action(async () => {
|
|
3170
|
+
await stopService();
|
|
3171
|
+
});
|
|
3145
3172
|
program.command("agent").description("Interact with the agent directly").option("-m, --message <message>", "Message to send to the agent").option("-s, --session <session>", "Session ID", "cli:default").option("--no-markdown", "Disable Markdown rendering").action(async (opts) => {
|
|
3146
3173
|
const config = loadConfig();
|
|
3147
3174
|
const bus = new MessageBus();
|
|
@@ -3418,6 +3445,154 @@ function resolveUiApiBase(host, port) {
|
|
|
3418
3445
|
const normalizedHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
|
|
3419
3446
|
return `http://${normalizedHost}:${port}`;
|
|
3420
3447
|
}
|
|
3448
|
+
async function startService(options) {
|
|
3449
|
+
const config = loadConfig();
|
|
3450
|
+
const uiConfig = resolveUiConfig(config, options.uiOverrides);
|
|
3451
|
+
const uiUrl = resolveUiApiBase(uiConfig.host, uiConfig.port);
|
|
3452
|
+
const apiUrl = `${uiUrl}/api`;
|
|
3453
|
+
const staticDir = resolveUiStaticDir();
|
|
3454
|
+
const existing = readServiceState();
|
|
3455
|
+
if (existing && isProcessRunning(existing.pid)) {
|
|
3456
|
+
console.log(`\u2713 ${APP_NAME} is already running (PID ${existing.pid})`);
|
|
3457
|
+
console.log(`UI: ${existing.uiUrl}`);
|
|
3458
|
+
console.log(`API: ${existing.apiUrl}`);
|
|
3459
|
+
console.log(`Logs: ${existing.logPath}`);
|
|
3460
|
+
console.log(`Stop: ${APP_NAME} stop`);
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
if (existing) {
|
|
3464
|
+
clearServiceState();
|
|
3465
|
+
}
|
|
3466
|
+
if (!staticDir && !options.frontend) {
|
|
3467
|
+
console.log("Warning: UI frontend not found. Use --frontend to start the dev server.");
|
|
3468
|
+
}
|
|
3469
|
+
const logPath = resolveServiceLogPath();
|
|
3470
|
+
const logDir = resolve(logPath, "..");
|
|
3471
|
+
mkdirSync5(logDir, { recursive: true });
|
|
3472
|
+
const logFd = openSync(logPath, "a");
|
|
3473
|
+
const serveArgs = buildServeArgs({
|
|
3474
|
+
uiHost: uiConfig.host,
|
|
3475
|
+
uiPort: uiConfig.port,
|
|
3476
|
+
frontend: options.frontend,
|
|
3477
|
+
frontendPort: options.frontendPort
|
|
3478
|
+
});
|
|
3479
|
+
const child = spawn(process.execPath, [...process.execArgv, ...serveArgs], {
|
|
3480
|
+
env: process.env,
|
|
3481
|
+
stdio: ["ignore", logFd, logFd],
|
|
3482
|
+
detached: true
|
|
3483
|
+
});
|
|
3484
|
+
closeSync(logFd);
|
|
3485
|
+
if (!child.pid) {
|
|
3486
|
+
console.error("Error: Failed to start background service.");
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3489
|
+
child.unref();
|
|
3490
|
+
const state = {
|
|
3491
|
+
pid: child.pid,
|
|
3492
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3493
|
+
uiUrl,
|
|
3494
|
+
apiUrl,
|
|
3495
|
+
logPath
|
|
3496
|
+
};
|
|
3497
|
+
writeServiceState(state);
|
|
3498
|
+
console.log(`\u2713 ${APP_NAME} started in background (PID ${state.pid})`);
|
|
3499
|
+
console.log(`UI: ${uiUrl}`);
|
|
3500
|
+
console.log(`API: ${apiUrl}`);
|
|
3501
|
+
console.log(`Logs: ${logPath}`);
|
|
3502
|
+
console.log(`Stop: ${APP_NAME} stop`);
|
|
3503
|
+
if (options.open) {
|
|
3504
|
+
openBrowser(uiUrl);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
async function stopService() {
|
|
3508
|
+
const state = readServiceState();
|
|
3509
|
+
if (!state) {
|
|
3510
|
+
console.log("No running service found.");
|
|
3511
|
+
return;
|
|
3512
|
+
}
|
|
3513
|
+
if (!isProcessRunning(state.pid)) {
|
|
3514
|
+
console.log("Service is not running. Cleaning up state.");
|
|
3515
|
+
clearServiceState();
|
|
3516
|
+
return;
|
|
3517
|
+
}
|
|
3518
|
+
console.log(`Stopping ${APP_NAME} (PID ${state.pid})...`);
|
|
3519
|
+
try {
|
|
3520
|
+
process.kill(state.pid, "SIGTERM");
|
|
3521
|
+
} catch (error) {
|
|
3522
|
+
console.error(`Failed to stop service: ${String(error)}`);
|
|
3523
|
+
return;
|
|
3524
|
+
}
|
|
3525
|
+
const stopped = await waitForExit(state.pid, 3e3);
|
|
3526
|
+
if (!stopped) {
|
|
3527
|
+
try {
|
|
3528
|
+
process.kill(state.pid, "SIGKILL");
|
|
3529
|
+
} catch (error) {
|
|
3530
|
+
console.error(`Failed to force stop service: ${String(error)}`);
|
|
3531
|
+
return;
|
|
3532
|
+
}
|
|
3533
|
+
await waitForExit(state.pid, 2e3);
|
|
3534
|
+
}
|
|
3535
|
+
clearServiceState();
|
|
3536
|
+
console.log(`\u2713 ${APP_NAME} stopped`);
|
|
3537
|
+
}
|
|
3538
|
+
function buildServeArgs(options) {
|
|
3539
|
+
const cliPath = fileURLToPath(import.meta.url);
|
|
3540
|
+
const args = [cliPath, "serve", "--ui-host", options.uiHost, "--ui-port", String(options.uiPort)];
|
|
3541
|
+
if (options.frontend) {
|
|
3542
|
+
args.push("--frontend");
|
|
3543
|
+
}
|
|
3544
|
+
if (Number.isFinite(options.frontendPort)) {
|
|
3545
|
+
args.push("--frontend-port", String(options.frontendPort));
|
|
3546
|
+
}
|
|
3547
|
+
return args;
|
|
3548
|
+
}
|
|
3549
|
+
function readServiceState() {
|
|
3550
|
+
const path = resolveServiceStatePath();
|
|
3551
|
+
if (!existsSync6(path)) {
|
|
3552
|
+
return null;
|
|
3553
|
+
}
|
|
3554
|
+
try {
|
|
3555
|
+
const raw = readFileSync5(path, "utf-8");
|
|
3556
|
+
return JSON.parse(raw);
|
|
3557
|
+
} catch {
|
|
3558
|
+
return null;
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
function writeServiceState(state) {
|
|
3562
|
+
const path = resolveServiceStatePath();
|
|
3563
|
+
mkdirSync5(resolve(path, ".."), { recursive: true });
|
|
3564
|
+
writeFileSync4(path, JSON.stringify(state, null, 2));
|
|
3565
|
+
}
|
|
3566
|
+
function clearServiceState() {
|
|
3567
|
+
const path = resolveServiceStatePath();
|
|
3568
|
+
if (existsSync6(path)) {
|
|
3569
|
+
rmSync(path, { force: true });
|
|
3570
|
+
}
|
|
3571
|
+
}
|
|
3572
|
+
function resolveServiceStatePath() {
|
|
3573
|
+
return resolve(getDataDir(), "run", "service.json");
|
|
3574
|
+
}
|
|
3575
|
+
function resolveServiceLogPath() {
|
|
3576
|
+
return resolve(getDataDir(), "logs", "service.log");
|
|
3577
|
+
}
|
|
3578
|
+
function isProcessRunning(pid) {
|
|
3579
|
+
try {
|
|
3580
|
+
process.kill(pid, 0);
|
|
3581
|
+
return true;
|
|
3582
|
+
} catch {
|
|
3583
|
+
return false;
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
async function waitForExit(pid, timeoutMs) {
|
|
3587
|
+
const start = Date.now();
|
|
3588
|
+
while (Date.now() - start < timeoutMs) {
|
|
3589
|
+
if (!isProcessRunning(pid)) {
|
|
3590
|
+
return true;
|
|
3591
|
+
}
|
|
3592
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
3593
|
+
}
|
|
3594
|
+
return !isProcessRunning(pid);
|
|
3595
|
+
}
|
|
3421
3596
|
function resolveUiStaticDir() {
|
|
3422
3597
|
const candidates = [];
|
|
3423
3598
|
const envDir = process.env.NEXTCLAW_UI_STATIC_DIR;
|