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
52 changes: 42 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,64 @@ A fast, worktree-centric terminal manager for coding agents. One tab per repo (l

The CLI is `wtd`: a daemon per host owns the PTYs and git watches, and a local hub serves the browser UI and reaches every daemon over one protocol. See `architecture/` for the structure and its principles.

## Installation

Requires Linux and Node 20 or later. From a checkout, `pnpm build`, then:

```sh
node dist/wtd.mjs install-local # this machine
node dist/wtd.mjs install-local --systemd # also run the daemon as a systemd user unit
```

An installed `wtd` reinstalls itself the same way: `wtd install-local` or `wtd install-local --systemd`.

`wtd install-local` copies the bundle into a new release under `~/.local/share/worktree-term/versions/`, points `~/.local/share/worktree-term/current` at it, writes the shim `~/.local/bin/wtd` (bound to the Node that ran the installer) and a desktop launcher, `~/.local/share/applications/worktree-term.desktop`, that runs `wtd ui`. Reinstalling keeps the previous release, so running daemons and hubs keep working; older releases are removed. Run it again after moving Node.

With `--systemd` it also writes `~/.config/systemd/user/worktree-term-daemon.service`, enables it and starts it unless a daemon already runs; from then on every auto-start of the daemon goes through `systemctl --user start`. A unit's daemon stops when your last session ends unless lingering is on: `loginctl enable-linger`.

## Configuration

The hub reads `$XDG_CONFIG_HOME/worktree-term/config.json` (by default `~/.config/worktree-term/config.json`); every key is optional, and a new browser session picks up edits. `presets` lists the terminals a new tab or split can start, in order: `command` runs as `$SHELL -l -i -c <command>`, and `null` starts the plain login shell. Without `presets`, the only preset is a plain `shell`.

`repos` and `roots` belong to this machine: `repos` are the repo tabs (absolute or `~/…`), `roots` are where "Add repo" looks for repositories, 3 levels deep (default `["~"]`). `hosts` lists remote hosts, each shown after this machine's repos: `name` labels its tabs (`name:repo`), `ssh` is the SSH alias, `repos` must be absolute paths on that host, and its `roots` (`~` meaning the remote home) default to `["~"]`. "Add repo" and "Remove repo" on the page edit `repos` here, keeping everything else as written.

```json
{
"port": 7417,
"repos": ["~/src/app", "/srv/lib"],
"roots": ["~/src"],
"presets": [
{ "name": "claude", "command": "claude" },
{ "name": "shell", "command": null }
]
],
"hosts": [{ "name": "devbox", "ssh": "devbox", "repos": ["/home/me/src/api"], "roots": ["~/src"] }]
}
```

## Remote hosts

A remote host needs only Linux, Node 20 or later and an SSH alias that logs in without a prompt (keys or an agent: the hub runs SSH with `BatchMode=yes`). Install wtd there with

```sh
wtd install-remote devbox # finds Node on the remote PATH or in its login shell
wtd install-remote devbox --node /opt/node22/bin/node
```

or with "Install" on the page while the host is down. The remote gets the same layout as `wtd install-local`, without launcher or unit. Its shim runs the Node found at install time, so `"$HOME/.local/bin/wtd" connect` works over plain `ssh` with no dotfile changes.

Each browser session holds one `ssh <alias> "$HOME/.local/bin/wtd" connect` per remote host, multiplexed over one SSH control master per host (`ControlPersist=10m`); a lost link reconnects to the same daemon and keeps its terminals. A host that keeps failing shows as down with SSH's last error line. sshd's `MaxSessions` (default 10) caps the sessions of one master: each open page uses one per host, plus a brief one for each "Add repo" or "Remove repo". When a host's daemon speaks an older protocol, the page offers "Restart daemon" (this machine) or "Reinstall & restart" (a remote host), naming the terminals that will be killed. `WTD_SSH` replaces the `ssh` program.

## Keyboard

| Keys | Action |
| --- | --- |
| Alt+Up / Alt+Down | Previous / next worktree in the sidebar, under its filter |
| Alt+Left / Alt+Right | Previous / next terminal tab |
| Alt+Shift+arrows | Focus the pane in that direction |
| Ctrl+Shift+T | New tab |
| Ctrl+Shift+D / Ctrl+Shift+E | Split the focused pane right / down |
| Ctrl+Shift+W | Close the focused pane |
| Ctrl+Shift+C / Ctrl+Shift+V | Copy the selection / paste |
| Keys | Action |
| --------------------------- | --------------------------------------------------------- |
| Alt+Up / Alt+Down | Previous / next worktree in the sidebar, under its filter |
| Alt+Left / Alt+Right | Previous / next terminal tab |
| Alt+Shift+arrows | Focus the pane in that direction |
| Ctrl+Shift+T | New tab |
| Ctrl+Shift+D / Ctrl+Shift+E | Split the focused pane right / down |
| Ctrl+Shift+W | Close the focused pane |
| Ctrl+Shift+C / Ctrl+Shift+V | Copy the selection / paste |

The preset picker takes 1–9, the arrow keys and Enter, and Escape cancels it.

Expand Down
4 changes: 3 additions & 1 deletion architecture/hub.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ flowchart TD
subgraph platform["Platform"]
platform__files["Files"]
platform__dialer["Dialer"]
platform__install["Install"]
end
subgraph hub["Hub"]
hub__server["Server"]
Expand All @@ -29,6 +30,7 @@ flowchart TD
hub__server --> platform__files
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
hub__main --> hub__server
hub__main --> hub__router
hub__main --> hub__links
Expand All @@ -38,7 +40,7 @@ flowchart TD
hub__router --> hub__config
cli --> hub__main
classDef leaf fill:none;
class protocol,platform__files,platform__dialer,hub__server,hub__links,hub__config,hub__router,hub__main,cli leaf;
class protocol,platform__files,platform__dialer,platform__install,hub__server,hub__links,hub__config,hub__router,hub__main,cli leaf;
```
<!-- /likec4:hub -->

Expand Down
5 changes: 5 additions & 0 deletions architecture/model/typescript.c4
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ model {
}
files = component 'Files'
dialer = component 'Dialer'
install = component 'Install'
}

daemon = node 'Daemon' {
Expand Down Expand Up @@ -74,6 +75,8 @@ model {
}

platform.dialer -> platform.files
platform.install -> platform.files
platform.install -> platform.dialer

daemon -> protocol
daemon -> platform.files
Expand All @@ -89,6 +92,7 @@ model {
hub.server -> platform.files
hub.config -> platform.files
hub.links -> platform.dialer
hub.links -> platform.install
hub.main -> hub.server
hub.main -> hub.router
hub.main -> hub.links
Expand All @@ -101,6 +105,7 @@ model {
cli -> hub.main
cli -> platform.files
cli -> platform.dialer
cli -> platform.install
cli -> protocol

web.client -> protocol
Expand Down
9 changes: 7 additions & 2 deletions architecture/platform.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Purpose & context

Node-only infrastructure shared by daemon, hub and CLI: XDG paths and file I/O (`files`); daemon socket connect and auto-start (`dialer`).
Node-only infrastructure shared by daemon, hub and CLI: XDG paths and file I/O (`files`); reaching a host's daemon over its socket, with auto-start, or over `ssh` (`dialer`); installing the bundle locally or over `ssh` (`install`).

## Building blocks

Expand All @@ -15,6 +15,7 @@ flowchart TD
subgraph platform["Platform"]
platform__files["Files"]
platform__dialer["Dialer"]
platform__install["Install"]
end
daemon["Daemon"]
subgraph hub["Hub"]
Expand All @@ -24,14 +25,18 @@ flowchart TD
end
cli["CLI"]
platform__dialer --> platform__files
platform__install --> platform__files
platform__install --> platform__dialer
daemon --> platform__files
hub__server --> platform__files
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
cli --> platform__files
cli --> platform__dialer
cli --> platform__install
classDef leaf fill:none;
class platform__files,platform__dialer,daemon,hub__server,hub__links,hub__config,cli leaf;
class platform__files,platform__dialer,platform__install,daemon,hub__server,hub__links,hub__config,cli leaf;
```
<!-- /likec4:platform -->

Expand Down
7 changes: 6 additions & 1 deletion architecture/system.arc42.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ flowchart TD
subgraph platform["Platform"]
platform__files["Files"]
platform__dialer["Dialer"]
platform__install["Install"]
end
subgraph daemon["Daemon"]
daemon__worktrees["Worktrees"]
Expand All @@ -40,6 +41,8 @@ flowchart TD
web__main["Main"]
end
platform__dialer --> platform__files
platform__install --> platform__files
platform__install --> platform__dialer
daemon --> protocol
daemon --> platform__files
daemon__main --> daemon__server
Expand All @@ -53,6 +56,7 @@ flowchart TD
hub__server --> platform__files
hub__config --> platform__files
hub__links --> platform__dialer
hub__links --> platform__install
hub__main --> hub__server
hub__main --> hub__router
hub__main --> hub__links
Expand All @@ -64,6 +68,7 @@ flowchart TD
cli --> hub__main
cli --> platform__files
cli --> platform__dialer
cli --> platform__install
cli --> protocol
web__client --> protocol
web__main --> web__ui
Expand All @@ -74,7 +79,7 @@ flowchart TD
web__ui --> web__layout
web__terminals --> web__client
classDef leaf fill:none;
class protocol,platform__files,platform__dialer,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,hub__server,hub__links,hub__config,hub__router,hub__main,cli,web__client,web__terminals,web__layout,web__ui,web__main leaf;
class protocol,platform__files,platform__dialer,platform__install,daemon__worktrees,daemon__terminals,daemon__state,daemon__server,daemon__main,hub__server,hub__links,hub__config,hub__router,hub__main,cli,web__client,web__terminals,web__layout,web__ui,web__main leaf;
```
<!-- /likec4:system -->

Expand Down
18 changes: 9 additions & 9 deletions living-architecture.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
tracker: linear # detected: Linear MCP + egor/dev-NNNN-<slug> branches
openspec: true # detected: openspec/
architecture: true # detected: architecture/index.yaml
tracker: linear # detected: Linear MCP + egor/dev-NNNN-<slug> branches
openspec: true # detected: openspec/
architecture: true # detected: architecture/index.yaml
reviewers:
codex: true # detected: Codex MCP server available
codex: true # detected: Codex MCP server available
sonar:
project_key: MotleyAI_worktree-term # plan: design.md D8
issue_key_pattern: "DEV-\\d+" # plan: design.md D8
project_key: MotleyAI_worktree-term # plan: design.md D8
issue_key_pattern: "DEV-\\d+" # plan: design.md D8
commands:
test: pnpm test # plan: design.md D8
lint: pnpm lint # plan: design.md D8
test: pnpm test # plan: design.md D8
lint: pnpm lint # plan: design.md D8
typecheck:
typescript: tsc -b # plan: design.md D8
typescript: tsc -b # plan: design.md D8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-06
Loading
Loading