backant-io/jevelry

Use Jev everywhere to make & track decisions

TypeScript

1

112 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: Open Code for Jev

1

Sep 23, 2026

README

jevelry

Overview · Install · Usage · Commands · Keys · Jevels · Configuration · Jev docs

npm Node 22 Tests MIT

Use Jev everywhere to make & track decisions.

jevelry tui: the start screen, a live ask of ticket-triage, the dashboard and Review

[!WARNING] Early development: jevelry is young and changes fast. Commands, the jevel format and the answer document may change between minor versions until 1.0, so pin the version you depend on.

jevelry lets your code and your coding agent ask Jev, the decision model from TypeSafe, a small typed question and get back a decision it can act on, and it keeps every decision so you can see how often Jev was right.

Overview

Your code and your coding agent make the same small calls every day: is this ticket urgent, which team should get it, is this bug a duplicate, did this test fail because of the code or because of the machine it ran on. Today each of those is usually a prompt that returns text you parse, and you don't learn how sure the model was.

With jevelry you ask each of those questions to Jev with one command. A question lives in a small file called a jevel, and seventeen ready-made jevels ship with the package. Jev answers with how sure it is, and the jevel turns that into one of three decisions:

DecisionMeaningWhat your code does
actJev is sureuses the answer
markJev is fairly sureuses the answer and flags it for a person
fall_backJev is unsuredoes what it did before jevelry

One ticket triage is about 1,280 input tokens at $0.042 per million, roughly $0.00005, so a dollar covers around 18,000 of them. jevelry itself is free and MIT, and TypeSafe charges only for input tokens.

Where it helps

In your app. A support ticket comes in and needs a team and a priority. On act your code routes it to the team Jev picked, on mark it routes it and flags it for the queue owner, and on fall_back it leaves the ticket for a person, the same way it works today:

npx jevelry ask ticket-triage --state '{"ticket": {"subject": "Charged twice", "message": "I was billed twice this month, please refund one today."}}'

In your coding agent. A test fails with connect EPERM because the sandbox blocked a network call, and left alone your agent will probably start rewriting code that was fine. With the jevelry skill installed it asks first, and on environment it reports the sandbox problem and leaves your code alone:

npx jevelry ask review-comment-kind --state '{"comment": {"author": "ci-bot", "text": "FAIL tests/api.test.ts\nTypeError: fetch failed\n  cause: Error: connect EPERM 104.18.2.1:443"}}'

Features

  • Interactive TUI: Built with Ink for a full-screen terminal experience
  • Ready-made jevels: Seventeen decisions for tickets, messages, issues, pull requests, logs, alerts and failing tests
  • Typed decisions in code: Load a jevel once and get a typed decision wherever your code asks
  • Commands Jev runs: Jev picks the command to run and asks you first when it is only fairly sure
  • Review and tuning: Mark whether Jev was right and move thresholds based on real outcomes
  • Persistent log: Every decision in one local log, with a report per question
  • Tested jevels: Every shipped jevel is checked against the real API on its own cases
  • Agent skill: One install for Claude Code, Codex, Cursor, opencode and pi
  • Typed answers: Your editor knows the options of every jevel

Installation

jevelry runs on Node 22 or newer, so check node --version first.

Using npm, on your machine

npm install -g jevelry
jevelry install

jevelry install puts the skill into every coding agent it finds and asks for your TypeSafe key. You can skip the key with Enter and add it later. Get a key at https://docs.typesafe.ai.

As a library in your project

npm install jevelry

In your coding agent

Run this inside Claude Code, Codex, pi, opencode or Cursor, or in your terminal:

npx jevelry install

If you prefer the skills installer, npx skills add backant-io/jevelry --skill jevelry does the same for the agents it supports.

From source

git clone https://github.com/backant-io/jevelry.git
cd jevelry
npm install
npm run build
node bin/jevelry.js --help

Usage

Start

jevelry tui

It opens on the start screen with what you can do from there: try a jevel, review the decisions Jev marked, open the dashboard or go through your history. Press d for the dashboard, which shows you how Jev decided today, what needs you and how each of your jevels is doing. When your log is still empty, the first row on the start screen offers you a sample ticket: press enter to open it in Try and enter again to ask Jev about it live, and you see your first answer in a few seconds. If you have no key yet, Try tells you how to add one.

Ask from the command line

export TYPESAFE_API_KEY=...
npx jevelry ask ticket-triage --state '{"ticket": {"subject": "Charged twice", "message": "I was billed twice this month, please refund one today."}}'

You get one JSON document back with an answer per question, and the part your code reads is the decision:

"team":        { "choice": "billing",  "confidence": 1.0,  "decision": "act"  }
"urgent":      { "noul": 0.98,         "yes": true,        "decision": "act"  }
"frustration": { "score": 0.24,        "confidence": 0.64, "decision": "mark" }

The whole document and a jq one-liner are in docs/examples.md.

In your program

When the decision happens inside your own code, you load the jevel once when your program starts and call decide at the point where your code decides today:

import { jevel } from "jevelry";

const triage = jevel("ticket-triage");

const d = await triage.decide({ ticket });
switch (d.team?.decision) {
  case "act": route(ticket, d.team.answer); break;
  case "mark": route(ticket, d.team.answer); flagForQueueOwner(ticket); break;
  case "fall_back": leaveInGeneralQueue(ticket); break;
}

decide asks Jev, writes the ask to your log and hands you every question with its decision and its answer. When Jev can't answer, because TypeSafe is busy or your network is down, every question comes back fall_back with the reason in d.error, so your code just keeps its old path. Run npx jevelry types --out src/jevels.d.ts once and d.team.answer is typed as "billing" | "technical" | "account" | "other".

