Skip to main content

jev-preview

A TUI — a full-screen, keyboard-driven terminal application — for the TypeSafe System One API (POST https://api.typesafe.ai/v1/systemone).

It is not a command-line tool that prints and exits: running jev-preview takes over your terminal with three live panes. You write a context, build the questions in a form rather than by hand, press enter to send, and read the answers as probability bars instead of raw JSON. Everything is driven by single keystrokes — vim-style h j k l to move between panes, e to edit the focused one, q to quit — and there are no subcommands to learn.

┌─ context ──────────┬─ response ─────────┐
│  the `state` you   │  answers, rendered │
│  send   (1/3)      │  with probability  │
├─ questions ────────┤  bars, then the    │
│  the `questions`   │  raw JSON          │
│  map, as a form    │                    │
│         (2/3)      │                    │
└────────────────────┴────────────────────┘

Every request you work on is saved as a JSON file holding its name, creation date, request body, and the full history of responses it has received, so the sandbox remembers what you tried and what came back.

Built with Textual. It needs a real terminal — any modern one will do (kitty, Alacritty, WezTerm, iTerm2, Windows Terminal, tmux) — and does not work through a pipe or in a non-interactive shell. The small handful of things that are plain command-line flags (--set-key, --show-config, --version) are listed below and never open the interface.

Install

Arch Linux — from the AUR, with any helper:

paru -S jev-preview          # or: yay -S jev-preview

macOS and Linux — from the Homebrew tap:

brew install arkadyburyakov/tap/jev-preview

Anywhere with Python 3.11+ — from PyPI:

pipx install jev-preview     # or: uv tool install jev-preview

Then:

jev-preview                  # launches the TUI

Both jev-preview and the shorter jev start the same application.

To install from a clone of this repository instead, run uv tool install . (or pipx install .) from its root.

The first run opens the TUI and asks for your TypeSafe API key, storing it in your user config directory (~/.config/jev-preview/config.json on Linux), readable only by you. Declining the prompt exits without writing anything. Press ctrl+k at any time to change it.

To set the key from the shell instead, without opening the TUI:

jev-preview --set-key sk-…
pbpaste | jev-preview --set-key -     # or read it from stdin
jev-preview --show-config             # where everything lives

TYPESAFE_API_KEY is honoured if you would rather keep the key in your environment; it wins over the config file and is never written to disk.

Keys

Normal mode

key action
h j k l or arrows move between panes
e edit the focused pane here
E edit it in $EDITOR instead
enter send the request
[ / ] previous / next response in history
o open the requests catalogue
n start a new, empty request
c copy the current request (context + questions, no response history)
r rename the current request
d delete the current request
m cycle model (jev-latestjev-previewjev-1.13.0)
ctrl+k change the API key
? help
q quit

In the response pane, j/k scroll (there is no pane above or below it), g/G jump to top and bottom, and pgup/pgdn page through. In the catalogue, typing filters by name or filename, and ctrl+d deletes the highlighted request after a confirmation.

Edit mode

e opens the focused pane for editing; esc returns to normal mode and saves. The border turns green and the title gains a ✎ esc marker.

context is a plain text buffer: every key types, arrows move the cursor, enter inserts a newline.

questions is a form, not a text buffer. Each question is a card with an id field, a type dropdown, an instructions box, and criteria widgets that follow the type — so the structure is chosen, and only free text is typed:

type criteria
noul optional descriptions of what true and false mean
choice option rows: a name and an optional description, + option to add
score ordered level rows, numbered as the API reports them, + level to add

tab/shift+tab and the arrow keys both cycle the fields, and stay inside the form; enter or space activates a button, including opening the type dropdown. + question appends a card and ✕ remove deletes one. Switching type keeps what the other types held, so flipping choice → score → choice loses nothing.

Pressing e with no questions yet starts one for you, with its generated id selected so typing renames it; leaving with esc before filling anything in discards it again.

Problems are reported live — a missing id, a duplicate id, absent instructions, a choice with fewer than two options, a score with fewer than two levels — in the pane title and under the summary, and enter refuses to send until they are gone.

ctrl+h/j/k/l are deliberately unbound, so they pass straight through to a terminal-level splits navigator (kitty's pass_keys.py, tmux, and friends) and still move you between terminal windows from inside jev.

Editing the panes

Edits are written to disk when you leave edit mode, about a second and a half after you stop typing, and again on send and on quit — there is no explicit save.

context is the request's state. It is plain text, and stays plain text unless what you type parses as a JSON object or array — so both a support ticket like this and a structured chat log work.

questions is the questions map. The form covers the documented shapes; a question using the advanced structure the API also accepts — an object or array for instructions, say — is shown read-only and carried through byte-for-byte rather than flattened. Edit those with E.

E hands the focused pane to $EDITOR: the raw text for context, the raw questions JSON for questions. If that JSON does not parse, it is kept verbatim in the file as draft_questions — the form refuses to open over it, and E reopens your text to fix.

The three question types (see primitives):

{
  "is_urgent":   { "type": "noul",   "instructions": "Does this convey urgency?" },
  "department":  { "type": "choice", "instructions": "Which team should handle this?",
                   "criteria": { "billing": "Payments", "technical": "Bugs", "sales": "Pricing" } },
  "frustration": { "type": "score",  "instructions": "How frustrated is the customer?",
                   "criteria": ["Calm", "Frustrated", "Very angry"] }
}

Naming

A new request names itself after the first line of its context and keeps following it, renaming its file as you go (auto_name: true in the file). r gives it a real name, which pins both the name and the filename from then on. A copy is born with the pinned name Copy of ….

Where things are stored

what where override
config (API key, base URL, models) ~/.config/jev-preview/config.json JEV_CONFIG_DIR
saved requests ~/.local/share/jev-preview/requests/ JEV_REQUESTS_DIR, --requests-dir

macOS and Windows get their own platform-appropriate locations via platformdirs. The config file is written with 0600 permissions; a key taken from TYPESAFE_API_KEY is never written at all.

{
  "api_key": "sk-…",
  "base_url": "https://api.typesafe.ai/v1",
  "models": ["jev-latest", "jev-preview", "jev-1.13.0"],
  "timeout": 60.0
}

Everything but api_key is optional; edit the file to point at a different deployment or to change the models m cycles through.

Saved request format

{
  "name": "Help! My payouts have been failing",
  "created_at": "2026-09-17T15:02:45+00:00",
  "auto_name": true,
  "body": { "model": "jev-latest", "state": "...", "questions": {} },
  "responses": [
    { "timestamp": "...", "status": 200, "duration_ms": 979, "body": {} }
  ]
}

auto_name is present only while the name tracks the context, and draft_questions only while the questions buffer does not parse.

Development

uv sync                                  # install with dev dependencies
uv run pytest                            # unit + end-to-end tests
uv run ruff check . && uv run ruff format --check .
uv run mypy
uv run textual run --dev jev_preview.app:JevApp   # with the Textual devtools console

Tests drive the real TUI through Textual's Pilot, with HTTP mocked by respx, and never touch your own config or saved requests — conftest.py redirects both to a temporary directory.

Releasing

A release is one commit to main: bump __version__ in src/jev_preview/__init__.py and add that version's section to CHANGELOG.md. pyproject.toml has no version of its own — hatchling reads it from __init__.py, so the two can never disagree.

On push, release.yml tags v<version> and creates the GitHub release with that changelog section as its notes. Publishing to the three channels then happens on release: published, in parallel and independently:

workflow channel built from
publish_pypi.yml PyPI the sdist and wheel, trusted publishing — no token
publish_aur.yml AUR packaging/AUR/PKGBUILD.template
publish_homebrew.yml the arkadyburyakov/tap tap packaging/homebrew/jev-preview.rb.template

Each renders its template with the version and the release tarball's checksum, builds the package, and runs packaging/smoke-test against the installed result — which starts the TUI headless, so a package that installed without app.tcss, or against a textual too old, fails before it is published. Nothing is committed back to this repository; the rendered PKGBUILD and formula exist only inside the workflow run and in the AUR and tap repositories.

Changing the runtime dependencies in pyproject.toml means updating both packaging templates: the Arch depends=() array, and the Homebrew formula's pinned resource blocks (regenerate those with brew update-python-resources against the rendered formula).

License

MIT — see LICENSE.

Release files for jev-preview 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jev-preview 0.1.0
File Size Uploaded
jev_preview-0.1.0.tar.gz 54.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jev-preview 0.1.0
File Interpreter ABI Platform
jev_preview-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size:89.9 kB

Release files / jev_preview-0.1.0.tar.gz

Download URL jev_preview-0.1.0.tar.gz
Size 54.0 kB
Tags Source
SHA-256 checksum
How to use checksums
5a053d508245b09534cfe85763190d38b1da828347ac892d13e28513d819080f
BLAKE2b-256 checksum
How to use checksums
d7d211a1d99c02aec95f48fa63368861b8a8e28a9b849d60b2bd393cb9376933
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release files / jev_preview-0.1.0-py3-none-any.whl

Download URL jev_preview-0.1.0-py3-none-any.whl
Size 35.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a1e14149954e0878faa245cdb0d574e69d4da201b6ae21cd60e1e41b30d1b00e
BLAKE2b-256 checksum
How to use checksums
64b15c083991bebbaf6c390eb62b85d337447706b8a502af2a50756be6f36a26
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page