Skip to content
MrJev

jev-cli

Typed judgments from the command line, published to npm as jevctl.

View on GitHub →

Hands-on review

Thirteen Jev subcommands with CI-shaped exit codes, real release hygiene and the best key handling here. The flag gaps we found shipped fixed in 0.2.3.

Good for

  • Exit codes 0/1/2 that actually mean something in a CI gate
  • Keychain first, `0600` file second, and every echo truncated to the last four
  • Trusted Publishing with provenance, and tags, releases and npm versions all aligned

Watch out for

  • Four flag and fan-out gaps, all fixed in 0.2.3 — install at least that version
  • An `OPENROUTER_API_KEY` in your shell still reroutes you; it now says so on stderr
  • `--timeout` is per attempt, and the SDK retries twice underneath it

Tested Sep 21, 2026 at b4eae54 · re · node:24 in Docker, published package from npm; 188 tests run offline, the re-check with no network at all

How we reviewed this: we installed the published jevctl@0.2.2 in node:24, ran it against a fake key and a dead base URL, and read the source at b4eae54. After the fixes shipped we re-ran everything against jevctl@0.2.3, the last part in a container with no network at all. We made no Jev calls.

What it does

Thirteen subcommands that wrap a Jev decision in a shape a script can use: verify claims against evidence, screen and classify text, extract fields, rerank a shortlist, route a request, ask an arbitrary question. Each maps probabilities onto a verdict and an exit code, and the exit codes are the point — 0 clean, 1 needs review, 2 a hard fail. In a CI gate that is the whole interface.

The claim-verification question is a good one. src/core/verify.ts:57:

questions[`relation_${claim.id}`] = choice(
  `How does the evidence relate to claim \`${claim.id}\` (${claim.text})?`,
  {
    supports: "The evidence states the claim or directly implies that it is true",
    contradicts: "The evidence states the opposite of the claim or implies that it is false",
    says_nothing: "The evidence does not address what the claim asserts, either way",
  },
);

says_nothing as a first-class option is the difference between a useful fact-checker and one that forces everything into true or false.

What we found, and what 0.2.3 does now

Four things did not match the docs at 0.2.2. All four are fixed in 0.2.3, released on 20 September, from a pull request of ours that the maintainer invited, reviewed and merged the same day — so read what follows knowing we wrote the patch as well as the report. Each finding below is followed by what the published package does now, re-checked from npm rather than taken from the changelog.

-q is documented and doesn’t work. The root command defines -q, --quiet (src/cli.ts:103) and ask defines -q, --questions <ref> (src/commands/ask.ts:113). Commander scans the root option table first, so the short flag never reaches the subcommand:

$ jev ask "I was charged twice" -q @/tmp/q.json
error: too many arguments for 'ask'. Expected 1 argument but got 2

--questions works. It is a naming collision, not a deep bug, but --help advertised the broken form.

Now: the short form is -Q, and jev ask "I was charged twice" -Q @/tmp/q.json --dry-run prints the payload. A test in the suite fails if any subcommand ever declares a short flag a global option already uses.

--dry-run has exceptions. README.md:37 promises it shows “exactly what would be sent without calling the API”, and docs/config.md:75 says “no API call”. For the judging commands that holds — jev ask --dry-run printed the payload and sent nothing. jev models has no dry-run branch at all (src/commands/models.ts:8), so with the base URL pointed at a dead port:

$ jev models --dry-run
jev: Connection error: fetch failed

It tried. auth, config and update ignore the flag too.

Now: models --dry-run prints {"command":"models","provider":"typesafe","method":"GET","url":"…/v1/models"} and exits 0 without a request. auth, config and update never reach the API at all, which the docs now say instead of leaving it to be found.

An OpenRouter key in your shell reroutes your data. src/provider.ts:85:

case "auto":
  if (hasTypesafe) return "typesafe";
  if (hasOpenRouter) return "openrouter";
  if (hasCloudflare) return "cloudflare";

