jev
jev turns a judgment call into a typed answer with a calibrated probability. It reads a state (text or
JSON) and answers named questions of three kinds — noul (P(yes)), choice (one of N), score
(ordinal scale, at most 10 levels). It never generates text. A call costs about $0.00002, so 1,000
decisions run about 2 cents: use it wherever a script or an agent needs a quick, consistent verdict,
and keep LLM reasoning for what survives the filter.
Setup
- Run
jev --version. If the command is missing, the CLI ships with this skill atscripts/jev(Python 3.9+, standard library only). Link it withmkdir -p ~/.local/bin && ln -sf "<this skill's directory>/scripts/jev" ~/.local/bin/jev, or call it by its absolute path. - Keys:
TYPESAFE_API_KEY(default provider) orOPENROUTER_API_KEY, read from the environment, then$JEV_ENV_FILE, then<config dir>/.env. Provider:--provider→$JEV_PROVIDER→ the default pinned withjev provider default ID→ auto (TypeSafe if its key exists, else OpenRouter).jev provider list,jev auth statusandjev auth checkare read-only and never print a key — run them freely. On a missing key or a 401/403, ask the user to runjev auth set(plus--provider openrouterfor that key) in their own terminal: it needs a TTY and hidden input, so you cannot run it for them. Never ask for a key in chat, pass one as an argument, pipe one intoauth set --stdin, read or print a key file, or runjev auth removeunless the user asks.
For designing the questions themselves (choosing noul/choice/score, writing criteria,
confidence and calibration semantics), see TypeSafe's own docs:
primitives, confidence,
full index at llms.txt.
Choose the verb
| Need | Command | Plain stdout | Exit code |
|---|---|---|---|
| Yes/no | jev yes "QUESTION" | yes\t0.97 | 0 yes · 1 no · 2 error |
| One of N | jev pick "QUESTION" a="desc" b="desc" [--other] | a | 0 · 1 below --min-confidence · 2 error |
| Ordinal score | jev score "QUESTION" low mid high or --range 1-5 | 1.43\tmid | 0 · 2 error |
| Keep matching lines | jev filter "QUESTION" | the matching input lines | 0 some · 1 none · 2 error |
| Several questions at once | jev run SPEC | aligned table | 0 · 2 error |
| Raw API body | jev raw < body.json | response JSON | 0 · 2 error |
- Input comes from stdin,
-s "TEXT"or-s @file. JSON objects and arrays go out as structured state (--textturns that off). -lmakes one decision per input line: concurrent (-j 8), order preserved, streamed. For JSONL,--field KEYnames the field to judge.filteralways works per line.- Add
--jsonwhenever you parse results (JSONL with-l): each answer hastypeplusnoulandyes, orchoice, orscoreandlabel;choiceandscorealso carryprobabilitiesandconfidence. Plain output suits humans and shell tests. --verboseprints provider, model, latency, tokens and cost to stderr (≈$when the cost is an estimate).- Speed: a single call opens a new connection, about 0.5–1 s in total.
-lkeeps each worker's connection open, so later calls take about 0.3–0.4 s. For more than a handful of items use-lorfilterinstead of looping single calls; raise-jfor large batches (≤16). yesandfiltertake-t P(threshold, default 0.5) and--true/--falseto spell out what yes and no mean.pick -pandscore -pprint the whole distribution.
jev yes "用户在要求退款吗?" -s "Zipic 一打开就闪退,钱退我!" # yes 0.97
jev pick "该交给谁处理" code="写代码或改项目" research="需要联网查资料" --other -s "$request" --json
jev score "这条评价给几星" --range 1-5 -s @review.txt # 3.30 3
tail -f app.log | jev filter "日志表示用户可见的故障" --false "调试信息、正常请求"
jev run feedback -l --field text --json < tickets.jsonl > labeled.jsonl
Write questions Jev can answer
Jev reads conditions literally and does not infer intent. In testing, filter "包含具体可行动的信息" passed an ad ("加微信领取免费 AI 课程,限时三天") at about 0.9, because an ad is literally actionable. filter "消息给出了具体的技术、产品或行业信息" --false "闲聊、问候、广告、引流、卖课" excluded it.
- State observable conditions, not goals. Define both sides with
--true/--false. - Describe every option (
name="description"); add--otherwhen the list may not cover every input. Order score labels low to high, 2–10 of them. - Put questions about the same input into one spec: one call answers all of them for that input (
-lstill makes one call per line). - Trim the state to what the question needs, well below the context limit — irrelevant text dilutes the signal; do math and date arithmetic in code and put the result into the state.
- Gate actions on certainty: act on high
confidenceor probability, ask the user or escalate on low ones (pick --min-confidence 0.7,yes -t 0.8). Jev only decides — narrow the set with it, then read, write or summarize the survivors yourself.
Specs
jev run lists the specs; jev run NAME or jev run path/to/spec.json runs one. Built-ins: mail (category, urgency, needs reply, promo), feedback (intent, sentiment, needs human, churn risk), signal (worth reading, topic, novelty for chat messages, tweets, RSS), commit (Conventional Commit type, secret leak, breaking change, risk for git diff --cached), route (handler, complexity, needs web, needs private data for a user request). Built-ins ship as JSON so every Python 3.9+ can load them. A spec's own model (if set) is normalized for whichever provider is active.
A spec mirrors the API body. Save new ones under <config dir>/specs/ (~/.config/jev/specs/ unless XDG_CONFIG_HOME is set), which survives reinstalling the skill. JSON works on every supported Python; TOML needs Python 3.11+ (tomllib) and errors clearly below that:
{
"description": "One line shown by `jev run`",
"threshold": 0.5,
"questions": {
"category": { "type": "choice", "instructions": "Which kind of request is this?",
"criteria": { "bug": "Crash or broken feature", "billing": "Payment or license problem", "other": "None of the above" } },
"urgency": { "type": "score", "instructions": "How soon must someone act?",
"criteria": ["can wait", "this week", "today", "now"] },
"needs_reply": { "type": "noul", "instructions": "The sender expects a personal reply",
"criteria": { "true": "Asks a question or waits for a decision", "false": "Notice or receipt only" } }
}
}
criteria differs by type: choice and noul take a {name: description} map (noul needs both true and false), score takes an ordered array, low to high. A question may hold only type, instructions and criteria; anything else is rejected before a request is sent.
Limits
- Decisions only: no text, no field extraction.
scoreallows at most 10 levels. - TypeSafe native: 64K token context per request, 32K for state + the longest question, about 1,200 requests/min. OpenRouter:
https://openrouter.ai/api/alpha/decisionsis an alpha endpoint and may move — override withJEV_BASE_URLif it does. - The CLI retries 429/5xx with backoff on both providers; errors go to stderr as
jev: API 错误 (HTTP n): …, exit code 2.
Full option reference, provider table, exit codes, error shapes and recipes: references/cli.md.