Files
ContractorAgent/scripts/install.mjs
hzhang 07a0f06e2e refactor: restructure to plugin/ + services/ layout and add per-turn bootstrap injection
- Migrate src/ → plugin/ (plugin/core/, plugin/web/, plugin/commands/)
  and src/mcp/ → services/ per OpenClaw plugin dev spec
- Add Gemini CLI backend (plugin/core/gemini/sdk-adapter.ts) with GEMINI.md
  system-prompt injection
- Inject bootstrap as stateless system prompt on every turn instead of
  first turn only: Claude via --system-prompt, Gemini via workspace/GEMINI.md;
  eliminates isFirstTurn branch, keeps skills in sync with OpenClaw snapshots
- Fix session-map-store defensive parsing (sessions ?? []) to handle bare {}
  reset files without crashing on .find()
- Add docs/TEST_FLOW.md with E2E test scenarios and expected outcomes
- Add docs/claude/BRIDGE_MODEL_FINDINGS.md with contractor-probe results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 21:21:32 +01:00

184 lines
6.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Install / uninstall the contractor-agent plugin into OpenClaw.
*
* Usage:
* node scripts/install.mjs --install
* node scripts/install.mjs --uninstall
*/
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
import { execSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PROJECT_ROOT = path.resolve(__dirname, "..");
const PLUGIN_ID = "contractor-agent";
const PLUGIN_INSTALL_DIR = path.join(os.homedir(), ".openclaw", "plugins", PLUGIN_ID);
const OPENCLAW_CONFIG = path.join(os.homedir(), ".openclaw", "openclaw.json");
const BRIDGE_PORT = 18800;
const BRIDGE_API_KEY = "contractor-bridge-local";
// ── Helpers ───────────────────────────────────────────────────────────────────
function readConfig() {
return JSON.parse(fs.readFileSync(OPENCLAW_CONFIG, "utf8"));
}
function writeConfig(cfg) {
fs.writeFileSync(OPENCLAW_CONFIG, JSON.stringify(cfg, null, 2) + "\n", "utf8");
}
function setIfMissing(obj, key, value) {
if (obj[key] === undefined || obj[key] === null) obj[key] = value;
}
// ── Install ───────────────────────────────────────────────────────────────────
function install() {
console.log(`[install] Installing ${PLUGIN_ID}...`);
// 1. Copy plugin files to ~/.openclaw/plugins/contractor-agent/
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
}
fs.mkdirSync(PLUGIN_INSTALL_DIR, { recursive: true });
// Copy plugin/ contents to the install root.
// plugin/ contains index.ts, openclaw.plugin.json, package.json, and subdirs.
const pluginDir = path.join(PROJECT_ROOT, "plugin");
fs.cpSync(pluginDir, PLUGIN_INSTALL_DIR, { recursive: true });
// Copy services/ (standalone processes used by the plugin) into the install root.
const servicesDir = path.join(PROJECT_ROOT, "services");
if (fs.existsSync(servicesDir)) {
fs.cpSync(servicesDir, path.join(PLUGIN_INSTALL_DIR, "services"), { recursive: true });
}
// 2. Install npm dependencies inside the plugin dir
console.log(`[install] Installing npm dependencies...`);
execSync("npm install --omit=dev --no-audit --no-fund", {
cwd: PLUGIN_INSTALL_DIR,
stdio: "inherit",
});
// 3. Update openclaw.json
const cfg = readConfig();
// Add provider
cfg.models = cfg.models ?? {};
cfg.models.providers = cfg.models.providers ?? {};
cfg.models.providers[PLUGIN_ID] = {
baseUrl: `http://127.0.0.1:${BRIDGE_PORT}/v1`,
apiKey: BRIDGE_API_KEY,
api: "openai-completions",
models: [
{
id: "contractor-claude-bridge",
name: "Contractor Claude Bridge",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 16000,
},
{
id: "contractor-gemini-bridge",
name: "Contractor Gemini Bridge",
reasoning: false,
input: ["text"],
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
contextWindow: 200000,
maxTokens: 16000,
},
],
};
// Add to plugin allow list
cfg.plugins = cfg.plugins ?? {};
cfg.plugins.allow = cfg.plugins.allow ?? [];
if (!cfg.plugins.allow.includes(PLUGIN_ID)) {
cfg.plugins.allow.push(PLUGIN_ID);
}
// Add load path
cfg.plugins.load = cfg.plugins.load ?? {};
cfg.plugins.load.paths = cfg.plugins.load.paths ?? [];
if (!cfg.plugins.load.paths.includes(PLUGIN_INSTALL_DIR)) {
cfg.plugins.load.paths.push(PLUGIN_INSTALL_DIR);
}
// Add plugin entry
cfg.plugins.entries = cfg.plugins.entries ?? {};
cfg.plugins.entries[PLUGIN_ID] = cfg.plugins.entries[PLUGIN_ID] ?? {};
cfg.plugins.entries[PLUGIN_ID].enabled = true;
// Set default config — setIfMissing so user values are preserved
const pluginCfg = cfg.plugins.entries[PLUGIN_ID].config ?? {};
setIfMissing(pluginCfg, "bridgePort", BRIDGE_PORT);
setIfMissing(pluginCfg, "bridgeApiKey", BRIDGE_API_KEY);
setIfMissing(pluginCfg, "permissionMode", "bypassPermissions");
cfg.plugins.entries[PLUGIN_ID].config = pluginCfg;
writeConfig(cfg);
console.log(`[install] Done. Restart the gateway to activate:`);
console.log(` openclaw gateway restart`);
console.log(` openclaw contractor-agents add --agent-id <id> --workspace <path> --contractor claude`);
}
// ── Uninstall ─────────────────────────────────────────────────────────────────
function uninstall() {
console.log(`[uninstall] Removing ${PLUGIN_ID}...`);
const cfg = readConfig();
// Remove provider
if (cfg.models?.providers?.[PLUGIN_ID]) {
delete cfg.models.providers[PLUGIN_ID];
}
// Remove from allow list
if (Array.isArray(cfg.plugins?.allow)) {
cfg.plugins.allow = cfg.plugins.allow.filter((id) => id !== PLUGIN_ID);
}
// Remove load path
if (Array.isArray(cfg.plugins?.load?.paths)) {
cfg.plugins.load.paths = cfg.plugins.load.paths.filter(
(p) => p !== PLUGIN_INSTALL_DIR,
);
}
// Remove plugin entry
if (cfg.plugins?.entries?.[PLUGIN_ID]) {
delete cfg.plugins.entries[PLUGIN_ID];
}
writeConfig(cfg);
// Remove installed files
if (fs.existsSync(PLUGIN_INSTALL_DIR)) {
fs.rmSync(PLUGIN_INSTALL_DIR, { recursive: true });
console.log(`[uninstall] Removed ${PLUGIN_INSTALL_DIR}`);
}
console.log(`[uninstall] Done. Restart the gateway:`);
console.log(` openclaw gateway restart`);
}
// ── Main ──────────────────────────────────────────────────────────────────────
const arg = process.argv[2];
if (arg === "--install") {
install();
} else if (arg === "--uninstall") {
uninstall();
} else {
console.error("Usage: node scripts/install.mjs --install | --uninstall");
process.exit(1);
}