paisa-mcp 0.4.1 → 0.4.3

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/dist/index.js CHANGED
@@ -38033,17 +38033,6 @@ var inputRequired = Object.assign(buildInputRequired, {
38033
38033
  return { method: "roots/list" };
38034
38034
  }
38035
38035
  });
38036
- function acceptedContent(responses, key, schema) {
38037
- const view = inputResponse(responses, key);
38038
- if (view.kind !== "elicit" || view.action !== "accept" || view.content === undefined)
38039
- return;
38040
- if (schema === undefined)
38041
- return view.content;
38042
- const outcome = schema["~standard"].validate(view.content);
38043
- if (outcome instanceof Promise)
38044
- throw new TypeError("acceptedContent(responses, key, schema) requires a synchronously-validating schema");
38045
- return outcome.issues === undefined ? outcome.value : undefined;
38046
- }
38047
38036
  function inputResponse(responses, key) {
38048
38037
  if (responses === undefined || typeof responses !== "object" || responses === null)
38049
38038
  return { kind: "missing" };
@@ -53267,25 +53256,8 @@ async function decryptFields(input2, paths, { decrypt, isCiphertext }) {
53267
53256
  return { data, undecrypted };
53268
53257
  }
53269
53258
 
53270
- // src/crypto.ts
53259
+ // ../crypto/src/index.ts
53271
53260
  var import_hash_wasm = __toESM(require_index_umd(), 1);
53272
- var ENCRYPTED_TXN_FIELDS = ["description", "referenceNumber", "notes"];
53273
- var ENCRYPTED_MERCHANT_FIELDS = ["cleanName", "rawId", "notes"];
53274
- async function hmacWithKey(value, key) {
53275
- const rawKey = await crypto.subtle.exportKey("raw", key);
53276
- const hmacKey = await crypto.subtle.importKey("raw", rawKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
53277
- const sig = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(value));
53278
- return btoa(String.fromCharCode(...new Uint8Array(sig)));
53279
- }
53280
- async function encryptPersonCipher(fields, key) {
53281
- const cipher = await encryptField(JSON.stringify(fields), key);
53282
- const searchHash = await hmacWithKey(fields.name.toLowerCase().trim().replace(/\s+/g, " "), key);
53283
- return { cipher, searchHash };
53284
- }
53285
- async function decryptPersonCipher(cipher, key) {
53286
- const json2 = await decryptField(cipher, key);
53287
- return JSON.parse(json2);
53288
- }
53289
53261
  async function deriveKey(userId, pin, salt) {
53290
53262
  const enc = new TextEncoder;
53291
53263
  const baseKey = await crypto.subtle.importKey("raw", enc.encode(userId + pin), "PBKDF2", false, [
@@ -53329,6 +53301,34 @@ async function decryptField(encrypted, key) {
53329
53301
  const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
53330
53302
  return new TextDecoder().decode(plaintext);
53331
53303
  }
53304
+ var VERIFIER_PLAINTEXT = "paisa-verify-v1";
53305
+ async function checkVerifier(key, encryptedVerifier) {
53306
+ try {
53307
+ const decrypted = await decryptField(encryptedVerifier, key);
53308
+ return decrypted === VERIFIER_PLAINTEXT;
53309
+ } catch {
53310
+ return false;
53311
+ }
53312
+ }
53313
+
53314
+ // src/crypto.ts
53315
+ var ENCRYPTED_TXN_FIELDS = ["description", "referenceNumber", "notes"];
53316
+ var ENCRYPTED_MERCHANT_FIELDS = ["cleanName", "rawId", "notes"];
53317
+ async function hmacWithKey(value, key) {
53318
+ const rawKey = await crypto.subtle.exportKey("raw", key);
53319
+ const hmacKey = await crypto.subtle.importKey("raw", rawKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
53320
+ const sig = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(value));
53321
+ return btoa(String.fromCharCode(...new Uint8Array(sig)));
53322
+ }
53323
+ async function encryptPersonCipher(fields, key) {
53324
+ const cipher = await encryptField(JSON.stringify(fields), key);
53325
+ const searchHash = await hmacWithKey(fields.name.toLowerCase().trim().replace(/\s+/g, " "), key);
53326
+ return { cipher, searchHash };
53327
+ }
53328
+ async function decryptPersonCipher(cipher, key) {
53329
+ const json2 = await decryptField(cipher, key);
53330
+ return JSON.parse(json2);
53331
+ }
53332
53332
  async function encryptObjectFields(obj, fields, key) {
53333
53333
  for (const field of fields) {
53334
53334
  const val = obj[field];
@@ -53386,15 +53386,6 @@ async function decryptPersonCiphers(input2, key) {
53386
53386
  merge3(data);
53387
53387
  return { data, undecrypted };
53388
53388
  }
53389
- var VERIFIER_PLAINTEXT = "paisa-verify-v1";
53390
- async function checkVerifier(key, encryptedVerifier) {
53391
- try {
53392
- const decrypted = await decryptField(encryptedVerifier, key);
53393
- return decrypted === VERIFIER_PLAINTEXT;
53394
- } catch {
53395
- return false;
53396
- }
53397
- }
53398
53389
 
53399
53390
  // src/lib/crypto-init.ts
53400
53391
  async function initCrypto(client, pin, userId) {
@@ -53439,10 +53430,153 @@ async function initCrypto(client, pin, userId) {
53439
53430
  return { key };
53440
53431
  }
53441
53432
 
53433
+ // src/server.ts
53434
+ import { randomBytes } from "node:crypto";
53435
+
53442
53436
  // ../../node_modules/.bun/@modelcontextprotocol+server@2.1.0/node_modules/@modelcontextprotocol/server/dist/index.mjs
53443
53437
  var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
53438
+ var PREFIX = "v1.";
53439
+ function bytesToBase64Url(bytes) {
53440
+ let bin = "";
53441
+ for (const b of bytes)
53442
+ bin += String.fromCodePoint(b);
53443
+ return btoa(bin).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
53444
+ }
53445
+ function constantTimeTagEqual(a, b) {
53446
+ if (a.length !== b.length)
53447
+ return false;
53448
+ let r = 0;
53449
+ for (let i = 0;i < a.length; i++)
53450
+ r |= a.codePointAt(i) ^ b.codePointAt(i);
53451
+ return r === 0;
53452
+ }
53453
+ function base64UrlToBytes(s) {
53454
+ const b64 = s.replaceAll("-", "+").replaceAll("_", "/");
53455
+ const bin = atob(b64);
53456
+ const bytes = new Uint8Array(bin.length);
53457
+ for (let i = 0;i < bin.length; i++)
53458
+ bytes[i] = bin.codePointAt(i);
53459
+ return bytes;
53460
+ }
53461
+ function createRequestStateCodec(options) {
53462
+ const subtle = globalThis.crypto?.subtle;
53463
+ if (subtle === undefined)
53464
+ throw new TypeError("createRequestStateCodec requires the Web Crypto API (globalThis.crypto.subtle); see https://ts.sdk.modelcontextprotocol.io/v2/troubleshooting for the Node.js polyfill instructions");
53465
+ const keyBytes = typeof options.key === "string" ? new TextEncoder().encode(options.key) : Uint8Array.from(options.key);
53466
+ if (keyBytes.byteLength < 32)
53467
+ throw new RangeError(`createRequestStateCodec: key must be at least 32 bytes (got ${keyBytes.byteLength})`);
53468
+ const ttlSeconds = options.ttlSeconds ?? 600;
53469
+ if (!Number.isFinite(ttlSeconds))
53470
+ throw new RangeError("createRequestStateCodec: ttlSeconds must be a finite number");
53471
+ const bind2 = options.bind;
53472
+ let cryptoKey;
53473
+ const importedKey = () => cryptoKey ??= subtle.importKey("raw", keyBytes, {
53474
+ name: "HMAC",
53475
+ hash: "SHA-256"
53476
+ }, false, ["sign", "verify"]);
53477
+ const utf8 = new TextEncoder;
53478
+ const BIND_LABEL = "mcp.requestState.bind:";
53479
+ const bindTag = async (value) => {
53480
+ return bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(BIND_LABEL + value))).slice(0, 16));
53481
+ };
53482
+ return {
53483
+ async mint(payload, ctx) {
53484
+ const envelope = {
53485
+ p: payload,
53486
+ exp: Math.floor(Date.now() / 1000) + ttlSeconds
53487
+ };
53488
+ if (bind2 !== undefined) {
53489
+ if (ctx === undefined)
53490
+ throw new TypeError("createRequestStateCodec: mint() requires ctx when a bind callback is configured");
53491
+ envelope.b = await bindTag(bind2(ctx));
53492
+ }
53493
+ const body = bytesToBase64Url(utf8.encode(JSON.stringify(envelope)));
53494
+ return `${PREFIX}${body}.${bytesToBase64Url(new Uint8Array(await subtle.sign("HMAC", await importedKey(), utf8.encode(PREFIX + body))))}`;
53495
+ },
53496
+ async verify(state, ctx) {
53497
+ const dot = state.lastIndexOf(".");
53498
+ if (!state.startsWith(PREFIX) || dot <= 3)
53499
+ throw new Error("malformed");
53500
+ const body = state.slice(3, dot);
53501
+ let macBytes;
53502
+ try {
53503
+ macBytes = base64UrlToBytes(state.slice(dot + 1));
53504
+ } catch {
53505
+ throw new Error("malformed");
53506
+ }
53507
+ if (!await subtle.verify("HMAC", await importedKey(), macBytes, utf8.encode(PREFIX + body)))
53508
+ throw new Error("mac");
53509
+ let envelope;
53510
+ try {
53511
+ envelope = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(base64UrlToBytes(body)));
53512
+ } catch {
53513
+ throw new Error("malformed");
53514
+ }
53515
+ if (typeof envelope.exp !== "number" || envelope.exp < Math.floor(Date.now() / 1000))
53516
+ throw new Error("expired");
53517
+ if (bind2 !== undefined) {
53518
+ const expected = await bindTag(bind2(ctx));
53519
+ if (envelope.b === undefined || !constantTimeTagEqual(envelope.b, expected))
53520
+ throw new Error("bind");
53521
+ } else if (envelope.b !== undefined)
53522
+ throw new Error("bind");
53523
+ return envelope.p;
53524
+ }
53525
+ };
53526
+ }
53444
53527
 
53445
- // ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/interaction.js
53528
+ // ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/interaction.js
53529
+ var CONFIRM_KEY = "signet_confirm";
53530
+ function canonicalize(value) {
53531
+ if (Array.isArray(value))
53532
+ return value.map(canonicalize);
53533
+ if (value !== null && typeof value === "object") {
53534
+ const obj = value;
53535
+ return Object.fromEntries(Object.keys(obj).sort().map((k) => [k, canonicalize(obj[k])]));
53536
+ }
53537
+ return value;
53538
+ }
53539
+ async function fingerprint(args) {
53540
+ const bytes = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(canonicalize(args ?? null))));
53541
+ return [...new Uint8Array(bytes)].map((b) => b.toString(16).padStart(2, "0")).join("");
53542
+ }
53543
+ async function confirm(ctx, { message, detail, args, key }) {
53544
+ const id = key ?? `${CONFIRM_KEY}:${ctx.tool}`;
53545
+ const view = inputResponse(ctx.mcp.mcpReq.inputResponses, id);
53546
+ if (view.kind === "missing") {
53547
+ if (!ctx.confirmSupport.getClientCapabilities()?.elicitation)
53548
+ return "unsupported";
53549
+ const payload = { tool: ctx.tool, id, fp: await fingerprint(args) };
53550
+ const requestState = await ctx.confirmSupport.mint?.(payload);
53551
+ return inputRequired({
53552
+ inputRequests: {
53553
+ [id]: inputRequired.elicit({
53554
+ message: detail ? `${message}
53555
+
53556
+ ${detail}` : message,
53557
+ requestedSchema: {
53558
+ type: "object",
53559
+ properties: { confirm: { type: "boolean", title: "Confirm" } },
53560
+ required: ["confirm"]
53561
+ }
53562
+ })
53563
+ },
53564
+ ...requestState ? { requestState } : {}
53565
+ });
53566
+ }
53567
+ if (view.kind !== "elicit" || view.action !== "accept" || view.content?.confirm !== true) {
53568
+ return "declined";
53569
+ }
53570
+ if (ctx.confirmSupport.mint) {
53571
+ const state = ctx.mcp.mcpReq.requestState();
53572
+ if (!state || state.tool !== ctx.tool || state.id !== id || state.fp !== await fingerprint(args))
53573
+ return "declined";
53574
+ }
53575
+ return "confirmed";
53576
+ }
53577
+ function isConfirmPending(value) {
53578
+ return isInputRequiredResult(value);
53579
+ }
53446
53580
  async function progress(ctx, done, total, message) {
53447
53581
  const token = ctx.mcp.mcpReq._meta?.progressToken;
53448
53582
  if (token === undefined)
@@ -53459,7 +53593,7 @@ async function progress(ctx, done, total, message) {
53459
53593
  });
53460
53594
  } catch {}
