financy 0.2.0 → 0.4.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/README.md +165 -1
- package/dist/bin.js +219 -7
- package/dist/{chunk-QYWDNIM2.js → chunk-LZSZIBYL.js} +240 -19
- package/dist/lib.d.ts +1 -0
- package/dist/lib.js +21 -8
- package/dist/server-WGN36UXM.js +459 -0
- package/package.json +2 -4
- package/skills/financy-setup/SKILL.md +33 -4
- package/dist/chunk-74AQOXZZ.js +0 -180
- package/dist/remote/start.js +0 -179
- package/dist/server-N5ZPO432.js +0 -37
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "financy",
|
|
6
|
-
version: "0.
|
|
6
|
+
version: "0.4.0",
|
|
7
7
|
description: "Your Open-Finance accounts, balances and transactions in the terminal \u2014 with an embedded MCP server and agent skills",
|
|
8
8
|
type: "module",
|
|
9
9
|
bin: {
|
|
@@ -52,7 +52,6 @@ var package_default = {
|
|
|
52
52
|
scripts: {
|
|
53
53
|
prebuild: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
|
|
54
54
|
build: "tsup",
|
|
55
|
-
"build:lambda": "tsup --config tsup.lambda.config.ts",
|
|
56
55
|
test: "vitest run",
|
|
57
56
|
"test:watch": "vitest",
|
|
58
57
|
typecheck: "tsc --noEmit",
|
|
@@ -62,8 +61,7 @@ var package_default = {
|
|
|
62
61
|
},
|
|
63
62
|
dependencies: {
|
|
64
63
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
65
|
-
commander: "^12.1.0"
|
|
66
|
-
jose: "^6.2.9"
|
|
64
|
+
commander: "^12.1.0"
|
|
67
65
|
},
|
|
68
66
|
devDependencies: {
|
|
69
67
|
"@eslint/js": "^10.0.1",
|
|
@@ -111,7 +109,7 @@ var authFailed = (message = "authentication failed \u2014 check your credentials
|
|
|
111
109
|
var planNotEligible = () => new CliError(
|
|
112
110
|
EXIT.PLAN,
|
|
113
111
|
"NOT_AVAILABLE_ON_PLAN",
|
|
114
|
-
"the API is available on Starter and
|
|
112
|
+
"the API is available on every paid plan, including Starter, Pro, and Ultra \u2014 upgrade in Financy \u2192 Settings \u2192 Plan"
|
|
115
113
|
);
|
|
116
114
|
var forbidden = (detail) => new CliError(EXIT.PLAN, "FORBIDDEN", `the API denied this request (403): ${detail}`);
|
|
117
115
|
var insufficientCredits = (cost, balance) => new CliError(
|
|
@@ -122,6 +120,29 @@ var insufficientCredits = (cost, balance) => new CliError(
|
|
|
122
120
|
);
|
|
123
121
|
var noRefreshableConnections = (message = "no connections are eligible for refresh") => new CliError(EXIT.UNEXPECTED, "NO_REFRESHABLE_CONNECTIONS", message);
|
|
124
122
|
var apiUnavailable = (message = "the Open-Finance API is unavailable \u2014 check your connection and try again") => new CliError(EXIT.API, "API_UNAVAILABLE", message);
|
|
123
|
+
var PLAN_TYPES = /* @__PURE__ */ new Set([
|
|
124
|
+
"UPGRADE_REQUIRED",
|
|
125
|
+
"INTEGRATION_LIMIT_REACHED",
|
|
126
|
+
"NOT_ENTITLED",
|
|
127
|
+
"ENTITLEMENT_NOT_PROVISIONED"
|
|
128
|
+
]);
|
|
129
|
+
function integrationsExitCode(type, status) {
|
|
130
|
+
if (type === "SIGN_IN_AGAIN") return EXIT.AUTH;
|
|
131
|
+
if (PLAN_TYPES.has(type) || status === 403) return EXIT.PLAN;
|
|
132
|
+
if (status === 404) return EXIT.NOT_FOUND;
|
|
133
|
+
if (status === 400 || status === 422) return EXIT.USAGE;
|
|
134
|
+
if (status === 429 || status >= 500) return EXIT.API;
|
|
135
|
+
return EXIT.UNEXPECTED;
|
|
136
|
+
}
|
|
137
|
+
function integrationsError(body, status) {
|
|
138
|
+
const hint = body.action?.url ? ` \u2014 ${body.action.url}` : body.type === "UPGRADE_REQUIRED" ? " \u2014 upgrade in Financy \u2192 Settings \u2192 Plan" : "";
|
|
139
|
+
return new CliError(integrationsExitCode(body.type, status), body.type, `${body.message}${hint}`, {
|
|
140
|
+
retryable: body.retryable === true,
|
|
141
|
+
userActionRequired: body.userActionRequired === true,
|
|
142
|
+
...body.action ? { action: body.action } : {},
|
|
143
|
+
...body.requestId ? { requestId: body.requestId } : {}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
125
146
|
|
|
126
147
|
// src/config.ts
|
|
127
148
|
import { homedir } from "os";
|
|
@@ -143,6 +164,16 @@ function sanitizeCredential(raw) {
|
|
|
143
164
|
var DEFAULT_AUTH_TOKEN_URL = "https://api.open-finance.ai/oauth/token";
|
|
144
165
|
var DEFAULT_API_BASE_URL = "https://api.open-finance.ai/v2";
|
|
145
166
|
var DEFAULT_CHAT_BASE_URL = "https://api.open-finance.ai/chat/chat";
|
|
167
|
+
var DEFAULT_INTEGRATIONS_BASE_URL = "https://api.open-finance.ai/integrations";
|
|
168
|
+
function resolveIntegrationsBaseUrl(env) {
|
|
169
|
+
if (env.FINANCY_INTEGRATIONS_URL) return env.FINANCY_INTEGRATIONS_URL;
|
|
170
|
+
if (!env.FINANCY_API_URL) return DEFAULT_INTEGRATIONS_BASE_URL;
|
|
171
|
+
try {
|
|
172
|
+
return `${new URL(env.FINANCY_API_URL).origin}/integrations`;
|
|
173
|
+
} catch {
|
|
174
|
+
return DEFAULT_INTEGRATIONS_BASE_URL;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
146
177
|
function resolveConfigDir(env) {
|
|
147
178
|
if (env.FINANCY_CONFIG_DIR) return env.FINANCY_CONFIG_DIR;
|
|
148
179
|
const base = env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
@@ -153,6 +184,7 @@ function resolveEndpoints(env) {
|
|
|
153
184
|
authTokenUrl: env.FINANCY_AUTH_URL ?? DEFAULT_AUTH_TOKEN_URL,
|
|
154
185
|
apiBaseUrl: env.FINANCY_API_URL ?? DEFAULT_API_BASE_URL,
|
|
155
186
|
chatBaseUrl: env.FINANCY_CHAT_URL ?? DEFAULT_CHAT_BASE_URL,
|
|
187
|
+
integrationsBaseUrl: resolveIntegrationsBaseUrl(env),
|
|
156
188
|
configDir: resolveConfigDir(env)
|
|
157
189
|
};
|
|
158
190
|
}
|
|
@@ -201,16 +233,6 @@ async function writeCredentialsFile(configDir, credentials) {
|
|
|
201
233
|
{ mode: 384 }
|
|
202
234
|
);
|
|
203
235
|
}
|
|
204
|
-
function bearerConfig(env, bearerToken, abortSignal) {
|
|
205
|
-
return {
|
|
206
|
-
...resolveEndpoints(env),
|
|
207
|
-
clientId: "",
|
|
208
|
-
clientSecret: "",
|
|
209
|
-
userId: "",
|
|
210
|
-
bearerToken,
|
|
211
|
-
abortSignal
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
236
|
function pick(envValue, fileValue) {
|
|
215
237
|
const raw = envValue ?? fileValue;
|
|
216
238
|
if (raw === void 0) return void 0;
|
|
@@ -332,13 +354,14 @@ async function getAccessToken(config, now) {
|
|
|
332
354
|
}
|
|
333
355
|
return mintAndCache(config);
|
|
334
356
|
}
|
|
335
|
-
|
|
357
|
+
var isAuthFailure = (error) => error.code === "AUTH_FAILED";
|
|
358
|
+
async function authorized(config, now, request, remintOn = isAuthFailure) {
|
|
336
359
|
if (config.bearerToken) return request(config.bearerToken);
|
|
337
360
|
const token = await getAccessToken(config, now);
|
|
338
361
|
try {
|
|
339
362
|
return await request(token);
|
|
340
363
|
} catch (error) {
|
|
341
|
-
if (error instanceof CliError && error
|
|
364
|
+
if (error instanceof CliError && remintOn(error)) {
|
|
342
365
|
return await request(await mintAndCache(config));
|
|
343
366
|
}
|
|
344
367
|
throw error;
|
|
@@ -422,6 +445,195 @@ async function getConnections(config, token) {
|
|
|
422
445
|
return getPage(config, token, "/connections");
|
|
423
446
|
}
|
|
424
447
|
|
|
448
|
+
// src/integrations-api.ts
|
|
449
|
+
function isApiError(value) {
|
|
450
|
+
return value !== null && typeof value === "object" && typeof value.type === "string" && typeof value.message === "string";
|
|
451
|
+
}
|
|
452
|
+
function integrationsUrl(config, path, query) {
|
|
453
|
+
const url = new URL(config.integrationsBaseUrl.replace(/\/+$/, "") + path);
|
|
454
|
+
for (const [key, value] of Object.entries(query)) {
|
|
455
|
+
if (value !== void 0) url.searchParams.set(key, value);
|
|
456
|
+
}
|
|
457
|
+
return url.toString();
|
|
458
|
+
}
|
|
459
|
+
async function send(config, token, method, path, options = {}) {
|
|
460
|
+
const res = await httpFetch(integrationsUrl(config, path, options.query ?? {}), {
|
|
461
|
+
method,
|
|
462
|
+
headers: {
|
|
463
|
+
authorization: `Bearer ${token}`,
|
|
464
|
+
accept: "application/json",
|
|
465
|
+
"user-agent": USER_AGENT,
|
|
466
|
+
...options.body === void 0 ? {} : { "content-type": "application/json" },
|
|
467
|
+
...options.headers
|
|
468
|
+
},
|
|
469
|
+
...options.body === void 0 ? {} : { body: JSON.stringify(options.body) },
|
|
470
|
+
signal: config.abortSignal
|
|
471
|
+
});
|
|
472
|
+
const parsed = await res.json().catch(() => void 0);
|
|
473
|
+
if (res.ok) {
|
|
474
|
+
debugBody(path, parsed);
|
|
475
|
+
return { body: parsed ?? {}, replayed: res.headers.get("idempotent-replayed") === "true" };
|
|
476
|
+
}
|
|
477
|
+
if (isApiError(parsed)) throw integrationsError(parsed, res.status);
|
|
478
|
+
if (res.status === 401) throw authFailed();
|
|
479
|
+
throw apiUnavailable(`the integrations API returned HTTP ${res.status}`);
|
|
480
|
+
}
|
|
481
|
+
async function integrationsGet(config, token, path, query = {}) {
|
|
482
|
+
return (await send(config, token, "GET", path, { query })).body;
|
|
483
|
+
}
|
|
484
|
+
async function integrationsPost(config, token, path, body, headers = {}) {
|
|
485
|
+
return send(config, token, "POST", path, { body, headers });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/core/integrations.ts
|
|
489
|
+
var PATH_SEGMENT = /^[A-Za-z0-9_-]{1,128}$/;
|
|
490
|
+
function segment(name, value) {
|
|
491
|
+
if (!PATH_SEGMENT.test(value)) {
|
|
492
|
+
throw new CliError(EXIT.USAGE, "INVALID_ARGUMENT", `'${value}' is not a valid ${name}`);
|
|
493
|
+
}
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
var isScopeError = (error) => error.code === "INSUFFICIENT_SCOPE";
|
|
497
|
+
async function integrationsAuthorized(config, now, request) {
|
|
498
|
+
try {
|
|
499
|
+
return await authorized(config, now, request, (e) => e.code === "AUTH_FAILED" || isScopeError(e));
|
|
500
|
+
} catch (error) {
|
|
501
|
+
if (error instanceof CliError && isScopeError(error)) {
|
|
502
|
+
throw new CliError(
|
|
503
|
+
EXIT.PLAN,
|
|
504
|
+
"INSUFFICIENT_SCOPE",
|
|
505
|
+
"this API key doesn't have integration access yet \u2014 integrations need a Starter plan or above, and the key may need refreshing: rotate it in Financy \u2192 Settings \u2192 API, then run financy setup",
|
|
506
|
+
error.extra
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
throw error;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var get = (config, now, path, query = {}) => integrationsAuthorized(config, now, (token) => integrationsGet(config, token, path, query));
|
|
513
|
+
async function cursorList(config, now, path, query, all) {
|
|
514
|
+
const data = [];
|
|
515
|
+
let cursor = query.cursor;
|
|
516
|
+
let freshness;
|
|
517
|
+
do {
|
|
518
|
+
const page = await get(config, now, path, { ...query, cursor });
|
|
519
|
+
data.push(...Array.isArray(page.items) ? page.items : []);
|
|
520
|
+
freshness = page.freshness;
|
|
521
|
+
cursor = page.nextCursor ?? void 0;
|
|
522
|
+
} while (all && cursor);
|
|
523
|
+
return {
|
|
524
|
+
data,
|
|
525
|
+
count: data.length,
|
|
526
|
+
nextPage: all ? null : cursor ?? null,
|
|
527
|
+
...freshness === void 0 ? {} : { freshness }
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function fetchIntegrations(config, now) {
|
|
531
|
+
return cursorList(config, now, "/", {}, false);
|
|
532
|
+
}
|
|
533
|
+
async function fetchIntegration(config, now, id) {
|
|
534
|
+
const body = await get(config, now, `/${segment("integration id", id)}`);
|
|
535
|
+
return { data: body.integration };
|
|
536
|
+
}
|
|
537
|
+
async function fetchReceivables(config, now, args) {
|
|
538
|
+
const ids = (args.integrations ?? []).flatMap((v) => v.split(",")).filter((v) => v.length > 0);
|
|
539
|
+
const body = await get(config, now, "/receivables", {
|
|
540
|
+
integrationIds: ids.length > 0 ? ids.map((id) => segment("integration id", id)).join(",") : void 0,
|
|
541
|
+
asOf: args.asOf,
|
|
542
|
+
format: args.detailed ? "detailed" : void 0,
|
|
543
|
+
customerLimit: args.customerLimit,
|
|
544
|
+
includeProforma: args.includeProforma === void 0 ? void 0 : String(args.includeProforma)
|
|
545
|
+
});
|
|
546
|
+
return { data: body };
|
|
547
|
+
}
|
|
548
|
+
function fetchInvoicingDocuments(config, now, integrationId, args) {
|
|
549
|
+
return cursorList(
|
|
550
|
+
config,
|
|
551
|
+
now,
|
|
552
|
+
`/${segment("integration id", integrationId)}/documents`,
|
|
553
|
+
{
|
|
554
|
+
status: args.status,
|
|
555
|
+
type: args.type,
|
|
556
|
+
counterpartyId: args.customer,
|
|
557
|
+
issuedFrom: args.from,
|
|
558
|
+
issuedTo: args.to,
|
|
559
|
+
receivableOnly: args.receivableOnly ? "true" : void 0,
|
|
560
|
+
number: args.number,
|
|
561
|
+
limit: args.limit,
|
|
562
|
+
cursor: args.cursor
|
|
563
|
+
},
|
|
564
|
+
!!args.all
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
async function fetchInvoicingDocument(config, now, integrationId, documentId) {
|
|
568
|
+
const body = await get(
|
|
569
|
+
config,
|
|
570
|
+
now,
|
|
571
|
+
`/${segment("integration id", integrationId)}/documents/${segment("document id", documentId)}`
|
|
572
|
+
);
|
|
573
|
+
return { data: body.document, ...body.freshness === void 0 ? {} : { freshness: body.freshness } };
|
|
574
|
+
}
|
|
575
|
+
function fetchInvoicingCustomers(config, now, integrationId, args) {
|
|
576
|
+
return cursorList(
|
|
577
|
+
config,
|
|
578
|
+
now,
|
|
579
|
+
`/${segment("integration id", integrationId)}/customers`,
|
|
580
|
+
{ q: args.search, limit: args.limit, cursor: args.cursor },
|
|
581
|
+
!!args.all
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
async function fetchInvoicingCustomer(config, now, integrationId, customerId) {
|
|
585
|
+
const body = await get(
|
|
586
|
+
config,
|
|
587
|
+
now,
|
|
588
|
+
`/${segment("integration id", integrationId)}/customers/${segment("customer id", customerId)}`
|
|
589
|
+
);
|
|
590
|
+
return { data: body.customer, ...body.freshness === void 0 ? {} : { freshness: body.freshness } };
|
|
591
|
+
}
|
|
592
|
+
function fetchProposals(config, now, args) {
|
|
593
|
+
return cursorList(
|
|
594
|
+
config,
|
|
595
|
+
now,
|
|
596
|
+
"/proposals",
|
|
597
|
+
{
|
|
598
|
+
integrationId: args.integration === void 0 ? void 0 : segment("integration id", args.integration),
|
|
599
|
+
status: args.status,
|
|
600
|
+
clientReference: args.reference,
|
|
601
|
+
limit: args.limit
|
|
602
|
+
},
|
|
603
|
+
false
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
async function fetchProposal(config, now, id) {
|
|
607
|
+
const body = await get(config, now, `/proposals/${segment("proposal id", id)}`);
|
|
608
|
+
return { data: body.proposal };
|
|
609
|
+
}
|
|
610
|
+
function assertDraftBody(body) {
|
|
611
|
+
const obj = body;
|
|
612
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || typeof obj.action !== "string" || !Array.isArray(obj.items)) {
|
|
613
|
+
throw new CliError(
|
|
614
|
+
EXIT.USAGE,
|
|
615
|
+
"INVALID_DRAFT",
|
|
616
|
+
'a draft is a JSON object with an "action" (e.g. "create_tax_invoice") and an "items" array'
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return body;
|
|
620
|
+
}
|
|
621
|
+
async function createDraft(config, now, integrationId, body, idempotencyKey) {
|
|
622
|
+
const draft = assertDraftBody(body);
|
|
623
|
+
const path = `/${segment("integration id", integrationId)}/proposals`;
|
|
624
|
+
const headers = idempotencyKey ? { "idempotency-key": idempotencyKey } : {};
|
|
625
|
+
const res = await integrationsAuthorized(
|
|
626
|
+
config,
|
|
627
|
+
now,
|
|
628
|
+
(token) => integrationsPost(config, token, path, draft, headers)
|
|
629
|
+
);
|
|
630
|
+
return {
|
|
631
|
+
data: res.body.proposal,
|
|
632
|
+
replayed: res.replayed,
|
|
633
|
+
...idempotencyKey ? { idempotencyKey } : {}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
425
637
|
// src/core/status.ts
|
|
426
638
|
var STALE_THRESHOLD_DAYS = 2;
|
|
427
639
|
function toRow(c) {
|
|
@@ -565,7 +777,6 @@ export {
|
|
|
565
777
|
resolveEndpoints,
|
|
566
778
|
readCredentialsFile,
|
|
567
779
|
writeCredentialsFile,
|
|
568
|
-
bearerConfig,
|
|
569
780
|
loadConfig,
|
|
570
781
|
notConfigured,
|
|
571
782
|
VERSION,
|
|
@@ -582,5 +793,15 @@ export {
|
|
|
582
793
|
fetchTransaction,
|
|
583
794
|
fetchCategories,
|
|
584
795
|
fetchProviders,
|
|
585
|
-
fetchBranches
|
|
796
|
+
fetchBranches,
|
|
797
|
+
fetchIntegrations,
|
|
798
|
+
fetchIntegration,
|
|
799
|
+
fetchReceivables,
|
|
800
|
+
fetchInvoicingDocuments,
|
|
801
|
+
fetchInvoicingDocument,
|
|
802
|
+
fetchInvoicingCustomers,
|
|
803
|
+
fetchInvoicingCustomer,
|
|
804
|
+
fetchProposals,
|
|
805
|
+
fetchProposal,
|
|
806
|
+
createDraft
|
|
586
807
|
};
|
package/dist/lib.d.ts
CHANGED
package/dist/lib.js
CHANGED
|
@@ -43,7 +43,7 @@ var authFailed = (message = "authentication failed \u2014 check your credentials
|
|
|
43
43
|
var planNotEligible = () => new CliError(
|
|
44
44
|
EXIT.PLAN,
|
|
45
45
|
"NOT_AVAILABLE_ON_PLAN",
|
|
46
|
-
"the API is available on Starter and
|
|
46
|
+
"the API is available on every paid plan, including Starter, Pro, and Ultra \u2014 upgrade in Financy \u2192 Settings \u2192 Plan"
|
|
47
47
|
);
|
|
48
48
|
var forbidden = (detail) => new CliError(EXIT.PLAN, "FORBIDDEN", `the API denied this request (403): ${detail}`);
|
|
49
49
|
var insufficientCredits = (cost, balance) => new CliError(
|
|
@@ -59,6 +59,16 @@ var apiUnavailable = (message = "the Open-Finance API is unavailable \u2014 chec
|
|
|
59
59
|
var DEFAULT_AUTH_TOKEN_URL = "https://api.open-finance.ai/oauth/token";
|
|
60
60
|
var DEFAULT_API_BASE_URL = "https://api.open-finance.ai/v2";
|
|
61
61
|
var DEFAULT_CHAT_BASE_URL = "https://api.open-finance.ai/chat/chat";
|
|
62
|
+
var DEFAULT_INTEGRATIONS_BASE_URL = "https://api.open-finance.ai/integrations";
|
|
63
|
+
function resolveIntegrationsBaseUrl(env) {
|
|
64
|
+
if (env.FINANCY_INTEGRATIONS_URL) return env.FINANCY_INTEGRATIONS_URL;
|
|
65
|
+
if (!env.FINANCY_API_URL) return DEFAULT_INTEGRATIONS_BASE_URL;
|
|
66
|
+
try {
|
|
67
|
+
return `${new URL(env.FINANCY_API_URL).origin}/integrations`;
|
|
68
|
+
} catch {
|
|
69
|
+
return DEFAULT_INTEGRATIONS_BASE_URL;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
62
72
|
function resolveConfigDir(env) {
|
|
63
73
|
if (env.FINANCY_CONFIG_DIR) return env.FINANCY_CONFIG_DIR;
|
|
64
74
|
const base = env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
@@ -69,6 +79,7 @@ function resolveEndpoints(env) {
|
|
|
69
79
|
authTokenUrl: env.FINANCY_AUTH_URL ?? DEFAULT_AUTH_TOKEN_URL,
|
|
70
80
|
apiBaseUrl: env.FINANCY_API_URL ?? DEFAULT_API_BASE_URL,
|
|
71
81
|
chatBaseUrl: env.FINANCY_CHAT_URL ?? DEFAULT_CHAT_BASE_URL,
|
|
82
|
+
integrationsBaseUrl: resolveIntegrationsBaseUrl(env),
|
|
72
83
|
configDir: resolveConfigDir(env)
|
|
73
84
|
};
|
|
74
85
|
}
|
|
@@ -155,7 +166,7 @@ function notConfigured(result) {
|
|
|
155
166
|
// package.json
|
|
156
167
|
var package_default = {
|
|
157
168
|
name: "financy",
|
|
158
|
-
version: "0.
|
|
169
|
+
version: "0.4.0",
|
|
159
170
|
description: "Your Open-Finance accounts, balances and transactions in the terminal \u2014 with an embedded MCP server and agent skills",
|
|
160
171
|
type: "module",
|
|
161
172
|
bin: {
|
|
@@ -204,7 +215,6 @@ var package_default = {
|
|
|
204
215
|
scripts: {
|
|
205
216
|
prebuild: `node -e "require('node:fs').rmSync('dist',{recursive:true,force:true})"`,
|
|
206
217
|
build: "tsup",
|
|
207
|
-
"build:lambda": "tsup --config tsup.lambda.config.ts",
|
|
208
218
|
test: "vitest run",
|
|
209
219
|
"test:watch": "vitest",
|
|
210
220
|
typecheck: "tsc --noEmit",
|
|
@@ -214,8 +224,7 @@ var package_default = {
|
|
|
214
224
|
},
|
|
215
225
|
dependencies: {
|
|
216
226
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
217
|
-
commander: "^12.1.0"
|
|
218
|
-
jose: "^6.2.9"
|
|
227
|
+
commander: "^12.1.0"
|
|
219
228
|
},
|
|
220
229
|
devDependencies: {
|
|
221
230
|
"@eslint/js": "^10.0.1",
|
|
@@ -322,13 +331,14 @@ async function getAccessToken(config, now) {
|
|
|
322
331
|
}
|
|
323
332
|
return mintAndCache(config);
|
|
324
333
|
}
|
|
325
|
-
|
|
334
|
+
var isAuthFailure = (error) => error.code === "AUTH_FAILED";
|
|
335
|
+
async function authorized(config, now, request, remintOn = isAuthFailure) {
|
|
326
336
|
if (config.bearerToken) return request(config.bearerToken);
|
|
327
337
|
const token = await getAccessToken(config, now);
|
|
328
338
|
try {
|
|
329
339
|
return await request(token);
|
|
330
340
|
} catch (error) {
|
|
331
|
-
if (error instanceof CliError && error
|
|
341
|
+
if (error instanceof CliError && remintOn(error)) {
|
|
332
342
|
return await request(await mintAndCache(config));
|
|
333
343
|
}
|
|
334
344
|
throw error;
|
|
@@ -688,8 +698,11 @@ function errorEnvelope(error) {
|
|
|
688
698
|
return { error: { code: e.code, message: e.message, ...e.extra } };
|
|
689
699
|
}
|
|
690
700
|
async function callTool(name, args, io) {
|
|
701
|
+
return dispatchTool(BY_NAME, name, args, io);
|
|
702
|
+
}
|
|
703
|
+
async function dispatchTool(byName, name, args, io) {
|
|
691
704
|
try {
|
|
692
|
-
const tool =
|
|
705
|
+
const tool = byName.get(name);
|
|
693
706
|
if (!tool) {
|
|
694
707
|
throw new CliError(EXIT.USAGE, "UNKNOWN_TOOL", `no tool named '${name}'`);
|
|
695
708
|
}
|