paisa-mcp 0.4.2 → 0.4.4

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
@@ -53113,6 +53113,9 @@ function retryAfterMs(err) {
53113
53113
  const ms = Number.isFinite(secs) ? secs * 1000 : Date.parse(raw) - Date.now();
53114
53114
  return Number.isFinite(ms) && ms >= 0 ? Math.min(ms, 1e4) : undefined;
53115
53115
  }
53116
+ function apiPath(strings, ...values) {
53117
+ return strings.reduce((out, s, i) => out + s + (i < values.length ? encodeURIComponent(String(values[i])) : ""), "");
53118
+ }
53116
53119
 
53117
53120
  class PaisaApiClient {
53118
53121
  retryBaseMs;
@@ -53256,25 +53259,8 @@ async function decryptFields(input2, paths, { decrypt, isCiphertext }) {
53256
53259
  return { data, undecrypted };
53257
53260
  }
53258
53261
 
53259
- // src/crypto.ts
53262
+ // ../crypto/src/index.ts
53260
53263
  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
53264
  async function deriveKey(userId, pin, salt) {
53279
53265
  const enc = new TextEncoder;
53280
53266
  const baseKey = await crypto.subtle.importKey("raw", enc.encode(userId + pin), "PBKDF2", false, [
@@ -53318,6 +53304,34 @@ async function decryptField(encrypted, key) {
53318
53304
  const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
53319
53305
  return new TextDecoder().decode(plaintext);
53320
53306
  }
53307
+ var VERIFIER_PLAINTEXT = "paisa-verify-v1";
53308
+ async function checkVerifier(key, encryptedVerifier) {
53309
+ try {
53310
+ const decrypted = await decryptField(encryptedVerifier, key);
53311
+ return decrypted === VERIFIER_PLAINTEXT;
53312
+ } catch {
53313
+ return false;
53314
+ }
53315
+ }
53316
+
53317
+ // src/crypto.ts
53318
+ var ENCRYPTED_TXN_FIELDS = ["description", "referenceNumber", "notes"];
53319
+ var ENCRYPTED_MERCHANT_FIELDS = ["cleanName", "rawId", "notes"];
53320
+ async function hmacWithKey(value, key) {
53321
+ const rawKey = await crypto.subtle.exportKey("raw", key);
53322
+ const hmacKey = await crypto.subtle.importKey("raw", rawKey, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
53323
+ const sig = await crypto.subtle.sign("HMAC", hmacKey, new TextEncoder().encode(value));
53324
+ return btoa(String.fromCharCode(...new Uint8Array(sig)));
53325
+ }
53326
+ async function encryptPersonCipher(fields, key) {
53327
+ const cipher = await encryptField(JSON.stringify(fields), key);
53328
+ const searchHash = await hmacWithKey(fields.name.toLowerCase().trim().replace(/\s+/g, " "), key);
53329
+ return { cipher, searchHash };
53330
+ }
53331
+ async function decryptPersonCipher(cipher, key) {
53332
+ const json2 = await decryptField(cipher, key);
53333
+ return JSON.parse(json2);
53334
+ }
53321
53335
  async function encryptObjectFields(obj, fields, key) {
53322
53336
  for (const field of fields) {
53323
53337
  const val = obj[field];
@@ -53375,15 +53389,6 @@ async function decryptPersonCiphers(input2, key) {
53375
53389
  merge3(data);
53376
53390
  return { data, undecrypted };
53377
53391
  }
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
53392
 
53388
53393
  // src/lib/crypto-init.ts
53389
53394
  async function initCrypto(client, pin, userId) {
@@ -54357,7 +54362,7 @@ function alertRuleVariants(client) {
54357
54362
  type: exports_external.string().describe("Rule type (e.g. budget_threshold, spending_spike)"),
54358
54363
  enabled: exports_external.boolean().optional(),
54359
54364
  threshold: exports_external.string().regex(/^\d{1,4}(\.\d{1,2})?$/).optional().nullable().describe('Decimal string, e.g. "80.50"')
54360
- }), ({ type, enabled, threshold }) => client.patch(`/api/alerts/rules/${type}`, {
54365
+ }), ({ type, enabled, threshold }) => client.patch(apiPath`/api/alerts/rules/${type}`, {
54361
54366
  ...enabled !== undefined ? { enabled } : {},
54362
54367
  ...threshold !== undefined ? { threshold } : {}
54363
54368
  }))
@@ -54378,14 +54383,14 @@ function alertTools(client) {
54378
54383
  key: "action",
54379
54384
  describe: { id: "Alert UUID", all: "", type: "" },
54380
54385
  variants: {
54381
- dismiss: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.patch(`/api/alerts/${id}/dismiss`)),
54386
+ dismiss: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.patch(apiPath`/api/alerts/${id}/dismiss`)),
54382
54387
  read: variant(exports_external.object({
54383
54388
  id: exports_external.string().uuid().optional(),
54384
54389
  all: exports_external.boolean().optional().describe("read: mark every alert read")
54385
54390
  }), async ({ id, all: all2 }) => {
54386
54391
  if (!all2 && !id)
54387
54392
  throw new Error("Provide an alert id, or all:true");
54388
- const data = all2 ? await client.post("/api/alerts/read-all") : await client.patch(`/api/alerts/${id}/read`);
54393
+ const data = all2 ? await client.post("/api/alerts/read-all") : await client.patch(apiPath`/api/alerts/${id}/read`);
54389
54394
  return data;
54390
54395
  }),
54391
54396
  list_rules: rules.list,
@@ -54573,7 +54578,7 @@ async function decryptAnalytics(data, key) {
54573
54578
  ], key);
54574
54579
  return undecrypted.length ? { ...decrypted, undecrypted } : decrypted;
54575
54580
  }
54576
- function analyticsTools(client, crypto3) {
54581
+ function analyticsTools(client, crypto4) {
54577
54582
  return [
54578
54583
  defineTool({
54579
54584
  name: "get_analytics",
@@ -54598,8 +54603,8 @@ function analyticsTools(client, crypto3) {
54598
54603
  data = await client.get(`/api/analytics/merchants/${params.id}`, params.months ? { months: params.months } : undefined);
54599
54604
  break;
54600
54605
  }
54601
- if (crypto3)
54602
- data = await decryptAnalytics(data, crypto3.key);
54606
+ if (crypto4)
54607
+ data = await decryptAnalytics(data, crypto4.key);
54603
54608
  return data;
54604
54609
  }
54605
54610
  })
@@ -54657,7 +54662,7 @@ function budgetTools(client) {
54657
54662
  }
54658
54663
 
54659
54664
  // src/tools/dashboard.ts
54660
- function dashboardTools(client, crypto3) {
54665
+ function dashboardTools(client, crypto4) {
54661
54666
  return [
54662
54667
  defineTool({
54663
54668
  name: "get_dashboard",
@@ -54666,8 +54671,8 @@ function dashboardTools(client, crypto3) {
54666
54671
  output: dashboardOutputSchema,
54667
54672
  handler: async (params) => {
54668
54673
  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);
54674
+ if (crypto4 && Array.isArray(data?.recentTransactions)) {
54675
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.recentTransactions, crypto4.key);
54671
54676
  data.recentTransactions = decrypted;
54672
54677
  if (undecrypted.length)
54673
54678
  return { ...data, undecrypted };
@@ -54682,7 +54687,7 @@ function dashboardTools(client, crypto3) {
54682
54687
  function debtVariants(client) {
54683
54688
  return {
54684
54689
  list: variant(exports_external.object({}), () => client.get("/api/debts")),
54685
- delete: variant(exports_external.object({ id: exports_external.string().uuid() }), ({ id }) => client.delete(`/api/debts/${id}`))
54690
+ delete: variant(exports_external.object({ id: exports_external.string().uuid() }), ({ id }) => client.delete(apiPath`/api/debts/${id}`))
54686
54691
  };
54687
54692
  }
54688
54693
  function debtTools(client) {
@@ -54713,7 +54718,7 @@ function debtTools(client) {
54713
54718
  id: exports_external.string(),
54714
54719
  settledAmount: exports_external.string().optional().describe("settle: actual amount, if not 0")
54715
54720
  }), async ({ id, settledAmount }) => {
54716
- const data = await client.patch(`/api/debts/${id}`, {
54721
+ const data = await client.patch(apiPath`/api/debts/${id}`, {
54717
54722
  isActive: false,
54718
54723
  outstandingBalance: settledAmount ?? "0"
54719
54724
  });
@@ -54740,7 +54745,7 @@ function accountVariants(client) {
54740
54745
  billingDay: exports_external.number().optional(),
54741
54746
  color: exports_external.string().optional()
54742
54747
  }), async ({ id, ...rest }) => {
54743
- const data = id ? await client.patch(`/api/accounts/${id}`, rest) : await client.post("/api/accounts", rest);
54748
+ const data = id ? await client.patch(apiPath`/api/accounts/${id}`, rest) : await client.post("/api/accounts", rest);
54744
54749
  return data;
54745
54750
  })
54746
54751
  };
@@ -54759,10 +54764,10 @@ function categoryVariants(client) {
54759
54764
  icon: exports_external.string().optional(),
54760
54765
  color: exports_external.string().optional()
54761
54766
  }), async ({ id, ...rest }) => {
54762
- const data = id ? await client.patch(`/api/categories/${id}`, rest) : await client.post("/api/categories", rest);
54767
+ const data = id ? await client.patch(apiPath`/api/categories/${id}`, rest) : await client.post("/api/categories", rest);
54763
54768
  return data;
54764
54769
  }),
54765
- delete: variant(exports_external.object({ id: exports_external.string().describe("Category UUID") }), ({ id }) => client.delete(`/api/categories/${id}`))
54770
+ delete: variant(exports_external.object({ id: exports_external.string().describe("Category UUID") }), ({ id }) => client.delete(apiPath`/api/categories/${id}`))
54766
54771
  };
54767
54772
  }
54768
54773
 
@@ -54786,13 +54791,13 @@ function goalVariants(client) {
54786
54791
  isActive: exports_external.boolean().optional(),
54787
54792
  notes: exports_external.string().optional()
54788
54793
  }), async ({ id, ...body }) => {
54789
- const data = id ? await client.patch(`/api/goals/insurance/${id}`, body) : await client.post("/api/goals/insurance", body);
54794
+ const data = id ? await client.patch(apiPath`/api/goals/insurance/${id}`, body) : await client.post("/api/goals/insurance", body);
54790
54795
  return data;
54791
54796
  }),
54792
- deleteGoal: variant(byId, ({ id }) => client.delete(`/api/goals/${id}`)),
54793
- deleteEmi: variant(byId, ({ id }) => client.delete(`/api/goals/emis/${id}`)),
54794
- deleteInsurance: variant(byId, ({ id }) => client.delete(`/api/goals/insurance/${id}`)),
54795
- deleteSinkingFund: variant(byId, ({ id }) => client.delete(`/api/goals/sinking-funds/${id}`))
54797
+ deleteGoal: variant(byId, ({ id }) => client.delete(apiPath`/api/goals/${id}`)),
54798
+ deleteEmi: variant(byId, ({ id }) => client.delete(apiPath`/api/goals/emis/${id}`)),
54799
+ deleteInsurance: variant(byId, ({ id }) => client.delete(apiPath`/api/goals/insurance/${id}`)),
54800
+ deleteSinkingFund: variant(byId, ({ id }) => client.delete(apiPath`/api/goals/sinking-funds/${id}`))
54796
54801
  };
54797
54802
  }
54798
54803
  function goalListVariants(client) {
@@ -54806,7 +54811,7 @@ function goalListVariants(client) {
54806
54811
  emis: collection("emis"),
54807
54812
  sinking_funds: collection("sinking_funds"),
54808
54813
  insurance: collection("insurance"),
54809
- goal_contributions: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.get(`/api/goals/${id}/contributions`))
54814
+ goal_contributions: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.get(apiPath`/api/goals/${id}/contributions`))
54810
54815
  };
54811
54816
  }
54812
54817
  function goalTools(client) {
@@ -54882,7 +54887,7 @@ function goalTools(client) {
54882
54887
  };
54883
54888
  if (!id2)
54884
54889
  require2(["name", "targetAmount", "goalType"]);
54885
- const data2 = id2 ? await client.patch(`/api/goals/${id2}`, body2) : await client.post("/api/goals", body2);
54890
+ const data2 = id2 ? await client.patch(apiPath`/api/goals/${id2}`, body2) : await client.post("/api/goals", body2);
54886
54891
  return data2;
54887
54892
  }
54888
54893
  if (type === "emi") {
@@ -54924,7 +54929,7 @@ function goalTools(client) {
54924
54929
  owner,
54925
54930
  status
54926
54931
  };
54927
- const data2 = id2 ? await client.patch(`/api/goals/emis/${id2}`, body2) : await client.post("/api/goals/emis", body2);
54932
+ const data2 = id2 ? await client.patch(apiPath`/api/goals/emis/${id2}`, body2) : await client.post("/api/goals/emis", body2);
54928
54933
  return data2;
54929
54934
  }
54930
54935
  const { id, name, targetAmount, targetDate, monthlyContribution, currentAmount } = fields;
@@ -54937,7 +54942,7 @@ function goalTools(client) {
54937
54942
  monthlySetAside: monthlyContribution,
54938
54943
  currentAmount
54939
54944
  };
54940
- const data = id ? await client.patch(`/api/goals/sinking-funds/${id}`, body) : await client.post("/api/goals/sinking-funds", body);
54945
+ const data = id ? await client.patch(apiPath`/api/goals/sinking-funds/${id}`, body) : await client.post("/api/goals/sinking-funds", body);
54941
54946
  return data;
54942
54947
  }
54943
54948
  }),