53461
53595
  }
53462
- // ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/http/status-codes.js
53596
+ // ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/http/status-codes.js
53463
53597
  var STATUS_CODES = {
53464
53598
  400: "BAD_REQUEST",
53465
53599
  401: "UNAUTHORIZED",
@@ -53473,7 +53607,7 @@ var STATUS_CODES = {
53473
53607
  429: "RATE_LIMITED"
53474
53608
  };
53475
53609
 
53476
- // ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/errors.js
53610
+ // ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/errors.js
53477
53611
  function isZodError(err) {
53478
53612
  return err instanceof Error && err.name === "ZodError" && Array.isArray(err.issues);
53479
53613
  }
@@ -53537,7 +53671,7 @@ function report(opts, err, meta3, c) {
53537
53671
  } catch {}
53538
53672
  }
53539
53673
 
53540
- // ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/primitives.js
53674
+ // ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/primitives.js
53541
53675
  var RAW = Symbol.for("signet.mcp.rawResult");
53542
53676
  function protocolError(err, deps, meta3) {
53543
53677
  if (ProtocolError.isInstance(err))
@@ -53570,8 +53704,14 @@ function checkScopes(scopes, token) {
53570
53704
  });
53571
53705
  }
53572
53706
  function baseContext(mcp, deps) {
53573
- const { c, token } = deps.context;
53574
- return { signal: mcp.mcpReq.signal, mcp, ...c ? { c } : {}, ...token ? { token } : {} };
53707
+ const { c, token, confirmSupport } = deps.context;
53708
+ return {
53709
+ signal: mcp.mcpReq.signal,
53710
+ mcp,
53711
+ confirmSupport,
53712
+ ...c ? { c } : {},
53713
+ ...token ? { token } : {}
53714
+ };
53575
53715
  }
53576
53716
  function toContents(uri, value, mimeType) {
53577
53717
  if (value !== null && typeof value === "object" && RAW in value)
@@ -53681,7 +53821,7 @@ function registerPrompts(server, prompts, deps) {
53681
53821
  }
53682
53822
  }
53683
53823
 
53684
- // ../../node_modules/.bun/@nimit9+signet-server@0.2.6+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/server.js
53824
+ // ../../node_modules/.bun/@nimit9+signet-server@0.2.7+e803b2305afdb064/node_modules/@nimit9/signet-server/dist/mcp/server.js
53685
53825
  var RAW2 = Symbol.for("signet.mcp.rawResult");