Let Jev run the command

When the next step after a decision is always one of a few commands, you put those commands into the jevel and jevelry runs the one Jev picks. failing-test ships with the package and works this way:

npx jevelry run failing-test --dry-run --state '{"test": {"name": "checkout > pays with a saved card", "output": "TimeoutError: waiting for selector failed: timeout 5000ms exceeded", "history": "fails about once a week and passes on a rerun"}}'
DecisionWhat happens
actthe command runs
markjevelry asks you first, and --yes runs it straight away
fall_backthe jevel's fall_back command runs

--dry-run prints what would run. Your state goes to the command on stdin and in the file JEVELRY_STATE points at, so a ticket that says ; rm -rf ~ stays plain text. jevelry prints the path of the jevel on stderr before it runs anything, because a jevels/ folder in your project comes before the ones that ship.

Use it with your coding agent

You probably want your coding agent to write the jevel and wire it in, while you review the questions. Once the skill is installed, talk to your agent the way you would to a colleague who has read the guide:

Using the jevelry skill, write a jevel that decides which team a support ticket goes to
and whether it is urgent, run check on it, and wire the decisions into src/inbox.ts.

The skill keeps every jevel in ./jevels/ and leaves the questions and thresholds for you to read. The guide the agent reads is skills/jevelry/references/writing-jevels.md and works for people too.

Command-line reference

CommandWhat it does
jevelry ask [jevel]asks every question of a jevel (or of --questions) about a state and prints one JSON document
jevelry run <jevel>asks a jevel whose options name commands, then runs the command for Jev's decision
jevelry outcome <log_id> <question> <value>records what proved true: agree, disagree, an option, a level index, yes or no
jevelry reportagreement per jevel and question, from the log
jevelry tuithe full-screen view: the start screen, the dashboard, Review, Jevel, Try and History
jevelry listevery jevel jevelry can find, with its folder; the first folder wins
jevelry show <jevel>the resolved frontmatter as JSON, then the body, then example.json when the jevel has one
jevelry check <jevel>refuses a defective jevel with exit 2 and warns about questions Jev handles poorly
jevelry typeswrites a TypeScript declaration so jevel(name).decide() returns each jevel's own answer types
jevelry modelsthe model names your account may send, from GET /v1/models
jevelry installinstalls the skill into your coding agents, then stores a TypeSafe key
jevelry install-skillinstalls the skill into your coding agents, and asks for no key
jevelry help [command]the help of one command

-V, --version prints the version and -h, --help prints the help, on the program and on every command. A <source> is @file, - for stdin, or inline JSON.

ask

FlagWhat it does
--state <source>the state to ask about; required
--questions <source>the API's questions map, for a one-off ask with no jevel
--model <id>the model, over the jevel's pin and JEVELRY_MODEL
--jevels <dir>a jevels folder searched first; repeatable
--no-logkeeps this ask out of the log
--log-statewrites the state itself into the log line, next to its hash

run

FlagWhat it does
--state <source>the state to ask about; required
--yesruns a mark straight away
--dry-runprints what would run and runs nothing
--model <id>the model, over the jevel's pin and JEVELRY_MODEL
--jevels <dir>a jevels folder searched first; repeatable
--log-statewrites the state itself into the log line, next to its hash

outcome

FlagWhat it does
--note <text>a sentence kept with the outcome

report

FlagWhat it does
--jevel <name>only this jevel
--since <iso>only asks at or after this time
--jsonmachine-readable rows

tui

FlagWhat it does
--dashboardopens on the dashboard
--jevel <name>opens History filtered to this jevel
--since <iso>opens History filtered to asks since this time
--jevels <dir>a jevels folder searched first; repeatable

tui needs an interactive terminal and exits 1 in a pipe.

list, show, check

FlagWhat it does
--jevels <dir>a jevels folder searched first; repeatable

types

FlagWhat it does
--jevels <dir>a jevels folder searched first; repeatable
--out <file>writes the declaration to this file; stdout by default

install, install-skill

FlagWhat it does
--projectinstalls into this project (.claude/skills and .agents/skills)
--agent <names...>only these agents: claude-code, codex, cursor, opencode, pi, or agents with --project
--no-keyinstall only: installs the skill and asks for no key

Keyboard shortcuts

The footer shows the keys of the screen you are on, and ? lists them all.

Global

KeyAction
hthe start screen
dthe dashboard
vReview
yHistory
tpick a jevel to try
ctrl+pcommand palette: screens, theme, help, quit and every jevel
?help
qquit; asks first when a state you edited in Try would be lost

ctrl+p works on every screen, also while you type. The other global keys pause while a screen takes typed text.

Start screen

KeyAction
j k or move
enteropen the selected row
t v d yopen that row directly
t or enter on an empty logtry ticket-triage with a sample ticket

Dashboard

KeyAction
j k or move
enteropen the entry: the review queue, a jevel, a decision or the failed asks
enter on an empty logtry ticket-triage with a sample ticket
escthe start screen

Review

KeyAction
cJev is correct
wJev is wrong: a yes/no question records the other answer, a choice or score asks for the right one
sskip this decision
nwrite a note for the next c or w
mswitch between marked decisions and act decisions
j k or scroll what Jev saw
escthe start screen

c, w and s count once a card has been up for 450 ms, and a held key records once.

