multicorn-shield 0.11.0 → 0.12.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [X.Y.Z] - YYYY-MM-DD
9
+
10
+ ### Added
11
+
12
+ - Cline native plugin support via PreToolUse/PostToolUse hooks
13
+ - Hook scripts for Cline: pre-tool-use.cjs, post-tool-use.cjs, shared.cjs
14
+ - Cline plugin README with setup instructions and troubleshooting
15
+ - Browser auto-open for consent screen when Shield blocks an action
16
+ - Licence headers on all plugin scripts
17
+
18
+ ### Changed
19
+
20
+ - CLI wizard installs Cline hooks to ~/Documents/Cline/Hooks/ (previously ~/Documents/Cline/Rules/Hooks/)
21
+ - Cline hook reads toolName field from hook input (Cline v3.81+ sends toolName, not tool)
22
+ - Consent flow no longer polls for approval (blocks immediately with consent URL to avoid Cline's 30-second hook timeout)
23
+ - Extracted shared utilities (config loading, HTTP, tool mapping) into shared.cjs to eliminate duplication between hooks
24
+ - Parameter metadata scrubbed before sending to Shield API (file contents redacted, commands truncated)
25
+ - HTTPS enforced for non-local Shield API connections
26
+
27
+ ### Removed
28
+
29
+ - Polling-based consent approval flow (replaced with immediate block + consent URL)
30
+ - Consent marker filesystem state (no longer needed without polling)
31
+
32
+ ### Security
33
+
34
+ - Fixed Windows shell injection in openBrowser (replaced execSync with execFileSync)
35
+ - Added HTTPS enforcement for non-localhost baseUrl in hook config
36
+ - Added parameter and result scrubbing to prevent sensitive data leakage in audit metadata
37
+
8
38
  ## [0.11.0] - 2026-04-25
9
39
 
10
40
  ### Added
package/dist/badge.js CHANGED
@@ -1,4 +1,4 @@
1
- var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",accent:"#8b5cf6",accentDim:"rgba(139, 92, 246, 0.12)"};var E="#0f172a",y="#f8fafc",A="#f1f5f9",v="#e2e8f0";function h(){return `
1
+ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",accent:"#8b5cf6",accentDim:"rgba(139, 92, 246, 0.12)"};var E="#0f172a",y="#f8fafc",L="#f1f5f9",A="#e2e8f0";function h(){return `
2
2
  :host { display: inline-block; line-height: 0; }
3
3
  .badge {
4
4
  display: inline-flex;
@@ -19,7 +19,7 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
19
19
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
20
20
  }
21
21
  :host([theme="light"]) .badge {
22
- border-color: ${v};
22
+ border-color: ${A};
23
23
  background: ${y};
24
24
  color: ${E};
25
25
  }
@@ -29,7 +29,7 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
29
29
  box-shadow: 0 0 0 1px ${n.accentDim};
30
30
  }
31
31
  :host([theme="light"]) .badge:hover {
32
- background: ${A};
32
+ background: ${L};
33
33
  border-color: ${n.accent};
34
34
  }
