Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
b5c04c8
Add a provider type for any Chat Completions server
azeemshaik025 Oct 11, 2026
7d3f994
Let providers be added and removed while Alan runs
azeemshaik025 Oct 11, 2026
29e4d01
Save custom providers and load them at startup
azeemshaik025 Oct 11, 2026
7980746
Add /provider to add, edit and remove Chat Completions providers
azeemshaik025 Oct 11, 2026
8b8490b
Start on the default model when the saved provider is gone
azeemshaik025 Oct 11, 2026
03fe9c8
Retire /local in favour of /provider
azeemshaik025 Oct 11, 2026
3c3933c
Document /provider
azeemshaik025 Oct 11, 2026
cade792
Apply /provider edit to the model already in use
azeemshaik025 Oct 11, 2026
6a602ee
Stop the docs saying every provider needs an API key or env variable
azeemshaik025 Oct 13, 2026
ef28e2b
Keep the reason when a server's model list does not parse
azeemshaik025 Oct 13, 2026
b45a2b0
Fetch a custom provider's models one request at a time, like the buil…
azeemshaik025 Oct 13, 2026
99a448a
Let the model picker only switch models, and remove a replaced provid…
azeemshaik025 Oct 13, 2026
4789cf5
Keep the startup provider fallback out of main.rs
azeemshaik025 Oct 13, 2026
c7e964a
Match the house style in the provider code and clean up its test files
azeemshaik025 Oct 13, 2026
c1331f0
Tell /provider and /model-providers apart in /help
azeemshaik025 Oct 13, 2026
27abc6e
Exit with a way back in when the saved provider is gone, not switch m…
azeemshaik025 Oct 13, 2026
a3a4197
Keep the original Provider not found error at startup
azeemshaik025 Oct 15, 2026
d2bbcd9
Keep built-in and custom providers in separate tracks of the registry
azeemshaik025 Oct 15, 2026
7fcb94b
Record each custom provider's API standard so others can follow Chat …
azeemshaik025 Oct 15, 2026
7d182a6
Let the built-in track reserve its own names, and suggest a free one
azeemshaik025 Oct 15, 2026
a62b767
Skip only the custom providers Alan cannot load, and say when a built…
azeemshaik025 Oct 15, 2026
b1dff6e
Save providers.json before the key, and take a new entry back if its …
azeemshaik025 Oct 15, 2026
4696433
Document the built-in and custom provider tracks
azeemshaik025 Oct 15, 2026
c8a7348
Keep unreadable providers.json entries without letting them block sav…
azeemshaik025 Oct 15, 2026
4036e49
Drop a custom entry a built-in now names, without touching the built-…
azeemshaik025 Oct 15, 2026
439243a
Stop AlanRoot carrying custom providers; ChatView builds them when /p…
azeemshaik025 Oct 15, 2026
e827919
Call OpenRouter's pinned providers upstream providers, and describe t…
azeemshaik025 Oct 15, 2026
3ae2464
Say a custom provider's key was rejected, as built-ins do
azeemshaik025 Oct 15, 2026
4e4573a
Keep the key error when taking a new entry back also fails
azeemshaik025 Oct 15, 2026
41dec56
Show empty and failed provider pickers the way profile pickers do
azeemshaik025 Oct 15, 2026
c531d5b
Delete code nothing reaches: NoAuth, ModelNotFound, unread results an…
azeemshaik025 Oct 15, 2026
23c8724
Drop tests another test already covers and share one fake server and …
azeemshaik025 Oct 15, 2026
5a4170a
Shorten the provider code: one form constructor, fewer derives and he…
azeemshaik025 Oct 15, 2026
27c253d
Say what failed and what to check when a provider's URL or key does n…
azeemshaik025 Oct 15, 2026
2a420d3
Show a text cursor in the provider form, like other inputs, instead o…
azeemshaik025 Oct 15, 2026
877fa1a
Bring in upstream v0.1.7: ChatGPT provider, headless mode and /fork-c…
azeemshaik025 Oct 15, 2026
31c6d30
Keep custom providers in one registry list and save them through one …
azeemshaik025 Oct 18, 2026
609a99e
Bring in upstream v0.1.8: word wrap, ssh copy fix and the new system …
azeemshaik025 Oct 18, 2026
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
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,21 @@ settings for future launches; manual settings changes clear its active-profile
marker. Profiles are stored separately in `<data dir>/profiles.json`. See
`core::paths` for how the data directory is resolved.

`build_providers` in `crates/alan/src/main.rs` lists the registered providers.
The `providers` crate also has a Google provider that is not registered. Sign in
with `/login`, or set the provider's API key variable:
`build_providers` in `crates/alan/src/main.rs` lists the built-in providers.
The `providers` crate also has a Google provider that is not registered. Sign
in with `/login`, or set the provider's API key variable:

```bash
OPENROUTER_API_KEY=... cargo run -p alan
```

