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
72 changes: 72 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1255,3 +1255,75 @@ Some commands are available only under certain account or platform conditions:
### Feature-Flagged Commands

Some commands check `isEnabled()` at runtime. For example, voice-related commands check for audio device availability; the desktop command checks for a display server.

## Bang Commands (`!`)

Execute shell commands directly from the input prompt without going through
the model. Zero token consumption. Output is display-only — the model never
sees it.

### Usage

```
! ls -la
! git status
! echo "hello"
```

Type `!` followed by a shell command. The command runs via `bash -c` in the
project working directory and the result is shown inline in the transcript.

### Configuration (`settings.json`)

```json
"bangCommands": {
"enabled": true,
"addToHistory": true,
"showInTranscript": true
}
```

| Option | Default | Description |
|--------|---------|-------------|
| `enabled` | `false` (opt-in) | Toggle the feature |
| `addToHistory` | `false` | Store `!` commands in a separate shell-command history |
| `showInTranscript` | `true` | Display command + output in the chat transcript |

### Notes

- Output is display-only — the model never sees it (zero token consumption).
- Blocked in plan mode (read-only restriction).
- Uses `bash -c` for execution (not the PTY-based Bash tool).
- `!!` (double bang) is NOT treated as a bang command.
- A single `!` with no command is ignored.

## `/yolo` — Toggle YOLO Mode

Toggle YOLO mode, which skips all permission prompts and auto-approves every
tool call. When enabled, the effective permission mode is set to
`bypassPermissions`. The setting persists in `settings.json` under
`"yoloMode"` and is restored on the next session.

```
/yolo — toggle YOLO mode on/off
```

YOLO mode propagates to sub-agents: they inherit the parent's permission mode
via the cloned tool context. You can also enable YOLO for a single session via
the CLI flag `--dangerously-skip-permissions` (alias `--yolo`).

## `/poke` — Toggle Auto-Poke

Toggle or configure the auto-poke feature, which automatically sends a
continuation prompt when the model stops with incomplete todos.

```
/poke — toggle auto-poke on/off
/poke on — enable auto-poke
/poke off — disable auto-poke
/poke status — show current status, budget, and incomplete todo count
```

Auto-poke has a safety budget of 48 pokes per session and stops after 3
consecutive no-progress turns. The setting persists in `settings.json` under
`"autoPokeEnabled"` (default: `true`).
98 changes: 98 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ values. Keys absent from the project file fall back to the global value.
"config": { ... },
"providers": { ... },
"modelOverrides": { ... },
"favoriteModels": [ ... ],
"projects": { ... },
"commands": { ... },
"formatter": { ... },
Expand Down Expand Up @@ -79,6 +80,7 @@ The `config` object holds runtime behaviour options.
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `permission_mode` | string | `"default"` | Controls how tool permissions are enforced. One of `"default"`, `"acceptEdits"`, `"bypassPermissions"`, `"plan"`. |
| `yoloMode` | boolean | false | When enabled, sets `permission_mode` to `bypassPermissions` automatically. All tool calls are auto-approved without prompts. Sub-agents inherit the parent's permission mode, so YOLO mode propagates to them. Toggle at runtime with `/yolo` or pass `--dangerously-skip-permissions` (`--yolo`) on the CLI for a session-only override. |

See [Permission Modes](#permission-modes) for a full description of each value.

Expand Down Expand Up @@ -380,6 +382,69 @@ and `api_base` override the corresponding environment variables.
| `models_blacklist` | array | These model IDs are never offered. |
| `options` | object | Provider-specific passthrough options. |

### Custom OpenAI-Compatible Providers

For OpenAI-compatible endpoints not in the built-in provider list (e.g.
self-hosted gateways, internal LLM proxies), define them under the
`customProviders` map. Each entry is a self-contained provider with its
own base URL, API key, custom headers, and model catalog.

```json
"customProviders": {
"my-gateway": {
"name": "My Gateway",
"apiBase": "https://gateway.example.com/v1",
"apiKey": "{env:GATEWAY_API_KEY}",
"headers": {
"X-Custom-Header": "value"
},
"models": {
"model-1": {
"name": "Model One",
"contextWindow": 128000,
"maxOutputTokens": 8192,
"reasoningEffort": "high",
"variants": {
"max": { "reasoningEffort": "max" },
"none": { "reasoningEffort": "none" }
}
}
},
"requestTimeoutSecs": 300
}
}
```

`CustomProviderDef` fields:

| Field | Type | Description |
|-------|------|-------------|
| `name` | string | Display name shown in the provider picker. |
| `apiBase` | string | OpenAI-compatible base URL. Claurst appends `/chat/completions`. |
| `apiKey` | string \| null | API key. Supports `{env:VAR}` substitution. `null` = no key. |
| `headers` | object | Custom HTTP headers sent on every request. |
| `models` | object | Model catalog local to this provider, keyed by model id. |
| `requestTimeoutSecs` | number \| null | Per-provider request timeout override in seconds. |

`CustomModelDef` fields (inside `models`):

| Field | Type | Description |
|-------|------|-------------|
| `name` | string \| null | Display name shown in the model picker. |
| `contextWindow` | number \| null | Total context window size in tokens. |
| `maxOutputTokens` | number \| null | Maximum tokens the model can emit in one response. |
| `reasoningEffort` | string \| null | Reasoning effort level (`"high"`, `"max"`, `"none"`). |
| `variants` | object | Named variants that override specific fields. |

Adding a provider via the `/add` command:

```
/add my-gateway https://gateway.example.com/v1 {env:GATEWAY_API_KEY}
```

The map key (e.g. `"my-gateway"`) becomes the provider id used in
`provider/model` routing (e.g. `my-gateway/model-1`).

---

## Environment Variables
Expand Down Expand Up @@ -639,6 +704,13 @@ matches. They are defined in the `formatter` map:
}
},