35
35
  .badge:focus-visible {
@@ -41,4 +41,4 @@ var n={surface:"#14141f",surfaceHover:"#1a1a2e",border:"#2a2a3d",text:"#e8e8f0",
41
41
  :host([size="compact"]) .text { display: none; }
42
42
  :host([size="compact"]) .badge { padding: 4px 6px; }
43
43
  @media (prefers-reduced-motion: reduce) { .badge { transition: none; } }
44
- `.trim()}var L="https://multicorn.ai/verify/",x="multicorn-badge",w="M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";function C(e){if(e==null||e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}var m=class extends HTMLElement{#e=false;ensureShadow(){return this.shadowRoot!=null?this.shadowRoot:this.attachShadow({mode:"open"})}static get observedAttributes(){return ["agent-id","size","theme","action-count"]}connectedCallback(){this.render();}attributeChangedCallback(){this.render();}render(){let t=this.ensureShadow();if(!this.#e){let g=document.createElement("style");g.textContent=h(),t.appendChild(g),this.#e=true;}let s=(this.getAttribute("agent-id")??"").trim(),c=C(this.getAttribute("action-count")),a=t.querySelector("a.badge");if(a&&a.remove(),s==="")return;let d,l;if(c==null)d="Secured by Multicorn",l="Secured by Multicorn, verify this agent";else {let f=String(c);d="Secured by Multicorn \xB7 "+f+" actions secured",l="Secured by Multicorn \xB7 "+f+" actions secured, verify this agent";}let S=`${L}${encodeURIComponent(s)}`,r=document.createElement("a");r.className="badge",r.href=S,r.target="_blank",r.rel="noopener noreferrer",r.setAttribute("aria-label",l);let p="http://www.w3.org/2000/svg",o=document.createElementNS(p,"svg");o.setAttribute("class","icon"),o.setAttribute("width","16"),o.setAttribute("height","16"),o.setAttribute("viewBox","0 0 24 24"),o.setAttribute("aria-hidden","true");let u=document.createElementNS(p,"path");u.setAttribute("d",w),u.setAttribute("fill",n.accent),o.appendChild(u),r.appendChild(o);let b=document.createElement("span");b.className="text",b.textContent=d,r.appendChild(b),t.appendChild(r);}};customElements.get(x)===void 0&&customElements.define(x,m);function T(e){return e==="compact"||e==="standard"}function H(e){return e==="dark"||e==="light"}var i=(typeof document<"u"&&document.currentScript!==null?document.currentScript:null)??null;if(i==null)console.warn("[Multicorn] badge.js must be loaded as a classic script (document.currentScript was null).");else {let e=i.dataset.agentId?.trim();if(e==null||e==="")console.warn("[Multicorn] Skipping trust badge: missing data-agent-id on the badge script tag.");else {let t=document.createElement("multicorn-badge");t.setAttribute("agent-id",e);let s=i.dataset.size;T(s)&&t.setAttribute("size",s);let c=i.dataset.theme;H(c)&&t.setAttribute("theme",c);let a=i.dataset.actionCount;a!=null&&a!==""&&t.setAttribute("action-count",a),i.parentNode?.insertBefore(t,i.nextSibling);}}
44
+ `.trim()}var T="https://multicorn.ai/verify/",x="multicorn-badge",v="M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z",w=typeof HTMLElement<"u"?HTMLElement:class{connectedCallback(){}};function C(e){if(e==null||e==="")return;let t=Number(e);return Number.isNaN(t)?void 0:t}var m=class extends w{#e=false;ensureShadow(){return this.shadowRoot!=null?this.shadowRoot:this.attachShadow({mode:"open"})}static get observedAttributes(){return ["agent-id","size","theme","action-count"]}connectedCallback(){this.render();}attributeChangedCallback(){this.render();}render(){let t=this.ensureShadow();if(!this.#e){let g=document.createElement("style");g.textContent=h(),t.appendChild(g),this.#e=true;}let s=(this.getAttribute("agent-id")??"").trim(),c=C(this.getAttribute("action-count")),a=t.querySelector("a.badge");if(a&&a.remove(),s==="")return;let d,l;if(c==null)d="Secured by Multicorn",l="Secured by Multicorn, verify this agent";else {let f=String(c);d="Secured by Multicorn \xB7 "+f+" actions secured",l="Secured by Multicorn \xB7 "+f+" actions secured, verify this agent";}let S=`${T}${encodeURIComponent(s)}`,o=document.createElement("a");o.className="badge",o.href=S,o.target="_blank",o.rel="noopener noreferrer",o.setAttribute("aria-label",l);let p="http://www.w3.org/2000/svg",r=document.createElementNS(p,"svg");r.setAttribute("class","icon"),r.setAttribute("width","16"),r.setAttribute("height","16"),r.setAttribute("viewBox","0 0 24 24"),r.setAttribute("aria-hidden","true");let u=document.createElementNS(p,"path");u.setAttribute("d",v),u.setAttribute("fill",n.accent),r.appendChild(u),o.appendChild(r);let b=document.createElement("span");b.className="text",b.textContent=d,o.appendChild(b),t.appendChild(o);}};typeof customElements<"u"&&customElements.get(x)===void 0&&customElements.define(x,m);function H(e){return e==="compact"||e==="standard"}function M(e){return e==="dark"||e==="light"}var i=(typeof document<"u"&&document.currentScript!==null?document.currentScript:null)??null;if(i==null)console.warn("[Multicorn] badge.js must be loaded as a classic script (document.currentScript was null).");else {let e=i.dataset.agentId?.trim();if(e==null||e==="")console.warn("[Multicorn] Skipping trust badge: missing data-agent-id on the badge script tag.");else {let t=document.createElement("multicorn-badge");t.setAttribute("agent-id",e);let s=i.dataset.size;H(s)&&t.setAttribute("size",s);let c=i.dataset.theme;M(c)&&t.setAttribute("theme",c);let a=i.dataset.actionCount;a!=null&&a!==""&&t.setAttribute("action-count",a),i.parentNode?.insertBefore(t,i.nextSibling);}}
package/dist/index.cjs CHANGED
@@ -4,13 +4,12 @@ var lit = require('lit');
4
4
  var decorators_js = require('lit/decorators.js');
5
5
 
6
6
  var __defProp = Object.defineProperty;
7
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
7
  var __decorateClass = (decorators, target, key, kind) => {
9
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
8
+ var result = void 0 ;
10
9
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
10
  if (decorator = decorators[i])
12
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
- if (kind && result) __defProp(target, key, result);
11
+ result = (decorator(target, key, result) ) || result;
12
+ if (result) __defProp(target, key, result);
14
13
  return result;
15
14
  };
16
15
 
@@ -1128,7 +1127,7 @@ function groupScopesByService(scopes) {
1128
1127
  function scopeKey(scope) {
1129
1128
  return `${scope.service}:${scope.permissionLevel}`;
1130
1129
  }
1131
- exports.MulticornConsent = class MulticornConsent extends lit.LitElement {
1130
+ var MulticornConsent = class extends lit.LitElement {
1132
1131
  constructor() {
1133
1132
  super(...arguments);
1134
1133
  this.agentName = "";
@@ -1201,6 +1200,9 @@ exports.MulticornConsent = class MulticornConsent extends lit.LitElement {
1201
1200
  }
1202
1201
  };
1203
1202
  }
1203
+ static {
1204
+ this.styles = [consentStyles];
1205
+ }
1204
1206
  connectedCallback() {
1205
1207
  super.connectedCallback();
1206
1208
  if (Array.isArray(this.scopes) && this.scopes.length > 0) {
@@ -1574,34 +1576,33 @@ exports.MulticornConsent = class MulticornConsent extends lit.LitElement {
1574
1576
  this.requestUpdate();
1575
1577
  }
1576
1578
  };
1577
- exports.MulticornConsent.styles = [consentStyles];
1578
1579
  __decorateClass([
1579
1580
  decorators_js.property({ type: String, attribute: "agent-name" })
1580
- ], exports.MulticornConsent.prototype, "agentName", 2);
1581
+ ], MulticornConsent.prototype, "agentName");
1581
1582
  __decorateClass([
1582
1583
  decorators_js.property({ type: String, attribute: "agent-color" })
1583
- ], exports.MulticornConsent.prototype, "agentColor", 2);
1584
+ ], MulticornConsent.prototype, "agentColor");
1584
1585
  __decorateClass([
1585
1586
  decorators_js.property({ type: Array, attribute: "scopes" })
1586
- ], exports.MulticornConsent.prototype, "scopes", 2);
1587
+ ], MulticornConsent.prototype, "scopes");
1587
1588
  __decorateClass([
1588
1589
  decorators_js.property({ type: Number, attribute: "spend-limit" })
1589
- ], exports.MulticornConsent.prototype, "spendLimit", 2);
1590
+ ], MulticornConsent.prototype, "spendLimit");
1590
1591
  __decorateClass([
1591
1592
  decorators_js.property({ type: String })
1592
- ], exports.MulticornConsent.prototype, "mode", 2);
1593
+ ], MulticornConsent.prototype, "mode");
1593
1594
  __decorateClass([
1594
1595
  decorators_js.state()
1595
- ], exports.MulticornConsent.prototype, "_grantedScopes", 2);
1596
+ ], MulticornConsent.prototype, "_grantedScopes");
1596
1597
  __decorateClass([
1597
1598
  decorators_js.state()
1598
- ], exports.MulticornConsent.prototype, "_adjustedSpendLimit", 2);
1599
+ ], MulticornConsent.prototype, "_adjustedSpendLimit");
1599
1600
  __decorateClass([
1600
1601
  decorators_js.state()
1601
- ], exports.MulticornConsent.prototype, "_isOpen", 2);
1602
- exports.MulticornConsent = __decorateClass([
1603
- decorators_js.customElement(CONSENT_ELEMENT_TAG)
1604
- ], exports.MulticornConsent);
1602
+ ], MulticornConsent.prototype, "_isOpen");
1603
+ if (typeof customElements !== "undefined" && customElements.get(CONSENT_ELEMENT_TAG) === void 0) {
1604
+ customElements.define(CONSENT_ELEMENT_TAG, MulticornConsent);
1605
+ }
1605
1606
 