WhenKeyAction
picking the right answerj k or move
picking the right answerenterrecord it
picking the right answeresccancel
writing a noteenterkeep the note
writing a noteescdrop the note
all reviewedmswitch between marked and act decisions
all reviewedsshow the skipped ones again
all reviewedescthe start screen

Jevel

KeyAction
j k or move between questions
enterthe question's decisions, in History
Tset the proposed act threshold in the jevel file, when there is one
escthe start screen

Try

KeyAction
enterask Jev live
rask again
eedit the state
ppick another jevel
j k or scroll the answer
escthe start screen

While you edit the state, typing and pasting go into the text, the arrows, home and end move, tab adds two spaces, enter asks and esc stops editing.

History

KeyAction
j k or move
enteropen the decision, where c, w, n, j k and esc work as in Review
fcycle the decision filter
Jcycle the jevel filter
oonly decisions with no outcome yet
xonly failed asks
rthe report
escback

In the report, j k scroll, J cycles the jevel and esc goes back to the list.

Dialogs

DialogKeyAction
commands, pick a jevel, themetypingfilter the list
commands, pick a jevel, theme or ctrl+n ctrl+pmove
commands, pick a jevel, themeenterchoose
commands, pick a jevel, themeescclose
helpesc, ? or enterclose
set thresholdenterwrite the threshold, copying a shipped jevel into ./jevels first
set thresholdesccancel
set thresholdqquit
quitenter, q or yquit
quitesc or nstay

Jevels

A jevel is a folder with one JEVEL.md in it, the same way a skill for your coding agent is a folder with one SKILL.md. The frontmatter holds the questions, their options and the thresholds, and the body is what you and your agents read. Jev takes three kinds of question:

QuestionYou askYou get back
choicewhich of these optionsthe option, a probability per option, a confidence
scorehow much, on your own levelsa value between the levels, a probability per level, a confidence
noulis this trueone probability, 0 to 1

Seventeen ship with the package, each with an example.json to ask it with and a cases.json of realistic states with the answer a person would give. The table with their questions and state keys is in jevels/README.md.

JevelWhen to use it
alert-causean alert fired and you want a first hypothesis
change-riska pull request before merge
checklist-compliancea report that claims to follow a checklist
duplicate-issuea new issue against the open ones
escalation-routea request or incident in a shared inbox
failing-testa failing test, with jevelry run
issue-readinessan issue before it reaches the backlog
log-triagea burst of log lines
meeting-notesnotes after a meeting
message-triagean inbound message in mail or chat
notification-triagea batch of notifications for one person
pr-description-checka pull request description against its diff
reply-checka reply, to close the thread or keep it open
review-comment-kinda review comment or a failing test output
review-qualitya finished code review
task-difficultya task before you route it
ticket-triagea new support ticket

jevelry finds jevels in --jevels <dir>, then JEVELRY_JEVELS, then ./jevels, then $JEVELRY_HOME/jevels, then the ones that ship, and the first one with the name wins. To write your own, copy the nearest folder into ./jevels, change the name, rewrite the questions and run npx jevelry check <name> until it prints 0 warnings. check refuses what the API would refuse (more than 255 options, a threshold outside 0 to 1, a question with no instructions) and warns about a yes/no question whose "yes" means no, a double negative, options that carry different fields and a model that is not pinned. The whole method is in skills/jevelry/references/writing-jevels.md.

Then write a cases.json with at least three states from your own data: one clear one way, one clear the other way and one you would ask a colleague about. The clear cases should land on the right answer, and the unclear one should stay below act.

Decisions and the log

DecisionMeaningWhat your code usually does
actcertainty at or above the act thresholdacts on the answer
markcertainty at or above the mark thresholdacts and flags it for a person
fall_backbelow the mark threshold, or Jev could not answerwhat it did before jevelry

A yes/no question's certainty is how far its probability sits from 0.5, and a choice or score uses the confidence Jev reports. The thresholds live in the jevel, per question, with act: 0.9, mark: 0.7 when the jevel sets none, so a question that skips an expensive step can ask for a higher bar than one that only sorts a list.

The answer document

ask prints one JSON document, protocol 2. Its schema is docs/protocol/ask.schema.json.

FieldWhat it holds
protocol2
log_idthe id of the log line, for jevelry outcome; null with --no-log
jevelname and version; null for --questions
modelthe model that answered
state_hashsha256: of the state
answersone entry per question: type, the value (choice, score or noul), the probabilities, decision
usageinput and output tokens
runjevelry run only: the option, command, decision, exit, duration and whether you confirmed it

On failure the document is { "protocol": 2, "error": { "exit", "code", "message" } }, with field when a field is wrong and retry_after_ms when TypeSafe gave one.

The log, outcomes and the report

Every ask goes into $JEVELRY_HOME/log.jsonl with its answers, its decisions and a hash of the state. The state itself goes in only with --log-state, jevel(name, { logState: true }) or JEVELRY_LOG_STATE=1, because states often hold customer text, and an ask from Try always keeps its state. When you later know what was true, you tell jevelry:

npx jevelry outcome <log_id> urgent yes
npx jevelry report --jevel ticket-triage

jevel          question     asks  act  mark  fall_back  outcomes  agree(act)  agree(mark)  certainty
ticket-triage  frustration  1     0    1     0          0         -           -            0.64
ticket-triage  team         1     1    0     0          0         -           -            1.00
ticket-triage  urgent       1     1    0     0          1         100%        -            0.98

agree(act) is how often act was right, and that is the number you move a threshold by. For a question that repeats over a list, the question name carries the index, so you write same_as[0].

Tuning thresholds

