Scripting it
The machine-facing half — which verbs have --json and what each one emits, the two exit codes, the stdout/stderr split, and how to branch on all of it in a shell script.
Nine of the ten verbs are non-interactive, which makes vike-cli a reasonable thing to put in a
cron job, a deploy script or a systemd unit. This page is the contract a wrapper can rely on: what
comes out, on which stream, and what the status code means.
Six verbs speak JSON, across seven surfaces
--json exists on backtest, sweep, walkforward, strategy-status, indicators, config show and config check — seven places to type it, six of the ten verbs, because config carries
the flag twice, once on each subcommand. It is a bare boolean everywhere — args::no_value rejects
an inline value rather than reading --json=1 as a silent true — and both flag spellings work on
every valued flag beside it, so --addr 1.2.3.4:7878 and --addr=1.2.3.4:7878 are the same thing.
What --json actually emits differs in a way worth knowing before you write a parser:
| verb | what you get |
|---|---|
backtest | the server's report bytes, printed exactly as they arrived. Without --json the same JSON is re-parsed and pretty-printed for a person |
sweep, walkforward | the server's ranked or stitched report JSON, same idiom |
strategy-status | the node's wire payload, serialized verbatim — identity, effective_params, mounts |
indicators | machine-readable rows instead of the grouped listing, with your own indicators under their own key and no category field |
config show | one object carrying the settings directory, the per-file status, both row sets and the loader's warnings |
config check | one object with settings_dir, settings_dir_origin, strict, ok, failures, warnings and one entry per finding |
The pattern in the first three rows is the rule rather than a coincidence: where a server computed the answer, the machine view is that server's own schema, not a second hand-maintained shape that could drift from it. The same discipline is why no metric is recomputed on this side — ranking and every printed statistic in a sweep are the server's numbers.
config check --json is the one that pre-computes its own verdict. ok is the exit code as a
boolean, deliberately: a reader must not have to re-implement the --strict promotion rule to learn
what the status was going to be.
Four verbs have no --json, and each absence is a decision. secrets prints key names and
never values, and its output is a disclosure meant to be read and pasted into an issue. init
prints a map for a human who has just installed something. trade is a REPL and mcp is already a
protocol.
Two exit codes
Every verb exits 0 or 1. There is no ladder of numeric codes to switch on, so a wrapper branches on success and reads stderr for the rest.
| outcome | code | stream |
|---|---|---|
| the verb did its job | 0 | the product on stdout |
-h / --help | 0 | the usage on stdout |
--version / -V | 0 | stdout |
| a usage error | 1 | vike-cli <command>: <msg> plus the usage, on stderr |
| the verb failed | 1 | the message on stderr |
| an unknown command | 1 | the error on stderr, then the help |
| no subcommand at all | 1 | the help — there was nothing to do |
crates/vike-cli/src/cmd/args.rs's exit_for_parse_error is the one place the help-versus-error
decision is made, which is not a detail: four verbs used to route the help short-circuit into their
usage-error arm and exit 1 with help requested printed to stderr as though it were a
diagnostic. A non-zero --help breaks every wrapper that checks a status, and config had already
been fixed at its verb level and not at its show --help level, in the same file. The shipped-binary
gate is crates/vike-cli/tests/help_cli.rs, which runs the real binary and asserts the status, the
stream, and that no internal token leaks.
The stream split is a contract
stdout is the product; stderr is everything else. That holds in every verb, not only under
mcp where stdout is literally a JSON-RPC stream. The settings loader's non-fatal resolutions, a
credential-store permissions warning, a rejected user indicator, and every failure message go to
stderr.
So vike-cli backtest --profile p.toml --json | jq '.sharpe' is safe, and 2>/dev/null around it
is safe in the narrow sense that stdout stays clean — but you will have thrown away the line that
tells you a preference was clamped or that your credential file is world-readable. Redirect stderr
to a log, not to the void.
Branching on it
The obvious use, and the one the shipped units actually make, is config check as a pre-flight:
if ! vike-cli config check; then
echo "this box is misconfigured; not starting" >&2
exit 1
fiThat is precisely what ExecStartPre= in the shipped service units runs. The exit code is the
product of that verb: a settings tree that fails to load, a removed environment variable still set,
or a VIKE_SETTINGS_DIR naming a path that is not a directory each fail it, while an absent
credential store — the ordinary live gate — does not.
Run it with --strict when you are asking whether the box is fully configured; never in a unit.
--strict promotes every warning to a failure, and a correctly installed box legitimately
carries warnings: a 0644 store, which vike_secrets::PermissionWarning argues must be reported
rather than refused, or the <project>/.env a shipped recorder unit uses as its EnvironmentFile,
which is the same file to vike_secrets::legacy_store_warning as a pre-one-store leftover. An
absent store is not one of them — crates/vike-cli/src/cmd/config_check.rs classifies it ok
outright, and an_absent_credential_store_is_not_even_a_warning pins that not even --strict may
fail a paper deployment over it.
A monitoring check over a live node is the other common shape:
vike-cli strategy-status --node "$NODE" --json |
jq -e '[.mounts[] | select(.live)] | length > 0'Each mount row carries strategy, params and a boolean live, and the identity block carries its
own live — the daemon's process-wide arming gate. Those two disagreeing is the normal,
informative case rather than a contradiction: the daemon is armed and this particular mount is not,
because its venue holds no credentials or its connect demoted it to paper. A monitor that reads only
one of them will eventually be surprised by the other.
and a nightly research run is the third:
vike-cli backtest --profile profiles/backtest.toml --json > "reports/$(date +%F).json"A stale environment variable fails every verb, not just the trading ones
refuse_removed_env runs before dispatch, so a script that exports VIKE_MAX_ORDER_NOTIONAL
anywhere in its environment gets exit 1 out of secrets path as readily as out of trade. That is
deliberate — a variable that is stale is stale regardless of the verb, and an operator who set it
believes a ceiling is in force. The message names the file, the key and a paste-ready TOML line
carrying their own value; the fix is to write that line and unset the variable.
Two things that make a run reproducible
--profile ships the file's text, so what ran is exactly what is on disk and in your version
control — there is no server-side profile library to drift from it. And --list-params is offline:
vike-cli backtest --list-params --script strategies/rhai/rsi_meanrev.rhaicompiles the script and prints one name = default line per tunable knob with no profile, no
connection and no server, which makes it cheap to assert in a pre-commit hook that a script still
declares the parameters a profile sets.
For what the report's fields mean, see the report; for the flag grammar every verb shares, global flags.
The agent door
What vike-cli mcp is as a CLI verb — a local stdio server, one vocabulary shared with the REPL, a preview gate that fails closed, a profile that can withhold the write tools outright — and where the tool-by-tool reference lives.
When it goes wrong
The failures a real vike-cli install actually hits — a store that is not there, a data service that is not listening, a variable that no longer exists — and what each message is telling you.