Skip to content
Merged
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,58 @@ Supply credentials through a secured environment or protected files, not secret-
| Default environment variable | Purpose |
| --- | --- |
| `JEV_API_KEY` | Upstream Jev API access for decisions and the MCP server's backend |
| `GOOGLE_API_KEY`, then `GEMINI_API_KEY` | Google Gemini Decisions API access when the `gemini` provider is selected |
| `JEVWISE_MCP_TOKEN` | Optional override for local MCP authentication; otherwise generated and privately stored |

### Providers

| Provider | Service | Key |
| --- | --- | --- |
| `jev` (default) | TypeSafe Jev API | `JEV_API_KEY` |
| `gemini` | Google Gemini Decisions API (`gemini-decision-1`) | `GOOGLE_API_KEY`, then `GEMINI_API_KEY` |
| `codex`, `claude` | Reserved, not implemented | |

Select one per call with `--provider gemini`, or for every call with `provider = "gemini"` under `[general]` in your configuration. Each provider reads only its own keys, so a Jev key never reaches Google and the reverse. See [configuration](docs/configuration.md#gemini-decisions-provider).

### Upstream API key

Set `JEV_API_KEY` in your secured environment, or reference **any exported variable** in your selected TOML configuration:

```toml
[provider.jev]
api_key = "$ABCD_TOKEN"
# Equivalent: api_key = "${ABCD_TOKEN}"
```

- `ABCD_TOKEN` is only an example; custom names such as `JEV_MCP_API_KEY` also work. Export the referenced variable before starting Jevwise.
- Only TOML `api_key` expands environment references. Unset variables expand to empty; Jev service initialization requires a nonblank key.
- Only the TOML `api_key` expands environment references. Unset variables expand to empty; Jev service initialization requires a nonblank key.
- Changed `--api-key` flags override nonblank API environment values, which override TOML. Direct environment and flag values stay literal.
- API fallback names, in order: `JEVWISE_API_KEY`, `TS_JEV_API_KEY`, `TYPESAFE_API_KEY`. The standalone Go SDK retains `TYPESAFE_API_KEY` as its environment fallback. See [configuration](docs/configuration.md).

### Google API key

Jevwise sends the key in the `x-goog-api-key` header; it does not use OAuth or Application Default Credentials (ADC) at runtime. Get a key in one of two ways:

| Way | Best for | How |
| --- | --- | --- |
| 1. Personal (AI Studio) | Trying it out, personal use | Sign in at [aistudio.google.com/api-keys](https://aistudio.google.com/api-keys) and create a key. AI Studio sets up a Google Cloud project if you have none. |
| 2. Organization project (Terraform) | Teams, billing and key restrictions under an organization | Use ADC once to let Terraform enable the Gemini API, link billing and create a key restricted to `generativelanguage.googleapis.com`. Follow [Google API key via Terraform](docs/gcp_adc.md). |

```shell
export GEMINI_API_KEY='...' # or GOOGLE_API_KEY, which wins if both are set
jevwise decide --provider gemini --prompt "What is 2 + 2?" --option 4 --option 5
```

- Restrict the key to the Generative Language API, and keep it out of command arguments and stored files, like the Jev key.
- A paid project needs a positive Gemini prepaid balance in AI Studio; at $0 its keys stop working. See the [Terraform guide](docs/gcp_adc.md#7-buy-gemini-prepaid-credits).
- Work accounts may block key creation by organization policy; the error names the constraint.

### Local MCP authentication

- Use a separate bearer token: **never reuse the upstream API key or give it to the agent**.
- With no token override, `jevwise mcp` generates a random token in `<runtime-dir>/agent.token` and reuses it across restarts. Only your upstream API key needs manual setup.
- `jevwise mcp token-path` prints the automatic file path, never the secret; see [agent onboarding](docs/mcp-agents.md) to load it into the client environment.
- Register the installed client with `jevwise mcp install codex` or `jevwise mcp install claude`. Both default to user-wide settings; custom ports use `--listen IP:PORT` or `mcp_port` in [configuration](docs/configuration.md). Registration stores only an environment reference, not the token. See [agent onboarding](docs/mcp-agents.md#3-choose-a-client).
- Register the installed client with `jevwise mcp install codex` or `jevwise mcp install claude`. Both default to user-wide settings; custom ports use `--listen IP:PORT` or `port` under `[mcp]` in [configuration](docs/configuration.md). Registration stores only an environment reference, not the token. See [agent onboarding](docs/mcp-agents.md#3-choose-a-client).
- Overrides take precedence: `--token-file /path/to/protected/agent-token` > `JEVWISE_MCP_TOKEN` (including aliases) > automatic token. Explicit values are never copied into the automatic file.
- MCP fallback names, in order: `JEVWISE_MCP_AUTH_TOKEN`, `JEV_MCP_TOKEN`. Invalid nonempty tokens fail without fallback; tokens must be printable ASCII without whitespace.
- Keep secrets out of shell history, logs and stored agent configuration. Follow [local MCP setup](docs/mcp.md) and [agent onboarding](docs/mcp-agents.md) for protected files and client environment references.
Expand All @@ -88,7 +118,8 @@ api_key = "$ABCD_TOKEN"
| Guide | Contents |
| --- | --- |
| [CLI usage](docs/usage.md) | Decisions and skill setup |
| [Configuration](docs/configuration.md) | Precedence, API keys, local config and editor |
| [Configuration](docs/configuration.md) | Precedence, API keys, providers, local config and editor |
| [Google API key via Terraform](docs/gcp_adc.md) | Organization project key for the Gemini provider |
| [Go SDK](docs/sdk.md) | Quickstart, request options and retries |
| [Development](docs/development.md) | Source builds, platform builds and tests |
| [Debugging](docs/debug.md) | Debug logging and safe diagnostics |
Expand Down
28 changes: 19 additions & 9 deletions config.example.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
# Optional CLI configuration. Prefer an environment variable for credentials.
# Copy to ~/.config/ts-jev/config.toml, or select with --config.
api_key = ""
base_url = "https://api.typesafe.ai"
model = "jev-latest"
# Copy to ~/.config/jevwise/jevwise.toml, or select with --config.
[general]
provider = "jev"
timeout = "10s"
editor = "vim"
# Local MCP server: literal loopback IP (no scheme) and port.
mcp_base_url = "127.0.0.1"
mcp_port = 8080
# Structured debug logs on stderr; --debug or --verbose overrides.
debug = false
editor = "vim"

# Local MCP server: literal loopback IP (no scheme) and port.
[mcp]
host = "127.0.0.1"
port = 8080

[provider.jev]
api_key = "$JEV_API_KEY"
base_url = "https://api.typesafe.ai"
model = "jev-latest"
timeout = "10s"

# Google Gemini Decisions: select with provider = "gemini" or --provider gemini.
[provider.gemini]
api_key = "$GEMINI_API_KEY"
model = "gemini-decision-1"
91 changes: 76 additions & 15 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,87 @@

Precedence: **changed flags > nonblank environment > TOML > defaults**. These names apply to the CLI; the standalone Go SDK retains its `TYPESAFE_API_KEY` fallback.

| Setting | Flag | Environment | Default |
Settings are grouped into tables. `[general]` selects the provider, which reads its settings from `[provider.<name>]` (`jev`, `gemini`, `codex` or `claude`); other provider tables are ignored.

```toml
[general]
provider = "jev"
debug = false
editor = "vim"

[mcp]
host = "127.0.0.1"
port = 8080

[provider.jev]
api_key = "$JEV_API_KEY"
base_url = "https://api.typesafe.ai"
model = "jev-latest"
timeout = "10s"
```

| TOML key | Flag | Environment | Default |
| --- | --- | --- | --- |
| API key | `--api-key` | `JEV_API_KEY`; aliases: `JEVWISE_API_KEY`, `TS_JEV_API_KEY`, `TYPESAFE_API_KEY` | Required for Jev |
| Base URL | `--base-url` | `TS_JEV_BASE_URL`, then `TYPESAFE_BASE_URL` | `https://api.typesafe.ai` |
| Model | `--model` | `TS_JEV_MODEL`, then `TYPESAFE_DEFAULT_MODEL` | `jev-latest` |
| Provider | `--provider` | `TS_JEV_PROVIDER` | `jev` |
| Operation timeout | `--timeout` | `TS_JEV_TIMEOUT` | `10s` |
| MCP host | `--listen` (with port) | `TS_JEV_MCP_BASE_URL` | `127.0.0.1` |
| MCP port | `--listen` (with host) | `TS_JEV_MCP_PORT` | `8080` |
| Debug logging | `--debug`, `--verbose` | `TS_JEV_DEBUG` | `false` |

- `mcp_base_url` is a literal loopback IP without scheme or port (`127.0.0.1`, `::1`); `http://`, `localhost` and non-loopback hosts fail. `mcp_port` is an integer from 1 to 65535 (`mcp_port = 8080` or `"8080"`). `jevwise mcp` and `jevwise mcp install` read only these two keys for the address; a changed `--listen` overrides both, so `jevwise mcp --listen 127.0.0.1:9090` equals `jevwise mcp` with `mcp_port = 9090`.
| `general.provider` | `--provider` | `TS_JEV_PROVIDER` | `jev` |
| `general.debug` | `--debug`, `--verbose` | `TS_JEV_DEBUG` | `false` |
| `general.editor` | `config edit --editor` | Not read | `vim` |
| `mcp.host` | `--listen` (with port) | `TS_JEV_MCP_BASE_URL` | `127.0.0.1` |
| `mcp.port` | `--listen` (with host) | `TS_JEV_MCP_PORT` | `8080` |
| `provider.<name>.api_key` | `--api-key` | See provider table below | Required for Jev and Gemini |
| `provider.<name>.base_url` | `--base-url` | See provider table below | See provider table below |
| `provider.<name>.model` | `--model` | See provider table below | See provider table below |
| `provider.<name>.timeout` | `--timeout` | `TS_JEV_TIMEOUT` | `10s` |

Provider environment names and defaults. Each provider reads only its own names, so a Jev key or URL never reaches Gemini, and the reverse:

| Provider | API key environment | Base URL environment, default | Model environment, default |
| --- | --- | --- | --- |
| `jev` (also `codex`, `claude`) | `JEV_API_KEY`; aliases: `JEVWISE_API_KEY`, `TS_JEV_API_KEY`, `TYPESAFE_API_KEY` | `TS_JEV_BASE_URL`, then `TYPESAFE_BASE_URL`; `https://api.typesafe.ai` | `TS_JEV_MODEL`, then `TYPESAFE_DEFAULT_MODEL`; `jev-latest` |
| `gemini` | `GOOGLE_API_KEY`, then `GEMINI_API_KEY` (as in google-genai) | `GOOGLE_GEMINI_BASE_URL`; `https://generativelanguage.googleapis.com` | None; `gemini-decision-1` |

- Changed flags apply to the selected provider. Other environment names are unchanged from the flat layout, so `TS_JEV_MCP_BASE_URL` still sets `mcp.host`.

### Gemini Decisions provider

Select Google's [Gemini Decisions API](https://blog.google/innovation-and-ai/technology/developers-tools/gemini-decisions-api/) (`gemini-decision-1`) for `decide` and MCP:

```toml
[general]
provider = "gemini"

[provider.gemini]
api_key = "$GEMINI_API_KEY"
```

- Or select it per call with `--provider gemini`, keeping the Jev settings in place. The key comes from `GOOGLE_API_KEY`, then `GEMINI_API_KEY`, then TOML; never from Jev names.
- Each decision sends `POST /v1beta/decisions` with one `choice` question: the prompt is the shared `input`, every option is a criterion with an empty description, and a fixed instruction asks for the best-fitting option. At most 255 options.
- The response carries no model name, so output reports the configured model. Probabilities and confidence are returned as received, never normalized.
- Inputs blocked by Google's content filters fail the decision. Upstream error messages are not shown or logged, as they may echo input.
- The endpoint is `v1beta`: Google may change it. Ordinary decisions bill input tokens to your Google account.
- `mcp.host` is a literal loopback IP without scheme or port (`127.0.0.1`, `::1`); `http://`, `localhost` and non-loopback hosts fail. `mcp.port` is an integer from 1 to 65535 (`port = 8080` or `"8080"`). `jevwise mcp` and `jevwise mcp install` read only these two keys for the address; a changed `--listen` overrides both, so `jevwise mcp --listen 127.0.0.1:9090` equals `jevwise mcp` with `port = 9090` under `[mcp]`.

### Upgrading a flat configuration

Files from earlier releases put every key at the top level (`api_key = ...`, `mcp_port = ...`). They are no longer read: commands that load settings fail and ask you to migrate. Convert the selected file once:

```sh
jevwise config migrate # selected local or global file
jevwise config migrate --config PATH # an explicit file
```

- Keys move to their tables unchanged: `$NAME` references stay literal and values are not validated, so you can fix them afterwards with `jevwise config edit`. `mcp_base_url` becomes `mcp.host` and `mcp_port` becomes `mcp.port`.
- The original is kept beside the file as `<file>.flat.bak` (owner-only). It may contain your API key: delete it once the migrated file is correct. Migration refuses to run if that backup exists, and refuses links, unknown keys, files mixing both layouts, and values other than strings, integers or booleans; errors name keys, never values.
- Comments are not carried over. A file already using tables is left unchanged.
- `config edit`, `version` and `mcp status`/`stop` still run on a flat file, so it can be repaired or migrated.
- Explicit file: `--config /path/to/config.toml`. Missing or invalid explicit files fail.
- Otherwise: current-directory `.jevwise.toml`, then `$XDG_CONFIG_HOME/jevwise/jevwise.toml` or `$HOME/.config/jevwise/jevwise.toml`. Only a missing global file is allowed. The selected local file replaces rather than merges with global settings; no ancestor-directory search or visible-name fallback.
- On Windows, the default global path is `%USERPROFILE%\.config\jevwise\jevwise.toml` when `XDG_CONFIG_HOME` is unset.
- Quoted `--config "~/.config/jevwise/jevwise.toml"` paths expand the current user's home directory for init, view, edit and runtime loading. `~otheruser` and shell/environment evaluation of paths are not supported. An XDG path beginning with `~/` is supported too.
- Trust the current directory before running decision/MCP commands: local settings can change the upstream destination. Environment/changed flags still win. Invalid, oversized (over 1 MiB), linked or nonregular local files fail without global fallback; help/version do not load them, except that `version` and other commands read only the `debug` key and ignore an invalid or unreadable file.
- Start with [config.example.toml](../config.example.toml). Prefer environment credentials rather than command-line secrets.
- TOML `api_key` supports `$NAME`/`${NAME}`, for example `api_key = "$JEV_API_KEY"` or `api_key = "${JEV_MCP_API_KEY}"`. Runtime loading expands references and trims the result; undefined variables become empty.
- TOML `provider.<name>.api_key` supports `$NAME`/`${NAME}`, for example `api_key = "$JEV_API_KEY"` or `api_key = "${JEV_MCP_API_KEY}"`. Runtime loading expands references and trims the result; undefined variables become empty.
- API environment names above are checked left to right, skipping blank values. `JEV_API_KEY` is for the upstream API; `JEVWISE_MCP_TOKEN` is separate local MCP authentication. Never reuse the same credential.
- TOML `api_key` supports `$NAME`/`${NAME}`, for example `api_key = "${MY_JEV_KEY}"`. Runtime loading expands references and trims the result; undefined variables become empty.
- TOML `provider.<name>.api_key` supports `$NAME`/`${NAME}`, for example `api_key = "${MY_JEV_KEY}"`. Runtime loading expands references and trims the result; undefined variables become empty.
- Custom environment names work through TOML references, for example `api_key = "${JEV_MCP_API_KEY}"`. Export the variable in the process environment before starting Jevwise. Exporting `JEV_MCP_API_KEY` alone is not an automatic API-key override; nonblank `TS_JEV_API_KEY`/`TYPESAFE_API_KEY` still take precedence over the file.
- Local discovery never loads `config.toml`, `configs.toml`, YAML files or ancestor configs. Only `.jevwise.toml` is a local candidate; visible `jevwise.toml` is not auto-loaded. Legacy `ts-jev/config.toml` is no longer discovered: migrate manually or select it with `--config`. An explicit `--config PATH` can still select another trusted filename.
- `JEV_MCP_API_KEY` is your chosen upstream-key variable when referenced by `api_key`; it is not the separate MCP agent bearer token (`JEV_MCP_TOKEN` or `--token-file`).
Expand All @@ -41,11 +101,12 @@ jevwise config view # Selected file/defaults, API key redacted
jevwise config view --config /path/to/config.toml
jevwise config edit # Selected local/global file, vim by default
jevwise config edit --editor nvim # One-shot override, also for repairs
jevwise config migrate # Convert a flat file to grouped tables
```

- Init never overwrites existing files, directories or links. It writes a template, not your environment credentials; Unix file permissions are requested as `0600`, new parent directories as `0700`. Use a trusted parent with safe ownership/ACLs: mode bits alone do not guarantee privacy, including inherited Unix ACLs. Windows users must manage ACLs separately.
- `--config PATH` also selects an init destination; do not combine it with `--local` or pass a blank path. View ignores environment/decision flag overrides, unknown keys and comments; invalid files fail safely. It is not a dump of effective runtime settings. Only the `api_key` field is automatically redacted; do not store credentials in other displayed settings.
- Set `editor = "nvim"` (or another executable name/path) in TOML to change the editor. Selection is `config edit --editor` > selected file's `editor` > `vim` on PATH; `$EDITOR`/`$VISUAL` are not read. No editor is bundled and no shell is used. Arguments are not parsed: use a trusted wrapper executable if options such as `--wait` are needed.
- `--config PATH` also selects an init destination; do not combine it with `--local` or pass a blank path. View prints the `[general]`, `[mcp]` and selected `[provider.<name>]` tables. It ignores environment/decision flag overrides, unknown keys, other provider tables and comments; invalid files fail safely. It is not a dump of effective runtime settings. Only the `api_key` field is automatically redacted; do not store credentials in other displayed settings.
- Set `editor = "nvim"` (or another executable name/path) under `[general]` to change the editor. Selection is `config edit --editor` > selected file's `general.editor` (or a flat file's `editor`) > `vim` on PATH; `$EDITOR`/`$VISUAL` are not read. No editor is bundled and no shell is used. Arguments are not parsed: use a trusted wrapper executable if options such as `--wait` are needed.
- Edit forwards terminal input/output and waits for the direct editor process. It uses the command context, not the decision timeout; missing editors/nonzero exits fail safely. Invalid TOML falls back to Vim for repair; wrong-typed editor settings or oversized files can be repaired using `--editor`. It refuses missing files, directories and final symlinks.
- Go-copy pipe waits are bounded after process exit/cancellation. Only the direct editor process is managed, not wrapper descendants; arbitrary blocking custom readers/writers cannot be forcibly interrupted.
- Trust local editor settings and wrappers before editing: they select a program to execute. Editor terminal output intentionally may show the file you are editing. macOS/Linux support assumes an installed editor; no Windows editor availability/behavior is promised.
Expand Down
Loading
Loading