473 lines
19 KiB
JavaScript
Executable File
473 lines
19 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Dirigent plugin installer/uninstaller/updater with delta-tracking.
|
|
*
|
|
* Usage:
|
|
* node install-dirigent-openclaw.mjs --install Install (or reinstall) plugin
|
|
* node install-dirigent-openclaw.mjs --uninstall Remove plugin config & files
|
|
* node install-dirigent-openclaw.mjs --update Pull latest from git and reinstall
|
|
*
|
|
* OpenClaw directory resolution (priority order):
|
|
* 1. --openclaw-profile-path <path> CLI argument
|
|
* 2. $OPENCLAW_DIR environment variable
|
|
* 3. ~/.openclaw (fallback)
|
|
*/
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import { execFileSync, spawnSync } from "node:child_process";
|
|
|
|
// ── Arg parsing ───────────────────────────────────────────────────────────
|
|
const VALID_MODES = ["--install", "--uninstall", "--update"];
|
|
let modeArg = null;
|
|
let argOpenClawDir = null;
|
|
|
|
for (let i = 2; i < process.argv.length; i++) {
|
|
const arg = process.argv[i];
|
|
if (VALID_MODES.includes(arg)) {
|
|
modeArg = arg;
|
|
} else if (arg === "--openclaw-profile-path" && i + 1 < process.argv.length) {
|
|
argOpenClawDir = process.argv[++i];
|
|
} else if (arg.startsWith("--openclaw-profile-path=")) {
|
|
argOpenClawDir = arg.split("=").slice(1).join("=");
|
|
}
|
|
}
|
|
|
|
if (!modeArg) {
|
|
console.error("Usage: install-dirigent-openclaw.mjs --install | --uninstall | --update [--openclaw-profile-path <path>]");
|
|
process.exit(2);
|
|
}
|
|
const mode = modeArg.slice(2); // "install" | "uninstall" | "update"
|
|
|
|
// ── OpenClaw directory resolution ─────────────────────────────────────────
|
|
// Priority: --openclaw-profile-path arg > $OPENCLAW_DIR env > ~/.openclaw
|
|
function resolveOpenClawDir() {
|
|
// 1. CLI argument
|
|
if (argOpenClawDir) {
|
|
const dir = argOpenClawDir.replace(/^~(?=$|\/)/, os.homedir());
|
|
if (fs.existsSync(dir)) return dir;
|
|
console.error(`[dirigent] --openclaw-profile-path=${dir} does not exist`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// 2. Environment variable
|
|
if (process.env.OPENCLAW_DIR) {
|
|
const dir = process.env.OPENCLAW_DIR.replace(/^~(?=$|\/)/, os.homedir());
|
|
if (fs.existsSync(dir)) return dir;
|
|
console.warn(`[dirigent] OPENCLAW_DIR=${dir} does not exist, falling back...`);
|
|
}
|
|
|
|
// 3. Fallback
|
|
const fallback = path.join(os.homedir(), ".openclaw");
|
|
if (fs.existsSync(fallback)) return fallback;
|
|
|
|
console.error("[dirigent] cannot resolve OpenClaw directory. Use --openclaw-profile-path or set OPENCLAW_DIR.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const OPENCLAW_DIR = resolveOpenClawDir();
|
|
console.log(`[dirigent] OpenClaw dir: ${OPENCLAW_DIR}`);
|
|
|
|
const OPENCLAW_CONFIG_PATH = process.env.OPENCLAW_CONFIG_PATH || path.join(OPENCLAW_DIR, "openclaw.json");
|
|
if (!fs.existsSync(OPENCLAW_CONFIG_PATH)) {
|
|
console.error(`[dirigent] config not found: ${OPENCLAW_CONFIG_PATH}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const env = process.env;
|
|
const __dirname = path.dirname(new URL(import.meta.url).pathname);
|
|
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
|
|
// ── Update mode: git pull then reinstall ──────────────────────────────────
|
|
const GIT_REPO_URL = env.DIRIGENT_GIT_URL || "https://git.hangman-lab.top/nav/Dirigent.git";
|
|
const GIT_BRANCH = env.DIRIGENT_GIT_BRANCH || "latest";
|
|
|
|
if (mode === "update") {
|
|
console.log(`[dirigent] updating from ${GIT_REPO_URL} branch=${GIT_BRANCH} ...`);
|
|
|
|
// Check if we're in a git repo
|
|
const gitDir = path.join(REPO_ROOT, ".git");
|
|
if (!fs.existsSync(gitDir)) {
|
|
console.error("[dirigent] not a git repo — cannot update. Clone the repo first.");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Fetch and checkout latest
|
|
try {
|
|
execFileSync("git", ["fetch", "origin", GIT_BRANCH], { cwd: REPO_ROOT, stdio: "inherit" });
|
|
execFileSync("git", ["checkout", GIT_BRANCH], { cwd: REPO_ROOT, stdio: "inherit" });
|
|
execFileSync("git", ["pull", "origin", GIT_BRANCH], { cwd: REPO_ROOT, stdio: "inherit" });
|
|
console.log("[dirigent] source updated successfully");
|
|
} catch (err) {
|
|
console.error(`[dirigent] git update failed: ${String(err)}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// Re-exec as install (the updated script may differ), pass through openclaw dir
|
|
const updatedScript = path.join(REPO_ROOT, "scripts", "install-dirigent-openclaw.mjs");
|
|
const installArgs = [updatedScript, "--install", "--openclaw-profile-path", OPENCLAW_DIR];
|
|
const result = spawnSync(process.execPath, installArgs, {
|
|
env: process.env,
|
|
stdio: "inherit",
|
|
cwd: REPO_ROOT,
|
|
});
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
|
|
// ── Build: copy plugin + no-reply-api to dist ─────────────────────────────
|
|
const PLUGIN_SRC_DIR = path.resolve(REPO_ROOT, "plugin");
|
|
const NO_REPLY_API_SRC_DIR = path.resolve(REPO_ROOT, "no-reply-api");
|
|
const DIST_PLUGIN_DIR = path.resolve(REPO_ROOT, "dist", "dirigent");
|
|
const DIST_NO_REPLY_DIR = path.resolve(REPO_ROOT, "dist", "no-reply-api");
|
|
|
|
function syncDir(srcDir, destDir) {
|
|
fs.mkdirSync(destDir, { recursive: true });
|
|
for (const f of fs.readdirSync(srcDir)) {
|
|
const srcFile = path.join(srcDir, f);
|
|
if (fs.statSync(srcFile).isFile()) {
|
|
fs.copyFileSync(srcFile, path.join(destDir, f));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Determine plugin install path ─────────────────────────────────────────
|
|
const PLUGINS_DIR = path.join(OPENCLAW_DIR, "plugins");
|
|
const PLUGIN_INSTALL_DIR = path.join(PLUGINS_DIR, "dirigent");
|
|
const NO_REPLY_INSTALL_DIR = path.join(PLUGINS_DIR, "no-reply-api");
|
|
|
|
// ── Config helpers ────────────────────────────────────────────────────────
|
|
const NO_REPLY_PROVIDER_ID = env.NO_REPLY_PROVIDER_ID || "dirigentway";
|
|
const NO_REPLY_MODEL_ID = env.NO_REPLY_MODEL_ID || "no-reply";
|
|
const NO_REPLY_BASE_URL = env.NO_REPLY_BASE_URL || "http://127.0.0.1:8787/v1";
|
|
const NO_REPLY_API_KEY = env.NO_REPLY_API_KEY || "wg-local-test-token";
|
|
const LIST_MODE = env.LIST_MODE || "human-list";
|
|
const HUMAN_LIST_JSON = env.HUMAN_LIST_JSON || '["561921120408698910","1474088632750047324"]';
|
|
const AGENT_LIST_JSON = env.AGENT_LIST_JSON || "[]";
|
|
const CHANNEL_POLICIES_FILE = (env.CHANNEL_POLICIES_FILE || path.join(OPENCLAW_DIR, "dirigent-channel-policies.json"));
|
|
const CHANNEL_POLICIES_JSON = env.CHANNEL_POLICIES_JSON || "{}";
|
|
const END_SYMBOLS_JSON = env.END_SYMBOLS_JSON || '["🔚"]';
|
|
const SCHEDULING_IDENTIFIER = env.SCHEDULING_IDENTIFIER || "➡️";
|
|
|
|
const STATE_DIR = env.STATE_DIR || path.join(OPENCLAW_DIR, "dirigent-install-records");
|
|
const LATEST_RECORD_LINK = env.LATEST_RECORD_LINK || path.join(OPENCLAW_DIR, "dirigent-install-record-latest.json");
|
|
|
|
const ts = new Date().toISOString().replace(/[-:TZ.]/g, "").slice(0, 14);
|
|
const BACKUP_PATH = `${OPENCLAW_CONFIG_PATH}.bak-dirigent-${mode}-${ts}`;
|
|
const RECORD_PATH = path.join(STATE_DIR, `dirigent-${ts}.json`);
|
|
|
|
const PATH_PLUGINS_LOAD = "plugins.load.paths";
|
|
const PATH_PLUGIN_ENTRY = "plugins.entries.dirigent";
|
|
const PATH_PROVIDERS = "models.providers";
|
|
const PATH_PROVIDER_ENTRY = `models.providers.${NO_REPLY_PROVIDER_ID}`;
|
|
const PATH_PLUGINS_ALLOW = "plugins.allow";
|
|
|
|
function runOpenclaw(args, { allowFail = false } = {}) {
|
|
try {
|
|
return execFileSync("openclaw", args, { encoding: "utf8" }).trim();
|
|
} catch (e) {
|
|
if (allowFail) return null;
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function getJson(pathKey) {
|
|
const out = runOpenclaw(["config", "get", pathKey, "--json"], { allowFail: true });
|
|
if (out == null || out === "" || out === "undefined") return undefined;
|
|
try {
|
|
return JSON.parse(out);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function setJson(pathKey, value) {
|
|
runOpenclaw(["config", "set", pathKey, JSON.stringify(value), "--json"]);
|
|
}
|
|
|
|
function unsetPath(pathKey) {
|
|
runOpenclaw(["config", "unset", pathKey], { allowFail: true });
|
|
}
|
|
|
|
function writeRecord(modeName, delta) {
|
|
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
const rec = {
|
|
mode: modeName,
|
|
timestamp: ts,
|
|
openclawDir: OPENCLAW_DIR,
|
|
openclawConfigPath: OPENCLAW_CONFIG_PATH,
|
|
backupPath: BACKUP_PATH,
|
|
pluginInstallDir: PLUGIN_INSTALL_DIR,
|
|
delta,
|
|
};
|
|
fs.writeFileSync(RECORD_PATH, JSON.stringify(rec, null, 2));
|
|
fs.copyFileSync(RECORD_PATH, LATEST_RECORD_LINK);
|
|
return rec;
|
|
}
|
|
|
|
function readRecord(file) {
|
|
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
}
|
|
|
|
function findLatestInstallRecord() {
|
|
if (!fs.existsSync(STATE_DIR)) return "";
|
|
const files = fs
|
|
.readdirSync(STATE_DIR)
|
|
.filter((f) => /^dirigent-\d+\.json$/.test(f))
|
|
.sort()
|
|
.reverse();
|
|
for (const f of files) {
|
|
const p = path.join(STATE_DIR, f);
|
|
try {
|
|
const rec = readRecord(p);
|
|
if (rec?.mode === "install") return p;
|
|
} catch {
|
|
// ignore broken records
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
function clone(v) {
|
|
if (v === undefined) return undefined;
|
|
return JSON.parse(JSON.stringify(v));
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// INSTALL
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
if (mode === "install") {
|
|
// Check if already installed - if so, uninstall first
|
|
const existingRecord = findLatestInstallRecord();
|
|
if (existingRecord) {
|
|
console.log("[dirigent] existing installation detected, uninstalling first...");
|
|
process.env.RECORD_FILE = existingRecord;
|
|
const result = spawnSync(process.execPath, [import.meta.filename, "--uninstall"], {
|
|
env: { ...process.env, OPENCLAW_DIR },
|
|
stdio: "inherit",
|
|
});
|
|
if (result.status !== 0) {
|
|
console.error("[dirigent] reinstall failed during uninstall phase");
|
|
process.exit(1);
|
|
}
|
|
console.log("[dirigent] previous installation removed, proceeding with fresh install...");
|
|
}
|
|
|
|
// 1. Build dist
|
|
console.log("[dirigent] building dist...");
|
|
syncDir(PLUGIN_SRC_DIR, DIST_PLUGIN_DIR);
|
|
syncDir(NO_REPLY_API_SRC_DIR, DIST_NO_REPLY_DIR);
|
|
|
|
// 2. Copy to plugins dir
|
|
console.log(`[dirigent] installing plugin to ${PLUGIN_INSTALL_DIR}`);
|
|
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
syncDir(DIST_PLUGIN_DIR, PLUGIN_INSTALL_DIR);
|
|
|
|
// Also install no-reply-api next to plugin (plugin expects ../no-reply-api/)
|
|
console.log(`[dirigent] installing no-reply-api to ${NO_REPLY_INSTALL_DIR}`);
|
|
syncDir(DIST_NO_REPLY_DIR, NO_REPLY_INSTALL_DIR);
|
|
|
|
// 3. Backup config
|
|
fs.copyFileSync(OPENCLAW_CONFIG_PATH, BACKUP_PATH);
|
|
console.log(`[dirigent] safety backup: ${BACKUP_PATH}`);
|
|
|
|
// 4. Initialize channel policies file
|
|
if (!fs.existsSync(CHANNEL_POLICIES_FILE)) {
|
|
fs.mkdirSync(path.dirname(CHANNEL_POLICIES_FILE), { recursive: true });
|
|
fs.writeFileSync(CHANNEL_POLICIES_FILE, `${CHANNEL_POLICIES_JSON}\n`);
|
|
console.log(`[dirigent] initialized channel policies file: ${CHANNEL_POLICIES_FILE}`);
|
|
}
|
|
|
|
const delta = { added: {}, replaced: {}, removed: {}, _prev: {} };
|
|
|
|
try {
|
|
// ── plugins.load.paths ────────────────────────────────────────────────
|
|
const plugins = getJson("plugins") || {};
|
|
const oldPaths = clone(plugins.load?.paths) || [];
|
|
const newPaths = clone(oldPaths);
|
|
if (!newPaths.includes(PLUGIN_INSTALL_DIR)) {
|
|
newPaths.push(PLUGIN_INSTALL_DIR);
|
|
delta.added[PATH_PLUGINS_LOAD] = PLUGIN_INSTALL_DIR;
|
|
}
|
|
delta._prev[PATH_PLUGINS_LOAD] = oldPaths;
|
|
plugins.load = plugins.load || {};
|
|
plugins.load.paths = newPaths;
|
|
|
|
// ── plugins.entries.dirigent ──────────────────────────────────────────
|
|
const oldEntry = clone(plugins.entries?.dirigent);
|
|
const newEntry = {
|
|
enabled: true,
|
|
config: {
|
|
enabled: true,
|
|
discordOnly: true,
|
|
listMode: LIST_MODE,
|
|
humanList: JSON.parse(HUMAN_LIST_JSON),
|
|
agentList: JSON.parse(AGENT_LIST_JSON),
|
|
channelPoliciesFile: CHANNEL_POLICIES_FILE,
|
|
endSymbols: JSON.parse(END_SYMBOLS_JSON),
|
|
schedulingIdentifier: SCHEDULING_IDENTIFIER,
|
|
noReplyProvider: NO_REPLY_PROVIDER_ID,
|
|
noReplyModel: NO_REPLY_MODEL_ID,
|
|
},
|
|
};
|
|
if (oldEntry === undefined) {
|
|
delta.added[PATH_PLUGIN_ENTRY] = newEntry;
|
|
} else {
|
|
delta.replaced[PATH_PLUGIN_ENTRY] = oldEntry;
|
|
}
|
|
plugins.entries = plugins.entries || {};
|
|
plugins.entries.dirigent = newEntry;
|
|
setJson("plugins", plugins);
|
|
|
|
// ── models.providers.<providerId> ─────────────────────────────────────
|
|
const providers = getJson(PATH_PROVIDERS) || {};
|
|
const oldProvider = clone(providers[NO_REPLY_PROVIDER_ID]);
|
|
const newProvider = {
|
|
baseUrl: NO_REPLY_BASE_URL,
|
|
apiKey: NO_REPLY_API_KEY,
|
|
api: "openai-completions",
|
|
models: [
|
|
{
|
|
id: NO_REPLY_MODEL_ID,
|
|
name: `${NO_REPLY_MODEL_ID} (Custom Provider)`,
|
|
reasoning: false,
|
|
input: ["text"],
|
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
contextWindow: 200000,
|
|
maxTokens: 8192,
|
|
},
|
|
],
|
|
};
|
|
if (oldProvider === undefined) {
|
|
delta.added[PATH_PROVIDER_ENTRY] = newProvider;
|
|
} else {
|
|
delta.replaced[PATH_PROVIDER_ENTRY] = oldProvider;
|
|
}
|
|
providers[NO_REPLY_PROVIDER_ID] = newProvider;
|
|
setJson(PATH_PROVIDERS, providers);
|
|
|
|
// ── plugins.allow ─────────────────────────────────────────────────────
|
|
const allowList = getJson(PATH_PLUGINS_ALLOW) || [];
|
|
const oldAllow = clone(allowList);
|
|
if (!allowList.includes("dirigent")) {
|
|
allowList.push("dirigent");
|
|
delta.added[PATH_PLUGINS_ALLOW] = "dirigent";
|
|
delta._prev[PATH_PLUGINS_ALLOW] = oldAllow;
|
|
setJson(PATH_PLUGINS_ALLOW, allowList);
|
|
console.log("[dirigent] added 'dirigent' to plugins.allow");
|
|
}
|
|
|
|
writeRecord("install", delta);
|
|
console.log("[dirigent] install ok (config written)");
|
|
console.log(`[dirigent] plugin dir: ${PLUGIN_INSTALL_DIR}`);
|
|
console.log(`[dirigent] record: ${RECORD_PATH}`);
|
|
console.log("[dirigent] >>> restart gateway to apply: openclaw gateway restart");
|
|
} catch (e) {
|
|
fs.copyFileSync(BACKUP_PATH, OPENCLAW_CONFIG_PATH);
|
|
console.error(`[dirigent] install failed; rollback complete: ${String(e)}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// UNINSTALL
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
else if (mode === "uninstall") {
|
|
const recFile = env.RECORD_FILE || findLatestInstallRecord();
|
|
if (!recFile || !fs.existsSync(recFile)) {
|
|
console.log("[dirigent] no install record found, nothing to uninstall.");
|
|
process.exit(0);
|
|
}
|
|
|
|
fs.copyFileSync(OPENCLAW_CONFIG_PATH, BACKUP_PATH);
|
|
console.log(`[dirigent] safety backup: ${BACKUP_PATH}`);
|
|
|
|
const rec = readRecord(recFile);
|
|
const delta = rec.delta || { added: {}, replaced: {}, removed: {} };
|
|
const installedPluginDir = rec.pluginInstallDir || PLUGIN_INSTALL_DIR;
|
|
|
|
try {
|
|
// 1. Remove from allow list
|
|
if (delta.added[PATH_PLUGINS_ALLOW] !== undefined) {
|
|
const allowList = getJson(PATH_PLUGINS_ALLOW) || [];
|
|
const idx = allowList.indexOf("dirigent");
|
|
if (idx !== -1) {
|
|
allowList.splice(idx, 1);
|
|
setJson(PATH_PLUGINS_ALLOW, allowList);
|
|
console.log("[dirigent] removed 'dirigent' from plugins.allow");
|
|
}
|
|
}
|
|
|
|
// 2. Remove plugin entry
|
|
if (delta.added[PATH_PLUGIN_ENTRY] !== undefined || delta.replaced[PATH_PLUGIN_ENTRY] !== undefined) {
|
|
unsetPath(PATH_PLUGIN_ENTRY);
|
|
console.log("[dirigent] removed plugins.entries.dirigent");
|
|
}
|
|
|
|
// 3. Remove plugin path from load paths
|
|
if (delta.added[PATH_PLUGINS_LOAD] !== undefined) {
|
|
const plugins = getJson("plugins") || {};
|
|
const paths = plugins.load?.paths || [];
|
|
const pluginPath = delta.added[PATH_PLUGINS_LOAD];
|
|
const idx = paths.indexOf(pluginPath);
|
|
if (idx !== -1) {
|
|
paths.splice(idx, 1);
|
|
plugins.load.paths = paths;
|
|
setJson("plugins", plugins);
|
|
console.log("[dirigent] removed plugin path from plugins.load.paths");
|
|
}
|
|
}
|
|
|
|
// 4. Remove provider
|
|
if (delta.added[PATH_PROVIDER_ENTRY] !== undefined) {
|
|
const providers = getJson(PATH_PROVIDERS) || {};
|
|
delete providers[NO_REPLY_PROVIDER_ID];
|
|
setJson(PATH_PROVIDERS, providers);
|
|
console.log(`[dirigent] removed models.providers.${NO_REPLY_PROVIDER_ID}`);
|
|
}
|
|
|
|
// Handle replaced provider: restore old value
|
|
if (delta.replaced[PATH_PROVIDER_ENTRY] !== undefined) {
|
|
const providers = getJson(PATH_PROVIDERS) || {};
|
|
providers[NO_REPLY_PROVIDER_ID] = delta.replaced[PATH_PROVIDER_ENTRY];
|
|
setJson(PATH_PROVIDERS, providers);
|
|
console.log(`[dirigent] restored previous models.providers.${NO_REPLY_PROVIDER_ID}`);
|
|
}
|
|
|
|
// Handle plugins.load.paths restoration
|
|
if (delta._prev?.[PATH_PLUGINS_LOAD] && delta.added[PATH_PLUGINS_LOAD] === undefined) {
|
|
const plugins = getJson("plugins") || {};
|
|
plugins.load = plugins.load || {};
|
|
plugins.load.paths = delta._prev[PATH_PLUGINS_LOAD];
|
|
setJson("plugins", plugins);
|
|
console.log("[dirigent] restored previous plugins.load.paths");
|
|
}
|
|
|
|
// 5. Remove installed plugin files
|
|
if (fs.existsSync(installedPluginDir)) {
|
|
fs.rmSync(installedPluginDir, { recursive: true, force: true });
|
|
console.log(`[dirigent] removed plugin dir: ${installedPluginDir}`);
|
|
}
|
|
// Also remove no-reply-api dir
|
|
const noReplyDir = path.join(path.dirname(installedPluginDir), "no-reply-api");
|
|
if (fs.existsSync(noReplyDir)) {
|
|
fs.rmSync(noReplyDir, { recursive: true, force: true });
|
|
console.log(`[dirigent] removed no-reply-api dir: ${noReplyDir}`);
|
|
}
|
|
// Backward-compat cleanup for older mistaken install path
|
|
const legacyNoReplyDir = path.join(path.dirname(installedPluginDir), "dirigent-no-reply-api");
|
|
if (fs.existsSync(legacyNoReplyDir)) {
|
|
fs.rmSync(legacyNoReplyDir, { recursive: true, force: true });
|
|
console.log(`[dirigent] removed legacy no-reply-api dir: ${legacyNoReplyDir}`);
|
|
}
|
|
|
|
writeRecord("uninstall", delta);
|
|
console.log("[dirigent] uninstall ok");
|
|
console.log(`[dirigent] record: ${RECORD_PATH}`);
|
|
console.log("[dirigent] >>> restart gateway to apply: openclaw gateway restart");
|
|
} catch (e) {
|
|
fs.copyFileSync(BACKUP_PATH, OPENCLAW_CONFIG_PATH);
|
|
console.error(`[dirigent] uninstall failed; rollback complete: ${String(e)}`);
|
|
process.exit(1);
|
|
}
|
|
}
|