Once a question has 20 reviewed decisions, the Jevel screen proposes a lower act threshold when the decisions above it were right at least 90% of the time and at least as often as at the current threshold, less two points. T writes it into the jevel file. A jevel that came with jevelry is first copied into ./jevels, and from then on your project uses that copy.

Configuration

Environment variables

VariableWhat it doesDefault
TYPESAFE_API_KEYyour key; read from the environment, then the macOS keychain, then $JEVELRY_HOME/envrequired
TYPESAFE_BASE_URLthe API root, read by the SDKhttps://api.typesafe.ai
TYPESAFE_DEFAULT_MODELthe model the SDK sends when --model, the jevel and JEVELRY_MODEL name nonejev-latest
TYPESAFE_LOG_LEVELthe SDK's log level; its lines go to stderr as jevelry: sdk:the SDK's
JEVELRY_MODELthe model when the jevel pins noneTYPESAFE_DEFAULT_MODEL
JEVELRY_HOMEwhere the log, the key file and the TUI settings live~/.jevelry
JEVELRY_JEVELSmore jevel folders, colon separatednone
JEVELRY_TIMEOUT_MSthe request timeout, per attempt30000
JEVELRY_LOG_STATE1 writes the state itself into each ask line, for ask, run and decideoff, only the hash
JEVELRY_KEY_STOREfile keeps the key in $JEVELRY_HOME/env on macOS toothe keychain on macOS

The model an ask uses is the first one set of --model, the jevel's model, JEVELRY_MODEL and TYPESAFE_DEFAULT_MODEL.

A command started by jevelry run gets these, and TYPESAFE_API_KEY is removed from its environment:

VariableWhat it holds
JEVELRY_STATEthe path of a file with the state, removed afterwards
JEVELRY_DECISIONact, mark or fall_back
JEVELRY_OPTIONthe option Jev picked, empty on fall_back
JEVELRY_LOG_IDthe id of the ask in the log

Where the key is stored

jevelry install stores the key in the macOS keychain (service typesafe-api-key, account jevelry) and on every other machine in $JEVELRY_HOME/env as TYPESAFE_API_KEY=..., readable only by you. A key that is already in the environment, the keychain or the file is left as it is.

$JEVELRY_HOME

PathWhat it holds
log.jsonlevery ask, outcome and run, one JSON line each
envthe key, when the keychain is unavailable
tui.jsonthe TUI theme, dark or light, set from ctrl+p
jevels/your own jevels for every project on this machine

When it fails

ask and run print one JSON document on stdout in every case. On failure it is an error document and the exit code says what happened:

ExitMeaning
0answered
1a usage error, tui outside a terminal, or anything the codes below leave out
2the jevel or the state is wrong, and the document names the field
3rate limited or overloaded, with retry_after_ms when TypeSafe gave one
4the key is missing or refused, and no request was made
5over the token budget, and no request was made
6the network or TypeSafe's servers
7TypeSafe answered a shape this build cannot read
9jevelry run only: Jev marked the call and nobody confirmed it, so the command stayed put

When jevelry run runs a command, it exits with that command's exit code.

Architecture

ModuleWhat it does
src/cli.ts, src/program.tsthe entry and the commands, built with commander
src/jevel.tsfinds, parses and checks jevels, and expands a question that repeats over a list
src/budget.tsestimates the tokens of a request before it is sent
src/ask.tsone ask: checks the state, sends every question in one request, reads the answers
src/decision.tscertainty and thresholds into act, mark or fall_back
src/decide.tsthe library: jevel(name).decide(), .run() and the client
src/run.tsplans the command for a decision and runs it
src/log.tsthe log, outcomes and runs
src/report.tsagreement per question
src/key.ts, src/install.tsthe key store and the skill installer
src/protocol.tsthe answer document and the exit codes
src/tui/the full-screen view, built with ink

An ask flows like this: the jevel is found and checked, the state is checked against the jevel's required keys, questions that repeat over a list are expanded, the request is estimated against the budget, and then the official @typesafe-ai/sdk sends every question in one request. Each answer is checked against its question, turned into a decision by the thresholds and written to stdout, and the ask is appended to the log.

Development

Prerequisites

  • Node 22 or newer
  • a TypeSafe key, for the live tests only

Build and test

git clone https://github.com/backant-io/jevelry.git
cd jevelry
npm install
npm run build
npm test
npm run lint

npm test builds first and runs the offline suite against recorded answers. The live suite asks the real API with the key from your environment:

export TYPESAFE_API_KEY=...
npm run test:live
npm run test:live -- tests/live/jevels.test.ts

How do we know you can trust it

364 tests run offline against recorded answers from TypeSafe's API reference, against the seventeen jevels and their cases, and against every screen of jevelry tui at 80x24 and 120x40. 99 tests run against the real API on demand with npm run test:live: every jevel answers its own example.json, every case in every cases.json gets the answer it expects, five more cover the API itself, one routes the ticket-triage example through decide in your program, one asks ticket-triage from the Try screen of jevelry tui and one lets failing-test run its command. One of those tests reads the log afterwards and checks that your key stays out of it.

License

MIT. See LICENSE.

Contributing

Issues and pull requests are welcome at https://github.com/backant-io/jevelry. Run npm test and npm run lint before you open one, and when you add a jevel, give it an example.json and a cases.json and run its cases with npm run test:live -- tests/live/jevels.test.ts.

agent-skill
classification
claude-code
codex
confidence
cursor
decision-model
jev
llm
opencode
structured-decisions
system-one
typesafe
typesafe-ai

Contributors

Sorcecoder

112 commits

backant-io/jevelry

Use Jev everywhere to make & track decisions