// Pin frequently-used models to the top of the /model picker.
"favoriteModels": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-4o",
"nvidia/z-ai/glm-5.2"
],

// Custom slash commands
"commands": {
"test": {
Expand All @@ -657,3 +729,29 @@ matches. They are defined in the `formatter` map:
}
}
```

---

## Favorite Models

Pin frequently-used models to the top of the `/model` picker by adding them to
the `favoriteModels` array in `settings.json`:

```json
"favoriteModels": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-4o",
"nvidia/z-ai/glm-5.2"
]
```

Entries use the canonical `"provider/model"` format (the same key used by
`modelOverrides`). For the `anthropic` and `free` composite providers, the
provider prefix is optional — the bare model id (`"claude-sonnet-4-6"`) is
accepted too.

In the model picker, press `f` (or `*`) to toggle favorite status on the
highlighted model. Favorited models appear with a ★ prefix at the top of the
list and persist across sessions in `~/.claurst/settings.json`. Stale
favorites (models no longer in the catalog) are hidden from the picker but
kept in settings until you un-favorite them.
26 changes: 26 additions & 0 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,29 @@ When the keyed model exists in the catalog, the override patches it in place.
When it does not (a self-hosted alias), Claurst materialises a synthetic entry
so the corrected values flow everywhere the metadata is read: the `/model`
picker, the token-usage warnings, and the auto-compact thresholds.

## Cursor ACP (`cursor-acp`)

Connect to Cursor's CLI agent via the Agent Client Protocol (ACP).

### Setup

1. Install Cursor CLI (`agent` must be on PATH)
2. Set `cursor-acp` as your provider:
```json
{
"provider": "cursor-acp"
}
```

### Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `CLAURST_CURSOR_ACP_PATH` | `agent` | Path to the Cursor CLI executable |
| `CLAURST_CURSOR_ACP_ARGS` | `acp` | ACP subcommand argument |
| `CLAURST_CURSOR_ACP_EXTRA_ARGS` | `--force --trust` | Permission arguments before `acp` |
| `CLAURST_CURSOR_ACP_MODEL` | (empty) | Default model ID |

The provider spawns `agent --force --trust acp` as a subprocess and
communicates via newline-delimited JSON-RPC 2.0 over stdin/stdout.
60 changes: 60 additions & 0 deletions src-rust/crates/api/src/model_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1243,6 +1243,66 @@ impl ModelRegistry {
}
apply_overrides_to_entries(&mut self.entries, &self.overrides);
}

/// Register user-defined custom provider models into the registry.
/// Each model is keyed as `<provider_id>/<model_id>` and a synthetic
/// `ModelEntry` is created from the `CustomModelDef` metadata.
/// A `ProviderEntry` is also created for each custom provider so it
/// appears in `/providers` listing.
pub fn apply_custom_providers(
&mut self,
custom_providers: &HashMap<String, claurst_core::config::CustomProviderDef>,
) {
for (provider_id, def) in custom_providers {
// Register the provider entry so it shows up in /providers.
self.providers.entry(provider_id.clone()).or_insert(ProviderEntry {
id: ProviderId::new(provider_id),
name: def.name.clone(),
env: Vec::new(),
api: Some(def.api_base.clone()),
npm: None,
doc: None,
});

for (model_id, model_def) in &def.models {
let key = format!("{}/{}", provider_id, model_id);
self.entries.insert(key, ModelEntry {
info: ModelInfo {
id: ModelId::new(model_id),
provider_id: ProviderId::new(provider_id),
name: model_def.name.clone().unwrap_or_else(|| model_id.to_string()),
context_window: model_def.context_window.unwrap_or(0),
max_output_tokens: model_def.max_output_tokens.unwrap_or(0),
release_date: None,
status: None,
},
family: None,
status: Default::default(),
release_date: None,
last_updated: None,
knowledge: None,
open_weights: false,
tool_calling: true,
reasoning: model_def.reasoning_effort.is_some(),
structured_output: false,
temperature: true,
attachment: false,
interleaved: None,
modalities_input: vec![Modality::Text],
modalities_output: vec![Modality::Text],
cost_input: None,
cost_output: None,
cost_cache_read: None,
cost_cache_write: None,
cost: Default::default(),
provider_override: None,
experimental_modes: Default::default(),
options: Default::default(),
headers: Default::default(),
});
}
}
}
}

/// Layer `overrides` onto `entries`: patch existing catalog rows in place and
Expand Down
Loading
Loading