Skip to content

jev CLI

Binary: jev (built at ./bin/jev). Source: internal/cli.

Synopsis

$ ./bin/jev --help
jev sends a state and a set of typed questions (noul, choice, score) to the TypeSafe System One API and prints the answers as JSON.

Usage:
  jev [command]

Available Commands:
  ask         Send a state and typed questions, print the answers as JSON
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  models      List the models available to the account as JSON
  review      Ask Jev whether your tests cover the behaviors your spec requires
  version     Print version information as JSON

Flags:
      --api-key string     TypeSafe API key (env TYPESAFE_API_KEY)
      --base-url string    API base URL (env TYPESAFE_BASE_URL)
  -h, --help               help for jev
      --log-level string   debug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
      --max-retries int    retries after the first attempt (default 2)
      --model string       model name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
      --no-trace           disable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
      --pretty             indent JSON output
      --timeout duration   per-attempt HTTP timeout (default 10s)
      --trace              force OpenTelemetry tracing on
  -v, --version            version for jev

Use "jev [command] --help" for more information about a command.

completion and help are provided by the underlying Cobra command framework and are not detailed further on this page.

-v, --version (global flag) versus jev version (subcommand)

The root command sets Version: version.Get().Version, which makes Cobra auto-provide a -v/--version flag on jev itself. That flag differs from the jev version subcommand:

jev --version / jev -vjev version
KindGlobal flag on the root commandSubcommand
OutputOne line: jev version <string>A JSON object with five fields: {"version":...,"commit":...,"modified":...,"source":...,"go":...}
Exit code00

The <string> is the Version field of version.Get()’s result, the same resolved value reported by the version field of the jev version subcommand (see below). Verified against the built binary:

$ ./bin/jev --version
jev version 0556925

Global flags

These are persistent flags (Cobra PersistentFlags, defined in internal/cli/root.go, NewRootCmd). They apply to every subcommand and appear under “Global Flags” in each subcommand’s own --help output. (-v/--version, above, is a separate, Cobra-provided flag on the root command only; it is not a persistent flag and does not appear on subcommands.)

FlagTypeFlag defaultEnv fallback--help description
--api-keystring""TYPESAFE_API_KEYTypeSafe API key (env TYPESAFE_API_KEY)
--base-urlstring""TYPESAFE_BASE_URLAPI base URL (env TYPESAFE_BASE_URL)
--modelstring""TYPESAFE_DEFAULT_MODELmodel name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
--timeoutduration0noneper-attempt HTTP timeout (default 10s)
--max-retriesint2noneretries after the first attempt (default 2)
--log-levelstring""TYPESAFE_LOG_LEVELdebug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
--traceboolfalsenoneforce OpenTelemetry tracing on
--no-traceboolfalsenonedisable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
--prettyboolfalsenoneindent JSON output
-h, --helpboolfalsenonehelp for jev

clientOptions (internal/cli/root.go) turns globals fields into typesafe.Option values. Whether a flag was explicitly passed is tracked with cmd.Flags().Changed("<name>"), not by any sentinel value. A flag not explicitly passed is omitted from the options, letting the typesafe package’s own default (env var, then package default; see the client options and environment reference) apply unmodified.

--max-retries

The flag’s Cobra default is 2 (pf.IntVar(&g.maxRetries, "max-retries", 2, "retries after the first attempt")). This is the same value the library’s own DefaultRetryPolicy() uses, and there is no sentinel value. Whether --max-retries was explicitly passed is tracked via cmd.Flags().Changed("max-retries"):

  • Not passed: clientOptions does not call typesafe.WithRetryPolicy at all. The client’s own configured policy (or typesafe.DefaultRetryPolicy()) applies as-is.
  • Passed (including --max-retries 2, matching the default): clientOptions calls p := typesafe.DefaultRetryPolicy(); p.MaxRetries = g.maxRetries; typesafe.WithRetryPolicy(p). Every other RetryPolicy field still comes from DefaultRetryPolicy().

--max-retries 2 and omitting the flag both end at MaxRetries: 2 with every other RetryPolicy field at its default; the difference is that the first calls WithRetryPolicy and the second does not call it at all. --max-retries 0 disables retries.

