@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.
- package/dist/actions/finances.d.ts +38 -0
- package/dist/actions/finances.d.ts.map +1 -0
- package/dist/actions/finances.js +368 -0
- package/dist/actions/finances.js.map +1 -0
- package/dist/components/finances/FinancesSpatialView.d.ts +80 -0
- package/dist/components/finances/FinancesSpatialView.d.ts.map +1 -0
- package/dist/components/finances/FinancesSpatialView.js +157 -0
- package/dist/components/finances/FinancesSpatialView.js.map +1 -0
- package/dist/components/finances/FinancesView.d.ts +97 -0
- package/dist/components/finances/FinancesView.d.ts.map +1 -0
- package/dist/components/finances/FinancesView.js +231 -0
- package/dist/components/finances/FinancesView.js.map +1 -0
- package/dist/components/finances/finances-view-bundle.d.ts +10 -0
- package/dist/components/finances/finances-view-bundle.d.ts.map +1 -0
- package/dist/components/finances/finances-view-bundle.js +5 -0
- package/dist/components/finances/finances-view-bundle.js.map +1 -0
- package/dist/db/finances-repository.d.ts +51 -0
- package/dist/db/finances-repository.d.ts.map +1 -0
- package/dist/db/finances-repository.js +521 -0
- package/dist/db/finances-repository.js.map +1 -0
- package/dist/db/index.d.ts +3 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +6 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +2615 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +133 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/sql.d.ts +65 -0
- package/dist/db/sql.d.ts.map +1 -0
- package/dist/db/sql.js +182 -0
- package/dist/db/sql.js.map +1 -0
- package/dist/finance-normalize.d.ts +24 -0
- package/dist/finance-normalize.d.ts.map +1 -0
- package/dist/finance-normalize.js +66 -0
- package/dist/finance-normalize.js.map +1 -0
- package/dist/finances-service.d.ts +179 -0
- package/dist/finances-service.d.ts.map +1 -0
- package/dist/finances-service.js +1122 -0
- package/dist/finances-service.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/payment-csv-import.d.ts +23 -0
- package/dist/payment-csv-import.d.ts.map +1 -0
- package/dist/payment-csv-import.js +271 -0
- package/dist/payment-csv-import.js.map +1 -0
- package/dist/payment-recurrence.d.ts +14 -0
- package/dist/payment-recurrence.d.ts.map +1 -0
- package/dist/payment-recurrence.js +190 -0
- package/dist/payment-recurrence.js.map +1 -0
- package/dist/payment-types.d.ts +158 -0
- package/dist/payment-types.d.ts.map +1 -0
- package/dist/payment-types.js +1 -0
- package/dist/payment-types.js.map +1 -0
- package/dist/plugin.d.ts +15 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +5 -0
- package/dist/register.js.map +1 -0
- package/dist/services/browser-bridge-seam.d.ts +40 -0
- package/dist/services/browser-bridge-seam.d.ts.map +1 -0
- package/dist/services/browser-bridge-seam.js +39 -0
- package/dist/services/browser-bridge-seam.js.map +1 -0
- package/dist/services/gmail-seam.d.ts +40 -0
- package/dist/services/gmail-seam.d.ts.map +1 -0
- package/dist/services/gmail-seam.js +208 -0
- package/dist/services/gmail-seam.js.map +1 -0
- package/dist/services/migration.d.ts +65 -0
- package/dist/services/migration.d.ts.map +1 -0
- package/dist/services/migration.js +116 -0
- package/dist/services/migration.js.map +1 -0
- package/dist/services/subscriptions-service.d.ts +76 -0
- package/dist/services/subscriptions-service.d.ts.map +1 -0
- package/dist/services/subscriptions-service.js +1002 -0
- package/dist/services/subscriptions-service.js.map +1 -0
- package/dist/subscriptions-playbooks.d.ts +79 -0
- package/dist/subscriptions-playbooks.d.ts.map +1 -0
- package/dist/subscriptions-playbooks.js +871 -0
- package/dist/subscriptions-playbooks.js.map +1 -0
- package/dist/subscriptions-types.d.ts +80 -0
- package/dist/subscriptions-types.d.ts.map +1 -0
- package/dist/subscriptions-types.js +1 -0
- package/dist/subscriptions-types.js.map +1 -0
- package/dist/token-encryption.d.ts +42 -0
- package/dist/token-encryption.d.ts.map +1 -0
- package/dist/token-encryption.js +96 -0
- package/dist/token-encryption.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/views/bundle.js +411 -0
- package/dist/views/bundle.js.map +1 -0
- package/package.json +11 -11
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/finances-service.ts"],"sourcesContent":["/**\n * FinancesService — the finance back-end (payment sources, transactions,\n * spending summaries, recurring-charge detection, email bills, and the\n * Plaid / PayPal managed bridges).\n *\n * This is the standalone successor to the `withPayments` LifeOps service\n * mixin. It holds its own runtime + {@link FinancesRepository} and the small\n * identity / logging helpers the methods need, so it has no dependency on\n * `@elizaos/plugin-personal-assistant`. Behavior and the data it returns are\n * preserved verbatim from the original mixin.\n *\n * Subscription audit / cancellation lives in the sibling\n * `./services/subscriptions-service.ts` (`SubscriptionsService`), which reaches\n * Gmail + the browser bridge through runtime-service seams.\n */\n\nimport crypto from \"node:crypto\";\nimport path from \"node:path\";\nimport { loadElizaConfig, resolveOAuthDir } from \"@elizaos/agent\";\nimport { type IAgentRuntime, logger } from \"@elizaos/core\";\nimport {\n type ElizaCloudManagedClientConfig,\n normalizeCloudSiteUrl,\n normalizeElizaCloudApiKey,\n type PaypalCallbackResponse,\n PaypalManagedClient,\n PaypalManagedClientError,\n type PaypalTransactionDto,\n type PlaidExchangeResponse,\n PlaidManagedClient,\n PlaidManagedClientError,\n type PlaidSyncResponse,\n type PlaidTransactionDto,\n resolveCloudApiBaseUrl,\n} from \"@elizaos/plugin-elizacloud/cloud/managed-payment-clients\";\nimport { FinancesRepository } from \"./db/finances-repository.js\";\nimport {\n fail,\n normalizeOptionalString,\n requireAgentId,\n requireNonEmptyString,\n} from \"./finance-normalize.js\";\nimport {\n type ParsedCsvTransaction,\n parseTransactionsCsv,\n} from \"./payment-csv-import.js\";\nimport {\n detectRecurringCharges,\n normalizeMerchant,\n} from \"./payment-recurrence.js\";\nimport type {\n AddPaymentSourceRequest,\n ImportTransactionsCsvRequest,\n ImportTransactionsCsvResult,\n LifeOpsPaymentSource,\n LifeOpsPaymentSourceKind,\n LifeOpsPaymentsDashboard,\n LifeOpsPaymentTransaction,\n LifeOpsRecurringCharge,\n LifeOpsSpendingCategoryBreakdown,\n LifeOpsSpendingSummary,\n LifeOpsUpcomingBill,\n ListTransactionsRequest,\n SpendingSummaryRequest,\n} from \"./payment-types.js\";\nimport { findLifeOpsSubscriptionPlaybook } from \"./subscriptions-playbooks.js\";\nimport {\n decryptTokenEnvelope,\n type EncryptedTokenEnvelope,\n encryptTokenPayload,\n isEncryptedTokenEnvelope,\n resolveTokenEncryptionKey,\n} from \"./token-encryption.js\";\n\nconst DEFAULT_WINDOW_DAYS = 30;\nconst MS_PER_DAY = 86_400_000;\nconst VALID_SOURCE_KINDS: readonly LifeOpsPaymentSourceKind[] = [\n \"csv\",\n \"plaid\",\n \"manual\",\n \"paypal\",\n \"email\",\n];\n\nconst EMAIL_SOURCE_LABEL = \"Email bills\";\nconst SENSITIVE_PAYMENT_SOURCE_METADATA_KEYS = new Set([\"plaid\", \"paypal\"]);\n\n/** Optional construction options (mirrors the LifeOps service shape). */\nexport type FinancesServiceOptions = {\n ownerEntityId?: string | null;\n};\n\nfunction resolveFinancesCloudManagedClientConfig(): ElizaCloudManagedClientConfig {\n let configKey: string | null = null;\n let configBase: string | null = null;\n try {\n const config = loadElizaConfig();\n const cloud =\n config.cloud && typeof config.cloud === \"object\"\n ? (config.cloud as Record<string, unknown>)\n : null;\n if (cloud) {\n if (typeof cloud.apiKey === \"string\") {\n configKey = normalizeElizaCloudApiKey(cloud.apiKey);\n }\n if (typeof cloud.baseUrl === \"string\" && cloud.baseUrl.trim().length) {\n configBase = cloud.baseUrl.trim();\n }\n }\n } catch {\n // Fall through to env.\n }\n const apiKey =\n configKey ?? normalizeElizaCloudApiKey(process.env.ELIZAOS_CLOUD_API_KEY);\n const baseUrl = configBase ?? process.env.ELIZAOS_CLOUD_BASE_URL ?? undefined;\n return {\n configured: Boolean(apiKey),\n apiKey,\n apiBaseUrl: resolveCloudApiBaseUrl(baseUrl),\n siteUrl: normalizeCloudSiteUrl(baseUrl),\n };\n}\n\ntype PlaidPaymentMetadata = Record<string, unknown> & {\n accessToken?: unknown;\n cursor?: string;\n};\n\ntype PaypalCapability = { hasReporting: boolean; hasIdentity: boolean };\n\ntype PaypalPaymentMetadata = Record<string, unknown> & {\n accessToken?: unknown;\n refreshToken?: unknown;\n tokenExpiresAt?: string;\n scope?: string;\n capability?: PaypalCapability;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isPaypalCapability(value: unknown): value is PaypalCapability {\n return (\n isRecord(value) &&\n typeof value.hasReporting === \"boolean\" &&\n typeof value.hasIdentity === \"boolean\"\n );\n}\n\nfunction readPlaidPaymentMetadata(value: unknown): PlaidPaymentMetadata | null {\n if (!isRecord(value)) {\n return null;\n }\n const metadata: PlaidPaymentMetadata = { ...value };\n if (typeof metadata.cursor !== \"string\") {\n delete metadata.cursor;\n }\n return metadata;\n}\n\nfunction readPaypalPaymentMetadata(\n value: unknown,\n): PaypalPaymentMetadata | null {\n if (!isRecord(value)) {\n return null;\n }\n const metadata: PaypalPaymentMetadata = { ...value };\n if (typeof metadata.tokenExpiresAt !== \"string\") {\n delete metadata.tokenExpiresAt;\n }\n if (typeof metadata.scope !== \"string\") {\n delete metadata.scope;\n }\n if (!isPaypalCapability(metadata.capability)) {\n delete metadata.capability;\n }\n return metadata;\n}\n\nfunction paymentTokenStorageRoot(env: NodeJS.ProcessEnv = process.env): string {\n return path.join(resolveOAuthDir(env), \"lifeops\", \"payments\");\n}\n\nexport function encryptPaymentMetadataToken(\n token: string,\n env: NodeJS.ProcessEnv = process.env,\n): EncryptedTokenEnvelope {\n const normalized = requireNonEmptyString(token, \"token\");\n const key = resolveTokenEncryptionKey(paymentTokenStorageRoot(env), env);\n return encryptTokenPayload(normalized, key);\n}\n\nexport function readPaymentMetadataToken(\n value: unknown,\n field: string,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n if (value === null || value === undefined) {\n return null;\n }\n if (!isEncryptedTokenEnvelope(value)) {\n fail(409, `${field} token metadata is malformed. Re-link the account.`);\n }\n try {\n return decryptTokenEnvelope(\n value,\n resolveTokenEncryptionKey(paymentTokenStorageRoot(env), env),\n );\n } catch {\n fail(\n 409,\n `${field} token metadata could not be decrypted. Restore ELIZA_TOKEN_ENCRYPTION_KEY or re-link the account.`,\n );\n }\n}\n\nexport function sanitizePaymentSourceForClient(\n source: LifeOpsPaymentSource,\n): LifeOpsPaymentSource {\n const metadata: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(source.metadata)) {\n if (!SENSITIVE_PAYMENT_SOURCE_METADATA_KEYS.has(key.toLowerCase())) {\n metadata[key] = value;\n }\n }\n return { ...source, metadata };\n}\n\nfunction normalizeSourceKind(value: unknown): LifeOpsPaymentSourceKind {\n if (typeof value !== \"string\") {\n fail(400, \"paymentSource.kind must be a string.\");\n }\n const normalized = value.trim().toLowerCase();\n if (!VALID_SOURCE_KINDS.includes(normalized as LifeOpsPaymentSourceKind)) {\n fail(\n 400,\n `paymentSource.kind must be one of: ${VALID_SOURCE_KINDS.join(\", \")}.`,\n );\n }\n return normalized as LifeOpsPaymentSourceKind;\n}\n\nfunction buildTransactionId(args: {\n agentId: string;\n sourceId: string;\n parsed: ParsedCsvTransaction;\n}): string {\n // Deterministic id so re-importing the same CSV is idempotent under the\n // unique (agent, source, posted_at, amount, merchant) constraint.\n const key = [\n args.agentId,\n args.sourceId,\n args.parsed.postedAt,\n args.parsed.amountUsd.toFixed(2),\n args.parsed.merchantNormalized,\n args.parsed.rowIndex,\n ].join(\"|\");\n return crypto.createHash(\"sha1\").update(key).digest(\"hex\").slice(0, 32);\n}\n\nfunction computeSpendingSummary(args: {\n transactions: readonly LifeOpsPaymentTransaction[];\n recurring: readonly LifeOpsRecurringCharge[];\n windowDays: number;\n}): LifeOpsSpendingSummary {\n const sinceMs = Date.now() - args.windowDays * MS_PER_DAY;\n const scoped = args.transactions.filter((transaction) => {\n const ms = Date.parse(transaction.postedAt);\n return Number.isFinite(ms) && ms >= sinceMs;\n });\n\n let totalSpend = 0;\n let totalIncome = 0;\n const categoryTotals = new Map<string, { total: number; count: number }>();\n const merchantTotals = new Map<\n string,\n { display: string; total: number; count: number }\n >();\n\n for (const transaction of scoped) {\n if (transaction.direction === \"debit\") {\n totalSpend += transaction.amountUsd;\n const categoryKey = transaction.category ?? \"Uncategorized\";\n const existingCategory = categoryTotals.get(categoryKey);\n if (existingCategory) {\n existingCategory.total += transaction.amountUsd;\n existingCategory.count += 1;\n } else {\n categoryTotals.set(categoryKey, {\n total: transaction.amountUsd,\n count: 1,\n });\n }\n const merchantKey = transaction.merchantNormalized;\n const existingMerchant = merchantTotals.get(merchantKey);\n if (existingMerchant) {\n existingMerchant.total += transaction.amountUsd;\n existingMerchant.count += 1;\n } else {\n merchantTotals.set(merchantKey, {\n display: transaction.merchantRaw,\n total: transaction.amountUsd,\n count: 1,\n });\n }\n } else {\n totalIncome += transaction.amountUsd;\n }\n }\n\n const topCategories: LifeOpsSpendingCategoryBreakdown[] = Array.from(\n categoryTotals.entries(),\n )\n .map(([category, agg]) => ({\n category,\n totalUsd: Number(agg.total.toFixed(2)),\n transactionCount: agg.count,\n }))\n .sort((a, b) => b.totalUsd - a.totalUsd)\n .slice(0, 6);\n\n const topMerchants = Array.from(merchantTotals.entries())\n .map(([merchantNormalized, agg]) => ({\n merchantNormalized,\n merchantDisplay: agg.display,\n totalUsd: Number(agg.total.toFixed(2)),\n transactionCount: agg.count,\n }))\n .sort((a, b) => b.totalUsd - a.totalUsd)\n .slice(0, 10);\n\n const recurringSpendUsd = args.recurring.reduce((total, charge) => {\n if (charge.cadence === \"irregular\") {\n return total;\n }\n const monthly =\n charge.cadence === \"weekly\"\n ? charge.averageAmountUsd * 4.33\n : charge.cadence === \"biweekly\"\n ? charge.averageAmountUsd * 2.17\n : charge.cadence === \"monthly\"\n ? charge.averageAmountUsd\n : charge.cadence === \"quarterly\"\n ? charge.averageAmountUsd / 3\n : charge.averageAmountUsd / 12;\n return total + monthly;\n }, 0);\n\n const toDate = new Date().toISOString();\n const fromDate = new Date(sinceMs).toISOString();\n\n return {\n windowDays: args.windowDays,\n fromDate,\n toDate,\n totalSpendUsd: Number(totalSpend.toFixed(2)),\n totalIncomeUsd: Number(totalIncome.toFixed(2)),\n netUsd: Number((totalIncome - totalSpend).toFixed(2)),\n transactionCount: scoped.length,\n recurringSpendUsd: Number(recurringSpendUsd.toFixed(2)),\n topCategories,\n topMerchants,\n };\n}\n\nexport class FinancesService {\n public readonly repository: FinancesRepository;\n public readonly ownerEntityId: string | null;\n public plaidManagedClientCache: PlaidManagedClient | null = null;\n public paypalManagedClientCache: PaypalManagedClient | null = null;\n\n constructor(\n public readonly runtime: IAgentRuntime,\n options: FinancesServiceOptions = {},\n ) {\n this.repository = new FinancesRepository(runtime);\n this.ownerEntityId = normalizeOptionalString(options.ownerEntityId) ?? null;\n }\n\n agentId(): string {\n return requireAgentId(this.runtime);\n }\n\n private logFinancesWarn(\n operation: string,\n message: string,\n context: Record<string, unknown> = {},\n ): void {\n logger.warn(\n {\n boundary: \"finances\",\n operation,\n agentId: this.agentId(),\n ...context,\n },\n message,\n );\n }\n\n async listPaymentSources(): Promise<LifeOpsPaymentSource[]> {\n const sources = await this.repository.listPaymentSources(this.agentId());\n return sources.map((source) => sanitizePaymentSourceForClient(source));\n }\n\n async addPaymentSource(\n request: AddPaymentSourceRequest,\n ): Promise<LifeOpsPaymentSource> {\n const kind = normalizeSourceKind(request.kind);\n const label = requireNonEmptyString(request.label, \"label\").slice(0, 120);\n const institution =\n normalizeOptionalString(request.institution)?.slice(0, 120) ?? null;\n const accountMask =\n normalizeOptionalString(request.accountMask)?.slice(0, 16) ?? null;\n const now = new Date().toISOString();\n const source: LifeOpsPaymentSource = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n kind,\n label,\n institution,\n accountMask,\n status: kind === \"plaid\" ? \"needs_attention\" : \"active\",\n lastSyncedAt: null,\n transactionCount: 0,\n metadata:\n request.metadata && typeof request.metadata === \"object\"\n ? { ...request.metadata }\n : {},\n createdAt: now,\n updatedAt: now,\n };\n await this.repository.upsertPaymentSource(source);\n return source;\n }\n\n async deletePaymentSource(sourceId: string): Promise<{ ok: true }> {\n const trimmed = requireNonEmptyString(sourceId, \"sourceId\");\n await this.repository.deletePaymentSource(this.agentId(), trimmed);\n return { ok: true };\n }\n\n async importTransactionsCsv(\n request: ImportTransactionsCsvRequest,\n ): Promise<ImportTransactionsCsvResult> {\n const sourceId = requireNonEmptyString(request.sourceId, \"sourceId\");\n const csvText = requireNonEmptyString(request.csvText, \"csvText\");\n const source = await this.repository.getPaymentSource(\n this.agentId(),\n sourceId,\n );\n if (!source) {\n fail(404, `Payment source ${sourceId} not found.`);\n }\n const parsed = parseTransactionsCsv(csvText, {\n dateColumn: request.dateColumn,\n amountColumn: request.amountColumn,\n merchantColumn: request.merchantColumn,\n descriptionColumn: request.descriptionColumn,\n categoryColumn: request.categoryColumn,\n });\n let inserted = 0;\n let skipped = 0;\n for (const txn of parsed.transactions) {\n const record: LifeOpsPaymentTransaction = {\n id: buildTransactionId({\n agentId: this.agentId(),\n sourceId,\n parsed: txn,\n }),\n agentId: this.agentId(),\n sourceId,\n externalId: txn.externalId,\n postedAt: txn.postedAt,\n amountUsd: Number(txn.amountUsd.toFixed(2)),\n direction: txn.direction,\n merchantRaw: txn.merchantRaw,\n merchantNormalized:\n txn.merchantNormalized || normalizeMerchant(txn.merchantRaw),\n description: txn.description,\n category: txn.category,\n currency: txn.currency,\n metadata: { sourceRowIndex: txn.rowIndex },\n createdAt: new Date().toISOString(),\n };\n const didInsert = await this.repository.insertPaymentTransaction(record);\n if (didInsert) {\n inserted += 1;\n } else {\n skipped += 1;\n }\n }\n const newCount = await this.repository.countPaymentTransactionsForSource(\n this.agentId(),\n sourceId,\n );\n await this.repository.upsertPaymentSource({\n ...source,\n status: \"active\",\n lastSyncedAt: new Date().toISOString(),\n transactionCount: newCount,\n updatedAt: new Date().toISOString(),\n });\n return {\n sourceId,\n rowsRead: parsed.rowsRead,\n inserted,\n skipped,\n errors: parsed.errors,\n };\n }\n\n async listTransactions(\n request: ListTransactionsRequest = {},\n ): Promise<LifeOpsPaymentTransaction[]> {\n return this.repository.listPaymentTransactions(this.agentId(), {\n sourceId: normalizeOptionalString(request.sourceId) ?? null,\n sinceAt: normalizeOptionalString(request.sinceAt) ?? null,\n untilAt: normalizeOptionalString(request.untilAt) ?? null,\n limit:\n typeof request.limit === \"number\" && Number.isFinite(request.limit)\n ? Math.trunc(request.limit)\n : null,\n merchantContains:\n normalizeOptionalString(request.merchantContains) ?? null,\n onlyDebits: request.onlyDebits ?? null,\n });\n }\n\n async getRecurringCharges(\n args: { sourceId?: string | null; sinceDays?: number | null } = {},\n ): Promise<LifeOpsRecurringCharge[]> {\n const sinceDays = Math.max(\n 30,\n Math.min(\n 720,\n typeof args.sinceDays === \"number\" && Number.isFinite(args.sinceDays)\n ? Math.trunc(args.sinceDays)\n : 365,\n ),\n );\n const transactions = await this.listTransactions({\n sourceId: args.sourceId ?? null,\n sinceAt: new Date(Date.now() - sinceDays * MS_PER_DAY).toISOString(),\n limit: 5000,\n onlyDebits: true,\n });\n return detectRecurringCharges(transactions);\n }\n\n async getSpendingSummary(\n request: SpendingSummaryRequest = {},\n ): Promise<LifeOpsSpendingSummary> {\n const windowDays = Math.max(\n 1,\n Math.min(\n 365,\n typeof request.windowDays === \"number\" &&\n Number.isFinite(request.windowDays)\n ? Math.trunc(request.windowDays)\n : DEFAULT_WINDOW_DAYS,\n ),\n );\n const transactions = await this.listTransactions({\n sourceId: request.sourceId ?? null,\n sinceAt: new Date(Date.now() - windowDays * MS_PER_DAY).toISOString(),\n limit: 5000,\n });\n const recurring = await this.getRecurringCharges({\n sourceId: request.sourceId ?? null,\n sinceDays: Math.max(windowDays, 180),\n });\n return computeSpendingSummary({\n transactions,\n recurring,\n windowDays,\n });\n }\n\n async getPaymentsDashboard(\n args: { windowDays?: number | null } = {},\n ): Promise<LifeOpsPaymentsDashboard> {\n const windowDays = Math.max(\n 7,\n Math.min(\n 365,\n typeof args.windowDays === \"number\" && Number.isFinite(args.windowDays)\n ? Math.trunc(args.windowDays)\n : DEFAULT_WINDOW_DAYS,\n ),\n );\n const [sources, recurring, spending, upcomingBills] = await Promise.all([\n this.listPaymentSources(),\n this.getRecurringCharges({}),\n this.getSpendingSummary({ windowDays }),\n this.getUpcomingBills(),\n ]);\n const latestAudit = await this.repository.getLatestSubscriptionAudit(\n this.agentId(),\n );\n const recurringPlaybookHits = recurring\n .map((charge) => {\n const direct =\n findLifeOpsSubscriptionPlaybook(charge.merchantDisplay) ??\n findLifeOpsSubscriptionPlaybook(charge.merchantNormalized);\n if (!direct) {\n return null;\n }\n return {\n merchantNormalized: charge.merchantNormalized,\n playbookKey: direct.key,\n serviceName: direct.serviceName,\n managementUrl: direct.managementUrl,\n executorPreference: direct.executorPreference,\n };\n })\n .filter((hit): hit is NonNullable<typeof hit> => hit !== null);\n return {\n sources,\n recurring,\n recurringPlaybookHits,\n spending,\n upcomingBills,\n gmailSubscriptionAuditId: latestAudit?.id ?? null,\n generatedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Look up the singleton \"Email bills\" payment source for this agent,\n * creating it on first use. Bills detected from email are persisted\n * against this source so the existing transactions table can carry them\n * without a parallel schema.\n */\n async getOrCreateEmailPaymentSource(): Promise<LifeOpsPaymentSource> {\n const sources = await this.listPaymentSources();\n const existing = sources.find((source) => source.kind === \"email\");\n if (existing) return existing;\n const now = new Date().toISOString();\n const source: LifeOpsPaymentSource = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n kind: \"email\",\n label: EMAIL_SOURCE_LABEL,\n institution: null,\n accountMask: null,\n status: \"active\",\n lastSyncedAt: now,\n transactionCount: 0,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n };\n await this.repository.upsertPaymentSource(source);\n return source;\n }\n\n /**\n * Idempotent insert of a bill extracted from an email. The transaction\n * id is derived from `(agent, sourceId, sourceMessageId)` so re-ingesting\n * the same Gmail message never creates a duplicate row.\n */\n async upsertBillFromEmail(args: {\n sourceMessageId: string;\n merchant: string;\n amountUsd: number;\n currency: string;\n dueDate: string | null;\n postedAt?: string | null;\n confidence: number;\n }): Promise<{ inserted: boolean; transactionId: string }> {\n const source = await this.getOrCreateEmailPaymentSource();\n const merchantRaw = requireNonEmptyString(args.merchant, \"merchant\").slice(\n 0,\n 200,\n );\n const externalId = `email:${args.sourceMessageId}`;\n const transactionId = crypto\n .createHash(\"sha1\")\n .update(`${this.agentId()}|${source.id}|${args.sourceMessageId}`)\n .digest(\"hex\")\n .slice(0, 32);\n const postedAt =\n normalizeOptionalString(args.postedAt) ?? new Date().toISOString();\n const record: LifeOpsPaymentTransaction = {\n id: transactionId,\n agentId: this.agentId(),\n sourceId: source.id,\n externalId,\n postedAt,\n amountUsd: Number(Math.abs(args.amountUsd).toFixed(2)),\n direction: \"debit\",\n merchantRaw,\n merchantNormalized: merchantRaw.toLowerCase().slice(0, 200),\n description: null,\n category: \"Bills\",\n currency: args.currency || \"USD\",\n metadata: {\n kind: \"bill\",\n sourceMessageId: args.sourceMessageId,\n dueDate: args.dueDate,\n confidence: Number(args.confidence.toFixed(2)),\n },\n createdAt: new Date().toISOString(),\n };\n const inserted = await this.repository.insertPaymentTransaction(record);\n if (inserted) {\n const newCount = await this.repository.countPaymentTransactionsForSource(\n this.agentId(),\n source.id,\n );\n await this.repository.upsertPaymentSource({\n ...source,\n lastSyncedAt: new Date().toISOString(),\n transactionCount: newCount,\n updatedAt: new Date().toISOString(),\n });\n }\n return { inserted, transactionId };\n }\n\n /**\n * Mark a previously-extracted bill as paid. Idempotent — repeated calls\n * just re-stamp the metadata. The row itself is not deleted so the\n * transaction history stays intact.\n */\n async markBillPaid(args: {\n billId: string;\n paidAt?: string | null;\n }): Promise<{ ok: true }> {\n const billId = requireNonEmptyString(args.billId, \"billId\");\n const transactions = await this.repository.listPaymentTransactions(\n this.agentId(),\n { limit: 5000 },\n );\n const target = transactions.find((tx) => tx.id === billId);\n if (!target) {\n fail(404, `Bill ${billId} not found.`);\n }\n const paidAt =\n normalizeOptionalString(args.paidAt) ?? new Date().toISOString();\n const nextMetadata = {\n ...target.metadata,\n kind: \"bill_paid\",\n paidAt,\n };\n await this.repository.deletePaymentTransactionById(this.agentId(), billId);\n await this.repository.insertPaymentTransaction({\n ...target,\n metadata: nextMetadata,\n });\n return { ok: true };\n }\n\n /**\n * Push a bill's due date out by N days. Used for \"Snooze 1w\" UI.\n */\n async snoozeBill(args: {\n billId: string;\n days: number;\n }): Promise<{ ok: true; dueDate: string }> {\n const billId = requireNonEmptyString(args.billId, \"billId\");\n const days =\n Number.isFinite(args.days) && args.days > 0\n ? Math.min(60, Math.trunc(args.days))\n : 7;\n const transactions = await this.repository.listPaymentTransactions(\n this.agentId(),\n { limit: 5000 },\n );\n const target = transactions.find((tx) => tx.id === billId);\n if (!target) {\n fail(404, `Bill ${billId} not found.`);\n }\n const currentDue =\n typeof target.metadata.dueDate === \"string\"\n ? target.metadata.dueDate\n : null;\n const baseDate = currentDue\n ? new Date(`${currentDue}T00:00:00.000Z`)\n : new Date();\n if (Number.isNaN(baseDate.getTime())) {\n fail(409, \"Bill has an unparseable due date.\");\n }\n const nextDue = new Date(baseDate.getTime() + days * 86_400_000)\n .toISOString()\n .slice(0, 10);\n await this.repository.deletePaymentTransactionById(this.agentId(), billId);\n await this.repository.insertPaymentTransaction({\n ...target,\n metadata: {\n ...target.metadata,\n dueDate: nextDue,\n },\n });\n return { ok: true, dueDate: nextDue };\n }\n\n /**\n * Read bills extracted from email. This includes overdue and no-date bills\n * so extraction misses do not disappear from the user's review queue.\n */\n async getUpcomingBills(\n args: { now?: Date } = {},\n ): Promise<LifeOpsUpcomingBill[]> {\n const sources = await this.listPaymentSources();\n const emailSource = sources.find((source) => source.kind === \"email\");\n if (!emailSource) return [];\n const transactions = await this.repository.listPaymentTransactions(\n this.agentId(),\n {\n sourceId: emailSource.id,\n limit: 200,\n },\n );\n const now = args.now ?? new Date();\n const todayIso = now.toISOString().slice(0, 10);\n const bills: LifeOpsUpcomingBill[] = [];\n for (const transaction of transactions) {\n const metadata = transaction.metadata;\n if (metadata.kind !== \"bill\") continue;\n const dueDate =\n typeof metadata.dueDate === \"string\" ? metadata.dueDate : null;\n const status =\n dueDate === null\n ? \"needs_due_date\"\n : dueDate < todayIso\n ? \"overdue\"\n : \"upcoming\";\n const sourceMessageId =\n typeof metadata.sourceMessageId === \"string\"\n ? metadata.sourceMessageId\n : null;\n const confidence =\n typeof metadata.confidence === \"number\" &&\n Number.isFinite(metadata.confidence)\n ? metadata.confidence\n : 0.5;\n bills.push({\n id: transaction.id,\n merchant: transaction.merchantRaw,\n amountUsd: transaction.amountUsd,\n currency: transaction.currency,\n dueDate,\n status,\n postedAt: transaction.postedAt,\n sourceMessageId,\n confidence,\n });\n }\n const statusRank: Record<LifeOpsUpcomingBill[\"status\"], number> = {\n overdue: 0,\n needs_due_date: 1,\n upcoming: 2,\n };\n bills.sort((a, b) => {\n const rankDelta = statusRank[a.status] - statusRank[b.status];\n if (rankDelta !== 0) return rankDelta;\n if (a.dueDate && b.dueDate) return a.dueDate.localeCompare(b.dueDate);\n if (a.dueDate) return 1;\n if (b.dueDate) return -1;\n return b.postedAt.localeCompare(a.postedAt);\n });\n return bills;\n }\n\n summarizePaymentsDashboard(dashboard: LifeOpsPaymentsDashboard): string {\n const lines = [\n `Spent $${dashboard.spending.totalSpendUsd.toFixed(2)} in the last ${dashboard.spending.windowDays} days across ${dashboard.spending.transactionCount} transactions.`,\n ];\n if (dashboard.recurring.length > 0) {\n const annualized = dashboard.recurring.reduce(\n (total, charge) => total + charge.annualizedCostUsd,\n 0,\n );\n lines.push(\n `Detected ${dashboard.recurring.length} recurring charge${dashboard.recurring.length === 1 ? \"\" : \"s\"} worth ~$${annualized.toFixed(2)}/yr.`,\n );\n const topThree = dashboard.recurring.slice(0, 3);\n for (const charge of topThree) {\n lines.push(\n `- ${charge.merchantDisplay} (${charge.cadence}, $${charge.averageAmountUsd.toFixed(2)})`,\n );\n }\n } else {\n lines.push(\n \"No recurring charges detected yet. Import transactions to start tracking.\",\n );\n }\n if (dashboard.sources.length === 0) {\n lines.push(\n \"No payment sources connected. Add one (CSV import) to see your spending.\",\n );\n }\n return lines.join(\"\\n\");\n }\n\n // -----------------------------------------------------------------------\n // Plaid bridge — uses Eliza Cloud as the secret holder for the Plaid\n // access_token. Cloud routes live at /api/v1/eliza/plaid/*.\n // -----------------------------------------------------------------------\n\n getPlaidManagedClient(): PlaidManagedClient {\n if (!this.plaidManagedClientCache) {\n this.plaidManagedClientCache = new PlaidManagedClient(\n resolveFinancesCloudManagedClientConfig,\n );\n }\n return this.plaidManagedClientCache;\n }\n\n /** Returns a Plaid Link token for the frontend to drive the Plaid Link UI. */\n async createPlaidLinkToken(): Promise<{\n linkToken: string;\n expiration: string;\n environment: string;\n }> {\n try {\n return await this.getPlaidManagedClient().createLinkToken();\n } catch (error) {\n if (error instanceof PlaidManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n }\n\n /**\n * Completes a Plaid Link flow by exchanging the public_token for an\n * access_token and creating (or updating) a payment_source row whose\n * metadata holds the access_token + cursor for sync.\n */\n async completePlaidLink(args: {\n publicToken: string;\n label?: string | null;\n }): Promise<LifeOpsPaymentSource> {\n const publicToken = requireNonEmptyString(args.publicToken, \"publicToken\");\n let result: PlaidExchangeResponse;\n try {\n result = await this.getPlaidManagedClient().exchangePublicToken({\n publicToken,\n });\n } catch (error) {\n if (error instanceof PlaidManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n const label =\n normalizeOptionalString(args.label) ??\n `${result.institution.institutionName}${\n result.institution.primaryAccountMask\n ? ` ··${result.institution.primaryAccountMask}`\n : \"\"\n }`;\n const now = new Date().toISOString();\n const source: LifeOpsPaymentSource = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n kind: \"plaid\",\n label: label.slice(0, 120),\n institution: result.institution.institutionName.slice(0, 120),\n accountMask: result.institution.primaryAccountMask?.slice(0, 16) ?? null,\n status: \"active\",\n lastSyncedAt: null,\n transactionCount: 0,\n metadata: {\n plaid: {\n accessToken: encryptPaymentMetadataToken(result.accessToken),\n itemId: result.itemId,\n institutionId: result.institution.institutionId,\n cursor: \"\",\n accounts: result.institution.accounts,\n },\n },\n createdAt: now,\n updatedAt: now,\n };\n await this.repository.upsertPaymentSource(source);\n return source;\n }\n\n /**\n * Pulls the latest transaction delta for a Plaid-backed source and\n * inserts the new rows into life_payment_transactions.\n */\n async syncPlaidTransactions(args: {\n sourceId: string;\n }): Promise<{ inserted: number; skipped: number; nextCursor: string }> {\n const sourceId = requireNonEmptyString(args.sourceId, \"sourceId\");\n const source = await this.repository.getPaymentSource(\n this.agentId(),\n sourceId,\n );\n if (!source) {\n fail(404, `Payment source ${sourceId} not found.`);\n }\n if (source.kind !== \"plaid\") {\n fail(409, `Source ${sourceId} is not a Plaid source.`);\n }\n const plaidMetadata = readPlaidPaymentMetadata(source.metadata.plaid);\n const accessToken = readPaymentMetadataToken(\n plaidMetadata?.accessToken,\n \"Plaid access\",\n );\n if (!accessToken) {\n fail(\n 409,\n \"Plaid source is missing an access token. Re-link the account.\",\n );\n }\n const cursor = plaidMetadata?.cursor ?? \"\";\n\n let cumulativeInserted = 0;\n let cumulativeSkipped = 0;\n let pageCursor = cursor;\n let hasMore = true;\n let pageGuard = 0;\n while (hasMore && pageGuard < 20) {\n let delta: PlaidSyncResponse;\n try {\n delta = await this.getPlaidManagedClient().syncTransactions({\n accessToken,\n cursor: pageCursor,\n });\n } catch (error) {\n if (error instanceof PlaidManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n for (const transaction of delta.added) {\n const inserted = await this.upsertPlaidTransaction({\n sourceId,\n transaction,\n });\n if (inserted) {\n cumulativeInserted += 1;\n } else {\n cumulativeSkipped += 1;\n }\n }\n for (const transaction of delta.modified) {\n await this.upsertPlaidTransaction({\n sourceId,\n transaction,\n });\n }\n pageCursor = delta.nextCursor;\n hasMore = delta.hasMore;\n pageGuard += 1;\n }\n const newCount = await this.repository.countPaymentTransactionsForSource(\n this.agentId(),\n sourceId,\n );\n await this.repository.upsertPaymentSource({\n ...source,\n status: \"active\",\n lastSyncedAt: new Date().toISOString(),\n transactionCount: newCount,\n metadata: {\n ...source.metadata,\n plaid: {\n ...plaidMetadata,\n accessToken: encryptPaymentMetadataToken(accessToken),\n cursor: pageCursor,\n },\n },\n updatedAt: new Date().toISOString(),\n });\n return {\n inserted: cumulativeInserted,\n skipped: cumulativeSkipped,\n nextCursor: pageCursor,\n };\n }\n\n // -----------------------------------------------------------------------\n // PayPal bridge — uses Eliza Cloud as the OAuth + Reporting API proxy.\n // Cloud routes live at /api/v1/eliza/paypal/*.\n //\n // Personal-tier PayPal accounts CANNOT use the Reporting API. The cloud\n // surfaces this as a 403 with `fallback: \"csv_export\"`; we propagate\n // that to the caller via PaypalManagedClientError.fallback so the UI\n // can route the user to CSV import.\n // -----------------------------------------------------------------------\n\n getPaypalManagedClient(): PaypalManagedClient {\n if (!this.paypalManagedClientCache) {\n this.paypalManagedClientCache = new PaypalManagedClient(\n resolveFinancesCloudManagedClientConfig,\n );\n }\n return this.paypalManagedClientCache;\n }\n\n /** Returns a PayPal Login URL the frontend should open in a popup. */\n async createPaypalAuthorizeUrl(args: { state: string }): Promise<{\n url: string;\n scope: string;\n environment: \"live\" | \"sandbox\";\n }> {\n const state = requireNonEmptyString(args.state, \"state\");\n try {\n return await this.getPaypalManagedClient().buildAuthorizeUrl({ state });\n } catch (error) {\n if (error instanceof PaypalManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n }\n\n /**\n * Completes the PayPal OAuth flow by exchanging the authorization code\n * for tokens, then creating a payment_source row keyed to the PayPal\n * payer. The access_token + refresh_token are stored in source.metadata\n * so the runtime can refresh on demand without re-prompting the user.\n */\n async completePaypalLink(args: {\n code: string;\n label?: string | null;\n }): Promise<{\n source: LifeOpsPaymentSource;\n capability: { hasReporting: boolean; hasIdentity: boolean };\n }> {\n const code = requireNonEmptyString(args.code, \"code\");\n let exchange: PaypalCallbackResponse;\n try {\n exchange = await this.getPaypalManagedClient().exchangeCode({ code });\n } catch (error) {\n if (error instanceof PaypalManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n const display =\n exchange.identity?.name ??\n exchange.identity?.emails[0] ??\n exchange.identity?.payerId ??\n \"PayPal\";\n const label = normalizeOptionalString(args.label) ?? `PayPal · ${display}`;\n const tokenExpiresAt = new Date(\n Date.now() + Math.max(0, exchange.expiresIn - 60) * 1_000,\n ).toISOString();\n const now = new Date().toISOString();\n const source: LifeOpsPaymentSource = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n kind: \"paypal\",\n label: label.slice(0, 120),\n institution: \"PayPal\",\n accountMask: null,\n status: exchange.capability.hasReporting ? \"active\" : \"needs_attention\",\n lastSyncedAt: null,\n transactionCount: 0,\n metadata: {\n paypal: {\n accessToken: encryptPaymentMetadataToken(exchange.accessToken),\n refreshToken: exchange.refreshToken\n ? encryptPaymentMetadataToken(exchange.refreshToken)\n : null,\n tokenExpiresAt,\n scope: exchange.scope,\n capability: exchange.capability,\n payerId: exchange.identity?.payerId ?? null,\n payerEmails: exchange.identity?.emails ?? [],\n },\n },\n createdAt: now,\n updatedAt: now,\n };\n await this.repository.upsertPaymentSource(source);\n return { source, capability: exchange.capability };\n }\n\n /**\n * Pulls PayPal transactions for a date window via the Reporting API.\n * Returns the imported count and an explicit `fallback: \"csv_export\"`\n * flag when the account is personal-tier.\n */\n async syncPaypalTransactions(args: {\n sourceId: string;\n windowDays?: number | null;\n }): Promise<{\n inserted: number;\n skipped: number;\n fallback: \"csv_export\" | null;\n }> {\n const sourceId = requireNonEmptyString(args.sourceId, \"sourceId\");\n const source = await this.repository.getPaymentSource(\n this.agentId(),\n sourceId,\n );\n if (!source) {\n fail(404, `Payment source ${sourceId} not found.`);\n }\n if (source.kind !== \"paypal\") {\n fail(409, `Source ${sourceId} is not a PayPal source.`);\n }\n let paypalMetadata = readPaypalPaymentMetadata(source.metadata.paypal);\n let accessToken = readPaymentMetadataToken(\n paypalMetadata?.accessToken,\n \"PayPal access\",\n );\n let refreshToken = readPaymentMetadataToken(\n paypalMetadata?.refreshToken,\n \"PayPal refresh\",\n );\n if (!accessToken) {\n fail(409, \"PayPal source is missing an access token. Re-link.\");\n }\n // Refresh if we're within 60s of expiry — saves a round-trip 401.\n const expiryMs = paypalMetadata?.tokenExpiresAt\n ? Date.parse(paypalMetadata.tokenExpiresAt)\n : 0;\n if (Number.isFinite(expiryMs) && expiryMs <= Date.now() + 60_000) {\n if (refreshToken) {\n try {\n const refreshed =\n await this.getPaypalManagedClient().refreshAccessToken({\n refreshToken,\n });\n accessToken = refreshed.accessToken;\n refreshToken = refreshed.refreshToken ?? refreshToken;\n const tokenExpiresAt = new Date(\n Date.now() + Math.max(0, refreshed.expiresIn - 60) * 1_000,\n ).toISOString();\n paypalMetadata = {\n ...paypalMetadata,\n accessToken: encryptPaymentMetadataToken(accessToken),\n refreshToken: refreshToken\n ? encryptPaymentMetadataToken(refreshToken)\n : null,\n tokenExpiresAt,\n scope: refreshed.scope,\n };\n await this.repository.upsertPaymentSource({\n ...source,\n metadata: {\n ...source.metadata,\n paypal: paypalMetadata,\n },\n updatedAt: new Date().toISOString(),\n });\n } catch (error) {\n // Refresh failed — fall through with the stale token; the\n // search call below will likely 401 and surface a clear error.\n this.logFinancesWarn(\n \"paypal_refresh\",\n `PayPal refresh failed for ${sourceId}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n );\n }\n }\n }\n\n const windowDays = Math.max(\n 7,\n Math.min(\n 365,\n typeof args.windowDays === \"number\" && Number.isFinite(args.windowDays)\n ? Math.trunc(args.windowDays)\n : 90,\n ),\n );\n const now = new Date();\n const startDate = new Date(\n now.getTime() - windowDays * MS_PER_DAY,\n ).toISOString();\n const endDate = now.toISOString();\n\n let inserted = 0;\n let skipped = 0;\n let page = 1;\n let totalPages = 1;\n try {\n do {\n const result = await this.getPaypalManagedClient().searchTransactions({\n accessToken,\n startDate,\n endDate,\n page,\n });\n totalPages = result.totalPages;\n for (const transaction of result.transactions) {\n const wasInserted = await this.upsertPaypalTransaction({\n sourceId,\n transaction,\n });\n if (wasInserted) {\n inserted += 1;\n } else {\n skipped += 1;\n }\n }\n page += 1;\n } while (page <= totalPages && page <= 50);\n } catch (error) {\n if (\n error instanceof PaypalManagedClientError &&\n error.fallback === \"csv_export\"\n ) {\n // Personal-tier — mark the source so the UI nudges to CSV import.\n await this.repository.upsertPaymentSource({\n ...source,\n status: \"needs_attention\",\n metadata: {\n ...source.metadata,\n paypal: {\n ...paypalMetadata,\n accessToken: encryptPaymentMetadataToken(accessToken),\n refreshToken: refreshToken\n ? encryptPaymentMetadataToken(refreshToken)\n : null,\n capability: { hasReporting: false, hasIdentity: true },\n lastFallbackError: error.message,\n },\n },\n updatedAt: new Date().toISOString(),\n });\n return { inserted: 0, skipped: 0, fallback: \"csv_export\" };\n }\n if (error instanceof PaypalManagedClientError) {\n fail(error.status, error.message);\n }\n throw error;\n }\n\n const newCount = await this.repository.countPaymentTransactionsForSource(\n this.agentId(),\n sourceId,\n );\n await this.repository.upsertPaymentSource({\n ...source,\n status: \"active\",\n lastSyncedAt: new Date().toISOString(),\n transactionCount: newCount,\n metadata: {\n ...source.metadata,\n paypal: {\n ...paypalMetadata,\n accessToken: encryptPaymentMetadataToken(accessToken),\n refreshToken: refreshToken\n ? encryptPaymentMetadataToken(refreshToken)\n : null,\n },\n },\n updatedAt: new Date().toISOString(),\n });\n return { inserted, skipped, fallback: null };\n }\n\n async upsertPaypalTransaction(args: {\n sourceId: string;\n transaction: PaypalTransactionDto;\n }): Promise<boolean> {\n const txn = args.transaction;\n const amountValue = Number(txn.transaction_info.transaction_amount.value);\n if (!Number.isFinite(amountValue)) {\n return false;\n }\n // PayPal convention: positive = money IN (credit), negative = money OUT.\n // Our schema uses the absolute value + a `direction` enum.\n const direction = amountValue < 0 ? \"debit\" : \"credit\";\n const merchantRaw = (\n txn.payer_info?.payer_name?.alternate_full_name ??\n txn.payer_info?.email_address ??\n txn.shipping_info?.name ??\n txn.transaction_info.transaction_subject ??\n \"PayPal payment\"\n ).trim();\n const merchantNormalized = normalizeMerchant(merchantRaw);\n const description =\n txn.transaction_info.transaction_subject ??\n txn.transaction_info.transaction_note ??\n txn.cart_info?.item_details?.[0]?.item_name ??\n null;\n const record: LifeOpsPaymentTransaction = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n sourceId: args.sourceId,\n externalId: txn.transaction_info.transaction_id,\n postedAt: new Date(\n txn.transaction_info.transaction_initiation_date,\n ).toISOString(),\n amountUsd: Number(Math.abs(amountValue).toFixed(2)),\n direction,\n merchantRaw,\n merchantNormalized,\n description,\n category: null,\n currency: txn.transaction_info.transaction_amount.currency_code,\n metadata: {\n paypalTransactionId: txn.transaction_info.transaction_id,\n paypalStatus: txn.transaction_info.transaction_status,\n },\n createdAt: new Date().toISOString(),\n };\n return this.repository.insertPaymentTransaction(record);\n }\n\n async upsertPlaidTransaction(args: {\n sourceId: string;\n transaction: PlaidTransactionDto;\n }): Promise<boolean> {\n const txn = args.transaction;\n // Plaid `amount` convention: positive = money OUT (debit), negative =\n // money IN (credit/refund). Our schema stores the absolute USD amount\n // and a `direction` enum.\n const direction = txn.amount >= 0 ? \"debit\" : \"credit\";\n const merchantRaw = (txn.merchant_name ?? txn.name).trim();\n const merchantNormalized = normalizeMerchant(merchantRaw);\n const category =\n txn.personal_finance_category?.detailed ??\n txn.personal_finance_category?.primary ??\n txn.category?.[0] ??\n null;\n const record: LifeOpsPaymentTransaction = {\n id: crypto.randomUUID(),\n agentId: this.agentId(),\n sourceId: args.sourceId,\n externalId: txn.transaction_id,\n postedAt: txn.authorized_date\n ? `${txn.authorized_date}T00:00:00.000Z`\n : `${txn.date}T00:00:00.000Z`,\n amountUsd: Number(Math.abs(txn.amount).toFixed(2)),\n direction,\n merchantRaw,\n merchantNormalized,\n description: txn.name,\n category,\n currency: txn.iso_currency_code ?? \"USD\",\n metadata: {\n accountId: txn.account_id,\n pending: txn.pending,\n plaidTransactionId: txn.transaction_id,\n },\n createdAt: new Date().toISOString(),\n };\n return this.repository.insertPaymentTransaction(record);\n }\n}\n"],"mappings":"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","names":["source"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public entry for @elizaos/plugin-finances.
|
|
3
|
+
*
|
|
4
|
+
* Default export is the runtime Plugin object. Named exports expose the
|
|
5
|
+
* finance back-end (service, repository, action handler + parameter schema),
|
|
6
|
+
* the schema/types, and the React view component so other packages can import
|
|
7
|
+
* them directly — most notably `@elizaos/plugin-personal-assistant`, which
|
|
8
|
+
* registers the OWNER_FINANCES umbrella action + the /api/lifeops/money/*
|
|
9
|
+
* routes and delegates the payments back-end here.
|
|
10
|
+
*/
|
|
11
|
+
export { MONEY_CONTEXTS, MONEY_PARAMETERS, MONEY_TAGS, OWNER_FINANCE_SIMILES, runPaymentsHandler, } from "./actions/finances.js";
|
|
12
|
+
export { EMPTY_FINANCES_SNAPSHOT, type FinanceBalanceCard, type FinanceRecurringCard, type FinancesSnapshot, FinancesSpatialView, type FinancesViewState, type FinanceTransactionCard, } from "./components/finances/FinancesSpatialView.js";
|
|
13
|
+
export { type FinancesFetchers, FinancesView, type FinancesViewProps as FinancesViewComponentProps, formatMinor, } from "./components/finances/FinancesView.js";
|
|
14
|
+
export { createLifeOpsSubscriptionAudit, createLifeOpsSubscriptionCancellation, createLifeOpsSubscriptionCandidate, FinancesRepository, } from "./db/finances-repository.js";
|
|
15
|
+
export { financesDbSchema, financesSchema, type LifePaymentSourceInsert, type LifePaymentSourceRow, type LifePaymentTransactionInsert, type LifePaymentTransactionRow, type LifeSubscriptionAuditInsert, type LifeSubscriptionAuditRow, type LifeSubscriptionCancellationInsert, type LifeSubscriptionCancellationRow, type LifeSubscriptionCandidateInsert, type LifeSubscriptionCandidateRow, lifePaymentSources, lifePaymentTransactions, lifeSubscriptionAudits, lifeSubscriptionCancellations, lifeSubscriptionCandidates, } from "./db/schema.js";
|
|
16
|
+
export { FinancesServiceError, financeErrorMessage, } from "./finance-normalize.js";
|
|
17
|
+
export { encryptPaymentMetadataToken, FinancesService, type FinancesServiceOptions, readPaymentMetadataToken, sanitizePaymentSourceForClient, } from "./finances-service.js";
|
|
18
|
+
export * from "./payment-csv-import.js";
|
|
19
|
+
export * from "./payment-recurrence.js";
|
|
20
|
+
export * from "./payment-types.js";
|
|
21
|
+
export { default, financesPlugin } from "./plugin.js";
|
|
22
|
+
export { registerFinancesTerminalView, setFinancesTerminalSnapshot, } from "./register-terminal-view.js";
|
|
23
|
+
export { createSubscriptionsBrowserGateway, type SubscriptionsBrowserGateway, } from "./services/browser-bridge-seam.js";
|
|
24
|
+
export { createSubscriptionsGmailGateway, type SubscriptionsGmailGateway, } from "./services/gmail-seam.js";
|
|
25
|
+
export { FinancesMigrationService } from "./services/migration.js";
|
|
26
|
+
export { SubscriptionsService, type SubscriptionsServiceOptions, } from "./services/subscriptions-service.js";
|
|
27
|
+
export * from "./subscriptions-playbooks.js";
|
|
28
|
+
export * from "./subscriptions-types.js";
|
|
29
|
+
export { decryptTokenEnvelope, type EncryptedTokenEnvelope, encryptTokenPayload, isEncryptedTokenEnvelope, resolveTokenEncryptionKey, } from "./token-encryption.js";
|
|
30
|
+
export * from "./types.js";
|
|
31
|
+
import "./register.js";
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,uBAAuB,EACvB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,+CAA+C,CAAC;AACvD,OAAO,EACL,KAAK,gBAAgB,EACrB,YAAY,EACZ,KAAK,iBAAiB,IAAI,0BAA0B,EACpD,WAAW,GACZ,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,8BAA8B,EAC9B,qCAAqC,EACrC,kCAAkC,EAClC,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,kCAAkC,EACvC,KAAK,+BAA+B,EACpC,KAAK,+BAA+B,EACpC,KAAK,4BAA4B,EACjC,kBAAkB,EAClB,uBAAuB,EACvB,sBAAsB,EACtB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,KAAK,sBAAsB,EAC3B,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EACL,4BAA4B,EAC5B,2BAA2B,GAC5B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,iCAAiC,EACjC,KAAK,2BAA2B,GACjC,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,+BAA+B,EAC/B,KAAK,yBAAyB,GAC/B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,GACjC,MAAM,qCAAqC,CAAC;AAC7C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,OAAO,EACL,oBAAoB,EACpB,KAAK,sBAAsB,EAC3B,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAK3B,OAAO,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MONEY_CONTEXTS,
|
|
3
|
+
MONEY_PARAMETERS,
|
|
4
|
+
MONEY_TAGS,
|
|
5
|
+
OWNER_FINANCE_SIMILES,
|
|
6
|
+
runPaymentsHandler
|
|
7
|
+
} from "./actions/finances.js";
|
|
8
|
+
import {
|
|
9
|
+
EMPTY_FINANCES_SNAPSHOT,
|
|
10
|
+
FinancesSpatialView
|
|
11
|
+
} from "./components/finances/FinancesSpatialView.js";
|
|
12
|
+
import {
|
|
13
|
+
FinancesView,
|
|
14
|
+
formatMinor
|
|
15
|
+
} from "./components/finances/FinancesView.js";
|
|
16
|
+
import {
|
|
17
|
+
createLifeOpsSubscriptionAudit,
|
|
18
|
+
createLifeOpsSubscriptionCancellation,
|
|
19
|
+
createLifeOpsSubscriptionCandidate,
|
|
20
|
+
FinancesRepository
|
|
21
|
+
} from "./db/finances-repository.js";
|
|
22
|
+
import {
|
|
23
|
+
financesDbSchema,
|
|
24
|
+
financesSchema,
|
|
25
|
+
lifePaymentSources,
|
|
26
|
+
lifePaymentTransactions,
|
|
27
|
+
lifeSubscriptionAudits,
|
|
28
|
+
lifeSubscriptionCancellations,
|
|
29
|
+
lifeSubscriptionCandidates
|
|
30
|
+
} from "./db/schema.js";
|
|
31
|
+
import {
|
|
32
|
+
FinancesServiceError,
|
|
33
|
+
financeErrorMessage
|
|
34
|
+
} from "./finance-normalize.js";
|
|
35
|
+
import {
|
|
36
|
+
encryptPaymentMetadataToken,
|
|
37
|
+
FinancesService,
|
|
38
|
+
readPaymentMetadataToken,
|
|
39
|
+
sanitizePaymentSourceForClient
|
|
40
|
+
} from "./finances-service.js";
|
|
41
|
+
export * from "./payment-csv-import.js";
|
|
42
|
+
export * from "./payment-recurrence.js";
|
|
43
|
+
export * from "./payment-types.js";
|
|
44
|
+
import { default as default2, financesPlugin } from "./plugin.js";
|
|
45
|
+
import {
|
|
46
|
+
registerFinancesTerminalView,
|
|
47
|
+
setFinancesTerminalSnapshot
|
|
48
|
+
} from "./register-terminal-view.js";
|
|
49
|
+
import {
|
|
50
|
+
createSubscriptionsBrowserGateway
|
|
51
|
+
} from "./services/browser-bridge-seam.js";
|
|
52
|
+
import {
|
|
53
|
+
createSubscriptionsGmailGateway
|
|
54
|
+
} from "./services/gmail-seam.js";
|
|
55
|
+
import { FinancesMigrationService } from "./services/migration.js";
|
|
56
|
+
import {
|
|
57
|
+
SubscriptionsService
|
|
58
|
+
} from "./services/subscriptions-service.js";
|
|
59
|
+
export * from "./subscriptions-playbooks.js";
|
|
60
|
+
export * from "./subscriptions-types.js";
|
|
61
|
+
import {
|
|
62
|
+
decryptTokenEnvelope,
|
|
63
|
+
encryptTokenPayload,
|
|
64
|
+
isEncryptedTokenEnvelope,
|
|
65
|
+
resolveTokenEncryptionKey
|
|
66
|
+
} from "./token-encryption.js";
|
|
67
|
+
export * from "./types.js";
|
|
68
|
+
import "./register.js";
|
|
69
|
+
export {
|
|
70
|
+
EMPTY_FINANCES_SNAPSHOT,
|
|
71
|
+
FinancesMigrationService,
|
|
72
|
+
FinancesRepository,
|
|
73
|
+
FinancesService,
|
|
74
|
+
FinancesServiceError,
|
|
75
|
+
FinancesSpatialView,
|
|
76
|
+
FinancesView,
|
|
77
|
+
MONEY_CONTEXTS,
|
|
78
|
+
MONEY_PARAMETERS,
|
|
79
|
+
MONEY_TAGS,
|
|
80
|
+
OWNER_FINANCE_SIMILES,
|
|
81
|
+
SubscriptionsService,
|
|
82
|
+
createLifeOpsSubscriptionAudit,
|
|
83
|
+
createLifeOpsSubscriptionCancellation,
|
|
84
|
+
createLifeOpsSubscriptionCandidate,
|
|
85
|
+
createSubscriptionsBrowserGateway,
|
|
86
|
+
createSubscriptionsGmailGateway,
|
|
87
|
+
decryptTokenEnvelope,
|
|
88
|
+
default2 as default,
|
|
89
|
+
encryptPaymentMetadataToken,
|
|
90
|
+
encryptTokenPayload,
|
|
91
|
+
financeErrorMessage,
|
|
92
|
+
financesDbSchema,
|
|
93
|
+
financesPlugin,
|
|
94
|
+
financesSchema,
|
|
95
|
+
formatMinor,
|
|
96
|
+
isEncryptedTokenEnvelope,
|
|
97
|
+
lifePaymentSources,
|
|
98
|
+
lifePaymentTransactions,
|
|
99
|
+
lifeSubscriptionAudits,
|
|
100
|
+
lifeSubscriptionCancellations,
|
|
101
|
+
lifeSubscriptionCandidates,
|
|
102
|
+
readPaymentMetadataToken,
|
|
103
|
+
registerFinancesTerminalView,
|
|
104
|
+
resolveTokenEncryptionKey,
|
|
105
|
+
runPaymentsHandler,
|
|
106
|
+
sanitizePaymentSourceForClient,
|
|
107
|
+
setFinancesTerminalSnapshot
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Public entry for @elizaos/plugin-finances.\n *\n * Default export is the runtime Plugin object. Named exports expose the\n * finance back-end (service, repository, action handler + parameter schema),\n * the schema/types, and the React view component so other packages can import\n * them directly — most notably `@elizaos/plugin-personal-assistant`, which\n * registers the OWNER_FINANCES umbrella action + the /api/lifeops/money/*\n * routes and delegates the payments back-end here.\n */\n\nexport {\n MONEY_CONTEXTS,\n MONEY_PARAMETERS,\n MONEY_TAGS,\n OWNER_FINANCE_SIMILES,\n runPaymentsHandler,\n} from \"./actions/finances.js\";\nexport {\n EMPTY_FINANCES_SNAPSHOT,\n type FinanceBalanceCard,\n type FinanceRecurringCard,\n type FinancesSnapshot,\n FinancesSpatialView,\n type FinancesViewState,\n type FinanceTransactionCard,\n} from \"./components/finances/FinancesSpatialView.js\";\nexport {\n type FinancesFetchers,\n FinancesView,\n type FinancesViewProps as FinancesViewComponentProps,\n formatMinor,\n} from \"./components/finances/FinancesView.js\";\nexport {\n createLifeOpsSubscriptionAudit,\n createLifeOpsSubscriptionCancellation,\n createLifeOpsSubscriptionCandidate,\n FinancesRepository,\n} from \"./db/finances-repository.js\";\nexport {\n financesDbSchema,\n financesSchema,\n type LifePaymentSourceInsert,\n type LifePaymentSourceRow,\n type LifePaymentTransactionInsert,\n type LifePaymentTransactionRow,\n type LifeSubscriptionAuditInsert,\n type LifeSubscriptionAuditRow,\n type LifeSubscriptionCancellationInsert,\n type LifeSubscriptionCancellationRow,\n type LifeSubscriptionCandidateInsert,\n type LifeSubscriptionCandidateRow,\n lifePaymentSources,\n lifePaymentTransactions,\n lifeSubscriptionAudits,\n lifeSubscriptionCancellations,\n lifeSubscriptionCandidates,\n} from \"./db/schema.js\";\nexport {\n FinancesServiceError,\n financeErrorMessage,\n} from \"./finance-normalize.js\";\nexport {\n encryptPaymentMetadataToken,\n FinancesService,\n type FinancesServiceOptions,\n readPaymentMetadataToken,\n sanitizePaymentSourceForClient,\n} from \"./finances-service.js\";\nexport * from \"./payment-csv-import.js\";\nexport * from \"./payment-recurrence.js\";\nexport * from \"./payment-types.js\";\nexport { default, financesPlugin } from \"./plugin.js\";\nexport {\n registerFinancesTerminalView,\n setFinancesTerminalSnapshot,\n} from \"./register-terminal-view.js\";\nexport {\n createSubscriptionsBrowserGateway,\n type SubscriptionsBrowserGateway,\n} from \"./services/browser-bridge-seam.js\";\nexport {\n createSubscriptionsGmailGateway,\n type SubscriptionsGmailGateway,\n} from \"./services/gmail-seam.js\";\nexport { FinancesMigrationService } from \"./services/migration.js\";\nexport {\n SubscriptionsService,\n type SubscriptionsServiceOptions,\n} from \"./services/subscriptions-service.js\";\nexport * from \"./subscriptions-playbooks.js\";\nexport * from \"./subscriptions-types.js\";\nexport {\n decryptTokenEnvelope,\n type EncryptedTokenEnvelope,\n encryptTokenPayload,\n isEncryptedTokenEnvelope,\n resolveTokenEncryptionKey,\n} from \"./token-encryption.js\";\nexport * from \"./types.js\";\n\n// Side-effect: in a terminal host (Node agent, no DOM) this registers the\n// finances terminal view. DOM-guarded so the terminal engine stays out of\n// browser bundles.\nimport \"./register.js\";\n"],"mappings":"AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EAIA;AAAA,OAGK;AACP;AAAA,EAEE;AAAA,EAEA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EAWA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,WAAAA,UAAS,sBAAsB;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,OAEK;AACP,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,cAAc;AAKd,OAAO;","names":["default"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { LifeOpsPaymentTransaction } from "./payment-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal RFC 4180 CSV parser. Handles quoted fields with embedded commas
|
|
4
|
+
* and escaped double quotes. Returns rows as string arrays.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseCsv(text: string): string[][];
|
|
7
|
+
export interface ParsedCsvTransaction extends Pick<LifeOpsPaymentTransaction, "postedAt" | "amountUsd" | "direction" | "merchantRaw" | "merchantNormalized" | "description" | "category" | "currency" | "externalId"> {
|
|
8
|
+
rowIndex: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ParseCsvOptions {
|
|
11
|
+
dateColumn?: string;
|
|
12
|
+
amountColumn?: string;
|
|
13
|
+
merchantColumn?: string;
|
|
14
|
+
descriptionColumn?: string;
|
|
15
|
+
categoryColumn?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ParseCsvResult {
|
|
18
|
+
transactions: ParsedCsvTransaction[];
|
|
19
|
+
rowsRead: number;
|
|
20
|
+
errors: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function parseTransactionsCsv(csvText: string, options?: ParseCsvOptions): ParseCsvResult;
|
|
23
|
+
//# sourceMappingURL=payment-csv-import.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-csv-import.d.ts","sourceRoot":"","sources":["../src/payment-csv-import.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,yBAAyB,EAC1B,MAAM,oBAAoB,CAAC;AAuB5B;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,CAuDjD;AAsGD,MAAM,WAAW,oBACf,SAAQ,IAAI,CACV,yBAAyB,EACvB,UAAU,GACV,WAAW,GACX,WAAW,GACX,aAAa,GACb,oBAAoB,GACpB,aAAa,GACb,UAAU,GACV,UAAU,GACV,YAAY,CACf;IACD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAkBD,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,eAAoB,GAC5B,cAAc,CAqGhB"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { normalizeMerchant } from "./payment-recurrence.js";
|
|
2
|
+
const DATE_COLUMN_HINTS = ["date", "posted", "posted date", "transaction date"];
|
|
3
|
+
const AMOUNT_COLUMN_HINTS = ["amount", "amount (usd)", "transaction amount"];
|
|
4
|
+
const DEBIT_COLUMN_HINTS = ["debit", "withdrawal", "amount debit"];
|
|
5
|
+
const CREDIT_COLUMN_HINTS = ["credit", "deposit", "amount credit"];
|
|
6
|
+
const MERCHANT_COLUMN_HINTS = [
|
|
7
|
+
"merchant",
|
|
8
|
+
"payee",
|
|
9
|
+
"name",
|
|
10
|
+
"description",
|
|
11
|
+
"memo",
|
|
12
|
+
"details"
|
|
13
|
+
];
|
|
14
|
+
const CATEGORY_COLUMN_HINTS = [
|
|
15
|
+
"category",
|
|
16
|
+
"transaction category",
|
|
17
|
+
"plaid category"
|
|
18
|
+
];
|
|
19
|
+
function parseCsv(text) {
|
|
20
|
+
const rows = [];
|
|
21
|
+
let current = [];
|
|
22
|
+
let field = "";
|
|
23
|
+
let inQuotes = false;
|
|
24
|
+
let index = 0;
|
|
25
|
+
while (index < text.length) {
|
|
26
|
+
const char = text[index];
|
|
27
|
+
if (inQuotes) {
|
|
28
|
+
if (char === '"') {
|
|
29
|
+
if (text[index + 1] === '"') {
|
|
30
|
+
field += '"';
|
|
31
|
+
index += 2;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
inQuotes = false;
|
|
35
|
+
index += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
field += char;
|
|
39
|
+
index += 1;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === '"') {
|
|
43
|
+
inQuotes = true;
|
|
44
|
+
index += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === ",") {
|
|
48
|
+
current.push(field);
|
|
49
|
+
field = "";
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (char === "\r") {
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (char === "\n") {
|
|
58
|
+
current.push(field);
|
|
59
|
+
rows.push(current);
|
|
60
|
+
current = [];
|
|
61
|
+
field = "";
|
|
62
|
+
index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
field += char;
|
|
66
|
+
index += 1;
|
|
67
|
+
}
|
|
68
|
+
if (field.length > 0 || current.length > 0) {
|
|
69
|
+
current.push(field);
|
|
70
|
+
rows.push(current);
|
|
71
|
+
}
|
|
72
|
+
return rows.filter((row) => row.some((value) => value.trim().length > 0));
|
|
73
|
+
}
|
|
74
|
+
function findColumn(header, hints) {
|
|
75
|
+
for (let index = 0; index < header.length; index += 1) {
|
|
76
|
+
const normalized = header[index].trim().toLowerCase();
|
|
77
|
+
if (hints.includes(normalized)) {
|
|
78
|
+
return index;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (let index = 0; index < header.length; index += 1) {
|
|
82
|
+
const normalized = header[index].trim().toLowerCase();
|
|
83
|
+
for (const hint of hints) {
|
|
84
|
+
if (normalized.includes(hint)) {
|
|
85
|
+
return index;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return -1;
|
|
90
|
+
}
|
|
91
|
+
function parseAmount(row, amountIndex, debitIndex, creditIndex) {
|
|
92
|
+
const readNumber = (raw) => {
|
|
93
|
+
if (raw === void 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
const cleaned = raw.replace(/[$,]/g, "").trim();
|
|
97
|
+
if (!cleaned) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const negative = /^\(.+\)$/.test(cleaned);
|
|
101
|
+
const value = Number(cleaned.replace(/[()]/g, ""));
|
|
102
|
+
if (!Number.isFinite(value)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return negative ? -value : value;
|
|
106
|
+
};
|
|
107
|
+
if (amountIndex >= 0) {
|
|
108
|
+
const amount = readNumber(row[amountIndex]);
|
|
109
|
+
if (amount === null) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
amountUsd: Math.abs(amount),
|
|
114
|
+
direction: amount < 0 ? "debit" : "credit"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (debitIndex >= 0) {
|
|
118
|
+
const debit = readNumber(row[debitIndex]);
|
|
119
|
+
if (debit !== null && debit !== 0) {
|
|
120
|
+
return { amountUsd: Math.abs(debit), direction: "debit" };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (creditIndex >= 0) {
|
|
124
|
+
const credit = readNumber(row[creditIndex]);
|
|
125
|
+
if (credit !== null && credit !== 0) {
|
|
126
|
+
return { amountUsd: Math.abs(credit), direction: "credit" };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function normalizeDate(raw) {
|
|
132
|
+
const trimmed = raw.trim();
|
|
133
|
+
if (!trimmed) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const native = Date.parse(trimmed);
|
|
137
|
+
if (Number.isFinite(native)) {
|
|
138
|
+
return new Date(native).toISOString();
|
|
139
|
+
}
|
|
140
|
+
const isoMatch = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})/);
|
|
141
|
+
if (isoMatch) {
|
|
142
|
+
return new Date(
|
|
143
|
+
Date.UTC(
|
|
144
|
+
Number(isoMatch[1]),
|
|
145
|
+
Number(isoMatch[2]) - 1,
|
|
146
|
+
Number(isoMatch[3])
|
|
147
|
+
)
|
|
148
|
+
).toISOString();
|
|
149
|
+
}
|
|
150
|
+
const usMatch = trimmed.match(/^(\d{1,2})[/-](\d{1,2})[/-](\d{2,4})/);
|
|
151
|
+
if (usMatch) {
|
|
152
|
+
const month = Number(usMatch[1]);
|
|
153
|
+
const day = Number(usMatch[2]);
|
|
154
|
+
const rawYear = Number(usMatch[3]);
|
|
155
|
+
const year = rawYear < 100 ? 2e3 + rawYear : rawYear;
|
|
156
|
+
return new Date(Date.UTC(year, month - 1, day)).toISOString();
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
function resolveColumnIndex(header, hint, fallbackHints) {
|
|
161
|
+
if (hint) {
|
|
162
|
+
const explicitIndex = header.findIndex(
|
|
163
|
+
(value) => value.trim().toLowerCase() === hint.trim().toLowerCase()
|
|
164
|
+
);
|
|
165
|
+
if (explicitIndex >= 0) {
|
|
166
|
+
return explicitIndex;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return findColumn(header, fallbackHints);
|
|
170
|
+
}
|
|
171
|
+
function parseTransactionsCsv(csvText, options = {}) {
|
|
172
|
+
const rows = parseCsv(csvText);
|
|
173
|
+
if (rows.length < 2) {
|
|
174
|
+
return {
|
|
175
|
+
transactions: [],
|
|
176
|
+
rowsRead: Math.max(0, rows.length - 1),
|
|
177
|
+
errors: ["CSV has no data rows."]
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const header = rows[0].map((value) => value.trim());
|
|
181
|
+
const dateIndex = resolveColumnIndex(
|
|
182
|
+
header,
|
|
183
|
+
options.dateColumn,
|
|
184
|
+
DATE_COLUMN_HINTS
|
|
185
|
+
);
|
|
186
|
+
const amountIndex = resolveColumnIndex(
|
|
187
|
+
header,
|
|
188
|
+
options.amountColumn,
|
|
189
|
+
AMOUNT_COLUMN_HINTS
|
|
190
|
+
);
|
|
191
|
+
const debitIndex = findColumn(header, DEBIT_COLUMN_HINTS);
|
|
192
|
+
const creditIndex = findColumn(header, CREDIT_COLUMN_HINTS);
|
|
193
|
+
const merchantIndex = resolveColumnIndex(
|
|
194
|
+
header,
|
|
195
|
+
options.merchantColumn,
|
|
196
|
+
MERCHANT_COLUMN_HINTS
|
|
197
|
+
);
|
|
198
|
+
const descriptionIndex = resolveColumnIndex(
|
|
199
|
+
header,
|
|
200
|
+
options.descriptionColumn,
|
|
201
|
+
["description", "memo", "details"]
|
|
202
|
+
);
|
|
203
|
+
const categoryIndex = resolveColumnIndex(
|
|
204
|
+
header,
|
|
205
|
+
options.categoryColumn,
|
|
206
|
+
CATEGORY_COLUMN_HINTS
|
|
207
|
+
);
|
|
208
|
+
const errors = [];
|
|
209
|
+
if (dateIndex < 0) {
|
|
210
|
+
errors.push("Could not find a date column in the CSV header.");
|
|
211
|
+
}
|
|
212
|
+
if (amountIndex < 0 && debitIndex < 0 && creditIndex < 0) {
|
|
213
|
+
errors.push(
|
|
214
|
+
"Could not find an amount/debit/credit column in the CSV header."
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
if (merchantIndex < 0) {
|
|
218
|
+
errors.push("Could not find a merchant / payee / description column.");
|
|
219
|
+
}
|
|
220
|
+
const transactions = [];
|
|
221
|
+
if (dateIndex < 0 || merchantIndex < 0) {
|
|
222
|
+
return { transactions, rowsRead: rows.length - 1, errors };
|
|
223
|
+
}
|
|
224
|
+
for (let rowIndex = 1; rowIndex < rows.length; rowIndex += 1) {
|
|
225
|
+
const row = rows[rowIndex];
|
|
226
|
+
if (row.length === 0) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const postedAt = normalizeDate(row[dateIndex] ?? "");
|
|
230
|
+
if (!postedAt) {
|
|
231
|
+
errors.push(
|
|
232
|
+
`Row ${rowIndex + 1}: unparseable date "${row[dateIndex] ?? ""}".`
|
|
233
|
+
);
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const amount = parseAmount(row, amountIndex, debitIndex, creditIndex);
|
|
237
|
+
if (!amount) {
|
|
238
|
+
errors.push(`Row ${rowIndex + 1}: unparseable amount.`);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const merchantRaw = (row[merchantIndex] ?? "").trim();
|
|
242
|
+
if (!merchantRaw) {
|
|
243
|
+
errors.push(`Row ${rowIndex + 1}: empty merchant.`);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const description = descriptionIndex >= 0 ? (row[descriptionIndex] ?? "").trim() : "";
|
|
247
|
+
const category = categoryIndex >= 0 ? (row[categoryIndex] ?? "").trim() : "";
|
|
248
|
+
transactions.push({
|
|
249
|
+
postedAt,
|
|
250
|
+
amountUsd: amount.amountUsd,
|
|
251
|
+
direction: amount.direction,
|
|
252
|
+
merchantRaw,
|
|
253
|
+
merchantNormalized: normalizeMerchant(merchantRaw),
|
|
254
|
+
description: description.length > 0 ? description : null,
|
|
255
|
+
category: category.length > 0 ? category : null,
|
|
256
|
+
currency: "USD",
|
|
257
|
+
externalId: null,
|
|
258
|
+
rowIndex
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
transactions,
|
|
263
|
+
rowsRead: rows.length - 1,
|
|
264
|
+
errors
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
export {
|
|
268
|
+
parseCsv,
|
|
269
|
+
parseTransactionsCsv
|
|
270
|
+
};
|
|
271
|
+
//# sourceMappingURL=payment-csv-import.js.map
|