@slothmoney/agent-cli 0.3.0 → 0.3.1
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 +7 -0
- package/README.md +20 -1
- package/dist/cli.js +11 -5
- package/dist/contracts.js +24 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.1 - 2026-07-31
|
|
6
|
+
|
|
7
|
+
- Coordinate transaction reads with the Sloth Budget daily refresh process.
|
|
8
|
+
- Wait up to 45 seconds for fresh persisted data, then return readable cached
|
|
9
|
+
transactions with structured refresh status when work continues or fails.
|
|
10
|
+
- Validate the additive transaction refresh response contract.
|
|
11
|
+
|
|
5
12
|
## 0.3.0 - 2026-07-30
|
|
6
13
|
|
|
7
14
|
- Add command-specific help for every command and auth subcommand, including
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ sloth-agent --version
|
|
|
15
15
|
For a one-off pinned run:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm exec --yes --package=@slothmoney/agent-cli@0.3.
|
|
18
|
+
npm exec --yes --package=@slothmoney/agent-cli@0.3.1 -- sloth-agent --help
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Authenticate
|
|
@@ -221,6 +221,25 @@ Read uncategorised contributions to the joint budget:
|
|
|
221
221
|
sloth-agent transactions --assignment-scope joint --uncategorized
|
|
222
222
|
```
|
|
223
223
|
|
|
224
|
+
The first transaction read after the UTC day changes may refresh linked bank
|
|
225
|
+
data. The CLI waits up to 45 seconds for that refresh to persist, then returns
|
|
226
|
+
the requested booked transactions. If the refresh is still running, partially
|
|
227
|
+
fails, or fails globally, readable cached transactions are still returned with
|
|
228
|
+
a structured `refresh` object:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"refresh": {
|
|
233
|
+
"status": "in_progress",
|
|
234
|
+
"reason": "wait_timeout",
|
|
235
|
+
"utcDate": "2026-07-31"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Re-run the transaction query later to observe the completed refresh. A partial
|
|
241
|
+
account failure remains eligible for an automatic retry.
|
|
242
|
+
|
|
224
243
|
Set `"assignmentScope": "joint"` on an assignment to categorise the eligible
|
|
225
244
|
shared portion for the joint budget.
|
|
226
245
|
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@ import { parseArgs, resolveBaseUrl, } from './args.js';
|
|
|
3
3
|
import { parseApiResponse, validateAssignmentPayload, validateJointBudgetSettingsResponse, } from './contracts.js';
|
|
4
4
|
import { createSystemCredentialStore, secureStorageUnavailableError, } from './credential-store.js';
|
|
5
5
|
import { ApiError, CliError, ConfigError, UsageError, } from './errors.js';
|
|
6
|
-
export const CLI_VERSION = '0.3.
|
|
7
|
-
const REQUEST_TIMEOUT_MS =
|
|
6
|
+
export const CLI_VERSION = '0.3.1';
|
|
7
|
+
const REQUEST_TIMEOUT_MS = 60_000;
|
|
8
8
|
const API_ORIGIN_HELP_LINES = [
|
|
9
9
|
'',
|
|
10
10
|
'API origin:',
|
|
@@ -208,10 +208,14 @@ export function transactionsHelpText() {
|
|
|
208
208
|
'Constraints:',
|
|
209
209
|
' All filters are omitted by default.',
|
|
210
210
|
' --end-date must not be before --start-date.',
|
|
211
|
-
'
|
|
211
|
+
' The first transaction read each UTC day may refresh linked bank data.',
|
|
212
|
+
' Refresh remotely persists booked transactions and account balances.',
|
|
213
|
+
' The command waits up to 45 seconds, then returns cached data if refresh continues.',
|
|
212
214
|
'',
|
|
213
215
|
'Output:',
|
|
214
|
-
' JSON containing transactions
|
|
216
|
+
' JSON containing transactions, nextCursor, and structured refresh status.',
|
|
217
|
+
' Refresh failures do not hide readable cached transactions.',
|
|
218
|
+
' Use nextCursor with --cursor',
|
|
215
219
|
' to request the next page. A null nextCursor means there are no more pages.',
|
|
216
220
|
'',
|
|
217
221
|
'Examples:',
|
|
@@ -905,7 +909,9 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
|
|
|
905
909
|
})()}`;
|
|
906
910
|
const response = await fetchImplementation(`${baseUrl}${path}`, {
|
|
907
911
|
method: 'GET',
|
|
908
|
-
headers
|
|
912
|
+
headers: parsed.command === 'transactions'
|
|
913
|
+
? { ...headers, Prefer: 'wait=45' }
|
|
914
|
+
: headers,
|
|
909
915
|
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
910
916
|
});
|
|
911
917
|
const data = parseApiResponse(parsed.command, await parseHttpResponse(response, token));
|
package/dist/contracts.js
CHANGED
|
@@ -2,6 +2,12 @@ import { ApiError, UsageError, } from './errors.js';
|
|
|
2
2
|
function isObject(value) {
|
|
3
3
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
4
4
|
}
|
|
5
|
+
function isIsoDate(value) {
|
|
6
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
7
|
+
return false;
|
|
8
|
+
const parsed = new Date(`${value}T00:00:00.000Z`);
|
|
9
|
+
return !Number.isNaN(parsed.valueOf()) && parsed.toISOString().slice(0, 10) === value;
|
|
10
|
+
}
|
|
5
11
|
function requireObject(value, label) {
|
|
6
12
|
if (!isObject(value))
|
|
7
13
|
throw new UsageError(`${label} must be an object`);
|
|
@@ -158,10 +164,27 @@ function isTransaction(value) {
|
|
|
158
164
|
|| value.incomeSubtype === 'interest'));
|
|
159
165
|
}
|
|
160
166
|
function isTransactionsResponse(value) {
|
|
167
|
+
const validStatuses = new Set(['skipped', 'completed', 'in_progress', 'partial', 'failed']);
|
|
168
|
+
const validReasons = new Set([
|
|
169
|
+
'all_fetched_today',
|
|
170
|
+
'no_api_connections',
|
|
171
|
+
'no_selected_accounts',
|
|
172
|
+
'refreshed',
|
|
173
|
+
'wait_timeout',
|
|
174
|
+
'account_failures',
|
|
175
|
+
'refresh_error',
|
|
176
|
+
]);
|
|
177
|
+
const refresh = isObject(value) ? value.refresh : undefined;
|
|
161
178
|
return (isObject(value)
|
|
162
179
|
&& Array.isArray(value.transactions)
|
|
163
180
|
&& value.transactions.every(isTransaction)
|
|
164
|
-
&& (value.nextCursor === null || typeof value.nextCursor === 'string')
|
|
181
|
+
&& (value.nextCursor === null || typeof value.nextCursor === 'string')
|
|
182
|
+
&& isObject(refresh)
|
|
183
|
+
&& typeof refresh.status === 'string'
|
|
184
|
+
&& validStatuses.has(refresh.status)
|
|
185
|
+
&& typeof refresh.reason === 'string'
|
|
186
|
+
&& validReasons.has(refresh.reason)
|
|
187
|
+
&& isIsoDate(refresh.utcDate));
|
|
165
188
|
}
|
|
166
189
|
function isAssignmentResponse(value) {
|
|
167
190
|
return (isObject(value)
|