@opentabs-dev/opentabs-plugin-ynab 0.0.85 → 0.0.86
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/dist/adapter.iife.js +870 -523
- package/dist/adapter.iife.js.map +4 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -16
- package/dist/index.js.map +1 -1
- package/dist/tools/create-transaction.d.ts +2 -2
- package/dist/tools/create-transaction.d.ts.map +1 -1
- package/dist/tools/create-transaction.js +63 -22
- package/dist/tools/create-transaction.js.map +1 -1
- package/dist/tools/delete-transaction.d.ts.map +1 -1
- package/dist/tools/delete-transaction.js +22 -7
- package/dist/tools/delete-transaction.js.map +1 -1
- package/dist/tools/get-account.d.ts.map +1 -1
- package/dist/tools/get-account.js +3 -3
- package/dist/tools/get-account.js.map +1 -1
- package/dist/tools/get-month.d.ts +2 -1
- package/dist/tools/get-month.d.ts.map +1 -1
- package/dist/tools/get-month.js +10 -33
- package/dist/tools/get-month.js.map +1 -1
- package/dist/tools/get-plan.d.ts.map +1 -1
- package/dist/tools/get-plan.js +12 -5
- package/dist/tools/get-plan.js.map +1 -1
- package/dist/tools/get-transaction.d.ts +2 -2
- package/dist/tools/get-transaction.d.ts.map +1 -1
- package/dist/tools/get-transaction.js +5 -4
- package/dist/tools/get-transaction.js.map +1 -1
- package/dist/tools/list-accounts.d.ts.map +1 -1
- package/dist/tools/list-accounts.js +4 -4
- package/dist/tools/list-accounts.js.map +1 -1
- package/dist/tools/list-categories.d.ts.map +1 -1
- package/dist/tools/list-categories.js +9 -29
- package/dist/tools/list-categories.js.map +1 -1
- package/dist/tools/list-months.d.ts +1 -1
- package/dist/tools/list-months.d.ts.map +1 -1
- package/dist/tools/list-months.js +5 -14
- package/dist/tools/list-months.js.map +1 -1
- package/dist/tools/list-payees.d.ts.map +1 -1
- package/dist/tools/list-payees.js +3 -3
- package/dist/tools/list-payees.js.map +1 -1
- package/dist/tools/list-scheduled-transactions.d.ts.map +1 -1
- package/dist/tools/list-scheduled-transactions.js +7 -5
- package/dist/tools/list-scheduled-transactions.js.map +1 -1
- package/dist/tools/list-transactions.d.ts +4 -2
- package/dist/tools/list-transactions.d.ts.map +1 -1
- package/dist/tools/list-transactions.js +40 -8
- package/dist/tools/list-transactions.js.map +1 -1
- package/dist/tools/move-category-budget.d.ts +24 -0
- package/dist/tools/move-category-budget.d.ts.map +1 -0
- package/dist/tools/move-category-budget.js +105 -0
- package/dist/tools/move-category-budget.js.map +1 -0
- package/dist/tools/schemas.d.ts +105 -27
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +176 -27
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/update-category-budget.d.ts.map +1 -1
- package/dist/tools/update-category-budget.js +55 -27
- package/dist/tools/update-category-budget.js.map +1 -1
- package/dist/tools/update-transaction.d.ts +3 -2
- package/dist/tools/update-transaction.d.ts.map +1 -1
- package/dist/tools/update-transaction.js +84 -31
- package/dist/tools/update-transaction.js.map +1 -1
- package/dist/tools.json +192 -43
- package/dist/ynab-api.d.ts +4 -1
- package/dist/ynab-api.d.ts.map +1 -1
- package/dist/ynab-api.js +47 -26
- package/dist/ynab-api.js.map +1 -1
- package/package.json +3 -3
package/dist/adapter.iife.js
CHANGED
|
@@ -329,162 +329,6 @@
|
|
|
329
329
|
configSchema;
|
|
330
330
|
};
|
|
331
331
|
|
|
332
|
-
// src/ynab-api.ts
|
|
333
|
-
var generateDeviceId = () => crypto.randomUUID();
|
|
334
|
-
var extractPlanId = () => {
|
|
335
|
-
const url2 = getCurrentUrl();
|
|
336
|
-
const match = url2.match(/app\.ynab\.com\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
|
|
337
|
-
return match?.[1] ?? null;
|
|
338
|
-
};
|
|
339
|
-
var getAuth = () => {
|
|
340
|
-
const cached2 = getAuthCache("ynab");
|
|
341
|
-
if (cached2?.sessionToken && cached2.planId) return cached2;
|
|
342
|
-
const sessionToken = getMetaContent("session-token");
|
|
343
|
-
if (!sessionToken) return null;
|
|
344
|
-
const user = getPageGlobal("YNAB_CLIENT_CONSTANTS.USER");
|
|
345
|
-
if (!user?.id) return null;
|
|
346
|
-
const planId = extractPlanId();
|
|
347
|
-
if (!planId) return null;
|
|
348
|
-
const deviceId = cached2?.deviceId ?? generateDeviceId();
|
|
349
|
-
const auth = {
|
|
350
|
-
sessionToken,
|
|
351
|
-
deviceId,
|
|
352
|
-
userId: user.id,
|
|
353
|
-
planId
|
|
354
|
-
};
|
|
355
|
-
setAuthCache("ynab", auth);
|
|
356
|
-
return auth;
|
|
357
|
-
};
|
|
358
|
-
var isAuthenticated = () => getAuth() !== null;
|
|
359
|
-
var waitForAuth = () => waitUntil(() => isAuthenticated(), { interval: 500, timeout: 5e3 }).then(
|
|
360
|
-
() => true,
|
|
361
|
-
() => false
|
|
362
|
-
);
|
|
363
|
-
var getPlanId = () => {
|
|
364
|
-
const auth = getAuth();
|
|
365
|
-
if (!auth) throw ToolError.auth("Not authenticated \u2014 please log in to YNAB.");
|
|
366
|
-
return auth.planId;
|
|
367
|
-
};
|
|
368
|
-
var getHeaders = () => {
|
|
369
|
-
const auth = getAuth();
|
|
370
|
-
if (!auth) throw ToolError.auth("Not authenticated \u2014 please log in to YNAB.");
|
|
371
|
-
const appVersion = getPageGlobal("YNAB_CLIENT_CONSTANTS.YNAB_APP_VERSION");
|
|
372
|
-
const headers = {
|
|
373
|
-
"X-Session-Token": auth.sessionToken,
|
|
374
|
-
"X-YNAB-Device-Id": auth.deviceId,
|
|
375
|
-
"X-YNAB-Device-OS": "web",
|
|
376
|
-
"X-Requested-With": "XMLHttpRequest",
|
|
377
|
-
Accept: "application/json, text/javascript, */*; q=0.01"
|
|
378
|
-
};
|
|
379
|
-
if (appVersion) headers["X-YNAB-Device-App-Version"] = appVersion;
|
|
380
|
-
return headers;
|
|
381
|
-
};
|
|
382
|
-
var handleApiError = async (response, context) => {
|
|
383
|
-
const errorBody = (await response.text().catch(() => "")).substring(0, 512);
|
|
384
|
-
if (response.status === 426) {
|
|
385
|
-
clearAuthCache("ynab");
|
|
386
|
-
throw ToolError.auth(
|
|
387
|
-
"YNAB requires an app update (426). The session has been cleared \u2014 please reload the YNAB tab and try again."
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
if (response.status === 429) {
|
|
391
|
-
const retryAfter = response.headers.get("Retry-After");
|
|
392
|
-
const retryMs = retryAfter !== null ? parseRetryAfterMs(retryAfter) : void 0;
|
|
393
|
-
throw ToolError.rateLimited(`Rate limited: ${context} \u2014 ${errorBody}`, retryMs);
|
|
394
|
-
}
|
|
395
|
-
if (response.status === 401 || response.status === 403) {
|
|
396
|
-
clearAuthCache("ynab");
|
|
397
|
-
throw ToolError.auth(`Auth error (${response.status}): ${errorBody}`);
|
|
398
|
-
}
|
|
399
|
-
if (response.status === 404) throw ToolError.notFound(`Not found: ${context} \u2014 ${errorBody}`);
|
|
400
|
-
if (response.status === 422) throw ToolError.validation(`Validation error: ${context} \u2014 ${errorBody}`);
|
|
401
|
-
throw ToolError.internal(`API error (${response.status}): ${context} \u2014 ${errorBody}`);
|
|
402
|
-
};
|
|
403
|
-
var catalog = async (operationName, requestData = {}) => {
|
|
404
|
-
const headers = getHeaders();
|
|
405
|
-
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8";
|
|
406
|
-
let response;
|
|
407
|
-
try {
|
|
408
|
-
response = await fetch("/api/v1/catalog", {
|
|
409
|
-
method: "POST",
|
|
410
|
-
headers,
|
|
411
|
-
credentials: "include",
|
|
412
|
-
body: `operation_name=${encodeURIComponent(operationName)}&request_data=${encodeURIComponent(JSON.stringify(requestData))}`,
|
|
413
|
-
signal: AbortSignal.timeout(3e4)
|
|
414
|
-
});
|
|
415
|
-
} catch (err2) {
|
|
416
|
-
if (err2 instanceof DOMException && err2.name === "TimeoutError")
|
|
417
|
-
throw ToolError.timeout(`Catalog request timed out: ${operationName}`);
|
|
418
|
-
if (err2 instanceof DOMException && err2.name === "AbortError") throw new ToolError("Request was aborted", "aborted");
|
|
419
|
-
throw new ToolError(`Network error: ${err2 instanceof Error ? err2.message : String(err2)}`, "network_error", {
|
|
420
|
-
category: "internal",
|
|
421
|
-
retryable: true
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
if (!response.ok) return handleApiError(response, operationName);
|
|
425
|
-
const data = await response.json();
|
|
426
|
-
if (data.error) {
|
|
427
|
-
throw ToolError.internal(`Catalog error (${operationName}): ${data.error.message}`);
|
|
428
|
-
}
|
|
429
|
-
return data;
|
|
430
|
-
};
|
|
431
|
-
var BUDGET_SCHEMA_VERSION = 41;
|
|
432
|
-
var syncBudget = async (planId) => catalog("syncBudgetData", {
|
|
433
|
-
budget_version_id: planId,
|
|
434
|
-
sync_type: "delta",
|
|
435
|
-
starting_device_knowledge: 0,
|
|
436
|
-
ending_device_knowledge: 0,
|
|
437
|
-
device_knowledge_of_server: 0,
|
|
438
|
-
calculated_entities_included: false,
|
|
439
|
-
schema_version: BUDGET_SCHEMA_VERSION,
|
|
440
|
-
schema_version_of_knowledge: BUDGET_SCHEMA_VERSION,
|
|
441
|
-
changed_entities: {}
|
|
442
|
-
});
|
|
443
|
-
var syncWrite = async (planId, changedEntities) => {
|
|
444
|
-
const readResult = await syncBudget(planId);
|
|
445
|
-
const serverKnowledge = readResult.current_server_knowledge ?? 0;
|
|
446
|
-
return catalog("syncBudgetData", {
|
|
447
|
-
budget_version_id: planId,
|
|
448
|
-
sync_type: "delta",
|
|
449
|
-
starting_device_knowledge: 0,
|
|
450
|
-
ending_device_knowledge: 1,
|
|
451
|
-
device_knowledge_of_server: serverKnowledge,
|
|
452
|
-
calculated_entities_included: false,
|
|
453
|
-
schema_version: BUDGET_SCHEMA_VERSION,
|
|
454
|
-
schema_version_of_knowledge: BUDGET_SCHEMA_VERSION,
|
|
455
|
-
changed_entities: changedEntities
|
|
456
|
-
});
|
|
457
|
-
};
|
|
458
|
-
var api = async (endpoint, options = {}) => {
|
|
459
|
-
const headers = getHeaders();
|
|
460
|
-
let fetchBody;
|
|
461
|
-
if (options.body) {
|
|
462
|
-
headers["Content-Type"] = "application/json";
|
|
463
|
-
fetchBody = JSON.stringify(options.body);
|
|
464
|
-
}
|
|
465
|
-
let response;
|
|
466
|
-
try {
|
|
467
|
-
response = await fetch(`/api/v2${endpoint}`, {
|
|
468
|
-
method: options.method ?? "GET",
|
|
469
|
-
headers,
|
|
470
|
-
body: fetchBody,
|
|
471
|
-
credentials: "include",
|
|
472
|
-
signal: AbortSignal.timeout(3e4)
|
|
473
|
-
});
|
|
474
|
-
} catch (err2) {
|
|
475
|
-
if (err2 instanceof DOMException && err2.name === "TimeoutError")
|
|
476
|
-
throw ToolError.timeout(`API request timed out: ${endpoint}`);
|
|
477
|
-
if (err2 instanceof DOMException && err2.name === "AbortError") throw new ToolError("Request was aborted", "aborted");
|
|
478
|
-
throw new ToolError(`Network error: ${err2 instanceof Error ? err2.message : String(err2)}`, "network_error", {
|
|
479
|
-
category: "internal",
|
|
480
|
-
retryable: true
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
if (!response.ok) return handleApiError(response, endpoint);
|
|
484
|
-
if (response.status === 204) return {};
|
|
485
|
-
return await response.json();
|
|
486
|
-
};
|
|
487
|
-
|
|
488
332
|
// node_modules/zod/v4/classic/external.js
|
|
489
333
|
var external_exports = {};
|
|
490
334
|
__export(external_exports, {
|
|
@@ -14253,19 +14097,187 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14253
14097
|
// node_modules/zod/v4/classic/external.js
|
|
14254
14098
|
config(en_default());
|
|
14255
14099
|
|
|
14256
|
-
// src/
|
|
14257
|
-
var
|
|
14258
|
-
|
|
14259
|
-
|
|
14100
|
+
// src/ynab-api.ts
|
|
14101
|
+
var NOT_AUTHENTICATED_MESSAGE = "Not authenticated \u2014 please log in to YNAB.";
|
|
14102
|
+
var generateDeviceId = () => crypto.randomUUID();
|
|
14103
|
+
var extractPlanId = () => {
|
|
14104
|
+
const url2 = getCurrentUrl();
|
|
14105
|
+
const match = url2.match(/app\.ynab\.com\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
|
|
14106
|
+
return match?.[1] ?? null;
|
|
14260
14107
|
};
|
|
14261
|
-
var
|
|
14262
|
-
|
|
14263
|
-
|
|
14264
|
-
|
|
14108
|
+
var getAuth = () => {
|
|
14109
|
+
const cached2 = getAuthCache("ynab");
|
|
14110
|
+
if (cached2?.sessionToken && cached2.planId) return cached2;
|
|
14111
|
+
const sessionToken = getMetaContent("session-token");
|
|
14112
|
+
if (!sessionToken) return null;
|
|
14113
|
+
const user = getPageGlobal("YNAB_CLIENT_CONSTANTS.USER");
|
|
14114
|
+
if (!user?.id) return null;
|
|
14115
|
+
const planId = extractPlanId();
|
|
14116
|
+
if (!planId) return null;
|
|
14117
|
+
const deviceId = cached2?.deviceId ?? generateDeviceId();
|
|
14118
|
+
const auth = {
|
|
14119
|
+
sessionToken,
|
|
14120
|
+
deviceId,
|
|
14121
|
+
userId: user.id,
|
|
14122
|
+
planId
|
|
14123
|
+
};
|
|
14124
|
+
setAuthCache("ynab", auth);
|
|
14125
|
+
return auth;
|
|
14126
|
+
};
|
|
14127
|
+
var isAuthenticated = () => getAuth() !== null;
|
|
14128
|
+
var waitForAuth = () => waitUntil(() => isAuthenticated(), { interval: 500, timeout: 5e3 }).then(
|
|
14129
|
+
() => true,
|
|
14130
|
+
() => false
|
|
14131
|
+
);
|
|
14132
|
+
var getPlanId = () => {
|
|
14133
|
+
const auth = getAuth();
|
|
14134
|
+
if (!auth) throw ToolError.auth(NOT_AUTHENTICATED_MESSAGE);
|
|
14135
|
+
return auth.planId;
|
|
14136
|
+
};
|
|
14137
|
+
var getDeviceId = () => {
|
|
14138
|
+
const auth = getAuth();
|
|
14139
|
+
if (!auth) throw ToolError.auth(NOT_AUTHENTICATED_MESSAGE);
|
|
14140
|
+
return auth.deviceId;
|
|
14141
|
+
};
|
|
14142
|
+
var getUserId = () => {
|
|
14143
|
+
const auth = getAuth();
|
|
14144
|
+
if (!auth) throw ToolError.auth(NOT_AUTHENTICATED_MESSAGE);
|
|
14145
|
+
return auth.userId;
|
|
14146
|
+
};
|
|
14147
|
+
var assertAuthenticated = () => {
|
|
14148
|
+
if (!getAuth()) throw ToolError.auth(NOT_AUTHENTICATED_MESSAGE);
|
|
14149
|
+
};
|
|
14150
|
+
var getHeaders = () => {
|
|
14151
|
+
const auth = getAuth();
|
|
14152
|
+
if (!auth) throw ToolError.auth(NOT_AUTHENTICATED_MESSAGE);
|
|
14153
|
+
const appVersion = getPageGlobal("YNAB_CLIENT_CONSTANTS.YNAB_APP_VERSION");
|
|
14154
|
+
const headers = {
|
|
14155
|
+
"X-Session-Token": auth.sessionToken,
|
|
14156
|
+
"X-YNAB-Device-Id": auth.deviceId,
|
|
14157
|
+
"X-YNAB-Device-OS": "web",
|
|
14158
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
14159
|
+
Accept: "application/json, text/javascript, */*; q=0.01"
|
|
14160
|
+
};
|
|
14161
|
+
if (appVersion) headers["X-YNAB-Device-App-Version"] = appVersion;
|
|
14162
|
+
return headers;
|
|
14163
|
+
};
|
|
14164
|
+
var handleApiError = async (response, context) => {
|
|
14165
|
+
const errorBody = (await response.text().catch(() => "")).substring(0, 512);
|
|
14166
|
+
if (response.status === 426) {
|
|
14167
|
+
clearAuthCache("ynab");
|
|
14168
|
+
throw ToolError.auth(
|
|
14169
|
+
"YNAB requires an app update (426). The session has been cleared \u2014 please reload the YNAB tab and try again."
|
|
14170
|
+
);
|
|
14171
|
+
}
|
|
14172
|
+
if (response.status === 429) {
|
|
14173
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
14174
|
+
const retryMs = retryAfter !== null ? parseRetryAfterMs(retryAfter) : void 0;
|
|
14175
|
+
throw ToolError.rateLimited(`Rate limited: ${context} \u2014 ${errorBody}`, retryMs);
|
|
14176
|
+
}
|
|
14177
|
+
if (response.status === 401 || response.status === 403) {
|
|
14178
|
+
clearAuthCache("ynab");
|
|
14179
|
+
throw ToolError.auth(`Auth error (${response.status}): ${errorBody}`);
|
|
14180
|
+
}
|
|
14181
|
+
if (response.status === 404) throw ToolError.notFound(`Not found: ${context} \u2014 ${errorBody}`);
|
|
14182
|
+
if (response.status === 422) throw ToolError.validation(`Validation error: ${context} \u2014 ${errorBody}`);
|
|
14183
|
+
throw ToolError.internal(`API error (${response.status}): ${context} \u2014 ${errorBody}`);
|
|
14184
|
+
};
|
|
14185
|
+
var handleNetworkError = (err2, context) => {
|
|
14186
|
+
if (err2 instanceof DOMException && err2.name === "TimeoutError")
|
|
14187
|
+
throw ToolError.timeout(`Request timed out: ${context}`);
|
|
14188
|
+
if (err2 instanceof DOMException && err2.name === "AbortError") throw new ToolError("Request was aborted", "aborted");
|
|
14189
|
+
throw new ToolError(`Network error: ${err2 instanceof Error ? err2.message : String(err2)}`, "network_error", {
|
|
14190
|
+
category: "internal",
|
|
14191
|
+
retryable: true
|
|
14192
|
+
});
|
|
14193
|
+
};
|
|
14194
|
+
var catalog = async (operationName, requestData = {}) => {
|
|
14195
|
+
const headers = getHeaders();
|
|
14196
|
+
headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8";
|
|
14197
|
+
let response;
|
|
14198
|
+
try {
|
|
14199
|
+
response = await fetch("/api/v1/catalog", {
|
|
14200
|
+
method: "POST",
|
|
14201
|
+
headers,
|
|
14202
|
+
credentials: "include",
|
|
14203
|
+
body: `operation_name=${encodeURIComponent(operationName)}&request_data=${encodeURIComponent(JSON.stringify(requestData))}`,
|
|
14204
|
+
signal: AbortSignal.timeout(3e4)
|
|
14205
|
+
});
|
|
14206
|
+
} catch (err2) {
|
|
14207
|
+
return handleNetworkError(err2, operationName);
|
|
14208
|
+
}
|
|
14209
|
+
if (!response.ok) return handleApiError(response, operationName);
|
|
14210
|
+
const data = await response.json();
|
|
14211
|
+
if (data.error) {
|
|
14212
|
+
throw ToolError.internal(`Catalog error (${operationName}): ${data.error.message}`);
|
|
14213
|
+
}
|
|
14214
|
+
return data;
|
|
14215
|
+
};
|
|
14216
|
+
var BUDGET_SCHEMA_VERSION = 41;
|
|
14217
|
+
var syncBudget = async (planId) => catalog("syncBudgetData", {
|
|
14218
|
+
budget_version_id: planId,
|
|
14219
|
+
sync_type: "delta",
|
|
14220
|
+
starting_device_knowledge: 0,
|
|
14221
|
+
ending_device_knowledge: 0,
|
|
14222
|
+
device_knowledge_of_server: 0,
|
|
14223
|
+
calculated_entities_included: false,
|
|
14224
|
+
schema_version: BUDGET_SCHEMA_VERSION,
|
|
14225
|
+
schema_version_of_knowledge: BUDGET_SCHEMA_VERSION,
|
|
14226
|
+
changed_entities: {}
|
|
14265
14227
|
});
|
|
14266
|
-
var
|
|
14267
|
-
|
|
14268
|
-
|
|
14228
|
+
var syncWrite = async (planId, changedEntities, serverKnowledge) => {
|
|
14229
|
+
const knowledge = serverKnowledge ?? (await syncBudget(planId)).current_server_knowledge ?? 0;
|
|
14230
|
+
return catalog("syncBudgetData", {
|
|
14231
|
+
budget_version_id: planId,
|
|
14232
|
+
sync_type: "delta",
|
|
14233
|
+
starting_device_knowledge: 0,
|
|
14234
|
+
ending_device_knowledge: 1,
|
|
14235
|
+
device_knowledge_of_server: knowledge,
|
|
14236
|
+
calculated_entities_included: false,
|
|
14237
|
+
schema_version: BUDGET_SCHEMA_VERSION,
|
|
14238
|
+
schema_version_of_knowledge: BUDGET_SCHEMA_VERSION,
|
|
14239
|
+
changed_entities: changedEntities
|
|
14240
|
+
});
|
|
14241
|
+
};
|
|
14242
|
+
var api = async (endpoint, options = {}) => {
|
|
14243
|
+
const headers = getHeaders();
|
|
14244
|
+
let fetchBody;
|
|
14245
|
+
if (options.body) {
|
|
14246
|
+
headers["Content-Type"] = "application/json";
|
|
14247
|
+
fetchBody = JSON.stringify(options.body);
|
|
14248
|
+
}
|
|
14249
|
+
let response;
|
|
14250
|
+
try {
|
|
14251
|
+
response = await fetch(`/api/v2${endpoint}`, {
|
|
14252
|
+
method: options.method ?? "GET",
|
|
14253
|
+
headers,
|
|
14254
|
+
body: fetchBody,
|
|
14255
|
+
credentials: "include",
|
|
14256
|
+
signal: AbortSignal.timeout(3e4)
|
|
14257
|
+
});
|
|
14258
|
+
} catch (err2) {
|
|
14259
|
+
return handleNetworkError(err2, endpoint);
|
|
14260
|
+
}
|
|
14261
|
+
if (!response.ok) return handleApiError(response, endpoint);
|
|
14262
|
+
if (response.status === 204) return {};
|
|
14263
|
+
return await response.json();
|
|
14264
|
+
};
|
|
14265
|
+
|
|
14266
|
+
// src/tools/schemas.ts
|
|
14267
|
+
var formatMilliunits = (milliunits) => {
|
|
14268
|
+
const amount = milliunits / 1e3;
|
|
14269
|
+
return amount.toFixed(2);
|
|
14270
|
+
};
|
|
14271
|
+
var toMilliunits = (amount) => Math.round(amount * 1e3);
|
|
14272
|
+
var notTombstone = (x) => !x.is_tombstone;
|
|
14273
|
+
var userSchema = external_exports.object({
|
|
14274
|
+
id: external_exports.string().describe("User ID"),
|
|
14275
|
+
first_name: external_exports.string().describe("First name"),
|
|
14276
|
+
email: external_exports.string().describe("Email address")
|
|
14277
|
+
});
|
|
14278
|
+
var planSchema = external_exports.object({
|
|
14279
|
+
id: external_exports.string().describe("Plan (budget version) ID used in API calls"),
|
|
14280
|
+
budget_id: external_exports.string().describe("Underlying budget ID"),
|
|
14269
14281
|
name: external_exports.string().describe("Plan name"),
|
|
14270
14282
|
date_format: external_exports.string().describe("Date format string (e.g. MM/DD/YYYY)"),
|
|
14271
14283
|
currency_symbol: external_exports.string().describe("Currency symbol (e.g. $)"),
|
|
@@ -14317,7 +14329,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14317
14329
|
cleared: external_exports.string().describe("Cleared status: cleared, uncleared, or reconciled"),
|
|
14318
14330
|
approved: external_exports.boolean().describe("Whether the transaction is approved"),
|
|
14319
14331
|
flag_color: external_exports.string().describe("Flag color or empty string"),
|
|
14320
|
-
flag_name: external_exports.string().describe("Custom flag name or empty string"),
|
|
14321
14332
|
account_id: external_exports.string().describe("Account ID"),
|
|
14322
14333
|
account_name: external_exports.string().describe("Account name"),
|
|
14323
14334
|
payee_id: external_exports.string().describe("Payee ID"),
|
|
@@ -14325,7 +14336,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14325
14336
|
category_id: external_exports.string().describe("Category ID"),
|
|
14326
14337
|
category_name: external_exports.string().describe("Category name"),
|
|
14327
14338
|
transfer_account_id: external_exports.string().describe("If a transfer, the destination account ID"),
|
|
14328
|
-
|
|
14339
|
+
imported_payee: external_exports.string().describe("Bank-imported payee name after YNAB cleansing (empty if manually entered)"),
|
|
14340
|
+
original_imported_payee: external_exports.string().describe("Raw payee string from the bank feed before any cleansing (empty if manually entered)"),
|
|
14329
14341
|
deleted: external_exports.boolean().describe("Whether the transaction is deleted")
|
|
14330
14342
|
});
|
|
14331
14343
|
var subtransactionSchema = external_exports.object({
|
|
@@ -14351,7 +14363,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14351
14363
|
budgeted_milliunits: external_exports.number().describe("Total budgeted in milliunits"),
|
|
14352
14364
|
activity_milliunits: external_exports.number().describe("Total activity in milliunits"),
|
|
14353
14365
|
to_be_budgeted_milliunits: external_exports.number().describe("Ready to Assign in milliunits"),
|
|
14354
|
-
age_of_money: external_exports.number().describe("Age of money in days")
|
|
14366
|
+
age_of_money: external_exports.number().nullable().describe("Age of money in days, or null if not yet computed")
|
|
14355
14367
|
});
|
|
14356
14368
|
var scheduledTransactionSchema = external_exports.object({
|
|
14357
14369
|
id: external_exports.string().describe("Scheduled transaction ID"),
|
|
@@ -14372,6 +14384,119 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14372
14384
|
category_name: external_exports.string().describe("Category name"),
|
|
14373
14385
|
deleted: external_exports.boolean().describe("Whether the scheduled transaction is deleted")
|
|
14374
14386
|
});
|
|
14387
|
+
var MONEY_MOVEMENT_SOURCE = {
|
|
14388
|
+
/** RTA ↔ category (in either direction). */
|
|
14389
|
+
ASSIGN: "manual_assign",
|
|
14390
|
+
/** Category-to-category transfer. */
|
|
14391
|
+
MOVEMENT: "manual_movement"
|
|
14392
|
+
};
|
|
14393
|
+
var SUBCATEGORY_BUDGET_PREFIX = "mcb";
|
|
14394
|
+
var MONTHLY_BUDGET_PREFIX = "mb";
|
|
14395
|
+
var toMonthKey = (month) => month.substring(0, 7);
|
|
14396
|
+
var currentMonthKey = () => {
|
|
14397
|
+
const now = /* @__PURE__ */ new Date();
|
|
14398
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
14399
|
+
};
|
|
14400
|
+
var formatSubcategoryBudgetId = (monthKey, categoryId) => `${SUBCATEGORY_BUDGET_PREFIX}/${monthKey}/${categoryId}`;
|
|
14401
|
+
var formatMonthlyBudgetId = (monthKey, planId) => `${MONTHLY_BUDGET_PREFIX}/${monthKey}/${planId}`;
|
|
14402
|
+
var CLEARED_MAP = {
|
|
14403
|
+
cleared: "Cleared",
|
|
14404
|
+
uncleared: "Uncleared",
|
|
14405
|
+
reconciled: "Reconciled"
|
|
14406
|
+
};
|
|
14407
|
+
var FLAG_MAP = {
|
|
14408
|
+
red: "Red",
|
|
14409
|
+
orange: "Orange",
|
|
14410
|
+
yellow: "Yellow",
|
|
14411
|
+
green: "Green",
|
|
14412
|
+
blue: "Blue",
|
|
14413
|
+
purple: "Purple"
|
|
14414
|
+
};
|
|
14415
|
+
var resolvePayee = (existingPayees, payeeName) => {
|
|
14416
|
+
const target = payeeName.toLowerCase();
|
|
14417
|
+
const match = existingPayees.find((p) => notTombstone(p) && p.name?.toLowerCase() === target);
|
|
14418
|
+
if (match?.id) return { payeeId: match.id };
|
|
14419
|
+
const payeeId = crypto.randomUUID();
|
|
14420
|
+
const newPayee = {
|
|
14421
|
+
id: payeeId,
|
|
14422
|
+
is_tombstone: false,
|
|
14423
|
+
entities_account_id: null,
|
|
14424
|
+
enabled: true,
|
|
14425
|
+
auto_fill_subcategory_id: null,
|
|
14426
|
+
auto_fill_memo: null,
|
|
14427
|
+
auto_fill_amount: 0,
|
|
14428
|
+
auto_fill_subcategory_enabled: true,
|
|
14429
|
+
auto_fill_memo_enabled: false,
|
|
14430
|
+
auto_fill_amount_enabled: false,
|
|
14431
|
+
rename_on_import_enabled: true,
|
|
14432
|
+
name: payeeName,
|
|
14433
|
+
internal_name: null
|
|
14434
|
+
};
|
|
14435
|
+
return { payeeId, newPayee };
|
|
14436
|
+
};
|
|
14437
|
+
var buildAccountCalcMap = (entities) => new Map((entities.be_account_calculations ?? []).map((c) => [c.entities_account_id, c]));
|
|
14438
|
+
var buildMonthlyBudgetCalcMap = (calcs) => {
|
|
14439
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
14440
|
+
for (const calc of calcs) {
|
|
14441
|
+
const entityId = calc.entities_monthly_budget_id;
|
|
14442
|
+
if (!entityId) continue;
|
|
14443
|
+
const parts = entityId.split("/");
|
|
14444
|
+
const month = parts[1];
|
|
14445
|
+
if (parts.length >= 2 && month) map2.set(month, calc);
|
|
14446
|
+
}
|
|
14447
|
+
return map2;
|
|
14448
|
+
};
|
|
14449
|
+
var subcategoryCalcKey = (month, categoryId) => `${month}/${categoryId}`;
|
|
14450
|
+
var parseSubcategoryEntityId = (entityId) => {
|
|
14451
|
+
if (!entityId) return null;
|
|
14452
|
+
const parts = entityId.split("/");
|
|
14453
|
+
if (parts.length !== 3) return null;
|
|
14454
|
+
const [, month, categoryId] = parts;
|
|
14455
|
+
if (!month || !categoryId) return null;
|
|
14456
|
+
return { month, categoryId };
|
|
14457
|
+
};
|
|
14458
|
+
var buildSubcategoryCalcMap = (calcs) => {
|
|
14459
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
14460
|
+
for (const calc of calcs) {
|
|
14461
|
+
const parsed = parseSubcategoryEntityId(calc.entities_monthly_subcategory_budget_id);
|
|
14462
|
+
if (parsed) map2.set(subcategoryCalcKey(parsed.month, parsed.categoryId), calc);
|
|
14463
|
+
}
|
|
14464
|
+
return map2;
|
|
14465
|
+
};
|
|
14466
|
+
var buildSubcategoryBudgetMap = (budgets) => {
|
|
14467
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
14468
|
+
for (const budget of budgets) {
|
|
14469
|
+
if (budget.is_tombstone) continue;
|
|
14470
|
+
const parsed = parseSubcategoryEntityId(budget.id);
|
|
14471
|
+
if (parsed) map2.set(subcategoryCalcKey(parsed.month, parsed.categoryId), budget);
|
|
14472
|
+
}
|
|
14473
|
+
return map2;
|
|
14474
|
+
};
|
|
14475
|
+
var mapCategoryForMonth = (c, budgetMap, calcMap, month) => {
|
|
14476
|
+
const key = subcategoryCalcKey(month, c.id ?? "");
|
|
14477
|
+
const budget = budgetMap.get(key);
|
|
14478
|
+
const calc = calcMap.get(key);
|
|
14479
|
+
return mapCategory({
|
|
14480
|
+
...c,
|
|
14481
|
+
budgeted: budget?.budgeted ?? c.budgeted,
|
|
14482
|
+
activity: (calc?.cash_outflows ?? 0) + (calc?.credit_outflows ?? 0),
|
|
14483
|
+
balance: calc?.balance ?? c.balance,
|
|
14484
|
+
goal_target: calc?.goal_target ?? c.goal_target,
|
|
14485
|
+
goal_percentage_complete: calc?.goal_percentage_complete ?? c.goal_percentage_complete
|
|
14486
|
+
});
|
|
14487
|
+
};
|
|
14488
|
+
var hasId = (x) => !!x.id;
|
|
14489
|
+
var buildLookups = (entities) => ({
|
|
14490
|
+
payees: new Map(
|
|
14491
|
+
(entities.be_payees ?? []).filter(notTombstone).filter(hasId).map((p) => [p.id, p.name ?? ""])
|
|
14492
|
+
),
|
|
14493
|
+
accounts: new Map(
|
|
14494
|
+
(entities.be_accounts ?? []).filter(notTombstone).filter(hasId).map((a) => [a.id, a.account_name ?? ""])
|
|
14495
|
+
),
|
|
14496
|
+
categories: new Map(
|
|
14497
|
+
(entities.be_subcategories ?? []).filter(notTombstone).filter(hasId).map((c) => [c.id, c.name ?? ""])
|
|
14498
|
+
)
|
|
14499
|
+
});
|
|
14375
14500
|
var mapUser = (u) => ({
|
|
14376
14501
|
id: u.id ?? "",
|
|
14377
14502
|
first_name: u.first_name ?? "",
|
|
@@ -14386,8 +14511,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14386
14511
|
const df = JSON.parse(p.date_format);
|
|
14387
14512
|
dateFormat = df.format ?? "";
|
|
14388
14513
|
}
|
|
14389
|
-
} catch {
|
|
14390
|
-
|
|
14514
|
+
} catch (err2) {
|
|
14515
|
+
log.warn("mapPlan: failed to parse date_format", { raw: p.date_format, err: err2 });
|
|
14391
14516
|
}
|
|
14392
14517
|
try {
|
|
14393
14518
|
if (p.currency_format) {
|
|
@@ -14395,7 +14520,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14395
14520
|
currencySymbol = cf.currency_symbol ?? "$";
|
|
14396
14521
|
currencyIsoCode = cf.iso_code ?? "USD";
|
|
14397
14522
|
}
|
|
14398
|
-
} catch {
|
|
14523
|
+
} catch (err2) {
|
|
14524
|
+
log.warn("mapPlan: failed to parse currency_format", { raw: p.currency_format, err: err2 });
|
|
14399
14525
|
}
|
|
14400
14526
|
return {
|
|
14401
14527
|
id: p.id ?? "",
|
|
@@ -14443,36 +14569,36 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14443
14569
|
name: p.name ?? "",
|
|
14444
14570
|
transfer_account_id: p.entities_account_id ?? ""
|
|
14445
14571
|
});
|
|
14446
|
-
var mapTransaction = (t) => ({
|
|
14572
|
+
var mapTransaction = (t, lookups) => ({
|
|
14447
14573
|
id: t.id ?? "",
|
|
14448
14574
|
date: t.date ?? "",
|
|
14449
14575
|
amount: formatMilliunits(t.amount ?? 0),
|
|
14450
14576
|
amount_milliunits: t.amount ?? 0,
|
|
14451
14577
|
memo: t.memo ?? "",
|
|
14452
|
-
cleared: t.cleared ?? "uncleared",
|
|
14453
|
-
approved: t.
|
|
14454
|
-
flag_color: t.
|
|
14455
|
-
flag_name: t.flag_name ?? "",
|
|
14578
|
+
cleared: t.cleared?.toLowerCase() ?? "uncleared",
|
|
14579
|
+
approved: t.accepted ?? false,
|
|
14580
|
+
flag_color: t.flag?.toLowerCase() ?? "",
|
|
14456
14581
|
account_id: t.entities_account_id ?? "",
|
|
14457
|
-
account_name: t.
|
|
14582
|
+
account_name: lookups?.accounts.get(t.entities_account_id ?? "") ?? "",
|
|
14458
14583
|
payee_id: t.entities_payee_id ?? "",
|
|
14459
|
-
payee_name: t.
|
|
14584
|
+
payee_name: lookups?.payees.get(t.entities_payee_id ?? "") ?? "",
|
|
14460
14585
|
category_id: t.entities_subcategory_id ?? "",
|
|
14461
|
-
category_name: t.
|
|
14586
|
+
category_name: lookups?.categories.get(t.entities_subcategory_id ?? "") ?? "",
|
|
14462
14587
|
transfer_account_id: t.transfer_account_id ?? "",
|
|
14463
|
-
|
|
14588
|
+
imported_payee: t.imported_payee ?? "",
|
|
14589
|
+
original_imported_payee: t.original_imported_payee ?? "",
|
|
14464
14590
|
deleted: t.is_tombstone === true
|
|
14465
14591
|
});
|
|
14466
|
-
var mapSubtransaction = (s) => ({
|
|
14592
|
+
var mapSubtransaction = (s, lookups) => ({
|
|
14467
14593
|
id: s.id ?? "",
|
|
14468
14594
|
transaction_id: s.entities_transaction_id ?? "",
|
|
14469
14595
|
amount: formatMilliunits(s.amount ?? 0),
|
|
14470
14596
|
amount_milliunits: s.amount ?? 0,
|
|
14471
14597
|
memo: s.memo ?? "",
|
|
14472
14598
|
payee_id: s.entities_payee_id ?? "",
|
|
14473
|
-
payee_name: s.
|
|
14599
|
+
payee_name: lookups?.payees.get(s.entities_payee_id ?? "") ?? "",
|
|
14474
14600
|
category_id: s.entities_subcategory_id ?? "",
|
|
14475
|
-
category_name: s.
|
|
14601
|
+
category_name: lookups?.categories.get(s.entities_subcategory_id ?? "") ?? "",
|
|
14476
14602
|
transfer_account_id: s.transfer_account_id ?? "",
|
|
14477
14603
|
deleted: s.is_tombstone === true
|
|
14478
14604
|
});
|
|
@@ -14491,27 +14617,198 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14491
14617
|
budgeted_milliunits: budgeted,
|
|
14492
14618
|
activity_milliunits: activity,
|
|
14493
14619
|
to_be_budgeted_milliunits: toBeBudgeted,
|
|
14494
|
-
age_of_money: calc?.age_of_money ??
|
|
14620
|
+
age_of_money: calc?.age_of_money ?? null
|
|
14495
14621
|
};
|
|
14496
14622
|
};
|
|
14497
|
-
var mapScheduledTransaction = (s) => ({
|
|
14623
|
+
var mapScheduledTransaction = (s, lookups) => ({
|
|
14498
14624
|
id: s.id ?? "",
|
|
14499
|
-
date_first: s.
|
|
14500
|
-
date_next: s.
|
|
14625
|
+
date_first: s.date ?? "",
|
|
14626
|
+
date_next: s.upcoming_instances?.[0] ?? s.date ?? "",
|
|
14501
14627
|
frequency: s.frequency ?? "never",
|
|
14502
14628
|
amount: formatMilliunits(s.amount ?? 0),
|
|
14503
14629
|
amount_milliunits: s.amount ?? 0,
|
|
14504
14630
|
memo: s.memo ?? "",
|
|
14505
|
-
flag_color: s.
|
|
14631
|
+
flag_color: s.flag?.toLowerCase() ?? "",
|
|
14506
14632
|
account_id: s.entities_account_id ?? "",
|
|
14507
|
-
account_name: s.
|
|
14633
|
+
account_name: lookups?.accounts.get(s.entities_account_id ?? "") ?? "",
|
|
14508
14634
|
payee_id: s.entities_payee_id ?? "",
|
|
14509
|
-
payee_name: s.
|
|
14635
|
+
payee_name: lookups?.payees.get(s.entities_payee_id ?? "") ?? "",
|
|
14510
14636
|
category_id: s.entities_subcategory_id ?? "",
|
|
14511
|
-
category_name: s.
|
|
14637
|
+
category_name: lookups?.categories.get(s.entities_subcategory_id ?? "") ?? "",
|
|
14512
14638
|
deleted: s.is_tombstone === true
|
|
14513
14639
|
});
|
|
14514
14640
|
|
|
14641
|
+
// src/tools/create-transaction.ts
|
|
14642
|
+
var createTransaction = defineTool({
|
|
14643
|
+
name: "create_transaction",
|
|
14644
|
+
displayName: "Create Transaction",
|
|
14645
|
+
description: "Create a new transaction in the active YNAB plan. Amount is in currency units (e.g. -42.50 for a $42.50 expense, 1500 for $1500 income). Negative amounts are outflows (expenses), positive amounts are inflows (income).",
|
|
14646
|
+
summary: "Create a new transaction",
|
|
14647
|
+
icon: "plus",
|
|
14648
|
+
group: "Transactions",
|
|
14649
|
+
input: external_exports.object({
|
|
14650
|
+
account_id: external_exports.string().min(1).describe("Account ID to create the transaction in"),
|
|
14651
|
+
date: external_exports.string().min(1).describe("Transaction date in YYYY-MM-DD format"),
|
|
14652
|
+
amount: external_exports.number().describe(
|
|
14653
|
+
"Amount in currency units (negative for expenses, positive for income). E.g. -42.50 for a $42.50 expense."
|
|
14654
|
+
),
|
|
14655
|
+
payee_name: external_exports.string().optional().describe("Payee name (creates new payee if not found)"),
|
|
14656
|
+
payee_id: external_exports.string().optional().describe("Existing payee ID (takes precedence over payee_name)"),
|
|
14657
|
+
category_id: external_exports.string().optional().describe("Category ID to assign"),
|
|
14658
|
+
memo: external_exports.string().optional().describe("Transaction memo"),
|
|
14659
|
+
cleared: external_exports.enum(["cleared", "uncleared", "reconciled"]).optional().describe("Cleared status (default uncleared)"),
|
|
14660
|
+
approved: external_exports.boolean().optional().describe("Whether the transaction is approved (default true)"),
|
|
14661
|
+
flag_color: external_exports.enum(["red", "orange", "yellow", "green", "blue", "purple"]).optional().describe("Flag color")
|
|
14662
|
+
}),
|
|
14663
|
+
output: external_exports.object({
|
|
14664
|
+
transaction: transactionSchema
|
|
14665
|
+
}),
|
|
14666
|
+
handle: async (params) => {
|
|
14667
|
+
const planId = getPlanId();
|
|
14668
|
+
const milliunits = toMilliunits(params.amount);
|
|
14669
|
+
const txId = crypto.randomUUID();
|
|
14670
|
+
const budget = await syncBudget(planId);
|
|
14671
|
+
const serverKnowledge = budget.current_server_knowledge ?? 0;
|
|
14672
|
+
const lookups = buildLookups(budget.changed_entities ?? {});
|
|
14673
|
+
const changedEntities = {};
|
|
14674
|
+
let payeeId = params.payee_id ?? null;
|
|
14675
|
+
if (!payeeId && params.payee_name) {
|
|
14676
|
+
const resolved = resolvePayee(budget.changed_entities?.be_payees ?? [], params.payee_name);
|
|
14677
|
+
payeeId = resolved.payeeId;
|
|
14678
|
+
if (resolved.newPayee) {
|
|
14679
|
+
changedEntities.be_payees = [resolved.newPayee];
|
|
14680
|
+
lookups.payees.set(resolved.payeeId, params.payee_name);
|
|
14681
|
+
}
|
|
14682
|
+
}
|
|
14683
|
+
changedEntities.be_transaction_groups = [
|
|
14684
|
+
{
|
|
14685
|
+
id: txId,
|
|
14686
|
+
be_transaction: {
|
|
14687
|
+
id: txId,
|
|
14688
|
+
is_tombstone: false,
|
|
14689
|
+
entities_account_id: params.account_id,
|
|
14690
|
+
entities_payee_id: payeeId,
|
|
14691
|
+
entities_subcategory_id: params.category_id ?? null,
|
|
14692
|
+
entities_scheduled_transaction_id: null,
|
|
14693
|
+
date: params.date,
|
|
14694
|
+
date_entered_from_schedule: null,
|
|
14695
|
+
amount: milliunits,
|
|
14696
|
+
// cash_amount and credit_amount are server-computed splits the account
|
|
14697
|
+
// type determines. Captured from a credit card account create where
|
|
14698
|
+
// YNAB's UI sent zeros and the server populated them on response —
|
|
14699
|
+
// not yet verified for cash/checking accounts but likely the same
|
|
14700
|
+
// pattern.
|
|
14701
|
+
cash_amount: 0,
|
|
14702
|
+
credit_amount: 0,
|
|
14703
|
+
credit_amount_adjusted: 0,
|
|
14704
|
+
subcategory_credit_amount_preceding: 0,
|
|
14705
|
+
memo: params.memo ?? null,
|
|
14706
|
+
cleared: CLEARED_MAP[params.cleared ?? "uncleared"],
|
|
14707
|
+
// YNAB's wire format calls this "accepted"; the public tool surface uses "approved".
|
|
14708
|
+
accepted: params.approved ?? true,
|
|
14709
|
+
check_number: null,
|
|
14710
|
+
flag: params.flag_color ? FLAG_MAP[params.flag_color] : null,
|
|
14711
|
+
transfer_account_id: null,
|
|
14712
|
+
transfer_transaction_id: null,
|
|
14713
|
+
transfer_subtransaction_id: null,
|
|
14714
|
+
matched_transaction_id: null,
|
|
14715
|
+
ynab_id: null,
|
|
14716
|
+
// Import-related fields are only populated by bank-feed imports, not manual entry.
|
|
14717
|
+
imported_payee: null,
|
|
14718
|
+
imported_date: null,
|
|
14719
|
+
original_imported_payee: null,
|
|
14720
|
+
provider_cleansed_payee: null,
|
|
14721
|
+
source: null,
|
|
14722
|
+
debt_transaction_type: null
|
|
14723
|
+
},
|
|
14724
|
+
be_subtransactions: null
|
|
14725
|
+
}
|
|
14726
|
+
];
|
|
14727
|
+
const result = await syncWrite(planId, changedEntities, serverKnowledge);
|
|
14728
|
+
const saved = result.changed_entities?.be_transactions?.find((t) => t.id === txId);
|
|
14729
|
+
if (!saved) {
|
|
14730
|
+
throw ToolError.internal("Transaction was created but no data was returned");
|
|
14731
|
+
}
|
|
14732
|
+
return { transaction: mapTransaction(saved, lookups) };
|
|
14733
|
+
}
|
|
14734
|
+
});
|
|
14735
|
+
|
|
14736
|
+
// src/tools/delete-transaction.ts
|
|
14737
|
+
var deleteTransaction = defineTool({
|
|
14738
|
+
name: "delete_transaction",
|
|
14739
|
+
displayName: "Delete Transaction",
|
|
14740
|
+
description: "Delete a transaction from the active YNAB plan. This marks the transaction as deleted (soft delete). Transfer transactions cannot be deleted through this tool \u2014 delete them directly in YNAB.",
|
|
14741
|
+
summary: "Delete a transaction",
|
|
14742
|
+
icon: "trash-2",
|
|
14743
|
+
group: "Transactions",
|
|
14744
|
+
input: external_exports.object({
|
|
14745
|
+
transaction_id: external_exports.string().min(1).describe("Transaction ID to delete"),
|
|
14746
|
+
account_id: external_exports.string().min(1).describe("Account ID the transaction belongs to")
|
|
14747
|
+
}),
|
|
14748
|
+
output: external_exports.object({
|
|
14749
|
+
success: external_exports.boolean().describe("Whether the operation succeeded")
|
|
14750
|
+
}),
|
|
14751
|
+
handle: async (params) => {
|
|
14752
|
+
const planId = getPlanId();
|
|
14753
|
+
const budget = await syncBudget(planId);
|
|
14754
|
+
const serverKnowledge = budget.current_server_knowledge ?? 0;
|
|
14755
|
+
const existing2 = budget.changed_entities?.be_transactions?.find(
|
|
14756
|
+
(t) => t.id === params.transaction_id && !t.is_tombstone
|
|
14757
|
+
);
|
|
14758
|
+
if (!existing2) {
|
|
14759
|
+
throw ToolError.notFound(`Transaction not found: ${params.transaction_id}`);
|
|
14760
|
+
}
|
|
14761
|
+
if (existing2.transfer_account_id) {
|
|
14762
|
+
throw ToolError.validation("Cannot delete transfer transactions \u2014 delete them in YNAB directly.");
|
|
14763
|
+
}
|
|
14764
|
+
await syncWrite(
|
|
14765
|
+
planId,
|
|
14766
|
+
{
|
|
14767
|
+
be_transaction_groups: [
|
|
14768
|
+
{
|
|
14769
|
+
id: params.transaction_id,
|
|
14770
|
+
be_transaction: {
|
|
14771
|
+
...existing2,
|
|
14772
|
+
is_tombstone: true
|
|
14773
|
+
},
|
|
14774
|
+
be_subtransactions: null
|
|
14775
|
+
}
|
|
14776
|
+
]
|
|
14777
|
+
},
|
|
14778
|
+
serverKnowledge
|
|
14779
|
+
);
|
|
14780
|
+
return { success: true };
|
|
14781
|
+
}
|
|
14782
|
+
});
|
|
14783
|
+
|
|
14784
|
+
// src/tools/get-account.ts
|
|
14785
|
+
var getAccount = defineTool({
|
|
14786
|
+
name: "get_account",
|
|
14787
|
+
displayName: "Get Account",
|
|
14788
|
+
description: "Get details for a specific account in the active YNAB plan by its ID. Returns name, type, balances, and on-budget status.",
|
|
14789
|
+
summary: "Get account details by ID",
|
|
14790
|
+
icon: "landmark",
|
|
14791
|
+
group: "Accounts",
|
|
14792
|
+
input: external_exports.object({
|
|
14793
|
+
account_id: external_exports.string().min(1).describe("Account ID to retrieve")
|
|
14794
|
+
}),
|
|
14795
|
+
output: external_exports.object({
|
|
14796
|
+
account: accountSchema
|
|
14797
|
+
}),
|
|
14798
|
+
handle: async (params) => {
|
|
14799
|
+
const planId = getPlanId();
|
|
14800
|
+
const result = await syncBudget(planId);
|
|
14801
|
+
const entities = result.changed_entities;
|
|
14802
|
+
const raw = entities?.be_accounts ?? [];
|
|
14803
|
+
const calcMap = buildAccountCalcMap(entities ?? {});
|
|
14804
|
+
const account = raw.find((a) => a.id === params.account_id && !a.is_tombstone);
|
|
14805
|
+
if (!account) {
|
|
14806
|
+
throw ToolError.notFound(`Account not found: ${params.account_id}`);
|
|
14807
|
+
}
|
|
14808
|
+
return { account: mapAccount(account, calcMap.get(account.id)) };
|
|
14809
|
+
}
|
|
14810
|
+
});
|
|
14811
|
+
|
|
14515
14812
|
// src/tools/get-current-user.ts
|
|
14516
14813
|
var getCurrentUser = defineTool({
|
|
14517
14814
|
name: "get_current_user",
|
|
@@ -14528,6 +14825,47 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14528
14825
|
}
|
|
14529
14826
|
});
|
|
14530
14827
|
|
|
14828
|
+
// src/tools/get-month.ts
|
|
14829
|
+
var getMonth = defineTool({
|
|
14830
|
+
name: "get_month",
|
|
14831
|
+
displayName: "Get Month",
|
|
14832
|
+
description: "Get budget summary and category details for a specific month. Returns the month overview (income, budgeted, activity, Ready to Assign) plus per-category breakdowns. Month format is YYYY-MM-DD using the first of the month (e.g. 2026-03-01).",
|
|
14833
|
+
summary: "Get budget details for a month",
|
|
14834
|
+
icon: "calendar",
|
|
14835
|
+
group: "Months",
|
|
14836
|
+
input: external_exports.object({
|
|
14837
|
+
month: external_exports.string().min(1).describe("Month in YYYY-MM-DD format (first of month, e.g. 2026-03-01)"),
|
|
14838
|
+
include_hidden: external_exports.boolean().optional().describe("Include hidden categories (default false)")
|
|
14839
|
+
}),
|
|
14840
|
+
output: external_exports.object({
|
|
14841
|
+
month: monthSchema,
|
|
14842
|
+
categories: external_exports.array(categorySchema).describe("Category budgets for this month")
|
|
14843
|
+
}),
|
|
14844
|
+
handle: async (params) => {
|
|
14845
|
+
const planId = getPlanId();
|
|
14846
|
+
const result = await syncBudget(planId);
|
|
14847
|
+
const entities = result.changed_entities;
|
|
14848
|
+
const rawMonths = entities?.be_monthly_budgets ?? [];
|
|
14849
|
+
const monthData = rawMonths.find((m) => m.month === params.month && !m.is_tombstone);
|
|
14850
|
+
if (!monthData) {
|
|
14851
|
+
throw ToolError.notFound(`Month not found: ${params.month}`);
|
|
14852
|
+
}
|
|
14853
|
+
const monthKey = toMonthKey(params.month);
|
|
14854
|
+
const monthCalcMap = buildMonthlyBudgetCalcMap(entities?.be_monthly_budget_calculations ?? []);
|
|
14855
|
+
const monthCalc = monthCalcMap.get(monthKey);
|
|
14856
|
+
const rawCategories = (entities?.be_subcategories ?? []).filter(
|
|
14857
|
+
(c) => notTombstone(c) && (params.include_hidden || c.is_hidden !== true)
|
|
14858
|
+
);
|
|
14859
|
+
const budgetMap = buildSubcategoryBudgetMap(entities?.be_monthly_subcategory_budgets ?? []);
|
|
14860
|
+
const calcMap = buildSubcategoryCalcMap(entities?.be_monthly_subcategory_budget_calculations ?? []);
|
|
14861
|
+
const categories = rawCategories.map((c) => mapCategoryForMonth(c, budgetMap, calcMap, monthKey));
|
|
14862
|
+
return {
|
|
14863
|
+
month: mapMonth(monthData, monthCalc),
|
|
14864
|
+
categories
|
|
14865
|
+
};
|
|
14866
|
+
}
|
|
14867
|
+
});
|
|
14868
|
+
|
|
14531
14869
|
// src/tools/get-plan.ts
|
|
14532
14870
|
var getPlan = defineTool({
|
|
14533
14871
|
name: "get_plan",
|
|
@@ -14539,67 +14877,77 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14539
14877
|
input: external_exports.object({}),
|
|
14540
14878
|
output: external_exports.object({ plan: planSchema }),
|
|
14541
14879
|
handle: async () => {
|
|
14542
|
-
|
|
14880
|
+
assertAuthenticated();
|
|
14543
14881
|
const result = await catalog("getInitialUserData", {
|
|
14544
|
-
device_info: { id:
|
|
14882
|
+
device_info: { id: getDeviceId(), device_os: "web" }
|
|
14545
14883
|
});
|
|
14546
|
-
|
|
14884
|
+
const budgetVersion = result.budget_version;
|
|
14885
|
+
if (!budgetVersion) {
|
|
14886
|
+
throw ToolError.notFound("No active plan found");
|
|
14887
|
+
}
|
|
14888
|
+
return { plan: mapPlan(budgetVersion) };
|
|
14547
14889
|
}
|
|
14548
14890
|
});
|
|
14549
14891
|
|
|
14550
|
-
// src/tools/
|
|
14551
|
-
var
|
|
14552
|
-
name: "
|
|
14553
|
-
displayName: "
|
|
14554
|
-
description: "
|
|
14555
|
-
summary: "
|
|
14556
|
-
icon: "
|
|
14557
|
-
group: "
|
|
14892
|
+
// src/tools/get-transaction.ts
|
|
14893
|
+
var getTransaction = defineTool({
|
|
14894
|
+
name: "get_transaction",
|
|
14895
|
+
displayName: "Get Transaction",
|
|
14896
|
+
description: "Get details for a specific transaction by its ID. Returns full transaction data including any split subtransactions.",
|
|
14897
|
+
summary: "Get transaction details by ID",
|
|
14898
|
+
icon: "receipt",
|
|
14899
|
+
group: "Transactions",
|
|
14558
14900
|
input: external_exports.object({
|
|
14559
|
-
|
|
14901
|
+
transaction_id: external_exports.string().min(1).describe("Transaction ID to retrieve")
|
|
14560
14902
|
}),
|
|
14561
14903
|
output: external_exports.object({
|
|
14562
|
-
|
|
14904
|
+
transaction: transactionSchema,
|
|
14905
|
+
subtransactions: external_exports.array(subtransactionSchema).describe("Split subtransactions (empty if not a split)")
|
|
14563
14906
|
}),
|
|
14564
14907
|
handle: async (params) => {
|
|
14565
14908
|
const planId = getPlanId();
|
|
14566
14909
|
const result = await syncBudget(planId);
|
|
14567
14910
|
const entities = result.changed_entities;
|
|
14568
|
-
const raw = entities?.
|
|
14569
|
-
const
|
|
14570
|
-
|
|
14571
|
-
|
|
14572
|
-
accounts = accounts.filter((a) => !a.closed);
|
|
14911
|
+
const raw = entities?.be_transactions ?? [];
|
|
14912
|
+
const tx = raw.find((t) => t.id === params.transaction_id && !t.is_tombstone);
|
|
14913
|
+
if (!tx) {
|
|
14914
|
+
throw ToolError.notFound(`Transaction not found: ${params.transaction_id}`);
|
|
14573
14915
|
}
|
|
14574
|
-
|
|
14916
|
+
const lookups = buildLookups(entities ?? {});
|
|
14917
|
+
const allSubs = entities?.be_subtransactions ?? [];
|
|
14918
|
+
const subtransactions = allSubs.filter((s) => s.entities_transaction_id === params.transaction_id && !s.is_tombstone).map((s) => mapSubtransaction(s, lookups));
|
|
14919
|
+
return {
|
|
14920
|
+
transaction: mapTransaction(tx, lookups),
|
|
14921
|
+
subtransactions
|
|
14922
|
+
};
|
|
14575
14923
|
}
|
|
14576
14924
|
});
|
|
14577
14925
|
|
|
14578
|
-
// src/tools/
|
|
14579
|
-
var
|
|
14580
|
-
name: "
|
|
14581
|
-
displayName: "
|
|
14582
|
-
description: "
|
|
14583
|
-
summary: "
|
|
14926
|
+
// src/tools/list-accounts.ts
|
|
14927
|
+
var listAccounts = defineTool({
|
|
14928
|
+
name: "list_accounts",
|
|
14929
|
+
displayName: "List Accounts",
|
|
14930
|
+
description: "List all accounts in the active YNAB plan. Returns account name, type, balances, and on-budget status. Includes checking, savings, credit cards, and tracking accounts.",
|
|
14931
|
+
summary: "List all budget accounts",
|
|
14584
14932
|
icon: "landmark",
|
|
14585
14933
|
group: "Accounts",
|
|
14586
14934
|
input: external_exports.object({
|
|
14587
|
-
|
|
14935
|
+
include_closed: external_exports.boolean().optional().describe("Include closed accounts (default false)")
|
|
14588
14936
|
}),
|
|
14589
14937
|
output: external_exports.object({
|
|
14590
|
-
|
|
14938
|
+
accounts: external_exports.array(accountSchema).describe("List of accounts")
|
|
14591
14939
|
}),
|
|
14592
14940
|
handle: async (params) => {
|
|
14593
14941
|
const planId = getPlanId();
|
|
14594
14942
|
const result = await syncBudget(planId);
|
|
14595
14943
|
const entities = result.changed_entities;
|
|
14596
14944
|
const raw = entities?.be_accounts ?? [];
|
|
14597
|
-
const calcMap =
|
|
14598
|
-
|
|
14599
|
-
if (!
|
|
14600
|
-
|
|
14945
|
+
const calcMap = buildAccountCalcMap(entities ?? {});
|
|
14946
|
+
let accounts = raw.filter(notTombstone).map((a) => mapAccount(a, calcMap.get(a.id)));
|
|
14947
|
+
if (!params.include_closed) {
|
|
14948
|
+
accounts = accounts.filter((a) => !a.closed);
|
|
14601
14949
|
}
|
|
14602
|
-
return {
|
|
14950
|
+
return { accounts };
|
|
14603
14951
|
}
|
|
14604
14952
|
});
|
|
14605
14953
|
|
|
@@ -14607,7 +14955,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14607
14955
|
var listCategories = defineTool({
|
|
14608
14956
|
name: "list_categories",
|
|
14609
14957
|
displayName: "List Categories",
|
|
14610
|
-
description: "List
|
|
14958
|
+
description: "List category groups and categories in the active YNAB plan. Returns budgeted amounts, activity, and available balances for the current month. Hidden and deleted categories are excluded by default \u2014 pass include_hidden=true to also see hidden categories (useful for editing budgets on hidden categories).",
|
|
14611
14959
|
summary: "List budget categories with balances",
|
|
14612
14960
|
icon: "tags",
|
|
14613
14961
|
group: "Categories",
|
|
@@ -14622,31 +14970,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14622
14970
|
const planId = getPlanId();
|
|
14623
14971
|
const result = await syncBudget(planId);
|
|
14624
14972
|
const entities = result.changed_entities;
|
|
14625
|
-
const rawGroups = (entities?.be_master_categories ?? []).filter(
|
|
14626
|
-
const rawCategories = (entities?.be_subcategories ?? []).filter(
|
|
14627
|
-
const
|
|
14628
|
-
const calcMap =
|
|
14629
|
-
|
|
14630
|
-
const entityId = calc.entities_monthly_subcategory_budget_id;
|
|
14631
|
-
if (entityId) {
|
|
14632
|
-
const parts = entityId.split("/");
|
|
14633
|
-
const categoryId = parts.length >= 3 ? parts.slice(2).join("/") : entityId;
|
|
14634
|
-
calcMap.set(categoryId, calc);
|
|
14635
|
-
}
|
|
14636
|
-
}
|
|
14973
|
+
const rawGroups = (entities?.be_master_categories ?? []).filter(notTombstone);
|
|
14974
|
+
const rawCategories = (entities?.be_subcategories ?? []).filter(notTombstone);
|
|
14975
|
+
const budgetMap = buildSubcategoryBudgetMap(entities?.be_monthly_subcategory_budgets ?? []);
|
|
14976
|
+
const calcMap = buildSubcategoryCalcMap(entities?.be_monthly_subcategory_budget_calculations ?? []);
|
|
14977
|
+
const currentMonth = currentMonthKey();
|
|
14637
14978
|
let groups = rawGroups.map(mapCategoryGroup);
|
|
14638
|
-
let categories = rawCategories.map((c) =>
|
|
14639
|
-
const calc = calcMap.get(c.id ?? "");
|
|
14640
|
-
return mapCategory({
|
|
14641
|
-
...c,
|
|
14642
|
-
budgeted: calc?.budgeted ?? c.budgeted,
|
|
14643
|
-
activity: calc?.activity ?? c.activity,
|
|
14644
|
-
balance: calc?.balance ?? c.balance,
|
|
14645
|
-
goal_type: calc?.goal_type ?? c.goal_type,
|
|
14646
|
-
goal_target: calc?.goal_target ?? c.goal_target,
|
|
14647
|
-
goal_percentage_complete: calc?.goal_percentage_complete ?? c.goal_percentage_complete
|
|
14648
|
-
});
|
|
14649
|
-
});
|
|
14979
|
+
let categories = rawCategories.map((c) => mapCategoryForMonth(c, budgetMap, calcMap, currentMonth));
|
|
14650
14980
|
if (!params.include_hidden) {
|
|
14651
14981
|
groups = groups.filter((g) => !g.hidden);
|
|
14652
14982
|
categories = categories.filter((c) => !c.hidden);
|
|
@@ -14655,48 +14985,26 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14655
14985
|
}
|
|
14656
14986
|
});
|
|
14657
14987
|
|
|
14658
|
-
// src/tools/
|
|
14659
|
-
var
|
|
14660
|
-
name: "
|
|
14661
|
-
displayName: "
|
|
14662
|
-
description: "
|
|
14663
|
-
summary: "
|
|
14664
|
-
icon: "
|
|
14665
|
-
group: "
|
|
14666
|
-
input: external_exports.object({
|
|
14667
|
-
category_id: external_exports.string().min(1).describe("Category ID to budget"),
|
|
14668
|
-
month: external_exports.string().min(1).describe("Month in YYYY-MM format (e.g. 2026-03)"),
|
|
14669
|
-
budgeted: external_exports.number().describe("Amount to budget in currency units (e.g. 500 for $500)")
|
|
14670
|
-
}),
|
|
14988
|
+
// src/tools/list-months.ts
|
|
14989
|
+
var listMonths = defineTool({
|
|
14990
|
+
name: "list_months",
|
|
14991
|
+
displayName: "List Months",
|
|
14992
|
+
description: "List all budget months in the active YNAB plan. Returns income, budgeted, activity, and Ready to Assign amounts for each month. Sorted from most recent to oldest.",
|
|
14993
|
+
summary: "List budget months with summaries",
|
|
14994
|
+
icon: "calendar",
|
|
14995
|
+
group: "Months",
|
|
14996
|
+
input: external_exports.object({}),
|
|
14671
14997
|
output: external_exports.object({
|
|
14672
|
-
|
|
14998
|
+
months: external_exports.array(monthSchema).describe("List of budget months")
|
|
14673
14999
|
}),
|
|
14674
|
-
handle: async (
|
|
15000
|
+
handle: async () => {
|
|
14675
15001
|
const planId = getPlanId();
|
|
14676
|
-
const
|
|
14677
|
-
const
|
|
14678
|
-
const
|
|
14679
|
-
const
|
|
14680
|
-
const
|
|
14681
|
-
|
|
14682
|
-
{
|
|
14683
|
-
id: budgetId,
|
|
14684
|
-
entities_monthly_budget_id: monthlyBudgetId,
|
|
14685
|
-
entities_subcategory_id: params.category_id,
|
|
14686
|
-
budgeted: milliunits,
|
|
14687
|
-
overspending_handling: "AffectsBuffer",
|
|
14688
|
-
is_tombstone: false
|
|
14689
|
-
}
|
|
14690
|
-
]
|
|
14691
|
-
});
|
|
14692
|
-
const budgets = result.changed_entities?.be_monthly_subcategory_budgets;
|
|
14693
|
-
const updatedBudget = budgets?.[0]?.budgeted ?? milliunits;
|
|
14694
|
-
return {
|
|
14695
|
-
category: mapCategory({
|
|
14696
|
-
id: params.category_id,
|
|
14697
|
-
budgeted: updatedBudget
|
|
14698
|
-
})
|
|
14699
|
-
};
|
|
15002
|
+
const result = await syncBudget(planId);
|
|
15003
|
+
const entities = result.changed_entities;
|
|
15004
|
+
const rawMonths = entities?.be_monthly_budgets ?? [];
|
|
15005
|
+
const calcMap = buildMonthlyBudgetCalcMap(entities?.be_monthly_budget_calculations ?? []);
|
|
15006
|
+
const months = rawMonths.filter(notTombstone).map((m) => mapMonth(m, calcMap.get((m.month ?? "").substring(0, 7)))).sort((a, b) => b.month.localeCompare(a.month));
|
|
15007
|
+
return { months };
|
|
14700
15008
|
}
|
|
14701
15009
|
});
|
|
14702
15010
|
|
|
@@ -14716,11 +15024,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14716
15024
|
const planId = getPlanId();
|
|
14717
15025
|
const result = await syncBudget(planId);
|
|
14718
15026
|
const raw = result.changed_entities?.be_payees ?? [];
|
|
14719
|
-
const payees = raw.filter(
|
|
15027
|
+
const payees = raw.filter(notTombstone).map(mapPayee);
|
|
14720
15028
|
return { payees };
|
|
14721
15029
|
}
|
|
14722
15030
|
});
|
|
14723
15031
|
|
|
15032
|
+
// src/tools/list-scheduled-transactions.ts
|
|
15033
|
+
var listScheduledTransactions = defineTool({
|
|
15034
|
+
name: "list_scheduled_transactions",
|
|
15035
|
+
displayName: "List Scheduled Transactions",
|
|
15036
|
+
description: "List all scheduled (recurring) transactions in the active YNAB plan. Returns frequency, next occurrence date, amount, payee, and category for each.",
|
|
15037
|
+
summary: "List scheduled/recurring transactions",
|
|
15038
|
+
icon: "clock",
|
|
15039
|
+
group: "Transactions",
|
|
15040
|
+
input: external_exports.object({}),
|
|
15041
|
+
output: external_exports.object({
|
|
15042
|
+
scheduled_transactions: external_exports.array(scheduledTransactionSchema).describe("List of scheduled transactions")
|
|
15043
|
+
}),
|
|
15044
|
+
handle: async () => {
|
|
15045
|
+
const planId = getPlanId();
|
|
15046
|
+
const result = await syncBudget(planId);
|
|
15047
|
+
const entities = result.changed_entities;
|
|
15048
|
+
const raw = entities?.be_scheduled_transactions ?? [];
|
|
15049
|
+
const lookups = buildLookups(entities ?? {});
|
|
15050
|
+
const scheduledTransactions = raw.filter(notTombstone).map((s) => mapScheduledTransaction(s, lookups)).sort((a, b) => a.date_next.localeCompare(b.date_next));
|
|
15051
|
+
return { scheduled_transactions: scheduledTransactions };
|
|
15052
|
+
}
|
|
15053
|
+
});
|
|
15054
|
+
|
|
14724
15055
|
// src/tools/list-transactions.ts
|
|
14725
15056
|
var listTransactions = defineTool({
|
|
14726
15057
|
name: "list_transactions",
|
|
@@ -14731,7 +15062,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14731
15062
|
group: "Transactions",
|
|
14732
15063
|
input: external_exports.object({
|
|
14733
15064
|
account_id: external_exports.string().optional().describe("Filter by account ID. Omit to list all transactions."),
|
|
14734
|
-
since_date: external_exports.string().optional().describe("Only return transactions on or after this date (YYYY-MM-DD). Omit for all transactions.")
|
|
15065
|
+
since_date: external_exports.string().optional().describe("Only return transactions on or after this date (YYYY-MM-DD). Omit for all transactions."),
|
|
15066
|
+
until_date: external_exports.string().optional().describe(
|
|
15067
|
+
"Only return transactions on or before this date (YYYY-MM-DD). Combine with since_date for a date range."
|
|
15068
|
+
),
|
|
15069
|
+
payee_search: external_exports.string().optional().describe(
|
|
15070
|
+
"Case-insensitive substring match against payee_name, imported_payee, and original_imported_payee. Useful for finding all transactions for a merchant without knowing the exact payee ID."
|
|
15071
|
+
)
|
|
14735
15072
|
}),
|
|
14736
15073
|
output: external_exports.object({
|
|
14737
15074
|
transactions: external_exports.array(transactionSchema).describe("List of transactions")
|
|
@@ -14739,112 +15076,211 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14739
15076
|
handle: async (params) => {
|
|
14740
15077
|
const planId = getPlanId();
|
|
14741
15078
|
const result = await syncBudget(planId);
|
|
14742
|
-
const
|
|
14743
|
-
|
|
15079
|
+
const entities = result.changed_entities;
|
|
15080
|
+
const raw = entities?.be_transactions ?? [];
|
|
15081
|
+
const lookups = buildLookups(entities ?? {});
|
|
15082
|
+
let filtered = raw.filter(notTombstone);
|
|
14744
15083
|
if (params.account_id) {
|
|
14745
|
-
|
|
15084
|
+
filtered = filtered.filter((t) => t.entities_account_id === params.account_id);
|
|
14746
15085
|
}
|
|
14747
15086
|
if (params.since_date) {
|
|
14748
15087
|
const sinceDate = params.since_date;
|
|
14749
|
-
|
|
15088
|
+
filtered = filtered.filter((t) => (t.date ?? "") >= sinceDate);
|
|
15089
|
+
}
|
|
15090
|
+
if (params.until_date) {
|
|
15091
|
+
const untilDate = params.until_date;
|
|
15092
|
+
filtered = filtered.filter((t) => (t.date ?? "") <= untilDate);
|
|
15093
|
+
}
|
|
15094
|
+
if (params.payee_search) {
|
|
15095
|
+
const needle = params.payee_search.toLowerCase();
|
|
15096
|
+
const matchingPayeeIds = /* @__PURE__ */ new Set();
|
|
15097
|
+
for (const [id, name] of lookups.payees) {
|
|
15098
|
+
if (name.toLowerCase().includes(needle)) matchingPayeeIds.add(id);
|
|
15099
|
+
}
|
|
15100
|
+
filtered = filtered.filter((t) => {
|
|
15101
|
+
if (t.entities_payee_id && matchingPayeeIds.has(t.entities_payee_id)) return true;
|
|
15102
|
+
if (t.imported_payee?.toLowerCase().includes(needle)) return true;
|
|
15103
|
+
if (t.original_imported_payee?.toLowerCase().includes(needle)) return true;
|
|
15104
|
+
return false;
|
|
15105
|
+
});
|
|
14750
15106
|
}
|
|
14751
|
-
transactions.sort((a, b) => b.date.localeCompare(a.date));
|
|
15107
|
+
const transactions = filtered.map((t) => mapTransaction(t, lookups)).sort((a, b) => b.date.localeCompare(a.date));
|
|
14752
15108
|
return { transactions };
|
|
14753
15109
|
}
|
|
14754
15110
|
});
|
|
14755
15111
|
|
|
14756
|
-
// src/tools/
|
|
14757
|
-
var
|
|
14758
|
-
name: "
|
|
14759
|
-
displayName: "
|
|
14760
|
-
description: "
|
|
14761
|
-
summary: "
|
|
14762
|
-
icon: "
|
|
14763
|
-
group: "
|
|
15112
|
+
// src/tools/move-category-budget.ts
|
|
15113
|
+
var moveCategoryBudget = defineTool({
|
|
15114
|
+
name: "move_category_budget",
|
|
15115
|
+
displayName: "Move Category Budget",
|
|
15116
|
+
description: "Move budgeted money between categories or to/from Ready to Assign for a specific month. Omit from_category_id to move money out of Ready to Assign; omit to_category_id to move money back to Ready to Assign. Both null is invalid.",
|
|
15117
|
+
summary: "Move money between budget categories",
|
|
15118
|
+
icon: "arrow-left-right",
|
|
15119
|
+
group: "Categories",
|
|
14764
15120
|
input: external_exports.object({
|
|
14765
|
-
|
|
15121
|
+
month: external_exports.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, "Month must be YYYY-MM or YYYY-MM-DD").describe("Month in YYYY-MM format (e.g. 2026-03)"),
|
|
15122
|
+
amount: external_exports.number().positive().describe("Amount to move in currency units (e.g. 50 for $50)"),
|
|
15123
|
+
from_category_id: external_exports.string().optional().describe("Source category ID. Omit to move from Ready to Assign."),
|
|
15124
|
+
to_category_id: external_exports.string().optional().describe("Destination category ID. Omit to move to Ready to Assign.")
|
|
15125
|
+
}).refine((p) => p.from_category_id || p.to_category_id, {
|
|
15126
|
+
message: "At least one of from_category_id or to_category_id must be provided"
|
|
15127
|
+
}).refine((p) => !p.from_category_id || !p.to_category_id || p.from_category_id !== p.to_category_id, {
|
|
15128
|
+
message: "from_category_id and to_category_id must differ"
|
|
14766
15129
|
}),
|
|
14767
15130
|
output: external_exports.object({
|
|
14768
|
-
|
|
14769
|
-
subtransactions: external_exports.array(subtransactionSchema).describe("Split subtransactions (empty if not a split)")
|
|
15131
|
+
categories: external_exports.array(categorySchema).describe("Updated categories (1 if RTA is involved, 2 for category-to-category)")
|
|
14770
15132
|
}),
|
|
14771
15133
|
handle: async (params) => {
|
|
14772
15134
|
const planId = getPlanId();
|
|
14773
|
-
const
|
|
14774
|
-
const
|
|
14775
|
-
const
|
|
14776
|
-
const
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
const
|
|
14781
|
-
const
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
15135
|
+
const userId = getUserId();
|
|
15136
|
+
const milliunits = toMilliunits(params.amount);
|
|
15137
|
+
const monthKey = toMonthKey(params.month);
|
|
15138
|
+
const monthlyBudgetId = formatMonthlyBudgetId(monthKey, planId);
|
|
15139
|
+
const budget = await syncBudget(planId);
|
|
15140
|
+
const serverKnowledge = budget.current_server_knowledge ?? 0;
|
|
15141
|
+
const subcategories = budget.changed_entities?.be_subcategories ?? [];
|
|
15142
|
+
const existingBudgets = budget.changed_entities?.be_monthly_subcategory_budgets ?? [];
|
|
15143
|
+
const findCategory = (id) => {
|
|
15144
|
+
const c = subcategories.find((s) => s.id === id && notTombstone(s));
|
|
15145
|
+
if (!c?.id) throw ToolError.notFound(`Category not found: ${id}`);
|
|
15146
|
+
return { ...c, id: c.id };
|
|
15147
|
+
};
|
|
15148
|
+
const fromCategory = params.from_category_id ? findCategory(params.from_category_id) : null;
|
|
15149
|
+
const toCategory = params.to_category_id ? findCategory(params.to_category_id) : null;
|
|
15150
|
+
const fromBudgetId = fromCategory ? formatSubcategoryBudgetId(monthKey, fromCategory.id) : null;
|
|
15151
|
+
const toBudgetId = toCategory ? formatSubcategoryBudgetId(monthKey, toCategory.id) : null;
|
|
15152
|
+
const buildEntry = (categoryId, budgetId, signedDelta) => {
|
|
15153
|
+
const current = existingBudgets.find((b) => b.id === budgetId && notTombstone(b))?.budgeted ?? 0;
|
|
15154
|
+
return {
|
|
15155
|
+
id: budgetId,
|
|
15156
|
+
is_tombstone: false,
|
|
15157
|
+
entities_monthly_budget_id: monthlyBudgetId,
|
|
15158
|
+
entities_subcategory_id: categoryId,
|
|
15159
|
+
budgeted: current + signedDelta
|
|
15160
|
+
};
|
|
14785
15161
|
};
|
|
15162
|
+
const budgetEntries = [];
|
|
15163
|
+
if (fromCategory && fromBudgetId) budgetEntries.push(buildEntry(fromCategory.id, fromBudgetId, -milliunits));
|
|
15164
|
+
if (toCategory && toBudgetId) budgetEntries.push(buildEntry(toCategory.id, toBudgetId, milliunits));
|
|
15165
|
+
const source = fromCategory && toCategory ? MONEY_MOVEMENT_SOURCE.MOVEMENT : MONEY_MOVEMENT_SOURCE.ASSIGN;
|
|
15166
|
+
const result = await syncWrite(
|
|
15167
|
+
planId,
|
|
15168
|
+
{
|
|
15169
|
+
be_monthly_subcategory_budgets: budgetEntries,
|
|
15170
|
+
be_money_movements: [
|
|
15171
|
+
{
|
|
15172
|
+
id: crypto.randomUUID(),
|
|
15173
|
+
is_tombstone: false,
|
|
15174
|
+
from_entities_monthly_subcategory_budget_id: fromBudgetId,
|
|
15175
|
+
to_entities_monthly_subcategory_budget_id: toBudgetId,
|
|
15176
|
+
entities_money_movement_group_id: null,
|
|
15177
|
+
amount: milliunits,
|
|
15178
|
+
performed_by_user_id: userId,
|
|
15179
|
+
note: null,
|
|
15180
|
+
source,
|
|
15181
|
+
move_started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15182
|
+
move_accepted_at: null
|
|
15183
|
+
}
|
|
15184
|
+
]
|
|
15185
|
+
},
|
|
15186
|
+
serverKnowledge
|
|
15187
|
+
);
|
|
15188
|
+
const calcMap = buildSubcategoryCalcMap(result.changed_entities?.be_monthly_subcategory_budget_calculations ?? []);
|
|
15189
|
+
const budgetMap = buildSubcategoryBudgetMap(result.changed_entities?.be_monthly_subcategory_budgets ?? []);
|
|
15190
|
+
for (const e of budgetEntries) {
|
|
15191
|
+
const key = `${monthKey}/${e.entities_subcategory_id}`;
|
|
15192
|
+
if (!budgetMap.has(key)) budgetMap.set(key, e);
|
|
15193
|
+
}
|
|
15194
|
+
const categories = [];
|
|
15195
|
+
if (fromCategory) categories.push(mapCategoryForMonth(fromCategory, budgetMap, calcMap, monthKey));
|
|
15196
|
+
if (toCategory) categories.push(mapCategoryForMonth(toCategory, budgetMap, calcMap, monthKey));
|
|
15197
|
+
return { categories };
|
|
14786
15198
|
}
|
|
14787
15199
|
});
|
|
14788
15200
|
|
|
14789
|
-
// src/tools/
|
|
14790
|
-
var
|
|
14791
|
-
name: "
|
|
14792
|
-
displayName: "
|
|
14793
|
-
description: "
|
|
14794
|
-
summary: "
|
|
14795
|
-
icon: "
|
|
14796
|
-
group: "
|
|
15201
|
+
// src/tools/update-category-budget.ts
|
|
15202
|
+
var updateCategoryBudget = defineTool({
|
|
15203
|
+
name: "update_category_budget",
|
|
15204
|
+
displayName: "Update Category Budget",
|
|
15205
|
+
description: "Set the budgeted amount for a category in a specific month. Amount is in currency units (e.g. 500 to budget $500). The month should be in YYYY-MM format (e.g. 2026-03 for March 2026).",
|
|
15206
|
+
summary: "Set budgeted amount for a category",
|
|
15207
|
+
icon: "pencil",
|
|
15208
|
+
group: "Categories",
|
|
14797
15209
|
input: external_exports.object({
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
"Amount in currency units (negative for expenses, positive for income). E.g. -42.50 for a $42.50 expense."
|
|
14802
|
-
),
|
|
14803
|
-
payee_name: external_exports.string().optional().describe("Payee name (creates new payee if not found)"),
|
|
14804
|
-
payee_id: external_exports.string().optional().describe("Existing payee ID (takes precedence over payee_name)"),
|
|
14805
|
-
category_id: external_exports.string().optional().describe("Category ID to assign"),
|
|
14806
|
-
memo: external_exports.string().optional().describe("Transaction memo"),
|
|
14807
|
-
cleared: external_exports.enum(["cleared", "uncleared", "reconciled"]).optional().describe("Cleared status (default uncleared)"),
|
|
14808
|
-
approved: external_exports.boolean().optional().describe("Whether the transaction is approved (default true)"),
|
|
14809
|
-
flag_color: external_exports.enum(["red", "orange", "yellow", "green", "blue", "purple"]).optional().describe("Flag color")
|
|
15210
|
+
category_id: external_exports.string().min(1).describe("Category ID to budget"),
|
|
15211
|
+
month: external_exports.string().regex(/^\d{4}-\d{2}(-\d{2})?$/, "Month must be YYYY-MM or YYYY-MM-DD").describe("Month in YYYY-MM format (e.g. 2026-03)"),
|
|
15212
|
+
budgeted: external_exports.number().describe("Amount to budget in currency units (e.g. 500 for $500)")
|
|
14810
15213
|
}),
|
|
14811
15214
|
output: external_exports.object({
|
|
14812
|
-
|
|
15215
|
+
category: categorySchema
|
|
14813
15216
|
}),
|
|
14814
15217
|
handle: async (params) => {
|
|
14815
15218
|
const planId = getPlanId();
|
|
14816
|
-
const
|
|
14817
|
-
const
|
|
14818
|
-
const
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
|
|
14826
|
-
|
|
14827
|
-
|
|
14828
|
-
|
|
14829
|
-
|
|
14830
|
-
|
|
15219
|
+
const userId = getUserId();
|
|
15220
|
+
const milliunits = toMilliunits(params.budgeted);
|
|
15221
|
+
const monthKey = toMonthKey(params.month);
|
|
15222
|
+
const budgetId = formatSubcategoryBudgetId(monthKey, params.category_id);
|
|
15223
|
+
const monthlyBudgetId = formatMonthlyBudgetId(monthKey, planId);
|
|
15224
|
+
const budget = await syncBudget(planId);
|
|
15225
|
+
const serverKnowledge = budget.current_server_knowledge ?? 0;
|
|
15226
|
+
const category = (budget.changed_entities?.be_subcategories ?? []).find(
|
|
15227
|
+
(c) => c.id === params.category_id && notTombstone(c)
|
|
15228
|
+
);
|
|
15229
|
+
if (!category) {
|
|
15230
|
+
throw ToolError.notFound(`Category not found: ${params.category_id}`);
|
|
15231
|
+
}
|
|
15232
|
+
const existingBudget = (budget.changed_entities?.be_monthly_subcategory_budgets ?? []).find(
|
|
15233
|
+
(b) => b.id === budgetId && notTombstone(b)
|
|
15234
|
+
);
|
|
15235
|
+
const delta = milliunits - (existingBudget?.budgeted ?? 0);
|
|
15236
|
+
const budgetEntry = {
|
|
15237
|
+
id: budgetId,
|
|
15238
|
+
is_tombstone: false,
|
|
15239
|
+
entities_monthly_budget_id: monthlyBudgetId,
|
|
15240
|
+
entities_subcategory_id: params.category_id,
|
|
15241
|
+
budgeted: milliunits
|
|
14831
15242
|
};
|
|
14832
|
-
const
|
|
14833
|
-
|
|
14834
|
-
|
|
14835
|
-
|
|
14836
|
-
|
|
14837
|
-
|
|
15243
|
+
const changedEntities = { be_monthly_subcategory_budgets: [budgetEntry] };
|
|
15244
|
+
if (delta !== 0) {
|
|
15245
|
+
changedEntities.be_money_movements = [
|
|
15246
|
+
{
|
|
15247
|
+
id: crypto.randomUUID(),
|
|
15248
|
+
is_tombstone: false,
|
|
15249
|
+
to_entities_monthly_subcategory_budget_id: delta > 0 ? budgetId : null,
|
|
15250
|
+
from_entities_monthly_subcategory_budget_id: delta < 0 ? budgetId : null,
|
|
15251
|
+
entities_money_movement_group_id: null,
|
|
15252
|
+
amount: Math.abs(delta),
|
|
15253
|
+
performed_by_user_id: userId,
|
|
15254
|
+
note: null,
|
|
15255
|
+
source: MONEY_MOVEMENT_SOURCE.ASSIGN,
|
|
15256
|
+
move_started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15257
|
+
move_accepted_at: null
|
|
15258
|
+
}
|
|
15259
|
+
];
|
|
14838
15260
|
}
|
|
14839
|
-
|
|
15261
|
+
const result = await syncWrite(planId, changedEntities, serverKnowledge);
|
|
15262
|
+
const calcMap = buildSubcategoryCalcMap(result.changed_entities?.be_monthly_subcategory_budget_calculations ?? []);
|
|
15263
|
+
const budgetMap = buildSubcategoryBudgetMap(result.changed_entities?.be_monthly_subcategory_budgets ?? []);
|
|
15264
|
+
const key = `${monthKey}/${params.category_id}`;
|
|
15265
|
+
if (!budgetMap.has(key)) budgetMap.set(key, budgetEntry);
|
|
15266
|
+
return { category: mapCategoryForMonth(category, budgetMap, calcMap, monthKey) };
|
|
14840
15267
|
}
|
|
14841
15268
|
});
|
|
14842
15269
|
|
|
14843
15270
|
// src/tools/update-transaction.ts
|
|
15271
|
+
var resolveFlag = (requested, existing2) => {
|
|
15272
|
+
if (requested === "none") return null;
|
|
15273
|
+
if (requested) return FLAG_MAP[requested];
|
|
15274
|
+
return existing2 ?? null;
|
|
15275
|
+
};
|
|
15276
|
+
var resolveCleared = (requested, existing2) => {
|
|
15277
|
+
if (requested) return CLEARED_MAP[requested];
|
|
15278
|
+
return existing2 ?? "Uncleared";
|
|
15279
|
+
};
|
|
14844
15280
|
var updateTransaction = defineTool({
|
|
14845
15281
|
name: "update_transaction",
|
|
14846
15282
|
displayName: "Update Transaction",
|
|
14847
|
-
description: "Update an existing transaction in the active YNAB plan. Only specified fields are changed; omitted fields remain unchanged. Amount is in currency units (negative for expenses, positive for income).",
|
|
15283
|
+
description: "Update an existing transaction in the active YNAB plan. Only specified fields are changed; omitted fields remain unchanged. Amount is in currency units (negative for expenses, positive for income). Transfers and split transactions cannot be updated through this tool \u2014 edit them directly in YNAB.",
|
|
14848
15284
|
summary: "Update a transaction",
|
|
14849
15285
|
icon: "pencil",
|
|
14850
15286
|
group: "Transactions",
|
|
@@ -14859,175 +15295,85 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
14859
15295
|
memo: external_exports.string().optional().describe("New transaction memo"),
|
|
14860
15296
|
cleared: external_exports.enum(["cleared", "uncleared", "reconciled"]).optional().describe("New cleared status"),
|
|
14861
15297
|
approved: external_exports.boolean().optional().describe("New approval status"),
|
|
14862
|
-
flag_color: external_exports.enum(["red", "orange", "yellow", "green", "blue", "purple"]).optional().describe(
|
|
15298
|
+
flag_color: external_exports.enum(["red", "orange", "yellow", "green", "blue", "purple", "none"]).optional().describe('New flag color (pass "none" to clear)')
|
|
14863
15299
|
}),
|
|
14864
15300
|
output: external_exports.object({
|
|
14865
15301
|
transaction: transactionSchema
|
|
14866
15302
|
}),
|
|
14867
15303
|
handle: async (params) => {
|
|
14868
15304
|
const planId = getPlanId();
|
|
14869
|
-
const
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
14874
|
-
|
|
14875
|
-
if (
|
|
14876
|
-
|
|
14877
|
-
if (params.category_id !== void 0) transaction.entities_subcategory_id = params.category_id;
|
|
14878
|
-
if (params.memo !== void 0) transaction.memo = params.memo;
|
|
14879
|
-
if (params.cleared !== void 0) transaction.cleared = params.cleared;
|
|
14880
|
-
if (params.approved !== void 0) transaction.approved = params.approved;
|
|
14881
|
-
if (params.flag_color !== void 0) transaction.flag_color = params.flag_color;
|
|
14882
|
-
const result = await syncWrite(planId, {
|
|
14883
|
-
be_transactions: [transaction]
|
|
14884
|
-
});
|
|
14885
|
-
const saved = result.changed_entities?.be_transactions?.[0];
|
|
14886
|
-
if (!saved) {
|
|
14887
|
-
throw ToolError.internal("Transaction was updated but no data was returned");
|
|
15305
|
+
const budget = await syncBudget(planId);
|
|
15306
|
+
const serverKnowledge = budget.current_server_knowledge ?? 0;
|
|
15307
|
+
const lookups = buildLookups(budget.changed_entities ?? {});
|
|
15308
|
+
const existing2 = budget.changed_entities?.be_transactions?.find(
|
|
15309
|
+
(t) => t.id === params.transaction_id && !t.is_tombstone
|
|
15310
|
+
);
|
|
15311
|
+
if (!existing2) {
|
|
15312
|
+
throw ToolError.notFound(`Transaction not found: ${params.transaction_id}`);
|
|
14888
15313
|
}
|
|
14889
|
-
|
|
14890
|
-
|
|
14891
|
-
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
|
|
14895
|
-
|
|
14896
|
-
|
|
14897
|
-
|
|
14898
|
-
|
|
14899
|
-
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
|
|
14904
|
-
|
|
14905
|
-
|
|
14906
|
-
|
|
14907
|
-
|
|
14908
|
-
|
|
14909
|
-
|
|
14910
|
-
|
|
14911
|
-
|
|
14912
|
-
{
|
|
15314
|
+
if (existing2.transfer_account_id) {
|
|
15315
|
+
throw ToolError.validation("Cannot update transfer transactions \u2014 edit them in YNAB directly.");
|
|
15316
|
+
}
|
|
15317
|
+
const hasSubtransactions = (budget.changed_entities?.be_subtransactions ?? []).some(
|
|
15318
|
+
(s) => s.entities_transaction_id === params.transaction_id && !s.is_tombstone
|
|
15319
|
+
);
|
|
15320
|
+
if (hasSubtransactions) {
|
|
15321
|
+
throw ToolError.validation("Cannot update split transactions \u2014 edit them in YNAB directly.");
|
|
15322
|
+
}
|
|
15323
|
+
const changedEntities = {};
|
|
15324
|
+
let payeeId = params.payee_id ?? existing2.entities_payee_id ?? null;
|
|
15325
|
+
if (params.payee_name && !params.payee_id) {
|
|
15326
|
+
const resolved = resolvePayee(budget.changed_entities?.be_payees ?? [], params.payee_name);
|
|
15327
|
+
payeeId = resolved.payeeId;
|
|
15328
|
+
if (resolved.newPayee) {
|
|
15329
|
+
changedEntities.be_payees = [resolved.newPayee];
|
|
15330
|
+
lookups.payees.set(resolved.payeeId, params.payee_name);
|
|
15331
|
+
}
|
|
15332
|
+
}
|
|
15333
|
+
changedEntities.be_transaction_groups = [
|
|
15334
|
+
{
|
|
15335
|
+
id: params.transaction_id,
|
|
15336
|
+
be_transaction: {
|
|
14913
15337
|
id: params.transaction_id,
|
|
15338
|
+
is_tombstone: false,
|
|
14914
15339
|
entities_account_id: params.account_id,
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
|
|
14932
|
-
|
|
14933
|
-
|
|
14934
|
-
|
|
14935
|
-
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14942
|
-
|
|
14943
|
-
|
|
14944
|
-
// src/tools/list-months.ts
|
|
14945
|
-
var listMonths = defineTool({
|
|
14946
|
-
name: "list_months",
|
|
14947
|
-
displayName: "List Months",
|
|
14948
|
-
description: "List all budget months in the active YNAB plan. Returns income, budgeted, activity, and Ready to Assign amounts for each month. Sorted from most recent to oldest.",
|
|
14949
|
-
summary: "List budget months with summaries",
|
|
14950
|
-
icon: "calendar",
|
|
14951
|
-
group: "Months",
|
|
14952
|
-
input: external_exports.object({}),
|
|
14953
|
-
output: external_exports.object({
|
|
14954
|
-
months: external_exports.array(monthSchema).describe("List of budget months")
|
|
14955
|
-
}),
|
|
14956
|
-
handle: async () => {
|
|
14957
|
-
const planId = getPlanId();
|
|
14958
|
-
const result = await syncBudget(planId);
|
|
14959
|
-
const entities = result.changed_entities;
|
|
14960
|
-
const rawMonths = entities?.be_monthly_budgets ?? [];
|
|
14961
|
-
const rawCalcs = entities?.be_monthly_budget_calculations ?? [];
|
|
14962
|
-
const calcMap = /* @__PURE__ */ new Map();
|
|
14963
|
-
for (const calc of rawCalcs) {
|
|
14964
|
-
const budgetId = calc.entities_monthly_budget_id;
|
|
14965
|
-
if (budgetId) {
|
|
14966
|
-
const month = budgetId.replace("mb/", "");
|
|
14967
|
-
calcMap.set(month, calc);
|
|
15340
|
+
entities_payee_id: payeeId,
|
|
15341
|
+
entities_subcategory_id: params.category_id ?? existing2.entities_subcategory_id ?? null,
|
|
15342
|
+
entities_scheduled_transaction_id: existing2.entities_scheduled_transaction_id ?? null,
|
|
15343
|
+
date: params.date ?? existing2.date ?? "",
|
|
15344
|
+
date_entered_from_schedule: null,
|
|
15345
|
+
amount: params.amount !== void 0 ? toMilliunits(params.amount) : existing2.amount ?? 0,
|
|
15346
|
+
cash_amount: 0,
|
|
15347
|
+
credit_amount: 0,
|
|
15348
|
+
credit_amount_adjusted: 0,
|
|
15349
|
+
subcategory_credit_amount_preceding: 0,
|
|
15350
|
+
memo: params.memo ?? existing2.memo ?? null,
|
|
15351
|
+
cleared: resolveCleared(params.cleared, existing2.cleared),
|
|
15352
|
+
// YNAB's wire format calls this "accepted"; the public tool surface uses "approved".
|
|
15353
|
+
accepted: params.approved ?? existing2.accepted ?? false,
|
|
15354
|
+
check_number: null,
|
|
15355
|
+
flag: resolveFlag(params.flag_color, existing2.flag),
|
|
15356
|
+
transfer_account_id: existing2.transfer_account_id ?? null,
|
|
15357
|
+
transfer_transaction_id: null,
|
|
15358
|
+
transfer_subtransaction_id: null,
|
|
15359
|
+
matched_transaction_id: null,
|
|
15360
|
+
ynab_id: existing2.ynab_id ?? null,
|
|
15361
|
+
imported_payee: existing2.imported_payee ?? null,
|
|
15362
|
+
imported_date: null,
|
|
15363
|
+
original_imported_payee: existing2.original_imported_payee ?? null,
|
|
15364
|
+
provider_cleansed_payee: null,
|
|
15365
|
+
source: existing2.source ?? null,
|
|
15366
|
+
debt_transaction_type: null
|
|
15367
|
+
},
|
|
15368
|
+
be_subtransactions: null
|
|
14968
15369
|
}
|
|
15370
|
+
];
|
|
15371
|
+
const result = await syncWrite(planId, changedEntities, serverKnowledge);
|
|
15372
|
+
const saved = result.changed_entities?.be_transactions?.find((t) => t.id === params.transaction_id);
|
|
15373
|
+
if (!saved) {
|
|
15374
|
+
throw ToolError.internal("Transaction was updated but no data was returned");
|
|
14969
15375
|
}
|
|
14970
|
-
|
|
14971
|
-
return { months };
|
|
14972
|
-
}
|
|
14973
|
-
});
|
|
14974
|
-
|
|
14975
|
-
// src/tools/get-month.ts
|
|
14976
|
-
var getMonth = defineTool({
|
|
14977
|
-
name: "get_month",
|
|
14978
|
-
displayName: "Get Month",
|
|
14979
|
-
description: "Get budget summary and category details for a specific month. Returns the month overview (income, budgeted, activity, Ready to Assign) plus per-category breakdowns. Month format is YYYY-MM-DD using the first of the month (e.g. 2026-03-01).",
|
|
14980
|
-
summary: "Get budget details for a month",
|
|
14981
|
-
icon: "calendar",
|
|
14982
|
-
group: "Months",
|
|
14983
|
-
input: external_exports.object({
|
|
14984
|
-
month: external_exports.string().min(1).describe("Month in YYYY-MM-DD format (first of month, e.g. 2026-03-01)")
|
|
14985
|
-
}),
|
|
14986
|
-
output: external_exports.object({
|
|
14987
|
-
month: monthSchema,
|
|
14988
|
-
categories: external_exports.array(categorySchema).describe("Category budgets for this month")
|
|
14989
|
-
}),
|
|
14990
|
-
handle: async (params) => {
|
|
14991
|
-
const planId = getPlanId();
|
|
14992
|
-
const result = await syncBudget(planId);
|
|
14993
|
-
const entities = result.changed_entities;
|
|
14994
|
-
const rawMonths = entities?.be_monthly_budgets ?? [];
|
|
14995
|
-
const monthData = rawMonths.find((m) => m.month === params.month && !m.is_tombstone);
|
|
14996
|
-
if (!monthData) {
|
|
14997
|
-
throw ToolError.notFound(`Month not found: ${params.month}`);
|
|
14998
|
-
}
|
|
14999
|
-
const monthlyCalcs = entities?.be_monthly_budget_calculations ?? [];
|
|
15000
|
-
const monthCalc = monthlyCalcs.find((c) => {
|
|
15001
|
-
const budgetId = c.entities_monthly_budget_id;
|
|
15002
|
-
return budgetId && budgetId.replace("mb/", "") === params.month;
|
|
15003
|
-
});
|
|
15004
|
-
const rawCategories = (entities?.be_subcategories ?? []).filter((c) => !c.is_tombstone && c.is_hidden !== true);
|
|
15005
|
-
const subcatCalcs = entities?.be_monthly_subcategory_budget_calculations ?? [];
|
|
15006
|
-
const calcMap = /* @__PURE__ */ new Map();
|
|
15007
|
-
for (const calc of subcatCalcs) {
|
|
15008
|
-
const entityId = calc.entities_monthly_subcategory_budget_id;
|
|
15009
|
-
if (entityId) {
|
|
15010
|
-
const parts = entityId.split("/");
|
|
15011
|
-
const categoryId = parts.length >= 3 ? parts.slice(2).join("/") : entityId;
|
|
15012
|
-
calcMap.set(categoryId, calc);
|
|
15013
|
-
}
|
|
15014
|
-
}
|
|
15015
|
-
const categories = rawCategories.map((c) => {
|
|
15016
|
-
const calc = calcMap.get(c.id ?? "");
|
|
15017
|
-
return mapCategory({
|
|
15018
|
-
...c,
|
|
15019
|
-
budgeted: calc?.budgeted ?? c.budgeted,
|
|
15020
|
-
activity: calc?.activity ?? c.activity,
|
|
15021
|
-
balance: calc?.balance ?? c.balance,
|
|
15022
|
-
goal_type: calc?.goal_type ?? c.goal_type,
|
|
15023
|
-
goal_target: calc?.goal_target ?? c.goal_target,
|
|
15024
|
-
goal_percentage_complete: calc?.goal_percentage_complete ?? c.goal_percentage_complete
|
|
15025
|
-
});
|
|
15026
|
-
});
|
|
15027
|
-
return {
|
|
15028
|
-
month: mapMonth(monthData, monthCalc),
|
|
15029
|
-
categories
|
|
15030
|
-
};
|
|
15376
|
+
return { transaction: mapTransaction(saved, lookups) };
|
|
15031
15377
|
}
|
|
15032
15378
|
});
|
|
15033
15379
|
|
|
@@ -15049,6 +15395,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15049
15395
|
// Categories
|
|
15050
15396
|
listCategories,
|
|
15051
15397
|
updateCategoryBudget,
|
|
15398
|
+
moveCategoryBudget,
|
|
15052
15399
|
// Payees
|
|
15053
15400
|
listPayees,
|
|
15054
15401
|
// Transactions
|
|
@@ -15069,7 +15416,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15069
15416
|
};
|
|
15070
15417
|
var src_default = new YnabPlugin();
|
|
15071
15418
|
|
|
15072
|
-
// dist/
|
|
15419
|
+
// dist/_adapter_entry_fb321989-7b03-4647-9b5e-64822784cb35.ts
|
|
15073
15420
|
if (!globalThis.__openTabs) {
|
|
15074
15421
|
globalThis.__openTabs = {};
|
|
15075
15422
|
} else {
|
|
@@ -15285,5 +15632,5 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
15285
15632
|
};
|
|
15286
15633
|
delete src_default.onDeactivate;
|
|
15287
15634
|
}
|
|
15288
|
-
})();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["ynab"]){var a=o.adapters["ynab"];a.__adapterHash="
|
|
15635
|
+
})();(function(){var o=(globalThis).__openTabs;if(o&&o.adapters&&o.adapters["ynab"]){var a=o.adapters["ynab"];a.__adapterHash="dddcb110d2a66a4da7a57c142d0df2274bcc27f65c27b3a8d89c81d827caa8de";if(a.tools&&Array.isArray(a.tools)){for(var i=0;i<a.tools.length;i++){Object.freeze(a.tools[i]);}Object.freeze(a.tools);}Object.freeze(a);Object.defineProperty(o.adapters,"ynab",{value:a,writable:false,configurable:false,enumerable:true});Object.defineProperty(o,"adapters",{value:o.adapters,writable:false,configurable:false});}})();
|
|
15289
15636
|
//# sourceMappingURL=adapter.iife.js.map
|