multicorn-shield 1.3.1 → 1.3.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/CHANGELOG.md CHANGED
@@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - Bump `version` in `package.json` before publishing to npm.
11
11
 
12
+ ## [1.3.2] - 2026-05-07
13
+
14
+ ### Fixed
15
+
16
+ - Pass action cost (USD) to the backend when logging approved and spending-blocked actions via the MCP proxy. Previously cost was computed locally for spending-limit checks but never sent to the API, causing all agent spend totals to show $0.
17
+ - Add optional `cost` field to `ActionLogPayload` in `shield-client.ts` so OpenClaw plugin callers can include cost when it becomes available upstream.
18
+ - Sanitise cost values extracted from tool arguments before logging (reject negative, NaN, Infinity, and values exceeding $1M).
19
+
20
+ ### Added
21
+
22
+ - Print signup URL and API key instructions before the key prompt in the CLI wizard for new users who don't yet have an account.
23
+ - Print dashboard URL at the end of the CLI wizard setup summary.
24
+ - Log a one-time "First action recorded" message with dashboard link on the first approved action in both the MCP proxy and OpenClaw plugin.
25
+
12
26
  ## [1.3.1] - 2026-05-07
13
27
 
14
28
  ### Fixed
@@ -1724,6 +1724,16 @@ async function runInit(explicitBaseUrl) {
1724
1724
  apiKey = existing.apiKey;
1725
1725
  }
1726
1726
  }
1727
+ if (apiKey.length === 0) {
1728
+ const signupDashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
1729
+ console.log("");
1730
+ console.log(" Multicorn Shield controls what your AI agents can do.");
1731
+ console.log(" You need a free account to get an API key.");
1732
+ console.log("");
1733
+ console.log(` 1. Sign up or log in \u2192 ${signupDashboardUrl}`);
1734
+ console.log(" 2. Go to Settings \u2192 API Keys to create a key");
1735
+ console.log("");
1736
+ }
1727
1737
  while (apiKey.length === 0) {
1728
1738
  const input = await ask("API key (starts with mcs_): ");
1729
1739
  const key = input.trim();
@@ -2416,6 +2426,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2416
2426
  process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
2417
2427
  process.stderr.write(blocks.join("") + "\n");
2418
2428
  }
2429
+ const dashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
2430
+ console.log("");
2431
+ console.log(" Your dashboard");
2432
+ console.log(` \u2192 ${dashboardUrl}/agents`);
2433
+ console.log("");
2434
+ console.log(" Use any tool in your agent to see it appear.");
2435
+ console.log("");
2419
2436
  }
2420
2437
  return lastConfig;
2421
2438
  }
@@ -3012,6 +3029,7 @@ function createProxyServer(config) {
3012
3029
  const pendingLines = [];
3013
3030
  let draining = false;
3014
3031
  let stopped = false;
3032
+ let hasLoggedFirstAction = false;
3015
3033
  async function refreshScopes() {
3016
3034
  if (stopped) return;
3017
3035
  if (agentId.length === 0) return;
@@ -3117,8 +3135,10 @@ function createProxyServer(config) {
3117
3135
  );
3118
3136
  }
3119
3137
  }
3138
+ const rawCostCents = extractCostCents(toolParams.arguments);
3139
+ const costCents = Number.isFinite(rawCostCents) && rawCostCents > 0 ? Math.min(rawCostCents, 1e8) : 0;
3140
+ const costUsd = costCents > 0 ? costCents / 100 : void 0;
3120
3141
  if (spendingChecker !== null) {
3121
- const costCents = extractCostCents(toolParams.arguments);
3122
3142
  if (costCents > 0) {
3123
3143
  const spendResult = spendingChecker.checkSpend(costCents);
3124
3144
  if (!spendResult.allowed) {
@@ -3137,7 +3157,8 @@ function createProxyServer(config) {
3137
3157
  agent: config.agentName,
3138
3158
  service,
3139
3159
  actionType: action,
3140
- status: "blocked"
3160
+ status: "blocked",
3161
+ ...costUsd !== void 0 ? { cost: costUsd } : {}
3141
3162
  });
3142
3163
  config.logger.debug("Spending-blocked action logged.", { tool: toolParams.name });
3143
3164
  }
@@ -3165,9 +3186,15 @@ function createProxyServer(config) {
3165
3186
  agent: config.agentName,
3166
3187
  service,
3167
3188
  actionType: action,
3168
- status: "approved"
3189
+ status: "approved",
3190
+ ...costUsd !== void 0 ? { cost: costUsd } : {}
3169
3191
  });
3170
3192
  config.logger.debug("Approved action logged.", { tool: toolParams.name });
3193
+ if (!hasLoggedFirstAction) {
3194
+ hasLoggedFirstAction = true;
3195
+ const dashUrl = deriveDashboardUrl(config.baseUrl).replace(/\/+$/, "");
3196
+ config.logger.info(`First action recorded. View activity \u2192 ${dashUrl}/agents`);
3197
+ }
3171
3198
  }
3172
3199
  }
3173
3200
  return null;
@@ -1795,6 +1795,16 @@ async function runInit(explicitBaseUrl) {
1795
1795
  apiKey = existing.apiKey;
1796
1796
  }
1797
1797
  }