TypeScript

1

112 commits

updated Sep 23, 2026

See the code

See what people are saying

SourceMessageScoreDate

Show HN: Open Code for Jev

1

Sep 23, 2026

README

jevelry

Overview · Install · Usage · Commands · Keys · Jevels · Configuration · Jev docs

npm Node 22 Tests MIT

Use Jev everywhere to make & track decisions.

jevelry tui: the start screen, a live ask of ticket-triage, the dashboard and Review

[!WARNING] Early development: jevelry is young and changes fast. Commands, the jevel format and the answer document may change between minor versions until 1.0, so pin the version you depend on.

jevelry lets your code and your coding agent ask Jev, the decision model from TypeSafe, a small typed question and get back a decision it can act on, and it keeps every decision so you can see how often Jev was right.

Overview

Your code and your coding agent make the same small calls every day: is this ticket urgent, which team should get it, is this bug a duplicate, did this test fail because of the code or because of the machine it ran on. Today each of those is usually a prompt that returns text you parse, and you don't learn how sure the model was.

With jevelry you ask each of those questions to Jev with one command. A question lives in a small file called a jevel, and seventeen ready-made jevels ship with the package. Jev answers with how sure it is, and the jevel turns that into one of three decisions:

DecisionMeaningWhat your code does
actJev is sureuses the answer
markJev is fairly sureuses the answer and flags it for a person
fall_backJev is unsuredoes what it did before jevelry

One ticket triage is about 1,280 input tokens at $0.042 per million, roughly $0.00005, so a dollar covers around 18,000 of them. jevelry itself is free and MIT, and TypeSafe charges only for input tokens.

Where it helps

In your app. A support ticket comes in and needs a team and a priority. On act your code routes it to the team Jev picked, on mark it routes it and flags it for the queue owner, and on fall_back it leaves the ticket for a person, the same way it works today:

npx jevelry ask ticket-triage --state '{"ticket": {"subject": "Charged twice", "message": "I was billed twice this month, please refund one today."}}'

In your coding agent. A test fails with connect EPERM because the sandbox blocked a network call, and left alone your agent will probably start rewriting code that was fine. With the jevelry skill installed it asks first, and on environment it reports the sandbox problem and leaves your code alone:

npx jevelry ask review-comment-kind --state '{"comment": {"author": "ci-bot", "text": "FAIL tests/api.test.ts\nTypeError: fetch failed\n  cause: Error: connect EPERM 104.18.2.1:443"}}'

Features

  • Interactive TUI: Built with Ink for a full-screen terminal experience
  • Ready-made jevels: Seventeen decisions for tickets, messages, issues, pull requests, logs, alerts and failing tests
  • Typed decisions in code: Load a jevel once and get a typed decision wherever your code asks
  • Commands Jev runs: Jev picks the command to run and asks you first when it is only fairly sure
  • Review and tuning: Mark whether Jev was right and move thresholds based on real outcomes
  • Persistent log: Every decision in one local log, with a report per question
  • Tested jevels: Every shipped jevel is checked against the real API on its own cases
  • Agent skill: One install for Claude Code, Codex, Cursor, opencode and pi
  • Typed answers: Your editor knows the options of every jevel

Installation

jevelry runs on Node 22 or newer, so check node --version first.

Using npm, on your machine

npm install -g jevelry
jevelry install

jevelry install puts the skill into every coding agent it finds and asks for your TypeSafe key. You can skip the key with Enter and add it later. Get a key at https://docs.typesafe.ai.

As a library in your project

npm install jevelry

In your coding agent

Run this inside Claude Code, Codex, pi, opencode or Cursor, or in your terminal:

npx jevelry install

If you prefer the skills installer, npx skills add backant-io/jevelry --skill jevelry does the same for the agents it supports.

From source

git clone https://github.com/backant-io/jevelry.git
cd jevelry
npm install
npm run build
node bin/jevelry.js --help

Usage

Start

jevelry tui

It opens on the start screen with what you can do from there: try a jevel, review the decisions Jev marked, open the dashboard or go through your history. Press d for the dashboard, which shows you how Jev decided today, what needs you and how each of your jevels is doing. When your log is still empty, the first row on the start screen offers you a sample ticket: press enter to open it in Try and enter again to ask Jev about it live, and you see your first answer in a few seconds. If you have no key yet, Try tells you how to add one.

Ask from the command line

export TYPESAFE_API_KEY=...
npx jevelry ask ticket-triage --state '{"ticket": {"subject": "Charged twice", "message": "I was billed twice this month, please refund one today."}}'

You get one JSON document back with an answer per question, and the part your code reads is the decision:

"team":        { "choice": "billing",  "confidence": 1.0,  "decision": "act"  }
"urgent":      { "noul": 0.98,         "yes": true,        "decision": "act"  }
"frustration": { "score": 0.24,        "confidence": 0.64, "decision": "mark" }

The whole document and a jq one-liner are in docs/examples.md.

In your program

When the decision happens inside your own code, you load the jevel once when your program starts and call decide at the point where your code decides today:

import { jevel } from "jevelry";

const triage = jevel("ticket-triage");

const d = await triage.decide({ ticket });
switch (d.team?.decision) {
  case "act": route(ticket, d.team.answer); break;
  case "mark": route(ticket, d.team.answer); flagForQueueOwner(ticket); break;
  case "fall_back": leaveInGeneralQueue(ticket); break;
}

decide asks Jev, writes the ask to your log and hands you every question with its decision and its answer. When Jev can't answer, because TypeSafe is busy or your network is down, every question comes back fall_back with the reason in d.error, so your code just keeps its old path. Run npx jevelry types --out src/jevels.d.ts once and d.team.answer is typed as "billing" | "technical" | "account" | "other".

