cata-centavo 0.1.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/LICENSE +21 -0
- package/README.md +159 -0
- package/dist/bin/cata-centavo.js +226 -0
- package/dist/bin/cata-centavo.js.map +1 -0
- package/dist/cli/dispatch.js +67 -0
- package/dist/cli/dispatch.js.map +1 -0
- package/dist/cli/doctor.js +178 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.js +125 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/progress.js +85 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/cli/sync.js +26 -0
- package/dist/cli/sync.js.map +1 -0
- package/dist/config.js +119 -0
- package/dist/config.js.map +1 -0
- package/dist/core/account.js +12 -0
- package/dist/core/account.js.map +1 -0
- package/dist/core/accounts.js +91 -0
- package/dist/core/accounts.js.map +1 -0
- package/dist/core/aggregate.js +83 -0
- package/dist/core/aggregate.js.map +1 -0
- package/dist/core/balance.js +68 -0
- package/dist/core/balance.js.map +1 -0
- package/dist/core/bill-rows.js +132 -0
- package/dist/core/bill-rows.js.map +1 -0
- package/dist/core/bill.js +103 -0
- package/dist/core/bill.js.map +1 -0
- package/dist/core/category-source.js +26 -0
- package/dist/core/category-source.js.map +1 -0
- package/dist/core/category.js +72 -0
- package/dist/core/category.js.map +1 -0
- package/dist/core/consent.js +19 -0
- package/dist/core/consent.js.map +1 -0
- package/dist/core/contracts.js +9 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/counterparty.js +69 -0
- package/dist/core/counterparty.js.map +1 -0
- package/dist/core/date.js +25 -0
- package/dist/core/date.js.map +1 -0
- package/dist/core/description.js +58 -0
- package/dist/core/description.js.map +1 -0
- package/dist/core/diagnose.js +32 -0
- package/dist/core/diagnose.js.map +1 -0
- package/dist/core/mcc.js +124 -0
- package/dist/core/mcc.js.map +1 -0
- package/dist/core/refresh.js +142 -0
- package/dist/core/refresh.js.map +1 -0
- package/dist/core/self-transfer.js +22 -0
- package/dist/core/self-transfer.js.map +1 -0
- package/dist/core/taxonomy-tree.js +148 -0
- package/dist/core/taxonomy-tree.js.map +1 -0
- package/dist/core/taxonomy.js +70 -0
- package/dist/core/taxonomy.js.map +1 -0
- package/dist/core/transaction.js +2 -0
- package/dist/core/transaction.js.map +1 -0
- package/dist/core/transactions.js +46 -0
- package/dist/core/transactions.js.map +1 -0
- package/dist/logging.js +93 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp/cursor.js +64 -0
- package/dist/mcp/cursor.js.map +1 -0
- package/dist/mcp/format.js +38 -0
- package/dist/mcp/format.js.map +1 -0
- package/dist/mcp/server.js +50 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/source.js +2 -0
- package/dist/mcp/source.js.map +1 -0
- package/dist/mcp/tools/accounts.js +144 -0
- package/dist/mcp/tools/accounts.js.map +1 -0
- package/dist/mcp/tools/balance.js +96 -0
- package/dist/mcp/tools/balance.js.map +1 -0
- package/dist/mcp/tools/bill-summary-format.js +161 -0
- package/dist/mcp/tools/bill-summary-format.js.map +1 -0
- package/dist/mcp/tools/bill-summary.js +105 -0
- package/dist/mcp/tools/bill-summary.js.map +1 -0
- package/dist/mcp/tools/bills.js +144 -0
- package/dist/mcp/tools/bills.js.map +1 -0
- package/dist/mcp/tools/closing-days.js +90 -0
- package/dist/mcp/tools/closing-days.js.map +1 -0
- package/dist/mcp/tools/list-transactions.js +122 -0
- package/dist/mcp/tools/list-transactions.js.map +1 -0
- package/dist/mcp/tools/result.js +12 -0
- package/dist/mcp/tools/result.js.map +1 -0
- package/dist/mcp/tools/set-category.js +74 -0
- package/dist/mcp/tools/set-category.js.map +1 -0
- package/dist/mcp/tools/sources.js +73 -0
- package/dist/mcp/tools/sources.js.map +1 -0
- package/dist/mcp/tools/transaction-details.js +146 -0
- package/dist/mcp/tools/transaction-details.js.map +1 -0
- package/dist/mcp/tools/transaction-input.js +51 -0
- package/dist/mcp/tools/transaction-input.js.map +1 -0
- package/dist/mcp/tools/transactions.js +186 -0
- package/dist/mcp/tools/transactions.js.map +1 -0
- package/dist/pluggy/client.js +123 -0
- package/dist/pluggy/client.js.map +1 -0
- package/dist/pluggy/errors.js +152 -0
- package/dist/pluggy/errors.js.map +1 -0
- package/dist/pluggy/mapper.js +155 -0
- package/dist/pluggy/mapper.js.map +1 -0
- package/dist/pluggy/money.js +53 -0
- package/dist/pluggy/money.js.map +1 -0
- package/dist/pluggy/transaction-mapper.js +124 -0
- package/dist/pluggy/transaction-mapper.js.map +1 -0
- package/dist/pluggy/transport.js +170 -0
- package/dist/pluggy/transport.js.map +1 -0
- package/dist/pluggy/wire.js +207 -0
- package/dist/pluggy/wire.js.map +1 -0
- package/dist/storage/categories.js +101 -0
- package/dist/storage/categories.js.map +1 -0
- package/dist/storage/category-sql.js +37 -0
- package/dist/storage/category-sql.js.map +1 -0
- package/dist/storage/closing-days.js +27 -0
- package/dist/storage/closing-days.js.map +1 -0
- package/dist/storage/db.js +162 -0
- package/dist/storage/db.js.map +1 -0
- package/dist/storage/diagnostics.js +57 -0
- package/dist/storage/diagnostics.js.map +1 -0
- package/dist/storage/harvest.js +87 -0
- package/dist/storage/harvest.js.map +1 -0
- package/dist/storage/migrations.js +125 -0
- package/dist/storage/migrations.js.map +1 -0
- package/dist/storage/transaction-row.js +74 -0
- package/dist/storage/transaction-row.js.map +1 -0
- package/dist/storage/transactions.js +213 -0
- package/dist/storage/transactions.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { ClosingDateSource, newestBill, } from "../../core/bill.js";
|
|
2
|
+
import { deriveBillCommitment, derivePostedCents, partitionBillRows, } from "../../core/bill-rows.js";
|
|
3
|
+
import { isSelfTransfer } from "../../core/self-transfer.js";
|
|
4
|
+
import { toDecimal } from "../format.js";
|
|
5
|
+
/** The card's own figures, always available because they come from the account itself. */
|
|
6
|
+
export function formatSummaryBase(account) {
|
|
7
|
+
return {
|
|
8
|
+
accountId: account.id,
|
|
9
|
+
institution: account.institution,
|
|
10
|
+
currency: account.currency,
|
|
11
|
+
utilization: toDecimal(account.amountCents),
|
|
12
|
+
creditLimit: decimalOrNull(account.credit?.limitCents ?? null),
|
|
13
|
+
availableLimit: decimalOrNull(account.credit?.availableLimitCents ?? null),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The two estimates of the open bill, plus the gap the caller must quote.
|
|
18
|
+
* They are measured differently and neither one bounds the other.
|
|
19
|
+
*/
|
|
20
|
+
export function formatDerivedFigures(account, rows, bills, openCycle) {
|
|
21
|
+
let openBillId = null;
|
|
22
|
+
if (openCycle.source === ClosingDateSource.openBill) {
|
|
23
|
+
openBillId = newestBill(bills)?.id ?? null;
|
|
24
|
+
}
|
|
25
|
+
const partition = partitionBillRows(rows, openCycle.openCycle, openBillId);
|
|
26
|
+
const postedCents = postedFor(openCycle, bills, partition.openCycleRows);
|
|
27
|
+
const commitment = deriveBillCommitment(partition, account.amountCents);
|
|
28
|
+
return {
|
|
29
|
+
posted: toDecimal(postedCents),
|
|
30
|
+
committed: toDecimal(commitment.committedCents),
|
|
31
|
+
futureInstalments: toDecimal(commitment.futureCents),
|
|
32
|
+
gap: toDecimal(Math.abs(commitment.committedCents - postedCents)),
|
|
33
|
+
committedIsNegative: commitment.committedCents < 0,
|
|
34
|
+
postedExceedsCommitted: postedCents > commitment.committedCents,
|
|
35
|
+
topTransactions: topTransactions(partition.openCycleRows),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function postedFor(openCycle, bills, rows) {
|
|
39
|
+
if (openCycle.source === ClosingDateSource.openBill) {
|
|
40
|
+
return newestBill(bills)?.totalCents ?? derivePostedCents(rows);
|
|
41
|
+
}
|
|
42
|
+
return derivePostedCents(rows);
|
|
43
|
+
}
|
|
44
|
+
function topTransactions(rows) {
|
|
45
|
+
return rows
|
|
46
|
+
.filter((row) => row.amountCents < 0 && !isSelfTransfer(row))
|
|
47
|
+
.toSorted(compareCharges)
|
|
48
|
+
.slice(0, 5)
|
|
49
|
+
.map((row) => ({
|
|
50
|
+
id: row.id,
|
|
51
|
+
date: row.localDate,
|
|
52
|
+
description: row.description,
|
|
53
|
+
amount: toDecimal(-row.amountCents),
|
|
54
|
+
category: row.category,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
function compareCharges(left, right) {
|
|
58
|
+
const amountOrder = left.amountCents - right.amountCents;
|
|
59
|
+
if (amountOrder !== 0) {
|
|
60
|
+
return amountOrder;
|
|
61
|
+
}
|
|
62
|
+
const dateOrder = right.localDate.localeCompare(left.localDate);
|
|
63
|
+
if (dateOrder !== 0) {
|
|
64
|
+
return dateOrder;
|
|
65
|
+
}
|
|
66
|
+
return left.id.localeCompare(right.id);
|
|
67
|
+
}
|
|
68
|
+
/** The open cycle's own dates, derived differently depending on what identified the cycle. */
|
|
69
|
+
export function formatCycle(openCycle, bills, account, storedDay) {
|
|
70
|
+
const dates = cycleDates(openCycle, bills, account, storedDay);
|
|
71
|
+
return {
|
|
72
|
+
openCycle: openCycle.openCycle,
|
|
73
|
+
...dates,
|
|
74
|
+
closingDateSource: openCycle.source,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function cycleDates(openCycle, bills, account, storedDay) {
|
|
78
|
+
const latestBill = newestBill(bills);
|
|
79
|
+
if (openCycle.source === ClosingDateSource.openBill) {
|
|
80
|
+
return datesFromOpenBill(latestBill);
|
|
81
|
+
}
|
|
82
|
+
if (openCycle.source === ClosingDateSource.lastClosed) {
|
|
83
|
+
return datesAfterClosedBill(latestBill);
|
|
84
|
+
}
|
|
85
|
+
if (openCycle.source === ClosingDateSource.local) {
|
|
86
|
+
return datesFromLocalDay(openCycle.openCycle, storedDay, account);
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
closingDate: dateInCycleFrom(creditDate(account, "balanceCloseDate"), openCycle.openCycle),
|
|
90
|
+
dueDate: dateInCycleFrom(creditDate(account, "balanceDueDate"), openCycle.openCycle),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function datesFromOpenBill(bill) {
|
|
94
|
+
if (bill === null) {
|
|
95
|
+
return { closingDate: null, dueDate: null };
|
|
96
|
+
}
|
|
97
|
+
return { closingDate: bill.closingDate, dueDate: bill.dueDate };
|
|
98
|
+
}
|
|
99
|
+
function datesAfterClosedBill(bill) {
|
|
100
|
+
if (bill === null) {
|
|
101
|
+
return { closingDate: null, dueDate: null };
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
closingDate: shiftOneMonth(bill.closingDate),
|
|
105
|
+
dueDate: shiftOneMonth(bill.dueDate),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function datesFromLocalDay(cycle, storedDay, account) {
|
|
109
|
+
let closingDate = null;
|
|
110
|
+
if (storedDay !== null) {
|
|
111
|
+
closingDate = dateInCycle(cycle, storedDay);
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
closingDate,
|
|
115
|
+
dueDate: dateInCycleFrom(creditDate(account, "balanceDueDate"), cycle),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function creditDate(account, field) {
|
|
119
|
+
if (account.credit === null) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
return account.credit[field];
|
|
123
|
+
}
|
|
124
|
+
function decimalOrNull(cents) {
|
|
125
|
+
if (cents === null) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return toDecimal(cents);
|
|
129
|
+
}
|
|
130
|
+
function shiftOneMonth(date) {
|
|
131
|
+
if (date === null) {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
const year = Number(date.slice(0, 4));
|
|
135
|
+
const month = Number(date.slice(5, 7));
|
|
136
|
+
const day = Number(date.slice(8, 10));
|
|
137
|
+
let shiftedYear = year;
|
|
138
|
+
let shiftedMonth = month + 1;
|
|
139
|
+
if (shiftedMonth === 13) {
|
|
140
|
+
shiftedYear += 1;
|
|
141
|
+
shiftedMonth = 1;
|
|
142
|
+
}
|
|
143
|
+
return dateFromParts(shiftedYear, shiftedMonth, day);
|
|
144
|
+
}
|
|
145
|
+
function dateInCycleFrom(date, cycle) {
|
|
146
|
+
if (date === null) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
return dateInCycle(cycle, date.getUTCDate());
|
|
150
|
+
}
|
|
151
|
+
function dateInCycle(cycle, day) {
|
|
152
|
+
const year = Number(cycle.slice(0, 4));
|
|
153
|
+
const month = Number(cycle.slice(5, 7));
|
|
154
|
+
return dateFromParts(year, month, day);
|
|
155
|
+
}
|
|
156
|
+
function dateFromParts(year, month, day) {
|
|
157
|
+
const lastDay = new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
158
|
+
const clampedDay = Math.min(day, lastDay);
|
|
159
|
+
return `${String(year).padStart(4, "0")}-${String(month).padStart(2, "0")}-${String(clampedDay).padStart(2, "0")}`;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=bill-summary-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bill-summary-format.js","sourceRoot":"","sources":["../../../src/mcp/tools/bill-summary-format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { identifyOpenCycle } from "../../core/bill.js";
|
|
2
|
+
import { localDayOf, todayIn } from "../../core/date.js";
|
|
3
|
+
import { formatCycle, formatDerivedFigures, formatSummaryBase } from "./bill-summary-format.js";
|
|
4
|
+
import { finishToolError, textResult } from "./result.js";
|
|
5
|
+
/** Refreshes the card's connection, then reports whatever the cache can support. */
|
|
6
|
+
export async function executeBillSummary(options) {
|
|
7
|
+
const { deps, source, reader, account: resolvedAccount, startedAt } = options;
|
|
8
|
+
const loaded = await reader.load([resolvedAccount.connectionId]);
|
|
9
|
+
const unavailable = unavailableFor(loaded.unavailable, resolvedAccount.connectionId);
|
|
10
|
+
const refusal = unavailableResult(unavailable, resolvedAccount, deps.log, startedAt);
|
|
11
|
+
if (refusal !== null) {
|
|
12
|
+
return refusal;
|
|
13
|
+
}
|
|
14
|
+
const account = loaded.accounts.find(({ id }) => id === resolvedAccount.id) ?? resolvedAccount;
|
|
15
|
+
return summarizeCachedCard({ deps, source, reader, account });
|
|
16
|
+
}
|
|
17
|
+
async function summarizeCachedCard(options) {
|
|
18
|
+
const { deps, source, reader, account } = options;
|
|
19
|
+
const rows = reader.cardRows(account.id);
|
|
20
|
+
const base = formatSummaryBase(account);
|
|
21
|
+
if (rows.length === 0) {
|
|
22
|
+
return textResult({
|
|
23
|
+
...base,
|
|
24
|
+
message: "This card has never been synced, or its latest sync returned no cached transactions.",
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const today = todayIn(deps.clock);
|
|
28
|
+
const dataThrough = reader.dataThrough([account.id], today).get(account.connectionId);
|
|
29
|
+
if (dataThrough === undefined) {
|
|
30
|
+
return textResult({
|
|
31
|
+
...base,
|
|
32
|
+
message: "No cached transaction date is available through today, so bill figures were omitted.",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return summarizeDatedCard({ deps, source, account, rows, base, today, dataThrough });
|
|
36
|
+
}
|
|
37
|
+
async function summarizeDatedCard(options) {
|
|
38
|
+
const { deps, source, account, rows, base, today, dataThrough } = options;
|
|
39
|
+
const bills = await source.bank.getBills(account);
|
|
40
|
+
const storedDay = storedClosingDay(deps.closingDays, account.id);
|
|
41
|
+
const balanceDueDate = dateOnly(account.credit?.balanceDueDate ?? null);
|
|
42
|
+
const openCycle = identifyOpenCycle(bills, storedDay, balanceDueDate, today);
|
|
43
|
+
const freshness = {
|
|
44
|
+
dataThrough,
|
|
45
|
+
staleDays: staleDays(account.lastUpdatedAt, dataThrough),
|
|
46
|
+
};
|
|
47
|
+
if (openCycle === null) {
|
|
48
|
+
return textResult({
|
|
49
|
+
...base,
|
|
50
|
+
...freshness,
|
|
51
|
+
message: "The open cycle cannot be identified. Use setClosingDay for this card.",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return textResult({
|
|
55
|
+
...base,
|
|
56
|
+
cycle: formatCycle(openCycle, bills, account, storedDay),
|
|
57
|
+
...formatDerivedFigures(account, rows, bills, openCycle),
|
|
58
|
+
...freshness,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function storedClosingDay(store, accountId) {
|
|
62
|
+
if (store === null || store === undefined) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return store.list().find((entry) => entry.accountId === accountId)?.day ?? null;
|
|
66
|
+
}
|
|
67
|
+
function unavailableFor(unavailable, connectionId) {
|
|
68
|
+
return unavailable.find((candidate) => candidate.connectionId === connectionId) ?? null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Only the failures the model can act on become readable content. Anything else
|
|
72
|
+
* throws, because a protocol error is the channel a human reads.
|
|
73
|
+
*/
|
|
74
|
+
function unavailableResult(unavailable, account, log, startedAt) {
|
|
75
|
+
if (unavailable === null) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
if (!isReadableUnavailable(unavailable.kind)) {
|
|
79
|
+
throw new Error(unavailable.message);
|
|
80
|
+
}
|
|
81
|
+
return finishToolError(log, startedAt, unavailable.message, {
|
|
82
|
+
tool: "getBillSummary",
|
|
83
|
+
accountId: account.id,
|
|
84
|
+
connectionId: unavailable.connectionId,
|
|
85
|
+
kind: unavailable.kind,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function isReadableUnavailable(kind) {
|
|
89
|
+
return kind === "consent-revoked"
|
|
90
|
+
|| kind === "consent-expired"
|
|
91
|
+
|| kind === "unknown-connection"
|
|
92
|
+
|| kind === "no-accounts";
|
|
93
|
+
}
|
|
94
|
+
function dateOnly(date) {
|
|
95
|
+
return date?.toISOString().slice(0, 10) ?? null;
|
|
96
|
+
}
|
|
97
|
+
function staleDays(lastUpdatedAt, dataThrough) {
|
|
98
|
+
if (lastUpdatedAt === null) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const updatedDay = localDayOf(lastUpdatedAt.toISOString());
|
|
102
|
+
const milliseconds = Date.parse(`${updatedDay}T00:00:00.000Z`) - Date.parse(`${dataThrough}T00:00:00.000Z`);
|
|
103
|
+
return Math.max(0, Math.trunc(milliseconds / 86_400_000));
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=bill-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bill-summary.js","sourceRoot":"","sources":["../../../src/mcp/tools/bill-summary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ACCOUNT_TYPES } from "../../core/account.js";
|
|
3
|
+
import { toDecimal } from "../format.js";
|
|
4
|
+
import { executeBillSummary } from "./bill-summary.js";
|
|
5
|
+
import { configurationProblems, finishToolError, textResult, } from "./result.js";
|
|
6
|
+
export const GET_BILLS_DESCRIPTION = `Credit card statements for one card, newest first.
|
|
7
|
+
|
|
8
|
+
Use this tool when:
|
|
9
|
+
- the user asks what a past bill totalled, or when one was due
|
|
10
|
+
- you need the closing day of recent cycles
|
|
11
|
+
- the user asks whether a bill was paid
|
|
12
|
+
|
|
13
|
+
Returns: cycles with closing date, due date, total, minimum payment, finance
|
|
14
|
+
charges and payments. Usually these are closed statements, but on some banks the
|
|
15
|
+
newest entry is the cycle still in progress — getBillSummary says which. An empty
|
|
16
|
+
list means this bank does not publish bills, which is normal on non-regulated
|
|
17
|
+
connections.`;
|
|
18
|
+
export const GET_BILL_SUMMARY_DESCRIPTION = `The credit card bill currently in progress, as two independent estimates.
|
|
19
|
+
|
|
20
|
+
Use this tool when:
|
|
21
|
+
- the user asks what their open or current bill is
|
|
22
|
+
- the user asks how much of the card limit is used
|
|
23
|
+
- the user is deciding whether to spend more this cycle
|
|
24
|
+
|
|
25
|
+
Returns: posted, what has actually posted to the open cycle, valid only through
|
|
26
|
+
dataThrough; and committed, the used limit minus instalments scheduled for later
|
|
27
|
+
cycles. They are measured differently and neither is a proven bound, so quote
|
|
28
|
+
their gap rather than a midpoint. Also returns cycle and limit details, feed
|
|
29
|
+
staleness, and the five largest purchases in the cycle.`;
|
|
30
|
+
const getBillsSchema = z.object({
|
|
31
|
+
accountId: z.string().min(1),
|
|
32
|
+
limit: z.number().int().min(1).max(100).default(12),
|
|
33
|
+
});
|
|
34
|
+
const getBillSummarySchema = z.object({
|
|
35
|
+
accountId: z.string().min(1),
|
|
36
|
+
});
|
|
37
|
+
export function registerGetBills(server, deps) {
|
|
38
|
+
server.registerTool("getBills", { description: GET_BILLS_DESCRIPTION, inputSchema: getBillsSchema }, async (input) => handleGetBills(deps, input));
|
|
39
|
+
}
|
|
40
|
+
export function registerGetBillSummary(server, deps) {
|
|
41
|
+
server.registerTool("getBillSummary", { description: GET_BILL_SUMMARY_DESCRIPTION, inputSchema: getBillSummarySchema }, async (input) => handleGetBillSummary(deps, input));
|
|
42
|
+
}
|
|
43
|
+
export async function handleGetBills(deps, rawInput) {
|
|
44
|
+
const startedAt = Date.now();
|
|
45
|
+
const parsed = getBillsSchema.safeParse(rawInput);
|
|
46
|
+
if (!parsed.success) {
|
|
47
|
+
return finishToolError(deps.log, startedAt, parsed.error.message, { tool: "getBills" });
|
|
48
|
+
}
|
|
49
|
+
if (!deps.source.ok) {
|
|
50
|
+
return finishToolError(deps.log, startedAt, configurationProblems(deps.source.problems), { tool: "getBills" });
|
|
51
|
+
}
|
|
52
|
+
const resolved = await resolveCreditAccount({
|
|
53
|
+
source: deps.source,
|
|
54
|
+
log: deps.log,
|
|
55
|
+
startedAt,
|
|
56
|
+
accountId: parsed.data.accountId,
|
|
57
|
+
tool: "getBills",
|
|
58
|
+
});
|
|
59
|
+
if (!resolved.ok) {
|
|
60
|
+
return resolved.result;
|
|
61
|
+
}
|
|
62
|
+
const bills = await deps.source.bank.getBills(resolved.account);
|
|
63
|
+
return textResult({ bills: bills.slice(0, parsed.data.limit).map(formatBill) });
|
|
64
|
+
}
|
|
65
|
+
export async function handleGetBillSummary(deps, rawInput) {
|
|
66
|
+
const startedAt = Date.now();
|
|
67
|
+
const parsed = getBillSummarySchema.safeParse(rawInput);
|
|
68
|
+
if (!parsed.success) {
|
|
69
|
+
return finishToolError(deps.log, startedAt, parsed.error.message, { tool: "getBillSummary" });
|
|
70
|
+
}
|
|
71
|
+
if (!deps.source.ok) {
|
|
72
|
+
return finishToolError(deps.log, startedAt, configurationProblems(deps.source.problems), { tool: "getBillSummary" });
|
|
73
|
+
}
|
|
74
|
+
if (deps.reader === null) {
|
|
75
|
+
return finishToolError(deps.log, startedAt, "Transaction reader is unavailable.", { tool: "getBillSummary" });
|
|
76
|
+
}
|
|
77
|
+
const source = deps.source;
|
|
78
|
+
const reader = deps.reader;
|
|
79
|
+
const resolved = await resolveCreditAccount({
|
|
80
|
+
source,
|
|
81
|
+
log: deps.log,
|
|
82
|
+
startedAt,
|
|
83
|
+
accountId: parsed.data.accountId,
|
|
84
|
+
tool: "getBillSummary",
|
|
85
|
+
});
|
|
86
|
+
if (!resolved.ok) {
|
|
87
|
+
return resolved.result;
|
|
88
|
+
}
|
|
89
|
+
return executeBillSummary({ deps, source, reader, account: resolved.account, startedAt });
|
|
90
|
+
}
|
|
91
|
+
async function resolveCreditAccount(options) {
|
|
92
|
+
const { source, log, startedAt, accountId, tool } = options;
|
|
93
|
+
let account;
|
|
94
|
+
try {
|
|
95
|
+
account = await source.bank.getAccount(accountId);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
if (source.toFailure(error).kind === "unknown-connection") {
|
|
99
|
+
return unknownAccount(log, startedAt, accountId, tool);
|
|
100
|
+
}
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
if (!source.connections.includes(account.connectionId)) {
|
|
104
|
+
return unknownAccount(log, startedAt, accountId, tool);
|
|
105
|
+
}
|
|
106
|
+
if (account.type !== ACCOUNT_TYPES.credit) {
|
|
107
|
+
let actualType = account.type;
|
|
108
|
+
if (account.subtype !== null) {
|
|
109
|
+
actualType = `${account.type} (${account.subtype})`;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
result: finishToolError(log, startedAt, `Account ${account.id} has type ${actualType}; ${tool} requires a CREDIT card.`, { tool, accountId: account.id, accountType: account.type, accountSubtype: account.subtype }),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return { ok: true, account };
|
|
117
|
+
}
|
|
118
|
+
function unknownAccount(log, startedAt, accountId, tool) {
|
|
119
|
+
return {
|
|
120
|
+
ok: false,
|
|
121
|
+
result: finishToolError(log, startedAt, `Unknown account ${accountId}: not found.`, {
|
|
122
|
+
tool,
|
|
123
|
+
accountId,
|
|
124
|
+
}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function formatBill(bill) {
|
|
128
|
+
let minimumPayment = null;
|
|
129
|
+
if (bill.minimumPaymentCents !== null) {
|
|
130
|
+
minimumPayment = toDecimal(bill.minimumPaymentCents);
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
id: bill.id,
|
|
134
|
+
closingDate: bill.closingDate,
|
|
135
|
+
dueDate: bill.dueDate,
|
|
136
|
+
total: toDecimal(bill.totalCents),
|
|
137
|
+
currency: bill.currency,
|
|
138
|
+
minimumPayment,
|
|
139
|
+
financeCharges: toDecimal(bill.financeChargesCents),
|
|
140
|
+
payments: toDecimal(bill.paymentsCents),
|
|
141
|
+
paymentCount: bill.paymentCount,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=bills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bills.js","sourceRoot":"","sources":["../../../src/mcp/tools/bills.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { configurationProblems, finishToolError, textResult } from "./result.js";
|
|
3
|
+
export const LIST_CLOSING_DAYS_DESCRIPTION = `Lists the closing days stored locally for credit cards.
|
|
4
|
+
|
|
5
|
+
Use this tool when:
|
|
6
|
+
- You need to see which cards have a manual closing-day fallback.
|
|
7
|
+
- You need the stored day before changing or deleting it.
|
|
8
|
+
|
|
9
|
+
Returns: Every stored account id and closing day. An empty list means no closing days are stored.`;
|
|
10
|
+
export const SET_CLOSING_DAY_DESCRIPTION = `Stores the billing-cycle closing day for one credit card.
|
|
11
|
+
|
|
12
|
+
Use this tool when:
|
|
13
|
+
- The bank does not report the card's closing day.
|
|
14
|
+
- The user wants to correct an existing stored day.
|
|
15
|
+
|
|
16
|
+
Returns: The account id and day that were stored. This replaces any earlier day for the card.`;
|
|
17
|
+
export const DELETE_CLOSING_DAY_DESCRIPTION = `Deletes the closing day stored locally for one credit card.
|
|
18
|
+
|
|
19
|
+
Use this tool when:
|
|
20
|
+
- A stored closing day is wrong and should no longer be used.
|
|
21
|
+
- The bank now reports the closing day and the manual fallback is no longer needed.
|
|
22
|
+
|
|
23
|
+
Returns: The account id and the number of stored rows deleted.`;
|
|
24
|
+
const accountIdSchema = z.string().min(1);
|
|
25
|
+
const setClosingDaySchema = z.object({
|
|
26
|
+
accountId: accountIdSchema,
|
|
27
|
+
day: z.number().int().min(1).max(31),
|
|
28
|
+
});
|
|
29
|
+
const deleteClosingDaySchema = z.object({
|
|
30
|
+
accountId: accountIdSchema,
|
|
31
|
+
});
|
|
32
|
+
export function registerListClosingDays(server, deps) {
|
|
33
|
+
server.registerTool("listClosingDays", { description: LIST_CLOSING_DAYS_DESCRIPTION }, async () => handleListClosingDays(deps));
|
|
34
|
+
}
|
|
35
|
+
export function registerSetClosingDay(server, deps) {
|
|
36
|
+
server.registerTool("setClosingDay", { description: SET_CLOSING_DAY_DESCRIPTION, inputSchema: setClosingDaySchema }, async (input) => handleSetClosingDay(deps, input));
|
|
37
|
+
}
|
|
38
|
+
export function registerDeleteClosingDay(server, deps) {
|
|
39
|
+
server.registerTool("deleteClosingDay", { description: DELETE_CLOSING_DAY_DESCRIPTION, inputSchema: deleteClosingDaySchema }, async (input) => handleDeleteClosingDay(deps, input));
|
|
40
|
+
}
|
|
41
|
+
export async function handleListClosingDays(deps) {
|
|
42
|
+
const startedAt = Date.now();
|
|
43
|
+
const resolved = resolveStore(deps, startedAt, "listClosingDays");
|
|
44
|
+
if (!resolved.ok) {
|
|
45
|
+
return resolved.result;
|
|
46
|
+
}
|
|
47
|
+
return textResult({ closingDays: resolved.store.list() });
|
|
48
|
+
}
|
|
49
|
+
export async function handleSetClosingDay(deps, rawInput) {
|
|
50
|
+
const startedAt = Date.now();
|
|
51
|
+
const parsed = setClosingDaySchema.safeParse(rawInput);
|
|
52
|
+
if (!parsed.success) {
|
|
53
|
+
return finishToolError(deps.log, startedAt, parsed.error.message, { tool: "setClosingDay" });
|
|
54
|
+
}
|
|
55
|
+
const resolved = resolveStore(deps, startedAt, "setClosingDay");
|
|
56
|
+
if (!resolved.ok) {
|
|
57
|
+
return resolved.result;
|
|
58
|
+
}
|
|
59
|
+
resolved.store.set(parsed.data.accountId, parsed.data.day);
|
|
60
|
+
return textResult(parsed.data);
|
|
61
|
+
}
|
|
62
|
+
export async function handleDeleteClosingDay(deps, rawInput) {
|
|
63
|
+
const startedAt = Date.now();
|
|
64
|
+
const parsed = deleteClosingDaySchema.safeParse(rawInput);
|
|
65
|
+
if (!parsed.success) {
|
|
66
|
+
return finishToolError(deps.log, startedAt, parsed.error.message, { tool: "deleteClosingDay" });
|
|
67
|
+
}
|
|
68
|
+
const resolved = resolveStore(deps, startedAt, "deleteClosingDay");
|
|
69
|
+
if (!resolved.ok) {
|
|
70
|
+
return resolved.result;
|
|
71
|
+
}
|
|
72
|
+
const deleted = resolved.store.delete(parsed.data.accountId);
|
|
73
|
+
return textResult({ accountId: parsed.data.accountId, deleted });
|
|
74
|
+
}
|
|
75
|
+
function resolveStore(deps, startedAt, tool) {
|
|
76
|
+
if (!deps.source.ok) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
result: finishToolError(deps.log, startedAt, configurationProblems(deps.source.problems), { tool }),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (deps.closingDays === null || deps.closingDays === undefined) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
result: finishToolError(deps.log, startedAt, "Closing-day storage is unavailable.", { tool }),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return { ok: true, store: deps.closingDays };
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=closing-days.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"closing-days.js","sourceRoot":"","sources":["../../../src/mcp/tools/closing-days.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { isCategoryFilterValue } from "../../core/category.js";
|
|
4
|
+
import { decodeCursor, encodeCursor } from "../cursor.js";
|
|
5
|
+
import { toDecimal } from "../format.js";
|
|
6
|
+
import { configurationProblems, finishToolError, textResult } from "./result.js";
|
|
7
|
+
import { validateRange, validationMessage, toTransactionFilter } from "./transaction-input.js";
|
|
8
|
+
const dateInput = z.string().regex(/^\d{4}-\d{2}-\d{2}$/u, "must be YYYY-MM-DD");
|
|
9
|
+
const categoryInput = z.string().refine(isCategoryFilterValue, 'must be a known category id, or "none" for uncategorized');
|
|
10
|
+
const listTransactionsInput = z.object({
|
|
11
|
+
startDate: dateInput,
|
|
12
|
+
endDate: dateInput,
|
|
13
|
+
categories: z.array(categoryInput).optional(),
|
|
14
|
+
minAmountCents: z.number().int().optional(),
|
|
15
|
+
maxAmountCents: z.number().int().optional(),
|
|
16
|
+
accountType: z.enum(["BANK", "CREDIT", "INVESTMENT", "LOAN"]).optional(),
|
|
17
|
+
accountSubtype: z.string().min(1).optional(),
|
|
18
|
+
limit: z.number().int().min(1).max(100),
|
|
19
|
+
cursor: z.string().optional(),
|
|
20
|
+
}).superRefine(validateRange);
|
|
21
|
+
export const LIST_TRANSACTIONS_DESCRIPTION = `Lists individual transactions over a date range with a bounded page size.
|
|
22
|
+
|
|
23
|
+
Use this tool when:
|
|
24
|
+
- You need the descriptions, amounts and ids behind an aggregate.
|
|
25
|
+
- You need to inspect a page of matching transactions before requesting details.
|
|
26
|
+
- You need to continue through a large result set with the returned cursor.
|
|
27
|
+
|
|
28
|
+
- You need to find what has no category yet, by passing \`categories: ["none"]\`.
|
|
29
|
+
|
|
30
|
+
Returns: Up to 100 transactions ordered newest first, with a cursor when more rows remain. Each row carries its category and \`categorySrc\`, which says where that category came from: \`override\` and \`counterparty\` are the user's own corrections, \`pluggy\` came from the bank data, and \`learned\` and \`mcc\` are inferred. The cursor belongs to the exact filters used for the request. If a connection is unavailable, available rows are returned with an explicit notice.`;
|
|
31
|
+
export function registerListTransactions(server, deps) {
|
|
32
|
+
server.registerTool("listTransactions", { description: LIST_TRANSACTIONS_DESCRIPTION, inputSchema: listTransactionsInput }, async (input) => handleListTransactions(deps, input));
|
|
33
|
+
}
|
|
34
|
+
export async function handleListTransactions(deps, input) {
|
|
35
|
+
const startedAt = Date.now();
|
|
36
|
+
const parsed = listTransactionsInput.safeParse(input);
|
|
37
|
+
if (!parsed.success) {
|
|
38
|
+
return finishToolError(deps.log, startedAt, validationMessage(parsed.error), { outcome: "invalid-input" });
|
|
39
|
+
}
|
|
40
|
+
const log = deps.log.child({ tool: "listTransactions" });
|
|
41
|
+
log.info({}, "listTransactions started");
|
|
42
|
+
if (!deps.source.ok) {
|
|
43
|
+
return finishToolError(log, startedAt, configurationProblems(deps.source.problems), { problems: deps.source.problems.length });
|
|
44
|
+
}
|
|
45
|
+
if (deps.reader === null) {
|
|
46
|
+
return finishToolError(log, startedAt, "Transaction reader is unavailable.", { outcome: "reader-unavailable" });
|
|
47
|
+
}
|
|
48
|
+
return executeListTransactions({ deps, input: parsed.data, startedAt, log, reader: deps.reader });
|
|
49
|
+
}
|
|
50
|
+
async function executeListTransactions(options) {
|
|
51
|
+
const { deps, input, startedAt, log, reader } = options;
|
|
52
|
+
if (!deps.source.ok) {
|
|
53
|
+
return finishToolError(log, startedAt, configurationProblems(deps.source.problems), { problems: deps.source.problems.length });
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const loaded = await reader.load(deps.source.connections);
|
|
57
|
+
const filter = toTransactionFilter(input, loaded.accounts);
|
|
58
|
+
const after = cursorPosition(input.cursor, filter);
|
|
59
|
+
if (after.error !== null) {
|
|
60
|
+
return finishToolError(log, startedAt, after.error, { outcome: "invalid-cursor" });
|
|
61
|
+
}
|
|
62
|
+
const rows = reader.query(addPage(filter, input.limit, after.position));
|
|
63
|
+
const response = formatListResponse({ rows, limit: input.limit, filter, unavailable: loaded.unavailable });
|
|
64
|
+
log.info({ durationMs: Date.now() - startedAt, outcome: "ok", rows: rows.length }, "listTransactions finished");
|
|
65
|
+
return textResult(response);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return finishToolError(log, startedAt, `Cannot list transactions: ${messageOf(error)}`, { outcome: "failure" });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function cursorPosition(cursor, filter) {
|
|
72
|
+
if (cursor === undefined) {
|
|
73
|
+
return { error: null };
|
|
74
|
+
}
|
|
75
|
+
const decoded = decodeCursor(cursor, filter);
|
|
76
|
+
if (!decoded.ok) {
|
|
77
|
+
return { error: `Invalid transaction cursor: ${decoded.message}.` };
|
|
78
|
+
}
|
|
79
|
+
return { position: decoded.position, error: null };
|
|
80
|
+
}
|
|
81
|
+
function addPage(filter, limit, after) {
|
|
82
|
+
if (after === undefined) {
|
|
83
|
+
return { ...filter, limit: limit + 1 };
|
|
84
|
+
}
|
|
85
|
+
return { ...filter, limit: limit + 1, after };
|
|
86
|
+
}
|
|
87
|
+
function formatListResponse(options) {
|
|
88
|
+
const page = options.rows.slice(0, options.limit);
|
|
89
|
+
let cursor;
|
|
90
|
+
if (options.rows.length > options.limit) {
|
|
91
|
+
const last = page.at(-1);
|
|
92
|
+
if (last !== undefined) {
|
|
93
|
+
cursor = encodeCursor({ localDate: last.localDate, id: last.id }, options.filter);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let notice;
|
|
97
|
+
if (options.unavailable.length > 0) {
|
|
98
|
+
notice = `Some connections were unavailable: ${options.unavailable.map(({ connectionId }) => connectionId).join(", ")}.`;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
transactions: page.map((row) => formatListRow(row)),
|
|
102
|
+
cursor,
|
|
103
|
+
notice,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function formatListRow(row) {
|
|
107
|
+
return {
|
|
108
|
+
id: row.id,
|
|
109
|
+
date: row.localDate,
|
|
110
|
+
descriptionNorm: row.descriptionNorm,
|
|
111
|
+
amount: toDecimal(row.amountCents),
|
|
112
|
+
category: row.category,
|
|
113
|
+
categorySrc: row.categorySrc,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function messageOf(error) {
|
|
117
|
+
if (error instanceof Error) {
|
|
118
|
+
return error.message;
|
|
119
|
+
}
|
|
120
|
+
return String(error);
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=list-transactions.js.map
|