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,46 @@
|
|
|
1
|
+
import { collectAccounts } from "./accounts.js";
|
|
2
|
+
export function createTransactionReader(options) {
|
|
3
|
+
const inFlight = new Map();
|
|
4
|
+
return {
|
|
5
|
+
load: async (connectionIds) => {
|
|
6
|
+
const collected = await collectAccounts(options.bank, connectionIds, options.toFailure, options.clock);
|
|
7
|
+
await Promise.all(collected.accounts.map((account) => walkIfStale(options, inFlight, account)));
|
|
8
|
+
return collected;
|
|
9
|
+
},
|
|
10
|
+
query: (filter) => options.store.query(filter),
|
|
11
|
+
byIds: (ids) => options.store.byIds(ids),
|
|
12
|
+
cardRows: (accountId) => options.store.cardRows(accountId),
|
|
13
|
+
dataThrough: (accountIds, today) => options.store.dataThrough(accountIds, today),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function walkIfStale(options, inFlight, account) {
|
|
17
|
+
const existing = inFlight.get(account.id);
|
|
18
|
+
if (existing !== undefined) {
|
|
19
|
+
return existing;
|
|
20
|
+
}
|
|
21
|
+
if (!needsWalk(options.store, account)) {
|
|
22
|
+
return Promise.resolve();
|
|
23
|
+
}
|
|
24
|
+
const walk = performWalk(options, account).finally(() => inFlight.delete(account.id));
|
|
25
|
+
inFlight.set(account.id, walk);
|
|
26
|
+
return walk;
|
|
27
|
+
}
|
|
28
|
+
function needsWalk(store, account) {
|
|
29
|
+
const current = freshnessStamp(account);
|
|
30
|
+
if (current === null) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
return store.syncedLastUpdatedAt(account.id) !== current;
|
|
34
|
+
}
|
|
35
|
+
async function performWalk(options, account) {
|
|
36
|
+
const rows = await options.bank.getTransactions(account);
|
|
37
|
+
const deleted = options.store.replaceAccount(account.id, account.connectionId, rows, freshnessStamp(account));
|
|
38
|
+
options.log.info({ accountId: account.id, connectionId: account.connectionId, rows: rows.length, deleted }, "Walked transactions");
|
|
39
|
+
}
|
|
40
|
+
function freshnessStamp(account) {
|
|
41
|
+
if (account.lastUpdatedAt === null) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return account.lastUpdatedAt.toISOString();
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=transactions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactions.js","sourceRoot":"","sources":["../../src/core/transactions.ts"],"names":[],"mappings":"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"}
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { chmodSync, closeSync, mkdirSync, openSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { Writable } from "node:stream";
|
|
4
|
+
import pino, {} from "pino";
|
|
5
|
+
const REDACT_PATHS = [
|
|
6
|
+
"clientSecret",
|
|
7
|
+
"apiKey",
|
|
8
|
+
"authorization",
|
|
9
|
+
"key",
|
|
10
|
+
"*.clientSecret",
|
|
11
|
+
"*.apiKey",
|
|
12
|
+
"*.authorization",
|
|
13
|
+
"*.key",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Builds the process logger. The real stderr destination is fd 2 because fd 1
|
|
17
|
+
* is the MCP JSON-RPC channel. File failures degrade to stderr only.
|
|
18
|
+
*/
|
|
19
|
+
export function createLogger(options) {
|
|
20
|
+
const level = options.env.CATA_CENTAVO_LOG_LEVEL ?? "info";
|
|
21
|
+
let stderrLevel;
|
|
22
|
+
if (level === "debug") {
|
|
23
|
+
stderrLevel = "debug";
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
stderrLevel = "warn";
|
|
27
|
+
}
|
|
28
|
+
let stderr;
|
|
29
|
+
if (options.write === undefined) {
|
|
30
|
+
stderr = pino.destination({ dest: 2, sync: true });
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
stderr = testDestination(options.write);
|
|
34
|
+
}
|
|
35
|
+
const streams = [
|
|
36
|
+
{ level: stderrLevel, stream: stderr },
|
|
37
|
+
];
|
|
38
|
+
const file = fileStream(options, level);
|
|
39
|
+
if (file !== null)
|
|
40
|
+
streams.push(file);
|
|
41
|
+
const root = pino({
|
|
42
|
+
level,
|
|
43
|
+
// Pino's runtime sentinel for the design's `base: undefined` is null;
|
|
44
|
+
// it omits pid and hostname without violating exactOptionalPropertyTypes.
|
|
45
|
+
base: null,
|
|
46
|
+
redact: { paths: REDACT_PATHS, censor: "[Redacted]" },
|
|
47
|
+
}, pino.multistream(streams));
|
|
48
|
+
return wrap(root);
|
|
49
|
+
}
|
|
50
|
+
/** Null whenever the file is switched off, unconfigured, or could not be opened. */
|
|
51
|
+
function fileStream(options, level) {
|
|
52
|
+
if (options.logFile === null || options.env.CATA_CENTAVO_LOG_FILE === "off") {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const stream = createFileDestination(options.logFile);
|
|
56
|
+
if (stream === null) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return { level, stream };
|
|
60
|
+
}
|
|
61
|
+
function wrap(log) {
|
|
62
|
+
return {
|
|
63
|
+
debug: (fields, message) => log.debug(fields, message),
|
|
64
|
+
info: (fields, message) => log.info(fields, message),
|
|
65
|
+
warn: (fields, message) => log.warn(fields, message),
|
|
66
|
+
error: (fields, message) => log.error(fields, message),
|
|
67
|
+
child: (fields) => wrap(log.child(fields)),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function testDestination(write) {
|
|
71
|
+
return new Writable({
|
|
72
|
+
write(chunk, _encoding, callback) {
|
|
73
|
+
write(String(chunk));
|
|
74
|
+
callback();
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function createFileDestination(path) {
|
|
79
|
+
try {
|
|
80
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
81
|
+
const descriptor = openSync(path, "a", 0o600);
|
|
82
|
+
closeSync(descriptor);
|
|
83
|
+
chmodSync(path, 0o600);
|
|
84
|
+
return pino.transport({
|
|
85
|
+
target: "pino-roll",
|
|
86
|
+
options: { file: path, size: "5m", mkdir: true, mode: 0o600 },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=logging.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function encodeCursor(position, filter) {
|
|
3
|
+
const payload = { d: position.localDate, i: position.id, f: fingerprint(filter) };
|
|
4
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
5
|
+
}
|
|
6
|
+
export function decodeCursor(cursor, filter) {
|
|
7
|
+
const payload = parseCursor(cursor);
|
|
8
|
+
if (payload === null) {
|
|
9
|
+
return invalidCursor("cursor is not valid base64url JSON");
|
|
10
|
+
}
|
|
11
|
+
if (payload.f !== fingerprint(filter)) {
|
|
12
|
+
return invalidCursor("cursor does not match these filters");
|
|
13
|
+
}
|
|
14
|
+
return { ok: true, position: { localDate: payload.d, id: payload.i } };
|
|
15
|
+
}
|
|
16
|
+
function parseCursor(cursor) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
19
|
+
if (!isRecord(parsed)) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const d = parsed["d"];
|
|
23
|
+
const i = parsed["i"];
|
|
24
|
+
const f = parsed["f"];
|
|
25
|
+
if (typeof d !== "string" || typeof i !== "string" || typeof f !== "string") {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
return { d, i, f };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function invalidCursor(message) {
|
|
35
|
+
return { ok: false, message };
|
|
36
|
+
}
|
|
37
|
+
function fingerprint(filter) {
|
|
38
|
+
const canonical = {};
|
|
39
|
+
for (const key of Object.keys(filter).sort()) {
|
|
40
|
+
if (key === "limit" || key === "after") {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
canonical[key] = filter[key];
|
|
44
|
+
}
|
|
45
|
+
const stable = stableValue(canonical);
|
|
46
|
+
return createHash("sha256").update(JSON.stringify(stable)).digest("hex");
|
|
47
|
+
}
|
|
48
|
+
function stableValue(value) {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map(stableValue);
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(value)) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
const sorted = {};
|
|
56
|
+
for (const key of Object.keys(value).sort()) {
|
|
57
|
+
sorted[key] = stableValue(value[key]);
|
|
58
|
+
}
|
|
59
|
+
return sorted;
|
|
60
|
+
}
|
|
61
|
+
function isRecord(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/mcp/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAazC,MAAM,UAAU,YAAY,CAAC,QAAwB,EAAE,MAAyB;IAC9E,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,SAAS,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;IAClF,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,MAAyB;IACpE,MAAM,OAAO,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,aAAa,CAAC,oCAAoC,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,KAAK,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,OAAO,aAAa,CAAC,qCAAqC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC;AACzE,CAAC;AAID,SAAS,WAAW,CAAC,MAAc;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;QACtF,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;YAC5E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAe;IACpC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAChC,CAAC;AAED,SAAS,WAAW,CAAC,MAAyB;IAC5C,MAAM,SAAS,GAA4B,EAAE,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC7C,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACvC,SAAS;QACX,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAA8B,CAAC,CAAC;IAC1D,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function toDecimal(cents) {
|
|
2
|
+
let sign;
|
|
3
|
+
if (cents < 0) {
|
|
4
|
+
sign = "-";
|
|
5
|
+
}
|
|
6
|
+
else {
|
|
7
|
+
sign = "";
|
|
8
|
+
}
|
|
9
|
+
const absoluteCents = Math.abs(cents);
|
|
10
|
+
const whole = Math.trunc(absoluteCents / 100);
|
|
11
|
+
const fraction = absoluteCents % 100;
|
|
12
|
+
return `${sign}${whole}.${fraction.toString().padStart(2, "0")}`;
|
|
13
|
+
}
|
|
14
|
+
export function prune(value) {
|
|
15
|
+
if (value === null || value === undefined) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.flatMap((item) => {
|
|
20
|
+
const pruned = prune(item);
|
|
21
|
+
if (pruned === undefined) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
return [pruned];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (typeof value === "object") {
|
|
28
|
+
return Object.fromEntries(Object.entries(value).flatMap(([key, item]) => {
|
|
29
|
+
const pruned = prune(item);
|
|
30
|
+
if (pruned === undefined) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return [[key, pruned]];
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/mcp/format.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,IAAI,IAAY,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;QACd,IAAI,GAAG,GAAG,CAAC;IACb,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,GAAG,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,aAAa,GAAG,GAAG,CAAC;IAErC,OAAO,GAAG,IAAI,GAAG,KAAK,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,KAAc;IAClC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QAC1C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE;YAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACzB,CAAC,CAAC,CACH,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerGetAccounts, registerGetBalanceByAccount } from "./tools/accounts.js";
|
|
3
|
+
import { registerGetBalance } from "./tools/balance.js";
|
|
4
|
+
import { registerGetBills, registerGetBillSummary } from "./tools/bills.js";
|
|
5
|
+
import { registerDeleteClosingDay, registerListClosingDays, registerSetClosingDay, } from "./tools/closing-days.js";
|
|
6
|
+
import { registerListSources } from "./tools/sources.js";
|
|
7
|
+
import { registerGetTransactionDetails } from "./tools/transaction-details.js";
|
|
8
|
+
import { registerGetTransactions, registerListTransactions } from "./tools/transactions.js";
|
|
9
|
+
import { registerSetCategory, registerSetCounterpartyCategory } from "./tools/set-category.js";
|
|
10
|
+
/** Creates the MCP server and registers its financial tools. */
|
|
11
|
+
export function createServer(options) {
|
|
12
|
+
const server = new McpServer({ name: "cata-centavo", version: options.version });
|
|
13
|
+
const deps = toolDeps(options);
|
|
14
|
+
for (const register of REGISTRARS) {
|
|
15
|
+
register(server, deps);
|
|
16
|
+
}
|
|
17
|
+
return server;
|
|
18
|
+
}
|
|
19
|
+
/** Every tool this server exposes. One list, so registering is not a place to forget one. */
|
|
20
|
+
const REGISTRARS = [
|
|
21
|
+
registerGetAccounts,
|
|
22
|
+
registerGetBalanceByAccount,
|
|
23
|
+
registerGetBalance,
|
|
24
|
+
registerGetTransactions,
|
|
25
|
+
registerListTransactions,
|
|
26
|
+
registerGetTransactionDetails,
|
|
27
|
+
registerSetCategory,
|
|
28
|
+
registerSetCounterpartyCategory,
|
|
29
|
+
registerListClosingDays,
|
|
30
|
+
registerSetClosingDay,
|
|
31
|
+
registerDeleteClosingDay,
|
|
32
|
+
registerGetBills,
|
|
33
|
+
registerGetBillSummary,
|
|
34
|
+
registerListSources,
|
|
35
|
+
];
|
|
36
|
+
function toolDeps(options) {
|
|
37
|
+
const clock = { now: () => new Date() };
|
|
38
|
+
if (!options.source.ok) {
|
|
39
|
+
return { source: options.source, log: options.log, reader: null, writer: null, closingDays: null, clock };
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
source: options.source,
|
|
43
|
+
log: options.log,
|
|
44
|
+
reader: options.source.reader,
|
|
45
|
+
writer: options.source.writer,
|
|
46
|
+
closingDays: options.source.closingDays ?? null,
|
|
47
|
+
clock,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AACvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAC;AAC/E,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,+BAA+B,EAAE,MAAM,yBAAyB,CAAC;AAE/F,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,OAI5B;IACC,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACjF,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAE/B,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;QAClC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzB,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,6FAA6F;AAC7F,MAAM,UAAU,GAA6D;IAC3E,mBAAmB;IACnB,2BAA2B;IAC3B,kBAAkB;IAClB,uBAAuB;IACvB,wBAAwB;IACxB,6BAA6B;IAC7B,mBAAmB;IACnB,+BAA+B;IAC/B,uBAAuB;IACvB,qBAAqB;IACrB,wBAAwB;IACxB,gBAAgB;IAChB,sBAAsB;IACtB,mBAAmB;CACpB,CAAC;AAEF,SAAS,QAAQ,CAAC,OAA0D;IAC1E,MAAM,KAAK,GAAU,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;IAC/C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IAC5G,CAAC;IACD,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;QAC7B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM;QAC7B,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,IAAI,IAAI;QAC/C,KAAK;KACN,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/mcp/source.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { collectAccounts } from "../../core/accounts.js";
|
|
5
|
+
import { ACCOUNT_TYPES } from "../../core/account.js";
|
|
6
|
+
import { toDecimal } from "../format.js";
|
|
7
|
+
import { configurationProblems, finishToolError, textResult } from "./result.js";
|
|
8
|
+
const getBalanceByAccountInput = z.object({ accountId: z.string().min(1) });
|
|
9
|
+
export const GET_ACCOUNTS_DESCRIPTION = `Lists every account across the configured bank connections.
|
|
10
|
+
|
|
11
|
+
Use this tool when:
|
|
12
|
+
- You need an account map before inspecting one account or filtering financial activity.
|
|
13
|
+
- You need to see which configured connections could not provide account data.
|
|
14
|
+
|
|
15
|
+
Returns: Per-account figures in different units. A BANK account reports \`balance\`, the money available. A credit card reports \`usedCredit\`, how much of its limit is currently taken — this mixes the cycle in progress with instalments not yet charged, so it is not what the card owes this month and does not match the statement in a banking app. No tool here reports a card's statement amount.`;
|
|
16
|
+
export const GET_BALANCE_BY_ACCOUNT_DESCRIPTION = `Gets the current figures and details for one account.
|
|
17
|
+
|
|
18
|
+
Use this tool when:
|
|
19
|
+
- You need one account's numbers and already have its ID from getAccounts.
|
|
20
|
+
- You need that account's credit limit and cycle dates.
|
|
21
|
+
|
|
22
|
+
Returns: For a BANK account, its balance. For a credit card, \`usedCredit\` — the portion of the limit currently taken, which is not the amount owed for the month. Also the currency, type, credit limit and cycle dates when applicable, and when the connection last supplied data.`;
|
|
23
|
+
/** Registers the account map tool. */
|
|
24
|
+
export function registerGetAccounts(server, deps) {
|
|
25
|
+
server.registerTool("getAccounts", { description: GET_ACCOUNTS_DESCRIPTION }, async () => handleGetAccounts(deps));
|
|
26
|
+
}
|
|
27
|
+
/** Registers the single-account balance tool. */
|
|
28
|
+
export function registerGetBalanceByAccount(server, deps) {
|
|
29
|
+
server.registerTool("getBalanceByAccount", { description: GET_BALANCE_BY_ACCOUNT_DESCRIPTION, inputSchema: getBalanceByAccountInput }, async (input) => handleGetBalanceByAccount(deps, input));
|
|
30
|
+
}
|
|
31
|
+
/** Lists accounts from every configured connection, retaining any unavailable connections. */
|
|
32
|
+
export async function handleGetAccounts(deps) {
|
|
33
|
+
const startedAt = Date.now();
|
|
34
|
+
const log = deps.log.child({ tool: "getAccounts", callId: randomUUID() });
|
|
35
|
+
log.info({}, "getAccounts started");
|
|
36
|
+
if (!deps.source.ok) {
|
|
37
|
+
return finishToolError(log, startedAt, configurationProblems(deps.source.problems), { problems: deps.source.problems.length });
|
|
38
|
+
}
|
|
39
|
+
const collected = await collectAccounts(deps.source.bank, deps.source.connections, deps.source.toFailure, deps.clock);
|
|
40
|
+
for (const unavailable of collected.unavailable) {
|
|
41
|
+
log.warn({
|
|
42
|
+
connectionId: unavailable.connectionId,
|
|
43
|
+
kind: unavailable.kind,
|
|
44
|
+
message: unavailable.message,
|
|
45
|
+
accounts: collected.accounts.length,
|
|
46
|
+
unavailable: collected.unavailable.length,
|
|
47
|
+
}, "Connection unavailable");
|
|
48
|
+
}
|
|
49
|
+
if (collected.accounts.length === 0 && collected.unavailable.length > 0) {
|
|
50
|
+
return finishToolError(log, startedAt, noAccountsMessage(collected.unavailable), {
|
|
51
|
+
accounts: 0,
|
|
52
|
+
unavailable: collected.unavailable.length,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const response = { accounts: collected.accounts.map(formatAccount), unavailable: collected.unavailable };
|
|
56
|
+
log.debug({ accounts: response.accounts }, "getAccounts returned account details");
|
|
57
|
+
log.info({ durationMs: Date.now() - startedAt, outcome: "ok", accounts: response.accounts.length, unavailable: collected.unavailable.length }, "getAccounts finished");
|
|
58
|
+
return textResult(response);
|
|
59
|
+
}
|
|
60
|
+
/** Gets the figures and details of one configured account. */
|
|
61
|
+
export async function handleGetBalanceByAccount(deps, input) {
|
|
62
|
+
const startedAt = Date.now();
|
|
63
|
+
const parsed = getBalanceByAccountInput.parse(input);
|
|
64
|
+
const log = deps.log.child({ tool: "getBalanceByAccount", callId: randomUUID() });
|
|
65
|
+
log.info({ accountId: parsed.accountId }, "getBalanceByAccount started");
|
|
66
|
+
if (!deps.source.ok) {
|
|
67
|
+
return finishToolError(log, startedAt, configurationProblems(deps.source.problems), { problems: deps.source.problems.length });
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const account = await getConfiguredAccount(deps.source, parsed.accountId);
|
|
71
|
+
if (account === null) {
|
|
72
|
+
return finishToolError(log, startedAt, "Unknown account.", { accounts: 0 });
|
|
73
|
+
}
|
|
74
|
+
const response = formatAccount(account);
|
|
75
|
+
log.debug({ account: response }, "getBalanceByAccount returned account details");
|
|
76
|
+
log.info({ durationMs: Date.now() - startedAt, outcome: "ok", accounts: 1 }, "getBalanceByAccount finished");
|
|
77
|
+
return textResult(response);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
log.info({ durationMs: Date.now() - startedAt, outcome: "crash" }, "getBalanceByAccount crashed");
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function getConfiguredAccount(source, accountId) {
|
|
85
|
+
try {
|
|
86
|
+
const account = await source.bank.getAccount(accountId);
|
|
87
|
+
if (source.connections.includes(account.connectionId)) {
|
|
88
|
+
return account;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (source.toFailure(error).kind === "unknown-connection")
|
|
94
|
+
return null;
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
function noAccountsMessage(unavailable) {
|
|
99
|
+
return `No accounts came back from any configured connection: ${unavailable
|
|
100
|
+
.map(({ connectionId, kind, message }) => `${connectionId} (${kind}): ${message}`)
|
|
101
|
+
.join("; ")}`;
|
|
102
|
+
}
|
|
103
|
+
function formatAccount(account) {
|
|
104
|
+
let amountField;
|
|
105
|
+
if (account.type === ACCOUNT_TYPES.credit) {
|
|
106
|
+
amountField = { usedCredit: toDecimal(account.amountCents) };
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
amountField = { balance: toDecimal(account.amountCents) };
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
id: account.id,
|
|
113
|
+
connectionId: account.connectionId,
|
|
114
|
+
institution: account.institution,
|
|
115
|
+
name: account.name,
|
|
116
|
+
type: account.type,
|
|
117
|
+
subtype: account.subtype,
|
|
118
|
+
...amountField,
|
|
119
|
+
currency: account.currency,
|
|
120
|
+
lastUpdatedAt: account.lastUpdatedAt?.toISOString() ?? null,
|
|
121
|
+
credit: formatCredit(account.credit),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function formatCredit(credit) {
|
|
125
|
+
if (credit === null)
|
|
126
|
+
return null;
|
|
127
|
+
return {
|
|
128
|
+
limit: decimalOrNull(credit.limitCents),
|
|
129
|
+
availableLimit: decimalOrNull(credit.availableLimitCents),
|
|
130
|
+
balanceCloseDate: isoOrNull(credit.balanceCloseDate),
|
|
131
|
+
balanceDueDate: isoOrNull(credit.balanceDueDate),
|
|
132
|
+
brand: credit.brand,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function decimalOrNull(cents) {
|
|
136
|
+
if (cents === null) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return toDecimal(cents);
|
|
140
|
+
}
|
|
141
|
+
function isoOrNull(date) {
|
|
142
|
+
return date?.toISOString() ?? null;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=accounts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounts.js","sourceRoot":"","sources":["../../../src/mcp/tools/accounts.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { collectAccounts } from "../../core/accounts.js";
|
|
4
|
+
import { summarize } from "../../core/balance.js";
|
|
5
|
+
import { prune, toDecimal } from "../format.js";
|
|
6
|
+
import { configurationProblems, finishToolError, textResult } from "./result.js";
|
|
7
|
+
export const GET_BALANCE_DESCRIPTION = `Gets consolidated figures across all configured bank connections.
|
|
8
|
+
|
|
9
|
+
Use this tool when:
|
|
10
|
+
- You need total money available without mixing it with what cards have taken.
|
|
11
|
+
- You need to know when each connection last supplied its account data.
|
|
12
|
+
|
|
13
|
+
Returns: \`cash\`, the money available across bank accounts, and \`creditUsed\`, the limit currently taken across cards — never added together, because they are not the same unit. Investment and loan totals when reported, plus the currency, how many accounts were counted, and each source's update time.`;
|
|
14
|
+
/** Registers the consolidated balance tool. */
|
|
15
|
+
export function registerGetBalance(server, deps) {
|
|
16
|
+
server.registerTool("getBalance", { description: GET_BALANCE_DESCRIPTION }, async () => handleGetBalance(deps));
|
|
17
|
+
}
|
|
18
|
+
/** Gets separate totals for every available configured connection. */
|
|
19
|
+
export async function handleGetBalance(deps) {
|
|
20
|
+
const startedAt = Date.now();
|
|
21
|
+
const log = deps.log.child({ tool: "getBalance", callId: randomUUID() });
|
|
22
|
+
log.info({}, "getBalance started");
|
|
23
|
+
if (!deps.source.ok) {
|
|
24
|
+
return finishToolError(log, startedAt, configurationProblems(deps.source.problems), { problems: deps.source.problems.length });
|
|
25
|
+
}
|
|
26
|
+
const collected = await collectAccounts(deps.source.bank, deps.source.connections, deps.source.toFailure, deps.clock);
|
|
27
|
+
if (collected.unavailable.length > 0) {
|
|
28
|
+
logUnavailableConnections(log, collected.accounts.length, collected.unavailable);
|
|
29
|
+
return finishToolError(log, startedAt, unavailableMessage(collected.unavailable), {
|
|
30
|
+
accounts: collected.accounts.length,
|
|
31
|
+
unavailable: collected.unavailable.length,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const summary = summarize(collected.accounts);
|
|
35
|
+
if (!summary.ok) {
|
|
36
|
+
return finishToolError(log, startedAt, mixedCurrencyMessage(summary.currencies), { currencies: summary.currencies });
|
|
37
|
+
}
|
|
38
|
+
const response = formatSummary(summary.summary, collected.accounts);
|
|
39
|
+
log.debug({ balance: response }, "getBalance returned consolidated balances");
|
|
40
|
+
log.info({ durationMs: Date.now() - startedAt, outcome: "ok", accounts: summary.summary.accountsCounted }, "getBalance finished");
|
|
41
|
+
return textResult(response);
|
|
42
|
+
}
|
|
43
|
+
function logUnavailableConnections(log, accounts, unavailable) {
|
|
44
|
+
for (const connection of unavailable) {
|
|
45
|
+
log.warn({
|
|
46
|
+
connectionId: connection.connectionId,
|
|
47
|
+
kind: connection.kind,
|
|
48
|
+
message: connection.message,
|
|
49
|
+
accounts,
|
|
50
|
+
unavailable: unavailable.length,
|
|
51
|
+
}, "Connection unavailable");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function unavailableMessage(unavailable) {
|
|
55
|
+
return `Cannot consolidate balances because ${unavailable
|
|
56
|
+
.map(({ connectionId, kind, message }) => `${connectionId} (${kind}): ${message}`)
|
|
57
|
+
.join("; ")}`;
|
|
58
|
+
}
|
|
59
|
+
function mixedCurrencyMessage(currencies) {
|
|
60
|
+
return `Cannot consolidate balances across currencies: ${currencies.join(", ")}.`;
|
|
61
|
+
}
|
|
62
|
+
function formatSummary(summary, accounts) {
|
|
63
|
+
let investedField;
|
|
64
|
+
if (summary.investedCents === undefined) {
|
|
65
|
+
investedField = {};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
investedField = { invested: toDecimal(summary.investedCents) };
|
|
69
|
+
}
|
|
70
|
+
let loansField;
|
|
71
|
+
if (summary.loanCents === undefined) {
|
|
72
|
+
loansField = {};
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
loansField = { loans: toDecimal(summary.loanCents) };
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
cash: toDecimal(summary.cashCents),
|
|
79
|
+
creditUsed: toDecimal(summary.creditUsedCents),
|
|
80
|
+
...investedField,
|
|
81
|
+
...loansField,
|
|
82
|
+
currency: summary.currency,
|
|
83
|
+
accountsCounted: summary.accountsCounted,
|
|
84
|
+
asOf: accountsAsOf(accounts),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function accountsAsOf(accounts) {
|
|
88
|
+
const byConnection = new Map();
|
|
89
|
+
for (const account of accounts) {
|
|
90
|
+
if (!byConnection.has(account.connectionId)) {
|
|
91
|
+
byConnection.set(account.connectionId, account);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return [...byConnection.values()].map((account) => prune({ connectionId: account.connectionId, lastUpdatedAt: account.lastUpdatedAt?.toISOString() ?? null }));
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=balance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"balance.js","sourceRoot":"","sources":["../../../src/mcp/tools/balance.ts"],"names":[],"mappings":"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"}
|