Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ coverage/
!.env.example
*.tsbuildinfo
.vitest/

build/
Thumbs.db
*.tmp
*.swp
.vscode/
.idea/
.agents
.specify
/specs
16 changes: 16 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
node_modules/
coverage/
build/
*.log
.env*
.git/
.agents/
.specify/
specs/
tests/
.DS_Store
Thumbs.db
*.tmp
*.swp
.vscode/
.idea/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

### Added

- Explicit custom System One providers with a user-defined display name, API base URL, fixed
`JEV_CODE_API_KEY`, and manual model ID, shared by CLI, MCP, Pi, and OpenCode.
- Interactive custom setup with `setup --provider custom`, missing-field prompts, configuration
validation before registration, and matching offline doctor diagnostics.
- Zero retries by default for custom providers, with an explicit retry override; existing
built-in provider behavior is preserved.

## [0.4.1] - 2026-10-03

### Fixed
Expand Down
49 changes: 47 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ npx -y @french-castle/jev-code@latest setup

That detects the harnesses on your machine and, for each one, copies the skill and registers the
tool. When no API key is in your shell, it asks for one right there (hidden input) and which host
it belongs to: TypeSafe, OpenRouter, Vercel AI Gateway, or OpenAI. There is nothing else to
configure.
it belongs to: TypeSafe, OpenRouter, Vercel AI Gateway, OpenAI, or Custom. Custom setup also
asks for a provider name, API base URL, and model ID.

Add harness names to be explicit (`setup claude codex pi opencode`), `--project` to install into
the current repository instead of your user profile, or `--dry-run` to see the plan first.
Expand Down Expand Up @@ -82,6 +82,51 @@ npx -y @french-castle/jev-code@latest doctor --live
The agent loads the `jev` skill, calls `jev_classify` with the failures and a class set, acts on
the `auto` results, and tells you which ones it double-checked by hand.

## Custom System One providers

Use any gateway that implements the existing `POST /v1/systemone` request and response format.
Select Custom interactively, including when another provider key is already in your shell:

```bash
npx -y @french-castle/jev-code@latest setup --provider custom
```

Setup asks for the provider display name, API base URL, exact model ID, and a hidden API key.
It reuses fields already set in the environment. `--no-prompt` and non-TTY setup require all
fields to be set; `--dry-run` never prompts or writes, and `--no-tool` installs only the skill.
Incomplete or cancelled custom configuration never registers a tool or falls back to another host.

For environment configuration, set all five variables before starting your agent:

```bash
export JEV_CODE_PROVIDER=custom
export JEV_CODE_PROVIDER_NAME='My Gateway'
export JEV_CODE_BASE_URL='https://gateway.example/api'
export JEV_CODE_MODEL='vendor/model-id'
export JEV_CODE_API_KEY='<set your credential locally>'
```

The name is a display label; enter the model ID manually exactly as your provider lists it.
The client appends `/v1/systemone`, so this example targets
`https://gateway.example/api/v1/systemone`. Use an absolute HTTP(S) API base without the final
endpoint, credentials, query, or fragment. A model catalog page is not an API base.