`ProviderRegistry` holds the built-ins from `build_providers`, then the custom
providers added with `/provider add`; a built-in wins a name clash.
`core::custom_providers::CustomProviderStore` keeps a custom provider's name,
URL and API standard (`ApiId`) in `<data dir>/providers.json` and its key in
`auth.json`. `/provider` saves there first, then registers a
`providers::CustomProvider`. Only `ApiId::ChatCompletions` is implemented.

Optional model override:

```bash
Expand Down Expand Up @@ -143,7 +150,7 @@ auto-includes it in release archives.

`ChatView` (`views/components/chat_view/`) owns the chat session: the
`ChatController` and the agent stream. `PromptEditor` owns input. Overlays
such as login and local models live beside `root.rs`.
such as login and providers live beside `root.rs`.

Keep UI simple. Avoid borders, unnecessary widgets, and premature abstraction.

Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `/provider add`, `/provider edit` and `/provider remove` to use any Chat Completions server by name. Alan checks the server's `/models` before saving, lists all of its models in `/models`, and keeps the key in `auth.json` so `/login` can change it.

### Changed

- Update default system prompt to include output formatting.
Expand Down
34 changes: 18 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ A minimal coding agent in your terminal.
- [Providers](#providers)
- [Usage](#usage)
- [Permissions](#permissions)
- [Local models](#local-models)
- [Custom providers](#custom-providers)
- [Skills](#skills)
- [Configuration and data locations](#configuration-and-data-locations)
- [Building from source](#building-from-source)
- [Requirements](#requirements)

## Features

- **First-class OpenRouter support** — full model catalog, per-model custom provider pinning (`/model-providers`), and reasoning effort control (`/effort`).
- **First-class OpenRouter support** — full model catalog, per-model upstream provider pinning (`/model-providers`), and reasoning effort control (`/effort`).
- **Paste images** — drop a screenshot from your clipboard straight into the conversation and ask about it.
- **Compact with focus** — `/summarize-new "focus hint"` summarizes the context and restarts with just what matters.
- **Steering** — queue a message mid-run and the agent picks it up without waiting for the response to finish.
- **Plan and review modes** — cycle through them with `Shift+Tab` when you want the agent to think before it acts.
- **Local models** — point Alan at any OpenAI-compatible endpoint, no API key required.
- **Skills** — type `#name` in the prompt to attach a skill's full instructions to that message.
- **Custom providers** — add any Chat Completions server, hosted or on your machine, with `/provider add`; all of its models appear in `/models`.

## Installation

Expand Down Expand Up @@ -79,7 +79,9 @@ Alan currently supports:

Use the `/login` command to sign in to any of the available providers.

