multicorn-shield 0.6.0 → 0.6.2

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.
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![Multicorn Shield](readme-header.svg)
1
+ ![Multicorn Shield](https://multicorn.ai/images/og-image-shield.png)
2
2
 
3
3
  # Multicorn Shield
4
4
 
@@ -196,7 +196,7 @@ With the dashboard you can:
196
196
  The dashboard works with both the SDK integration and the MCP proxy. No extra setup needed.
197
197
 
198
198
  <p align="center">
199
- <img src="https://app.multicorn.ai/og-image-shield.png" alt="Dashboard overview showing total actions, blocked count, spend, and live activity feed" width="800" />
199
+ <img src="https://multicorn.ai/images/og-image-shield.png" alt="Dashboard overview showing total actions, blocked count, spend, and live activity feed" width="800" />
200
200
  </p>
201
201
 
202
202
  <p align="center">
@@ -94,32 +94,69 @@ function collectAgentsFromConfig(cfg) {
94
94
  }
95
95
  return [];
96
96
  }
97
- async function loadConfig() {
97
+ async function parseConfigFile() {
98
98
  try {
99
99
  const raw = await readFile(CONFIG_PATH, "utf8");
100
- const parsed = JSON.parse(raw);
101
- if (!isProxyConfig(parsed)) return null;
102
- const obj = parsed;
103
- const agentNameRaw = obj["agentName"];
104
- const agentsRaw = obj["agents"];
105
- const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
106
- const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
107
- if (!needsMigrate) {
108
- return parsed;
109
- }
110
- const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
111
- const next = { ...obj };
112
- delete next["agentName"];
113
- delete next["platform"];
114
- next["agents"] = [{ name: agentNameRaw, platform }];
115
- next["defaultAgent"] = agentNameRaw;
116
- const migrated = next;
117
- await saveConfig(migrated);
118
- return migrated;
119
- } catch {
120
- return null;
100
+ try {
101
+ return { kind: "ok", value: JSON.parse(raw) };
102
+ } catch {
103
+ return { kind: "parseError" };
104
+ }
105
+ } catch (error) {
106
+ if (isErrnoException(error) && error.code === "ENOENT") {
107
+ return { kind: "missing" };
108
+ }
109
+ const message = error instanceof Error ? error.message : String(error);
110
+ return { kind: "readError", message };
121
111
  }
122
112
  }
113
+ function isAllowedShieldApiBaseUrl(url) {
114
+ return url.startsWith("https://") || url.startsWith("http://localhost") || url.startsWith("http://127.0.0.1");
115
+ }
116
+ async function loadConfig() {
117
+ const result = await parseConfigFile();
118
+ if (result.kind !== "ok") return null;
119
+ const parsed = result.value;
120
+ if (!isProxyConfig(parsed)) return null;
121
+ const obj = parsed;
122
+ const agentNameRaw = obj["agentName"];
123
+ const agentsRaw = obj["agents"];
124
+ const hasNonEmptyAgents = Array.isArray(agentsRaw) && agentsRaw.length > 0 && agentsRaw.every((e) => isAgentEntry(e));
125
+ const needsMigrate = typeof agentNameRaw === "string" && agentNameRaw.length > 0 && !hasNonEmptyAgents;
126
+ if (!needsMigrate) {
127
+ return parsed;
128
+ }
129
+ const platform = typeof obj["platform"] === "string" && obj["platform"].length > 0 ? obj["platform"] : "unknown";
130
+ const next = { ...obj };
131
+ delete next["agentName"];
132
+ delete next["platform"];
133
+ next["agents"] = [{ name: agentNameRaw, platform }];
134
+ next["defaultAgent"] = agentNameRaw;
135
+ const migrated = next;
136
+ await saveConfig(migrated);
137
+ return migrated;
138
+ }
139
+ async function readBaseUrlFromConfig() {
140
+ const result = await parseConfigFile();
141
+ if (result.kind === "missing") return void 0;
142
+ if (result.kind === "readError") {
143
+ process.stderr.write(
144
+ style.yellow(`Warning: could not read base URL from config file: ${result.message}`) + "\n"
145
+ );
146
+ return void 0;
147
+ }
148
+ if (result.kind === "parseError") {
149
+ process.stderr.write(
150
+ style.yellow("Warning: could not parse ~/.multicorn/config.json as JSON.") + "\n"
151
+ );
152
+ return void 0;
153
+ }
154
+ const parsed = result.value;
155
+ if (typeof parsed !== "object" || parsed === null) return void 0;
156
+ const u = parsed["baseUrl"];
157
+ if (typeof u !== "string" || u.length === 0) return void 0;
158
+ return u;
159
+ }
123
160
  async function deleteAgentByName(name) {
124
161
  const config = await loadConfig();
125
162
  if (config === null) return false;
@@ -507,7 +544,8 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
507
544
  );
508
545
  }
