@dizzlkheinz/ynab-mcpb 0.12.1

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 (435) hide show
  1. package/.chunkhound.json +11 -0
  2. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
  3. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
  4. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
  5. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
  6. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
  7. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
  8. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
  9. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
  10. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
  11. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
  12. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
  13. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
  14. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
  15. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
  16. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
  17. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
  18. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
  19. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
  20. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
  21. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
  22. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
  23. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
  24. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
  25. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
  26. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
  27. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
  28. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
  29. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
  30. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
  31. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
  32. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
  33. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
  34. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
  35. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
  36. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
  37. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
  38. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
  39. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
  40. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
  41. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
  42. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
  43. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
  44. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
  45. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
  46. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
  47. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
  48. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
  49. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
  50. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
  51. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
  52. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
  53. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
  54. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
  55. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
  56. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
  57. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
  58. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
  59. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
  60. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
  61. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
  62. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
  63. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
  64. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
  65. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
  66. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
  67. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
  68. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
  69. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
  70. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
  71. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
  72. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
  73. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
  74. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
  75. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
  76. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
  77. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
  78. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
  79. package/.dxtignore +57 -0
  80. package/.env.example +44 -0
  81. package/.gemini/settings.json +8 -0
  82. package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
  83. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  84. package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
  85. package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
  86. package/.github/pull_request_template.md +41 -0
  87. package/.github/workflows/ci-tests.yml +41 -0
  88. package/.github/workflows/claude-code-review.yml +57 -0
  89. package/.github/workflows/claude.yml +50 -0
  90. package/.github/workflows/full-integration.yml +22 -0
  91. package/.github/workflows/pr-description-check.yml +88 -0
  92. package/.github/workflows/publish.yml +33 -0
  93. package/.github/workflows/release.yml +89 -0
  94. package/.mcpbignore +58 -0
  95. package/.prettierignore +10 -0
  96. package/.prettierrc.json +10 -0
  97. package/ADOS-2-Module-1-Complete-Manual.md +757 -0
  98. package/AGENTS.md +36 -0
  99. package/CHANGELOG.md +187 -0
  100. package/CLAUDE.md +414 -0
  101. package/CODEREVIEW_RESPONSE.md +128 -0
  102. package/LICENSE +17 -0
  103. package/NUL +1 -0
  104. package/README.md +222 -0
  105. package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
  106. package/TESTING_NOTES.md +217 -0
  107. package/WARP.md +245 -0
  108. package/accountactivity-merged.csv +149 -0
  109. package/bin/ynab-mcp-server.cjs +4 -0
  110. package/bin/ynab-mcp-server.js +8 -0
  111. package/bundle-analysis.html +13110 -0
  112. package/dist/bundle/index.cjs +124 -0
  113. package/dist/index.d.ts +2 -0
  114. package/dist/index.js +85 -0
  115. package/dist/server/YNABMCPServer.d.ts +264 -0
  116. package/dist/server/YNABMCPServer.js +845 -0
  117. package/dist/server/budgetResolver.d.ts +15 -0
  118. package/dist/server/budgetResolver.js +99 -0
  119. package/dist/server/cacheManager.d.ts +74 -0
  120. package/dist/server/cacheManager.js +306 -0
  121. package/dist/server/config.d.ts +3 -0
  122. package/dist/server/config.js +19 -0
  123. package/dist/server/deltaCache.d.ts +61 -0
  124. package/dist/server/deltaCache.js +206 -0
  125. package/dist/server/deltaCache.merge.d.ts +9 -0
  126. package/dist/server/deltaCache.merge.js +111 -0
  127. package/dist/server/diagnostics.d.ts +90 -0
  128. package/dist/server/diagnostics.js +163 -0
  129. package/dist/server/errorHandler.d.ts +69 -0
  130. package/dist/server/errorHandler.js +524 -0
  131. package/dist/server/prompts.d.ts +31 -0
  132. package/dist/server/prompts.js +205 -0
  133. package/dist/server/rateLimiter.d.ts +27 -0
  134. package/dist/server/rateLimiter.js +82 -0
  135. package/dist/server/requestLogger.d.ts +62 -0
  136. package/dist/server/requestLogger.js +190 -0
  137. package/dist/server/resources.d.ts +39 -0
  138. package/dist/server/resources.js +85 -0
  139. package/dist/server/responseFormatter.d.ts +14 -0
  140. package/dist/server/responseFormatter.js +42 -0
  141. package/dist/server/securityMiddleware.d.ts +87 -0
  142. package/dist/server/securityMiddleware.js +117 -0
  143. package/dist/server/serverKnowledgeStore.d.ts +11 -0
  144. package/dist/server/serverKnowledgeStore.js +42 -0
  145. package/dist/server/toolRegistry.d.ts +85 -0
  146. package/dist/server/toolRegistry.js +272 -0
  147. package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
  148. package/dist/tools/__tests__/deltaTestUtils.js +26 -0
  149. package/dist/tools/accountTools.d.ts +37 -0
  150. package/dist/tools/accountTools.js +175 -0
  151. package/dist/tools/budgetTools.d.ts +10 -0
  152. package/dist/tools/budgetTools.js +68 -0
  153. package/dist/tools/categoryTools.d.ts +27 -0
  154. package/dist/tools/categoryTools.js +232 -0
  155. package/dist/tools/compareTransactions/formatter.d.ts +71 -0
  156. package/dist/tools/compareTransactions/formatter.js +97 -0
  157. package/dist/tools/compareTransactions/index.d.ts +30 -0
  158. package/dist/tools/compareTransactions/index.js +160 -0
  159. package/dist/tools/compareTransactions/matcher.d.ts +12 -0
  160. package/dist/tools/compareTransactions/matcher.js +140 -0
  161. package/dist/tools/compareTransactions/parser.d.ts +14 -0
  162. package/dist/tools/compareTransactions/parser.js +430 -0
  163. package/dist/tools/compareTransactions/types.d.ts +27 -0
  164. package/dist/tools/compareTransactions/types.js +1 -0
  165. package/dist/tools/compareTransactions.d.ts +1 -0
  166. package/dist/tools/compareTransactions.js +1 -0
  167. package/dist/tools/deltaFetcher.d.ts +22 -0
  168. package/dist/tools/deltaFetcher.js +137 -0
  169. package/dist/tools/deltaSupport.d.ts +20 -0
  170. package/dist/tools/deltaSupport.js +176 -0
  171. package/dist/tools/exportTransactions.d.ts +17 -0
  172. package/dist/tools/exportTransactions.js +191 -0
  173. package/dist/tools/monthTools.d.ts +16 -0
  174. package/dist/tools/monthTools.js +107 -0
  175. package/dist/tools/payeeTools.d.ts +17 -0
  176. package/dist/tools/payeeTools.js +82 -0
  177. package/dist/tools/reconcileAdapter.d.ts +25 -0
  178. package/dist/tools/reconcileAdapter.js +167 -0
  179. package/dist/tools/reconciliation/analyzer.d.ts +3 -0
  180. package/dist/tools/reconciliation/analyzer.js +567 -0
  181. package/dist/tools/reconciliation/executor.d.ts +94 -0
  182. package/dist/tools/reconciliation/executor.js +611 -0
  183. package/dist/tools/reconciliation/index.d.ts +54 -0
  184. package/dist/tools/reconciliation/index.js +249 -0
  185. package/dist/tools/reconciliation/matcher.d.ts +3 -0
  186. package/dist/tools/reconciliation/matcher.js +160 -0
  187. package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
  188. package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
  189. package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
  190. package/dist/tools/reconciliation/recommendationEngine.js +273 -0
  191. package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
  192. package/dist/tools/reconciliation/reportFormatter.js +214 -0
  193. package/dist/tools/reconciliation/types.d.ts +172 -0
  194. package/dist/tools/reconciliation/types.js +7 -0
  195. package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
  196. package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
  197. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
  198. package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
  199. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
  200. package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
  201. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
  202. package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
  203. package/dist/tools/schemas/outputs/index.d.ts +14 -0
  204. package/dist/tools/schemas/outputs/index.js +14 -0
  205. package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
  206. package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
  207. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
  208. package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
  209. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
  210. package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
  211. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
  212. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
  213. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
  214. package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
  215. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
  216. package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
  217. package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
  218. package/dist/tools/schemas/shared/commonOutputs.js +27 -0
  219. package/dist/tools/toolCategories.d.ts +32 -0
  220. package/dist/tools/toolCategories.js +32 -0
  221. package/dist/tools/transactionTools.d.ts +315 -0
  222. package/dist/tools/transactionTools.js +1722 -0
  223. package/dist/tools/utilityTools.d.ts +10 -0
  224. package/dist/tools/utilityTools.js +56 -0
  225. package/dist/types/index.d.ts +20 -0
  226. package/dist/types/index.js +16 -0
  227. package/dist/types/toolAnnotations.d.ts +7 -0
  228. package/dist/types/toolAnnotations.js +1 -0
  229. package/dist/utils/amountUtils.d.ts +3 -0
  230. package/dist/utils/amountUtils.js +10 -0
  231. package/dist/utils/dateUtils.d.ts +9 -0
  232. package/dist/utils/dateUtils.js +43 -0
  233. package/dist/utils/money.d.ts +21 -0
  234. package/dist/utils/money.js +51 -0
  235. package/docs/README.md +72 -0
  236. package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
  237. package/docs/assets/schemas/reconciliation-v2.json +338 -0
  238. package/docs/getting-started/CONFIGURATION.md +175 -0
  239. package/docs/getting-started/INSTALLATION.md +333 -0
  240. package/docs/getting-started/QUICKSTART.md +282 -0
  241. package/docs/guides/ARCHITECTURE.md +650 -0
  242. package/docs/guides/DEPLOYMENT.md +189 -0
  243. package/docs/guides/INTEGRATION_TESTING.md +730 -0
  244. package/docs/guides/TESTING.md +591 -0
  245. package/docs/reconciliation-flow.md +83 -0
  246. package/docs/reference/API.md +1450 -0
  247. package/docs/reference/EXAMPLES.md +946 -0
  248. package/docs/reference/TOOLS.md +348 -0
  249. package/docs/reference/TROUBLESHOOTING.md +481 -0
  250. package/esbuild.config.mjs +68 -0
  251. package/eslint.config.js +49 -0
  252. package/fix-types.sh +17 -0
  253. package/meta.json +12550 -0
  254. package/package.json +105 -0
  255. package/package.json.tmp +105 -0
  256. package/scripts/analyze-bundle.mjs +41 -0
  257. package/scripts/create-pr-description.js +203 -0
  258. package/scripts/generate-mcpb.ps1 +96 -0
  259. package/scripts/run-domain-integration-tests.js +33 -0
  260. package/scripts/run-generate-mcpb.js +29 -0
  261. package/scripts/run-throttled-integration-tests.js +116 -0
  262. package/scripts/test-delta-params.mjs +140 -0
  263. package/scripts/test-recommendations.ts +53 -0
  264. package/scripts/tmpTransaction.ts +48 -0
  265. package/scripts/validate-env.js +122 -0
  266. package/scripts/verify-build.js +105 -0
  267. package/scripts/watch-and-restart.ps1 +50 -0
  268. package/src/__tests__/comprehensive.integration.test.ts +1196 -0
  269. package/src/__tests__/delta.performance.test.ts +80 -0
  270. package/src/__tests__/performance.test.ts +725 -0
  271. package/src/__tests__/setup.ts +449 -0
  272. package/src/__tests__/testRunner.ts +444 -0
  273. package/src/__tests__/testUtils.ts +563 -0
  274. package/src/__tests__/workflows.e2e.test.ts +1675 -0
  275. package/src/index.ts +124 -0
  276. package/src/server/.gitkeep +1 -0
  277. package/src/server/YNABMCPServer.ts +1188 -0
  278. package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
  279. package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
  280. package/src/server/__tests__/budgetResolver.test.ts +425 -0
  281. package/src/server/__tests__/cacheManager.test.ts +880 -0
  282. package/src/server/__tests__/config.test.ts +166 -0
  283. package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
  284. package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
  285. package/src/server/__tests__/deltaCache.test.ts +774 -0
  286. package/src/server/__tests__/diagnostics.test.ts +823 -0
  287. package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
  288. package/src/server/__tests__/errorHandler.test.ts +416 -0
  289. package/src/server/__tests__/prompts.test.ts +354 -0
  290. package/src/server/__tests__/rateLimiter.test.ts +314 -0
  291. package/src/server/__tests__/requestLogger.test.ts +408 -0
  292. package/src/server/__tests__/resources.test.ts +299 -0
  293. package/src/server/__tests__/security.integration.test.ts +426 -0
  294. package/src/server/__tests__/securityMiddleware.test.ts +449 -0
  295. package/src/server/__tests__/server-startup.integration.test.ts +477 -0
  296. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
  297. package/src/server/__tests__/toolRegistry.test.ts +855 -0
  298. package/src/server/budgetResolver.ts +235 -0
  299. package/src/server/cacheManager.ts +503 -0
  300. package/src/server/config.ts +41 -0
  301. package/src/server/deltaCache.merge.ts +149 -0
  302. package/src/server/deltaCache.ts +341 -0
  303. package/src/server/diagnostics.ts +338 -0
  304. package/src/server/errorHandler.ts +756 -0
  305. package/src/server/prompts.ts +291 -0
  306. package/src/server/rateLimiter.ts +156 -0
  307. package/src/server/requestLogger.ts +344 -0
  308. package/src/server/resources.ts +168 -0
  309. package/src/server/responseFormatter.ts +51 -0
  310. package/src/server/securityMiddleware.ts +236 -0
  311. package/src/server/serverKnowledgeStore.ts +91 -0
  312. package/src/server/toolRegistry.ts +489 -0
  313. package/src/tools/.gitkeep +1 -0
  314. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
  315. package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
  316. package/src/tools/__tests__/accountTools.test.ts +653 -0
  317. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
  318. package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
  319. package/src/tools/__tests__/budgetTools.test.ts +423 -0
  320. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
  321. package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
  322. package/src/tools/__tests__/categoryTools.test.ts +622 -0
  323. package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
  324. package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
  325. package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
  326. package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
  327. package/src/tools/__tests__/compareTransactions.test.ts +342 -0
  328. package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
  329. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
  330. package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
  331. package/src/tools/__tests__/deltaSupport.test.ts +188 -0
  332. package/src/tools/__tests__/deltaTestUtils.ts +46 -0
  333. package/src/tools/__tests__/exportTransactions.test.ts +213 -0
  334. package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
  335. package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
  336. package/src/tools/__tests__/monthTools.test.ts +523 -0
  337. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
  338. package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
  339. package/src/tools/__tests__/payeeTools.test.ts +445 -0
  340. package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
  341. package/src/tools/__tests__/transactionTools.test.ts +3521 -0
  342. package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
  343. package/src/tools/__tests__/utilityTools.test.ts +205 -0
  344. package/src/tools/accountTools.ts +283 -0
  345. package/src/tools/budgetTools.ts +112 -0
  346. package/src/tools/categoryTools.ts +366 -0
  347. package/src/tools/compareTransactions/formatter.ts +163 -0
  348. package/src/tools/compareTransactions/index.ts +228 -0
  349. package/src/tools/compareTransactions/matcher.ts +240 -0
  350. package/src/tools/compareTransactions/parser.ts +557 -0
  351. package/src/tools/compareTransactions/types.ts +60 -0
  352. package/src/tools/compareTransactions.ts +3 -0
  353. package/src/tools/deltaFetcher.ts +278 -0
  354. package/src/tools/deltaSupport.ts +293 -0
  355. package/src/tools/exportTransactions.ts +273 -0
  356. package/src/tools/monthTools.ts +164 -0
  357. package/src/tools/payeeTools.ts +140 -0
  358. package/src/tools/reconcileAdapter.ts +312 -0
  359. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
  360. package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
  361. package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
  362. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
  363. package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
  364. package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
  365. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
  366. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
  367. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
  368. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
  369. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
  370. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
  371. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
  372. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
  373. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
  374. package/src/tools/reconciliation/analyzer.ts +824 -0
  375. package/src/tools/reconciliation/executor.ts +880 -0
  376. package/src/tools/reconciliation/index.ts +400 -0
  377. package/src/tools/reconciliation/matcher.ts +269 -0
  378. package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
  379. package/src/tools/reconciliation/recommendationEngine.ts +506 -0
  380. package/src/tools/reconciliation/reportFormatter.ts +363 -0
  381. package/src/tools/reconciliation/types.ts +314 -0
  382. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
  383. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
  384. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
  385. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
  386. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
  387. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
  388. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
  389. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
  390. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
  391. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
  392. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
  393. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
  394. package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
  395. package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
  396. package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
  397. package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
  398. package/src/tools/schemas/outputs/index.ts +270 -0
  399. package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
  400. package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
  401. package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
  402. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
  403. package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
  404. package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
  405. package/src/tools/schemas/shared/commonOutputs.ts +140 -0
  406. package/src/tools/toolCategories.ts +140 -0
  407. package/src/tools/transactionTools.ts +2509 -0
  408. package/src/tools/utilityTools.ts +90 -0
  409. package/src/types/.gitkeep +1 -0
  410. package/src/types/__tests__/index.test.ts +52 -0
  411. package/src/types/index.ts +67 -0
  412. package/src/types/integration-tests.d.ts +35 -0
  413. package/src/types/toolAnnotations.ts +44 -0
  414. package/src/utils/__tests__/dateUtils.test.ts +170 -0
  415. package/src/utils/__tests__/money.test.ts +189 -0
  416. package/src/utils/amountUtils.ts +32 -0
  417. package/src/utils/dateUtils.ts +108 -0
  418. package/src/utils/money.ts +123 -0
  419. package/test-csv-sample.csv +28 -0
  420. package/test-exports/sample_bank_statement.csv +7 -0
  421. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
  422. package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
  423. package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
  424. package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
  425. package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
  426. package/test-reconcile-autodetect.js +40 -0
  427. package/test-reconcile-tool.js +152 -0
  428. package/test-reconcile-with-csv.cjs +89 -0
  429. package/test-statement.csv +8 -0
  430. package/test_debug.js +47 -0
  431. package/test_simple.mjs +16 -0
  432. package/tsconfig.json +31 -0
  433. package/tsconfig.prod.json +18 -0
  434. package/vitest-reporters/split-json-reporter.ts +211 -0
  435. package/vitest.config.ts +96 -0
