@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.
- package/.chunkhound.json +11 -0
- package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +8 -0
- package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +3 -0
- package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +3 -0
- package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +1 -0
- package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +12 -0
- package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +1 -0
- package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +1 -0
- package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +5 -0
- package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +1453 -0
- package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +8 -0
- package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +9 -0
- package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +1 -0
- package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +5 -0
- package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +81 -0
- package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +610 -0
- package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +1 -0
- package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +1 -0
- package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +1 -0
- package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +3 -0
- package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +8 -0
- package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +5 -0
- package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +1 -0
- package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +9 -0
- package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +1 -0
- package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +13 -0
- package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +14 -0
- package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +5 -0
- package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +5 -0
- package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +5 -0
- package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +3 -0
- package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +1 -0
- package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +1 -0
- package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +5 -0
- package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +5 -0
- package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +8 -0
- package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +8 -0
- package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +5 -0
- package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +1 -0
- package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +1 -0
- package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +1 -0
- package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +1 -0
- package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +1 -0
- package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +5 -0
- package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +1 -0
- package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +1 -0
- package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +1 -0
- package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +3 -0
- package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +8 -0
- package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +1 -0
- package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +1 -0
- package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +8 -0
- package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +3 -0
- package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +5 -0
- package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +1 -0
- package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +1 -0
- package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +5 -0
- package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +1 -0
- package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +14 -0
- package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +1 -0
- package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +5 -0
- package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +5 -0
- package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +11 -0
- package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +1 -0
- package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +8 -0
- package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +1 -0
- package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +3 -0
- package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +3 -0
- package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +8 -0
- package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +1 -0
- package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +5 -0
- package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +1 -0
- package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +1 -0
- package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +1 -0
- package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +1 -0
- package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +3 -0
- package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +8 -0
- package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +1 -0
- package/.dxtignore +57 -0
- package/.env.example +44 -0
- package/.gemini/settings.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +41 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/ISSUE_TEMPLATE/release_checklist.md +31 -0
- package/.github/pull_request_template.md +41 -0
- package/.github/workflows/ci-tests.yml +41 -0
- package/.github/workflows/claude-code-review.yml +57 -0
- package/.github/workflows/claude.yml +50 -0
- package/.github/workflows/full-integration.yml +22 -0
- package/.github/workflows/pr-description-check.yml +88 -0
- package/.github/workflows/publish.yml +33 -0
- package/.github/workflows/release.yml +89 -0
- package/.mcpbignore +58 -0
- package/.prettierignore +10 -0
- package/.prettierrc.json +10 -0
- package/ADOS-2-Module-1-Complete-Manual.md +757 -0
- package/AGENTS.md +36 -0
- package/CHANGELOG.md +187 -0
- package/CLAUDE.md +414 -0
- package/CODEREVIEW_RESPONSE.md +128 -0
- package/LICENSE +17 -0
- package/NUL +1 -0
- package/README.md +222 -0
- package/SCHEMA_IMPROVEMENT_SUMMARY.md +120 -0
- package/TESTING_NOTES.md +217 -0
- package/WARP.md +245 -0
- package/accountactivity-merged.csv +149 -0
- package/bin/ynab-mcp-server.cjs +4 -0
- package/bin/ynab-mcp-server.js +8 -0
- package/bundle-analysis.html +13110 -0
- package/dist/bundle/index.cjs +124 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +85 -0
- package/dist/server/YNABMCPServer.d.ts +264 -0
- package/dist/server/YNABMCPServer.js +845 -0
- package/dist/server/budgetResolver.d.ts +15 -0
- package/dist/server/budgetResolver.js +99 -0
- package/dist/server/cacheManager.d.ts +74 -0
- package/dist/server/cacheManager.js +306 -0
- package/dist/server/config.d.ts +3 -0
- package/dist/server/config.js +19 -0
- package/dist/server/deltaCache.d.ts +61 -0
- package/dist/server/deltaCache.js +206 -0
- package/dist/server/deltaCache.merge.d.ts +9 -0
- package/dist/server/deltaCache.merge.js +111 -0
- package/dist/server/diagnostics.d.ts +90 -0
- package/dist/server/diagnostics.js +163 -0
- package/dist/server/errorHandler.d.ts +69 -0
- package/dist/server/errorHandler.js +524 -0
- package/dist/server/prompts.d.ts +31 -0
- package/dist/server/prompts.js +205 -0
- package/dist/server/rateLimiter.d.ts +27 -0
- package/dist/server/rateLimiter.js +82 -0
- package/dist/server/requestLogger.d.ts +62 -0
- package/dist/server/requestLogger.js +190 -0
- package/dist/server/resources.d.ts +39 -0
- package/dist/server/resources.js +85 -0
- package/dist/server/responseFormatter.d.ts +14 -0
- package/dist/server/responseFormatter.js +42 -0
- package/dist/server/securityMiddleware.d.ts +87 -0
- package/dist/server/securityMiddleware.js +117 -0
- package/dist/server/serverKnowledgeStore.d.ts +11 -0
- package/dist/server/serverKnowledgeStore.js +42 -0
- package/dist/server/toolRegistry.d.ts +85 -0
- package/dist/server/toolRegistry.js +272 -0
- package/dist/tools/__tests__/deltaTestUtils.d.ts +18 -0
- package/dist/tools/__tests__/deltaTestUtils.js +26 -0
- package/dist/tools/accountTools.d.ts +37 -0
- package/dist/tools/accountTools.js +175 -0
- package/dist/tools/budgetTools.d.ts +10 -0
- package/dist/tools/budgetTools.js +68 -0
- package/dist/tools/categoryTools.d.ts +27 -0
- package/dist/tools/categoryTools.js +232 -0
- package/dist/tools/compareTransactions/formatter.d.ts +71 -0
- package/dist/tools/compareTransactions/formatter.js +97 -0
- package/dist/tools/compareTransactions/index.d.ts +30 -0
- package/dist/tools/compareTransactions/index.js +160 -0
- package/dist/tools/compareTransactions/matcher.d.ts +12 -0
- package/dist/tools/compareTransactions/matcher.js +140 -0
- package/dist/tools/compareTransactions/parser.d.ts +14 -0
- package/dist/tools/compareTransactions/parser.js +430 -0
- package/dist/tools/compareTransactions/types.d.ts +27 -0
- package/dist/tools/compareTransactions/types.js +1 -0
- package/dist/tools/compareTransactions.d.ts +1 -0
- package/dist/tools/compareTransactions.js +1 -0
- package/dist/tools/deltaFetcher.d.ts +22 -0
- package/dist/tools/deltaFetcher.js +137 -0
- package/dist/tools/deltaSupport.d.ts +20 -0
- package/dist/tools/deltaSupport.js +176 -0
- package/dist/tools/exportTransactions.d.ts +17 -0
- package/dist/tools/exportTransactions.js +191 -0
- package/dist/tools/monthTools.d.ts +16 -0
- package/dist/tools/monthTools.js +107 -0
- package/dist/tools/payeeTools.d.ts +17 -0
- package/dist/tools/payeeTools.js +82 -0
- package/dist/tools/reconcileAdapter.d.ts +25 -0
- package/dist/tools/reconcileAdapter.js +167 -0
- package/dist/tools/reconciliation/analyzer.d.ts +3 -0
- package/dist/tools/reconciliation/analyzer.js +567 -0
- package/dist/tools/reconciliation/executor.d.ts +94 -0
- package/dist/tools/reconciliation/executor.js +611 -0
- package/dist/tools/reconciliation/index.d.ts +54 -0
- package/dist/tools/reconciliation/index.js +249 -0
- package/dist/tools/reconciliation/matcher.d.ts +3 -0
- package/dist/tools/reconciliation/matcher.js +160 -0
- package/dist/tools/reconciliation/payeeNormalizer.d.ts +6 -0
- package/dist/tools/reconciliation/payeeNormalizer.js +77 -0
- package/dist/tools/reconciliation/recommendationEngine.d.ts +2 -0
- package/dist/tools/reconciliation/recommendationEngine.js +273 -0
- package/dist/tools/reconciliation/reportFormatter.d.ts +13 -0
- package/dist/tools/reconciliation/reportFormatter.js +214 -0
- package/dist/tools/reconciliation/types.d.ts +172 -0
- package/dist/tools/reconciliation/types.js +7 -0
- package/dist/tools/schemas/outputs/accountOutputs.d.ts +58 -0
- package/dist/tools/schemas/outputs/accountOutputs.js +24 -0
- package/dist/tools/schemas/outputs/budgetOutputs.d.ts +48 -0
- package/dist/tools/schemas/outputs/budgetOutputs.js +15 -0
- package/dist/tools/schemas/outputs/categoryOutputs.d.ts +93 -0
- package/dist/tools/schemas/outputs/categoryOutputs.js +37 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +269 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.js +181 -0
- package/dist/tools/schemas/outputs/index.d.ts +14 -0
- package/dist/tools/schemas/outputs/index.js +14 -0
- package/dist/tools/schemas/outputs/monthOutputs.d.ts +122 -0
- package/dist/tools/schemas/outputs/monthOutputs.js +51 -0
- package/dist/tools/schemas/outputs/payeeOutputs.d.ts +34 -0
- package/dist/tools/schemas/outputs/payeeOutputs.js +16 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +1275 -0
- package/dist/tools/schemas/outputs/reconciliationOutputs.js +377 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +717 -0
- package/dist/tools/schemas/outputs/transactionMutationOutputs.js +260 -0
- package/dist/tools/schemas/outputs/transactionOutputs.d.ts +98 -0
- package/dist/tools/schemas/outputs/transactionOutputs.js +49 -0
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +219 -0
- package/dist/tools/schemas/outputs/utilityOutputs.js +120 -0
- package/dist/tools/schemas/shared/commonOutputs.d.ts +24 -0
- package/dist/tools/schemas/shared/commonOutputs.js +27 -0
- package/dist/tools/toolCategories.d.ts +32 -0
- package/dist/tools/toolCategories.js +32 -0
- package/dist/tools/transactionTools.d.ts +315 -0
- package/dist/tools/transactionTools.js +1722 -0
- package/dist/tools/utilityTools.d.ts +10 -0
- package/dist/tools/utilityTools.js +56 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/types/index.js +16 -0
- package/dist/types/toolAnnotations.d.ts +7 -0
- package/dist/types/toolAnnotations.js +1 -0
- package/dist/utils/amountUtils.d.ts +3 -0
- package/dist/utils/amountUtils.js +10 -0
- package/dist/utils/dateUtils.d.ts +9 -0
- package/dist/utils/dateUtils.js +43 -0
- package/dist/utils/money.d.ts +21 -0
- package/dist/utils/money.js +51 -0
- package/docs/README.md +72 -0
- package/docs/assets/examples/reconciliation-with-recommendations.json +68 -0
- package/docs/assets/schemas/reconciliation-v2.json +338 -0
- package/docs/getting-started/CONFIGURATION.md +175 -0
- package/docs/getting-started/INSTALLATION.md +333 -0
- package/docs/getting-started/QUICKSTART.md +282 -0
- package/docs/guides/ARCHITECTURE.md +650 -0
- package/docs/guides/DEPLOYMENT.md +189 -0
- package/docs/guides/INTEGRATION_TESTING.md +730 -0
- package/docs/guides/TESTING.md +591 -0
- package/docs/reconciliation-flow.md +83 -0
- package/docs/reference/API.md +1450 -0
- package/docs/reference/EXAMPLES.md +946 -0
- package/docs/reference/TOOLS.md +348 -0
- package/docs/reference/TROUBLESHOOTING.md +481 -0
- package/esbuild.config.mjs +68 -0
- package/eslint.config.js +49 -0
- package/fix-types.sh +17 -0
- package/meta.json +12550 -0
- package/package.json +105 -0
- package/package.json.tmp +105 -0
- package/scripts/analyze-bundle.mjs +41 -0
- package/scripts/create-pr-description.js +203 -0
- package/scripts/generate-mcpb.ps1 +96 -0
- package/scripts/run-domain-integration-tests.js +33 -0
- package/scripts/run-generate-mcpb.js +29 -0
- package/scripts/run-throttled-integration-tests.js +116 -0
- package/scripts/test-delta-params.mjs +140 -0
- package/scripts/test-recommendations.ts +53 -0
- package/scripts/tmpTransaction.ts +48 -0
- package/scripts/validate-env.js +122 -0
- package/scripts/verify-build.js +105 -0
- package/scripts/watch-and-restart.ps1 +50 -0
- package/src/__tests__/comprehensive.integration.test.ts +1196 -0
- package/src/__tests__/delta.performance.test.ts +80 -0
- package/src/__tests__/performance.test.ts +725 -0
- package/src/__tests__/setup.ts +449 -0
- package/src/__tests__/testRunner.ts +444 -0
- package/src/__tests__/testUtils.ts +563 -0
- package/src/__tests__/workflows.e2e.test.ts +1675 -0
- package/src/index.ts +124 -0
- package/src/server/.gitkeep +1 -0
- package/src/server/YNABMCPServer.ts +1188 -0
- package/src/server/__tests__/YNABMCPServer.integration.test.ts +903 -0
- package/src/server/__tests__/YNABMCPServer.test.ts +894 -0
- package/src/server/__tests__/budgetResolver.test.ts +425 -0
- package/src/server/__tests__/cacheManager.test.ts +880 -0
- package/src/server/__tests__/config.test.ts +166 -0
- package/src/server/__tests__/deltaCache.merge.test.ts +724 -0
- package/src/server/__tests__/deltaCache.swr.test.ts +168 -0
- package/src/server/__tests__/deltaCache.test.ts +774 -0
- package/src/server/__tests__/diagnostics.test.ts +823 -0
- package/src/server/__tests__/errorHandler.integration.test.ts +466 -0
- package/src/server/__tests__/errorHandler.test.ts +416 -0
- package/src/server/__tests__/prompts.test.ts +354 -0
- package/src/server/__tests__/rateLimiter.test.ts +314 -0
- package/src/server/__tests__/requestLogger.test.ts +408 -0
- package/src/server/__tests__/resources.test.ts +299 -0
- package/src/server/__tests__/security.integration.test.ts +426 -0
- package/src/server/__tests__/securityMiddleware.test.ts +449 -0
- package/src/server/__tests__/server-startup.integration.test.ts +477 -0
- package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -0
- package/src/server/__tests__/toolRegistry.test.ts +855 -0
- package/src/server/budgetResolver.ts +235 -0
- package/src/server/cacheManager.ts +503 -0
- package/src/server/config.ts +41 -0
- package/src/server/deltaCache.merge.ts +149 -0
- package/src/server/deltaCache.ts +341 -0
- package/src/server/diagnostics.ts +338 -0
- package/src/server/errorHandler.ts +756 -0
- package/src/server/prompts.ts +291 -0
- package/src/server/rateLimiter.ts +156 -0
- package/src/server/requestLogger.ts +344 -0
- package/src/server/resources.ts +168 -0
- package/src/server/responseFormatter.ts +51 -0
- package/src/server/securityMiddleware.ts +236 -0
- package/src/server/serverKnowledgeStore.ts +91 -0
- package/src/server/toolRegistry.ts +489 -0
- package/src/tools/.gitkeep +1 -0
- package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -0
- package/src/tools/__tests__/accountTools.integration.test.ts +117 -0
- package/src/tools/__tests__/accountTools.test.ts +653 -0
- package/src/tools/__tests__/budgetTools.delta.integration.test.ts +90 -0
- package/src/tools/__tests__/budgetTools.integration.test.ts +134 -0
- package/src/tools/__tests__/budgetTools.test.ts +423 -0
- package/src/tools/__tests__/categoryTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/categoryTools.integration.test.ts +295 -0
- package/src/tools/__tests__/categoryTools.test.ts +622 -0
- package/src/tools/__tests__/compareTransactions/formatter.test.ts +486 -0
- package/src/tools/__tests__/compareTransactions/index.test.ts +383 -0
- package/src/tools/__tests__/compareTransactions/matcher.test.ts +410 -0
- package/src/tools/__tests__/compareTransactions/parser.test.ts +764 -0
- package/src/tools/__tests__/compareTransactions.test.ts +342 -0
- package/src/tools/__tests__/compareTransactions.window.test.ts +147 -0
- package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +76 -0
- package/src/tools/__tests__/deltaFetcher.test.ts +270 -0
- package/src/tools/__tests__/deltaSupport.test.ts +188 -0
- package/src/tools/__tests__/deltaTestUtils.ts +46 -0
- package/src/tools/__tests__/exportTransactions.test.ts +213 -0
- package/src/tools/__tests__/monthTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/monthTools.integration.test.ts +174 -0
- package/src/tools/__tests__/monthTools.test.ts +523 -0
- package/src/tools/__tests__/payeeTools.delta.integration.test.ts +80 -0
- package/src/tools/__tests__/payeeTools.integration.test.ts +150 -0
- package/src/tools/__tests__/payeeTools.test.ts +445 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +762 -0
- package/src/tools/__tests__/transactionTools.test.ts +3521 -0
- package/src/tools/__tests__/utilityTools.integration.test.ts +128 -0
- package/src/tools/__tests__/utilityTools.test.ts +205 -0
- package/src/tools/accountTools.ts +283 -0
- package/src/tools/budgetTools.ts +112 -0
- package/src/tools/categoryTools.ts +366 -0
- package/src/tools/compareTransactions/formatter.ts +163 -0
- package/src/tools/compareTransactions/index.ts +228 -0
- package/src/tools/compareTransactions/matcher.ts +240 -0
- package/src/tools/compareTransactions/parser.ts +557 -0
- package/src/tools/compareTransactions/types.ts +60 -0
- package/src/tools/compareTransactions.ts +3 -0
- package/src/tools/deltaFetcher.ts +278 -0
- package/src/tools/deltaSupport.ts +293 -0
- package/src/tools/exportTransactions.ts +273 -0
- package/src/tools/monthTools.ts +164 -0
- package/src/tools/payeeTools.ts +140 -0
- package/src/tools/reconcileAdapter.ts +312 -0
- package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +122 -0
- package/src/tools/reconciliation/__tests__/adapter.test.ts +234 -0
- package/src/tools/reconciliation/__tests__/analyzer.test.ts +406 -0
- package/src/tools/reconciliation/__tests__/executor.integration.test.ts +366 -0
- package/src/tools/reconciliation/__tests__/executor.test.ts +779 -0
- package/src/tools/reconciliation/__tests__/matcher.test.ts +650 -0
- package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +278 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +658 -0
- package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1000 -0
- package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +151 -0
- package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +573 -0
- package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +78 -0
- package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +47 -0
- package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +61 -0
- package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +49 -0
- package/src/tools/reconciliation/analyzer.ts +824 -0
- package/src/tools/reconciliation/executor.ts +880 -0
- package/src/tools/reconciliation/index.ts +400 -0
- package/src/tools/reconciliation/matcher.ts +269 -0
- package/src/tools/reconciliation/payeeNormalizer.ts +167 -0
- package/src/tools/reconciliation/recommendationEngine.ts +506 -0
- package/src/tools/reconciliation/reportFormatter.ts +363 -0
- package/src/tools/reconciliation/types.ts +314 -0
- package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +424 -0
- package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +310 -0
- package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +448 -0
- package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +519 -0
- package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +155 -0
- package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +288 -0
- package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +478 -0
- package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +370 -0
- package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +401 -0
- package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +213 -0
- package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +474 -0
- package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +333 -0
- package/src/tools/schemas/outputs/accountOutputs.ts +137 -0
- package/src/tools/schemas/outputs/budgetOutputs.ts +86 -0
- package/src/tools/schemas/outputs/categoryOutputs.ts +194 -0
- package/src/tools/schemas/outputs/comparisonOutputs.ts +600 -0
- package/src/tools/schemas/outputs/index.ts +270 -0
- package/src/tools/schemas/outputs/monthOutputs.ts +243 -0
- package/src/tools/schemas/outputs/payeeOutputs.ts +105 -0
- package/src/tools/schemas/outputs/reconciliationOutputs.ts +796 -0
- package/src/tools/schemas/outputs/transactionMutationOutputs.ts +758 -0
- package/src/tools/schemas/outputs/transactionOutputs.ts +243 -0
- package/src/tools/schemas/outputs/utilityOutputs.ts +411 -0
- package/src/tools/schemas/shared/commonOutputs.ts +140 -0
- package/src/tools/toolCategories.ts +140 -0
- package/src/tools/transactionTools.ts +2509 -0
- package/src/tools/utilityTools.ts +90 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/__tests__/index.test.ts +52 -0
- package/src/types/index.ts +67 -0
- package/src/types/integration-tests.d.ts +35 -0
- package/src/types/toolAnnotations.ts +44 -0
- package/src/utils/__tests__/dateUtils.test.ts +170 -0
- package/src/utils/__tests__/money.test.ts +189 -0
- package/src/utils/amountUtils.ts +32 -0
- package/src/utils/dateUtils.ts +108 -0
- package/src/utils/money.ts +123 -0
- package/test-csv-sample.csv +28 -0
- package/test-exports/sample_bank_statement.csv +7 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +23 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +44 -0
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +58 -0
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +115 -0
- package/test-reconcile-autodetect.js +40 -0
- package/test-reconcile-tool.js +152 -0
- package/test-reconcile-with-csv.cjs +89 -0
- package/test-statement.csv +8 -0
- package/test_debug.js +47 -0
- package/test_simple.mjs +16 -0
- package/tsconfig.json +31 -0
- package/tsconfig.prod.json +18 -0
- package/vitest-reporters/split-json-reporter.ts +211 -0
- package/vitest.config.ts +96 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import * as ynab from 'ynab';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
import { withToolErrorHandling } from '../types/index.js';
|
|
5
|
+
import { responseFormatter } from '../server/responseFormatter.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Schema for ynab:convert_amount tool parameters
|
|
9
|
+
*/
|
|
10
|
+
export const ConvertAmountSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
amount: z.number().finite(),
|
|
13
|
+
to_milliunits: z.boolean(),
|
|
14
|
+
})
|
|
15
|
+
.strict();
|
|
16
|
+
|
|
17
|
+
export type ConvertAmountParams = z.infer<typeof ConvertAmountSchema>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Handles the ynab:get_user tool call
|
|
21
|
+
* Gets information about the authenticated user
|
|
22
|
+
*/
|
|
23
|
+
export async function handleGetUser(ynabAPI: ynab.API): Promise<CallToolResult> {
|
|
24
|
+
return await withToolErrorHandling(
|
|
25
|
+
async () => {
|
|
26
|
+
const response = await ynabAPI.user.getUser();
|
|
27
|
+
const userInfo = response.data.user;
|
|
28
|
+
|
|
29
|
+
const user = {
|
|
30
|
+
id: userInfo.id,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content: [
|
|
35
|
+
{
|
|
36
|
+
type: 'text',
|
|
37
|
+
text: responseFormatter.format({ user }),
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
'ynab:get_user',
|
|
43
|
+
'getting user information',
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handles the ynab:convert_amount tool call
|
|
49
|
+
* Converts between dollars and milliunits with integer arithmetic for precision
|
|
50
|
+
*/
|
|
51
|
+
export async function handleConvertAmount(params: ConvertAmountParams): Promise<CallToolResult> {
|
|
52
|
+
return await withToolErrorHandling(
|
|
53
|
+
async () => {
|
|
54
|
+
const { amount, to_milliunits } = params;
|
|
55
|
+
|
|
56
|
+
let result: number;
|
|
57
|
+
let description: string;
|
|
58
|
+
|
|
59
|
+
if (to_milliunits) {
|
|
60
|
+
// Convert from dollars to milliunits
|
|
61
|
+
// Use integer arithmetic to avoid floating-point precision issues
|
|
62
|
+
result = Math.round(amount * 1000);
|
|
63
|
+
description = `$${amount.toFixed(2)} = ${result} milliunits`;
|
|
64
|
+
} else {
|
|
65
|
+
// Convert from milliunits to dollars
|
|
66
|
+
// Assume input amount is in milliunits
|
|
67
|
+
result = amount / 1000;
|
|
68
|
+
description = `${amount} milliunits = $${result.toFixed(2)}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: 'text',
|
|
75
|
+
text: responseFormatter.format({
|
|
76
|
+
conversion: {
|
|
77
|
+
original_amount: amount,
|
|
78
|
+
converted_amount: result,
|
|
79
|
+
to_milliunits,
|
|
80
|
+
description,
|
|
81
|
+
},
|
|
82
|
+
}),
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
'ynab:convert_amount',
|
|
88
|
+
'converting amount',
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file ensures the types directory is tracked by git
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { AuthenticationError, ConfigurationError } from '../index';
|
|
3
|
+
|
|
4
|
+
describe('Error Classes', () => {
|
|
5
|
+
describe('AuthenticationError', () => {
|
|
6
|
+
it('should create error with correct name and message', () => {
|
|
7
|
+
const message = 'Invalid access token';
|
|
8
|
+
const error = new AuthenticationError(message);
|
|
9
|
+
|
|
10
|
+
expect(error).toBeInstanceOf(Error);
|
|
11
|
+
expect(error).toBeInstanceOf(AuthenticationError);
|
|
12
|
+
expect(error.name).toBe('AuthenticationError');
|
|
13
|
+
expect(error.message).toBe(message);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should be throwable and catchable', () => {
|
|
17
|
+
const message = 'Token expired';
|
|
18
|
+
|
|
19
|
+
expect(() => {
|
|
20
|
+
throw new AuthenticationError(message);
|
|
21
|
+
}).toThrow(AuthenticationError);
|
|
22
|
+
|
|
23
|
+
expect(() => {
|
|
24
|
+
throw new AuthenticationError(message);
|
|
25
|
+
}).toThrow(message);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('ConfigurationError', () => {
|
|
30
|
+
it('should create error with correct name and message', () => {
|
|
31
|
+
const message = 'Missing environment variable';
|
|
32
|
+
const error = new ConfigurationError(message);
|
|
33
|
+
|
|
34
|
+
expect(error).toBeInstanceOf(Error);
|
|
35
|
+
expect(error).toBeInstanceOf(ConfigurationError);
|
|
36
|
+
expect(error.name).toBe('ConfigurationError');
|
|
37
|
+
expect(error.message).toBe(message);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should be throwable and catchable', () => {
|
|
41
|
+
const message = 'Invalid configuration';
|
|
42
|
+
|
|
43
|
+
expect(() => {
|
|
44
|
+
throw new ConfigurationError(message);
|
|
45
|
+
}).toThrow(ConfigurationError);
|
|
46
|
+
|
|
47
|
+
expect(() => {
|
|
48
|
+
throw new ConfigurationError(message);
|
|
49
|
+
}).toThrow(message);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for YNAB MCP Server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface AuthenticationConfig {
|
|
6
|
+
accessToken: string;
|
|
7
|
+
validateToken(): Promise<boolean>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ServerConfig {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
defaultBudgetId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class AuthenticationError extends Error {
|
|
16
|
+
constructor(message: string) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'AuthenticationError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class ConfigurationError extends Error {
|
|
23
|
+
constructor(message: string) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'ConfigurationError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Re-export error handling types for convenience
|
|
30
|
+
export {
|
|
31
|
+
ErrorHandler,
|
|
32
|
+
YNABAPIError,
|
|
33
|
+
ValidationError,
|
|
34
|
+
YNABErrorCode,
|
|
35
|
+
SecurityErrorCode,
|
|
36
|
+
type ErrorResponse,
|
|
37
|
+
handleToolError,
|
|
38
|
+
withToolErrorHandling,
|
|
39
|
+
} from '../server/errorHandler.js';
|
|
40
|
+
|
|
41
|
+
// Re-export security modules
|
|
42
|
+
export {
|
|
43
|
+
RateLimiter,
|
|
44
|
+
RateLimitError,
|
|
45
|
+
globalRateLimiter,
|
|
46
|
+
type RateLimitConfig,
|
|
47
|
+
type RateLimitInfo,
|
|
48
|
+
} from '../server/rateLimiter.js';
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
RequestLogger,
|
|
52
|
+
globalRequestLogger,
|
|
53
|
+
type LogEntry,
|
|
54
|
+
type LoggerConfig,
|
|
55
|
+
} from '../server/requestLogger.js';
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
SecurityMiddleware,
|
|
59
|
+
withSecurityWrapper,
|
|
60
|
+
type SecurityContext,
|
|
61
|
+
} from '../server/securityMiddleware.js';
|
|
62
|
+
|
|
63
|
+
// Re-export tool annotation types
|
|
64
|
+
export type { MCPToolAnnotations } from './toolAnnotations.js';
|
|
65
|
+
|
|
66
|
+
// Re-export tool registry types for convenience
|
|
67
|
+
export type { ToolDefinition } from '../server/toolRegistry.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type IntegrationTestTier = 'core' | 'domain' | 'full';
|
|
2
|
+
type IntegrationTestDomain =
|
|
3
|
+
| 'budgets'
|
|
4
|
+
| 'accounts'
|
|
5
|
+
| 'transactions'
|
|
6
|
+
| 'categories'
|
|
7
|
+
| 'payees'
|
|
8
|
+
| 'months'
|
|
9
|
+
| 'delta'
|
|
10
|
+
| 'reconciliation'
|
|
11
|
+
| 'utility'
|
|
12
|
+
| 'security'
|
|
13
|
+
| 'server'
|
|
14
|
+
| 'workflows'
|
|
15
|
+
| (string & {});
|
|
16
|
+
|
|
17
|
+
export interface IntegrationTestMeta {
|
|
18
|
+
tier: IntegrationTestTier;
|
|
19
|
+
domain: IntegrationTestDomain;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module '@vitest/runner' {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
24
|
+
interface TaskMeta extends Partial<IntegrationTestMeta> {}
|
|
25
|
+
|
|
26
|
+
interface TestOptions {
|
|
27
|
+
meta?: IntegrationTestMeta;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare module 'vitest' {
|
|
32
|
+
interface TestOptions {
|
|
33
|
+
meta?: IntegrationTestMeta;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-safe MCP tool annotations interface
|
|
3
|
+
*
|
|
4
|
+
* These annotations are advisory hints per the MCP specification to help AI clients
|
|
5
|
+
* understand tool behavior. They do not enforce behavior at runtime.
|
|
6
|
+
*
|
|
7
|
+
* @see https://blog.marcnuri.com/mcp-tool-annotations-introduction
|
|
8
|
+
*/
|
|
9
|
+
export interface MCPToolAnnotations {
|
|
10
|
+
/**
|
|
11
|
+
* Human-readable title for UI display
|
|
12
|
+
*
|
|
13
|
+
* @example "YNAB: Delete Transaction"
|
|
14
|
+
*/
|
|
15
|
+
title?: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Indicates tool only reads data without modifications
|
|
19
|
+
*
|
|
20
|
+
* @example true for list_budgets, get_account
|
|
21
|
+
*/
|
|
22
|
+
readOnlyHint?: boolean;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* For non-read-only tools, indicates irreversible operations
|
|
26
|
+
*
|
|
27
|
+
* @example true for delete_transaction
|
|
28
|
+
*/
|
|
29
|
+
destructiveHint?: boolean;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Indicates repeated identical calls have same effect
|
|
33
|
+
*
|
|
34
|
+
* @example true for set_default_budget
|
|
35
|
+
*/
|
|
36
|
+
idempotentHint?: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Indicates tool interacts with external systems like YNAB API
|
|
40
|
+
*
|
|
41
|
+
* @example true for all YNAB tools that call the YNAB API
|
|
42
|
+
*/
|
|
43
|
+
openWorldHint?: boolean;
|
|
44
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
formatYNABMonth,
|
|
4
|
+
formatISODate,
|
|
5
|
+
getCurrentMonth,
|
|
6
|
+
getToday,
|
|
7
|
+
getHistoricalMonths,
|
|
8
|
+
subtractMonths,
|
|
9
|
+
isValidISODate,
|
|
10
|
+
isValidYNABMonth,
|
|
11
|
+
yearMonthToYNABMonth,
|
|
12
|
+
} from '../dateUtils.js';
|
|
13
|
+
|
|
14
|
+
describe('dateUtils', () => {
|
|
15
|
+
describe('formatYNABMonth', () => {
|
|
16
|
+
it('should format date as YYYY-MM-01', () => {
|
|
17
|
+
const date = new Date('2024-03-15T10:30:00.000Z');
|
|
18
|
+
expect(formatYNABMonth(date)).toBe('2024-03-01');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should handle December correctly', () => {
|
|
22
|
+
const date = new Date('2024-12-31T23:59:59.999Z');
|
|
23
|
+
expect(formatYNABMonth(date)).toBe('2024-12-01');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('formatISODate', () => {
|
|
28
|
+
it('should format date as YYYY-MM-DD', () => {
|
|
29
|
+
const date = new Date('2024-03-15T10:30:00.000Z');
|
|
30
|
+
expect(formatISODate(date)).toBe('2024-03-15');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('getCurrentMonth', () => {
|
|
35
|
+
it('should return current month in YYYY-MM-01 format', () => {
|
|
36
|
+
const result = getCurrentMonth();
|
|
37
|
+
expect(result).toMatch(/^\d{4}-\d{2}-01$/);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('getToday', () => {
|
|
42
|
+
it('should return today in YYYY-MM-DD format', () => {
|
|
43
|
+
const result = getToday();
|
|
44
|
+
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('getHistoricalMonths', () => {
|
|
49
|
+
it('should generate correct number of historical months', () => {
|
|
50
|
+
const baseDate = new Date('2024-03-15');
|
|
51
|
+
const months = getHistoricalMonths(3, baseDate);
|
|
52
|
+
|
|
53
|
+
expect(months).toHaveLength(3);
|
|
54
|
+
expect(months[0]).toBe('2024-03-01'); // Current month (i=0)
|
|
55
|
+
expect(months[1]).toBe('2024-02-01'); // 1 month back
|
|
56
|
+
expect(months[2]).toBe('2024-01-01'); // 2 months back
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should handle year boundary correctly', () => {
|
|
60
|
+
const baseDate = new Date('2024-01-15');
|
|
61
|
+
const months = getHistoricalMonths(3, baseDate);
|
|
62
|
+
|
|
63
|
+
expect(months).toHaveLength(3);
|
|
64
|
+
expect(months[0]).toBe('2024-01-01');
|
|
65
|
+
expect(months[1]).toBe('2023-12-01');
|
|
66
|
+
expect(months[2]).toBe('2023-11-01');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should use current date when no base date provided', () => {
|
|
70
|
+
const months = getHistoricalMonths(2);
|
|
71
|
+
expect(months).toHaveLength(2);
|
|
72
|
+
expect(months[0]).toMatch(/^\d{4}-\d{2}-01$/);
|
|
73
|
+
expect(months[1]).toMatch(/^\d{4}-\d{2}-01$/);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should generate 6 months correctly (bug test case)', () => {
|
|
77
|
+
const baseDate = new Date('2025-08-15');
|
|
78
|
+
const months = getHistoricalMonths(6, baseDate);
|
|
79
|
+
|
|
80
|
+
expect(months).toEqual([
|
|
81
|
+
'2025-08-01',
|
|
82
|
+
'2025-07-01',
|
|
83
|
+
'2025-06-01',
|
|
84
|
+
'2025-05-01',
|
|
85
|
+
'2025-04-01',
|
|
86
|
+
'2025-03-01',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
// Ensure no duplicates (the original bug)
|
|
90
|
+
const uniqueMonths = new Set(months);
|
|
91
|
+
expect(uniqueMonths.size).toBe(6);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('subtractMonths', () => {
|
|
96
|
+
it('should subtract months correctly', () => {
|
|
97
|
+
const baseDate = new Date('2024-03-15');
|
|
98
|
+
const result = subtractMonths(baseDate, 2);
|
|
99
|
+
|
|
100
|
+
expect(result.getFullYear()).toBe(2024);
|
|
101
|
+
expect(result.getMonth()).toBe(0); // January (0-indexed)
|
|
102
|
+
// Note: When subtracting months, if the target month doesn't have enough days,
|
|
103
|
+
// JavaScript adjusts the date (e.g., March 15 - 2 months might become January 14)
|
|
104
|
+
expect(result.getDate()).toBeGreaterThanOrEqual(14);
|
|
105
|
+
expect(result.getDate()).toBeLessThanOrEqual(15);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should handle year boundary', () => {
|
|
109
|
+
const baseDate = new Date('2024-01-15');
|
|
110
|
+
const result = subtractMonths(baseDate, 2);
|
|
111
|
+
|
|
112
|
+
expect(result.getFullYear()).toBe(2023);
|
|
113
|
+
expect(result.getMonth()).toBe(10); // November (0-indexed)
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('isValidISODate', () => {
|
|
118
|
+
it('should validate correct ISO dates', () => {
|
|
119
|
+
expect(isValidISODate('2024-03-15')).toBe(true);
|
|
120
|
+
expect(isValidISODate('2024-12-31')).toBe(true);
|
|
121
|
+
expect(isValidISODate('2024-02-29')).toBe(true); // Leap year
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should reject invalid formats', () => {
|
|
125
|
+
expect(isValidISODate('03/15/2024')).toBe(false);
|
|
126
|
+
expect(isValidISODate('2024-3-15')).toBe(false);
|
|
127
|
+
expect(isValidISODate('2024-03-5')).toBe(false);
|
|
128
|
+
expect(isValidISODate('24-03-15')).toBe(false);
|
|
129
|
+
expect(isValidISODate('not-a-date')).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should reject invalid dates with correct format', () => {
|
|
133
|
+
expect(isValidISODate('2024-02-30')).toBe(false); // Invalid date
|
|
134
|
+
expect(isValidISODate('2024-13-01')).toBe(false); // Invalid month
|
|
135
|
+
expect(isValidISODate('2023-02-29')).toBe(false); // Not a leap year
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('isValidYNABMonth', () => {
|
|
140
|
+
it('should validate correct YNAB month format', () => {
|
|
141
|
+
expect(isValidYNABMonth('2024-03-01')).toBe(true);
|
|
142
|
+
expect(isValidYNABMonth('2024-12-01')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should reject invalid formats', () => {
|
|
146
|
+
expect(isValidYNABMonth('2024-03-15')).toBe(false);
|
|
147
|
+
expect(isValidYNABMonth('2024-03')).toBe(false);
|
|
148
|
+
expect(isValidYNABMonth('03-01-2024')).toBe(false);
|
|
149
|
+
expect(isValidYNABMonth('not-a-month')).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should reject invalid months with correct format', () => {
|
|
153
|
+
expect(isValidYNABMonth('2024-13-01')).toBe(false); // Invalid month
|
|
154
|
+
expect(isValidYNABMonth('2024-00-01')).toBe(false); // Invalid month
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('yearMonthToYNABMonth', () => {
|
|
159
|
+
it('should convert YYYY-MM to YYYY-MM-01', () => {
|
|
160
|
+
expect(yearMonthToYNABMonth('2024-03')).toBe('2024-03-01');
|
|
161
|
+
expect(yearMonthToYNABMonth('2024-12')).toBe('2024-12-01');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should throw error for invalid format', () => {
|
|
165
|
+
expect(() => yearMonthToYNABMonth('2024-3')).toThrow('Invalid year-month format');
|
|
166
|
+
expect(() => yearMonthToYNABMonth('24-03')).toThrow('Invalid year-month format');
|
|
167
|
+
expect(() => yearMonthToYNABMonth('not-valid')).toThrow('Invalid year-month format');
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import type * as ynab from 'ynab';
|
|
3
|
+
import {
|
|
4
|
+
toMilli,
|
|
5
|
+
fromMilli,
|
|
6
|
+
assertMilli,
|
|
7
|
+
addMilli,
|
|
8
|
+
inWindow,
|
|
9
|
+
moneyDirection,
|
|
10
|
+
formatMoney,
|
|
11
|
+
toMoneyValue,
|
|
12
|
+
toMoneyValueFromDecimal,
|
|
13
|
+
getDecimalDigits,
|
|
14
|
+
getCurrencyCode,
|
|
15
|
+
} from '../money.js';
|
|
16
|
+
|
|
17
|
+
describe('money utilities', () => {
|
|
18
|
+
describe('toMilli', () => {
|
|
19
|
+
it('converts dollars to milliunits correctly', () => {
|
|
20
|
+
expect(toMilli(1.23)).toBe(1230);
|
|
21
|
+
expect(toMilli(-5.67)).toBe(-5670);
|
|
22
|
+
expect(toMilli('10.50')).toBe(10500);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('throws for invalid amounts', () => {
|
|
26
|
+
expect(() => toMilli(Number.MAX_SAFE_INTEGER)).toThrow('Invalid/unsafe amount');
|
|
27
|
+
expect(() => toMilli('invalid')).toThrow('Invalid/unsafe amount');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('fromMilli', () => {
|
|
32
|
+
it('converts milliunits with 2 decimal digits (USD) by default', () => {
|
|
33
|
+
expect(fromMilli(1230)).toBe(1.23);
|
|
34
|
+
expect(fromMilli(-5670)).toBe(-5.67);
|
|
35
|
+
expect(fromMilli(0)).toBe(0);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('converts milliunits with 0 decimal digits (JPY)', () => {
|
|
39
|
+
expect(fromMilli(123000, 0)).toBe(123);
|
|
40
|
+
expect(fromMilli(1000, 0)).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('converts milliunits with 3 decimal digits (BHD)', () => {
|
|
44
|
+
expect(fromMilli(1234, 3)).toBe(1.234);
|
|
45
|
+
expect(fromMilli(500, 3)).toBe(0.5);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('converts milliunits with explicit 2 decimal digits', () => {
|
|
49
|
+
expect(fromMilli(1230, 2)).toBe(1.23);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('assertMilli', () => {
|
|
54
|
+
it('passes for safe integers', () => {
|
|
55
|
+
expect(() => assertMilli(1000)).not.toThrow();
|
|
56
|
+
expect(() => assertMilli(-500)).not.toThrow();
|
|
57
|
+
expect(() => assertMilli(0)).not.toThrow();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('throws for non-safe integers', () => {
|
|
61
|
+
expect(() => assertMilli(1.5)).toThrow('Expected safe integer milliunits');
|
|
62
|
+
expect(() => assertMilli(Number.MAX_SAFE_INTEGER + 1)).toThrow(
|
|
63
|
+
'Expected safe integer milliunits',
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('addMilli', () => {
|
|
69
|
+
it('adds milliunits correctly', () => {
|
|
70
|
+
expect(addMilli(1000, 2000)).toBe(3000);
|
|
71
|
+
expect(addMilli(-500, 300)).toBe(-200);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('throws on overflow', () => {
|
|
75
|
+
expect(() => addMilli(Number.MAX_SAFE_INTEGER, 1)).toThrow('Milliunit sum overflow');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('inWindow', () => {
|
|
80
|
+
it('checks date windows correctly', () => {
|
|
81
|
+
expect(inWindow('2024-01-15', '2024-01-01', '2024-01-31')).toBe(true);
|
|
82
|
+
expect(inWindow('2023-12-31', '2024-01-01', '2024-01-31')).toBe(false);
|
|
83
|
+
expect(inWindow('2024-02-01', '2024-01-01', '2024-01-31')).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('handles optional bounds', () => {
|
|
87
|
+
expect(inWindow('2024-01-15', undefined, '2024-01-31')).toBe(true);
|
|
88
|
+
expect(inWindow('2024-01-15', '2024-01-01', undefined)).toBe(true);
|
|
89
|
+
expect(inWindow('2024-01-15', undefined, undefined)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('currency format helpers', () => {
|
|
94
|
+
it('extracts decimal digits from currency format', () => {
|
|
95
|
+
const usdFormat: ynab.CurrencyFormat = {
|
|
96
|
+
iso_code: 'USD',
|
|
97
|
+
example_format: '$1,234.56',
|
|
98
|
+
decimal_digits: 2,
|
|
99
|
+
decimal_separator: '.',
|
|
100
|
+
symbol_first: true,
|
|
101
|
+
group_separator: ',',
|
|
102
|
+
currency_symbol: '$',
|
|
103
|
+
display_symbol: true,
|
|
104
|
+
};
|
|
105
|
+
expect(getDecimalDigits(usdFormat)).toBe(2);
|
|
106
|
+
|
|
107
|
+
const jpyFormat: ynab.CurrencyFormat = {
|
|
108
|
+
iso_code: 'JPY',
|
|
109
|
+
example_format: '¥1,234',
|
|
110
|
+
decimal_digits: 0,
|
|
111
|
+
decimal_separator: '.',
|
|
112
|
+
symbol_first: true,
|
|
113
|
+
group_separator: ',',
|
|
114
|
+
currency_symbol: '¥',
|
|
115
|
+
display_symbol: true,
|
|
116
|
+
};
|
|
117
|
+
expect(getDecimalDigits(jpyFormat)).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('returns default decimal digits for null/undefined', () => {
|
|
121
|
+
expect(getDecimalDigits(null)).toBe(2);
|
|
122
|
+
expect(getDecimalDigits(undefined)).toBe(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('extracts currency code from currency format', () => {
|
|
126
|
+
const eurFormat: ynab.CurrencyFormat = {
|
|
127
|
+
iso_code: 'EUR',
|
|
128
|
+
example_format: '€1.234,56',
|
|
129
|
+
decimal_digits: 2,
|
|
130
|
+
decimal_separator: ',',
|
|
131
|
+
symbol_first: false,
|
|
132
|
+
group_separator: '.',
|
|
133
|
+
currency_symbol: '€',
|
|
134
|
+
display_symbol: true,
|
|
135
|
+
};
|
|
136
|
+
expect(getCurrencyCode(eurFormat)).toBe('EUR');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('returns default currency for null/undefined', () => {
|
|
140
|
+
expect(getCurrencyCode(null)).toBe('USD');
|
|
141
|
+
expect(getCurrencyCode(undefined)).toBe('USD');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('moneyValue helpers', () => {
|
|
146
|
+
it('derives direction correctly', () => {
|
|
147
|
+
expect(moneyDirection(0)).toBe('balanced');
|
|
148
|
+
expect(moneyDirection(1500)).toBe('credit');
|
|
149
|
+
expect(moneyDirection(-2500)).toBe('debit');
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('formats milliunits into currency strings with default 2 decimals', () => {
|
|
153
|
+
expect(formatMoney(1234)).toBe('$1.23');
|
|
154
|
+
expect(formatMoney(-9870)).toBe('-$9.87');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('formats milliunits with custom currency format', () => {
|
|
158
|
+
expect(formatMoney(123000, 'JPY', 0)).toBe('¥123');
|
|
159
|
+
expect(formatMoney(1234, 'USD', 2)).toBe('$1.23');
|
|
160
|
+
expect(formatMoney(1234, 'EUR', 3)).toBe('€1.234'); // 3 decimal digits requested
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('creates money values from milliunits with default 2 decimals', () => {
|
|
164
|
+
const value = toMoneyValue(22220);
|
|
165
|
+
expect(value.value).toBe(22.22);
|
|
166
|
+
expect(value.value_display).toBe('$22.22');
|
|
167
|
+
expect(value.direction).toBe('credit');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('creates currency-aware money values with decimal digits', () => {
|
|
171
|
+
const valueJPY = toMoneyValue(123000, 'JPY', 0);
|
|
172
|
+
expect(valueJPY.value).toBe(123);
|
|
173
|
+
expect(valueJPY.value_display).toBe('¥123');
|
|
174
|
+
expect(valueJPY.currency).toBe('JPY');
|
|
175
|
+
expect(valueJPY.direction).toBe('credit');
|
|
176
|
+
|
|
177
|
+
const valueUSD = toMoneyValue(1234, 'USD', 2);
|
|
178
|
+
expect(valueUSD.value).toBe(1.23);
|
|
179
|
+
expect(valueUSD.value_display).toBe('$1.23');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('creates money values from decimal amounts', () => {
|
|
183
|
+
const value = toMoneyValueFromDecimal(-45.67);
|
|
184
|
+
expect(value.value_milliunits).toBe(-45670);
|
|
185
|
+
expect(value.value_display).toBe('-$45.67');
|
|
186
|
+
expect(value.direction).toBe('debit');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for converting between YNAB milliunits and dollars
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Converts an amount from milliunits to dollars
|
|
7
|
+
* @param milliunits - Amount in milliunits (1000 milliunits = $1.00)
|
|
8
|
+
* @returns Amount in dollars as a number with 2 decimal places
|
|
9
|
+
*/
|
|
10
|
+
export function milliunitsToAmount(milliunits: number): number {
|
|
11
|
+
return Math.round(milliunits) / 1000;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts an amount from dollars to milliunits
|
|
16
|
+
* @param amount - Amount in dollars
|
|
17
|
+
* @returns Amount in milliunits
|
|
18
|
+
*/
|
|
19
|
+
export function amountToMilliunits(amount: number): number {
|
|
20
|
+
return Math.round(amount * 1000);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Formats an amount from milliunits to a currency string
|
|
25
|
+
* @param milliunits - Amount in milliunits
|
|
26
|
+
* @param currencySymbol - Currency symbol (default: '$')
|
|
27
|
+
* @returns Formatted currency string
|
|
28
|
+
*/
|
|
29
|
+
export function formatAmount(milliunits: number, currencySymbol = '$'): string {
|
|
30
|
+
const amount = milliunitsToAmount(milliunits);
|
|
31
|
+
return `${currencySymbol}${amount.toFixed(2)}`;
|
|
32
|
+
}
|