jev-kit
v0.1.30
Published
An agent-first CLI for TypeSafe Jev decisions.
Maintainers
Readme
jev-kit
Agent-first CLI for TypeSafe Jev — for Cursor, Claude Code, Codex, Pi, and subagents.
Pipe logs, diffs, or files in. Ask Noul, Choice, or Score questions. Get official JSON on stdout. The agent (or jq) decides what to do next.
COMMAND 2>&1 ──► jev-kit ──► TypeSafe Jev ──► JSON ──► agent / jqYes: pipes · mixed questions in one call · probabilities preserved · System One via @typesafe-ai/sdk
No: chat model · review/validate commands · running Choice labels as shell · replacing tests or lint
Quickstart
Node 22+ · a TypeSafe API key from console.typesafe.ai/keys
npm install -g jev-kitSet your API key. Use either option:
Option 1: save the key to ~/.jev-kit/config.json. The command checks the key, then saves it.
jev-kit set-keyOption 2: set the key in your terminal. This takes precedence over a saved key.
export TYPESAFE_API_KEY="your-key"Check the setup. This prints the version, Node, key source, model, and API URL, then checks the key with TypeSafe:
jev-kit doctorThen:
jev-kit --help
jev-kit --versionHow an agent uses it
- Run a normal shell command (test, diff,
catfile). - Pipe output into
jev-kitwith--noul,--choice, or--scoreflags. - Read stdout JSON; use stderr only if something failed locally.
- Route with your skill rules or
jq— neverevalan answer label.
git diff 2>&1 |
jev-kit \
--noul unrelated_changes \
"Does this diff contain unrelated changes?" \
--choice risk \
"What is the highest review risk?" \
--option behavior "Behavior may be wrong." \
--option security "Auth, secrets, or trust boundaries changed." \
--option compatibility "Public or persisted interface changed." \
--option low "No material risk visible."git diff 2>&1 |
jev-kit ... |
jq '.answers.risk.choice, .answers.unrelated_changes.noul'Use jev-kit for bounded judgments over shell evidence. Read jev-kit --help first. Do not pass a model. The user sets JEV_KIT_MODEL; otherwise the model is jev-latest. Tests, lint, thresholds, and edits stay outside Jev. One narrow question per name; batch over the same state in one call. Preserve JSON and probabilities; use jq. Never execute Choice labels as shell. Failed tests/lint always mean revise.Mental model
STATE + QUESTIONS → RESPONSE| Part | Meaning |
| --- | --- |
| State | Evidence Jev inspects (pipe, --state, file, JSON, attachments) |
| Question | Named Noul, Choice, or Score |
| Response | answers.NAME in official JSON |
Model: JEV_KIT_MODEL (non-empty after trim) → jev-latest
Locked CLI
API schema · SDK · Noul · Choice · Score
TypeSafe does not publish a jev-kit CLI. This is the public interface:
COMMAND 2>&1 | jev-kit QUESTION...
jev-kit [STATE] QUESTION...
STATE:
--state TEXT
--state-file PATH
--state-json JSON
--attach NAME PATH Repeatable. PATH is a file, or - for the pipe (once)
QUESTION:
--noul NAME INSTRUCTIONS
[--true-when TEXT]
[--false-when TEXT]
--choice NAME INSTRUCTIONS
--option NAME DESCRIPTION Repeatable
--score NAME INSTRUCTIONS
--level DESCRIPTION Repeatable, orderedVocabulary
| Term | Meaning |
| --- | --- |
| State | Complete evidence every question evaluates |
| State source | Exactly one of: pipe, --state, --state-file, --state-json |
| Attachment | One exact text file, or the pipe with -, under attachments.NAME (with --state only) |
| Question name | Key under answers in the response |
| Policy | Caller-owned thresholds and routing |
Grammar rules
- Put state and attachments before the first primitive.
- Each new
--noul,--choice, or--scorestarts the next question. - Child flags belong only to their active primitive.
- Multi-word values are one quoted shell argument.
- Unique question names; Choice/Score need at least two criteria.
- All questions share one state and one SDK request.
- stdout is success JSON only; help and errors use stderr.
- Empty piped, literal, or file state fails before any API call.
- The pipe is read only when no state flag is given, or by one
--attach NAME -.
Command patterns
| Goal | Pattern |
| --- | --- |
| Judge command output | COMMAND 2>&1 \| jev-kit QUESTIONS... |
| Judge short text | jev-kit --state "..." QUESTIONS... |
| Judge one text file | jev-kit --state-file PATH QUESTIONS... |
| Judge structured data | jev-kit --state-json '{...}' QUESTIONS... |
| Compare exact files | jev-kit --state "task" --attach NAME PATH... QUESTIONS... |
| Pin a model | Set JEV_KIT_MODEL; unset uses jev-latest |
| Ask several questions | Several primitive blocks in one invocation |
| Read one answer | jq '.answers.NAME' on stdout |
flowchart TB
G["Global flags"] --> Q1["Question 1"]
Q1 --> Q2["Question 2"]
Q2 --> QN["Question N"]
QN --> R["One SDK request"]
R --> O["One JSON response"]State examples
Piped state
jev-kit follows the Unix rule of grep, cat, and diff: an explicit input wins, and - means the pipe.
| Command | State sent |
| --- | --- |
| npm test 2>&1 \| jev-kit --noul … | "<npm test output>" |
| npm test 2>&1 \| jev-kit --state "Task" --noul … | "Task" (pipe ignored) |
| npm test 2>&1 \| jev-kit --state "Task" --attach tests - --noul … | {"task":"Task","attachments":{"tests":"<npm test output>"}} |
| rg -l expiry src \| while read -r f; do jev-kit --state "Task" --attach file "$f" --noul …; done | {"task":"Task","attachments":{"file":"<text of $f>"}} per file |
npm test 2>&1 | jev-kit --state "Task: fix session expiry handling." --attach tests - --noul failed 'Does `attachments.tests` show a failing test?'< file and heredocs act like a pipe. With a state flag and no -, jev-kit never reads stdin, so it never waits on an open pipe and never consumes a loop's input. A file literally named - is ./-.
Literal state
jev-kit \
--state "The customer was charged twice and requests a refund." \
--noul refund_requested \
"Does the customer request a refund?"File state
jev-kit \
--state-file bug-report.txt \
--score bug_severity \
"How severe is the reported issue?" \
--level "Cosmetic; no impact to functionality" \
--level "Broken or degraded feature, but workaround exists" \
--level "Blocking issue; no workaround exists"Example file contents:
The export button crashes the settings page in Safari. It works in Chrome, but a few of our customers only use Safari.JSON state
jev-kit \
--state-json '{
"ticket": {
"subject": "Duplicate charge",
"messages": [
{"from": "customer", "text": "I was charged twice for order A-104. Please refund the duplicate."},
{"from": "support", "text": "We are checking the charges."}
]
},
"order": {
"id": "A-104",
"charges": [
{"amount_usd": 49, "status": "captured"},
{"amount_usd": 49, "status": "captured"}
]
},
"refund_policy": "Duplicate charges are eligible for a refund."
}' \
--noul refund_requested \
'Does `ticket.messages[0].text` request a refund?' \
--noul policy_supports_refund \
'Does `refund_policy` support the refund requested in `ticket.messages[0].text`, given `order.charges`?'Attachments
--attach loads exact UTF-8 text files into object state. See State and citation check.
jev-kit \
--state "Task: fix refresh-token rotation after session expiry." \
--attach file src/auth/refresh.ts \
--noul relevant 'Is `attachments.file` materially relevant to `task`?'Assembled state shape:
{
"task": "Task: fix refresh-token rotation after session expiry.",
"attachments": {
"file": "…file contents…"
}
}To rank several files, make one call per file and sort in the shell:
for f in src/auth/session.ts src/auth/refresh.ts src/http/cookies.ts tests/auth/refresh.test.ts; do
jev-kit \
--state "Task: fix refresh-token rotation after session expiry." \
--attach file "$f" \
--noul relevant 'Is `attachments.file` materially relevant to `task`?' |
jq -r --arg f "$f" '"\(.answers.relevant.noul)\t\($f)"' &
done | sort -rnTip: One file per call keeps each request small and free of other files. Unrelated text in the state lowers accuracy (Jev 1.13 jaggedness), and one request holds at most 32k tokens of state (Models). This follows the official one-question-per-candidate pattern in Re-ranking. Attach several files to one call only when a question must compare them, such as a diff and its test output.
Folder, glob, and path-regex collection are deferred; see architect.md.
Question examples
Noul
Probability of yes, 0–1 at answers.NAME.noul. Optional --true-when / --false-when.
printf '%s\n' "The customer was charged twice and wants their money back." |
jev-kit \
--noul refund_requested \
"Does the customer request a refund?" \
--true-when "The customer asks for money or credit back." \
--false-when "The customer does not request a refund or credit."Choice
jev-kit \
--state "The checkout test hangs in CI. Diagnose it; do not change code." \
--choice skill \
"Which skill best matches the request?" \
--option diagnosing_bugs "Investigate without automatically implementing a fix." \
--option tdd "Implement behavior test-first." \
--option code_review "Review an existing change." \
--option none "No listed skill fits."Returns choice, full probabilities, and confidence.
Score
jev-kit \
--state-file compiler-output.txt \
--score actionability \
"How actionable is this compiler output?" \
--level "No useful diagnostic evidence." \
--level "Names an affected area." \
--level "Shows a useful location or symptom." \
--level "Directly identifies the likely cause."Returns weighted score, per-level probabilities, legend, and confidence.
Mixed primitives
jev-kit \
--state-file resume.txt \
--score technical_depth \
"How deep is the demonstrated technical experience?" \
--level "Basic familiarity." \
--level "Independent implementation." \
--level "Deep production ownership." \
--noul mentorship \
"Does the candidate demonstrate mentoring experience?" \
--choice primary_profile \
"Which profile is the strongest match?" \
--option frontend "Primarily frontend engineering." \
--option backend "Primarily backend engineering." \
--option full_stack "Substantial frontend and backend ownership." \
--option other "None is a strong match."Response
jev-kit prints the official SDK result without a workflow wrapper:
{
"model": "jev-1.13.0",
"answers": {
"refund_requested": {
"type": "noul",
"noul": 0.99
},
"department": {
"type": "choice",
"choice": "billing",
"probabilities": {
"billing": 0.96,
"technical": 0.03,
"other": 0.01
},
"confidence": 0.94
},
"frustration": {
"type": "score",
"score": 1.6,
"legend": {
"0": "Calm",
"1": "Frustrated",
"2": "Very angry"
},
"probabilities": {
"0": 0.05,
"1": 0.3,
"2": 0.65
},
"confidence": 0.78
}
},
"usage": {
"input_tokens": 312,
"output_tokens": 48
}
}| Path | Meaning |
| --- | --- |
| .answers.NAME | Typed answer for question NAME |
| .answers.NAME.noul | Probability of yes |
| .answers.NAME.choice | Highest-probability Choice label |
| .answers.NAME.probabilities | Full Choice or Score distribution |
| .answers.NAME.score | Probability-weighted Score |
| .answers.NAME.confidence | Choice/Score certainty from distribution |
Agent quality gates
LLM guardrails · Composite scoring
Final-answer grounding: exact user request + draft answer + inspected evidence → send, revise, inspect more, abstain, or escalate.
Code-change handoff: deterministic checks first, then request + diff + check output + changed source. Failed checks always mean revise.
flowchart TB
W["Agent work"] --> D["Deterministic checks"]
D -->|fail| R["Revise"]
D -->|pass| C["Code semantic gate"]
C -->|pass| F["Final answer grounding"]
C -->|revise| R
C -->|escalate| H["Human review"]
F -->|send| U["User"]
F -->|revise| R
F -->|inspect more| W
F -->|escalate| HConfiguration
| Setting | Source |
| --- | --- |
| API key | TYPESAFE_API_KEY if set, otherwise the key saved by jev-kit set-key. There is no key flag. |
| Saved key | $XDG_CONFIG_HOME/jev-kit/config.json when set, otherwise ~/.jev-kit/config.json, mode 0600 |
| Model | JEV_KIT_MODEL when non-empty after trim, otherwise jev-latest |
There is no model flag. Pin a version by setting JEV_KIT_MODEL=jev-1.13.0. TYPESAFE_DEFAULT_MODEL is not read.
Logs
One JSON line per run in ~/.jev-kit/logs/. See docs/LOGGING.md.
CI
Pushes and pull requests to main and develop run GitHub Actions on Node 22 and 24:
npm ci → lint → typecheck → npm run test:coverage → npm pack → install the tarball → jev-kit --version, --helpnpm run test:coverage compiles with tsc, then runs node:test against dist/ under c8. Those tests do not use TYPESAFE_API_KEY and do not call the TypeSafe API. They also fail when --help names a flag outside the locked interface, or README.md, architect.md stops naming one. On a pull request, CI warns without failing when src/ changes and test/ does not. Publish stays manual — see RELEASE.md.
Dependabot opens weekly PRs against develop for outdated npm and GitHub Actions dependencies; each still goes through the CI checks above before merge.
Errors
No success JSON on stdout; one diagnostic on stderr; non-zero exit; no API call when local validation fails.
jev-kit: E_INPUT: attachment "tests" is empty| Exit | Code | Meaning |
| --- | --- | --- |
| 0 | — | Success JSON on stdout |
| 2 | E_USAGE | Invalid flags or question grammar |
| 3 | E_INPUT | Empty, missing, binary, or oversized state or attachment |
| 4 | E_CONFIG | No API key (run jev-kit set-key), set-key could not save a key, or TypeSafe rejected the key in set-key or doctor |
| 5 | E_API | TypeSafe API or network failure |
| 70 | E_INTERNAL | Unexpected failure |
See architect.md.
Report a bug
jev-kit doctorOpen an issue at github.com/nikheal25/jev-kit/issues. Paste the doctor output, the command you ran, its stderr, and its exit code. Report security problems through SECURITY.md instead.
