-
Implemented the Phase 03 cli/ package — a local-only hlvaults CLI reusing the shared sync + query engines.
-
Added cli/ with a Vite SSR build (cli/vite.config.ts resolves $lib/$sql aliases, ?raw, and import.meta.glob), bin hlvaults, and root scripts cli:build / cli:dev plus a make cli-build target.
-
Implemented the command surface: detail (web Details-tab parity via DETAIL_COLUMN_IDS + buildVaultDetailRows, --wallet override, not-found path with on-demand sync), sync (sync-ordering contract enforced inside: global with excludeAddress before syncUserWallet), status, sql (argument, stdin -, and TTY REPL), wallet track/untrack/show/sync, reset (--yes, --dry-run, --keep-wallets), plus self-describing schema and columns detail commands.
-
Added the two-mode output contract: stdout is data (JSON envelopes with meta), stderr is narration; machine mode engages on --json, non-TTY stdout, or HLVAULTS_NON_INTERACTIVE=1; stable exit codes 0/1/2/3/4/5 with machine-readable error hints.
-
Added the migration gate with web parity: fresh installs apply db.generated.sql and seed the ledger silently; existing DBs prompt (human) or exit 3 with a --migrate hint (machine) — commands never run against a stale schema.
-
Verified against the live sync API: 437-vault global import, per-vault detail/history sync, tracked-wallet user columns populated via --wallet, reset-to-blank-slate with fresh-install re-seed, and 6 cli/src/render.spec.ts unit tests passing.
-
Hardened after adversarial review: routed the shared engines' console.log narration to stderr so machine-mode stdout is byte-pure JSON (the web build strips console via terser, which hid this), added a PID lockfile (PGlite is officially single-connection per data dir — concurrent processes now exit 4 with a hint), switched schema row counts from pg_stat_user_tables estimates to exact count(*), fixed cli:build to preserve the bin's exec bit, and reported on-demand syncs in meta.syncedThisRun.