For example, the [Experiential Jev model page](https://platform.experientiallabs.ai/models/jev-latest)
documents API base `https://api.experientiallabs.ai` and model ID `jev-latest`. Place your gateway
credential in this tool's fixed `JEV_CODE_API_KEY` variable, even if the provider's examples call
it `EXPERIENTIAL_API_KEY`. Experiential is an example, not a built-in preset.

Custom mode uses only its own key/base/model settings. Ambient built-in credentials are never
borrowed, and `TYPESAFE_BASE_URL`/`TYPESAFE_DEFAULT_MODEL` are ignored with a diagnostic note.
Custom retries default to **0** (one attempt); explicitly set `JEV_CODE_MAX_RETRIES` to a
non-negative integer to opt into retries. `JEV_CODE_TIMEOUT_MS` keeps its existing 30000 ms
default. Built-in defaults remain unchanged.

Setup carries custom settings into harnesses that store server environments; `--no-env` omits
the key. Pi and direct CLI use read the launching shell, so export the same settings there.
Setup does not modify your shell profile. Run `jev-code doctor` for offline diagnostics;
`doctor --live` explicitly makes a provider request.

## What a call looks like

The agent sends raw evidence and its own classes:
Expand Down
22 changes: 22 additions & 0 deletions docs/harnesses/claude-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,25 @@ jev-code doctor
```

Inside a session, `/mcp` lists the server and `/jev` loads the skill.

## Custom provider

```bash
npx -y @french-castle/jev-code@latest setup claude --provider custom
```

Enter a display name, HTTP(S) API base URL without `/v1/systemone`, manual provider model ID,
and hidden key. The gateway must implement the existing System One protocol. Alternatively,
export `JEV_CODE_PROVIDER=custom`, `JEV_CODE_PROVIDER_NAME`, `JEV_CODE_BASE_URL`,
`JEV_CODE_API_KEY`, and `JEV_CODE_MODEL` before setup. Custom retries default to zero; optional
`JEV_CODE_MAX_RETRIES` and `JEV_CODE_TIMEOUT_MS` overrides follow the selected configuration.

Setup carries the selected custom variables into the harness server environment using its
existing configuration mechanism. `--no-env` omits the key; ensure `JEV_CODE_API_KEY` reaches
the server from the launching environment. No unrelated provider key is copied. Pi and the
direct CLI require the variables in their launching shell rather than this harness config.

Use `--no-prompt` for complete environment-based setup. Incomplete custom settings prevent
registration, and `--dry-run` never prompts or writes. Restart the agent after setup and run
`jev-code doctor` to inspect configuration offline. See [custom-provider setup](../../README.md#custom-system-one-providers)
for URL examples and provider-specific key-variable mapping.
22 changes: 22 additions & 0 deletions docs/harnesses/codex.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,25 @@ jev-code doctor

Inside Codex, type `$jev` to invoke the skill explicitly, or just describe a task that needs
classification and let it trigger on the description.

## Custom provider

```bash
npx -y @french-castle/jev-code@latest setup codex --provider custom
```

Enter a display name, HTTP(S) API base URL without `/v1/systemone`, manual provider model ID,
and hidden key. The gateway must implement the existing System One protocol. Alternatively,
export `JEV_CODE_PROVIDER=custom`, `JEV_CODE_PROVIDER_NAME`, `JEV_CODE_BASE_URL`,
`JEV_CODE_API_KEY`, and `JEV_CODE_MODEL` before setup. Custom retries default to zero; optional
`JEV_CODE_MAX_RETRIES` and `JEV_CODE_TIMEOUT_MS` overrides follow the selected configuration.

Setup carries the selected custom variables into the harness server environment using its
existing configuration mechanism. `--no-env` omits the key; ensure `JEV_CODE_API_KEY` reaches
the server from the launching environment. No unrelated provider key is copied. Pi and the
direct CLI require the variables in their launching shell rather than this harness config.

Use `--no-prompt` for complete environment-based setup. Incomplete custom settings prevent
registration, and `--dry-run` never prompts or writes. Restart the agent after setup and run
`jev-code doctor` to inspect configuration offline. See [custom-provider setup](../../README.md#custom-system-one-providers)
for URL examples and provider-specific key-variable mapping.
22 changes: 22 additions & 0 deletions docs/harnesses/opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,25 @@ jev-code doctor

`opencode mcp list` should show `jev connected`. OpenCode prefixes MCP tools with the server
name, so inside a session the tools appear as `jev_jev_classify`, `jev_jev_check`, and so on.

## Custom provider

```bash
npx -y @french-castle/jev-code@latest setup opencode --provider custom
```

Enter a display name, HTTP(S) API base URL without `/v1/systemone`, manual provider model ID,
and hidden key. The gateway must implement the existing System One protocol. Alternatively,
export `JEV_CODE_PROVIDER=custom`, `JEV_CODE_PROVIDER_NAME`, `JEV_CODE_BASE_URL`,
`JEV_CODE_API_KEY`, and `JEV_CODE_MODEL` before setup. Custom retries default to zero; optional
`JEV_CODE_MAX_RETRIES` and `JEV_CODE_TIMEOUT_MS` overrides follow the selected configuration.

Setup carries the selected custom variables into the harness server environment using its
existing configuration mechanism. `--no-env` omits the key; ensure `JEV_CODE_API_KEY` reaches
the server from the launching environment. No unrelated provider key is copied. Pi and the
direct CLI require the variables in their launching shell rather than this harness config.

Use `--no-prompt` for complete environment-based setup. Incomplete custom settings prevent
registration, and `--dry-run` never prompts or writes. Restart the agent after setup and run
`jev-code doctor` to inspect configuration offline. See [custom-provider setup](../../README.md#custom-system-one-providers)
for URL examples and provider-specific key-variable mapping.
22 changes: 22 additions & 0 deletions docs/harnesses/pi.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,25 @@ jev-code doctor

Inside pi, the tools appear in the `Available tools` section of the system prompt and
`/skill:jev` loads the skill.

## Custom provider

```bash
npx -y @french-castle/jev-code@latest setup pi --provider custom
```

Enter a display name, HTTP(S) API base URL without `/v1/systemone`, manual provider model ID,
and hidden key. The gateway must implement the existing System One protocol. Alternatively,
export `JEV_CODE_PROVIDER=custom`, `JEV_CODE_PROVIDER_NAME`, `JEV_CODE_BASE_URL`,
`JEV_CODE_API_KEY`, and `JEV_CODE_MODEL` before setup. Custom retries default to zero; optional
`JEV_CODE_MAX_RETRIES` and `JEV_CODE_TIMEOUT_MS` overrides follow the selected configuration.

Pi reads these settings from its launching shell; setup does not store them in Pi settings.
Export the same variables before starting Pi, then restart or `/reload`. The direct CLI also
uses the shell. `--no-env` continues to omit keys from harness server environments; it does not
change Pi's shell-based configuration.

Use `--no-prompt` for complete environment-based setup. Incomplete custom settings prevent
registration, and `--dry-run` never prompts or writes. Restart the agent after setup and run
`jev-code doctor` to inspect configuration offline. See [custom-provider setup](../../README.md#custom-system-one-providers)
for URL examples and provider-specific key-variable mapping.
5 changes: 4 additions & 1 deletion integrations/opencode/jev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,8 @@ export const ask = define(askTool, {
}),
)
.describe("Question id to question; answers come back under the same ids."),
model: z.string().optional().describe("Model override. Default jev-latest."),
model: z
.string()
.optional()
.describe("Model override. Defaults to the configured provider model."),
});
6 changes: 4 additions & 2 deletions integrations/pi/jev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* pi at this directory) or by `jev-code setup pi`. The extension imports the package's
* own build, so the tool contract has a single source of truth: src/tools.
*
* Reads TYPESAFE_API_KEY from the shell pi runs in.
* Reads the selected provider settings from the shell pi runs in, including custom providers.
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";
Expand Down Expand Up @@ -132,7 +132,9 @@ const PARAMETERS: Record<ToolName, ReturnType<typeof Type.Object>> = {
description: "Question id to question; answers come back under the same ids.",
},
),
model: Type.Optional(Type.String({ description: "Model override. Default jev-latest." })),
model: Type.Optional(
Type.String({ description: "Model override. Defaults to the configured provider model." }),
),
}),
};

Expand Down
6 changes: 4 additions & 2 deletions skills/jev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ description: >-
a classifier, router, guardrail, or verifier built on TypeSafe's API or SDKs. Needs a
TypeSafe, OpenRouter, or Vercel AI Gateway API key.
license: MIT
compatibility: Requires Node.js 20+ and one API key in the environment (TYPESAFE_API_KEY, OPENROUTER_API_KEY, or AI_GATEWAY_API_KEY). Tools come from the jev-code MCP server, the Pi extension, or the jev-code CLI.
compatibility: Requires Node.js 20+ and one API key in the environment (TYPESAFE_API_KEY, OPENROUTER_API_KEY, or AI_GATEWAY_API_KEY), or explicit custom System One settings with JEV_CODE_API_KEY. Tools come from the jev-code MCP server, the Pi extension, or the jev-code CLI.
metadata:
author: FrancoisChastel
source: https://github.com/FrancoisChastel/jev-code
Expand Down Expand Up @@ -127,7 +127,9 @@ cookbook links, judgment design, and SDK snippets, adapted from TypeSafe's own s
- The API key is read from `TYPESAFE_API_KEY`, `OPENROUTER_API_KEY`, or `AI_GATEWAY_API_KEY`
(OpenAI's Decisions API only with `JEV_CODE_PROVIDER=openai`; a local Ollama with
`JEV_CODE_PROVIDER=ollama` and no key); `jev-code doctor` shows which host is in use. Never
print a key, and never paste one into chat.
print a key, and never paste one into chat. Custom System One gateways use
`JEV_CODE_PROVIDER=custom` with fixed `JEV_CODE_API_KEY` and explicit name/base/model;
see [custom setup](references/cli.md).

## Troubleshooting

Expand Down
15 changes: 15 additions & 0 deletions skills/jev/references/building-with-typesafe.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,18 @@ without limitation the rights to use, copy, modify, merge, publish, distribute,
sell copies of the Software, subject to the condition that the above copyright notice and this
permission notice are included in all copies or substantial portions of the Software. THE
SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.*

## Custom gateways in jev-code

When using this tool rather than a vendor SDK, `jev-code setup --provider custom` configures
any host implementing the existing System One protocol. Set a display name, HTTP(S) API base
without `/v1/systemone`, manual model ID, and fixed `JEV_CODE_API_KEY`. Provider SDK examples
may use another key variable; map the credential to this tool's variable without printing it.
For example, the [Experiential Jev page](https://platform.experientiallabs.ai/models/jev-latest)
documents base `https://api.experientiallabs.ai` and model `jev-latest`, while this tool reads
its credential from `JEV_CODE_API_KEY` rather than `EXPERIENTIAL_API_KEY`.

Custom retries default to zero; explicit `JEV_CODE_MAX_RETRIES` overrides that policy.
Settings are shared across CLI/MCP/Pi/OpenCode; Pi and CLI read their launching environment.
See [custom configuration](cli.md#custom-provider-configuration) for all variables and setup
controls. Vendor SDK configuration is separate; these jev-code variables do not configure it.
28 changes: 28 additions & 0 deletions skills/jev/references/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,31 @@ code 2 for input or configuration problems and 1 for API failures.
To wire the tools permanently, ask the user to run `jev-code setup` (or
`npx -y @french-castle/jev-code@latest setup`); it installs this skill and registers the tool in
every detected harness.

## Custom provider configuration

Use `jev-code setup --provider custom` in a terminal to enter a display name, API base URL,
manual provider model ID, and hidden key. Complete environment settings bypass prompts:

```bash
export JEV_CODE_PROVIDER=custom
export JEV_CODE_PROVIDER_NAME='My Gateway'
export JEV_CODE_BASE_URL='https://gateway.example/api'
export JEV_CODE_MODEL='vendor/model-id'
export JEV_CODE_API_KEY='<set locally>'
```

The gateway must speak System One. The base must be absolute HTTP(S), without the terminal
`/v1/systemone`, credentials, query, or fragment; the client appends that path, preserving
base prefixes. A model catalog page is not the API base. Use this tool's fixed key variable
regardless of the provider's own environment-variable name, and enter the model ID exactly.

Custom retries default to zero; `JEV_CODE_MAX_RETRIES` explicitly opts into retries.
`JEV_CODE_TIMEOUT_MS` retains the 30000 ms default. Ambient built-in keys are never borrowed;
legacy TypeSafe URL/model overrides are ignored with a note in custom mode.

`--no-prompt`/non-TTY require complete settings, `--dry-run` never prompts or writes,
`--no-tool` permits skill-only setup, and invalid/cancelled custom input does not register a
tool. Setup stores selected settings in supported harness environments; `--no-env` omits the
key. Pi and the CLI read the launching shell. `doctor` is offline; `doctor --live` makes an
explicit provider request. Never print or paste real credentials into chat.
14 changes: 14 additions & 0 deletions skills/jev/references/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ Shared conventions:
`gpt-6-luna` on OpenAI's Decisions API, `nimble` (or the model you pulled) on a local Ollama.
- Thresholds are optional inputs; the effective values are echoed under `thresholds`.

## Provider configuration

All five tools use the shared resolved provider. For a custom System One gateway, set
`JEV_CODE_PROVIDER=custom`, `JEV_CODE_PROVIDER_NAME`, `JEV_CODE_BASE_URL`, `JEV_CODE_API_KEY`,
and `JEV_CODE_MODEL`, or run `jev-code setup --provider custom`. The model ID is entered
manually and used verbatim after trimming; the optional request-level model override still
wins where supported. Results report the model ID returned by the host.

Use an HTTP(S) API base without the final `/v1/systemone` (the client appends it), credentials,
query, or fragment. Custom mode never borrows built-in keys or legacy URL/model overrides.
Custom retries default to zero; `JEV_CODE_MAX_RETRIES` is an explicit non-negative override.
Built-in retry defaults remain two; timeout defaults to 30000 ms through `JEV_CODE_TIMEOUT_MS`.
See [CLI setup details](cli.md#custom-provider-configuration) for harness and environment use.

## jev_classify

Label each item with exactly one class. One request, one Choice question per item.
Expand Down
2 changes: 1 addition & 1 deletion src/cli/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export interface ParsedArgs {
}

/** Flags that consume the next token as their value. Everything else is boolean. */
const VALUE_FLAGS = new Set(["input", "json", "command", "pi-source"]);
const VALUE_FLAGS = new Set(["input", "json", "command", "pi-source", "provider"]);

/** Tiny argv parser: `--key value`, `--key=value`, `--flag`, `--no-flag`, `-h`, `-v`. */
export function parseArgs(argv: readonly string[]): ParsedArgs {
Expand Down
15 changes: 8 additions & 7 deletions src/cli/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {
"",
);
lines.push("Configuration");
if (!config.hasApiKey) {
if (config.problem) {
ok = false;
lines.push(` ${"configuration".padEnd(22)} PROBLEM: ${config.problem}`);
} else if (!config.hasApiKey) {
ok = false;
lines.push(` ${"API key".padEnd(22)} NOT SET → export one of these, then run doctor again:`);
for (const provider of PROVIDERS) {
Expand All @@ -64,15 +67,13 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {
` ${"".padEnd(22)} ${variable.padEnd(20)} ${provider.label.padEnd(20)} ${provider.keysUrl}${optIn}`,
);
}
} else if (config.problem) {
ok = false;
lines.push(` ${"API key".padEnd(22)} PROBLEM: ${config.problem}`);
} else {
const key = config.keyEnv ? `${config.keyEnv} ${config.apiKeyHint}` : "no key";
lines.push(` ${"provider".padEnd(22)} ${config.providerLabel} (${key})`);
}
lines.push(` ${"base URL".padEnd(22)} ${config.baseUrl}`);
lines.push(` ${"model".padEnd(22)} ${config.model}`);
lines.push(` ${"retries".padEnd(22)} ${config.maxRetries}`);
for (const note of config.notes) lines.push(` ${"note".padEnd(22)} ${note}`);
lines.push("");

Expand Down Expand Up @@ -110,10 +111,10 @@ export async function runDoctor(options: DoctorOptions): Promise<DoctorReport> {
lines.push("");

if (options.live) {
if (!config.hasApiKey) {
lines.push("Live check: skipped, no API key.");
} else if (config.problem) {
if (config.problem) {
lines.push("Live check: skipped, fix the configuration problem above first.");
} else if (!config.hasApiKey) {
lines.push("Live check: skipped, no API key.");
} else {
const started = Date.now();
try {
Expand Down
Loading