@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,128 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
+
import * as ynab from 'ynab';
|
|
3
|
+
import { handleGetUser, handleConvertAmount } from '../utilityTools.js';
|
|
4
|
+
import { skipOnRateLimit } from '../../__tests__/testUtils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Utility Tools Integration Tests
|
|
8
|
+
* Skips if YNAB_ACCESS_TOKEN is not set or if SKIP_E2E_TESTS is true
|
|
9
|
+
*/
|
|
10
|
+
const hasToken = !!process.env['YNAB_ACCESS_TOKEN'];
|
|
11
|
+
const shouldSkip = process.env['SKIP_E2E_TESTS'] === 'true' || !hasToken;
|
|
12
|
+
const describeIntegration = shouldSkip ? describe.skip : describe;
|
|
13
|
+
|
|
14
|
+
describeIntegration('Utility Tools Integration Tests', () => {
|
|
15
|
+
let ynabAPI: ynab.API;
|
|
16
|
+
|
|
17
|
+
beforeAll(() => {
|
|
18
|
+
const accessToken = process.env['YNAB_ACCESS_TOKEN']!;
|
|
19
|
+
ynabAPI = new ynab.API(accessToken);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('handleGetUser', () => {
|
|
23
|
+
it(
|
|
24
|
+
'should retrieve user information from YNAB API',
|
|
25
|
+
{ meta: { tier: 'core', domain: 'utility' } },
|
|
26
|
+
async (ctx) => {
|
|
27
|
+
await skipOnRateLimit(async () => {
|
|
28
|
+
const result = await handleGetUser(ynabAPI);
|
|
29
|
+
const response = JSON.parse(result.content[0].text);
|
|
30
|
+
|
|
31
|
+
// If response contains an error, throw it so skipOnRateLimit can catch it
|
|
32
|
+
if (response.error) {
|
|
33
|
+
throw new Error(JSON.stringify(response.error));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
expect(response).toHaveProperty('user');
|
|
37
|
+
expect(response.user).toHaveProperty('id');
|
|
38
|
+
expect(typeof response.user.id).toBe('string');
|
|
39
|
+
expect(response.user.id.length).toBeGreaterThan(0);
|
|
40
|
+
}, ctx);
|
|
41
|
+
},
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('handleConvertAmount', () => {
|
|
46
|
+
it(
|
|
47
|
+
'should convert various dollar amounts to milliunits',
|
|
48
|
+
{ meta: { tier: 'domain', domain: 'utility' } },
|
|
49
|
+
async () => {
|
|
50
|
+
const testCases = [
|
|
51
|
+
{ dollars: 1.0, expectedMilliunits: 1000 },
|
|
52
|
+
{ dollars: 0.01, expectedMilliunits: 10 },
|
|
53
|
+
{ dollars: 10.5, expectedMilliunits: 10500 },
|
|
54
|
+
{ dollars: 999.99, expectedMilliunits: 999990 },
|
|
55
|
+
{ dollars: 0, expectedMilliunits: 0 },
|
|
56
|
+
{ dollars: -5.25, expectedMilliunits: -5250 },
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
for (const testCase of testCases) {
|
|
60
|
+
const result = await handleConvertAmount({
|
|
61
|
+
amount: testCase.dollars,
|
|
62
|
+
to_milliunits: true,
|
|
63
|
+
});
|
|
64
|
+
const response = JSON.parse(result.content[0].text);
|
|
65
|
+
|
|
66
|
+
expect(response.conversion.converted_amount).toBe(testCase.expectedMilliunits);
|
|
67
|
+
expect(response.conversion.to_milliunits).toBe(true);
|
|
68
|
+
expect(response.conversion.description).toContain(`$${testCase.dollars.toFixed(2)}`);
|
|
69
|
+
expect(response.conversion.description).toContain(
|
|
70
|
+
`${testCase.expectedMilliunits} milliunits`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
it(
|
|
77
|
+
'should convert various milliunit amounts to dollars',
|
|
78
|
+
{ meta: { tier: 'domain', domain: 'utility' } },
|
|
79
|
+
async () => {
|
|
80
|
+
const testCases = [
|
|
81
|
+
{ milliunits: 1000, expectedDollars: 1.0 },
|
|
82
|
+
{ milliunits: 10, expectedDollars: 0.01 },
|
|
83
|
+
{ milliunits: 10500, expectedDollars: 10.5 },
|
|
84
|
+
{ milliunits: 999990, expectedDollars: 999.99 },
|
|
85
|
+
{ milliunits: 0, expectedDollars: 0 },
|
|
86
|
+
{ milliunits: -5250, expectedDollars: -5.25 },
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const testCase of testCases) {
|
|
90
|
+
const result = await handleConvertAmount({
|
|
91
|
+
amount: testCase.milliunits,
|
|
92
|
+
to_milliunits: false,
|
|
93
|
+
});
|
|
94
|
+
const response = JSON.parse(result.content[0].text);
|
|
95
|
+
|
|
96
|
+
expect(response.conversion.converted_amount).toBe(testCase.expectedDollars);
|
|
97
|
+
expect(response.conversion.to_milliunits).toBe(false);
|
|
98
|
+
expect(response.conversion.description).toContain(`${testCase.milliunits} milliunits`);
|
|
99
|
+
expect(response.conversion.description).toContain(
|
|
100
|
+
`$${testCase.expectedDollars.toFixed(2)}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
it(
|
|
107
|
+
'should handle precision edge cases',
|
|
108
|
+
{ meta: { tier: 'domain', domain: 'utility' } },
|
|
109
|
+
async () => {
|
|
110
|
+
// Test floating-point precision issues
|
|
111
|
+
const precisionTests = [
|
|
112
|
+
{ amount: 0.1 + 0.2, to_milliunits: true }, // Should handle 0.30000000000000004
|
|
113
|
+
{ amount: 1.005, to_milliunits: true }, // Should round correctly
|
|
114
|
+
{ amount: 999.999, to_milliunits: true }, // Should handle near-integer values
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
for (const test of precisionTests) {
|
|
118
|
+
const result = await handleConvertAmount(test);
|
|
119
|
+
const response = JSON.parse(result.content[0].text);
|
|
120
|
+
|
|
121
|
+
expect(response.conversion).toHaveProperty('converted_amount');
|
|
122
|
+
expect(typeof response.conversion.converted_amount).toBe('number');
|
|
123
|
+
expect(Number.isInteger(response.conversion.converted_amount)).toBe(true);
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as ynab from 'ynab';
|
|
3
|
+
import { handleGetUser, handleConvertAmount, ConvertAmountSchema } from '../utilityTools.js';
|
|
4
|
+
|
|
5
|
+
// Mock the YNAB API
|
|
6
|
+
const mockYnabAPI = {
|
|
7
|
+
user: {
|
|
8
|
+
getUser: vi.fn(),
|
|
9
|
+
},
|
|
10
|
+
} as unknown as ynab.API;
|
|
11
|
+
|
|
12
|
+
describe('Utility Tools', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
vi.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('handleGetUser', () => {
|
|
18
|
+
it('should return user information successfully', async () => {
|
|
19
|
+
const mockUser = {
|
|
20
|
+
id: 'user-123',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const mockResponse = {
|
|
24
|
+
data: {
|
|
25
|
+
user: mockUser,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
(mockYnabAPI.user.getUser as any).mockResolvedValue(mockResponse);
|
|
30
|
+
|
|
31
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
32
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
33
|
+
expect(parsed.user).toBeDefined();
|
|
34
|
+
expect(parsed.user.id).toBe('user-123');
|
|
35
|
+
expect(mockYnabAPI.user.getUser).toHaveBeenCalledTimes(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle 401 authentication errors', async () => {
|
|
39
|
+
const error = new Error('401 Unauthorized');
|
|
40
|
+
(mockYnabAPI.user.getUser as any).mockRejectedValue(error);
|
|
41
|
+
|
|
42
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
43
|
+
|
|
44
|
+
expect(result.content[0].text).toContain('Invalid or expired YNAB access token');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should handle 403 authorization errors', async () => {
|
|
48
|
+
const error = new Error('403 Forbidden');
|
|
49
|
+
(mockYnabAPI.user.getUser as any).mockRejectedValue(error);
|
|
50
|
+
|
|
51
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
52
|
+
|
|
53
|
+
expect(result.content[0].text).toContain('Insufficient permissions to access YNAB data');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should handle 429 rate limiting errors', async () => {
|
|
57
|
+
const error = new Error('429 Too Many Requests');
|
|
58
|
+
(mockYnabAPI.user.getUser as any).mockRejectedValue(error);
|
|
59
|
+
|
|
60
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
61
|
+
|
|
62
|
+
expect(result.content[0].text).toContain('Rate limit exceeded. Please try again later');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should handle 500 server errors', async () => {
|
|
66
|
+
const error = new Error('500 Internal Server Error');
|
|
67
|
+
(mockYnabAPI.user.getUser as any).mockRejectedValue(error);
|
|
68
|
+
|
|
69
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
70
|
+
|
|
71
|
+
expect(result.content[0].text).toContain('YNAB service is currently unavailable');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should handle generic errors', async () => {
|
|
75
|
+
const error = new Error('Network error');
|
|
76
|
+
(mockYnabAPI.user.getUser as any).mockRejectedValue(error);
|
|
77
|
+
|
|
78
|
+
const result = await handleGetUser(mockYnabAPI);
|
|
79
|
+
|
|
80
|
+
expect(result.content[0].text).toContain('Failed to get user information');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('handleConvertAmount', () => {
|
|
85
|
+
it('should convert dollars to milliunits correctly', async () => {
|
|
86
|
+
const params = { amount: 10.5, to_milliunits: true };
|
|
87
|
+
|
|
88
|
+
const result = await handleConvertAmount(params);
|
|
89
|
+
const response = JSON.parse(result.content[0].text);
|
|
90
|
+
|
|
91
|
+
expect(response.conversion.original_amount).toBe(10.5);
|
|
92
|
+
expect(response.conversion.converted_amount).toBe(10500);
|
|
93
|
+
expect(response.conversion.to_milliunits).toBe(true);
|
|
94
|
+
expect(response.conversion.description).toBe('$10.50 = 10500 milliunits');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should convert milliunits to dollars correctly', async () => {
|
|
98
|
+
const params = { amount: 10500, to_milliunits: false };
|
|
99
|
+
|
|
100
|
+
const result = await handleConvertAmount(params);
|
|
101
|
+
const response = JSON.parse(result.content[0].text);
|
|
102
|
+
|
|
103
|
+
expect(response.conversion.original_amount).toBe(10500);
|
|
104
|
+
expect(response.conversion.converted_amount).toBe(10.5);
|
|
105
|
+
expect(response.conversion.to_milliunits).toBe(false);
|
|
106
|
+
expect(response.conversion.description).toBe('10500 milliunits = $10.50');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle zero amounts', async () => {
|
|
110
|
+
const params = { amount: 0, to_milliunits: true };
|
|
111
|
+
|
|
112
|
+
const result = await handleConvertAmount(params);
|
|
113
|
+
const response = JSON.parse(result.content[0].text);
|
|
114
|
+
|
|
115
|
+
expect(response.conversion.original_amount).toBe(0);
|
|
116
|
+
expect(response.conversion.converted_amount).toBe(0);
|
|
117
|
+
expect(response.conversion.description).toBe('$0.00 = 0 milliunits');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should handle negative amounts', async () => {
|
|
121
|
+
const params = { amount: -5.25, to_milliunits: true };
|
|
122
|
+
|
|
123
|
+
const result = await handleConvertAmount(params);
|
|
124
|
+
const response = JSON.parse(result.content[0].text);
|
|
125
|
+
|
|
126
|
+
expect(response.conversion.original_amount).toBe(-5.25);
|
|
127
|
+
expect(response.conversion.converted_amount).toBe(-5250);
|
|
128
|
+
expect(response.conversion.description).toBe('$-5.25 = -5250 milliunits');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should handle floating-point precision correctly', async () => {
|
|
132
|
+
const params = { amount: 0.01, to_milliunits: true };
|
|
133
|
+
|
|
134
|
+
const result = await handleConvertAmount(params);
|
|
135
|
+
const response = JSON.parse(result.content[0].text);
|
|
136
|
+
|
|
137
|
+
expect(response.conversion.converted_amount).toBe(10);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should handle large amounts', async () => {
|
|
141
|
+
const params = { amount: 999999.99, to_milliunits: true };
|
|
142
|
+
|
|
143
|
+
const result = await handleConvertAmount(params);
|
|
144
|
+
const response = JSON.parse(result.content[0].text);
|
|
145
|
+
|
|
146
|
+
expect(response.conversion.converted_amount).toBe(999999990);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should round to nearest milliunit when converting from dollars', async () => {
|
|
150
|
+
const params = { amount: 10.5555, to_milliunits: true };
|
|
151
|
+
|
|
152
|
+
const result = await handleConvertAmount(params);
|
|
153
|
+
const response = JSON.parse(result.content[0].text);
|
|
154
|
+
|
|
155
|
+
expect(response.conversion.converted_amount).toBe(10556); // Rounded from 10555.5
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe('ConvertAmountSchema validation', () => {
|
|
160
|
+
it('should validate correct parameters', () => {
|
|
161
|
+
const validParams = { amount: 10.5, to_milliunits: true };
|
|
162
|
+
const result = ConvertAmountSchema.safeParse(validParams);
|
|
163
|
+
|
|
164
|
+
expect(result.success).toBe(true);
|
|
165
|
+
if (result.success) {
|
|
166
|
+
expect(result.data).toEqual(validParams);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should reject non-finite numbers', () => {
|
|
171
|
+
const invalidParams = { amount: Infinity, to_milliunits: true };
|
|
172
|
+
const result = ConvertAmountSchema.safeParse(invalidParams);
|
|
173
|
+
|
|
174
|
+
expect(result.success).toBe(false);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should reject NaN values', () => {
|
|
178
|
+
const invalidParams = { amount: NaN, to_milliunits: true };
|
|
179
|
+
const result = ConvertAmountSchema.safeParse(invalidParams);
|
|
180
|
+
|
|
181
|
+
expect(result.success).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('should reject missing amount parameter', () => {
|
|
185
|
+
const invalidParams = { to_milliunits: true };
|
|
186
|
+
const result = ConvertAmountSchema.safeParse(invalidParams);
|
|
187
|
+
|
|
188
|
+
expect(result.success).toBe(false);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should reject missing to_milliunits parameter', () => {
|
|
192
|
+
const invalidParams = { amount: 10.5 };
|
|
193
|
+
const result = ConvertAmountSchema.safeParse(invalidParams);
|
|
194
|
+
|
|
195
|
+
expect(result.success).toBe(false);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('should reject non-boolean to_milliunits parameter', () => {
|
|
199
|
+
const invalidParams = { amount: 10.5, to_milliunits: 'true' };
|
|
200
|
+
const result = ConvertAmountSchema.safeParse(invalidParams);
|
|
201
|
+
|
|
202
|
+
expect(result.success).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,283 @@
|
|
|
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
|
+
import { milliunitsToAmount } from '../utils/amountUtils.js';
|
|
7
|
+
import { cacheManager, CACHE_TTLS, CacheManager } from '../server/cacheManager.js';
|
|
8
|
+
import type { DeltaFetcher } from './deltaFetcher.js';
|
|
9
|
+
import type { DeltaCache } from '../server/deltaCache.js';
|
|
10
|
+
import type { ServerKnowledgeStore } from '../server/serverKnowledgeStore.js';
|
|
11
|
+
import { resolveDeltaFetcherArgs, resolveDeltaWriteArgs } from './deltaSupport.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Schema for ynab:list_accounts tool parameters
|
|
15
|
+
*/
|
|
16
|
+
export const ListAccountsSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
budget_id: z.string().min(1, 'Budget ID is required'),
|
|
19
|
+
limit: z.number().int().positive().optional(),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
|
|
23
|
+
export type ListAccountsParams = z.infer<typeof ListAccountsSchema>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Schema for ynab:get_account tool parameters
|
|
27
|
+
*/
|
|
28
|
+
export const GetAccountSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
budget_id: z.string().min(1, 'Budget ID is required'),
|
|
31
|
+
account_id: z.string().min(1, 'Account ID is required'),
|
|
32
|
+
})
|
|
33
|
+
.strict();
|
|
34
|
+
|
|
35
|
+
export type GetAccountParams = z.infer<typeof GetAccountSchema>;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Schema for ynab:create_account tool parameters
|
|
39
|
+
*/
|
|
40
|
+
export const CreateAccountSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
budget_id: z.string().min(1, 'Budget ID is required'),
|
|
43
|
+
name: z.string().min(1, 'Account name is required'),
|
|
44
|
+
type: z.enum([
|
|
45
|
+
'checking',
|
|
46
|
+
'savings',
|
|
47
|
+
'creditCard',
|
|
48
|
+
'cash',
|
|
49
|
+
'lineOfCredit',
|
|
50
|
+
'otherAsset',
|
|
51
|
+
'otherLiability',
|
|
52
|
+
]),
|
|
53
|
+
balance: z.number().optional(),
|
|
54
|
+
dry_run: z.boolean().optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict();
|
|
57
|
+
|
|
58
|
+
export type CreateAccountParams = z.infer<typeof CreateAccountSchema>;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Handles the ynab:list_accounts tool call
|
|
62
|
+
* Lists all accounts for a specific budget
|
|
63
|
+
*/
|
|
64
|
+
export async function handleListAccounts(
|
|
65
|
+
ynabAPI: ynab.API,
|
|
66
|
+
deltaFetcher: DeltaFetcher,
|
|
67
|
+
params: ListAccountsParams,
|
|
68
|
+
): Promise<CallToolResult>;
|
|
69
|
+
export async function handleListAccounts(
|
|
70
|
+
ynabAPI: ynab.API,
|
|
71
|
+
params: ListAccountsParams,
|
|
72
|
+
): Promise<CallToolResult>;
|
|
73
|
+
export async function handleListAccounts(
|
|
74
|
+
ynabAPI: ynab.API,
|
|
75
|
+
deltaFetcherOrParams: DeltaFetcher | ListAccountsParams,
|
|
76
|
+
maybeParams?: ListAccountsParams,
|
|
77
|
+
): Promise<CallToolResult> {
|
|
78
|
+
const { deltaFetcher, params } = resolveDeltaFetcherArgs(
|
|
79
|
+
ynabAPI,
|
|
80
|
+
deltaFetcherOrParams,
|
|
81
|
+
maybeParams,
|
|
82
|
+
);
|
|
83
|
+
return await withToolErrorHandling(
|
|
84
|
+
async () => {
|
|
85
|
+
const result = await deltaFetcher.fetchAccounts(params.budget_id);
|
|
86
|
+
let accounts = result.data;
|
|
87
|
+
const wasCached = result.wasCached;
|
|
88
|
+
|
|
89
|
+
// Apply limit if specified
|
|
90
|
+
const totalCount = accounts.length;
|
|
91
|
+
if (params.limit !== undefined) {
|
|
92
|
+
accounts = accounts.slice(0, params.limit);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: responseFormatter.format({
|
|
100
|
+
accounts: accounts.map((account) => ({
|
|
101
|
+
id: account.id,
|
|
102
|
+
name: account.name,
|
|
103
|
+
type: account.type,
|
|
104
|
+
on_budget: account.on_budget,
|
|
105
|
+
closed: account.closed,
|
|
106
|
+
note: account.note,
|
|
107
|
+
balance: milliunitsToAmount(account.balance),
|
|
108
|
+
cleared_balance: milliunitsToAmount(account.cleared_balance),
|
|
109
|
+
uncleared_balance: milliunitsToAmount(account.uncleared_balance),
|
|
110
|
+
transfer_payee_id: account.transfer_payee_id,
|
|
111
|
+
direct_import_linked: account.direct_import_linked,
|
|
112
|
+
direct_import_in_error: account.direct_import_in_error,
|
|
113
|
+
})),
|
|
114
|
+
total_count: totalCount,
|
|
115
|
+
returned_count: accounts.length,
|
|
116
|
+
cached: wasCached,
|
|
117
|
+
cache_info: wasCached
|
|
118
|
+
? `Data retrieved from cache for improved performance${result.usedDelta ? ' (delta merge applied)' : ''}`
|
|
119
|
+
: 'Fresh data retrieved from YNAB API',
|
|
120
|
+
}),
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
'ynab:list_accounts',
|
|
126
|
+
'listing accounts',
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Handles the ynab:get_account tool call
|
|
132
|
+
* Gets detailed information for a specific account
|
|
133
|
+
*/
|
|
134
|
+
export async function handleGetAccount(
|
|
135
|
+
ynabAPI: ynab.API,
|
|
136
|
+
params: GetAccountParams,
|
|
137
|
+
): Promise<CallToolResult> {
|
|
138
|
+
return await withToolErrorHandling(
|
|
139
|
+
async () => {
|
|
140
|
+
// Use enhanced CacheManager wrap method
|
|
141
|
+
const cacheKey = CacheManager.generateKey(
|
|
142
|
+
'account',
|
|
143
|
+
'get',
|
|
144
|
+
params.budget_id,
|
|
145
|
+
params.account_id,
|
|
146
|
+
);
|
|
147
|
+
const wasCached = cacheManager.has(cacheKey);
|
|
148
|
+
const account = await cacheManager.wrap<ynab.Account>(cacheKey, {
|
|
149
|
+
ttl: CACHE_TTLS.ACCOUNTS,
|
|
150
|
+
loader: async () => {
|
|
151
|
+
const response = await ynabAPI.accounts.getAccountById(
|
|
152
|
+
params.budget_id,
|
|
153
|
+
params.account_id,
|
|
154
|
+
);
|
|
155
|
+
return response.data.account;
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: responseFormatter.format({
|
|
164
|
+
account: {
|
|
165
|
+
id: account.id,
|
|
166
|
+
name: account.name,
|
|
167
|
+
type: account.type,
|
|
168
|
+
on_budget: account.on_budget,
|
|
169
|
+
closed: account.closed,
|
|
170
|
+
note: account.note,
|
|
171
|
+
balance: milliunitsToAmount(account.balance),
|
|
172
|
+
cleared_balance: milliunitsToAmount(account.cleared_balance),
|
|
173
|
+
uncleared_balance: milliunitsToAmount(account.uncleared_balance),
|
|
174
|
+
transfer_payee_id: account.transfer_payee_id,
|
|
175
|
+
direct_import_linked: account.direct_import_linked,
|
|
176
|
+
direct_import_in_error: account.direct_import_in_error,
|
|
177
|
+
},
|
|
178
|
+
cached: wasCached,
|
|
179
|
+
cache_info: wasCached
|
|
180
|
+
? 'Data retrieved from cache for improved performance'
|
|
181
|
+
: 'Fresh data retrieved from YNAB API',
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
'ynab:get_account',
|
|
188
|
+
'getting account details',
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handles the ynab:create_account tool call
|
|
194
|
+
* Creates a new account in the specified budget
|
|
195
|
+
*/
|
|
196
|
+
export async function handleCreateAccount(
|
|
197
|
+
ynabAPI: ynab.API,
|
|
198
|
+
deltaCache: DeltaCache,
|
|
199
|
+
knowledgeStore: ServerKnowledgeStore,
|
|
200
|
+
params: CreateAccountParams,
|
|
201
|
+
): Promise<CallToolResult>;
|
|
202
|
+
export async function handleCreateAccount(
|
|
203
|
+
ynabAPI: ynab.API,
|
|
204
|
+
params: CreateAccountParams,
|
|
205
|
+
): Promise<CallToolResult>;
|
|
206
|
+
export async function handleCreateAccount(
|
|
207
|
+
ynabAPI: ynab.API,
|
|
208
|
+
deltaCacheOrParams: DeltaCache | CreateAccountParams,
|
|
209
|
+
knowledgeStoreOrParams?: ServerKnowledgeStore | CreateAccountParams,
|
|
210
|
+
maybeParams?: CreateAccountParams,
|
|
211
|
+
): Promise<CallToolResult> {
|
|
212
|
+
const { deltaCache, params } = resolveDeltaWriteArgs(
|
|
213
|
+
deltaCacheOrParams,
|
|
214
|
+
knowledgeStoreOrParams,
|
|
215
|
+
maybeParams,
|
|
216
|
+
);
|
|
217
|
+
return await withToolErrorHandling(
|
|
218
|
+
async () => {
|
|
219
|
+
if (params.dry_run) {
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: 'text',
|
|
224
|
+
text: responseFormatter.format({
|
|
225
|
+
dry_run: true,
|
|
226
|
+
action: 'create_account',
|
|
227
|
+
request: {
|
|
228
|
+
budget_id: params.budget_id,
|
|
229
|
+
name: params.name,
|
|
230
|
+
type: params.type,
|
|
231
|
+
balance: params.balance ?? 0,
|
|
232
|
+
},
|
|
233
|
+
}),
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
const accountData: ynab.SaveAccount = {
|
|
239
|
+
name: params.name,
|
|
240
|
+
type: params.type as ynab.Account['type'],
|
|
241
|
+
balance: params.balance ? params.balance * 1000 : 0, // Convert to milliunits
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const response = await ynabAPI.accounts.createAccount(params.budget_id, {
|
|
245
|
+
account: accountData,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
const account = response.data.account;
|
|
249
|
+
|
|
250
|
+
// Invalidate accounts list cache after successful account creation
|
|
251
|
+
const accountsListCacheKey = CacheManager.generateKey('accounts', 'list', params.budget_id);
|
|
252
|
+
cacheManager.delete(accountsListCacheKey);
|
|
253
|
+
|
|
254
|
+
deltaCache.invalidate(params.budget_id, 'accounts');
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
content: [
|
|
258
|
+
{
|
|
259
|
+
type: 'text',
|
|
260
|
+
text: responseFormatter.format({
|
|
261
|
+
account: {
|
|
262
|
+
id: account.id,
|
|
263
|
+
name: account.name,
|
|
264
|
+
type: account.type,
|
|
265
|
+
on_budget: account.on_budget,
|
|
266
|
+
closed: account.closed,
|
|
267
|
+
note: account.note,
|
|
268
|
+
balance: milliunitsToAmount(account.balance),
|
|
269
|
+
cleared_balance: milliunitsToAmount(account.cleared_balance),
|
|
270
|
+
uncleared_balance: milliunitsToAmount(account.uncleared_balance),
|
|
271
|
+
transfer_payee_id: account.transfer_payee_id,
|
|
272
|
+
direct_import_linked: account.direct_import_linked,
|
|
273
|
+
direct_import_in_error: account.direct_import_in_error,
|
|
274
|
+
},
|
|
275
|
+
}),
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
'ynab:create_account',
|
|
281
|
+
'creating account',
|
|
282
|
+
);
|
|
283
|
+
}
|