Actual Budget CLI 完全实战指南:在终端中查询与修改个人预算数据
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
Actual Budget 是一款本地优先(local-first)的个人财务管理应用,而@actual-app/cli是它的官方命令行工具,让你不必打开网页就能在终端里查询和修改预算数据——账户、交易、分类、收款方、规则、排期交易一应俱全。本文以 packages/cli/README.md 为骨架,结合 packages/cli/src 下的源码实现,完整讲解 CLI 的安装、配置体系、全部命令、金额约定、缓存与锁机制,以及如何在 monorepo 内本地运行调试。读完本文,你将掌握用一行命令查余额、批量导入导出交易、执行 ActualQL 查询、自动化设置预算等实战技能。
一、认识 @actual-app/cli:定位与安装
CLI 的定位在 packages/cli/README.md 开头就已明确:它是 Actual Budget 的命令行接口,用于在终端中查询和修改预算数据。有一个关键前提必须理解:
该 CLI 连接的是正在运行的 Actual 同步服务器(sync server),它不会直接操作本地预算文件。
也就是说,CLI 是同步服务器的一个客户端:预算数据存储在服务器端,CLI 通过@actual-app/api与之通信,并会在本地保留一份缓存副本用于加速重复读取(详见后文"缓存机制"章节)。这一设计从 packages/cli/src/connection.ts 的withConnection实现中可以得到印证——每次命令执行都会先api.init建立连接,再按需下载/加载/同步预算。
安装与运行环境
npm install -g @actual-app/cli安装要求Node.js >= 22,这一点同时写在 README 与 packages/cli/package.json 的engines字段中。安装后,npm 会注册两个可执行命令(见 packages/cli/package.json):
actualactual-cli(actual的别名)
两者都指向构建产物./dist/cli.js。CLI 基于commander构建(packages/cli/src/index.ts),因此每个命令都支持标准的--help帮助输出。
二、快速开始:三个命令上手
安装完成后,先通过环境变量配置连接信息,然后就能立即开始使用:
# 设置连接信息 export ACTUAL_SERVER_URL=http://localhost:5006 export ACTUAL_PASSWORD=your-password export ACTUAL_SYNC_ID=your-sync-id # 在 设置 → 高级 → Sync ID 中找到 # 列出你的账户 actual accounts list # 查看某个账户余额 actual accounts balance <account-id> # 查看某月预算 actual budgets month 2026-03三个环境变量各自的职责:
| 变量 | 用途 |
|---|---|
ACTUAL_SERVER_URL | Actual 同步服务器的 URL(必填) |
ACTUAL_PASSWORD | 服务器密码(未使用 token 时必填) |
ACTUAL_SYNC_ID | 预算的 Sync ID(大多数命令需要) |
其中 Sync ID 需要在 Actual 网页端「设置 → 高级 → Sync ID」页面获取,它用于把 CLI 指向服务器上对应的那一个预算文件。
三、配置体系:从全局 Flag 到配置文件
3.1 配置解析优先级
CLI 按以下顺序解析配置,优先级从高到低:
- CLI flags(
--server-url、--password等) - 环境变量
- 配置文件(通过 cosmiconfig 查找)
- 默认值(
dataDir默认是~/.actual-cli/data)
这套优先级逻辑在 packages/cli/src/config.ts 的resolveConfig中逐字段实现,例如serverUrl的取值顺序是cliOpts.serverUrl ?? process.env.ACTUAL_SERVER_URL ?? fileConfig.serverUrl ?? '',dataDir的兜底默认值是join(homedir(), '.actual-cli', 'data')。最终解析出的配置结构CliConfig包含serverUrl、password、sessionToken、syncId、dataDir、encryptionPassword、cacheTtl、lockTimeout、refresh、noLock等字段。
需要特别指出的是,resolveConfig中有两道硬性校验(packages/cli/src/config.ts):
- 未配置
serverUrl直接报错:"Server URL is required..." - 未同时配置
password与sessionToken直接报错:"Authentication required..."
所以密码和会话令牌(session token)至少二选一,二者都没有时命令无法运行。
3.2 环境变量全表
| 变量 | 说明 |
|---|---|
ACTUAL_SERVER_URL | Actual 同步服务器的 URL(必填) |
ACTUAL_PASSWORD | 服务器密码(未使用 token 时必填) |
ACTUAL_SESSION_TOKEN | 会话令牌(密码的替代方案) |
ACTUAL_SYNC_ID | 预算 Sync ID(大多数命令需要) |
ACTUAL_DATA_DIR | 缓存预算数据的本地目录 |
ACTUAL_CACHE_TTL | 缓存 TTL,单位秒(默认 60) |
ACTUAL_LOCK_TIMEOUT | 预算目录锁的等待超时,单位秒(默认 10) |
ACTUAL_NO_LOCK | 设为1时禁用预算目录锁 |
除此之外,从源码还可以发现一个 README 环境变量表中未列出的变量:ACTUAL_ENCRYPTION_PASSWORD,对应--encryption-password全局 Flag,用于端到端加密(E2E encryption)预算的解密(见 packages/cli/src/index.ts)。如果你的预算开启了端到端加密,需要在同步时提供这把加密密码,connection.ts中下载预算时会将config.encryptionPassword传给api.downloadBudget(packages/cli/src/connection.ts)。
3.3 配置文件
CLI 使用 cosmiconfig 查找配置文件,查找范围是当前工作目录到主目录之间的任意位置(向上逐级查找)。可以使用的文件名格式:
.actualrc(JSON 或 YAML).actualrc.json、.actualrc.yaml、.actualrc.ymlactual.config.json、actual.config.yaml、actual.config.ymlpackage.json中的"actual"键
此外,还可以把配置放在全局配置目录的actual子目录中(Linux 上例如~/.config/actual/),支持的文件名:
config(JSON 或 YAML)config.jsonconfig.yamlconfig.yml
配置查找顺序在 packages/cli/src/config.ts 中与 cosmiconfig 的searchPlaces完全对应。
一个典型的.actualrc.json示例:
{ "serverUrl": "http://localhost:5006", "password": "your-password", "syncId": "1cfdbb80-6274-49bf-b0c2-737235a4c81f", "cacheTtl": 60, "lockTimeout": 10, "noLock": false }配置文件可用的键与全局配置一一对应:serverUrl、password、sessionToken、syncId、dataDir、encryptionPassword(字符串键),cacheTtl、lockTimeout(非负整数键),noLock(布尔键)。配置文件的校验逻辑相当严格(packages/cli/src/config.ts):文件必须是对象、不能包含未知键、字符串键必须是字符串、数值键必须是非负整数、布尔键必须是布尔值,否则直接报错。
安全提示(重要):不要在配置文件中保存明文密码(包括上面示例里的password键)。如果文件确实包含密码:
- 请设置严格的文件权限(Linux 上例如
600); - 如果文件位于 git 仓库内,务必加入
.gitignore; - 更推荐使用
ACTUAL_PASSWORD或ACTUAL_SESSION_TOKEN环境变量; - 或者在配置文件中使用会话令牌(session token)代替密码。
3.4 全局 Flags 全表
| Flag | 说明 |
|---|---|
--server-url <url> | 服务器 URL |
--password <pw> | 服务器密码 |
--session-token <token> | 会话令牌 |
--sync-id <id> | 预算 Sync ID |
--data-dir <path> | 数据目录 |
--cache-ttl <seconds> | 缓存 TTL;0表示禁用缓存(默认 60) |
--refresh | 本次调用强制同步,忽略缓存 |
--no-cache | --refresh的别名 |
--lock-timeout <secs> | 锁等待超时(默认 10) |
--no-lock | 禁用预算目录锁(谨慎使用) |
--format <format> | 输出格式:json(默认)、table、csv |
--verbose | 显示信息性消息 |
以上 Flags 在 packages/cli/src/index.ts 中均有对应定义,且每个选项都标注了对应的环境变量。两个值得注意的细节:
--format使用 commander 的.choices(['json', 'table', 'csv'])约束取值,非法值会直接报错;--cache-ttl与--lock-timeout都经过parseNonNegativeIntFlag校验,必须是非负整数(packages/cli/src/index.ts)。
四、命令总览与实战解析
4.1 命令总览表
| 命令 | 说明 |
|---|---|
accounts | 管理账户 |
budgets | 管理预算与分配 |
categories | 管理分类 |
category-groups | 管理分类组 |
transactions | 管理交易 |
payees | 管理收款方 |
tags | 管理标签 |
rules | 管理交易规则 |
schedules | 管理排期交易 |
query | 运行 ActualQL 查询 |
server | 服务器工具与查找 |
sync | 刷新或检查本地缓存 |
运行actual <command> --help可以查看每个命令的子命令和选项。下面重点剖析几个最常用的命令组(均可在 packages/cli/src/commands 目录中看到实现)。
4.2 accounts:账户管理
accounts命令组(packages/cli/src/commands/accounts.ts)包含以下子命令:
accounts list [--include-closed]:列出所有账户。默认排除已关闭账户,加--include-closed才包含;输出会做稳定排序——预算内账户在前、预算外账户在后,并在每组内保持 API 的sort_order;同时批量拉取每个账户的余额。accounts create --name <name> [--offbudget] [--balance <amount>]:创建账户,--balance是初始余额(整数分,默认0)。accounts update <id> [--name <name>] [--offbudget <bool>]:更新账户名称或预算内外属性;不提供任何字段会报错。accounts close <id> [--transfer-account <id>] [--transfer-category <id>]:关闭账户,可把剩余余额转入指定账户或分类。accounts reopen <id>:重新打开已关闭的账户。accounts delete <id>:删除账户。accounts balance <id> [--cutoff <date>]:查询账户余额,--cutoff支持指定日期(YYYY-MM-DD),返回该日期节点的余额。
4.3 budgets:预算管理
budgets命令组(packages/cli/src/commands/budgets.ts)负责预算数据与分配操作:
budgets list:列出服务器上所有可用预算(此命令无需 Sync ID,skipBudget: true)。budgets download <syncId> [--encryption-password <password>]:按 Sync ID 下载预算,加密预算需要提供加密密码。budgets months:列出所有存在数据的预算月份。budgets month <month>:查看指定月份(YYYY-MM)的完整预算数据。budgets set-amount --month <month> --category <id> --amount <amount>:为某分类在指定月份设置预算金额(整数分)。budgets set-carryover --month <month> --category <id> --flag <bool>:开启/关闭某分类的结转(carryover)。budgets hold-next-month --month <month> --amount <amount>:为下月预留预算金额。budgets reset-hold --month <month>:重置某月的预算预留。
4.4 transactions:交易增删改查与导入导出
transactions命令组(packages/cli/src/commands/transactions.ts):
transactions list --account <id> --start <date> --end <date>:列出指定账户在日期区间内的交易。transactions add --account <id> [--data <json>] [--file <path>] [--learn-categories] [--run-transfers]:新增交易,--data传 JSON 数组,或用--file从文件/标准输入读取;--learn-categories启用分类学习,--run-transfers处理转账。transactions import --account <id> [--data <json>] [--file <path>] [--dry-run]:导入交易(去重语义与add不同),--dry-run可以先预览导入结果而不真正写入。transactions update <id> [--data <json>] [--file <path>]:更新指定交易字段。transactions delete <id>:删除交易。
--data与--file是互斥的 JSON 输入来源:--file -表示从标准输入读取,方便与其他命令用管道组合(详见readJsonInput的实现)。
4.5 query:用 ActualQL 查询数据
query命令组(packages/cli/src/commands/query.ts)是 CLI 中最强大的数据检索能力,底层使用 Actual 的查询语言 ActualQL(AQL):
query run:执行一条 AQL 查询。query tables:列出可查询的表。query fields <table>:列出某张表的所有字段及其类型。
query run的核心选项:
| 选项 | 说明 |
|---|---|
--table <table> | 要查询的表(可用query tables查看) |
--select <fields> | 逗号分隔的字段列表 |
--filter <json> | JSON 格式的过滤条件,如'{"amount":{"$lt":0}}' |
--where <json> | --filter的别名(不能同时使用) |
--order-by <fields> | 排序字段,可带方向:field1:desc,field2(默认 asc) |
--limit <n> | 结果数量上限 |
--offset <n> | 跳过前 N 条(用于分页) |
--last <n> | 显示最近 N 笔交易(隐含--table transactions与--order-by date:desc) |
--count | 统计匹配行数而非返回数据 |
--group-by <fields> | 分组字段(配合聚合 select 使用) |
--file <path> | 从 JSON 文件读取完整查询对象(-表示标准输入) |
从 packages/cli/src/commands/query.ts 的TABLE_SCHEMA可以看到当前可查询的表:transactions、accounts、categories、payees、rules、schedules。transactions表的字段非常丰富,包括id、account、date、amount、payee、category、notes、cleared、reconciled、is_parent、is_child、parent_id、schedule,以及便捷的关联字段account.name、payee.name、category.name、category.group.name。
--order-by支持date:desc,amount:asc,id这种带方向的多字段写法,解析逻辑在 packages/cli/src/commands/query.ts 的parseOrderBy中实现。
4.6 server 与 sync:服务器工具与缓存管理
server命令组(packages/cli/src/commands/server.ts):
server version:获取服务器版本(此命令不需要 Sync ID)。server get-id --type <type> --name <name>:按名称查找实体 ID,--type可选accounts、categories、payees、schedules。在脚本中非常实用——先用名字查 ID,再拿去查询或写入。server bank-sync [--account <id>]:触发银行同步,可只同步指定账户。
sync命令组(packages/cli/src/commands/sync.ts):
sync:立即把本地缓存与服务器同步。sync --status:显示本地缓存有多"旧"(stale),返回syncedAt、lastDownloadedAt、ageSeconds、ttlSeconds、stale等字段。sync --clear:删除本地缓存,下次命令会重新下载。
五、金额约定:整数分(cents)
所有作为输入传入的货币金额(无论是 Flag 还是 JSON)都使用整数分:
| CLI 值 | 美元金额 |
|---|---|
5000 | $50.00 |
-12350 | -$123.50 |
例如-2500表示 -$25.00,50000表示 $500.00。这种约定避免了一切浮点精度问题,与 Actual 内部的数据存储方式一致。
输出格式化规则:
--format table和--format csv输出时,会把分值字段自动转换为十进制(例如显示1665.00而不是166500);--format json输出始终返回原始分值,方便程序化处理。
这个自动转换在 packages/cli/src/output.ts 中通过AMOUNT_FIELDS集合实现——amount、balance、balance_available、balance_current、balance_limit、budgeted、spent、carryover这些字段在表格/CSV 输出时都会执行value / 100并保留两位小数。
值得一提的安全细节:CSV 输出对以=、+、-、@、制表符、回车开头的非数值字符串做了公式注入防护(前缀'中和),但数值型金额(如-25.00)不会被打引号,保证负数金额在电子表格中仍以数值呈现(packages/cli/src/output.ts)。
六、实战示例合集
以下示例完整覆盖 README 中的常用场景,并补充了便于直接复制运行的注释:
# 以表格形式列出所有账户(默认排除已关闭账户) actual accounts list [--include-closed] --format table # 按名称查找实体 ID(脚本自动化第一步) actual server get-id --type accounts --name "Checking" # 新增一笔交易(金额为整数分:-2500 = -$25.00) actual transactions add --account <id> \ --data '[{"date":"2026-03-14","amount":-2500,"payee_name":"Coffee Shop"}]' # 导出整年交易到 CSV actual transactions list --account <id> \ --start 2026-01-01 --end 2026-12-31 --format csv > transactions.csv # 为某分类设置预算金额($500 = 50000 分) actual budgets set-amount --month 2026-03 --category <id> --amount 50000 # 运行一条 ActualQL 查询:最近 10 笔支出 actual query run --table transactions \ --select "date,amount,payee" --filter '{"amount":{"$lt":0}}' --limit 10 # 快捷方式:最近 5 笔交易 actual query run --last 5 # 统计交易数量 actual query run --table transactions --count # 按分类分组聚合(配合 --file 使用聚合表达式) echo '{"table":"transactions","groupBy":["category.name"],"select":["category.name",{"amount":{"$sum":"$amount"}}]}' | actual query run --file - # 分页 actual query run --table transactions --order-by "date:desc" --limit 10 --offset 20 # --where 是 --filter 的别名 actual query run --table transactions --where '{"payee.name":"Grocery Store"}' --limit 5 # 从 JSON 文件读取完整查询 actual query run --file query.jsonquery run还支持从标准输入读取查询 JSON(--file -),这让它天然适合被其他脚本和命令管道驱动。AQL 常用过滤操作符包括$eq、$ne、$lt、$lte、$gt、$gte、$like、$and、$or。
七、常见坑与实用建议
1. 拆分交易(Split transactions)会重复计数。统计或求和交易时,务必过滤"is_parent": false。拆分交易的父交易持有总额,子交易持有各部分金额——把两者都算进去等于把总额数了两遍。这是 README 明确强调、且query帮助文本中反复出现的注意事项。
2. 未分类交易的category.name是null。按分类过滤或分组时要把这个情况考虑进去。
3. AQL 不支持日期子字段。date.month、date.year等不能作为查询字段使用。如果需要按月份分组,正确做法是用日期范围过滤拉取原始交易,然后在本地脚本中自行聚合。
4. 高频顺序请求的性能。CLI 默认会在本地缓存预算(见下一节),所以读多写少的脚本不再需要"单查询"式的规避方案。对于请求非常频繁的脚本,可以先执行一次actual sync,然后用较长的--cache-ttl做后续读取:
actual sync actual --cache-ttl 3600 query run ... actual --cache-ttl 3600 accounts list这样后续的读命令在 TTL 内直接命中本地缓存,不产生网络往返。
5. 先查 ID 再操作。所有写操作(如transactions add、budgets set-amount)都要求传实体 ID。在脚本中建议先用actual server get-id --type ... --name ...把人类可读的名字解析成 ID,再执行后续操作,避免硬编码 UUID。
八、缓存机制深度解析
CLI 会在本地保留一份预算副本,让重复命令不必每次都打同步服务器。在默认 TTL(60 秒)内,读命令(list、balance、query run等)直接复用缓存,不做网络往返;写命令(add、update、set-amount等)则始终在写入前后各同步一次服务器。
缓存相关的操作:
actual sync—— 立即刷新缓存。actual sync --status—— 查看本地缓存有多旧。actual sync --clear—— 删除本地缓存,下次命令重新下载。--refresh(或--no-cache)—— 单次调用强制同步。--cache-ttl <seconds>—— 单次调用覆盖 TTL(用0禁用缓存)。
缓存的底层决策逻辑非常清晰,见 packages/cli/src/cache.ts 的decideSyncAction:
- 本地没有缓存状态 → 动作
download(首次下载预算); - 缓存中的
syncId或serverUrl与当前配置不一致 → 动作download(重新下载); - 是写操作、或指定了
--refresh、或 TTL 为0、或预算加密→ 动作sync(先加载再同步); - 距上次同步时间小于 TTL → 动作
skip(直接用缓存); - 缓存过期(超过 TTL)→ 动作
sync。
缓存状态存放在数据目录下以syncId命名的子目录中的state.json文件里,包含version、syncId、budgetId、serverUrl、lastSyncedAt、lastDownloadedAt等字段(packages/cli/src/cache.ts),目录结构为<dataDir>/.actual-cli/<syncId>/state.json。缓存写入采用"临时文件 + 原子重命名"策略,并用进程 PID + 随机数保证临时文件名唯一,避免并发写者互相破坏(packages/cli/src/cache.ts);同时缓存持久化是"尽力而为"的,写不进也不至于让命令崩溃。
从 packages/cli/src/connection.ts 可以看到完整流程:读缓存状态 →decideSyncAction决策 → 按需downloadBudget/loadBudget/sync→ 执行命令回调 → 若是写操作再sync一次并刷新lastSyncedAt。
九、并发与锁机制
CLI 针对每个预算的缓存目录加锁:读操作加共享锁(多个并行读安全),写操作加排他锁(写操作串行化)。如果另一个 CLI 进程持锁,后续调用最多等待--lock-timeout秒(默认 10)然后报错退出。在可信的单进程环境中可以传--no-lock跳过加锁。
锁实现细节见 packages/cli/src/lock.ts:排他锁基于proper-lockfile的目录锁(锁文件lock),等待超时换算为重试策略;共享锁则在readers目录下写入以PID-随机数命名的标记文件,写操作要等所有 reader 标记消失才继续。实现里还包含陈旧 reader 清理——通过process.kill(pid, 0)探测进程是否存活,自动清扫死进程遗留的标记,避免死锁(packages/cli/src/lock.ts)。锁文件另有 30 秒的 stale 判定,崩溃进程遗留的锁也能被后续进程接管。
十、在 monorepo 中本地运行与开发
如果你想直接在这个仓库里跑 CLI(例如调试新功能或不想走 npm 全局安装),README 给出了完整流程:
# 1. 构建 CLI yarn build:cli # 2. 在另一个终端启动本地同步服务器 yarn start:server-dev # 3. 浏览器打开 http://localhost:5006,创建一个预算, # 然后在 设置 → 高级 → Sync ID 中拿到 Sync ID # 4. 直接从构建产物运行 CLI ACTUAL_SERVER_URL=http://localhost:5006 \ ACTUAL_PASSWORD=your-password \ ACTUAL_SYNC_ID=your-sync-id \ node packages/cli/dist/cli.js accounts list # 或者定义一个简写别名方便使用 alias actual-dev="node $(pwd)/packages/cli/dist/cli.js" actual-dev budgets listyarn build:cli会调用 packages/cli/package.json 中的vite build将 TypeScript 源码编译到dist/。CLI 依赖@actual-app/api(工作区内的 packages/api)、commander(参数解析)、cosmiconfig(配置文件查找)、proper-lockfile(目录锁)、cli-table3(表格输出),这些依赖关系同样记录在 packages/cli/package.json。
结语
@actual-app/cli把 Actual Budget 的绝大部分核心操作带到了终端:账户、交易、分类、预算分配、规则、排期,乃至完整的 ActualQL 查询能力。配合环境变量/配置文件的三层配置体系、整数分的金额约定、本地缓存与细粒度锁机制,它非常适合承担定时脚本、CI 流水线、数据导出备份等自动化任务。需要进一步探索某个子命令的细节时,actual <command> --help永远是最好的起点——查询相关的actual query run --help还会直接列出所有可用表与常用过滤操作符。
【免费下载链接】actualA local-first personal finance app项目地址: https://gitcode.com/GitHub_Trending/ac/actual
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考