@elizaos/plugin-finances 2.0.3-beta.5 → 2.0.3-beta.7

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.
Files changed (103) hide show
  1. package/dist/actions/finances.d.ts +38 -0
  2. package/dist/actions/finances.d.ts.map +1 -0
  3. package/dist/actions/finances.js +368 -0
  4. package/dist/actions/finances.js.map +1 -0
  5. package/dist/components/finances/FinancesSpatialView.d.ts +80 -0
  6. package/dist/components/finances/FinancesSpatialView.d.ts.map +1 -0
  7. package/dist/components/finances/FinancesSpatialView.js +157 -0
  8. package/dist/components/finances/FinancesSpatialView.js.map +1 -0
  9. package/dist/components/finances/FinancesView.d.ts +97 -0
  10. package/dist/components/finances/FinancesView.d.ts.map +1 -0
  11. package/dist/components/finances/FinancesView.js +231 -0
  12. package/dist/components/finances/FinancesView.js.map +1 -0
  13. package/dist/components/finances/finances-view-bundle.d.ts +10 -0
  14. package/dist/components/finances/finances-view-bundle.d.ts.map +1 -0
  15. package/dist/components/finances/finances-view-bundle.js +5 -0
  16. package/dist/components/finances/finances-view-bundle.js.map +1 -0
  17. package/dist/db/finances-repository.d.ts +51 -0
  18. package/dist/db/finances-repository.d.ts.map +1 -0
  19. package/dist/db/finances-repository.js +521 -0
  20. package/dist/db/finances-repository.js.map +1 -0
  21. package/dist/db/index.d.ts +3 -0
  22. package/dist/db/index.d.ts.map +1 -0
  23. package/dist/db/index.js +6 -0
  24. package/dist/db/index.js.map +1 -0
  25. package/dist/db/schema.d.ts +2615 -0
  26. package/dist/db/schema.d.ts.map +1 -0
  27. package/dist/db/schema.js +133 -0
  28. package/dist/db/schema.js.map +1 -0
  29. package/dist/db/sql.d.ts +65 -0
  30. package/dist/db/sql.d.ts.map +1 -0
  31. package/dist/db/sql.js +182 -0
  32. package/dist/db/sql.js.map +1 -0
  33. package/dist/finance-normalize.d.ts +24 -0
  34. package/dist/finance-normalize.d.ts.map +1 -0
  35. package/dist/finance-normalize.js +66 -0
  36. package/dist/finance-normalize.js.map +1 -0
  37. package/dist/finances-service.d.ts +179 -0
  38. package/dist/finances-service.d.ts.map +1 -0
  39. package/dist/finances-service.js +1122 -0
  40. package/dist/finances-service.js.map +1 -0
  41. package/dist/index.d.ts +32 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +109 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/payment-csv-import.d.ts +23 -0
  46. package/dist/payment-csv-import.d.ts.map +1 -0
  47. package/dist/payment-csv-import.js +271 -0
  48. package/dist/payment-csv-import.js.map +1 -0
  49. package/dist/payment-recurrence.d.ts +14 -0
  50. package/dist/payment-recurrence.d.ts.map +1 -0
  51. package/dist/payment-recurrence.js +190 -0
  52. package/dist/payment-recurrence.js.map +1 -0
  53. package/dist/payment-types.d.ts +158 -0
  54. package/dist/payment-types.d.ts.map +1 -0
  55. package/dist/payment-types.js +1 -0
  56. package/dist/payment-types.js.map +1 -0
  57. package/dist/plugin.d.ts +15 -0
  58. package/dist/plugin.d.ts.map +1 -0
  59. package/dist/plugin.js +31 -0
  60. package/dist/plugin.js.map +1 -0
  61. package/dist/register-terminal-view.d.ts +15 -0
  62. package/dist/register-terminal-view.d.ts.map +1 -0
  63. package/dist/register-terminal-view.js +21 -0
  64. package/dist/register-terminal-view.js.map +1 -0
  65. package/dist/register.d.ts +9 -0
  66. package/dist/register.d.ts.map +1 -0
  67. package/dist/register.js +5 -0
  68. package/dist/register.js.map +1 -0
  69. package/dist/services/browser-bridge-seam.d.ts +40 -0
  70. package/dist/services/browser-bridge-seam.d.ts.map +1 -0
  71. package/dist/services/browser-bridge-seam.js +39 -0
  72. package/dist/services/browser-bridge-seam.js.map +1 -0
  73. package/dist/services/gmail-seam.d.ts +40 -0
  74. package/dist/services/gmail-seam.d.ts.map +1 -0
  75. package/dist/services/gmail-seam.js +208 -0
  76. package/dist/services/gmail-seam.js.map +1 -0
  77. package/dist/services/migration.d.ts +65 -0
  78. package/dist/services/migration.d.ts.map +1 -0
  79. package/dist/services/migration.js +116 -0
  80. package/dist/services/migration.js.map +1 -0
  81. package/dist/services/subscriptions-service.d.ts +76 -0
  82. package/dist/services/subscriptions-service.d.ts.map +1 -0
  83. package/dist/services/subscriptions-service.js +1002 -0
  84. package/dist/services/subscriptions-service.js.map +1 -0
  85. package/dist/subscriptions-playbooks.d.ts +79 -0
  86. package/dist/subscriptions-playbooks.d.ts.map +1 -0
  87. package/dist/subscriptions-playbooks.js +871 -0
  88. package/dist/subscriptions-playbooks.js.map +1 -0
  89. package/dist/subscriptions-types.d.ts +80 -0
  90. package/dist/subscriptions-types.d.ts.map +1 -0
  91. package/dist/subscriptions-types.js +1 -0
  92. package/dist/subscriptions-types.js.map +1 -0
  93. package/dist/token-encryption.d.ts +42 -0
  94. package/dist/token-encryption.d.ts.map +1 -0
  95. package/dist/token-encryption.js +96 -0
  96. package/dist/token-encryption.js.map +1 -0
  97. package/dist/types.d.ts +55 -0
  98. package/dist/types.d.ts.map +1 -0
  99. package/dist/types.js +18 -0
  100. package/dist/types.js.map +1 -0
  101. package/dist/views/bundle.js +411 -0
  102. package/dist/views/bundle.js.map +1 -0
  103. package/package.json +11 -11
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/payment-csv-import.ts"],"sourcesContent":["import { normalizeMerchant } from \"./payment-recurrence.js\";\nimport type {\n LifeOpsPaymentDirection,\n LifeOpsPaymentTransaction,\n} from \"./payment-types.js\";\n\nconst DATE_COLUMN_HINTS = [\"date\", \"posted\", \"posted date\", \"transaction date\"];\n// Only single-amount formats match here. Separate Debit/Credit columns are\n// handled below via DEBIT_COLUMN_HINTS / CREDIT_COLUMN_HINTS so we don't\n// collapse them into a single amount column.\nconst AMOUNT_COLUMN_HINTS = [\"amount\", \"amount (usd)\", \"transaction amount\"];\nconst DEBIT_COLUMN_HINTS = [\"debit\", \"withdrawal\", \"amount debit\"];\nconst CREDIT_COLUMN_HINTS = [\"credit\", \"deposit\", \"amount credit\"];\nconst MERCHANT_COLUMN_HINTS = [\n \"merchant\",\n \"payee\",\n \"name\",\n \"description\",\n \"memo\",\n \"details\",\n];\nconst CATEGORY_COLUMN_HINTS = [\n \"category\",\n \"transaction category\",\n \"plaid category\",\n];\n\n/**\n * Minimal RFC 4180 CSV parser. Handles quoted fields with embedded commas\n * and escaped double quotes. Returns rows as string arrays.\n */\nexport function parseCsv(text: string): string[][] {\n const rows: string[][] = [];\n let current: string[] = [];\n let field = \"\";\n let inQuotes = false;\n let index = 0;\n while (index < text.length) {\n const char = text[index];\n if (inQuotes) {\n if (char === '\"') {\n if (text[index + 1] === '\"') {\n field += '\"';\n index += 2;\n continue;\n }\n inQuotes = false;\n index += 1;\n continue;\n }\n field += char;\n index += 1;\n continue;\n }\n if (char === '\"') {\n inQuotes = true;\n index += 1;\n continue;\n }\n if (char === \",\") {\n current.push(field);\n field = \"\";\n index += 1;\n continue;\n }\n if (char === \"\\r\") {\n // Ignore — the \\n branch below does the newline handling.\n index += 1;\n continue;\n }\n if (char === \"\\n\") {\n current.push(field);\n rows.push(current);\n current = [];\n field = \"\";\n index += 1;\n continue;\n }\n field += char;\n index += 1;\n }\n if (field.length > 0 || current.length > 0) {\n current.push(field);\n rows.push(current);\n }\n return rows.filter((row) => row.some((value) => value.trim().length > 0));\n}\n\nfunction findColumn(\n header: readonly string[],\n hints: readonly string[],\n): number {\n for (let index = 0; index < header.length; index += 1) {\n const normalized = header[index].trim().toLowerCase();\n if (hints.includes(normalized)) {\n return index;\n }\n }\n for (let index = 0; index < header.length; index += 1) {\n const normalized = header[index].trim().toLowerCase();\n for (const hint of hints) {\n if (normalized.includes(hint)) {\n return index;\n }\n }\n }\n return -1;\n}\n\nfunction parseAmount(\n row: readonly string[],\n amountIndex: number,\n debitIndex: number,\n creditIndex: number,\n): { amountUsd: number; direction: LifeOpsPaymentDirection } | null {\n const readNumber = (raw: string | undefined): number | null => {\n if (raw === undefined) {\n return null;\n }\n const cleaned = raw.replace(/[$,]/g, \"\").trim();\n if (!cleaned) {\n return null;\n }\n // Handle \"(12.34)\" accounting negatives.\n const negative = /^\\(.+\\)$/.test(cleaned);\n const value = Number(cleaned.replace(/[()]/g, \"\"));\n if (!Number.isFinite(value)) {\n return null;\n }\n return negative ? -value : value;\n };\n\n if (amountIndex >= 0) {\n const amount = readNumber(row[amountIndex]);\n if (amount === null) {\n return null;\n }\n return {\n amountUsd: Math.abs(amount),\n direction: amount < 0 ? \"debit\" : \"credit\",\n };\n }\n if (debitIndex >= 0) {\n const debit = readNumber(row[debitIndex]);\n if (debit !== null && debit !== 0) {\n return { amountUsd: Math.abs(debit), direction: \"debit\" };\n }\n }\n if (creditIndex >= 0) {\n const credit = readNumber(row[creditIndex]);\n if (credit !== null && credit !== 0) {\n return { amountUsd: Math.abs(credit), direction: \"credit\" };\n }\n }\n return null;\n}\n\nfunction normalizeDate(raw: string): string | null {\n const trimmed = raw.trim();\n if (!trimmed) {\n return null;\n }\n // Try native parse first. Falls back to YYYY-MM-DD and MM/DD/YYYY.\n const native = Date.parse(trimmed);\n if (Number.isFinite(native)) {\n return new Date(native).toISOString();\n }\n const isoMatch = trimmed.match(/^(\\d{4})-(\\d{2})-(\\d{2})/);\n if (isoMatch) {\n return new Date(\n Date.UTC(\n Number(isoMatch[1]),\n Number(isoMatch[2]) - 1,\n Number(isoMatch[3]),\n ),\n ).toISOString();\n }\n const usMatch = trimmed.match(/^(\\d{1,2})[/-](\\d{1,2})[/-](\\d{2,4})/);\n if (usMatch) {\n const month = Number(usMatch[1]);\n const day = Number(usMatch[2]);\n const rawYear = Number(usMatch[3]);\n const year = rawYear < 100 ? 2000 + rawYear : rawYear;\n return new Date(Date.UTC(year, month - 1, day)).toISOString();\n }\n return null;\n}\n\nexport interface ParsedCsvTransaction\n extends Pick<\n LifeOpsPaymentTransaction,\n | \"postedAt\"\n | \"amountUsd\"\n | \"direction\"\n | \"merchantRaw\"\n | \"merchantNormalized\"\n | \"description\"\n | \"category\"\n | \"currency\"\n | \"externalId\"\n > {\n rowIndex: number;\n}\n\nexport interface ParseCsvOptions {\n dateColumn?: string;\n amountColumn?: string;\n merchantColumn?: string;\n descriptionColumn?: string;\n categoryColumn?: string;\n}\n\nexport interface ParseCsvResult {\n transactions: ParsedCsvTransaction[];\n rowsRead: number;\n errors: string[];\n}\n\nfunction resolveColumnIndex(\n header: readonly string[],\n hint: string | undefined,\n fallbackHints: readonly string[],\n): number {\n if (hint) {\n const explicitIndex = header.findIndex(\n (value) => value.trim().toLowerCase() === hint.trim().toLowerCase(),\n );\n if (explicitIndex >= 0) {\n return explicitIndex;\n }\n }\n return findColumn(header, fallbackHints);\n}\n\nexport function parseTransactionsCsv(\n csvText: string,\n options: ParseCsvOptions = {},\n): ParseCsvResult {\n const rows = parseCsv(csvText);\n if (rows.length < 2) {\n return {\n transactions: [],\n rowsRead: Math.max(0, rows.length - 1),\n errors: [\"CSV has no data rows.\"],\n };\n }\n const header = rows[0].map((value) => value.trim());\n const dateIndex = resolveColumnIndex(\n header,\n options.dateColumn,\n DATE_COLUMN_HINTS,\n );\n const amountIndex = resolveColumnIndex(\n header,\n options.amountColumn,\n AMOUNT_COLUMN_HINTS,\n );\n const debitIndex = findColumn(header, DEBIT_COLUMN_HINTS);\n const creditIndex = findColumn(header, CREDIT_COLUMN_HINTS);\n const merchantIndex = resolveColumnIndex(\n header,\n options.merchantColumn,\n MERCHANT_COLUMN_HINTS,\n );\n const descriptionIndex = resolveColumnIndex(\n header,\n options.descriptionColumn,\n [\"description\", \"memo\", \"details\"],\n );\n const categoryIndex = resolveColumnIndex(\n header,\n options.categoryColumn,\n CATEGORY_COLUMN_HINTS,\n );\n\n const errors: string[] = [];\n if (dateIndex < 0) {\n errors.push(\"Could not find a date column in the CSV header.\");\n }\n if (amountIndex < 0 && debitIndex < 0 && creditIndex < 0) {\n errors.push(\n \"Could not find an amount/debit/credit column in the CSV header.\",\n );\n }\n if (merchantIndex < 0) {\n errors.push(\"Could not find a merchant / payee / description column.\");\n }\n\n const transactions: ParsedCsvTransaction[] = [];\n if (dateIndex < 0 || merchantIndex < 0) {\n return { transactions, rowsRead: rows.length - 1, errors };\n }\n\n for (let rowIndex = 1; rowIndex < rows.length; rowIndex += 1) {\n const row = rows[rowIndex];\n if (row.length === 0) {\n continue;\n }\n const postedAt = normalizeDate(row[dateIndex] ?? \"\");\n if (!postedAt) {\n errors.push(\n `Row ${rowIndex + 1}: unparseable date \"${row[dateIndex] ?? \"\"}\".`,\n );\n continue;\n }\n const amount = parseAmount(row, amountIndex, debitIndex, creditIndex);\n if (!amount) {\n errors.push(`Row ${rowIndex + 1}: unparseable amount.`);\n continue;\n }\n const merchantRaw = (row[merchantIndex] ?? \"\").trim();\n if (!merchantRaw) {\n errors.push(`Row ${rowIndex + 1}: empty merchant.`);\n continue;\n }\n const description =\n descriptionIndex >= 0 ? (row[descriptionIndex] ?? \"\").trim() : \"\";\n const category =\n categoryIndex >= 0 ? (row[categoryIndex] ?? \"\").trim() : \"\";\n transactions.push({\n postedAt,\n amountUsd: amount.amountUsd,\n direction: amount.direction,\n merchantRaw,\n merchantNormalized: normalizeMerchant(merchantRaw),\n description: description.length > 0 ? description : null,\n category: category.length > 0 ? category : null,\n currency: \"USD\",\n externalId: null,\n rowIndex,\n });\n }\n\n return {\n transactions,\n rowsRead: rows.length - 1,\n errors,\n };\n}\n"],"mappings":"AAAA,SAAS,yBAAyB;AAMlC,MAAM,oBAAoB,CAAC,QAAQ,UAAU,eAAe,kBAAkB;AAI9E,MAAM,sBAAsB,CAAC,UAAU,gBAAgB,oBAAoB;AAC3E,MAAM,qBAAqB,CAAC,SAAS,cAAc,cAAc;AACjE,MAAM,sBAAsB,CAAC,UAAU,WAAW,eAAe;AACjE,MAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACA,MAAM,wBAAwB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AACF;AAMO,SAAS,SAAS,MAA0B;AACjD,QAAM,OAAmB,CAAC;AAC1B,MAAI,UAAoB,CAAC;AACzB,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,MAAI,QAAQ;AACZ,SAAO,QAAQ,KAAK,QAAQ;AAC1B,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,UAAU;AACZ,UAAI,SAAS,KAAK;AAChB,YAAI,KAAK,QAAQ,CAAC,MAAM,KAAK;AAC3B,mBAAS;AACT,mBAAS;AACT;AAAA,QACF;AACA,mBAAW;AACX,iBAAS;AACT;AAAA,MACF;AACA,eAAS;AACT,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,KAAK;AAChB,iBAAW;AACX,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,KAAK;AAChB,cAAQ,KAAK,KAAK;AAClB,cAAQ;AACR,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,MAAM;AAEjB,eAAS;AACT;AAAA,IACF;AACA,QAAI,SAAS,MAAM;AACjB,cAAQ,KAAK,KAAK;AAClB,WAAK,KAAK,OAAO;AACjB,gBAAU,CAAC;AACX,cAAQ;AACR,eAAS;AACT;AAAA,IACF;AACA,aAAS;AACT,aAAS;AAAA,EACX;AACA,MAAI,MAAM,SAAS,KAAK,QAAQ,SAAS,GAAG;AAC1C,YAAQ,KAAK,KAAK;AAClB,SAAK,KAAK,OAAO;AAAA,EACnB;AACA,SAAO,KAAK,OAAO,CAAC,QAAQ,IAAI,KAAK,CAAC,UAAU,MAAM,KAAK,EAAE,SAAS,CAAC,CAAC;AAC1E;AAEA,SAAS,WACP,QACA,OACQ;AACR,WAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;AACrD,UAAM,aAAa,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY;AACpD,QAAI,MAAM,SAAS,UAAU,GAAG;AAC9B,aAAO;AAAA,IACT;AAAA,EACF;AACA,WAAS,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;AACrD,UAAM,aAAa,OAAO,KAAK,EAAE,KAAK,EAAE,YAAY;AACpD,eAAW,QAAQ,OAAO;AACxB,UAAI,WAAW,SAAS,IAAI,GAAG;AAC7B,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,YACP,KACA,aACA,YACA,aACkE;AAClE,QAAM,aAAa,CAAC,QAA2C;AAC7D,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,UAAM,UAAU,IAAI,QAAQ,SAAS,EAAE,EAAE,KAAK;AAC9C,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,WAAW,KAAK,OAAO;AACxC,UAAM,QAAQ,OAAO,QAAQ,QAAQ,SAAS,EAAE,CAAC;AACjD,QAAI,CAAC,OAAO,SAAS,KAAK,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,WAAO,WAAW,CAAC,QAAQ;AAAA,EAC7B;AAEA,MAAI,eAAe,GAAG;AACpB,UAAM,SAAS,WAAW,IAAI,WAAW,CAAC;AAC1C,QAAI,WAAW,MAAM;AACnB,aAAO;AAAA,IACT;AACA,WAAO;AAAA,MACL,WAAW,KAAK,IAAI,MAAM;AAAA,MAC1B,WAAW,SAAS,IAAI,UAAU;AAAA,IACpC;AAAA,EACF;AACA,MAAI,cAAc,GAAG;AACnB,UAAM,QAAQ,WAAW,IAAI,UAAU,CAAC;AACxC,QAAI,UAAU,QAAQ,UAAU,GAAG;AACjC,aAAO,EAAE,WAAW,KAAK,IAAI,KAAK,GAAG,WAAW,QAAQ;AAAA,IAC1D;AAAA,EACF;AACA,MAAI,eAAe,GAAG;AACpB,UAAM,SAAS,WAAW,IAAI,WAAW,CAAC;AAC1C,QAAI,WAAW,QAAQ,WAAW,GAAG;AACnC,aAAO,EAAE,WAAW,KAAK,IAAI,MAAM,GAAG,WAAW,SAAS;AAAA,IAC5D;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,KAA4B;AACjD,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,EACT;AAEA,QAAM,SAAS,KAAK,MAAM,OAAO;AACjC,MAAI,OAAO,SAAS,MAAM,GAAG;AAC3B,WAAO,IAAI,KAAK,MAAM,EAAE,YAAY;AAAA,EACtC;AACA,QAAM,WAAW,QAAQ,MAAM,0BAA0B;AACzD,MAAI,UAAU;AACZ,WAAO,IAAI;AAAA,MACT,KAAK;AAAA,QACH,OAAO,SAAS,CAAC,CAAC;AAAA,QAClB,OAAO,SAAS,CAAC,CAAC,IAAI;AAAA,QACtB,OAAO,SAAS,CAAC,CAAC;AAAA,MACpB;AAAA,IACF,EAAE,YAAY;AAAA,EAChB;AACA,QAAM,UAAU,QAAQ,MAAM,sCAAsC;AACpE,MAAI,SAAS;AACX,UAAM,QAAQ,OAAO,QAAQ,CAAC,CAAC;AAC/B,UAAM,MAAM,OAAO,QAAQ,CAAC,CAAC;AAC7B,UAAM,UAAU,OAAO,QAAQ,CAAC,CAAC;AACjC,UAAM,OAAO,UAAU,MAAM,MAAO,UAAU;AAC9C,WAAO,IAAI,KAAK,KAAK,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC,EAAE,YAAY;AAAA,EAC9D;AACA,SAAO;AACT;AAgCA,SAAS,mBACP,QACA,MACA,eACQ;AACR,MAAI,MAAM;AACR,UAAM,gBAAgB,OAAO;AAAA,MAC3B,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,MAAM,KAAK,KAAK,EAAE,YAAY;AAAA,IACpE;AACA,QAAI,iBAAiB,GAAG;AACtB,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO,WAAW,QAAQ,aAAa;AACzC;AAEO,SAAS,qBACd,SACA,UAA2B,CAAC,GACZ;AAChB,QAAM,OAAO,SAAS,OAAO;AAC7B,MAAI,KAAK,SAAS,GAAG;AACnB,WAAO;AAAA,MACL,cAAc,CAAC;AAAA,MACf,UAAU,KAAK,IAAI,GAAG,KAAK,SAAS,CAAC;AAAA,MACrC,QAAQ,CAAC,uBAAuB;AAAA,IAClC;AAAA,EACF;AACA,QAAM,SAAS,KAAK,CAAC,EAAE,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC;AAClD,QAAM,YAAY;AAAA,IAChB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACA,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACA,QAAM,aAAa,WAAW,QAAQ,kBAAkB;AACxD,QAAM,cAAc,WAAW,QAAQ,mBAAmB;AAC1D,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AACA,QAAM,mBAAmB;AAAA,IACvB;AAAA,IACA,QAAQ;AAAA,IACR,CAAC,eAAe,QAAQ,SAAS;AAAA,EACnC;AACA,QAAM,gBAAgB;AAAA,IACpB;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF;AAEA,QAAM,SAAmB,CAAC;AAC1B,MAAI,YAAY,GAAG;AACjB,WAAO,KAAK,iDAAiD;AAAA,EAC/D;AACA,MAAI,cAAc,KAAK,aAAa,KAAK,cAAc,GAAG;AACxD,WAAO;AAAA,MACL;AAAA,IACF;AAAA,EACF;AACA,MAAI,gBAAgB,GAAG;AACrB,WAAO,KAAK,yDAAyD;AAAA,EACvE;AAEA,QAAM,eAAuC,CAAC;AAC9C,MAAI,YAAY,KAAK,gBAAgB,GAAG;AACtC,WAAO,EAAE,cAAc,UAAU,KAAK,SAAS,GAAG,OAAO;AAAA,EAC3D;AAEA,WAAS,WAAW,GAAG,WAAW,KAAK,QAAQ,YAAY,GAAG;AAC5D,UAAM,MAAM,KAAK,QAAQ;AACzB,QAAI,IAAI,WAAW,GAAG;AACpB;AAAA,IACF;AACA,UAAM,WAAW,cAAc,IAAI,SAAS,KAAK,EAAE;AACnD,QAAI,CAAC,UAAU;AACb,aAAO;AAAA,QACL,OAAO,WAAW,CAAC,uBAAuB,IAAI,SAAS,KAAK,EAAE;AAAA,MAChE;AACA;AAAA,IACF;AACA,UAAM,SAAS,YAAY,KAAK,aAAa,YAAY,WAAW;AACpE,QAAI,CAAC,QAAQ;AACX,aAAO,KAAK,OAAO,WAAW,CAAC,uBAAuB;AACtD;AAAA,IACF;AACA,UAAM,eAAe,IAAI,aAAa,KAAK,IAAI,KAAK;AACpD,QAAI,CAAC,aAAa;AAChB,aAAO,KAAK,OAAO,WAAW,CAAC,mBAAmB;AAClD;AAAA,IACF;AACA,UAAM,cACJ,oBAAoB,KAAK,IAAI,gBAAgB,KAAK,IAAI,KAAK,IAAI;AACjE,UAAM,WACJ,iBAAiB,KAAK,IAAI,aAAa,KAAK,IAAI,KAAK,IAAI;AAC3D,iBAAa,KAAK;AAAA,MAChB;AAAA,MACA,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB;AAAA,MACA,oBAAoB,kBAAkB,WAAW;AAAA,MACjD,aAAa,YAAY,SAAS,IAAI,cAAc;AAAA,MACpD,UAAU,SAAS,SAAS,IAAI,WAAW;AAAA,MAC3C,UAAU;AAAA,MACV,YAAY;AAAA,MACZ;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA,UAAU,KAAK,SAAS;AAAA,IACxB;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,14 @@
1
+ import type { LifeOpsPaymentTransaction, LifeOpsRecurringCharge } from "./payment-types.js";
2
+ /**
3
+ * Normalize a merchant string for grouping. Strips trailing location/ref
4
+ * suffixes, collapses whitespace, removes non-alpha noise, lowercases.
5
+ *
6
+ * Bank feeds commonly produce variants like:
7
+ * - "NETFLIX.COM 866-579-7172 CA"
8
+ * - "NETFLIX.COM #8432"
9
+ * - "Netflix Monthly 11.99"
10
+ * We want all three to collapse to "netflix".
11
+ */
12
+ export declare function normalizeMerchant(raw: string): string;
13
+ export declare function detectRecurringCharges(transactions: readonly LifeOpsPaymentTransaction[]): LifeOpsRecurringCharge[];
14
+ //# sourceMappingURL=payment-recurrence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-recurrence.d.ts","sourceRoot":"","sources":["../src/payment-recurrence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,yBAAyB,EAEzB,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAW5B;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBrD;AAwID,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,SAAS,yBAAyB,EAAE,GACjD,sBAAsB,EAAE,CAiF1B"}
@@ -0,0 +1,190 @@
1
+ const MIN_RECURRENCE_OCCURRENCES = 2;
2
+ const MS_PER_DAY = 864e5;
3
+ function normalizeMerchant(raw) {
4
+ const trimmed = raw.toLowerCase().replace(/\.(com|co|io|net|org|tv|app)\b/g, " ").replace(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g, " ").replace(/#\s*\d+/g, " ").replace(/\bpos\b/g, " ").replace(/\$?\d+(?:\.\d{2})?/g, " ").replace(/\b(ca|ny|tx|fl|wa|il|us|usa)\b/g, " ").replace(/[^a-z]+/g, " ").replace(/\s+/g, " ").trim();
5
+ return trimmed.split(" ").slice(0, 3).join(" ");
6
+ }
7
+ function humanizeMerchant(normalized) {
8
+ return normalized.split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
9
+ }
10
+ function daysBetween(a, b) {
11
+ const aMs = Date.parse(a);
12
+ const bMs = Date.parse(b);
13
+ if (!Number.isFinite(aMs) || !Number.isFinite(bMs)) {
14
+ return Number.NaN;
15
+ }
16
+ return Math.abs(bMs - aMs) / MS_PER_DAY;
17
+ }
18
+ function mean(values) {
19
+ if (values.length === 0) {
20
+ return 0;
21
+ }
22
+ let total = 0;
23
+ for (const value of values) {
24
+ total += value;
25
+ }
26
+ return total / values.length;
27
+ }
28
+ function stddev(values, avg) {
29
+ if (values.length === 0) {
30
+ return 0;
31
+ }
32
+ let sum = 0;
33
+ for (const value of values) {
34
+ const diff = value - avg;
35
+ sum += diff * diff;
36
+ }
37
+ return Math.sqrt(sum / values.length);
38
+ }
39
+ function classifyCadence(avgIntervalDays) {
40
+ if (avgIntervalDays >= 5 && avgIntervalDays <= 9) return "weekly";
41
+ if (avgIntervalDays >= 12 && avgIntervalDays <= 16) return "biweekly";
42
+ if (avgIntervalDays >= 25 && avgIntervalDays <= 35) return "monthly";
43
+ if (avgIntervalDays >= 85 && avgIntervalDays <= 95) return "quarterly";
44
+ if (avgIntervalDays >= 350 && avgIntervalDays <= 380) return "annual";
45
+ return "irregular";
46
+ }
47
+ function cadenceMultiplier(cadence) {
48
+ switch (cadence) {
49
+ case "weekly":
50
+ return 52;
51
+ case "biweekly":
52
+ return 26;
53
+ case "monthly":
54
+ return 12;
55
+ case "quarterly":
56
+ return 4;
57
+ case "annual":
58
+ return 1;
59
+ case "irregular":
60
+ return 0;
61
+ }
62
+ }
63
+ function detectInterval(sortedPostedAts) {
64
+ if (sortedPostedAts.length < MIN_RECURRENCE_OCCURRENCES) {
65
+ return null;
66
+ }
67
+ const intervals = [];
68
+ for (let index = 1; index < sortedPostedAts.length; index += 1) {
69
+ const days = daysBetween(
70
+ sortedPostedAts[index - 1],
71
+ sortedPostedAts[index]
72
+ );
73
+ if (Number.isFinite(days)) {
74
+ intervals.push(days);
75
+ }
76
+ }
77
+ if (intervals.length === 0) {
78
+ return null;
79
+ }
80
+ const avg = mean(intervals);
81
+ const dev = stddev(intervals, avg);
82
+ const cadence = classifyCadence(avg);
83
+ return { cadence, avgIntervalDays: avg, stddevDays: dev };
84
+ }
85
+ function amountSimilarity(amounts) {
86
+ if (amounts.length === 0) {
87
+ return 0;
88
+ }
89
+ const avg = mean(amounts);
90
+ if (avg === 0) {
91
+ return 0;
92
+ }
93
+ const dev = stddev(amounts, avg);
94
+ const ratio = dev / Math.abs(avg);
95
+ return Math.max(0, Math.min(1, 1 - ratio));
96
+ }
97
+ function confidenceScore(args) {
98
+ const cadenceBoost = args.cadence === "irregular" ? 0.2 : 0.55;
99
+ const occurrenceBoost = Math.min(0.25, args.occurrenceCount * 0.04);
100
+ const intervalConsistency = args.avgIntervalDays > 0 ? Math.max(0, 1 - args.intervalStddev / args.avgIntervalDays) : 0;
101
+ const intervalBoost = intervalConsistency * 0.1;
102
+ const amountBoost = args.amountSimilarity * 0.1;
103
+ return Math.min(
104
+ 0.99,
105
+ cadenceBoost + occurrenceBoost + intervalBoost + amountBoost
106
+ );
107
+ }
108
+ function addDaysIso(value, days) {
109
+ const ms = Date.parse(value);
110
+ if (!Number.isFinite(ms)) {
111
+ return value;
112
+ }
113
+ return new Date(ms + days * MS_PER_DAY).toISOString();
114
+ }
115
+ function detectRecurringCharges(transactions) {
116
+ const debits = transactions.filter(
117
+ (transaction) => transaction.direction === "debit"
118
+ );
119
+ const groups = /* @__PURE__ */ new Map();
120
+ for (const transaction of debits) {
121
+ const existing = groups.get(transaction.merchantNormalized);
122
+ if (existing) {
123
+ existing.push(transaction);
124
+ } else {
125
+ groups.set(transaction.merchantNormalized, [transaction]);
126
+ }
127
+ }
128
+ const charges = [];
129
+ for (const [merchant, group] of groups) {
130
+ if (!merchant || group.length < MIN_RECURRENCE_OCCURRENCES) {
131
+ continue;
132
+ }
133
+ const sorted = [...group].sort(
134
+ (a, b) => a.postedAt.localeCompare(b.postedAt)
135
+ );
136
+ const postedAts = sorted.map((transaction) => transaction.postedAt);
137
+ const interval = detectInterval(postedAts);
138
+ if (!interval) {
139
+ continue;
140
+ }
141
+ const amounts = sorted.map(
142
+ (transaction) => Math.abs(transaction.amountUsd)
143
+ );
144
+ const avgAmount = mean(amounts);
145
+ const lastAmount = amounts[amounts.length - 1] ?? 0;
146
+ const similarity = amountSimilarity(amounts);
147
+ if (interval.cadence === "irregular" && similarity < 0.7) {
148
+ continue;
149
+ }
150
+ const multiplier = cadenceMultiplier(interval.cadence);
151
+ const confidence = confidenceScore({
152
+ occurrenceCount: group.length,
153
+ cadence: interval.cadence,
154
+ intervalStddev: interval.stddevDays,
155
+ avgIntervalDays: interval.avgIntervalDays,
156
+ amountSimilarity: similarity
157
+ });
158
+ const latestSeenAt = postedAts[postedAts.length - 1];
159
+ const nextExpectedAt = interval.cadence === "irregular" ? null : addDaysIso(latestSeenAt, interval.avgIntervalDays);
160
+ const sourceIds = Array.from(
161
+ new Set(sorted.map((transaction) => transaction.sourceId))
162
+ );
163
+ const sampleTransactionIds = sorted.slice(-5).map((transaction) => transaction.id);
164
+ const category = sorted.find((transaction) => transaction.category)?.category ?? null;
165
+ const display = sorted[sorted.length - 1]?.merchantRaw?.trim() || humanizeMerchant(merchant);
166
+ charges.push({
167
+ merchantNormalized: merchant,
168
+ merchantDisplay: display,
169
+ cadence: interval.cadence,
170
+ averageAmountUsd: Number(avgAmount.toFixed(2)),
171
+ lastAmountUsd: Number(lastAmount.toFixed(2)),
172
+ annualizedCostUsd: Number((avgAmount * multiplier).toFixed(2)),
173
+ occurrenceCount: group.length,
174
+ firstSeenAt: postedAts[0],
175
+ latestSeenAt,
176
+ nextExpectedAt,
177
+ sourceIds,
178
+ sampleTransactionIds,
179
+ confidence,
180
+ category
181
+ });
182
+ }
183
+ charges.sort((a, b) => b.annualizedCostUsd - a.annualizedCostUsd);
184
+ return charges;
185
+ }
186
+ export {
187
+ detectRecurringCharges,
188
+ normalizeMerchant
189
+ };
190
+ //# sourceMappingURL=payment-recurrence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/payment-recurrence.ts"],"sourcesContent":["import type {\n LifeOpsPaymentTransaction,\n LifeOpsRecurringCadence,\n LifeOpsRecurringCharge,\n} from \"./payment-types.js\";\n\nconst MIN_RECURRENCE_OCCURRENCES = 2;\nconst MS_PER_DAY = 86_400_000;\n\ninterface IntervalMatch {\n cadence: LifeOpsRecurringCadence;\n avgIntervalDays: number;\n stddevDays: number;\n}\n\n/**\n * Normalize a merchant string for grouping. Strips trailing location/ref\n * suffixes, collapses whitespace, removes non-alpha noise, lowercases.\n *\n * Bank feeds commonly produce variants like:\n * - \"NETFLIX.COM 866-579-7172 CA\"\n * - \"NETFLIX.COM #8432\"\n * - \"Netflix Monthly 11.99\"\n * We want all three to collapse to \"netflix\".\n */\nexport function normalizeMerchant(raw: string): string {\n const trimmed = raw\n .toLowerCase()\n // Strip common URL TLDs: \".com\", \".co\", \".io\", \".net\", \".org\"\n .replace(/\\.(com|co|io|net|org|tv|app)\\b/g, \" \")\n // Drop phone numbers\n .replace(/\\b\\d{3}[-.]?\\d{3}[-.]?\\d{4}\\b/g, \" \")\n // Drop reference-style \"#12345\" or \"POS 12345\"\n .replace(/#\\s*\\d+/g, \" \")\n .replace(/\\bpos\\b/g, \" \")\n // Drop dollar-ish amounts that sometimes leak into descriptions\n .replace(/\\$?\\d+(?:\\.\\d{2})?/g, \" \")\n // Drop state codes and common noise\n .replace(/\\b(ca|ny|tx|fl|wa|il|us|usa)\\b/g, \" \")\n // Collapse non-alpha to space\n .replace(/[^a-z]+/g, \" \")\n .replace(/\\s+/g, \" \")\n .trim();\n // Keep first 3 tokens max — that's almost always the brand identity.\n return trimmed.split(\" \").slice(0, 3).join(\" \");\n}\n\nfunction humanizeMerchant(normalized: string): string {\n return normalized\n .split(\" \")\n .filter(Boolean)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n}\n\nfunction daysBetween(a: string, b: string): number {\n const aMs = Date.parse(a);\n const bMs = Date.parse(b);\n if (!Number.isFinite(aMs) || !Number.isFinite(bMs)) {\n return Number.NaN;\n }\n return Math.abs(bMs - aMs) / MS_PER_DAY;\n}\n\nfunction mean(values: readonly number[]): number {\n if (values.length === 0) {\n return 0;\n }\n let total = 0;\n for (const value of values) {\n total += value;\n }\n return total / values.length;\n}\n\nfunction stddev(values: readonly number[], avg: number): number {\n if (values.length === 0) {\n return 0;\n }\n let sum = 0;\n for (const value of values) {\n const diff = value - avg;\n sum += diff * diff;\n }\n return Math.sqrt(sum / values.length);\n}\n\nfunction classifyCadence(avgIntervalDays: number): LifeOpsRecurringCadence {\n if (avgIntervalDays >= 5 && avgIntervalDays <= 9) return \"weekly\";\n if (avgIntervalDays >= 12 && avgIntervalDays <= 16) return \"biweekly\";\n if (avgIntervalDays >= 25 && avgIntervalDays <= 35) return \"monthly\";\n if (avgIntervalDays >= 85 && avgIntervalDays <= 95) return \"quarterly\";\n if (avgIntervalDays >= 350 && avgIntervalDays <= 380) return \"annual\";\n return \"irregular\";\n}\n\nfunction cadenceMultiplier(cadence: LifeOpsRecurringCadence): number {\n switch (cadence) {\n case \"weekly\":\n return 52;\n case \"biweekly\":\n return 26;\n case \"monthly\":\n return 12;\n case \"quarterly\":\n return 4;\n case \"annual\":\n return 1;\n case \"irregular\":\n return 0;\n }\n}\n\nfunction detectInterval(\n sortedPostedAts: readonly string[],\n): IntervalMatch | null {\n if (sortedPostedAts.length < MIN_RECURRENCE_OCCURRENCES) {\n return null;\n }\n const intervals: number[] = [];\n for (let index = 1; index < sortedPostedAts.length; index += 1) {\n const days = daysBetween(\n sortedPostedAts[index - 1],\n sortedPostedAts[index],\n );\n if (Number.isFinite(days)) {\n intervals.push(days);\n }\n }\n if (intervals.length === 0) {\n return null;\n }\n const avg = mean(intervals);\n const dev = stddev(intervals, avg);\n const cadence = classifyCadence(avg);\n return { cadence, avgIntervalDays: avg, stddevDays: dev };\n}\n\nfunction amountSimilarity(amounts: readonly number[]): number {\n if (amounts.length === 0) {\n return 0;\n }\n const avg = mean(amounts);\n if (avg === 0) {\n return 0;\n }\n const dev = stddev(amounts, avg);\n const ratio = dev / Math.abs(avg);\n // Perfect similarity (0 dev) → 1. Ratio of 0.25 → ~0.5. Ratio ≥ 1 → 0.\n return Math.max(0, Math.min(1, 1 - ratio));\n}\n\nfunction confidenceScore(args: {\n occurrenceCount: number;\n cadence: LifeOpsRecurringCadence;\n intervalStddev: number;\n avgIntervalDays: number;\n amountSimilarity: number;\n}): number {\n const cadenceBoost = args.cadence === \"irregular\" ? 0.2 : 0.55;\n const occurrenceBoost = Math.min(0.25, args.occurrenceCount * 0.04);\n const intervalConsistency =\n args.avgIntervalDays > 0\n ? Math.max(0, 1 - args.intervalStddev / args.avgIntervalDays)\n : 0;\n const intervalBoost = intervalConsistency * 0.1;\n const amountBoost = args.amountSimilarity * 0.1;\n return Math.min(\n 0.99,\n cadenceBoost + occurrenceBoost + intervalBoost + amountBoost,\n );\n}\n\nfunction addDaysIso(value: string, days: number): string {\n const ms = Date.parse(value);\n if (!Number.isFinite(ms)) {\n return value;\n }\n return new Date(ms + days * MS_PER_DAY).toISOString();\n}\n\nexport function detectRecurringCharges(\n transactions: readonly LifeOpsPaymentTransaction[],\n): LifeOpsRecurringCharge[] {\n const debits = transactions.filter(\n (transaction) => transaction.direction === \"debit\",\n );\n const groups = new Map<string, LifeOpsPaymentTransaction[]>();\n for (const transaction of debits) {\n const existing = groups.get(transaction.merchantNormalized);\n if (existing) {\n existing.push(transaction);\n } else {\n groups.set(transaction.merchantNormalized, [transaction]);\n }\n }\n\n const charges: LifeOpsRecurringCharge[] = [];\n for (const [merchant, group] of groups) {\n if (!merchant || group.length < MIN_RECURRENCE_OCCURRENCES) {\n continue;\n }\n const sorted = [...group].sort((a, b) =>\n a.postedAt.localeCompare(b.postedAt),\n );\n const postedAts = sorted.map((transaction) => transaction.postedAt);\n const interval = detectInterval(postedAts);\n if (!interval) {\n continue;\n }\n const amounts = sorted.map((transaction) =>\n Math.abs(transaction.amountUsd),\n );\n const avgAmount = mean(amounts);\n const lastAmount = amounts[amounts.length - 1] ?? 0;\n const similarity = amountSimilarity(amounts);\n if (interval.cadence === \"irregular\" && similarity < 0.7) {\n // Skip non-recurring merchants that only happen to hit multiple times\n // with different amounts (e.g. Amazon purchases).\n continue;\n }\n const multiplier = cadenceMultiplier(interval.cadence);\n const confidence = confidenceScore({\n occurrenceCount: group.length,\n cadence: interval.cadence,\n intervalStddev: interval.stddevDays,\n avgIntervalDays: interval.avgIntervalDays,\n amountSimilarity: similarity,\n });\n const latestSeenAt = postedAts[postedAts.length - 1];\n const nextExpectedAt =\n interval.cadence === \"irregular\"\n ? null\n : addDaysIso(latestSeenAt, interval.avgIntervalDays);\n const sourceIds = Array.from(\n new Set(sorted.map((transaction) => transaction.sourceId)),\n );\n const sampleTransactionIds = sorted\n .slice(-5)\n .map((transaction) => transaction.id);\n const category =\n sorted.find((transaction) => transaction.category)?.category ?? null;\n const display =\n sorted[sorted.length - 1]?.merchantRaw?.trim() ||\n humanizeMerchant(merchant);\n charges.push({\n merchantNormalized: merchant,\n merchantDisplay: display,\n cadence: interval.cadence,\n averageAmountUsd: Number(avgAmount.toFixed(2)),\n lastAmountUsd: Number(lastAmount.toFixed(2)),\n annualizedCostUsd: Number((avgAmount * multiplier).toFixed(2)),\n occurrenceCount: group.length,\n firstSeenAt: postedAts[0],\n latestSeenAt,\n nextExpectedAt,\n sourceIds,\n sampleTransactionIds,\n confidence,\n category,\n });\n }\n charges.sort((a, b) => b.annualizedCostUsd - a.annualizedCostUsd);\n return charges;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,158 @@
1
+ export type LifeOpsPaymentSourceKind = "csv" | "plaid" | "manual" | "paypal" | "email";
2
+ export type LifeOpsPaymentSourceStatus = "active" | "disconnected" | "needs_attention";
3
+ export interface LifeOpsPaymentSource {
4
+ id: string;
5
+ agentId: string;
6
+ kind: LifeOpsPaymentSourceKind;
7
+ label: string;
8
+ institution: string | null;
9
+ accountMask: string | null;
10
+ status: LifeOpsPaymentSourceStatus;
11
+ lastSyncedAt: string | null;
12
+ transactionCount: number;
13
+ metadata: Record<string, unknown>;
14
+ createdAt: string;
15
+ updatedAt: string;
16
+ }
17
+ export type LifeOpsPaymentDirection = "debit" | "credit";
18
+ export interface LifeOpsPaymentTransaction {
19
+ id: string;
20
+ agentId: string;
21
+ sourceId: string;
22
+ externalId: string | null;
23
+ postedAt: string;
24
+ amountUsd: number;
25
+ direction: LifeOpsPaymentDirection;
26
+ merchantRaw: string;
27
+ merchantNormalized: string;
28
+ description: string | null;
29
+ category: string | null;
30
+ currency: string;
31
+ metadata: Record<string, unknown>;
32
+ createdAt: string;
33
+ }
34
+ export type LifeOpsRecurringCadence = "weekly" | "biweekly" | "monthly" | "quarterly" | "annual" | "irregular";
35
+ export interface LifeOpsRecurringCharge {
36
+ merchantNormalized: string;
37
+ merchantDisplay: string;
38
+ cadence: LifeOpsRecurringCadence;
39
+ averageAmountUsd: number;
40
+ lastAmountUsd: number;
41
+ annualizedCostUsd: number;
42
+ occurrenceCount: number;
43
+ firstSeenAt: string;
44
+ latestSeenAt: string;
45
+ nextExpectedAt: string | null;
46
+ sourceIds: string[];
47
+ sampleTransactionIds: string[];
48
+ confidence: number;
49
+ category: string | null;
50
+ }
51
+ export interface LifeOpsSpendingCategoryBreakdown {
52
+ category: string;
53
+ totalUsd: number;
54
+ transactionCount: number;
55
+ }
56
+ export interface LifeOpsSpendingSummary {
57
+ windowDays: number;
58
+ fromDate: string;
59
+ toDate: string;
60
+ totalSpendUsd: number;
61
+ totalIncomeUsd: number;
62
+ netUsd: number;
63
+ transactionCount: number;
64
+ recurringSpendUsd: number;
65
+ topCategories: LifeOpsSpendingCategoryBreakdown[];
66
+ topMerchants: Array<{
67
+ merchantNormalized: string;
68
+ merchantDisplay: string;
69
+ totalUsd: number;
70
+ transactionCount: number;
71
+ }>;
72
+ }
73
+ export interface LifeOpsRecurringChargePlaybookHit {
74
+ merchantNormalized: string;
75
+ playbookKey: string;
76
+ serviceName: string;
77
+ managementUrl: string;
78
+ executorPreference: "user_browser" | "agent_browser" | "desktop_native";
79
+ }
80
+ /**
81
+ * A bill detected from email (or other source) and surfaced on the Money
82
+ * dashboard. Backed by a `life_payment_transactions` row whose
83
+ * `source.kind === "email"` and whose metadata carries bill-specific data.
84
+ */
85
+ export type LifeOpsUpcomingBillStatus = "upcoming" | "overdue" | "needs_due_date";
86
+ export interface LifeOpsUpcomingBill {
87
+ id: string;
88
+ merchant: string;
89
+ amountUsd: number;
90
+ currency: string;
91
+ dueDate: string | null;
92
+ status: LifeOpsUpcomingBillStatus;
93
+ postedAt: string;
94
+ sourceMessageId: string | null;
95
+ confidence: number;
96
+ }
97
+ export interface LifeOpsPaymentsDashboard {
98
+ sources: LifeOpsPaymentSource[];
99
+ recurring: LifeOpsRecurringCharge[];
100
+ /**
101
+ * For every recurring charge whose merchant matches a known cancellation
102
+ * playbook (Netflix, Spotify, NYT, etc.), this map carries the playbook
103
+ * descriptor. UI shows a deep-link "Cancel" button only when there is a hit.
104
+ */
105
+ recurringPlaybookHits: LifeOpsRecurringChargePlaybookHit[];
106
+ spending: LifeOpsSpendingSummary;
107
+ /**
108
+ * Bills extracted from email-classified messages. Includes upcoming bills,
109
+ * overdue bills, and bills that need a reviewed due date.
110
+ */
111
+ upcomingBills: LifeOpsUpcomingBill[];
112
+ gmailSubscriptionAuditId: string | null;
113
+ generatedAt: string;
114
+ }
115
+ export interface AddPaymentSourceRequest {
116
+ kind: LifeOpsPaymentSourceKind;
117
+ label: string;
118
+ institution?: string | null;
119
+ accountMask?: string | null;
120
+ metadata?: Record<string, unknown>;
121
+ }
122
+ export interface ImportTransactionsCsvRequest {
123
+ sourceId: string;
124
+ csvText: string;
125
+ /** Optional column hints; auto-detected when absent. */
126
+ dateColumn?: string;
127
+ amountColumn?: string;
128
+ merchantColumn?: string;
129
+ descriptionColumn?: string;
130
+ categoryColumn?: string;
131
+ }
132
+ export interface ImportTransactionsCsvResult {
133
+ sourceId: string;
134
+ rowsRead: number;
135
+ inserted: number;
136
+ skipped: number;
137
+ errors: string[];
138
+ }
139
+ export interface ListTransactionsRequest {
140
+ sourceId?: string | null;
141
+ sinceAt?: string | null;
142
+ untilAt?: string | null;
143
+ limit?: number | null;
144
+ merchantContains?: string | null;
145
+ onlyDebits?: boolean | null;
146
+ }
147
+ export interface SpendingSummaryRequest {
148
+ windowDays?: number | null;
149
+ sourceId?: string | null;
150
+ }
151
+ export type LifeOpsMoneyDashboard = LifeOpsPaymentsDashboard;
152
+ export type LifeOpsMoneySource = LifeOpsPaymentSource;
153
+ export type LifeOpsMoneySourceKind = LifeOpsPaymentSourceKind;
154
+ export type LifeOpsMoneySourceStatus = LifeOpsPaymentSourceStatus;
155
+ export type LifeOpsMoneyTransaction = LifeOpsPaymentTransaction;
156
+ export type LifeOpsMoneyDirection = LifeOpsPaymentDirection;
157
+ export type AddMoneySourceRequest = AddPaymentSourceRequest;
158
+ //# sourceMappingURL=payment-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-types.d.ts","sourceRoot":"","sources":["../src/payment-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=payment-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * elizaOS runtime plugin for the Finances overlay app.
3
+ *
4
+ * Hosts the OWNER_FINANCES action (stub during migration) and the
5
+ * `/finances` view that renders the owner finance dashboard.
6
+ *
7
+ * The drizzle schema (`pgSchema("app_finances")`) is registered via the
8
+ * Plugin `schema` field so the elizaOS runtime handles migrations. Loading
9
+ * this plugin requires @elizaos/plugin-sql (declared as a peer dep and listed
10
+ * in `dependencies`).
11
+ */
12
+ import type { Plugin } from "@elizaos/core";
13
+ export declare const financesPlugin: Plugin;
14
+ export default financesPlugin;
15
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAM5C,eAAO,MAAM,cAAc,EAAE,MAuB5B,CAAC;AAEF,eAAe,cAAc,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,31 @@
1
+ import { financesDbSchema } from "./db/schema.js";
2
+ import { FinancesMigrationService } from "./services/migration.js";
3
+ const FINANCES_APP_NAME = "@elizaos/plugin-finances";
4
+ const financesPlugin = {
5
+ name: FINANCES_APP_NAME,
6
+ description: "Owner finance overlay: dashboard, transactions, recurring charges, and the /finances view. Owns the FinancesService / FinancesRepository payments back-end (sources, CSV import, transactions, spending, recurring charges, email bills, Plaid / PayPal bridges). The OWNER_FINANCES umbrella action + the /api/lifeops/money/* routes are registered by @elizaos/plugin-personal-assistant, which delegates the payments back-end here. Backed by drizzle pgSchema('app_finances'); requires @elizaos/plugin-sql.",
7
+ dependencies: ["@elizaos/plugin-sql"],
8
+ services: [FinancesMigrationService],
9
+ schema: financesDbSchema,
10
+ views: [
11
+ {
12
+ id: "finances",
13
+ label: "Finances",
14
+ description: "Owner finance dashboard \u2014 balance, transactions, recurring charges",
15
+ icon: "Wallet",
16
+ path: "/finances",
17
+ modalities: ["gui", "xr", "tui"],
18
+ bundlePath: "dist/views/bundle.js",
19
+ componentExport: "FinancesView",
20
+ tags: ["finances", "owner", "money"],
21
+ visibleInManager: true,
22
+ desktopTabEnabled: true
23
+ }
24
+ ]
25
+ };
26
+ var plugin_default = financesPlugin;
27
+ export {
28
+ plugin_default as default,
29
+ financesPlugin
30
+ };
31
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["/**\n * elizaOS runtime plugin for the Finances overlay app.\n *\n * Hosts the OWNER_FINANCES action (stub during migration) and the\n * `/finances` view that renders the owner finance dashboard.\n *\n * The drizzle schema (`pgSchema(\"app_finances\")`) is registered via the\n * Plugin `schema` field so the elizaOS runtime handles migrations. Loading\n * this plugin requires @elizaos/plugin-sql (declared as a peer dep and listed\n * in `dependencies`).\n */\n\nimport type { Plugin } from \"@elizaos/core\";\nimport { financesDbSchema } from \"./db/schema.js\";\nimport { FinancesMigrationService } from \"./services/migration.js\";\n\nconst FINANCES_APP_NAME = \"@elizaos/plugin-finances\";\n\nexport const financesPlugin: Plugin = {\n name: FINANCES_APP_NAME,\n description:\n \"Owner finance overlay: dashboard, transactions, recurring charges, and the /finances view. Owns the FinancesService / FinancesRepository payments back-end (sources, CSV import, transactions, spending, recurring charges, email bills, Plaid / PayPal bridges). The OWNER_FINANCES umbrella action + the /api/lifeops/money/* routes are registered by @elizaos/plugin-personal-assistant, which delegates the payments back-end here. Backed by drizzle pgSchema('app_finances'); requires @elizaos/plugin-sql.\",\n dependencies: [\"@elizaos/plugin-sql\"],\n services: [FinancesMigrationService],\n schema: financesDbSchema,\n views: [\n {\n id: \"finances\",\n label: \"Finances\",\n description:\n \"Owner finance dashboard — balance, transactions, recurring charges\",\n icon: \"Wallet\",\n path: \"/finances\",\n modalities: [\"gui\", \"xr\", \"tui\"],\n bundlePath: \"dist/views/bundle.js\",\n componentExport: \"FinancesView\",\n tags: [\"finances\", \"owner\", \"money\"],\n visibleInManager: true,\n desktopTabEnabled: true,\n },\n ],\n};\n\nexport default financesPlugin;\n"],"mappings":"AAaA,SAAS,wBAAwB;AACjC,SAAS,gCAAgC;AAEzC,MAAM,oBAAoB;AAEnB,MAAM,iBAAyB;AAAA,EACpC,MAAM;AAAA,EACN,aACE;AAAA,EACF,cAAc,CAAC,qBAAqB;AAAA,EACpC,UAAU,CAAC,wBAAwB;AAAA,EACnC,QAAQ;AAAA,EACR,OAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,aACE;AAAA,MACF,MAAM;AAAA,MACN,MAAM;AAAA,MACN,YAAY,CAAC,OAAO,MAAM,KAAK;AAAA,MAC/B,YAAY;AAAA,MACZ,iBAAiB;AAAA,MACjB,MAAM,CAAC,YAAY,SAAS,OAAO;AAAA,MACnC,kBAAkB;AAAA,MAClB,mBAAmB;AAAA,IACrB;AAAA,EACF;AACF;AAEA,IAAO,iBAAQ;","names":[]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Register the finances view for terminal rendering.
3
+ *
4
+ * The agent terminal mounts plugin views by id from the `@elizaos/tui` terminal
5
+ * registry. This makes the finances `tui` modality render for real in the
6
+ * terminal (the unified {@link FinancesSpatialView}) rather than only navigating
7
+ * a GUI shell. A module-level snapshot lets a host push live dashboard data;
8
+ * absent a push it defaults to the loading state.
9
+ */
10
+ import { type FinancesSnapshot } from "./components/finances/FinancesSpatialView.js";
11
+ /** Update the snapshot the registered terminal view renders from. */
12
+ export declare function setFinancesTerminalSnapshot(next: FinancesSnapshot): void;
13
+ /** Register the finances terminal view; returns an unregister function. */
14
+ export declare function registerFinancesTerminalView(): () => void;
15
+ //# sourceMappingURL=register-terminal-view.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-terminal-view.d.ts","sourceRoot":"","sources":["../src/register-terminal-view.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,EAEL,KAAK,gBAAgB,EAEtB,MAAM,+CAA+C,CAAC;AAIvD,qEAAqE;AACrE,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAExE;AAED,2EAA2E;AAC3E,wBAAgB,4BAA4B,IAAI,MAAM,IAAI,CAIzD"}
@@ -0,0 +1,21 @@
1
+ import { registerSpatialTerminalView } from "@elizaos/ui/spatial/tui";
2
+ import { createElement } from "react";
3
+ import {
4
+ EMPTY_FINANCES_SNAPSHOT,
5
+ FinancesSpatialView
6
+ } from "./components/finances/FinancesSpatialView.js";
7
+ let current = EMPTY_FINANCES_SNAPSHOT;
8
+ function setFinancesTerminalSnapshot(next) {
9
+ current = next;
10
+ }
11
+ function registerFinancesTerminalView() {
12
+ return registerSpatialTerminalView(
13
+ "finances",
14
+ () => createElement(FinancesSpatialView, { snapshot: current })
15
+ );
16
+ }
17
+ export {
18
+ registerFinancesTerminalView,
19
+ setFinancesTerminalSnapshot
20
+ };
21
+ //# sourceMappingURL=register-terminal-view.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/register-terminal-view.tsx"],"sourcesContent":["/**\n * Register the finances view for terminal rendering.\n *\n * The agent terminal mounts plugin views by id from the `@elizaos/tui` terminal\n * registry. This makes the finances `tui` modality render for real in the\n * terminal (the unified {@link FinancesSpatialView}) rather than only navigating\n * a GUI shell. A module-level snapshot lets a host push live dashboard data;\n * absent a push it defaults to the loading state.\n */\n\nimport { registerSpatialTerminalView } from \"@elizaos/ui/spatial/tui\";\nimport { createElement } from \"react\";\nimport {\n EMPTY_FINANCES_SNAPSHOT,\n type FinancesSnapshot,\n FinancesSpatialView,\n} from \"./components/finances/FinancesSpatialView.js\";\n\nlet current: FinancesSnapshot = EMPTY_FINANCES_SNAPSHOT;\n\n/** Update the snapshot the registered terminal view renders from. */\nexport function setFinancesTerminalSnapshot(next: FinancesSnapshot): void {\n current = next;\n}\n\n/** Register the finances terminal view; returns an unregister function. */\nexport function registerFinancesTerminalView(): () => void {\n return registerSpatialTerminalView(\"finances\", () =>\n createElement(FinancesSpatialView, { snapshot: current }),\n );\n}\n"],"mappings":"AAUA,SAAS,mCAAmC;AAC5C,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EAEA;AAAA,OACK;AAEP,IAAI,UAA4B;AAGzB,SAAS,4BAA4B,MAA8B;AACxE,YAAU;AACZ;AAGO,SAAS,+BAA2C;AACzD,SAAO;AAAA,IAA4B;AAAA,IAAY,MAC7C,cAAc,qBAAqB,EAAE,UAAU,QAAQ,CAAC;AAAA,EAC1D;AACF;","names":[]}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Side-effect entry point — registers the Finances view for terminal rendering.
3
+ *
4
+ * In a terminal host (the Node agent, no DOM), register the finances view so it
5
+ * renders inline in the terminal. Lazy + DOM-guarded so the terminal engine
6
+ * stays out of browser/mobile bundles. Web, iOS, desktop, and Android leave
7
+ * this a no-op (a DOM is present), so the same import is safe everywhere.
8
+ */
9
+ //# sourceMappingURL=register.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -0,0 +1,5 @@
1
+ if (typeof window === "undefined") {
2
+ void import("./register-terminal-view.js").then((m) => m.registerFinancesTerminalView()).catch(() => {
3
+ });
4
+ }
5
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/register.ts"],"sourcesContent":["/**\n * Side-effect entry point — registers the Finances view for terminal rendering.\n *\n * In a terminal host (the Node agent, no DOM), register the finances view so it\n * renders inline in the terminal. Lazy + DOM-guarded so the terminal engine\n * stays out of browser/mobile bundles. Web, iOS, desktop, and Android leave\n * this a no-op (a DOM is present), so the same import is safe everywhere.\n */\n\nif (typeof window === \"undefined\") {\n void import(\"./register-terminal-view.js\")\n .then((m) => m.registerFinancesTerminalView())\n .catch(() => {\n // Terminal rendering is best-effort; never block plugin load.\n });\n}\n"],"mappings":"AASA,IAAI,OAAO,WAAW,aAAa;AACjC,OAAK,OAAO,6BAA6B,EACtC,KAAK,CAAC,MAAM,EAAE,6BAA6B,CAAC,EAC5C,MAAM,MAAM;AAAA,EAEb,CAAC;AACL;","names":[]}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Browser-bridge runtime-service seam for the subscriptions back-end.
3
+ *
4
+ * Subscription cancellation drives the user's real Chrome / Safari through the
5
+ * Agent Browser Bridge companion: it lists connected companions, creates a
6
+ * browser session from a cancellation playbook, and polls that session's
7
+ * status. `@elizaos/plugin-browser` owns the contract for this — it exports the
8
+ * `BrowserBridgeRouteService` interface and the
9
+ * `BROWSER_BRIDGE_ROUTE_SERVICE_TYPE` ("lifeops_browser_plugin") service type;
10
+ * a host plugin (today, plugin-personal-assistant) registers the implementor
11
+ * that persists companions and sessions.
12
+ *
13
+ * This module resolves that runtime service by service-type and exposes only
14
+ * the narrow surface the subscriptions path needs. It carries no dependency on
15
+ * `@elizaos/plugin-personal-assistant`: the contract lives entirely in
16
+ * `@elizaos/plugin-browser`, the same way the BrowserService bridge target
17
+ * resolves it.
18
+ */
19
+ import type { IAgentRuntime } from "@elizaos/core";
20
+ import { type BrowserBridgeCompanionStatus } from "@elizaos/plugin-browser";
21
+ import type { CreateLifeOpsBrowserSessionRequest, LifeOpsBrowserSession } from "@elizaos/plugin-browser/lifeops-session-contracts";
22
+ /**
23
+ * The browser-bridge surface the subscriptions back-end needs, bound to the
24
+ * registered `lifeops_browser_plugin` runtime service for the resolved owner.
25
+ */
26
+ export interface SubscriptionsBrowserGateway {
27
+ /** Connected + pending Agent Browser Bridge companions for the owner. */
28
+ listBrowserCompanions(): Promise<BrowserBridgeCompanionStatus[]>;
29
+ /** Create a browser session that runs the cancellation playbook. */
30
+ createBrowserSession(request: CreateLifeOpsBrowserSessionRequest): Promise<LifeOpsBrowserSession>;
31
+ /** Fetch a browser session by id (used to reconcile cancellation status). */
32
+ getBrowserSession(sessionId: string): Promise<LifeOpsBrowserSession>;
33
+ }
34
+ /**
35
+ * Build the subscriptions browser gateway bound to a runtime + owner. The owner
36
+ * entity id is forwarded to the host service so companion + session scoping
37
+ * matches the owner the finances service resolved.
38
+ */
39
+ export declare function createSubscriptionsBrowserGateway(runtime: IAgentRuntime, ownerEntityId: string | null): SubscriptionsBrowserGateway;
40
+ //# sourceMappingURL=browser-bridge-seam.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-bridge-seam.d.ts","sourceRoot":"","sources":["../../src/services/browser-bridge-seam.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAEL,KAAK,4BAA4B,EAElC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EACV,kCAAkC,EAClC,qBAAqB,EACtB,MAAM,mDAAmD,CAAC;AAG3D;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,yEAAyE;IACzE,qBAAqB,IAAI,OAAO,CAAC,4BAA4B,EAAE,CAAC,CAAC;IACjE,oEAAoE;IACpE,oBAAoB,CAClB,OAAO,EAAE,kCAAkC,GAC1C,OAAO,CAAC,qBAAqB,CAAC,CAAC;IAClC,6EAA6E;IAC7E,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtE;AAeD;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,aAAa,EACtB,aAAa,EAAE,MAAM,GAAG,IAAI,GAC3B,2BAA2B,CAsB7B"}