--timeout

The flag’s Go zero value is 0, which is also a meaningful value the flag can carry explicitly. Whether --timeout was explicitly passed is tracked via cmd.Flags().Changed("timeout"):

  • Not passed: clientOptions does not call typesafe.WithTimeout at all. typesafe.DefaultTimeout (10 * time.Second) applies at the library level, untouched.
  • Passed, at any value including 0: clientOptions calls typesafe.WithTimeout(g.timeout) regardless of the value. --timeout 0 explicitly disables the per-attempt timeout at the library level.

Negative --timeout or --max-retries

validateFlags (internal/cli/root.go) rejects an explicitly-passed negative value for either flag, before any client is built:

ConditionErrorExit codekind
changed("timeout") && g.timeout < 0--timeout must not be negative, got <value>1"usage"
changed("max-retries") && g.maxRetries < 0--max-retries must not be negative, got <value>1"usage"

Verified against the built binary:

$ ./bin/jev ask --timeout -1s --state x --noul a=b
{"error":{"kind":"usage","message":"--timeout must not be negative, got -1s"}}

jev ask

$ ./bin/jev ask --help
Send one System One request and print the response as JSON.

Two input modes:

  JSON   jev ask -f request.json        (or pipe the JSON to stdin)
         The document is the HTTP body shape:
         {"state": ..., "questions": {"id": {"type": "noul", ...}}, "model": "..."}

  Flags  jev ask --state "text" --noul billing="Is this about billing?" \
             --choice tone="What is the tone?:calm,angry" \
             --score urgency="How urgent?:low|medium|high"

Output: {"model": ..., "answers": {...}, "usage": {...}, "request_id": ...}

Usage:
  jev ask [flags]

Flags:
      --choice stringArray   key=instructions:label1,label2,... (repeatable; labels must not contain ':' or ',')
  -f, --file string          request JSON file; '-' or omitted reads stdin
  -h, --help                 help for ask
      --noul stringArray     key=instructions (repeatable)
      --raw                  print the server response body unchanged
      --score stringArray    key=instructions:level0|level1|... (repeatable; levels must not contain ':' or '|')
      --state string         state text, @path to read a file, or '-' for stdin

Global Flags:
      --api-key string     TypeSafe API key (env TYPESAFE_API_KEY)
      --base-url string    API base URL (env TYPESAFE_BASE_URL)
      --log-level string   debug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
      --max-retries int    retries after the first attempt (default 2)
      --model string       model name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
      --no-trace           disable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
      --pretty             indent JSON output
      --timeout duration   per-attempt HTTP timeout (default 10s)
      --trace              force OpenTelemetry tracing on

Flags

FlagTypeDefaultRepeatableDescription
-f, --filestring""NoRequest JSON file; - or omitted reads stdin.
--statestring""NoState text, @path to read a file, or - for stdin.
--noulstringArraynoneYeskey=instructions
--choicestringArraynoneYeskey=instructions:label1,label2,... (repeatable; labels must not contain : or ,)
--scorestringArraynoneYeskey=instructions:level0|level1|... (repeatable; levels must not contain : or |)
--rawboolfalseNoPrint the server response body unchanged.

Input size limit

maxRequestBytes (internal/cli/request.go) is 16 << 20 (16 MiB). Reading stops one byte past this cap, so an oversized input is detected rather than silently truncated. It applies everywhere jev ask reads bytes from stdin or a file: stdin in JSON mode, -f <path>, --state -, and --state @path. Exceeding it produces the error request exceeds 16 MiB (a *usageError: exit code 1, kind "usage").

Input modes

jev ask accepts exactly one of two mutually exclusive input modes, decided by buildRequest (internal/cli/ask.go): question flags (--state, --noul, --choice, --score; any one of them present triggers flag mode) versus JSON (-f/stdin). Combining -f with any question flag is an error:

--file cannot be combined with --state, --noul, --choice, or --score

JSON mode

jev ask -f request.json
jev ask < request.json

Used when -f is given, or when -f is empty/omitted and no question flags are set (in which case JSON is read from stdin). The document is the HTTP request body shape:

