@openher/cli 1.0.0 → 1.0.2
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/cli.mjs +64 -55
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -73,22 +73,41 @@ function which(bin) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Single shared readline instance — prevents piped stdin races
|
|
77
|
+
let _rl = null;
|
|
78
|
+
function getRL() {
|
|
79
|
+
if (!_rl) {
|
|
80
|
+
_rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
81
|
+
_rl.on("close", () => { _rl = null; });
|
|
82
|
+
}
|
|
83
|
+
return _rl;
|
|
84
|
+
}
|
|
85
|
+
function closeRL() {
|
|
86
|
+
if (_rl) { _rl.close(); _rl = null; }
|
|
87
|
+
}
|
|
88
|
+
|
|
76
89
|
function ask(question) {
|
|
77
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
78
90
|
return new Promise((resolve) => {
|
|
91
|
+
const rl = getRL();
|
|
92
|
+
if (!rl) { resolve(""); return; }
|
|
79
93
|
rl.question(`${C.cyan}[openher]${C.reset} ${question}`, (answer) => {
|
|
80
|
-
rl.close();
|
|
81
94
|
resolve(answer.trim());
|
|
82
95
|
});
|
|
83
96
|
});
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
// Keep event loop alive until we explicitly exit (prevents early exit with piped stdin)
|
|
100
|
+
const _keepAlive = setInterval(() => {}, 60000);
|
|
101
|
+
process.on("exit", () => { clearInterval(_keepAlive); closeRL(); });
|
|
102
|
+
|
|
86
103
|
function askSecret(question) {
|
|
104
|
+
// Non-TTY fallback: just use regular readline (shows input)
|
|
105
|
+
if (!process.stdin.isTTY) {
|
|
106
|
+
return ask(question);
|
|
107
|
+
}
|
|
87
108
|
return new Promise((resolve) => {
|
|
88
109
|
process.stdout.write(`${C.cyan}[openher]${C.reset} ${question}`);
|
|
89
|
-
|
|
90
|
-
// Mute echo
|
|
91
|
-
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
110
|
+
process.stdin.setRawMode(true);
|
|
92
111
|
let buf = "";
|
|
93
112
|
process.stdin.resume();
|
|
94
113
|
process.stdin.on("data", function handler(chunk) {
|
|
@@ -96,14 +115,13 @@ function askSecret(question) {
|
|
|
96
115
|
for (const ch of s) {
|
|
97
116
|
if (ch === "\n" || ch === "\r") {
|
|
98
117
|
process.stdin.removeListener("data", handler);
|
|
99
|
-
|
|
118
|
+
process.stdin.setRawMode(false);
|
|
100
119
|
process.stdin.pause();
|
|
101
120
|
process.stdout.write("\n");
|
|
102
|
-
rl.close();
|
|
103
121
|
resolve(buf);
|
|
104
122
|
return;
|
|
105
123
|
} else if (ch === "\u0003") {
|
|
106
|
-
|
|
124
|
+
process.stdin.setRawMode(false);
|
|
107
125
|
process.exit(1);
|
|
108
126
|
} else if (ch === "\x7f" || ch === "\b") {
|
|
109
127
|
buf = buf.slice(0, -1);
|
|
@@ -155,6 +173,15 @@ async function install() {
|
|
|
155
173
|
// ── Step 1: Check prerequisites ──
|
|
156
174
|
log("Checking prerequisites...");
|
|
157
175
|
|
|
176
|
+
// Node.js version (openclaw requires v22.12+)
|
|
177
|
+
const nodeVer = process.versions.node.split(".").map(Number);
|
|
178
|
+
if (nodeVer[0] < 22 || (nodeVer[0] === 22 && nodeVer[1] < 12)) {
|
|
179
|
+
error(`Node.js v22.12+ required (current: v${process.versions.node})`);
|
|
180
|
+
console.log(" Install via: https://nodejs.org/ or nvm install 22");
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
success(`Node.js v${process.versions.node}`);
|
|
184
|
+
|
|
158
185
|
if (!which("openclaw")) {
|
|
159
186
|
error("OpenClaw not found. Please install it first:");
|
|
160
187
|
console.log(" npm install -g openclaw");
|
|
@@ -356,35 +383,6 @@ async function install() {
|
|
|
356
383
|
env: { ...process.env, PORT: String(DEFAULT_PORT) },
|
|
357
384
|
});
|
|
358
385
|
|
|
359
|
-
// Wait for startup
|
|
360
|
-
let started = false;
|
|
361
|
-
const startTimeout = setTimeout(() => {
|
|
362
|
-
if (!started) {
|
|
363
|
-
warn("Backend is still starting. Check manually:");
|
|
364
|
-
console.log(` cd ${backendDir} && .venv/bin/python main.py`);
|
|
365
|
-
}
|
|
366
|
-
}, 15000);
|
|
367
|
-
|
|
368
|
-
child.stdout.on("data", (data) => {
|
|
369
|
-
const str = data.toString();
|
|
370
|
-
if (str.includes("Uvicorn running") || str.includes("Application startup")) {
|
|
371
|
-
started = true;
|
|
372
|
-
clearTimeout(startTimeout);
|
|
373
|
-
success(`Backend running on http://localhost:${DEFAULT_PORT}`);
|
|
374
|
-
finalize();
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
child.stderr.on("data", (data) => {
|
|
379
|
-
const str = data.toString();
|
|
380
|
-
if (str.includes("Uvicorn running") || str.includes("Application startup")) {
|
|
381
|
-
started = true;
|
|
382
|
-
clearTimeout(startTimeout);
|
|
383
|
-
success(`Backend running on http://localhost:${DEFAULT_PORT}`);
|
|
384
|
-
finalize();
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
386
|
child.unref();
|
|
389
387
|
|
|
390
388
|
// Write PID for later stop command
|
|
@@ -392,28 +390,39 @@ async function install() {
|
|
|
392
390
|
writeFileSync(join(openherDir, "backend.pid"), String(child.pid), "utf-8");
|
|
393
391
|
} catch {}
|
|
394
392
|
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
393
|
+
// Wait for startup signal from stdout/stderr
|
|
394
|
+
let started = false;
|
|
395
|
+
const onOutput = (data) => {
|
|
396
|
+
const str = data.toString();
|
|
397
|
+
if (!started && (str.includes("Uvicorn running") || str.includes("Application startup"))) {
|
|
398
|
+
started = true;
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
child.stdout.on("data", onOutput);
|
|
402
|
+
child.stderr.on("data", onOutput);
|
|
403
|
+
|
|
404
|
+
// Poll for up to 20s
|
|
405
|
+
await new Promise((resolve) => {
|
|
406
|
+
const check = setInterval(() => {
|
|
407
|
+
if (started) {
|
|
407
408
|
clearInterval(check);
|
|
408
409
|
resolve();
|
|
409
|
-
}
|
|
410
|
-
});
|
|
411
|
-
|
|
410
|
+
}
|
|
411
|
+
}, 500);
|
|
412
|
+
setTimeout(() => {
|
|
413
|
+
clearInterval(check);
|
|
414
|
+
resolve();
|
|
415
|
+
}, 20000);
|
|
416
|
+
});
|
|
412
417
|
|
|
413
|
-
if (
|
|
414
|
-
|
|
415
|
-
|
|
418
|
+
if (started) {
|
|
419
|
+
success(`Backend running on http://localhost:${DEFAULT_PORT}`);
|
|
420
|
+
} else {
|
|
421
|
+
warn("Backend may still be starting up. Check:");
|
|
422
|
+
console.log(` cd ${backendDir} && .venv/bin/python main.py`);
|
|
416
423
|
}
|
|
424
|
+
|
|
425
|
+
finalize();
|
|
417
426
|
}
|
|
418
427
|
|
|
419
428
|
function finalize() {
|