paisa-mcp 0.4.2 → 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
@@ -53256,25 +53256,8 @@ async function decryptFields(input2, paths, { decrypt, isCiphertext }) {
53256
53256
  return { data, undecrypted };
53257
53257
  }
53258
53258
 
53259
- // src/crypto.ts
53259
+ // ../crypto/src/index.ts
53260
53260
  var import_hash_wasm = __toESM(require_index_umd(), 1);
53261
- var ENCRYPTED_TXN_FIELDS = ["description", "referenceNumber", "notes"];
53262
- var ENCRYPTED_MERCHANT_FIELDS = ["cleanName", "rawId", "notes"];
53263
- async function hmacWithKey(value, key) {
53264
- const rawKey = await crypto.subtle.exportKey("raw", key);
53265
- const hmacKey = await crypto.subtle.importKey("raw", rawKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
53266
- const sig = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(value));
53267
- return btoa(String.fromCharCode(...new Uint8Array(sig)));
53268
- }
53269
- async function encryptPersonCipher(fields, key) {
53270
- const cipher = await encryptField(JSON.stringify(fields), key);
53271
- const searchHash = await hmacWithKey(fields.name.toLowerCase().trim().replace(/\s+/g, " "), key);
53272
- return { cipher, searchHash };
53273
- }
53274
- async function decryptPersonCipher(cipher, key) {
53275
- const json2 = await decryptField(cipher, key);
53276
- return JSON.parse(json2);
53277
- }
53278
53261
  async function deriveKey(userId, pin, salt) {
53279
53262
  const enc = new TextEncoder;
53280
53263
  const baseKey = await crypto.subtle.importKey("raw", enc.encode(userId + pin), "PBKDF2", false, [
@@ -53318,6 +53301,34 @@ async function decryptField(encrypted, key) {
53318
53301
  const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
53319
53302
  return new TextDecoder().decode(plaintext);
53320
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
+ }
53321
53332
  async function encryptObjectFields(obj, fields, key) {
53322
53333
  for (const field of fields) {
53323
53334
  const val = obj[field];
@@ -53375,15 +53386,6 @@ async function decryptPersonCiphers(input2, key) {
53375
53386
  merge3(data);
53376
53387
  return { data, undecrypted };
53377
53388
  }
53378
- var VERIFIER_PLAINTEXT = "paisa-verify-v1";
53379
- async function checkVerifier(key, encryptedVerifier) {
53380
- try {
53381
- const decrypted = await decryptField(encryptedVerifier, key);
53382
- return decrypted === VERIFIER_PLAINTEXT;
53383
- } catch {
53384
- return false;
53385
- }
53386
- }
53387
53389
 
53388
53390
  // src/lib/crypto-init.ts
53389
53391
  async function initCrypto(client, pin, userId) {
@@ -54573,7 +54575,7 @@ async function decryptAnalytics(data, key) {
54573
54575
  ], key);
54574
54576
  return undecrypted.length ? { ...decrypted, undecrypted } : decrypted;
54575
54577
  }
54576
- function analyticsTools(client, crypto3) {
54578
+ function analyticsTools(client, crypto4) {
54577
54579
  return [
54578
54580
  defineTool({
54579
54581
  name: "get_analytics",
@@ -54598,8 +54600,8 @@ function analyticsTools(client, crypto3) {
54598
54600
  data = await client.get(`/api/analytics/merchants/${params.id}`, params.months ? { months: params.months } : undefined);
54599
54601
  break;
54600
54602
  }
54601
- if (crypto3)
54602
- data = await decryptAnalytics(data, crypto3.key);
54603
+ if (crypto4)
54604
+ data = await decryptAnalytics(data, crypto4.key);
54603
54605
  return data;
54604
54606
  }
54605
54607
  })
@@ -54657,7 +54659,7 @@ function budgetTools(client) {
54657
54659
  }
54658
54660
 
54659
54661
  // src/tools/dashboard.ts
54660
- function dashboardTools(client, crypto3) {
54662
+ function dashboardTools(client, crypto4) {
54661
54663
  return [
54662
54664
  defineTool({
54663
54665
  name: "get_dashboard",
@@ -54666,8 +54668,8 @@ function dashboardTools(client, crypto3) {
54666
54668
  output: dashboardOutputSchema,
54667
54669
  handler: async (params) => {
54668
54670
  const data = await client.get("/api/dashboard", params.months ? { months: params.months } : undefined);
54669
- if (crypto3 && Array.isArray(data?.recentTransactions)) {
54670
- 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);
54671
54673
  data.recentTransactions = decrypted;
54672
54674
  if (undecrypted.length)
54673
54675
  return { ...data, undecrypted };
@@ -55231,13 +55233,13 @@ async function categorySlugToId(client) {
55231
55233
  }
55232
55234
  return map2;
55233
55235
  }
55234
- function merchantVariants(client, crypto3) {
55236
+ function merchantVariants(client, crypto4) {
55235
55237
  return {
55236
55238
  list: variant(exports_external.object({ rawId: exports_external.string().optional().describe("merchants: find one by UPI ID or name") }), async ({ rawId }) => {
55237
55239
  const data = await client.get("/api/merchants");
55238
55240
  let undecrypted = [];
55239
- if (crypto3 && data.data) {
55240
- const dec = await decryptMerchantFields(data.data, crypto3.key);
55241
+ if (crypto4 && data.data) {
55242
+ const dec = await decryptMerchantFields(data.data, crypto4.key);
55241
55243
  data.data = dec.data;
55242
55244
  undecrypted = dec.undecrypted;
55243
55245
  }
@@ -55295,7 +55297,7 @@ function merchantTools(client) {
55295
55297
  }
55296
55298
 
55297
55299
  // src/tools/persons.ts
55298
- function personVariants(client, crypto3) {
55300
+ function personVariants(client, crypto4) {
55299
55301
  return {
55300
55302
  upsert: variant(exports_external.object({
55301
55303
  id: exports_external.string().optional().describe("Person UUID — pass to update, omit to create"),
@@ -55305,13 +55307,13 @@ function personVariants(client, crypto3) {
55305
55307
  relationship: exports_external.string().optional(),
55306
55308
  notes: exports_external.string().optional()
55307
55309
  }), async ({ id, ...updates }) => {
55308
- if (crypto3) {
55310
+ if (crypto4) {
55309
55311
  if (id) {
55310
55312
  const existing = await client.get(`/api/persons/${id}`);
55311
55313
  const personData = existing.data;
55312
55314
  let current;
55313
55315
  if (personData.cipher) {
55314
- current = await decryptPersonCipher(personData.cipher, crypto3.key);
55316
+ current = await decryptPersonCipher(personData.cipher, crypto4.key);
55315
55317
  } else {
55316
55318
  current = {
55317
55319
  name: personData.name,
@@ -55328,7 +55330,7 @@ function personVariants(client, crypto3) {
55328
55330
  relationship: updates.relationship ?? current.relationship ?? null,
55329
55331
  notes: updates.notes ?? current.notes ?? null
55330
55332
  };
55331
- const payload2 = await encryptPersonCipher(merged, crypto3.key);
55333
+ const payload2 = await encryptPersonCipher(merged, crypto4.key);
55332
55334
  const data3 = await client.patch(`/api/persons/${id}`, payload2);
55333
55335
  return data3;
55334
55336
  }
@@ -55341,7 +55343,7 @@ function personVariants(client, crypto3) {
55341
55343
  relationship: updates.relationship ?? null,
55342
55344
  notes: updates.notes ?? null
55343
55345
  };
55344
- const payload = await encryptPersonCipher(fields, crypto3.key);
55346
+ const payload = await encryptPersonCipher(fields, crypto4.key);
55345
55347
  const data2 = await client.post("/api/persons", payload);
55346
55348
  return data2;
55347
55349
  }
@@ -55353,8 +55355,8 @@ function personVariants(client, crypto3) {
55353
55355
  }), async ({ id }) => {
55354
55356
  if (id) {
55355
55357
  const data2 = await client.get(`/api/persons/${id}`);
55356
- if (crypto3 && data2.data.cipher) {
55357
- 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);
55358
55360
  data2.data = decrypted;
55359
55361
  if (undecrypted.length)
55360
55362
  return { ...data2, undecrypted };
@@ -55362,8 +55364,8 @@ function personVariants(client, crypto3) {
55362
55364
  return data2;
55363
55365
  }
55364
55366
  const data = await client.get("/api/persons");
55365
- if (crypto3 && data.data) {
55366
- 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);
55367
55369
  data.data = decrypted;
55368
55370
  if (undecrypted.length)
55369
55371
  return { ...data, undecrypted };
@@ -55501,11 +55503,11 @@ function reportTools(client) {
55501
55503
  }
55502
55504
 
55503
55505
  // src/tools/entities.ts
55504
- function entityTools(client, crypto3) {
55506
+ function entityTools(client, crypto4) {
55505
55507
  const category = categoryVariants(client);
55506
55508
  const account = accountVariants(client);
55507
- const person = personVariants(client, crypto3);
55508
- const merchant = merchantVariants(client, crypto3);
55509
+ const person = personVariants(client, crypto4);
55510
+ const merchant = merchantVariants(client, crypto4);
55509
55511
  const debt = debtVariants(client);
55510
55512
  const household = householdVariants(client);
55511
55513
  const goal = goalVariants(client);
@@ -59154,10 +59156,10 @@ function toTxnRow(r2) {
59154
59156
  isDuplicate: !!r2.isDuplicate
59155
59157
  };
59156
59158
  }
59157
- async function fetchAllRows(client, crypto3, params = {}, maxPages = 100) {
59158
- 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;
59159
59161
  }
59160
- async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59162
+ async function fetchAllRowsPaged(client, crypto4, params = {}, maxPages = 100) {
59161
59163
  const out = [];
59162
59164
  let cursor;
59163
59165
  let truncated = false;
@@ -59167,7 +59169,7 @@ async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59167
59169
  q2.cursor = cursor;
59168
59170
  const res = await client.get("/api/transactions", q2);
59169
59171
  const batch0 = res.data ?? [];
59170
- const batch = crypto3 ? (await decryptTransactionFields(batch0, crypto3.key)).data : batch0;
59172
+ const batch = crypto4 ? (await decryptTransactionFields(batch0, crypto4.key)).data : batch0;
59171
59173
  out.push(...batch.map(toTxnRow));
59172
59174
  if (!res.hasMore || !res.nextCursor || batch.length === 0 || res.nextCursor === cursor)
59173
59175
  break;
@@ -59216,14 +59218,14 @@ function normalizePersonName(name) {
59216
59218
  function normalizeUpiHandle(handle) {
59217
59219
  return handle.toLowerCase().trim().split("@")[0];
59218
59220
  }
59219
- async function loadDecryptedPersons(client, crypto3) {
59221
+ async function loadDecryptedPersons(client, crypto4) {
59220
59222
  const res = await client.get("/api/persons");
59221
59223
  const persons = [];
59222
59224
  for (const p2 of res.data ?? []) {
59223
59225
  let name = p2.name ?? "";
59224
- if (crypto3 && p2.cipher) {
59226
+ if (crypto4 && p2.cipher) {
59225
59227
  try {
59226
- name = (await decryptPersonCipher(p2.cipher, crypto3.key)).name;
59228
+ name = (await decryptPersonCipher(p2.cipher, crypto4.key)).name;
59227
59229
  } catch {}
59228
59230
  }
59229
59231
  if (name)
@@ -59264,8 +59266,8 @@ function isPersonLikeNarration(description) {
59264
59266
  const { name, paymentMethod } = parseNarration(description);
59265
59267
  return classifyEntity({ paymentMethod, extractedName: name }) !== "merchant";
59266
59268
  }
59267
- async function backfillPersons(client, crypto3, opts = {}) {
59268
- const existing = await loadDecryptedPersons(client, crypto3);
59269
+ async function backfillPersons(client, crypto4, opts = {}) {
59270
+ const existing = await loadDecryptedPersons(client, crypto4);
59269
59271
  const personIdByNorm = new Map;
59270
59272
  const personIdByHandle = new Map;
59271
59273
  for (const p2 of existing) {
@@ -59273,7 +59275,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59273
59275
  for (const h2 of p2.upiHandles)
59274
59276
  personIdByHandle.set(normalizeUpiHandle(h2), p2.id);
59275
59277
  }
59276
- const { rows, truncated } = await fetchAllRowsPaged(client, crypto3, { unresolved: "true" });
59278
+ const { rows, truncated } = await fetchAllRowsPaged(client, crypto4, { unresolved: "true" });
59277
59279
  const candidates = rows.filter((r2) => !r2.merchantId && !r2.personId && !r2.isTransfer && !r2.isIgnored);
59278
59280
  const directLinks = [];
59279
59281
  const groups = new Map;
@@ -59365,7 +59367,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59365
59367
  relationship: null,
59366
59368
  notes: null
59367
59369
  };
59368
- 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 };
59369
59371
  }));
59370
59372
  const res = await client.post("/api/persons/bulk", {
59371
59373
  persons: bodies
@@ -59500,20 +59502,20 @@ function groupRows(rows) {
59500
59502
  function isCategorizable(r2) {
59501
59503
  return !r2.categoryId && !r2.isTransfer && !r2.isIgnored && !r2.isDuplicate;
59502
59504
  }
59503
- async function fetchUncategorized(client, crypto3, range = {}) {
59505
+ async function fetchUncategorized(client, crypto4, range = {}) {
59504
59506
  const params = { uncategorized: "true" };
59505
59507
  if (range.startDate)
59506
59508
  params.startDate = range.startDate;
59507
59509
  if (range.endDate)
59508
59510
  params.endDate = range.endDate;
59509
- return (await fetchAllRows(client, crypto3, params)).filter(isCategorizable);
59511
+ return (await fetchAllRows(client, crypto4, params)).filter(isCategorizable);
59510
59512
  }
59511
59513
  var MERCHANT_CREATE_CONCURRENCY = 5;
59512
59514
  var titleCase = (s2) => s2.replace(/\b[a-z]/g, (c2) => c2.toUpperCase());
59513
- async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59515
+ async function applyGroupAssignments(client, crypto4, assignments, opts = {}) {
59514
59516
  const catIndex = await loadCategoryIndex(client);
59515
59517
  const resolved = assignments.map((a2) => ({ a: a2, cat: requireCategory(catIndex, a2.categorySlug) }));
59516
- const groups = groupRows(await fetchUncategorized(client, crypto3, opts));
59518
+ const groups = groupRows(await fetchUncategorized(client, crypto4, opts));
59517
59519
  const result = { applied: [], notFound: [], rulesSaved: 0, unlearnable: [] };
59518
59520
  const plan = [];
59519
59521
  const claimed = new Set;
@@ -59568,7 +59570,7 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59568
59570
  const { group, learn, confirmMerchant } = p2;
59569
59571
  if (!learn || group.pattern === UNPARSED) {
59570
59572
  directPlans.push({ p: p2, reason: learn ? UNPARSED : undefined });
59571
- } else if (crypto3 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59573
+ } else if (crypto4 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59572
59574
  directPlans.push({ p: p2, reason: `${group.pattern} (private mode: person-like)` });
59573
59575
  } else if (taught.has(group.pattern) && taught.get(group.pattern) !== p2.categoryId) {
59574
59576
  directPlans.push({ p: p2, reason: `${group.pattern} (conflicting category for same pattern)` });
@@ -59666,12 +59668,12 @@ function flagBatchDuplicates(txns) {
59666
59668
  return false;
59667
59669
  });
59668
59670
  }
59669
- async function importTransactions(client, crypto3, opts) {
59671
+ async function importTransactions(client, crypto4, opts) {
59670
59672
  let txns = opts.txns;
59671
59673
  let duplicatesSkipped = 0;
59672
59674
  if (txns.length > 0) {
59673
59675
  const dates = txns.map((t2) => t2.date).sort();
59674
- const existing = await fetchAllRows(client, crypto3, {
59676
+ const existing = await fetchAllRows(client, crypto4, {
59675
59677
  startDate: dates[0],
59676
59678
  endDate: dates[dates.length - 1]
59677
59679
  });
@@ -59687,8 +59689,8 @@ async function importTransactions(client, crypto3, opts) {
59687
59689
  const base = { ...t2, isDuplicate: dupFlags[i2] };
59688
59690
  return enrichedData ? { ...base, enrichedData } : base;
59689
59691
  });
59690
- if (crypto3)
59691
- await encryptTransactionFields(rows, crypto3.key);
59692
+ if (crypto4)
59693
+ await encryptTransactionFields(rows, crypto4.key);
59692
59694
  const CHUNK = 200;
59693
59695
  let imported = 0;
59694
59696
  for (let i2 = 0;i2 < rows.length; i2 += CHUNK) {
@@ -59703,19 +59705,19 @@ async function importTransactions(client, crypto3, opts) {
59703
59705
  }
59704
59706
  return { imported, duplicatesSkipped, duplicatesFlagged };
59705
59707
  }
59706
- async function runRescan(client, crypto3) {
59708
+ async function runRescan(client, crypto4) {
59707
59709
  let bag = structuredClone(EMPTY_BAG);
59708
59710
  try {
59709
59711
  bag = (await loadBag(client)).bag;
59710
59712
  } catch {}
59711
59713
  const merchantsRes = await client.get("/api/merchants");
59712
59714
  const allMerchants0 = merchantsRes.data ?? [];
59713
- const allMerchants = crypto3 ? (await decryptMerchantFields(allMerchants0, crypto3.key)).data : allMerchants0;
59715
+ const allMerchants = crypto4 ? (await decryptMerchantFields(allMerchants0, crypto4.key)).data : allMerchants0;
59714
59716
  const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59715
59717
  const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59716
59718
  const merchantCategoryById = new Map(allMerchants.filter((m2) => m2.categoryId).map((m2) => [m2.id, m2.categoryId]));
59717
- const persons = await loadDecryptedPersons(client, crypto3);
59718
- const allRows = await fetchAllRows(client, crypto3, { unresolved: "true" });
59719
+ const persons = await loadDecryptedPersons(client, crypto4);
59720
+ const allRows = await fetchAllRows(client, crypto4, { unresolved: "true" });
59719
59721
  const categoryByTxn = new Map(allRows.map((r2) => [r2.id, r2.categoryId]));
59720
59722
  const plan = autoCategorizeFromDescriptions({
59721
59723
  rows: allRows,
@@ -59724,8 +59726,17 @@ async function runRescan(client, crypto3) {
59724
59726
  localMerchants,
59725
59727
  persons
59726
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));
59727
59738
  let categorized = 0;
59728
- const links = plan.suggestions.flatMap((s2) => {
59739
+ const links = live.flatMap((s2) => {
59729
59740
  const categoryId = s2.categoryId ?? (s2.entity.type === "merchant" ? merchantCategoryById.get(s2.entity.merchantId) ?? null : null);
59730
59741
  return s2.transactionIds.map((txId) => {
59731
59742
  const applyCategory = categoryId && !categoryByTxn.get(txId);
@@ -59745,7 +59756,7 @@ var TRUNCATED_WARNING = "TRUNCATED: the API capped this result, so it is INCOMPL
59745
59756
  function withTruncationWarning(data) {
59746
59757
  return data.truncated ? { warning: TRUNCATED_WARNING, ...data } : data;
59747
59758
  }
59748
- function transactionTools(client, crypto3) {
59759
+ function transactionTools(client, crypto4) {
59749
59760
  return [
59750
59761
  defineTool({
59751
59762
  name: "get_transactions",
@@ -59792,7 +59803,7 @@ function transactionTools(client, crypto3) {
59792
59803
  apiParams.categoryId = requireCategory(await loadCategoryIndex(client), categorySlug).id;
59793
59804
  }
59794
59805
  if (personName && !apiParams.personId) {
59795
- const persons = await loadDecryptedPersons(client, crypto3);
59806
+ const persons = await loadDecryptedPersons(client, crypto4);
59796
59807
  const match = resolvePersonByName(persons, personName);
59797
59808
  const err = (error62) => ({ error: error62 });
59798
59809
  if (!match) {
@@ -59809,14 +59820,14 @@ function transactionTools(client, crypto3) {
59809
59820
  const data = await client.get("/api/transactions", Object.fromEntries(Object.entries(apiParams).filter(([, v2]) => v2 !== undefined).map(([k2, v2]) => [k2, String(v2)])));
59810
59821
  let rows = data.data ?? [];
59811
59822
  let undecrypted = [];
59812
- if (crypto3) {
59813
- const dec = await decryptTransactionFields(rows, crypto3.key);
59823
+ if (crypto4) {
59824
+ const dec = await decryptTransactionFields(rows, crypto4.key);
59814
59825
  rows = dec.data;
59815
59826
  undecrypted = dec.undecrypted;
59816
59827
  }
59817
59828
  const personNames = new Map;
59818
59829
  if (rows.some((r2) => r2.personId)) {
59819
- for (const p2 of await loadDecryptedPersons(client, crypto3))
59830
+ for (const p2 of await loadDecryptedPersons(client, crypto4))
59820
59831
  personNames.set(p2.id, p2.name);
59821
59832
  }
59822
59833
  if (full) {
@@ -59847,7 +59858,7 @@ function transactionTools(client, crypto3) {
59847
59858
  limit: exports_external.number().optional().describe("Max groups (default 40, max 100)")
59848
59859
  }),
59849
59860
  handler: async ({ startDate, endDate, minTotal, limit }) => {
59850
- const rows = await fetchUncategorized(client, crypto3, { startDate, endDate });
59861
+ const rows = await fetchUncategorized(client, crypto4, { startDate, endDate });
59851
59862
  const all2 = groupRows(rows);
59852
59863
  const shown = all2.filter((g2) => g2.total >= (minTotal ?? 0)).slice(0, Math.min(Math.max(limit ?? 40, 1), 100));
59853
59864
  const shownRows = shown.reduce((n2, g2) => n2 + g2.rows.length, 0);
@@ -59884,7 +59895,7 @@ function transactionTools(client, crypto3) {
59884
59895
  handler: (args, ctx) => {
59885
59896
  const { assignments, startDate, endDate, dryRun } = args;
59886
59897
  const { expectedPlanHash } = args;
59887
- return applyGroupAssignments(client, crypto3, assignments, {
59898
+ return applyGroupAssignments(client, crypto4, assignments, {
59888
59899
  dryRun,
59889
59900
  startDate,
59890
59901
  endDate,
@@ -59897,7 +59908,7 @@ function transactionTools(client, crypto3) {
59897
59908
  name: "backfill_persons",
59898
59909
  description: "Create persons from UPI/IMPS counterparties in unlinked transactions (deduped by name) and link those transactions to them. Idempotent. dryRun previews.",
59899
59910
  input: exports_external.object({ dryRun: exports_external.boolean().optional() }),
59900
- handler: (args, ctx) => backfillPersons(client, crypto3, {
59911
+ handler: (args, ctx) => backfillPersons(client, crypto4, {
59901
59912
  onProgress: progressFor(ctx),
59902
59913
  dryRun: args.dryRun,
59903
59914
  expectedPlanHash: args.expectedPlanHash
@@ -59919,8 +59930,8 @@ function transactionTools(client, crypto3) {
59919
59930
  }),
59920
59931
  handler: async ({ id, ...updates }) => {
59921
59932
  const body = { ...updates };
59922
- if (crypto3 && body.notes)
59923
- body.notes = await encryptField(body.notes, crypto3.key);
59933
+ if (crypto4 && body.notes)
59934
+ body.notes = await encryptField(body.notes, crypto4.key);
59924
59935
  if (body.categorySlug) {
59925
59936
  const cat = requireCategory(await loadCategoryIndex(client), body.categorySlug);
59926
59937
  body.categoryId = cat.id;
@@ -59969,8 +59980,8 @@ function transactionTools(client, crypto3) {
59969
59980
  }
59970
59981
  if (action === "list") {
59971
59982
  const data2 = await client.get("/api/transactions/duplicates");
59972
- if (crypto3 && data2.data) {
59973
- 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);
59974
59985
  data2.data = decrypted;
59975
59986
  return withTruncationWarning(undecrypted.length ? { ...data2, undecrypted } : data2);
59976
59987
  }
@@ -59993,8 +60004,8 @@ function transactionTools(client, crypto3) {
59993
60004
  }),
59994
60005
  handler: async ({ accountId, startDate, endDate }) => {
59995
60006
  const data = await client.get("/api/transactions/reconciliation-context", { accountId, startDate, endDate });
59996
- if (crypto3 && data.data) {
59997
- 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);
59998
60009
  data.data = decrypted;
59999
60010
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60000
60011
  }
@@ -60081,7 +60092,7 @@ function transactionTools(client, crypto3) {
60081
60092
  name: "rescan_transactions",
60082
60093
  description: "Apply saved learning rules (merchant aliases, category rules) to uncategorized transactions, like the web app's auto-categorize.",
60083
60094
  input: exports_external.object({}),
60084
- handler: () => runRescan(client, crypto3)
60095
+ handler: () => runRescan(client, crypto4)
60085
60096
  }),
60086
60097
  defineTool({
60087
60098
  name: "get_settlement_candidates",
@@ -60089,8 +60100,8 @@ function transactionTools(client, crypto3) {
60089
60100
  input: exports_external.object({ ccAccountId: exports_external.string().uuid() }),
60090
60101
  handler: async ({ ccAccountId }) => {
60091
60102
  const data = await client.get("/api/transactions/settlement-candidates", { ccAccountId });
60092
- if (crypto3 && data.data) {
60093
- 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);
60094
60105
  data.data = decrypted;
60095
60106
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60096
60107
  }
@@ -60107,8 +60118,8 @@ function transactionTools(client, crypto3) {
60107
60118
  if (!row)
60108
60119
  return res;
60109
60120
  let undecrypted = [];
60110
- if (crypto3) {
60111
- const dec = await decryptTransactionFields([row], crypto3.key);
60121
+ if (crypto4) {
60122
+ const dec = await decryptTransactionFields([row], crypto4.key);
60112
60123
  row = dec.data[0];
60113
60124
  undecrypted = dec.undecrypted;
60114
60125
  }
@@ -60134,7 +60145,7 @@ function transactionTools(client, crypto3) {
60134
60145
  }
60135
60146
 
60136
60147
  // src/tools/export.ts
60137
- function exportTools(client, crypto3) {
60148
+ function exportTools(client, crypto4) {
60138
60149
  return [
60139
60150
  defineTool({
60140
60151
  name: "export_transactions",
@@ -60151,8 +60162,8 @@ function exportTools(client, crypto3) {
60151
60162
  ...Object.fromEntries(Object.entries(params).filter(([, v2]) => v2 !== undefined)),
60152
60163
  format: "json"
60153
60164
  });
60154
- if (crypto3 && Array.isArray(data?.transactions)) {
60155
- 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);
60156
60167
  data.transactions = decrypted;
60157
60168
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60158
60169
  }
@@ -60394,28 +60405,28 @@ var DEFAULT_MIN_CONFIDENCE = 0.95;
60394
60405
  var TIMEOUT_MS = 8000;
60395
60406
  var CONCURRENCY = 8;
60396
60407
  var MIN_LEARN_PATTERN_LEN = 5;
60397
- function assertPrivateModeAllowed(crypto3, env2 = process.env) {
60398
- if (crypto3 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60408
+ function assertPrivateModeAllowed(crypto4, env2 = process.env) {
60409
+ if (crypto4 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60399
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.");
60400
60411
  }
60401
60412
  }
60402
- function assertJevAllowed(crypto3, env2 = process.env) {
60403
- assertPrivateModeAllowed(crypto3, env2);
60413
+ function assertJevAllowed(crypto4, env2 = process.env) {
60414
+ assertPrivateModeAllowed(crypto4, env2);
60404
60415
  const key = env2.JEV_API_KEY;
60405
60416
  if (!key)
60406
60417
  throw new Error("JEV_API_KEY is not set — Jev categorization is off.");
60407
60418
  return key;
60408
60419
  }
60409
- function jevAllowSensitive(crypto3, env2) {
60410
- return crypto3 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60420
+ function jevAllowSensitive(crypto4, env2) {
60421
+ return crypto4 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60411
60422
  }
60412
- function makeJev(apiKey, crypto3, env2 = process.env, fetchImpl) {
60423
+ function makeJev(apiKey, crypto4, env2 = process.env, fetchImpl) {
60413
60424
  return createJev({
60414
60425
  apiKey,
60415
60426
  model: JEV_MODEL,
60416
60427
  minConfidence: DEFAULT_MIN_CONFIDENCE,
60417
60428
  timeoutMs: TIMEOUT_MS,
60418
- allowSensitive: jevAllowSensitive(crypto3, env2),
60429
+ allowSensitive: jevAllowSensitive(crypto4, env2),
60419
60430
  ...fetchImpl ? { fetch: fetchImpl } : {}
60420
60431
  });
60421
60432
  }
@@ -60435,10 +60446,10 @@ function groupState(type, sample) {
60435
60446
  const dir = type === "credit" ? "Money received" : "Money paid out";
60436
60447
  return `${dir}. Bank narration: "${sample.slice(0, 80)}"`;
60437
60448
  }
60438
- async function jevCategorize(client, crypto3, jev, opts = {}) {
60439
- assertPrivateModeAllowed(crypto3, opts.env);
60449
+ async function jevCategorize(client, crypto4, jev, opts = {}) {
60450
+ assertPrivateModeAllowed(crypto4, opts.env);
60440
60451
  const index = await loadCategoryIndex(client);
60441
- const rows = await fetchUncategorized(client, crypto3, opts);
60452
+ const rows = await fetchUncategorized(client, crypto4, opts);
60442
60453
  const groups = groupRows(rows);
60443
60454
  const unparsed = groups.filter((g2) => g2.pattern === UNPARSED).length;
60444
60455
  const candidates = groups.filter((g2) => g2.pattern !== UNPARSED);
@@ -60446,7 +60457,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60446
60457
  const suggestions = await mapLimit(candidates, CONCURRENCY, async (g2) => {
60447
60458
  const options = optionsFor(g2.type, index);
60448
60459
  const sample = g2.rows[0].description;
60449
- const text = crypto3 ? g2.pattern : sample;
60460
+ const text = crypto4 ? g2.pattern : sample;
60450
60461
  const d2 = await jev.choice({
60451
60462
  question: "Which spending category best fits this bank transaction?",
60452
60463
  options,
@@ -60488,7 +60499,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60488
60499
  }
60489
60500
  }
60490
60501
  review.sort((a2, b2) => b2.total - a2.total);
60491
- 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 });
60492
60503
  const conf = new Map(accepted.map((a2) => [`${a2.pattern}|${a2.type}`, a2.confidence]));
60493
60504
  return {
60494
60505
  dryRun: !!opts.dryRun,
@@ -60505,7 +60516,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60505
60516
  }
60506
60517
 
60507
60518
  // src/tools/jev.ts
60508
- function jevTools(client, crypto3) {
60519
+ function jevTools(client, crypto4) {
60509
60520
  return [
60510
60521
  defineTool({
60511
60522
  name: "auto_categorize_jev",
@@ -60517,9 +60528,9 @@ function jevTools(client, crypto3) {
60517
60528
  dryRun: exports_external.boolean().optional()
60518
60529
  }),
60519
60530
  handler: async ({ startDate, endDate, minConfidence, dryRun }) => {
60520
- const key = assertJevAllowed(crypto3);
60521
- const jev = makeJev(key, crypto3);
60522
- 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, {
60523
60534
  startDate,
60524
60535
  endDate,
60525
60536
  minConfidence,
@@ -60543,12 +60554,12 @@ function profileVariants(client) {
60543
60554
  function settingsTools(client) {
60544
60555
  const profile = profileVariants(client);
60545
60556
  const household = householdVariants(client);
60546
- const crypto3 = authCryptoVariants(client);
60557
+ const crypto4 = authCryptoVariants(client);
60547
60558
  const get = unionTool({
60548
60559
  name: "get_settings",
60549
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.",
60550
60561
  key: "section",
60551
- variants: { profile: profile.get, household: household.get, encryption: crypto3.status }
60562
+ variants: { profile: profile.get, household: household.get, encryption: crypto4.status }
60552
60563
  });
60553
60564
  const update = unionTool({
60554
60565
  name: "update_settings",
@@ -60630,7 +60641,7 @@ async function fetchReconciliationContext(client, accountId, start, end, budget
60630
60641
  }
60631
60642
  var sum2 = (rows) => Math.round(rows.reduce((n2, r2) => n2 + r2.amount, 0));
60632
60643
  async function runStatementImport(deps, args) {
60633
- const { client, crypto: crypto3 } = deps;
60644
+ const { client, crypto: crypto4 } = deps;
60634
60645
  const accounts = (await client.get("/api/accounts")).data ?? [];
60635
60646
  const account = resolveAccount(accounts, args.account);
60636
60647
  const format = pickFormat(account, args.format);
@@ -60649,7 +60660,7 @@ async function runStatementImport(deps, args) {
60649
60660
  }
60650
60661
  const dates = txns.map((t2) => t2.date).sort();
60651
60662
  const existing0 = await fetchReconciliationContext(client, account.id, dates[0], dates[dates.length - 1]);
60652
- const existing = crypto3 ? (await decryptTransactionFields(existing0, crypto3.key)).data : existing0;
60663
+ const existing = crypto4 ? (await decryptTransactionFields(existing0, crypto4.key)).data : existing0;
60653
60664
  const present = existing.map((r2) => ({ ...r2, isDuplicate: false, isIgnored: false }));
60654
60665
  const result = reconcile(present, { ...statement, transactions: txns });
60655
60666
  const fresh = result.unmatched;
@@ -60690,7 +60701,7 @@ async function runStatementImport(deps, args) {
60690
60701
  if (fresh.length === 0)
60691
60702
  return { ...summary, dryRun: false, imported: 0 };
60692
60703
  const savings = account.type !== "credit_card";
60693
- const imported = await importTransactions(client, crypto3, {
60704
+ const imported = await importTransactions(client, crypto4, {
60694
60705
  onProgress: deps.onProgress,
60695
60706
  accountId: account.id,
60696
60707
  source: savings ? "bank_statement" : "cc_statement",
@@ -60703,7 +60714,7 @@ async function runStatementImport(deps, args) {
60703
60714
  referenceNumber: t2.referenceNumber ?? undefined
60704
60715
  }))
60705
60716
  });
60706
- const rescan = await runRescan(client, crypto3);
60717
+ const rescan = await runRescan(client, crypto4);
60707
60718
  return { ...summary, dryRun: false, ...imported, rescan };
60708
60719
  }
60709
60720
 
@@ -60739,16 +60750,16 @@ var statementSource = exports_external.enum([
60739
60750
  "ai_import"
60740
60751
  ]);
60741
60752
  var granularity = exports_external.number().int().min(1).max(5).describe("1=bank, 2=CC, 3=manual, 4=app export, 5=telegram/AI");
60742
- function statementImportTools(client, crypto3) {
60753
+ function statementImportTools(client, crypto4) {
60743
60754
  const pdf = (confirm2) => variant(pdfInput, (args, ctx) => runStatementImport({
60744
60755
  client,
60745
- crypto: crypto3,
60756
+ crypto: crypto4,
60746
60757
  readFile: readFileAsArrayBuffer,
60747
60758
  parsers: realParsers,
60748
60759
  onProgress: progressFor(ctx)
60749
60760
  }, confirm2 ? { ...args, confirm: true } : args));
60750
60761
  const csvParse = variant(exports_external.object({ filePath: exports_external.string() }), async ({ filePath }) => {
60751
- if (crypto3)
60762
+ if (crypto4)
60752
60763
  return CSV_PRIVATE_MODE_ERROR;
60753
60764
  const buf = await readFile2(filePath);
60754
60765
  const form = new FormData;
@@ -60783,7 +60794,7 @@ function statementImportTools(client, crypto3) {
60783
60794
  skipDuplicates: exports_external.boolean().optional(),
60784
60795
  force: exports_external.boolean().optional()
60785
60796
  }), async ({ filePath, mapping, accountId, owner, skipDuplicates, force }) => {
60786
- if (crypto3)
60797
+ if (crypto4)
60787
60798
  return CSV_PRIVATE_MODE_ERROR;
60788
60799
  const csvText = await readFile2(filePath, "utf8");
60789
60800
  const data = await client.post("/api/transactions/import/confirm", {
@@ -60808,14 +60819,14 @@ function statementImportTools(client, crypto3) {
60808
60819
  referenceNumber: exports_external.string().optional()
60809
60820
  }))
60810
60821
  }), async ({ accountId, source, granularity: granularity2, transactions: txns }, ctx) => {
60811
- const data = await importTransactions(client, crypto3, {
60822
+ const data = await importTransactions(client, crypto4, {
60812
60823
  onProgress: progressFor(ctx),
60813
60824
  accountId,
60814
60825
  source,
60815
60826
  granularity: granularity2,
60816
60827
  txns
60817
60828
  });
60818
- const rescan = await runRescan(client, crypto3);
60829
+ const rescan = await runRescan(client, crypto4);
60819
60830
  return { import: data, rescan };
60820
60831
  }),
60821
60832
  json_file: variant(exports_external.object({
@@ -60839,14 +60850,14 @@ function statementImportTools(client, crypto3) {
60839
60850
  } catch (e2) {
60840
60851
  throw new Error(`Failed to read/parse ${filePath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
60841
60852
  }
60842
- const data = await importTransactions(client, crypto3, {
60853
+ const data = await importTransactions(client, crypto4, {
60843
60854
  onProgress: progressFor(ctx),
60844
60855
  accountId,
60845
60856
  source,
60846
60857
  granularity: granularity2,
60847
60858
  txns
60848
60859
  });
60849
- const rescan = await runRescan(client, crypto3);
60860
+ const rescan = await runRescan(client, crypto4);
60850
60861
  return { import: data, count: txns.length, rescan };
60851
60862
  })
60852
60863
  }
@@ -61003,7 +61014,7 @@ function registerResources2(server2, tools) {
61003
61014
  // package.json
61004
61015
  var package_default = {
61005
61016
  name: "paisa-mcp",
61006
- version: "0.4.2",
61017
+ version: "0.4.3",
61007
61018
  repository: {
61008
61019
  type: "git",
61009
61020
  url: "git+https://github.com/nimit9/paisa.git",
@@ -61033,13 +61044,12 @@ var package_default = {
61033
61044
  "@nimit9/signet-ai": "^0.1.11",
61034
61045
  "@nimit9/signet-lib": "^0.1.15",
61035
61046
  "@nimit9/signet-server": "^0.2.7",
61047
+ "@paisa/crypto": "workspace:*",
61036
61048
  "@paisa/parsers": "workspace:*",
61037
61049
  "@paisa/reconciliation": "workspace:*",
61038
61050
  "@paisa/types": "workspace:*",
61039
- "@scure/bip39": "^2.2.0",
61040
61051
  "@types/bun": "^1.3.14",
61041
61052
  axios: "^1.16.1",
61042
- "hash-wasm": "^4.12.0",
61043
61053
  typescript: "^7.0.2",
61044
61054
  zod: "^4.6.5"
61045
61055
  },
@@ -61059,8 +61069,8 @@ function paisaErrorResult(err) {
61059
61069
  });
61060
61070
  }
61061
61071
  var CONFIRM_KEY2 = randomBytes(32);
61062
- function buildServer(client, crypto3) {
61063
- const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
61072
+ function buildServer(client, crypto4) {
61073
+ const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto4)).map((t2) => {
61064
61074
  const describe3 = CONFIRMED_TOOLS[t2.name];
61065
61075
  if (describe3)
61066
61076
  return withConfirmation(t2, describe3);
@@ -61103,5 +61113,5 @@ main().catch((err) => {
61103
61113
  process.exit(1);
61104
61114
  });
61105
61115
 
61106
- //# debugId=24F552E300AC132164756E2164756E21
61116
+ //# debugId=1892106D83105DD764756E2164756E21
61107
61117
  //# sourceMappingURL=index.js.map