@profullstack/coinpay 0.13.0 → 0.16.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/CHANGELOG.md +75 -0
- package/bin/coinpay.js +63 -14
- package/package.json +1 -1
- package/src/finances-commands.js +153 -20
- package/src/finances-reports.d.ts +80 -3
- package/src/finances-reports.js +85 -5
- package/src/mcp.js +117 -3
- package/src/statements-fetch.d.ts +85 -4
- package/src/statements-fetch.js +495 -36
- package/src/statements-throttle.js +158 -0
package/src/finances-reports.js
CHANGED
|
@@ -256,6 +256,43 @@ export async function reconcileFinanceStatement(client, statementId, {
|
|
|
256
256
|
return data.reconciliation;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
// ── Document library ──
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* File a document (multipart POST /finances/documents). `file` is the bytes.
|
|
263
|
+
* Tax documents from the fetcher pass `category: 'tax'`, `source: 'fetch'`,
|
|
264
|
+
* `institutionKey`, `taxYear` and `docType`. Answers `{document, duplicate}`:
|
|
265
|
+
* the server keeps one copy per file (by sha256) and says when it had it.
|
|
266
|
+
*/
|
|
267
|
+
export async function uploadFinanceDocument(client, {
|
|
268
|
+
file, filename = 'document.pdf', title, category, periodLabel, notes, contentType = 'application/pdf', source, institutionKey, taxYear, docType,
|
|
269
|
+
} = {}) {
|
|
270
|
+
if (!file) throw new Error('A file is required');
|
|
271
|
+
const form = new FormData();
|
|
272
|
+
const blob = file instanceof Blob ? file : new Blob([file], { type: contentType });
|
|
273
|
+
form.append('file', blob, filename);
|
|
274
|
+
form.append('title', title || filename);
|
|
275
|
+
if (category) form.append('category', category);
|
|
276
|
+
if (periodLabel) form.append('period', periodLabel);
|
|
277
|
+
if (notes) form.append('notes', notes);
|
|
278
|
+
if (source) form.append('source', source);
|
|
279
|
+
if (institutionKey) form.append('institutionKey', institutionKey);
|
|
280
|
+
if (taxYear !== undefined && taxYear !== null) form.append('taxYear', String(taxYear));
|
|
281
|
+
if (docType) form.append('docType', docType);
|
|
282
|
+
return client.requestForm('/finances/documents', form);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** The document library, newest first; `category: 'tax'` for FTB/IRS notices, letters and transcripts. */
|
|
286
|
+
export async function listFinanceDocuments(client, { category, limit } = {}) {
|
|
287
|
+
const data = await call(client, `/finances/documents${query({ category, limit })}`);
|
|
288
|
+
return data.documents || [];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function getFinanceDocument(client, documentId) {
|
|
292
|
+
const data = await call(client, `/finances/documents/${encodeURIComponent(documentId)}`);
|
|
293
|
+
return data.document;
|
|
294
|
+
}
|
|
295
|
+
|
|
259
296
|
/** Report one bank's statement fetch run (counts only). */
|
|
260
297
|
export async function reportStatementFetchRun(client, run) {
|
|
261
298
|
const data = await call(client, '/finances/statements/fetch-runs', { method: 'POST', body: JSON.stringify(run) });
|
|
@@ -351,9 +388,13 @@ export async function getBooksSummary(client, { period, from, to, timezone, scop
|
|
|
351
388
|
return call(client, `/finances/books/summary${query({ period, from, to, timezone, scope, rows: rows ? 1 : undefined })}`);
|
|
352
389
|
}
|
|
353
390
|
|
|
354
|
-
/**
|
|
355
|
-
|
|
356
|
-
|
|
391
|
+
/**
|
|
392
|
+
* The CPA pack as bytes. With `withDocuments` the answer is a ZIP: the pack
|
|
393
|
+
* plus the period's tax documents that fit in 8 MiB, and MANIFEST.txt naming
|
|
394
|
+
* what was left out.
|
|
395
|
+
*/
|
|
396
|
+
export async function exportBooks(client, { period, from, to, timezone, scope, format = 'csv', withDocuments = false } = {}) {
|
|
397
|
+
return client.requestBinary(`/finances/books/export${query({ period, from, to, timezone, scope, format, with_documents: withDocuments ? 1 : undefined })}`);
|
|
357
398
|
}
|
|
358
399
|
|
|
359
400
|
// ── Raw provider payloads ──
|
|
@@ -377,10 +418,10 @@ export async function sendFinanceReportEmail(client, reportId, { to, formats, me
|
|
|
377
418
|
}
|
|
378
419
|
|
|
379
420
|
/** Email the CPA pack for a period. `toDate` is the exclusive end for a custom range. */
|
|
380
|
-
export async function sendBooksEmail(client, { to, period, from, toDate, timezone, scope, formats, message, attach, expiresInDays } = {}) {
|
|
421
|
+
export async function sendBooksEmail(client, { to, period, from, toDate, timezone, scope, formats, message, attach, expiresInDays, withDocuments } = {}) {
|
|
381
422
|
return call(client, '/finances/books/send', {
|
|
382
423
|
method: 'POST',
|
|
383
|
-
body: JSON.stringify({ to, period, from, toDate, timezone, scope, formats, message, attach, expiresInDays }),
|
|
424
|
+
body: JSON.stringify({ to, period, from, toDate, timezone, scope, formats, message, attach, expiresInDays, withDocuments: withDocuments === true ? true : undefined }),
|
|
384
425
|
});
|
|
385
426
|
}
|
|
386
427
|
|
|
@@ -404,3 +445,42 @@ export async function deleteWeeklyDigest(client) {
|
|
|
404
445
|
export async function sendWeeklyDigestNow(client) {
|
|
405
446
|
return call(client, '/finances/email-schedules/send-now', { method: 'POST', body: '{}' });
|
|
406
447
|
}
|
|
448
|
+
|
|
449
|
+
/** Charges due across every account: projected recurring charges plus statement due days. */
|
|
450
|
+
export async function getDueSoon(client, { days, timezone } = {}) {
|
|
451
|
+
const params = new URLSearchParams();
|
|
452
|
+
if (days !== undefined) params.set('days', String(days));
|
|
453
|
+
if (timezone) params.set('timezone', timezone);
|
|
454
|
+
const qs = params.toString();
|
|
455
|
+
const data = await call(client, `/finances/due-soon${qs ? `?${qs}` : ''}`);
|
|
456
|
+
return data.dueSoon;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export async function getDailySummary(client) {
|
|
460
|
+
const data = await call(client, '/finances/email-schedules?kind=daily_summary');
|
|
461
|
+
return data.schedule;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
export async function setDailySummary(client, { weekdays, hour, timezone, recipients, active } = {}) {
|
|
465
|
+
const data = await call(client, '/finances/email-schedules', {
|
|
466
|
+
method: 'POST',
|
|
467
|
+
body: JSON.stringify({ kind: 'daily_summary', weekdays, hour, timezone, recipients, active }),
|
|
468
|
+
});
|
|
469
|
+
return data.schedule;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export async function deleteDailySummary(client) {
|
|
473
|
+
return call(client, '/finances/email-schedules?kind=daily_summary', { method: 'DELETE' });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export async function sendDailySummaryNow(client) {
|
|
477
|
+
return call(client, '/finances/email-schedules/send-now?kind=daily_summary', { method: 'POST', body: '{}' });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/** Set (or clear with day null) a card or loan's statement due day and fixed amount. */
|
|
481
|
+
export async function setAccountDueDay(client, accountId, { day, amount } = {}) {
|
|
482
|
+
const body = { payment_due_day: day ?? null };
|
|
483
|
+
if (amount !== undefined) body.payment_due_amount = amount;
|
|
484
|
+
const data = await call(client, `/finances/accounts/${encodeURIComponent(accountId)}`, { method: 'PATCH', body: JSON.stringify(body) });
|
|
485
|
+
return data.account;
|
|
486
|
+
}
|
package/src/mcp.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { createInterface } from 'node:readline';
|
|
14
14
|
|
|
15
15
|
import * as reports from './finances-reports.js';
|
|
16
|
-
import { listFinanceAccounts } from './finances.js';
|
|
16
|
+
import { getFinanceSummary, listFinanceAccounts, listFinanceTransactions } from './finances.js';
|
|
17
17
|
import * as sf from './statements-fetch.js';
|
|
18
18
|
|
|
19
19
|
export const PROTOCOL_VERSION = '2025-06-18';
|
|
@@ -28,6 +28,88 @@ export const TOOLS = [
|
|
|
28
28
|
inputSchema: { type: 'object', properties: { includeHidden: { type: 'boolean' } }, additionalProperties: false },
|
|
29
29
|
handler: async (args, { client }) => text({ accounts: await listFinanceAccounts(client, { includeHidden: args.includeHidden === true }) }),
|
|
30
30
|
},
|
|
31
|
+
{
|
|
32
|
+
name: 'finance_due_soon',
|
|
33
|
+
description:
|
|
34
|
+
'What is due across every linked account: overdue, today and the next N days. Projected from each recurring charge\'s own cadence, plus statement due days set on cards and loans. Also lists charges that stopped appearing ("missed") and paid-this-cycle statements.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
days: { type: 'integer', minimum: 0, maximum: 60, description: 'Horizon in days (default 7)' },
|
|
39
|
+
timezone: { type: 'string', description: 'IANA zone for "today"; defaults to the saved finance timezone' },
|
|
40
|
+
},
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
handler: async (args, { client }) => {
|
|
44
|
+
const { recurring, ...due } = await reports.getDueSoon(client, { days: args.days, timezone: args.timezone });
|
|
45
|
+
return text({ ...due, recurringMonthlyTotal: recurring.monthlyTotal });
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'finance_recurring',
|
|
50
|
+
description: 'Every recurring charge detected across all accounts (subscriptions, bills, loan and card payments): cadence, typical amount, last and next date, monthly equivalent.',
|
|
51
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
52
|
+
handler: async (_args, { client }) => text((await reports.getDueSoon(client, { days: 0 })).recurring),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'finance_summary',
|
|
56
|
+
description: 'Balance sheet (assets, debt, net by currency), cashflow for the window, top spending categories, and the debt-and-income position.',
|
|
57
|
+
inputSchema: { type: 'object', properties: { days: { type: 'integer', minimum: 1, maximum: 3650, description: 'Cashflow window (default 30)' } }, additionalProperties: false },
|
|
58
|
+
handler: async (args, { client }) => text(await getFinanceSummary(client, { days: args.days || 30 })),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'finance_transactions',
|
|
62
|
+
description: 'The bank and card ledger, newest first. Filter by account id, text search, category or date range (YYYY-MM-DD).',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
accountId: { type: 'string' },
|
|
67
|
+
search: { type: 'string' },
|
|
68
|
+
category: { type: 'string' },
|
|
69
|
+
startDate: { type: 'string' },
|
|
70
|
+
endDate: { type: 'string' },
|
|
71
|
+
limit: { type: 'integer', minimum: 1, maximum: 500 },
|
|
72
|
+
offset: { type: 'integer', minimum: 0 },
|
|
73
|
+
},
|
|
74
|
+
additionalProperties: false,
|
|
75
|
+
},
|
|
76
|
+
handler: async (args, { client }) => text(await listFinanceTransactions(client, { ...args, limit: args.limit || 50 })),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'finance_set_due_day',
|
|
80
|
+
description: 'Set or clear a card or loan\'s statement due day (1-31, null clears) and optionally a fixed monthly amount (null = statement balance). Puts the account on the due-soon list.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
accountId: { type: 'string' },
|
|
85
|
+
day: { type: ['integer', 'null'], minimum: 1, maximum: 31 },
|
|
86
|
+
amount: { type: ['number', 'string', 'null'] },
|
|
87
|
+
},
|
|
88
|
+
required: ['accountId', 'day'],
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
handler: async (args, { client }) => text({ account: await reports.setAccountDueDay(client, args.accountId, { day: args.day, amount: args.amount }) }),
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'finance_daily_summary',
|
|
95
|
+
description: 'The daily summary email (due soon, new activity, position): show it, turn it on (optionally hour 0-23 and recipients), turn it off, or send one now.',
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: 'object',
|
|
98
|
+
properties: {
|
|
99
|
+
action: { type: 'string', enum: ['show', 'on', 'off', 'send_now'] },
|
|
100
|
+
hour: { type: 'integer', minimum: 0, maximum: 23 },
|
|
101
|
+
to: { type: 'array', items: { type: 'string' }, maxItems: 5 },
|
|
102
|
+
},
|
|
103
|
+
required: ['action'],
|
|
104
|
+
additionalProperties: false,
|
|
105
|
+
},
|
|
106
|
+
handler: async (args, { client }) => {
|
|
107
|
+
if (args.action === 'on') return text({ schedule: await reports.setDailySummary(client, { hour: args.hour, recipients: args.to, active: true }) });
|
|
108
|
+
if (args.action === 'off') return text(await reports.deleteDailySummary(client));
|
|
109
|
+
if (args.action === 'send_now') return text(await reports.sendDailySummaryNow(client));
|
|
110
|
+
return text({ schedule: await reports.getDailySummary(client) });
|
|
111
|
+
},
|
|
112
|
+
},
|
|
31
113
|
{
|
|
32
114
|
name: 'statements_list',
|
|
33
115
|
description: 'List PDF statements in the CoinPay statement library, optionally for one account or period (2026-08, 2026-Q3).',
|
|
@@ -60,9 +142,39 @@ export const TOOLS = [
|
|
|
60
142
|
return text({
|
|
61
143
|
chrome: sf.findChrome(),
|
|
62
144
|
banks: institutions.map((i) => ({ key: i.key, name: i.name, accounts: i.accounts.map((a) => a.name), signedIn: sf.signedIn(i.key, home), ...state.institutions[i.key] })),
|
|
145
|
+
// FTB and IRS need no linked bank; each is throttled (2 visits / 30 min, 4 / day).
|
|
146
|
+
taxSources: sf.standaloneSources().filter((s) => s.kind === 'tax').map((s) => ({ key: s.key, name: s.name, signedIn: sf.signedIn(s.key, home), throttle: sf.checkLocalThrottle(home, s.key), ...state.institutions[s.key] })),
|
|
147
|
+
// Brokerages with no linked account (Webull): statements, confirmations and 1099s go to Documents.
|
|
148
|
+
brokerages: sf.standaloneSources().filter((s) => s.kind === 'brokerage' && !institutions.some((i) => i.key === s.key)).map((s) => ({ key: s.key, name: s.name, signedIn: sf.signedIn(s.key, home), ...state.institutions[s.key] })),
|
|
63
149
|
});
|
|
64
150
|
},
|
|
65
151
|
},
|
|
152
|
+
{
|
|
153
|
+
name: 'tax_documents_list',
|
|
154
|
+
description:
|
|
155
|
+
'Tax documents in the CoinPay document library: notices, letters and transcripts fetched from California FTB and the IRS (or uploaded), with tax year, type, source and period. Details only, not the files. Optionally one tax year or one source (ftb, irs, irs-business).',
|
|
156
|
+
inputSchema: {
|
|
157
|
+
type: 'object',
|
|
158
|
+
properties: {
|
|
159
|
+
taxYear: { type: 'integer', minimum: 1990, maximum: 2100 },
|
|
160
|
+
source: { type: 'string', description: 'ftb, irs or irs-business' },
|
|
161
|
+
limit: { type: 'integer', minimum: 1, maximum: 500 },
|
|
162
|
+
},
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
},
|
|
165
|
+
handler: async (args, { client }) => {
|
|
166
|
+
const documents = (await reports.listFinanceDocuments(client, { category: 'tax', limit: args.limit }))
|
|
167
|
+
.filter((d) => args.taxYear === undefined || d.taxYear === args.taxYear)
|
|
168
|
+
.filter((d) => args.source === undefined || d.institutionKey === args.source);
|
|
169
|
+
return text({ documents });
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'tax_documents_get',
|
|
174
|
+
description: 'One document from the CoinPay document library by id: title, type, tax year, source, period, size, and its download path in CoinPay.',
|
|
175
|
+
inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'], additionalProperties: false },
|
|
176
|
+
handler: async (args, { client }) => text({ document: await reports.getFinanceDocument(client, args.id) }),
|
|
177
|
+
},
|
|
66
178
|
{
|
|
67
179
|
name: 'statements_cloud_status',
|
|
68
180
|
description:
|
|
@@ -121,7 +233,7 @@ export async function handleMessage(message, ctx) {
|
|
|
121
233
|
capabilities: { tools: {} },
|
|
122
234
|
serverInfo: { name: 'coinpay', version: ctx.version || '0.0.0' },
|
|
123
235
|
instructions:
|
|
124
|
-
'CoinPay finances. Statement PDFs come from the banks themselves (SimpleFIN has none): statements_coverage shows what is missing, statements_fetch downloads what is new on this machine. A bank marked login_needed needs a person to run `coinpay finances statements login <bank>`.',
|
|
236
|
+
'CoinPay finances. finance_due_soon answers "what is due" across every account; finance_recurring lists subscriptions and bills; finance_summary is the balance sheet. Statement PDFs come from the banks themselves (SimpleFIN has none): statements_coverage shows what is missing, statements_fetch downloads what is new on this machine. A bank marked login_needed needs a person to run `coinpay finances statements login <bank>`. Tax notices, letters and transcripts (California FTB, IRS) are in tax_documents_list; tax data stays on CoinPay surfaces and is never sent elsewhere.',
|
|
125
237
|
});
|
|
126
238
|
case 'notifications/initialized':
|
|
127
239
|
case 'notifications/cancelled':
|
|
@@ -129,7 +241,9 @@ export async function handleMessage(message, ctx) {
|
|
|
129
241
|
case 'ping':
|
|
130
242
|
return reply({});
|
|
131
243
|
case 'tools/list':
|
|
132
|
-
|
|
244
|
+
// Claude Code's client (revision 2026-07-28) rejects a whole list that
|
|
245
|
+
// lacks these three cache fields, showing "tools fetch failed".
|
|
246
|
+
return reply({ tools: TOOLS.map(({ name, description, inputSchema }) => ({ name, description, inputSchema })), resultType: 'complete', ttlMs: 0, cacheScope: 'private' });
|
|
133
247
|
case 'tools/call': {
|
|
134
248
|
const tool = TOOLS.find((t) => t.name === params.name);
|
|
135
249
|
if (!tool) return fail(-32602, `Unknown tool: ${params.name}`);
|
|
@@ -14,6 +14,52 @@ export interface Institution {
|
|
|
14
14
|
name: string;
|
|
15
15
|
url: string | null;
|
|
16
16
|
accounts: FetcherAccount[];
|
|
17
|
+
/** 'tax' for an agency (FTB, IRS), 'brokerage' for a brokerage with no linked account (Webull); absent for a bank. */
|
|
18
|
+
kind?: 'tax' | 'brokerage';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TaxSource {
|
|
22
|
+
key: string;
|
|
23
|
+
kind: 'tax';
|
|
24
|
+
name: string;
|
|
25
|
+
login: string;
|
|
26
|
+
lockoutMinutes: number;
|
|
27
|
+
cloudWarning?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface BrokerageSource {
|
|
31
|
+
key: string;
|
|
32
|
+
kind: 'brokerage';
|
|
33
|
+
name: string;
|
|
34
|
+
login: string;
|
|
35
|
+
statements?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BrokerageDocumentInfo {
|
|
39
|
+
category: 'statement' | 'tax';
|
|
40
|
+
docType: 'form' | null;
|
|
41
|
+
taxYear: number | null;
|
|
42
|
+
periodLabel: string;
|
|
43
|
+
title: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ThrottleLimits {
|
|
47
|
+
windowMs: number;
|
|
48
|
+
perWindow: number;
|
|
49
|
+
perDay: number;
|
|
50
|
+
defaultLockoutMinutes: number;
|
|
51
|
+
marginMinutes: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type ThrottleVerdict = { ok: true } | { ok: false; reason: 'locked' | 'daily_limit' | 'window_limit'; retryAt: string; message: string };
|
|
55
|
+
|
|
56
|
+
export interface TaxDocumentInfo {
|
|
57
|
+
docType: 'transcript' | 'notice' | 'letter' | 'form' | 'return' | 'other';
|
|
58
|
+
code: string | null;
|
|
59
|
+
taxYear: number | null;
|
|
60
|
+
noticeDate: string | null;
|
|
61
|
+
periodLabel: string;
|
|
62
|
+
title: string;
|
|
17
63
|
}
|
|
18
64
|
|
|
19
65
|
export interface Period {
|
|
@@ -55,7 +101,7 @@ export interface Download {
|
|
|
55
101
|
key: string | null;
|
|
56
102
|
}
|
|
57
103
|
|
|
58
|
-
export type PageStatus = 'ok' | 'login_needed' | 'no_statements';
|
|
104
|
+
export type PageStatus = 'ok' | 'login_needed' | 'no_statements' | 'locked';
|
|
59
105
|
|
|
60
106
|
export function lastFour(name: string): string | null;
|
|
61
107
|
export function slug(value: string): string;
|
|
@@ -63,6 +109,37 @@ export function institutionKey(domain: string | null | undefined, name: string |
|
|
|
63
109
|
export function groupInstitutions(accounts: ReadonlyArray<{ id: string; name: string; org_name?: string | null; org_domain?: string | null; is_hidden?: boolean }>): Institution[];
|
|
64
110
|
export function pickInstitution(institutions: readonly Institution[], name: string): Institution;
|
|
65
111
|
export const DRIVERS: ReadonlyArray<{ key: string; login: string; statements?: string }>;
|
|
112
|
+
export const HOST_KEYS: ReadonlyArray<[RegExp, string]>;
|
|
113
|
+
export const TAX_SOURCES: readonly TaxSource[];
|
|
114
|
+
export function taxSource(key: string): TaxSource | null;
|
|
115
|
+
export const BROKERAGE_SOURCES: readonly BrokerageSource[];
|
|
116
|
+
export function brokerageSource(key: string): BrokerageSource | null;
|
|
117
|
+
export function isBrokerageSource(institution: { key: string; kind?: string; accounts?: readonly unknown[] } | null | undefined): boolean;
|
|
118
|
+
export function sourceMode(institution: { key: string; kind?: string; accounts?: readonly unknown[] }): 'statements' | 'tax' | 'documents';
|
|
119
|
+
export function standaloneSources(): Institution[];
|
|
120
|
+
export function withStandaloneSources(institutions: readonly Institution[]): Institution[];
|
|
121
|
+
export function isTaxSource(institution: { key: string; kind?: string; accounts?: readonly unknown[] } | null | undefined): boolean;
|
|
122
|
+
export function keepCandidate(mode: 'statements' | 'tax' | 'documents', label: string, context: string, href: string | null): boolean;
|
|
123
|
+
export function collectScript(mode?: 'statements' | 'tax' | 'documents'): string;
|
|
124
|
+
export const COLLECT_TAX: string;
|
|
125
|
+
export const OPEN_TAX_DOCS: string;
|
|
126
|
+
export const COLLECT_DOCS: string;
|
|
127
|
+
export const OPEN_DOCUMENTS: string;
|
|
128
|
+
export const PAGE_TEXT: string;
|
|
129
|
+
export function classifyTaxDocument(download: { label?: string; context?: string; suggestedName?: string }, fetchedAt?: Date): TaxDocumentInfo;
|
|
130
|
+
export function keepTaxDocument(options: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
131
|
+
export function classifyBrokerageDocument(download: { label?: string; context?: string; suggestedName?: string }, fetchedAt?: Date): BrokerageDocumentInfo;
|
|
132
|
+
export function keepBrokerageDocument(options: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
133
|
+
export const TAX_THROTTLE: ThrottleLimits;
|
|
134
|
+
export function evaluateThrottle(source: { attempts?: ReadonlyArray<string | { at: string }>; lockedUntil?: string | null; lockReason?: string | null }, now?: Date, limits?: ThrottleLimits): ThrottleVerdict;
|
|
135
|
+
export function detectLockout(text: string | null | undefined): { minutes: number | null } | null;
|
|
136
|
+
export function lockoutUntil(found: { minutes: number | null } | null, now?: Date, limits?: ThrottleLimits, defaultMinutes?: number): string;
|
|
137
|
+
export function throttlePath(home: string): string;
|
|
138
|
+
export function loadThrottle(home: string): { sources: Record<string, { attempts: Array<{ at: string; kind: string }>; lockedUntil: string | null; lockReason: string | null }> };
|
|
139
|
+
export function saveThrottle(ledger: unknown, home: string): void;
|
|
140
|
+
export function checkLocalThrottle(home: string, key: string, now?: Date, limits?: ThrottleLimits): ThrottleVerdict;
|
|
141
|
+
export function takeLocalAttempt(home: string, key: string, kind: string, now?: Date, limits?: ThrottleLimits): ThrottleVerdict;
|
|
142
|
+
export function recordLocalLockout(home: string, key: string, until: string, reason?: string): unknown;
|
|
66
143
|
export function startUrls(institution: { key: string; url: string | null }, learnt?: string | null): { login: string | null; fetch: string | null };
|
|
67
144
|
export function isSignInUrl(url: string): boolean;
|
|
68
145
|
export function findDates(source: string | null | undefined): { date: string; precise: boolean }[];
|
|
@@ -106,10 +183,13 @@ export function fetchInstitution(
|
|
|
106
183
|
renderMs?: number;
|
|
107
184
|
onFile: (download: Download) => Promise<void> | void;
|
|
108
185
|
log?: (line: string) => void;
|
|
186
|
+
mode?: 'statements' | 'tax' | 'documents';
|
|
187
|
+
watchLockout?: boolean;
|
|
188
|
+
pauseMs?: number;
|
|
109
189
|
},
|
|
110
|
-
): Promise<{ status: PageStatus; url: string; candidates: number; silent: string[] }>;
|
|
111
|
-
export function loginWindow(browser: Browser, url: string): Promise<string | null>;
|
|
112
|
-
export function assistWindow(browser: Browser, options: { start: string; onFile: (download: Download) => Promise<void> | void }): Promise<number>;
|
|
190
|
+
): Promise<{ status: PageStatus; url: string; candidates: number; silent: string[]; lockout?: { minutes: number | null } }>;
|
|
191
|
+
export function loginWindow(browser: Browser, url: string, options?: { onPageText?: (text: string) => unknown }): Promise<string | null>;
|
|
192
|
+
export function assistWindow(browser: Browser, options: { start: string; onFile: (download: Download) => Promise<void> | void; onPageText?: (text: string) => unknown }): Promise<number>;
|
|
113
193
|
export function loadLocal(home?: string): { institutions: Record<string, Record<string, unknown>>; entries: Record<string, unknown>[] };
|
|
114
194
|
export function saveLocal(state: unknown, home?: string): void;
|
|
115
195
|
export function archive(home: string, institution: Institution, account: FetcherAccount | null, period: Period | null, suggestedName: string, bytes: Uint8Array): string;
|
|
@@ -119,5 +199,6 @@ export function clientApi(client: unknown): Promise<{
|
|
|
119
199
|
listAccounts(): Promise<unknown[]>;
|
|
120
200
|
importStatement(options: Record<string, unknown>): Promise<unknown>;
|
|
121
201
|
reportRun(run: Record<string, unknown>): Promise<unknown>;
|
|
202
|
+
fileDocument(options: Record<string, unknown>): Promise<{ document?: { id: string }; duplicate?: boolean }>;
|
|
122
203
|
}>;
|
|
123
204
|
export function retryImports(options: Record<string, unknown>): Promise<{ imported: number; skipped: number; failed: number }>;
|