diff --git a/scripts/install.mjs b/scripts/install.mjs index 56d6089..25f8dce 100755 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -25,20 +25,32 @@ for (let i = 2; i < process.argv.length; i++) { } if (!modeArg) { - console.error("Usage: node scripts/install.mjs --install|--uninstall|--update [--openclaw-profile-path ] [--no-reply-port ]"); + fail("Usage: node scripts/install.mjs --install|--uninstall|--update [--openclaw-profile-path ] [--no-reply-port ]"); process.exit(2); } if (!Number.isFinite(argNoReplyPort) || argNoReplyPort < 1 || argNoReplyPort > 65535) { - console.error("[dirigent] invalid --no-reply-port (1-65535)"); + fail("invalid --no-reply-port (1-65535)"); process.exit(2); } const mode = modeArg.slice(2); -function step(msg) { console.log(`→ ${msg}`); } -function ok(msg) { console.log(`✓ ${msg}`); } -function warn(msg) { console.log(`! ${msg}`); } +const C = { + reset: "\x1b[0m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", + blue: "\x1b[34m", + cyan: "\x1b[36m", +}; + +function color(t, c = "reset") { return `${C[c] || ""}${t}${C.reset}`; } +function title(t) { console.log(color(`\n[dirigent] ${t}`, "cyan")); } +function step(n, total, msg) { console.log(color(`[${n}/${total}] ${msg}`, "blue")); } +function ok(msg) { console.log(color(`\t✓ ${msg}`, "green")); } +function warn(msg) { console.log(color(`\t⚠ ${msg}`, "yellow")); } +function fail(msg) { console.log(color(`\t✗ ${msg}`, "red")); } function resolveOpenClawDir() { if (argOpenClawDir) { @@ -59,7 +71,7 @@ function resolveOpenClawDir() { const OPENCLAW_DIR = resolveOpenClawDir(); 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}`); + fail(`config not found: ${OPENCLAW_CONFIG_PATH}`); process.exit(1); } @@ -116,13 +128,15 @@ function isRegistered() { } if (mode === "update") { + title("Update"); const branch = process.env.DIRIGENT_GIT_BRANCH || "latest"; - step(`update source branch=${branch}`); + step(1, 2, `update source branch=${branch}`); execFileSync("git", ["fetch", "origin", branch], { cwd: REPO_ROOT, stdio: "inherit" }); execFileSync("git", ["checkout", branch], { cwd: REPO_ROOT, stdio: "inherit" }); execFileSync("git", ["pull", "origin", branch], { cwd: REPO_ROOT, stdio: "inherit" }); ok("source updated"); + step(2, 2, "run install after update"); const script = path.join(REPO_ROOT, "scripts", "install.mjs"); const args = [script, "--install", "--openclaw-profile-path", OPENCLAW_DIR, "--no-reply-port", String(NO_REPLY_PORT)]; const ret = spawnSync(process.execPath, args, { cwd: REPO_ROOT, stdio: "inherit", env: process.env }); @@ -130,13 +144,14 @@ if (mode === "update") { } if (mode === "install") { - step(`OpenClaw dir: ${OPENCLAW_DIR}`); + title("Install"); + step(1, 6, `environment: ${OPENCLAW_DIR}`); if (isRegistered()) { warn("plugins.entries.dirigent exists; reinstalling in-place"); } - step("build dist assets"); + step(2, 6, "build dist assets"); const pluginSrc = path.resolve(REPO_ROOT, "plugin"); const noReplySrc = path.resolve(REPO_ROOT, "no-reply-api"); const distPlugin = path.resolve(REPO_ROOT, "dist", "dirigent"); @@ -144,7 +159,7 @@ if (mode === "install") { syncDirRecursive(pluginSrc, distPlugin); syncDirRecursive(noReplySrc, distNoReply); - step(`install plugin -> ${PLUGIN_INSTALL_DIR}`); + step(3, 6, `install files -> ${PLUGIN_INSTALL_DIR}`); fs.mkdirSync(PLUGINS_DIR, { recursive: true }); syncDirRecursive(distPlugin, PLUGIN_INSTALL_DIR); syncDirRecursive(distNoReply, NO_REPLY_INSTALL_DIR); @@ -155,6 +170,7 @@ if (mode === "install") { ok(`init channel policies file: ${CHANNEL_POLICIES_FILE}`); } + step(4, 6, "configure plugin entry/path"); const plugins = getJson("plugins") || {}; const loadPaths = Array.isArray(plugins?.load?.paths) ? plugins.load.paths : []; if (!loadPaths.includes(PLUGIN_INSTALL_DIR)) loadPaths.push(PLUGIN_INSTALL_DIR); @@ -180,6 +196,7 @@ if (mode === "install") { }; setJson("plugins", plugins); + step(5, 6, "configure no-reply provider"); const providers = getJson("models.providers") || {}; providers[NO_REPLY_PROVIDER_ID] = { baseUrl: NO_REPLY_BASE_URL, @@ -199,6 +216,7 @@ if (mode === "install") { }; setJson("models.providers", providers); + step(6, 6, "enable plugin in allowlist"); const allow = getJson("plugins.allow") || []; if (!allow.includes("dirigent")) { allow.push("dirigent"); @@ -211,8 +229,10 @@ if (mode === "install") { } if (mode === "uninstall") { - step(`OpenClaw dir: ${OPENCLAW_DIR}`); + title("Uninstall"); + step(1, 5, `environment: ${OPENCLAW_DIR}`); + step(2, 5, "remove allowlist + plugin entry"); const allow = getJson("plugins.allow") || []; const idx = allow.indexOf("dirigent"); if (idx >= 0) { @@ -224,6 +244,7 @@ if (mode === "uninstall") { unsetPath("plugins.entries.dirigent"); ok("removed plugins.entries.dirigent"); + step(3, 5, "remove plugin load path"); const plugins = getJson("plugins") || {}; const paths = Array.isArray(plugins?.load?.paths) ? plugins.load.paths : []; plugins.load = plugins.load || {}; @@ -231,11 +252,13 @@ if (mode === "uninstall") { setJson("plugins", plugins); ok("removed plugin path from plugins.load.paths"); + step(4, 5, "remove no-reply provider"); const providers = getJson("models.providers") || {}; delete providers[NO_REPLY_PROVIDER_ID]; setJson("models.providers", providers); ok(`removed provider ${NO_REPLY_PROVIDER_ID}`); + step(5, 5, "remove installed files"); if (fs.existsSync(PLUGIN_INSTALL_DIR)) fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true, force: true }); if (fs.existsSync(NO_REPLY_INSTALL_DIR)) fs.rmSync(NO_REPLY_INSTALL_DIR, { recursive: true, force: true }); const legacyNoReply = path.join(PLUGINS_DIR, "dirigent-no-reply-api");