Skip to content

CLI reference

The softprobe CLI is the primary interface for humans, CI, and AI agents. It speaks only HTTP to the runtime — no local state, no config files to manage. Commands that emit structured results support --json for machine-readable output (not completion, which prints a shell script). All commands return stable exit codes.

Global options

FlagDefaultPurpose
--runtime-url URL$SOFTPROBE_RUNTIME_URL or https://runtime.softprobe.devWhere to find the runtime
--jsonoffEmit structured JSON instead of human text
--verbose / -voffExtra diagnostic logging to stderr
--quiet / -qoffSuppress non-error output
--help / -hPrint command help
--versionPrint softprobe SEMVER (spec http-control-api@v…); release builds set SEMVER with -ldflags "-X softprobe-runtime/internal/version.Version=v0.5.0"

Exit codes

CodeMeaning
0Success
1Generic failure (see stderr)
2Invalid arguments
3Runtime unreachable
4Session not found
5Schema / validation error
10doctor reports one or more failed checks
20suite run completed with failures

Agents and CI should check $? (or the exit code field in JSON) before parsing further.


softprobe doctor

Check the local environment. Run this first whenever something is wrong.

bash
softprobe doctor                # human-readable
softprobe doctor --json         # machine-readable
softprobe doctor --verbose      # include HTTP request/response details

Example output (shape; paths and markers vary by machine):

softprobe v0.5.0 (spec http-control-api@v1)
runtime healthy: ok
runtimeVersion: 0.0.0-dev
specVersion: http-control-api@v1
schemaVersion: 1
✓ runtime-reachable
✓ version-drift
✓ schema-version
⚠ wasm-binary: WASM binary not found at /etc/envoy/sp_istio_agent.wasm
⚠ header-echo: x-softprobe-session-id: header not echoed by proxy (may indicate misconfig)

Exit code: 0 on all green, 10 on any failure. Warnings don't affect exit code.

What it checks

CheckFailing conditionExit contribution
Runtime reachableHTTP error on GET /healthfatal → exit 10
CLI ↔ runtime version driftRuntime's specVersion / schemaVersion differs from CLI's embedded expectations in a way that breaks compatibilityfatal → exit 10
Schema versionRuntime schemaVersion not in the CLI's supported listfatal → exit 10
Proxy WASM presentOptional — checks well-known paths (/etc/envoy/…, $WASM_PATH). Missing → warning onlynon-fatal
Header echoOptional — smoke-tests x-softprobe-session-id through the proxy. Absent → warning onlynon-fatal

Spec-drift detection

doctor compares three version fields when it reaches a runtime:

FieldWhere reportedSemantics
cliVersionEmbedded in the binary (softprobe --version)Changes with every CLI release
runtimeVersionRuntime's /health payloadChanges with every runtime release
specVersionRuntime's /v1/meta payloadChanges only on breaking protocol changes
schemaVersionRuntime's /v1/meta payloadChanges only on breaking schema changes

Compatibility rule: the CLI embeds the expected specVersion and schemaVersion constants (http-control-api@v1, schema 1, today). If /v1/meta reports different values, doctor exits 10 with a runtime_drift error — the protocol or schema is not wire-compatible with this binary.

This gives agents and CI a cheap, deterministic way to detect "someone upgraded half the stack" without staring at version strings.

--json output

json
{
  "status": "ok",
  "exitCode": 0,
  "cliVersion": "v0.5.0 (spec http-control-api@v1)",
  "runtimeVersion": "0.0.0-dev",
  "specVersion": "http-control-api@v1",
  "schemaVersion": "1",
  "checks": [
    {
      "name": "runtime-reachable",
      "status": "ok",
      "details": { "url": "https://runtime.softprobe.dev", "latencyMs": 4 }
    },
    {
      "name": "version-drift",
      "status": "ok",
      "details": { "cli": "v0.5.0", "runtime": "0.0.0-dev" }
    },
    {
      "name": "schema-version",
      "status": "ok",
      "details": { "expected": "1", "got": "1" }
    },
    {
      "name": "wasm-binary",
      "status": "warn",
      "details": { "message": "WASM binary not found at /etc/envoy/sp_istio_agent.wasm" }
    }
  ]
}

