openclaw-openviking-setup-helper 0.2.8 → 0.2.9-dev.1
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 +528 -108
- package/package.json +2 -2
package/install.js
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Direct run:
|
|
13
13
|
* node install.js [ -y | --yes ] [ --zh ] [ --workdir PATH ]
|
|
14
|
-
* [ --openviking-version=V ] [ --repo=PATH ]
|
|
14
|
+
* [ --plugin-version=TAG ] [ --openviking-version=V ] [ --repo=PATH ]
|
|
15
15
|
*
|
|
16
16
|
* Environment variables:
|
|
17
|
-
* REPO, BRANCH, OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
|
|
17
|
+
* REPO, PLUGIN_VERSION (or BRANCH), OPENVIKING_INSTALL_YES, SKIP_OPENCLAW, SKIP_OPENVIKING
|
|
18
18
|
* OPENVIKING_VERSION Pip install openviking==VERSION (omit for latest)
|
|
19
19
|
* OPENVIKING_REPO Repo path: source install (pip -e) + local plugin (default: off)
|
|
20
20
|
* NPM_REGISTRY, PIP_INDEX_URL
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { spawn } from "node:child_process";
|
|
26
|
-
import { mkdir,
|
|
26
|
+
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
27
27
|
import { existsSync, readdirSync } from "node:fs";
|
|
28
|
-
import { dirname, join } from "node:path";
|
|
28
|
+
import { dirname, join, relative } from "node:path";
|
|
29
29
|
import { createInterface } from "node:readline";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
|
|
32
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
let REPO = process.env.REPO || "volcengine/OpenViking";
|
|
35
|
+
// PLUGIN_VERSION takes precedence over BRANCH (legacy)
|
|
36
|
+
let PLUGIN_VERSION = process.env.PLUGIN_VERSION || process.env.BRANCH || "main";
|
|
37
37
|
const NPM_REGISTRY = process.env.NPM_REGISTRY || "https://registry.npmmirror.com";
|
|
38
|
-
const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://
|
|
38
|
+
const PIP_INDEX_URL = process.env.PIP_INDEX_URL || "https://mirrors.volces.com/pypi/simple/";
|
|
39
39
|
|
|
40
40
|
const IS_WIN = process.platform === "win32";
|
|
41
41
|
const HOME = process.env.HOME || process.env.USERPROFILE || "";
|
|
42
42
|
|
|
43
43
|
const DEFAULT_OPENCLAW_DIR = join(HOME, ".openclaw");
|
|
44
44
|
let OPENCLAW_DIR = DEFAULT_OPENCLAW_DIR;
|
|
45
|
-
let PLUGIN_DEST =
|
|
45
|
+
let PLUGIN_DEST = ""; // Will be set after resolving plugin config
|
|
46
46
|
|
|
47
47
|
const OPENVIKING_DIR = join(HOME, ".openviking");
|
|
48
48
|
|
|
@@ -51,21 +51,35 @@ const DEFAULT_AGFS_PORT = 1833;
|
|
|
51
51
|
const DEFAULT_VLM_MODEL = "doubao-seed-2-0-pro-260215";
|
|
52
52
|
const DEFAULT_EMBED_MODEL = "doubao-embedding-vision-251215";
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
|
|
54
|
+
// Fallback configs for old versions without manifest
|
|
55
|
+
const FALLBACK_LEGACY = {
|
|
56
|
+
dir: "openclaw-memory-plugin",
|
|
57
|
+
id: "memory-openviking",
|
|
58
|
+
kind: "memory",
|
|
59
|
+
slot: "memory",
|
|
60
|
+
required: ["index.ts", "config.ts", "openclaw.plugin.json", "package.json"],
|
|
61
|
+
optional: ["package-lock.json", ".gitignore"],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const FALLBACK_CURRENT = {
|
|
65
|
+
dir: "openclaw-plugin",
|
|
66
|
+
id: "openviking",
|
|
67
|
+
kind: "context-engine",
|
|
68
|
+
slot: "contextEngine",
|
|
69
|
+
required: ["index.ts", "config.ts", "openclaw.plugin.json", "package.json"],
|
|
70
|
+
optional: ["context-engine.ts", "client.ts", "process-manager.ts", "memory-ranking.ts", "text-utils.ts", "session-transcript-repair.ts", "tool-call-id.ts", "tsconfig.json", "package-lock.json", ".gitignore"],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Resolved plugin config (set by resolvePluginConfig)
|
|
74
|
+
let resolvedPluginDir = "";
|
|
75
|
+
let resolvedPluginId = "";
|
|
76
|
+
let resolvedPluginKind = "";
|
|
77
|
+
let resolvedPluginSlot = "";
|
|
78
|
+
let resolvedFilesRequired = [];
|
|
79
|
+
let resolvedFilesOptional = [];
|
|
80
|
+
let resolvedNpmOmitDev = true;
|
|
81
|
+
let resolvedMinOpenclawVersion = "";
|
|
82
|
+
let resolvedMinOpenvikingVersion = "";
|
|
69
83
|
|
|
70
84
|
let installYes = process.env.OPENVIKING_INSTALL_YES === "1";
|
|
71
85
|
let langZh = false;
|
|
@@ -102,38 +116,86 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
102
116
|
i += 1;
|
|
103
117
|
continue;
|
|
104
118
|
}
|
|
119
|
+
if (arg.startsWith("--plugin-version=")) {
|
|
120
|
+
PLUGIN_VERSION = arg.slice("--plugin-version=".length).trim();
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (arg === "--plugin-version") {
|
|
124
|
+
const version = argv[i + 1]?.trim();
|
|
125
|
+
if (!version) {
|
|
126
|
+
console.error("--plugin-version requires a value");
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
PLUGIN_VERSION = version;
|
|
130
|
+
i += 1;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
105
133
|
if (arg.startsWith("--openviking-version=")) {
|
|
106
134
|
openvikingVersion = arg.slice("--openviking-version=".length).trim();
|
|
107
135
|
continue;
|
|
108
136
|
}
|
|
137
|
+
if (arg === "--openviking-version") {
|
|
138
|
+
const version = argv[i + 1]?.trim();
|
|
139
|
+
if (!version) {
|
|
140
|
+
console.error("--openviking-version requires a value");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
openvikingVersion = version;
|
|
144
|
+
i += 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
109
147
|
if (arg.startsWith("--repo=")) {
|
|
110
148
|
openvikingRepo = arg.slice("--repo=".length).trim();
|
|
111
149
|
continue;
|
|
112
150
|
}
|
|
151
|
+
if (arg.startsWith("--github-repo=")) {
|
|
152
|
+
REPO = arg.slice("--github-repo=".length).trim();
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (arg === "--github-repo") {
|
|
156
|
+
const repo = argv[i + 1]?.trim();
|
|
157
|
+
if (!repo) {
|
|
158
|
+
console.error("--github-repo requires a value (e.g. owner/repo)");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
REPO = repo;
|
|
162
|
+
i += 1;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
113
165
|
if (arg === "-h" || arg === "--help") {
|
|
114
166
|
printHelp();
|
|
115
167
|
process.exit(0);
|
|
116
168
|
}
|
|
117
169
|
}
|
|
118
170
|
|
|
119
|
-
const OPENVIKING_PIP_SPEC = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
|
|
120
|
-
|
|
121
171
|
function setOpenClawDir(dir) {
|
|
122
172
|
OPENCLAW_DIR = dir;
|
|
123
|
-
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", "memory-openviking");
|
|
124
173
|
}
|
|
125
174
|
|
|
126
175
|
function printHelp() {
|
|
127
|
-
console.log("Usage: node install.js [
|
|
176
|
+
console.log("Usage: node install.js [ OPTIONS ]");
|
|
177
|
+
console.log("");
|
|
178
|
+
console.log("Options:");
|
|
179
|
+
console.log(" --github-repo=OWNER/REPO GitHub repository (default: volcengine/OpenViking)");
|
|
180
|
+
console.log(" --plugin-version=TAG Plugin version (Git tag, e.g. v0.2.9, default: main)");
|
|
181
|
+
console.log(" --openviking-version=V OpenViking PyPI version (e.g. 0.2.9, default: latest)");
|
|
182
|
+
console.log(" --workdir PATH OpenClaw config directory (default: ~/.openclaw)");
|
|
183
|
+
console.log(" --repo=PATH Use local OpenViking repo at PATH (pip -e + local plugin)");
|
|
184
|
+
console.log(" -y, --yes Non-interactive (use defaults)");
|
|
185
|
+
console.log(" --zh Chinese prompts");
|
|
186
|
+
console.log(" -h, --help This help");
|
|
187
|
+
console.log("");
|
|
188
|
+
console.log("Examples:");
|
|
189
|
+
console.log(" # Install latest version");
|
|
190
|
+
console.log(" node install.js");
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log(" # Install from a fork repository");
|
|
193
|
+
console.log(" node install.js --github-repo=yourname/OpenViking --plugin-version=dev-branch");
|
|
128
194
|
console.log("");
|
|
129
|
-
console.log("
|
|
130
|
-
console.log(" --
|
|
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");
|
|
195
|
+
console.log(" # Install specific plugin version");
|
|
196
|
+
console.log(" node install.js --plugin-version=v0.2.8");
|
|
135
197
|
console.log("");
|
|
136
|
-
console.log("Env:
|
|
198
|
+
console.log("Env: REPO, PLUGIN_VERSION, OPENVIKING_VERSION, SKIP_OPENCLAW, SKIP_OPENVIKING, NPM_REGISTRY, PIP_INDEX_URL");
|
|
137
199
|
}
|
|
138
200
|
|
|
139
201
|
function tr(en, zh) {
|
|
@@ -234,8 +296,19 @@ function question(prompt, defaultValue = "") {
|
|
|
234
296
|
});
|
|
235
297
|
}
|
|
236
298
|
|
|
299
|
+
async function resolveAbsoluteCommand(cmd) {
|
|
300
|
+
if (cmd.startsWith("/") || (IS_WIN && /^[A-Za-z]:[/\\]/.test(cmd))) return cmd;
|
|
301
|
+
if (IS_WIN) {
|
|
302
|
+
const r = await runCapture("where", [cmd], { shell: true });
|
|
303
|
+
return r.out.split(/\r?\n/)[0]?.trim() || cmd;
|
|
304
|
+
}
|
|
305
|
+
const r = await runCapture("which", [cmd], { shell: false });
|
|
306
|
+
return r.out.trim() || cmd;
|
|
307
|
+
}
|
|
308
|
+
|
|
237
309
|
async function checkPython() {
|
|
238
|
-
const
|
|
310
|
+
const raw = process.env.OPENVIKING_PYTHON || (IS_WIN ? "python" : "python3");
|
|
311
|
+
const py = await resolveAbsoluteCommand(raw);
|
|
239
312
|
const result = await runCapture(py, ["-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"]);
|
|
240
313
|
if (result.code !== 0 || !result.out) {
|
|
241
314
|
return {
|
|
@@ -389,6 +462,222 @@ async function checkOpenClaw() {
|
|
|
389
462
|
process.exit(1);
|
|
390
463
|
}
|
|
391
464
|
|
|
465
|
+
// Compare versions: returns true if v1 >= v2
|
|
466
|
+
function versionGte(v1, v2) {
|
|
467
|
+
const parseVersion = (v) => {
|
|
468
|
+
const cleaned = v.replace(/^v/, "").replace(/-.*$/, "");
|
|
469
|
+
const parts = cleaned.split(".").map((p) => Number.parseInt(p, 10) || 0);
|
|
470
|
+
while (parts.length < 3) parts.push(0);
|
|
471
|
+
return parts;
|
|
472
|
+
};
|
|
473
|
+
const [a1, a2, a3] = parseVersion(v1);
|
|
474
|
+
const [b1, b2, b3] = parseVersion(v2);
|
|
475
|
+
if (a1 !== b1) return a1 > b1;
|
|
476
|
+
if (a2 !== b2) return a2 > b2;
|
|
477
|
+
return a3 >= b3;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function isSemverLike(value) {
|
|
481
|
+
return /^v?\d+(\.\d+){1,2}$/.test(value);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Detect OpenClaw version
|
|
485
|
+
async function detectOpenClawVersion() {
|
|
486
|
+
try {
|
|
487
|
+
const result = await runCapture("openclaw", ["--version"], { shell: IS_WIN });
|
|
488
|
+
if (result.code === 0 && result.out) {
|
|
489
|
+
const match = result.out.match(/\d+\.\d+(\.\d+)?/);
|
|
490
|
+
if (match) return match[0];
|
|
491
|
+
}
|
|
492
|
+
} catch {}
|
|
493
|
+
return "0.0.0";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Try to fetch a URL, return response text or null
|
|
497
|
+
async function tryFetch(url, timeout = 15000) {
|
|
498
|
+
try {
|
|
499
|
+
const controller = new AbortController();
|
|
500
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
501
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
502
|
+
clearTimeout(timeoutId);
|
|
503
|
+
if (response.ok) {
|
|
504
|
+
return await response.text();
|
|
505
|
+
}
|
|
506
|
+
} catch {}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Check if a remote file exists
|
|
511
|
+
async function testRemoteFile(url) {
|
|
512
|
+
try {
|
|
513
|
+
const controller = new AbortController();
|
|
514
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
515
|
+
const response = await fetch(url, { method: "HEAD", signal: controller.signal });
|
|
516
|
+
clearTimeout(timeoutId);
|
|
517
|
+
return response.ok;
|
|
518
|
+
} catch {}
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Resolve plugin configuration from manifest or fallback
|
|
523
|
+
async function resolvePluginConfig() {
|
|
524
|
+
const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
|
|
525
|
+
|
|
526
|
+
info(tr(`Resolving plugin configuration for version: ${PLUGIN_VERSION}`, `正在解析插件配置,版本: ${PLUGIN_VERSION}`));
|
|
527
|
+
|
|
528
|
+
let pluginDir = "";
|
|
529
|
+
let manifestData = null;
|
|
530
|
+
|
|
531
|
+
// Try to detect plugin directory and download manifest
|
|
532
|
+
const manifestCurrent = await tryFetch(`${ghRaw}/examples/openclaw-plugin/install-manifest.json`);
|
|
533
|
+
if (manifestCurrent) {
|
|
534
|
+
pluginDir = "openclaw-plugin";
|
|
535
|
+
try {
|
|
536
|
+
manifestData = JSON.parse(manifestCurrent);
|
|
537
|
+
} catch {}
|
|
538
|
+
info(tr("Found manifest in openclaw-plugin", "在 openclaw-plugin 中找到 manifest"));
|
|
539
|
+
} else {
|
|
540
|
+
const manifestLegacy = await tryFetch(`${ghRaw}/examples/openclaw-memory-plugin/install-manifest.json`);
|
|
541
|
+
if (manifestLegacy) {
|
|
542
|
+
pluginDir = "openclaw-memory-plugin";
|
|
543
|
+
try {
|
|
544
|
+
manifestData = JSON.parse(manifestLegacy);
|
|
545
|
+
} catch {}
|
|
546
|
+
info(tr("Found manifest in openclaw-memory-plugin", "在 openclaw-memory-plugin 中找到 manifest"));
|
|
547
|
+
} else if (await testRemoteFile(`${ghRaw}/examples/openclaw-plugin/index.ts`)) {
|
|
548
|
+
pluginDir = "openclaw-plugin";
|
|
549
|
+
info(tr("No manifest found, using fallback for openclaw-plugin", "未找到 manifest,使用 openclaw-plugin 回退配置"));
|
|
550
|
+
} else if (await testRemoteFile(`${ghRaw}/examples/openclaw-memory-plugin/index.ts`)) {
|
|
551
|
+
pluginDir = "openclaw-memory-plugin";
|
|
552
|
+
info(tr("No manifest found, using fallback for openclaw-memory-plugin", "未找到 manifest,使用 openclaw-memory-plugin 回退配置"));
|
|
553
|
+
} else {
|
|
554
|
+
err(tr(`Cannot find plugin directory for version: ${PLUGIN_VERSION}`, `无法找到版本 ${PLUGIN_VERSION} 的插件目录`));
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
resolvedPluginDir = pluginDir;
|
|
560
|
+
|
|
561
|
+
if (manifestData) {
|
|
562
|
+
resolvedPluginId = manifestData.plugin?.id || "";
|
|
563
|
+
resolvedPluginKind = manifestData.plugin?.kind || "";
|
|
564
|
+
resolvedPluginSlot = manifestData.plugin?.slot || "";
|
|
565
|
+
resolvedMinOpenclawVersion = manifestData.compatibility?.minOpenclawVersion || "";
|
|
566
|
+
resolvedMinOpenvikingVersion = manifestData.compatibility?.minOpenvikingVersion || "";
|
|
567
|
+
resolvedNpmOmitDev = manifestData.npm?.omitDev !== false;
|
|
568
|
+
resolvedFilesRequired = manifestData.files?.required || [];
|
|
569
|
+
resolvedFilesOptional = manifestData.files?.optional || [];
|
|
570
|
+
} else {
|
|
571
|
+
// No manifest — determine plugin identity by package.json name
|
|
572
|
+
let fallbackKey = pluginDir === "openclaw-memory-plugin" ? "legacy" : "current";
|
|
573
|
+
let compatVer = "";
|
|
574
|
+
|
|
575
|
+
const pkgJson = await tryFetch(`${ghRaw}/examples/${pluginDir}/package.json`);
|
|
576
|
+
if (pkgJson) {
|
|
577
|
+
try {
|
|
578
|
+
const pkg = JSON.parse(pkgJson);
|
|
579
|
+
const pkgName = pkg.name || "";
|
|
580
|
+
if (pkgName && pkgName !== "@openclaw/openviking") {
|
|
581
|
+
fallbackKey = "legacy";
|
|
582
|
+
info(tr(`Detected legacy plugin by package name: ${pkgName}`, `通过 package.json 名称检测到旧版插件: ${pkgName}`));
|
|
583
|
+
} else if (pkgName) {
|
|
584
|
+
fallbackKey = "current";
|
|
585
|
+
}
|
|
586
|
+
compatVer = (pkg.engines?.openclaw || "").replace(/^>=?\s*/, "").trim();
|
|
587
|
+
if (compatVer) {
|
|
588
|
+
info(tr(`Read minOpenclawVersion from package.json engines.openclaw: >=${compatVer}`, `从 package.json engines.openclaw 读取到最低版本: >=${compatVer}`));
|
|
589
|
+
}
|
|
590
|
+
} catch {}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const fallback = fallbackKey === "legacy" ? FALLBACK_LEGACY : FALLBACK_CURRENT;
|
|
594
|
+
resolvedPluginDir = pluginDir;
|
|
595
|
+
resolvedPluginId = fallback.id;
|
|
596
|
+
resolvedPluginKind = fallback.kind;
|
|
597
|
+
resolvedPluginSlot = fallback.slot;
|
|
598
|
+
resolvedFilesRequired = fallback.required;
|
|
599
|
+
resolvedFilesOptional = fallback.optional;
|
|
600
|
+
resolvedNpmOmitDev = true;
|
|
601
|
+
|
|
602
|
+
// If no compatVer from package.json, try main branch manifest
|
|
603
|
+
if (!compatVer && PLUGIN_VERSION !== "main") {
|
|
604
|
+
const mainRaw = `https://raw.githubusercontent.com/${REPO}/main`;
|
|
605
|
+
const mainManifest = await tryFetch(`${mainRaw}/examples/openclaw-plugin/install-manifest.json`);
|
|
606
|
+
if (mainManifest) {
|
|
607
|
+
try {
|
|
608
|
+
const m = JSON.parse(mainManifest);
|
|
609
|
+
compatVer = m.compatibility?.minOpenclawVersion || "";
|
|
610
|
+
if (compatVer) {
|
|
611
|
+
info(tr(`Read minOpenclawVersion from main branch manifest: >=${compatVer}`, `从 main 分支 manifest 读取到最低版本: >=${compatVer}`));
|
|
612
|
+
}
|
|
613
|
+
} catch {}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
resolvedMinOpenclawVersion = compatVer || "2026.3.7";
|
|
618
|
+
resolvedMinOpenvikingVersion = "";
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Set plugin destination
|
|
622
|
+
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId);
|
|
623
|
+
|
|
624
|
+
info(tr(`Plugin: ${resolvedPluginId} (${resolvedPluginKind})`, `插件: ${resolvedPluginId} (${resolvedPluginKind})`));
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Check OpenClaw version compatibility
|
|
628
|
+
async function checkOpenClawCompatibility() {
|
|
629
|
+
if (process.env.SKIP_OPENCLAW === "1") {
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const ocVersion = await detectOpenClawVersion();
|
|
634
|
+
info(tr(`Detected OpenClaw version: ${ocVersion}`, `检测到 OpenClaw 版本: ${ocVersion}`));
|
|
635
|
+
|
|
636
|
+
// If no minimum version required, pass
|
|
637
|
+
if (!resolvedMinOpenclawVersion) {
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// If user explicitly requested an old version, pass
|
|
642
|
+
if (PLUGIN_VERSION !== "main" && isSemverLike(PLUGIN_VERSION) && !versionGte(PLUGIN_VERSION, "v0.2.9")) {
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Check compatibility
|
|
647
|
+
if (!versionGte(ocVersion, resolvedMinOpenclawVersion)) {
|
|
648
|
+
err(tr(
|
|
649
|
+
`OpenClaw ${ocVersion} does not support this plugin (requires >= ${resolvedMinOpenclawVersion})`,
|
|
650
|
+
`OpenClaw ${ocVersion} 不支持此插件(需要 >= ${resolvedMinOpenclawVersion})`
|
|
651
|
+
));
|
|
652
|
+
console.log("");
|
|
653
|
+
bold(tr("Please choose one of the following options:", "请选择以下方案之一:"));
|
|
654
|
+
console.log("");
|
|
655
|
+
console.log(` ${tr("Option 1: Upgrade OpenClaw", "方案 1:升级 OpenClaw")}`);
|
|
656
|
+
console.log(` npm update -g openclaw --registry ${NPM_REGISTRY}`);
|
|
657
|
+
console.log("");
|
|
658
|
+
console.log(` ${tr("Option 2: Install legacy plugin (v0.2.8)", "方案 2:安装旧版插件 (v0.2.8)")}`);
|
|
659
|
+
console.log(` node install.js --plugin-version=v0.2.8${langZh ? " --zh" : ""}`);
|
|
660
|
+
console.log("");
|
|
661
|
+
process.exit(1);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
function checkRequestedOpenVikingCompatibility() {
|
|
666
|
+
if (!resolvedMinOpenvikingVersion || !openvikingVersion) return;
|
|
667
|
+
if (versionGte(openvikingVersion, resolvedMinOpenvikingVersion)) return;
|
|
668
|
+
|
|
669
|
+
err(tr(
|
|
670
|
+
`OpenViking ${openvikingVersion} does not support this plugin (requires >= ${resolvedMinOpenvikingVersion})`,
|
|
671
|
+
`OpenViking ${openvikingVersion} 不支持此插件(需要 >= ${resolvedMinOpenvikingVersion})`,
|
|
672
|
+
));
|
|
673
|
+
console.log("");
|
|
674
|
+
console.log(tr(
|
|
675
|
+
"Use a newer OpenViking version, or omit --openviking-version to install the latest release.",
|
|
676
|
+
"请使用更新版本的 OpenViking,或省略 --openviking-version 以安装最新版本。",
|
|
677
|
+
));
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
|
|
392
681
|
async function installOpenViking() {
|
|
393
682
|
if (process.env.SKIP_OPENVIKING === "1") {
|
|
394
683
|
info(tr("Skipping OpenViking install (SKIP_OPENVIKING=1)", "跳过 OpenViking 安装 (SKIP_OPENVIKING=1)"));
|
|
@@ -400,6 +689,12 @@ async function installOpenViking() {
|
|
|
400
689
|
err(tr("Python check failed.", "Python 校验失败"));
|
|
401
690
|
process.exit(1);
|
|
402
691
|
}
|
|
692
|
+
if (!python.ok) {
|
|
693
|
+
warn(tr(
|
|
694
|
+
`${python.detail}. Will attempt to find a suitable Python for pip install.`,
|
|
695
|
+
`${python.detail}。将尝试查找合适的 Python 进行 pip 安装。`,
|
|
696
|
+
));
|
|
697
|
+
}
|
|
403
698
|
|
|
404
699
|
const py = python.cmd;
|
|
405
700
|
|
|
@@ -412,14 +707,20 @@ async function installOpenViking() {
|
|
|
412
707
|
return;
|
|
413
708
|
}
|
|
414
709
|
|
|
415
|
-
|
|
710
|
+
// Determine package spec
|
|
711
|
+
const pkgSpec = openvikingVersion ? `openviking==${openvikingVersion}` : "openviking";
|
|
712
|
+
if (openvikingVersion) {
|
|
713
|
+
info(tr(`Installing OpenViking ${openvikingVersion} from PyPI...`, `正在安装 OpenViking ${openvikingVersion} (PyPI)...`));
|
|
714
|
+
} else {
|
|
715
|
+
info(tr("Installing OpenViking (latest) from PyPI...", "正在安装 OpenViking (最新版) (PyPI)..."));
|
|
716
|
+
}
|
|
416
717
|
info(tr(`Using pip index: ${PIP_INDEX_URL}`, `使用 pip 镜像源: ${PIP_INDEX_URL}`));
|
|
417
718
|
|
|
418
|
-
info(`Package: ${
|
|
719
|
+
info(`Package: ${pkgSpec}`);
|
|
419
720
|
await runCapture(py, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
420
721
|
const installResult = await runLiveCapture(
|
|
421
722
|
py,
|
|
422
|
-
["-m", "pip", "install", "--progress-bar", "on",
|
|
723
|
+
["-m", "pip", "install", "--progress-bar", "on", pkgSpec, "-i", PIP_INDEX_URL],
|
|
423
724
|
{ shell: false },
|
|
424
725
|
);
|
|
425
726
|
if (installResult.code === 0) {
|
|
@@ -439,7 +740,7 @@ async function installOpenViking() {
|
|
|
439
740
|
if (reuseCheck.code === 0) {
|
|
440
741
|
await runLiveCapture(
|
|
441
742
|
venvPy,
|
|
442
|
-
["-m", "pip", "install", "--progress-bar", "on", "-U",
|
|
743
|
+
["-m", "pip", "install", "--progress-bar", "on", "-U", pkgSpec, "-i", PIP_INDEX_URL],
|
|
443
744
|
{ shell: false },
|
|
444
745
|
);
|
|
445
746
|
openvikingPythonPath = venvPy;
|
|
@@ -475,7 +776,7 @@ async function installOpenViking() {
|
|
|
475
776
|
await runCapture(venvPy, ["-m", "pip", "install", "--upgrade", "pip", "-q", "-i", PIP_INDEX_URL], { shell: false });
|
|
476
777
|
const venvInstall = await runLiveCapture(
|
|
477
778
|
venvPy,
|
|
478
|
-
["-m", "pip", "install", "--progress-bar", "on",
|
|
779
|
+
["-m", "pip", "install", "--progress-bar", "on", pkgSpec, "-i", PIP_INDEX_URL],
|
|
479
780
|
{ shell: false },
|
|
480
781
|
);
|
|
481
782
|
if (venvInstall.code === 0) {
|
|
@@ -492,7 +793,7 @@ async function installOpenViking() {
|
|
|
492
793
|
if (process.env.OPENVIKING_ALLOW_BREAK_SYSTEM_PACKAGES === "1") {
|
|
493
794
|
const systemInstall = await runLiveCapture(
|
|
494
795
|
py,
|
|
495
|
-
["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages",
|
|
796
|
+
["-m", "pip", "install", "--progress-bar", "on", "--break-system-packages", pkgSpec, "-i", PIP_INDEX_URL],
|
|
496
797
|
{ shell: false },
|
|
497
798
|
);
|
|
498
799
|
if (systemInstall.code === 0) {
|
|
@@ -549,9 +850,17 @@ async function configureOvConf() {
|
|
|
549
850
|
vectordb: { name: "context", backend: "local", project: "default" },
|
|
550
851
|
agfs: { port: agfsPortNum, log_level: "warn", backend: "local", timeout: 10, retry_times: 3 },
|
|
551
852
|
},
|
|
853
|
+
log: {
|
|
854
|
+
level: "WARNING",
|
|
855
|
+
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
856
|
+
output: "file",
|
|
857
|
+
rotation: true,
|
|
858
|
+
rotation_days: 3,
|
|
859
|
+
rotation_interval: "midnight",
|
|
860
|
+
},
|
|
552
861
|
embedding: {
|
|
553
862
|
dense: {
|
|
554
|
-
|
|
863
|
+
provider: "volcengine",
|
|
555
864
|
api_key: embeddingApiKey || null,
|
|
556
865
|
model: embeddingModel,
|
|
557
866
|
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
@@ -560,7 +869,7 @@ async function configureOvConf() {
|
|
|
560
869
|
},
|
|
561
870
|
},
|
|
562
871
|
vlm: {
|
|
563
|
-
|
|
872
|
+
provider: "volcengine",
|
|
564
873
|
api_key: vlmApiKey || null,
|
|
565
874
|
model: vlmModel,
|
|
566
875
|
api_base: "https://ark.cn-beijing.volces.com/api/v3",
|
|
@@ -574,9 +883,7 @@ async function configureOvConf() {
|
|
|
574
883
|
info(tr(`Config generated: ${configPath}`, `已生成配置: ${configPath}`));
|
|
575
884
|
}
|
|
576
885
|
|
|
577
|
-
async function downloadPluginFile(
|
|
578
|
-
const fileName = relPath.split("/").pop();
|
|
579
|
-
const url = `${GH_RAW}/${relPath}`;
|
|
886
|
+
async function downloadPluginFile(fileName, url, required, index, total) {
|
|
580
887
|
const maxRetries = 3;
|
|
581
888
|
|
|
582
889
|
process.stdout.write(` [${index}/${total}] ${fileName} `);
|
|
@@ -591,7 +898,7 @@ async function downloadPluginFile(relPath, required, index, total) {
|
|
|
591
898
|
return;
|
|
592
899
|
}
|
|
593
900
|
if (!required && response.status === 404) {
|
|
594
|
-
console.log(tr("(
|
|
901
|
+
console.log(tr("(skipped)", "(跳过)"));
|
|
595
902
|
return;
|
|
596
903
|
}
|
|
597
904
|
} catch {}
|
|
@@ -607,98 +914,188 @@ async function downloadPluginFile(relPath, required, index, total) {
|
|
|
607
914
|
return;
|
|
608
915
|
}
|
|
609
916
|
|
|
917
|
+
if (!required) {
|
|
918
|
+
console.log(tr("(skipped)", "(跳过)"));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
610
922
|
console.log("");
|
|
611
923
|
err(tr(`Download failed: ${url}`, `下载失败: ${url}`));
|
|
612
924
|
process.exit(1);
|
|
613
925
|
}
|
|
614
926
|
|
|
615
927
|
async function downloadPlugin() {
|
|
928
|
+
const ghRaw = `https://raw.githubusercontent.com/${REPO}/${PLUGIN_VERSION}`;
|
|
929
|
+
const pluginDir = resolvedPluginDir;
|
|
930
|
+
const total = resolvedFilesRequired.length + resolvedFilesOptional.length;
|
|
931
|
+
|
|
616
932
|
await mkdir(PLUGIN_DEST, { recursive: true });
|
|
617
|
-
const files = [
|
|
618
|
-
...REQUIRED_PLUGIN_FILES.map((relPath) => ({ relPath, required: true })),
|
|
619
|
-
...OPTIONAL_PLUGIN_FILES.map((relPath) => ({ relPath, required: false })),
|
|
620
|
-
];
|
|
621
933
|
|
|
622
|
-
info(tr(`Downloading
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
934
|
+
info(tr(`Downloading plugin from ${REPO}@${PLUGIN_VERSION} (${total} files)...`, `正在从 ${REPO}@${PLUGIN_VERSION} 下载插件(共 ${total} 个文件)...`));
|
|
935
|
+
|
|
936
|
+
let i = 0;
|
|
937
|
+
// Download required files
|
|
938
|
+
for (const name of resolvedFilesRequired) {
|
|
939
|
+
if (!name) continue;
|
|
940
|
+
i++;
|
|
941
|
+
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
942
|
+
await downloadPluginFile(name, url, true, i, total);
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Download optional files
|
|
946
|
+
for (const name of resolvedFilesOptional) {
|
|
947
|
+
if (!name) continue;
|
|
948
|
+
i++;
|
|
949
|
+
const url = `${ghRaw}/examples/${pluginDir}/${name}`;
|
|
950
|
+
await downloadPluginFile(name, url, false, i, total);
|
|
626
951
|
}
|
|
627
952
|
|
|
953
|
+
// npm install
|
|
628
954
|
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
629
|
-
|
|
955
|
+
const npmArgs = resolvedNpmOmitDev
|
|
956
|
+
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
957
|
+
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
958
|
+
await run("npm", npmArgs, { cwd: PLUGIN_DEST, silent: false });
|
|
630
959
|
info(tr(`Plugin deployed: ${PLUGIN_DEST}`, `插件部署完成: ${PLUGIN_DEST}`));
|
|
631
960
|
}
|
|
632
961
|
|
|
633
|
-
async function
|
|
962
|
+
async function deployLocalPlugin(localPluginDir) {
|
|
963
|
+
await rm(PLUGIN_DEST, { recursive: true, force: true });
|
|
964
|
+
await mkdir(PLUGIN_DEST, { recursive: true });
|
|
965
|
+
await cp(localPluginDir, PLUGIN_DEST, {
|
|
966
|
+
recursive: true,
|
|
967
|
+
force: true,
|
|
968
|
+
filter: (sourcePath) => {
|
|
969
|
+
const rel = relative(localPluginDir, sourcePath);
|
|
970
|
+
if (!rel) return true;
|
|
971
|
+
const firstSegment = rel.split(/[\\/]/)[0];
|
|
972
|
+
return firstSegment !== "node_modules" && firstSegment !== ".git";
|
|
973
|
+
},
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
async function configureOpenClawPlugin() {
|
|
634
978
|
info(tr("Configuring OpenClaw plugin...", "正在配置 OpenClaw 插件..."));
|
|
635
979
|
|
|
636
|
-
const
|
|
637
|
-
|
|
980
|
+
const pluginId = resolvedPluginId;
|
|
981
|
+
const pluginSlot = resolvedPluginSlot;
|
|
638
982
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (raw.trim()) config = JSON.parse(raw);
|
|
643
|
-
} catch {
|
|
644
|
-
warn(tr("Existing openclaw.json invalid. Rebuilding required sections.", "已有 openclaw.json 非法,将重建相关配置节点。"));
|
|
645
|
-
}
|
|
983
|
+
const ocEnv = { ...process.env };
|
|
984
|
+
if (OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR) {
|
|
985
|
+
ocEnv.OPENCLAW_STATE_DIR = OPENCLAW_DIR;
|
|
646
986
|
}
|
|
647
987
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
988
|
+
const oc = async (args) => {
|
|
989
|
+
const result = await runCapture("openclaw", args, { env: ocEnv, shell: IS_WIN });
|
|
990
|
+
if (result.code !== 0) {
|
|
991
|
+
const detail = result.err || result.out;
|
|
992
|
+
throw new Error(`openclaw ${args.join(" ")} failed (exit code ${result.code})${detail ? `: ${detail}` : ""}`);
|
|
993
|
+
}
|
|
994
|
+
return result;
|
|
995
|
+
};
|
|
653
996
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
config
|
|
657
|
-
config.plugins.slots.memory = "memory-openviking";
|
|
658
|
-
config.plugins.load.paths = [...new Set([...existingPaths, pluginPath])];
|
|
997
|
+
// Enable plugin (files already deployed to extensions dir by deployPlugin)
|
|
998
|
+
await oc(["plugins", "enable", pluginId]);
|
|
999
|
+
await oc(["config", "set", `plugins.slots.${pluginSlot}`, pluginId]);
|
|
659
1000
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
targetUri: "viking://user/memories",
|
|
663
|
-
autoRecall: true,
|
|
664
|
-
autoCapture: true,
|
|
665
|
-
};
|
|
1001
|
+
// Set gateway mode
|
|
1002
|
+
await oc(["config", "set", "gateway.mode", "local"]);
|
|
666
1003
|
|
|
1004
|
+
// Set plugin config for the selected mode
|
|
667
1005
|
if (selectedMode === "local") {
|
|
668
|
-
|
|
669
|
-
|
|
1006
|
+
const ovConfPath = join(OPENVIKING_DIR, "ov.conf");
|
|
1007
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "local"]);
|
|
1008
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.configPath`, ovConfPath]);
|
|
1009
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.port`, String(selectedServerPort)]);
|
|
670
1010
|
} else {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (
|
|
1011
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.mode`, "remote"]);
|
|
1012
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.baseUrl`, remoteBaseUrl]);
|
|
1013
|
+
if (remoteApiKey) {
|
|
1014
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.apiKey`, remoteApiKey]);
|
|
1015
|
+
}
|
|
1016
|
+
if (remoteAgentId) {
|
|
1017
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.agentId`, remoteAgentId]);
|
|
1018
|
+
}
|
|
674
1019
|
}
|
|
675
1020
|
|
|
676
|
-
|
|
677
|
-
|
|
1021
|
+
// Legacy (memory) plugins need explicit targetUri/autoRecall/autoCapture (new version has defaults in config.ts)
|
|
1022
|
+
if (resolvedPluginKind === "memory") {
|
|
1023
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.targetUri`, "viking://user/memories"]);
|
|
1024
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.autoRecall`, "true", "--json"]);
|
|
1025
|
+
await oc(["config", "set", `plugins.entries.${pluginId}.config.autoCapture`, "true", "--json"]);
|
|
1026
|
+
}
|
|
678
1027
|
|
|
679
|
-
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
680
|
-
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
681
1028
|
info(tr("OpenClaw plugin configured", "OpenClaw 插件配置完成"));
|
|
682
1029
|
}
|
|
683
1030
|
|
|
1031
|
+
async function discoverOpenvikingPython(failedPy) {
|
|
1032
|
+
const candidates = IS_WIN
|
|
1033
|
+
? ["python3", "python", "py -3"]
|
|
1034
|
+
: ["python3.13", "python3.12", "python3.11", "python3.10", "python3", "python"];
|
|
1035
|
+
for (const candidate of candidates) {
|
|
1036
|
+
if (candidate === failedPy) continue;
|
|
1037
|
+
const resolved = await resolveAbsoluteCommand(candidate);
|
|
1038
|
+
if (!resolved || resolved === candidate || resolved === failedPy) continue;
|
|
1039
|
+
const check = await runCapture(resolved, ["-c", "import openviking"], { shell: false });
|
|
1040
|
+
if (check.code === 0) return resolved;
|
|
1041
|
+
}
|
|
1042
|
+
return "";
|
|
1043
|
+
}
|
|
1044
|
+
|
|
684
1045
|
async function resolvePythonPath() {
|
|
685
1046
|
if (openvikingPythonPath) return openvikingPythonPath;
|
|
686
1047
|
const python = await checkPython();
|
|
687
|
-
|
|
688
|
-
if (!py) return "";
|
|
689
|
-
|
|
690
|
-
if (IS_WIN) {
|
|
691
|
-
const result = await runCapture("where", [py], { shell: true });
|
|
692
|
-
return result.out.split(/\r?\n/)[0]?.trim() || py;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
const result = await runCapture("which", [py], { shell: false });
|
|
696
|
-
return result.out.trim() || py;
|
|
1048
|
+
return python.cmd || "";
|
|
697
1049
|
}
|
|
698
1050
|
|
|
699
1051
|
async function writeOpenvikingEnv({ includePython }) {
|
|
700
1052
|
const needStateDir = OPENCLAW_DIR !== DEFAULT_OPENCLAW_DIR;
|
|
701
|
-
|
|
1053
|
+
let pythonPath = "";
|
|
1054
|
+
if (includePython) {
|
|
1055
|
+
pythonPath = await resolvePythonPath();
|
|
1056
|
+
if (!pythonPath) {
|
|
1057
|
+
pythonPath = (process.env.OPENVIKING_PYTHON || "").trim() || (IS_WIN ? "python" : "python3");
|
|
1058
|
+
warn(
|
|
1059
|
+
tr(
|
|
1060
|
+
"Could not resolve absolute Python path; wrote fallback OPENVIKING_PYTHON to openviking.env. Edit that file if OpenViking fails to start.",
|
|
1061
|
+
"未能解析 Python 绝对路径,已在 openviking.env 中写入后备值。若启动失败请手动修改为虚拟环境中的 python 可执行文件路径。",
|
|
1062
|
+
),
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Verify the resolved Python can actually import openviking
|
|
1067
|
+
if (pythonPath) {
|
|
1068
|
+
const verify = await runCapture(pythonPath, ["-c", "import openviking"], { shell: false });
|
|
1069
|
+
if (verify.code !== 0) {
|
|
1070
|
+
warn(
|
|
1071
|
+
tr(
|
|
1072
|
+
`Resolved Python (${pythonPath}) cannot import openviking. The pip install target may differ from the runtime python3.`,
|
|
1073
|
+
`解析到的 Python(${pythonPath})无法 import openviking。pip 安装目标可能与运行时的 python3 不一致。`,
|
|
1074
|
+
),
|
|
1075
|
+
);
|
|
1076
|
+
// Try to discover the correct Python via pip show
|
|
1077
|
+
const corrected = await discoverOpenvikingPython(pythonPath);
|
|
1078
|
+
if (corrected) {
|
|
1079
|
+
info(
|
|
1080
|
+
tr(
|
|
1081
|
+
`Auto-corrected OPENVIKING_PYTHON to ${corrected}`,
|
|
1082
|
+
`已自动修正 OPENVIKING_PYTHON 为 ${corrected}`,
|
|
1083
|
+
),
|
|
1084
|
+
);
|
|
1085
|
+
pythonPath = corrected;
|
|
1086
|
+
} else {
|
|
1087
|
+
warn(
|
|
1088
|
+
tr(
|
|
1089
|
+
`Could not auto-detect the correct Python. Edit OPENVIKING_PYTHON in the env file manually.`,
|
|
1090
|
+
`无法自动检测正确的 Python。请手动修改 env 文件中的 OPENVIKING_PYTHON。`,
|
|
1091
|
+
),
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// Remote mode + default state dir + no python line → nothing to persist
|
|
702
1099
|
if (!needStateDir && !pythonPath) return null;
|
|
703
1100
|
|
|
704
1101
|
await mkdir(OPENCLAW_DIR, { recursive: true });
|
|
@@ -752,6 +1149,11 @@ async function main() {
|
|
|
752
1149
|
|
|
753
1150
|
await selectWorkdir();
|
|
754
1151
|
info(tr(`Target: ${OPENCLAW_DIR}`, `目标实例: ${OPENCLAW_DIR}`));
|
|
1152
|
+
info(tr(`Repository: ${REPO}`, `仓库: ${REPO}`));
|
|
1153
|
+
info(tr(`Plugin version: ${PLUGIN_VERSION}`, `插件版本: ${PLUGIN_VERSION}`));
|
|
1154
|
+
if (openvikingVersion) {
|
|
1155
|
+
info(tr(`OpenViking version: ${openvikingVersion}`, `OpenViking 版本: ${openvikingVersion}`));
|
|
1156
|
+
}
|
|
755
1157
|
|
|
756
1158
|
await selectMode();
|
|
757
1159
|
info(tr(`Mode: ${selectedMode}`, `模式: ${selectedMode}`));
|
|
@@ -759,28 +1161,38 @@ async function main() {
|
|
|
759
1161
|
if (selectedMode === "local") {
|
|
760
1162
|
await validateEnvironment();
|
|
761
1163
|
await checkOpenClaw();
|
|
1164
|
+
// Resolve plugin config after OpenClaw is available (for version detection)
|
|
1165
|
+
await resolvePluginConfig();
|
|
1166
|
+
await checkOpenClawCompatibility();
|
|
1167
|
+
checkRequestedOpenVikingCompatibility();
|
|
762
1168
|
await installOpenViking();
|
|
763
1169
|
await configureOvConf();
|
|
764
1170
|
} else {
|
|
765
1171
|
await checkOpenClaw();
|
|
1172
|
+
await resolvePluginConfig();
|
|
1173
|
+
await checkOpenClawCompatibility();
|
|
766
1174
|
await collectRemoteConfig();
|
|
767
1175
|
}
|
|
768
1176
|
|
|
769
1177
|
let pluginPath;
|
|
770
|
-
const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", "openclaw-
|
|
1178
|
+
const localPluginDir = openvikingRepo ? join(openvikingRepo, "examples", resolvedPluginDir || "openclaw-plugin") : "";
|
|
771
1179
|
if (openvikingRepo && existsSync(join(localPluginDir, "index.ts"))) {
|
|
772
1180
|
pluginPath = localPluginDir;
|
|
1181
|
+
PLUGIN_DEST = join(OPENCLAW_DIR, "extensions", resolvedPluginId || "openviking");
|
|
773
1182
|
info(tr(`Using local plugin from repo: ${pluginPath}`, `使用仓库内插件: ${pluginPath}`));
|
|
774
|
-
|
|
1183
|
+
await deployLocalPlugin(pluginPath);
|
|
775
1184
|
info(tr("Installing plugin npm dependencies...", "正在安装插件 npm 依赖..."));
|
|
776
|
-
|
|
777
|
-
|
|
1185
|
+
const npmArgs = resolvedNpmOmitDev
|
|
1186
|
+
? ["install", "--omit=dev", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY]
|
|
1187
|
+
: ["install", "--no-audit", "--no-fund", "--registry", NPM_REGISTRY];
|
|
1188
|
+
await run("npm", npmArgs, { cwd: PLUGIN_DEST, silent: false });
|
|
1189
|
+
pluginPath = PLUGIN_DEST;
|
|
778
1190
|
} else {
|
|
779
1191
|
await downloadPlugin();
|
|
780
1192
|
pluginPath = PLUGIN_DEST;
|
|
781
1193
|
}
|
|
782
1194
|
|
|
783
|
-
await configureOpenClawPlugin(
|
|
1195
|
+
await configureOpenClawPlugin();
|
|
784
1196
|
const envFiles = await writeOpenvikingEnv({
|
|
785
1197
|
includePython: selectedMode === "local",
|
|
786
1198
|
});
|
|
@@ -803,6 +1215,14 @@ async function main() {
|
|
|
803
1215
|
console.log("");
|
|
804
1216
|
|
|
805
1217
|
if (selectedMode === "local") {
|
|
1218
|
+
if (envFiles?.shellPath && !IS_WIN) {
|
|
1219
|
+
info(
|
|
1220
|
+
tr(
|
|
1221
|
+
'If source fails, set: export OPENVIKING_PYTHON="$(command -v python3)"',
|
|
1222
|
+
'若 source 失败,可执行: export OPENVIKING_PYTHON="$(command -v python3)"',
|
|
1223
|
+
),
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
806
1226
|
info(tr(`You can edit the config freely: ${OPENVIKING_DIR}/ov.conf`, `你可以按需自由修改配置文件: ${OPENVIKING_DIR}/ov.conf`));
|
|
807
1227
|
} else {
|
|
808
1228
|
info(tr(`Remote server: ${remoteBaseUrl}`, `远程服务器: ${remoteBaseUrl}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-openviking-setup-helper",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9-dev.1",
|
|
4
4
|
"description": "Setup helper for installing OpenViking memory plugin into OpenClaw",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
23
|
"url": "git+https://github.com/volcengine/OpenViking.git",
|
|
24
|
-
"directory": "examples/openclaw-
|
|
24
|
+
"directory": "examples/openclaw-plugin/setup-helper"
|
|
25
25
|
},
|
|
26
26
|
"files": [
|
|
27
27
|
"install.js"
|