{
  "state": "...",
  "questions": { "<id>": { "type": "noul", "...": "..." } },
  "model": "..."
}

Parsing rules (parseRequestJSON, internal/cli/request.go):

  • state is required (error: invalid request: "state" is required).
  • questions is required (error: invalid request: "questions" is required); each entry must be a JSON object with a string type field (error otherwise: invalid request: questions.<id>: must be an object with a "type", or invalid request: questions.<id>.type: is required). type of "noul", "choice", or "score" is decoded, with a strict decoder (json.NewDecoder(...).DisallowUnknownFields()), into typesafe.Noul, typesafe.Choice, or typesafe.Score respectively. An unrecognized field inside that question object is rejected, naming the field: invalid request: questions.<id>: json: unknown field "<field>". Strictness is nested: an unrecognized field inside a nested object (for example a noul question’s criteria, which accepts only true and false) is rejected the same way. Any other type value is decoded into a typesafe.RawQuestion (passed through unmodeled with a plain json.Unmarshal, so it is not subject to the strict decoder; a forward-compatible question type accepts any fields).
  • model is optional and, if present, becomes the request’s model (see Model selection, below).
  • Any other top-level field is collected and sent via typesafe.WithExtraBody.
  • An empty document (after trimming whitespace) is an error: no request given: pass --file, pipe JSON to stdin, or use --state with --noul/--choice/--score.
  • Malformed JSON is reported as invalid request JSON: <json error> (top-level) or invalid request: <field>: <json error> (per-field).
  • Reading stdin, -f <path>, or --state @path refuses input over 16 MiB. See Input size limit above.

Verified against the built binary:

$ echo '{"state":"x","questions":{"a":{"type":"noul","instructions":"y","bogus":1}}}' | ./bin/jev ask
{"error":{"kind":"usage","message":"invalid request: questions.a: json: unknown field \"bogus\""}}

If jev ask is invoked with no -f/--file and no question flags, and stdin is an interactive terminal (stdinIsTerminal, checked via os.File.Stat()’s os.ModeCharDevice), it returns the no request given usage error immediately rather than blocking on input that will never arrive. This check only applies when --file is omitted entirely: an explicit -f - always reads stdin unconditionally, terminal or not. Piped or redirected stdin (with --file omitted) is unaffected by the check. The same no request given error is also returned when the input, once read, is present but blank after trimming whitespace.

Flag mode

jev ask --state "text" --noul billing="Is this about billing?" \
    --choice tone="What is the tone?:calm,angry" \
    --score urgency="How urgent?:low|medium|high"

Used when any of --state, --noul, --choice, --score is set (requestFromFlags, internal/cli/request.go, called from internal/cli/ask.go). Parsing rules:

  • --state is required whenever --noul, --choice, or --score is given (error: --state is required when using --noul, --choice, or --score).
  • --state is also required to come with at least one question flag: --state given with zero --noul/--choice/--score flags is an error: --state needs at least one --noul, --choice, or --score question.
  • --state value resolution: - reads state from stdin; a value starting with @ reads the remainder as a file path (both subject to the 16 MiB input limit); any other value is used literally as text.
  • Every --noul/--choice/--score value is first split on the first = into a key and a remainder (splitKV). A missing or leading = is an error: <flag> "<value>": expected key=instructions. Otherwise, the key must be non-blank after trimming whitespace, or it is an error: <flag> "<value>": question key must not be blank.
  • --noul key=instructions: the remainder after key= is used directly as Instructions. Produces typesafe.Noul{Instructions: instructions} with no criteria.
  • --choice key=instructions:label1,label2,... and --score key=instructions:level0|level1|...: the remainder after key= is split on its last : (strings.LastIndex, in splitInstrLabels) into instructions and a label/level list, which is then split on , (--choice) or | (--score). Splitting on the last : (not the first) allows instructions itself to contain colons, for example --choice tone="What time is it: morning or evening?:calm,angry" parses instructions as "What time is it: morning or evening?" and labels as calm,angry. A missing : is an error: <flag> "<value>": expected key=instructions:labels; an empty label/level (after trimming) is an error: <flag> "<value>": empty label.
  • There is no character-forbidding validation on a label or level: since the split point is always the last : in the whole value, a label or level occurring after that point must not itself contain a :, or that colon is mistaken for the split point (shifting where instructions ends). For example, --choice 'tone=What tone?:calm,an:gry' splits on the last : (the one inside an:gry), producing Instructions: "What tone?:calm,an" and a single label "gry". calm silently disappears into the instructions text rather than being rejected. Likewise a --choice label containing ,, or a --score level containing |, is not rejected. It is mechanically split into multiple labels/levels at that separator, the same as any other occurrence of it. None of these cases produce a validation error message; each is a parsing consequence of where the splits land, matching the --help text above (labels must not contain ':' or ',', levels must not contain ':' or '|').
  • --score additionally requires at least two levels (error: --score "<value>": needs at least two levels separated by |). Produces typesafe.Choice{Instructions: instructions, Criteria: {label: nil, ...}} for --choice, typesafe.Score{Instructions: instructions, Criteria: [level0, level1, ...]} for --score.
  • A question key repeated across --noul/--choice/--score is an error: duplicate question key "<key>".

