Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .roadmap/features/f-mcp-control-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ shippable:
terminal half: a mouse event inside a session. Unblocked #155, now fixed.
- [ ] [F-mcp-pointer-chrome](#f-mcp-pointer-chrome) — The pointer rung, chrome
half: click and drag termherd's own interface.
- [x] [F-mcp-options-bridge](#f-mcp-options-bridge) — `list_options` and
`set_option` on the live bridge too, over the stdio slice's catalogue.
20 changes: 20 additions & 0 deletions .roadmap/features/f-mcp-options-bridge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
+++
id = "F-mcp-options-bridge"
type = "feature"
area = ["mcp"]
status = "done"
target = ["Could"]
+++

`list_options` and `set_option` on the live bridge, not only the stdio slice.

Shipped as #298. A session termherd launched — the only kind it launches —
saw seventeen tools, none about settings, so "switch me to a light theme"
needed the stdio server registered by hand. The bridge now carries both over
the same pure catalogue in `crates/mcp` (one implementation, two transports;
the tool descriptions are pinned equal by a test, since `#[tool]` takes only
a literal). Neither surface writes over a `settings.json` that does not
parse. Paired with the settings hot reload (#297): this makes the change
reachable from a session, that one makes it visible without a restart. Rung
of [F-mcp-control-surface](#f-mcp-control-surface), after
[F-mcp-config-write](#f-mcp-config-write).
20 changes: 20 additions & 0 deletions .roadmap/features/f-settings-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
+++
id = "F-settings-panel"
type = "feature"
area = ["workspace"]
status = "done"
target = ["Should"]
+++

An in-app settings panel — appearance first, applied live and saved on pick.

The ⚙ in the sidebar header, or `mod+,` (`open-settings`), opens a modal
panel over the workspace: the chrome theme (`dark`, `light`, and the four
presets named after the terminal schemes) and the terminal scheme. A pick
applies at once — the chrome on the next frame, every running terminal through
a shared palette the PTY manager repaints — and writes only its own key to
`settings.json`, leaving the rest of the file as written. This is the live
reload [F-terminal-palette](#f-terminal-palette) deferred, and the in-app
panel [F-settings](#f-settings) promised. A running Claude session keeps the
light/dark look it chose from its startup colour query until it restarts.
Other settings stay file-only for now.
5 changes: 3 additions & 2 deletions .roadmap/features/f-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ Shell select, theme, window prefs (M3): `~/.termherd/settings.json` (serde,
defaults on missing/corrupt) carries a shell profile (program + args), injected
into the `PtyManager` so each session launches the chosen shell, and a GUI
theme (dark/light) wired to the iced chrome. (thin) Window bounds keep their
own `window.json` (FR12). File-based for now; an in-app settings panel is the
full version later
own `window.json` (FR12). The file is reloaded on save (#297) —
every setting applies without a restart, the shell from the next session —
and the appearance is also set from [F-settings-panel](#f-settings-panel)
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,17 @@ into its `mcpServers` at spawn (loopback, per-session token) — so it can read
and drive the workspace it runs in. This is the richer sibling of the capture
dump above: same `WorkspaceSnapshot` model, live instead of a file.

**Settled.** Seventeen tools: `list_sessions` + `snapshot`
**Settled.** Nineteen tools: `list_sessions` + `snapshot`
(perception), `open_session` / `split_pane` / `focus_pane` / `rename_tab` /
`close_pane` / `run_in_session` / `mouse_in_session` (action),
`wait_for_status` + `read_terminal` (synchronisation), `screenshot` (pixels),
`press_keys` + `run_action` (the app's own keyboard), `add_repo` +
`forget_repo` (membership — what the sidebar *contains*, as against what the
window draws), and `prompt_in_session` (the loop below, composed). The loop
window draws), `prompt_in_session` (the loop below, composed), and
`list_options` + `set_option` (settings — the stdio server's two tools over
its own pure catalogue in `crates/mcp`, writing `settings.json`, which the
shell's settings watch then applies live; the one pair that touches a file
rather than `core`, so it needs no bridge round trip). The loop
they exist to serve is **act → wait → observe**: `run_in_session` returns
immediately, so synchronise with `wait_for_status` and then `read_terminal`.
Do **not** poll `snapshot` in a loop — it races the transition you are
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 19 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,15 @@ cargo run -p termherd-app
## Configuration

Optional user settings live in `~/.termherd/settings.json` (on Windows,
`%USERPROFILE%\.termherd\settings.json`). The file is read at startup; if it
is missing or invalid, TermHerd falls back to defaults rather than refusing
to start — out-of-range values clamp, and a single bad value (a typo'd
colour, an unknown key action) degrades alone with a logged warning instead
of resetting the rest of the file. There is no in-app settings panel yet —
edit the file and restart.
`%USERPROFILE%\.termherd\settings.json`). A change **applies as soon as
the file is saved** — no restart; a save that does not parse changes
nothing. If the file is invalid at startup, TermHerd falls back to defaults
rather than refusing to start (the file is first set aside as
`settings.json.corrupt-1`, and the sidebar says so). Out-of-range values
clamp, and a single bad value (a typo'd colour, an unknown key action)
degrades alone with a logged warning instead of resetting the rest of the
file. The appearance (chrome theme and terminal colours) can also be set
from the settings panel — ⚙ in the sidebar, or `Cmd`/`Ctrl`+`,`.

The annotated reference template is
[`docs/settings.example.jsonc`](docs/settings.example.jsonc): every option
Expand All @@ -127,8 +130,9 @@ you want and strip the comments (the real file is strict JSON). In short:

- `shell` — program + args launched for each session (default: the platform
login shell).
- `theme` — `"dark"` (default) or `"light"` GUI chrome; the terminal grid
keeps its own colours.
- `theme` — GUI chrome: `"dark"` (default), `"light"`, or a named preset
(`solarized-*`, `gruvbox-*`) matching the terminal schemes; the terminal
grid keeps its own colours.
- `close` — per-action close confirmation (`tab`, `app`): always, only while
a foreground process runs (default), or never.
- `terminal` — base `font_size` (the zoom shortcuts step from it), grid
Expand Down Expand Up @@ -245,6 +249,7 @@ nothing to configure. It exposes the running workspace:
| `mouse_in_session` | a mouse event at a cell of a terminal — forwarded to a program reading the mouse, else the terminal's own selection |
| `add_repo` · `forget_repo` | put a repository in the sidebar before it has any session, and drop that addition |
| `prompt_in_session` | type, wait and read in one round trip — prompting another Claude session is opt-in |
| `list_options` · `set_option` | read and change `settings.json` — the same catalogue as the stdio server; a running TermHerd applies a change at once |

The loop that makes it useful is **act → wait → observe**: `run_in_session`,
then `wait_for_status`, then `read_terminal`. Sessions are addressed by a
Expand Down Expand Up @@ -274,11 +279,12 @@ the sidebar, tabs or gutters.

`termherd-mcp` is a separate small binary that exposes termherd's own
**configuration** — so you can ask "what can I configure here?", or "switch me
to a light theme", from any Claude session. Two tools, `list_options` (read)
and `set_option` (write), plus the option **schema** as a resource, all
reflecting `~/.termherd/settings.json`. The two options that name what
termherd executes — `shell.program`, `shell.args` — are readable but never
writable over MCP.
to a light theme", from a Claude session termherd did **not** launch (one it
launched already has the same tools on the live bridge). Two tools,
`list_options` (read) and `set_option` (write), plus the option **schema** as
a resource, all reflecting `~/.termherd/settings.json`. The two options that
name what termherd executes — `shell.program`, `shell.args` — are readable but
never writable over MCP.

It speaks JSON-RPC over stdio. Register it with Claude Code by adding it to your
`mcpServers` config (point `command` at the built binary):
Expand Down
43 changes: 41 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ issues #18–#29.
| [F-terminal-split](#f-terminal-split) | feature | workspace, keymap | ☐ | Split panes with directional focus; drag-resize is what remains. |
| [F-close-on-exit](#f-close-on-exit) | feature | terminal, workspace | ✅ | A pane whose shell exits cleanly closes itself; a failed one stays readable. |
| [F-repo-add](#f-repo-add) | feature | sidebar, sessions | ✅ | Declare a repository in the sidebar, before it has any session. |
| [F-settings-panel](#f-settings-panel) | feature | workspace | ✅ | An in-app settings panel — appearance first, applied live and saved on pick. |
| [F-terminal-cwd](#f-terminal-cwd) | feature | terminal, mcp, sessions | ✅ | The shell announces the directory it is in, so a session's `cwd` follows a `cd`. |

## Could
Expand All @@ -98,6 +99,7 @@ issues #18–#29.
| [F-mcp-config-write](#f-mcp-config-write) | feature | mcp | ✅ | `set_option` and `keys` on the stateless stdio slice. |
| [F-mcp-keys](#f-mcp-keys) | feature | mcp, keymap | ✅ | The keyboard rung: drive the app by key chords through the real keymap. |
| [F-mcp-live-bridge](#f-mcp-live-bridge) | feature | mcp | ✅ | The gate: an in-process MCP server on loopback, reaching the live `core::App`. |
| [F-mcp-options-bridge](#f-mcp-options-bridge) | feature | mcp | ✅ | `list_options` and `set_option` on the live bridge, not only the stdio slice. |
| [F-mcp-orchestration](#f-mcp-orchestration) | feature | mcp, workspace | ✅ | The action rung: six mutating tools, each over an existing `core::App` event. |
| [F-mcp-pointer-terminal](#f-mcp-pointer-terminal) | feature | mcp, terminal | ✅ | The pointer rung, terminal half: place a mouse event inside a session. |
| [F-mcp-screenshot](#f-mcp-screenshot) | feature | mcp, workspace | ✅ | The pixel rung: the window as a PNG, for what text cannot answer. |
Expand Down Expand Up @@ -409,8 +411,9 @@ Shell select, theme, window prefs (M3): `~/.termherd/settings.json` (serde,
defaults on missing/corrupt) carries a shell profile (program + args), injected
into the `PtyManager` so each session launches the chosen shell, and a GUI
theme (dark/light) wired to the iced chrome. (thin) Window bounds keep their
own `window.json` (FR12). File-based for now; an in-app settings panel is the
full version later
own `window.json` (FR12). The file is reloaded on save (#297) —
every setting applies without a restart, the shell from the next session —
and the appearance is also set from [F-settings-panel](#f-settings-panel)

<a id="f-status-notifications"></a>

Expand Down Expand Up @@ -672,6 +675,23 @@ read as one that never had any and was pinned to the top as freshly added.
Adjacent: [F-repo-view](#f-repo-view) (#148) takes the other end — this is
about a repository *existing* in the sidebar, that one about *viewing* it.

<a id="f-settings-panel"></a>

### F-settings-panel

An in-app settings panel — appearance first, applied live and saved on pick.

The ⚙ in the sidebar header, or `mod+,` (`open-settings`), opens a modal
panel over the workspace: the chrome theme (`dark`, `light`, and the four
presets named after the terminal schemes) and the terminal scheme. A pick
applies at once — the chrome on the next frame, every running terminal through
a shared palette the PTY manager repaints — and writes only its own key to
`settings.json`, leaving the rest of the file as written. This is the live
reload [F-terminal-palette](#f-terminal-palette) deferred, and the in-app
panel [F-settings](#f-settings) promised. A running Claude session keeps the
light/dark look it chose from its startup colour query until it restarts.
Other settings stay file-only for now.

<a id="f-terminal-cwd"></a>

### F-terminal-cwd
Expand Down Expand Up @@ -913,6 +933,8 @@ shippable:
terminal half: a mouse event inside a session. Unblocked #155, now fixed.
- [ ] [F-mcp-pointer-chrome](#f-mcp-pointer-chrome) — The pointer rung, chrome
half: click and drag termherd's own interface.
- [x] [F-mcp-options-bridge](#f-mcp-options-bridge) — `list_options` and
`set_option` on the live bridge too, over the stdio slice's catalogue.

<a id="f-mcp-ide-bridge"></a>

Expand Down Expand Up @@ -1233,6 +1255,23 @@ Apache-2.0 (cargo-deny allows it), edition 2024, tokio-native (reuses the #192
runtime, now `enable_all` for the listener); the http stack is
`hyper`/`hyper-util` (frugal base — no axum). Unblocks #194/#195/#196

<a id="f-mcp-options-bridge"></a>

### F-mcp-options-bridge

`list_options` and `set_option` on the live bridge, not only the stdio slice.

Shipped as #298. A session termherd launched — the only kind it launches —
saw seventeen tools, none about settings, so "switch me to a light theme"
needed the stdio server registered by hand. The bridge now carries both over
the same pure catalogue in `crates/mcp` (one implementation, two transports;
the tool descriptions are pinned equal by a test, since `#[tool]` takes only
a literal). Neither surface writes over a `settings.json` that does not
parse. Paired with the settings hot reload (#297): this makes the change
reachable from a session, that one makes it visible without a restart. Rung
of [F-mcp-control-surface](#f-mcp-control-surface), after
[F-mcp-config-write](#f-mcp-config-write).

<a id="f-mcp-orchestration"></a>

### F-mcp-orchestration
Expand Down
1 change: 1 addition & 0 deletions crates/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ termherd-core = { path = "../core" }
termherd-claude = { path = "../claude" }
termherd-scan = { path = "../scan" }
termherd-pty = { path = "../pty" }
termherd-mcp = { path = "../mcp" }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
single-instance = "0.3"
Expand Down
Loading
Loading