Table of contents
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.
papote
Getting started
Using it
Under the hood