@@ -54950,7 +54955,7 @@ function goalTools(client) {
54950
54955
  date: exports_external.string().optional().describe("YYYY-MM-DD (default today)"),
54951
54956
  notes: exports_external.string().optional()
54952
54957
  }),
54953
- handler: ({ id, ...body }) => client.post(`/api/goals/${id}/contribute`, body)
54958
+ handler: ({ id, ...body }) => client.post(apiPath`/api/goals/${id}/contribute`, body)
54954
54959
  })
54955
54960
  ];
54956
54961
  }
@@ -55061,7 +55066,7 @@ function householdVariants(client) {
55061
55066
  }), (body) => client.patch("/api/households", body)),
55062
55067
  listInvites: variant(exports_external.object({}), () => client.get("/api/households/invites")),
55063
55068
  createInvite: variant(exports_external.object({ email: exports_external.string().email() }), ({ email: email3 }) => client.post("/api/households/invites", { email: email3 })),
55064
- revokeInvite: variant(exports_external.object({ id: exports_external.string().describe("Invite UUID") }), ({ id }) => client.delete(`/api/households/invites/${id}`))
55069
+ revokeInvite: variant(exports_external.object({ id: exports_external.string().describe("Invite UUID") }), ({ id }) => client.delete(apiPath`/api/households/invites/${id}`))
55065
55070
  };