Verified against the built binary:

$ ./bin/jev ask --state x --noul "  =instructions"
{"error":{"kind":"usage","message":"--noul \"  =instructions\": question key must not be blank"}}
$ ./bin/jev ask --state "just text"
{"error":{"kind":"usage","message":"--state needs at least one --noul, --choice, or --score question"}}

--raw

Without --raw, jev ask writes its own JSON encoding of the decoded *typesafe.SystemOneResponse via the shared writeJSON helper (honoring --pretty). With --raw, it writes res.Raw.Body (the server’s response bytes, unchanged), followed by a trailing newline; --pretty has no effect on --raw output.

Output shape (non---raw)

{
  "model": "...",
  "answers": { "<id>": { "type": "noul|choice|score", "...": "..." } },
  "usage": { "input_tokens": 0, "output_tokens": 0 },
  "request_id": "..."
}

This is the JSON encoding of typesafe.SystemOneResponse. Per-answer wire shapes (NoulAnswer, ChoiceAnswer, ScoreAnswer) are documented on the response types reference. request_id is omitted when empty.

Model selection

If the parsed request has a non-empty Model (from JSON mode’s "model" field; flag mode never sets it) and --model was not explicitly passed on the command line (cmd.Flags().Changed("model") is false), the request’s model is sent via typesafe.WithRequestModel, overriding the client’s configured model for that call only. If --model was passed explicitly, it wins (the client-level model set through clientOptions/typesafe.WithModel applies, and the request’s Model field is not separately forwarded).

jev models

$ ./bin/jev models --help
List the models available to the account as JSON

Usage:
  jev models [flags]

Flags:
  -h, --help   help for models

Global Flags:
      --api-key string     TypeSafe API key (env TYPESAFE_API_KEY)
      --base-url string    API base URL (env TYPESAFE_BASE_URL)
      --log-level string   debug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
      --max-retries int    retries after the first attempt (default 2)
      --model string       model name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
      --no-trace           disable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
      --pretty             indent JSON output
      --timeout duration   per-attempt HTTP timeout (default 10s)
      --trace              force OpenTelemetry tracing on

No command-specific flags. Calls typesafe.Client.ListModels and prints the JSON encoding of the result:

{ "models": [ ... ], "request_id": "..." }

ListModelsResponse and ModelMetadata are documented on the response types reference.

jev review

$ ./bin/jev review --help
Review a codebase against its specification.

For each unit in the config file, jev review sends the named spec section and
the implementation and test files to Jev and asks whether the tests exercise
each listed behavior, whether the implementation contradicts the spec, how
thorough the tests are, and which area is weakest. It reports probabilities
and flags readings past the thresholds.

It does not find bugs, review style, check security, or verify that the code
is correct. Readings drift between runs on the same input.

Start with: jev review init

Usage:
  jev review [flags]
  jev review [command]

Available Commands:
  init        Write a starter jev-review.json

