FinSight is designed for AI agents and shell scripts to drive via the CLI.
Every command supports --json. This document defines what stability
guarantees we make about that JSON shape, so consumers know what they can
rely on.
- We follow semver-style stability on
--jsonoutput. Within a major version:- Existing fields will not be renamed or removed.
- Existing field types will not change.
- New fields may be added at any level.
- Breaking changes go behind a version bump (
finsight --version). - Exit codes (0/1/2/3/4) are also stable. See Exit codes.
If today finsight account list --json returns:
{ "accounts": [{ "id": "...", "name": "...", "type": "cash", "balance": 0 }] }You can safely:
- Read
accounts[].id/name/type/balanceand expect them to keep the same names and types - Assume the top level is an object with an
accountsarray - Add new fields to your parser's allowed set without code changes
- New top-level fields appearing (e.g.
accountspayload could gain atotal_countsibling) - New per-row fields appearing (e.g.
accounts[].annualized_return) - Ordering of fields within an object — JSON has no field order
- Whitespace / pretty-printing — we emit compact JSON, but don't depend on the exact byte sequence
- Renaming a field (
balance→cash_balance) - Removing a field
- Changing a field's type (
balance: number→balance: string) - Reordering an array's natural sort (this is rare but does count)
- Changing exit code semantics for an existing command
| Code | Name | Meaning |
|---|---|---|
| 0 | OK | Success |
| 1 | USER_ERROR | Bad input, missing arg, validation failure |
| 2 | DATA_CONFLICT | Operation OK but state is off (off-target, large reconcile delta, pending rationales) — useful for AI loops to detect "something's wrong but not user-facing error" |
| 3 | NOT_FOUND | Account/position/snapshot/decision ID doesn't exist |
| 4 | INTERNAL | Uncaught exception (file a bug) |
Error payloads in --json mode go to stderr with shape:
{ "error": "human message", "code": "USER_ERROR", "hint": "optional fix" }Stdout in --json mode is always valid JSON or empty (never half-JSON,
never mixed with prose).
- CI runs
pnpm typecheck+pnpm teston every PR - The CI workflow also runs a
skill-driftcheck: every command referenced inskills/finsight/SKILL.mdmust exist infinsight --help - Adding a field is fine and doesn't require a test
- Removing or renaming a field requires updating tests AND bumping the package version
Current version: see finsight --version.
We use the major version to signal --json shape changes. Patch and minor
bumps preserve the contract; major bumps may break it (with documented
migration notes in CHANGELOG.md).
These are anchor examples for the AI agent's mental model. Real outputs have more fields; these are the documented core.
{
"snapshot_date": "2026-05-29",
"total_net_worth": 1465471.34,
"currency": "CNY",
"by_account": [{ "id": "...", "name": "...", "balance_base": 0, "type": "...", "currency": "..." }],
"allocation": { "cash": 0.5, "us-stock": 0.3 },
"snapshots": [{ "id": "...", "date": "2026-05-29", "total": 1465471.34 }]
}{
"accounts": [{
"account_id": "...", "account_name": "...", "currency": "CNY",
"period_start": "2026-05-29", "period_end": "2026-05-29", "days": 0,
"net_deposits": 0, "current_value": 0,
"total_return": 0, "total_return_pct": null,
"cagr": null, "xirr": null, "cashflow_count": 0
}],
"overall": { /* same shape, account_id = null, account_name = "Overall" */ }
}{
"checks": [{ "name": "...", "level": "ok|warn|error", "message": "...", "hint": "..." }],
"summary": { "ok": 5, "warn": 2, "error": 0 }
}The single most useful command for "give the AI the user's whole portfolio
state in one shot". Shape includes config, as_of, net_worth,
by_asset_class, top_symbols, accounts, recent_decisions,
triggered_alerts, pending_rationale_count, target. Optional fields
may be present/absent depending on state.