@dizzlkheinz/ynab-mcpb 0.16.1 → 0.17.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/.code/agents/0098661e-0fa3-4990-beb9-c0cbf3f123aa/status.txt +1 -0
- package/.code/agents/1324/exec-call_tIpx9uV1TpARbAMZonRQm8AO.txt +757 -0
- package/.code/agents/1572/exec-call_GjVFBFOWcY7lE0idc5nWlLNh.txt +781 -0
- package/.code/agents/1846/exec-call_1YNAVD18RjrMN7JnfkkQhUP3.txt +766 -0
- package/.code/agents/1846/exec-call_lh3lDzE4WJAh1lFiomiiZ73D.txt +766 -0
- package/.code/agents/2038/exec-call_DYwOukaYsL8VCONWmV2rUW5u.txt +766 -0
- package/.code/agents/2038/exec-call_c7fOQ7UrpVcTtvdfGBRM146V.txt +652 -0
- package/.code/agents/2038/exec-call_ySNyq9Mm55jWE480s54r5QcA.txt +766 -0
- package/.code/agents/2256/exec-call_AtPcRWPmFPMcmX6qOFm1fCEY.txt +766 -0
- package/.code/agents/2454/exec-call_aFJpupwjfZeOBm7ixI5Vc8z2.txt +766 -0
- package/.code/agents/2454/exec-call_wogZ4HfXTodTEXvdgXlVUBpv.txt +766 -0
- package/.code/agents/2e905864-aa07-4314-bcf9-c5b32277e4ac/result.txt +36 -0
- package/.code/agents/3073/exec-call_Peeagc9DxGYLgE6pNdMZhqIE.txt +766 -0
- package/.code/agents/3073/exec-call_d2YSE3hXF08KRSoUM3qd8Z3x.txt +766 -0
- package/.code/agents/335aa031-466d-4fb7-925f-3cd864e264d0/result.txt +191 -0
- package/.code/agents/3364/exec-call_NbhIrsM5HhyDZDmJZG5CuCYL.txt +766 -0
- package/.code/agents/3364/exec-call_cKtJg0NrXiwXEFwlsE3uPZRA.txt +766 -0
- package/.code/agents/36d98414-5cde-4d9d-9a67-a240a18c1f07/result.txt +189 -0
- package/.code/agents/4604e866-b7b8-44f5-992f-2f683b0a523b/status.txt +1 -0
- package/.code/agents/5f8dc01c-47b3-4163-b0b3-aa31be89fcdc/status.txt +1 -0
- package/.code/agents/7/exec-call_HltHpkDox0Zm1vGEjdksUgpE.txt +1120 -0
- package/.code/agents/7/exec-call_LCATrOPPAgbxW9Q1z0XaVi2E.txt +2646 -0
- package/.code/agents/7/exec-call_W8DeRfNG9hvbgVFvf0clBf6R.txt +2646 -0
- package/.code/agents/94a0ddf3-a304-4ec3-913e-3cceef509948/error.txt +1 -0
- package/.code/agents/e2c752b7-711d-423a-af57-f53c809deb84/result.txt +160 -0
- package/.code/agents/e6601719-c31f-4a0e-8c71-d70787d0ab71/status.txt +1 -0
- package/.code/agents/f250b7ed-5bd5-4036-aa8c-ce63caee7d61/result.txt +20 -0
- package/AGENTS.md +1 -36
- package/CLAUDE.md +28 -43
- package/NUL +0 -1
- package/README.md +8 -10
- package/dist/bundle/index.cjs +41 -41
- package/dist/server/YNABMCPServer.js +28 -381
- package/dist/server/config.d.ts +2 -0
- package/dist/server/config.js +1 -0
- package/dist/tools/accountTools.d.ts +2 -0
- package/dist/tools/accountTools.js +45 -0
- package/dist/tools/adapters.d.ts +12 -0
- package/dist/tools/adapters.js +25 -0
- package/dist/tools/budgetTools.d.ts +2 -0
- package/dist/tools/budgetTools.js +30 -0
- package/dist/tools/categoryTools.d.ts +2 -0
- package/dist/tools/categoryTools.js +45 -0
- package/dist/tools/monthTools.d.ts +2 -0
- package/dist/tools/monthTools.js +32 -0
- package/dist/tools/payeeTools.d.ts +2 -0
- package/dist/tools/payeeTools.js +32 -0
- package/dist/tools/reconciliation/index.d.ts +2 -0
- package/dist/tools/reconciliation/index.js +33 -0
- package/dist/tools/schemas/common.d.ts +3 -0
- package/dist/tools/schemas/common.js +3 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/transactionTools.d.ts +2 -0
- package/dist/tools/transactionTools.js +124 -0
- package/dist/tools/utilityTools.d.ts +3 -1
- package/dist/tools/utilityTools.js +32 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/toolRegistration.d.ts +27 -0
- package/dist/types/toolRegistration.js +1 -0
- package/package.json +2 -2
- package/scripts/run-domain-integration-tests.js +4 -1
- package/src/__tests__/workflows.e2e.test.ts +1 -7
- package/src/server/YNABMCPServer.ts +33 -519
- package/src/server/__tests__/toolRegistration.test.ts +236 -0
- package/src/server/config.ts +1 -0
- package/src/tools/__tests__/adapters.test.ts +113 -0
- package/src/tools/__tests__/utilityTools.test.ts +7 -7
- package/src/tools/accountTools.ts +53 -0
- package/src/tools/adapters.ts +74 -0
- package/src/tools/budgetTools.ts +37 -0
- package/src/tools/categoryTools.ts +53 -0
- package/src/tools/monthTools.ts +39 -0
- package/src/tools/payeeTools.ts +39 -0
- package/src/tools/reconciliation/index.ts +45 -0
- package/src/tools/schemas/common.ts +18 -0
- package/src/tools/transactionTools.ts +140 -0
- package/src/tools/utilityTools.ts +42 -2
- package/src/types/index.ts +3 -0
- package/src/types/toolRegistration.ts +88 -0
- package/.github/workflows/pr-description-check.yml +0 -88
- package/docs/README.md +0 -72
- package/docs/getting-started/CONFIGURATION.md +0 -175
- package/docs/getting-started/INSTALLATION.md +0 -333
- package/docs/getting-started/QUICKSTART.md +0 -282
- package/docs/guides/ARCHITECTURE.md +0 -533
- package/docs/guides/DEPLOYMENT.md +0 -189
- package/docs/guides/INTEGRATION_TESTING.md +0 -730
- package/docs/guides/TESTING.md +0 -591
- package/docs/reconciliation-flow.md +0 -83
- package/docs/reference/EXAMPLES.md +0 -946
- package/docs/reference/TOOLS.md +0 -348
- package/docs/reference/TROUBLESHOOTING.md +0 -481
|
@@ -1,946 +0,0 @@
|
|
|
1
|
-
# YNAB MCP Server Usage Examples
|
|
2
|
-
|
|
3
|
-
This document provides practical examples of using the YNAB MCP Server tools in real-world scenarios. Each example includes complete code with error handling and best practices.
|
|
4
|
-
|
|
5
|
-
## Table of Contents
|
|
6
|
-
|
|
7
|
-
- [Basic Examples](#basic-examples)
|
|
8
|
-
- [Budget Management Examples](#budget-management-examples)
|
|
9
|
-
- [Account Management Examples](#account-management-examples)
|
|
10
|
-
- [Transaction Management Examples](#transaction-management-examples)
|
|
11
|
-
- [Category Management Examples](#category-management-examples)
|
|
12
|
-
- [Financial Analysis Examples](#financial-analysis-examples)
|
|
13
|
-
- [Advanced Workflows](#advanced-workflows)
|
|
14
|
-
- [Integration Examples](#integration-examples)
|
|
15
|
-
|
|
16
|
-
## Basic Examples
|
|
17
|
-
|
|
18
|
-
### Getting Started - User Information
|
|
19
|
-
|
|
20
|
-
```javascript
|
|
21
|
-
// Get authenticated user information
|
|
22
|
-
async function getUserInfo() {
|
|
23
|
-
try {
|
|
24
|
-
const result = await client.callTool('get_user', {});
|
|
25
|
-
const data = JSON.parse(result.content[0].text);
|
|
26
|
-
|
|
27
|
-
console.log('User Information:');
|
|
28
|
-
console.log(`Email: ${data.user.email}`);
|
|
29
|
-
console.log(`Subscription: ${data.user.subscription.frequency}`);
|
|
30
|
-
|
|
31
|
-
return data.user;
|
|
32
|
-
} catch (error) {
|
|
33
|
-
console.error('Failed to get user info:', error.message);
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Amount Conversion Examples
|
|
40
|
-
|
|
41
|
-
```javascript
|
|
42
|
-
// Convert dollars to milliunits for API calls
|
|
43
|
-
async function convertToMilliunits(dollarAmount) {
|
|
44
|
-
const result = await client.callTool('convert_amount', {
|
|
45
|
-
amount: dollarAmount,
|
|
46
|
-
to_milliunits: true
|
|
47
|
-
});
|
|
48
|
-
const data = JSON.parse(result.content[0].text);
|
|
49
|
-
return data.converted_amount;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Convert milliunits to dollars for display
|
|
53
|
-
async function convertToDollars(milliunits) {
|
|
54
|
-
const result = await client.callTool('convert_amount', {
|
|
55
|
-
amount: milliunits,
|
|
56
|
-
to_milliunits: false
|
|
57
|
-
});
|
|
58
|
-
const data = JSON.parse(result.content[0].text);
|
|
59
|
-
return data.converted_amount;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Example usage
|
|
63
|
-
const userInput = 25.50; // User enters $25.50
|
|
64
|
-
const milliunits = await convertToMilliunits(userInput);
|
|
65
|
-
console.log(`$${userInput} = ${milliunits} milliunits`);
|
|
66
|
-
|
|
67
|
-
const displayAmount = await convertToDollars(milliunits);
|
|
68
|
-
console.log(`${milliunits} milliunits = $${displayAmount}`);
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Budget Management Examples
|
|
72
|
-
|
|
73
|
-
### List All Budgets
|
|
74
|
-
|
|
75
|
-
```javascript
|
|
76
|
-
async function listAllBudgets() {
|
|
77
|
-
try {
|
|
78
|
-
const result = await client.callTool('list_budgets', {});
|
|
79
|
-
const data = JSON.parse(result.content[0].text);
|
|
80
|
-
|
|
81
|
-
console.log('Available Budgets:');
|
|
82
|
-
data.budgets.forEach((budget, index) => {
|
|
83
|
-
console.log(`${index + 1}. ${budget.name} (${budget.id})`);
|
|
84
|
-
console.log(` Last modified: ${new Date(budget.last_modified_on).toLocaleDateString()}`);
|
|
85
|
-
console.log(` Currency: ${budget.currency_format.currency_symbol}`);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
return data.budgets;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error('Failed to list budgets:', error.message);
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Get Detailed Budget Information
|
|
97
|
-
|
|
98
|
-
```javascript
|
|
99
|
-
async function getBudgetDetails(budgetId) {
|
|
100
|
-
try {
|
|
101
|
-
const result = await client.callTool('get_budget', {
|
|
102
|
-
budget_id: budgetId
|
|
103
|
-
});
|
|
104
|
-
const data = JSON.parse(result.content[0].text);
|
|
105
|
-
|
|
106
|
-
const budget = data.budget;
|
|
107
|
-
console.log(`Budget: ${budget.name}`);
|
|
108
|
-
console.log(`Accounts: ${budget.accounts.length}`);
|
|
109
|
-
console.log(`Categories: ${budget.categories.length}`);
|
|
110
|
-
console.log(`Transactions: ${budget.transactions.length}`);
|
|
111
|
-
|
|
112
|
-
return budget;
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error('Failed to get budget details:', error.message);
|
|
115
|
-
throw error;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Account Management Examples
|
|
121
|
-
|
|
122
|
-
### List Accounts by Type
|
|
123
|
-
|
|
124
|
-
```javascript
|
|
125
|
-
async function listAccountsByType(budgetId, accountType = null) {
|
|
126
|
-
try {
|
|
127
|
-
const result = await client.callTool('list_accounts', {
|
|
128
|
-
budget_id: budgetId
|
|
129
|
-
});
|
|
130
|
-
const data = JSON.parse(result.content[0].text);
|
|
131
|
-
|
|
132
|
-
let accounts = data.accounts.filter(account => !account.closed);
|
|
133
|
-
|
|
134
|
-
if (accountType) {
|
|
135
|
-
accounts = accounts.filter(account => account.type === accountType);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
console.log(`${accountType || 'All'} Accounts:`);
|
|
139
|
-
accounts.forEach(account => {
|
|
140
|
-
const balance = (account.balance / 1000).toFixed(2);
|
|
141
|
-
console.log(`- ${account.name}: $${balance} (${account.type})`);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
return accounts;
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.error('Failed to list accounts:', error.message);
|
|
147
|
-
throw error;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Usage examples
|
|
152
|
-
await listAccountsByType(budgetId); // All accounts
|
|
153
|
-
await listAccountsByType(budgetId, 'checking'); // Only checking accounts
|
|
154
|
-
await listAccountsByType(budgetId, 'creditCard'); // Only credit cards
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### Create New Account
|
|
158
|
-
|
|
159
|
-
```javascript
|
|
160
|
-
async function createNewAccount(budgetId, accountName, accountType, initialBalance = 0) {
|
|
161
|
-
try {
|
|
162
|
-
// Convert initial balance to milliunits
|
|
163
|
-
const balanceMilliunits = await convertToMilliunits(initialBalance);
|
|
164
|
-
|
|
165
|
-
const result = await client.callTool('create_account', {
|
|
166
|
-
budget_id: budgetId,
|
|
167
|
-
name: accountName,
|
|
168
|
-
type: accountType,
|
|
169
|
-
balance: balanceMilliunits
|
|
170
|
-
});
|
|
171
|
-
const data = JSON.parse(result.content[0].text);
|
|
172
|
-
|
|
173
|
-
const account = data.account;
|
|
174
|
-
console.log(`Created account: ${account.name}`);
|
|
175
|
-
console.log(`Type: ${account.type}`);
|
|
176
|
-
console.log(`Initial balance: $${(account.balance / 1000).toFixed(2)}`);
|
|
177
|
-
|
|
178
|
-
return account;
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error('Failed to create account:', error.message);
|
|
181
|
-
throw error;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Example: Create a new savings account with $1000 initial balance
|
|
186
|
-
const newAccount = await createNewAccount(
|
|
187
|
-
budgetId,
|
|
188
|
-
'Emergency Fund',
|
|
189
|
-
'savings',
|
|
190
|
-
1000.00
|
|
191
|
-
);
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Get Account Balance Summary
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
async function getAccountBalanceSummary(budgetId) {
|
|
198
|
-
try {
|
|
199
|
-
const result = await client.callTool('list_accounts', {
|
|
200
|
-
budget_id: budgetId
|
|
201
|
-
});
|
|
202
|
-
const data = JSON.parse(result.content[0].text);
|
|
203
|
-
|
|
204
|
-
const summary = {
|
|
205
|
-
totalAssets: 0,
|
|
206
|
-
totalLiabilities: 0,
|
|
207
|
-
netWorth: 0,
|
|
208
|
-
accountsByType: {}
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
data.accounts.forEach(account => {
|
|
212
|
-
if (account.closed) return;
|
|
213
|
-
|
|
214
|
-
const balance = account.balance / 1000;
|
|
215
|
-
|
|
216
|
-
// Group by account type
|
|
217
|
-
if (!summary.accountsByType[account.type]) {
|
|
218
|
-
summary.accountsByType[account.type] = {
|
|
219
|
-
count: 0,
|
|
220
|
-
totalBalance: 0,
|
|
221
|
-
accounts: []
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
summary.accountsByType[account.type].count++;
|
|
226
|
-
summary.accountsByType[account.type].totalBalance += balance;
|
|
227
|
-
summary.accountsByType[account.type].accounts.push({
|
|
228
|
-
name: account.name,
|
|
229
|
-
balance: balance
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Calculate assets vs liabilities
|
|
233
|
-
if (['checking', 'savings', 'cash', 'otherAsset'].includes(account.type)) {
|
|
234
|
-
summary.totalAssets += balance;
|
|
235
|
-
} else if (['creditCard', 'lineOfCredit', 'otherLiability'].includes(account.type)) {
|
|
236
|
-
summary.totalLiabilities += Math.abs(balance);
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
summary.netWorth = summary.totalAssets - summary.totalLiabilities;
|
|
241
|
-
|
|
242
|
-
console.log('Account Balance Summary:');
|
|
243
|
-
console.log(`Total Assets: $${summary.totalAssets.toFixed(2)}`);
|
|
244
|
-
console.log(`Total Liabilities: $${summary.totalLiabilities.toFixed(2)}`);
|
|
245
|
-
console.log(`Net Worth: $${summary.netWorth.toFixed(2)}`);
|
|
246
|
-
|
|
247
|
-
return summary;
|
|
248
|
-
} catch (error) {
|
|
249
|
-
console.error('Failed to get balance summary:', error.message);
|
|
250
|
-
throw error;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
## Transaction Management Examples
|
|
256
|
-
|
|
257
|
-
### Create Simple Transaction
|
|
258
|
-
|
|
259
|
-
```javascript
|
|
260
|
-
async function createSimpleTransaction(budgetId, accountId, amount, payeeName, memo = '') {
|
|
261
|
-
try {
|
|
262
|
-
// Convert amount to milliunits
|
|
263
|
-
const milliunits = await convertToMilliunits(Math.abs(amount));
|
|
264
|
-
const transactionAmount = amount < 0 ? -milliunits : milliunits;
|
|
265
|
-
|
|
266
|
-
// Use today's date
|
|
267
|
-
const today = new Date().toISOString().split('T')[0];
|
|
268
|
-
|
|
269
|
-
const result = await client.callTool('create_transaction', {
|
|
270
|
-
budget_id: budgetId,
|
|
271
|
-
account_id: accountId,
|
|
272
|
-
amount: transactionAmount,
|
|
273
|
-
date: today,
|
|
274
|
-
payee_name: payeeName,
|
|
275
|
-
memo: memo,
|
|
276
|
-
cleared: 'uncleared',
|
|
277
|
-
approved: true
|
|
278
|
-
});
|
|
279
|
-
const data = JSON.parse(result.content[0].text);
|
|
280
|
-
|
|
281
|
-
const transaction = data.transaction;
|
|
282
|
-
console.log(`Created transaction: ${payeeName} for $${Math.abs(amount)}`);
|
|
283
|
-
|
|
284
|
-
return transaction;
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error('Failed to create transaction:', error.message);
|
|
287
|
-
throw error;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Example: Record a $25.50 coffee purchase
|
|
292
|
-
const transaction = await createSimpleTransaction(
|
|
293
|
-
budgetId,
|
|
294
|
-
checkingAccountId,
|
|
295
|
-
-25.50,
|
|
296
|
-
'Coffee Shop',
|
|
297
|
-
'Morning coffee'
|
|
298
|
-
);
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### Get Recent Transactions
|
|
302
|
-
|
|
303
|
-
```javascript
|
|
304
|
-
async function getRecentTransactions(budgetId, days = 30, accountId = null) {
|
|
305
|
-
try {
|
|
306
|
-
// Calculate date filter
|
|
307
|
-
const sinceDate = new Date();
|
|
308
|
-
sinceDate.setDate(sinceDate.getDate() - days);
|
|
309
|
-
const sinceDateStr = sinceDate.toISOString().split('T')[0];
|
|
310
|
-
|
|
311
|
-
const params = {
|
|
312
|
-
budget_id: budgetId,
|
|
313
|
-
since_date: sinceDateStr
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
if (accountId) {
|
|
317
|
-
params.account_id = accountId;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const result = await client.callTool('list_transactions', params);
|
|
321
|
-
const data = JSON.parse(result.content[0].text);
|
|
322
|
-
|
|
323
|
-
console.log(`Recent Transactions (last ${days} days):`);
|
|
324
|
-
data.transactions.forEach(transaction => {
|
|
325
|
-
const amount = (transaction.amount / 1000).toFixed(2);
|
|
326
|
-
const sign = transaction.amount < 0 ? '-' : '+';
|
|
327
|
-
console.log(`${transaction.date}: ${transaction.payee_name} ${sign}$${Math.abs(amount)}`);
|
|
328
|
-
if (transaction.memo) {
|
|
329
|
-
console.log(` Memo: ${transaction.memo}`);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
return data.transactions;
|
|
334
|
-
} catch (error) {
|
|
335
|
-
console.error('Failed to get recent transactions:', error.message);
|
|
336
|
-
throw error;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
### Update Transaction
|
|
342
|
-
|
|
343
|
-
```javascript
|
|
344
|
-
async function updateTransaction(budgetId, transactionId, updates) {
|
|
345
|
-
try {
|
|
346
|
-
const params = {
|
|
347
|
-
budget_id: budgetId,
|
|
348
|
-
transaction_id: transactionId
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
// Convert amount if provided
|
|
352
|
-
if (updates.amount !== undefined) {
|
|
353
|
-
const milliunits = await convertToMilliunits(Math.abs(updates.amount));
|
|
354
|
-
params.amount = updates.amount < 0 ? -milliunits : milliunits;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Add other updates
|
|
358
|
-
Object.keys(updates).forEach(key => {
|
|
359
|
-
if (key !== 'amount') {
|
|
360
|
-
params[key] = updates[key];
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
const result = await client.callTool('update_transaction', params);
|
|
365
|
-
const data = JSON.parse(result.content[0].text);
|
|
366
|
-
|
|
367
|
-
console.log('Transaction updated successfully');
|
|
368
|
-
return data.transaction;
|
|
369
|
-
} catch (error) {
|
|
370
|
-
console.error('Failed to update transaction:', error.message);
|
|
371
|
-
throw error;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Example: Update transaction amount and add flag
|
|
376
|
-
const updatedTransaction = await updateTransaction(budgetId, transactionId, {
|
|
377
|
-
amount: -30.00,
|
|
378
|
-
flag_color: 'red',
|
|
379
|
-
memo: 'Updated amount'
|
|
380
|
-
});
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Bulk Transaction Import
|
|
384
|
-
|
|
385
|
-
```javascript
|
|
386
|
-
async function importTransactions(budgetId, transactions) {
|
|
387
|
-
const results = [];
|
|
388
|
-
const errors = [];
|
|
389
|
-
|
|
390
|
-
console.log(`Importing ${transactions.length} transactions...`);
|
|
391
|
-
|
|
392
|
-
for (let i = 0; i < transactions.length; i++) {
|
|
393
|
-
const transaction = transactions[i];
|
|
394
|
-
|
|
395
|
-
try {
|
|
396
|
-
// Validate transaction data
|
|
397
|
-
if (!transaction.accountId || !transaction.amount || !transaction.date) {
|
|
398
|
-
throw new Error('Missing required fields: accountId, amount, date');
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Convert amount
|
|
402
|
-
const milliunits = await convertToMilliunits(Math.abs(transaction.amount));
|
|
403
|
-
const amount = transaction.amount < 0 ? -milliunits : milliunits;
|
|
404
|
-
|
|
405
|
-
const result = await client.callTool('create_transaction', {
|
|
406
|
-
budget_id: budgetId,
|
|
407
|
-
account_id: transaction.accountId,
|
|
408
|
-
amount: amount,
|
|
409
|
-
date: transaction.date,
|
|
410
|
-
payee_name: transaction.payeeName || 'Unknown',
|
|
411
|
-
category_id: transaction.categoryId,
|
|
412
|
-
memo: transaction.memo || '',
|
|
413
|
-
cleared: transaction.cleared || 'uncleared',
|
|
414
|
-
approved: transaction.approved !== false
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
const data = JSON.parse(result.content[0].text);
|
|
418
|
-
results.push({
|
|
419
|
-
index: i,
|
|
420
|
-
success: true,
|
|
421
|
-
transaction: data.transaction
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
console.log(`✓ Imported: ${transaction.payeeName} ($${Math.abs(transaction.amount)})`);
|
|
425
|
-
|
|
426
|
-
} catch (error) {
|
|
427
|
-
errors.push({
|
|
428
|
-
index: i,
|
|
429
|
-
transaction: transaction,
|
|
430
|
-
error: error.message
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
console.log(`✗ Failed: ${transaction.payeeName} - ${error.message}`);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Add delay to respect rate limits
|
|
437
|
-
if (i < transactions.length - 1) {
|
|
438
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
console.log(`Import complete: ${results.length} successful, ${errors.length} failed`);
|
|
443
|
-
|
|
444
|
-
return { results, errors };
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Example usage
|
|
448
|
-
const transactionsToImport = [
|
|
449
|
-
{
|
|
450
|
-
accountId: 'checking-account-id',
|
|
451
|
-
amount: -25.50,
|
|
452
|
-
date: '2024-01-15',
|
|
453
|
-
payeeName: 'Coffee Shop',
|
|
454
|
-
memo: 'Morning coffee'
|
|
455
|
-
},
|
|
456
|
-
{
|
|
457
|
-
accountId: 'checking-account-id',
|
|
458
|
-
amount: -45.00,
|
|
459
|
-
date: '2024-01-15',
|
|
460
|
-
payeeName: 'Gas Station',
|
|
461
|
-
memo: 'Fill up'
|
|
462
|
-
}
|
|
463
|
-
];
|
|
464
|
-
|
|
465
|
-
const importResults = await importTransactions(budgetId, transactionsToImport);
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
## Category Management Examples
|
|
469
|
-
|
|
470
|
-
### List Categories with Budget Analysis
|
|
471
|
-
|
|
472
|
-
```javascript
|
|
473
|
-
async function analyzeCategoryBudgets(budgetId) {
|
|
474
|
-
try {
|
|
475
|
-
const result = await client.callTool('list_categories', {
|
|
476
|
-
budget_id: budgetId
|
|
477
|
-
});
|
|
478
|
-
const data = JSON.parse(result.content[0].text);
|
|
479
|
-
|
|
480
|
-
const analysis = {
|
|
481
|
-
totalBudgeted: 0,
|
|
482
|
-
totalActivity: 0,
|
|
483
|
-
overspentCategories: [],
|
|
484
|
-
underspentCategories: []
|
|
485
|
-
};
|
|
486
|
-
|
|
487
|
-
console.log('Category Budget Analysis:');
|
|
488
|
-
console.log('========================');
|
|
489
|
-
|
|
490
|
-
data.category_groups.forEach(group => {
|
|
491
|
-
if (group.hidden || group.deleted) return;
|
|
492
|
-
|
|
493
|
-
console.log(`\n${group.name}:`);
|
|
494
|
-
|
|
495
|
-
group.categories.forEach(category => {
|
|
496
|
-
if (category.hidden || category.deleted) return;
|
|
497
|
-
|
|
498
|
-
const budgeted = category.budgeted / 1000;
|
|
499
|
-
const activity = category.activity / 1000;
|
|
500
|
-
const balance = category.balance / 1000;
|
|
501
|
-
|
|
502
|
-
analysis.totalBudgeted += budgeted;
|
|
503
|
-
analysis.totalActivity += Math.abs(activity);
|
|
504
|
-
|
|
505
|
-
const percentUsed = budgeted !== 0 ? (Math.abs(activity) / budgeted) * 100 : 0;
|
|
506
|
-
|
|
507
|
-
console.log(` ${category.name}:`);
|
|
508
|
-
console.log(` Budgeted: $${budgeted.toFixed(2)}`);
|
|
509
|
-
console.log(` Activity: $${activity.toFixed(2)}`);
|
|
510
|
-
console.log(` Balance: $${balance.toFixed(2)}`);
|
|
511
|
-
console.log(` Used: ${percentUsed.toFixed(1)}%`);
|
|
512
|
-
|
|
513
|
-
// Track overspent categories
|
|
514
|
-
if (balance < 0) {
|
|
515
|
-
analysis.overspentCategories.push({
|
|
516
|
-
name: category.name,
|
|
517
|
-
overspent: Math.abs(balance)
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// Track significantly underspent categories
|
|
522
|
-
if (budgeted > 0 && percentUsed < 50) {
|
|
523
|
-
analysis.underspentCategories.push({
|
|
524
|
-
name: category.name,
|
|
525
|
-
budgeted: budgeted,
|
|
526
|
-
used: percentUsed
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
|
|
532
|
-
console.log('\n=== SUMMARY ===');
|
|
533
|
-
console.log(`Total Budgeted: $${analysis.totalBudgeted.toFixed(2)}`);
|
|
534
|
-
console.log(`Total Activity: $${analysis.totalActivity.toFixed(2)}`);
|
|
535
|
-
|
|
536
|
-
if (analysis.overspentCategories.length > 0) {
|
|
537
|
-
console.log('\nOverspent Categories:');
|
|
538
|
-
analysis.overspentCategories.forEach(cat => {
|
|
539
|
-
console.log(` ${cat.name}: -$${cat.overspent.toFixed(2)}`);
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (analysis.underspentCategories.length > 0) {
|
|
544
|
-
console.log('\nUnderspent Categories (< 50% used):');
|
|
545
|
-
analysis.underspentCategories.forEach(cat => {
|
|
546
|
-
console.log(` ${cat.name}: ${cat.used.toFixed(1)}% of $${cat.budgeted.toFixed(2)}`);
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
return analysis;
|
|
551
|
-
} catch (error) {
|
|
552
|
-
console.error('Failed to analyze categories:', error.message);
|
|
553
|
-
throw error;
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
### Update Category Budget
|
|
559
|
-
|
|
560
|
-
```javascript
|
|
561
|
-
async function updateCategoryBudget(budgetId, categoryId, newBudgetAmount) {
|
|
562
|
-
try {
|
|
563
|
-
// Convert to milliunits
|
|
564
|
-
const milliunits = await convertToMilliunits(newBudgetAmount);
|
|
565
|
-
|
|
566
|
-
const result = await client.callTool('update_category', {
|
|
567
|
-
budget_id: budgetId,
|
|
568
|
-
category_id: categoryId,
|
|
569
|
-
budgeted: milliunits
|
|
570
|
-
});
|
|
571
|
-
const data = JSON.parse(result.content[0].text);
|
|
572
|
-
|
|
573
|
-
console.log(`Updated category budget to $${newBudgetAmount}`);
|
|
574
|
-
return data.category;
|
|
575
|
-
} catch (error) {
|
|
576
|
-
console.error('Failed to update category budget:', error.message);
|
|
577
|
-
throw error;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
```
|
|
581
|
-
|
|
582
|
-
## Financial Analysis Examples
|
|
583
|
-
|
|
584
|
-
### Comprehensive Financial Overview
|
|
585
|
-
|
|
586
|
-
### Advanced Workflows
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
### Monthly Budget Review
|
|
590
|
-
|
|
591
|
-
```javascript
|
|
592
|
-
async function monthlyBudgetReview(budgetId, month = null) {
|
|
593
|
-
try {
|
|
594
|
-
// Use current month if not specified
|
|
595
|
-
if (!month) {
|
|
596
|
-
const now = new Date();
|
|
597
|
-
month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-01`;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
console.log(`Monthly Budget Review for ${month}`);
|
|
601
|
-
console.log('=====================================');
|
|
602
|
-
|
|
603
|
-
// Get monthly data
|
|
604
|
-
const monthResult = await client.callTool('get_month', {
|
|
605
|
-
budget_id: budgetId,
|
|
606
|
-
month: month
|
|
607
|
-
});
|
|
608
|
-
const monthData = JSON.parse(monthResult.content[0].text);
|
|
609
|
-
|
|
610
|
-
// Get category structure for names
|
|
611
|
-
const categoriesResult = await client.callTool('list_categories', {
|
|
612
|
-
budget_id: budgetId
|
|
613
|
-
});
|
|
614
|
-
const categoriesData = JSON.parse(categoriesResult.content[0].text);
|
|
615
|
-
|
|
616
|
-
// Create category lookup
|
|
617
|
-
const categoryLookup = {};
|
|
618
|
-
categoriesData.category_groups.forEach(group => {
|
|
619
|
-
group.categories.forEach(category => {
|
|
620
|
-
categoryLookup[category.id] = {
|
|
621
|
-
name: category.name,
|
|
622
|
-
groupName: group.name
|
|
623
|
-
};
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
const review = {
|
|
628
|
-
month: month,
|
|
629
|
-
income: monthData.month.income / 1000,
|
|
630
|
-
budgeted: monthData.month.budgeted / 1000,
|
|
631
|
-
activity: monthData.month.activity / 1000,
|
|
632
|
-
toBeBudgeted: monthData.month.to_be_budgeted / 1000,
|
|
633
|
-
categoryPerformance: []
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
console.log(`Income: $${review.income.toFixed(2)}`);
|
|
637
|
-
console.log(`Budgeted: $${review.budgeted.toFixed(2)}`);
|
|
638
|
-
console.log(`Activity: $${review.activity.toFixed(2)}`);
|
|
639
|
-
console.log(`To Be Budgeted: $${review.toBeBudgeted.toFixed(2)}`);
|
|
640
|
-
|
|
641
|
-
console.log('\nCategory Performance:');
|
|
642
|
-
monthData.month.categories.forEach(category => {
|
|
643
|
-
const categoryInfo = categoryLookup[category.id];
|
|
644
|
-
if (!categoryInfo) return;
|
|
645
|
-
|
|
646
|
-
const budgeted = category.budgeted / 1000;
|
|
647
|
-
const activity = category.activity / 1000;
|
|
648
|
-
const balance = category.balance / 1000;
|
|
649
|
-
|
|
650
|
-
if (budgeted === 0 && activity === 0) return; // Skip inactive categories
|
|
651
|
-
|
|
652
|
-
const performance = {
|
|
653
|
-
name: categoryInfo.name,
|
|
654
|
-
group: categoryInfo.groupName,
|
|
655
|
-
budgeted: budgeted,
|
|
656
|
-
activity: activity,
|
|
657
|
-
balance: balance,
|
|
658
|
-
percentUsed: budgeted !== 0 ? (Math.abs(activity) / budgeted) * 100 : 0,
|
|
659
|
-
status: balance < 0 ? 'OVERSPENT' : balance > budgeted * 0.8 ? 'UNDERUSED' : 'GOOD'
|
|
660
|
-
};
|
|
661
|
-
|
|
662
|
-
review.categoryPerformance.push(performance);
|
|
663
|
-
|
|
664
|
-
const status = performance.status === 'OVERSPENT' ? '🔴' :
|
|
665
|
-
performance.status === 'UNDERUSED' ? '🟡' : '🟢';
|
|
666
|
-
|
|
667
|
-
console.log(`${status} ${categoryInfo.name}: $${activity.toFixed(2)} of $${budgeted.toFixed(2)} (${performance.percentUsed.toFixed(1)}%)`);
|
|
668
|
-
});
|
|
669
|
-
|
|
670
|
-
return review;
|
|
671
|
-
} catch (error) {
|
|
672
|
-
console.error('Failed to generate monthly review:', error.message);
|
|
673
|
-
throw error;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
```
|
|
677
|
-
|
|
678
|
-
### Account Reconciliation Helper
|
|
679
|
-
|
|
680
|
-
```javascript
|
|
681
|
-
async function reconcileAccount(budgetId, accountId, statementBalance, statementDate) {
|
|
682
|
-
try {
|
|
683
|
-
console.log('Account Reconciliation Helper');
|
|
684
|
-
console.log('============================');
|
|
685
|
-
|
|
686
|
-
// Get account details
|
|
687
|
-
const accountResult = await client.callTool('get_account', {
|
|
688
|
-
budget_id: budgetId,
|
|
689
|
-
account_id: accountId
|
|
690
|
-
});
|
|
691
|
-
const accountData = JSON.parse(accountResult.content[0].text);
|
|
692
|
-
const account = accountData.account;
|
|
693
|
-
|
|
694
|
-
console.log(`Account: ${account.name}`);
|
|
695
|
-
console.log(`YNAB Balance: $${(account.balance / 1000).toFixed(2)}`);
|
|
696
|
-
console.log(`Statement Balance: $${statementBalance.toFixed(2)}`);
|
|
697
|
-
console.log(`Statement Date: ${statementDate}`);
|
|
698
|
-
|
|
699
|
-
// Get uncleared transactions
|
|
700
|
-
const transactionsResult = await client.callTool('list_transactions', {
|
|
701
|
-
budget_id: budgetId,
|
|
702
|
-
account_id: accountId,
|
|
703
|
-
since_date: statementDate
|
|
704
|
-
});
|
|
705
|
-
const transactionsData = JSON.parse(transactionsResult.content[0].text);
|
|
706
|
-
|
|
707
|
-
const unclearedTransactions = transactionsData.transactions.filter(
|
|
708
|
-
transaction => transaction.cleared === 'uncleared'
|
|
709
|
-
);
|
|
710
|
-
|
|
711
|
-
const clearedBalance = account.cleared_balance / 1000;
|
|
712
|
-
const unclearedAmount = unclearedTransactions.reduce(
|
|
713
|
-
(sum, transaction) => sum + (transaction.amount / 1000), 0
|
|
714
|
-
);
|
|
715
|
-
|
|
716
|
-
console.log(`\nCleared Balance: $${clearedBalance.toFixed(2)}`);
|
|
717
|
-
console.log(`Uncleared Amount: $${unclearedAmount.toFixed(2)}`);
|
|
718
|
-
console.log(`Difference from Statement: $${(clearedBalance - statementBalance).toFixed(2)}`);
|
|
719
|
-
|
|
720
|
-
if (Math.abs(clearedBalance - statementBalance) > 0.01) {
|
|
721
|
-
console.log('\n⚠️ Reconciliation needed!');
|
|
722
|
-
console.log('Uncleared transactions:');
|
|
723
|
-
unclearedTransactions.forEach(transaction => {
|
|
724
|
-
const amount = (transaction.amount / 1000).toFixed(2);
|
|
725
|
-
const sign = transaction.amount < 0 ? '-' : '+';
|
|
726
|
-
console.log(` ${transaction.date}: ${transaction.payee_name} ${sign}$${Math.abs(amount)}`);
|
|
727
|
-
});
|
|
728
|
-
} else {
|
|
729
|
-
console.log('\n✅ Account is reconciled!');
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
return {
|
|
733
|
-
account: account,
|
|
734
|
-
clearedBalance: clearedBalance,
|
|
735
|
-
statementBalance: statementBalance,
|
|
736
|
-
difference: clearedBalance - statementBalance,
|
|
737
|
-
unclearedTransactions: unclearedTransactions,
|
|
738
|
-
isReconciled: Math.abs(clearedBalance - statementBalance) <= 0.01
|
|
739
|
-
};
|
|
740
|
-
} catch (error) {
|
|
741
|
-
console.error('Failed to reconcile account:', error.message);
|
|
742
|
-
throw error;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
## Integration Examples
|
|
748
|
-
|
|
749
|
-
### Express.js API Integration
|
|
750
|
-
|
|
751
|
-
```javascript
|
|
752
|
-
const express = require('express');
|
|
753
|
-
const app = express();
|
|
754
|
-
|
|
755
|
-
app.use(express.json());
|
|
756
|
-
|
|
757
|
-
// Middleware to validate YNAB connection
|
|
758
|
-
async function validateYNABConnection(req, res, next) {
|
|
759
|
-
try {
|
|
760
|
-
await client.callTool('get_user', {});
|
|
761
|
-
next();
|
|
762
|
-
} catch (error) {
|
|
763
|
-
res.status(401).json({ error: 'YNAB connection failed' });
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// Get budgets endpoint
|
|
768
|
-
app.get('/api/budgets', validateYNABConnection, async (req, res) => {
|
|
769
|
-
try {
|
|
770
|
-
const result = await client.callTool('list_budgets', {});
|
|
771
|
-
const data = JSON.parse(result.content[0].text);
|
|
772
|
-
res.json(data.budgets);
|
|
773
|
-
} catch (error) {
|
|
774
|
-
res.status(500).json({ error: error.message });
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
// Create transaction endpoint
|
|
779
|
-
app.post('/api/budgets/:budgetId/transactions', validateYNABConnection, async (req, res) => {
|
|
780
|
-
try {
|
|
781
|
-
const { budgetId } = req.params;
|
|
782
|
-
const { accountId, amount, payeeName, memo, date } = req.body;
|
|
783
|
-
|
|
784
|
-
// Validate required fields
|
|
785
|
-
if (!accountId || !amount || !payeeName || !date) {
|
|
786
|
-
return res.status(400).json({
|
|
787
|
-
error: 'Missing required fields: accountId, amount, payeeName, date'
|
|
788
|
-
});
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
// Convert amount to milliunits
|
|
792
|
-
const milliunits = await convertToMilliunits(Math.abs(amount));
|
|
793
|
-
const transactionAmount = amount < 0 ? -milliunits : milliunits;
|
|
794
|
-
|
|
795
|
-
const result = await client.callTool('create_transaction', {
|
|
796
|
-
budget_id: budgetId,
|
|
797
|
-
account_id: accountId,
|
|
798
|
-
amount: transactionAmount,
|
|
799
|
-
date: date,
|
|
800
|
-
payee_name: payeeName,
|
|
801
|
-
memo: memo || '',
|
|
802
|
-
cleared: 'uncleared',
|
|
803
|
-
approved: true
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
const data = JSON.parse(result.content[0].text);
|
|
807
|
-
res.status(201).json(data.transaction);
|
|
808
|
-
} catch (error) {
|
|
809
|
-
res.status(500).json({ error: error.message });
|
|
810
|
-
}
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
app.listen(3000, () => {
|
|
814
|
-
console.log('YNAB API server running on port 3000');
|
|
815
|
-
});
|
|
816
|
-
```
|
|
817
|
-
|
|
818
|
-
### Slack Bot Integration
|
|
819
|
-
|
|
820
|
-
```javascript
|
|
821
|
-
const { App } = require('@slack/bolt');
|
|
822
|
-
|
|
823
|
-
const app = new App({
|
|
824
|
-
token: process.env.SLACK_BOT_TOKEN,
|
|
825
|
-
signingSecret: process.env.SLACK_SIGNING_SECRET
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
// Command to add expense
|
|
829
|
-
app.command('/expense', async ({ command, ack, respond }) => {
|
|
830
|
-
await ack();
|
|
831
|
-
|
|
832
|
-
try {
|
|
833
|
-
// Parse command text: "/expense 25.50 Coffee Shop Starbucks"
|
|
834
|
-
const parts = command.text.split(' ');
|
|
835
|
-
const amount = parseFloat(parts[0]);
|
|
836
|
-
const payeeName = parts.slice(1).join(' ');
|
|
837
|
-
|
|
838
|
-
if (isNaN(amount) || !payeeName) {
|
|
839
|
-
await respond('Usage: /expense <amount> <payee name>');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
// Get user's default budget and account (you'd store this per user)
|
|
844
|
-
const budgetId = 'user-default-budget-id';
|
|
845
|
-
const accountId = 'user-default-account-id';
|
|
846
|
-
|
|
847
|
-
// Create transaction
|
|
848
|
-
const milliunits = await convertToMilliunits(amount);
|
|
849
|
-
const result = await client.callTool('create_transaction', {
|
|
850
|
-
budget_id: budgetId,
|
|
851
|
-
account_id: accountId,
|
|
852
|
-
amount: -milliunits, // Negative for expense
|
|
853
|
-
date: new Date().toISOString().split('T')[0],
|
|
854
|
-
payee_name: payeeName,
|
|
855
|
-
cleared: 'uncleared',
|
|
856
|
-
approved: true
|
|
857
|
-
});
|
|
858
|
-
|
|
859
|
-
await respond(`✅ Added expense: ${payeeName} for $${amount.toFixed(2)}`);
|
|
860
|
-
} catch (error) {
|
|
861
|
-
await respond(`❌ Failed to add expense: ${error.message}`);
|
|
862
|
-
}
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
// Command to check balance
|
|
866
|
-
app.command('/balance', async ({ command, ack, respond }) => {
|
|
867
|
-
await ack();
|
|
868
|
-
|
|
869
|
-
try {
|
|
870
|
-
const budgetId = 'user-default-budget-id';
|
|
871
|
-
|
|
872
|
-
const result = await client.callTool('list_accounts', {
|
|
873
|
-
budget_id: budgetId
|
|
874
|
-
});
|
|
875
|
-
const data = JSON.parse(result.content[0].text);
|
|
876
|
-
|
|
877
|
-
const balanceText = data.accounts
|
|
878
|
-
.filter(account => !account.closed && account.on_budget)
|
|
879
|
-
.map(account => {
|
|
880
|
-
const balance = (account.balance / 1000).toFixed(2);
|
|
881
|
-
return `${account.name}: $${balance}`;
|
|
882
|
-
})
|
|
883
|
-
.join('\n');
|
|
884
|
-
|
|
885
|
-
await respond(`💰 Account Balances:\n${balanceText}`);
|
|
886
|
-
} catch (error) {
|
|
887
|
-
await respond(`❌ Failed to get balances: ${error.message}`);
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
|
|
891
|
-
(async () => {
|
|
892
|
-
await app.start(process.env.PORT || 3000);
|
|
893
|
-
console.log('⚡️ Slack bot is running!');
|
|
894
|
-
})();
|
|
895
|
-
```
|
|
896
|
-
|
|
897
|
-
### CSV Import Script
|
|
898
|
-
|
|
899
|
-
```javascript
|
|
900
|
-
const fs = require('fs');
|
|
901
|
-
const csv = require('csv-parser');
|
|
902
|
-
|
|
903
|
-
async function importCSV(budgetId, accountId, csvFilePath) {
|
|
904
|
-
const transactions = [];
|
|
905
|
-
|
|
906
|
-
return new Promise((resolve, reject) => {
|
|
907
|
-
fs.createReadStream(csvFilePath)
|
|
908
|
-
.pipe(csv())
|
|
909
|
-
.on('data', (row) => {
|
|
910
|
-
// Assuming CSV format: Date,Payee,Amount,Memo
|
|
911
|
-
transactions.push({
|
|
912
|
-
date: row.Date,
|
|
913
|
-
payeeName: row.Payee,
|
|
914
|
-
amount: parseFloat(row.Amount),
|
|
915
|
-
memo: row.Memo || ''
|
|
916
|
-
});
|
|
917
|
-
})
|
|
918
|
-
.on('end', async () => {
|
|
919
|
-
try {
|
|
920
|
-
console.log(`Importing ${transactions.length} transactions from CSV...`);
|
|
921
|
-
|
|
922
|
-
const results = await importTransactions(budgetId, transactions.map(t => ({
|
|
923
|
-
...t,
|
|
924
|
-
accountId: accountId
|
|
925
|
-
})));
|
|
926
|
-
|
|
927
|
-
resolve(results);
|
|
928
|
-
} catch (error) {
|
|
929
|
-
reject(error);
|
|
930
|
-
}
|
|
931
|
-
})
|
|
932
|
-
.on('error', reject);
|
|
933
|
-
});
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Usage
|
|
937
|
-
const importResults = await importCSV(
|
|
938
|
-
'budget-id',
|
|
939
|
-
'account-id',
|
|
940
|
-
'./transactions.csv'
|
|
941
|
-
);
|
|
942
|
-
|
|
943
|
-
console.log(`Import completed: ${importResults.results.length} successful, ${importResults.errors.length} failed`);
|
|
944
|
-
```
|
|
945
|
-
|
|
946
|
-
These examples demonstrate practical usage patterns for the YNAB MCP Server. Each example includes proper error handling, data validation, and follows best practices for working with the YNAB API through the MCP server.
|