55066
55071
  }
55067
55072
 
@@ -55069,8 +55074,8 @@ function householdVariants(client) {
55069
55074
  function investmentVariants(client) {
55070
55075
  const byId = exports_external.object({ id: exports_external.string().describe("SIP or FD UUID") });
55071
55076
  return {
55072
- deleteSip: variant(byId, ({ id }) => client.delete(`/api/investments/sips/${id}`)),
55073
- deleteFd: variant(byId, ({ id }) => client.delete(`/api/investments/fds/${id}`))
55077
+ deleteSip: variant(byId, ({ id }) => client.delete(apiPath`/api/investments/sips/${id}`)),
55078
+ deleteFd: variant(byId, ({ id }) => client.delete(apiPath`/api/investments/fds/${id}`))
55074
55079
  };
55075
55080
  }
55076
55081
  function investmentListVariants(client) {
@@ -55078,7 +55083,7 @@ function investmentListVariants(client) {
55078
55083
  holdings: variant(exports_external.object({}), () => client.get("/api/investments")),
55079
55084
  sips: variant(exports_external.object({}), () => client.get("/api/investments/sips")),
55080
55085
  fixed_deposits: variant(exports_external.object({}), () => client.get("/api/investments/fds")),
55081
- investment_transactions: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.get(`/api/investments/${id}/transactions`))
55086
+ investment_transactions: variant(exports_external.object({ id: exports_external.string() }), ({ id }) => client.get(apiPath`/api/investments/${id}/transactions`))
55082
55087
  };
55083
55088
  }
55084
55089
  function investmentTools(client) {
@@ -55175,7 +55180,7 @@ function investmentTools(client) {
55175
55180
  endDate,
55176
55181
  platform: id2 ? platform : platform ?? "manual"
55177
55182
  };
55178
- const data2 = id2 ? await client.patch(`/api/investments/sips/${id2}`, sipFields) : await client.post("/api/investments/sips", sipFields);
55183
+ const data2 = id2 ? await client.patch(apiPath`/api/investments/sips/${id2}`, sipFields) : await client.post("/api/investments/sips", sipFields);
55179
55184
  return data2;
55180
55185
  }
55181
55186
  const { id, bankName, amount, interestRate, startDate, maturityDate, owner, notes } = fields;
@@ -55201,7 +55206,7 @@ function investmentTools(client) {
55201
55206
  owner,
55202
55207
  notes
55203
55208
  };
55204
- const data = id ? await client.patch(`/api/investments/fds/${id}`, fdBody) : await client.post("/api/investments/fds", fdBody);
55209
+ const data = id ? await client.patch(apiPath`/api/investments/fds/${id}`, fdBody) : await client.post("/api/investments/fds", fdBody);
55205
55210
  return data;
55206
55211
  }
55207
55212
  }),
@@ -55231,13 +55236,13 @@ async function categorySlugToId(client) {
55231
55236
  }
55232
55237
  return map2;
55233
55238
  }