Let Jev run the command

When the next step after a decision is always one of a few commands, you put those commands into the jevel and jevelry runs the one Jev picks. failing-test ships with the package and works this way:

npx jevelry run failing-test --dry-run --state '{"test": {"name": "checkout > pays with a saved card", "output": "TimeoutError: waiting for selector failed: timeout 5000ms exceeded", "history": "fails about once a week and passes on a rerun"}}'
DecisionWhat happens
actthe command runs
markjevelry asks you first, and --yes runs it straight away
fall_backthe jevel's fall_back command runs

--dry-run prints what would run. Your state goes to the command on stdin and in the file JEVELRY_STATE points at, so a ticket that says ; rm -rf ~ stays plain text. jevelry prints the path of the jevel on stderr before it runs anything, because a jevels/ folder in your project comes before the ones that ship.

Use it with your coding agent

You probably want your coding agent to write the jevel and wire it in, while you review the questions. Once the skill is installed, talk to your agent the way you would to a colleague who has read the guide:

Using the jevelry skill, write a jevel that decides which team a support ticket goes to
and whether it is urgent, run check on it, and wire the decisions into src/inbox.ts.

The skill keeps every jevel in ./jevels/ and leaves the questions and thresholds for you to read. The guide the agent reads is skills/jevelry/references/writing-jevels.md and works for people too.

Command-line reference

CommandWhat it does
jevelry ask [jevel]asks every question of a jevel (or of --questions) about a state and prints one JSON document
jevelry run <jevel>asks a jevel whose options name commands, then runs the command for Jev's decision
jevelry outcome <log_id> <question> <value>records what proved true: agree, disagree, an option, a level index, yes or no
jevelry reportagreement per jevel and question, from the log
jevelry tuithe full-screen view: the start screen, the dashboard, Review, Jevel, Try and History
jevelry listevery jevel jevelry can find, with its folder; the first folder wins
jevelry show <jevel>the resolved frontmatter as JSON, then the body, then example.json when the jevel has one
jevelry check <jevel>refuses a defective jevel with exit 2 and warns about questions Jev handles poorly
jevelry typeswrites a TypeScript declaration so jevel(name).decide() returns each jevel's own answer types
jevelry modelsthe model names your account may send, from GET /v1/models
jevelry installinstalls the skill into your coding agents, then stores a TypeSafe key
jevelry install-skillinstalls the skill into your coding agents, and asks for no key
jevelry help [command]the help of one command

-V, --version prints the version and -h, --help prints the help, on the program and on every command. A <source> is @file, - for stdin, or inline JSON.

ask

FlagWhat it does
--state <source>the state to ask about; required
--questions <source>the API's questions map, for a one-off ask with no jevel
--model <id>the model, over the jevel's pin and JEVELRY_MODEL
--jevels <dir>a jevels folder searched first; repeatable
--no-logkeeps this ask out of the log
--log-statewrites the state itself into the log line, next to its hash

run

FlagWhat it does
--state <source>the state to ask about; required
--yesruns a mark straight away
--dry-runprints what would run and runs nothing
--model <id>the model, over the jevel's pin and JEVELRY_MODEL
--jevels <dir>a jevels folder searched first; repeatable
--log-statewrites the state itself into the log line, next to its hash

outcome

FlagWhat it does
--note <text>a sentence kept with the outcome

report

FlagWhat it does
--jevel <name>only this jevel
--since <iso>only asks at or after this time
--jsonmachine-readable rows

tui

FlagWhat it does
--dashboardopens on the dashboard
--jevel <name>opens History filtered to this jevel
--since <iso>opens History filtered to asks since this time
--jevels <dir>a jevels folder searched first; repeatable

tui needs an interactive terminal and exits 1 in a pipe.

list, show, check

FlagWhat it does
--jevels <dir>a jevels folder searched first; repeatable

types

FlagWhat it does
--jevels <dir>a jevels folder searched first; repeatable
--out <file>writes the declaration to this file; stdout by default

install, install-skill

FlagWhat it does
--projectinstalls into this project (.claude/skills and .agents/skills)
--agent <names...>only these agents: claude-code, codex, cursor, opencode, pi, or agents with --project
--no-keyinstall only: installs the skill and asks for no key

Keyboard shortcuts

The footer shows the keys of the screen you are on, and ? lists them all.

Global

KeyAction
hthe start screen
dthe dashboard
vReview
yHistory
tpick a jevel to try
ctrl+pcommand palette: screens, theme, help, quit and every jevel
?help
qquit; asks first when a state you edited in Try would be lost

ctrl+p works on every screen, also while you type. The other global keys pause while a screen takes typed text.

Start screen

KeyAction
j k or move
enteropen the selected row
t v d yopen that row directly
t or enter on an empty logtry ticket-triage with a sample ticket

Dashboard

KeyAction
j k or move
enteropen the entry: the review queue, a jevel, a decision or the failed asks
enter on an empty logtry ticket-triage with a sample ticket
escthe start screen

Review

KeyAction
cJev is correct
wJev is wrong: a yes/no question records the other answer, a choice or score asks for the right one
sskip this decision
nwrite a note for the next c or w
mswitch between marked decisions and act decisions
j k or scroll what Jev saw
escthe start screen

c, w and s count once a card has been up for 450 ms, and a held key records once.

