- plugin/ directory structure: hooks/, tools/, core/
- export default { id, name, register } entry format
- globalThis state management with lifecycle protection
- WeakSet dedup on before_prompt_build hook
- Tool uses inputSchema + execute (not parameters + handler)
- additionalProperties: false in config schema
- Core logic in plugin/core/ (no plugin-sdk dependency)
- Install/uninstall script (scripts/install.mjs)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
144 lines
4.1 KiB
JavaScript
144 lines
4.1 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { execSync } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const projRoot = path.resolve(__dirname, "..");
|
|
const pluginSrcDir = path.join(projRoot, "plugin");
|
|
const routersSrcDir = path.join(projRoot, "routers");
|
|
|
|
const PLUGIN_ID = "prism-facet";
|
|
const ocDir = path.join(process.env.HOME || "~", ".openclaw");
|
|
const pluginsDir = path.join(ocDir, "plugins");
|
|
const installDir = path.join(pluginsDir, PLUGIN_ID);
|
|
const configPath = path.join(ocDir, "openclaw.json");
|
|
|
|
const args = process.argv.slice(2);
|
|
const action = args[0];
|
|
|
|
if (!action || !["--install", "--uninstall", "--update"].includes(action)) {
|
|
console.log("Usage: node scripts/install.mjs --install | --uninstall | --update");
|
|
process.exit(1);
|
|
}
|
|
|
|
function readConfig() {
|
|
return JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
}
|
|
|
|
function writeConfig(cfg) {
|
|
fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
}
|
|
|
|
function setIfMissing(obj, key, value) {
|
|
if (obj[key] === undefined || obj[key] === null) {
|
|
obj[key] = value;
|
|
}
|
|
}
|
|
|
|
function buildPlugin() {
|
|
console.log("Building plugin...");
|
|
// Compile TypeScript from plugin/ to plugin/ (in-place, JS output)
|
|
execSync("npx tsc --project tsconfig.plugin.json", { cwd: projRoot, stdio: "inherit" });
|
|
}
|
|
|
|
function install() {
|
|
// 1. Build
|
|
buildPlugin();
|
|
|
|
// 2. Clean and copy plugin to install dir
|
|
if (fs.existsSync(installDir)) {
|
|
fs.rmSync(installDir, { recursive: true });
|
|
}
|
|
fs.mkdirSync(installDir, { recursive: true });
|
|
|
|
// Copy compiled plugin files
|
|
copyDirRecursive(pluginSrcDir, installDir, [".ts"]);
|
|
|
|
// Copy routers alongside plugin
|
|
const routersInstallDir = path.join(installDir, "routers");
|
|
if (fs.existsSync(routersSrcDir)) {
|
|
fs.mkdirSync(routersInstallDir, { recursive: true });
|
|
copyDirRecursive(routersSrcDir, routersInstallDir);
|
|
}
|
|
|
|
// Create empty rules.json if not exists
|
|
const rulesPath = path.join(installDir, "rules.json");
|
|
if (!fs.existsSync(rulesPath)) {
|
|
fs.writeFileSync(rulesPath, "{}\n", "utf8");
|
|
}
|
|
|
|
// 3. Update openclaw.json
|
|
const cfg = readConfig();
|
|
const plugins = cfg.plugins ??= {};
|
|
const allow = plugins.allow ??= [];
|
|
const loadPaths = (plugins.load ??= {}).paths ??= [];
|
|
const entries = plugins.entries ??= {};
|
|
|
|
if (!allow.includes(PLUGIN_ID)) allow.push(PLUGIN_ID);
|
|
if (!loadPaths.includes(installDir)) loadPaths.push(installDir);
|
|
|
|
const entry = entries[PLUGIN_ID] ??= {};
|
|
setIfMissing(entry, "enabled", true);
|
|
const hooks = entry.hooks ??= {};
|
|
setIfMissing(hooks, "allowPromptInjection", true);
|
|
|
|
writeConfig(cfg);
|
|
console.log(`Installed ${PLUGIN_ID} to ${installDir}`);
|
|
console.log("Restart gateway: openclaw gateway restart");
|
|
}
|
|
|
|
function uninstall() {
|
|
const cfg = readConfig();
|
|
const plugins = cfg.plugins ?? {};
|
|
|
|
// Remove from allow
|
|
if (Array.isArray(plugins.allow)) {
|
|
plugins.allow = plugins.allow.filter((id) => id !== PLUGIN_ID);
|
|
}
|
|
|
|
// Remove entry
|
|
if (plugins.entries) delete plugins.entries[PLUGIN_ID];
|
|
|
|
// Remove load path
|
|
if (plugins.load?.paths) {
|
|
plugins.load.paths = plugins.load.paths.filter((p) => !p.includes(PLUGIN_ID));
|
|
}
|
|
|
|
writeConfig(cfg);
|
|
|
|
// Remove install dir
|
|
if (fs.existsSync(installDir)) {
|
|
fs.rmSync(installDir, { recursive: true });
|
|
console.log(`Removed ${installDir}`);
|
|
}
|
|
|
|
console.log(`Uninstalled ${PLUGIN_ID}`);
|
|
}
|
|
|
|
function copyDirRecursive(src, dest, excludeExts = []) {
|
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
const srcPath = path.join(src, entry.name);
|
|
const destPath = path.join(dest, entry.name);
|
|
if (entry.isDirectory()) {
|
|
if (entry.name === "node_modules") continue;
|
|
copyDirRecursive(srcPath, destPath, excludeExts);
|
|
} else {
|
|
if (excludeExts.some((ext) => entry.name.endsWith(ext))) continue;
|
|
fs.copyFileSync(srcPath, destPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (action) {
|
|
case "--install":
|
|
case "--update":
|
|
install();
|
|
break;
|
|
case "--uninstall":
|
|
uninstall();
|
|
break;
|
|
}
|