refactor: restructure PrismFacet per OpenClaw plugin spec

- 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>
This commit is contained in:
zhi
2026-04-18 17:22:17 +00:00
parent c4a72b13c0
commit d5c057a3f9
17 changed files with 502 additions and 306 deletions

143
scripts/install.mjs Normal file
View File

@@ -0,0 +1,143 @@
#!/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;
}