55234
- function merchantVariants(client, crypto3) {
55239
+ function merchantVariants(client, crypto4) {
55235
55240
  return {
55236
55241
  list: variant(exports_external.object({ rawId: exports_external.string().optional().describe("merchants: find one by UPI ID or name") }), async ({ rawId }) => {
55237
55242
  const data = await client.get("/api/merchants");
55238
55243
  let undecrypted = [];
55239
- if (crypto3 && data.data) {
55240
- const dec = await decryptMerchantFields(data.data, crypto3.key);
55244
+ if (crypto4 && data.data) {
55245
+ const dec = await decryptMerchantFields(data.data, crypto4.key);
55241
55246
  data.data = dec.data;
55242
55247
  undecrypted = dec.undecrypted;
55243
55248
  }
@@ -55265,7 +55270,7 @@ function merchantVariants(client, crypto3) {
55265
55270
  }
55266
55271
  payload.categoryId = categoryId;
55267
55272
  }
55268
- const data = id ? await client.patch(`/api/merchants/${id}`, payload) : await client.post("/api/merchants", payload);
55273
+ const data = id ? await client.patch(apiPath`/api/merchants/${id}`, payload) : await client.post("/api/merchants", payload);
55269
55274
  return data;
55270
55275
  }),
55271
55276
  delete: variant(exports_external.object({
@@ -55275,7 +55280,7 @@ function merchantVariants(client, crypto3) {
55275
55280
  if (!id && (!ids || ids.length === 0)) {
55276
55281
  throw new Error("Provide either id or ids");
55277
55282
  }
55278
- const data = ids ? await client.post("/api/merchants/bulk-delete", { ids }) : await client.delete(`/api/merchants/${id}`);
55283
+ const data = ids ? await client.post("/api/merchants/bulk-delete", { ids }) : await client.delete(apiPath`/api/merchants/${id}`);
55279
55284
  return data;
55280
55285
  })
55281
55286
  };
@@ -55295,7 +55300,7 @@ function merchantTools(client) {
55295
55300
  }
55296
55301
 
55297
55302
  // src/tools/persons.ts
55298
- function personVariants(client, crypto3) {
55303
+ function personVariants(client, crypto4) {
55299
55304
  return {
55300
55305
  upsert: variant(exports_external.object({
55301
55306
  id: exports_external.string().optional().describe("Person UUID — pass to update, omit to create"),
@@ -55305,13 +55310,13 @@ function personVariants(client, crypto3) {
55305
55310
  relationship: exports_external.string().optional(),
55306
55311
  notes: exports_external.string().optional()
55307
55312
  }), async ({ id, ...updates }) => {
55308
- if (crypto3) {
55313
+ if (crypto4) {
55309
55314
  if (id) {
55310
- const existing = await client.get(`/api/persons/${id}`);
55315
+ const existing = await client.get(apiPath`/api/persons/${id}`);
55311
55316
  const personData = existing.data;
55312
55317
  let current;
55313
55318
  if (personData.cipher) {
55314
- current = await decryptPersonCipher(personData.cipher, crypto3.key);
55319
+ current = await decryptPersonCipher(personData.cipher, crypto4.key);
55315
55320
  } else {
55316
55321
  current = {
55317
55322
  name: personData.name,
@@ -55328,8 +55333,8 @@ function personVariants(client, crypto3) {
55328
55333
  relationship: updates.relationship ?? current.relationship ?? null,
55329
55334
  notes: updates.notes ?? current.notes ?? null
55330
55335
  };
55331
- const payload2 = await encryptPersonCipher(merged, crypto3.key);
55332
- const data3 = await client.patch(`/api/persons/${id}`, payload2);
55336
+ const payload2 = await encryptPersonCipher(merged, crypto4.key);
55337
+ const data3 = await client.patch(apiPath`/api/persons/${id}`, payload2);
55333
55338
  return data3;
55334
55339
  }
55335
55340
  if (!updates.name)
@@ -55341,20 +55346,20 @@ function personVariants(client, crypto3) {
55341
55346
  relationship: updates.relationship ?? null,
55342
55347
  notes: updates.notes ?? null
55343
55348
  };
55344
- const payload = await encryptPersonCipher(fields, crypto3.key);
55349
+ const payload = await encryptPersonCipher(fields, crypto4.key);
55345
55350
  const data2 = await client.post("/api/persons", payload);
55346
55351
  return data2;
55347
55352
  }
55348
- const data = id ? await client.patch(`/api/persons/${id}`, updates) : await client.post("/api/persons", updates);
55353
+ const data = id ? await client.patch(apiPath`/api/persons/${id}`, updates) : await client.post("/api/persons", updates);
55349
55354
  return data;
55350
55355
  }),
55351
55356
  list: variant(exports_external.object({
55352
55357
  id: exports_external.string().optional().describe("persons: one person's UUID; omit to list all")
55353
55358
  }), async ({ id }) => {
55354
55359
  if (id) {
55355
- 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);
55360
+ const data2 = await client.get(apiPath`/api/persons/${id}`);
55361
+ if (crypto4 && data2.data.cipher) {
55362
+ const { data: decrypted, undecrypted } = await decryptPersonCiphers(data2.data, crypto4.key);
55358
55363
  data2.data = decrypted;
55359
55364
  if (undecrypted.length)
55360
55365
  return { ...data2, undecrypted };
@@ -55362,15 +55367,15 @@ function personVariants(client, crypto3) {
55362
55367
  return data2;
55363
55368
  }
55364
55369
  const data = await client.get("/api/persons");
55365
- if (crypto3 && data.data) {
55366
- const { data: decrypted, undecrypted } = await decryptPersonCiphers(data.data, crypto3.key);
55370
+ if (crypto4 && data.data) {
55371
+ const { data: decrypted, undecrypted } = await decryptPersonCiphers(data.data, crypto4.key);
55367
55372
  data.data = decrypted;
55368
55373
  if (undecrypted.length)
55369
55374
  return { ...data, undecrypted };
55370
55375
  }
55371
55376
  return data;
55372
55377
  }),
55373
- delete: variant(exports_external.object({ id: exports_external.string().describe("Person UUID") }), ({ id }) => client.delete(`/api/persons/${id}`))
55378
+ delete: variant(exports_external.object({ id: exports_external.string().describe("Person UUID") }), ({ id }) => client.delete(apiPath`/api/persons/${id}`))
55374
55379
  };
55375
55380
  }
55376
55381
 
@@ -55501,11 +55506,11 @@ function reportTools(client) {
55501
55506
  }
55502
55507
 
55503
55508
  // src/tools/entities.ts
55504
- function entityTools(client, crypto3) {
55509
+ function entityTools(client, crypto4) {
55505
55510
  const category = categoryVariants(client);
55506
55511
  const account = accountVariants(client);
55507
- const person = personVariants(client, crypto3);
55508
- const merchant = merchantVariants(client, crypto3);
55512
+ const person = personVariants(client, crypto4);
55513
+ const merchant = merchantVariants(client, crypto4);
55509
55514
  const debt = debtVariants(client);
55510
55515
  const household = householdVariants(client);
55511
55516
  const goal = goalVariants(client);
@@ -59154,10 +59159,10 @@ function toTxnRow(r2) {
59154
59159
  isDuplicate: !!r2.isDuplicate
59155
59160
  };
59156
59161
  }
59157
- async function fetchAllRows(client, crypto3, params = {}, maxPages = 100) {
59158
- return (await fetchAllRowsPaged(client, crypto3, params, maxPages)).rows;
59162
+ async function fetchAllRows(client, crypto4, params = {}, maxPages = 100) {
59163
+ return (await fetchAllRowsPaged(client, crypto4, params, maxPages)).rows;
59159
59164
  }
59160
- async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59165
+ async function fetchAllRowsPaged(client, crypto4, params = {}, maxPages = 100) {
59161
59166
  const out = [];
59162
59167
  let cursor;
59163
59168
  let truncated = false;
@@ -59167,7 +59172,7 @@ async function fetchAllRowsPaged(client, crypto3, params = {}, maxPages = 100) {
59167
59172
  q2.cursor = cursor;
59168
59173
  const res = await client.get("/api/transactions", q2);
59169
59174
  const batch0 = res.data ?? [];
59170
- const batch = crypto3 ? (await decryptTransactionFields(batch0, crypto3.key)).data : batch0;
59175
+ const batch = crypto4 ? (await decryptTransactionFields(batch0, crypto4.key)).data : batch0;
59171
59176
  out.push(...batch.map(toTxnRow));
59172
59177
  if (!res.hasMore || !res.nextCursor || batch.length === 0 || res.nextCursor === cursor)
59173
59178
  break;
@@ -59216,14 +59221,14 @@ function normalizePersonName(name) {
59216
59221
  function normalizeUpiHandle(handle) {
59217
59222
  return handle.toLowerCase().trim().split("@")[0];
59218
59223
  }
59219
- async function loadDecryptedPersons(client, crypto3) {
59224
+ async function loadDecryptedPersons(client, crypto4) {
59220
59225
  const res = await client.get("/api/persons");
59221
59226
  const persons = [];
59222
59227
  for (const p2 of res.data ?? []) {
59223
59228
  let name = p2.name ?? "";
59224
- if (crypto3 && p2.cipher) {
59229
+ if (crypto4 && p2.cipher) {
59225
59230
  try {
59226
- name = (await decryptPersonCipher(p2.cipher, crypto3.key)).name;
59231
+ name = (await decryptPersonCipher(p2.cipher, crypto4.key)).name;
59227
59232
  } catch {}
59228
59233
  }
59229
59234
  if (name)
@@ -59264,8 +59269,8 @@ function isPersonLikeNarration(description) {
59264
59269
  const { name, paymentMethod } = parseNarration(description);
59265
59270
  return classifyEntity({ paymentMethod, extractedName: name }) !== "merchant";
59266
59271
  }
59267
- async function backfillPersons(client, crypto3, opts = {}) {
59268
- const existing = await loadDecryptedPersons(client, crypto3);
59272
+ async function backfillPersons(client, crypto4, opts = {}) {
59273
+ const existing = await loadDecryptedPersons(client, crypto4);
59269
59274
  const personIdByNorm = new Map;
59270
59275
  const personIdByHandle = new Map;
59271
59276
  for (const p2 of existing) {
@@ -59273,7 +59278,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59273
59278
  for (const h2 of p2.upiHandles)
59274
59279
  personIdByHandle.set(normalizeUpiHandle(h2), p2.id);
59275
59280
  }
59276
- const { rows, truncated } = await fetchAllRowsPaged(client, crypto3, { unresolved: "true" });
59281
+ const { rows, truncated } = await fetchAllRowsPaged(client, crypto4, { unresolved: "true" });
59277
59282
  const candidates = rows.filter((r2) => !r2.merchantId && !r2.personId && !r2.isTransfer && !r2.isIgnored);
59278
59283
  const directLinks = [];
59279
59284
  const groups = new Map;
@@ -59365,7 +59370,7 @@ async function backfillPersons(client, crypto3, opts = {}) {
59365
59370
  relationship: null,
59366
59371
  notes: null
59367
59372
  };
59368
- return crypto3 ? await encryptPersonCipher(fields, crypto3.key) : { name: displayName, upiHandles, phoneNumbers: [], relationship: null, notes: null };
59373
+ return crypto4 ? await encryptPersonCipher(fields, crypto4.key) : { name: displayName, upiHandles, phoneNumbers: [], relationship: null, notes: null };
59369
59374
  }));
59370
59375
  const res = await client.post("/api/persons/bulk", {
59371
59376
  persons: bodies
@@ -59500,20 +59505,20 @@ function groupRows(rows) {
59500
59505
  function isCategorizable(r2) {
59501
59506
  return !r2.categoryId && !r2.isTransfer && !r2.isIgnored && !r2.isDuplicate;
59502
59507
  }
59503
- async function fetchUncategorized(client, crypto3, range = {}) {
59508
+ async function fetchUncategorized(client, crypto4, range = {}) {
59504
59509
  const params = { uncategorized: "true" };
59505
59510
  if (range.startDate)
59506
59511
  params.startDate = range.startDate;
59507
59512
  if (range.endDate)
59508
59513
  params.endDate = range.endDate;
59509
- return (await fetchAllRows(client, crypto3, params)).filter(isCategorizable);
59514
+ return (await fetchAllRows(client, crypto4, params)).filter(isCategorizable);
59510
59515
  }
59511
59516
  var MERCHANT_CREATE_CONCURRENCY = 5;
59512
59517
  var titleCase = (s2) => s2.replace(/\b[a-z]/g, (c2) => c2.toUpperCase());
59513
- async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59518
+ async function applyGroupAssignments(client, crypto4, assignments, opts = {}) {
59514
59519
  const catIndex = await loadCategoryIndex(client);
59515
59520
  const resolved = assignments.map((a2) => ({ a: a2, cat: requireCategory(catIndex, a2.categorySlug) }));
59516
- const groups = groupRows(await fetchUncategorized(client, crypto3, opts));
59521
+ const groups = groupRows(await fetchUncategorized(client, crypto4, opts));
59517
59522
  const result = { applied: [], notFound: [], rulesSaved: 0, unlearnable: [] };
59518
59523
  const plan = [];
59519
59524
  const claimed = new Set;
@@ -59568,7 +59573,7 @@ async function applyGroupAssignments(client, crypto3, assignments, opts = {}) {
59568
59573
  const { group, learn, confirmMerchant } = p2;
59569
59574
  if (!learn || group.pattern === UNPARSED) {
59570
59575
  directPlans.push({ p: p2, reason: learn ? UNPARSED : undefined });
59571
- } else if (crypto3 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59576
+ } else if (crypto4 && !confirmMerchant && group.rows.some((r2) => isPersonLikeNarration(r2.description))) {
59572
59577
  directPlans.push({ p: p2, reason: `${group.pattern} (private mode: person-like)` });
59573
59578
  } else if (taught.has(group.pattern) && taught.get(group.pattern) !== p2.categoryId) {
59574
59579
  directPlans.push({ p: p2, reason: `${group.pattern} (conflicting category for same pattern)` });
@@ -59666,12 +59671,12 @@ function flagBatchDuplicates(txns) {
59666
59671
  return false;
59667
59672
  });
59668
59673
  }
59669
- async function importTransactions(client, crypto3, opts) {
59674
+ async function importTransactions(client, crypto4, opts) {
59670
59675
  let txns = opts.txns;
59671
59676
  let duplicatesSkipped = 0;
59672
59677
  if (txns.length > 0) {
59673
59678
  const dates = txns.map((t2) => t2.date).sort();
59674
- const existing = await fetchAllRows(client, crypto3, {
59679
+ const existing = await fetchAllRows(client, crypto4, {
59675
59680
  startDate: dates[0],
59676
59681
  endDate: dates[dates.length - 1]
59677
59682
  });
@@ -59687,8 +59692,8 @@ async function importTransactions(client, crypto3, opts) {
59687
59692
  const base = { ...t2, isDuplicate: dupFlags[i2] };
59688
59693
  return enrichedData ? { ...base, enrichedData } : base;
59689
59694
  });
59690
- if (crypto3)
59691
- await encryptTransactionFields(rows, crypto3.key);
59695
+ if (crypto4)
59696
+ await encryptTransactionFields(rows, crypto4.key);
59692
59697
  const CHUNK = 200;
59693
59698
  let imported = 0;
59694
59699
  for (let i2 = 0;i2 < rows.length; i2 += CHUNK) {
@@ -59703,19 +59708,19 @@ async function importTransactions(client, crypto3, opts) {
59703
59708
  }
59704
59709
  return { imported, duplicatesSkipped, duplicatesFlagged };
59705
59710
  }
59706
- async function runRescan(client, crypto3) {
59711
+ async function runRescan(client, crypto4) {
59707
59712
  let bag = structuredClone(EMPTY_BAG);
59708
59713
  try {
59709
59714
  bag = (await loadBag(client)).bag;
59710
59715
  } catch {}
59711
59716
  const merchantsRes = await client.get("/api/merchants");
59712
59717
  const allMerchants0 = merchantsRes.data ?? [];
59713
- const allMerchants = crypto3 ? (await decryptMerchantFields(allMerchants0, crypto3.key)).data : allMerchants0;
59718
+ const allMerchants = crypto4 ? (await decryptMerchantFields(allMerchants0, crypto4.key)).data : allMerchants0;
59714
59719
  const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59715
59720
  const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
59716
59721
  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" });
59722
+ const persons = await loadDecryptedPersons(client, crypto4);
59723
+ const allRows = await fetchAllRows(client, crypto4, { unresolved: "true" });
59719
59724
  const categoryByTxn = new Map(allRows.map((r2) => [r2.id, r2.categoryId]));
59720
59725
  const plan = autoCategorizeFromDescriptions({
59721
59726
  rows: allRows,
@@ -59724,8 +59729,17 @@ async function runRescan(client, crypto3) {
59724
59729
  localMerchants,
59725
59730
  persons
59726
59731
  });
59732
+ const knownMerchants = new Set([
59733
+ ...allMerchants.map((m2) => m2.id),
59734
+ ...allRows.flatMap((r2) => r2.merchantId ? [r2.merchantId] : [])
59735
+ ]);
59736
+ const knownPersons = new Set([
59737
+ ...persons.map((p2) => p2.id),
59738
+ ...allRows.flatMap((r2) => r2.personId ? [r2.personId] : [])
59739
+ ]);
59740
+ const live = plan.suggestions.filter((s2) => s2.entity.type === "merchant" ? knownMerchants.has(s2.entity.merchantId) : knownPersons.has(s2.entity.personId));
59727
59741
  let categorized = 0;
59728
- const links = plan.suggestions.flatMap((s2) => {
59742
+ const links = live.flatMap((s2) => {
59729
59743
  const categoryId = s2.categoryId ?? (s2.entity.type === "merchant" ? merchantCategoryById.get(s2.entity.merchantId) ?? null : null);
59730
59744
  return s2.transactionIds.map((txId) => {
59731
59745
  const applyCategory = categoryId && !categoryByTxn.get(txId);
@@ -59745,7 +59759,7 @@ var TRUNCATED_WARNING = "TRUNCATED: the API capped this result, so it is INCOMPL
59745
59759
  function withTruncationWarning(data) {
59746
59760
  return data.truncated ? { warning: TRUNCATED_WARNING, ...data } : data;
59747
59761
  }
59748
- function transactionTools(client, crypto3) {
59762
+ function transactionTools(client, crypto4) {
59749
59763
  return [
59750
59764
  defineTool({
59751
59765
  name: "get_transactions",
@@ -59792,7 +59806,7 @@ function transactionTools(client, crypto3) {
59792
59806
  apiParams.categoryId = requireCategory(await loadCategoryIndex(client), categorySlug).id;
59793
59807
  }
59794
59808
  if (personName && !apiParams.personId) {
59795
- const persons = await loadDecryptedPersons(client, crypto3);
59809
+ const persons = await loadDecryptedPersons(client, crypto4);
59796
59810
  const match = resolvePersonByName(persons, personName);
59797
59811
  const err = (error62) => ({ error: error62 });
59798
59812
  if (!match) {
@@ -59809,14 +59823,14 @@ function transactionTools(client, crypto3) {
59809
59823
  const data = await client.get("/api/transactions", Object.fromEntries(Object.entries(apiParams).filter(([, v2]) => v2 !== undefined).map(([k2, v2]) => [k2, String(v2)])));
59810
59824
  let rows = data.data ?? [];
59811
59825
  let undecrypted = [];
59812
- if (crypto3) {
59813
- const dec = await decryptTransactionFields(rows, crypto3.key);
59826
+ if (crypto4) {
59827
+ const dec = await decryptTransactionFields(rows, crypto4.key);
59814
59828
  rows = dec.data;
59815
59829
  undecrypted = dec.undecrypted;
59816
59830
  }
59817
59831
  const personNames = new Map;
59818
59832
  if (rows.some((r2) => r2.personId)) {
59819
- for (const p2 of await loadDecryptedPersons(client, crypto3))
59833
+ for (const p2 of await loadDecryptedPersons(client, crypto4))
59820
59834
  personNames.set(p2.id, p2.name);
59821
59835
  }
59822
59836
  if (full) {
@@ -59847,7 +59861,7 @@ function transactionTools(client, crypto3) {
59847
59861
  limit: exports_external.number().optional().describe("Max groups (default 40, max 100)")
59848
59862
  }),
59849
59863
  handler: async ({ startDate, endDate, minTotal, limit }) => {
59850
- const rows = await fetchUncategorized(client, crypto3, { startDate, endDate });
59864
+ const rows = await fetchUncategorized(client, crypto4, { startDate, endDate });
59851
59865
  const all2 = groupRows(rows);
59852
59866
  const shown = all2.filter((g2) => g2.total >= (minTotal ?? 0)).slice(0, Math.min(Math.max(limit ?? 40, 1), 100));
59853
59867
  const shownRows = shown.reduce((n2, g2) => n2 + g2.rows.length, 0);
@@ -59884,7 +59898,7 @@ function transactionTools(client, crypto3) {
59884
59898
  handler: (args, ctx) => {
59885
59899
  const { assignments, startDate, endDate, dryRun } = args;
59886
59900
  const { expectedPlanHash } = args;
59887
- return applyGroupAssignments(client, crypto3, assignments, {
59901
+ return applyGroupAssignments(client, crypto4, assignments, {
59888
59902
  dryRun,
59889
59903
  startDate,
59890
59904
  endDate,
@@ -59897,7 +59911,7 @@ function transactionTools(client, crypto3) {
59897
59911
  name: "backfill_persons",
59898
59912
  description: "Create persons from UPI/IMPS counterparties in unlinked transactions (deduped by name) and link those transactions to them. Idempotent. dryRun previews.",
59899
59913
  input: exports_external.object({ dryRun: exports_external.boolean().optional() }),
59900
- handler: (args, ctx) => backfillPersons(client, crypto3, {
59914
+ handler: (args, ctx) => backfillPersons(client, crypto4, {
59901
59915
  onProgress: progressFor(ctx),
59902
59916
  dryRun: args.dryRun,
59903
59917
  expectedPlanHash: args.expectedPlanHash
@@ -59919,15 +59933,15 @@ function transactionTools(client, crypto3) {
59919
59933
  }),
59920
59934
  handler: async ({ id, ...updates }) => {
59921
59935
  const body = { ...updates };
59922
- if (crypto3 && body.notes)
59923
- body.notes = await encryptField(body.notes, crypto3.key);
59936
+ if (crypto4 && body.notes)
59937
+ body.notes = await encryptField(body.notes, crypto4.key);
59924
59938
  if (body.categorySlug) {
59925
59939
  const cat = requireCategory(await loadCategoryIndex(client), body.categorySlug);
59926
59940
  body.categoryId = cat.id;
59927
59941
  body.categoryType = cat.type;
59928
59942
  delete body.categorySlug;
59929
59943
  }
59930
- const data = await client.patch(`/api/transactions/${id}`, body);
59944
+ const data = await client.patch(apiPath`/api/transactions/${id}`, body);
59931
59945
  return data;
59932
59946
  }
59933
59947
  }),
@@ -59961,7 +59975,7 @@ function transactionTools(client, crypto3) {
59961
59975
  throw new Error("ids and resolution are required for action 'resolve'");
59962
59976
  }
59963
59977
  if (action === "flag") {
59964
- const data2 = await client.patch(`/api/transactions/${transactionId}`, {
59978
+ const data2 = await client.patch(apiPath`/api/transactions/${transactionId}`, {
59965
59979
  isDuplicate: true,
59966
59980
  isIgnored: true
59967
59981
  });
@@ -59969,8 +59983,8 @@ function transactionTools(client, crypto3) {
59969
59983
  }
59970
59984
  if (action === "list") {
59971
59985
  const data2 = await client.get("/api/transactions/duplicates");
59972
- if (crypto3 && data2.data) {
59973
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data2.data, crypto3.key);
59986
+ if (crypto4 && data2.data) {
59987
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data2.data, crypto4.key);
59974
59988
  data2.data = decrypted;
59975
59989
  return withTruncationWarning(undecrypted.length ? { ...data2, undecrypted } : data2);
59976
59990
  }
@@ -59993,8 +60007,8 @@ function transactionTools(client, crypto3) {
59993
60007
  }),
59994
60008
  handler: async ({ accountId, startDate, endDate }) => {
59995
60009
  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);
60010
+ if (crypto4 && data.data) {
60011
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto4.key);
59998
60012
  data.data = decrypted;
59999
60013
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60000
60014
  }
@@ -60069,7 +60083,7 @@ function transactionTools(client, crypto3) {
60069
60083
  throw new Error("filters is empty (matches every transaction) — refusing; set confirmDeleteAll only after explicit user confirmation");
60070
60084
  }
60071
60085
  if (id) {
60072
- const data2 = await client.delete(`/api/transactions/${id}`);
60086
+ const data2 = await client.delete(apiPath`/api/transactions/${id}`);
60073
60087
  return data2;
60074
60088
  }
60075
60089
  const body = ids ? { ids } : { filters, ...confirmDeleteAll ? { confirmDeleteAll } : {} };
@@ -60081,7 +60095,7 @@ function transactionTools(client, crypto3) {
60081
60095
  name: "rescan_transactions",
60082
60096
  description: "Apply saved learning rules (merchant aliases, category rules) to uncategorized transactions, like the web app's auto-categorize.",
60083
60097
  input: exports_external.object({}),
60084
- handler: () => runRescan(client, crypto3)
60098
+ handler: () => runRescan(client, crypto4)
60085
60099
  }),
60086
60100
  defineTool({
60087
60101
  name: "get_settlement_candidates",
@@ -60089,8 +60103,8 @@ function transactionTools(client, crypto3) {
60089
60103
  input: exports_external.object({ ccAccountId: exports_external.string().uuid() }),
60090
60104
  handler: async ({ ccAccountId }) => {
60091
60105
  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);
60106
+ if (crypto4 && data.data) {
60107
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.data, crypto4.key);
60094
60108
  data.data = decrypted;
60095
60109
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60096
60110
  }
@@ -60107,8 +60121,8 @@ function transactionTools(client, crypto3) {
60107
60121
  if (!row)
60108
60122
  return res;
60109
60123
  let undecrypted = [];
60110
- if (crypto3) {
60111
- const dec = await decryptTransactionFields([row], crypto3.key);
60124
+ if (crypto4) {
60125
+ const dec = await decryptTransactionFields([row], crypto4.key);
60112
60126
  row = dec.data[0];
60113
60127
  undecrypted = dec.undecrypted;
60114
60128
  }
@@ -60134,7 +60148,7 @@ function transactionTools(client, crypto3) {
60134
60148
  }
60135
60149
 
60136
60150
  // src/tools/export.ts
60137
- function exportTools(client, crypto3) {
60151
+ function exportTools(client, crypto4) {
60138
60152
  return [
60139
60153
  defineTool({
60140
60154
  name: "export_transactions",
@@ -60151,8 +60165,8 @@ function exportTools(client, crypto3) {
60151
60165
  ...Object.fromEntries(Object.entries(params).filter(([, v2]) => v2 !== undefined)),
60152
60166
  format: "json"
60153
60167
  });
60154
- if (crypto3 && Array.isArray(data?.transactions)) {
60155
- const { data: decrypted, undecrypted } = await decryptTransactionFields(data.transactions, crypto3.key);
60168
+ if (crypto4 && Array.isArray(data?.transactions)) {
60169
+ const { data: decrypted, undecrypted } = await decryptTransactionFields(data.transactions, crypto4.key);
60156
60170
  data.transactions = decrypted;
60157
60171
  return withTruncationWarning(undecrypted.length ? { ...data, undecrypted } : data);
60158
60172
  }
@@ -60394,28 +60408,28 @@ var DEFAULT_MIN_CONFIDENCE = 0.95;
60394
60408
  var TIMEOUT_MS = 8000;
60395
60409
  var CONCURRENCY = 8;
60396
60410
  var MIN_LEARN_PATTERN_LEN = 5;
60397
- function assertPrivateModeAllowed(crypto3, env2 = process.env) {
60398
- if (crypto3 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60411
+ function assertPrivateModeAllowed(crypto4, env2 = process.env) {
60412
+ if (crypto4 && env2.JEV_ALLOW_PRIVATE_MODE !== "1") {
60399
60413
  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
60414
  }
60401
60415
  }
60402
- function assertJevAllowed(crypto3, env2 = process.env) {
60403
- assertPrivateModeAllowed(crypto3, env2);
60416
+ function assertJevAllowed(crypto4, env2 = process.env) {
60417
+ assertPrivateModeAllowed(crypto4, env2);
60404
60418
  const key = env2.JEV_API_KEY;
60405
60419
  if (!key)
60406
60420
  throw new Error("JEV_API_KEY is not set — Jev categorization is off.");
60407
60421
  return key;
60408
60422
  }
60409
- function jevAllowSensitive(crypto3, env2) {
60410
- return crypto3 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60423
+ function jevAllowSensitive(crypto4, env2) {
60424
+ return crypto4 === undefined || env2.JEV_ALLOW_PRIVATE_MODE === "1";
60411
60425
  }
60412
- function makeJev(apiKey, crypto3, env2 = process.env, fetchImpl) {
60426
+ function makeJev(apiKey, crypto4, env2 = process.env, fetchImpl) {
60413
60427
  return createJev({
60414
60428
  apiKey,
60415
60429
  model: JEV_MODEL,
60416
60430
  minConfidence: DEFAULT_MIN_CONFIDENCE,
60417
60431
  timeoutMs: TIMEOUT_MS,
60418
- allowSensitive: jevAllowSensitive(crypto3, env2),
60432
+ allowSensitive: jevAllowSensitive(crypto4, env2),
60419
60433
  ...fetchImpl ? { fetch: fetchImpl } : {}
60420
60434
  });
60421
60435
  }
@@ -60435,10 +60449,10 @@ function groupState(type, sample) {
60435
60449
  const dir = type === "credit" ? "Money received" : "Money paid out";
60436
60450
  return `${dir}. Bank narration: "${sample.slice(0, 80)}"`;
60437
60451
  }
60438
- async function jevCategorize(client, crypto3, jev, opts = {}) {
60439
- assertPrivateModeAllowed(crypto3, opts.env);
60452
+ async function jevCategorize(client, crypto4, jev, opts = {}) {
60453
+ assertPrivateModeAllowed(crypto4, opts.env);
60440
60454
  const index = await loadCategoryIndex(client);
60441
- const rows = await fetchUncategorized(client, crypto3, opts);
60455
+ const rows = await fetchUncategorized(client, crypto4, opts);
60442
60456
  const groups = groupRows(rows);
60443
60457
  const unparsed = groups.filter((g2) => g2.pattern === UNPARSED).length;
60444
60458
  const candidates = groups.filter((g2) => g2.pattern !== UNPARSED);
@@ -60446,7 +60460,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60446
60460
  const suggestions = await mapLimit(candidates, CONCURRENCY, async (g2) => {
60447
60461
  const options = optionsFor(g2.type, index);
60448
60462
  const sample = g2.rows[0].description;
60449
- const text = crypto3 ? g2.pattern : sample;
60463
+ const text = crypto4 ? g2.pattern : sample;
60450
60464
  const d2 = await jev.choice({
60451
60465
  question: "Which spending category best fits this bank transaction?",
60452
60466
  options,
@@ -60488,7 +60502,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60488
60502
  }
60489
60503
  }
60490
60504
  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 });
60505
+ const applied = await applyGroupAssignments(client, crypto4, accepted.map(({ confidence: _c, ...a2 }) => a2), { dryRun: opts.dryRun, startDate: opts.startDate, endDate: opts.endDate });
60492
60506
  const conf = new Map(accepted.map((a2) => [`${a2.pattern}|${a2.type}`, a2.confidence]));
60493
60507
  return {
60494
60508
  dryRun: !!opts.dryRun,
@@ -60505,7 +60519,7 @@ async function jevCategorize(client, crypto3, jev, opts = {}) {
60505
60519
  }
60506
60520
 
60507
60521
  // src/tools/jev.ts
60508
- function jevTools(client, crypto3) {
60522
+ function jevTools(client, crypto4) {
60509
60523
  return [
60510
60524
  defineTool({
60511
60525
  name: "auto_categorize_jev",
@@ -60517,9 +60531,9 @@ function jevTools(client, crypto3) {
60517
60531
  dryRun: exports_external.boolean().optional()
60518
60532
  }),
60519
60533
  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, {
60534
+ const key = assertJevAllowed(crypto4);
60535
+ const jev = makeJev(key, crypto4);
60536
+ const result = await jevCategorize(client, crypto4, jev, {
60523
60537
  startDate,
60524
60538
  endDate,
60525
60539
  minConfidence,
@@ -60543,12 +60557,12 @@ function profileVariants(client) {
60543
60557
  function settingsTools(client) {
60544
60558
  const profile = profileVariants(client);
60545
60559
  const household = householdVariants(client);
60546
- const crypto3 = authCryptoVariants(client);
60560
+ const crypto4 = authCryptoVariants(client);
60547
60561
  const get = unionTool({
60548
60562
  name: "get_settings",
60549
60563
  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
60564
  key: "section",
60551
- variants: { profile: profile.get, household: household.get, encryption: crypto3.status }
60565
+ variants: { profile: profile.get, household: household.get, encryption: crypto4.status }
60552
60566
  });
60553
60567
  const update = unionTool({
60554
60568
  name: "update_settings",
@@ -60630,7 +60644,7 @@ async function fetchReconciliationContext(client, accountId, start, end, budget
60630
60644
  }
60631
60645
  var sum2 = (rows) => Math.round(rows.reduce((n2, r2) => n2 + r2.amount, 0));
60632
60646
  async function runStatementImport(deps, args) {
60633
- const { client, crypto: crypto3 } = deps;
60647
+ const { client, crypto: crypto4 } = deps;
60634
60648
  const accounts = (await client.get("/api/accounts")).data ?? [];
60635
60649
  const account = resolveAccount(accounts, args.account);
60636
60650
  const format = pickFormat(account, args.format);
@@ -60649,7 +60663,7 @@ async function runStatementImport(deps, args) {
60649
60663
  }
60650
60664
  const dates = txns.map((t2) => t2.date).sort();
60651
60665
  const existing0 = await fetchReconciliationContext(client, account.id, dates[0], dates[dates.length - 1]);
60652
- const existing = crypto3 ? (await decryptTransactionFields(existing0, crypto3.key)).data : existing0;
60666
+ const existing = crypto4 ? (await decryptTransactionFields(existing0, crypto4.key)).data : existing0;
60653
60667
  const present = existing.map((r2) => ({ ...r2, isDuplicate: false, isIgnored: false }));
60654
60668
  const result = reconcile(present, { ...statement, transactions: txns });
60655
60669
  const fresh = result.unmatched;
@@ -60690,7 +60704,7 @@ async function runStatementImport(deps, args) {
60690
60704
  if (fresh.length === 0)
60691
60705
  return { ...summary, dryRun: false, imported: 0 };
60692
60706
  const savings = account.type !== "credit_card";
60693
- const imported = await importTransactions(client, crypto3, {
60707
+ const imported = await importTransactions(client, crypto4, {
60694
60708
  onProgress: deps.onProgress,
60695
60709
  accountId: account.id,
60696
60710
  source: savings ? "bank_statement" : "cc_statement",
@@ -60703,7 +60717,7 @@ async function runStatementImport(deps, args) {
60703
60717
  referenceNumber: t2.referenceNumber ?? undefined
60704
60718
  }))
60705
60719
  });
60706
- const rescan = await runRescan(client, crypto3);
60720
+ const rescan = await runRescan(client, crypto4);
60707
60721
  return { ...summary, dryRun: false, ...imported, rescan };
60708
60722
  }
60709
60723
 
@@ -60739,16 +60753,16 @@ var statementSource = exports_external.enum([
60739
60753
  "ai_import"
60740
60754
  ]);
60741
60755
  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) {
60756
+ function statementImportTools(client, crypto4) {
60743
60757
  const pdf = (confirm2) => variant(pdfInput, (args, ctx) => runStatementImport({
60744
60758
  client,
60745
- crypto: crypto3,
60759
+ crypto: crypto4,
60746
60760
  readFile: readFileAsArrayBuffer,
60747
60761
  parsers: realParsers,
60748
60762
  onProgress: progressFor(ctx)
60749
60763
  }, confirm2 ? { ...args, confirm: true } : args));
60750
60764
  const csvParse = variant(exports_external.object({ filePath: exports_external.string() }), async ({ filePath }) => {
60751
- if (crypto3)
60765
+ if (crypto4)
60752
60766
  return CSV_PRIVATE_MODE_ERROR;
60753
60767
  const buf = await readFile2(filePath);
60754
60768
  const form = new FormData;
@@ -60783,7 +60797,7 @@ function statementImportTools(client, crypto3) {
60783
60797
  skipDuplicates: exports_external.boolean().optional(),
60784
60798
  force: exports_external.boolean().optional()
60785
60799
  }), async ({ filePath, mapping, accountId, owner, skipDuplicates, force }) => {
60786
- if (crypto3)
60800
+ if (crypto4)
60787
60801
  return CSV_PRIVATE_MODE_ERROR;
60788
60802
  const csvText = await readFile2(filePath, "utf8");
60789
60803
  const data = await client.post("/api/transactions/import/confirm", {
@@ -60808,14 +60822,14 @@ function statementImportTools(client, crypto3) {
60808
60822
  referenceNumber: exports_external.string().optional()
60809
60823
  }))
60810
60824
  }), async ({ accountId, source, granularity: granularity2, transactions: txns }, ctx) => {
60811
- const data = await importTransactions(client, crypto3, {
60825
+ const data = await importTransactions(client, crypto4, {
60812
60826
  onProgress: progressFor(ctx),
60813
60827
  accountId,
60814
60828
  source,
60815
60829
  granularity: granularity2,
60816
60830
  txns
60817
60831
  });
60818
- const rescan = await runRescan(client, crypto3);
60832
+ const rescan = await runRescan(client, crypto4);
60819
60833
  return { import: data, rescan };
60820
60834
  }),
60821
60835
  json_file: variant(exports_external.object({
@@ -60839,14 +60853,14 @@ function statementImportTools(client, crypto3) {
60839
60853
  } catch (e2) {
60840
60854
  throw new Error(`Failed to read/parse ${filePath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
60841
60855
  }
60842
- const data = await importTransactions(client, crypto3, {
60856
+ const data = await importTransactions(client, crypto4, {
60843
60857
  onProgress: progressFor(ctx),
60844
60858
  accountId,
60845
60859
  source,
60846
60860
  granularity: granularity2,
60847
60861
  txns
60848
60862
  });
60849
- const rescan = await runRescan(client, crypto3);
60863
+ const rescan = await runRescan(client, crypto4);
60850
60864
  return { import: data, count: txns.length, rescan };
60851
60865
  })
60852
60866
  }
@@ -61003,7 +61017,7 @@ function registerResources2(server2, tools) {
61003
61017
  // package.json
61004
61018
  var package_default = {
61005
61019
  name: "paisa-mcp",
61006
- version: "0.4.2",
61020
+ version: "0.4.4",
61007
61021
  repository: {
61008
61022
  type: "git",
61009
61023
  url: "git+https://github.com/nimit9/paisa.git",
@@ -61033,13 +61047,12 @@ var package_default = {
61033
61047
  "@nimit9/signet-ai": "^0.1.11",
61034
61048
  "@nimit9/signet-lib": "^0.1.15",
61035
61049
  "@nimit9/signet-server": "^0.2.7",
61050
+ "@paisa/crypto": "workspace:*",
61036
61051
  "@paisa/parsers": "workspace:*",
61037
61052
  "@paisa/reconciliation": "workspace:*",
61038
61053
  "@paisa/types": "workspace:*",
61039
- "@scure/bip39": "^2.2.0",
61040
61054
  "@types/bun": "^1.3.14",
61041
61055
  axios: "^1.16.1",
61042
- "hash-wasm": "^4.12.0",
61043
61056
  typescript: "^7.0.2",
61044
61057
  zod: "^4.6.5"
61045
61058
  },
@@ -61059,8 +61072,8 @@ function paisaErrorResult(err) {
61059
61072
  });
61060
61073
  }
61061
61074
  var CONFIRM_KEY2 = randomBytes(32);
61062
- function buildServer(client, crypto3) {
61063
- const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto3)).map((t2) => {
61075
+ function buildServer(client, crypto4) {
61076
+ const tools = applyAnnotations(REGISTRATIONS.flatMap((group) => group.tools(client, crypto4)).map((t2) => {
61064
61077
  const describe3 = CONFIRMED_TOOLS[t2.name];
61065
61078
  if (describe3)
61066
61079
  return withConfirmation(t2, describe3);
@@ -61103,5 +61116,5 @@ main().catch((err) => {
61103
61116
  process.exit(1);
61104
61117
  });
61105
61118
 
61106
- //# debugId=24F552E300AC132164756E2164756E21
61119
+ //# debugId=0BCBA1F2C8CA2E2E64756E2164756E21
61107
61120
  //# sourceMappingURL=index.js.map