jishushell 0.4.2 → 0.4.17
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/Dockerfile.openclaw-slim +58 -0
- package/INSTALL-NOTICE +45 -0
- package/dist/auth.js +3 -3
- package/dist/auth.js.map +1 -1
- package/dist/cli/app.d.ts +3 -0
- package/dist/cli/app.js +156 -0
- package/dist/cli/app.js.map +1 -0
- package/dist/{doctor.d.ts → cli/doctor.d.ts} +6 -1
- package/dist/{doctor.js → cli/doctor.js} +389 -27
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/helpers.d.ts +4 -0
- package/dist/cli/helpers.js +32 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/job.d.ts +3 -0
- package/dist/cli/job.js +260 -0
- package/dist/cli/job.js.map +1 -0
- package/dist/cli/llm.d.ts +24 -0
- package/dist/cli/llm.js +593 -0
- package/dist/cli/llm.js.map +1 -0
- package/dist/cli/openclaw.d.ts +12 -0
- package/dist/cli/openclaw.js +156 -0
- package/dist/cli/openclaw.js.map +1 -0
- package/dist/cli/panel.d.ts +25 -0
- package/dist/cli/panel.js +734 -0
- package/dist/cli/panel.js.map +1 -0
- package/dist/cli.js +476 -219
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +22 -4
- package/dist/config.js +96 -55
- package/dist/config.js.map +1 -1
- package/dist/control.d.ts +13 -41
- package/dist/control.js +12 -1355
- package/dist/control.js.map +1 -1
- package/dist/install.d.ts +1 -1
- package/dist/install.js +15 -29
- package/dist/install.js.map +1 -1
- package/dist/routes/apps.d.ts +3 -0
- package/dist/routes/apps.js +99 -0
- package/dist/routes/apps.js.map +1 -0
- package/dist/routes/backup.d.ts +2 -0
- package/dist/routes/backup.js +370 -0
- package/dist/routes/backup.js.map +1 -0
- package/dist/routes/instances.d.ts +1 -0
- package/dist/routes/instances.js +61 -15
- package/dist/routes/instances.js.map +1 -1
- package/dist/routes/llm.d.ts +15 -0
- package/dist/routes/llm.js +246 -0
- package/dist/routes/llm.js.map +1 -0
- package/dist/routes/setup.js +32 -7
- package/dist/routes/setup.js.map +1 -1
- package/dist/routes/system.js +31 -6
- package/dist/routes/system.js.map +1 -1
- package/dist/server.js +69 -5
- package/dist/server.js.map +1 -1
- package/dist/services/app-compiler.d.ts +15 -0
- package/dist/services/app-compiler.js +169 -0
- package/dist/services/app-compiler.js.map +1 -0
- package/dist/services/app-manager.d.ts +17 -0
- package/dist/services/app-manager.js +168 -0
- package/dist/services/app-manager.js.map +1 -0
- package/dist/services/backup-manager.d.ts +253 -0
- package/dist/services/backup-manager.js +2014 -0
- package/dist/services/backup-manager.js.map +1 -0
- package/dist/services/backup-verify.d.ts +26 -0
- package/dist/services/backup-verify.js +240 -0
- package/dist/services/backup-verify.js.map +1 -0
- package/dist/services/instance-manager.d.ts +73 -5
- package/dist/services/instance-manager.js +446 -74
- package/dist/services/instance-manager.js.map +1 -1
- package/dist/services/job-manager.d.ts +22 -0
- package/dist/services/job-manager.js +102 -0
- package/dist/services/job-manager.js.map +1 -0
- package/dist/services/llm-proxy/adapters.js +5 -1
- package/dist/services/llm-proxy/adapters.js.map +1 -1
- package/dist/services/llm-proxy/index.d.ts +30 -0
- package/dist/services/llm-proxy/index.js +71 -1
- package/dist/services/llm-proxy/index.js.map +1 -1
- package/dist/services/llm-proxy/ssrf.js +1 -1
- package/dist/services/llm-proxy/ssrf.js.map +1 -1
- package/dist/services/nomad-manager.js +263 -159
- package/dist/services/nomad-manager.js.map +1 -1
- package/dist/services/panel-manager.d.ts +40 -0
- package/dist/services/panel-manager.js +346 -0
- package/dist/services/panel-manager.js.map +1 -0
- package/dist/services/process-manager.js +24 -10
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/setup-manager.d.ts +4 -2
- package/dist/services/setup-manager.js +578 -154
- package/dist/services/setup-manager.js.map +1 -1
- package/dist/services/telemetry/activation.js +10 -7
- package/dist/services/telemetry/activation.js.map +1 -1
- package/dist/services/telemetry/client.js +7 -18
- package/dist/services/telemetry/client.js.map +1 -1
- package/dist/services/telemetry/heartbeat.js +12 -6
- package/dist/services/telemetry/heartbeat.js.map +1 -1
- package/dist/services/update-manager.d.ts +47 -0
- package/dist/services/update-manager.js +305 -0
- package/dist/services/update-manager.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/utils/fs.d.ts +85 -0
- package/dist/utils/fs.js +111 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/safe-json.d.ts +2 -0
- package/dist/utils/safe-json.js +22 -16
- package/dist/utils/safe-json.js.map +1 -1
- package/install/jishu-install.sh +582 -138
- package/install/jishu-uninstall.sh +276 -391
- package/install/post-install.sh +85 -3
- package/openclaw-entry.sh +15 -0
- package/package.json +12 -5
- package/public/assets/Dashboard-CQsp1Mr9.js +1 -0
- package/public/assets/InitPassword-BEC8SE4A.js +1 -0
- package/public/assets/InstanceDetail-B5wTgNEg.js +17 -0
- package/public/assets/{Login-RkjzTNWg.js → Login-D1Bt-Lyk.js} +1 -1
- package/public/assets/NewInstance-GQzm3K9D.js +1 -0
- package/public/assets/Settings-ByjGlqhP.js +1 -0
- package/public/assets/Setup-cMF21Y-8.js +1 -0
- package/public/assets/index-B6qQP4mH.css +1 -0
- package/public/assets/index-BuTQtuNy.js +16 -0
- package/public/assets/logo-black-theme-DywLAtFy.png +0 -0
- package/public/assets/logo-white-theme-DXffFAWw.png +0 -0
- package/public/assets/{providers-lBSOjUWy.js → providers-V-vwrExZ.js} +1 -1
- package/public/assets/{usePolling-CqQ8hrNc.js → usePolling-CK0DfI4h.js} +1 -1
- package/public/assets/{vendor-i18n-Bvxxh8Di.js → vendor-i18n-CfW0RvgE.js} +1 -1
- package/public/assets/vendor-react-B1-3Yrt-.js +59 -0
- package/public/index.html +4 -4
- package/dist/doctor.js.map +0 -1
- package/public/assets/Dashboard-CAOQDYDR.js +0 -1
- package/public/assets/InitPassword-CkehIkJG.js +0 -1
- package/public/assets/InstanceDetail-CzW2S95J.js +0 -14
- package/public/assets/NewInstance-DdbErdjA.js +0 -1
- package/public/assets/Settings-BUD7zwv9.js +0 -1
- package/public/assets/Setup-RRTIERGG.js +0 -1
- package/public/assets/index-77Ug7feY.css +0 -1
- package/public/assets/index-DfRnVUQR.js +0 -16
- package/public/assets/vendor-react-DONn7uBV.js +0 -59
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
if (process.platform === "darwin") {
|
|
3
|
-
const extra = ["/usr/local/bin", "/opt/homebrew/bin", "/opt/homebrew/sbin"
|
|
3
|
+
const extra = ["/usr/local/bin", "/opt/homebrew/bin", "/opt/homebrew/sbin"];
|
|
4
4
|
const current = process.env.PATH ?? "";
|
|
5
5
|
const missing = extra.filter(p => !current.split(":").includes(p));
|
|
6
6
|
if (missing.length > 0)
|
|
@@ -10,12 +10,17 @@ import { existsSync } from "fs";
|
|
|
10
10
|
import { join } from "path";
|
|
11
11
|
import { homedir } from "os";
|
|
12
12
|
import { exec } from "child_process";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
if (process.platform === "darwin") {
|
|
14
|
+
const jHome = process.env.JISHUSHELL_HOME || join(homedir(), ".jishushell");
|
|
15
|
+
const sock = join(jHome, "colima", "jishushell", "docker.sock");
|
|
16
|
+
if (existsSync(sock))
|
|
17
|
+
process.env.DOCKER_HOST = `unix://${sock}`;
|
|
18
|
+
}
|
|
19
|
+
const [subcommand, ...rest] = process.argv.slice(2);
|
|
20
|
+
function cliError(err) {
|
|
21
|
+
console.error(err.message || err);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
19
24
|
function openBrowser(url) {
|
|
20
25
|
const platform = process.platform;
|
|
21
26
|
const cmd = platform === "darwin" ? `open "${url}"`
|
|
@@ -23,265 +28,517 @@ function openBrowser(url) {
|
|
|
23
28
|
: `xdg-open "${url}"`;
|
|
24
29
|
exec(cmd, () => { });
|
|
25
30
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
// ── install subcommand ─────────────────────────────────────────────────────
|
|
39
|
-
if (subcommand === "install") {
|
|
40
|
-
const opts = parseInstallArgs(rest);
|
|
41
|
-
runInstall(opts).catch((err) => {
|
|
42
|
-
console.error("Installation failed:", err);
|
|
43
|
-
process.exit(1);
|
|
31
|
+
// ── HTTP thin-client helpers (used by backup/restore/export/import commands) ──
|
|
32
|
+
async function apiCall(path, opts = {}) {
|
|
33
|
+
const port = process.env.JISHUSHELL_PORT || "8090";
|
|
34
|
+
const url = `http://127.0.0.1:${port}/api${path}`;
|
|
35
|
+
const res = await fetch(url, {
|
|
36
|
+
method: opts.method || "GET",
|
|
37
|
+
headers: opts.body ? { "Content-Type": "application/json" } : {},
|
|
38
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
44
39
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
console.error("start failed:", err.message);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
});
|
|
53
|
-
// ── stop ───────────────────────────────────────────────────────────────────
|
|
54
|
-
}
|
|
55
|
-
else if (subcommand === "stop") {
|
|
56
|
-
// --jobs: also stop (but not purge) all Nomad jobs before stopping the panel
|
|
57
|
-
const withJobs = rest.includes("--jobs") || rest.includes("-j");
|
|
58
|
-
const stopFn = withJobs ? stopWithJobs : stopPanel;
|
|
59
|
-
stopFn().then(() => process.exit(0)).catch((err) => {
|
|
60
|
-
console.error("stop failed:", err.message);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
});
|
|
63
|
-
// ── restart ────────────────────────────────────────────────────────────────
|
|
40
|
+
if (!res.ok) {
|
|
41
|
+
const err = await res.json().catch(() => ({ detail: res.statusText }));
|
|
42
|
+
throw new Error(err.detail || `HTTP ${res.status}`);
|
|
43
|
+
}
|
|
44
|
+
return res.json();
|
|
64
45
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
46
|
+
async function waitForJob(jobId) {
|
|
47
|
+
while (true) {
|
|
48
|
+
const job = await apiCall(`/backup/jobs/${jobId}`);
|
|
49
|
+
if (job.status === "completed")
|
|
50
|
+
return job;
|
|
51
|
+
if (job.status === "failed")
|
|
52
|
+
throw new Error(job.error || "Job failed");
|
|
53
|
+
process.stdout.write(`\r ${job.progress || job.status}...`);
|
|
54
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
55
|
+
}
|
|
71
56
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
// ── doctor ─────────────────────────────────────────────────────────────────
|
|
57
|
+
// ── Subcommand dispatch ────────────────────────────────────────────────────
|
|
58
|
+
if (["install", "uninstall", "start", "stop", "restart", "status", "serve", "onboard", "reset", "update-check", "update", "config"].includes(subcommand)) {
|
|
59
|
+
const { run } = await import("./cli/panel.js");
|
|
60
|
+
run([subcommand, ...rest]).catch(cliError);
|
|
78
61
|
}
|
|
79
62
|
else if (subcommand === "doctor") {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
console.error("doctor failed:", err.message);
|
|
83
|
-
process.exit(1);
|
|
84
|
-
});
|
|
85
|
-
// ── onboard ────────────────────────────────────────────────────────────────
|
|
63
|
+
const { run } = await import("./cli/doctor.js");
|
|
64
|
+
run(rest).catch(cliError);
|
|
86
65
|
}
|
|
87
|
-
else if (subcommand === "
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
console.error("onboard failed:", err.message);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
});
|
|
93
|
-
// ── reset ──────────────────────────────────────────────────────────────────
|
|
66
|
+
else if (subcommand === "openclaw") {
|
|
67
|
+
const { run } = await import("./cli/openclaw.js");
|
|
68
|
+
run(rest).catch(cliError);
|
|
94
69
|
}
|
|
95
|
-
else if (subcommand === "
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
process.exit(1);
|
|
100
|
-
});
|
|
101
|
-
// ── update-check ───────────────────────────────────────────────────────────
|
|
70
|
+
else if (subcommand === "pairing") {
|
|
71
|
+
// Backward-compat alias: jishushell pairing ... → jishushell openclaw pairing ...
|
|
72
|
+
const { run } = await import("./cli/openclaw.js");
|
|
73
|
+
run(["pairing", ...rest]).catch(cliError);
|
|
102
74
|
}
|
|
103
|
-
else if (subcommand === "
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
process.exit(info.hasUpdate ? 0 : 1);
|
|
107
|
-
}).catch((err) => {
|
|
108
|
-
console.error("update-check failed:", err.message);
|
|
109
|
-
process.exit(2);
|
|
110
|
-
});
|
|
111
|
-
// ── update-run ─────────────────────────────────────────────────────────────
|
|
75
|
+
else if (subcommand === "job") {
|
|
76
|
+
const { run } = await import("./cli/job.js");
|
|
77
|
+
run(rest).catch(cliError);
|
|
112
78
|
}
|
|
113
|
-
else if (subcommand === "
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
process.exit(1);
|
|
117
|
-
});
|
|
118
|
-
// ── pairing: approve / list DM pairing requests inside running containers ──
|
|
79
|
+
else if (subcommand === "app") {
|
|
80
|
+
const { run } = await import("./cli/app.js");
|
|
81
|
+
run(rest).catch(cliError);
|
|
119
82
|
}
|
|
120
|
-
else if (subcommand === "
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
83
|
+
else if (subcommand === "llm") {
|
|
84
|
+
const { run } = await import("./cli/llm.js");
|
|
85
|
+
run(rest).catch(cliError);
|
|
86
|
+
// ── backup ─────────────────────────────────────────────────────────────────
|
|
87
|
+
}
|
|
88
|
+
else if (subcommand === "backup") {
|
|
89
|
+
// Handle "backup verify <instance-id> <filename>" subcommand
|
|
90
|
+
if (rest[0] === "verify") {
|
|
91
|
+
const instanceId = rest[1];
|
|
92
|
+
const filename = rest[2];
|
|
93
|
+
if (!instanceId || !filename) {
|
|
94
|
+
console.error("Usage: jishushell backup verify <instance-id> <filename>");
|
|
131
95
|
process.exit(1);
|
|
132
|
-
}
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const result = await apiCall(`/instances/${instanceId}/backup/verify/${encodeURIComponent(filename)}`, { method: "POST" });
|
|
99
|
+
console.log(result.valid ? "Verification PASSED." : "Verification FAILED.");
|
|
100
|
+
for (const check of result.checks || []) {
|
|
101
|
+
console.log(` ${check.passed ? "+" : "x"} ${check.name}${check.detail ? ": " + check.detail : ""}`);
|
|
102
|
+
}
|
|
103
|
+
if (rest.includes("--json"))
|
|
104
|
+
console.log(JSON.stringify(result, null, 2));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
console.error(`Verify failed: ${e.message}`);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
process.exit(0);
|
|
133
111
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
112
|
+
const id = rest[0];
|
|
113
|
+
if (!id) {
|
|
114
|
+
console.error("Usage: jishushell backup <id> [--with-sessions] [--only-config] [--state-only] [--no-workspace]");
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
const withSessions = rest.includes("--with-sessions");
|
|
118
|
+
const onlyConfig = rest.includes("--only-config");
|
|
119
|
+
const stateOnly = rest.includes("--state-only");
|
|
120
|
+
const noWorkspace = rest.includes("--no-workspace");
|
|
121
|
+
try {
|
|
122
|
+
console.log(`Creating backup for ${id}...`);
|
|
123
|
+
const result = await apiCall(`/instances/${id}/backup`, {
|
|
124
|
+
method: "POST",
|
|
125
|
+
body: {
|
|
126
|
+
include_sessions: withSessions,
|
|
127
|
+
only_config: onlyConfig,
|
|
128
|
+
scope: stateOnly ? "state" : "home",
|
|
129
|
+
...(noWorkspace ? { include_workspace: false } : {}),
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
if (result.job_id) {
|
|
133
|
+
const job = await waitForJob(result.job_id);
|
|
134
|
+
console.log(`\nBackup complete: ${job.result?.filename} (${job.result?.size} bytes)`);
|
|
141
135
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
136
|
+
if (rest.includes("--verify") && result.job_id) {
|
|
137
|
+
const job = await apiCall(`/backup/jobs/${result.job_id}`);
|
|
138
|
+
if (job.result?.filename) {
|
|
139
|
+
console.log("Verifying...");
|
|
140
|
+
const v = await apiCall(`/instances/${id}/backup/verify/${encodeURIComponent(job.result.filename)}`, { method: "POST" });
|
|
141
|
+
console.log(v.valid ? "Verification passed." : "Verification FAILED.");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
console.error(`Backup failed: ${e.message}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
process.exit(0);
|
|
150
|
+
// ── restore ────────────────────────────────────────────────────────────────
|
|
151
|
+
}
|
|
152
|
+
else if (subcommand === "restore") {
|
|
153
|
+
if (rest.includes("--new")) {
|
|
154
|
+
const fromIdx = rest.indexOf("--from");
|
|
155
|
+
const idxNewId = rest.indexOf("--id");
|
|
156
|
+
if (fromIdx !== -1) {
|
|
157
|
+
const sourceId = rest[fromIdx + 1];
|
|
158
|
+
if (!sourceId) {
|
|
159
|
+
console.error("Usage: jishushell restore --new --from <src-id> --latest|--pick [--id <new-id>]");
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
let backups;
|
|
163
|
+
try {
|
|
164
|
+
backups = await apiCall(`/instances/${sourceId}/backups`);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
console.error(`Restore failed: ${e.message}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
if (!backups.length) {
|
|
171
|
+
console.error("No backups found.");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
let backupFile = "";
|
|
175
|
+
if (rest.includes("--latest")) {
|
|
176
|
+
backupFile = backups[0].filename;
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log("Available backups:");
|
|
180
|
+
backups.forEach((b, i) => {
|
|
181
|
+
console.log(` ${i + 1}. [${b.type}] ${new Date(b.created_at).toLocaleString()} (${(b.size / 1024 / 1024).toFixed(1)}MB)`);
|
|
182
|
+
});
|
|
183
|
+
const readline = await import("readline");
|
|
184
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
185
|
+
const answer = await new Promise(r => rl.question("Select number: ", r));
|
|
186
|
+
rl.close();
|
|
187
|
+
const idx = parseInt(answer, 10) - 1;
|
|
188
|
+
if (idx < 0 || idx >= backups.length) {
|
|
189
|
+
console.error("Invalid selection.");
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
backupFile = backups[idx].filename;
|
|
193
|
+
}
|
|
194
|
+
const newId = idxNewId !== -1 ? rest[idxNewId + 1] : `${sourceId}-copy`;
|
|
195
|
+
console.log(`Creating new instance ${newId} from ${sourceId}/${backupFile}...`);
|
|
196
|
+
try {
|
|
197
|
+
const result = await apiCall("/instances/create-from-backup", { method: "POST", body: { source_id: sourceId, backup_file: backupFile, new_id: newId, new_name: newId } });
|
|
198
|
+
if (result.ok) {
|
|
199
|
+
console.log(`Instance created: ${result.instance_id}`);
|
|
200
|
+
if (result.warnings?.length)
|
|
201
|
+
result.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
console.error(`Create from backup failed: ${result.detail || "unknown error"}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
console.error(`Restore --new failed: ${e.message}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
145
212
|
}
|
|
146
213
|
else {
|
|
147
|
-
|
|
214
|
+
const file = rest.find((a) => !a.startsWith("--"));
|
|
215
|
+
if (!file) {
|
|
216
|
+
console.error("Usage: jishushell restore --new <file> [--id <new-id>]");
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
console.log(`Use: jishushell import ${file}`);
|
|
220
|
+
}
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
const id = rest[0];
|
|
224
|
+
if (!id) {
|
|
225
|
+
console.error("Usage: jishushell restore <id> <--latest|--pick|file>");
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
let file = "";
|
|
229
|
+
if (rest.includes("--latest") || rest.includes("--pick")) {
|
|
230
|
+
let backups;
|
|
231
|
+
try {
|
|
232
|
+
backups = await apiCall(`/instances/${id}/backups`);
|
|
233
|
+
}
|
|
234
|
+
catch (e) {
|
|
235
|
+
console.error(`Restore failed: ${e.message}`);
|
|
148
236
|
process.exit(1);
|
|
149
237
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
runPairingApprove({ instanceId, channel, code, notify }).then((ok) => process.exit(ok ? 0 : 1)).catch((err) => {
|
|
153
|
-
console.error("pairing approve failed:", err.message);
|
|
238
|
+
if (!backups.length) {
|
|
239
|
+
console.error("No backups found.");
|
|
154
240
|
process.exit(1);
|
|
155
|
-
}
|
|
241
|
+
}
|
|
242
|
+
if (rest.includes("--latest")) {
|
|
243
|
+
file = backups[0].filename;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
console.log("Available backups:");
|
|
247
|
+
backups.forEach((b, i) => {
|
|
248
|
+
console.log(` ${i + 1}. ${b.type} ${new Date(b.created_at).toLocaleString()} (${(b.size / 1024 / 1024).toFixed(1)}MB)`);
|
|
249
|
+
});
|
|
250
|
+
const readline = await import("readline");
|
|
251
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
252
|
+
const answer = await new Promise(r => rl.question("Select number: ", r));
|
|
253
|
+
rl.close();
|
|
254
|
+
const idx = parseInt(answer, 10) - 1;
|
|
255
|
+
if (idx < 0 || idx >= backups.length) {
|
|
256
|
+
console.error("Invalid selection.");
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
file = backups[idx].filename;
|
|
260
|
+
}
|
|
156
261
|
}
|
|
157
262
|
else {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
list 列出等待审批的配对请求
|
|
163
|
-
approve <channel> <code> 审批配对码(例: feishu LVU7PNYK)
|
|
164
|
-
approve <code> 默认 channel=feishu 简写形式
|
|
165
|
-
|
|
166
|
-
Options:
|
|
167
|
-
--instance <id> 指定实例 ID(只有一个实例时可省略)
|
|
168
|
-
--notify 审批后通知申请方
|
|
169
|
-
|
|
170
|
-
Examples:
|
|
171
|
-
jishushell pairing list
|
|
172
|
-
jishushell pairing approve feishu LVU7PNYK
|
|
173
|
-
jishushell pairing approve LVU7PNYK
|
|
174
|
-
jishushell pairing approve feishu LVU7PNYK --instance my-instance
|
|
175
|
-
`);
|
|
263
|
+
file = rest[1];
|
|
264
|
+
}
|
|
265
|
+
if (!file) {
|
|
266
|
+
console.error("No backup file specified.");
|
|
176
267
|
process.exit(1);
|
|
177
268
|
}
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
269
|
+
try {
|
|
270
|
+
console.log(`Restoring ${id} from ${file}...`);
|
|
271
|
+
const result = await apiCall(`/instances/${id}/restore/${encodeURIComponent(file)}`, { method: "POST" });
|
|
272
|
+
if (result.job_id) {
|
|
273
|
+
const job = await waitForJob(result.job_id);
|
|
274
|
+
const r = job.result || {};
|
|
275
|
+
if (r.ok) {
|
|
276
|
+
console.log(`\nRestore complete.`);
|
|
277
|
+
if (r.api_key_status === "lost")
|
|
278
|
+
console.log("Warning: API Key may need re-verification on this host.");
|
|
279
|
+
if (r.warnings?.length)
|
|
280
|
+
r.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
190
281
|
}
|
|
191
|
-
|
|
192
|
-
|
|
282
|
+
else {
|
|
283
|
+
console.error(`\nRestore failed.${r.rolled_back ? " Auto-rollback completed." : ""}`);
|
|
284
|
+
if (r.warnings?.length)
|
|
285
|
+
r.warnings.forEach((w) => console.log(` ${w}`));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
console.error(`Restore failed: ${e.message}`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
process.exit(0);
|
|
294
|
+
// ── export ─────────────────────────────────────────────────────────────────
|
|
295
|
+
}
|
|
296
|
+
else if (subcommand === "export") {
|
|
297
|
+
const id = rest[0];
|
|
298
|
+
if (!id) {
|
|
299
|
+
console.error("Usage: jishushell export <id> [--with-sessions]");
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
const withSessions = rest.includes("--with-sessions");
|
|
303
|
+
try {
|
|
304
|
+
console.log(`Exporting ${id}...`);
|
|
305
|
+
const result = await apiCall(`/instances/${id}/export`, { method: "POST", body: { include_sessions: withSessions } });
|
|
306
|
+
if (result.job_id) {
|
|
307
|
+
const job = await waitForJob(result.job_id);
|
|
308
|
+
const r = job.result || {};
|
|
309
|
+
console.log(`\nExport complete: ${r.filename}`);
|
|
310
|
+
console.log(`Download: http://127.0.0.1:${process.env.JISHUSHELL_PORT || "8090"}/api/instances/${id}/export/download/${encodeURIComponent(r.filename)}`);
|
|
311
|
+
if (r.warnings?.length)
|
|
312
|
+
r.warnings.forEach((w) => console.log(` Warning: ${w}`));
|
|
193
313
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
console.error(`Export failed: ${e.message}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
process.exit(0);
|
|
320
|
+
// ── import ─────────────────────────────────────────────────────────────────
|
|
321
|
+
}
|
|
322
|
+
else if (subcommand === "import") {
|
|
323
|
+
const file = rest[0];
|
|
324
|
+
if (!file) {
|
|
325
|
+
console.error("Usage: jishushell import <file> [--id <id>] [--name <name>]");
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
const idIdx = rest.indexOf("--id");
|
|
329
|
+
const nameIdx = rest.indexOf("--name");
|
|
330
|
+
const customId = idIdx !== -1 ? rest[idIdx + 1] : undefined;
|
|
331
|
+
const customName = nameIdx !== -1 ? rest[nameIdx + 1] : undefined;
|
|
332
|
+
try {
|
|
333
|
+
const { readFileSync } = await import("fs");
|
|
334
|
+
const { basename } = await import("path");
|
|
335
|
+
const port = process.env.JISHUSHELL_PORT || "8090";
|
|
336
|
+
console.log("Uploading file...");
|
|
337
|
+
const fileData = readFileSync(file);
|
|
338
|
+
const boundary = "----JishuShellCLI" + Date.now();
|
|
339
|
+
const fileName = basename(file);
|
|
340
|
+
const body = Buffer.concat([
|
|
341
|
+
Buffer.from(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${fileName}"\r\nContent-Type: application/gzip\r\n\r\n`),
|
|
342
|
+
fileData,
|
|
343
|
+
Buffer.from(`\r\n--${boundary}--\r\n`),
|
|
344
|
+
]);
|
|
345
|
+
const uploadRes = await fetch(`http://127.0.0.1:${port}/api/instances/import/upload`, {
|
|
346
|
+
method: "POST",
|
|
347
|
+
headers: { "Content-Type": `multipart/form-data; boundary=${boundary}` },
|
|
348
|
+
body,
|
|
349
|
+
});
|
|
350
|
+
if (!uploadRes.ok) {
|
|
351
|
+
const err = await uploadRes.json().catch(() => ({ detail: uploadRes.statusText }));
|
|
352
|
+
throw new Error(err.detail || `Upload failed: ${uploadRes.status}`);
|
|
353
|
+
}
|
|
354
|
+
const { temp_id } = await uploadRes.json();
|
|
355
|
+
console.log("Previewing...");
|
|
356
|
+
const preview = await apiCall("/instances/import/preview", { method: "POST", body: { temp_id } });
|
|
357
|
+
console.log(` Name: ${preview.manifest?.name || "unknown"}`);
|
|
358
|
+
console.log(` Type: ${preview.manifest?.type || "unknown"}`);
|
|
359
|
+
console.log(` Size: ${((preview.estimated_size || 0) / 1024 / 1024).toFixed(1)}MB`);
|
|
360
|
+
if (preview.warnings?.length)
|
|
361
|
+
for (const w of preview.warnings)
|
|
362
|
+
console.log(` Warning: ${w}`);
|
|
363
|
+
const instanceId = customId || preview.manifest?.name?.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 63) || `import-${Date.now()}`;
|
|
364
|
+
const instanceName = customName || preview.manifest?.name || instanceId;
|
|
365
|
+
console.log(`Creating instance ${instanceId}...`);
|
|
366
|
+
const result = await apiCall("/instances/import", { method: "POST", body: { temp_id, id: instanceId, name: instanceName } });
|
|
367
|
+
if (result.ok) {
|
|
368
|
+
console.log(`Instance created: ${result.instance_id}`);
|
|
369
|
+
if (result.warnings?.length)
|
|
370
|
+
for (const w of result.warnings)
|
|
371
|
+
console.log(` Warning: ${w}`);
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
console.error(`Import failed: ${result.detail || "unknown error"}`);
|
|
375
|
+
process.exit(1);
|
|
197
376
|
}
|
|
198
377
|
}
|
|
199
|
-
|
|
200
|
-
console.error(
|
|
378
|
+
catch (e) {
|
|
379
|
+
console.error(`Import failed: ${e.message}`);
|
|
201
380
|
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
|
|
381
|
+
}
|
|
382
|
+
process.exit(0);
|
|
383
|
+
// ── clone ──────────────────────────────────────────────────────────────────
|
|
204
384
|
}
|
|
205
|
-
else if (subcommand === "
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
385
|
+
else if (subcommand === "clone") {
|
|
386
|
+
const src = rest[0];
|
|
387
|
+
const newId = rest[1];
|
|
388
|
+
if (!src || !newId) {
|
|
389
|
+
console.error("Usage: jishushell clone <src-id> <new-id> [--with-sessions] [--no-memory]");
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
const withSessions = rest.includes("--with-sessions");
|
|
393
|
+
const noMemory = rest.includes("--no-memory");
|
|
394
|
+
try {
|
|
395
|
+
console.log(`Cloning ${src} to ${newId}...`);
|
|
396
|
+
const result = await apiCall("/instances", { method: "POST", body: { id: newId, name: newId, clone_from: src, clone_options: { include_sessions: withSessions, include_memory: !noMemory } } });
|
|
397
|
+
console.log(`Clone created: ${result.id || newId}`);
|
|
398
|
+
}
|
|
399
|
+
catch (e) {
|
|
400
|
+
console.error(`Clone failed: ${e.message}`);
|
|
212
401
|
process.exit(1);
|
|
213
402
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
403
|
+
process.exit(0);
|
|
404
|
+
// ── backups ────────────────────────────────────────────────────────────────
|
|
405
|
+
}
|
|
406
|
+
else if (subcommand === "backups") {
|
|
407
|
+
const sub = rest[0];
|
|
408
|
+
if (sub === "list") {
|
|
409
|
+
const id = rest[1];
|
|
410
|
+
try {
|
|
411
|
+
if (id) {
|
|
412
|
+
const backups = await apiCall(`/instances/${id}/backups`);
|
|
413
|
+
if (!backups.length)
|
|
414
|
+
console.log("No backups.");
|
|
415
|
+
else
|
|
416
|
+
backups.forEach((b, i) => console.log(` ${i + 1}. [${b.type}] ${new Date(b.created_at).toLocaleString()} ${(b.size / 1024 / 1024).toFixed(1)}MB ${b.filename}`));
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
const orphans = await apiCall("/backups/orphaned");
|
|
420
|
+
if (orphans.length) {
|
|
421
|
+
console.log("Orphaned backups:");
|
|
422
|
+
for (const o of orphans)
|
|
423
|
+
console.log(` ${o.instance_id}: ${o.backups?.length || 0} files`);
|
|
424
|
+
}
|
|
425
|
+
else
|
|
426
|
+
console.log("No orphaned backups.");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
console.error(`backups list failed: ${e.message}`);
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else if (sub === "clean") {
|
|
435
|
+
try {
|
|
436
|
+
const orphans = await apiCall("/backups/orphaned");
|
|
437
|
+
if (!orphans.length) {
|
|
438
|
+
console.log("No orphaned backups to clean.");
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
const force = rest.includes("--force");
|
|
442
|
+
console.log(`Found ${orphans.length} orphaned backup(s):`);
|
|
443
|
+
for (const o of orphans)
|
|
444
|
+
console.log(` ${o.instance_id}: ${o.backups?.length || 0} files`);
|
|
445
|
+
if (!force) {
|
|
446
|
+
const readline = await import("readline");
|
|
447
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
448
|
+
const answer = await new Promise(r => rl.question("Delete all orphaned backups? (y/N): ", r));
|
|
219
449
|
rl.close();
|
|
220
|
-
if (
|
|
450
|
+
if (answer.toLowerCase() !== "y") {
|
|
221
451
|
console.log("Cancelled.");
|
|
222
452
|
process.exit(0);
|
|
223
453
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
454
|
+
}
|
|
455
|
+
for (const o of orphans) {
|
|
456
|
+
for (const b of o.backups || [])
|
|
457
|
+
await apiCall(`/backups/orphaned/${encodeURIComponent(o.instance_id)}/${encodeURIComponent(b.filename)}`, { method: "DELETE" });
|
|
458
|
+
console.log(` Cleaned: ${o.instance_id}`);
|
|
459
|
+
}
|
|
460
|
+
console.log("Done.");
|
|
461
|
+
}
|
|
462
|
+
catch (e) {
|
|
463
|
+
console.error(`backups clean failed: ${e.message}`);
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
227
466
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
process.exit(
|
|
233
|
-
// ──
|
|
467
|
+
else {
|
|
468
|
+
console.error("Usage: jishushell backups list [id]\n jishushell backups clean [--force]");
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
process.exit(0);
|
|
472
|
+
// ── auto-backup ────────────────────────────────────────────────────────────
|
|
473
|
+
}
|
|
474
|
+
else if (subcommand === "auto-backup") {
|
|
475
|
+
const id = rest[0];
|
|
476
|
+
const action = rest[1];
|
|
477
|
+
if (!id || !action) {
|
|
478
|
+
console.error("Usage: jishushell auto-backup <id> enable|disable|status");
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
if (action === "status") {
|
|
483
|
+
const config = await apiCall(`/instances/${id}/auto-backup`);
|
|
484
|
+
console.log(JSON.stringify(config, null, 2));
|
|
485
|
+
}
|
|
486
|
+
else if (action === "enable") {
|
|
487
|
+
const interval = parseInt(rest[rest.indexOf("--interval") + 1], 10) || 24;
|
|
488
|
+
const keep = parseInt(rest[rest.indexOf("--keep") + 1], 10) || 7;
|
|
489
|
+
await apiCall(`/instances/${id}/auto-backup`, { method: "PUT", body: { enabled: true, interval_hours: interval, keep_count: keep } });
|
|
490
|
+
console.log(`Auto-backup enabled for ${id} (every ${interval}h, keep ${keep})`);
|
|
491
|
+
}
|
|
492
|
+
else if (action === "disable") {
|
|
493
|
+
await apiCall(`/instances/${id}/auto-backup`, { method: "PUT", body: { enabled: false } });
|
|
494
|
+
console.log(`Auto-backup disabled for ${id}`);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
console.error(`Unknown action: ${action}. Use enable|disable|status`);
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (e) {
|
|
502
|
+
console.error(`auto-backup failed: ${e.message}`);
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
process.exit(0);
|
|
234
506
|
}
|
|
235
507
|
else if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
|
|
508
|
+
const [{ brief: panelBrief }, { brief: doctorBrief }, { brief: openclawBrief }, { brief: jobBrief }, { brief: appBrief }, { brief: llmBrief },] = await Promise.all([
|
|
509
|
+
import("./cli/panel.js"),
|
|
510
|
+
import("./cli/doctor.js"),
|
|
511
|
+
import("./cli/openclaw.js"),
|
|
512
|
+
import("./cli/job.js"),
|
|
513
|
+
import("./cli/app.js"),
|
|
514
|
+
import("./cli/llm.js"),
|
|
515
|
+
]);
|
|
236
516
|
console.log(`
|
|
237
517
|
JishuShell — OpenClaw 实例管理面板
|
|
238
518
|
|
|
239
519
|
Usage:
|
|
240
|
-
jishushell
|
|
520
|
+
jishushell serve [--port N] 前台启动面板服务器(默认端口 8090)
|
|
241
521
|
jishushell <command> [options]
|
|
242
522
|
|
|
243
523
|
Commands:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
pairing approve <ch> <code> 审批飞书/Lark 配对码(在容器内执行)
|
|
259
|
-
help 显示此帮助信息
|
|
260
|
-
|
|
261
|
-
Server options:
|
|
262
|
-
--port <port> 监听端口(默认 8090 或 panel.json 中的值)
|
|
263
|
-
--host <host> 监听地址(默认 0.0.0.0)
|
|
264
|
-
|
|
265
|
-
Install options:
|
|
266
|
-
--port <port> 面板端口(默认 8090)
|
|
267
|
-
--yes, -y 跳过所有交互提示(非交互模式)
|
|
268
|
-
--no-service 跳过 systemd/launchd 注册
|
|
269
|
-
--skip-docker 跳过 Docker 安装
|
|
270
|
-
--skip-nomad 跳过 Nomad 安装
|
|
271
|
-
--skip-openclaw 跳过 OpenClaw 安装
|
|
272
|
-
--force, -f 即使已配置也强制重新安装
|
|
273
|
-
--dry-run, -n 预演,不实际执行任何变更
|
|
524
|
+
${panelBrief()}
|
|
525
|
+
${doctorBrief()}
|
|
526
|
+
${openclawBrief()}
|
|
527
|
+
${jobBrief()}
|
|
528
|
+
${appBrief()}
|
|
529
|
+
${llmBrief()}
|
|
530
|
+
backup <id> [--with-sessions] 备份实例
|
|
531
|
+
restore <id> <--latest|--pick|file> 还原实例
|
|
532
|
+
export <id> 导出实例为归档包
|
|
533
|
+
import <file> [--id <id>] 导入归档包为新实例
|
|
534
|
+
clone <src-id> <new-id> 克隆实例
|
|
535
|
+
backups list [id] 列出备份文件
|
|
536
|
+
auto-backup <id> enable|disable|status 管理定时自动备份
|
|
537
|
+
help 显示此帮助
|
|
274
538
|
|
|
275
|
-
|
|
276
|
-
jishushell status # 查看当前所有组件状态
|
|
277
|
-
jishushell start # 后台启动面板
|
|
278
|
-
jishushell stop --jobs # 停止面板及所有实例 Job
|
|
279
|
-
jishushell restart # 重启面板
|
|
280
|
-
jishushell doctor --fix # 检查并自动修复
|
|
281
|
-
jishushell reset --yes # 非交互式重置(CI 场景)
|
|
539
|
+
运行 'jishushell <command> help' 查看详细用法。
|
|
282
540
|
`);
|
|
283
541
|
process.exit(0);
|
|
284
|
-
// ── unknown command ────────────────────────────────────────────────────────
|
|
285
542
|
}
|
|
286
543
|
else {
|
|
287
544
|
console.error(`Unknown command: ${subcommand}\nRun 'jishushell help' for usage.`);
|