nu_plugin_jev 0.1.1

Nushell plugin for TypeSafe Jev structured decisions
nu_plugin_jev-0.1.1 is not a library.

nu_plugin_jev

crates.io

nu_plugin_jev brings TypeSafe Jev / System One decisions into Nushell. Nu builds the state and processes the answers; the plugin sends the request.

The command set is deliberately small:

Command Purpose
jev ask Ask several named questions about one state in one request.
jev annotate Ask the same questions independently for each table row.
jev question noul Build a probability-of-true question.
jev question choice Build a categorical question.
jev question score Build an ordered-score question.
jev Show offline usage guidance.

Use native Nu commands such as where, sort-by, select, and group-by on the typed answers. There are no plugin-specific filtering or sorting commands. Related questions share one request; table processing streams with bounded concurrency instead of collecting every row.

Install

The plugin targets Nushell 0.116.x.

cargo install --path . --locked
plugin add ~/.cargo/bin/nu_plugin_jev
plugin use jev
help jev

The default build uses mimalloc. Pass --no-default-features to cargo install to use the system allocator.

Live requests need an API key from TYPESAFE_API_KEY or a private TOML file. Question constructors and --dry-run work without a key or network access.

Ask about one state

Questions are ordinary Nu records. The constructors only build data; they do not contact Jev.

let questions = {
    spam: (jev question noul "Is this unsolicited?" --yes "Unrequested bulk mail")
    kind: (jev question choice "Message kind?" [normal promo spam])
    urgency: (jev question score "Review urgency?" ["later" "today" "now"])
}

let result = (
    {message: "Hello", sender: "Ada"}
    | jev ask $questions
)

$result | get answers.spam.noul
$result | get answers.kind.choice
$result | get answers.urgency.score

jev ask returns {model, answers, usage}. Each answer retains its type and service-provided details, including confidence and probability distributions. The caller chooses thresholds; the plugin does not decide what counts as true.

The pipeline value is the state. A string stays a string, a record becomes a JSON object, and a list becomes a JSON array. A finite stream passed to jev ask is one array state, not a batch of independent requests.

Add separate context with --context <value>. The outgoing state becomes {input: <pipeline value>, context: <value>}; fields are not merged.

Inspect the exact request body before sending data:

{message: "Hello"} | jev ask $questions --dry-run

The preview contains neither the API key nor a network response.

Annotate a table

jev annotate evaluates each record row independently. It preserves the source fields and adds the named answers under jev, or under --into.

open messages.nuon
| jev annotate $questions --fields [message sender] --into ai
| where ai.spam.noul >= 0.98
| sort-by ai.urgency.score --reverse

--fields sends only the named top-level columns. Use --state document.text instead to send one cell path; these selectors cannot be combined. The original row remains intact either way. --context has the same wrapping behavior as in jev ask.

Useful options:

Option Effect
--meta jev_meta Add model, token usage, and a local request_id separately.
--jobs 32 Limit concurrent distinct evaluations; default is 16.
--unordered Emit ready rows without waiting for earlier slow rows.
--on-error keep Pass a failed row through without an annotation.
--on-error record Add a jev_error record to a failed row.
--dry-run Stream request bodies without a key or network call.

The default error mode is fail. Successful duplicates can share an in-progress request or a bounded, per-invocation cache. Eviction permits a later request for the same state. Output and input are bounded, and stopping downstream consumption cancels outstanding local work. The service has no independent-row batch endpoint: an array sent through jev ask is still one shared state.

Configuration

Each setting is resolved independently, from highest to lowest priority:

  1. Command flag, where available.
  2. $env.config.plugins.jev.
  3. Caller environment (NU_PLUGIN_JEV_*; key: TYPESAFE_API_KEY).
  4. Local TOML file.
  5. User TOML file.
  6. Built-in default.

An invalid value is an error, not a reason to try a lower-priority source. Settings are captured per command invocation, so later calls can see edited files even when the plugin process persists.

Setting Environment TOML Default
Model NU_PLUGIN_JEV_MODEL model jev-latest
Service root NU_PLUGIN_JEV_BASE_URL base_url https://api.typesafe.ai
Timeout NU_PLUGIN_JEV_TIMEOUT_MS timeout_ms 30 seconds
Table jobs NU_PLUGIN_JEV_JOBS jobs 16
Additional retries NU_PLUGIN_JEV_RETRIES retries 3
Proxy policy NU_PLUGIN_JEV_PROXY proxy auto

The optional user file is nu_plugin_jev/config.toml in the platform user config directory (usually ~/.config/nu_plugin_jev/config.toml on Linux). The optional local file is .nu_plugin_jev.toml in the calling Nu directory. Select another local file with --config <path> or NU_PLUGIN_JEV_CONFIG. Every TOML field is optional.

A local file discovered implicitly cannot set base_url or proxy. Select it explicitly if you intend to allow those settings. A TOML file containing api_key must be owner-only on Unix (for example, chmod 600). Keep it out of version control. The key can also come from TYPESAFE_API_KEY; it is never accepted as a command flag or included in a request preview.

The proxy setting accepts auto, direct, http://..., or socks5h://.... auto uses the process/OS proxy settings captured when the plugin starts; restart it with plugin stop jev after changing those settings. Jev-specific proxy settings are resolved on each command invocation. An explicit proxy does not honor global NO_PROXY and does not silently fall back to a direct connection.

Diagnostics and failures

Set NU_PLUGIN_JEV_LOG=info or debug before the plugin starts to write diagnostics to stderr. In an existing Nu session, run plugin stop jev after changing the variable. Logs omit credentials, state, questions, and request bodies. Use --meta when downstream Nu code needs model, usage, or the local request ID as data.

One logical evaluation has a total timeout, including retry waits. HTTP 429, 502, 503, 504, and 529 may be retried; Retry-After guidance is honored when valid. Other client errors and invalid responses are not retried. Retries can repeat remote work, so request_id is not an idempotency or billing guarantee.

For exact question validation, value conversion, retry, cache, and proxy contracts, see the OpenSpec requirements. The repository usage skill contains additional pipeline guidance.

Development and release

Run cargo nextest run --all-features --all-targets --locked for the test suite. CI also checks formatting, Clippy, OpenSpec, dependency policy, and the publishable crate. Integration tests use local mock servers and need no TypeSafe key.

A v<crate-version> tag starts the release workflow, which builds platform archives and publishes through configured crates.io trusted publishing. Release notes live in CHANGELOG.md.

MIT licensed. See LICENSE.