Preferring TypeSafe when both are present is the right call, and deliberate — it stops a stray key re-billing an existing setup. But if you have no TypeSafe key and an OPENROUTER_API_KEY left over from something else, your text went through OpenRouter without a word. docs/config.md:61 notes “Adds a hop”; the README’s front page does not.

Now: it says so. With only an OpenRouter key in the environment, in a container with no network:

$ jev screen "hello there"
jev: no TypeSafe key found, using openrouter: your state and questions pass through
openrouter.ai. Run `jev auth login` for TypeSafe direct, or pass -P openrouter to
choose it deliberately and silence this.

Choosing the provider yourself keeps it quiet — the point is the accident, not the choice. It goes to stderr, so piping --json is unaffected.

compact fans out without a limit. src/vendor/compaction/compact.ts:277:

const answered = await Promise.all(
  batches.map((batch) => askBatch(asker, state.state, batch)),
);

Every batch went at once, each re-sending the full fitted state. jev batch has a --concurrency setting capped at 64 (src/config.ts:61); compact had nothing. The SDK also retries twice by default and jev-cli never overrides it, so the worst case multiplies again.

Now: compact --concurrency <n> (config compact.concurrency, default 4, 1 to 64), with the limiter wrapped around the provider so the vendored compaction library stays byte-identical to upstream; the Claude Code plugin’s compaction hook takes the same bound. On a transcript that splits into twelve request batches, in flight at once: twelve before, two at --concurrency 2 after. The retry point is now documented next to --timeout, which is per attempt.

What it gets right

The key handling is the best in this batch, and it is worth being specific. Keychain or secret-tool first, a 0600 file at ~/.config/jev/credentials.json as the documented fallback, every echo truncated to the last four characters, and TYPESAFE_LOG_LEVEL=debug printing Bearer ***TAIL. We planted a canary key, ran a command with debug logging on, and grepped the whole tree for it: Authorization: 'Bearer ***cdef' in the log, and no copy of the key anywhere on disk.

What goes over the wire is state, questions, model and nothing else. No working directory, no git information, no environment dump, no telemetry ping — we grepped for outbound URLs and found three, all of them the real provider endpoints.

And the release hygiene is the kind most of this ecosystem skips: npm test gives 188 passed across 20 files, fully offline; CI runs a Node 20/22/24 matrix through typecheck, lint, build, tests and a smoke test of the built artifact; and publishing uses npm Trusted Publishing with --provenance, so there is no long-lived token to leak. Four tags, four GitHub releases, four npm versions, all aligned.

Verdict

MIT with a NOTICE that properly attributes its vendored code, 31 commits, and — unusually for this batch — an install command that works exactly as documented: npm i jevctl, binary jev. Its only open issue when we reviewed it was one of ours.

This is the one from that batch we would actually put in a pipeline, and the day since made the case better rather than worse: we reported four things, the maintainer asked us for a patch, reviewed ours and shipped 0.2.3 within hours. Install that version or later.

For the same job as a library rather than a CLI, see jev-repl.

See how it compares with other tools in Best Jev tools, tested hands-on.

Review updated Sep 21, 2026. Numbers quoted from the project are its author's own; we don't publish our own measurements of Jev.

More in Command-Line Tools

jevgrep (dzhng)

★ 3.1k▲ 239

dzhng/jevgrep

Finds code by what it does rather than what it matches: a CLI for coding agents that asks which files and which regions are relevant to a description. Unrelated to the JevGrep above, which has the same name.

TypeScriptReviewed

webctl

★ 163▲ 1

dorkitude/webctl

Search CLI for agents: results from up to three backends are scored by Jev against your query and an explicit --goal, and only the relevant ones reach the agent's context. Its benchmark excludes an arm it could not observe.

GoReviewed

sys1grep

★ 150

uehaj/sys1grep

Greps by meaning rather than pattern, scoring every line against a description and combining meanings with AND, OR and NOT; a single dependency-free Node file. Japanese and English. Formerly jev-semgrep.

JavaScriptReviewed

Get new Jev projects every week

New Jev releases, pricing changes, and the best new projects, once a week. No spam; unsubscribe anytime.

Powered by Buttondown. See our privacy policy.