#!/bin/bash set -e # Agentic Control Plane — Universal Installer # # Usage: # curl -sf https://agenticcontrolplane.com/install.sh | bash # # Detects your environment automatically: # - Claude Code → installs PreToolUse hook + govern.mjs # - OpenClaw → installs before_tool_call plugin # - Both → installs both # # Then: browser login → workspace provisioned → governance active API_BASE="${ACP_API_BASE:-https://api.agenticcontrolplane.com}" DASHBOARD_BASE="${ACP_DASHBOARD_BASE:-https://cloud.agenticcontrolplane.com}" CONFIG_DIR="$HOME/.acp" CREDS_FILE="$CONFIG_DIR/credentials" # ── Detect available clients ────────────────────────────────────────── HAS_CLAUDE=false HAS_OPENCLAW=false INSTALLED="" if [ -d "$HOME/.claude" ] || command -v claude &> /dev/null; then HAS_CLAUDE=true fi if command -v openclaw &> /dev/null; then HAS_OPENCLAW=true fi if [ "$HAS_CLAUDE" = false ] && [ "$HAS_OPENCLAW" = false ]; then echo " No supported AI clients detected." echo " Supported: Claude Code, OpenClaw" echo "" echo " Install one first, then re-run this script." exit 1 fi TARGETS="" if [ "$HAS_CLAUDE" = true ]; then TARGETS="Claude Code"; fi if [ "$HAS_OPENCLAW" = true ]; then if [ -n "$TARGETS" ]; then TARGETS="$TARGETS + OpenClaw"; else TARGETS="OpenClaw"; fi fi echo "" echo " Agentic Control Plane" echo " Identity & governance for $TARGETS" echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" mkdir -p "$CONFIG_DIR" # ── Step 1a: Claude Code setup ──────────────────────────────────────── if [ "$HAS_CLAUDE" = true ]; then echo " [Claude Code] Setting up governance hook..." # Write govern.mjs to ~/.acp/ cat > "$CONFIG_DIR/govern.mjs" << 'GOVERN' #!/usr/bin/env node import { readFileSync } from "fs"; import { homedir } from "os"; import { join } from "path"; const ACP_API = process.env.ACP_API_BASE || "https://api.agenticcontrolplane.com"; function readToken() { if (process.env.ACP_BEARER_TOKEN) return process.env.ACP_BEARER_TOKEN; try { return readFileSync(join(homedir(), ".acp", "credentials"), "utf8").trim(); } catch { return null; } } const token = readToken(); if (!token) process.exit(0); let input; try { input = JSON.parse(readFileSync("/dev/stdin", "utf8")); } catch { process.exit(0); } function resolveAgentTier() { const mode = input.permission_mode; if (mode === "auto") return "subagent"; if (mode === "bypassPermissions") return "background"; return "interactive"; } const body = JSON.stringify({ tool_name: input.tool_name, tool_input: input.tool_input, session_id: input.session_id, cwd: input.cwd, hook_event_name: input.hook_event_name || "PreToolUse", agent_tier: resolveAgentTier(), permission_mode: input.permission_mode, }); const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json", "X-GS-Client": "claude-code-plugin/0.3.0", }; function deny(reason) { process.stdout.write(JSON.stringify({ hookSpecificOutput: { permissionDecision: "deny" }, systemMessage: `[ACP] Blocked: ${reason}`, })); process.exit(0); } async function enforce() { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 4000); try { const res = await fetch(`${ACP_API}/govern/tool-use`, { method: "POST", headers, body, signal: controller.signal, }); clearTimeout(timeout); if (!res.ok) { deny("ACP unreachable (HTTP " + res.status + ")"); return; } const data = await res.json(); if (data.decision === "deny") { deny(data.reason || "denied by policy"); } } catch { deny("ACP unreachable — tool call blocked for safety"); } finally { clearTimeout(timeout); } process.exit(0); } enforce(); GOVERN chmod +x "$CONFIG_DIR/govern.mjs" # Add hook to Claude Code settings.json CLAUDE_SETTINGS="$HOME/.claude/settings.json" if [ -f "$CLAUDE_SETTINGS" ]; then if ! grep -q "govern.mjs" "$CLAUDE_SETTINGS" 2>/dev/null; then node -e " const fs = require('fs'); const p = process.argv[1]; const s = JSON.parse(fs.readFileSync(p,'utf8')); s.hooks = s.hooks || {}; s.hooks.PreToolUse = s.hooks.PreToolUse || []; s.hooks.PreToolUse.push({ matcher: '.*', hooks: [{ type: 'command', command: 'node \$HOME/.acp/govern.mjs', timeout: 5 }] }); fs.writeFileSync(p, JSON.stringify(s, null, 2)); " "$CLAUDE_SETTINGS" echo " [Claude Code] Hook registered" INSTALLED="${INSTALLED:+$INSTALLED, }Claude Code" else echo " [Claude Code] Hook already registered" INSTALLED="${INSTALLED:+$INSTALLED, }Claude Code" fi else mkdir -p "$HOME/.claude" cat > "$CLAUDE_SETTINGS" << 'SETTINGS' { "hooks": { "PreToolUse": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "node $HOME/.acp/govern.mjs", "timeout": 5 } ] } ] } } SETTINGS echo " [Claude Code] Hook registered" INSTALLED="${INSTALLED:+$INSTALLED, }Claude Code" fi fi # ── Step 1b: OpenClaw setup ─────────────────────────────────────────── if [ "$HAS_OPENCLAW" = true ]; then echo " [OpenClaw] Installing governance plugin..." openclaw plugins install @gatewaystack/acp-governance 2>/dev/null && { echo " [OpenClaw] Plugin installed" INSTALLED="${INSTALLED:+$INSTALLED, }OpenClaw" } || { echo " [OpenClaw] Plugin install failed — try: openclaw plugins install @gatewaystack/acp-governance" } fi # ── Step 2: Authenticate ────────────────────────────────────────────── if [ -f "$CREDS_FILE" ]; then echo " Credentials already configured." echo "" read -p " Reconfigure? (y/N) " -n 1 -r /dev/null; then open "$AUTH_URL" elif command -v xdg-open &> /dev/null; then xdg-open "$AUTH_URL" else echo " Open this URL in your browser:" echo " $AUTH_URL" echo "" fi echo " Opening browser to set up your workspace..." echo "" AUTH_URL="$DASHBOARD_BASE/plugin/authorize?setup=cli" if command -v open &> /dev/null; then open "$AUTH_URL" elif command -v xdg-open &> /dev/null; then xdg-open "$AUTH_URL" else echo " Open this URL in your browser:" echo " $AUTH_URL" fi # ── Done ────────────────────────────────────────────────────────────── echo "" echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo " Hooks installed for: $INSTALLED" echo " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo " Complete setup in the browser. After logging in, the page" echo " will show your API key. Save it with:" echo "" echo " echo 'YOUR_API_KEY' > ~/.acp/credentials" echo "" if [ "$HAS_CLAUDE" = true ]; then echo " Then restart Claude Code (Ctrl+C, then claude --continue)" fi if [ "$HAS_OPENCLAW" = true ]; then echo " Then restart OpenClaw to activate the plugin" fi echo ""