WhenKeyAction
picking the right answerj k or move
picking the right answerenterrecord it
picking the right answeresccancel
writing a noteenterkeep the note
writing a noteescdrop the note
all reviewedmswitch between marked and act decisions
all reviewedsshow the skipped ones again
all reviewedescthe start screen

Jevel

KeyAction
j k or move between questions
enterthe question's decisions, in History
Tset the proposed act threshold in the jevel file, when there is one
escthe start screen

Try

KeyAction
enterask Jev live
rask again
eedit the state
ppick another jevel
j k or scroll the answer
escthe start screen

While you edit the state, typing and pasting go into the text, the arrows, home and end move, tab adds two spaces, enter asks and esc stops editing.

History

KeyAction
j k or move
enteropen the decision, where c, w, n, j k and esc work as in Review
fcycle the decision filter
Jcycle the jevel filter
oonly decisions with no outcome yet
xonly failed asks
rthe report
escback

In the report, j k scroll, J cycles the jevel and esc goes back to the list.

Dialogs

DialogKeyAction
commands, pick a jevel, themetypingfilter the list
commands, pick a jevel, theme or ctrl+n ctrl+pmove
commands, pick a jevel, themeenterchoose
commands, pick a jevel, themeescclose
helpesc, ? or enterclose
set thresholdenterwrite the threshold, copying a shipped jevel into ./jevels first
set thresholdesccancel
set thresholdqquit
quitenter, q or yquit
quitesc or nstay

Jevels

A jevel is a folder with one JEVEL.md in it, the same way a skill for your coding agent is a folder with one SKILL.md. The frontmatter holds the questions, their options and the thresholds, and the body is what you and your agents read. Jev takes three kinds of question:

QuestionYou askYou get back
choicewhich of these optionsthe option, a probability per option, a confidence
scorehow much, on your own levelsa value between the levels, a probability per level, a confidence
noulis this trueone probability, 0 to 1

Seventeen ship with the package, each with an example.json to ask it with and a cases.json of realistic states with the answer a person would give. The table with their questions and state keys is in jevels/README.md.

JevelWhen to use it
alert-causean alert fired and you want a first hypothesis
change-riska pull request before merge
checklist-compliancea report that claims to follow a checklist
duplicate-issuea new issue against the open ones
escalation-routea request or incident in a shared inbox
failing-testa failing test, with jevelry run
issue-readinessan issue before it reaches the backlog
log-triagea burst of log lines
meeting-notesnotes after a meeting
message-triagean inbound message in mail or chat
notification-triagea batch of notifications for one person
pr-description-checka pull request description against its diff
reply-checka reply, to close the thread or keep it open
review-comment-kinda review comment or a failing test output
review-qualitya finished code review
task-difficultya task before you route it
ticket-triagea new support ticket

jevelry finds jevels in --jevels <dir>, then JEVELRY_JEVELS, then ./jevels, then $JEVELRY_HOME/jevels, then the ones that ship, and the first one with the name wins. To write your own, copy the nearest folder into ./jevels, change the name, rewrite the questions and run npx jevelry check <name> until it prints 0 warnings. check refuses what the API would refuse (more than 255 options, a threshold outside 0 to 1, a question with no instructions) and warns about a yes/no question whose "yes" means no, a double negative, options that carry different fields and a model that is not pinned. The whole method is in skills/jevelry/references/writing-jevels.md.

Then write a cases.json with at least three states from your own data: one clear one way, one clear the other way and one you would ask a colleague about. The clear cases should land on the right answer, and the unclear one should stay below act.

Decisions and the log

DecisionMeaningWhat your code usually does
actcertainty at or above the act thresholdacts on the answer
markcertainty at or above the mark thresholdacts and flags it for a person
fall_backbelow the mark threshold, or Jev could not answerwhat it did before jevelry

A yes/no question's certainty is how far its probability sits from 0.5, and a choice or score uses the confidence Jev reports. The thresholds live in the jevel, per question, with act: 0.9, mark: 0.7 when the jevel sets none, so a question that skips an expensive step can ask for a higher bar than one that only sorts a list.

The answer document

ask prints one JSON document, protocol 2. Its schema is docs/protocol/ask.schema.json.

FieldWhat it holds
protocol2
log_idthe id of the log line, for jevelry outcome; null with --no-log
jevelname and version; null for --questions
modelthe model that answered
state_hashsha256: of the state
answersone entry per question: type, the value (choice, score or noul), the probabilities, decision
usageinput and output tokens
runjevelry run only: the option, command, decision, exit, duration and whether you confirmed it

On failure the document is { "protocol": 2, "error": { "exit", "code", "message" } }, with field when a field is wrong and retry_after_ms when TypeSafe gave one.

The log, outcomes and the report

Every ask goes into $JEVELRY_HOME/log.jsonl with its answers, its decisions and a hash of the state. The state itself goes in only with --log-state, jevel(name, { logState: true }) or JEVELRY_LOG_STATE=1, because states often hold customer text, and an ask from Try always keeps its state. When you later know what was true, you tell jevelry:

npx jevelry outcome <log_id> urgent yes
npx jevelry report --jevel ticket-triage

jevel          question     asks  act  mark  fall_back  outcomes  agree(act)  agree(mark)  certainty
ticket-triage  frustration  1     0    1     0          0         -           -            0.64
ticket-triage  team         1     1    0     0          0         -           -            1.00
ticket-triage  urgent       1     1    0     0          1         100%        -            0.98

agree(act) is how often act was right, and that is the number you move a threshold by. For a question that repeats over a list, the question name carries the index, so you write same_as[0].

Tuning thresholds