Flags:
  -h, --help                    help for review
      --json                    print the report JSON on stdout instead of the Markdown summary
      --max-contradict float    flag a unit whose contradiction probability is above this (default 0.4)
      --max-state-bytes int     byte cap for implementation and tests together (default 100000)
      --min-cover float         flag a behavior whose coverage probability is below this (default 0.6)
      --min-thorough float      flag a unit whose thoroughness score is below this (default 2)
      --only string             run a single unit by name
      --report string           where to write the JSON report (default "jev-review-report.json")
      --unit-timeout duration   time allowed for one unit's call (default 2m0s)
      --units string            config file; paths inside it resolve relative to the file (default "jev-review.json")

Global Flags:
      --api-key string     TypeSafe API key (env TYPESAFE_API_KEY)
      --base-url string    API base URL (env TYPESAFE_BASE_URL)
      --log-level string   debug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
      --max-retries int    retries after the first attempt (default 2)
      --model string       model name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
      --no-trace           disable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
      --pretty             indent JSON output
      --timeout duration   per-attempt HTTP timeout (default 10s)
      --trace              force OpenTelemetry tracing on

Use "jev review [command] --help" for more information about a command.

jev review and jev review init build the client from the same global flags as every other subcommand. jev review init writes a starter jev-review.json and refuses to overwrite an existing file. The config schema, the four questions sent to Jev per unit, the JSON report shape, the Markdown summary, default thresholds, and exit code 8 are documented in full on the jev review reference.

jev version

$ ./bin/jev version --help
Print version information as JSON

Usage:
  jev version [flags]

Flags:
  -h, --help   help for version

Global Flags:
      --api-key string     TypeSafe API key (env TYPESAFE_API_KEY)
      --base-url string    API base URL (env TYPESAFE_BASE_URL)
      --log-level string   debug|info|warning|error|off (env TYPESAFE_LOG_LEVEL)
      --max-retries int    retries after the first attempt (default 2)
      --model string       model name (env TYPESAFE_DEFAULT_MODEL; default jev-latest)
      --no-trace           disable tracing even when HONEYCOMB_API_KEY or OTEL_* is set
      --pretty             indent JSON output
      --timeout duration   per-attempt HTTP timeout (default 10s)
      --trace              force OpenTelemetry tracing on

No command-specific flags. Makes no network call. Prints five fields:

{ "version": "...", "commit": "...", "modified": ..., "source": "...", "go": "..." }

internal/version.Get() resolves these values in order:

  1. If the ldflags-set Version variable is not "dev", it is used as-is, with the ldflags-set Commit. Source is "ldflags". Both make build and the release binaries GoReleaser builds produce this, since both inject Version and Commit via -ldflags.
  2. Otherwise, runtime/debug.ReadBuildInfo() is consulted. If the main module’s recorded Version is set and is not "(devel)", that value is used as Version. Source is "module". Commit is the first 7 characters of the build’s vcs.revision setting, or "none" if that setting is absent. A plain go build in a git clone produces this, as does go install .../jev@vX.Y.Z: Go 1.24 and newer stamp the main module’s version from the VCS commit automatically, even with no tag (producing a pseudo-version like v0.0.0-<timestamp>-<commit>), or from the exact tag when one is given to go install.
  3. Otherwise, if a vcs.revision build setting is present but the module version is empty or "(devel)", Version is "(devel)" and Commit is the short revision. Source is "vcs". This is the fallback for a build whose module version is "(devel)".
  4. Otherwise Version is "dev" and Commit is "none". Source is "unknown": the binary has no module or VCS information available to it at all.

Modified is true when the build’s vcs.modified setting is "true", meaning an uncommitted change was present in the tree at build time. It is only ever populated from that build setting, in cases 2 and 3 above. A "ldflags"-sourced build (case 1: make build, or a GoReleaser release binary) always reports modified: false, regardless of whether the tree was actually clean, because dirty-tree state is not threaded through ldflags.

go is the value of runtime.Version(). Because this is encoded from a Go map, encoding/json emits the keys in alphabetical order (commit, go, modified, source, version), independent of --pretty.

