vike

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:

verbwhat you get
backtestthe server's report bytes, printed exactly as they arrived. Without --json the same JSON is re-parsed and pretty-printed for a person
sweep, walkforwardthe server's ranked or stitched report JSON, same idiom
strategy-statusthe node's wire payload, serialized verbatim — identity, effective_params, mounts
indicatorsmachine-readable rows instead of the grouped listing, with your own indicators under their own key and no category field
config showone object carrying the settings directory, the per-file status, both row sets and the loader's warnings
config checkone 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.

outcomecodestream
the verb did its job0the product on stdout
-h / --help0the usage on stdout
--version / -V0stdout
a usage error1vike-cli <command>: <msg> plus the usage, on stderr
the verb failed1the message on stderr
an unknown command1the error on stderr, then the help
no subcommand at all1the 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
fi

That 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.rhai

compiles 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.

On this page