On drift, status: "drift" and the specific check fails with status: "fail" plus a message explaining the incompatible pair. See --json field stability below.


softprobe session

Manage sessions directly (usually wrapped inside tests or suite runs).

session start

bash
softprobe session start --mode replay --json
# {"sessionId":"sess_01H...","sessionRevision":1}

softprobe session start --mode capture --shell
# export SOFTPROBE_SESSION_ID=sess_01H...
FlagValuesDefault
--modecapture, replay, generate(required)
--jsonoff
--shellemit export line for evaloff
--policy FILEYAML policy to apply
--case FILEcase file to load immediately

session load-case

bash
softprobe session load-case --session $SOFTPROBE_SESSION_ID --file cases/x.case.json

Uploads the case to the runtime (which parses + validates it).

session rules apply

bash
softprobe session rules apply --session $SOFTPROBE_SESSION_ID --file rules/stripe.yaml

Replaces the session's rule document with the contents of the file.

session policy set

bash
softprobe session policy set --session $SOFTPROBE_SESSION_ID --strict
softprobe session policy set --session $SOFTPROBE_SESSION_ID --file policy.yaml

--strict is a shortcut for externalHttp: strict, defaultOnMiss: error.

session close

bash
softprobe session close --session $SOFTPROBE_SESSION_ID
softprobe session close --session $SOFTPROBE_SESSION_ID --out cases/captured.case.json

Closes the session. For capture sessions, optionally redirects the flushed case file path.

session stats

bash
softprobe session stats --session $SOFTPROBE_SESSION_ID --json
# {"extractedSpans":2,"injectedSpans":4,"sessionRevision":5,"mode":"replay"}

softprobe capture

One-shot capture orchestration.

capture run

bash
softprobe capture run \
  --driver "npm run smoke-test" \
  --target http://127.0.0.1:8082 \
  --out cases/checkout.case.json

Starts a session, exports SOFTPROBE_SESSION_ID for the driver process, runs it, closes the session, and writes the case file.

FlagPurpose
--driver CMDShell command to run; receives SOFTPROBE_SESSION_ID in env
--target URLBase URL for informational purposes (currently unused)
--out PATHWhere to write the case file
--redact-file PATHRedaction rules to apply before the driver runs
--timeout DURATIONMaximum runtime (e.g. 300s, default 10m)

softprobe replay

replay run

bash
softprobe replay run --session $SOFTPROBE_SESSION_ID --json
# {"sessionId":"sess_...","hits":12,"misses":0}

Diagnostic: reports inject statistics for a live session. Usually more useful than most users need; prefer suite run for real workflows.


softprobe suite

Ships in this build

softprobe suite run, validate, and diff are implemented. suite run spawns a Node sidecar per invocation to resolve RequestHook / MockResponseHook / BodyAssertHook / HeadersAssertHook references from your suite.yaml, and emits JUnit XML + a standalone HTML report. A worked end-to-end harness — docker-compose + two cases off one capture file (one hook-driven, one YAML-only source: inline) — lives at e2e/cli-suite-run/.

Read one or more case files and run them as a test suite.

suite run

The recommended way to regression-test captured traffic at scale. Full walkthrough: Run a suite at scale.

bash
softprobe suite run suites/checkout.suite.yaml \
  --parallel 32 \
  --hooks hooks/checkout.ts \
  --junit out/junit.xml \
  --report out/report.html
FlagDefaultPurpose
--runtime-url URL$SOFTPROBE_RUNTIME_URL or https://runtime.softprobe.devControl-plane runtime
--app-url URL$APP_URLhttp://127.0.0.1:8081SUT base URL; prefer the ingress proxy so x-softprobe-session-id becomes tracestate on egress
--parallel Nmin(32, cpu*4)Concurrent cases
--hooks PATHHook file; repeatable. TS accepted on Node 22+ (uses --experimental-strip-types); otherwise compile to .js/.mjs
--junit PATHEmit JUnit XML
--report PATHEmit standalone HTML report
--jsonoffEmit JSON envelope on stdout
--filter SUBSTRKeep cases whose resolved path or name: contains SUBSTR
--fail-fastoffStop on first failure
--env-file PATHLoad KEY=VALUE lines into process env before YAML expansion

