paisa-mcp 0.0.2 → 0.0.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
@@ -42581,21 +42581,23 @@ function registerGoalTools(server, client) {
42581
42581
  inputSchema: {
42582
42582
  type: exports_external.enum(["goal", "emi", "sinking_fund"]),
42583
42583
  id: exports_external.string().optional().describe("[goal] Goal UUID — omit to create, pass to update"),
42584
+ goalType: exports_external.enum(["emergency_fund", "travel", "car", "house", "education", "retirement", "custom"]).optional().describe("[goal] Goal category — required when creating"),
42584
42585
  name: exports_external.string().optional().describe("[goal, emi, sinking_fund] Name"),
42585
42586
  targetAmount: exports_external.string().optional().describe("[goal, sinking_fund] Target amount"),
42586
42587
  targetDate: exports_external.string().optional().describe("[goal, sinking_fund] YYYY-MM-DD"),
42587
42588
  currentAmount: exports_external.string().optional().describe("[goal, sinking_fund] Already saved"),
42589
+ monthlyContribution: exports_external.string().optional().describe("[goal] Monthly contribution"),
42590
+ owner: exports_external.string().optional().describe("[goal, emi] Owner"),
42588
42591
  icon: exports_external.string().optional().describe("[goal] Icon"),
42589
42592
  color: exports_external.string().optional().describe("[goal] Color"),
42593
+ notes: exports_external.string().optional().describe("[goal] Notes"),
42590
42594
  totalAmount: exports_external.string().optional().describe("[emi] Total loan amount"),
42591
42595
  emiAmount: exports_external.string().optional().describe("[emi] Monthly EMI amount"),
42592
42596
  interestRate: exports_external.string().optional().describe("[emi] Interest rate (optional)"),
42593
42597
  startDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD"),
42594
42598
  endDate: exports_external.string().optional().describe("[emi] YYYY-MM-DD last payment date"),
42595
42599
  totalMonths: exports_external.number().optional().describe("[emi] Tenure in months"),
42596
- paidMonths: exports_external.number().optional().describe("[emi] Months already paid"),
42597
- owner: exports_external.string().optional().describe("[emi] Owner"),
42598
- monthlyContribution: exports_external.string().optional().describe("[sinking_fund] Monthly contribution (optional)")
42600
+ paidMonths: exports_external.number().optional().describe("[emi] Months already paid")
42599
42601
  }
42600
42602
  }, async ({ type, ...fields }) => {
42601
42603
  const require2 = (keys) => {
@@ -42605,10 +42607,10 @@ function registerGoalTools(server, client) {
42605
42607
  }
42606
42608
  };
42607
42609
  if (type === "goal") {
42608
- const { id, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, icon, color } = fields;
42609
- const body2 = { name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, icon, color };
42610
+ const { id, goalType, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, monthlyContribution: monthlyContribution2, owner, icon, color, notes } = fields;
42611
+ const body2 = { type: goalType, name: name2, targetAmount: targetAmount2, targetDate: targetDate2, currentAmount: currentAmount2, monthlyContribution: monthlyContribution2, owner, icon, color, notes };
42610
42612
  if (!id)
42611
- require2(["name", "targetAmount"]);
42613
+ require2(["name", "targetAmount", "goalType"]);
42612
42614
  const data2 = id ? await client.patch(`/api/goals/${id}`, body2) : await client.post("/api/goals", body2);
42613
42615
  return { content: [{ type: "text", text: JSON.stringify(data2) }] };
42614
42616
  }
@@ -42635,6 +42637,28 @@ function registerGoalTools(server, client) {
42635
42637
  const data = await client.post("/api/goals/sinking-funds", body);
42636
42638
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
42637
42639
  });