53686
53826
  function toolResult(result) {
53687
53827
  return Object.defineProperty({ ...result }, RAW2, { value: true, enumerable: false });
@@ -53709,9 +53849,14 @@ function createMcpServer(options) {
53709
53849
  ...options.resources?.length ? { resources: { listChanged: false } } : {},
53710
53850
  ...options.prompts?.length ? { prompts: { listChanged: false } } : {}
53711
53851
  };
53852
+ const confirmCodec = options.confirm ? createRequestStateCodec({
53853
+ key: options.confirm.key,
53854
+ ...options.confirm.ttlSeconds !== undefined ? { ttlSeconds: options.confirm.ttlSeconds } : {}
53855
+ }) : undefined;
53712
53856
  const server = new McpServer({ name: options.name, version: options.version }, {
53713
53857
  ...unlisted.size ? {} : { capabilities: toolsCapability },
53714
- ...options.instructions ? { instructions: options.instructions } : {}
53858
+ ...options.instructions ? { instructions: options.instructions } : {},
53859
+ ...confirmCodec ? { requestState: { verify: confirmCodec.verify } } : {}
53715
53860
  });
53716
53861
  let restore;
53717
53862
  if (unlisted.size) {
@@ -53726,6 +53871,10 @@ function createMcpServer(options) {
53726
53871
  tool: tool.name,
53727
53872
  signal: mcp.mcpReq.signal,
53728
53873
  mcp,
53874
+ confirmSupport: {
53875
+ getClientCapabilities: () => server.server.getClientCapabilities(),
53876
+ ...confirmCodec ? { mint: (payload) => confirmCodec.mint(payload, mcp) } : {}
53877
+ },
53729
53878
  ...c ? { c } : {},
53730
53879
  ...token ? { token } : {}
53731
53880
  };
@@ -53764,7 +53913,14 @@ function createMcpServer(options) {
53764
53913
  restore?.();
53765
53914
  restore = undefined;
53766
53915
  }
53767
- const deps = { options, context: { ...c ? { c } : {}, ...token ? { token } : {} } };
53916
+ const confirmSupport = {
53917
+ getClientCapabilities: () => server.server.getClientCapabilities(),
53918
+ ...confirmCodec ? { mint: (payload) => confirmCodec.mint(payload) } : {}
53919
+ };
53920
+ const deps = {
53921
+ options,
53922
+ context: { confirmSupport, ...c ? { c } : {}, ...token ? { token } : {} }
53923
+ };
53768
53924
  if (options.resources?.length)
53769
53925
  registerResources(server, options.resources, deps);
53770
53926
  if (options.prompts?.length)
@@ -53867,84 +54023,34 @@ function applyAnnotations(tools) {
53867
54023
  }
53868
54024
 
53869
54025
  // src/lib/confirm.ts
53870
- import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
53871
- var ANSWER_KEY = "confirm";
53872
- function clientCanConfirm(mcp, initializeCapabilities) {
53873
- const envelope = mcp.mcpReq.envelope;
53874
- const caps = envelope?.[CLIENT_CAPABILITIES_META_KEY] ?? initializeCapabilities();
53875
- return Boolean(caps?.elicitation);
53876
- }
53877
- function planFingerprint(plan) {
53878
- const canonical = (v) => Array.isArray(v) ? v.map(canonical) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).filter(([, x]) => x !== undefined).sort(([a], [b]) => a.localeCompare(b)).map(([k, x]) => [k, canonical(x)])) : v;
53879
- return createHash("sha256").update(JSON.stringify(canonical(plan))).digest("hex");
53880
- }
53881
- var STATE_KEY = randomBytes(32);
53882
- function sign(nonce, fingerprint) {
53883
- return createHmac("sha256", STATE_KEY).update(`${nonce}.${fingerprint}`).digest("hex");
53884
- }
53885
- function mintState(fingerprint) {
53886
- const nonce = randomBytes(16).toString("hex");
53887
- return `${nonce}.${sign(nonce, fingerprint)}`;
53888
- }
53889
- function verifyState(state, fingerprint) {
53890
- if (typeof state !== "string")
53891
- return false;
53892
- const [nonce, mac3, ...rest] = state.split(".");
53893
- if (!nonce || !mac3 || rest.length)
53894
- return false;
53895
- const expected = Buffer.from(sign(nonce, fingerprint), "hex");
53896
- const given = Buffer.from(mac3, "hex");
53897
- return given.length === expected.length && timingSafeEqual(given, expected);
54026
+ var CANCELLED = { cancelled: true, message: "Cancelled by the user. Nothing was changed." };
54027
+ var PLAN_CHANGED = {
54028
+ planChanged: true,
54029
+ message: "The request changed after you confirmed, so nothing was changed. Run it again to see the new plan."
54030
+ };
54031
+ function acceptedDifferentPlan(ctx) {
54032
+ const view = inputResponse(ctx.mcp.mcpReq.inputResponses, `signet_confirm:${ctx.tool}`);
54033
+ return view.kind === "elicit" && view.action === "accept" && view.content?.confirm === true;
53898
54034
  }
53899
- function confirmPlan(mcp, message, plan, initializeCapabilities = () => {
53900
- return;
53901
- }) {
53902
- if (!clientCanConfirm(mcp, initializeCapabilities))
53903
- return { kind: "unsupported" };
53904
- const fingerprint = planFingerprint(plan);
53905
- const responses = mcp.mcpReq.inputResponses;
53906
- const answered = responses !== undefined && ANSWER_KEY in responses;
53907
- const echoed = mcp.mcpReq.requestState?.();
53908
- if (answered && verifyState(echoed, fingerprint)) {
53909
- const content = acceptedContent(responses, ANSWER_KEY);
53910
- if (content?.confirm === true)
53911
- return { kind: "confirmed" };
53912
- return {
53913
- kind: "declined",
53914
- result: { cancelled: true, message: "Cancelled by the user. Nothing was changed." }
53915
- };
53916
- }
53917
- return {
53918
- kind: "ask",
53919
- result: inputRequired({
53920
- inputRequests: {
53921
- [ANSWER_KEY]: inputRequired.elicit({
53922
- message,
53923
- requestedSchema: {
53924
- type: "object",
53925
- properties: {
53926
- confirm: { type: "boolean", title: "Confirm", description: "This can't be undone." }
53927
- },
53928
- required: ["confirm"]
53929
- }
53930
- })
53931
- },
53932
- requestState: mintState(fingerprint)
53933
- })
53934
- };
54035
+ async function askThenRun(ctx, message, plan, run, fallback = run) {
54036
+ const answer = await confirm(ctx, { message, args: plan });
54037
+ if (isConfirmPending(answer))
54038
+ return answer;
54039
+ if (answer === "confirmed")
54040
+ return run();
54041
+ if (answer === "unsupported")
54042
+ return fallback();
54043
+ return acceptedDifferentPlan(ctx) ? PLAN_CHANGED : CANCELLED;
53935
54044
  }
53936
- function withConfirmation(tool, describe3, initializeCapabilities) {
54045
+ function withConfirmation(tool, describe3) {
53937
54046
  const run = tool.handler;
53938
54047
  return {
53939
54048
  ...tool,
53940
- handler: (args, ctx) => {
54049
+ handler: async (args, ctx) => {
53941
54050
  const message = describe3(args);
53942
54051
  if (message === null || !ctx?.mcp)
53943
54052
  return run(args, ctx);
53944
- const outcome = confirmPlan(ctx.mcp, message, { tool: tool.name, args }, initializeCapabilities);
53945
- if (outcome.kind === "confirmed" || outcome.kind === "unsupported")
53946
- return run(args, ctx);
53947
- return outcome.result;
54053
+ return askThenRun(ctx, message, { tool: tool.name, args }, () => run(args, ctx));
53948
54054
  }
53949
54055
  };
53950
54056
  }
@@ -54021,11 +54127,7 @@ var BULK_CONFIRMED_TOOLS = {
54021
54127
  }),
54022
54128
  bulk_link_entity: async (args) => ({ n: Array.isArray(args.links) ? args.links.length : 0 })
54023
54129
  };
54024
- var PLAN_CHANGED = {
54025
- planChanged: true,
54026
- message: "The transactions changed after they were counted, so nothing was written. Run it again to see the new count."
54027
- };
54028
- function withBulkConfirmation(tool, count, initializeCapabilities) {
54130
+ function withBulkConfirmation(tool, count) {
54029
54131
  const run = tool.handler;
54030
54132
  const runPlanned = async (args, ctx, hash2) => {
54031
54133
  const result = await run(hash2 === undefined ? args : { ...args, expectedPlanHash: hash2 }, ctx);
@@ -54034,18 +54136,13 @@ function withBulkConfirmation(tool, count, initializeCapabilities) {
54034
54136
  return {
54035
54137
  ...tool,
54036
54138
  handler: async (args, ctx) => {
54037
- if (args.dryRun === true || !ctx?.mcp || !clientCanConfirm(ctx.mcp, initializeCapabilities))
54139
+ const canAsk = Boolean(ctx?.confirmSupport?.getClientCapabilities()?.elicitation);
54140
+ if (args.dryRun === true || !ctx?.mcp || !canAsk)
54038
54141
  return run(args, ctx);
54039
54142
  const { n, planHash: hash2 } = await count(args, async (a) => run(a, ctx));
54040
54143
  if (n <= BULK_CONFIRM_THRESHOLD)
54041
54144
  return runPlanned(args, ctx, hash2);
54042
- const message = `Update ${plural2(n, "transaction")} in one go?`;
54043
- const outcome = confirmPlan(ctx.mcp, message, { tool: tool.name, args, n, planHash: hash2 }, initializeCapabilities);
54044
- if (outcome.kind === "confirmed")
54045
- return runPlanned(args, ctx, hash2);
54046
- if (outcome.kind === "unsupported")
54047
- return run(args, ctx);
54048
- return outcome.result;
54145
+ return askThenRun(ctx, `Update ${plural2(n, "transaction")} in one go?`, { tool: tool.name, args, n, planHash: hash2 }, () => runPlanned(args, ctx, hash2), () => run(args, ctx));
54049
54146
  }
54050
54147
  };
54051
54148
  }
@@ -54478,7 +54575,7 @@ async function decryptAnalytics(data, key) {
54478
54575
  ], key);
54479
54576
  return undecrypted.length ? { ...decrypted, undecrypted } : decrypted;
54480
54577
  }
54481
- function analyticsTools(client, crypto3) {
54578
+ function analyticsTools(client, crypto4) {
54482
54579
  return [
54483
54580
  defineTool({
54484
54581
  name: "get_analytics",
@@ -54503,8 +54600,8 @@ function analyticsTools(client, crypto3) {
54503
54600
  data = await client.get(`/api/analytics/merchants/${params.id}`, params.months ? { months: params.months } : undefined);
54504
54601
  break;
54505
54602
  }
54506
- if (crypto3)
54507
- data = await decryptAnalytics(data, crypto3.key);
54603
+ if (crypto4)
54604
+ data = await decryptAnalytics(data, crypto4.key);
54508
54605
  return data;
54509
54606
  }
54510
54607
  })
@@ -54562,7 +54659,7 @@ function budgetTools(client) {
54562
54659
  }
54563
54660
 
54564
54661
  // src/tools/dashboard.ts
54565
- function dashboardTools(client, crypto3) {
54662
+ function dashboardTools(client, crypto4) {
54566
54663
  return [
54567
54664
  defineTool({
54568
54665
  name: "get_dashboard",
@@ -54571,8 +54668,8 @@ function dashboardTools(client, crypto3) {
54571
54668
  output: dashboardOutputSchema,
54572
54669
  handler: async (params) => {
54573
54670
  const data = await client.get("/api/dashboard", params.months ? { months: params.months } : undefined);
54574
- if (crypto3 && Array.isArray(data?.recentTransactions)) {
54575
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data.recentTransactions, crypto3.key);
54671
+ if (crypto4 && Array.isArray(data?.recentTransactions)) {
54672
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.recentTransactions, crypto4.key);
54576
54673
  data.recentTransactions = decrypted;
54577
54674
  if (undecrypted.length)
54578
54675
  return { ...data, undecrypted };
@@ -54718,7 +54815,7 @@ function goalTools(client) {
54718
54815
  return [
54719
54816
  defineTool({
54720
54817
  name: "upsert_savings",
54721
- description: "Create (omit id) or update (pass id; all fields optional) a savings goal, EMI or sinking fund. Amounts are ₹ decimal strings, dates YYYY-MM-DD. Required on create — " + "goal: goalType, name, targetAmount. emi: name, totalAmount, emiAmount (monthly), startDate, endDate (last payment), totalMonths, owner. " + "sinking_fund: name, targetAmount, targetDate (due), monthlyContribution.",
54818
+ description: "Create (omit id) or update (pass id; all fields optional) a savings goal, EMI or sinking fund. Amounts are ₹ decimal strings, dates YYYY-MM-DD. Required on create — " + "goal: goalType, name, targetAmount. emi: name, totalAmount, emiAmount (monthly), startDate, endDate (last payment), totalMonths, owner. " + "sinking_fund: name, targetAmount, targetDate (due), monthlyContribution. " + "status — goal: active|paused|completed|cancelled; emi: active|completed|foreclosed; sinking funds have none.",
54722
54819
  input: exports_external.object({
54723
54820
  type: exports_external.enum(["goal", "emi", "sinking_fund"]),
54724
54821
  id: exports_external.string().optional(),
@@ -54739,7 +54836,8 @@ function goalTools(client) {
54739
54836
  startDate: exports_external.string().optional(),
54740
54837
  endDate: exports_external.string().optional(),
54741
54838
  totalMonths: exports_external.number().optional(),
54742
- paidMonths: exports_external.number().optional()
54839
+ paidMonths: exports_external.number().optional(),
54840
+ status: exports_external.enum(["active", "paused", "completed", "cancelled", "foreclosed"]).optional().describe("goal: active|paused|completed|cancelled; emi: active|completed|foreclosed")
54743
54841
  }),
54744
54842
  handler: async ({ type, ...fields }) => {
54745
54843
  const require2 = (keys) => {
@@ -54748,6 +54846,14 @@ function goalTools(client) {
54748
54846
  throw new Error(`upsert_savings type='${type}' requires: ${missing.join(", ")}`);
54749
54847
  }
54750
54848
  };
54849
+ const allowedStatus = {
54850
+ goal: ["active", "paused", "completed", "cancelled"],
54851
+ emi: ["active", "completed", "foreclosed"],
54852
+ sinking_fund: []
54853
+ };
54854
+ if (fields.status !== undefined && !allowedStatus[type].includes(fields.status)) {
54855
+ throw new Error(type === "sinking_fund" ? "upsert_savings type='sinking_fund' has no status" : `upsert_savings type='${type}' status must be one of: ${allowedStatus[type].join(", ")}`);
54856
+ }
54751
54857
  if (type === "goal") {
54752
54858
  const {
54753
54859
  id: id2,
@@ -54760,7 +54866,8 @@ function goalTools(client) {
54760
54866
  owner,
54761
54867
  icon,
54762
54868
  color,
54763
- notes
54869
+ notes,
54870
+ status
54764
54871
  } = fields;
54765
54872
  const body2 = {
54766
54873
  type: goalType,
@@ -54772,7 +54879,8 @@ function goalTools(client) {
54772
54879
  owner,
54773
54880
  icon,
54774
54881
  color,
54775
- notes
54882
+ notes,
54883
+ status
54776
54884
  };
54777
54885
  if (!id2)
54778
54886
  require2(["name", "targetAmount", "goalType"]);
@@ -54790,7 +54898,8 @@ function goalTools(client) {
54790
54898
  totalMonths,
54791
54899
  paidMonths,
54792
54900
  interestRate,
54793
- outstandingPrincipal
54901
+ outstandingPrincipal,
54902
+ status
54794
54903
  } = fields;
54795
54904
  const { owner } = fields;
54796
54905
  if (!id2) {
@@ -54814,7 +54923,8 @@ function goalTools(client) {
54814
54923
  paidMonths,
54815
54924
  interestRate,
54816
54925
  outstandingPrincipal,
54817
- owner
54926
+ owner,
54927
+ status
54818
54928
  };
54819
54929
  const data2 = id2 ? await client.patch(`/api/goals/emis/${id2}`, body2) : await client.post("/api/goals/emis", body2);
54820
54930
  return data2;
@@ -55123,13 +55233,13 @@ async function categorySlugToId(client) {
55123
55233
  }
55124
55234
  return map2;
55125
55235
  }
55126
- function merchantVariants(client, crypto3) {
55236
+ function merchantVariants(client, crypto4) {
55127
55237
  return {
55128
55238
  list: variant(exports_external.object({ rawId: exports_external.string().optional().describe("merchants: find one by UPI ID or name") }), async ({ rawId }) => {
55129
55239
  const data = await client.get("/api/merchants");
55130
55240
  let undecrypted = [];
55131
- if (crypto3 && data.data) {
55132
- const dec = await decryptMerchantFields(data.data, crypto3.key);
55241
+ if (crypto4 && data.data) {
55242
+ const dec = await decryptMerchantFields(data.data, crypto4.key);
55133
55243
  data.data = dec.data;
55134
55244
  undecrypted = dec.undecrypted;
55135
55245
  }
@@ -55187,7 +55297,7 @@ function merchantTools(client) {
55187
55297
  }
55188
55298
 
55189
55299
  // src/tools/persons.ts
55190
- function personVariants(client, crypto3) {
55300
+ function personVariants(client, crypto4) {
55191
55301
  return {
55192
55302
  upsert: variant(exports_external.object({
55193
55303
  id: exports_external.string().optional().describe("Person UUID — pass to update, omit to create"),
@@ -55197,13 +55307,13 @@ function personVariants(client, crypto3) {
55197
55307
  relationship: exports_external.string().optional(),
55198
55308
  notes: exports_external.string().optional()
55199
55309
  }), async ({ id, ...updates }) => {
55200
- if (crypto3) {
55310
+ if (crypto4) {
55201
55311
  if (id) {
55202
55312
  const existing = await client.get(`/api/persons/${id}`);
55203
55313
  const personData = existing.data;
55204
55314
  let current;
55205
55315
  if (personData.cipher) {
55206
- current = await decryptPersonCipher(personData.cipher, crypto3.key);
55316
+ current = await decryptPersonCipher(personData.cipher, crypto4.key);
55207
55317
  } else {
55208
55318
  current = {
55209
55319
  name: personData.name,
@@ -55220,7 +55330,7 @@ function personVariants(client, crypto3) {
55220
55330
  relationship: updates.relationship ?? current.relationship ?? null,
55221
55331
  notes: updates.notes ?? current.notes ?? null
55222
55332
  };
55223
- const payload2 = await encryptPersonCipher(merged, crypto3.key);
55333
+ const payload2 = await encryptPersonCipher(merged, crypto4.key);
55224
55334
  const data3 = await client.patch(`/api/persons/${id}`, payload2);
55225
55335
  return data3;
55226
55336
  }
@@ -55233,7 +55343,7 @@ function personVariants(client, crypto3) {
55233
55343
  relationship: updates.relationship ?? null,
55234
55344
  notes: updates.notes ?? null
55235
55345
  };
55236
- const payload = await encryptPersonCipher(fields, crypto3.key);
55346
+ const payload = await encryptPersonCipher(fields, crypto4.key);
55237
55347
  const data2 = await client.post("/api/persons", payload);
55238
55348
  return data2;
55239
55349
  }
@@ -55245,8 +55355,8 @@ function personVariants(client, crypto3) {
55245
55355
  }), async ({ id }) => {
55246
55356
  if (id) {
55247
55357
  const data2 = await client.get(`/api/persons/${id}`);
55248
- if (crypto3 && data2.data.cipher) {
55249
- const { data: decrypted, undecrypted } = await decryptPersonCiphers(data2.data, crypto3.key);
55358
+ if (crypto4 && data2.data.cipher) {
55359
+ const { data: decrypted, undecrypted } = await decryptPersonCiphers(data2.data, crypto4.key);
55250
55360
  data2.data = decrypted;
55251
55361
  if (undecrypted.length)
55252
55362
  return { ...data2, undecrypted };
@@ -55254,8 +55364,8 @@ function personVariants(client, crypto3) {
55254
55364
  return data2;
55255
55365
  }
55256
55366
  const data = await client.get("/api/persons");
55257
- if (crypto3 && data.data) {
55258
- const { data: decrypted, undecrypted } = await decryptPersonCiphers(data.data, crypto3.key);
55367
+ if (crypto4 && data.data) {
55368
+ const { data: decrypted, undecrypted } = await decryptPersonCiphers(data.data, crypto4.key);
55259
55369
  data.data = decrypted;
55260
55370
  if (undecrypted.length)
55261
55371
  return { ...data, undecrypted };
@@ -55393,11 +55503,11 @@ function reportTools(client) {
55393
55503
  }
55394
55504
 
55395
55505
  // src/tools/entities.ts
55396
- function entityTools(client, crypto3) {
55506
+ function entityTools(client, crypto4) {
55397
55507
  const category = categoryVariants(client);
55398
55508
  const account = accountVariants(client);
55399
- const person = personVariants(client, crypto3);
55400
- const merchant = merchantVariants(client, crypto3);
55509
+ const person = personVariants(client, crypto4);
55510
+ const merchant = merchantVariants(client, crypto4);
55401
55511
  const debt = debtVariants(client);
55402
55512
  const household = householdVariants(client);
55403
55513
  const goal = goalVariants(client);
@@ -59025,9 +59135,9 @@ function parseAmount4(raw) {
59025
59135
  return Number.isNaN(num) ? 0 : Math.abs(num);
59026
59136
  }
59027
59137
  // src/lib/plan-hash.ts
59028
- import { createHash as createHash2 } from "node:crypto";
59138
+ import { createHash } from "node:crypto";
59029
59139
  function planHash(ids) {
59030
- return createHash2("sha256").update([...ids].sort().join(",")).digest("hex");
59140
+ return createHash("sha256").update([...ids].sort().join(",")).digest("hex");
59031
59141
  }
59032
59142
 
59033
59143
  // src/lib/txn-rows.ts
@@ -59046,10 +59156,10 @@ function toTxnRow(r2) {
59046
59156
  isDuplicate: !!r2.isDuplicate
59047
59157
  };
59048
59158
  }
59049
- async function fetchAllRows(client, crypto3, params = {}, maxPages = 100) {
59050
- return (await fetchAllRowsPaged(client, crypto3, params, maxPages)).rows;
59159
+ async function fetchAllRows(client, crypto4, params = {}, maxPages = 100) {
59160
+ return (await fetchAllRowsPaged(client, crypto4, params, maxPages)).rows;
59051
59161
  }
59052
- async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59162
+ async function fetchAllRowsPaged(client, crypto4, params = {}, maxPages = 100) {
59053
59163
  const out = [];
59054
59164
  let cursor;
59055
59165
  let truncated = false;
@@ -59059,7 +59169,7 @@ async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59059
59169
  q2.cursor = cursor;
59060
59170
  const res = await client.get("/api/transactions", q2);
59061
59171
  const batch0 = res.data ?? [];
59062
- const batch = crypto3 ? (await decryptTransactionFields(batch0, crypto3.key)).data : batch0;
59172
+ const batch = crypto4 ? (await decryptTransactionFields(batch0, crypto4.key)).data : batch0;
59063
59173
  out.push(...batch.map(toTxnRow));
59064
59174
  if (!res.hasMore || !res.nextCursor || batch.length === 0 || res.nextCursor === cursor)
59065
59175
  break;
@@ -59105,18 +59215,21 @@ function toLeanRow(r2, personNames) {
59105
59215
  function normalizePersonName(name) {
59106
59216
  return name.toLowerCase().trim().replace(/\s+/g, " ");
59107
59217
  }
59108
- async function loadDecryptedPersons(client, crypto3) {
59218
+ function normalizeUpiHandle(handle) {
59219
+ return handle.toLowerCase().trim().split("@")[0];
59220
+ }
59221
+ async function loadDecryptedPersons(client, crypto4) {
59109
59222
  const res = await client.get("/api/persons");
59110
59223
  const persons = [];
59111
59224
  for (const p2 of res.data ?? []) {
59112
59225
  let name = p2.name ?? "";
59113
- if (crypto3 && p2.cipher) {
59226
+ if (crypto4 && p2.cipher) {
59114
59227
  try {
59115
- name = (await decryptPersonCipher(p2.cipher, crypto3.key)).name;
59228
+ name = (await decryptPersonCipher(p2.cipher, crypto4.key)).name;
59116
59229
  } catch {}
59117
59230
  }
59118
59231
  if (name)
59119
- persons.push({ id: p2.id, name });
59232
+ persons.push({ id: p2.id, name, upiHandles: p2.upiHandles ?? [] });
59120
59233
  }
59121
59234
  return persons;
59122
59235
  }
@@ -59153,13 +59266,18 @@ function isPersonLikeNarration(description) {
59153
59266
  const { name, paymentMethod } = parseNarration(description);
59154
59267
  return classifyEntity({ paymentMethod, extractedName: name }) !== "merchant";
59155
59268
  }
59156
- async function backfillPersons(client, crypto3, opts = {}) {
59157
- const existing = await loadDecryptedPersons(client, crypto3);
59269
+ async function backfillPersons(client, crypto4, opts = {}) {
59270
+ const existing = await loadDecryptedPersons(client, crypto4);
59158
59271
  const personIdByNorm = new Map;
59159
- for (const p2 of existing)
59272
+ const personIdByHandle = new Map;
59273
+ for (const p2 of existing) {
59160
59274
  personIdByNorm.set(normalizePersonName(p2.name), p2.id);
59161
- const { rows, truncated } = await fetchAllRowsPaged(client, crypto3, { unresolved: "true" });
59275
+ for (const h2 of p2.upiHandles)
59276
+ personIdByHandle.set(normalizeUpiHandle(h2), p2.id);
59277
+ }
59278
+ const { rows, truncated } = await fetchAllRowsPaged(client, crypto4, { unresolved: "true" });
59162
59279
  const candidates = rows.filter((r2) => !r2.merchantId && !r2.personId && !r2.isTransfer && !r2.isIgnored);
59280
+ const directLinks = [];
59163
59281
  const groups = new Map;
59164
59282
  for (const t2 of candidates) {
59165
59283
  const { name, upiId, paymentMethod } = parseNarration(t2.description);
@@ -59167,6 +59285,11 @@ async function backfillPersons(client, crypto3, opts = {}) {
59167
59285
  continue;
59168
59286
  if (classifyEntity({ paymentMethod, extractedName: name }) !== "person")
59169
59287
  continue;
59288
+ const handlePersonId = upiId ? personIdByHandle.get(normalizeUpiHandle(upiId)) : undefined;
59289
+ if (handlePersonId) {
59290
+ directLinks.push({ transactionId: t2.id, personId: handlePersonId, upiHandle: upiId });
59291
+ continue;
59292
+ }
59170
59293
  const norm = normalizePersonName(name);
59171
59294
  if (!norm)
59172
59295
  continue;
@@ -59182,7 +59305,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59182
59305
  }
59183
59306
  const plan = [];
59184
59307
  const newGroups = [];
59185
- let reused = 0;
59308
+ const reusedPersonIds = new Set;
59186
59309
  for (const [norm, g2] of groups) {
59187
59310
  const upiHandles = [...g2.upiHandles];
59188
59311
  const isReuse = personIdByNorm.has(norm);
@@ -59194,11 +59317,34 @@ async function backfillPersons(client, crypto3, opts = {}) {
59194
59317
  action: isReuse ? "reuse" : "create"
59195
59318
  });
59196
59319
  if (isReuse)
59197
- reused++;
59320
+ reusedPersonIds.add(personIdByNorm.get(norm));
59198
59321
  else
59199
59322
  newGroups.push({ norm, displayName: g2.displayName, upiHandles });
59200
59323
  }
59201
- const hash2 = planHash([...groups.values()].flatMap((g2) => g2.txnIds));
59324
+ const directByPerson = new Map;
59325
+ for (const d2 of directLinks) {
59326
+ const e2 = directByPerson.get(d2.personId) ?? { txnIds: [], handles: new Set };
59327
+ e2.txnIds.push(d2.transactionId);
59328
+ if (d2.upiHandle)
59329
+ e2.handles.add(d2.upiHandle);
59330
+ directByPerson.set(d2.personId, e2);
59331
+ }
59332
+ for (const [personId, e2] of directByPerson) {
59333
+ const person = existing.find((p2) => p2.id === personId);
59334
+ plan.push({
59335
+ name: person?.name ?? personId,
59336
+ normalized: person ? normalizePersonName(person.name) : personId,
59337
+ txnCount: e2.txnIds.length,
59338
+ upiHandles: [...e2.handles],
59339
+ action: "reuse"
59340
+ });
59341
+ reusedPersonIds.add(personId);
59342
+ }
59343
+ const reused = reusedPersonIds.size;
59344
+ const hash2 = planHash([
59345
+ ...[...groups.values()].flatMap((g2) => g2.txnIds),
59346
+ ...directLinks.map((d2) => d2.transactionId)
59347
+ ]);
59202
59348
  if (!opts.dryRun && opts.expectedPlanHash !== undefined && opts.expectedPlanHash !== hash2) {
59203
59349
  return {
59204
59350
  dryRun: false,
@@ -59221,7 +59367,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59221
59367
  relationship: null,
59222
59368
  notes: null
59223
59369
  };
59224
- return crypto3 ? await encryptPersonCipher(fields, crypto3.key) : { name: displayName, upiHandles, phoneNumbers: [], relationship: null, notes: null };
59370
+ return crypto4 ? await encryptPersonCipher(fields, crypto4.key) : { name: displayName, upiHandles, phoneNumbers: [], relationship: null, notes: null };
59225
59371
  }));
59226
59372
  const res = await client.post("/api/persons/bulk", {
59227
59373
  persons: bodies
@@ -59240,6 +59386,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59240
59386
  for (const txnId of g2.txnIds)
59241
59387
  links.push({ transactionId: txnId, personId });
59242
59388
  }
59389
+ links.push(...directLinks);
59243
59390
  }
59244
59391
  if (!opts.dryRun && links.length > 0) {
59245
59392
  const CHUNK = 25;
@@ -59355,20 +59502,20 @@ function groupRows(rows) {
59355
59502
  function isCategorizable(r2) {
59356
59503
  return !r2.categoryId && !r2.isTransfer && !r2.isIgnored && !r2.isDuplicate;
59357
59504
  }
59358
- async function fetchUncategorized(client, crypto3, range = {}) {
59505
+ async function fetchUncategorized(client, crypto4, range = {}) {
59359
59506
  const params = { uncategorized: "true" };
59360
59507
  if (range.startDate)
59361
59508
  params.startDate = range.startDate;
59362
59509
  if (range.endDate)
59363
59510
  params.endDate = range.endDate;
59364
- return (await fetchAllRows(client, crypto3, params)).filter(isCategorizable);
59511
+ return (await fetchAllRows(client, crypto4, params)).filter(isCategorizable);
59365
59512
  }
59366
59513
  var MERCHANT_CREATE_CONCURRENCY = 5;
59367
59514
  var titleCase = (s2) => s2.replace(/\b[a-z]/g, (c2) => c2.toUpperCase());
59368
- async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59515
+ async function applyGroupAssignments(client, crypto4, assignments, opts = {}) {
59369
59516
  const catIndex = await loadCategoryIndex(client);
59370
59517
  const resolved = assignments.map((a2) => ({ a: a2, cat: requireCategory(catIndex, a2.categorySlug) }));
59371
- const groups = groupRows(await fetchUncategorized(client, crypto3, opts));
59518
+ const groups = groupRows(await fetchUncategorized(client, crypto4, opts));
59372
59519
  const result = { applied: [], notFound: [], rulesSaved: 0, unlearnable: [] };
59373
59520
  const plan = [];
59374
59521
  const claimed = new Set;
@@ -59380,7 +59527,13 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59380
59527
  }
59381
59528
  for (const g2 of hits) {
59382
59529
  claimed.add(g2);
59383
- plan.push({ group: g2, categoryId: cat.id, slug: cat.slug, learn: a2.learn !== false });
59530
+ plan.push({
59531
+ group: g2,
59532
+ categoryId: cat.id,
59533
+ slug: cat.slug,
59534
+ learn: a2.learn !== false,
59535
+ confirmMerchant: !!a2.confirmMerchant
59536
+ });
59384
59537
  }
59385
59538
  }
59386
59539
  result.planHash = planHash(plan.flatMap((p2) => p2.group.rows.map((r2) => r2.id)));
@@ -59414,10 +59567,10 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59414
59567
  const learnPlans = [];
59415
59568
  const directPlans = [];
59416
59569
  for (const p2 of plan) {
59417
- const { group, learn } = p2;
59570
+ const { group, learn, confirmMerchant } = p2;
59418
59571
  if (!learn || group.pattern === UNPARSED) {
59419
59572
  directPlans.push({ p: p2, reason: learn ? UNPARSED : undefined });
59420
- } else if (crypto3 && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59573
+ } else if (crypto4 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59421
59574
  directPlans.push({ p: p2, reason: `${group.pattern} (private mode: person-like)` });
59422
59575
  } else if (taught.has(group.pattern) && taught.get(group.pattern) !== p2.categoryId) {
59423
59576
  directPlans.push({ p: p2, reason: `${group.pattern} (conflicting category for same pattern)` });
@@ -59515,12 +59668,12 @@ function flagBatchDuplicates(txns) {
59515
59668
  return false;
59516
59669
  });
59517
59670
  }
59518
- async function importTransactions(client, crypto3, opts) {
59671
+ async function importTransactions(client, crypto4, opts) {
59519
59672
  let txns = opts.txns;
59520
59673
  let duplicatesSkipped = 0;
59521
59674
  if (txns.length > 0) {
59522
59675
  const dates = txns.map((t2) => t2.date).sort();
59523
- const existing = await fetchAllRows(client, crypto3, {
59676
+ const existing = await fetchAllRows(client, crypto4, {
59524
59677
  startDate: dates[0],
59525
59678
  endDate: dates[dates.length - 1]
59526
59679
  });
@@ -59536,8 +59689,8 @@ async function importTransactions(client, crypto3, opts) {
59536
59689
  const base = { ...t2, isDuplicate: dupFlags[i2] };
59537
59690
  return enrichedData ? { ...base, enrichedData } : base;
59538
59691
  });
59539
- if (crypto3)
59540
- await encryptTransactionFields(rows, crypto3.key);
59692
+ if (crypto4)
59693
+ await encryptTransactionFields(rows, crypto4.key);
59541
59694
  const CHUNK = 200;
59542
59695
  let imported = 0;
59543
59696
  for (let i2 = 0;i2 < rows.length; i2 += CHUNK) {
@@ -59552,19 +59705,19 @@ async function importTransactions(client, crypto3, opts) {
59552
59705
  }
59553
59706
  return { imported, duplicatesSkipped, duplicatesFlagged };
59554
59707
  }
59555
- async function runRescan(client, crypto3) {
59708
+ async function runRescan(client, crypto4) {
59556
59709
  let bag = structuredClone(EMPTY_BAG);
59557
59710
  try {
59558
59711
  bag = (await loadBag(client)).bag;
59559
59712
  } catch {}
59560
59713
  const merchantsRes = await client.get("/api/merchants");
59561
59714
  const allMerchants0 = merchantsRes.data ?? [];
59562
- const allMerchants = crypto3 ? (await decryptMerchantFields(allMerchants0, crypto3.key)).data : allMerchants0;
59715
+ const allMerchants = crypto4 ? (await decryptMerchantFields(allMerchants0, crypto4.key)).data : allMerchants0;
59563
59716
  const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59564
59717
  const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59565
59718
  const merchantCategoryById = new Map(allMerchants.filter((m2) => m2.categoryId).map((m2) => [m2.id, m2.categoryId]));
59566
- const persons = await loadDecryptedPersons(client, crypto3);
59567
- const allRows = await fetchAllRows(client, crypto3, { unresolved: "true" });
59719
+ const persons = await loadDecryptedPersons(client, crypto4);
59720
+ const allRows = await fetchAllRows(client, crypto4, { unresolved: "true" });
59568
59721
  const categoryByTxn = new Map(allRows.map((r2) => [r2.id, r2.categoryId]));
59569
59722
  const plan = autoCategorizeFromDescriptions({
59570
59723
  rows: allRows,
@@ -59573,8 +59726,17 @@ async function runRescan(client, crypto3) {
59573
59726
  localMerchants,
59574
59727
  persons
59575
59728
  });
59729
+ const knownMerchants = new Set([
59730
+ ...allMerchants.map((m2) => m2.id),
59731
+ ...allRows.flatMap((r2) => r2.merchantId ? [r2.merchantId] : [])
59732
+ ]);
59733
+ const knownPersons = new Set([
59734
+ ...persons.map((p2) => p2.id),
59735
+ ...allRows.flatMap((r2) => r2.personId ? [r2.personId] : [])
59736
+ ]);
59737
+ const live = plan.suggestions.filter((s2) => s2.entity.type === "merchant" ? knownMerchants.has(s2.entity.merchantId) : knownPersons.has(s2.entity.personId));
59576
59738
  let categorized = 0;
59577
- const links = plan.suggestions.flatMap((s2) => {
59739
+ const links = live.flatMap((s2) => {
59578
59740
  const categoryId = s2.categoryId ?? (s2.entity.type === "merchant" ? merchantCategoryById.get(s2.entity.merchantId) ?? null : null);
59579
59741
  return s2.transactionIds.map((txId) => {
59580
59742
  const applyCategory = categoryId && !categoryByTxn.get(txId);
@@ -59594,7 +59756,7 @@ var TRUNCATED_WARNING = "TRUNCATED: the API capped this result, so it is INCOMPL
59594
59756
  function withTruncationWarning(data) {
59595
59757
  return data.truncated ? { warning: TRUNCATED_WARNING, ...data } : data;
59596
59758
  }
59597
- function transactionTools(client, crypto3) {
59759
+ function transactionTools(client, crypto4) {
59598
59760
  return [
59599
59761
  defineTool({
59600
59762
  name: "get_transactions",
@@ -59641,7 +59803,7 @@ function transactionTools(client, crypto3) {
59641
59803
  apiParams.categoryId = requireCategory(await loadCategoryIndex(client), categorySlug).id;
59642
59804
  }
59643
59805
  if (personName && !apiParams.personId) {
59644
- const persons = await loadDecryptedPersons(client, crypto3);
59806
+ const persons = await loadDecryptedPersons(client, crypto4);
59645
59807
  const match = resolvePersonByName(persons, personName);
59646
59808
  const err = (error62) => ({ error: error62 });
59647
59809
  if (!match) {
@@ -59658,14 +59820,14 @@ function transactionTools(client, crypto3) {
59658
59820
  const data = await client.get("/api/transactions", Object.fromEntries(Object.entries(apiParams).filter(([, v2]) => v2 !== undefined).map(([k2, v2]) => [k2, String(v2)])));
59659
59821
  let rows = data.data ?? [];
59660
59822
  let undecrypted = [];
59661
- if (crypto3) {
59662
- const dec = await decryptTransactionFields(rows, crypto3.key);
59823
+ if (crypto4) {
59824
+ const dec = await decryptTransactionFields(rows, crypto4.key);
59663
59825
  rows = dec.data;
59664
59826
  undecrypted = dec.undecrypted;
59665
59827
  }
59666
59828
  const personNames = new Map;
59667
59829
  if (rows.some((r2) => r2.personId)) {
59668
- for (const p2 of await loadDecryptedPersons(client, crypto3))
59830
+ for (const p2 of await loadDecryptedPersons(client, crypto4))
59669
59831
  personNames.set(p2.id, p2.name);
59670
59832
  }
59671
59833
  if (full) {
@@ -59696,7 +59858,7 @@ function transactionTools(client, crypto3) {
59696
59858
  limit: exports_external.number().optional().describe("Max groups (default 40, max 100)")
59697
59859
  }),
59698
59860
  handler: async ({ startDate, endDate, minTotal, limit }) => {
59699
- const rows = await fetchUncategorized(client, crypto3, { startDate, endDate });
59861
+ const rows = await fetchUncategorized(client, crypto4, { startDate, endDate });
59700
59862
  const all2 = groupRows(rows);
59701
59863
  const shown = all2.filter((g2) => g2.total >= (minTotal ?? 0)).slice(0, Math.min(Math.max(limit ?? 40, 1), 100));
59702
59864
  const shownRows = shown.reduce((n2, g2) => n2 + g2.rows.length, 0);
@@ -59723,7 +59885,8 @@ function transactionTools(client, crypto3) {
59723
59885
  pattern: exports_external.string().describe("Exactly as returned"),
59724
59886
  type: exports_external.enum(["debit", "credit"]).optional(),
59725
59887
  categorySlug: exports_external.string(),
59726
- learn: exports_external.boolean().optional().describe("false = no rule (short/generic patterns)")
59888
+ learn: exports_external.boolean().optional().describe("false = no rule (short/generic patterns)"),
59889
+ confirmMerchant: exports_external.boolean().optional().describe("true = you've confirmed this pattern is a real business (brand lookup, web search, etc), not a person — skips the private-mode person-like block that would otherwise refuse to save a merchant rule")
59727
59890
  })).min(1).max(100),
59728
59891
  startDate: exports_external.string().optional().describe("Same range as the listing"),
59729
59892
  endDate: exports_external.string().optional(),
@@ -59732,7 +59895,7 @@ function transactionTools(client, crypto3) {
59732
59895
  handler: (args, ctx) => {
59733
59896
  const { assignments, startDate, endDate, dryRun } = args;
59734
59897
  const { expectedPlanHash } = args;
59735
- return applyGroupAssignments(client, crypto3, assignments, {
59898
+ return applyGroupAssignments(client, crypto4, assignments, {
59736
59899
  dryRun,
59737
59900
  startDate,
59738
59901
  endDate,
@@ -59745,7 +59908,7 @@ function transactionTools(client, crypto3) {
59745
59908
  name: "backfill_persons",
59746
59909
  description: "Create persons from UPI/IMPS counterparties in unlinked transactions (deduped by name) and link those transactions to them. Idempotent. dryRun previews.",
59747
59910
  input: exports_external.object({ dryRun: exports_external.boolean().optional() }),
59748
- handler: (args, ctx) => backfillPersons(client, crypto3, {
59911
+ handler: (args, ctx) => backfillPersons(client, crypto4, {
59749
59912
  onProgress: progressFor(ctx),
59750
59913
  dryRun: args.dryRun,
59751
59914
  expectedPlanHash: args.expectedPlanHash
@@ -59767,8 +59930,8 @@ function transactionTools(client, crypto3) {
59767
59930
  }),
59768
59931
  handler: async ({ id, ...updates }) => {
59769
59932
  const body = { ...updates };
59770
- if (crypto3 && body.notes)
59771
- body.notes = await encryptField(body.notes, crypto3.key);
59933
+ if (crypto4 && body.notes)
59934
+ body.notes = await encryptField(body.notes, crypto4.key);
59772
59935
  if (body.categorySlug) {
59773
59936
  const cat = requireCategory(await loadCategoryIndex(client), body.categorySlug);
59774
59937
  body.categoryId = cat.id;
@@ -59817,8 +59980,8 @@ function transactionTools(client, crypto3) {
59817
59980
  }
59818
59981
  if (action === "list") {
59819
59982
  const data2 = await client.get("/api/transactions/duplicates");
59820
- if (crypto3 && data2.data) {
59821
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data2.data, crypto3.key);
59983
+ if (crypto4 && data2.data) {
59984
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data2.data, crypto4.key);
59822
59985
  data2.data = decrypted;
59823
59986
  return withTruncationWarning(undecrypted.length ? { ...data2, undecrypted } : data2);
59824
59987
  }
@@ -59841,8 +60004,8 @@ function transactionTools(client, crypto3) {
59841
60004
  }),
59842
60005
  handler: async ({ accountId, startDate, endDate }) => {
59843
60006
  const data = await client.get("/api/transactions/reconciliation-context", { accountId, startDate, endDate });
59844
- if (crypto3 && data.data) {
59845
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto3.key);
60007
+ if (crypto4 && data.data) {
60008
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto4.key);
59846
60009
  data.data = decrypted;
59847
60010
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
59848
60011
  }
@@ -59929,7 +60092,7 @@ function transactionTools(client, crypto3) {
59929
60092
  name: "rescan_transactions",
59930
60093
  description: "Apply saved learning rules (merchant aliases, category rules) to uncategorized transactions, like the web app's auto-categorize.",
59931
60094
  input: exports_external.object({}),
59932
- handler: () => runRescan(client, crypto3)
60095
+ handler: () => runRescan(client, crypto4)
59933
60096
  }),
59934
60097
  defineTool({
59935
60098
  name: "get_settlement_candidates",
@@ -59937,8 +60100,8 @@ function transactionTools(client, crypto3) {
59937
60100
  input: exports_external.object({ ccAccountId: exports_external.string().uuid() }),
59938
60101
  handler: async ({ ccAccountId }) => {
59939
60102
  const data = await client.get("/api/transactions/settlement-candidates", { ccAccountId });
59940
- if (crypto3 && data.data) {
59941
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto3.key);
60103
+ if (crypto4 && data.data) {
60104
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto4.key);
59942
60105
  data.data = decrypted;
59943
60106
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
59944
60107
  }
@@ -59955,8 +60118,8 @@ function transactionTools(client, crypto3) {
59955
60118
  if (!row)
59956
60119
  return res;
59957
60120
  let undecrypted = [];
59958
- if (crypto3) {
59959
- const dec = await decryptTransactionFields([row], crypto3.key);
60121
+ if (crypto4) {
60122
+ const dec = await decryptTransactionFields([row], crypto4.key);
59960
60123
  row = dec.data[0];
59961
60124
  undecrypted = dec.undecrypted;
59962
60125
  }
@@ -59982,7 +60145,7 @@ function transactionTools(client, crypto3) {
59982
60145
  }
59983
60146
 
59984
60147
  // src/tools/export.ts
59985
- function exportTools(client, crypto3) {
60148
+ function exportTools(client, crypto4) {
59986
60149
  return [
59987
60150
  defineTool({
59988
60151
  name: "export_transactions",
@@ -59999,8 +60162,8 @@ function exportTools(client, crypto3) {
59999
60162
  ...Object.fromEntries(Object.entries(params).filter(([, v2]) => v2 !== undefined)),
60000
60163
  format: "json"
60001
60164
  });
60002
- if (crypto3 && Array.isArray(data?.transactions)) {
60003
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data.transactions, crypto3.key);
60165
+ if (crypto4 && Array.isArray(data?.transactions)) {
60166
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.transactions, crypto4.key);
60004
60167
  data.transactions = decrypted;
60005
60168
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60006
60169
  }
@@ -60242,28 +60405,28 @@ var DEFAULT_MIN_CONFIDENCE = 0.95;
60242
60405
  var TIMEOUT_MS = 8000;
60243
60406
  var CONCURRENCY = 8;
60244
60407
  var MIN_LEARN_PATTERN_LEN = 5;
60245
- function assertPrivateModeAllowed(crypto3, env2 = process.env) {
60246
- if (crypto3 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60408
+ function assertPrivateModeAllowed(crypto4, env2 = process.env) {
60409
+ if (crypto4 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60247
60410
  throw new Error("Jev categorization is disabled in private mode: it would send transaction text to a third party. " + "To opt in for your own account, set JEV_ALLOW_PRIVATE_MODE=1 in the MCP's environment. " + "Otherwise use get_uncategorized_groups + categorize_groups.");
60248
60411
  }
60249
60412
  }
60250
- function assertJevAllowed(crypto3, env2 = process.env) {
60251
- assertPrivateModeAllowed(crypto3, env2);
60413
+ function assertJevAllowed(crypto4, env2 = process.env) {
60414
+ assertPrivateModeAllowed(crypto4, env2);
60252
60415
  const key = env2.JEV_API_KEY;
60253
60416
  if (!key)
60254
60417
  throw new Error("JEV_API_KEY is not set — Jev categorization is off.");
60255
60418
  return key;
60256
60419
  }
60257
- function jevAllowSensitive(crypto3, env2) {
60258
- return crypto3 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60420
+ function jevAllowSensitive(crypto4, env2) {
60421
+ return crypto4 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60259
60422
  }
60260
- function makeJev(apiKey, crypto3, env2 = process.env, fetchImpl) {
60423
+ function makeJev(apiKey, crypto4, env2 = process.env, fetchImpl) {
60261
60424
  return createJev({
60262
60425
  apiKey,
60263
60426
  model: JEV_MODEL,
60264
60427
  minConfidence: DEFAULT_MIN_CONFIDENCE,
60265
60428
  timeoutMs: TIMEOUT_MS,
60266
- allowSensitive: jevAllowSensitive(crypto3, env2),
60429
+ allowSensitive: jevAllowSensitive(crypto4, env2),
60267
60430
  ...fetchImpl ? { fetch: fetchImpl } : {}
60268
60431
  });
60269
60432
  }
@@ -60283,10 +60446,10 @@ function groupState(type, sample) {
60283
60446
  const dir = type === "credit" ? "Money received" : "Money paid out";
60284
60447
  return `${dir}. Bank narration: "${sample.slice(0, 80)}"`;
60285
60448
  }
60286
- async function jevCategorize(client, crypto3, jev, opts = {}) {
60287
- assertPrivateModeAllowed(crypto3, opts.env);
60449
+ async function jevCategorize(client, crypto4, jev, opts = {}) {
60450
+ assertPrivateModeAllowed(crypto4, opts.env);
60288
60451
  const index = await loadCategoryIndex(client);
60289
- const rows = await fetchUncategorized(client, crypto3, opts);
60452
+ const rows = await fetchUncategorized(client, crypto4, opts);
60290
60453
  const groups = groupRows(rows);
60291
60454
  const unparsed = groups.filter((g2) => g2.pattern === UNPARSED).length;
60292
60455
  const candidates = groups.filter((g2) => g2.pattern !== UNPARSED);
@@ -60294,7 +60457,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60294
60457
  const suggestions = await mapLimit(candidates, CONCURRENCY, async (g2) => {
60295
60458
  const options = optionsFor(g2.type, index);
60296
60459
  const sample = g2.rows[0].description;
60297
- const text = crypto3 ? g2.pattern : sample;
60460
+ const text = crypto4 ? g2.pattern : sample;
60298
60461
  const d2 = await jev.choice({
60299
60462
  question: "Which spending category best fits this bank transaction?",
60300
60463
  options,
@@ -60336,7 +60499,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60336
60499
  }
60337
60500
  }
60338
60501
  review.sort((a2, b2) => b2.total - a2.total);
60339
- const applied = await applyGroupAssignments(client, crypto3, accepted.map(({ confidence: _c, ...a2 }) => a2), { dryRun: opts.dryRun, startDate: opts.startDate, endDate: opts.endDate });
60502
+ const applied = await applyGroupAssignments(client, crypto4, accepted.map(({ confidence: _c, ...a2 }) => a2), { dryRun: opts.dryRun, startDate: opts.startDate, endDate: opts.endDate });
60340
60503
  const conf = new Map(accepted.map((a2) => [`${a2.pattern}|${a2.type}`, a2.confidence]));
60341
60504
  return {
60342
60505
  dryRun: !!opts.dryRun,
@@ -60353,7 +60516,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60353
60516
  }
60354
60517
 
60355
60518
  // src/tools/jev.ts
60356
- function jevTools(client, crypto3) {
60519
+ function jevTools(client, crypto4) {
60357
60520
  return [
60358
60521
  defineTool({
60359
60522
  name: "auto_categorize_jev",
@@ -60365,9 +60528,9 @@ function jevTools(client, crypto3) {
60365
60528
  dryRun: exports_external.boolean().optional()
60366
60529
  }),
60367
60530
  handler: async ({ startDate, endDate, minConfidence, dryRun }) => {
60368
- const key = assertJevAllowed(crypto3);
60369
- const jev = makeJev(key, crypto3);
60370
- const result = await jevCategorize(client, crypto3, jev, {
60531
+ const key = assertJevAllowed(crypto4);
60532
+ const jev = makeJev(key, crypto4);
60533
+ const result = await jevCategorize(client, crypto4, jev, {
60371
60534
  startDate,
60372
60535
  endDate,
60373
60536
  minConfidence,
@@ -60391,12 +60554,12 @@ function profileVariants(client) {
60391
60554
  function settingsTools(client) {
60392
60555
  const profile = profileVariants(client);
60393
60556
  const household = householdVariants(client);
60394
- const crypto3 = authCryptoVariants(client);
60557
+ const crypto4 = authCryptoVariants(client);
60395
60558
  const get = unionTool({
60396
60559
  name: "get_settings",
60397
60560
  description: "Read settings. profile: your name, email, owner. household: name, currency, locale, owner labels, members. encryption: mode (standard/private) and whether a PIN verifier / recovery key is set.",
60398
60561
  key: "section",
60399
- variants: { profile: profile.get, household: household.get, encryption: crypto3.status }
60562
+ variants: { profile: profile.get, household: household.get, encryption: crypto4.status }
60400
60563
  });
60401
60564
  const update = unionTool({
60402
60565
  name: "update_settings",
@@ -60478,7 +60641,7 @@ async function fetchReconciliationContext(client, accountId, start, end, budget
60478
60641
  }
60479
60642
  var sum2 = (rows) => Math.round(rows.reduce((n2, r2) => n2 + r2.amount, 0));
60480
60643
  async function runStatementImport(deps, args) {
60481
- const { client, crypto: crypto3 } = deps;
60644
+ const { client, crypto: crypto4 } = deps;
60482
60645
  const accounts = (await client.get("/api/accounts")).data ?? [];
60483
60646
  const account = resolveAccount(accounts, args.account);
60484
60647
  const format = pickFormat(account, args.format);
@@ -60497,7 +60660,7 @@ async function runStatementImport(deps, args) {
60497
60660
  }
60498
60661
  const dates = txns.map((t2) => t2.date).sort();
60499
60662
  const existing0 = await fetchReconciliationContext(client, account.id, dates[0], dates[dates.length - 1]);
60500
- const existing = crypto3 ? (await decryptTransactionFields(existing0, crypto3.key)).data : existing0;
60663
+ const existing = crypto4 ? (await decryptTransactionFields(existing0, crypto4.key)).data : existing0;
60501
60664
  const present = existing.map((r2) => ({ ...r2, isDuplicate: false, isIgnored: false }));
60502
60665
  const result = reconcile(present, { ...statement, transactions: txns });
60503
60666
  const fresh = result.unmatched;
@@ -60538,7 +60701,7 @@ async function runStatementImport(deps, args) {
60538
60701
  if (fresh.length === 0)
60539
60702
  return { ...summary, dryRun: false, imported: 0 };
60540
60703
  const savings = account.type !== "credit_card";
60541
- const imported = await importTransactions(client, crypto3, {
60704
+ const imported = await importTransactions(client, crypto4, {
60542
60705
  onProgress: deps.onProgress,
60543
60706
  accountId: account.id,
60544
60707
  source: savings ? "bank_statement" : "cc_statement",
@@ -60551,7 +60714,7 @@ async function runStatementImport(deps, args) {
60551
60714
  referenceNumber: t2.referenceNumber ?? undefined
60552
60715
  }))
60553
60716
  });
60554
- const rescan = await runRescan(client, crypto3);
60717
+ const rescan = await runRescan(client, crypto4);
60555
60718
  return { ...summary, dryRun: false, ...imported, rescan };
60556
60719
  }
60557
60720
 
@@ -60587,16 +60750,16 @@ var statementSource = exports_external.enum([
60587
60750
  "ai_import"
60588
60751
  ]);
60589
60752
  var granularity = exports_external.number().int().min(1).max(5).describe("1=bank, 2=CC, 3=manual, 4=app export, 5=telegram/AI");
60590
- function statementImportTools(client, crypto3) {
60753
+ function statementImportTools(client, crypto4) {
60591
60754
  const pdf = (confirm2) => variant(pdfInput, (args, ctx) => runStatementImport({
60592
60755
  client,
60593
- crypto: crypto3,
60756
+ crypto: crypto4,
60594
60757
  readFile: readFileAsArrayBuffer,
60595
60758
  parsers: realParsers,
60596
60759
  onProgress: progressFor(ctx)
60597
60760
  }, confirm2 ? { ...args, confirm: true } : args));
60598
60761
  const csvParse = variant(exports_external.object({ filePath: exports_external.string() }), async ({ filePath }) => {
60599
- if (crypto3)
60762
+ if (crypto4)
60600
60763
  return CSV_PRIVATE_MODE_ERROR;
60601
60764
  const buf = await readFile2(filePath);
60602
60765
  const form = new FormData;
@@ -60631,7 +60794,7 @@ function statementImportTools(client, crypto3) {
60631
60794
  skipDuplicates: exports_external.boolean().optional(),
60632
60795
  force: exports_external.boolean().optional()
60633
60796
  }), async ({ filePath, mapping, accountId, owner, skipDuplicates, force }) => {
60634
- if (crypto3)
60797
+ if (crypto4)
60635
60798
  return CSV_PRIVATE_MODE_ERROR;
60636
60799
  const csvText = await readFile2(filePath, "utf8");
60637
60800
  const data = await client.post("/api/transactions/import/confirm", {
@@ -60656,14 +60819,14 @@ function statementImportTools(client, crypto3) {
60656
60819
  referenceNumber: exports_external.string().optional()
60657
60820
  }))
60658
60821
  }), async ({ accountId, source, granularity: granularity2, transactions: txns }, ctx) => {
60659
- const data = await importTransactions(client, crypto3, {
60822
+ const data = await importTransactions(client, crypto4, {
60660
60823
  onProgress: progressFor(ctx),
60661
60824
  accountId,
60662
60825
  source,
60663
60826
  granularity: granularity2,
60664
60827
  txns
60665
60828
  });
60666
- const rescan = await runRescan(client, crypto3);
60829
+ const rescan = await runRescan(client, crypto4);
60667
60830
  return { import: data, rescan };
60668
60831
  }),
60669
60832
  json_file: variant(exports_external.object({
@@ -60687,14 +60850,14 @@ function statementImportTools(client, crypto3) {
60687
60850
  } catch (e2) {
60688
60851
  throw new Error(`Failed to read/parse ${filePath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
60689
60852
  }
60690
- const data = await importTransactions(client, crypto3, {
60853
+ const data = await importTransactions(client, crypto4, {
60691
60854
  onProgress: progressFor(ctx),
60692
60855
  accountId,
60693
60856
  source,
60694
60857
  granularity: granularity2,
60695
60858
  txns
60696
60859
  });
60697
- const rescan = await runRescan(client, crypto3);
60860
+ const rescan = await runRescan(client, crypto4);
60698
60861
  return { import: data, count: txns.length, rescan };
60699
60862
  })
60700
60863
  }
@@ -60809,7 +60972,14 @@ function scrubCiphertext(value) {
60809
60972
  }
60810
60973
  async function runTool(tool, args, mcp) {
60811
60974
  const input2 = tool.input ? await tool.input.parseAsync(args) : args;
60812
- const ctx = { tool: tool.name, signal: mcp.mcpReq.signal, mcp };
60975
+ const ctx = {
60976
+ tool: tool.name,
60977
+ signal: mcp.mcpReq.signal,
60978
+ mcp,
60979
+ confirmSupport: { getClientCapabilities: () => {
60980
+ return;
60981
+ } }
60982
+ };
60813
60983
  return tool.handler(input2, ctx);
60814
60984
  }
60815
60985
  function jsonContents(uri, value) {
@@ -60844,7 +61014,7 @@ function registerResources2(server2, tools) {
60844
61014
  // package.json
60845
61015
  var package_default = {
60846
61016
  name: "paisa-mcp",
60847
- version: "0.4.1",
61017
+ version: "0.4.3",
60848
61018
  repository: {
60849
61019
  type: "git",
60850
61020
  url: "git+https://github.com/nimit9/paisa.git",
@@ -60873,14 +61043,13 @@ var package_default = {
60873
61043
  "@modelcontextprotocol/server": "^2.1.0",
60874
61044
  "@nimit9/signet-ai": "^0.1.11",
60875
61045
  "@nimit9/signet-lib": "^0.1.15",
60876
- "@nimit9/signet-server": "^0.2.6",
61046
+ "@nimit9/signet-server": "^0.2.7",
61047
+ "@paisa/crypto": "workspace:*",
60877
61048
  "@paisa/parsers": "workspace:*",
60878
61049
  "@paisa/reconciliation": "workspace:*",
60879
61050
  "@paisa/types": "workspace:*",
60880
- "@scure/bip39": "^2.2.0",
60881
61051
  "@types/bun": "^1.3.14",
60882
61052
  axios: "^1.16.1",
60883
- "hash-wasm": "^4.12.0",
60884
61053
  typescript: "^7.0.2",
60885
61054
  zod: "^4.6.5"
60886
61055
  },
@@ -60899,17 +61068,14 @@ function paisaErrorResult(err) {
60899
61068
  content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }]
60900
61069
  });
60901
61070
  }
60902
- function buildServer(client, crypto3) {
60903
- let initializeCapabilities = () => {
60904
- return;
60905
- };
60906
- const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
60907
- const caps = () => initializeCapabilities();
61071
+ var CONFIRM_KEY2 = randomBytes(32);
61072
+ function buildServer(client, crypto4) {
61073
+ const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto4)).map((t2) => {
60908
61074
  const describe3 = CONFIRMED_TOOLS[t2.name];
60909
61075
  if (describe3)
60910
- return withConfirmation(t2, describe3, caps);
61076
+ return withConfirmation(t2, describe3);
60911
61077
  const count = BULK_CONFIRMED_TOOLS[t2.name];
60912
- return count ? withBulkConfirmation(t2, count, caps) : t2;
61078
+ return count ? withBulkConfirmation(t2, count) : t2;
60913
61079
  }));
60914
61080
  for (const t2 of tools)
60915
61081
  if (t2.input)
@@ -60918,9 +61084,9 @@ function buildServer(client, crypto3) {
60918
61084
  name: "paisa",
60919
61085
  version: VERSION2,
60920
61086
  tools,
60921
- errorResult: paisaErrorResult
61087
+ errorResult: paisaErrorResult,
61088
+ confirm: { key: CONFIRM_KEY2 }
60922
61089
  });
60923
- initializeCapabilities = () => server2.server.getClientCapabilities();
60924
61090
  registerResources2(server2, tools);
60925
61091
  registerPrompts2(server2);
60926
61092
  return server2;
@@ -60947,5 +61113,5 @@ main().catch((err) => {
60947
61113
  process.exit(1);
60948
61114
  });
60949
61115
 
60950
- //# debugId=882108748FF621AF64756E2164756E21
61116
+ //# debugId=1892106D83105DD764756E2164756E21
60951
61117
  //# sourceMappingURL=index.js.map