@@ -0,0 +1,557 @@
1
+ import { parse } from 'csv-parse/sync';
2
+ import { parse as parseDateFns } from 'date-fns';
3
+ import { toMilli } from '../../utils/money.js';
4
+ import type { Milli } from '../../utils/money.js';
5
+ import { BankTransaction, CSVFormat } from './types.js';
6
+ import { readFileSync } from 'fs';
7
+
8
+ /**
9
+ * Parse date string using date-fns for better reliability
10
+ */
11
+ export function parseDate(dateStr: string, format: string): Date {
12
+ const cleanDate = dateStr.trim();
13
+
14
+ // Map our format strings to date-fns format patterns
15
+ const formatMap: Record<string, string> = {
16
+ 'MM/DD/YYYY': 'MM/dd/yyyy',
17
+ 'M/D/YYYY': 'M/d/yyyy',
18
+ 'DD/MM/YYYY': 'dd/MM/yyyy',
19
+ 'D/M/YYYY': 'd/M/yyyy',
20
+ 'YYYY-MM-DD': 'yyyy-MM-dd',
21
+ 'MM-DD-YYYY': 'MM-dd-yyyy',
22
+ 'MMM dd, yyyy': 'MMM dd, yyyy',
23
+ 'MMM d, yyyy': 'MMM d, yyyy',
24
+ };
25
+
26
+ const dateFnsFormat = formatMap[format];
27
+ if (dateFnsFormat) {
28
+ try {
29
+ const parsed = parseDateFns(cleanDate, dateFnsFormat, new Date());
30
+ if (!isNaN(parsed.getTime())) {
31
+ return parsed;
32
+ }
33
+ } catch {
34
+ // Fall through to generic parsing
35
+ }
36
+ }
37
+
38
+ // Fallback to native Date parsing for any unrecognized formats
39
+ const parsed = new Date(cleanDate);
40
+ if (isNaN(parsed.getTime())) {
41
+ throw new Error(`Unable to parse date: ${dateStr} with format: ${format}`);
42
+ }
43
+ return parsed;
44
+ }
45
+
46
+ /**
47
+ * Convert dollar amount to milliunits
48
+ */
49
+ export function amountToMilliunits(amountStr: string): Milli {
50
+ const cleaned = amountStr.replace(/[$,\s]/g, '').trim();
51
+ let s = cleaned,
52
+ neg = false;
53
+ if (s.startsWith('(') && s.endsWith(')')) {
54
+ neg = true;
55
+ s = s.slice(1, -1);
56
+ }
57
+ if (s.startsWith('+')) s = s.slice(1);
58
+
59
+ const n = Number(s);
60
+ if (isNaN(n) || !isFinite(n)) {
61
+ throw new Error(`Invalid amount value: "${amountStr}" (cleaned: "${s}")`);
62
+ }
63
+
64
+ return toMilli(neg ? -n : n);
65
+ }
66
+
67
+ /**
68
+ * Check if a string looks like a date
69
+ */
70
+ function isDateLike(str: string): boolean {
71
+ if (!str) return false;
72
+ // Common date patterns
73
+ const datePatterns = [
74
+ /^\d{1,2}\/\d{1,2}\/\d{4}$/, // MM/DD/YYYY
75
+ /^\d{4}-\d{1,2}-\d{1,2}$/, // YYYY-MM-DD
76
+ /^\d{1,2}-\d{1,2}-\d{4}$/, // MM-DD-YYYY
77
+ /^[A-Za-z]{3}\s+\d{1,2},\s+\d{4}$/, // MMM dd, yyyy (e.g., "Sep 18, 2025")
78
+ ];
79
+ return datePatterns.some((pattern) => pattern.test(str.trim()));
80
+ }
81
+
82
+ /**
83
+ * Detect date format from a sample date string
84
+ */
85
+ export function detectDateFormat(dateStr: string | undefined): string {
86
+ if (!dateStr) return 'MM/DD/YYYY';
87
+ const cleaned = dateStr.trim();
88
+
89
+ if (cleaned.includes('/')) {
90
+ return 'MM/DD/YYYY';
91
+ } else if (cleaned.includes('-')) {
92
+ if (/^\d{4}-/.test(cleaned)) {
93
+ return 'YYYY-MM-DD';
94
+ } else {
95
+ return 'MM-DD-YYYY';
96
+ }
97
+ } else if (/^[A-Za-z]{3}\s+\d{1,2},\s+\d{4}$/.test(cleaned)) {
98
+ // Detect "Sep 18, 2025" format
99
+ return 'MMM dd, yyyy';
100
+ }
101
+ return 'MM/DD/YYYY';
102
+ }
103
+
104
+ /**
105
+ * Detect the most likely delimiter by evaluating candidates across sample lines
106
+ */
107
+ function detectDelimiter(lines: string[]): string {
108
+ const candidates = [',', ';', '\t', '|'];
109
+ const sampleLines = lines.slice(0, 3).filter((line) => line.trim()); // Use first 2-3 non-empty lines
110
+
111
+ if (sampleLines.length === 0) {
112
+ return ','; // Default fallback
113
+ }
114
+
115
+ let bestDelimiter = ',';
116
+ let bestScore = -1;
117
+
118
+ for (const delimiter of candidates) {
119
+ let score = 0;
120
+ const columnCounts: number[] = [];
121
+ let parseFailed = false;
122
+
123
+ for (const line of sampleLines) {
124
+ try {
125
+ const rows = parse(line, {
126
+ delimiter,
127
+ quote: '"',
128
+ escape: '"',
129
+ skip_empty_lines: true,
130
+ trim: true,
131
+ relax_column_count: true,
132
+ });
133
+
134
+ // rows should be an array with one row (since we're parsing one line)
135
+ if (rows && rows.length > 0 && rows[0]) {
136
+ const columns = Array.isArray(rows[0]) ? rows[0] : Object.values(rows[0]);
137
+ columnCounts.push(columns.length);
138
+ } else {
139
+ // If parsing failed or returned empty, fall back to simple split
140
+ const columns = line.split(delimiter);
141
+ columnCounts.push(columns.length);
142
+ }
143
+ } catch {
144
+ // If csv-parse fails, fall back to simple split method
145
+ parseFailed = true;
146
+ const columns = line.split(delimiter);
147
+ columnCounts.push(columns.length);
148
+ }
149
+ }
150
+
151
+ // Check consistency: all lines should have the same column count
152
+ if (columnCounts.length > 1) {
153
+ const firstCount = columnCounts[0];
154
+ if (firstCount === undefined) continue;
155
+ const isConsistent = columnCounts.every((count) => count === firstCount);
156
+
157
+ if (isConsistent && firstCount > 1) {
158
+ // Score based on column count (more columns = better, up to a reasonable limit)
159
+ score = Math.min(firstCount, 10); // Cap at 10 to avoid excessive weight
160
+
161
+ // Bonus points for common delimiters
162
+ if (delimiter === ',') score += 0.5;
163
+ if (delimiter === ';') score += 0.3;
164
+
165
+ // Bonus points if csv-parse succeeded (indicates proper CSV format)
166
+ if (!parseFailed) score += 0.2;
167
+ }
168
+ }
169
+
170
+ if (score > bestScore) {
171
+ bestScore = score;
172
+ bestDelimiter = delimiter;
173
+ }
174
+ }
175
+
176
+ return bestDelimiter;
177
+ }
178
+
179
+ /**
180
+ * Analyze header names to detect column purposes
181
+ */
182
+ function analyzeHeaders(headers: string[]): {
183
+ dateColumn: string | null;
184
+ amountColumn: string | null;
185
+ descriptionColumn: string | null;
186
+ debitColumn: string | null;
187
+ creditColumn: string | null;
188
+ } {
189
+ const datePattern = /^(date|trans.*date|transaction.*date|post.*date|dt)$/i;
190
+ const amountPattern = /^(amount|amt|dollar.*amount|transaction.*amount)$/i;
191
+ const descriptionPattern = /^(description|desc|memo|transaction.*description|payee|merchant)$/i;
192
+ const debitPattern = /^(debit|debits|withdrawal|withdrawals|out|outgoing)$/i;
193
+ const creditPattern = /^(credit|credits|deposit|deposits|in|incoming)$/i;
194
+
195
+ let dateColumn: string | null = null;
196
+ let amountColumn: string | null = null;
197
+ let descriptionColumn: string | null = null;
198
+ let debitColumn: string | null = null;
199
+ let creditColumn: string | null = null;
200
+
201
+ for (const header of headers) {
202
+ const cleanHeader = header.trim();
203
+
204
+ if (datePattern.test(cleanHeader)) {
205
+ dateColumn = cleanHeader;
206
+ } else if (amountPattern.test(cleanHeader)) {
207
+ amountColumn = cleanHeader;
208
+ } else if (descriptionPattern.test(cleanHeader)) {
209
+ descriptionColumn = cleanHeader;
210
+ } else if (debitPattern.test(cleanHeader)) {
211
+ debitColumn = cleanHeader;
212
+ } else if (creditPattern.test(cleanHeader)) {
213
+ creditColumn = cleanHeader;
214
+ }
215
+ }
216
+
217
+ return { dateColumn, amountColumn, descriptionColumn, debitColumn, creditColumn };
218
+ }
219
+
220
+ /**
221
+ * Auto-detect CSV format by analyzing the first few rows
222
+ */
223
+ export function autoDetectCSVFormat(csvContent: string): CSVFormat {
224
+ const linesRaw = csvContent.trim().split('\n').slice(0, 3);
225
+ if (linesRaw.length === 0) {
226
+ throw new Error('CSV file is empty');
227
+ }
228
+
229
+ // Safely handle the first line - check if it exists and is not empty after trimming
230
+ const firstLineRaw = linesRaw[0];
231
+ if (!firstLineRaw || !firstLineRaw.trim()) {
232
+ throw new Error('CSV file contains empty first line');
233
+ }
234
+
235
+ // Detect delimiter across sample lines
236
+ const delimiter = detectDelimiter(linesRaw);
237
+
238
+ const firstLine = firstLineRaw.split(delimiter);
239
+ const hasHeader = !isDateLike(firstLine[0] || '');
240
+
241
+ // Check for separate debit/credit columns by looking for empty cells pattern
242
+ let hasDebitCredit = false;
243
+ if (linesRaw.length > 1) {
244
+ const dataLines = hasHeader ? linesRaw.slice(1) : linesRaw;
245
+ hasDebitCredit = dataLines.some((line) => {
246
+ const cols = line.split(delimiter);
247
+ // Look for pattern: amount in col2 OR col3, but not both
248
+ return (
249
+ cols.length >= 4 &&
250
+ ((cols[2]?.trim() && !cols[3]?.trim()) || (!cols[2]?.trim() && cols[3]?.trim()))
251
+ );
252
+ });
253
+ }
254
+
255
+ if (hasHeader) {
256
+ const { dateColumn, amountColumn, descriptionColumn, debitColumn, creditColumn } =
257
+ analyzeHeaders(firstLine);
258
+
259
+ const safe = (v?: string) => (v && v.trim() ? v : undefined);
260
+
261
+ if (hasDebitCredit && debitColumn && creditColumn) {
262
+ const dateCol = safe(dateColumn ?? undefined) ?? safe(firstLine[0]);
263
+ if (!dateCol) throw new Error('Unable to detect date column name from header');
264
+ const descCol = safe(descriptionColumn ?? undefined) ?? safe(firstLine[1]);
265
+ if (!descCol) throw new Error('Unable to detect description column name from header');
266
+
267
+ return {
268
+ date_column: dateCol,
269
+ description_column: descCol,
270
+ debit_column: debitColumn,
271
+ credit_column: creditColumn,
272
+ date_format: detectDateFormat(linesRaw[1]?.split(delimiter)[0]),
273
+ has_header: hasHeader,
274
+ delimiter: delimiter,
275
+ };
276
+ } else {
277
+ const dateCol = safe(dateColumn ?? undefined) ?? safe(firstLine[0]);
278
+ if (!dateCol) throw new Error('Unable to detect date column name from header');
279
+ const amountCol = safe(amountColumn ?? undefined) ?? safe(firstLine[1]);
280
+ if (!amountCol) throw new Error('Unable to detect amount column name from header');
281
+ const descCol =
282
+ safe(descriptionColumn ?? undefined) ??
283
+ safe(firstLine.length >= 3 ? firstLine[2] : firstLine[1]);
284
+ if (!descCol) throw new Error('Unable to detect description column name from header');
285
+
286
+ return {
287
+ date_column: dateCol,
288
+ amount_column: amountCol,
289
+ description_column: descCol,
290
+ date_format: detectDateFormat(linesRaw[1]?.split(delimiter)[0]),
291
+ has_header: hasHeader,
292
+ delimiter: delimiter,
293
+ };
294
+ }
295
+ } else {
296
+ if (hasDebitCredit && firstLine.length >= 4) {
297
+ return {
298
+ date_column: 0,
299
+ description_column: 1,
300
+ debit_column: 2,
301
+ credit_column: 3,
302
+ date_format: detectDateFormat(firstLine[0]),
303
+ has_header: hasHeader,
304
+ delimiter: delimiter,
305
+ };
306
+ } else {
307
+ return {
308
+ date_column: 0,
309
+ amount_column: 1,
310
+ description_column: firstLine.length >= 3 ? 2 : 1,
311
+ date_format: detectDateFormat(firstLine[0]),
312
+ has_header: hasHeader,
313
+ delimiter: delimiter,
314
+ };
315
+ }
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Automatically fix common CSV issues like unquoted dates with commas
321
+ */
322
+ function preprocessCSV(csvContent: string, format: CSVFormat): string {
323
+ // Check if we're dealing with MMM dd, yyyy format dates that might need quoting
324
+ if (format.date_format?.includes('MMM') && format.date_format?.includes(',')) {
325
+ const lines = csvContent.split('\n');
326
+ const fixedLines = lines.map((line, index) => {
327
+ // Skip header row
328
+ if (format.has_header && index === 0) return line;
329
+ if (!line.trim()) return line;
330
+
331
+ // Check if this line has unquoted dates (more commas than expected)
332
+ const parts = line.split(format.delimiter);
333
+ const expectedColumns = format.has_header ? lines[0]?.split(format.delimiter).length || 3 : 3;
334
+
335
+ if (parts.length > expectedColumns) {
336
+ // Check if we have a date pattern split across first two parts (like "Sep 18, 2025")
337
+ const potentialDate = parts.slice(0, 2).join(',');
338
+ if (/^[A-Za-z]{3}\s+\d{1,2},\s+\d{4}/.test(potentialDate)) {
339
+ // This looks like "Sep 18, 2025" - quote it
340
+ const dateField = parts.slice(0, 2).join(','); // "Sep 18, 2025"
341
+ const remainingFields = parts.slice(2);
342
+ return `"${dateField}"${format.delimiter}${remainingFields.join(format.delimiter)}`;
343
+ }
344
+ }
345
+
346
+ return line;
347
+ });
348
+
349
+ return fixedLines.join('\n');
350
+ }
351
+
352
+ return csvContent;
353
+ }
354
+
355
+ /**
356
+ * Parse CSV data into bank transactions
357
+ */
358
+ export function parseBankCSV(
359
+ csvContent: string,
360
+ format: CSVFormat,
361
+ options: { debug?: boolean } = {},
362
+ ): BankTransaction[] {
363
+ // Preprocess CSV to fix common issues like unquoted dates
364
+ const processedCSV = preprocessCSV(csvContent, format);
365
+
366
+ const records = parse(processedCSV, {
367
+ delimiter: format.delimiter,
368
+ columns: format.has_header,
369
+ skip_empty_lines: true,
370
+ trim: true,
371
+ // Enhanced CSV parsing options for robust handling
372
+ quote: '"', // Handle quoted fields (for dates with commas)
373
+ escape: '"', // Handle escaped quotes within fields
374
+ relax_column_count: true, // Handle varying column counts
375
+ // Removed deprecated auto_parse and auto_parse_date options
376
+ // Removed relax_quotes as it may not be supported in current csv-parse version
377
+ });
378
+
379
+ const transactions: BankTransaction[] = [];
380
+
381
+ for (let i = 0; i < records.length; i++) {
382
+ const record = records[i];
383
+ const rowNumber = format.has_header ? i + 2 : i + 1; // Account for header row
384
+
385
+ try {
386
+ let rawDate: string;
387
+ let rawAmount: string;
388
+ let description: string;
389
+
390
+ if (format.has_header) {
391
+ // Record is an object when using headers
392
+ const recordObj = record as unknown as Record<string, string>;
393
+ rawDate = recordObj[format.date_column as string] || '';
394
+
395
+ if (format.amount_column) {
396
+ rawAmount = recordObj[format.amount_column as string] || '';
397
+ } else if (format.debit_column !== undefined && format.credit_column !== undefined) {
398
+ const debitVal = recordObj[format.debit_column as string] || '';
399
+ const creditVal = recordObj[format.credit_column as string] || '';
400
+ // Convert: debits negative, credits positive
401
+ // Check if debit has a value and is non-zero
402
+ const debitNum = parseFloat(debitVal.replace(/[^\d.-]/g, ''));
403
+ const creditNum = parseFloat(creditVal.replace(/[^\d.-]/g, ''));
404
+ if (!isNaN(debitNum) && debitNum !== 0) {
405
+ rawAmount = `-${debitVal}`;
406
+ } else if (!isNaN(creditNum) && creditNum !== 0) {
407
+ rawAmount = creditVal;
408
+ } else {
409
+ rawAmount = '0';
410
+ }
411
+ } else {
412
+ throw new Error('No amount column configuration found');
413
+ }
414
+
415
+ description = recordObj[format.description_column as string] || '';
416
+ } else {
417
+ // Record is an array when not using headers, so use column indices
418
+ const recordArray = record as string[];
419
+ const dateIndex =
420
+ typeof format.date_column === 'number'
421
+ ? format.date_column
422
+ : parseInt(format.date_column, 10);
423
+ const descIndex =
424
+ typeof format.description_column === 'number'
425
+ ? format.description_column
426
+ : parseInt(format.description_column, 10);
427
+
428
+ // Validate indices are valid numbers (fallback to defaults if invalid)
429
+ const safeDateIndex = isNaN(dateIndex) ? 0 : dateIndex;
430
+ const safeDescIndex = isNaN(descIndex) ? 2 : descIndex;
431
+
432
+ rawDate = recordArray[safeDateIndex] || '';
433
+
434
+ if (format.amount_column !== undefined) {
435
+ const amountIndex =
436
+ typeof format.amount_column === 'number'
437
+ ? format.amount_column
438
+ : parseInt(format.amount_column, 10);
439
+ const safeAmountIndex = isNaN(amountIndex) ? 1 : amountIndex;
440
+ rawAmount = recordArray[safeAmountIndex] || '';
441
+ } else if (format.debit_column !== undefined && format.credit_column !== undefined) {
442
+ const debitIndex =
443
+ typeof format.debit_column === 'number'
444
+ ? format.debit_column
445
+ : parseInt(format.debit_column, 10);
446
+ const creditIndex =
447
+ typeof format.credit_column === 'number'
448
+ ? format.credit_column
449
+ : parseInt(format.credit_column, 10);
450
+
451
+ const debitVal = recordArray[debitIndex] || '';
452
+ const creditVal = recordArray[creditIndex] || '';
453
+
454
+ // Convert: debits negative, credits positive
455
+ // Check if debit has a value and is non-zero
456
+ const debitNum = parseFloat(debitVal.replace(/[^\d.-]/g, ''));
457
+ const creditNum = parseFloat(creditVal.replace(/[^\d.-]/g, ''));
458
+ if (!isNaN(debitNum) && debitNum !== 0) {
459
+ rawAmount = `-${debitVal}`;
460
+ } else if (!isNaN(creditNum) && creditNum !== 0) {
461
+ rawAmount = creditVal;
462
+ } else {
463
+ rawAmount = '0';
464
+ }
465
+ } else {
466
+ throw new Error('No amount column configuration found');
467
+ }
468
+
469
+ description = recordArray[safeDescIndex] || '';
470
+ }
471
+
472
+ if (!rawDate || !rawAmount) {
473
+ if (options.debug) {
474
+ console.warn(`Skipping row ${rowNumber}: missing date or amount`);
475
+ }
476
+ continue;
477
+ }
478
+
479
+ const date = parseDate(rawDate, format.date_format);
480
+ let amount: Milli;
481
+ try {
482
+ amount = amountToMilliunits(rawAmount);
483
+ } catch (error) {
484
+ if (options.debug) {
485
+ console.warn(
486
+ `Skipping row ${rowNumber}: ${error instanceof Error ? error.message : 'Invalid amount'}`,
487
+ );
488
+ }
489
+ continue;
490
+ }
491
+
492
+ transactions.push({
493
+ date,
494
+ amount,
495
+ description: description.trim(),
496
+ raw_amount: rawAmount,
497
+ raw_date: rawDate,
498
+ row_number: rowNumber,
499
+ });
500
+ } catch (error) {
501
+ if (options.debug) {
502
+ console.warn(`Error parsing row ${rowNumber}:`, error);
503
+ }
504
+ continue;
505
+ }
506
+ }
507
+
508
+ return transactions;
509
+ }
510
+
511
+ /**
512
+ * Read CSV file safely with error handling
513
+ */
514
+ export function readCSVFile(filePath: string): string {
515
+ try {
516
+ return readFileSync(filePath, 'utf-8');
517
+ } catch (error) {
518
+ throw new Error(
519
+ `Unable to read CSV file: ${error instanceof Error ? error.message : 'Unknown error'}`,
520
+ );
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Extract date range from CSV bank statement
526
+ * Returns min and max dates in YYYY-MM-DD format
527
+ */
528
+ export function extractDateRangeFromCSV(
529
+ csvContent: string,
530
+ format: CSVFormat,
531
+ ): { minDate: string; maxDate: string } {
532
+ const transactions = parseBankCSV(csvContent, format);
533
+
534
+ if (transactions.length === 0) {
535
+ throw new Error('No transactions found in CSV');
536
+ }
537
+
538
+ // Extract all dates (already Date objects from parseBankCSV)
539
+ const dates = transactions.map((txn) => txn.date.getTime());
540
+
541
+ // Find min and max
542
+ const minDateObj = new Date(Math.min(...dates));
543
+ const maxDateObj = new Date(Math.max(...dates));
544
+
545
+ // Convert to YYYY-MM-DD format
546
+ const toYYYYMMDD = (date: Date): string => {
547
+ const year = date.getFullYear();
548
+ const month = String(date.getMonth() + 1).padStart(2, '0');
549
+ const day = String(date.getDate()).padStart(2, '0');
550
+ return `${year}-${month}-${day}`;
551
+ };
552
+
553
+ return {
554
+ minDate: toYYYYMMDD(minDateObj),
555
+ maxDate: toYYYYMMDD(maxDateObj),
556
+ };
557
+ }
@@ -0,0 +1,60 @@
1
+ import type * as ynab from 'ynab';
2
+ import { z } from 'zod/v4';
3
+ import type { CompareTransactionsSchema } from './index.js';
4
+
5
+ /**
6
+ * Represents a bank transaction from CSV
7
+ */
8
+ export interface BankTransaction {
9
+ /** Parsed date of the transaction */
10
+ date: Date;
11
+ /** Transaction amount in milliunits (YNAB format) */
12
+ amount: number;
13
+ /** Transaction description from CSV */
14
+ description: string;
15
+ /** Original amount string from CSV */
16
+ raw_amount: string;
17
+ /** Original date string from CSV */
18
+ raw_date: string;
19
+ /** Row number in CSV file for reference */
20
+ row_number: number;
21
+ }
22
+
23
+ /**
24
+ * Represents a YNAB transaction for comparison
25
+ */
26
+ export interface YNABTransaction {
27
+ /** YNAB transaction ID */
28
+ id: string;
29
+ /** Transaction date */
30
+ date: Date;
31
+ /** Transaction amount in milliunits */
32
+ amount: number;
33
+ /** Payee name (nullable) */
34
+ payee_name: string | null | undefined;
35
+ /** Transaction memo (nullable) */
36
+ memo: string | null | undefined;
37
+ /** Transaction cleared status */
38
+ cleared: string;
39
+ /** Original YNAB transaction detail object */
40
+ original: ynab.TransactionDetail;
41
+ }
42
+
43
+ /**
44
+ * Represents a matched pair of bank and YNAB transactions
45
+ */
46
+ export interface TransactionMatch {
47
+ /** Bank transaction from CSV */
48
+ bank_transaction: BankTransaction;
49
+ /** Matched YNAB transaction */
50
+ ynab_transaction: YNABTransaction;
51
+ /** Match score (0-100, higher is better) */
52
+ match_score: number;
53
+ /** Reasons for the match with explanations */
54
+ match_reasons: string[];
55
+ }
56
+
57
+ /**
58
+ * CSV format configuration type derived from zod schema for consistency
59
+ */
60
+ export type CSVFormat = z.infer<typeof CompareTransactionsSchema>['csv_format'];
@@ -0,0 +1,3 @@
1
+ // Shim file to preserve original import path for consumers
2
+ // Re-exports all public members from the refactored compareTransactions module
3
+ export * from './compareTransactions/index.js';