jishushell 0.4.10 → 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/INSTALL-NOTICE +10 -12
- 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} +343 -14
- 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 +67 -326
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +11 -4
- 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/routes/apps.d.ts +3 -0
- package/dist/routes/apps.js +99 -0
- package/dist/routes/apps.js.map +1 -0
- package/dist/routes/instances.js +12 -6
- 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 +29 -2
- 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 +40 -4
- 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/instance-manager.d.ts +51 -3
- package/dist/services/instance-manager.js +233 -30
- 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 +192 -29
- 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 +20 -7
- package/dist/services/process-manager.js.map +1 -1
- package/dist/services/setup-manager.js +316 -31
- package/dist/services/setup-manager.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/install/jishu-install.sh +279 -37
- package/install/post-install.sh +64 -5
- package/package.json +6 -2
- 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-CUoEZOWR.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/{providers-lBSOjUWy.js → providers-V-vwrExZ.js} +1 -1
- package/public/index.html +2 -2
- package/dist/doctor.js.map +0 -1
- package/install/jishu-install-china.sh +0 -3092
- package/public/assets/Dashboard-DhsrzJ4F.js +0 -1
- package/public/assets/InitPassword-BjubiVdd.js +0 -1
- package/public/assets/InstanceDetail-DMcywsof.js +0 -17
- package/public/assets/NewInstance-Bk0G4EiJ.js +0 -1
- package/public/assets/Settings-D5tHL_h5.js +0 -1
- package/public/assets/Setup-4t6E3Rut.js +0 -1
- package/public/assets/index-BJ47MWpF.css +0 -1
- package/public/assets/index-DbX85irc.js +0 -16
package/INSTALL-NOTICE
CHANGED
|
@@ -32,16 +32,14 @@ software bundled with or installed by this product.
|
|
|
32
32
|
https://github.com/abiosoft/colima/blob/main/LICENSE
|
|
33
33
|
Author : Abiola Ibrahim
|
|
34
34
|
|
|
35
|
-
Nomad v1.
|
|
36
|
-
URL : https://github.com/hashicorp/nomad
|
|
37
|
-
License :
|
|
38
|
-
https://github.com/hashicorp/nomad/blob/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
commercial use. For licensing details and FAQ, see:
|
|
46
|
-
https://www.hashicorp.com/license-faq
|
|
35
|
+
Nomad v1.6.5 (last MPL 2.0 release in the 1.6.x line)
|
|
36
|
+
URL : https://github.com/hashicorp/nomad/tree/v1.6.5
|
|
37
|
+
License : Mozilla Public License 2.0
|
|
38
|
+
https://github.com/hashicorp/nomad/blob/v1.6.5/LICENSE
|
|
39
|
+
Author : HashiCorp, Inc.
|
|
40
|
+
|
|
41
|
+
JishuShell pins Nomad to v1.6.5, the final release in the 1.6.x series
|
|
42
|
+
distributed under MPL 2.0. Nomad 1.6.6 and later (including 1.7.0+) are
|
|
43
|
+
licensed under the Business Source License 1.1 (BSL 1.1) and are not
|
|
44
|
+
used by JishuShell.
|
|
47
45
|
|
package/dist/cli/app.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
// ── ANSI colour helpers ───────────────────────────────────────────────────
|
|
3
|
+
const isTTY = process.stdout.isTTY ?? false;
|
|
4
|
+
const c = {
|
|
5
|
+
bold: (s) => isTTY ? `\x1b[1m${s}\x1b[0m` : s,
|
|
6
|
+
green: (s) => isTTY ? `\x1b[32m${s}\x1b[0m` : s,
|
|
7
|
+
red: (s) => isTTY ? `\x1b[31m${s}\x1b[0m` : s,
|
|
8
|
+
cyan: (s) => isTTY ? `\x1b[36m${s}\x1b[0m` : s,
|
|
9
|
+
dim: (s) => isTTY ? `\x1b[2m${s}\x1b[0m` : s,
|
|
10
|
+
};
|
|
11
|
+
function log(msg) { process.stdout.write(msg + "\n"); }
|
|
12
|
+
// ── Command implementations ───────────────────────────────────────────────
|
|
13
|
+
async function cmdList(args) {
|
|
14
|
+
const { listApps } = await import("../services/app-manager.js");
|
|
15
|
+
const apps = listApps();
|
|
16
|
+
if (args.includes("--json")) {
|
|
17
|
+
console.log(JSON.stringify(apps.map(a => ({
|
|
18
|
+
id: a.spec.id, name: a.spec.name, version: a.spec.version,
|
|
19
|
+
task_count: a.spec.tasks.length, installed_at: a.manifest.installed_at,
|
|
20
|
+
})), null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
log("");
|
|
24
|
+
log(c.bold(" Installed Apps"));
|
|
25
|
+
log(c.dim(" ─────────────────────────────────────────────────────"));
|
|
26
|
+
if (apps.length === 0) {
|
|
27
|
+
log(c.dim(" (no apps installed)"));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
for (const a of apps) {
|
|
31
|
+
log(` ${c.cyan(a.spec.id.padEnd(30))} ${(a.spec.version || "").padEnd(10)} ${c.dim(a.spec.name || "")}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
log("");
|
|
35
|
+
}
|
|
36
|
+
async function cmdShow(id) {
|
|
37
|
+
const { getApp } = await import("../services/app-manager.js");
|
|
38
|
+
const app = getApp(id);
|
|
39
|
+
if (!app) {
|
|
40
|
+
log(c.red(` ✗ App "${id}" not found.`));
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(JSON.stringify(app, null, 2));
|
|
45
|
+
}
|
|
46
|
+
async function cmdInstall(yamlPath) {
|
|
47
|
+
if (!existsSync(yamlPath)) {
|
|
48
|
+
log(c.red(` ✗ File not found: ${yamlPath}`));
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const yamlText = readFileSync(yamlPath, "utf-8");
|
|
53
|
+
const { installApp } = await import("../services/app-manager.js");
|
|
54
|
+
const result = await installApp(yamlText);
|
|
55
|
+
log(c.green(` ✓ Installed app: ${result.spec.id} (${result.spec.name || ""} v${result.spec.version || "?"})`));
|
|
56
|
+
}
|
|
57
|
+
async function cmdUninstall(id) {
|
|
58
|
+
const { uninstallApp, getApp } = await import("../services/app-manager.js");
|
|
59
|
+
if (!getApp(id)) {
|
|
60
|
+
log(c.red(` ✗ App "${id}" not found.`));
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
uninstallApp(id);
|
|
65
|
+
log(c.green(` ✓ Uninstalled app: ${id}`));
|
|
66
|
+
}
|
|
67
|
+
async function cmdCreateInstance(appId, instanceId, name) {
|
|
68
|
+
const { getApp, resolveRequires } = await import("../services/app-manager.js");
|
|
69
|
+
const { createInstance } = await import("../services/instance-manager.js");
|
|
70
|
+
const appData = getApp(appId);
|
|
71
|
+
if (!appData) {
|
|
72
|
+
log(c.red(` ✗ App "${appId}" not found.`));
|
|
73
|
+
process.exitCode = 1;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let resolvedEnv = {};
|
|
77
|
+
try {
|
|
78
|
+
resolvedEnv = resolveRequires(appData.spec);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
log(c.red(` ✗ ${e.message}`));
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const specWithEnv = Object.keys(resolvedEnv).length > 0
|
|
86
|
+
? {
|
|
87
|
+
...appData.spec,
|
|
88
|
+
tasks: appData.spec.tasks.map((t) => t.role === "service" ? { ...t, env: { ...t.env, ...resolvedEnv } } : t),
|
|
89
|
+
}
|
|
90
|
+
: appData.spec;
|
|
91
|
+
await createInstance(instanceId, name, "", undefined, undefined, specWithEnv);
|
|
92
|
+
log(c.green(` ✓ Created instance "${instanceId}" from app "${appId}"`));
|
|
93
|
+
}
|
|
94
|
+
// ── Entry point ───────────────────────────────────────────────────────────
|
|
95
|
+
export async function run(rest) {
|
|
96
|
+
const appCmd = rest[0];
|
|
97
|
+
try {
|
|
98
|
+
if (appCmd === "list") {
|
|
99
|
+
await cmdList(rest.slice(1));
|
|
100
|
+
}
|
|
101
|
+
else if (appCmd === "show" && rest[1]) {
|
|
102
|
+
await cmdShow(rest[1]);
|
|
103
|
+
}
|
|
104
|
+
else if (appCmd === "install" && rest[1]) {
|
|
105
|
+
await cmdInstall(rest[1]);
|
|
106
|
+
}
|
|
107
|
+
else if (appCmd === "uninstall" && rest[1]) {
|
|
108
|
+
await cmdUninstall(rest[1]);
|
|
109
|
+
}
|
|
110
|
+
else if (appCmd === "create-instance" && rest[1]) {
|
|
111
|
+
const appId = rest[1];
|
|
112
|
+
const instanceId = rest[2];
|
|
113
|
+
const name = rest[3] || instanceId;
|
|
114
|
+
if (!instanceId) {
|
|
115
|
+
console.error("Usage: jishushell app create-instance <app-id> <instance-id> [name]");
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
await cmdCreateInstance(appId, instanceId, name);
|
|
119
|
+
}
|
|
120
|
+
else if (appCmd === "help" || appCmd === "--help" || appCmd === "-h") {
|
|
121
|
+
printHelp();
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
printHelp();
|
|
125
|
+
if (appCmd)
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
log(c.red(` ✗ ${err.message}`));
|
|
131
|
+
process.exitCode = 1;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function brief() {
|
|
135
|
+
return " app <install|list|show|uninstall|create-instance> App Spec 管理";
|
|
136
|
+
}
|
|
137
|
+
export function printHelp() {
|
|
138
|
+
console.log(`
|
|
139
|
+
Usage: jishushell app <command> [options]
|
|
140
|
+
|
|
141
|
+
Commands:
|
|
142
|
+
list [--json] 列出已安装的 App
|
|
143
|
+
show <app-id> 查看 App 详情(JSON)
|
|
144
|
+
install <yaml-file> 从 YAML 文件安装 App
|
|
145
|
+
uninstall <app-id> 卸载 App
|
|
146
|
+
create-instance <app-id> <inst-id> [name] 从 App 创建实例
|
|
147
|
+
help 显示此帮助
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
jishushell app install ./my-app.yaml
|
|
151
|
+
jishushell app create-instance my-app inst1 "My Instance"
|
|
152
|
+
jishushell app list
|
|
153
|
+
jishushell app show my-app
|
|
154
|
+
`);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/cli/app.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* jishushell doctor --fix 检查并尝试自动修复
|
|
7
7
|
*
|
|
8
8
|
* Nomad 检查依据 install/jishu-install.sh 及 src/services/setup-manager.ts:
|
|
9
|
-
* - 二进制路径: ~/.jishushell/bin/nomad (v1.
|
|
9
|
+
* - 二进制路径: ~/.jishushell/bin/nomad (v1.6.5)
|
|
10
10
|
* - 配置文件: ~/.jishushell/nomad/nomad.hcl
|
|
11
11
|
* - 数据目录: ~/.jishushell/nomad/data/
|
|
12
12
|
* - Alloc 目录: ~/.jishushell/nomad/data/alloc/
|
|
@@ -38,9 +38,14 @@ export interface DoctorCheck {
|
|
|
38
38
|
}
|
|
39
39
|
export interface DoctorOptions {
|
|
40
40
|
fix?: boolean;
|
|
41
|
+
ai?: boolean;
|
|
41
42
|
}
|
|
42
43
|
export declare const ALL_CHECKS: DoctorCheck[];
|
|
43
44
|
/**
|
|
44
45
|
* 运行所有诊断检查,返回是否全部 error 级别检查通过。
|
|
45
46
|
*/
|
|
46
47
|
export declare function runDoctorChecks(opts?: DoctorOptions): Promise<boolean>;
|
|
48
|
+
export declare function brief(): string;
|
|
49
|
+
export declare function printHelp(): void;
|
|
50
|
+
export declare function runAiDiagnosis(): Promise<void>;
|
|
51
|
+
export declare function run(rest: string[]): Promise<void>;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* jishushell doctor --fix 检查并尝试自动修复
|
|
7
7
|
*
|
|
8
8
|
* Nomad 检查依据 install/jishu-install.sh 及 src/services/setup-manager.ts:
|
|
9
|
-
* - 二进制路径: ~/.jishushell/bin/nomad (v1.
|
|
9
|
+
* - 二进制路径: ~/.jishushell/bin/nomad (v1.6.5)
|
|
10
10
|
* - 配置文件: ~/.jishushell/nomad/nomad.hcl
|
|
11
11
|
* - 数据目录: ~/.jishushell/nomad/data/
|
|
12
12
|
* - Alloc 目录: ~/.jishushell/nomad/data/alloc/
|
|
@@ -21,9 +21,10 @@ import { existsSync, readFileSync, unlinkSync, } from "fs";
|
|
|
21
21
|
import * as http from "http";
|
|
22
22
|
import { homedir } from "os";
|
|
23
23
|
import { join } from "path";
|
|
24
|
-
import { AUTH_FILE, INSTANCES_DIR, JISHUSHELL_HOME, PANEL_CONFIG_FILE, ensureDirs, getNomadToken, getPanelConfig, } from "
|
|
25
|
-
import { loadNomadToken } from "
|
|
26
|
-
import { ensureDirContainer, writeConfigFile, writeSecretFile } from "
|
|
24
|
+
import { AUTH_FILE, DEFAULT_OPENCLAW_DOCKER_IMAGE, INSTANCES_DIR, JISHUSHELL_HOME, PANEL_CONFIG_FILE, ensureDirs, getNomadToken, getPanelConfig, } from "../config.js";
|
|
25
|
+
import { loadNomadToken } from "../services/setup-manager.js";
|
|
26
|
+
import { ensureDirContainer, writeConfigFile, writeSecretFile } from "../utils/fs.js";
|
|
27
|
+
import { LOG_FILE as PANEL_LOG_FILE } from "../services/panel-manager.js";
|
|
27
28
|
// ── ANSI 颜色(非 TTY 时自动禁用)──────────────────────────────────────────
|
|
28
29
|
const isTTY = process.stdout.isTTY ?? false;
|
|
29
30
|
const c = {
|
|
@@ -103,7 +104,7 @@ const NOMAD_DATA_DIR = join(NOMAD_DIR, "data");
|
|
|
103
104
|
const NOMAD_ALLOC_DIR = join(NOMAD_DIR, "data", "alloc");
|
|
104
105
|
const NOMAD_LOG = join(NOMAD_DIR, "nomad.log");
|
|
105
106
|
const NOMAD_SERVICE = "/etc/systemd/system/nomad.service";
|
|
106
|
-
const NOMAD_MIN_VER = "1.
|
|
107
|
+
const NOMAD_MIN_VER = "1.6.5";
|
|
107
108
|
function nomadEnabled() {
|
|
108
109
|
return getPanelConfig().service_manager === "nomad";
|
|
109
110
|
}
|
|
@@ -488,7 +489,7 @@ const checkSystemdEnabled = {
|
|
|
488
489
|
* 检查 Nomad 二进制(~/.jishushell/bin/nomad)
|
|
489
490
|
* 依据: install/jishu-install.sh _install_nomad_binary()
|
|
490
491
|
* 安装路径: JISHUSHELL_BIN_DIR=${JISHUSHELL_HOME}/bin/nomad
|
|
491
|
-
* 要求版本: NOMAD_VERSION=1.
|
|
492
|
+
* 要求版本: NOMAD_VERSION=1.6.5
|
|
492
493
|
*/
|
|
493
494
|
const checkNomadBin = {
|
|
494
495
|
id: "nomad-bin", label: "Nomad 二进制", category: "Nomad", severity: "error",
|
|
@@ -780,25 +781,43 @@ const checkNomadJobs = {
|
|
|
780
781
|
// 类别: AI 组件
|
|
781
782
|
// ════════════════════════════════════════════════════════════════════════════
|
|
782
783
|
const checkOpenclawPkg = {
|
|
783
|
-
id: "openclaw-pkg", label: "OpenClaw
|
|
784
|
+
id: "openclaw-pkg", label: "OpenClaw 运行时", category: "AI 组件", severity: "warning",
|
|
784
785
|
async check() {
|
|
785
786
|
const oclawBin = join(JISHUSHELL_HOME, "packages/openclaw/bin/openclaw");
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
|
|
787
|
+
const oclawPkgJson = join(JISHUSHELL_HOME, "packages/openclaw/lib/node_modules/openclaw/package.json");
|
|
788
|
+
const localBinOk = existsSync(oclawBin);
|
|
789
|
+
// Docker 镜像也是合法的运行时(与 setup-manager 逻辑一致)
|
|
790
|
+
const imageTag = getPanelConfig().openclaw_image || DEFAULT_OPENCLAW_DOCKER_IMAGE;
|
|
791
|
+
let dockerImageOk = false;
|
|
789
792
|
try {
|
|
790
|
-
|
|
791
|
-
|
|
793
|
+
execFileSync("docker", ["image", "inspect", imageTag], { stdio: "ignore", timeout: 5000 });
|
|
794
|
+
dockerImageOk = true;
|
|
792
795
|
}
|
|
793
796
|
catch {
|
|
794
|
-
|
|
797
|
+
try {
|
|
798
|
+
execFileSync("sudo", ["-n", "docker", "image", "inspect", imageTag], { stdio: "ignore", timeout: 5000 });
|
|
799
|
+
dockerImageOk = true;
|
|
800
|
+
}
|
|
801
|
+
catch { }
|
|
802
|
+
}
|
|
803
|
+
if (localBinOk) {
|
|
804
|
+
let ver = "已安装";
|
|
805
|
+
try {
|
|
806
|
+
ver = `v${JSON.parse(readFileSync(oclawPkgJson, "utf-8")).version}`;
|
|
807
|
+
}
|
|
808
|
+
catch { }
|
|
809
|
+
return { ok: true, detail: `本地包 ${ver}${dockerImageOk ? ",Docker 镜像已就绪" : ""}` };
|
|
810
|
+
}
|
|
811
|
+
if (dockerImageOk) {
|
|
812
|
+
return { ok: true, detail: `Docker 镜像已就绪(${imageTag})` };
|
|
795
813
|
}
|
|
814
|
+
return { ok: false, detail: "未安装(本地包与 Docker 镜像均缺失)", hint: "jishushell install", fixable: false };
|
|
796
815
|
},
|
|
797
816
|
};
|
|
798
817
|
const checkOpenclawImage = {
|
|
799
818
|
id: "openclaw-image", label: "OpenClaw Docker 镜像", category: "AI 组件", severity: "warning",
|
|
800
819
|
async check() {
|
|
801
|
-
const imageTag = getPanelConfig().openclaw_image ||
|
|
820
|
+
const imageTag = getPanelConfig().openclaw_image || DEFAULT_OPENCLAW_DOCKER_IMAGE;
|
|
802
821
|
let dockerAccessible = false;
|
|
803
822
|
try {
|
|
804
823
|
execFileSync("docker", ["info"], { stdio: "ignore", timeout: 4000 });
|
|
@@ -967,4 +986,314 @@ export async function runDoctorChecks(opts = {}) {
|
|
|
967
986
|
log("");
|
|
968
987
|
return errors.length === 0;
|
|
969
988
|
}
|
|
989
|
+
// ── CLI entry ──────────────────────────────────────────────────────────────
|
|
990
|
+
export function brief() {
|
|
991
|
+
return " doctor [--fix] [--ai] 环境诊断、自动修复与 AI 分析";
|
|
992
|
+
}
|
|
993
|
+
export function printHelp() {
|
|
994
|
+
console.log(`
|
|
995
|
+
Usage: jishushell doctor [options]
|
|
996
|
+
|
|
997
|
+
Options:
|
|
998
|
+
--fix 尝试自动修复发现的问题
|
|
999
|
+
--ai 将诊断结果发给 LLM 进行智能分析(需配置 LLM provider)
|
|
1000
|
+
help 显示此帮助
|
|
1001
|
+
`);
|
|
1002
|
+
}
|
|
1003
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
1004
|
+
// AI 诊断
|
|
1005
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
1006
|
+
/** Strip ANSI escape codes from a string. */
|
|
1007
|
+
function stripAnsi(s) {
|
|
1008
|
+
// eslint-disable-next-line no-control-regex
|
|
1009
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1010
|
+
}
|
|
1011
|
+
/** Silently run all checks and return a plain-text report (no ANSI). */
|
|
1012
|
+
async function collectDoctorReport() {
|
|
1013
|
+
const categories = [...new Set(ALL_CHECKS.map((ch) => ch.category))];
|
|
1014
|
+
const lines = ["=== JishuShell Doctor Report ===", ""];
|
|
1015
|
+
for (const category of categories) {
|
|
1016
|
+
lines.push(`[${category}]`);
|
|
1017
|
+
for (const check of ALL_CHECKS.filter((ch) => ch.category === category)) {
|
|
1018
|
+
const result = await check.check();
|
|
1019
|
+
const status = result.ok ? "OK" : check.severity === "error" ? "ERROR" : "WARN";
|
|
1020
|
+
lines.push(` ${status} ${check.label}: ${result.detail}`);
|
|
1021
|
+
if (!result.ok && result.hint)
|
|
1022
|
+
lines.push(` hint: ${result.hint}`);
|
|
1023
|
+
}
|
|
1024
|
+
lines.push("");
|
|
1025
|
+
}
|
|
1026
|
+
return lines.join("\n");
|
|
1027
|
+
}
|
|
1028
|
+
/** Read the tail of a log file (plain text, no ANSI). */
|
|
1029
|
+
function tailFile(path, lines = 80) {
|
|
1030
|
+
if (!existsSync(path))
|
|
1031
|
+
return "";
|
|
1032
|
+
try {
|
|
1033
|
+
const content = readFileSync(path, "utf-8");
|
|
1034
|
+
const tail = content.split("\n").slice(-lines).join("\n");
|
|
1035
|
+
return stripAnsi(tail);
|
|
1036
|
+
}
|
|
1037
|
+
catch {
|
|
1038
|
+
return "";
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/** Collect journalctl output for a service (best-effort). */
|
|
1042
|
+
function journalTail(service, lines = 60) {
|
|
1043
|
+
try {
|
|
1044
|
+
return execSync(`journalctl -u ${service} -n ${lines} --no-pager 2>/dev/null`, {
|
|
1045
|
+
encoding: "utf-8", timeout: 5000,
|
|
1046
|
+
}).trim();
|
|
1047
|
+
}
|
|
1048
|
+
catch {
|
|
1049
|
+
return "";
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
/** Collect instance status + recent logs for all local instances. */
|
|
1053
|
+
async function collectInstanceContext() {
|
|
1054
|
+
const { listInstanceIds, readInstanceMeta, getSvc } = await import("../services/job-manager.js");
|
|
1055
|
+
loadNomadToken();
|
|
1056
|
+
const ids = listInstanceIds();
|
|
1057
|
+
if (ids.length === 0)
|
|
1058
|
+
return "No instances.";
|
|
1059
|
+
const sections = [];
|
|
1060
|
+
let svc = null;
|
|
1061
|
+
try {
|
|
1062
|
+
svc = await getSvc();
|
|
1063
|
+
}
|
|
1064
|
+
catch { /* Nomad not available */ }
|
|
1065
|
+
for (const id of ids) {
|
|
1066
|
+
const meta = readInstanceMeta(id);
|
|
1067
|
+
const name = meta?.name || id;
|
|
1068
|
+
const header = `--- Instance: ${name} (${id}) ---`;
|
|
1069
|
+
const lines = [header];
|
|
1070
|
+
// Status
|
|
1071
|
+
if (svc) {
|
|
1072
|
+
try {
|
|
1073
|
+
const st = await svc.getStatus(id);
|
|
1074
|
+
lines.push(`status: ${st.status}`);
|
|
1075
|
+
}
|
|
1076
|
+
catch (e) {
|
|
1077
|
+
lines.push(`status: unavailable (${e.message})`);
|
|
1078
|
+
}
|
|
1079
|
+
// Logs (stderr first, then stdout)
|
|
1080
|
+
for (const logType of ["stderr", "stdout"]) {
|
|
1081
|
+
try {
|
|
1082
|
+
const logLines = await svc.getLogs(id, 60, logType);
|
|
1083
|
+
if (logLines.length > 0) {
|
|
1084
|
+
lines.push(`\n[${logType} tail]`);
|
|
1085
|
+
lines.push(...logLines.slice(-60));
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
catch { /* skip */ }
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
lines.push("status: Nomad unavailable");
|
|
1093
|
+
}
|
|
1094
|
+
sections.push(lines.join("\n"));
|
|
1095
|
+
}
|
|
1096
|
+
return sections.join("\n\n");
|
|
1097
|
+
}
|
|
1098
|
+
/** Build the full context string to send to the LLM. */
|
|
1099
|
+
async function buildAiContext() {
|
|
1100
|
+
const parts = [];
|
|
1101
|
+
// 1. Doctor checks
|
|
1102
|
+
log(c.dim(" ✦ 收集诊断检查结果…"));
|
|
1103
|
+
parts.push(await collectDoctorReport());
|
|
1104
|
+
// 2. Panel log
|
|
1105
|
+
log(c.dim(" ✦ 收集面板日志…"));
|
|
1106
|
+
const panelLog = tailFile(PANEL_LOG_FILE, 80);
|
|
1107
|
+
if (panelLog) {
|
|
1108
|
+
parts.push(`=== Panel Log (${PANEL_LOG_FILE}) ===\n${panelLog}`);
|
|
1109
|
+
}
|
|
1110
|
+
// 3. Panel journalctl
|
|
1111
|
+
const jLogs = journalTail("jishushell", 60);
|
|
1112
|
+
if (jLogs) {
|
|
1113
|
+
parts.push(`=== journalctl jishushell (last 60 lines) ===\n${jLogs}`);
|
|
1114
|
+
}
|
|
1115
|
+
// 4. Nomad log
|
|
1116
|
+
const nomadLog = join(JISHUSHELL_HOME, "nomad", "nomad.log");
|
|
1117
|
+
const nLog = tailFile(nomadLog, 60);
|
|
1118
|
+
if (nLog) {
|
|
1119
|
+
parts.push(`=== Nomad Log (${nomadLog}) ===\n${nLog}`);
|
|
1120
|
+
}
|
|
1121
|
+
// 5. Instance context
|
|
1122
|
+
log(c.dim(" ✦ 收集实例状态与日志…"));
|
|
1123
|
+
try {
|
|
1124
|
+
const instCtx = await collectInstanceContext();
|
|
1125
|
+
parts.push(`=== Instance Status & Logs ===\n${instCtx}`);
|
|
1126
|
+
}
|
|
1127
|
+
catch (e) {
|
|
1128
|
+
parts.push(`=== Instance Status & Logs ===\nError collecting: ${e.message}`);
|
|
1129
|
+
}
|
|
1130
|
+
return parts.join("\n\n");
|
|
1131
|
+
}
|
|
1132
|
+
/** Validate that a usable LLM provider exists and return it (or null). */
|
|
1133
|
+
async function getDefaultProvider() {
|
|
1134
|
+
const { decryptApiKey } = await import("../services/llm-proxy/encryption.js");
|
|
1135
|
+
const config = getPanelConfig();
|
|
1136
|
+
const providers = Array.isArray(config.providers) ? config.providers : [];
|
|
1137
|
+
const dp = config.default_provider;
|
|
1138
|
+
if (providers.length === 0 && dp && !dp.skipped) {
|
|
1139
|
+
return {
|
|
1140
|
+
id: dp.providerId || "default",
|
|
1141
|
+
baseUrl: dp.baseUrl || "",
|
|
1142
|
+
api: dp.api || "openai",
|
|
1143
|
+
apiKey: dp.apiKey ? (() => { try {
|
|
1144
|
+
return decryptApiKey(dp.apiKey);
|
|
1145
|
+
}
|
|
1146
|
+
catch {
|
|
1147
|
+
return "";
|
|
1148
|
+
} })() : undefined,
|
|
1149
|
+
authHeader: dp.authHeader,
|
|
1150
|
+
models: dp.models,
|
|
1151
|
+
selectedModelId: dp.selectedModelId,
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
const provider = providers.find((p) => p.isDefault) || providers[0];
|
|
1155
|
+
if (!provider)
|
|
1156
|
+
return null;
|
|
1157
|
+
return {
|
|
1158
|
+
id: provider.id,
|
|
1159
|
+
baseUrl: provider.baseUrl,
|
|
1160
|
+
api: provider.api || "openai",
|
|
1161
|
+
apiKey: provider.apiKey ? (() => { try {
|
|
1162
|
+
return decryptApiKey(provider.apiKey);
|
|
1163
|
+
}
|
|
1164
|
+
catch {
|
|
1165
|
+
return "";
|
|
1166
|
+
} })() : undefined,
|
|
1167
|
+
authHeader: provider.authHeader,
|
|
1168
|
+
models: provider.models,
|
|
1169
|
+
selectedModelId: provider.selectedModelId,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
const AI_SYSTEM_PROMPT = `你是 JishuShell 的智能运维助手。
|
|
1173
|
+
JishuShell 是一个用于管理 OpenClaw(AI Agent 运行时)实例的面板,运行在 Linux/ARM 设备上,依赖 Docker 和 Nomad 进行容器编排。
|
|
1174
|
+
|
|
1175
|
+
用户将提供系统诊断报告、日志和实例状态。
|
|
1176
|
+
你的任务:
|
|
1177
|
+
1. 识别所有异常项(ERROR/WARN)并解释可能的根本原因
|
|
1178
|
+
2. 按优先级给出具体的修复建议(优先 jishushell 命令,其次 shell 命令)
|
|
1179
|
+
3. 如果日志中有明显错误,指出具体错误行和含义
|
|
1180
|
+
4. 用中文回复,保持简洁,使用 Markdown 格式`;
|
|
1181
|
+
export async function runAiDiagnosis() {
|
|
1182
|
+
log("");
|
|
1183
|
+
log(c.bold(c.cyan(" JishuShell Doctor — AI 模式")));
|
|
1184
|
+
log(c.dim(" ────────────────────────────────────────────────"));
|
|
1185
|
+
log("");
|
|
1186
|
+
// 1. Check that a provider exists
|
|
1187
|
+
let provider;
|
|
1188
|
+
try {
|
|
1189
|
+
provider = await getDefaultProvider();
|
|
1190
|
+
}
|
|
1191
|
+
catch (e) {
|
|
1192
|
+
log(c.red(" ✗ 无法加载 LLM 配置: " + e.message));
|
|
1193
|
+
log(c.dim(" 请先运行: jishushell llm add <id> --url <url> --key <key>"));
|
|
1194
|
+
log("");
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
if (!provider) {
|
|
1198
|
+
log(c.red(" ✗ 未配置任何 LLM provider,无法使用 --ai 模式"));
|
|
1199
|
+
log("");
|
|
1200
|
+
log(c.dim(" 请先配置供应商,然后重新运行:"));
|
|
1201
|
+
log(c.dim(" jishushell llm add <id> --url <url> --key <key>"));
|
|
1202
|
+
log(c.dim(" 或在 Web 界面「设置 → 模型供应商」中添加"));
|
|
1203
|
+
log("");
|
|
1204
|
+
process.exit(1);
|
|
1205
|
+
}
|
|
1206
|
+
const model = provider.selectedModelId
|
|
1207
|
+
|| (Array.isArray(provider.models) && provider.models[0]?.id)
|
|
1208
|
+
|| undefined;
|
|
1209
|
+
if (!model) {
|
|
1210
|
+
log(c.yellow(` ! Provider '${provider.id}' 未配置默认模型`));
|
|
1211
|
+
log(c.dim(` 请运行: jishushell llm update ${provider.id} --model <model-id>`));
|
|
1212
|
+
log("");
|
|
1213
|
+
process.exit(1);
|
|
1214
|
+
}
|
|
1215
|
+
log(c.dim(` 使用 provider: ${provider.id} 模型: ${model}`));
|
|
1216
|
+
log("");
|
|
1217
|
+
// 2. Collect context
|
|
1218
|
+
log(c.bold(" ▸ 收集系统信息…"));
|
|
1219
|
+
const context = await buildAiContext();
|
|
1220
|
+
log("");
|
|
1221
|
+
// 3. Call LLM
|
|
1222
|
+
log(c.bold(" ▸ 发送给 AI 分析(请稍候…)"));
|
|
1223
|
+
log("");
|
|
1224
|
+
const apiKey = provider.apiKey || "";
|
|
1225
|
+
const baseUrl = provider.baseUrl.replace(/\/$/, "");
|
|
1226
|
+
const authHeader = (typeof provider.authHeader === "string" && provider.authHeader)
|
|
1227
|
+
? provider.authHeader : "Authorization";
|
|
1228
|
+
const userMessage = `以下是 JishuShell 的系统诊断信息,请分析问题并给出修复建议:\n\n${context}`;
|
|
1229
|
+
const body = {
|
|
1230
|
+
model,
|
|
1231
|
+
messages: [
|
|
1232
|
+
{ role: "system", content: AI_SYSTEM_PROMPT },
|
|
1233
|
+
{ role: "user", content: userMessage },
|
|
1234
|
+
],
|
|
1235
|
+
stream: false,
|
|
1236
|
+
};
|
|
1237
|
+
const endpoints = [`${baseUrl}/v1/chat/completions`, `${baseUrl}/chat/completions`];
|
|
1238
|
+
let lastErr = "";
|
|
1239
|
+
for (const url of endpoints) {
|
|
1240
|
+
try {
|
|
1241
|
+
const resp = await fetch(url, {
|
|
1242
|
+
method: "POST",
|
|
1243
|
+
headers: {
|
|
1244
|
+
"Content-Type": "application/json",
|
|
1245
|
+
[authHeader]: `Bearer ${apiKey}`,
|
|
1246
|
+
},
|
|
1247
|
+
body: JSON.stringify(body),
|
|
1248
|
+
signal: AbortSignal.timeout(120_000),
|
|
1249
|
+
});
|
|
1250
|
+
if (resp.ok) {
|
|
1251
|
+
const data = await resp.json();
|
|
1252
|
+
const content = data?.choices?.[0]?.message?.content ?? "";
|
|
1253
|
+
const usage = data?.usage;
|
|
1254
|
+
log(c.bold(c.cyan(" ═══════════════════════════════════════════════")));
|
|
1255
|
+
log(c.bold(" AI 诊断结果"));
|
|
1256
|
+
log(c.bold(c.cyan(" ═══════════════════════════════════════════════")));
|
|
1257
|
+
log("");
|
|
1258
|
+
// Print result with light indentation
|
|
1259
|
+
for (const line of content.split("\n")) {
|
|
1260
|
+
log(" " + line);
|
|
1261
|
+
}
|
|
1262
|
+
log("");
|
|
1263
|
+
log(c.bold(c.cyan(" ═══════════════════════════════════════════════")));
|
|
1264
|
+
if (usage) {
|
|
1265
|
+
log(c.dim(` [${provider.id} / ${model} tokens: ${usage.total_tokens ?? "?"}]`));
|
|
1266
|
+
}
|
|
1267
|
+
log("");
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
const text = await resp.text().catch(() => "");
|
|
1272
|
+
lastErr = `HTTP ${resp.status}: ${text.slice(0, 300)}`;
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
catch (e) {
|
|
1276
|
+
lastErr = e.message;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
log(c.red(` ✗ AI 请求失败 (${provider.id}): ${lastErr}`));
|
|
1280
|
+
log(c.dim(" 请检查 LLM provider 配置是否正确,或使用 --fix 尝试自动修复。"));
|
|
1281
|
+
log("");
|
|
1282
|
+
process.exit(1);
|
|
1283
|
+
}
|
|
1284
|
+
export async function run(rest) {
|
|
1285
|
+
if (rest.includes("help") || rest.includes("--help") || rest.includes("-h")) {
|
|
1286
|
+
printHelp();
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
if (rest.includes("--ai")) {
|
|
1290
|
+
// Run standard doctor first (to stdout), then AI analysis
|
|
1291
|
+
await runDoctorChecks({ fix: false });
|
|
1292
|
+
await runAiDiagnosis();
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const fix = rest.includes("--fix") || rest.includes("-f");
|
|
1296
|
+
const ok = await runDoctorChecks({ fix });
|
|
1297
|
+
process.exit(ok ? 0 : 1);
|
|
1298
|
+
}
|
|
970
1299
|
//# sourceMappingURL=doctor.js.map
|