Purpose: define a first-class CLI that is a thin, 1:1 client of the Local API (ADR-0003). The CLI exists for humans, CI, automation scripts, and AI coding agents (Claude Code) alike.
Principles
- No business logic. Each command maps onto one (or a short composition of) API calls.
- Scriptable by default: every command supports
--jsonfor machine-readable output and stable exit codes. Human-pretty output is the default for TTY only. - Same verbs as the API:
vflin <domain> <action>mirrorsdomain.action. - Safe by default: dangerous actions require
--mode danger(orpower) AND a confirm, unless--yesis passed in non-interactive contexts.
Shape
vflin profile create --name "x" --fingerprint preset:win11-chrome --proxy <id>
vflin profile start <id> [--headless] [--json]
vflin profile stop <id> [--sync]
vflin profile ls [--group y] [--json]
vflin proxy test <id> --json
vflin fingerprint validate -f config.json
vflin automation run --script <id> --group y --var k=v
vflin automation status <runId> --watch
vflin sync push <id> | pull <id> | status <id>
vflin core ls | core select <provider> --version 148.9 | core diagnose
vflin log tail --filter automation
vflin policy set danger # session-scoped, audited
Output & exit codes
0success ·1generic error ·2usage error ·3policy denied ·4conflict/locked ·5core launch failure. (Mirrors API error codes.)--jsonemits the API{ ok, data, error }envelope verbatim → trivial for agents to parse.
Connection model
- CLI talks to a running Application Core over the Local API socket.
vflin daemon status|start|stopmanages the Core if not already up (UI also starts it).
Why first-class from day 1
- Phase-1 exit criterion is a CLI command opening a real browser through the API.
- Makes the whole product testable headless in CI before any UI exists.
- Becomes the substrate the MCP server and Claude Code skills shell out to or mirror.
Non-goals
- The CLI does not read profile folders, the vault, or cookies directly. Ever.
- No alternate “fast path” that skips the API.
Sessions (Feature c — ADR-0034)
Bind a saved service session so a profile opens already logged in. Full operator guide:
SESSIONS_MODULE.md (internal: docs/20-control-plane/SESSIONS_MODULE.md).
vflin session recipes # supported services (green; always prints JSON)
vflin profile session list <id> # always prints JSON
vflin profile session get <id> <service>
vflin --mode danger profile session set <id> \
--service <name> --session-mode <inject|login|companion> \
--secrets-file <path.json> [--label <l>]
vflin --mode danger profile session clear <id> <service>
Two rules this surface exists to enforce:
- Secrets come from a FILE, never argv.
--secrets-filetakes a{"field":"value"}JSON object, so a session token cannot land in shell history or in another user’spsoutput. --modeand--session-modeare different flags.--modeis the global policy mode (normal|power|danger), stripped before dispatch;--session-modeis the recipe’s mode. They originally shared the name--mode, which made the documented invocation literally unrunnable — the global validator rejectedinjectbefore the command ever saw it (lockstep review 2026-07-19).