13 Configuration
Thomas Forgione edited this page 2026-09-15 12:05:56 +02:00

Configuration

Three files in ~/.config/papote/ (or $XDG_CONFIG_HOME/papote/), split by who writes them. No file is both hand-formatted and machine-rewritten, so nothing papote does can clobber your comments.

File Written by Holds
providers.toml you; papote appends endpoints and their credentials, 0600
config.toml papote alone which provider and model are in use
theme.toml you alone the colours

All three are optional. A missing one means "nothing configured yet"; one that exists but does not parse is a startup error, because silently ignoring a mistyped key would leave you editing a file that is never read.

providers.toml

[remote]
url = "https://ai.example.com/v1"     # base URL, not the completions endpoint
username = "me"                      # optional, but then password too
password = "hunter2"

[local]
url = "http://localhost:3000/v1"

url is the base of the OpenAI API: papote hangs /chat/completions and /models off it, so listing models and chatting can never disagree. Pasting a full …/v1/chat/completions works too — the setup prompt trims it.

username and password are all or nothing. A provider with only one of them is a startup error rather than a puzzling 401 later.

A name containing dots has to be quoted, as TOML would otherwise read it as nested tables:

["ai.example.com"]
url = "https://ai.example.com/v1"

The password sits in clear, like a .netrc; papote writes the file 0600 and you should keep it that way. Basic auth over plain http:// travels as readable base64, so pair it with https:// unless the endpoint is on localhost.

/provider appends to this file and leaves everything already in it alone.

config.toml

Papote owns this one — /model and /provider rewrite it whole. Editing it by hand works and the values survive a rewrite; only comments would not.

[model]
provider = "remote"           # a key from providers.toml
name = "gemma-4:26B-A4B:32K"
thinking = true    # show and count the model's reasoning; see Usage#thinking
tools = true       # offer the model papote's tools; see Usage#tools

[ui]
scroll_lines = 3   # lines per notch of the wheel; 0 turns the wheel off
auto_name = true   # ask the model to name a conversation after its first answer

[tools]
max_rounds = -1       # rounds of tool calls per question; negative = no limit
read_file = "allow"   # run it without asking
list_files = "ask"    # ask every time, showing the arguments
write_file = "deny"   # never run it, and tell the model so

[searxng]
url = "https://searxng.example.org"   # base URL, papote appends /search
username = "you"                      # optional, HTTP basic auth
password = "…"                        # both or neither

thinking drives chat_template_kwargs.enable_thinking. papote streams the thinking, shows it and counts it — see Thinking — so it is on by default: a model that reasons answers better, and what it costs is on the bar rather than hidden. Turn it off for a small context, where a model can spend the whole token budget reasoning before a single word of answer arrives.

tools puts what papote can do for the model in every request. Turn it off for an endpoint that refuses one carrying them: llama-server needs --jinja for tool calling, and without it the whole request is rejected rather than the field ignored. A config written before tools existed has no such key and offers them anyway, which is the default.

auto_name costs one short extra request per conversation, right after its first answer. With it off, /rename still asks on demand. See Saved conversations.

max_rounds shares the table with the tool names, and is the only reserved key in it. Negative means no limit, which is the default; zero refuses the first call. See When to stop.

[tools] is otherwise the only table where papote accepts a name it does not know: a tool it has no such thing as goes on the bar as a warning rather than stopping the program, since a name can outlive the tool. A tool that is not named here falls back to what the tool itself says, which is ask for everything but the clock. Choosing yes, and don't ask again … at the prompt writes the "allow" line for you; "deny" is only ever written by hand — and if a tool has stopped working, that is the first place to look: the bar says <tool> is denied in config.toml when it is. See Tools.

The web tools

[searxng] is where the web_search and read_url tools get their instance, and it is the switch that turns them on: without this table they are not offered to the model at all, rather than being offered and failing. A tool the model can call only to be told it is unconfigured costs it a turn to find out.

url is the base — papote appends /search — and the instance needs format: json among its search.formats in settings.yml, which is not on by default in a stock searxng. The credentials are HTTP basic auth, for an instance behind a password, and it is both of them or neither: a lone username sends user: and comes back as a puzzling 401, so papote refuses to start on it instead.

Because this table can hold a password, config.toml is written 0600, the same as providers.toml.

papote talks to searxng's own JSON API. It does not speak MCP, and running mcp-searxng beside your instance buys nothing here — the two tools it offers do what these two do.

The list of available models is deliberately not stored anywhere. Models get loaded and unloaded behind an endpoint — the same model reports loaded then unloaded between two calls — so a list written to disk starts lying immediately. papote asks /v1/models when it needs one, which is at setup and whenever you run /model.

theme.toml

Colours only, at the root of the file — no wrapping table. Every key is optional and keeps the default below when absent. All values are 256-colour indices.

fg = 252          # body text, and what an uncoloured span inherits
muted = 245
border = 240      # boxes, rules, table frames
heading = [44, 39, 74, 110]  # one per level; deeper levels reuse the last
link = 75
quote_bar = 67
bullet = 74       # unordered list marker
number = 179      # ordered list marker
task_done = 78    # ticked "- [x]" box; an unticked one is `muted`
table_rule = 237  # rule between two table rows that wrap
prompt = 44
selection = 238   # background of the completion candidate Tab is on
warning = 179     # bar: something that did not happen
error = 167       # bar: something that failed, and the provider form
gauge_used = 250  # bar: the filled part of the context gauge
gauge_free = 238  # bar: what is left of it; darker than gauge_used

[code]
fg = 209          # inline `code` span
bg = 236
plain = 252       # code with no token type, and unknown languages
comment = 245
string = 150
number = 216
keyword = 176
func = 111
type = 180
punct = 244
added = 78        # diff blocks: +, - and @@ lines
removed = 167
hunk = 111

A colour that is not in the table above is a startup error, so a typo in a key name is caught rather than silently ignored.