Default human output:

text
suite: <name>
  OK   <path> [<name>] (<ms>)
  FAIL <path> [<name>] (<ms>): <error>
  ...
result: passed=<n> failed=<n> total=<n>

The bracketed [<name>] surfaces cases[i].name so two cases that share the same path: (e.g. one capture driving multiple override shapes) stay distinguishable. JSON output carries both path and displayName; JUnit XML puts the caseId in name and the path in classname.

Exit code: 0 if all cases passed, 20 if any failed.

suite validate

bash
softprobe suite validate suites/checkout.suite.yaml

Parses the YAML, resolves globs, and checks that every referenced hook exists. Does not run any case.

suite diff

bash
softprobe suite diff \
  --baseline cases/checkout/baseline/*.case.json \
  --current cases/checkout/current/*.case.json

Compares two sets of captures for drift. Useful when regenerating cases.


softprobe inspect

Read-only inspection commands.

inspect case

bash
softprobe inspect case cases/checkout.case.json

Prints a table of spans: direction, method, URL, status, body size. Add --json for machine use.

inspect session

bash
softprobe inspect session --runtime-url $SOFTPROBE_RUNTIME_URL --session $SOFTPROBE_SESSION_ID
softprobe inspect session --session $SOFTPROBE_SESSION_ID --json

Dumps the current session's policy, rules, loaded case summary, and statistics. Uses GET /v1/sessions/{id}/state on the runtime when available; older runtimes fall back to stats-only.


softprobe validate

Schema validation for any of the supported artifact types.

bash
softprobe validate case cases/checkout.case.json
softprobe validate rules rules/stripe.yaml
softprobe validate suite suites/checkout.suite.yaml

Exit code: 0 on valid, 5 on invalid.


softprobe generate

Code generation. The generator is the default happy path for Jest; it compiles a case file into an importable session helper so tests never call the runtime's JSON API directly.

generate jest-session

bash
softprobe generate jest-session \
  --case cases/checkout.case.json \
  --out test/generated/checkout.replay.session.ts

Emits a TypeScript module that creates a replay session, loads the case, and registers one findInCase + mockOutbound pair per unique outbound hop — using only @softprobe/softprobe-js. No hand-rolled fetch is emitted. See the generate-jest-session guide for the full workflow and design doc §3.2 for the rationale.

Flags:

FlagRequiredPurpose
--case PATHyesInput case file (*.case.json)
--out PATHyesOutput TypeScript file; convention: test/generated/<scenario>.replay.session.ts
--framework jestnoReserved for future frameworks (Vitest, Mocha); jest is the default and only value in v0.5

Output conventions:

  • Import specifier is hard-coded to @softprobe/softprobe-js (the canonical TS SDK package).
  • Case-file path is stored relative to the generated file via path.dirname(__filename), so moving the test directory doesn't break the import.
  • Rules are deduplicated by (direction, method, path) and sorted lexicographically for stable diffs.

Exit codes:

CodeMeaning
0Module written successfully
2Missing or malformed flags
5Case file failed schema validation

Regenerate after every capture refresh. See generate-jest-session → regeneration workflow.

generate test

bash
softprobe generate test \
  --case cases/checkout.case.json \
  --framework vitest \
  --out test/checkout.replay.test.ts

Emits a full test file (not just a session helper) for the chosen framework. Currently supports:

--frameworkStatus
jestbeta
vitestpreview
pytestpreview
junitalpha

For stable codegen, prefer generate jest-session and write the describe / it wrapper yourself.


softprobe export

export otlp

bash
softprobe export otlp \
  --case 'cases/**/*.case.json' \
  --endpoint http://otel-collector:4318/v1/traces

Streams case file traces to an OTLP/HTTP JSON endpoint (for example a collector). This is separate from live proxy capture, which posts to sp_backend_url on the Softprobe runtime; see Proxy integration posture.


softprobe scrub

Redact sensitive fields from an on-disk case file in place (optional --rules file; otherwise conservative defaults). Intended for post-capture review workflows.

bash
softprobe scrub cases/checkout.case.json
softprobe scrub 'cases/**/*.case.json' --rules redactions.yaml