Once a question has 20 reviewed decisions, the Jevel screen proposes a lower act threshold when the decisions above it were right at least 90% of the time and at least as often as at the current threshold, less two points. T writes it into the jevel file. A jevel that came with jevelry is first copied into ./jevels, and from then on your project uses that copy.

Configuration

Environment variables

VariableWhat it doesDefault
TYPESAFE_API_KEYyour key; read from the environment, then the macOS keychain, then $JEVELRY_HOME/envrequired
TYPESAFE_BASE_URLthe API root, read by the SDKhttps://api.typesafe.ai
TYPESAFE_DEFAULT_MODELthe model the SDK sends when --model, the jevel and JEVELRY_MODEL name nonejev-latest
TYPESAFE_LOG_LEVELthe SDK's log level; its lines go to stderr as jevelry: sdk:the SDK's
JEVELRY_MODELthe model when the jevel pins noneTYPESAFE_DEFAULT_MODEL
JEVELRY_HOMEwhere the log, the key file and the TUI settings live~/.jevelry
JEVELRY_JEVELSmore jevel folders, colon separatednone
JEVELRY_TIMEOUT_MSthe request timeout, per attempt30000
JEVELRY_LOG_STATE1 writes the state itself into each ask line, for ask, run and decideoff, only the hash
JEVELRY_KEY_STOREfile keeps the key in $JEVELRY_HOME/env on macOS toothe keychain on macOS

The model an ask uses is the first one set of --model, the jevel's model, JEVELRY_MODEL and TYPESAFE_DEFAULT_MODEL.

A command started by jevelry run gets these, and TYPESAFE_API_KEY is removed from its environment:

VariableWhat it holds
JEVELRY_STATEthe path of a file with the state, removed afterwards
JEVELRY_DECISIONact, mark or fall_back
JEVELRY_OPTIONthe option Jev picked, empty on fall_back
JEVELRY_LOG_IDthe id of the ask in the log

Where the key is stored

jevelry install stores the key in the macOS keychain (service typesafe-api-key, account jevelry) and on every other machine in $JEVELRY_HOME/env as TYPESAFE_API_KEY=..., readable only by you. A key that is already in the environment, the keychain or the file is left as it is.

$JEVELRY_HOME

PathWhat it holds
log.jsonlevery ask, outcome and run, one JSON line each
envthe key, when the keychain is unavailable
tui.jsonthe TUI theme, dark or light, set from ctrl+p
jevels/your own jevels for every project on this machine

When it fails

ask and run print one JSON document on stdout in every case. On failure it is an error document and the exit code says what happened:

ExitMeaning
0answered
1a usage error, tui outside a terminal, or anything the codes below leave out
2the jevel or the state is wrong, and the document names the field
3rate limited or overloaded, with retry_after_ms when TypeSafe gave one
4the key is missing or refused, and no request was made
5over the token budget, and no request was made
6the network or TypeSafe's servers
7TypeSafe answered a shape this build cannot read
9jevelry run only: Jev marked the call and nobody confirmed it, so the command stayed put

When jevelry run runs a command, it exits with that command's exit code.

Architecture

ModuleWhat it does
src/cli.ts, src/program.tsthe entry and the commands, built with commander
src/jevel.tsfinds, parses and checks jevels, and expands a question that repeats over a list
src/budget.tsestimates the tokens of a request before it is sent
src/ask.tsone ask: checks the state, sends every question in one request, reads the answers
src/decision.tscertainty and thresholds into act, mark or fall_back
src/decide.tsthe library: jevel(name).decide(), .run() and the client
src/run.tsplans the command for a decision and runs it
src/log.tsthe log, outcomes and runs
src/report.tsagreement per question
src/key.ts, src/install.tsthe key store and the skill installer
src/protocol.tsthe answer document and the exit codes
src/tui/the full-screen view, built with ink

An ask flows like this: the jevel is found and checked, the state is checked against the jevel's required keys, questions that repeat over a list are expanded, the request is estimated against the budget, and then the official @typesafe-ai/sdk sends every question in one request. Each answer is checked against its question, turned into a decision by the thresholds and written to stdout, and the ask is appended to the log.

Development

Prerequisites

  • Node 22 or newer
  • a TypeSafe key, for the live tests only

Build and test

git clone https://github.com/backant-io/jevelry.git
cd jevelry
npm install
npm run build
npm test
npm run lint

npm test builds first and runs the offline suite against recorded answers. The live suite asks the real API with the key from your environment:

export TYPESAFE_API_KEY=...
npm run test:live
npm run test:live -- tests/live/jevels.test.ts

How do we know you can trust it

364 tests run offline against recorded answers from TypeSafe's API reference, against the seventeen jevels and their cases, and against every screen of jevelry tui at 80x24 and 120x40. 99 tests run against the real API on demand with npm run test:live: every jevel answers its own example.json, every case in every cases.json gets the answer it expects, five more cover the API itself, one routes the ticket-triage example through decide in your program, one asks ticket-triage from the Try screen of jevelry tui and one lets failing-test run its command. One of those tests reads the log afterwards and checks that your key stays out of it.

License

MIT. See LICENSE.

Contributing

Issues and pull requests are welcome at https://github.com/backant-io/jevelry. Run npm test and npm run lint before you open one, and when you add a jevel, give it an example.json and a cases.json and run its cases with npm run test:live -- tests/live/jevels.test.ts.

agent-skill
classification
claude-code
codex
confidence
cursor
decision-model
jev
llm
opencode
structured-decisions
system-one
typesafe
typesafe-ai

Contributors

Sorcecoder

112 commits

Languages

TypeScript

100.0%