1606
1607
  // src/badge/badge-styles.ts
1607
1608
  var LIGHT_TEXT = "#0f172a";
@@ -1659,6 +1660,10 @@ function getBadgeStyleText() {
1659
1660
  var VERIFY_BASE = "https://multicorn.ai/verify/";
1660
1661
  var BADGE_ELEMENT_TAG = "multicorn-badge";
1661
1662
  var SHIELD_PATH = "M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";
1663
+ var SafeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
1664
+ connectedCallback() {
1665
+ }
1666
+ };
1662
1667
  function parseOptionalCount(raw) {
1663
1668
  if (raw == null || raw === "") {
1664
1669
  return void 0;
@@ -1666,7 +1671,7 @@ function parseOptionalCount(raw) {
1666
1671
  const n = Number(raw);
1667
1672
  return Number.isNaN(n) ? void 0 : n;
1668
1673
  }
1669
- var MulticornBadge = class extends HTMLElement {
1674
+ var MulticornBadge = class extends SafeHTMLElement {
1670
1675
  #didInjectStyle = false;
1671
1676
  ensureShadow() {
1672
1677
  if (this.shadowRoot != null) {
@@ -1737,7 +1742,7 @@ var MulticornBadge = class extends HTMLElement {
1737
1742
  root.appendChild(a);
1738
1743
  }
1739
1744
  };
1740
- if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1745
+ if (typeof customElements !== "undefined" && customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1741
1746
  customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
1742
1747
  }
1743
1748
 
@@ -2892,6 +2897,7 @@ exports.AGENT_STATUSES = AGENT_STATUSES;
2892
2897
  exports.BUILT_IN_SERVICES = BUILT_IN_SERVICES;
2893
2898
  exports.CONSENT_ELEMENT_TAG = CONSENT_ELEMENT_TAG;
2894
2899
  exports.MulticornBadge = MulticornBadge;
2900
+ exports.MulticornConsent = MulticornConsent;
2895
2901
  exports.MulticornShield = MulticornShield;
2896
2902
  exports.PERMISSION_LEVELS = PERMISSION_LEVELS;
2897
2903
  exports.SERVICE_NAME_PATTERN = SERVICE_NAME_PATTERN;
package/dist/index.d.cts CHANGED
@@ -1078,7 +1078,11 @@ declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElem
1078
1078
  */
1079
1079
  /** Custom element tag for the trust badge. */
1080
1080
  declare const BADGE_ELEMENT_TAG: "multicorn-badge";
1081
- declare class MulticornBadge extends HTMLElement {
1081
+ declare const SafeHTMLElement: {
1082
+ new (): HTMLElement;
1083
+ prototype: HTMLElement;
1084
+ };
1085
+ declare class MulticornBadge extends SafeHTMLElement {
1082
1086
  #private;
1083
1087
  private ensureShadow;
1084
1088
  static get observedAttributes(): string[];
package/dist/index.d.ts CHANGED
@@ -1078,7 +1078,11 @@ declare function createFocusTrap(container: HTMLElement, initialFocus?: HTMLElem
1078
1078
  */
1079
1079
  /** Custom element tag for the trust badge. */
1080
1080
  declare const BADGE_ELEMENT_TAG: "multicorn-badge";
1081
- declare class MulticornBadge extends HTMLElement {
1081
+ declare const SafeHTMLElement: {
1082
+ new (): HTMLElement;
1083
+ prototype: HTMLElement;
1084
+ };
1085
+ declare class MulticornBadge extends SafeHTMLElement {
1082
1086
  #private;
1083
1087
  private ensureShadow;
1084
1088
  static get observedAttributes(): string[];
package/dist/index.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import { unsafeCSS, css, LitElement, html } from 'lit';
2
- import { property, state, customElement } from 'lit/decorators.js';
2
+ import { property, state } from 'lit/decorators.js';
3
3
 
4
4
  var __defProp = Object.defineProperty;
5
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
5
  var __decorateClass = (decorators, target, key, kind) => {
7
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
6
+ var result = void 0 ;
8
7
  for (var i = decorators.length - 1, decorator; i >= 0; i--)
9
8
  if (decorator = decorators[i])
10
- result = (kind ? decorator(target, key, result) : decorator(result)) || result;
11
- if (kind && result) __defProp(target, key, result);
9
+ result = (decorator(target, key, result) ) || result;
10
+ if (result) __defProp(target, key, result);
12
11
  return result;
13
12
  };
14
13
 
@@ -1199,6 +1198,9 @@ var MulticornConsent = class extends LitElement {
1199
1198
  }
1200
1199
  };
1201
1200
  }
1201
+ static {
1202
+ this.styles = [consentStyles];
1203
+ }
1202
1204
  connectedCallback() {
1203
1205
  super.connectedCallback();
1204
1206
  if (Array.isArray(this.scopes) && this.scopes.length > 0) {
@@ -1572,34 +1574,33 @@ var MulticornConsent = class extends LitElement {
1572
1574
  this.requestUpdate();
1573
1575
  }
1574
1576
  };
1575
- MulticornConsent.styles = [consentStyles];
1576
1577
  __decorateClass([
1577
1578
  property({ type: String, attribute: "agent-name" })
1578
- ], MulticornConsent.prototype, "agentName", 2);
1579
+ ], MulticornConsent.prototype, "agentName");
1579
1580
  __decorateClass([
1580
1581
  property({ type: String, attribute: "agent-color" })
1581
- ], MulticornConsent.prototype, "agentColor", 2);
1582
+ ], MulticornConsent.prototype, "agentColor");
1582
1583
  __decorateClass([
1583
1584
  property({ type: Array, attribute: "scopes" })
1584
- ], MulticornConsent.prototype, "scopes", 2);
1585
+ ], MulticornConsent.prototype, "scopes");
1585
1586
  __decorateClass([
1586
1587
  property({ type: Number, attribute: "spend-limit" })
1587
- ], MulticornConsent.prototype, "spendLimit", 2);
1588
+ ], MulticornConsent.prototype, "spendLimit");
1588
1589
  __decorateClass([
1589
1590
  property({ type: String })
1590
- ], MulticornConsent.prototype, "mode", 2);
1591
+ ], MulticornConsent.prototype, "mode");
1591
1592
  __decorateClass([
1592
1593
  state()
1593
- ], MulticornConsent.prototype, "_grantedScopes", 2);
1594
+ ], MulticornConsent.prototype, "_grantedScopes");
1594
1595
  __decorateClass([
1595
1596
  state()
1596
- ], MulticornConsent.prototype, "_adjustedSpendLimit", 2);
1597
+ ], MulticornConsent.prototype, "_adjustedSpendLimit");
1597
1598
  __decorateClass([
1598
1599
  state()
1599
- ], MulticornConsent.prototype, "_isOpen", 2);
1600
- MulticornConsent = __decorateClass([
1601
- customElement(CONSENT_ELEMENT_TAG)
1602
- ], MulticornConsent);
1600
+ ], MulticornConsent.prototype, "_isOpen");
1601
+ if (typeof customElements !== "undefined" && customElements.get(CONSENT_ELEMENT_TAG) === void 0) {
1602
+ customElements.define(CONSENT_ELEMENT_TAG, MulticornConsent);
1603
+ }
1603
1604
 
