mirror of
https://github.com/actualbudget/actual.git
synced 2026-05-07 12:28:57 -05:00
Every `actual <cmd>` call currently delta-syncs the budget with the
sync server via `api.downloadBudget`, which hits the server's
500-req/min rate limit on scripted workflows. Actual is local-first:
once the budget is on disk, most read commands do not need fresh
server data.
Introduce a CLI-only cache layer inside `withConnection` that
decides per invocation whether to skip, sync, or re-download:
- Cache state lives at `{dataDir}/.actual-cli/{syncId}/state.json`,
keyed by `syncId` to avoid the chicken-and-egg of not knowing the
on-disk `budgetId` before the first download. The on-disk id is
resolved via `api.getBudgets()` and persisted after first download.
- Read commands (list, balance, query run, …) skip the `/sync`
call while `now - lastSyncedAt < cacheTtl`. Write commands
(create, update, delete, set-*, etc.) sync before and after the
operation to keep server state consistent.
- Encrypted budgets force a sync per call since `api/load-budget`
does not re-verify the password.
- New `proper-lockfile`-backed shared/exclusive lock serializes
writes while allowing parallel reads. Reader markers live in
`{meta}/readers/`; writers sweep stale markers by PID.
New `actual sync` command with three modes: default (sync now),
`--status` (print cache age, TTL, stale flag), `--clear` (delete
cache, holding the exclusive lock to avoid racing writers).
New config surface, following the existing flag → env → config file
→ default precedence chain:
- `--cache-ttl <s>` / `ACTUAL_CACHE_TTL` / `cacheTtl` (default 60)
- `--refresh` / `--no-cache`
- `--lock-timeout <s>` / `ACTUAL_LOCK_TIMEOUT` / `lockTimeout` (10)
- `--no-lock` / `ACTUAL_NO_LOCK` / `noLock`
Every `withConnection` call site now passes an explicit
`{ mutates: boolean, skipBudget?: boolean }` so read/write intent is
visible at the edge.
The old `budgets sync` subcommand is removed — it silently diverged
from the new top-level `actual sync`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
48 lines
1.1 KiB
JSON
48 lines
1.1 KiB
JSON
{
|
|
"name": "@actual-app/cli",
|
|
"version": "26.4.0",
|
|
"description": "CLI for Actual Budget",
|
|
"license": "MIT",
|
|
"bin": {
|
|
"actual": "./dist/cli.js",
|
|
"actual-cli": "./dist/cli.js"
|
|
},
|
|
"files": [
|
|
"dist"
|
|
],
|
|
"type": "module",
|
|
"imports": {
|
|
"#cache": "./src/cache.ts",
|
|
"#commands/*": "./src/commands/*.ts",
|
|
"#config": "./src/config.ts",
|
|
"#connection": "./src/connection.ts",
|
|
"#input": "./src/input.ts",
|
|
"#lock": "./src/lock.ts",
|
|
"#output": "./src/output.ts",
|
|
"#utils": "./src/utils.ts"
|
|
},
|
|
"scripts": {
|
|
"build": "vite build",
|
|
"test": "vitest --run",
|
|
"typecheck": "tsgo -b"
|
|
},
|
|
"dependencies": {
|
|
"@actual-app/api": "workspace:*",
|
|
"cli-table3": "^0.6.5",
|
|
"commander": "^14.0.3",
|
|
"cosmiconfig": "^9.0.1",
|
|
"proper-lockfile": "^4.1.2"
|
|
},
|
|
"devDependencies": {
|
|
"@types/node": "^22.19.17",
|
|
"@types/proper-lockfile": "^4",
|
|
"@typescript/native-preview": "^7.0.0-dev.20260404.1",
|
|
"rollup-plugin-visualizer": "^7.0.1",
|
|
"vite": "^8.0.5",
|
|
"vitest": "^4.1.2"
|
|
},
|
|
"engines": {
|
|
"node": ">=22"
|
|
}
|
|
}
|