509
546
  }
510
- async function runInit(baseUrl = "https://api.multicorn.ai") {
547
+ var DEFAULT_SHIELD_API_BASE_URL = "https://api.multicorn.ai";
548
+ async function runInit(explicitBaseUrl) {
511
549
  if (!process.stdin.isTTY) {
512
550
  process.stderr.write(
513
551
  style.red("Error: interactive terminal required. Cannot run init with piped input.") + "\n"
@@ -525,16 +563,29 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
525
563
  style.dim("Get your API key at https://app.multicorn.ai/settings/api-keys") + "\n\n"
526
564
  );
527
565
  const existing = await loadConfig().catch(() => null);
528
- if (baseUrl === "https://api.multicorn.ai") {
529
- if (existing !== null && existing.baseUrl.length > 0) {
530
- baseUrl = existing.baseUrl;
566
+ let resolvedBaseUrl;
567
+ if (explicitBaseUrl !== void 0 && explicitBaseUrl.trim().length > 0) {
568
+ resolvedBaseUrl = explicitBaseUrl.trim();
569
+ } else if (existing !== null && existing.baseUrl.length > 0) {
570
+ resolvedBaseUrl = existing.baseUrl;
571
+ } else {
572
+ const fromFile = await readBaseUrlFromConfig();
573
+ if (fromFile !== void 0 && fromFile.length > 0) {
574
+ resolvedBaseUrl = fromFile;
531
575
  } else {
532
576
  const envBaseUrl = process.env["MULTICORN_BASE_URL"];
533
- if (envBaseUrl !== void 0 && envBaseUrl.length > 0) {
534
- baseUrl = envBaseUrl;
535
- }
577
+ resolvedBaseUrl = envBaseUrl !== void 0 && envBaseUrl.trim().length > 0 ? envBaseUrl.trim() : DEFAULT_SHIELD_API_BASE_URL;
536
578
  }
537
579
  }
580
+ if (!isAllowedShieldApiBaseUrl(resolvedBaseUrl)) {
581
+ process.stderr.write(
582
+ style.red(
583
+ "Base URL must use HTTPS (or http://localhost for local development). Received a non-HTTPS URL from config. Use --base-url to override."
584
+ ) + "\n"
585
+ );
586
+ rl.close();
587
+ return null;
588
+ }
538
589
  let apiKey = "";
539
590
  if (existing !== null && existing.apiKey.startsWith("mcs_") && existing.apiKey.length >= 8) {
540
591
  const masked = "mcs_..." + existing.apiKey.slice(-4);
@@ -554,7 +605,7 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
554
605
  const spinner = withSpinner("Validating key...");
555
606
  let result;
556
607
  try {
557
- result = await validateApiKey(key, baseUrl);
608
+ result = await validateApiKey(key, resolvedBaseUrl);
558
609
  } catch (error) {
559
610
  spinner.stop(false, "Validation failed");
560
611
  throw error;
@@ -566,18 +617,11 @@ async function runInit(baseUrl = "https://api.multicorn.ai") {
566
617
  spinner.stop(true, "Key validated");
567
618
  apiKey = key;
568
619
  }
569
- if (!baseUrl.startsWith("https://") && !baseUrl.startsWith("http://localhost") && !baseUrl.startsWith("http://127.0.0.1")) {
570
- process.stderr.write(
571
- style.red(`\u2717 Shield API base URL must use HTTPS. Got: ${baseUrl}`) + "\n"
572
- );
573
- rl.close();
574
- return null;
575
- }
576
620
  const configuredAgents = [];
577
621
  let currentAgents = collectAgentsFromConfig(existing);
578
622
  let lastConfig = {
579
623
  apiKey,
580
- baseUrl,
624
+ baseUrl: resolvedBaseUrl,
581
625
  ...currentAgents.length > 0 ? {
582
626
  agents: currentAgents,
583
627
  defaultAgent: existing !== null && typeof existing.defaultAgent === "string" && existing.defaultAgent.length > 0 ? existing.defaultAgent : currentAgents[currentAgents.length - 1]?.name ?? ""
@@ -657,7 +701,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
657
701
  }
658
702
  const spinner = withSpinner("Updating OpenClaw config...");
659
703
  try {
660
- const result = await updateOpenClawConfigIfPresent(apiKey, baseUrl, agentName);
704
+ const result = await updateOpenClawConfigIfPresent(apiKey, resolvedBaseUrl, agentName);
661
705
  if (result === "not-found") {
662
706
  spinner.stop(false, "OpenClaw config disappeared unexpectedly.");
663
707
  rl.close();
@@ -712,7 +756,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
712
756
  const spinner = withSpinner("Creating proxy config...");
713
757
  try {
714
758
  proxyUrl = await createProxyConfig(
715
- baseUrl,
759
+ resolvedBaseUrl,
716
760
  apiKey,
717
761
  agentName,
718
762
  targetUrl,
@@ -750,7 +794,7 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
750
794
  currentAgents.push({ name: agentName, platform: selectedPlatform });
751
795
  const raw = existing !== null ? { ...existing } : {};
752
796
  raw["apiKey"] = apiKey;
753
- raw["baseUrl"] = baseUrl;
797
+ raw["baseUrl"] = resolvedBaseUrl;
754
798
  raw["agents"] = currentAgents;
755
799
  raw["defaultAgent"] = agentName;
756
800
  delete raw["agentName"];
@@ -1920,7 +1964,7 @@ function parseArgs(argv) {
1920
1964
  let wrapCommand = "";
1921
1965
  let wrapArgs = [];
1922
1966
  let logLevel = "info";
1923
- let baseUrl = "https://api.multicorn.ai";
1967
+ let baseUrl = void 0;
1924
1968
  let dashboardUrl = "";
1925
1969
  let agentName = "";
1926
1970
  let deleteAgentName = "";
@@ -2098,13 +2142,13 @@ async function main() {
2098
2142
  `);
2099
2143
  return;
2100
2144
  }
2101
- if (!cli.baseUrl.startsWith("https://") && !cli.baseUrl.startsWith("http://localhost") && !cli.baseUrl.startsWith("http://127.0.0.1")) {
2102
- process.stderr.write(
2103
- `Error: --base-url must use HTTPS. Received: "${cli.baseUrl}"
2104
- Use https:// or http://localhost for local development.
2105
- `
2106
- );
2107
- process.exit(1);
2145
+ if (cli.baseUrl !== void 0 && cli.baseUrl.length > 0) {
2146
+ if (!isAllowedShieldApiBaseUrl(cli.baseUrl)) {
2147
+ process.stderr.write(
2148
+ "Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
2149
+ );
2150
+ process.exit(1);
2151
+ }
2108
2152
  }
2109
2153
  const config = await loadConfig();
2110
2154
  if (config === null) {
@@ -2113,8 +2157,16 @@ Use https:// or http://localhost for local development.
2113
2157
  );
2114
2158
  process.exit(1);
2115
2159
  }
2160
+ const finalBaseUrl = cli.baseUrl !== void 0 && cli.baseUrl.length > 0 ? cli.baseUrl : config.baseUrl;
2161
+ if (cli.baseUrl === void 0 || cli.baseUrl.length === 0) {
2162
+ if (!isAllowedShieldApiBaseUrl(finalBaseUrl)) {
2163
+ process.stderr.write(
2164
+ "Error: --base-url must use HTTPS. Received a non-HTTPS URL.\nUse https:// or http://localhost for local development.\n"
2165
+ );
2166
+ process.exit(1);
2167
+ }
2168
+ }
2116
2169
  const agentName = resolveWrapAgentName(cli, config);
2117
- const finalBaseUrl = cli.baseUrl !== "https://api.multicorn.ai" ? cli.baseUrl : config.baseUrl;
2118
2170
  const finalDashboardUrl = cli.dashboardUrl !== "" ? cli.dashboardUrl : deriveDashboardUrl(finalBaseUrl);
2119
2171
  const legacyPlatform = config.platform;
2120
2172
  const platformForServer = typeof legacyPlatform === "string" && legacyPlatform.length > 0 ? legacyPlatform : "other-mcp";
@@ -22358,7 +22358,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22358
22358
 
22359
22359
  // package.json
22360
22360
  var package_default = {
22361
- version: "0.6.0"};
22361
+ version: "0.6.2"};
22362
22362
 
22363
22363
  // src/package-meta.ts
22364
22364
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "type": "module",