vike

vike-cli

The unified command-line surface — a subcommand dispatcher that resolves settings once, opens a credential only when a verb needs one, and answers --help on stdout with exit 0.

vike-cli is the unified command-line surface over the light client crates: a git/cargo-style subcommand dispatcher, vike-cli <command> [args…], with argument parsing hand-rolled and no clap dependency (crates/vike-cli/src/lib.rs). The dispatcher itself never grows command logic — it only routes. Each verb is a sibling module under cmd exposing a run(args) -> ExitCode, and adding one is a module, an arm in dispatch, and a line in COMMANDS.

That constant is the single source of truth for both the help text and the "unknown command" hint, so a new command shows up in help for free. This is what it holds:

vike-cli — the unified vike command-line surface

usage: vike-cli <command> [args…]

commands:
  backtest     run a backtest on a remote vike-datahub server and print the report
  sweep        run a parameter-grid search on a remote vike-datahub server and print the ranked grid
  walkforward  run an anchored walk-forward validation on a remote vike-datahub server
  data         get market data into the hist store: fetch real bars, or seed the demo tape
  config       settings provenance (`show`) and a validating pre-flight (`check`) for this box
  mcp          serve the create+backtest tools over stdio MCP (for Claude / an agent)
  trade        interactive REPL to observe + control a running vike-tradehub node (for a human)
  strategy-status ask a running vike-tradehub node what it is running (read-only; --json for machines)
  secrets      inspect the credential store <project>/settings/secrets.env, and set ONE key in it (list | path | template | set)
  init         create <project>/user_data — strategies, profiles, results — with examples
  indicators   print the indicators a Rhai strategy can call, with their parameters

run `vike-cli <command> --help` for a command's own options
run `vike-cli --version` to print this build's version

One page each: backtest · sweep · walkforward · data · config · mcp · trade · strategy-status · secrets · init · indicators. The grammar every verb shares is on global flags.

Help exits 0, to stdout

A top-level --help, -h or help prints the command list and succeeds; --version or -V prints the version and succeeds; an unknown command prints its error to stderr, then the help, and fails. Invoking the binary with no subcommand prints help and exits non-zero — there was nothing to do.

A non-zero --help breaks every wrapper that checks a status

--help short-circuits each parser through the Err channel carrying an internal sentinel, and four commands used to read that as a usage error: exit 1, with help requested printed to stderr as though it were a diagnostic. crates/vike-cli/src/cmd/args.rs's exit_for_parse_error is now the one place that decision is made, and crates/vike-cli/tests/help_cli.rs runs the real binary and asserts the three properties a user sees — the status, the stream, and that no internal token leaks. A unit test of the parser cannot see any of them.

--version prints <name> <version> (<build identity>) through vike_buildinfo::version_line — the shape every --version on the box already prints, with this build's provenance appended so a packaging script or a bug report can answer which commit is this?. Both spellings are accepted; never -v, which is verbosity everywhere else. Until they were added, both fell into the unknown-command arm and exited 1.

What happens before any verb runs

run calls resolve_policy first, which runs the workspace's one startup sequence (vike_boot::boot): it refuses a removed environment variable, resolves <project>/settings once, and loads it. The environment read lives at the entry point, per the rule that only binaries read the process environment — see environment variables and startup preflight. The check runs for every subcommand, including read-only ones: a variable that is stale is stale regardless of which verb was typed.

The loader's non-fatal resolutions are surfaced, never swallowed — this dispatcher used to drop them, so a preference clamped to a policy ceiling took effect with no output at all. They print to stderr, never stdout, because vike-cli mcp's stdout is a protocol.

The same sweep yields the settings directory (secrets, trade, config take it as a parameter, so the directory they print is provably the one every other surface reads) and its sibling <project>/user_data, which reaches init alone — the one subcommand that writes to the project, and it writes only under user_data/. indicators takes nothing from the dispatcher and opens no file of its own: its built-in roster is a property of the build, never of the environment or a credential. The listing is still not a pure function of the build — a user's own indicators are read out of the process state installed below, so two boxes running one binary print different listings when one of them has written one.

The credential read is lazy, and scoped

node_keyring opens <project>/settings/secrets.env only for the node-facing arms — trade, mcp and the read-only strategy-status. A backtest command has no business opening the file that holds every venue key on the box, and config and secrets reach that store through their own readers rather than this keyring. A store that exists but cannot be read is reported on stderr and treated as empty rather than fatal.

The identity is negative

The crate is DataFusion-free and transport-free, and that is the test every dependency is argued against edge by edge — crates/vike-cli/Cargo.toml carries the argument on the rows themselves, describing the crate as "the unified, DataFusion-free command-line surface" and marking it DataFusion-free by construction. vike-secrets — a crate with zero dependencies — is linked instead of vike-bridge-core for the credential store, and vike-ops and vike-bridge-core are both taken default-features = false; restoring their default full features would drag the venue transport and signing stack into a laptop binary. The heavy work happens on the data service instead.

<project>/user_data/indicators/*.rhai are loaded and installed process-wide before any subcommand runs, so a script run by any verb can call them. Every rejected file is reported on stderr and a bad one never fails the command — aborting would let one half-edited indicator block every invocation, including the init that scaffolds the examples.

On this page