actual-mcp-server 0.22.50 → 0.23.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 +1 -1
- package/dist/src/lib/apiLogin.js +172 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -838,7 +838,7 @@ The software is provided **as-is**, without warranty of any kind. The author acc
|
|
|
838
838
|
|
|
839
839
|
---
|
|
840
840
|
|
|
841
|
-
**Version:** 0.
|
|
841
|
+
**Version:** 0.23.0 | **Tool Count:** 84 (verified LibreChat-compatible)
|
|
842
842
|
|
|
843
843
|
## Sponsor
|
|
844
844
|
|
package/dist/src/lib/apiLogin.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import api from '@actual-app/api';
|
|
2
2
|
import { withOpTimeout } from './opTimeout.js';
|
|
3
3
|
import { setApiLogin, setLoadedBudgetSyncId, registerBudgetLoad, loginsSpanServers } from './apiState.js';
|
|
4
|
+
import { createModuleLogger } from './loggerFactory.js';
|
|
5
|
+
const log = createModuleLogger('API-LOGIN');
|
|
4
6
|
// Upstream's `init` returns its module-level handler object, the same one every time. Upstream
|
|
5
7
|
// exports no other way to reach `close-budget`. Nothing can be open before the first init, which
|
|
6
8
|
// is when this is still unset.
|
|
@@ -43,14 +45,15 @@ let upstreamLib = null;
|
|
|
43
45
|
* after the caller has thrown and released the lock. A late close would close the budget the next
|
|
44
46
|
* operation loaded, in any deployment, so it is always registered with the #393 abandoned-load
|
|
45
47
|
* registry: every `withApiLock` acquisition settles it, bounded and failing closed, before it runs
|
|
46
|
-
* anything.
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* anything. With one login, upstream logs in itself and the login is not registered: it lands on
|
|
49
|
+
* the only server there is, and registering it would replace #270's per-request recovery with a
|
|
50
|
+
* fail-closed wait on every operation. With more than one (`loginsSpanServers`), a late login
|
|
51
|
+
* could move the client to another server, so #574 performs it as our own abortable fetch and
|
|
52
|
+
* registers only the credential-less init that follows (see `initOverFetchedLogin`). The close
|
|
50
53
|
* has no such exemption: it waits on upstream's spreadsheet and services (not on running handlers), so a close
|
|
51
54
|
* that stalls makes every session wait, bounded and failing closed, in a one-login deployment
|
|
52
|
-
* too (the accepted #393 trade-off;
|
|
53
|
-
*
|
|
55
|
+
* too (the accepted #393 trade-off; upstream's close takes no signal, so it stays unabortable).
|
|
56
|
+
* A late close, login or init records nothing (the records were cleared in step 1 and `setApiLogin` is only reached by
|
|
54
57
|
* a caller still waiting), so the next operation logs in again.
|
|
55
58
|
*/
|
|
56
59
|
export async function initApiLoggedIn(dataDir, login, label) {
|
|
@@ -61,15 +64,176 @@ export async function initApiLoggedIn(dataDir, login, label) {
|
|
|
61
64
|
registerBudgetLoad(closing);
|
|
62
65
|
await withOpTimeout(() => closing, `${label} (close outgoing budget)`);
|
|
63
66
|
}
|
|
67
|
+
if (loginsSpanServers()) {
|
|
68
|
+
await initOverFetchedLogin(dataDir, login, label);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
64
71
|
const loggingIn = api.init({
|
|
65
72
|
dataDir,
|
|
66
73
|
serverURL: login.serverUrl,
|
|
67
74
|
password: login.password,
|
|
68
75
|
});
|
|
69
|
-
if (loginsSpanServers())
|
|
70
|
-
registerBudgetLoad(loggingIn);
|
|
71
76
|
const lib = await withOpTimeout(() => loggingIn, label);
|
|
72
77
|
if (lib && typeof lib.send === 'function')
|
|
73
78
|
upstreamLib = lib;
|
|
74
79
|
setApiLogin(login);
|
|
75
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* #574: the multi-login path. Upstream's sign-in takes no signal, so a stalled login could only
|
|
83
|
+
* be raced, and its late answer then pointed the client at its server and wrote its token under
|
|
84
|
+
* whatever operation ran next. Here the login is our own fetch, aborted at the bound, and the
|
|
85
|
+
* client is only touched after that fetch has answered in time:
|
|
86
|
+
* 1. the bounded login: the fetch and its body read, nothing else. `finally` aborts it, which
|
|
87
|
+
* is a no-op once it has answered. There is deliberately no `signal.aborted` check after
|
|
88
|
+
* it (round 1 of the gate asked for one, round 2 reversed it): the `finally` aborts on
|
|
89
|
+
* success too, so the check would fail every login, and a timeout rejects the awaited race
|
|
90
|
+
* before the next line runs, which `multi_server_budget_selection.test.js` row 22 pins
|
|
91
|
+
* with a fetch that ignores the abort.
|
|
92
|
+
* 2. a credential-less `api.init` (local: `setServer` plus a fresh in-memory store), then
|
|
93
|
+
* `subscribe-set-token` on the handle it returns. Neither makes a network call, but both
|
|
94
|
+
* are still raced, so both are registered like the close: a late one would re-point the
|
|
95
|
+
* client or hand it the wrong server's token.
|
|
96
|
+
*
|
|
97
|
+
* An empty password keeps upstream's skip: no fetch and no token. Nothing of the previous server
|
|
98
|
+
* survives, because every `api.init` resets upstream's store (`init$3({ persist: false })`).
|
|
99
|
+
*/
|
|
100
|
+
async function initOverFetchedLogin(dataDir, login, label) {
|
|
101
|
+
let token = '';
|
|
102
|
+
if (login.password) {
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
try {
|
|
105
|
+
token = await withOpTimeout(() => loginOverFetch(login, controller.signal), label);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
controller.abort();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// `password: ''` is upstream's no-credential init (it signs in only on a truthy password).
|
|
112
|
+
const pointing = api.init({ dataDir, serverURL: login.serverUrl, password: '' });
|
|
113
|
+
registerBudgetLoad(pointing);
|
|
114
|
+
const lib = await withOpTimeout(() => pointing, label);
|
|
115
|
+
if (!lib || typeof lib.send !== 'function')
|
|
116
|
+
throw new Error(`Actual API ${label}: upstream init returned no handle`);
|
|
117
|
+
upstreamLib = lib;
|
|
118
|
+
if (token) {
|
|
119
|
+
// Local, but raced and registered like the init: a late token set after the next init would
|
|
120
|
+
// hand this server's token to whichever server the client then points at.
|
|
121
|
+
const setting = lib.send('subscribe-set-token', { token });
|
|
122
|
+
registerBudgetLoad(setting);
|
|
123
|
+
await withOpTimeout(() => setting, label);
|
|
124
|
+
}
|
|
125
|
+
setApiLogin(login);
|
|
126
|
+
}
|
|
127
|
+
function authError(reason) {
|
|
128
|
+
return Object.assign(new Error(`Authentication failed: ${reason}`), { code: reason });
|
|
129
|
+
}
|
|
130
|
+
/** The server URL as written into a message: no `user:pass@`, which an operator may put there. */
|
|
131
|
+
function displayUrl(serverUrl) {
|
|
132
|
+
try {
|
|
133
|
+
const u = new URL(serverUrl);
|
|
134
|
+
u.username = '';
|
|
135
|
+
u.password = '';
|
|
136
|
+
return u.toString().replace(/\/$/, '');
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return 'the configured server';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* #574: upstream's password login (`signIn` over `post`), as our own fetch so it can be aborted.
|
|
144
|
+
* Resolves the session token. Errors follow upstream's `post`/`throwIfNot200` order and carry
|
|
145
|
+
* `.code` like its `withErrorCode`, because `classifyInitFailure` and `isRetryableAuthError`
|
|
146
|
+
* read them: in particular a 429 keeps upstream's raw text (`Too many requests, ...`), which is
|
|
147
|
+
* what the auth-rate-limit retry matches. Three cases go beyond upstream on purpose:
|
|
148
|
+
* `redirect-refused` (upstream follows a redirect and re-POSTs the password to its target),
|
|
149
|
+
* `invalid-server-url`, and `openid-required` (upstream returns `{ redirectUrl }` as a success,
|
|
150
|
+
* which a server-side password login cannot follow).
|
|
151
|
+
*
|
|
152
|
+
* Reads `globalThis.fetch` at call time; upstream captured its own at module load. Never logs
|
|
153
|
+
* or throws the password, the token or a response body.
|
|
154
|
+
*/
|
|
155
|
+
export async function loginOverFetch(login, signal) {
|
|
156
|
+
let url;
|
|
157
|
+
try {
|
|
158
|
+
url = new URL(login.serverUrl);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
throw authError('invalid-server-url');
|
|
162
|
+
}
|
|
163
|
+
url.pathname = `${url.pathname.replace(/\/+$/, '')}/account/login`;
|
|
164
|
+
let res;
|
|
165
|
+
let text;
|
|
166
|
+
try {
|
|
167
|
+
res = await globalThis.fetch(url.toString(), {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: JSON.stringify({ loginMethod: 'password', password: login.password }),
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
// 'manual', not 'error': under 'error' Node rejects a redirect with the same
|
|
172
|
+
// `TypeError: fetch failed` as a refused connection, which would map to the retried
|
|
173
|
+
// `network-failure`. Here the 3xx arrives as a status and is refused below.
|
|
174
|
+
redirect: 'manual',
|
|
175
|
+
signal,
|
|
176
|
+
});
|
|
177
|
+
text = await res.text();
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (signal.aborted)
|
|
181
|
+
throw authError('aborted');
|
|
182
|
+
log.debug('Login request failed', { error: err instanceof Error ? err.name : 'unknown' });
|
|
183
|
+
throw authError('network-failure');
|
|
184
|
+
}
|
|
185
|
+
if (res.status >= 300 && res.status < 400) {
|
|
186
|
+
// Refused even for an http to https upgrade on the same host (the usual TLS proxy setup):
|
|
187
|
+
// following any redirect re-POSTs the password, and one rule is easier to trust than a
|
|
188
|
+
// same-host exception. The operator fixes it once, so the message names the server URL the
|
|
189
|
+
// redirect implies: its origin plus the path before `/account/login` (a subpath deployment
|
|
190
|
+
// keeps its subpath), never its userinfo or query. No hint when the Location is absent,
|
|
191
|
+
// unparseable, not http(s), or implies the URL already configured.
|
|
192
|
+
let hint = '';
|
|
193
|
+
const location = res.headers.get('Location');
|
|
194
|
+
try {
|
|
195
|
+
const target = new URL(location ?? '', url);
|
|
196
|
+
const suggested = target.origin + target.pathname.replace(/\/account\/login\/*$/, '').replace(/\/+$/, '');
|
|
197
|
+
if (location && /^https?:$/.test(target.protocol) && suggested !== displayUrl(login.serverUrl)) {
|
|
198
|
+
hint = ` (it redirects to ${suggested}; configure that URL instead)`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// An unparseable Location gives no hint; the refusal stands.
|
|
203
|
+
}
|
|
204
|
+
throw Object.assign(new Error(`Authentication failed: redirect refused by ${displayUrl(login.serverUrl)}${hint}`), { code: 'redirect-refused' });
|
|
205
|
+
}
|
|
206
|
+
if (res.status !== 200) {
|
|
207
|
+
if (res.status === 500)
|
|
208
|
+
throw authError('internal');
|
|
209
|
+
if ((res.headers.get('Content-Type') ?? '').toLowerCase().includes('application/json')) {
|
|
210
|
+
let body;
|
|
211
|
+
try {
|
|
212
|
+
body = JSON.parse(text);
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
throw authError('parse-json');
|
|
216
|
+
}
|
|
217
|
+
const reason = body?.reason === 'unauthorized' && body?.details === 'token-not-found' ? 'token-expired' : body?.reason;
|
|
218
|
+
throw authError(reason || 'network-failure');
|
|
219
|
+
}
|
|
220
|
+
if (res.headers.has('ngrok-error-code'))
|
|
221
|
+
throw authError('network-failure');
|
|
222
|
+
throw authError(text.slice(0, 200) || 'network-failure');
|
|
223
|
+
}
|
|
224
|
+
let data;
|
|
225
|
+
try {
|
|
226
|
+
data = JSON.parse(text);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
throw authError('parse-json');
|
|
230
|
+
}
|
|
231
|
+
if (data?.status !== 'ok')
|
|
232
|
+
throw authError(data?.description || data?.reason || 'unknown');
|
|
233
|
+
if (data.data?.returnUrl) {
|
|
234
|
+
throw Object.assign(new Error(`Authentication failed: ${displayUrl(login.serverUrl)} requires OpenID login, which a server password cannot use`), { code: 'openid-required' });
|
|
235
|
+
}
|
|
236
|
+
if (!data.data?.token)
|
|
237
|
+
throw new Error('login: User token not set');
|
|
238
|
+
return data.data.token;
|
|
239
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "actual-mcp-server",
|
|
3
3
|
"displayName": "Actual MCP Server",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.23.0",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.0.0",
|
|
7
7
|
"npm": ">=10.0.0"
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"verify-tools": "npm run build && node scripts/verify-tools.js",
|
|
31
31
|
"check:coverage": "node scripts/list-actual-api-methods.mjs",
|
|
32
32
|
"direct-sync": "node scripts/direct-sync/bank-sync-direct.mjs",
|
|
33
|
-
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/account_flow_summary.test.js && node tests/unit/recurring_expenses_summary.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/new_tool_completeness.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/adapter_set_budget_batch.test.js && node tests/unit/budget_updates_batch.test.js && node tests/unit/rules_create.test.js && node tests/unit/adapter_rule_reference_guard.test.js && node tests/unit/rules_create_batch.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/httpServer_init_failure_report.test.js && node tests/unit/stdio_init_failure_report.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/session_ownership.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/oidc_resource_identifier.test.js && node tests/unit/httpServer_oidc_resource_metadata.test.js && node tests/unit/oidc_error_cause.test.js && node tests/unit/httpServer_oidc_error_body.test.js && node tests/unit/oidc_token_source.test.js && node tests/unit/config_oidc_assertion_refines.test.js && node tests/unit/httpServer_oidc_assertion_mode.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpErrorHandler.test.js && node tests/unit/httpServer_error_handler_wiring.test.js && node tests/unit/httpServer_trust_proxy.test.js && node tests/unit/httpServer_batch_cap.test.js && node tests/unit/dotenv_stdout_quiet.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_acl_listing.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/multi_server_budget_selection.test.js && node tests/unit/static_tokens.test.js && node tests/unit/httpServer_static_tokens.test.js && node tests/unit/multi_server_budget_selection_stdio.test.js && node tests/unit/multi_server_session_open_http.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/transactions_update_split_convert.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/transactions_update_batch.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/tool_content_logging.test.js && node tests/unit/adapter_error_logging.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/batch_uncategorized_cleanup.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/server_version_probe_outside_op_timeout.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js && node tests/unit/oidc_scope_list.test.js && node tests/unit/get_context.test.js && node tests/unit/contributor_docs.test.js && node tests/unit/toolsets.test.js && node tests/unit/toolsets_entrypoint.test.js && node tests/unit/tools_list_size_budget.test.js && node tests/unit/rules_update_schedules_schema.test.js && node tests/unit/schedules_update_amount_guard.test.js && node tests/unit/source_text_helper.test.js && node tests/unit/transport_onerror.test.js && node tests/unit/adapter_transaction_batch_abort.test.js && node tests/unit/transactions_merge_adapter.test.js && node tests/unit/schedules_dead_conditions.test.js && node tests/unit/session_ownership_express.test.js && node tests/unit/pool_orphan_sweep.test.js && node tests/unit/session_budget_state_cleanup.test.js && node tests/unit/http_session_delete.test.js && node tests/unit/budget_acl_parse.test.js && node tests/unit/server_instructions.test.js && node tests/unit/query_run_empty_hint.test.js",
|
|
33
|
+
"test:unit-js": "node tests/unit/transactions_create.test.js && node tests/unit/transactions_aggregate.test.js && node tests/unit/account_flow_summary.test.js && node tests/unit/recurring_expenses_summary.test.js && node tests/unit/generated_tools.smoke.test.js &&node tests/unit/schema_validation.test.js && node tests/unit/schema_json_openai_compat.test.js && node tests/unit/config_https_validation.test.js && node tests/unit/config_data_dir.test.js && node tests/unit/config_drift.test.js && node tests/unit/port_alignment.test.js && node tests/unit/dockerfile_data_dir_alignment.test.js && node tests/unit/unraid_template_alignment.test.js && node tests/unit/entrypoint_invariants.test.js && node tests/unit/e2e_spec_collection.test.js && node tests/unit/compose_profile_sync.test.js && node tests/unit/advertised_tools_sync.test.js && node tests/unit/new_tool_completeness.test.js && node tests/unit/knip_config.test.js && node tests/unit/readme_stats_sync.test.js && node tests/unit/config_insecure_upstream.test.js && node tests/unit/workflow_release_guards.test.js && node tests/unit/report_train_failure.test.js && node tests/unit/unit_chain_membership.test.js && node tests/unit/api_surface_drift.test.js && node tests/unit/write_effect_audit_staleness.test.js && node tests/unit/write_effect_audit_membership.test.js && node tests/unit/tool_annotations.test.js && node tests/unit/tool_id_schema_drift.test.js && node tests/unit/filter_id_resolution.test.js && node tests/unit/filter_id_tool_wiring.test.js && node tests/unit/api_coverage_baseline.test.js && node tests/unit/train_preflight.test.js && node tests/unit/verify_published_artifacts.test.js && node tests/unit/report_train_stale.test.js && node tests/unit/stdio_framing_check.test.js && node tests/unit/server_info.test.js && node tests/unit/transfers_create.test.js && node tests/unit/bot_target_branch.test.js && node tests/unit/auth_posture.test.js && node tests/unit/auth_posture_wiring.test.js && node tests/unit/auth-acl.test.js && node tests/unit/bug76.test.js && node tests/unit/budgets_setAmount.test.js && node tests/unit/adapter_set_budget_batch.test.js && node tests/unit/budget_updates_batch.test.js && node tests/unit/rules_create.test.js && node tests/unit/adapter_rule_reference_guard.test.js && node tests/unit/rules_create_batch.test.js && node tests/unit/budgets_hold_for_next_month.test.js && node tests/unit/budgets_transfer.test.js && node tests/unit/transactions_uncategorized.test.js && node tests/unit/httpServer_session_init.test.js && node tests/unit/httpServer_session_not_found.test.js && node tests/unit/httpServer_init_failure_report.test.js && node tests/unit/stdio_init_failure_report.test.js && node tests/unit/manual_mcp_client_retry.test.js && node tests/unit/manual_mcp_client_session.test.js && node tests/unit/manual_mcp_client_circuit.test.js && node tests/unit/manual_runner_killswitch.test.js && node tests/unit/adapter_auth_rate_limit.test.js && node tests/unit/adapter_session_reuse.test.js && node tests/unit/retry_classifier.test.js && node tests/unit/adapter_module_surface.test.js && node tests/unit/adapter_nonidempotent_no_retry.test.js && node tests/unit/pool_liveness.test.js && node tests/unit/pool_shutdown_all.test.js && node tests/unit/query_where_operators.test.js && node tests/unit/query_run_validation.test.js && node tests/unit/adapter_with_write_session.test.js && node tests/unit/category_groups_delete.test.js && node tests/unit/rules_delete.test.js && node tests/unit/schedules_delete.test.js && node tests/unit/accounts_delete.test.js && node tests/unit/update_tools_not_found.test.js && node tests/unit/accounts_close.test.js && node tests/unit/accounts_reopen.test.js && node tests/unit/payees_delete.test.js && node tests/unit/payees_merge.test.js && node tests/unit/rules_create_or_update.test.js && node tests/unit/unhandled-rejection.test.js && node tests/unit/rejection-allowlist-purity.test.js && node tests/unit/httpServer_bearer_auth.test.js && node tests/unit/session_ownership.test.js && node tests/unit/httpServer_oidc_audience.test.js && node tests/unit/httpServer_oidc_auth_verification.test.js && node tests/unit/httpServer_oidc_jwks_discovery.test.js && node tests/unit/oidc_resource_identifier.test.js && node tests/unit/httpServer_oidc_resource_metadata.test.js && node tests/unit/oidc_error_cause.test.js && node tests/unit/httpServer_oidc_error_body.test.js && node tests/unit/oidc_token_source.test.js && node tests/unit/config_oidc_assertion_refines.test.js && node tests/unit/httpServer_oidc_assertion_mode.test.js && node tests/unit/httpServer_body_limit.test.js && node tests/unit/httpErrorHandler.test.js && node tests/unit/httpServer_error_handler_wiring.test.js && node tests/unit/httpServer_trust_proxy.test.js && node tests/unit/httpServer_batch_cap.test.js && node tests/unit/dotenv_stdout_quiet.test.js && node tests/unit/httpServer_bind_host.test.js && node tests/unit/adapter_write_pool_cooperation.test.js && node tests/unit/adapter_drain_listing_cache.test.js && node tests/unit/budget_acl_enforcement.test.js && node tests/unit/budget_acl_listing.test.js && node tests/unit/budget_selection_precondition.test.js && node tests/unit/multi_server_budget_selection.test.js && node tests/unit/static_tokens.test.js && node tests/unit/httpServer_static_tokens.test.js && node tests/unit/multi_server_budget_selection_stdio.test.js && node tests/unit/api_login_fetch.test.js && node tests/unit/multi_server_session_open_http.test.js && node tests/unit/budget_load_abandonment.test.js && node tests/unit/budget_loader_postcondition.test.js && node tests/unit/budget_loader_import_allowlist.test.js && node tests/unit/budget_import_tracking.test.js && node tests/unit/pool_shutdown_under_lock.test.js && node tests/unit/api_lock_affinity.test.js && node tests/unit/commit_message_closing_keyword.test.js && node tests/unit/write_queue_stranding.test.js && node tests/unit/write_queue_stranded_bound.test.js && node tests/unit/budget_preference_store.test.js && node tests/unit/budget_preference_restore.test.js && node tests/unit/tags_list.test.js && node tests/unit/tags_create.test.js && node tests/unit/tags_update.test.js && node tests/unit/tags_delete.test.js && node tests/unit/notes_get.test.js && node tests/unit/notes_update.test.js && node tests/unit/payees_common_list.test.js && node tests/unit/transactions_update_guard.test.js && node tests/unit/transactions_update_splits.test.js && node tests/unit/transactions_update_split_convert.test.js && node tests/unit/entities_search.test.js && node tests/unit/transactions_import_schema.test.js && node tests/unit/tool_schema_shape.test.js && node tests/unit/zod_error_format.test.js && node tests/unit/transactions_update_batch.test.js && node tests/unit/tool_count_sync.test.js && node tests/unit/stdio_transport.test.js && node tests/unit/stdio_budget_switch.test.js && node tests/unit/logger_structured.test.js && node tests/unit/logger_redaction.test.js && node tests/unit/tool_content_logging.test.js && node tests/unit/adapter_error_logging.test.js && node tests/unit/logger_correlation.test.js && node tests/unit/check_coverage.test.js && node tests/unit/adapter_op_timeout.test.js && node tests/unit/adapter_write_queue_wakeup.test.js && node tests/unit/ci_workflow_guards.test.js && node tests/unit/node_version_guard.test.js && node tests/unit/node_version_drift.test.js && node tests/unit/playwright_version_drift.test.js && node tests/unit/observability_smoke.test.js && node tests/unit/residue_pattern.test.js && node tests/unit/batch_uncategorized_cleanup.test.js && node tests/unit/dual_transport_gate.test.js && node tests/unit/manual_assertions_use_helper.test.js && node tests/unit/server_version_guard.test.js && node tests/unit/server_version_probe_outside_op_timeout.test.js && node tests/unit/payee_rules_reference_filter.test.js && node tests/unit/oidc_metadata_discovery.test.js && node tests/unit/httpServer_oauth_metadata_route.test.js && node tests/unit/httpServer_mcp_info_route.test.js && node tests/unit/residue_sweep_balance_account.test.js && node tests/unit/tool_factory.test.js && node tests/unit/budget_registry_parse.test.js && node tests/unit/preferences_get.test.js && node tests/unit/budgets_export.test.js && node tests/unit/budgets_import.test.js && node tests/unit/budgets_import_syncid.test.js && node tests/unit/budget_acl_dynamic.test.js && node tests/unit/budget_acl_identity_map.test.js && node tests/unit/budget_acl_userinfo.test.js && node tests/unit/acl_e2e_fixture_contract.test.js && node tests/unit/budget_acl_middleware.test.js && node tests/unit/rules_stage.test.js && node tests/unit/oidc_scope_list.test.js && node tests/unit/get_context.test.js && node tests/unit/contributor_docs.test.js && node tests/unit/toolsets.test.js && node tests/unit/toolsets_entrypoint.test.js && node tests/unit/tools_list_size_budget.test.js && node tests/unit/rules_update_schedules_schema.test.js && node tests/unit/schedules_update_amount_guard.test.js && node tests/unit/source_text_helper.test.js && node tests/unit/transport_onerror.test.js && node tests/unit/adapter_transaction_batch_abort.test.js && node tests/unit/transactions_merge_adapter.test.js && node tests/unit/schedules_dead_conditions.test.js && node tests/unit/session_ownership_express.test.js && node tests/unit/pool_orphan_sweep.test.js && node tests/unit/session_budget_state_cleanup.test.js && node tests/unit/http_session_delete.test.js && node tests/unit/budget_acl_parse.test.js && node tests/unit/server_instructions.test.js && node tests/unit/query_run_empty_hint.test.js",
|
|
34
34
|
"test:adapter": "npm run build && node dist/src/tests_adapter_runner.js",
|
|
35
35
|
"test:e2e": "npx playwright test",
|
|
36
36
|
"test:e2e:docker": "./tests/e2e/run-docker-e2e.sh",
|