42640
+ server.registerTool("upsert_insurance", {
42641
+ description: "Create or update an insurance policy. Omit id to create (name, type, provider, premiumAmount, premiumFrequency, startDate, owner required). Pass id to update.",
42642
+ inputSchema: {
42643
+ id: exports_external.string().optional().describe("Policy UUID — omit to create, pass to update"),
42644
+ name: exports_external.string().optional(),
42645
+ type: exports_external.enum(["term", "health", "vehicle", "home", "other"]).optional(),
42646
+ provider: exports_external.string().optional(),
42647
+ coverAmount: exports_external.string().optional(),
42648
+ premiumAmount: exports_external.string().optional(),
42649
+ premiumFrequency: exports_external.string().optional().describe('e.g. "yearly", "monthly"'),
42650
+ startDate: exports_external.string().optional().describe("YYYY-MM-DD"),
42651
+ endDate: exports_external.string().optional().describe("YYYY-MM-DD"),
42652
+ renewalDate: exports_external.string().optional().describe("YYYY-MM-DD"),
42653
+ policyNumber: exports_external.string().optional(),
42654
+ owner: exports_external.string().optional(),
42655
+ isActive: exports_external.boolean().optional(),
42656
+ notes: exports_external.string().optional()
42657
+ }
42658
+ }, async ({ id, ...body }) => {
42659
+ const data = id ? await client.patch(`/api/goals/insurance/${id}`, body) : await client.post("/api/goals/insurance", body);
42660
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
42661
+ });
42638
42662
  server.registerTool("contribute_to_goal", {
42639
42663
  description: "Add a contribution to a savings goal.",
42640
42664
  inputSchema: {
@@ -43254,6 +43278,7 @@ var MERCHANT_ALIASES = [
43254
43278
  ["LivPure", ["livpure smart homes", "livpure"]],
43255
43279
  ["HundredX", ["hundredx school", "hundredx"]]
43256
43280
  ];
43281
+ var GATEWAY_PREFIX = /^(paytm|payu|razorpay|cashfree|billdesk|easebuzz|rbl|pyu)\*/i;
43257
43282
  var aliasIndex = [];
43258
43283
  for (const [canonical, aliases] of MERCHANT_ALIASES) {
43259
43284
  for (const alias of aliases) {
@@ -43261,6 +43286,24 @@ for (const [canonical, aliases] of MERCHANT_ALIASES) {
43261
43286
  }
43262
43287
  }
43263
43288
  aliasIndex.sort((a, b) => b.alias.length - a.alias.length);
43289
+ function resolveAlias(name) {
43290
+ let lower = name.toLowerCase();
43291
+ const gwMatch = lower.match(GATEWAY_PREFIX);
43292
+ if (gwMatch) {
43293
+ lower = lower.slice(gwMatch[0].length).trim();
43294
+ }
43295
+ for (const { alias, canonical } of aliasIndex) {
43296
+ if (alias.length <= 4) {
43297
+ const re = new RegExp(`\\b${alias}\\b`);
43298
+ if (re.test(lower))
43299
+ return canonical;
43300
+ } else {
43301
+ if (lower.includes(alias))
43302
+ return canonical;
43303
+ }
43304
+ }
43305
+ return null;
43306
+ }
43264
43307
  // ../../node_modules/.bun/fuzzball@2.2.3/node_modules/fuzzball/dist/esm/fuzzball.esm.min.js
43265
43308
  var e;
43266
43309
  var t = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : {};
@@ -44975,9 +45018,152 @@ var Yo = Ro.process_and_sort;
44975
45018
  var ea = Ro.unique_tokens;
44976
45019
  var ta = Ro.dedupe;
44977
45020
 
45021
+ // ../reconciliation/src/normalise.ts
45022
+ var CORPORATE_SUFFIXES = /\b(PRIVATE|PVT\.?|LIMITED|LTD\.?)\s*\w*/gi;
45023
+ function normaliseName(name) {
45024
+ return name.replace(CORPORATE_SUFFIXES, "").replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
45025
+ }
45026
+
44978
45027
  // ../reconciliation/src/entity.ts
45028
+ var FUZZBALL_THRESHOLD = 92;
44979
45029
  var _aliasReCache = new Map;
45030
+ function getAliasRegex(lower) {
45031
+ let re2 = _aliasReCache.get(lower);
45032
+ if (!re2) {
45033
+ const escaped = lower.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45034
+ re2 = new RegExp(`\\b${escaped}\\b`);
45035
+ _aliasReCache.set(lower, re2);
45036
+ }
45037
+ return re2;
45038
+ }
44980
45039
  var _normCache = new WeakMap;
45040
+ function cachedNormaliseName(opt) {
45041
+ let cached2 = _normCache.get(opt);
45042
+ if (cached2 === undefined) {
45043
+ cached2 = normaliseName(opt.name);
45044
+ _normCache.set(opt, cached2);
45045
+ }
45046
+ return cached2;
45047
+ }
45048
+ function matchUserAlias(normalisedName, aliases) {
45049
+ const sorted = [...aliases].sort((a2, b2) => b2.pattern.length - a2.pattern.length);
45050
+ for (const alias of sorted) {
45051
+ const lower = alias.pattern.toLowerCase();
45052
+ if (lower.length <= 4) {
45053
+ if (getAliasRegex(lower).test(normalisedName))
45054
+ return alias;
45055
+ } else if (normalisedName.includes(lower)) {
45056
+ return alias;
45057
+ }
45058
+ }
45059
+ return null;
45060
+ }
45061
+ function fuzzMatch(normalisedName, options) {
45062
+ let best = null;
45063
+ for (const opt of options) {
45064
+ const candidate = cachedNormaliseName(opt);
45065
+ if (!candidate)
45066
+ continue;
45067
+ const score = Math.max(Fo(normalisedName, candidate), Bo(normalisedName, candidate));
45068
+ if (score >= FUZZBALL_THRESHOLD && (!best || score > best.score)) {
45069
+ best = { row: opt, score };
45070
+ }
45071
+ }
45072
+ return best?.row ?? null;
45073
+ }
45074
+ function resolveMerchant(normalisedName, ctx) {
45075
+ const canonical = resolveAlias(normalisedName);
45076
+ if (canonical) {
45077
+ const normCanonical = normaliseName(canonical);
45078
+ const match = ctx.globalMerchants.find((m2) => cachedNormaliseName(m2) === normCanonical);
45079
+ if (match)
45080
+ return { type: "merchant", merchantId: match.id };
45081
+ }
45082
+ const userAlias = matchUserAlias(normalisedName, ctx.userMerchantAliases);
45083
+ if (userAlias)
45084
+ return { type: "merchant", merchantId: userAlias.merchantId };
45085
+ const fuzzMerchant = fuzzMatch(normalisedName, [...ctx.globalMerchants, ...ctx.localMerchants]);
45086
+ if (fuzzMerchant)
45087
+ return { type: "merchant", merchantId: fuzzMerchant.id };
45088
+ return null;
45089
+ }
45090
+ function resolvePerson(normalisedName, ctx) {
45091
+ const userAlias = matchUserAlias(normalisedName, ctx.userPersonAliases);
45092
+ if (userAlias)
45093
+ return { type: "person", personId: userAlias.personId };
45094
+ const fuzzPerson = fuzzMatch(normalisedName, ctx.persons);
45095
+ if (fuzzPerson)
45096
+ return { type: "person", personId: fuzzPerson.id };
45097
+ return null;
45098
+ }
45099
+ function resolveEntity(name, hint, ctx) {
45100
+ const normalised = normaliseName(name);
45101
+ if (!normalised)
45102
+ return null;
45103
+ if (hint === "person") {
45104
+ return resolvePerson(normalised, ctx) ?? resolveMerchant(normalised, ctx) ?? null;
45105
+ }
45106
+ if (hint === "merchant") {
45107
+ return resolveMerchant(normalised, ctx) ?? null;
45108
+ }
45109
+ return resolveMerchant(normalised, ctx) ?? resolvePerson(normalised, ctx) ?? null;
45110
+ }
45111
+
45112
+ // ../reconciliation/src/categorize.ts
45113
+ function autoCategorizeFromDescriptions(ctx) {
45114
+ const resolveCtx = {
45115
+ globalMerchants: ctx.globalMerchants,
45116
+ localMerchants: ctx.localMerchants,
45117
+ persons: ctx.persons,
45118
+ userMerchantAliases: ctx.bag.merchantAliases,
45119
+ userPersonAliases: ctx.bag.personAliases
45120
+ };
45121
+ const groups = new Map;
45122
+ let unresolvedCount = 0;
45123
+ let totalAffected = 0;
45124
+ for (const row of ctx.rows) {
45125
+ if (row.isTransfer || row.isIgnored)
45126
+ continue;
45127
+ let entity = null;
45128
+ if (row.merchantId)
45129
+ entity = { type: "merchant", merchantId: row.merchantId };
45130
+ else if (row.personId)
45131
+ entity = { type: "person", personId: row.personId };
45132
+ else
45133
+ entity = resolveEntity(row.description, "unknown", resolveCtx);
45134
+ if (!entity) {
45135
+ unresolvedCount += 1;
45136
+ continue;
45137
+ }
45138
+ const rule = entityRule(entity, ctx.bag);
45139
+ if (row.categoryId && rule && rule.categoryId === row.categoryId)
45140
+ continue;
45141
+ const needsLink = !row.merchantId && !row.personId;
45142
+ const needsCategory = !row.categoryId && rule != null;
45143
+ if (!needsLink && !needsCategory)
45144
+ continue;
45145
+ const key = entityKey(entity);
45146
+ const existing = groups.get(key);
45147
+ const suggestion = existing ?? {
45148
+ entity,
45149
+ source: rule ? "category-rule" : "alias-only",
45150
+ categoryId: rule?.categoryId ?? null,
45151
+ transactionIds: []
45152
+ };
45153
+ suggestion.transactionIds.push(row.id);
45154
+ groups.set(key, suggestion);
45155
+ totalAffected += 1;
45156
+ }
45157
+ return { suggestions: Array.from(groups.values()), totalAffected, unresolvedCount };
45158
+ }
45159
+ function entityKey(e2) {
45160
+ return e2.type === "merchant" ? `m:${e2.merchantId}` : `p:${e2.personId}`;
45161
+ }
45162
+ function entityRule(e2, bag) {
45163
+ if (e2.type === "merchant")
45164
+ return bag.merchantRules[e2.merchantId] ?? null;
45165
+ return bag.personRules[e2.personId] ?? null;
45166
+ }
44981
45167
  // ../reconciliation/src/settlement.ts
44982
45168
  var SETTLEMENT_PATTERNS = [
44983
45169
  /\bCRED\b.*\bPAYMENT\b/i,
@@ -45055,6 +45241,45 @@ function settlementEnrichedData(description, type, source) {
45055
45241
  return out;
45056
45242
  }
45057
45243
  // src/tools/transactions.ts
45244
+ async function runRescan(client) {
45245
+ const learningRes = await client.get("/api/household-learning");
45246
+ const bag = learningRes.data?.bag ?? {
45247
+ merchantAliases: [],
45248
+ personAliases: [],
45249
+ merchantRules: {},
45250
+ personRules: {}
45251
+ };
45252
+ const merchantsRes = await client.get("/api/merchants");
45253
+ const allMerchants = merchantsRes.data ?? [];
45254
+ const globalMerchants = allMerchants.filter((m2) => !m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
45255
+ const localMerchants = allMerchants.filter((m2) => m2.householdId && m2.cleanName).map((m2) => ({ id: m2.id, name: m2.cleanName }));
45256
+ const personsRes = await client.get("/api/persons");
45257
+ const persons = (personsRes.data ?? []).filter((p2) => p2.name);
45258
+ const allRows = [];
45259
+ let offset = 0;
45260
+ const limit = 500;
45261
+ while (true) {
45262
+ const res = await client.get("/api/transactions", { limit, offset });
45263
+ const rows = res.data ?? [];
45264
+ allRows.push(...rows);
45265
+ if (!res.hasMore || rows.length === 0)
45266
+ break;
45267
+ offset += limit;
45268
+ }
45269
+ const plan = autoCategorizeFromDescriptions({ rows: allRows, bag, globalMerchants, localMerchants, persons });
45270
+ const links = plan.suggestions.filter((s2) => s2.categoryId).flatMap((s2) => s2.transactionIds.map((txId) => ({
45271
+ transactionId: txId,
45272
+ ...s2.entity.type === "merchant" ? { merchantId: s2.entity.merchantId } : { personId: s2.entity.personId },
45273
+ categoryId: s2.categoryId
45274
+ })));
45275
+ if (links.length > 0) {
45276
+ const CHUNK = 500;
45277
+ for (let i2 = 0;i2 < links.length; i2 += CHUNK) {
45278
+ await client.post("/api/transactions/bulk-link-entity", { links: links.slice(i2, i2 + CHUNK) });
45279
+ }
45280
+ }
45281
+ return { linked: links.length, unresolved: plan.unresolvedCount };
45282
+ }
45058
45283
  function registerTransactionTools(server, client, crypto3) {
45059
45284
  server.registerTool("get_transactions", {
45060
45285
  description: "Search and filter transactions. Returns paginated results with merchant and category details.",
@@ -45179,7 +45404,15 @@ function registerTransactionTools(server, client, crypto3) {
45179
45404
  granularity,
45180
45405
  transactions: rows
45181
45406
  });
45182
- return { content: [{ type: "text", text: JSON.stringify(data) }] };
45407
+ const rescan = await runRescan(client);
45408
+ return {
45409
+ content: [
45410
+ {
45411
+ type: "text",
45412
+ text: JSON.stringify({ import: data, rescan })
45413
+ }
45414
+ ]
45415
+ };
45183
45416
  });
45184
45417
  server.registerTool("delete_transactions", {
45185
45418
  description: "Hard-delete transactions. This is PERMANENT and cannot be undone. " + "Provide exactly one of: `id` (single transaction), `ids` (a list of transactions), " + "or `filters` (deletes all matching transactions). Returns the number of deleted transactions " + "for the bulk paths.",
@@ -45208,6 +45441,13 @@ function registerTransactionTools(server, client, crypto3) {
45208
45441
  const data = await client.delete("/api/transactions/bulk", body);
45209
45442
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
45210
45443
  });
45444
+ server.registerTool("rescan_transactions", {
45445
+ description: "Apply the household's saved learning rules (merchant aliases + category rules) to all uncategorized transactions. " + "Equivalent to the browser's auto-categorize flow. Run this after importing statements via MCP to populate merchants and categories.",
45446
+ inputSchema: {}
45447
+ }, async () => {
45448
+ const result = await runRescan(client);
45449
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
45450
+ });
45211
45451
  server.registerTool("get_settlement_candidates", {
45212
45452
  description: "Find bank debit transactions that look like bill payments for a specific credit card account. " + "Call this after importing a CC statement to identify which existing bank debits should be marked " + "as transfers (to avoid double-counting expenses). Returns transactions with their account name, " + "date, amount, and description.",
45213
45453
  inputSchema: {
@@ -45311,5 +45551,5 @@ main().catch((err) => {
45311
45551
  process.exit(1);
45312
45552
  });
45313
45553
 
45314
- //# debugId=671EA523E40384C364756E2164756E21
45554
+ //# debugId=E58A7271779D454264756E2164756E21
45315
45555
  //# sourceMappingURL=index.js.map