Skip to content

Commit 8848cca

Browse files
feat: setup command auto-configures proxy with API key detection
Setup now: - Finds Anthropic API key from auth.json or env - Writes proxy config to plugins.entries.carapace.config - Patches both openclaw.json and models.json with baseUrl - Does NOT deny built-in tools when proxy is enabled - Falls back to tool-deny mode only if no API key found - Supports --no-proxy flag to skip proxy setup
1 parent 6c062b1 commit 8848cca

File tree

3 files changed

+121
-29
lines changed

3 files changed

+121
-29
lines changed

openclaw.plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "carapace",
33
"name": "Carapace",
44
"description": "Immutable policy boundaries for MCP tool access. Your agent's exoskeleton.",
5-
"version": "0.4.4",
5+
"version": "0.5.0",
66
"configSchema": {
77
"type": "object",
88
"additionalProperties": true,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@clawdreyhepburn/carapace",
3-
"version": "0.4.4",
3+
"version": "0.5.0",
44
"description": "Immutable policy boundaries for MCP tool access. Powered by Cedar + Cedarling WASM.",
55
"license": "Apache-2.0",
66
"type": "module",

src/index.ts

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -634,47 +634,139 @@ export default function register(api: OpenClawPluginApi) {
634634

635635
cmd.command("setup")
636636
.description("Configure OpenClaw to route all traffic through Carapace")
637-
.action(async () => {
637+
.option("--no-proxy", "Skip LLM proxy setup (tool-deny mode only)")
638+
.action(async (opts: any) => {
639+
const { readFileSync, writeFileSync, existsSync, copyFileSync } = require("node:fs");
640+
const { join } = require("node:path");
641+
const { homedir } = require("node:os");
642+
638643
console.log("\n🦞 Carapace Setup\n");
639644
backupConfig();
640645
console.log(" 📦 Backed up openclaw.json → openclaw.json.carapace-backup");
641646
let anyChanges = false;
642647

643-
// 1. Deny built-in bypass tools
644-
const bypasses = checkForBypasses();
645-
if (bypasses.length > 0) {
646-
console.log(" Denying built-in tools that bypass Cedar:");
647-
const { patched, alreadyDenied } = patchConfigDenyTools();
648-
if (alreadyDenied.length > 0) {
649-
console.log(` Already denied: ${alreadyDenied.join(", ")}`);
648+
const configPath = join(homedir(), ".openclaw", "openclaw.json");
649+
const cfg = JSON.parse(readFileSync(configPath, "utf-8"));
650+
const skipProxy = opts.noProxy === true;
651+
652+
// Detect if proxy is already configured in the live config
653+
const existingProxyEnabled = cfg.plugins?.entries?.carapace?.config?.proxy?.enabled;
654+
655+
if (!skipProxy && !existingProxyEnabled) {
656+
// Enable the proxy by default — find the API key automatically
657+
console.log(" 🔍 Looking for Anthropic API key...");
658+
let apiKey = "";
659+
660+
// Check auth.json
661+
const authPath = join(homedir(), ".openclaw", "agents", "main", "agent", "auth.json");
662+
if (existsSync(authPath)) {
663+
try {
664+
const auth = JSON.parse(readFileSync(authPath, "utf-8"));
665+
apiKey = auth.anthropic?.key ?? "";
666+
} catch {}
650667
}
651-
if (patched.length > 0) {
652-
console.log(` ✅ Added to tools.deny: ${patched.join(", ")}`);
668+
669+
// Check environment
670+
if (!apiKey) apiKey = process.env.ANTHROPIC_API_KEY ?? "";
671+
672+
if (!apiKey) {
673+
console.log(" ⚠️ Could not find Anthropic API key.");
674+
console.log(" Checked: ~/.openclaw/agents/main/agent/auth.json, ANTHROPIC_API_KEY env");
675+
console.log(" Falling back to tool-deny mode (no proxy).\n");
676+
} else {
677+
console.log(` Found key: ${apiKey.slice(0, 12)}...${apiKey.slice(-4)}`);
678+
679+
// Write proxy config into plugin entry
680+
if (!cfg.plugins) cfg.plugins = {};
681+
if (!cfg.plugins.entries) cfg.plugins.entries = {};
682+
if (!cfg.plugins.entries.carapace) cfg.plugins.entries.carapace = {};
683+
cfg.plugins.entries.carapace.enabled = true;
684+
cfg.plugins.entries.carapace.config = {
685+
defaultPolicy: "allow-all",
686+
proxy: {
687+
enabled: true,
688+
port: 19821,
689+
upstream: "https://api.anthropic.com",
690+
apiKey,
691+
},
692+
};
693+
694+
// Set models.providers.anthropic.baseUrl
695+
const proxyUrl = "http://127.0.0.1:19821";
696+
if (!cfg.models) cfg.models = {};
697+
if (!cfg.models.mode) cfg.models.mode = "merge";
698+
if (!cfg.models.providers) cfg.models.providers = {};
699+
if (!cfg.models.providers.anthropic) cfg.models.providers.anthropic = {};
700+
if (!Array.isArray(cfg.models.providers.anthropic.models)) {
701+
cfg.models.providers.anthropic.models = [];
702+
}
703+
if (cfg.models.providers.anthropic.baseUrl && cfg.models.providers.anthropic.baseUrl !== proxyUrl) {
704+
cfg.models.providers.anthropic._originalBaseUrl = cfg.models.providers.anthropic.baseUrl;
705+
}
706+
cfg.models.providers.anthropic.baseUrl = proxyUrl;
707+
708+
// Do NOT deny built-in tools when proxy is enabled — proxy handles filtering
709+
// Remove any previous tool denials from earlier setup runs
710+
if (cfg.tools?.deny) {
711+
cfg.tools.deny = cfg.tools.deny.filter((t: string) => !BYPASS_TOOLS.includes(t));
712+
if (cfg.tools.deny.length === 0) delete cfg.tools.deny;
713+
if (cfg.tools && Object.keys(cfg.tools).length === 0) delete cfg.tools;
714+
}
715+
716+
writeFileSync(configPath, JSON.stringify(cfg, null, 2) + "\n", "utf-8");
717+
718+
// Also patch models.json directly
719+
const modelsPath = join(homedir(), ".openclaw", "agents", "main", "agent", "models.json");
720+
if (existsSync(modelsPath)) {
721+
try {
722+
const modelsBackup = modelsPath + ".carapace-backup";
723+
if (!existsSync(modelsBackup)) copyFileSync(modelsPath, modelsBackup);
724+
const models = JSON.parse(readFileSync(modelsPath, "utf-8"));
725+
if (!models.providers) models.providers = {};
726+
if (!models.providers.anthropic) models.providers.anthropic = {};
727+
if (models.providers.anthropic.baseUrl && models.providers.anthropic.baseUrl !== proxyUrl) {
728+
models.providers.anthropic._originalBaseUrl = models.providers.anthropic.baseUrl;
729+
}
730+
models.providers.anthropic.baseUrl = proxyUrl;
731+
writeFileSync(modelsPath, JSON.stringify(models, null, 2) + "\n", "utf-8");
732+
console.log(" ✅ Patched models.json with proxy baseUrl");
733+
} catch (e: any) {
734+
console.log(` ⚠️ Could not patch models.json: ${e.message}`);
735+
}
736+
}
737+
738+
console.log(" ✅ LLM proxy enabled (allow-all policy, port 19821)");
739+
console.log(" All API calls will route through Cedar.");
740+
console.log(" Built-in tools (exec, web_fetch, web_search) are NOT denied — proxy handles them.\n");
653741
anyChanges = true;
654742
}
655-
} else {
656-
console.log(" ✅ Built-in bypass tools already denied.");
657-
}
658-
659-
// 2. Set up LLM proxy baseUrl if proxy is configured
660-
if (config.proxy?.enabled) {
661-
console.log("\n Configuring LLM proxy baseUrl:");
743+
} else if (existingProxyEnabled) {
744+
console.log(" ✅ LLM proxy already configured.");
745+
// Ensure baseUrl is set
746+
console.log("\n Verifying baseUrl configuration:");
662747
const { patched, alreadySet } = patchConfigProxyBaseUrl();
663-
if (alreadySet.length > 0) {
664-
console.log(` Already set: ${alreadySet.join(", ")}`);
665-
}
748+
if (alreadySet.length > 0) console.log(` Already set: ${alreadySet.join(", ")}`);
666749
if (patched.length > 0) {
667750
console.log(` ✅ Set models.providers baseUrl for: ${patched.join(", ")}`);
668751
anyChanges = true;
669752
}
670-
if (patched.length === 0 && alreadySet.length === 0) {
671-
console.log(" ⚠️ No upstream providers configured in proxy config.");
672-
console.log(' Set proxy.upstream to a URL string (e.g., "https://api.anthropic.com") with proxy.apiKey,');
673-
console.log(" or use the object format: proxy.upstream = { anthropic: { apiKey: '...' } }");
753+
}
754+
755+
// If proxy not enabled (skipped or no key), fall back to tool-deny mode
756+
const finalCfg = JSON.parse(readFileSync(configPath, "utf-8"));
757+
if (!finalCfg.plugins?.entries?.carapace?.config?.proxy?.enabled) {
758+
console.log(" Falling back to tool-deny mode:");
759+
const bypasses = checkForBypasses();
760+
if (bypasses.length > 0) {
761+
const { patched, alreadyDenied } = patchConfigDenyTools();
762+
if (alreadyDenied.length > 0) console.log(` Already denied: ${alreadyDenied.join(", ")}`);
763+
if (patched.length > 0) {
764+
console.log(` ✅ Added to tools.deny: ${patched.join(", ")}`);
765+
anyChanges = true;
766+
}
767+
} else {
768+
console.log(" ✅ Built-in bypass tools already denied.");
674769
}
675-
} else {
676-
console.log("\n LLM proxy not enabled — skipping baseUrl setup.");
677-
console.log(" To enable, add proxy.enabled: true to your Carapace plugin config.");
678770
}
679771

680772
if (anyChanges) {

0 commit comments

Comments
 (0)