@fidacy/openclaw-plugin 0.4.0 → 0.4.1

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
@@ -94,6 +94,41 @@ This is a payment-control plugin, so audit it like one. What it actually does:
94
94
  JWKS at `api.fidacy.com/.well-known/jwks.json` with the open-source
95
95
  [`@fidacy/verify`](https://www.npmjs.com/package/@fidacy/verify).
96
96
 
97
+ ## Security
98
+
99
+ This plugin sits in front of money-moving tool calls, so it should be precise about
100
+ what it touches.
101
+
102
+ **What it reads.** Your engine credential, from the plugin config field `engineApiKey`
103
+ or from `FIDACY_ENGINE_API_KEY`. Optionally `FIDACY_SIGNING_KEY_B64`, which is your own
104
+ Ed25519 key, so grants are signed with a stable key instead of a per-session one. Both
105
+ belong to you and both are optional. With neither set, the plugin runs the free
106
+ anonymous path and signs with an ephemeral key.
107
+
108
+ **No credential is embedded.** There is no API key, token, or private key literal in the
109
+ published package, in any version. Check it yourself:
110
+
111
+ ```bash
112
+ cd $(mktemp -d) && curl -sL $(npm view @fidacy/openclaw-plugin dist.tarball) | tar xz && grep -rE "fky_(live|test)_[A-Za-z0-9]{8,}|BEGIN [A-Z ]*PRIVATE KEY" package/ || echo "no credential literals found"
113
+ ```
114
+
115
+ A static scanner may flag the line that resolves your credential as a hardcoded secret,
116
+ because the identifier on the right of the assignment is named like one. It reads your
117
+ config and falls back to the environment, defaulting to the empty string. The command
118
+ above is what settles the question, and the test suite fails the build if a literal ever
119
+ does get in.
120
+
121
+ **Turning the network off.** `FIDACY_DISABLE_TELEMETRY=1` stops anonymous install and
122
+ usage telemetry. `FIDACY_DISABLE_PROVISION=1` stops the background free-key provisioning.
123
+ With both set and no engine credential, the plugin decides locally and contacts nothing.
124
+
125
+ **Where the history lives.** Decisions go to a hash-chained log under `~/.fidacy` on your
126
+ machine. That local chain is what makes `spend_summary`, `list_decisions` and
127
+ `get_audit_proof` work with no network. Delete the directory to clear it.
128
+
129
+ **Files are never uploaded.** `anchor_artifact` and `check_artifact` hash the file locally
130
+ with SHA-256 and send only the 64 hex characters.
131
+
97
132
  ## Prefer MCP instead?
98
133
 
99
134
  If you'd rather run Fidacy as an MCP server (out-of-process), use
package/dist/index.js CHANGED
@@ -4739,7 +4739,7 @@ function resolveMandateRules(cfg) {
4739
4739
  }
4740
4740
 
4741
4741
  // ../mcp/src/telemetry.ts
4742
- var CLIENT_VERSION = true ? "0.4.0" : "dev";
4742
+ var CLIENT_VERSION = true ? "0.4.1" : "dev";
4743
4743
  function bandOf(amount) {
4744
4744
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
4745
4745
  if (amount < 10) return "lt10";
@@ -4927,7 +4927,7 @@ function requestUpgrade() {
4927
4927
  }
4928
4928
 
4929
4929
  // ../mcp/src/provision.ts
4930
- var CLIENT_VERSION2 = true ? "0.4.0" : "dev";
4930
+ var CLIENT_VERSION2 = true ? "0.4.1" : "dev";
4931
4931
  function provisionEnabled() {
4932
4932
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
4933
4933
  return !(v === "1" || v === "true" || v === "yes");
@@ -5069,7 +5069,7 @@ function trialCountdownLine(keyOverride) {
5069
5069
  }
5070
5070
 
5071
5071
  // ../mcp/src/register.ts
5072
- var CLIENT_VERSION3 = true ? "0.4.0" : "dev";
5072
+ var CLIENT_VERSION3 = true ? "0.4.1" : "dev";
5073
5073
  function endpoint3() {
5074
5074
  const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
5075
5075
  return `${base}/v1/register`;
@@ -5211,7 +5211,7 @@ function renderSummary(s) {
5211
5211
  ];
5212
5212
  if (s.denied > 0) {
5213
5213
  const ranked = Object.entries(s.denied_by_rule).sort((a, b) => b[1] - a[1]);
5214
- lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By rule: ${ranked.map(([rule, n]) => `${rule} (${n})`).join(", ")}.`);
5214
+ lines.push(`Blocked: ${money(s.blocked_totals)} in attempted value. By reason: ${ranked.map(([rule, n]) => `${ruleLabel(rule)} (${n})`).join(", ")}.`);
5215
5215
  }
5216
5216
  if (s.amount_unknown > 0) {
5217
5217
  lines.push(`${s.amount_unknown} record(s) in this window predate amount logging, so the totals above are a floor, not the exact figure.`);
@@ -5238,32 +5238,82 @@ function renderRows(rows) {
5238
5238
  return rows.map((r) => {
5239
5239
  const amount = typeof r.amount === "number" ? `${r.amount.toLocaleString("en-US")} ${r.currency ?? ""}`.trim() : "amount not recorded";
5240
5240
  const who = r.payee ?? "payee not recorded";
5241
- const why = r.status === "DENY" ? ` blocked by ${r.violatedRule ?? "a rule not recorded on this older entry"}.` : "";
5241
+ const why = r.status === "DENY" ? ` ${explainRule(r.violatedRule, r.payee)}` : "";
5242
5242
  const said = r.purpose ? ` Agent's stated reason: "${r.purpose}".` : "";
5243
5243
  return `#${r.seq} ${r.ts} ${r.status} ${amount} to ${who}.${why}${said} (decision ${r.decisionId})`;
5244
5244
  }).join("\n");
5245
5245
  }
5246
+ function ruleLabel(rule) {
5247
+ if (rule.startsWith("payee_lookalike")) return "lookalike payee";
5248
+ if (rule.startsWith("payee_not_in_allowlist")) return "payee not approved";
5249
+ if (rule.startsWith("category_not_allowed")) return "purpose not allowed";
5250
+ if (rule.startsWith("currency_not_allowed")) return "wrong currency";
5251
+ if (rule.startsWith("per_tx") || rule.includes("perTxMax")) return "over the per-payment cap";
5252
+ if (rule.includes("maxTotal") || rule.startsWith("total_cap") || rule.startsWith("budget")) return "over the total budget";
5253
+ if (rule.startsWith("invoice") || rule.includes("duplicate")) return "duplicate invoice";
5254
+ if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired")) return "outside the mandate window";
5255
+ if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked")) return "mandate revoked";
5256
+ if (rule.startsWith("invalid_mandate")) return "mandate misconfigured";
5257
+ if (rule.startsWith("non_positive_amount")) return "invalid amount";
5258
+ if (rule.startsWith("activation_required")) return "trial used up";
5259
+ if (rule === "not_recorded") return "rule not recorded";
5260
+ return rule;
5261
+ }
5246
5262
  function explainRule(rule, payee) {
5247
5263
  if (!rule) return "This entry predates readable rule logging, so the specific rule is not on the record. The decision payload is still covered by the chain digest.";
5264
+ if (rule.startsWith("invalid_mandate")) {
5265
+ const cap = rule.includes("perTxMax") ? "perTxMax" : rule.includes("maxTotal") ? "maxTotal" : null;
5266
+ if (cap)
5267
+ return `The mandate's \`${cap}\` is not a usable number, so the firewall denied rather than run without that cap. Values like "2,500", "$2500" or "2500 USD" all look right to a human and become NaN, and a cap that cannot be compared would silently let everything through. Write it as a bare number: ${cap}: 2500.`;
5268
+ return "The mandate is missing its `allow` block, so there is nothing to enforce and the firewall denied. Check ~/.fidacy/config.json or FIDACY_MANDATE_JSON.";
5269
+ }
5270
+ if (rule.startsWith("payee_lookalike")) {
5271
+ const approved = rule.includes("~") ? rule.split("~")[1] : "an approved payee";
5272
+ return `The payee${payee ? ` "${payee}"` : ""} is a lookalike of your approved payee "${approved}": same name to a human glance, different string. That is the signature of a payee swap, whether from a prompt injection or a spoofed invoice. If it really is a separate vendor, add it to \`payees\` explicitly; if it is not, this attempt is exactly what the firewall exists to stop.`;
5273
+ }
5248
5274
  if (rule.startsWith("payee_not_in_allowlist"))
5249
- return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor.`;
5275
+ return `The payee${payee ? ` "${payee}"` : ""} is not on the mandate's allowlist. This is the deny-by-default rule doing its job: an agent can only pay counterparties a human put on the list, which is what stops a swapped or lookalike vendor. To authorize this one, add it to \`payees\` in ~/.fidacy/config.json, or pass a full mandate through FIDACY_MANDATE_JSON.`;
5250
5276
  if (rule.startsWith("category_not_allowed"))
5251
- return "The stated purpose category is not one the mandate permits for this agent.";
5277
+ return "The stated purpose category is not one the mandate permits for this agent. Add it to `categories` in the mandate, or send the payment under a purpose the mandate already allows.";
5252
5278
  if (rule.startsWith("currency_not_allowed"))
5253
- return "The payment currency is not the mandate's currency.";
5279
+ return "The payment currency is not the mandate's currency. Set `currency` to the one you intend to pay in. A mandate authorizes one currency at a time on purpose, so a swapped currency cannot slip past a cap set in another.";
5254
5280
  if (rule.startsWith("per_tx") || rule.includes("perTxMax"))
5255
- return "The amount exceeds the per-transaction ceiling in the mandate.";
5256
- if (rule.includes("maxTotal") || rule.startsWith("budget"))
5257
- return "The payment would push cumulative spend past the mandate's total budget for the window.";
5281
+ return "The amount exceeds the per-transaction ceiling in the mandate. Raise `perTxMax` if payments this size are expected, or split the payment.";
5282
+ if (rule.includes("maxTotal") || rule.startsWith("budget") || rule.startsWith("total_cap"))
5283
+ return "The payment would push cumulative spend past the mandate's total budget for the window. Raise `maxTotal`, or wait for the window to roll over. Blocked attempts do not consume the budget; only payments that went through do.";
5258
5284
  if (rule.startsWith("invoice") || rule.includes("duplicate"))
5259
- return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure.";
5260
- if (rule.startsWith("window") || rule.includes("expired"))
5261
- return "The mandate was outside its validity window at the moment of the request.";
5262
- if (rule.startsWith("revoked")) return "The mandate had been revoked.";
5285
+ return "This invoice was already paid once. One payment per invoice is enforced regardless of amount, which is the control that stops a re-presented invoice at a higher figure. If this genuinely is a second, separate payment, give it its own invoiceRef.";
5286
+ if (rule.startsWith("window") || rule.includes("mandate_window") || rule.includes("expired"))
5287
+ return "The mandate was outside its validity window at the moment of the request. Check `notBefore` and `notAfter`, and issue a mandate whose window covers now.";
5288
+ if (rule.startsWith("revoked") || rule.startsWith("mandate_revoked"))
5289
+ return "The mandate had been revoked. A revoked mandate cannot be un-revoked; issue a new one to authorize further payments.";
5290
+ if (rule.startsWith("non_positive_amount"))
5291
+ return "The amount was zero or negative, which is not a payment anyone can authorize. Check what produced that value before retrying.";
5263
5292
  if (rule.startsWith("activation_required"))
5264
5293
  return "The anonymous trial had been used up, so the firewall failed closed. No money can move until the install is activated with a free API key.";
5265
5294
  return `The mandate rule "${rule}" was violated.`;
5266
5295
  }
5296
+ function explainEngineError(action, status, type, reasons) {
5297
+ const noun = action === "verdict" ? "assess this action" : action === "anchor" ? "anchor this artifact" : "check this artifact";
5298
+ if (status === 401 || status === 403)
5299
+ return `Fidacy could not ${noun}: the engine rejected the credential (HTTP ${status}). This is not an outage. The key is missing, wrong, or lacks the scope this call needs \u2014 an anonymous provisioned key has no scopes at all. Get an account key at https://fidacy.com/claim and set FIDACY_ENGINE_API_KEY. Nothing was executed.`;
5300
+ if (status === 402)
5301
+ return `Fidacy could not ${noun}: the account is past a spending or quota limit (HTTP 402). The integration is fine; the limit needs settling at https://fidacy.com/claim. Nothing was executed.`;
5302
+ if (status === 429)
5303
+ return `Fidacy could not ${noun}: too many requests in a short window (HTTP 429). Retry in a few seconds. Nothing was executed, and no decision was recorded.`;
5304
+ if (status === 404 && action === "check")
5305
+ return "No anchor exists for that hash on this account. Either the artifact was never anchored, or its contents changed since it was \u2014 if you expected a match, the file is not the one that was anchored.";
5306
+ if (status === 422 || type === "mandate_violation") {
5307
+ const first = reasons?.[0]?.key;
5308
+ const why = first ? ` ${explainRule(first)}` : "";
5309
+ return `Fidacy denied this action: it falls outside the mandate in force.${why} Nothing was executed.`;
5310
+ }
5311
+ if (status >= 500)
5312
+ return `Fidacy could not ${noun}: the engine returned an error (HTTP ${status}). Nothing was executed, and the call was refused rather than allowed through. Retrying is safe.`;
5313
+ if (status === 400)
5314
+ return `Fidacy could not ${noun}: the request was malformed (HTTP 400${type ? `, ${type}` : ""}). This is a bug in how the call was built, not a policy decision. Nothing was executed.`;
5315
+ return `Fidacy could not ${noun} (HTTP ${status}${type ? `, ${type}` : ""}), so the call was refused rather than allowed through. Nothing was executed.`;
5316
+ }
5267
5317
 
5268
5318
  // ../mcp/src/sentinel.ts
5269
5319
  var SENTINEL = {
@@ -5537,6 +5587,9 @@ async function findArtifacts(sha2562, cfg) {
5537
5587
  }
5538
5588
 
5539
5589
  // src/index.ts
5590
+ function operatorEngineKey(config) {
5591
+ return (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5592
+ }
5540
5593
  var loaded = false;
5541
5594
  function onPluginLoad() {
5542
5595
  if (loaded) return;
@@ -5657,7 +5710,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5657
5710
  async execute(_params, config) {
5658
5711
  const c = boot();
5659
5712
  const mandate = await c.getMandate(subjectOf(config));
5660
- const hasKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim().length > 0;
5713
+ const hasKey = operatorEngineKey(config).length > 0;
5661
5714
  const claim = hasKey ? null : claimUrl();
5662
5715
  if (claim) {
5663
5716
  return {
@@ -5697,7 +5750,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5697
5750
  async execute(params, config) {
5698
5751
  const records = await boot().history(5e3);
5699
5752
  const summary = summarize(records, params.days ?? 7);
5700
- const hasKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim().length > 0;
5753
+ const hasKey = operatorEngineKey(config).length > 0;
5701
5754
  const claim = hasKey ? null : claimUrl();
5702
5755
  return {
5703
5756
  summary,
@@ -5776,8 +5829,8 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5776
5829
  async execute(params, config) {
5777
5830
  boot();
5778
5831
  const engineUrl = config.engineUrl ?? process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
5779
- const apiKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5780
- if (!apiKey) {
5832
+ const engineKey = operatorEngineKey(config);
5833
+ if (!engineKey) {
5781
5834
  throw new Error(
5782
5835
  `assess_action needs an engine key \u2014 ${noKeyCta()}. (Or set plugins.entries.fidacy.config.engineApiKey.)`
5783
5836
  );
@@ -5785,7 +5838,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5785
5838
  try {
5786
5839
  const r = await assessAction(
5787
5840
  { kind: params.kind, mandate: params.mandate, mandateType: params.mandateType, idempotencyKey: params.idempotencyKey, spendingMandate: params.spendingMandate, a2a: params.a2a },
5788
- { engineUrl, apiKey }
5841
+ { engineUrl, apiKey: engineKey }
5789
5842
  );
5790
5843
  clearHostedLapsed();
5791
5844
  return { summary: `${r.decision} (score ${r.score}) signed by ${r.signingKeyId}`, ...r };
@@ -5795,8 +5848,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5795
5848
  markHostedLapsed();
5796
5849
  throw new Error(hostedWallCta("verdict", e.billing));
5797
5850
  }
5798
- const reasons = e.rejection_reasons?.length ? " (" + e.rejection_reasons.map((x) => x.key).join(",") + ")" : "";
5799
- throw new Error(`ASSESS ${e.status}: ${e.type}${reasons}`);
5851
+ throw new Error(explainEngineError("verdict", e.status, e.type, e.rejection_reasons));
5800
5852
  }
5801
5853
  throw new Error("ASSESS failed: unexpected error");
5802
5854
  }
@@ -5819,8 +5871,8 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5819
5871
  async execute(params, config) {
5820
5872
  boot();
5821
5873
  const engineUrl = config.engineUrl ?? process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
5822
- const apiKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5823
- if (!apiKey) {
5874
+ const engineKey = operatorEngineKey(config);
5875
+ if (!engineKey) {
5824
5876
  throw new Error(
5825
5877
  `anchor_artifact needs an engine key \u2014 ${noKeyCta()}. (Or set plugins.entries.fidacy.config.engineApiKey.)`
5826
5878
  );
@@ -5839,7 +5891,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5839
5891
  try {
5840
5892
  const r = await anchorArtifact(
5841
5893
  { sha256: hash, kind: params.kind ?? "document", ...params.label ? { label: params.label } : {}, ...params.subject ? { subject: params.subject } : {} },
5842
- { engineUrl, apiKey }
5894
+ { engineUrl, apiKey: engineKey }
5843
5895
  );
5844
5896
  return {
5845
5897
  summary: `ANCHORED ${r.kind} \xB7 sha256 ${hash.slice(0, 16)}\u2026 \xB7 audit seq ${r.audit.seq} \xB7 Bitcoin checkpoint: ${r.anchor.status}. The file never left this machine.`,
@@ -5851,7 +5903,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5851
5903
  markHostedLapsed();
5852
5904
  throw new Error(hostedWallCta("anchor", e.billing));
5853
5905
  }
5854
- throw new Error(`ANCHOR ${e.status}: ${e.type}`);
5906
+ throw new Error(explainEngineError("anchor", e.status, e.type));
5855
5907
  }
5856
5908
  throw new Error("ANCHOR failed: unexpected error");
5857
5909
  }
@@ -5869,8 +5921,8 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5869
5921
  async execute(params, config) {
5870
5922
  boot();
5871
5923
  const engineUrl = config.engineUrl ?? process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
5872
- const apiKey = (config.engineApiKey ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
5873
- if (!apiKey) {
5924
+ const engineKey = operatorEngineKey(config);
5925
+ if (!engineKey) {
5874
5926
  throw new Error(
5875
5927
  `check_artifact needs an engine key \u2014 ${noKeyCta()}. (Or set plugins.entries.fidacy.config.engineApiKey.)`
5876
5928
  );
@@ -5887,7 +5939,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5887
5939
  }
5888
5940
  }
5889
5941
  try {
5890
- const r = await findArtifacts(hash, { engineUrl, apiKey });
5942
+ const r = await findArtifacts(hash, { engineUrl, apiKey: engineKey });
5891
5943
  if (!r.artifacts.length) {
5892
5944
  return {
5893
5945
  summary: `NOT FOUND \xB7 sha256 ${hash.slice(0, 16)}\u2026 has no anchored record in this account. If you expected a match, the file changed since anchoring.`,
@@ -5902,7 +5954,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
5902
5954
  ...r
5903
5955
  };
5904
5956
  } catch (e) {
5905
- if (e instanceof AssessError) throw new Error(`CHECK ${e.status}: ${e.type}`);
5957
+ if (e instanceof AssessError) throw new Error(explainEngineError("check", e.status, e.type));
5906
5958
  throw new Error("CHECK failed: unexpected error");
5907
5959
  }
5908
5960
  }
@@ -3,7 +3,7 @@
3
3
  "name": "Fidacy AI Agent Firewall",
4
4
  "description": "A signed, independently-verifiable verdict on every money-moving agent action. Blocks wrong/lookalike payee, over-cap, and duplicate-invoice fraud before money moves. Non-custodial, local-first, deny-by-default.",
5
5
  "icon": "https://fidacy.com/logo.png",
6
- "version": "0.4.0",
6
+ "version": "0.4.1",
7
7
  "contracts": {
8
8
  "tools": [
9
9
  "request_payment",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/openclaw-plugin",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Fidacy payment firewall as a native OpenClaw plugin: signed, verifiable verdicts on every money-moving agent action, in-process (no MCP subprocess).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",
@@ -47,20 +47,21 @@
47
47
  "npmSpec": "@fidacy/openclaw-plugin"
48
48
  }
49
49
  },
50
+ "scripts": {
51
+ "build": "node scripts/bundle.mjs",
52
+ "typecheck": "tsc --noEmit",
53
+ "test": "npm run build && node --test test/*.test.mjs"
54
+ },
50
55
  "engines": {
51
56
  "node": ">=20"
52
57
  },
53
58
  "devDependencies": {
59
+ "@fidacy/firewall": "workspace:*",
60
+ "@fidacy/mcp": "workspace:*",
54
61
  "@types/node": "^22.10.0",
55
62
  "esbuild": "^0.24.0",
56
63
  "openclaw": "2026.6.11",
57
64
  "typebox": "1.1.39",
58
- "typescript": "^5.6.3",
59
- "@fidacy/firewall": "0.1.1",
60
- "@fidacy/mcp": "0.5.1"
61
- },
62
- "scripts": {
63
- "build": "node scripts/bundle.mjs",
64
- "typecheck": "tsc --noEmit"
65
+ "typescript": "^5.6.3"
65
66
  }
66
- }
67
+ }