1798
+ if (apiKey.length === 0) {
1799
+ const signupDashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
1800
+ console.log("");
1801
+ console.log(" Multicorn Shield controls what your AI agents can do.");
1802
+ console.log(" You need a free account to get an API key.");
1803
+ console.log("");
1804
+ console.log(` 1. Sign up or log in \u2192 ${signupDashboardUrl}`);
1805
+ console.log(" 2. Go to Settings \u2192 API Keys to create a key");
1806
+ console.log("");
1807
+ }
1798
1808
  while (apiKey.length === 0) {
1799
1809
  const input = await ask("API key (starts with mcs_): ");
1800
1810
  const key = input.trim();
@@ -2487,6 +2497,13 @@ You have ${String(agentsForPlatform.length)} agent(s) connected for ${selectedLa
2487
2497
  process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
2488
2498
  process.stderr.write(blocks.join("") + "\n");
2489
2499
  }
2500
+ const dashboardUrl = deriveDashboardUrl(resolvedBaseUrl).replace(/\/+$/, "");
2501
+ console.log("");
2502
+ console.log(" Your dashboard");
2503
+ console.log(` \u2192 ${dashboardUrl}/agents`);
2504
+ console.log("");
2505
+ console.log(" Use any tool in your agent to see it appear.");
2506
+ console.log("");
2490
2507
  }
2491
2508
  return lastConfig;
2492
2509
  }
@@ -2964,6 +2981,7 @@ function createProxyServer(config) {
2964
2981
  const pendingLines = [];
2965
2982
  let draining = false;
2966
2983
  let stopped = false;
2984
+ let hasLoggedFirstAction = false;
2967
2985
  async function refreshScopes() {
2968
2986
  if (stopped) return;
2969
2987
  if (agentId.length === 0) return;
@@ -3069,8 +3087,10 @@ function createProxyServer(config) {
3069
3087
  );
3070
3088
  }
3071
3089
  }
3090
+ const rawCostCents = extractCostCents(toolParams.arguments);
3091
+ const costCents = Number.isFinite(rawCostCents) && rawCostCents > 0 ? Math.min(rawCostCents, 1e8) : 0;
3092
+ const costUsd = costCents > 0 ? costCents / 100 : void 0;
3072
3093
  if (spendingChecker !== null) {
3073
- const costCents = extractCostCents(toolParams.arguments);
3074
3094
  if (costCents > 0) {
3075
3095
  const spendResult = spendingChecker.checkSpend(costCents);
3076
3096
  if (!spendResult.allowed) {
@@ -3089,7 +3109,8 @@ function createProxyServer(config) {
3089
3109
  agent: config.agentName,
3090
3110
  service,
3091
3111
  actionType: action,
3092
- status: "blocked"
3112
+ status: "blocked",
3113
+ ...costUsd !== void 0 ? { cost: costUsd } : {}
3093
3114
  });
3094
3115
  config.logger.debug("Spending-blocked action logged.", { tool: toolParams.name });
3095
3116
  }
@@ -3117,9 +3138,15 @@ function createProxyServer(config) {
3117
3138
  agent: config.agentName,
3118
3139
  service,
3119
3140
  actionType: action,
3120
- status: "approved"
3141
+ status: "approved",
3142
+ ...costUsd !== void 0 ? { cost: costUsd } : {}
3121
3143
  });
3122
3144
  config.logger.debug("Approved action logged.", { tool: toolParams.name });
3145
+ if (!hasLoggedFirstAction) {
3146
+ hasLoggedFirstAction = true;
3147
+ const dashUrl = deriveDashboardUrl(config.baseUrl).replace(/\/+$/, "");
3148
+ config.logger.info(`First action recorded. View activity \u2192 ${dashUrl}/agents`);
3149
+ }
3123
3150
  }
3124
3151
  }
3125
3152
  return null;
@@ -468,6 +468,7 @@ var pluginLogger = null;
468
468
  var pluginConfig;
469
469
  var connectionLogged = false;
470
470
  var pinnedAgentName = null;
471
+ var hasLoggedFirstAction = false;
471
472
  var SCOPE_REFRESH_INTERVAL_MS = 6e4;
472
473
  var cachedMulticornConfig = null;
473
474
  function loadMulticornConfig() {
@@ -863,6 +864,13 @@ function afterToolCall(event, ctx) {
863
864
  config.baseUrl,
864
865
  pluginLogger ?? void 0
865
866
  );
867
+ if (!event.error && !hasLoggedFirstAction) {
868
+ hasLoggedFirstAction = true;
869
+ const dashUrl = deriveDashboardUrl(config.baseUrl).replace(/\/+$/, "");
870
+ if (pluginLogger) {
871
+ pluginLogger.info(`First action recorded. View activity \u2192 ${dashUrl}/agents`);
872
+ }
873
+ }
866
874
  return Promise.resolve();
867
875
  }
868
876
  var plugin = {
@@ -908,6 +916,7 @@ function resetState() {
908
916
  cachedMulticornConfig = null;
909
917
  connectionLogged = false;
910
918
  pinnedAgentName = null;
919
+ hasLoggedFirstAction = false;
911
920
  }
912
921
 
913
922
  export { afterToolCall, beforeToolCall, plugin, readConfig, register, resetState, resolveAgentName };
@@ -22417,7 +22417,7 @@ async function writeExtensionBackup(claudeDesktopConfigPath, mcpServers) {
22417
22417
 
22418
22418
  // package.json
22419
22419
  var package_default = {
22420
- version: "1.3.1"};
22420
+ version: "1.3.2"};
22421
22421
 
22422
22422
  // src/package-meta.ts
22423
22423
  var PACKAGE_VERSION = package_default.version;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "multicorn-shield",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "The control layer for AI agents: permissions, consent, spending limits, and audit logging.",
5
5
  "license": "MIT",
6
6
  "author": "Multicorn AI Pty Ltd",