openclaw-openviking-setup-helper 0.2.2 → 0.2.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/install.js +800 -800
- package/package.json +2 -2
package/install.js
CHANGED
|
@@ -1,800 +1,800 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* OpenClaw + OpenViking cross-platform installer
|
|
4
|
-
*
|
|
5
|
-
* One-liner (after npm publish; use package name + bin name):
|
|
6
|
-
* npx -p openclaw-openviking-setup-helper ov-install [ -y ] [ --zh ] [ --workdir PATH ]
|
|
7
|
-
* Or install globally then run:
|
|
8
|
-
* npm i -g openclaw-openviking-setup-helper
|
|
9
|
-
* ov-install
|
|
10
|
-
* openclaw-openviking-install
|
|
11
|
-
*
|
|
12
|
-
* Direct run:
|
|
13
|
-
* node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ]
|
|
14
|
-
* [ --openviking-version=V ] [ --repo=PATH ]
|
|
15
|
-
*
|
|
16
|
-
* Environment variables:
|
|
17
|
-
* REPO, BRANCH, OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
|
|
18
|
-
* OPENVIKING_VERSION Pip install openviking==VERSION (omit for latest)
|
|
19
|
-
* OPENVIKING_REPO Repo path: source install (pip -e) + local plugin (default: off)
|
|
20
|
-
* NPM_REGISTRY, PIP_INDEX_URL
|
|
21
|
-
* OPENVIKING_VLM_API_KEY, OPENVIKING_EMBEDDING_API_KEY, OPENVIKING_ARK_API_KEY
|
|
22
|
-
* OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES (Linux)
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { spawn } from "node:child_process";
|
|
26
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
27
|
-
import { existsSync, readdirSync } from "node:fs";
|
|
28
|
-
import { dirname, join } from "node:path";
|
|
29
|
-
import { createInterface } from "node:readline";
|
|
30
|
-
import { fileURLToPath } from "node:url";
|
|
31
|
-
|
|
32
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
-
|
|
34
|
-
const REPO = process.env.REPO || "
|
|
35
|
-
const BRANCH = process.env.BRANCH || "publish";
|
|
36
|
-
const GH_RAW = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
|
|
37
|
-
const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
|
|
38
|
-
const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
|
|
39
|
-
|
|
40
|
-
const IS_WIN = process.platform === "win32";
|
|
41
|
-
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
42
|
-
|
|
43
|
-
const DEFAULT_OPENCLAW_DIR = join(HOME, ".openclaw");
|
|
44
|
-
let OPENCLAW_DIR = DEFAULT_OPENCLAW_DIR;
|
|
45
|
-
let PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
|
|
46
|
-
|
|
47
|
-
const OPENVIKING_DIR = join(HOME, ".openviking");
|
|
48
|
-
|
|
49
|
-
const DEFAULT_SERVER_PORT = 1933;
|
|
50
|
-
const DEFAULT_AGFS_PORT = 1833;
|
|
51
|
-
const DEFAULT_VLM_MODEL = "doubao-seed-2-0-pro-260215";
|
|
52
|
-
const DEFAULT_EMBED_MODEL = "doubao-embedding-vision-251215";
|
|
53
|
-
|
|
54
|
-
const REQUIRED_PLUGIN_FILES = [
|
|
55
|
-
"examples/openclaw-memory-plugin/index.ts",
|
|
56
|
-
"examples/openclaw-memory-plugin/config.ts",
|
|
57
|
-
"examples/openclaw-memory-plugin/openclaw.plugin.json",
|
|
58
|
-
"examples/openclaw-memory-plugin/package.json",
|
|
59
|
-
"examples/openclaw-memory-plugin/package-lock.json",
|
|
60
|
-
"examples/openclaw-memory-plugin/.gitignore",
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
const OPTIONAL_PLUGIN_FILES = [
|
|
64
|
-
"examples/openclaw-memory-plugin/client.ts",
|
|
65
|
-
"examples/openclaw-memory-plugin/process-manager.ts",
|
|
66
|
-
"examples/openclaw-memory-plugin/memory-ranking.ts",
|
|
67
|
-
"examples/openclaw-memory-plugin/text-utils.ts",
|
|
68
|
-
];
|
|
69
|
-
|
|
70
|
-
let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
|
|
71
|
-
let langZh = false;
|
|
72
|
-
let openvikingVersion = process.env.OPENVIKING_VERSION || "";
|
|
73
|
-
let openvikingRepo = process.env.OPENVIKING_REPO || "";
|
|
74
|
-
let workdirExplicit = false;
|
|
75
|
-
|
|
76
|
-
let selectedMode = "local";
|
|
77
|
-
let selectedServerPort = DEFAULT_SERVER_PORT;
|
|
78
|
-
let remoteBaseUrl = "http://127.0.0.1:1933";
|
|
79
|
-
let remoteApiKey = "";
|
|
80
|
-
let remoteAgentId = "";
|
|
81
|
-
let openvikingPythonPath = "";
|
|
82
|
-
|
|
83
|
-
const argv = process.argv.slice(2);
|
|
84
|
-
for (let i = 0; i < argv.length; i++) {
|
|
85
|
-
const arg = argv[i];
|
|
86
|
-
if (arg === "-y" || arg === "--yes") {
|
|
87
|
-
installYes = true;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (arg === "--zh") {
|
|
91
|
-
langZh = true;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
if (arg === "--workdir") {
|
|
95
|
-
const workdir = argv[i + 1]?.trim();
|
|
96
|
-
if (!workdir) {
|
|
97
|
-
console.error("--workdir requires a path");
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
setOpenClawDir(workdir);
|
|
101
|
-
workdirExplicit = true;
|
|
102
|
-
i += 1;
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
if (arg.startsWith("--openviking-version=")) {
|
|
106
|
-
openvikingVersion = arg.slice("--openviking-version=".length).trim();
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
if (arg.startsWith("--repo=")) {
|
|
110
|
-
openvikingRepo = arg.slice("--repo=".length).trim();
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
if (arg === "-h" || arg === "--help") {
|
|
114
|
-
printHelp();
|
|
115
|
-
process.exit(0);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const OPENVIKING_PIP_SPEC = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
|
|
120
|
-
|
|
121
|
-
function setOpenClawDir(dir) {
|
|
122
|
-
OPENCLAW_DIR = dir;
|
|
123
|
-
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function printHelp() {
|
|
127
|
-
console.log("Usage: node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ] [ --openviking-version=V ] [ --repo=PATH ]");
|
|
128
|
-
console.log("");
|
|
129
|
-
console.log(" -y, --yes Non-interactive (use defaults)");
|
|
130
|
-
console.log(" --zh Chinese prompts");
|
|
131
|
-
console.log(" --workdir OpenClaw config directory (default: ~/.openclaw)");
|
|
132
|
-
console.log(" --openviking-version=VERSION Pip install openviking==VERSION (default: latest)");
|
|
133
|
-
console.log(" --repo=PATH Use OpenViking repo at PATH: pip install -e PATH, plugin from repo (default: off)");
|
|
134
|
-
console.log(" -h, --help This help");
|
|
135
|
-
console.log("");
|
|
136
|
-
console.log("Env: OPENVIKING_REPO, REPO, BRANCH, SKIP_OPENCLAW, SKIP_OPENVIKING, OPENVIKING_VERSION, NPM_REGISTRY, PIP_INDEX_URL");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function tr(en, zh) {
|
|
140
|
-
return langZh ? zh : en;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function info(msg) {
|
|
144
|
-
console.log(`[INFO] ${msg}`);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function warn(msg) {
|
|
148
|
-
console.log(`[WARN] ${msg}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function err(msg) {
|
|
152
|
-
console.log(`[ERROR] ${msg}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function bold(msg) {
|
|
156
|
-
console.log(msg);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function run(cmd, args, opts = {}) {
|
|
160
|
-
return new Promise((resolve, reject) => {
|
|
161
|
-
const child = spawn(cmd, args, {
|
|
162
|
-
stdio: opts.silent ? "pipe" : "inherit",
|
|
163
|
-
shell: opts.shell ?? true,
|
|
164
|
-
...opts,
|
|
165
|
-
});
|
|
166
|
-
child.on("error", reject);
|
|
167
|
-
child.on("close", (code) => {
|
|
168
|
-
if (code === 0) resolve();
|
|
169
|
-
else reject(new Error(`exit ${code}`));
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function runCapture(cmd, args, opts = {}) {
|
|
175
|
-
return new Promise((resolve) => {
|
|
176
|
-
const child = spawn(cmd, args, {
|
|
177
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
178
|
-
shell: opts.shell ?? false,
|
|
179
|
-
...opts,
|
|
180
|
-
});
|
|
181
|
-
let out = "";
|
|
182
|
-
let errOut = "";
|
|
183
|
-
child.stdout?.on("data", (chunk) => {
|
|
184
|
-
out += String(chunk);
|
|
185
|
-
});
|
|
186
|
-
child.stderr?.on("data", (chunk) => {
|
|
187
|
-
errOut += String(chunk);
|
|
188
|
-
});
|
|
189
|
-
child.on("error", (error) => {
|
|
190
|
-
resolve({ code: -1, out: "", err: String(error) });
|
|
191
|
-
});
|
|
192
|
-
child.on("close", (code) => {
|
|
193
|
-
resolve({ code, out: out.trim(), err: errOut.trim() });
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function runLiveCapture(cmd, args, opts = {}) {
|
|
199
|
-
return new Promise((resolve) => {
|
|
200
|
-
const child = spawn(cmd, args, {
|
|
201
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
202
|
-
shell: opts.shell ?? false,
|
|
203
|
-
...opts,
|
|
204
|
-
});
|
|
205
|
-
let out = "";
|
|
206
|
-
let errOut = "";
|
|
207
|
-
child.stdout?.on("data", (chunk) => {
|
|
208
|
-
const text = String(chunk);
|
|
209
|
-
out += text;
|
|
210
|
-
process.stdout.write(text);
|
|
211
|
-
});
|
|
212
|
-
child.stderr?.on("data", (chunk) => {
|
|
213
|
-
const text = String(chunk);
|
|
214
|
-
errOut += text;
|
|
215
|
-
process.stderr.write(text);
|
|
216
|
-
});
|
|
217
|
-
child.on("error", (error) => {
|
|
218
|
-
resolve({ code: -1, out: "", err: String(error) });
|
|
219
|
-
});
|
|
220
|
-
child.on("close", (code) => {
|
|
221
|
-
resolve({ code, out: out.trim(), err: errOut.trim() });
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function question(prompt, defaultValue = "") {
|
|
227
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
228
|
-
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
229
|
-
return new Promise((resolve) => {
|
|
230
|
-
rl.question(`${prompt}${suffix}: `, (answer) => {
|
|
231
|
-
rl.close();
|
|
232
|
-
resolve((answer ?? defaultValue).trim() || defaultValue);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async function checkPython() {
|
|
238
|
-
const py = process.env.OPENVIKING_PYTHON || (IS_WIN ? "python" : "python3");
|
|
239
|
-
const result = await runCapture(py, ["-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]);
|
|
240
|
-
if (result.code !== 0 || !result.out) {
|
|
241
|
-
return {
|
|
242
|
-
ok: false,
|
|
243
|
-
detail: tr("Python not found or failed. Install Python >= 3.10.", "Python 未找到或执行失败,请安装 Python >= 3.10"),
|
|
244
|
-
cmd: py,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
const [major, minor] = result.out.split(".").map(Number);
|
|
248
|
-
if (major < 3 || (major === 3 && minor < 10)) {
|
|
249
|
-
return {
|
|
250
|
-
ok: false,
|
|
251
|
-
detail: tr(`Python ${result.out} is too old. Need >= 3.10.`, `Python ${result.out} 版本过低,需要 >= 3.10`),
|
|
252
|
-
cmd: py,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
return { ok: true, detail: result.out, cmd: py };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function checkNode() {
|
|
259
|
-
const result = await runCapture("node", ["-v"], { shell: IS_WIN });
|
|
260
|
-
if (result.code !== 0 || !result.out) {
|
|
261
|
-
return { ok: false, detail: tr("Node.js not found. Install Node.js >= 22.", "Node.js 未找到,请安装 Node.js >= 22") };
|
|
262
|
-
}
|
|
263
|
-
const major = Number.parseInt(result.out.replace(/^v/, "").split(".")[0], 10);
|
|
264
|
-
if (!Number.isFinite(major) || major < 22) {
|
|
265
|
-
return { ok: false, detail: tr(`Node.js ${result.out} is too old. Need >= 22.`, `Node.js ${result.out} 版本过低,需要 >= 22`) };
|
|
266
|
-
}
|
|
267
|
-
return { ok: true, detail: result.out };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function detectOpenClawInstances() {
|
|
271
|
-
const instances = [];
|
|
272
|
-
try {
|
|
273
|
-
const entries = readdirSync(HOME, { withFileTypes: true });
|
|
274
|
-
for (const entry of entries) {
|
|
275
|
-
if (!entry.isDirectory()) continue;
|
|
276
|
-
if (entry.name === ".openclaw" || entry.name.startsWith(".openclaw-")) {
|
|
277
|
-
instances.push(join(HOME, entry.name));
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
} catch {}
|
|
281
|
-
return instances.sort();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
async function selectWorkdir() {
|
|
285
|
-
if (workdirExplicit) return;
|
|
286
|
-
|
|
287
|
-
const instances = detectOpenClawInstances();
|
|
288
|
-
if (instances.length <= 1) return;
|
|
289
|
-
if (installYes) return;
|
|
290
|
-
|
|
291
|
-
console.log("");
|
|
292
|
-
bold(tr("Found multiple OpenClaw instances:", "发现多个 OpenClaw 实例:"));
|
|
293
|
-
for (let i = 0; i < instances.length; i++) {
|
|
294
|
-
console.log(` ${i + 1}) ${instances[i]}`);
|
|
295
|
-
}
|
|
296
|
-
console.log("");
|
|
297
|
-
|
|
298
|
-
const answer = await question(tr("Select instance number", "选择实例编号"), "1");
|
|
299
|
-
const index = Number.parseInt(answer, 10) - 1;
|
|
300
|
-
if (index >= 0 && index < instances.length) {
|
|
301
|
-
setOpenClawDir(instances[index]);
|
|
302
|
-
} else {
|
|
303
|
-
warn(tr("Invalid selection, using default", "无效选择,使用默认"));
|
|
304
|
-
setOpenClawDir(instances[0]);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function selectMode() {
|
|
309
|
-
if (installYes) {
|
|
310
|
-
selectedMode = "local";
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
const mode = (await question(tr("Plugin mode - local or remote", "插件模式 - local 或 remote"), "local")).toLowerCase();
|
|
314
|
-
selectedMode = mode === "remote" ? "remote" : "local";
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
async function collectRemoteConfig() {
|
|
318
|
-
if (installYes) return;
|
|
319
|
-
remoteBaseUrl = await question(tr("OpenViking server URL", "OpenViking 服务器地址"), remoteBaseUrl);
|
|
320
|
-
remoteApiKey = await question(tr("API Key (optional)", "API Key(可选)"), remoteApiKey);
|
|
321
|
-
remoteAgentId = await question(tr("Agent ID (optional)", "Agent ID(可选)"), remoteAgentId);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
async function validateEnvironment() {
|
|
325
|
-
info(tr("Checking OpenViking runtime environment...", "正在校验 OpenViking 运行环境..."));
|
|
326
|
-
console.log("");
|
|
327
|
-
|
|
328
|
-
const missing = [];
|
|
329
|
-
|
|
330
|
-
const python = await checkPython();
|
|
331
|
-
if (python.ok) {
|
|
332
|
-
info(` Python: ${python.detail} ✓`);
|
|
333
|
-
} else {
|
|
334
|
-
missing.push(`Python 3.10+ | ${python.detail}`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const node = await checkNode();
|
|
338
|
-
if (node.ok) {
|
|
339
|
-
info(` Node.js: ${node.detail} ✓`);
|
|
340
|
-
} else {
|
|
341
|
-
missing.push(`Node.js 22+ | ${node.detail}`);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (missing.length > 0) {
|
|
345
|
-
console.log("");
|
|
346
|
-
err(tr("Environment check failed. Install missing dependencies first.", "环境校验未通过,请先安装以下缺失组件。"));
|
|
347
|
-
console.log("");
|
|
348
|
-
if (missing.some((item) => item.startsWith("Python"))) {
|
|
349
|
-
console.log(tr("Python (example):", "Python(示例):"));
|
|
350
|
-
if (IS_WIN) console.log(" winget install --id Python.Python.3.11 -e");
|
|
351
|
-
else console.log(" pyenv install 3.11.12 && pyenv global 3.11.12");
|
|
352
|
-
console.log("");
|
|
353
|
-
}
|
|
354
|
-
if (missing.some((item) => item.startsWith("Node"))) {
|
|
355
|
-
console.log(tr("Node.js (example):", "Node.js(示例):"));
|
|
356
|
-
if (IS_WIN) console.log(" nvm install 22.22.0 && nvm use 22.22.0");
|
|
357
|
-
else console.log(" nvm install 22 && nvm use 22");
|
|
358
|
-
console.log("");
|
|
359
|
-
}
|
|
360
|
-
process.exit(1);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
console.log("");
|
|
364
|
-
info(tr("Environment check passed ✓", "环境校验通过 ✓"));
|
|
365
|
-
console.log("");
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async function checkOpenClaw() {
|
|
369
|
-
if (process.env.SKIP_OPENCLAW === "1") {
|
|
370
|
-
info(tr("Skipping OpenClaw check (SKIP_OPENCLAW=1)", "跳过 OpenClaw 校验 (SKIP_OPENCLAW=1)"));
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
info(tr("Checking OpenClaw...", "正在校验 OpenClaw..."));
|
|
375
|
-
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
376
|
-
if (result.code === 0) {
|
|
377
|
-
info(tr("OpenClaw detected ✓", "OpenClaw 已安装 ✓"));
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
err(tr("OpenClaw not found. Install it manually, then rerun this script.", "未检测到 OpenClaw,请先手动安装后再执行本脚本"));
|
|
382
|
-
console.log("");
|
|
383
|
-
console.log(tr("Recommended command:", "推荐命令:"));
|
|
384
|
-
console.log(` npm install -g openclaw --registry ${NPM_REGISTRY}`);
|
|
385
|
-
console.log("");
|
|
386
|
-
console.log(" openclaw --version");
|
|
387
|
-
console.log(" openclaw onboard");
|
|
388
|
-
console.log("");
|
|
389
|
-
process.exit(1);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
async function installOpenViking() {
|
|
393
|
-
if (process.env.SKIP_OPENVIKING === "1") {
|
|
394
|
-
info(tr("Skipping OpenViking install (SKIP_OPENVIKING=1)", "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)"));
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const python = await checkPython();
|
|
399
|
-
if (!python.cmd) {
|
|
400
|
-
err(tr("Python check failed.", "Python 校验失败"));
|
|
401
|
-
process.exit(1);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const py = python.cmd;
|
|
405
|
-
|
|
406
|
-
if (openvikingRepo && existsSync(join(openvikingRepo, "pyproject.toml"))) {
|
|
407
|
-
info(tr(`Installing OpenViking from source (editable): ${openvikingRepo}`, `正在从源码安装 OpenViking(可编辑): ${openvikingRepo}`));
|
|
408
|
-
await run(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { silent: true });
|
|
409
|
-
await run(py, ["-m", "pip", "install", "-e", openvikingRepo]);
|
|
410
|
-
openvikingPythonPath = py;
|
|
411
|
-
info(tr("OpenViking installed ✓ (source)", "OpenViking 安装完成 ✓(源码)"));
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
info(tr("Installing OpenViking from PyPI...", "正在安装 OpenViking (PyPI)..."));
|
|
416
|
-
info(tr(`Using pip index: ${PIP_INDEX_URL}`, `使用 pip 镜像源: ${PIP_INDEX_URL}`));
|
|
417
|
-
|
|
418
|
-
info(`Package: ${OPENVIKING_PIP_SPEC}`);
|
|
419
|
-
await runCapture(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
420
|
-
const installResult = await runLiveCapture(
|
|
421
|
-
py,
|
|
422
|
-
["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
423
|
-
{ shell: false },
|
|
424
|
-
);
|
|
425
|
-
if (installResult.code === 0) {
|
|
426
|
-
openvikingPythonPath = py;
|
|
427
|
-
info(tr("OpenViking installed ✓", "OpenViking 安装完成 ✓"));
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
const installOutput = `${installResult.out}\n${installResult.err}`;
|
|
432
|
-
const shouldTryVenv = !IS_WIN && /externally-managed-environment|externally managed|No module named pip/i.test(installOutput);
|
|
433
|
-
if (shouldTryVenv) {
|
|
434
|
-
const venvDir = join(OPENVIKING_DIR, "venv");
|
|
435
|
-
const venvPy = IS_WIN ? join(venvDir, "Scripts", "python.exe") : join(venvDir, "bin", "python");
|
|
436
|
-
|
|
437
|
-
if (existsSync(venvPy)) {
|
|
438
|
-
const reuseCheck = await runCapture(venvPy, ["-c", "import openviking"], { shell: false });
|
|
439
|
-
if (reuseCheck.code === 0) {
|
|
440
|
-
await runLiveCapture(
|
|
441
|
-
venvPy,
|
|
442
|
-
["-m", "pip", "install", "--progress-bar", "on", "-U", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
443
|
-
{ shell: false },
|
|
444
|
-
);
|
|
445
|
-
openvikingPythonPath = venvPy;
|
|
446
|
-
info(tr("OpenViking installed ✓ (venv)", "OpenViking 安装完成 ✓(虚拟环境)"));
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
await mkdir(OPENVIKING_DIR, { recursive: true });
|
|
452
|
-
const venvCreate = await runCapture(py, ["-m", "venv", venvDir], { shell: false });
|
|
453
|
-
if (venvCreate.code !== 0) {
|
|
454
|
-
err(tr("Could not create venv. Install python3-venv or use OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES=1", "无法创建虚拟环境,请安装 python3-venv 或设置 OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES=1"));
|
|
455
|
-
console.log(venvCreate.err || venvCreate.out);
|
|
456
|
-
process.exit(1);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
await runCapture(venvPy, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
460
|
-
const venvInstall = await runLiveCapture(
|
|
461
|
-
venvPy,
|
|
462
|
-
["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
463
|
-
{ shell: false },
|
|
464
|
-
);
|
|
465
|
-
if (venvInstall.code === 0) {
|
|
466
|
-
openvikingPythonPath = venvPy;
|
|
467
|
-
info(tr("OpenViking installed ✓ (venv)", "OpenViking 安装完成 ✓(虚拟环境)"));
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
err(tr("OpenViking install failed in venv.", "在虚拟环境中安装 OpenViking 失败。"));
|
|
472
|
-
console.log(venvInstall.err || venvInstall.out);
|
|
473
|
-
process.exit(1);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (process.env.OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES === "1") {
|
|
477
|
-
const systemInstall = await runLiveCapture(
|
|
478
|
-
py,
|
|
479
|
-
["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
480
|
-
{ shell: false },
|
|
481
|
-
);
|
|
482
|
-
if (systemInstall.code === 0) {
|
|
483
|
-
openvikingPythonPath = py;
|
|
484
|
-
info(tr("OpenViking installed ✓ (system)", "OpenViking 安装完成 ✓(系统)"));
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
err(tr("OpenViking install failed. Check Python >= 3.10 and pip.", "OpenViking 安装失败,请检查 Python >= 3.10 及 pip"));
|
|
490
|
-
console.log(installResult.err || installResult.out);
|
|
491
|
-
process.exit(1);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
async function configureOvConf() {
|
|
495
|
-
await mkdir(OPENVIKING_DIR, { recursive: true });
|
|
496
|
-
|
|
497
|
-
let workspace = join(OPENVIKING_DIR, "data");
|
|
498
|
-
let serverPort = String(DEFAULT_SERVER_PORT);
|
|
499
|
-
let agfsPort = String(DEFAULT_AGFS_PORT);
|
|
500
|
-
let vlmModel = DEFAULT_VLM_MODEL;
|
|
501
|
-
let embeddingModel = DEFAULT_EMBED_MODEL;
|
|
502
|
-
let vlmApiKey = process.env.OPENVIKING_VLM_API_KEY || process.env.OPENVIKING_ARK_API_KEY || "";
|
|
503
|
-
let embeddingApiKey = process.env.OPENVIKING_EMBEDDING_API_KEY || process.env.OPENVIKING_ARK_API_KEY || "";
|
|
504
|
-
|
|
505
|
-
if (!installYes) {
|
|
506
|
-
console.log("");
|
|
507
|
-
workspace = await question(tr("OpenViking workspace path", "OpenViking 数据目录"), workspace);
|
|
508
|
-
serverPort = await question(tr("OpenViking HTTP port", "OpenViking HTTP 端口"), serverPort);
|
|
509
|
-
agfsPort = await question(tr("AGFS port", "AGFS 端口"), agfsPort);
|
|
510
|
-
vlmModel = await question(tr("VLM model", "VLM 模型"), vlmModel);
|
|
511
|
-
embeddingModel = await question(tr("Embedding model", "Embedding 模型"), embeddingModel);
|
|
512
|
-
console.log(tr("VLM and Embedding API keys can differ. Leave empty to edit ov.conf later.", "说明:VLM 与 Embedding 的 API Key 可分别填写,留空可稍后在 ov.conf 修改。"));
|
|
513
|
-
const vlmInput = await question(tr("VLM API key (optional)", "VLM API Key(可留空)"), "");
|
|
514
|
-
const embInput = await question(tr("Embedding API key (optional)", "Embedding API Key(可留空)"), "");
|
|
515
|
-
if (vlmInput) vlmApiKey = vlmInput;
|
|
516
|
-
if (embInput) embeddingApiKey = embInput;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
selectedServerPort = Number.parseInt(serverPort, 10) || DEFAULT_SERVER_PORT;
|
|
520
|
-
const agfsPortNum = Number.parseInt(agfsPort, 10) || DEFAULT_AGFS_PORT;
|
|
521
|
-
|
|
522
|
-
await mkdir(workspace, { recursive: true });
|
|
523
|
-
|
|
524
|
-
const config = {
|
|
525
|
-
server: {
|
|
526
|
-
host: "127.0.0.1",
|
|
527
|
-
port: selectedServerPort,
|
|
528
|
-
root_api_key: null,
|
|
529
|
-
cors_origins: ["*"],
|
|
530
|
-
},
|
|
531
|
-
storage: {
|
|
532
|
-
workspace,
|
|
533
|
-
vectordb: { name: "context", backend: "local", project: "default" },
|
|
534
|
-
agfs: { port: agfsPortNum, log_level: "warn", backend: "local", timeout: 10, retry_times: 3 },
|
|
535
|
-
},
|
|
536
|
-
embedding: {
|
|
537
|
-
dense: {
|
|
538
|
-
backend: "volcengine",
|
|
539
|
-
api_key: embeddingApiKey || null,
|
|
540
|
-
model: embeddingModel,
|
|
541
|
-
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
542
|
-
dimension: 1024,
|
|
543
|
-
input: "multimodal",
|
|
544
|
-
},
|
|
545
|
-
},
|
|
546
|
-
vlm: {
|
|
547
|
-
backend: "volcengine",
|
|
548
|
-
api_key: vlmApiKey || null,
|
|
549
|
-
model: vlmModel,
|
|
550
|
-
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
551
|
-
temperature: 0.1,
|
|
552
|
-
max_retries: 3,
|
|
553
|
-
},
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
const configPath = join(OPENVIKING_DIR, "ov.conf");
|
|
557
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
558
|
-
info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
async function downloadPluginFile(relPath, required, index, total) {
|
|
562
|
-
const fileName = relPath.split("/").pop();
|
|
563
|
-
const url = `${GH_RAW}/${relPath}`;
|
|
564
|
-
const maxRetries = 3;
|
|
565
|
-
|
|
566
|
-
process.stdout.write(` [${index}/${total}] ${fileName} `);
|
|
567
|
-
|
|
568
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
569
|
-
try {
|
|
570
|
-
const response = await fetch(url);
|
|
571
|
-
if (response.ok) {
|
|
572
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
573
|
-
await writeFile(join(PLUGIN_DEST, fileName), buffer);
|
|
574
|
-
console.log("✓");
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
if (!required && response.status === 404) {
|
|
578
|
-
console.log(tr("(not present in target branch, skipped)", "(目标分支不存在,已跳过)"));
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
} catch {}
|
|
582
|
-
|
|
583
|
-
if (attempt < maxRetries) {
|
|
584
|
-
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (fileName === ".gitignore") {
|
|
589
|
-
console.log(tr("(retries failed, using minimal .gitignore)", "(重试失败,使用最小 .gitignore)"));
|
|
590
|
-
await writeFile(join(PLUGIN_DEST, fileName), "node_modules/\n", "utf8");
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
console.log("");
|
|
595
|
-
err(tr(`Download failed: ${url}`, `下载失败: ${url}`));
|
|
596
|
-
process.exit(1);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async function downloadPlugin() {
|
|
600
|
-
await mkdir(PLUGIN_DEST, { recursive: true });
|
|
601
|
-
const files = [
|
|
602
|
-
...REQUIRED_PLUGIN_FILES.map((relPath) => ({ relPath, required: true })),
|
|
603
|
-
...OPTIONAL_PLUGIN_FILES.map((relPath) => ({ relPath, required: false })),
|
|
604
|
-
];
|
|
605
|
-
|
|
606
|
-
info(tr(`Downloading memory-openviking plugin from ${REPO}@${BRANCH}...`, `正在从 ${REPO}@${BRANCH} 下载 memory-openviking 插件...`));
|
|
607
|
-
for (let i = 0; i < files.length; i++) {
|
|
608
|
-
const file = files[i];
|
|
609
|
-
await downloadPluginFile(file.relPath, file.required, i + 1, files.length);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
613
|
-
await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: PLUGIN_DEST, silent: false });
|
|
614
|
-
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
async function configureOpenClawPlugin(pluginPath = PLUGIN_DEST) {
|
|
618
|
-
info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
|
|
619
|
-
|
|
620
|
-
const configPath = join(OPENCLAW_DIR, "openclaw.json");
|
|
621
|
-
let config = {};
|
|
622
|
-
|
|
623
|
-
if (existsSync(configPath)) {
|
|
624
|
-
try {
|
|
625
|
-
const raw = await readFile(configPath, "utf8");
|
|
626
|
-
if (raw.trim()) config = JSON.parse(raw);
|
|
627
|
-
} catch {
|
|
628
|
-
warn(tr("Existing openclaw.json invalid. Rebuilding required sections.", "已有 openclaw.json 非法,将重建相关配置节点。"));
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
if (!config.plugins) config.plugins = {};
|
|
633
|
-
if (!config.gateway) config.gateway = {};
|
|
634
|
-
if (!config.plugins.slots) config.plugins.slots = {};
|
|
635
|
-
if (!config.plugins.load) config.plugins.load = {};
|
|
636
|
-
if (!config.plugins.entries) config.plugins.entries = {};
|
|
637
|
-
|
|
638
|
-
const existingPaths = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
|
|
639
|
-
config.plugins.enabled = true;
|
|
640
|
-
config.plugins.allow = ["memory-openviking"];
|
|
641
|
-
config.plugins.slots.memory = "memory-openviking";
|
|
642
|
-
config.plugins.load.paths = [...new Set([...existingPaths, pluginPath])];
|
|
643
|
-
|
|
644
|
-
const pluginConfig = {
|
|
645
|
-
mode: selectedMode,
|
|
646
|
-
targetUri: "viking://user/memories",
|
|
647
|
-
autoRecall: true,
|
|
648
|
-
autoCapture: true,
|
|
649
|
-
};
|
|
650
|
-
|
|
651
|
-
if (selectedMode === "local") {
|
|
652
|
-
pluginConfig.configPath = join(OPENVIKING_DIR, "ov.conf");
|
|
653
|
-
pluginConfig.port = selectedServerPort;
|
|
654
|
-
} else {
|
|
655
|
-
pluginConfig.baseUrl = remoteBaseUrl;
|
|
656
|
-
if (remoteApiKey) pluginConfig.apiKey = remoteApiKey;
|
|
657
|
-
if (remoteAgentId) pluginConfig.agentId = remoteAgentId;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
config.plugins.entries["memory-openviking"] = { config: pluginConfig };
|
|
661
|
-
config.gateway.mode = "local";
|
|
662
|
-
|
|
663
|
-
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
664
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
665
|
-
info(tr("OpenClaw plugin configured", "OpenClaw 插件配置完成"));
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
async function resolvePythonPath() {
|
|
669
|
-
if (openvikingPythonPath) return openvikingPythonPath;
|
|
670
|
-
const python = await checkPython();
|
|
671
|
-
const py = python.cmd;
|
|
672
|
-
if (!py) return "";
|
|
673
|
-
|
|
674
|
-
if (IS_WIN) {
|
|
675
|
-
const result = await runCapture("where", [py], { shell: true });
|
|
676
|
-
return result.out.split(/\r?\n/)[0]?.trim() || py;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
const result = await runCapture("which", [py], { shell: false });
|
|
680
|
-
return result.out.trim() || py;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
async function writeOpenvikingEnv({ includePython }) {
|
|
684
|
-
const needStateDir = OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR;
|
|
685
|
-
const pythonPath = includePython ? await resolvePythonPath() : "";
|
|
686
|
-
if (!needStateDir && !pythonPath) return null;
|
|
687
|
-
|
|
688
|
-
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
689
|
-
|
|
690
|
-
if (IS_WIN) {
|
|
691
|
-
const batLines = ["@echo off"];
|
|
692
|
-
const psLines = [];
|
|
693
|
-
|
|
694
|
-
if (needStateDir) {
|
|
695
|
-
batLines.push(`set "OPENCLAW_STATE_DIR=${OPENCLAW_DIR.replace(/"/g, '""')}"`);
|
|
696
|
-
psLines.push(`$env:OPENCLAW_STATE_DIR = "${OPENCLAW_DIR.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
697
|
-
}
|
|
698
|
-
if (pythonPath) {
|
|
699
|
-
batLines.push(`set "OPENVIKING_PYTHON=${pythonPath.replace(/"/g, '""')}"`);
|
|
700
|
-
psLines.push(`$env:OPENVIKING_PYTHON = "${pythonPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const batPath = join(OPENCLAW_DIR, "openviking.env.bat");
|
|
704
|
-
const ps1Path = join(OPENCLAW_DIR, "openviking.env.ps1");
|
|
705
|
-
await writeFile(batPath, `${batLines.join("\r\n")}\r\n`, "utf8");
|
|
706
|
-
await writeFile(ps1Path, `${psLines.join("\n")}\n`, "utf8");
|
|
707
|
-
|
|
708
|
-
info(tr(`Environment file generated: ${batPath}`, `已生成环境文件: ${batPath}`));
|
|
709
|
-
return { shellPath: batPath, powershellPath: ps1Path };
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
const lines = [];
|
|
713
|
-
if (needStateDir) {
|
|
714
|
-
lines.push(`export OPENCLAW_STATE_DIR='${OPENCLAW_DIR.replace(/'/g, "'\"'\"'")}'`);
|
|
715
|
-
}
|
|
716
|
-
if (pythonPath) {
|
|
717
|
-
lines.push(`export OPENVIKING_PYTHON='${pythonPath.replace(/'/g, "'\"'\"'")}'`);
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
const envPath = join(OPENCLAW_DIR, "openviking.env");
|
|
721
|
-
await writeFile(envPath, `${lines.join("\n")}\n`, "utf8");
|
|
722
|
-
info(tr(`Environment file generated: ${envPath}`, `已生成环境文件: ${envPath}`));
|
|
723
|
-
return { shellPath: envPath };
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
function wrapCommand(command, envFiles) {
|
|
727
|
-
if (!envFiles) return command;
|
|
728
|
-
if (IS_WIN) return `call "${envFiles.shellPath}" && ${command}`;
|
|
729
|
-
return `source '${envFiles.shellPath.replace(/'/g, "'\"'\"'")}' && ${command}`;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
async function main() {
|
|
733
|
-
console.log("");
|
|
734
|
-
bold(tr("🦣 OpenClaw + OpenViking Installer", "🦣 OpenClaw + OpenViking 一键安装"));
|
|
735
|
-
console.log("");
|
|
736
|
-
|
|
737
|
-
await selectWorkdir();
|
|
738
|
-
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
739
|
-
|
|
740
|
-
await selectMode();
|
|
741
|
-
info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
|
|
742
|
-
|
|
743
|
-
if (selectedMode === "local") {
|
|
744
|
-
await validateEnvironment();
|
|
745
|
-
await checkOpenClaw();
|
|
746
|
-
await installOpenViking();
|
|
747
|
-
await configureOvConf();
|
|
748
|
-
} else {
|
|
749
|
-
await checkOpenClaw();
|
|
750
|
-
await collectRemoteConfig();
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
let pluginPath;
|
|
754
|
-
const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", "openclaw-memory-plugin") : "";
|
|
755
|
-
if (openvikingRepo && existsSync(join(localPluginDir, "index.ts"))) {
|
|
756
|
-
pluginPath = localPluginDir;
|
|
757
|
-
info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
|
|
758
|
-
if (!existsSync(join(pluginPath, "node_modules"))) {
|
|
759
|
-
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
760
|
-
await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: pluginPath, silent: false });
|
|
761
|
-
}
|
|
762
|
-
} else {
|
|
763
|
-
await downloadPlugin();
|
|
764
|
-
pluginPath = PLUGIN_DEST;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
await configureOpenClawPlugin(pluginPath);
|
|
768
|
-
const envFiles = await writeOpenvikingEnv({
|
|
769
|
-
includePython: selectedMode === "local",
|
|
770
|
-
});
|
|
771
|
-
|
|
772
|
-
console.log("");
|
|
773
|
-
bold("═══════════════════════════════════════════════════════════");
|
|
774
|
-
bold(` ${tr("Installation complete!", "安装完成!")}`);
|
|
775
|
-
bold("═══════════════════════════════════════════════════════════");
|
|
776
|
-
console.log("");
|
|
777
|
-
|
|
778
|
-
if (selectedMode === "local") {
|
|
779
|
-
info(tr("Run these commands to start OpenClaw + OpenViking:", "请按以下命令启动 OpenClaw + OpenViking:"));
|
|
780
|
-
} else {
|
|
781
|
-
info(tr("Run these commands to start OpenClaw:", "请按以下命令启动 OpenClaw:"));
|
|
782
|
-
}
|
|
783
|
-
console.log(` 1) ${wrapCommand("openclaw --version", envFiles)}`);
|
|
784
|
-
console.log(` 2) ${wrapCommand("openclaw onboard", envFiles)}`);
|
|
785
|
-
console.log(` 3) ${wrapCommand("openclaw gateway", envFiles)}`);
|
|
786
|
-
console.log(` 4) ${wrapCommand("openclaw status", envFiles)}`);
|
|
787
|
-
console.log("");
|
|
788
|
-
|
|
789
|
-
if (selectedMode === "local") {
|
|
790
|
-
info(tr(`You can edit the config freely: ${OPENVIKING_DIR}/ov.conf`, `你可以按需自由修改配置文件: ${OPENVIKING_DIR}/ov.conf`));
|
|
791
|
-
} else {
|
|
792
|
-
info(tr(`Remote server: ${remoteBaseUrl}`, `远程服务器: ${remoteBaseUrl}`));
|
|
793
|
-
}
|
|
794
|
-
console.log("");
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
main().catch((error) => {
|
|
798
|
-
console.error(error);
|
|
799
|
-
process.exit(1);
|
|
800
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OpenClaw + OpenViking cross-platform installer
|
|
4
|
+
*
|
|
5
|
+
* One-liner (after npm publish; use package name + bin name):
|
|
6
|
+
* npx -p openclaw-openviking-setup-helper ov-install [ -y ] [ --zh ] [ --workdir PATH ]
|
|
7
|
+
* Or install globally then run:
|
|
8
|
+
* npm i -g openclaw-openviking-setup-helper
|
|
9
|
+
* ov-install
|
|
10
|
+
* openclaw-openviking-install
|
|
11
|
+
*
|
|
12
|
+
* Direct run:
|
|
13
|
+
* node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ]
|
|
14
|
+
* [ --openviking-version=V ] [ --repo=PATH ]
|
|
15
|
+
*
|
|
16
|
+
* Environment variables:
|
|
17
|
+
* REPO, BRANCH, OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
|
|
18
|
+
* OPENVIKING_VERSION Pip install openviking==VERSION (omit for latest)
|
|
19
|
+
* OPENVIKING_REPO Repo path: source install (pip -e) + local plugin (default: off)
|
|
20
|
+
* NPM_REGISTRY, PIP_INDEX_URL
|
|
21
|
+
* OPENVIKING_VLM_API_KEY, OPENVIKING_EMBEDDING_API_KEY, OPENVIKING_ARK_API_KEY
|
|
22
|
+
* OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES (Linux)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { spawn } from "node:child_process";
|
|
26
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
27
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
28
|
+
import { dirname, join } from "node:path";
|
|
29
|
+
import { createInterface } from "node:readline";
|
|
30
|
+
import { fileURLToPath } from "node:url";
|
|
31
|
+
|
|
32
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
|
+
|
|
34
|
+
const REPO = process.env.REPO || "volcengine/OpenViking";
|
|
35
|
+
const BRANCH = process.env.BRANCH || "publish";
|
|
36
|
+
const GH_RAW = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`;
|
|
37
|
+
const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
|
|
38
|
+
const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
|
|
39
|
+
|
|
40
|
+
const IS_WIN = process.platform === "win32";
|
|
41
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
42
|
+
|
|
43
|
+
const DEFAULT_OPENCLAW_DIR = join(HOME, ".openclaw");
|
|
44
|
+
let OPENCLAW_DIR = DEFAULT_OPENCLAW_DIR;
|
|
45
|
+
let PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
|
|
46
|
+
|
|
47
|
+
const OPENVIKING_DIR = join(HOME, ".openviking");
|
|
48
|
+
|
|
49
|
+
const DEFAULT_SERVER_PORT = 1933;
|
|
50
|
+
const DEFAULT_AGFS_PORT = 1833;
|
|
51
|
+
const DEFAULT_VLM_MODEL = "doubao-seed-2-0-pro-260215";
|
|
52
|
+
const DEFAULT_EMBED_MODEL = "doubao-embedding-vision-251215";
|
|
53
|
+
|
|
54
|
+
const REQUIRED_PLUGIN_FILES = [
|
|
55
|
+
"examples/openclaw-memory-plugin/index.ts",
|
|
56
|
+
"examples/openclaw-memory-plugin/config.ts",
|
|
57
|
+
"examples/openclaw-memory-plugin/openclaw.plugin.json",
|
|
58
|
+
"examples/openclaw-memory-plugin/package.json",
|
|
59
|
+
"examples/openclaw-memory-plugin/package-lock.json",
|
|
60
|
+
"examples/openclaw-memory-plugin/.gitignore",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const OPTIONAL_PLUGIN_FILES = [
|
|
64
|
+
"examples/openclaw-memory-plugin/client.ts",
|
|
65
|
+
"examples/openclaw-memory-plugin/process-manager.ts",
|
|
66
|
+
"examples/openclaw-memory-plugin/memory-ranking.ts",
|
|
67
|
+
"examples/openclaw-memory-plugin/text-utils.ts",
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
|
|
71
|
+
let langZh = false;
|
|
72
|
+
let openvikingVersion = process.env.OPENVIKING_VERSION || "";
|
|
73
|
+
let openvikingRepo = process.env.OPENVIKING_REPO || "";
|
|
74
|
+
let workdirExplicit = false;
|
|
75
|
+
|
|
76
|
+
let selectedMode = "local";
|
|
77
|
+
let selectedServerPort = DEFAULT_SERVER_PORT;
|
|
78
|
+
let remoteBaseUrl = "http://127.0.0.1:1933";
|
|
79
|
+
let remoteApiKey = "";
|
|
80
|
+
let remoteAgentId = "";
|
|
81
|
+
let openvikingPythonPath = "";
|
|
82
|
+
|
|
83
|
+
const argv = process.argv.slice(2);
|
|
84
|
+
for (let i = 0; i < argv.length; i++) {
|
|
85
|
+
const arg = argv[i];
|
|
86
|
+
if (arg === "-y" || arg === "--yes") {
|
|
87
|
+
installYes = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (arg === "--zh") {
|
|
91
|
+
langZh = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (arg === "--workdir") {
|
|
95
|
+
const workdir = argv[i + 1]?.trim();
|
|
96
|
+
if (!workdir) {
|
|
97
|
+
console.error("--workdir requires a path");
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
setOpenClawDir(workdir);
|
|
101
|
+
workdirExplicit = true;
|
|
102
|
+
i += 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (arg.startsWith("--openviking-version=")) {
|
|
106
|
+
openvikingVersion = arg.slice("--openviking-version=".length).trim();
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (arg.startsWith("--repo=")) {
|
|
110
|
+
openvikingRepo = arg.slice("--repo=".length).trim();
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg === "-h" || arg === "--help") {
|
|
114
|
+
printHelp();
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const OPENVIKING_PIP_SPEC = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
|
|
120
|
+
|
|
121
|
+
function setOpenClawDir(dir) {
|
|
122
|
+
OPENCLAW_DIR = dir;
|
|
123
|
+
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function printHelp() {
|
|
127
|
+
console.log("Usage: node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ] [ --openviking-version=V ] [ --repo=PATH ]");
|
|
128
|
+
console.log("");
|
|
129
|
+
console.log(" -y, --yes Non-interactive (use defaults)");
|
|
130
|
+
console.log(" --zh Chinese prompts");
|
|
131
|
+
console.log(" --workdir OpenClaw config directory (default: ~/.openclaw)");
|
|
132
|
+
console.log(" --openviking-version=VERSION Pip install openviking==VERSION (default: latest)");
|
|
133
|
+
console.log(" --repo=PATH Use OpenViking repo at PATH: pip install -e PATH, plugin from repo (default: off)");
|
|
134
|
+
console.log(" -h, --help This help");
|
|
135
|
+
console.log("");
|
|
136
|
+
console.log("Env: OPENVIKING_REPO, REPO, BRANCH, SKIP_OPENCLAW, SKIP_OPENVIKING, OPENVIKING_VERSION, NPM_REGISTRY, PIP_INDEX_URL");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function tr(en, zh) {
|
|
140
|
+
return langZh ? zh : en;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function info(msg) {
|
|
144
|
+
console.log(`[INFO] ${msg}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function warn(msg) {
|
|
148
|
+
console.log(`[WARN] ${msg}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function err(msg) {
|
|
152
|
+
console.log(`[ERROR] ${msg}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function bold(msg) {
|
|
156
|
+
console.log(msg);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function run(cmd, args, opts = {}) {
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
const child = spawn(cmd, args, {
|
|
162
|
+
stdio: opts.silent ? "pipe" : "inherit",
|
|
163
|
+
shell: opts.shell ?? true,
|
|
164
|
+
...opts,
|
|
165
|
+
});
|
|
166
|
+
child.on("error", reject);
|
|
167
|
+
child.on("close", (code) => {
|
|
168
|
+
if (code === 0) resolve();
|
|
169
|
+
else reject(new Error(`exit ${code}`));
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function runCapture(cmd, args, opts = {}) {
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
const child = spawn(cmd, args, {
|
|
177
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
178
|
+
shell: opts.shell ?? false,
|
|
179
|
+
...opts,
|
|
180
|
+
});
|
|
181
|
+
let out = "";
|
|
182
|
+
let errOut = "";
|
|
183
|
+
child.stdout?.on("data", (chunk) => {
|
|
184
|
+
out += String(chunk);
|
|
185
|
+
});
|
|
186
|
+
child.stderr?.on("data", (chunk) => {
|
|
187
|
+
errOut += String(chunk);
|
|
188
|
+
});
|
|
189
|
+
child.on("error", (error) => {
|
|
190
|
+
resolve({ code: -1, out: "", err: String(error) });
|
|
191
|
+
});
|
|
192
|
+
child.on("close", (code) => {
|
|
193
|
+
resolve({ code, out: out.trim(), err: errOut.trim() });
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function runLiveCapture(cmd, args, opts = {}) {
|
|
199
|
+
return new Promise((resolve) => {
|
|
200
|
+
const child = spawn(cmd, args, {
|
|
201
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
202
|
+
shell: opts.shell ?? false,
|
|
203
|
+
...opts,
|
|
204
|
+
});
|
|
205
|
+
let out = "";
|
|
206
|
+
let errOut = "";
|
|
207
|
+
child.stdout?.on("data", (chunk) => {
|
|
208
|
+
const text = String(chunk);
|
|
209
|
+
out += text;
|
|
210
|
+
process.stdout.write(text);
|
|
211
|
+
});
|
|
212
|
+
child.stderr?.on("data", (chunk) => {
|
|
213
|
+
const text = String(chunk);
|
|
214
|
+
errOut += text;
|
|
215
|
+
process.stderr.write(text);
|
|
216
|
+
});
|
|
217
|
+
child.on("error", (error) => {
|
|
218
|
+
resolve({ code: -1, out: "", err: String(error) });
|
|
219
|
+
});
|
|
220
|
+
child.on("close", (code) => {
|
|
221
|
+
resolve({ code, out: out.trim(), err: errOut.trim() });
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function question(prompt, defaultValue = "") {
|
|
227
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
228
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
229
|
+
return new Promise((resolve) => {
|
|
230
|
+
rl.question(`${prompt}${suffix}: `, (answer) => {
|
|
231
|
+
rl.close();
|
|
232
|
+
resolve((answer ?? defaultValue).trim() || defaultValue);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function checkPython() {
|
|
238
|
+
const py = process.env.OPENVIKING_PYTHON || (IS_WIN ? "python" : "python3");
|
|
239
|
+
const result = await runCapture(py, ["-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]);
|
|
240
|
+
if (result.code !== 0 || !result.out) {
|
|
241
|
+
return {
|
|
242
|
+
ok: false,
|
|
243
|
+
detail: tr("Python not found or failed. Install Python >= 3.10.", "Python 未找到或执行失败,请安装 Python >= 3.10"),
|
|
244
|
+
cmd: py,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
const [major, minor] = result.out.split(".").map(Number);
|
|
248
|
+
if (major < 3 || (major === 3 && minor < 10)) {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
detail: tr(`Python ${result.out} is too old. Need >= 3.10.`, `Python ${result.out} 版本过低,需要 >= 3.10`),
|
|
252
|
+
cmd: py,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { ok: true, detail: result.out, cmd: py };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function checkNode() {
|
|
259
|
+
const result = await runCapture("node", ["-v"], { shell: IS_WIN });
|
|
260
|
+
if (result.code !== 0 || !result.out) {
|
|
261
|
+
return { ok: false, detail: tr("Node.js not found. Install Node.js >= 22.", "Node.js 未找到,请安装 Node.js >= 22") };
|
|
262
|
+
}
|
|
263
|
+
const major = Number.parseInt(result.out.replace(/^v/, "").split(".")[0], 10);
|
|
264
|
+
if (!Number.isFinite(major) || major < 22) {
|
|
265
|
+
return { ok: false, detail: tr(`Node.js ${result.out} is too old. Need >= 22.`, `Node.js ${result.out} 版本过低,需要 >= 22`) };
|
|
266
|
+
}
|
|
267
|
+
return { ok: true, detail: result.out };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function detectOpenClawInstances() {
|
|
271
|
+
const instances = [];
|
|
272
|
+
try {
|
|
273
|
+
const entries = readdirSync(HOME, { withFileTypes: true });
|
|
274
|
+
for (const entry of entries) {
|
|
275
|
+
if (!entry.isDirectory()) continue;
|
|
276
|
+
if (entry.name === ".openclaw" || entry.name.startsWith(".openclaw-")) {
|
|
277
|
+
instances.push(join(HOME, entry.name));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch {}
|
|
281
|
+
return instances.sort();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async function selectWorkdir() {
|
|
285
|
+
if (workdirExplicit) return;
|
|
286
|
+
|
|
287
|
+
const instances = detectOpenClawInstances();
|
|
288
|
+
if (instances.length <= 1) return;
|
|
289
|
+
if (installYes) return;
|
|
290
|
+
|
|
291
|
+
console.log("");
|
|
292
|
+
bold(tr("Found multiple OpenClaw instances:", "发现多个 OpenClaw 实例:"));
|
|
293
|
+
for (let i = 0; i < instances.length; i++) {
|
|
294
|
+
console.log(` ${i + 1}) ${instances[i]}`);
|
|
295
|
+
}
|
|
296
|
+
console.log("");
|
|
297
|
+
|
|
298
|
+
const answer = await question(tr("Select instance number", "选择实例编号"), "1");
|
|
299
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
300
|
+
if (index >= 0 && index < instances.length) {
|
|
301
|
+
setOpenClawDir(instances[index]);
|
|
302
|
+
} else {
|
|
303
|
+
warn(tr("Invalid selection, using default", "无效选择,使用默认"));
|
|
304
|
+
setOpenClawDir(instances[0]);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function selectMode() {
|
|
309
|
+
if (installYes) {
|
|
310
|
+
selectedMode = "local";
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const mode = (await question(tr("Plugin mode - local or remote", "插件模式 - local 或 remote"), "local")).toLowerCase();
|
|
314
|
+
selectedMode = mode === "remote" ? "remote" : "local";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function collectRemoteConfig() {
|
|
318
|
+
if (installYes) return;
|
|
319
|
+
remoteBaseUrl = await question(tr("OpenViking server URL", "OpenViking 服务器地址"), remoteBaseUrl);
|
|
320
|
+
remoteApiKey = await question(tr("API Key (optional)", "API Key(可选)"), remoteApiKey);
|
|
321
|
+
remoteAgentId = await question(tr("Agent ID (optional)", "Agent ID(可选)"), remoteAgentId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async function validateEnvironment() {
|
|
325
|
+
info(tr("Checking OpenViking runtime environment...", "正在校验 OpenViking 运行环境..."));
|
|
326
|
+
console.log("");
|
|
327
|
+
|
|
328
|
+
const missing = [];
|
|
329
|
+
|
|
330
|
+
const python = await checkPython();
|
|
331
|
+
if (python.ok) {
|
|
332
|
+
info(` Python: ${python.detail} ✓`);
|
|
333
|
+
} else {
|
|
334
|
+
missing.push(`Python 3.10+ | ${python.detail}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const node = await checkNode();
|
|
338
|
+
if (node.ok) {
|
|
339
|
+
info(` Node.js: ${node.detail} ✓`);
|
|
340
|
+
} else {
|
|
341
|
+
missing.push(`Node.js 22+ | ${node.detail}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (missing.length > 0) {
|
|
345
|
+
console.log("");
|
|
346
|
+
err(tr("Environment check failed. Install missing dependencies first.", "环境校验未通过,请先安装以下缺失组件。"));
|
|
347
|
+
console.log("");
|
|
348
|
+
if (missing.some((item) => item.startsWith("Python"))) {
|
|
349
|
+
console.log(tr("Python (example):", "Python(示例):"));
|
|
350
|
+
if (IS_WIN) console.log(" winget install --id Python.Python.3.11 -e");
|
|
351
|
+
else console.log(" pyenv install 3.11.12 && pyenv global 3.11.12");
|
|
352
|
+
console.log("");
|
|
353
|
+
}
|
|
354
|
+
if (missing.some((item) => item.startsWith("Node"))) {
|
|
355
|
+
console.log(tr("Node.js (example):", "Node.js(示例):"));
|
|
356
|
+
if (IS_WIN) console.log(" nvm install 22.22.0 && nvm use 22.22.0");
|
|
357
|
+
else console.log(" nvm install 22 && nvm use 22");
|
|
358
|
+
console.log("");
|
|
359
|
+
}
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log("");
|
|
364
|
+
info(tr("Environment check passed ✓", "环境校验通过 ✓"));
|
|
365
|
+
console.log("");
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function checkOpenClaw() {
|
|
369
|
+
if (process.env.SKIP_OPENCLAW === "1") {
|
|
370
|
+
info(tr("Skipping OpenClaw check (SKIP_OPENCLAW=1)", "跳过 OpenClaw 校验 (SKIP_OPENCLAW=1)"));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
info(tr("Checking OpenClaw...", "正在校验 OpenClaw..."));
|
|
375
|
+
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
376
|
+
if (result.code === 0) {
|
|
377
|
+
info(tr("OpenClaw detected ✓", "OpenClaw 已安装 ✓"));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
err(tr("OpenClaw not found. Install it manually, then rerun this script.", "未检测到 OpenClaw,请先手动安装后再执行本脚本"));
|
|
382
|
+
console.log("");
|
|
383
|
+
console.log(tr("Recommended command:", "推荐命令:"));
|
|
384
|
+
console.log(` npm install -g openclaw --registry ${NPM_REGISTRY}`);
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log(" openclaw --version");
|
|
387
|
+
console.log(" openclaw onboard");
|
|
388
|
+
console.log("");
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function installOpenViking() {
|
|
393
|
+
if (process.env.SKIP_OPENVIKING === "1") {
|
|
394
|
+
info(tr("Skipping OpenViking install (SKIP_OPENVIKING=1)", "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)"));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const python = await checkPython();
|
|
399
|
+
if (!python.cmd) {
|
|
400
|
+
err(tr("Python check failed.", "Python 校验失败"));
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const py = python.cmd;
|
|
405
|
+
|
|
406
|
+
if (openvikingRepo && existsSync(join(openvikingRepo, "pyproject.toml"))) {
|
|
407
|
+
info(tr(`Installing OpenViking from source (editable): ${openvikingRepo}`, `正在从源码安装 OpenViking(可编辑): ${openvikingRepo}`));
|
|
408
|
+
await run(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { silent: true });
|
|
409
|
+
await run(py, ["-m", "pip", "install", "-e", openvikingRepo]);
|
|
410
|
+
openvikingPythonPath = py;
|
|
411
|
+
info(tr("OpenViking installed ✓ (source)", "OpenViking 安装完成 ✓(源码)"));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
info(tr("Installing OpenViking from PyPI...", "正在安装 OpenViking (PyPI)..."));
|
|
416
|
+
info(tr(`Using pip index: ${PIP_INDEX_URL}`, `使用 pip 镜像源: ${PIP_INDEX_URL}`));
|
|
417
|
+
|
|
418
|
+
info(`Package: ${OPENVIKING_PIP_SPEC}`);
|
|
419
|
+
await runCapture(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
420
|
+
const installResult = await runLiveCapture(
|
|
421
|
+
py,
|
|
422
|
+
["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
423
|
+
{ shell: false },
|
|
424
|
+
);
|
|
425
|
+
if (installResult.code === 0) {
|
|
426
|
+
openvikingPythonPath = py;
|
|
427
|
+
info(tr("OpenViking installed ✓", "OpenViking 安装完成 ✓"));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const installOutput = `${installResult.out}\n${installResult.err}`;
|
|
432
|
+
const shouldTryVenv = !IS_WIN && /externally-managed-environment|externally managed|No module named pip/i.test(installOutput);
|
|
433
|
+
if (shouldTryVenv) {
|
|
434
|
+
const venvDir = join(OPENVIKING_DIR, "venv");
|
|
435
|
+
const venvPy = IS_WIN ? join(venvDir, "Scripts", "python.exe") : join(venvDir, "bin", "python");
|
|
436
|
+
|
|
437
|
+
if (existsSync(venvPy)) {
|
|
438
|
+
const reuseCheck = await runCapture(venvPy, ["-c", "import openviking"], { shell: false });
|
|
439
|
+
if (reuseCheck.code === 0) {
|
|
440
|
+
await runLiveCapture(
|
|
441
|
+
venvPy,
|
|
442
|
+
["-m", "pip", "install", "--progress-bar", "on", "-U", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
443
|
+
{ shell: false },
|
|
444
|
+
);
|
|
445
|
+
openvikingPythonPath = venvPy;
|
|
446
|
+
info(tr("OpenViking installed ✓ (venv)", "OpenViking 安装完成 ✓(虚拟环境)"));
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
await mkdir(OPENVIKING_DIR, { recursive: true });
|
|
452
|
+
const venvCreate = await runCapture(py, ["-m", "venv", venvDir], { shell: false });
|
|
453
|
+
if (venvCreate.code !== 0) {
|
|
454
|
+
err(tr("Could not create venv. Install python3-venv or use OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES=1", "无法创建虚拟环境,请安装 python3-venv 或设置 OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES=1"));
|
|
455
|
+
console.log(venvCreate.err || venvCreate.out);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
await runCapture(venvPy, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
460
|
+
const venvInstall = await runLiveCapture(
|
|
461
|
+
venvPy,
|
|
462
|
+
["-m", "pip", "install", "--progress-bar", "on", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
463
|
+
{ shell: false },
|
|
464
|
+
);
|
|
465
|
+
if (venvInstall.code === 0) {
|
|
466
|
+
openvikingPythonPath = venvPy;
|
|
467
|
+
info(tr("OpenViking installed ✓ (venv)", "OpenViking 安装完成 ✓(虚拟环境)"));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
err(tr("OpenViking install failed in venv.", "在虚拟环境中安装 OpenViking 失败。"));
|
|
472
|
+
console.log(venvInstall.err || venvInstall.out);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (process.env.OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES === "1") {
|
|
477
|
+
const systemInstall = await runLiveCapture(
|
|
478
|
+
py,
|
|
479
|
+
["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages", OPENVIKING_PIP_SPEC, "-i", PIP_INDEX_URL],
|
|
480
|
+
{ shell: false },
|
|
481
|
+
);
|
|
482
|
+
if (systemInstall.code === 0) {
|
|
483
|
+
openvikingPythonPath = py;
|
|
484
|
+
info(tr("OpenViking installed ✓ (system)", "OpenViking 安装完成 ✓(系统)"));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
err(tr("OpenViking install failed. Check Python >= 3.10 and pip.", "OpenViking 安装失败,请检查 Python >= 3.10 及 pip"));
|
|
490
|
+
console.log(installResult.err || installResult.out);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function configureOvConf() {
|
|
495
|
+
await mkdir(OPENVIKING_DIR, { recursive: true });
|
|
496
|
+
|
|
497
|
+
let workspace = join(OPENVIKING_DIR, "data");
|
|
498
|
+
let serverPort = String(DEFAULT_SERVER_PORT);
|
|
499
|
+
let agfsPort = String(DEFAULT_AGFS_PORT);
|
|
500
|
+
let vlmModel = DEFAULT_VLM_MODEL;
|
|
501
|
+
let embeddingModel = DEFAULT_EMBED_MODEL;
|
|
502
|
+
let vlmApiKey = process.env.OPENVIKING_VLM_API_KEY || process.env.OPENVIKING_ARK_API_KEY || "";
|
|
503
|
+
let embeddingApiKey = process.env.OPENVIKING_EMBEDDING_API_KEY || process.env.OPENVIKING_ARK_API_KEY || "";
|
|
504
|
+
|
|
505
|
+
if (!installYes) {
|
|
506
|
+
console.log("");
|
|
507
|
+
workspace = await question(tr("OpenViking workspace path", "OpenViking 数据目录"), workspace);
|
|
508
|
+
serverPort = await question(tr("OpenViking HTTP port", "OpenViking HTTP 端口"), serverPort);
|
|
509
|
+
agfsPort = await question(tr("AGFS port", "AGFS 端口"), agfsPort);
|
|
510
|
+
vlmModel = await question(tr("VLM model", "VLM 模型"), vlmModel);
|
|
511
|
+
embeddingModel = await question(tr("Embedding model", "Embedding 模型"), embeddingModel);
|
|
512
|
+
console.log(tr("VLM and Embedding API keys can differ. Leave empty to edit ov.conf later.", "说明:VLM 与 Embedding 的 API Key 可分别填写,留空可稍后在 ov.conf 修改。"));
|
|
513
|
+
const vlmInput = await question(tr("VLM API key (optional)", "VLM API Key(可留空)"), "");
|
|
514
|
+
const embInput = await question(tr("Embedding API key (optional)", "Embedding API Key(可留空)"), "");
|
|
515
|
+
if (vlmInput) vlmApiKey = vlmInput;
|
|
516
|
+
if (embInput) embeddingApiKey = embInput;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
selectedServerPort = Number.parseInt(serverPort, 10) || DEFAULT_SERVER_PORT;
|
|
520
|
+
const agfsPortNum = Number.parseInt(agfsPort, 10) || DEFAULT_AGFS_PORT;
|
|
521
|
+
|
|
522
|
+
await mkdir(workspace, { recursive: true });
|
|
523
|
+
|
|
524
|
+
const config = {
|
|
525
|
+
server: {
|
|
526
|
+
host: "127.0.0.1",
|
|
527
|
+
port: selectedServerPort,
|
|
528
|
+
root_api_key: null,
|
|
529
|
+
cors_origins: ["*"],
|
|
530
|
+
},
|
|
531
|
+
storage: {
|
|
532
|
+
workspace,
|
|
533
|
+
vectordb: { name: "context", backend: "local", project: "default" },
|
|
534
|
+
agfs: { port: agfsPortNum, log_level: "warn", backend: "local", timeout: 10, retry_times: 3 },
|
|
535
|
+
},
|
|
536
|
+
embedding: {
|
|
537
|
+
dense: {
|
|
538
|
+
backend: "volcengine",
|
|
539
|
+
api_key: embeddingApiKey || null,
|
|
540
|
+
model: embeddingModel,
|
|
541
|
+
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
542
|
+
dimension: 1024,
|
|
543
|
+
input: "multimodal",
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
vlm: {
|
|
547
|
+
backend: "volcengine",
|
|
548
|
+
api_key: vlmApiKey || null,
|
|
549
|
+
model: vlmModel,
|
|
550
|
+
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
551
|
+
temperature: 0.1,
|
|
552
|
+
max_retries: 3,
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const configPath = join(OPENVIKING_DIR, "ov.conf");
|
|
557
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
558
|
+
info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function downloadPluginFile(relPath, required, index, total) {
|
|
562
|
+
const fileName = relPath.split("/").pop();
|
|
563
|
+
const url = `${GH_RAW}/${relPath}`;
|
|
564
|
+
const maxRetries = 3;
|
|
565
|
+
|
|
566
|
+
process.stdout.write(` [${index}/${total}] ${fileName} `);
|
|
567
|
+
|
|
568
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
569
|
+
try {
|
|
570
|
+
const response = await fetch(url);
|
|
571
|
+
if (response.ok) {
|
|
572
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
573
|
+
await writeFile(join(PLUGIN_DEST, fileName), buffer);
|
|
574
|
+
console.log("✓");
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (!required && response.status === 404) {
|
|
578
|
+
console.log(tr("(not present in target branch, skipped)", "(目标分支不存在,已跳过)"));
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
} catch {}
|
|
582
|
+
|
|
583
|
+
if (attempt < maxRetries) {
|
|
584
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (fileName === ".gitignore") {
|
|
589
|
+
console.log(tr("(retries failed, using minimal .gitignore)", "(重试失败,使用最小 .gitignore)"));
|
|
590
|
+
await writeFile(join(PLUGIN_DEST, fileName), "node_modules/\n", "utf8");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.log("");
|
|
595
|
+
err(tr(`Download failed: ${url}`, `下载失败: ${url}`));
|
|
596
|
+
process.exit(1);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function downloadPlugin() {
|
|
600
|
+
await mkdir(PLUGIN_DEST, { recursive: true });
|
|
601
|
+
const files = [
|
|
602
|
+
...REQUIRED_PLUGIN_FILES.map((relPath) => ({ relPath, required: true })),
|
|
603
|
+
...OPTIONAL_PLUGIN_FILES.map((relPath) => ({ relPath, required: false })),
|
|
604
|
+
];
|
|
605
|
+
|
|
606
|
+
info(tr(`Downloading memory-openviking plugin from ${REPO}@${BRANCH}...`, `正在从 ${REPO}@${BRANCH} 下载 memory-openviking 插件...`));
|
|
607
|
+
for (let i = 0; i < files.length; i++) {
|
|
608
|
+
const file = files[i];
|
|
609
|
+
await downloadPluginFile(file.relPath, file.required, i + 1, files.length);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
613
|
+
await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: PLUGIN_DEST, silent: false });
|
|
614
|
+
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function configureOpenClawPlugin(pluginPath = PLUGIN_DEST) {
|
|
618
|
+
info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
|
|
619
|
+
|
|
620
|
+
const configPath = join(OPENCLAW_DIR, "openclaw.json");
|
|
621
|
+
let config = {};
|
|
622
|
+
|
|
623
|
+
if (existsSync(configPath)) {
|
|
624
|
+
try {
|
|
625
|
+
const raw = await readFile(configPath, "utf8");
|
|
626
|
+
if (raw.trim()) config = JSON.parse(raw);
|
|
627
|
+
} catch {
|
|
628
|
+
warn(tr("Existing openclaw.json invalid. Rebuilding required sections.", "已有 openclaw.json 非法,将重建相关配置节点。"));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!config.plugins) config.plugins = {};
|
|
633
|
+
if (!config.gateway) config.gateway = {};
|
|
634
|
+
if (!config.plugins.slots) config.plugins.slots = {};
|
|
635
|
+
if (!config.plugins.load) config.plugins.load = {};
|
|
636
|
+
if (!config.plugins.entries) config.plugins.entries = {};
|
|
637
|
+
|
|
638
|
+
const existingPaths = Array.isArray(config.plugins.load.paths) ? config.plugins.load.paths : [];
|
|
639
|
+
config.plugins.enabled = true;
|
|
640
|
+
config.plugins.allow = ["memory-openviking"];
|
|
641
|
+
config.plugins.slots.memory = "memory-openviking";
|
|
642
|
+
config.plugins.load.paths = [...new Set([...existingPaths, pluginPath])];
|
|
643
|
+
|
|
644
|
+
const pluginConfig = {
|
|
645
|
+
mode: selectedMode,
|
|
646
|
+
targetUri: "viking://user/memories",
|
|
647
|
+
autoRecall: true,
|
|
648
|
+
autoCapture: true,
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
if (selectedMode === "local") {
|
|
652
|
+
pluginConfig.configPath = join(OPENVIKING_DIR, "ov.conf");
|
|
653
|
+
pluginConfig.port = selectedServerPort;
|
|
654
|
+
} else {
|
|
655
|
+
pluginConfig.baseUrl = remoteBaseUrl;
|
|
656
|
+
if (remoteApiKey) pluginConfig.apiKey = remoteApiKey;
|
|
657
|
+
if (remoteAgentId) pluginConfig.agentId = remoteAgentId;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
config.plugins.entries["memory-openviking"] = { config: pluginConfig };
|
|
661
|
+
config.gateway.mode = "local";
|
|
662
|
+
|
|
663
|
+
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
664
|
+
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
665
|
+
info(tr("OpenClaw plugin configured", "OpenClaw 插件配置完成"));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
async function resolvePythonPath() {
|
|
669
|
+
if (openvikingPythonPath) return openvikingPythonPath;
|
|
670
|
+
const python = await checkPython();
|
|
671
|
+
const py = python.cmd;
|
|
672
|
+
if (!py) return "";
|
|
673
|
+
|
|
674
|
+
if (IS_WIN) {
|
|
675
|
+
const result = await runCapture("where", [py], { shell: true });
|
|
676
|
+
return result.out.split(/\r?\n/)[0]?.trim() || py;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const result = await runCapture("which", [py], { shell: false });
|
|
680
|
+
return result.out.trim() || py;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async function writeOpenvikingEnv({ includePython }) {
|
|
684
|
+
const needStateDir = OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR;
|
|
685
|
+
const pythonPath = includePython ? await resolvePythonPath() : "";
|
|
686
|
+
if (!needStateDir && !pythonPath) return null;
|
|
687
|
+
|
|
688
|
+
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
689
|
+
|
|
690
|
+
if (IS_WIN) {
|
|
691
|
+
const batLines = ["@echo off"];
|
|
692
|
+
const psLines = [];
|
|
693
|
+
|
|
694
|
+
if (needStateDir) {
|
|
695
|
+
batLines.push(`set "OPENCLAW_STATE_DIR=${OPENCLAW_DIR.replace(/"/g, '""')}"`);
|
|
696
|
+
psLines.push(`$env:OPENCLAW_STATE_DIR = "${OPENCLAW_DIR.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
697
|
+
}
|
|
698
|
+
if (pythonPath) {
|
|
699
|
+
batLines.push(`set "OPENVIKING_PYTHON=${pythonPath.replace(/"/g, '""')}"`);
|
|
700
|
+
psLines.push(`$env:OPENVIKING_PYTHON = "${pythonPath.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
const batPath = join(OPENCLAW_DIR, "openviking.env.bat");
|
|
704
|
+
const ps1Path = join(OPENCLAW_DIR, "openviking.env.ps1");
|
|
705
|
+
await writeFile(batPath, `${batLines.join("\r\n")}\r\n`, "utf8");
|
|
706
|
+
await writeFile(ps1Path, `${psLines.join("\n")}\n`, "utf8");
|
|
707
|
+
|
|
708
|
+
info(tr(`Environment file generated: ${batPath}`, `已生成环境文件: ${batPath}`));
|
|
709
|
+
return { shellPath: batPath, powershellPath: ps1Path };
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
const lines = [];
|
|
713
|
+
if (needStateDir) {
|
|
714
|
+
lines.push(`export OPENCLAW_STATE_DIR='${OPENCLAW_DIR.replace(/'/g, "'\"'\"'")}'`);
|
|
715
|
+
}
|
|
716
|
+
if (pythonPath) {
|
|
717
|
+
lines.push(`export OPENVIKING_PYTHON='${pythonPath.replace(/'/g, "'\"'\"'")}'`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
const envPath = join(OPENCLAW_DIR, "openviking.env");
|
|
721
|
+
await writeFile(envPath, `${lines.join("\n")}\n`, "utf8");
|
|
722
|
+
info(tr(`Environment file generated: ${envPath}`, `已生成环境文件: ${envPath}`));
|
|
723
|
+
return { shellPath: envPath };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function wrapCommand(command, envFiles) {
|
|
727
|
+
if (!envFiles) return command;
|
|
728
|
+
if (IS_WIN) return `call "${envFiles.shellPath}" && ${command}`;
|
|
729
|
+
return `source '${envFiles.shellPath.replace(/'/g, "'\"'\"'")}' && ${command}`;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async function main() {
|
|
733
|
+
console.log("");
|
|
734
|
+
bold(tr("🦣 OpenClaw + OpenViking Installer", "🦣 OpenClaw + OpenViking 一键安装"));
|
|
735
|
+
console.log("");
|
|
736
|
+
|
|
737
|
+
await selectWorkdir();
|
|
738
|
+
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
739
|
+
|
|
740
|
+
await selectMode();
|
|
741
|
+
info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
|
|
742
|
+
|
|
743
|
+
if (selectedMode === "local") {
|
|
744
|
+
await validateEnvironment();
|
|
745
|
+
await checkOpenClaw();
|
|
746
|
+
await installOpenViking();
|
|
747
|
+
await configureOvConf();
|
|
748
|
+
} else {
|
|
749
|
+
await checkOpenClaw();
|
|
750
|
+
await collectRemoteConfig();
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
let pluginPath;
|
|
754
|
+
const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", "openclaw-memory-plugin") : "";
|
|
755
|
+
if (openvikingRepo && existsSync(join(localPluginDir, "index.ts"))) {
|
|
756
|
+
pluginPath = localPluginDir;
|
|
757
|
+
info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
|
|
758
|
+
if (!existsSync(join(pluginPath, "node_modules"))) {
|
|
759
|
+
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
760
|
+
await run("npm", ["install", "--no-audit", "--no-fund"], { cwd: pluginPath, silent: false });
|
|
761
|
+
}
|
|
762
|
+
} else {
|
|
763
|
+
await downloadPlugin();
|
|
764
|
+
pluginPath = PLUGIN_DEST;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
await configureOpenClawPlugin(pluginPath);
|
|
768
|
+
const envFiles = await writeOpenvikingEnv({
|
|
769
|
+
includePython: selectedMode === "local",
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
console.log("");
|
|
773
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
774
|
+
bold(` ${tr("Installation complete!", "安装完成!")}`);
|
|
775
|
+
bold("═══════════════════════════════════════════════════════════");
|
|
776
|
+
console.log("");
|
|
777
|
+
|
|
778
|
+
if (selectedMode === "local") {
|
|
779
|
+
info(tr("Run these commands to start OpenClaw + OpenViking:", "请按以下命令启动 OpenClaw + OpenViking:"));
|
|
780
|
+
} else {
|
|
781
|
+
info(tr("Run these commands to start OpenClaw:", "请按以下命令启动 OpenClaw:"));
|
|
782
|
+
}
|
|
783
|
+
console.log(` 1) ${wrapCommand("openclaw --version", envFiles)}`);
|
|
784
|
+
console.log(` 2) ${wrapCommand("openclaw onboard", envFiles)}`);
|
|
785
|
+
console.log(` 3) ${wrapCommand("openclaw gateway", envFiles)}`);
|
|
786
|
+
console.log(` 4) ${wrapCommand("openclaw status", envFiles)}`);
|
|
787
|
+
console.log("");
|
|
788
|
+
|
|
789
|
+
if (selectedMode === "local") {
|
|
790
|
+
info(tr(`You can edit the config freely: ${OPENVIKING_DIR}/ov.conf`, `你可以按需自由修改配置文件: ${OPENVIKING_DIR}/ov.conf`));
|
|
791
|
+
} else {
|
|
792
|
+
info(tr(`Remote server: ${remoteBaseUrl}`, `远程服务器: ${remoteBaseUrl}`));
|
|
793
|
+
}
|
|
794
|
+
console.log("");
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
main().catch((error) => {
|
|
798
|
+
console.error(error);
|
|
799
|
+
process.exit(1);
|
|
800
|
+
});
|