For OpenRouter, you can pin a custom provider for the selected model with `/model-providers`, followed by the provider name (no quotes):
Any other Chat Completions server can be added with `/provider add` (see [Custom providers](#custom-providers)).

For OpenRouter, you can pin an upstream provider for the selected model with `/model-providers`, followed by the provider name (no quotes):

- `/model-providers deepseek`
- `/model-providers xiaomi/fp8`
Expand All @@ -92,6 +94,9 @@ For OpenRouter, you can pin a custom provider for the selected model with `/mode
| `/profile` | Open the profile picker to switch profiles (`Ctrl+P`, or `Cmd+Option+P` where the terminal passes it through) |
| `/profile save <name>` | Save current model/provider, reasoning, and web settings as a new profile |
| `/profile delete` | Pick a saved profile to delete |
| `/provider add` | Add a Chat Completions provider: name, URL and optional API key |
| `/provider edit` | Change a provider's URL or API key |
| `/provider remove` | Remove a provider and its API key |
| `/plan` | Toggle plan mode (also `Shift+Tab`) |
| `/review` | Toggle review mode (also `Shift+Tab`) |
| `/normal` | Turn off plan and review mode |
Expand All @@ -105,8 +110,7 @@ For OpenRouter, you can pin a custom provider for the selected model with `/mode
| `/tool-free` | Allow all tool calls without asking |
| `/tool-slip` | Approve a command family once, allow its siblings |
| `/tool-strict` | Allow only exact commands already approved |
| `/local` | Manage local models (add, remove, edit) |
| `/model-providers <name>` | Pin a custom provider for the current OpenRouter model |
| `/model-providers <name>` | Pin an upstream provider for the current OpenRouter model |
| `/help` | List available commands |
| `/quit` | Exit Alan |

Expand Down Expand Up @@ -148,24 +152,22 @@ Alan starts in strict policy and remembers the policy you pick across runs. Swit
- `/tool-free`
- `/tool-strict`

## Local models
## Custom providers

Alan supports OpenAI-compatible local models.
Any server that speaks the Chat Completions API can be a provider: hosted APIs such as Groq, xAI or Mistral, or a model server on your machine such as Ollama or LM Studio.

```bash
/local add
```
`/provider add` asks for a name (not one a built-in provider already uses), the base URL (the part before `/chat/completions`, e.g. `https://api.groq.com/openai/v1` or `http://localhost:11434/v1`) and an API key if the server needs one. Alan calls `<url>/models` to check the URL and key, and saves the provider only when that call works. Every model the server lists then appears in `/models`, including ones it adds later.

This opens an overlay with fields for URL, API, API Key, and Model ID. Alan queries the server's `/models` endpoint and offers the detected models in a searchable picker. Use `/local remove` and `/local edit` to manage your local models. No API key or `/login` is needed for local models.
The key is stored in `auth.json` with the other providers' keys, so `/login` can change it too. `/provider edit` changes the URL or key; the name is fixed. `/provider remove` deletes the provider and its key. If one of its models is in use, Alan first asks which model to switch to.

## Configuration and data locations

Alan stores everything under `~/.alan/` by default:

- `settings.json` — your current settings and active-profile marker.
- `profiles.json` — saved named model/reasoning/web settings profiles.
- `auth.json` — provider credentials saved by `/login`.
- `local_models.json` — local models added with `/local`.
- `auth.json` — provider credentials saved by `/login` and `/provider`.
- `providers.json` — providers added with `/provider add`.
- `projects/` — per-project permission grants.
- `sessions/` — conversation history (append-only JSONL, one file per session).
- `skills/` — personal skills, available in every project.
Expand All @@ -180,7 +182,7 @@ Settings variables override `settings.json` for that run without changing it. St
| Variable | Effect |
| --- | --- |
| `ALAN_HOME` | Parent of the data directory: `ALAN_HOME=/tmp/x` puts everything in `/tmp/x/.alan`. |
| `ALAN_PROVIDER` | Provider id: `openrouter`, `deepseek`, `zai`, or `local`. |
| `ALAN_PROVIDER` | Provider id: `openrouter`, `deepseek`, `zai`, `chatgpt`, or a name added with `/provider add`. |
| `ALAN_MODEL` | Model id for that provider, e.g. `ALAN_MODEL=openai/gpt-4o-mini alan`. |
| `ALAN_PROFILE` | Select a saved profile by name for this run without changing `settings.json`. |
| `ALAN_REASONING_EFFORT` | `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`. |
Expand Down Expand Up @@ -247,4 +249,4 @@ cargo run -p alan
## Requirements

- Rust 1.88 or newer (only for building from source)
- An API key for your chosen provider (not needed for local models)
- An API key for your chosen provider, if it needs one
44 changes: 12 additions & 32 deletions crates/alan/src/core/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ pub enum SlashCommand {
ToolFree,
ToolSlip,
ToolStrict,
// Manage local models (add, remove, edit).
Local,
Profile,
Provider,
Quit,
}

Expand Down Expand Up @@ -87,8 +86,8 @@ impl SlashCommand {
| Self::ModelProviders
| Self::SummarizeNew
| Self::Rename
| Self::Local
| Self::Profile
| Self::Provider
)
}

Expand All @@ -107,13 +106,13 @@ impl SlashCommand {
"fork the whole session into a new file and print a command to open it"
}
Self::Help => "list the available commands",
Self::ModelProviders => "pick a provider from openrouter for the selected model",
Self::ModelProviders => "openrouter only: pick which upstream serves this model",
Self::Rename => "rename the current session",
Self::ToolFree => "allow all tool calls without asking",
Self::ToolSlip => "approve a command family once, allow its siblings",
Self::ToolStrict => "allow only exact commands already approved",
Self::Local => "manage local models (add, remove, edit)",
Self::Profile => "save and switch named model profiles",
Self::Provider => "add, edit or remove your own Chat Completions server",
Self::Quit => "abort and quit",
}
}
Expand Down Expand Up @@ -347,7 +346,6 @@ mod tests {
assert!(!SlashCommand::Normal.takes_args());
assert!(!SlashCommand::Help.takes_args());
// Argument-taking commands.
assert!(SlashCommand::Local.takes_args());
assert!(SlashCommand::Effort.takes_args());
assert!(SlashCommand::ModelProviders.takes_args());
assert!(SlashCommand::SummarizeNew.takes_args());
Expand All @@ -369,31 +367,13 @@ mod tests {
}

#[test]
fn parses_local_command() {
assert_eq!(SlashCommand::parse("/local"), Some(SlashCommand::Local));
}

#[test]
fn parses_local_subcommands() {
assert_eq!(
SlashCommand::parse_with_args("/local add"),
Some((SlashCommand::Local, "add"))
);
assert_eq!(
SlashCommand::parse_with_args("/local remove"),
Some((SlashCommand::Local, "remove"))
);
assert_eq!(
SlashCommand::parse_with_args("/local edit"),
Some((SlashCommand::Local, "edit"))
);
}

#[test]
fn parses_local_with_empty_args() {
assert_eq!(
SlashCommand::parse_with_args("/local"),
Some((SlashCommand::Local, ""))
);
fn parses_provider_subcommands() {
for sub in ["add", "edit", "remove"] {
assert_eq!(
SlashCommand::parse_with_args(&format!("/provider {sub}")),
Some((SlashCommand::Provider, sub))
);
}
assert!(SlashCommand::Provider.takes_args());
}
}
Loading
Loading