@dizzlkheinz/ynab-mcpb 0.17.0 → 0.18.0
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/.env.example +33 -33
- package/.github/workflows/ci-tests.yml +45 -45
- package/.github/workflows/claude-code-review.yml +57 -57
- package/.github/workflows/claude.yml +50 -50
- package/.github/workflows/full-integration.yml +22 -22
- package/.github/workflows/publish.yml +12 -3
- package/.github/workflows/release.yml +2 -2
- package/CHANGELOG.md +10 -1
- package/CLAUDE.md +16 -12
- package/README.md +6 -1
- package/dist/bundle/index.cjs +49 -49
- package/dist/server/YNABMCPServer.d.ts +125 -54
- package/dist/server/YNABMCPServer.js +42 -11
- package/dist/server/cacheManager.js +6 -5
- package/dist/server/completions.d.ts +25 -0
- package/dist/server/completions.js +160 -0
- package/dist/server/config.d.ts +2 -2
- package/dist/server/errorHandler.js +1 -0
- package/dist/server/rateLimiter.js +3 -1
- package/dist/server/resources.d.ts +1 -0
- package/dist/server/resources.js +33 -16
- package/dist/server/securityMiddleware.d.ts +38 -8
- package/dist/server/securityMiddleware.js +1 -0
- package/dist/server/toolRegistry.d.ts +9 -0
- package/dist/server/toolRegistry.js +11 -0
- package/dist/tools/adapters.d.ts +3 -1
- package/dist/tools/adapters.js +1 -0
- package/dist/tools/reconciliation/executor.d.ts +2 -0
- package/dist/tools/reconciliation/executor.js +26 -1
- package/dist/tools/reconciliation/index.d.ts +3 -2
- package/dist/tools/reconciliation/index.js +4 -3
- package/dist/tools/schemas/outputs/index.d.ts +2 -2
- package/dist/tools/schemas/outputs/index.js +2 -2
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +0 -15
- package/dist/tools/schemas/outputs/utilityOutputs.js +0 -9
- package/dist/tools/utilityTools.d.ts +0 -7
- package/dist/tools/utilityTools.js +1 -50
- package/docs/maintainers/npm-publishing.md +27 -0
- package/docs/reference/API.md +83 -97
- package/docs/technical/reconciliation-system-architecture.md +2251 -2251
- package/package.json +6 -6
- package/scripts/analyze-bundle.mjs +41 -41
- package/scripts/generate-mcpb.ps1 +95 -95
- package/scripts/watch-and-restart.ps1 +49 -49
- package/src/__tests__/comprehensive.integration.test.ts +4 -32
- package/src/__tests__/performance.test.ts +5 -14
- package/src/__tests__/setup.ts +45 -14
- package/src/__tests__/smoke.e2e.test.ts +70 -0
- package/src/__tests__/testUtils.ts +2 -113
- package/src/server/YNABMCPServer.ts +64 -10
- package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
- package/src/server/__tests__/completions.integration.test.ts +117 -0
- package/src/server/__tests__/completions.test.ts +319 -0
- package/src/server/__tests__/resources.template.test.ts +3 -3
- package/src/server/__tests__/resources.test.ts +3 -3
- package/src/server/__tests__/toolRegistration.test.ts +3 -3
- package/src/server/cacheManager.ts +7 -6
- package/src/server/completions.ts +279 -0
- package/src/server/errorHandler.ts +1 -0
- package/src/server/rateLimiter.ts +4 -1
- package/src/server/resources.ts +49 -13
- package/src/server/securityMiddleware.ts +1 -0
- package/src/server/toolRegistry.ts +42 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
- package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
- package/src/tools/__tests__/utilityTools.test.ts +1 -123
- package/src/tools/adapters.ts +22 -1
- package/src/tools/reconciliation/__tests__/executor.progress.test.ts +462 -0
- package/src/tools/reconciliation/executor.ts +55 -1
- package/src/tools/reconciliation/index.ts +7 -3
- package/src/tools/schemas/outputs/index.ts +0 -3
- package/src/tools/schemas/outputs/utilityOutputs.ts +2 -43
- package/src/tools/toolCategories.ts +0 -1
- package/src/tools/utilityTools.ts +5 -76
- package/vitest.config.ts +4 -1
- package/.chunkhound.json +0 -11
- package/.code/agents/0098661e-0fa3-4990-beb9-c0cbf3f123aa/status.txt +0 -1
- package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
- package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
- package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
- package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
- package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
- package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
- package/.code/agents/1324/exec-call_tIpx9uV1TpARbAMZonRQm8AO.txt +0 -757
- package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
- package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
- package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
- package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
- package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
- package/.code/agents/1572/exec-call_GjVFBFOWcY7lE0idc5nWlLNh.txt +0 -781
- package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +0 -1
- package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +0 -5217
- package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +0 -2594
- package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +0 -2594
- package/.code/agents/1846/exec-call_1YNAVD18RjrMN7JnfkkQhUP3.txt +0 -766
- package/.code/agents/1846/exec-call_lh3lDzE4WJAh1lFiomiiZ73D.txt +0 -766
- package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
- package/.code/agents/2038/exec-call_DYwOukaYsL8VCONWmV2rUW5u.txt +0 -766
- package/.code/agents/2038/exec-call_c7fOQ7UrpVcTtvdfGBRM146V.txt +0 -652
- package/.code/agents/2038/exec-call_ySNyq9Mm55jWE480s54r5QcA.txt +0 -766
- package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
- package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
- package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
- package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
- package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
- package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
- package/.code/agents/2256/exec-call_AtPcRWPmFPMcmX6qOFm1fCEY.txt +0 -766
- package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
- package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
- package/.code/agents/2454/exec-call_aFJpupwjfZeOBm7ixI5Vc8z2.txt +0 -766
- package/.code/agents/2454/exec-call_wogZ4HfXTodTEXvdgXlVUBpv.txt +0 -766
- package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
- package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
- package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
- package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
- package/.code/agents/2e905864-aa07-4314-bcf9-c5b32277e4ac/result.txt +0 -36
- package/.code/agents/3073/exec-call_Peeagc9DxGYLgE6pNdMZhqIE.txt +0 -766
- package/.code/agents/3073/exec-call_d2YSE3hXF08KRSoUM3qd8Z3x.txt +0 -766
- package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
- package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
- package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
- package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
- package/.code/agents/335aa031-466d-4fb7-925f-3cd864e264d0/result.txt +0 -191
- package/.code/agents/3364/exec-call_NbhIrsM5HhyDZDmJZG5CuCYL.txt +0 -766
- package/.code/agents/3364/exec-call_cKtJg0NrXiwXEFwlsE3uPZRA.txt +0 -766
- package/.code/agents/36d98414-5cde-4d9d-9a67-a240a18c1f07/result.txt +0 -189
- package/.code/agents/4604e866-b7b8-44f5-992f-2f683b0a523b/status.txt +0 -1
- package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
- package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
- package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
- package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
- package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
- package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
- package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
- package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
- package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
- package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
- package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
- package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
- package/.code/agents/5f8dc01c-47b3-4163-b0b3-aa31be89fcdc/status.txt +0 -1
- package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
- package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
- package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
- package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
- package/.code/agents/7/exec-call_HltHpkDox0Zm1vGEjdksUgpE.txt +0 -1120
- package/.code/agents/7/exec-call_LCATrOPPAgbxW9Q1z0XaVi2E.txt +0 -2646
- package/.code/agents/7/exec-call_W8DeRfNG9hvbgVFvf0clBf6R.txt +0 -2646
- package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
- package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
- package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
- package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
- package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
- package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
- package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
- package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
- package/.code/agents/94a0ddf3-a304-4ec3-913e-3cceef509948/error.txt +0 -1
- package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
- package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
- package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
- package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
- package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
- package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
- package/.code/agents/e2c752b7-711d-423a-af57-f53c809deb84/result.txt +0 -160
- package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
- package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
- package/.code/agents/e6601719-c31f-4a0e-8c71-d70787d0ab71/status.txt +0 -1
- package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
- package/.code/agents/f250b7ed-5bd5-4036-aa8c-ce63caee7d61/result.txt +0 -20
- package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
- package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
- package/AGENTS.md +0 -1
- package/NUL +0 -0
- package/package.json.tmp +0 -105
- package/src/__tests__/delta.performance.test.ts +0 -80
- package/src/__tests__/workflows.e2e.test.ts +0 -1702
- package/temp-recon.ts +0 -126
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
- package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type * as ynab from 'ynab';
|
|
3
|
+
import type { ProgressCallback } from '../../../server/toolRegistry.js';
|
|
4
|
+
import type { ReconciliationAnalysis, TransactionMatch, BankTransaction } from '../types.js';
|
|
5
|
+
import type { ReconcileAccountRequest } from '../index.js';
|
|
6
|
+
import { executeReconciliation, type ExecutionOptions } from '../executor.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Unit tests for progress notification functionality in reconciliation executor
|
|
10
|
+
*/
|
|
11
|
+
describe('Reconciliation Progress Notifications', () => {
|
|
12
|
+
let mockYnabAPI: Partial<ynab.API>;
|
|
13
|
+
let mockProgressCallback: ProgressCallback;
|
|
14
|
+
let progressCalls: { progress: number; total?: number; message?: string }[];
|
|
15
|
+
|
|
16
|
+
const createMockTransaction = (
|
|
17
|
+
overrides: Partial<ynab.TransactionDetail> = {},
|
|
18
|
+
): ynab.TransactionDetail => ({
|
|
19
|
+
id: 'txn-1',
|
|
20
|
+
date: '2024-01-15',
|
|
21
|
+
amount: -25000,
|
|
22
|
+
memo: 'Test transaction',
|
|
23
|
+
cleared: 'uncleared' as ynab.TransactionClearedStatus,
|
|
24
|
+
approved: true,
|
|
25
|
+
flag_color: null,
|
|
26
|
+
flag_name: null,
|
|
27
|
+
account_id: 'acc-1',
|
|
28
|
+
account_name: 'Test Account',
|
|
29
|
+
payee_id: 'payee-1',
|
|
30
|
+
payee_name: 'Test Payee',
|
|
31
|
+
category_id: 'cat-1',
|
|
32
|
+
category_name: 'Test Category',
|
|
33
|
+
transfer_account_id: null,
|
|
34
|
+
transfer_transaction_id: null,
|
|
35
|
+
matched_transaction_id: null,
|
|
36
|
+
import_id: null,
|
|
37
|
+
import_payee_name: null,
|
|
38
|
+
import_payee_name_original: null,
|
|
39
|
+
debt_transaction_type: null,
|
|
40
|
+
deleted: false,
|
|
41
|
+
subtransactions: [],
|
|
42
|
+
...overrides,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const createBankTransaction = (overrides: Partial<BankTransaction> = {}): BankTransaction => ({
|
|
46
|
+
date: '2024-01-15',
|
|
47
|
+
amount: -25000,
|
|
48
|
+
payee: 'Test Payee',
|
|
49
|
+
memo: 'Bank memo',
|
|
50
|
+
...overrides,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const createMatch = (
|
|
54
|
+
bankTxn: BankTransaction,
|
|
55
|
+
ynabTxn: ynab.TransactionDetail,
|
|
56
|
+
score = 0.95,
|
|
57
|
+
): TransactionMatch => ({
|
|
58
|
+
bankTransaction: bankTxn,
|
|
59
|
+
ynabTransaction: ynabTxn,
|
|
60
|
+
score,
|
|
61
|
+
matchType: 'exact',
|
|
62
|
+
matchDetails: {
|
|
63
|
+
amount_match: true,
|
|
64
|
+
date_match: true,
|
|
65
|
+
payee_similarity: 1.0,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
progressCalls = [];
|
|
71
|
+
mockProgressCallback = vi.fn().mockImplementation(async (params) => {
|
|
72
|
+
progressCalls.push(params);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
mockYnabAPI = {
|
|
76
|
+
transactions: {
|
|
77
|
+
createTransaction: vi.fn().mockResolvedValue({
|
|
78
|
+
data: { transaction: createMockTransaction() },
|
|
79
|
+
}),
|
|
80
|
+
createTransactions: vi.fn().mockResolvedValue({
|
|
81
|
+
data: {
|
|
82
|
+
transactions: [createMockTransaction()],
|
|
83
|
+
duplicate_import_ids: [],
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
updateTransactions: vi.fn().mockResolvedValue({
|
|
87
|
+
data: { transactions: [createMockTransaction({ cleared: 'cleared' })] },
|
|
88
|
+
}),
|
|
89
|
+
} as unknown as ynab.TransactionsApi,
|
|
90
|
+
accounts: {
|
|
91
|
+
getAccountById: vi.fn().mockResolvedValue({
|
|
92
|
+
data: {
|
|
93
|
+
account: {
|
|
94
|
+
balance: 100000,
|
|
95
|
+
cleared_balance: 100000,
|
|
96
|
+
uncleared_balance: 0,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
} as unknown as ynab.AccountsApi,
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('progress callback invocation', () => {
|
|
105
|
+
it('should call progress callback during bulk transaction creation', async () => {
|
|
106
|
+
const unmatchedBank: BankTransaction[] = [
|
|
107
|
+
createBankTransaction({ payee: 'Payee 1', amount: -10000 }),
|
|
108
|
+
createBankTransaction({ payee: 'Payee 2', amount: -20000 }),
|
|
109
|
+
createBankTransaction({ payee: 'Payee 3', amount: -30000 }),
|
|
110
|
+
];
|
|
111
|
+
|
|
112
|
+
const analysis: ReconciliationAnalysis = {
|
|
113
|
+
summary: {
|
|
114
|
+
bank_transactions_count: 3,
|
|
115
|
+
ynab_transactions_count: 0,
|
|
116
|
+
matches: 0,
|
|
117
|
+
unmatched_bank: 3,
|
|
118
|
+
unmatched_ynab: 0,
|
|
119
|
+
match_rate: 0,
|
|
120
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
121
|
+
},
|
|
122
|
+
auto_matches: [],
|
|
123
|
+
unmatched_bank: unmatchedBank,
|
|
124
|
+
unmatched_ynab: [],
|
|
125
|
+
balance_info: {
|
|
126
|
+
current_cleared: 100000,
|
|
127
|
+
target_statement: 40000,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const params: ReconcileAccountRequest = {
|
|
132
|
+
account_id: 'acc-1',
|
|
133
|
+
csv_data: 'date,amount,payee\n2024-01-15,-10,Payee 1',
|
|
134
|
+
dry_run: false,
|
|
135
|
+
auto_create_transactions: true,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const options: ExecutionOptions = {
|
|
139
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
140
|
+
analysis,
|
|
141
|
+
params,
|
|
142
|
+
budgetId: 'budget-1',
|
|
143
|
+
accountId: 'acc-1',
|
|
144
|
+
initialAccount: { balance: 100000, cleared_balance: 100000, uncleared_balance: 0 },
|
|
145
|
+
currencyCode: 'USD',
|
|
146
|
+
sendProgress: mockProgressCallback,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
await executeReconciliation(options);
|
|
150
|
+
|
|
151
|
+
expect(mockProgressCallback).toHaveBeenCalled();
|
|
152
|
+
expect(progressCalls.length).toBeGreaterThan(0);
|
|
153
|
+
|
|
154
|
+
// Verify progress structure
|
|
155
|
+
for (const call of progressCalls) {
|
|
156
|
+
expect(call).toHaveProperty('progress');
|
|
157
|
+
expect(call).toHaveProperty('total');
|
|
158
|
+
expect(call).toHaveProperty('message');
|
|
159
|
+
expect(typeof call.progress).toBe('number');
|
|
160
|
+
expect(typeof call.total).toBe('number');
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should report progress during sequential fallback', async () => {
|
|
165
|
+
const unmatchedBank: BankTransaction[] = [
|
|
166
|
+
createBankTransaction({ payee: 'Single Payee', amount: -10000 }),
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
const analysis: ReconciliationAnalysis = {
|
|
170
|
+
summary: {
|
|
171
|
+
bank_transactions_count: 1,
|
|
172
|
+
ynab_transactions_count: 0,
|
|
173
|
+
matches: 0,
|
|
174
|
+
unmatched_bank: 1,
|
|
175
|
+
unmatched_ynab: 0,
|
|
176
|
+
match_rate: 0,
|
|
177
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
178
|
+
},
|
|
179
|
+
auto_matches: [],
|
|
180
|
+
unmatched_bank: unmatchedBank,
|
|
181
|
+
unmatched_ynab: [],
|
|
182
|
+
balance_info: {
|
|
183
|
+
current_cleared: 100000,
|
|
184
|
+
target_statement: 90000,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const params: ReconcileAccountRequest = {
|
|
189
|
+
account_id: 'acc-1',
|
|
190
|
+
csv_data: 'date,amount,payee\n2024-01-15,-10,Single Payee',
|
|
191
|
+
dry_run: false,
|
|
192
|
+
auto_create_transactions: true,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const options: ExecutionOptions = {
|
|
196
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
197
|
+
analysis,
|
|
198
|
+
params,
|
|
199
|
+
budgetId: 'budget-1',
|
|
200
|
+
accountId: 'acc-1',
|
|
201
|
+
initialAccount: { balance: 100000, cleared_balance: 100000, uncleared_balance: 0 },
|
|
202
|
+
currencyCode: 'USD',
|
|
203
|
+
sendProgress: mockProgressCallback,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
await executeReconciliation(options);
|
|
207
|
+
|
|
208
|
+
// With only 1 transaction, it goes through sequential path
|
|
209
|
+
expect(mockProgressCallback).toHaveBeenCalled();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should not call progress callback when not provided', async () => {
|
|
213
|
+
const analysis: ReconciliationAnalysis = {
|
|
214
|
+
summary: {
|
|
215
|
+
bank_transactions_count: 1,
|
|
216
|
+
ynab_transactions_count: 0,
|
|
217
|
+
matches: 0,
|
|
218
|
+
unmatched_bank: 1,
|
|
219
|
+
unmatched_ynab: 0,
|
|
220
|
+
match_rate: 0,
|
|
221
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
222
|
+
},
|
|
223
|
+
auto_matches: [],
|
|
224
|
+
unmatched_bank: [createBankTransaction()],
|
|
225
|
+
unmatched_ynab: [],
|
|
226
|
+
balance_info: {
|
|
227
|
+
current_cleared: 100000,
|
|
228
|
+
target_statement: 75000,
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const params: ReconcileAccountRequest = {
|
|
233
|
+
account_id: 'acc-1',
|
|
234
|
+
csv_data: 'date,amount,payee\n2024-01-15,-25,Test',
|
|
235
|
+
dry_run: false,
|
|
236
|
+
auto_create_transactions: true,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const options: ExecutionOptions = {
|
|
240
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
241
|
+
analysis,
|
|
242
|
+
params,
|
|
243
|
+
budgetId: 'budget-1',
|
|
244
|
+
accountId: 'acc-1',
|
|
245
|
+
initialAccount: { balance: 100000, cleared_balance: 100000, uncleared_balance: 0 },
|
|
246
|
+
currencyCode: 'USD',
|
|
247
|
+
// No sendProgress callback
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Should not throw when no callback provided
|
|
251
|
+
await expect(executeReconciliation(options)).resolves.toBeDefined();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('should report progress during transaction updates', async () => {
|
|
255
|
+
const ynabTxn = createMockTransaction({ cleared: 'uncleared' });
|
|
256
|
+
const bankTxn = createBankTransaction();
|
|
257
|
+
const match = createMatch(bankTxn, ynabTxn);
|
|
258
|
+
|
|
259
|
+
const analysis: ReconciliationAnalysis = {
|
|
260
|
+
summary: {
|
|
261
|
+
bank_transactions_count: 1,
|
|
262
|
+
ynab_transactions_count: 1,
|
|
263
|
+
matches: 1,
|
|
264
|
+
unmatched_bank: 0,
|
|
265
|
+
unmatched_ynab: 0,
|
|
266
|
+
match_rate: 1,
|
|
267
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
268
|
+
},
|
|
269
|
+
auto_matches: [match],
|
|
270
|
+
unmatched_bank: [],
|
|
271
|
+
unmatched_ynab: [],
|
|
272
|
+
balance_info: {
|
|
273
|
+
current_cleared: 75000,
|
|
274
|
+
target_statement: 100000,
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const params: ReconcileAccountRequest = {
|
|
279
|
+
account_id: 'acc-1',
|
|
280
|
+
csv_data: 'date,amount,payee\n2024-01-15,-25,Test',
|
|
281
|
+
dry_run: false,
|
|
282
|
+
auto_update_cleared_status: true,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const options: ExecutionOptions = {
|
|
286
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
287
|
+
analysis,
|
|
288
|
+
params,
|
|
289
|
+
budgetId: 'budget-1',
|
|
290
|
+
accountId: 'acc-1',
|
|
291
|
+
initialAccount: { balance: 100000, cleared_balance: 75000, uncleared_balance: 25000 },
|
|
292
|
+
currencyCode: 'USD',
|
|
293
|
+
sendProgress: mockProgressCallback,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await executeReconciliation(options);
|
|
297
|
+
|
|
298
|
+
expect(mockProgressCallback).toHaveBeenCalled();
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
describe('progress calculation accuracy', () => {
|
|
303
|
+
it('should exclude skipped matches from total count', async () => {
|
|
304
|
+
// Create a match that won't need updating (already cleared, same date)
|
|
305
|
+
const clearedTxn = createMockTransaction({ cleared: 'cleared' });
|
|
306
|
+
const bankTxn = createBankTransaction();
|
|
307
|
+
const matchNoUpdate = createMatch(bankTxn, clearedTxn);
|
|
308
|
+
|
|
309
|
+
// Create a match that will need updating
|
|
310
|
+
const unclearedTxn = createMockTransaction({ cleared: 'uncleared', id: 'txn-2' });
|
|
311
|
+
const bankTxn2 = createBankTransaction({ payee: 'Payee 2' });
|
|
312
|
+
const matchNeedsUpdate = createMatch(bankTxn2, unclearedTxn);
|
|
313
|
+
|
|
314
|
+
const analysis: ReconciliationAnalysis = {
|
|
315
|
+
summary: {
|
|
316
|
+
bank_transactions_count: 2,
|
|
317
|
+
ynab_transactions_count: 2,
|
|
318
|
+
matches: 2,
|
|
319
|
+
unmatched_bank: 0,
|
|
320
|
+
unmatched_ynab: 0,
|
|
321
|
+
match_rate: 1,
|
|
322
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
323
|
+
},
|
|
324
|
+
auto_matches: [matchNoUpdate, matchNeedsUpdate],
|
|
325
|
+
unmatched_bank: [],
|
|
326
|
+
unmatched_ynab: [],
|
|
327
|
+
balance_info: {
|
|
328
|
+
current_cleared: 75000,
|
|
329
|
+
target_statement: 100000,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const params: ReconcileAccountRequest = {
|
|
334
|
+
account_id: 'acc-1',
|
|
335
|
+
csv_data: 'date,amount,payee\n2024-01-15,-25,Test',
|
|
336
|
+
dry_run: false,
|
|
337
|
+
auto_update_cleared_status: true,
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const options: ExecutionOptions = {
|
|
341
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
342
|
+
analysis,
|
|
343
|
+
params,
|
|
344
|
+
budgetId: 'budget-1',
|
|
345
|
+
accountId: 'acc-1',
|
|
346
|
+
initialAccount: { balance: 100000, cleared_balance: 75000, uncleared_balance: 25000 },
|
|
347
|
+
currencyCode: 'USD',
|
|
348
|
+
sendProgress: mockProgressCallback,
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
await executeReconciliation(options);
|
|
352
|
+
|
|
353
|
+
// The total should only count the match that needs updating (1), not both (2)
|
|
354
|
+
if (progressCalls.length > 0) {
|
|
355
|
+
const lastCall = progressCalls[progressCalls.length - 1]!;
|
|
356
|
+
// Total should be 1 (only the uncleared transaction needs updating)
|
|
357
|
+
expect(lastCall.total).toBe(1);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should include all operation types in total count', async () => {
|
|
362
|
+
const unmatchedBank = [createBankTransaction({ payee: 'New Payee' })];
|
|
363
|
+
const unclearedTxn = createMockTransaction({ cleared: 'uncleared' });
|
|
364
|
+
const matchNeedsUpdate = createMatch(createBankTransaction(), unclearedTxn);
|
|
365
|
+
const unmatchedYnab = [createMockTransaction({ id: 'unmatched-ynab', cleared: 'cleared' })];
|
|
366
|
+
|
|
367
|
+
const analysis: ReconciliationAnalysis = {
|
|
368
|
+
summary: {
|
|
369
|
+
bank_transactions_count: 2,
|
|
370
|
+
ynab_transactions_count: 2,
|
|
371
|
+
matches: 1,
|
|
372
|
+
unmatched_bank: 1,
|
|
373
|
+
unmatched_ynab: 1,
|
|
374
|
+
match_rate: 0.5,
|
|
375
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
376
|
+
},
|
|
377
|
+
auto_matches: [matchNeedsUpdate],
|
|
378
|
+
unmatched_bank: unmatchedBank,
|
|
379
|
+
unmatched_ynab: unmatchedYnab,
|
|
380
|
+
balance_info: {
|
|
381
|
+
current_cleared: 75000,
|
|
382
|
+
target_statement: 50000,
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
const params: ReconcileAccountRequest = {
|
|
387
|
+
account_id: 'acc-1',
|
|
388
|
+
csv_data: 'date,amount,payee\n2024-01-15,-25,Test',
|
|
389
|
+
dry_run: false,
|
|
390
|
+
auto_create_transactions: true,
|
|
391
|
+
auto_update_cleared_status: true,
|
|
392
|
+
auto_unclear_missing: true,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const options: ExecutionOptions = {
|
|
396
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
397
|
+
analysis,
|
|
398
|
+
params,
|
|
399
|
+
budgetId: 'budget-1',
|
|
400
|
+
accountId: 'acc-1',
|
|
401
|
+
initialAccount: { balance: 100000, cleared_balance: 75000, uncleared_balance: 25000 },
|
|
402
|
+
currencyCode: 'USD',
|
|
403
|
+
sendProgress: mockProgressCallback,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
await executeReconciliation(options);
|
|
407
|
+
|
|
408
|
+
// Total should be: 1 (create) + 1 (update match) + 1 (unclear) = 3
|
|
409
|
+
if (progressCalls.length > 0) {
|
|
410
|
+
const anyCall = progressCalls.find((c) => c.total !== undefined);
|
|
411
|
+
expect(anyCall?.total).toBe(3);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
describe('dry run behavior', () => {
|
|
417
|
+
it('should not call progress callback during dry run', async () => {
|
|
418
|
+
const analysis: ReconciliationAnalysis = {
|
|
419
|
+
summary: {
|
|
420
|
+
bank_transactions_count: 1,
|
|
421
|
+
ynab_transactions_count: 0,
|
|
422
|
+
matches: 0,
|
|
423
|
+
unmatched_bank: 1,
|
|
424
|
+
unmatched_ynab: 0,
|
|
425
|
+
match_rate: 0,
|
|
426
|
+
statement_date_range: '2024-01-01 to 2024-01-31',
|
|
427
|
+
},
|
|
428
|
+
auto_matches: [],
|
|
429
|
+
unmatched_bank: [createBankTransaction()],
|
|
430
|
+
unmatched_ynab: [],
|
|
431
|
+
balance_info: {
|
|
432
|
+
current_cleared: 100000,
|
|
433
|
+
target_statement: 75000,
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
const params: ReconcileAccountRequest = {
|
|
438
|
+
account_id: 'acc-1',
|
|
439
|
+
csv_data: 'date,amount,payee\n2024-01-15,-25,Test',
|
|
440
|
+
dry_run: true, // Dry run mode
|
|
441
|
+
auto_create_transactions: true,
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const options: ExecutionOptions = {
|
|
445
|
+
ynabAPI: mockYnabAPI as ynab.API,
|
|
446
|
+
analysis,
|
|
447
|
+
params,
|
|
448
|
+
budgetId: 'budget-1',
|
|
449
|
+
accountId: 'acc-1',
|
|
450
|
+
initialAccount: { balance: 100000, cleared_balance: 100000, uncleared_balance: 0 },
|
|
451
|
+
currencyCode: 'USD',
|
|
452
|
+
sendProgress: mockProgressCallback,
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
await executeReconciliation(options);
|
|
456
|
+
|
|
457
|
+
// In dry run, no actual operations happen, so no progress should be reported
|
|
458
|
+
// (Progress is only reported after successful API calls)
|
|
459
|
+
expect(mockYnabAPI.transactions?.createTransaction).not.toHaveBeenCalled();
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'crypto';
|
|
|
2
2
|
import type * as ynab from 'ynab';
|
|
3
3
|
import type { SaveTransaction } from 'ynab/dist/models/SaveTransaction.js';
|
|
4
4
|
import { YNABAPIError, YNABErrorCode } from '../../server/errorHandler.js';
|
|
5
|
+
import type { ProgressCallback } from '../../server/toolRegistry.js';
|
|
5
6
|
import { toMilli, toMoneyValue, addMilli } from '../../utils/money.js';
|
|
6
7
|
import type { ReconciliationAnalysis, TransactionMatch, BankTransaction } from './types.js';
|
|
7
8
|
import type { ReconcileAccountRequest } from './index.js';
|
|
@@ -25,6 +26,11 @@ export interface ExecutionOptions {
|
|
|
25
26
|
accountId: string;
|
|
26
27
|
initialAccount: AccountSnapshot;
|
|
27
28
|
currencyCode: string;
|
|
29
|
+
/**
|
|
30
|
+
* Optional progress callback for emitting MCP progress notifications.
|
|
31
|
+
* When provided, progress updates are sent during bulk operations.
|
|
32
|
+
*/
|
|
33
|
+
sendProgress?: ProgressCallback;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
export interface ExecutionActionRecord {
|
|
@@ -197,7 +203,16 @@ function isWithinStatementWindow(dateStr: string, window: StatementWindow): bool
|
|
|
197
203
|
}
|
|
198
204
|
|
|
199
205
|
export async function executeReconciliation(options: ExecutionOptions): Promise<ExecutionResult> {
|
|
200
|
-
const {
|
|
206
|
+
const {
|
|
207
|
+
analysis,
|
|
208
|
+
params,
|
|
209
|
+
ynabAPI,
|
|
210
|
+
budgetId,
|
|
211
|
+
accountId,
|
|
212
|
+
initialAccount,
|
|
213
|
+
currencyCode,
|
|
214
|
+
sendProgress,
|
|
215
|
+
} = options;
|
|
201
216
|
const actions_taken: ExecutionActionRecord[] = [];
|
|
202
217
|
|
|
203
218
|
const summary: ExecutionSummary = {
|
|
@@ -212,6 +227,29 @@ export async function executeReconciliation(options: ExecutionOptions): Promise<
|
|
|
212
227
|
dry_run: params.dry_run,
|
|
213
228
|
};
|
|
214
229
|
|
|
230
|
+
// Progress tracking for MCP notifications
|
|
231
|
+
// Pre-filter matches to only count those that will actually be updated
|
|
232
|
+
// This ensures accurate progress percentages (skipped matches don't inflate total)
|
|
233
|
+
const matchesNeedingUpdate = analysis.auto_matches.filter((match) => {
|
|
234
|
+
const flags = computeUpdateFlags(match, params);
|
|
235
|
+
return flags.needsClearedUpdate || flags.needsDateUpdate;
|
|
236
|
+
});
|
|
237
|
+
const totalOperations =
|
|
238
|
+
(params.auto_create_transactions ? analysis.unmatched_bank.length : 0) +
|
|
239
|
+
matchesNeedingUpdate.length +
|
|
240
|
+
(params.auto_unclear_missing ? analysis.unmatched_ynab.length : 0);
|
|
241
|
+
let completedOperations = 0;
|
|
242
|
+
|
|
243
|
+
const reportProgress = async (message: string): Promise<void> => {
|
|
244
|
+
if (sendProgress && totalOperations > 0) {
|
|
245
|
+
await sendProgress({
|
|
246
|
+
progress: completedOperations,
|
|
247
|
+
total: totalOperations,
|
|
248
|
+
message,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
215
253
|
let afterAccount: AccountSnapshot = { ...initialAccount };
|
|
216
254
|
let accountSnapshotDirty = false;
|
|
217
255
|
const statementTargetMilli = resolveStatementBalanceMilli(
|
|
@@ -336,6 +374,9 @@ export async function executeReconciliation(options: ExecutionOptions): Promise<
|
|
|
336
374
|
recordCreateAction(recordArgs);
|
|
337
375
|
accountSnapshotDirty = true;
|
|
338
376
|
applyClearedDelta(entry.amountMilli);
|
|
377
|
+
// Report progress for sequential/fallback operations
|
|
378
|
+
completedOperations += 1;
|
|
379
|
+
await reportProgress(`Created ${completedOperations} of ${totalOperations} transactions`);
|
|
339
380
|
const trigger = options.chunkIndex
|
|
340
381
|
? `creating ${entry.bankTransaction.payee ?? 'missing transaction'} (chunk ${options.chunkIndex})`
|
|
341
382
|
: `creating ${entry.bankTransaction.payee ?? 'missing transaction'}`;
|
|
@@ -496,6 +537,11 @@ export async function executeReconciliation(options: ExecutionOptions): Promise<
|
|
|
496
537
|
try {
|
|
497
538
|
await processBulkChunk(chunk, chunkIndex);
|
|
498
539
|
bulkOperationDetails.bulk_successes += 1;
|
|
540
|
+
// Report progress after successful chunk processing
|
|
541
|
+
completedOperations += chunk.length;
|
|
542
|
+
await reportProgress(
|
|
543
|
+
`Created ${completedOperations} of ${totalOperations} transactions`,
|
|
544
|
+
);
|
|
499
545
|
} catch (error) {
|
|
500
546
|
const ynabError = normalizeYnabError(error);
|
|
501
547
|
const failureReason = ynabError.message || 'unknown error';
|
|
@@ -619,6 +665,9 @@ export async function executeReconciliation(options: ExecutionOptions): Promise<
|
|
|
619
665
|
});
|
|
620
666
|
}
|
|
621
667
|
accountSnapshotDirty = true;
|
|
668
|
+
// Report progress after successful batch update
|
|
669
|
+
completedOperations += updatedTransactions.length;
|
|
670
|
+
await reportProgress(`Updated ${completedOperations} of ${totalOperations} transactions`);
|
|
622
671
|
} catch (error) {
|
|
623
672
|
const ynabError = normalizeYnabError(error);
|
|
624
673
|
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
@@ -724,6 +773,11 @@ export async function executeReconciliation(options: ExecutionOptions): Promise<
|
|
|
724
773
|
});
|
|
725
774
|
}
|
|
726
775
|
accountSnapshotDirty = true;
|
|
776
|
+
// Report progress after successful unclear batch
|
|
777
|
+
completedOperations += updatedTransactions.length;
|
|
778
|
+
await reportProgress(
|
|
779
|
+
`Marked ${completedOperations} of ${totalOperations} transactions uncleared`,
|
|
780
|
+
);
|
|
727
781
|
} catch (error) {
|
|
728
782
|
const ynabError = normalizeYnabError(error);
|
|
729
783
|
const failureReason = ynabError.message || 'Unknown error occurred';
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
import { promises as fs } from 'fs';
|
|
7
7
|
import { z } from 'zod/v4';
|
|
8
8
|
import type * as ynab from 'ynab';
|
|
9
|
-
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
10
|
+
import type { ProgressCallback } from '../../server/toolRegistry.js';
|
|
10
11
|
import { withToolErrorHandling } from '../../types/index.js';
|
|
11
12
|
import type { ToolFactory } from '../../types/toolRegistration.js';
|
|
12
13
|
import { createAdapters, createBudgetResolver } from '../adapters.js';
|
|
@@ -151,6 +152,7 @@ export async function handleReconcileAccount(
|
|
|
151
152
|
ynabAPI: ynab.API,
|
|
152
153
|
deltaFetcher: DeltaFetcher,
|
|
153
154
|
params: ReconcileAccountRequest,
|
|
155
|
+
sendProgress?: ProgressCallback,
|
|
154
156
|
): Promise<CallToolResult>;
|
|
155
157
|
export async function handleReconcileAccount(
|
|
156
158
|
ynabAPI: ynab.API,
|
|
@@ -160,6 +162,7 @@ export async function handleReconcileAccount(
|
|
|
160
162
|
ynabAPI: ynab.API,
|
|
161
163
|
deltaFetcherOrParams: DeltaFetcher | ReconcileAccountRequest,
|
|
162
164
|
maybeParams?: ReconcileAccountRequest,
|
|
165
|
+
sendProgress?: ProgressCallback,
|
|
163
166
|
): Promise<CallToolResult> {
|
|
164
167
|
const { deltaFetcher, params } = resolveDeltaFetcherArgs(
|
|
165
168
|
ynabAPI,
|
|
@@ -419,6 +422,7 @@ export async function handleReconcileAccount(
|
|
|
419
422
|
accountId: params.account_id,
|
|
420
423
|
initialAccount,
|
|
421
424
|
currencyCode,
|
|
425
|
+
...(sendProgress !== undefined && { sendProgress }),
|
|
422
426
|
});
|
|
423
427
|
}
|
|
424
428
|
|
|
@@ -468,7 +472,7 @@ export async function handleReconcileAccount(
|
|
|
468
472
|
* Registers reconciliation-domain tools (compare + reconcile) with the registry.
|
|
469
473
|
*/
|
|
470
474
|
export const registerReconciliationTools: ToolFactory = (registry, context) => {
|
|
471
|
-
const { adapt,
|
|
475
|
+
const { adapt, adaptWithDeltaAndProgress } = createAdapters(context);
|
|
472
476
|
const budgetResolver = createBudgetResolver(context);
|
|
473
477
|
|
|
474
478
|
registry.register({
|
|
@@ -491,7 +495,7 @@ export const registerReconciliationTools: ToolFactory = (registry, context) => {
|
|
|
491
495
|
description:
|
|
492
496
|
'Guided reconciliation workflow with human narrative, insight detection, and optional execution (create/update/unclear). Set include_structured_data=true to also get full JSON output (large).',
|
|
493
497
|
inputSchema: ReconcileAccountSchema,
|
|
494
|
-
handler:
|
|
498
|
+
handler: adaptWithDeltaAndProgress(handleReconcileAccount),
|
|
495
499
|
defaultArgumentResolver: budgetResolver<z.infer<typeof ReconcileAccountSchema>>(),
|
|
496
500
|
metadata: {
|
|
497
501
|
annotations: {
|
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
export {
|
|
26
26
|
GetUserOutputSchema,
|
|
27
27
|
type GetUserOutput,
|
|
28
|
-
ConvertAmountOutputSchema,
|
|
29
|
-
type ConvertAmountOutput,
|
|
30
28
|
GetDefaultBudgetOutputSchema,
|
|
31
29
|
type GetDefaultBudgetOutput,
|
|
32
30
|
SetDefaultBudgetOutputSchema,
|
|
@@ -44,7 +42,6 @@ export {
|
|
|
44
42
|
// Nested schemas that may be useful independently
|
|
45
43
|
export {
|
|
46
44
|
UserSchema,
|
|
47
|
-
ConversionSchema,
|
|
48
45
|
DateFormatSchema,
|
|
49
46
|
CurrencyFormatSchema,
|
|
50
47
|
BudgetDetailSchema,
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @fileoverview Output schemas for utility tools
|
|
3
3
|
*
|
|
4
4
|
* This file contains comprehensive Zod schemas for validating the output
|
|
5
|
-
* of utility tools including user info,
|
|
6
|
-
*
|
|
5
|
+
* of utility tools including user info, budget defaults, cache management,
|
|
6
|
+
* output formatting, and diagnostic information.
|
|
7
7
|
*
|
|
8
8
|
* All schemas include TypeScript type inference for type-safe usage throughout
|
|
9
9
|
* the codebase. Reference the corresponding handler implementations for
|
|
@@ -46,47 +46,6 @@ export const GetUserOutputSchema = z.object({
|
|
|
46
46
|
|
|
47
47
|
export type GetUserOutput = z.infer<typeof GetUserOutputSchema>;
|
|
48
48
|
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// CONVERT AMOUNT OUTPUT
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Schema for amount conversion details
|
|
55
|
-
*
|
|
56
|
-
* Contains the conversion result between dollars and YNAB milliunits.
|
|
57
|
-
*/
|
|
58
|
-
export const ConversionSchema = z.object({
|
|
59
|
-
original_amount: z.number(),
|
|
60
|
-
converted_amount: z.number(),
|
|
61
|
-
to_milliunits: z.boolean(),
|
|
62
|
-
description: z.string(),
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Output schema for convert_amount tool
|
|
67
|
-
*
|
|
68
|
-
* Converts between dollars and YNAB milliunits (1 dollar = 1000 milliunits).
|
|
69
|
-
*
|
|
70
|
-
* @see src/tools/utilityTools.ts:51-90 - Handler implementation
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```typescript
|
|
74
|
-
* const output: ConvertAmountOutput = {
|
|
75
|
-
* conversion: {
|
|
76
|
-
* original_amount: 25.50,
|
|
77
|
-
* converted_amount: 25500,
|
|
78
|
-
* to_milliunits: true,
|
|
79
|
-
* description: "$25.50 converted to 25500 milliunits"
|
|
80
|
-
* }
|
|
81
|
-
* };
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
export const ConvertAmountOutputSchema = z.object({
|
|
85
|
-
conversion: ConversionSchema,
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
export type ConvertAmountOutput = z.infer<typeof ConvertAmountOutputSchema>;
|
|
89
|
-
|
|
90
49
|
// ============================================================================
|
|
91
50
|
// GET DEFAULT BUDGET OUTPUT
|
|
92
51
|
// ============================================================================
|