1604
1605
  // src/badge/badge-styles.ts
1605
1606
  var LIGHT_TEXT = "#0f172a";
@@ -1657,6 +1658,10 @@ function getBadgeStyleText() {
1657
1658
  var VERIFY_BASE = "https://multicorn.ai/verify/";
1658
1659
  var BADGE_ELEMENT_TAG = "multicorn-badge";
1659
1660
  var SHIELD_PATH = "M12 1L3 5v6c0 5.55 3.84 9.95 9 12 5.16-2.05 9-6.45 9-12V5l-9-4z";
1661
+ var SafeHTMLElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
1662
+ connectedCallback() {
1663
+ }
1664
+ };
1660
1665
  function parseOptionalCount(raw) {
1661
1666
  if (raw == null || raw === "") {
1662
1667
  return void 0;
@@ -1664,7 +1669,7 @@ function parseOptionalCount(raw) {
1664
1669
  const n = Number(raw);
1665
1670
  return Number.isNaN(n) ? void 0 : n;
1666
1671
  }
1667
- var MulticornBadge = class extends HTMLElement {
1672
+ var MulticornBadge = class extends SafeHTMLElement {
1668
1673
  #didInjectStyle = false;
1669
1674
  ensureShadow() {
1670
1675
  if (this.shadowRoot != null) {
@@ -1735,7 +1740,7 @@ var MulticornBadge = class extends HTMLElement {
1735
1740
  root.appendChild(a);
1736
1741
  }
1737
1742
  };
1738
- if (customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1743
+ if (typeof customElements !== "undefined" && customElements.get(BADGE_ELEMENT_TAG) === void 0) {
1739
1744
  customElements.define(BADGE_ELEMENT_TAG, MulticornBadge);
1740
1745
  }
1741
1746
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { existsSync } from 'fs';
3
- import { mkdir, writeFile, readFile, copyFile, unlink } from 'fs/promises';
3
+ import { mkdir, writeFile, readFile, copyFile, chmod, unlink } from 'fs/promises';
4
4
  import { join, dirname } from 'path';
5
5
  import { homedir } from 'os';
6
6
  import { fileURLToPath } from 'url';
@@ -473,19 +473,86 @@ async function installWindsurfNativeHooks() {
473
473
  await mkdir(hooksDir, { recursive: true });
474
474
  await writeFile(hooksPath, JSON.stringify(base, null, 2) + "\n", { encoding: "utf8" });
475
475
  }
476
- var PLATFORM_LABELS = ["OpenClaw", "Claude Code", "Cursor", "Windsurf", "Local MCP / Other"];
476
+ function getClineHooksInstallDir() {
477
+ return join(homedir(), ".multicorn", "cline-hooks");
478
+ }
479
+ function getClineGlobalHooksDir() {
480
+ return join(homedir(), "Documents", "Cline", "Hooks");
481
+ }
482
+ async function installClineNativeHooks() {
483
+ const root = multicornShieldPackageRoot();
484
+ const srcPre = join(root, "plugins", "cline", "hooks", "scripts", "pre-tool-use.cjs");
485
+ const srcPost = join(root, "plugins", "cline", "hooks", "scripts", "post-tool-use.cjs");
486
+ const srcShared = join(root, "plugins", "cline", "hooks", "scripts", "shared.cjs");
487
+ if (!existsSync(srcPre) || !existsSync(srcPost) || !existsSync(srcShared)) {
488
+ throw new Error(
489
+ `Could not find Shield Cline hook scripts at ${srcPre}. If you use npm, install the latest multicorn-shield package.`
490
+ );
491
+ }
492
+ const installDir = getClineHooksInstallDir();
493
+ await mkdir(installDir, { recursive: true });
494
+ const destPre = join(installDir, "pre-tool-use.cjs");
495
+ const destPost = join(installDir, "post-tool-use.cjs");
496
+ const destShared = join(installDir, "shared.cjs");
497
+ await copyFile(srcPre, destPre);
498
+ await copyFile(srcPost, destPost);
499
+ await copyFile(srcShared, destShared);
500
+ const hookScriptMode = 493;
501
+ await chmod(destPre, hookScriptMode);
502
+ await chmod(destPost, hookScriptMode);
503
+ await chmod(destShared, hookScriptMode);
504
+ const hooksDir = getClineGlobalHooksDir();
505
+ await mkdir(hooksDir, { recursive: true });
506
+ const preWrapper = join(hooksDir, "PreToolUse");
507
+ const postWrapper = join(hooksDir, "PostToolUse");
508
+ const preContent = `#!/usr/bin/env node
509
+ require(${JSON.stringify(destPre)});
510
+ `;
511
+ const postContent = `#!/usr/bin/env node
512
+ require(${JSON.stringify(destPost)});
513
+ `;
514
+ await writeFile(preWrapper, preContent, { encoding: "utf8", mode: 493 });
515
+ await writeFile(postWrapper, postContent, { encoding: "utf8", mode: 493 });
516
+ }
517
+ async function promptClineIntegrationMode(ask) {
518
+ process.stderr.write("\n" + style.bold("Cline integration") + "\n");
519
+ process.stderr.write(
520
+ " " + style.violet("1") + ". Native plugin (recommended) - Cline Hooks see every file, terminal, browser, and MCP action\n"
521
+ );
522
+ process.stderr.write(
523
+ " " + style.violet("2") + ". Hosted proxy - govern MCP traffic only (paste proxy URL into Cline MCP settings)\n"
524
+ );
525
+ let choice = 0;
526
+ while (choice === 0) {
527
+ const input = await ask("Choose integration (1-2): ");
528
+ const num = parseInt(input.trim(), 10);
529
+ if (num === 1) choice = 1;
530
+ if (num === 2) choice = 2;
531
+ }
532
+ return choice === 1 ? "native" : "hosted";
533
+ }
534
+ var PLATFORM_LABELS = [
535
+ "OpenClaw",
536
+ "Claude Code",
537
+ "Cursor",
538
+ "Windsurf",
539
+ "Cline",
540
+ "Local MCP / Other"
541
+ ];
477
542
  var PLATFORM_BY_SELECTION = {
478
543
  1: "openclaw",
479
544
  2: "claude-code",
480
545
  3: "cursor",
481
546
  4: "windsurf",
482
- 5: "other-mcp"
547
+ 5: "cline",
548
+ 6: "other-mcp"
483
549
  };
484
550
  var DEFAULT_AGENT_NAMES = {
485
551
  openclaw: "my-openclaw-agent",
486
552
  "claude-code": "my-claude-code-agent",
487
553
  cursor: "my-cursor-agent",
488
- windsurf: "my-windsurf-agent"
554
+ windsurf: "my-windsurf-agent",
555
+ cline: "my-cline-agent"
489
556
  };
490
557
  async function promptPlatformSelection(ask) {
491
558
  process.stderr.write(
@@ -505,13 +572,13 @@ async function promptPlatformSelection(ask) {
505
572
  );
506
573
  }
507
574
  process.stderr.write(
508
- style.dim(" Pick 5 if you want to wrap a local MCP server with multicorn-proxy --wrap.") + "\n"
575
+ style.dim(" Pick 6 if you want to wrap a local MCP server with multicorn-proxy --wrap.") + "\n"
509
576
  );
510
577
  let selection = 0;
511
578
  while (selection === 0) {
512
- const input = await ask("Select (1-5): ");
579
+ const input = await ask("Select (1-6): ");
513
580
  const num = parseInt(input.trim(), 10);
514
- if (num >= 1 && num <= 5) {
581
+ if (num >= 1 && num <= 6) {
515
582
  selection = num;
516
583
  }
517
584
  }
@@ -632,7 +699,7 @@ async function createProxyConfig(baseUrl, apiKey, agentName, targetUrl, serverNa
632
699
  return typeof data?.["proxy_url"] === "string" ? data["proxy_url"] : "";
633
700
  }
634
701
  function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
635
- const usesInlineKey = platform === "cursor" || platform === "windsurf";
702
+ const usesInlineKey = platform === "cursor" || platform === "windsurf" || platform === "cline";
636
703
  const authHeader = usesInlineKey ? `Bearer ${apiKey}` : "Bearer YOUR_SHIELD_API_KEY";
637
704
  const urlKey = platform === "windsurf" ? "serverUrl" : "url";
638
705
  const mcpSnippet = JSON.stringify(
@@ -657,6 +724,23 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
657
724
  process.stderr.write(
658
725
  "\n" + style.dim("Add this to ~/.codeium/windsurf/mcp_config.json:") + "\n\n"
659
726
  );
727
+ } else if (platform === "cline") {
728
+ process.stderr.write("\n" + style.dim("Add this to your Cline MCP settings file:") + "\n");
729
+ process.stderr.write(
730
+ style.dim(
731
+ " macOS: ~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
732
+ ) + "\n"
733
+ );
734
+ process.stderr.write(
735
+ style.dim(
736
+ " Windows: %APPDATA%\\Code\\User\\globalStorage\\saoudrizwan.claude-dev\\settings\\cline_mcp_settings.json"
737
+ ) + "\n"
738
+ );
739
+ process.stderr.write(
740
+ style.dim(
741
+ " Linux: ~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json"
742
+ ) + "\n\n"
743
+ );
660
744
  } else {
661
745
  process.stderr.write("\n" + style.dim("Add this to ~/.cursor/mcp.json:") + "\n\n");
662
746
  }
@@ -680,6 +764,13 @@ function printPlatformSnippet(platform, routingToken, shortName, apiKey) {
680
764
  ) + "\n"
681
765
  );
682
766
  }
767
+ if (platform === "cline") {
768
+ process.stderr.write(
769
+ style.dim(
770
+ "After pasting, restart Cline or reload the VS Code window. Cline will discover the Shield tools automatically."
771
+ ) + "\n"
772
+ );
773
+ }
683
774
  if (platform === "windsurf") {
684
775
  process.stderr.write(style.dim("Then restart Windsurf (Cmd/Ctrl+Q, then reopen).") + "\n");
685
776
  process.stderr.write(
@@ -777,7 +868,7 @@ async function runInit(explicitBaseUrl) {
777
868
  const selection = await promptPlatformSelection(ask);
778
869
  const selectedPlatform = PLATFORM_BY_SELECTION[selection] ?? "cursor";
779
870
  const selectedLabel = PLATFORM_LABELS[selection - 1] ?? "Cursor";
780
- if (selection === 5) {
871
+ if (selection === 6) {
781
872
  const raw = existing !== null ? { ...existing } : {};
782
873
  raw["apiKey"] = apiKey;
783
874
  raw["baseUrl"] = resolvedBaseUrl;
@@ -1001,6 +1092,71 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
1001
1092
  setupSucceeded = true;
1002
1093
  }
1003
1094
  }
1095
+ } else if (selection === 5) {
1096
+ const clineMode = await promptClineIntegrationMode(ask);
1097
+ if (clineMode === "native") {
1098
+ try {
1099
+ await installClineNativeHooks();
1100
+ process.stderr.write("\n" + style.bold("Shield Cline hooks installed") + "\n\n");
1101
+ process.stderr.write(
1102
+ style.dim(
1103
+ "The Shield hook runs with your user permissions. It intercepts Cline tool calls to check permissions and log activity. Review the scripts under "
1104
+ ) + style.cyan("~/.multicorn/cline-hooks") + style.dim(" if that is a concern.") + "\n"
1105
+ );
1106
+ configuredAgents.push({
1107
+ selection,
1108
+ platform: selectedPlatform,
1109
+ platformLabel: selectedLabel,
1110
+ agentName,
1111
+ clineIntegration: "native"
1112
+ });
1113
+ setupSucceeded = true;
1114
+ } catch (error) {
1115
+ const detail = error instanceof Error ? error.message : String(error);
1116
+ process.stderr.write(style.red("\u2717 ") + detail + "\n");
1117
+ }
1118
+ } else {
1119
+ const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
1120
+ let proxyUrl = "";
1121
+ let created = false;
1122
+ while (!created) {
1123
+ const spinner = withSpinner("Creating proxy config...");
1124
+ try {
1125
+ proxyUrl = await createProxyConfig(
1126
+ resolvedBaseUrl,
1127
+ apiKey,
1128
+ agentName,
1129
+ targetUrl,
1130
+ shortName,
1131
+ selectedPlatform
1132
+ );
1133
+ spinner.stop(true, "Proxy config created!");
1134
+ created = true;
1135
+ } catch (error) {
1136
+ const detail = error instanceof Error ? error.message : String(error);
1137
+ spinner.stop(false, detail);
1138
+ const retry = await ask("Try again? (Y/n) ");
1139
+ if (retry.trim().toLowerCase() === "n") {
1140
+ break;
1141
+ }
1142
+ }
1143
+ }
1144
+ if (created && proxyUrl.length > 0) {
1145
+ process.stderr.write("\n" + style.bold("Your Shield proxy URL:") + "\n");
1146
+ process.stderr.write(" " + style.cyan(proxyUrl) + "\n");
1147
+ printPlatformSnippet(selectedPlatform, proxyUrl, shortName, apiKey);
1148
+ configuredAgents.push({
1149
+ selection,
1150
+ platform: selectedPlatform,
1151
+ platformLabel: selectedLabel,
1152
+ agentName,
1153
+ shortName,
1154
+ proxyUrl,
1155
+ clineIntegration: "hosted"
1156
+ });
1157
+ setupSucceeded = true;
1158
+ }
1159
+ }
1004
1160
  } else {
1005
1161
  const { targetUrl, shortName } = await promptProxyConfig(ask, agentName);
1006
1162
  let proxyUrl = "";
@@ -1119,6 +1275,22 @@ An agent for ${selectedLabel} already exists: ${style.cyan(existingForPlatform.n
1119
1275
  "\n" + style.bold("To complete your Windsurf hosted-proxy setup:") + "\n 1. If you don't have Windsurf yet, download it from " + style.cyan("https://windsurf.com/download") + "\n 2. Open " + style.cyan("~/.codeium/windsurf/mcp_config.json") + " and paste the config snippet shown above\n 3. Restart Windsurf (or launch it for the first time) to load the new MCP server\n"
1120
1276
  );
1121
1277
  }
1278
+ const clineNativeConfigured = configuredAgents.some(
1279
+ (a) => a.platform === "cline" && a.clineIntegration === "native"
1280
+ );
1281
+ const clineHostedConfigured = configuredAgents.some(
1282
+ (a) => a.platform === "cline" && a.clineIntegration === "hosted"
1283
+ );
1284
+ if (clineNativeConfigured) {
1285
+ blocks.push(
1286
+ "\n" + style.bold("To complete native Cline (Shield) setup:") + "\n 1. Enable Hooks in Cline: open VS Code, click the Cline sidebar icon, click the gear icon,\n scroll down to the Advanced section, and toggle Hooks on.\n 2. Reload the VS Code window (Cmd+Shift+P > Reload Window)\n 3. Trigger any tool call to verify Shield is intercepting\n"
1287
+ );
1288
+ }
1289
+ if (clineHostedConfigured) {
1290
+ blocks.push(
1291
+ "\n" + style.bold("To complete your Cline hosted-proxy setup:") + "\n 1. If you don't have Cline yet, install it from the VS Code marketplace\n 2. Open your Cline MCP settings file and paste the config snippet shown above\n 3. Restart Cline or reload the VS Code window\n"
1292
+ );
1293
+ }
1122
1294
  if (blocks.length > 0) {
1123
1295
  process.stderr.write("\n" + style.bold(style.violet("Next steps")) + "\n");
1124
1296
  process.stderr.write(blocks.join("") + "\n");
@@ -377,7 +377,6 @@ ${url}
377
377
  }
378
378
  async function waitForConsent(agentId, agentName, apiKey, baseUrl, scope, logger) {
379
379
  const dashboardUrl = deriveDashboardUrl(baseUrl);
380
- console.error("[SHIELD] buildConsentUrl baseUrl:", baseUrl);
381
380
  const consentUrl = buildConsentUrl(agentName, dashboardUrl, scope);
382
381
  process.stderr.write(
383
382
  `[multicorn-shield] Opening consent page...
@@ -315,14 +315,6 @@ async function fetchGrantedScopes(agentId, apiKey, baseUrl, logger) {
315
315
  }
316
316
  async function checkActionPermission(payload, apiKey, baseUrl, logger) {
317
317
  try {
318
- const requestBody = {
319
- agent: payload.agent,
320
- service: payload.service,
321
- actionType: payload.actionType,
322
- status: payload.status,
323
- metadata: payload.metadata
324
- };
325
- console.error("[SHIELD-CLIENT] POST /api/v1/actions request: " + JSON.stringify(requestBody));
326
318
  const response = await fetch(`${baseUrl}/api/v1/actions`, {
327
319
  method: "POST",
328
320
  headers: {
@@ -333,22 +325,18 @@ async function checkActionPermission(payload, apiKey, baseUrl, logger) {
333
325
  signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
334
326
  });
335
327
  if (response.status === 201) {
336
- console.error(
337
- "[SHIELD-CLIENT] response status=201, returning approved (body not read - backend may have failed approval creation)"
338
- );
328
+ console.error("[SHIELD-CLIENT] POST /api/v1/actions: 201 approved");
339
329
  return { status: "approved" };
340
330
  }
341
331
  if (response.status === 202) {
342
332
  const body = await response.json();
343
333
  const data = isApiSuccess(body) ? body.data : null;
344
- console.error("[SHIELD-CLIENT] response status=202 body=" + JSON.stringify(data ?? body));
334
+ console.error("[SHIELD-CLIENT] POST /api/v1/actions: 202 pending");
345
335
  if (!isApiSuccess(body) || data === null) {
346
336
  return { status: "blocked" };
347
337
  }
348
338
  const approvalId = typeof data["approval_id"] === "string" ? data["approval_id"] : void 0;
349
- console.error(
350
- "[SHIELD-CLIENT] extracted: status=" + String(data["status"]) + " approval_id=" + (approvalId ?? "undefined")
351
- );
339
+ console.error("[SHIELD-CLIENT] extracted: approval_id=" + (approvalId ?? "undefined"));
352
340
  if (approvalId === void 0) {
353
341
  return { status: "blocked" };
354
342
  }
@@ -439,7 +427,6 @@ ${url}
439
427
  }
440
428
  async function waitForConsent(agentId, agentName, apiKey, baseUrl, scope, logger) {
441
429
  const dashboardUrl = deriveDashboardUrl(baseUrl);
442
- console.error("[SHIELD] buildConsentUrl baseUrl:", baseUrl);
443
430
  const consentUrl = buildConsentUrl(agentName, dashboardUrl, scope);
444
431
  process.stderr.write(
445
432
  `[multicorn-shield] Opening consent page...
@@ -509,7 +496,14 @@ function readConfig() {
509
496
  const resolvedBaseUrl = asString(cachedMulticornConfig?.baseUrl) ?? asString(process.env["MULTICORN_BASE_URL"]) ?? "https://api.multicorn.ai";
510
497
  const agentName = asString(pc["agentName"]) ?? process.env["MULTICORN_AGENT_NAME"] ?? agentNameFromOpenclawPlatform(cachedMulticornConfig) ?? asString(cachedMulticornConfig?.agentName) ?? null;
511
498
  const failMode = "closed";
512
- return { apiKey: resolvedApiKey, baseUrl: resolvedBaseUrl, agentName, failMode };
499
+ let apiKey = resolvedApiKey;
500
+ if (apiKey.length > 0 && (!apiKey.startsWith("mcs_") || apiKey.length < 16)) {
501
+ pluginLogger?.error(
502
+ "Invalid API key format. Key must start with mcs_ and be at least 16 characters."
503
+ );
504
+ apiKey = "";
505
+ }
506
+ return { apiKey, baseUrl: resolvedBaseUrl, agentName, failMode };
513
507
  }
514
508
  function asString(value) {
515
509
  return typeof value === "string" && value.length > 0 ? value : void 0;