Configuring MulmoTerminal — start here
This skill does two things: it routes to whichever skill owns what the user wants, and it
reports on how things are configured right now. Anything that writes a setting lives in a
sibling skill.
Where settings live
| Place |
What |
Written by |
| Settings modal |
Theme, terminal font size / family / scroll, roster rows, the grid header's load read-out, notification sounds, Push, PR repos, self-hosted GitLab, work comments, PR footer, closing summary, decision digest, dev-work log, copy-on-select, Enter behaviour, launch commands, phone quick commands, MCP servers |
the user, in the UI |
~/.mulmoterminal/config.json |
Everything global — including the five Settings can only SHOW, never edit: keymap, themes, providers, customAgents, buttons / chips |
these skills, or by hand |
<project>/.mulmoterminal.json |
Per-project appearance and behaviour. No UI writes this file |
these skills only |
Check Settings first when a request is one setting. Most global settings now have a control
there, so "turn on work comments" is a click rather than a skill run — say where it is and let them
choose. The five above are the exception: a keymap binding, a colour scheme, a backend, your own
Claude Code command, a header button. Settings SHOWS each of them — what is configured now, and for
a backend whether it can be reached — but cannot edit them: they are structured enough to need the
questions a skill asks. Every one of those sections carries a button that launches the owning skill.
Routing
Ask what they want to change — one AskUserQuestion, concrete options — then invoke that skill and
carry on there. Do not re-explain its contents here; the sibling skill is the source of truth.
Two of those areas own a GLOBAL key as well as a per-project one, and the skill owns both halves:
mulmoterminal-dirs for headerStatusColors / headerStatusTint (the header while a session is
running), mulmoterminal-notify for the sounds. Route rather than editing the global file here.
| They want |
Skill |
| Colours for a project, colour-coding several, grid/launcher order, a name badge, a project icon image, terminal font size or font |
mulmoterminal-dirs |
| The header being unreadable, or changing colour, while a session runs |
mulmoterminal-dirs |
| Their own colour scheme, appearing in Settings' picker |
mulmoterminal-theme |
| Header buttons or info chips, globally or per project |
mulmoterminal-header |
| Keyboard shortcuts, copy-on-select, Enter vs. newline |
mulmoterminal-keys |
| Another model or backend (OpenRouter, a gateway, a per-project model) |
mulmoterminal-model |
Their own command for starting Claude Code (ollama launch claude …, a wrapper script), offered in the Agent Picker |
mulmoterminal-model |
| Which moments beep or push, and what they play |
mulmoterminal-notify |
| Work comments on an issue, the PR clone footer, the closing summary, the decision digest, the dev-work log, roster row length, a self-hosted GitLab |
stay here — the settings that live here |
| Something is broken and they don't know which setting |
Audit first (below), then route |
If the request already names an area, skip the question. "Make this project blue" goes straight to
mulmoterminal-dirs.
"I want to add my own agent" is two different requests, and the answer differs by which one:
an entry in the Agent Picker that runs Claude Code through the user's command line and receives
Claude Code's arguments (customAgents → mulmoterminal-model), or a launcher chip that runs a
command verbatim with nothing added and no session (launchers → Settings' Launch commands, which
has a UI and so needs no skill). Ask which before routing: they sit side by side in the launcher and
behave in opposite ways.
Some requests span two skills, and that is normal — say so and do them in order. "Give my new repo
the same look as the others, in my own theme" is mulmoterminal-theme (define it once) then
mulmoterminal-dirs (pin it and set the colours).
Audit — how is it configured now?
Reach for this when the user asks what their setup looks like, or says a setting "isn't working".
Read; do not fix anything until you have reported and they have chosen.
1. The global config
curl -s "http://localhost:${MULMOTERMINAL_PORT:-34567}/api/config"
Falling back to reading ~/.mulmoterminal/config.json directly is fine, but say which you used.
2. Each project
Take cwdPresets from the global config — the directories the user actually opens — and for each:
curl -sG "http://localhost:${MULMOTERMINAL_PORT:-34567}/api/dir-config-detail" --data-urlencode "cwd=$path"
Let curl encode cwd (-G + --data-urlencode). Interpolating the path raw truncates at the
first # or ? and mangles spaces, and a directory named that way is exactly the one someone
reaches for this audit about.
Use this route rather than reading the file. It returns what the app actually parsed: the
values in force, which keys the file set, and how each fared — applied, dropped in
validation, or not a key we read. Reading the file tells you what it says; only this tells you
what is in effect, and that gap is the entire content of "I set it and nothing happened". It also
resolves paths the way the app does, so a preset whose project was deleted answers as missing
instead of silently reporting on some other directory.
3. Report
Lead with what is not in effect, because that is what the user is asking about even when they
phrase it as a general question:
- Dropped keys — set in the file, rejected by validation. Name the key, the value, and why.
For the global config there is no route that reports this:
/api/config answers the SANITIZED
config, so an entry present in ~/.mulmoterminal/config.json and absent from that response is
precisely one that was dropped. Compare the two whenever a global list (providers,
customAgents, launchers, themes) is shorter than the user expects.
- Unrecognised keys — typos survive on purpose (
copyOnSlect is kept, not deleted), which is
what makes them findable. Say so; a kept key is not a working one.
- Set but invisible — a real setting doing nothing yet. The common ones:
orderPriority while the grid's ordering button is on auto or manual (the launcher's chips
still use it).
- Chrome colours while a session is busy or waiting — the working/attention colours take over,
and the configured ones only show when the cell is idle.
- A global change that needs a tab reload, or
fontFamily / a provider key, which need a
server restart.
- Then the settings that are working, grouped by area, briefly.
Offer to fix what you found, and route to the owning skill for anything they pick.
Rules every one of these skills follows
State these when they matter; they are the ones that cost people an afternoon.
- Global writes are a partial
POST /api/config merge — at the TOP level only. Write only the
keys you are changing; every key you do send replaces what was there, whole. Nothing below
the top level is merged, so a key holding a collection has to be sent complete or you delete
the rest of it. That is true of the arrays (themes, providers, buttons, chips,
soundKinds, pushKinds, gitlabHosts, prRepos, launchers, customAgents, quickCommands,
userMcpServers, cwdPresets) and equally of the objects (sounds, keymap, repoDirs,
headerStatusColors) — the objects are the ones that surprise people, because a map looks like
something you can add one key to. Posting {"sounds": {"waiting": "preset:coin"}} leaves the user
with exactly one sound. Read the current value from GET /api/config, change it, send it back.
<project>/.mulmoterminal.json applies live, and writing it with your Write/Edit tool is
itself the reload signal. There is no filesystem watcher: a file the user edits by hand does
nothing until something re-reads it. Always write it yourself rather than asking them to.
- Read the existing file and merge before writing. Never drop keys the user did not ask to
change — these files are shared between skills.
- Malformed values are silently dropped, so an invalid field just never takes effect. Check with
the audit above rather than assuming a write landed.
- When it takes effect: per-project → immediately. Most global → reload the tab.
fontFamily, provider keys, and any hand-edit made while the server is running → restart the
server.
The settings that live here
None is big enough to warrant its own skill. All but skills and decks also have a Settings control — offer that first, and use these when the user would rather be told the key, or is
setting up a machine without opening the browser.
skills — the header's Skill menu, per project
The header's Skill dropdown lists a directory's Claude skills (.claude/skills, user +
project scope) and runs the one picked. skills is an allowlist that also sets the order: an
array (≤ 100) of slugs — only these appear, in this order. Omit the key to show every discovered
skill (working-directory ones first). Slugs that don't resolve are ignored.
{ "skills": ["review-diff", "commit-msg"] }
decks — the header's Mulmo menu, per project
The header's Mulmo dropdown shows a mulmoScript deck in the Canvas beside the cell, without
asking the agent. Two sources, and it never searches the disk:
artifacts/stories/ under the workspace — where the plugin puts the decks an agent makes.
Always offered, nothing to configure.
decks — an array (≤ 50) of paths relative to this file, for a deck kept inside the
repository.
{ "decks": ["decks/launch.json", "docs/talks/retro.json"] }
- Paths must stay inside the directory that declares them.
../other-project/deck.json and an
absolute path are dropped: .mulmoterminal.json travels with a clone, so a declaration names a
deck kept in this repository.
- A path that is not a mulmoScript (no
$mulmocast), or is not there, is dropped silently —
the menu would open it and the server would refuse.
- Each deck is named by its own
title, falling back to the file name.
- The menu appears on agent cells only (beside Run and Skill), and only under a directory
MulmoTerminal serves decks from: the workspace it was started in, plus the launcher's saved
directories — up to 64 in total, skipping any no longer on disk. Those are read once at
startup, so a repository opened for the first time needs a restart before its decks show. A deck anywhere else is still one right-click away in
the file tree ("Open in the Canvas").
- Why you list them: searching for decks finds whatever parses as one, which in a real
workspace was 217 test fixtures from a checked-out repository against 33 real decks.
appendSystemPrompt — the closing summary
Every session is asked to end a reply with a short summary of what was asked, what was achieved and
what was not, under a --- rule. It exists for the grid: coming back to a cell later, that is
otherwise only recoverable by scrolling the whole session.
{ "appendSystemPrompt": false }
- On by default; only an explicit
false turns it off. Set it globally in
~/.mulmoterminal/config.json, or per project — the project wins.
- Nothing in the app reads what the summary says. Turning it off costs no feature; the roster's
"last reply" and push notifications just become the raw tail of the reply.
- Applies to sessions started from then on. No restart, but a running session keeps what it
launched with — reopen the cell.
true / false only. There is no way to substitute custom wording — do not offer one.
- Independent of
prWorkdirFooter: both ride on --append-system-prompt, and turning one off
leaves the other.
gitlabHosts — a GitLab of your own
The cross-repo PRs & Issues view reads github.com with gh and gitlab.com with glab. A
self-hosted GitLab cannot be recognised from its address — nothing in gitlab.example.com says
which forge runs there — so it is declared:
{
"gitlabHosts": ["gitlab.example.com"],
"prRepos": ["gitlab.example.com/group/project"]
}
- A declared host then does everything gitlab.com does: the list, starting work from an issue, work
comments, opening a merge request.
glab must be logged in to it: glab auth login --hostname gitlab.example.com. Check with
glab auth status — this app holds no token of its own.
- Hostnames only. A project path, a port, or a value that is not a hostname is dropped on load,
which looks exactly like never having written it — audit with
/api/config rather than assuming.
- Global only, and it takes effect on the next server start either way — a hand edit and the
Settings control are the same write.
- Until a host is declared, its row in the view says so and names this key.
issueWorkComments — telling an issue you are on it
A cell comments once on the issue it is working on: posted when the work starts, then edited
as the PR opens and merges (closing the issue if the forge has not already). It names the working
directory — the folder name only, never the path — so a reader can tell which clone, and two
terminals do not start the same issue twice.
{ "issueWorkComments": true }
- Off by default, and this is the one setting here that writes to somebody else's repo. Ask
before turning it on for someone.
- Needs
gh (or glab for a GitLab host) logged in. This app holds no token of its own.
- CI is deliberately never reported: it is on the PR already, and it flaps.
prWorkdirFooter — which clone made this PR
Ends a created PR's body with work in <clone name>, so a PR says which of several side-by-side
clones produced it.
{ "prWorkdirFooter": false }
- On by default; only an explicit
false turns it off. Independent of appendSystemPrompt
despite both riding on --append-system-prompt.
decisionDigest — what this project already decided
Keeps a Markdown digest of the decisions this project's sessions asked for, refreshed on a timer,
for an agent to read before asking something similar.
{ "decisionDigest": true }
- Off by default: it writes a file (under
~/.mulmoterminal/decisions/) that would otherwise
never exist. mulmoterminal-decisions is what READS and curates it; this key is the switch.
sessionIdleReapDays — ending sessions nothing is using
Terminals survive a server restart (that is tmux persistence), so they accumulate. At each start the
server ends the ones nothing is using: nobody attached, no pty of its own, and no output for this
many days.
{ "sessionIdleReapDays": 7 }
- Default 7.
0 turns it off — the only way to disable it, and the value to reach for when
someone parks work in a terminal for weeks.
- The conversation is not lost. A transcript on disk resumes without the tmux session; what ends
is the process and its scrollback. Say this before changing the number — it is the fact that makes
the sweep safe, and the reason "it has a transcript" is not a reason to keep a session alive.
- Whole days, 0–365. Anything else falls back to 7.
- Also a stepper in Settings → Sessions that survived a restart, beside the list it acts on; each
row there says whether the next start will take it.
worklogEnabled / worklogIntervalHours — the periodic dev-work log
A built-in scheduled task that summarizes recent work across the saved working dirs into weekly
wiki pages.
{ "worklogEnabled": true, "worklogIntervalHours": 6 }
- Off by default, and it costs tokens — each run spawns an LLM session. Say so before enabling.
- The interval is whole hours, clamped to 1–168. Anything else falls back to 6.
feedRefreshEnabled / calendarSyncEnabled — the two always-on scheduled tasks
MulmoTerminal registers two built-in scheduled tasks besides the worklog, both hourly: the
collection/feed refresh (one per root — the workspace and every saved project directory) and the
Google Calendar sync. These switch them off.
{ "feedRefreshEnabled": false, "calendarSyncEnabled": false }
- Both default ON. Only an explicit
false turns one off — an absent key, null, 0 or the
string "false" all leave it running, so an existing config never changes behaviour on upgrade.
Same rule as enabled on a task in config/scheduler/tasks.json.
- Takes effect at the next server start. The scheduler registers once at boot, so flipping
either one mid-session changes nothing until a restart.
- Turning one off does not delete anything already fetched; it stops the scheduled run. Feeds
and calendar collections still update when someone asks for them explicitly.
- These do not touch the tasks you wrote.
config/scheduler/tasks.json is a separate list with
its own per-task enabled, and an enabled one still registers and still drives the tick loop
with all three built-ins off.
- With both off,
worklogEnabled off, AND no enabled user task, nothing is registered at all —
which is also when the scheduler stops writing its state file and run logs.
- Reasons to turn one off: no feeds and no Google account (the runs are no-ops), or a machine
where the hourly wake-up is unwanted. Neither costs tokens, unlike the worklog.
- Also two checkboxes in Settings → Sessions, under Built-in scheduled tasks.
cockpitLines — how long a roster row is
How many lines each row of the cockpit roster (the list beside an enlarged cell) shows before it
clamps. Defaults to 2 / 2 / 3.
{ "cockpitLines": { "summary": 4, "prompt": 2, "response": 3 } }
- One field per line because they are worth different amounts: a summary says what a session is
doing NOW, while a prompt is usually done in two lines.
- Each is clamped to 1–20, per field, so one bad value cannot discard the two set correctly.
- Raising these trades how many sessions fit on screen for reading a long one in place. It is a
trade the user makes — do not "fix" a clamped row by raising it unasked.
showLoadAverage — the machine's load in the grid header
Whether the grid header draws this machine's load average beside the 5h / 7d usage windows, as a
percentage of its cores (load 334% = 66.8 on 20 cores). On unless set to false.
{ "showLoadAverage": false }
- Amber at 100% (every core has work queued), red at 200%. Hover gives the raw 1 / 5 / 15-minute
figures, the core count and the multiplier (
3.3x).
- A host that keeps no load average shows nothing whatever this says. Windows is the case:
os.loadavg() returns zeros there, and 0% would read as "idle" rather than "not measured".
- Also in Settings, under Grid header read-outs.
toolbarPins — pinned collections on the toolbar itself
Which of the pinned favourites get a permanent button in the toolbar, beside Grid and
Collections, so opening one is a single press instead of Collections-then-the-row-inside-it.
Empty by default — the toolbar is exactly as it was until something is promoted.
{ "toolbarPins": ["collection:works", "collection:todos", "feed:news"] }
- Each entry is
"<kind>:<slug>", where kind is collection or feed. The array order is the
order the buttons are drawn in.
- Five buttons. Past that they push the row into its horizontal scroll, which is the very
two-step this removes. The array may hold more than five: entries whose pins are currently unpinned
are kept, not drawn.
- It promotes; it does not pin. An entry must already be PINNED (the star in Collections, stored in
<workspace>/config/shortcuts.json, shared with MulmoClaude) — the button's name and icon are
read from the pin, so renaming a collection renames the button. A key whose pin is gone draws
nothing and does not hold one of the five slots, and nothing deletes it — re-pinning brings the
button back where it was, once the app re-reads the shared list (page load, or opening Settings'
Toolbar pins). Do not "tidy" such keys out of the config on the user's behalf.
- The array may hold up to 50 keys while only five are drawn, so a config with more than five
entries is not an error to correct.
- The shared pin list is read once per page load, and again when Settings' Toolbar pins opens.
So a pin removed in MulmoClaude leaves the button on the toolbar of an already-open session until
one of those happens; it is not a live feed.
- Also in Settings, under Toolbar pins, which lists what is pinned and takes a tick per entry.
1---2name: mulmoterminal-config3description: The way into configuring MulmoTerminal, and the way to find out how it is configured now. Use for a broad or unsure request — "configure MulmoTerminal", "set this up", "customize this", "what can I change?", first-run setup — and route to the skill that owns the area. Also answers "how is this set up right now?", "why isn't my setting working?", "did that take effect?" by reading the live config — the global `~/.mulmoterminal/config.json`, each project's `.mulmoterminal.json`, and what the app ACTUALLY parsed from them — including keys it dropped in validation, which is the difference between a setting you never made and one that silently never applied. Owns the global settings that have no skill of their own — work comments on an issue (issueWorkComments), the PR clone footer (prWorkdirFooter), the closing summary (appendSystemPrompt), the decision digest (decisionDigest), the periodic dev-work log (worklogEnabled), roster row length (cockpitLines), the grid header's load read-out (showLoadAverage), pinned c4---56# Configuring MulmoTerminal — start here78This skill does two things: it **routes** to whichever skill owns what the user wants, and it9**reports** on how things are configured right now. Anything that writes a setting lives in a10sibling skill.1112## Where settings live1314| Place | What | Written by |15|---|---|---|16| Settings modal | Theme, terminal font size / family / scroll, roster rows, the grid header's load read-out, notification sounds, Push, PR repos, self-hosted GitLab, work comments, PR footer, closing summary, decision digest, dev-work log, copy-on-select, Enter behaviour, launch commands, phone quick commands, MCP servers | the user, in the UI |17| `~/.mulmoterminal/config.json` | Everything global — including the five Settings can only SHOW, never edit: `keymap`, `themes`, `providers`, `customAgents`, `buttons` / `chips` | these skills, or by hand |18| `<project>/.mulmoterminal.json` | Per-project appearance and behaviour. **No UI writes this file** | these skills only |1920**Check Settings first when a request is one setting.** Most global settings now have a control21there, so "turn on work comments" is a click rather than a skill run — say where it is and let them22choose. The five above are the exception: a keymap binding, a colour scheme, a backend, your own23Claude Code command, a header button. Settings SHOWS each of them — what is configured now, and for24a backend whether it can be reached — but cannot edit them: they are structured enough to need the25questions a skill asks. Every one of those sections carries a button that launches the owning skill.2627## Routing2829Ask what they want to change — one `AskUserQuestion`, concrete options — then invoke that skill and30carry on there. Do not re-explain its contents here; the sibling skill is the source of truth.3132Two of those areas own a GLOBAL key as well as a per-project one, and the skill owns both halves:33`mulmoterminal-dirs` for `headerStatusColors` / `headerStatusTint` (the header while a session is34running), `mulmoterminal-notify` for the sounds. Route rather than editing the global file here.3536| They want | Skill |37|---|---|38| Colours for a project, colour-coding several, grid/launcher order, a name badge, a project icon image, terminal font size or font | `mulmoterminal-dirs` |39| The header being unreadable, or changing colour, while a session runs | `mulmoterminal-dirs` |40| Their **own** colour scheme, appearing in Settings' picker | `mulmoterminal-theme` |41| Header buttons or info chips, globally or per project | `mulmoterminal-header` |42| Keyboard shortcuts, copy-on-select, Enter vs. newline | `mulmoterminal-keys` |43| Another model or backend (OpenRouter, a gateway, a per-project model) | `mulmoterminal-model` |44| Their **own command** for starting Claude Code (`ollama launch claude …`, a wrapper script), offered in the Agent Picker | `mulmoterminal-model` |45| Which moments beep or push, and what they play | `mulmoterminal-notify` |46| Work comments on an issue, the PR clone footer, the closing summary, the decision digest, the dev-work log, roster row length, a self-hosted GitLab | **stay here** — [the settings that live here](#the-settings-that-live-here) |47| Something is broken and they don't know which setting | **Audit first** (below), then route |4849If the request already names an area, skip the question. "Make this project blue" goes straight to50`mulmoterminal-dirs`.5152**"I want to add my own agent" is two different requests**, and the answer differs by which one:53an entry in the **Agent Picker** that runs Claude Code through the user's command line and receives54Claude Code's arguments (`customAgents` → `mulmoterminal-model`), or a **launcher chip** that runs a55command verbatim with nothing added and no session (`launchers` → Settings' *Launch commands*, which56has a UI and so needs no skill). Ask which before routing: they sit side by side in the launcher and57behave in opposite ways.5859Some requests span two skills, and that is normal — say so and do them in order. "Give my new repo60the same look as the others, in my own theme" is `mulmoterminal-theme` (define it once) then61`mulmoterminal-dirs` (pin it and set the colours).6263## Audit — how is it configured now?6465Reach for this when the user asks what their setup looks like, or says a setting "isn't working".66**Read; do not fix anything until you have reported and they have chosen.**6768### 1. The global config6970```sh71curl -s "http://localhost:${MULMOTERMINAL_PORT:-34567}/api/config"72```7374Falling back to reading `~/.mulmoterminal/config.json` directly is fine, but say which you used.7576### 2. Each project7778Take `cwdPresets` from the global config — the directories the user actually opens — and for each:7980```sh81curl -sG "http://localhost:${MULMOTERMINAL_PORT:-34567}/api/dir-config-detail" --data-urlencode "cwd=$path"82```8384Let `curl` encode `cwd` (`-G` + `--data-urlencode`). Interpolating the path raw truncates at the85first `#` or `?` and mangles spaces, and a directory named that way is exactly the one someone86reaches for this audit about.8788**Use this route rather than reading the file.** It returns what the app *actually parsed*: the89values in force, which keys the file set, and how each fared — **applied**, **dropped in90validation**, or **not a key we read**. Reading the file tells you what it says; only this tells you91what is in effect, and that gap is the entire content of "I set it and nothing happened". It also92resolves paths the way the app does, so a preset whose project was deleted answers as missing93instead of silently reporting on some other directory.9495### 3. Report9697Lead with **what is not in effect**, because that is what the user is asking about even when they98phrase it as a general question:99100- **Dropped keys** — set in the file, rejected by validation. Name the key, the value, and why.101 For the global config there is no route that reports this: `/api/config` answers the SANITIZED102 config, so an entry present in `~/.mulmoterminal/config.json` and absent from that response is103 precisely one that was dropped. Compare the two whenever a global list (`providers`,104 `customAgents`, `launchers`, `themes`) is shorter than the user expects.105- **Unrecognised keys** — typos survive on purpose (`copyOnSlect` is kept, not deleted), which is106 what makes them findable. Say so; a kept key is not a working one.107- **Set but invisible** — a real setting doing nothing yet. The common ones:108 - `orderPriority` while the grid's ordering button is on auto or manual (the launcher's chips109 still use it).110 - Chrome colours while a session is busy or waiting — the working/attention colours take over,111 and the configured ones only show when the cell is idle.112 - A global change that needs a **tab reload**, or `fontFamily` / a provider key, which need a113 **server restart**.114- **Then** the settings that are working, grouped by area, briefly.115116Offer to fix what you found, and route to the owning skill for anything they pick.117118## Rules every one of these skills follows119120State these when they matter; they are the ones that cost people an afternoon.121122- **Global writes are a partial `POST /api/config` merge — at the TOP level only.** Write only the123 keys you are changing; every key you *do* send **replaces** what was there, whole. Nothing below124 the top level is merged, so a key holding a collection has to be sent **complete** or you delete125 the rest of it. That is true of the arrays (`themes`, `providers`, `buttons`, `chips`,126 `soundKinds`, `pushKinds`, `gitlabHosts`, `prRepos`, `launchers`, `customAgents`, `quickCommands`,127 `userMcpServers`, `cwdPresets`) **and equally of the objects** (`sounds`, `keymap`, `repoDirs`,128 `headerStatusColors`) — the objects are the ones that surprise people, because a map *looks* like129 something you can add one key to. Posting `{"sounds": {"waiting": "preset:coin"}}` leaves the user130 with exactly one sound. Read the current value from `GET /api/config`, change it, send it back.131- **`<project>/.mulmoterminal.json` applies live, and writing it with your Write/Edit tool is132 itself the reload signal.** There is **no filesystem watcher**: a file the user edits by hand does133 nothing until something re-reads it. Always write it yourself rather than asking them to.134- **Read the existing file and merge before writing.** Never drop keys the user did not ask to135 change — these files are shared between skills.136- **Malformed values are silently dropped**, so an invalid field just never takes effect. Check with137 the audit above rather than assuming a write landed.138- **When it takes effect**: per-project → immediately. Most global → **reload the tab**.139 `fontFamily`, provider keys, and any hand-edit made while the server is running → **restart the140 server**.141142## The settings that live here143144None is big enough to warrant its own skill. **All but `skills` and `decks` also have a Settings control** — offer that first, and use these when the user would rather be told the key, or is145setting up a machine without opening the browser.146147### `skills` — the header's Skill menu, per project148149The header's **Skill** dropdown lists a directory's Claude skills (`.claude/skills`, user +150project scope) and runs the one picked. `skills` is an **allowlist that also sets the order**: an151array (≤ 100) of slugs — only these appear, in this order. **Omit the key** to show every discovered152skill (working-directory ones first). Slugs that don't resolve are ignored.153154```json155{ "skills": ["review-diff", "commit-msg"] }156```157158### `decks` — the header's Mulmo menu, per project159160The header's **Mulmo** dropdown shows a mulmoScript **deck** in the Canvas beside the cell, without161asking the agent. Two sources, and it never searches the disk:1621631. `artifacts/stories/` under the workspace — where the plugin puts the decks an agent makes.164 Always offered, nothing to configure.1652. `decks` — an array (≤ 50) of paths **relative to this file**, for a deck kept inside the166 repository.167168```json169{ "decks": ["decks/launch.json", "docs/talks/retro.json"] }170```171172- Paths must stay **inside the directory that declares them**. `../other-project/deck.json` and an173 absolute path are dropped: `.mulmoterminal.json` travels with a clone, so a declaration names a174 deck kept in *this* repository.175- A path that is not a mulmoScript (no `$mulmocast`), or is not there, is **dropped silently** —176 the menu would open it and the server would refuse.177- Each deck is named by its own `title`, falling back to the file name.178- The menu appears on **agent cells** only (beside Run and Skill), and only under a directory179 MulmoTerminal serves decks from: the workspace it was started in, plus the launcher's saved180 directories — up to 64 in total, skipping any no longer on disk. Those are read **once at181 startup**, so a repository opened for the first time needs a restart before its decks show. A deck anywhere else is still one right-click away in182 the file tree ("Open in the Canvas").183- **Why you list them:** searching for decks finds whatever parses as one, which in a real184 workspace was 217 test fixtures from a checked-out repository against 33 real decks.185186### `appendSystemPrompt` — the closing summary187188Every session is asked to end a reply with a short summary of what was asked, what was achieved and189what was not, under a `---` rule. It exists for the grid: coming back to a cell later, that is190otherwise only recoverable by scrolling the whole session.191192```json193{ "appendSystemPrompt": false }194```195196- **On by default**; only an explicit `false` turns it off. Set it globally in197 `~/.mulmoterminal/config.json`, or per project — **the project wins**.198- **Nothing in the app reads what the summary says.** Turning it off costs no feature; the roster's199 "last reply" and push notifications just become the raw tail of the reply.200- Applies to sessions started **from then on**. No restart, but a running session keeps what it201 launched with — reopen the cell.202- `true` / `false` only. There is **no way to substitute custom wording** — do not offer one.203- Independent of `prWorkdirFooter`: both ride on `--append-system-prompt`, and turning one off204 leaves the other.205206### `gitlabHosts` — a GitLab of your own207208The cross-repo **PRs & Issues** view reads github.com with `gh` and gitlab.com with `glab`. A209self-hosted GitLab cannot be recognised from its address — nothing in `gitlab.example.com` says210which forge runs there — so it is declared:211212```json213{214 "gitlabHosts": ["gitlab.example.com"],215 "prRepos": ["gitlab.example.com/group/project"]216}217```218219- A declared host then does everything gitlab.com does: the list, starting work from an issue, work220 comments, opening a merge request.221- **`glab` must be logged in to it**: `glab auth login --hostname gitlab.example.com`. Check with222 `glab auth status` — this app holds no token of its own.223- **Hostnames only.** A project path, a port, or a value that is not a hostname is dropped on load,224 which looks exactly like never having written it — audit with `/api/config` rather than assuming.225- Global only, and it takes effect on the **next server start** either way — a hand edit and the226 Settings control are the same write.227- Until a host is declared, its row in the view says so and names this key.228229### `issueWorkComments` — telling an issue you are on it230231A cell comments **once** on the issue it is working on: posted when the work starts, then **edited**232as the PR opens and merges (closing the issue if the forge has not already). It names the working233**directory** — the folder name only, never the path — so a reader can tell which clone, and two234terminals do not start the same issue twice.235236```json237{ "issueWorkComments": true }238```239240- **Off by default**, and this is the one setting here that writes to somebody else's repo. Ask241 before turning it on for someone.242- Needs `gh` (or `glab` for a GitLab host) logged in. This app holds no token of its own.243- CI is deliberately never reported: it is on the PR already, and it flaps.244245### `prWorkdirFooter` — which clone made this PR246247Ends a created PR's body with `work in <clone name>`, so a PR says which of several side-by-side248clones produced it.249250```json251{ "prWorkdirFooter": false }252```253254- **On by default**; only an explicit `false` turns it off. Independent of `appendSystemPrompt`255 despite both riding on `--append-system-prompt`.256257### `decisionDigest` — what this project already decided258259Keeps a Markdown digest of the decisions this project's sessions asked for, refreshed on a timer,260for an agent to read before asking something similar.261262```json263{ "decisionDigest": true }264```265266- **Off by default**: it writes a file (under `~/.mulmoterminal/decisions/`) that would otherwise267 never exist. `mulmoterminal-decisions` is what READS and curates it; this key is the switch.268269### `sessionIdleReapDays` — ending sessions nothing is using270271Terminals survive a server restart (that is tmux persistence), so they accumulate. At each start the272server ends the ones **nothing is using**: nobody attached, no pty of its own, and no output for this273many days.274275```json276{ "sessionIdleReapDays": 7 }277```278279- **Default 7. `0` turns it off** — the only way to disable it, and the value to reach for when280 someone parks work in a terminal for weeks.281- **The conversation is not lost.** A transcript on disk resumes without the tmux session; what ends282 is the process and its scrollback. Say this before changing the number — it is the fact that makes283 the sweep safe, and the reason "it has a transcript" is not a reason to keep a session alive.284- Whole days, 0–365. Anything else falls back to 7.285- Also a stepper in **Settings → Sessions that survived a restart**, beside the list it acts on; each286 row there says whether the next start will take it.287288### `worklogEnabled` / `worklogIntervalHours` — the periodic dev-work log289290A built-in scheduled task that summarizes recent work across the saved working dirs into weekly291wiki pages.292293```json294{ "worklogEnabled": true, "worklogIntervalHours": 6 }295```296297- **Off by default, and it costs tokens** — each run spawns an LLM session. Say so before enabling.298- The interval is whole hours, clamped to 1–168. Anything else falls back to 6.299300### `feedRefreshEnabled` / `calendarSyncEnabled` — the two always-on scheduled tasks301302MulmoTerminal registers two built-in scheduled tasks besides the worklog, both hourly: the303collection/feed refresh (one per root — the workspace and every saved project directory) and the304Google Calendar sync. These switch them off.305306```json307{ "feedRefreshEnabled": false, "calendarSyncEnabled": false }308```309310- **Both default ON.** Only an explicit `false` turns one off — an absent key, `null`, `0` or the311 string `"false"` all leave it running, so an existing config never changes behaviour on upgrade.312 Same rule as `enabled` on a task in `config/scheduler/tasks.json`.313- **Takes effect at the next server start.** The scheduler registers once at boot, so flipping314 either one mid-session changes nothing until a restart.315- Turning one off does not delete anything already fetched; it stops the *scheduled* run. Feeds316 and calendar collections still update when someone asks for them explicitly.317- **These do not touch the tasks you wrote.** `config/scheduler/tasks.json` is a separate list with318 its own per-task `enabled`, and an enabled one still registers and still drives the tick loop319 with all three built-ins off.320- With both off, `worklogEnabled` off, AND no enabled user task, nothing is registered at all —321 which is also when the scheduler stops writing its state file and run logs.322- Reasons to turn one off: no feeds and no Google account (the runs are no-ops), or a machine323 where the hourly wake-up is unwanted. Neither costs tokens, unlike the worklog.324- Also two checkboxes in **Settings → Sessions**, under *Built-in scheduled tasks*.325326### `cockpitLines` — how long a roster row is327328How many lines each row of the cockpit roster (the list beside an enlarged cell) shows before it329clamps. Defaults to 2 / 2 / 3.330331```json332{ "cockpitLines": { "summary": 4, "prompt": 2, "response": 3 } }333```334335- One field per line because they are worth different amounts: a summary says what a session is336 doing NOW, while a prompt is usually done in two lines.337- Each is clamped to 1–20, **per field**, so one bad value cannot discard the two set correctly.338- Raising these trades how many sessions fit on screen for reading a long one in place. It is a339 trade the user makes — do not "fix" a clamped row by raising it unasked.340341### `showLoadAverage` — the machine's load in the grid header342343Whether the grid header draws this machine's load average beside the 5h / 7d usage windows, as a344percentage of its cores (`load 334%` = 66.8 on 20 cores). **On unless set to `false`.**345346```json347{ "showLoadAverage": false }348```349350- Amber at 100% (every core has work queued), red at 200%. Hover gives the raw 1 / 5 / 15-minute351 figures, the core count and the multiplier (`3.3x`).352- **A host that keeps no load average shows nothing whatever this says.** Windows is the case:353 `os.loadavg()` returns zeros there, and 0% would read as "idle" rather than "not measured".354- Also in Settings, under **Grid header read-outs**.355356### `toolbarPins` — pinned collections on the toolbar itself357358Which of the pinned favourites get a permanent button in the toolbar, beside **Grid** and359**Collections**, so opening one is a single press instead of Collections-then-the-row-inside-it.360**Empty by default** — the toolbar is exactly as it was until something is promoted.361362```json363{ "toolbarPins": ["collection:works", "collection:todos", "feed:news"] }364```365366- Each entry is `"<kind>:<slug>"`, where kind is `collection` or `feed`. The array order is the367 order the buttons are drawn in.368- **Five buttons.** Past that they push the row into its horizontal scroll, which is the very369 two-step this removes. The array may hold more than five: entries whose pins are currently unpinned370 are kept, not drawn.371- It promotes; it does not pin. An entry must already be PINNED (the star in Collections, stored in372 `<workspace>/config/shortcuts.json`, shared with MulmoClaude) — the button's name and icon are373 read from the pin, so renaming a collection renames the button. A key whose pin is gone draws374 nothing and does not hold one of the five slots, and **nothing deletes it** — re-pinning brings the375 button back where it was, once the app re-reads the shared list (page load, or opening Settings'376 Toolbar pins). Do not "tidy" such keys out of the config on the user's behalf.377- The array may hold up to 50 keys while only five are drawn, so a config with more than five378 entries is not an error to correct.379- The shared pin list is read once per page load, and again when Settings' **Toolbar pins** opens.380 So a pin removed in MulmoClaude leaves the button on the toolbar of an already-open session until381 one of those happens; it is not a live feed.382- Also in Settings, under **Toolbar pins**, which lists what is pinned and takes a tick per entry.