Verified against the built binary, from an ldflags build at commit 0556925 with a clean tree:

$ ./bin/jev version
{"commit":"0556925","go":"go1.27.1","modified":false,"source":"ldflags","version":"0556925"}
$ ./bin/jev --pretty version
{
  "commit": "0556925",
  "go": "go1.27.1",
  "modified": false,
  "source": "ldflags",
  "version": "0556925"
}

A plain go build outside any release, at the same commit and clean tree, reports the module source instead:

$ go build -o /tmp/jev-plain ./cmd/jev && /tmp/jev-plain version
{"commit":"0556925","go":"go1.27.1","modified":false,"source":"module","version":"v0.0.0-20260929193359-05569256a11c"}

Telemetry

Source: internal/cli/telemetry.go.

Enabling tracing

telemetryEnabled decides whether the OpenTelemetry SDK is started for the command:

ConditionResult
--no-trace passedTracing is disabled. This check runs first and short-circuits every other condition.
Otherwise, --trace passedTracing is enabled.
Otherwise, HONEYCOMB_API_KEY set (non-empty)Tracing is enabled.
Otherwise, OTEL_EXPORTER_OTLP_ENDPOINT set (non-empty)Tracing is enabled.
Otherwise, OTEL_CONFIG_FILE set (non-empty)Tracing is enabled.
None of the aboveTracing is disabled.

Configuration source

telemetryConfig chooses how the enabled configuration is built:

OTEL_CONFIG_FILEBehavior
Set (non-empty)The named file’s bytes are read, then ${VAR}-expanded against the environment via os.Expand, then parsed as OpenTelemetry YAML configuration via otelconf.ParseYAML. The parsed configuration is used directly. HONEYCOMB_API_KEY, OTEL_EXPORTER_OTLP_ENDPOINT, and OTEL_SERVICE_NAME are not consulted.
UnsetA configuration is built from HONEYCOMB_API_KEY, OTEL_EXPORTER_OTLP_ENDPOINT, and OTEL_SERVICE_NAME (below), via buildTelemetryConfig.

Built configuration (no OTEL_CONFIG_FILE)

Environment variableDefault when unsetEffect
HONEYCOMB_API_KEYnoneWhen non-empty, added as an x-honeycomb-team HTTP header on the OTLP exporter.
OTEL_EXPORTER_OTLP_ENDPOINThttps://api.honeycomb.ioA trailing / is trimmed; /v1/traces is appended if the (trimmed) value does not already end with it.
OTEL_SERVICE_NAMEjevSet as the service.name resource attribute.

The resulting configuration (buildTelemetryConfig) is FileFormat: "1.0" with a single tracer provider processor: a batch span processor exporting via OTLP/HTTP to the resolved endpoint.

Failure handling

A telemetry setup failure (reading or parsing OTEL_CONFIG_FILE, or SDK construction) is reported on stderr as:

jev: tracing disabled: <error>

and the command proceeds with tracing disabled (typesafe.Instrumentation is nil for that run). When OTEL_CONFIG_FILE fails to parse after ${VAR} expansion, every expanded environment value of 4 or more bytes is redacted out of <error> as [redacted], so a secret substituted into the file does not appear in this message. A shutdown failure (during the deferred SDK shutdown) is separately reported as:

jev: tracing shutdown: <error>

Exit codes

CodeConstantCondition
0ExitOKSuccess.
1ExitUsageBad flags, unreadable or invalid request JSON, missing API key, or an unrecognized error.
2ExitValidationRequest failed client-side validation.
3ExitAuth401 or 403.
4ExitRequestOther 4xx: 400, 404, 422.
5ExitRateLimit429 after retries.
6ExitServer5xx after retries, or an unreadable 2xx body.
7ExitConnectionConnection failure or timeout.
8ExitFlaggedjev review only: at least one unit is failing; see the report.
130ExitInterruptedThe context was cancelled, conventionally by SIGINT.

The full error-type-to-exit-code classification and the error JSON shape written to stdout on failure (including the "usage" versus "internal" split at code 1 and the "interrupted" case at code 130) are documented on the errors and exit codes reference.