Environment variables

VariableDefaultPurpose
SOFTPROBE_RUNTIME_URLhttps://runtime.softprobe.devDefault for --runtime-url
SOFTPROBE_API_TOKENBearer token for Authorization: Bearer <token> (hosted runtime or token-protected OSS runtime)
SOFTPROBE_SESSION_IDRead by --session flags if set
SOFTPROBE_CAPTURE_CASE_PATHe2e/captured.case.jsonOutput path for captured case files; supports {sessionId}, {ts}, {mode} placeholders and file:// URIs
SOFTPROBE_LOG_LEVELinfoRuntime log level: debug, info, warn, error
NO_COLORoffDisable ANSI color

Session-creating commands (session start, capture run) can export SOFTPROBE_SESSION_ID via --shell so subsequent commands don't need --session.


--json field stability

CI pipelines and AI agents depend on the JSON shape being stable. The following fields are considered stable — renaming, removing, or changing their type requires a major-version bump of the CLI. New fields may be added at any time.

Common envelope

Every --json response carries these fields (at the top level or nested under data depending on the command). softprobe completion does not use this envelope — it prints a shell script only.

FieldTypePresent onPurpose
status"ok" | "fail" | "drift"structured --json commandsOutcome marker
exitCodeintegerstructured --json commandsMirrors the process exit code
errorobject | nullon failure{ "code": "…", "message": "…" }

Per-command stable fields

CommandStable fields
doctor --jsoncliVersion, runtimeVersion, specVersion, schemaVersion, checks[] (each: name, status, details?)
session start --jsonsessionId, sessionRevision, mode, specVersion, schemaVersion
session load-case --jsonsessionId, sessionRevision, caseId, traceCount
session rules apply --jsonsessionId, sessionRevision, ruleCount
session policy set --jsonsessionId, sessionRevision
session close --jsonsessionId, stats (containing extractedSpans, injectedSpans, strictMisses), capturePath?
session stats --jsonsessionId, sessionRevision, stats.*
inspect case --jsoncaseId, version, traceCount, spanSummary[] (each: direction, method, host, path, status)
capture run --jsonsessionId, exitCode (of wrapped command), stats, capturePath
replay run --jsonsessionId, exitCode, stats
suite run --jsonsuite, total, passed, failed, cases[] (each: caseId, status, durationMs, error?)
suite validate --jsonsuite, errors[]
suite diff --jsonadded[], removed[] (outbound span signature strings)
generate jest-session --jsonoutputPath, rulesEmitted, caseId
generate test --jsonoutputPath, framework, rulesEmitted, caseId
validate case --jsonpath, valid, errors[]
validate rules --jsonpath, valid, errors[]
validate suite --jsonpath, valid, errors[]
inspect session --jsonsessionId, sessionRevision, mode, policy?, rules?, caseSummary, stats
export otlp --jsonsent, failed
scrub --jsonfiles[] (each: path, replaced, error?, updatedAtMs?)

Stability contract

  • Stable fields are guaranteed present when the command succeeds. If a field is optional, its presence depends on context (e.g. capturePath only on capture-mode sessions).
  • Breaking changes require a CLI major-version bump (softprobe --version tracks both CLI and specVersion).
  • The specVersion field in doctor and session start output lets you detect drift before parsing other fields — if specVersion doesn't match your agent's expectations, abort.

The full schemas are published in spec/schemas/cli-*.response.schema.json.


Shell integration

Bash / zsh completion

bash
softprobe completion bash > /usr/local/etc/bash_completion.d/softprobe
softprobe completion zsh > "${fpath[1]}/_softprobe"

Fish

bash
softprobe completion fish > ~/.config/fish/completions/softprobe.fish

Session-in-subshell pattern

bash
(
  eval "$(softprobe session start --mode capture --shell)"
  run-the-traffic
  softprobe session close --session "$SOFTPROBE_SESSION_ID"
)

The subshell scope prevents the variable from leaking into your regular shell session.


Versioning

The CLI follows semver. Breaking changes to commands or flags require a major version bump. The doctor command warns when the CLI and runtime are more than one minor version apart.

bash
softprobe --version
# softprobe v0.5.0 (spec http-control-api@v1)

See also