@clawdreyhepburn/carapace 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@
2
2
  "id": "carapace",
3
3
  "name": "Carapace",
4
4
  "description": "Immutable policy boundaries for MCP tool access. Your agent's exoskeleton.",
5
- "version": "0.4.3",
5
+ "version": "0.5.0",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawdreyhepburn/carapace",
3
- "version": "0.4.3",
3
+ "version": "0.5.0",
4
4
  "description": "Immutable policy boundaries for MCP tool access. Powered by Cedar + Cedarling WASM.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/index.ts CHANGED
@@ -634,47 +634,139 @@ export default function register(api: OpenClawPluginApi) {
634
634
 
635
635
  cmd.command("setup")
636
636
  .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
+
638
643
  console.log("\nšŸ¦ž Carapace Setup\n");
639
644
  backupConfig();
640
645
  console.log(" šŸ“¦ Backed up openclaw.json → openclaw.json.carapace-backup");
641
646
  let anyChanges = false;
642
647
 
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 {}
650
667
  }
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");
653
741
  anyChanges = true;
654
742
  }
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:");
662
747
  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(", ")}`);
666
749
  if (patched.length > 0) {
667
750
  console.log(` āœ… Set models.providers baseUrl for: ${patched.join(", ")}`);
668
751
  anyChanges = true;
669
752
  }
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.");
674
769
  }
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.");
678
770
  }
679
771
 
680
772
  if (anyChanges) {
@@ -773,38 +865,6 @@ export default function register(api: OpenClawPluginApi) {
773
865
  }
774
866
  }
775
867
 
776
- // Clean up per-agent models.json (this is what actually routes API calls)
777
- const agentModelsPath = join(homedir(), ".openclaw", "agents", "main", "agent", "models.json");
778
- if (existsSync(agentModelsPath)) {
779
- try {
780
- const agentModels = JSON.parse(readFileSync(agentModelsPath, "utf-8"));
781
- let modelsChanged = false;
782
- if (agentModels.providers) {
783
- for (const [name, provCfg] of Object.entries(agentModels.providers)) {
784
- if ((provCfg as any)?.baseUrl === proxyUrl) {
785
- if ((provCfg as any)._originalBaseUrl) {
786
- (provCfg as any).baseUrl = (provCfg as any)._originalBaseUrl;
787
- delete (provCfg as any)._originalBaseUrl;
788
- } else {
789
- delete (provCfg as any).baseUrl;
790
- }
791
- // Remove empty provider entries (but keep ones with other config)
792
- const remaining = Object.keys(provCfg as any).filter(k => k !== 'models' || (provCfg as any).models?.length > 0);
793
- if (remaining.length === 0) delete agentModels.providers[name];
794
- modelsChanged = true;
795
- console.log(` āœ… Cleaned proxy baseUrl from models.json (${name})`);
796
- }
797
- }
798
- }
799
- if (modelsChanged) {
800
- writeFileSync(agentModelsPath, JSON.stringify(agentModels, null, 2) + "\n", "utf-8");
801
- changed = true;
802
- }
803
- } catch (e: any) {
804
- console.log(` āš ļø Could not clean models.json: ${e.message}`);
805
- }
806
- }
807
-
808
868
  // Disable the plugin entry (don't delete — user might want to re-enable)
809
869
  if (cfg.plugins?.entries?.carapace?.enabled) {
810
870
  cfg.plugins.entries.carapace.enabled = false;