openclaw-manager 0.1.1 → 0.1.3
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/bin/commands/start.js +108 -0
- package/bin/commands/stop-all.js +82 -0
- package/bin/commands/stop.js +63 -0
- package/bin/lib/args.js +87 -0
- package/bin/lib/banner.js +13 -0
- package/bin/lib/config.js +74 -0
- package/bin/lib/help.js +9 -0
- package/bin/lib/paths.js +6 -0
- package/bin/lib/pids.js +22 -0
- package/bin/lib/system.js +31 -0
- package/bin/lib/types.js +1 -0
- package/bin/lib/version.js +17 -0
- package/bin/openclaw-manager.js +44 -190
- package/dist/lib/commands.js +2 -2
- package/dist/services/jobs.service.js +2 -2
- package/package.json +12 -2
- package/web-dist/assets/{index-BabnD_ew.js → index-C_m7eOq1.js} +2 -2
- package/web-dist/docker.sh +7 -7
- package/web-dist/index.html +2 -2
- package/web-dist/install.ps1 +1 -1
- package/web-dist/install.sh +2 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import { ensureDir, hasAdminConfig, resolveConfigPaths, writeAdminConfig } from "../lib/config.js";
|
|
7
|
+
import { isPidRunning, readPid } from "../lib/pids.js";
|
|
8
|
+
import { resolvePackageRoot } from "../lib/paths.js";
|
|
9
|
+
export async function startManager(flags) {
|
|
10
|
+
const paths = resolveConfigPaths(flags);
|
|
11
|
+
ensureDir(paths.configDir);
|
|
12
|
+
ensureDir(path.dirname(paths.logPath));
|
|
13
|
+
ensureDir(path.dirname(paths.errorLogPath));
|
|
14
|
+
if (fs.existsSync(paths.pidPath)) {
|
|
15
|
+
const pid = readPid(paths.pidPath);
|
|
16
|
+
if (pid && isPidRunning(pid)) {
|
|
17
|
+
console.log(`[manager] Already running (pid: ${pid}).`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const explicitUser = normalizeString(flags.user ??
|
|
22
|
+
process.env.MANAGER_ADMIN_USER ??
|
|
23
|
+
process.env.OPENCLAW_MANAGER_ADMIN_USER);
|
|
24
|
+
const explicitPass = normalizeString(flags.pass ??
|
|
25
|
+
process.env.MANAGER_ADMIN_PASS ??
|
|
26
|
+
process.env.OPENCLAW_MANAGER_ADMIN_PASS);
|
|
27
|
+
if (explicitUser || explicitPass) {
|
|
28
|
+
if (!explicitUser || !explicitPass) {
|
|
29
|
+
throw new Error("[manager] Both --user and --password are required when overriding admin config.");
|
|
30
|
+
}
|
|
31
|
+
writeAdminConfig(paths.configPath, explicitUser, explicitPass);
|
|
32
|
+
}
|
|
33
|
+
else if (!hasAdminConfig(paths.configPath)) {
|
|
34
|
+
if (flags.nonInteractive || !process.stdin.isTTY) {
|
|
35
|
+
throw new Error("[manager] Admin username/password is required. Use --user/--password.");
|
|
36
|
+
}
|
|
37
|
+
const response = await prompts([
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
name: "username",
|
|
41
|
+
message: "Admin username",
|
|
42
|
+
validate: (value) => (value ? true : "Username is required")
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: "password",
|
|
46
|
+
name: "password",
|
|
47
|
+
message: "Admin password",
|
|
48
|
+
validate: (value) => (value ? true : "Password is required")
|
|
49
|
+
}
|
|
50
|
+
], {
|
|
51
|
+
onCancel: () => {
|
|
52
|
+
throw new Error("[manager] Prompt cancelled.");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const username = normalizeString(response.username);
|
|
56
|
+
const password = normalizeString(response.password);
|
|
57
|
+
if (!username || !password) {
|
|
58
|
+
throw new Error("[manager] Admin username/password is required.");
|
|
59
|
+
}
|
|
60
|
+
writeAdminConfig(paths.configPath, username, password);
|
|
61
|
+
}
|
|
62
|
+
const pkgRoot = resolvePackageRoot();
|
|
63
|
+
const apiEntry = path.join(pkgRoot, "dist", "index.js");
|
|
64
|
+
const webDist = path.join(pkgRoot, "web-dist");
|
|
65
|
+
if (!fs.existsSync(apiEntry) || !fs.existsSync(webDist)) {
|
|
66
|
+
throw new Error("[manager] Package is missing build artifacts. Please reinstall or use a release that includes dist assets.");
|
|
67
|
+
}
|
|
68
|
+
const out = fs.openSync(paths.logPath, "a");
|
|
69
|
+
const err = fs.openSync(paths.errorLogPath, "a");
|
|
70
|
+
const child = spawn(process.execPath, [apiEntry], {
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
MANAGER_API_HOST: paths.apiHost,
|
|
74
|
+
MANAGER_API_PORT: String(paths.apiPort),
|
|
75
|
+
MANAGER_WEB_DIST: webDist,
|
|
76
|
+
MANAGER_CONFIG_PATH: paths.configPath
|
|
77
|
+
},
|
|
78
|
+
detached: true,
|
|
79
|
+
stdio: ["ignore", out, err]
|
|
80
|
+
});
|
|
81
|
+
child.unref();
|
|
82
|
+
fs.writeFileSync(paths.pidPath, String(child.pid), "utf-8");
|
|
83
|
+
const lanIp = resolveLanIp();
|
|
84
|
+
console.log(`[manager] Started (pid: ${child.pid}).`);
|
|
85
|
+
console.log(`[manager] Log: ${paths.logPath}`);
|
|
86
|
+
console.log(`[manager] Error log: ${paths.errorLogPath}`);
|
|
87
|
+
console.log(`[manager] Open (local): http://localhost:${paths.apiPort}`);
|
|
88
|
+
console.log(`[manager] Open (local): http://127.0.0.1:${paths.apiPort}`);
|
|
89
|
+
if (lanIp) {
|
|
90
|
+
console.log(`[manager] Open (LAN): http://${lanIp}:${paths.apiPort}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function resolveLanIp() {
|
|
94
|
+
const nets = os.networkInterfaces();
|
|
95
|
+
for (const name of Object.keys(nets)) {
|
|
96
|
+
for (const net of nets[name] ?? []) {
|
|
97
|
+
if (net.family === "IPv4" && !net.internal) {
|
|
98
|
+
return net.address;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function normalizeString(value) {
|
|
105
|
+
if (typeof value !== "string")
|
|
106
|
+
return "";
|
|
107
|
+
return value.trim();
|
|
108
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { listGatewayProcesses } from "../lib/system.js";
|
|
5
|
+
import { stopManager } from "./stop.js";
|
|
6
|
+
import { readPid } from "../lib/pids.js";
|
|
7
|
+
export function stopAll(flags) {
|
|
8
|
+
const messages = [];
|
|
9
|
+
const errors = [];
|
|
10
|
+
const managerResult = stopManager(flags);
|
|
11
|
+
messages.push(...managerResult.messages);
|
|
12
|
+
if (!managerResult.ok && managerResult.error)
|
|
13
|
+
errors.push(managerResult.error);
|
|
14
|
+
const sandboxes = listSandboxInstances();
|
|
15
|
+
if (!sandboxes.length) {
|
|
16
|
+
messages.push("sandbox: none");
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
for (const rootDir of sandboxes) {
|
|
20
|
+
const result = stopSandboxDir(rootDir);
|
|
21
|
+
if (result.ok) {
|
|
22
|
+
messages.push(`sandbox: ${result.message}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
errors.push(`sandbox: ${result.error ?? "stop failed"}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const gatewayPids = listGatewayProcesses();
|
|
30
|
+
if (!gatewayPids.length) {
|
|
31
|
+
messages.push("gateway: none");
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
for (const pid of gatewayPids) {
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid, "SIGTERM");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// ignore individual failures
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
messages.push(`gateway: stopped (${gatewayPids.join(", ")})`);
|
|
43
|
+
}
|
|
44
|
+
if (errors.length) {
|
|
45
|
+
return { ok: false, messages, error: errors.join("; ") };
|
|
46
|
+
}
|
|
47
|
+
return { ok: true, messages };
|
|
48
|
+
}
|
|
49
|
+
function listSandboxInstances() {
|
|
50
|
+
const dir = os.tmpdir();
|
|
51
|
+
let entries = [];
|
|
52
|
+
try {
|
|
53
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
return entries
|
|
59
|
+
.filter((entry) => {
|
|
60
|
+
return (entry.isDirectory() &&
|
|
61
|
+
(entry.name.startsWith("openclaw-manager-sandbox-") ||
|
|
62
|
+
entry.name.startsWith("clawdbot-manager-sandbox-")));
|
|
63
|
+
})
|
|
64
|
+
.map((entry) => path.join(dir, entry.name));
|
|
65
|
+
}
|
|
66
|
+
function stopSandboxDir(rootDir) {
|
|
67
|
+
const pidFile = path.join(rootDir, "manager-api.pid");
|
|
68
|
+
if (!fs.existsSync(pidFile)) {
|
|
69
|
+
return { ok: true, message: `already stopped (${rootDir})` };
|
|
70
|
+
}
|
|
71
|
+
const pid = readPid(pidFile);
|
|
72
|
+
if (!pid) {
|
|
73
|
+
return { ok: true, message: `pid invalid (${rootDir})` };
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
process.kill(pid, "SIGTERM");
|
|
77
|
+
return { ok: true, message: `stopped pid ${pid}` };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return { ok: false, error: `failed to stop pid ${pid}: ${String(err)}` };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { resolveConfigDirCandidates } from "../lib/config.js";
|
|
5
|
+
import { readPid } from "../lib/pids.js";
|
|
6
|
+
import { commandExists, findListeningPids } from "../lib/system.js";
|
|
7
|
+
export function stopManager(flags) {
|
|
8
|
+
const messages = [];
|
|
9
|
+
const errors = [];
|
|
10
|
+
let stopped = false;
|
|
11
|
+
if (process.platform !== "win32" && commandExists("systemctl")) {
|
|
12
|
+
const serviceName = "clawdbot-manager";
|
|
13
|
+
const servicePath = `/etc/systemd/system/${serviceName}.service`;
|
|
14
|
+
if (fs.existsSync(servicePath)) {
|
|
15
|
+
const result = spawnSync("systemctl", ["stop", serviceName], { encoding: "utf-8" });
|
|
16
|
+
if (result.status === 0) {
|
|
17
|
+
messages.push("manager: stopped systemd service");
|
|
18
|
+
stopped = true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const candidates = resolveConfigDirCandidates(flags);
|
|
23
|
+
for (const configDir of candidates) {
|
|
24
|
+
const pidPath = path.join(configDir, "manager.pid");
|
|
25
|
+
if (!fs.existsSync(pidPath))
|
|
26
|
+
continue;
|
|
27
|
+
const pid = readPid(pidPath);
|
|
28
|
+
if (!pid)
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, "SIGTERM");
|
|
32
|
+
fs.rmSync(pidPath, { force: true });
|
|
33
|
+
messages.push(`manager: stopped pid ${pid}`);
|
|
34
|
+
stopped = true;
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
errors.push(`manager: failed to stop pid ${pid}: ${String(err)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (!stopped) {
|
|
41
|
+
const port = flags.apiPort ?? Number(process.env.MANAGER_API_PORT ?? 17321);
|
|
42
|
+
const pids = findListeningPids(port);
|
|
43
|
+
if (pids.length) {
|
|
44
|
+
for (const pid of pids) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, "SIGTERM");
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
errors.push(`manager: failed to stop pid ${pid}: ${String(err)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
messages.push(`manager: stopped port ${port} (pids: ${pids.join(", ")})`);
|
|
53
|
+
stopped = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!stopped && !errors.length) {
|
|
57
|
+
messages.push("manager: not running");
|
|
58
|
+
}
|
|
59
|
+
if (errors.length) {
|
|
60
|
+
return { ok: false, messages, error: errors.join("; ") };
|
|
61
|
+
}
|
|
62
|
+
return { ok: true, messages };
|
|
63
|
+
}
|
package/bin/lib/args.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const longKeyMap = {
|
|
2
|
+
help: "help",
|
|
3
|
+
version: "version",
|
|
4
|
+
user: "user",
|
|
5
|
+
username: "user",
|
|
6
|
+
pass: "pass",
|
|
7
|
+
password: "pass",
|
|
8
|
+
"api-port": "apiPort",
|
|
9
|
+
"api-host": "apiHost",
|
|
10
|
+
"config-dir": "configDir",
|
|
11
|
+
"config-path": "configPath",
|
|
12
|
+
"log-path": "logPath",
|
|
13
|
+
"error-log-path": "errorLogPath",
|
|
14
|
+
"non-interactive": "nonInteractive"
|
|
15
|
+
};
|
|
16
|
+
const shortKeyMap = {
|
|
17
|
+
h: "help",
|
|
18
|
+
v: "version",
|
|
19
|
+
u: "user",
|
|
20
|
+
p: "pass"
|
|
21
|
+
};
|
|
22
|
+
export function parseArgs(argv) {
|
|
23
|
+
const flags = {};
|
|
24
|
+
const positionals = [];
|
|
25
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
26
|
+
const arg = argv[i];
|
|
27
|
+
if (arg === "--") {
|
|
28
|
+
positionals.push(...argv.slice(i + 1));
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (arg.startsWith("--")) {
|
|
32
|
+
const [rawKey, inlineValue] = arg.slice(2).split("=");
|
|
33
|
+
const key = longKeyMap[rawKey] ?? rawKey;
|
|
34
|
+
if (key === "help" || key === "version" || key === "nonInteractive") {
|
|
35
|
+
flags[key] = true;
|
|
36
|
+
}
|
|
37
|
+
else if (inlineValue !== undefined) {
|
|
38
|
+
setFlag(flags, key, inlineValue);
|
|
39
|
+
}
|
|
40
|
+
else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
41
|
+
setFlag(flags, key, argv[i + 1]);
|
|
42
|
+
i += 1;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
flags[key] = true;
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
50
|
+
const shorts = arg.slice(1).split("");
|
|
51
|
+
for (const short of shorts) {
|
|
52
|
+
const mapped = shortKeyMap[short] ?? short;
|
|
53
|
+
if (mapped === "help" || mapped === "version") {
|
|
54
|
+
flags[mapped] = true;
|
|
55
|
+
}
|
|
56
|
+
else if (mapped === "user" || mapped === "pass") {
|
|
57
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
58
|
+
setFlag(flags, mapped, argv[i + 1]);
|
|
59
|
+
i += 1;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
flags[mapped] = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
flags[mapped] = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
positionals.push(arg);
|
|
72
|
+
}
|
|
73
|
+
const command = (positionals[0] ?? "");
|
|
74
|
+
return { command, flags };
|
|
75
|
+
}
|
|
76
|
+
function setFlag(flags, key, value) {
|
|
77
|
+
if (key === "apiPort") {
|
|
78
|
+
const num = Number(value);
|
|
79
|
+
if (Number.isFinite(num)) {
|
|
80
|
+
flags.apiPort = num;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (key in flags || key in longKeyMap) {
|
|
85
|
+
flags[key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readPackageVersion } from "./version.js";
|
|
2
|
+
const BANNER_LINES = [
|
|
3
|
+
" ____ __ __ __ ___ ",
|
|
4
|
+
" / __ \\____ ___ ____/ /___ _/ /___ _____/ |/ /___ _____ ____ _____ _____ ",
|
|
5
|
+
" / / / / __ \\/ _ \\/ __ / __ `/ / __ \\___/ /|_/ / __ `/ __ \\/ __ `/ __ \\/ ___/ ",
|
|
6
|
+
"/ /_/ / /_/ / __/ /_/ / /_/ / / / / / /__/ / / / /_/ / / / / /_/ / /_/ / / ",
|
|
7
|
+
"\\____/ .___/\\___/\\__,_/\\__,_/_/_/ /_/\\___/_/ /_/\\__,_/_/ /_/\\__,_/\\____/_/ ",
|
|
8
|
+
" /_/ "
|
|
9
|
+
];
|
|
10
|
+
export function printBanner() {
|
|
11
|
+
const version = readPackageVersion();
|
|
12
|
+
console.log(`${BANNER_LINES.join("\n")}\nopenclaw-manager ${version}`);
|
|
13
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { randomBytes, scryptSync } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export function resolveConfigPaths(flags) {
|
|
6
|
+
const apiPort = flags.apiPort ?? Number(process.env.MANAGER_API_PORT ?? 17321);
|
|
7
|
+
const apiHost = flags.apiHost ?? process.env.MANAGER_API_HOST ?? "0.0.0.0";
|
|
8
|
+
const envConfigDir = process.env.MANAGER_CONFIG_DIR ?? "";
|
|
9
|
+
const envConfigPath = process.env.MANAGER_CONFIG_PATH ?? "";
|
|
10
|
+
let configDir = flags.configDir ?? envConfigDir;
|
|
11
|
+
let configPath = flags.configPath ?? envConfigPath;
|
|
12
|
+
if (!configDir && configPath) {
|
|
13
|
+
configDir = path.dirname(configPath);
|
|
14
|
+
}
|
|
15
|
+
if (!configDir) {
|
|
16
|
+
configDir = path.join(os.homedir(), ".openclaw-manager");
|
|
17
|
+
}
|
|
18
|
+
if (!configPath) {
|
|
19
|
+
configPath = path.join(configDir, "config.json");
|
|
20
|
+
}
|
|
21
|
+
const logPath = flags.logPath ?? process.env.MANAGER_LOG_PATH ?? path.join(configDir, "openclaw-manager.log");
|
|
22
|
+
const errorLogPath = flags.errorLogPath ??
|
|
23
|
+
process.env.MANAGER_ERROR_LOG_PATH ??
|
|
24
|
+
path.join(configDir, "openclaw-manager.error.log");
|
|
25
|
+
return {
|
|
26
|
+
apiPort,
|
|
27
|
+
apiHost,
|
|
28
|
+
configDir,
|
|
29
|
+
configPath,
|
|
30
|
+
logPath,
|
|
31
|
+
errorLogPath,
|
|
32
|
+
pidPath: path.join(configDir, "manager.pid")
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function ensureDir(dir) {
|
|
36
|
+
if (!dir)
|
|
37
|
+
return;
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
export function hasAdminConfig(configPath) {
|
|
41
|
+
if (!fs.existsSync(configPath))
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const raw = fs.readFileSync(configPath, "utf-8");
|
|
45
|
+
const parsed = JSON.parse(raw);
|
|
46
|
+
return Boolean(parsed?.auth?.username &&
|
|
47
|
+
typeof parsed.auth.username === "string" &&
|
|
48
|
+
typeof parsed.auth.salt === "string" &&
|
|
49
|
+
typeof parsed.auth.hash === "string");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function writeAdminConfig(configPath, username, password) {
|
|
56
|
+
const salt = randomBytes(16).toString("base64");
|
|
57
|
+
const hash = scryptSync(password, salt, 64).toString("base64");
|
|
58
|
+
const payload = {
|
|
59
|
+
auth: { username, salt, hash },
|
|
60
|
+
createdAt: new Date().toISOString()
|
|
61
|
+
};
|
|
62
|
+
ensureDir(path.dirname(configPath));
|
|
63
|
+
fs.writeFileSync(configPath, JSON.stringify(payload, null, 2));
|
|
64
|
+
console.log(`[manager] Admin config saved to ${configPath}`);
|
|
65
|
+
}
|
|
66
|
+
export function resolveConfigDirCandidates(flags) {
|
|
67
|
+
const explicit = flags.configDir ?? process.env.MANAGER_CONFIG_DIR;
|
|
68
|
+
if (explicit)
|
|
69
|
+
return [explicit];
|
|
70
|
+
return [
|
|
71
|
+
path.join(os.homedir(), ".openclaw-manager"),
|
|
72
|
+
path.join(os.homedir(), ".clawdbot-manager")
|
|
73
|
+
];
|
|
74
|
+
}
|
package/bin/lib/help.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { printBanner } from "./banner.js";
|
|
2
|
+
export function printHelp() {
|
|
3
|
+
printBanner();
|
|
4
|
+
console.log(`\nUsage:\n openclaw-manager <command> [options]\n\nCommands:\n start Start OpenClaw Manager\n stop Stop the running Manager process\n stop-all Stop Manager, sandboxes, and gateway processes\n\nOptions:\n -h, --help Show help\n -v, --version Show version\n -u, --user <name> Admin username (start)\n -p, --pass <value> Admin password (start)\n --non-interactive Fail instead of prompting for credentials\n --api-port <port> API port (default: 17321)\n --api-host <host> API host (default: 0.0.0.0)\n --config-dir <dir> Config directory\n --config-path <path> Config file path\n`);
|
|
5
|
+
}
|
|
6
|
+
export function printWelcome() {
|
|
7
|
+
printBanner();
|
|
8
|
+
console.log(`\nQuick start:\n openclaw-manager start\n\nCommon commands:\n openclaw-manager stop\n openclaw-manager stop-all\n\nTip: First start will ask for admin username/password.\nDocs: https://openclaw-manager.com\n`);
|
|
9
|
+
}
|
package/bin/lib/paths.js
ADDED
package/bin/lib/pids.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
export function readPid(pidPath) {
|
|
3
|
+
try {
|
|
4
|
+
const raw = fs.readFileSync(pidPath, "utf-8").trim();
|
|
5
|
+
const pid = Number(raw);
|
|
6
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
7
|
+
return null;
|
|
8
|
+
return pid;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function isPidRunning(pid) {
|
|
15
|
+
try {
|
|
16
|
+
process.kill(pid, 0);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
export function commandExists(cmd) {
|
|
3
|
+
const result = spawnSync("command", ["-v", cmd], { encoding: "utf-8", shell: true });
|
|
4
|
+
return result.status === 0;
|
|
5
|
+
}
|
|
6
|
+
export function findListeningPids(port) {
|
|
7
|
+
if (process.platform === "win32" || !commandExists("lsof"))
|
|
8
|
+
return [];
|
|
9
|
+
const result = spawnSync("lsof", ["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"], {
|
|
10
|
+
encoding: "utf-8"
|
|
11
|
+
});
|
|
12
|
+
if (result.error || result.status !== 0)
|
|
13
|
+
return [];
|
|
14
|
+
return String(result.stdout)
|
|
15
|
+
.split(/\s+/)
|
|
16
|
+
.map((value) => Number(value.trim()))
|
|
17
|
+
.filter((pid) => Number.isFinite(pid) && pid > 0);
|
|
18
|
+
}
|
|
19
|
+
export function listGatewayProcesses() {
|
|
20
|
+
if (process.platform === "win32" || !commandExists("pgrep"))
|
|
21
|
+
return [];
|
|
22
|
+
const result = spawnSync("pgrep", ["-fl", "clawdbot-gateway"], { encoding: "utf-8" });
|
|
23
|
+
if (result.error || result.status !== 0)
|
|
24
|
+
return [];
|
|
25
|
+
return String(result.stdout)
|
|
26
|
+
.split(/\n/)
|
|
27
|
+
.map((line) => line.trim())
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.map((line) => Number(line.split(/\s+/)[0]))
|
|
30
|
+
.filter((pid) => Number.isFinite(pid) && pid > 0);
|
|
31
|
+
}
|
package/bin/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolvePackageRoot } from "./paths.js";
|
|
4
|
+
export function readPackageVersion() {
|
|
5
|
+
try {
|
|
6
|
+
const pkgRoot = resolvePackageRoot();
|
|
7
|
+
const pkgPath = path.join(pkgRoot, "package.json");
|
|
8
|
+
const raw = fs.readFileSync(pkgPath, "utf-8");
|
|
9
|
+
const parsed = JSON.parse(raw);
|
|
10
|
+
if (parsed.version && typeof parsed.version === "string")
|
|
11
|
+
return parsed.version;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
return "0.0.0";
|
|
17
|
+
}
|