From fff2b2ce0547caf1b1a57dc15e9c8cfe1ee18435 Mon Sep 17 00:00:00 2001 From: Rousan Ali Date: Mon, 28 Sep 2026 15:55:59 +0530 Subject: [PATCH] feat(tmux): add a small accessory shell under nvim in the main window The main window's right side is now split: nvim on top and a short (20% height) shell below it for ad-hoc commands, so quick git/one-off commands don't need a trip to the run window. Claude stays full height on the left. Bump 4.11.0. Co-Authored-By: Claude Opus 5.5 (1M context) --- CLAUDE.md | 2 +- apps/cli/src/tmux.ts | 9 ++++++--- docs/commands.md | 2 +- npm/package.json | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 2c079d0..22df8dd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -231,7 +231,7 @@ The version gate: every runtime-touching command first checks the runtime `mx.js **Repos** (clones live at `repos//git/`, inside a per-repo container with a `repo.json`) — `mx repo`: `add [--name ]` (only command that clones; writes `repo.json`) · `new [--quick] [-o] [--description ]` (create a fresh **local** repo with no remote: `git init` on `main` + starter README + initial commit + `repo.json`; `--quick` also creates a `dev-` work + a worktree on `develop` and fires `post-worktree-create`, `-o` opens it — a one-shot quick-start for throwaway apps; the worktree forks `main` onto `develop` since the pristine holds `main`) · `ls [--lite]` (shows container path; `--lite` = a compact name + path table, one row per repo; porcelain `path`) · `-n fetch` (fires `pre/post-repo-fetch`; fast-forwards the checked-out **and** base branches; `mx repo fetch --all` fetches every repo) · `-n info` · `health` / `-n health` (same per-repo detail block showing only **metric** rows that carry a ✓/⚠ — current branch (vs default), uncommitted, untracked, ahead/behind (when upstream), last fetched (remote repos, ✓ if within 24h else stale), plus the captured `repo-health` hook output as the `extra` row; the repo name gets an aggregate ✓/⚠; without `-n` it prints every repo's block in turn) · `-n rm` (refuses if any work uses it). Health checks are purely local — they don't fetch; run `mx repo -n fetch` first if you want a fresh comparison against origin. -**Works** — `mx work`: `new [[:[:]]]... [--description ] [--branch ] [--base ] [--agent-managed] [--open|-o]` (creates folder, empty `work.json`, the per-work dirs `wt/`/`scripts/`/`files/`/`tmp/`/`sessions/`, the work `CLAUDE.md`; prints path; `--agent-managed` (alias `--agent-generated`; accepts `=true`/`=false`) stamps `isAgentManaged: true` in `work.json` — an AI agent creating a work for a scoped sub-task passes this so the work is grouped separately and skipped by session tooling; **positional repos after the name create initial worktrees**, each `[:[:]]` — per repo: branch = `:` → `--branch` → the work name, base = `:` → `--base` → pristine HEAD (empty branch like `app::develop` = default branch); repos validated up front so a bad/duplicate repo fails before any work is made; fires `pre/post-worktree-create` per worktree; `-o` builds the work's tmux session and opens it in a new terminal, see `attach`/`open`) · `ls [--all|--archived] [--lite]` (default: active only; shows folder path / porcelain `path`; `--lite` = a compact name + path table, one row per work; **groups into `user` and `agent` sections when agent-created (`isAgentManaged`) works are present** — flat otherwise) · `-n info` · `describe ` · `set-agent-managed [true|false]` (flip `isAgentManaged` on an existing work — the fix when an agent forgot `--agent-managed`; value defaults to true, `false` clears it) · `path` · `attach [--prompt ]` (the primary way in: **one work == one tmux session** `mx/`; builds it lazily if missing — `main` window with the work's Claude session (left; **resume-or-create keyed to the work name** — one name per work: if a session named `` exists (`findSessionsByName`, covering pre-tmux/hand-run sessions too) run `claude --resume ` (Claude resolves the title; picker on the rare duplicate), else create `claude -n `) + `nvim wt` (right; the work's worktrees folder, overridable in the `work-session` hook), and a `run` window with a 2×2 shell grid; seeds `MX_*` env incl. per-port `MX_PORT_*`; fires the `work-session` hook after building — then attaches THIS terminal (`switch-client` inside tmux, else `attach-session`); self-healing after reboot/kill; **interactive-only, so it has no `--porcelain` form** — passing `--porcelain` errors (`INTERACTIVE_ONLY`) and points at `ensure`) · `ensure [--prompt ]` (the non-attaching counterpart to `attach`: builds the work's tmux session if missing — same lazy, self-healing build, same `work-session` hook — but **does not attach**, leaving the current terminal alone; here `--porcelain` is a pure output-format flag (never changes behaviour), emitting `{work, session, created, claudeAction, attached:false}`; attach later with `attach`/`open`/`tmux attach -t mx/`) · `open` (alias `-o`: same, but a NEW terminal window — macOS fullscreen Terminal via osascript, Linux via `$MX_TERMINAL`/known emulators; falls back to printing the `attach` line) · `switch []` (jump between works: with `` it's `attach`; without, an fzf picker over this runtime's live `mx/*` sessions — `attachOrSwitch`) · `gc [--yes]` (prune **orphaned** sessions: live `mx/` sessions whose work — in this runtime, matched by the session's `MX_RUNTIME` — is archived or gone; warns on live panes, confirm or `--yes`; another runtime's sessions are never touched) · `worktree add [] [--branch ] [--base ]` (fires `pre-worktree-create`, then `post-worktree-create`; `` defaults to the repo and is the `wt/` dir + selector — pass a distinct one to hold **multiple worktrees of the same repo**) / `ls` / `rm ` (fires `pre/post-worktree-remove`) / `set-branch []` (metadata-only: after you `git checkout` a different branch in the worktree yourself, re-records its **live** branch in `work.json` — mx never checks out; optional `` guards against a mismatch; `DETACHED`/`BRANCH_MISMATCH`/`NO_WORKTREE` errors) · `port set []` / `unset` / `ls` · `health` / `-n health` (pure-local work-folder health: stray non-mx-native files in the work root, worktree presence vs `work.json`, cross-work port collisions from hand-edits, and archive invariants — an archived work should have its ports freed and worktrees removed; plus the captured `work-health` hook output as `extra`; bare `mx work health` shows every active work, `--all` adds archived) · `archive` (fires `pre/post-work-archive`; removes worktrees **and kills the work's tmux session** — warns first if a pane holds a live foreground process; keeps folder + manifest + sessions + branches; recoverable via `unarchive`; prompts for confirmation — pass `--yes`/`-y` to skip; required for `--porcelain` and non-TTY callers) · `unarchive [=...]` (fires `pre/post-work-unarchive`; re-creates worktrees from `work.json`; positional `=` overrides per-worktree when a recorded branch is missing) · `destroy --force` (PERMANENT: deletes the work folder including session summaries **and kills the tmux session**; branches still kept). `--base` resolves to a commit SHA (trying the ref, then `origin/`) so a bare branch name forks correctly; `worktree rm` / `archive` / `destroy` refuse on uncommitted changes; ports are unique across **all** works (no blocks). `archive` flips `isArchived: true` and stamps `archived_at` in `work.json`; `unarchive` clears them. All lifecycle hooks are central (`/hooks/`); a `pre-*` non-zero exit aborts the op (`HOOK_FAILED`), a `post-*` non-zero exit only warns — see § Hooks. +**Works** — `mx work`: `new [[:[:]]]... [--description ] [--branch ] [--base ] [--agent-managed] [--open|-o]` (creates folder, empty `work.json`, the per-work dirs `wt/`/`scripts/`/`files/`/`tmp/`/`sessions/`, the work `CLAUDE.md`; prints path; `--agent-managed` (alias `--agent-generated`; accepts `=true`/`=false`) stamps `isAgentManaged: true` in `work.json` — an AI agent creating a work for a scoped sub-task passes this so the work is grouped separately and skipped by session tooling; **positional repos after the name create initial worktrees**, each `[:[:]]` — per repo: branch = `:` → `--branch` → the work name, base = `:` → `--base` → pristine HEAD (empty branch like `app::develop` = default branch); repos validated up front so a bad/duplicate repo fails before any work is made; fires `pre/post-worktree-create` per worktree; `-o` builds the work's tmux session and opens it in a new terminal, see `attach`/`open`) · `ls [--all|--archived] [--lite]` (default: active only; shows folder path / porcelain `path`; `--lite` = a compact name + path table, one row per work; **groups into `user` and `agent` sections when agent-created (`isAgentManaged`) works are present** — flat otherwise) · `-n info` · `describe ` · `set-agent-managed [true|false]` (flip `isAgentManaged` on an existing work — the fix when an agent forgot `--agent-managed`; value defaults to true, `false` clears it) · `path` · `attach [--prompt ]` (the primary way in: **one work == one tmux session** `mx/`; builds it lazily if missing — `main` window with the work's Claude session (left; **resume-or-create keyed to the work name** — one name per work: if a session named `` exists (`findSessionsByName`, covering pre-tmux/hand-run sessions too) run `claude --resume ` (Claude resolves the title; picker on the rare duplicate), else create `claude -n `) + `nvim wt` (top right; the work's worktrees folder, overridable in the `work-session` hook) + a short accessory shell below nvim for ad-hoc commands, and a `run` window with a 2×2 shell grid; seeds `MX_*` env incl. per-port `MX_PORT_*`; fires the `work-session` hook after building — then attaches THIS terminal (`switch-client` inside tmux, else `attach-session`); self-healing after reboot/kill; **interactive-only, so it has no `--porcelain` form** — passing `--porcelain` errors (`INTERACTIVE_ONLY`) and points at `ensure`) · `ensure [--prompt ]` (the non-attaching counterpart to `attach`: builds the work's tmux session if missing — same lazy, self-healing build, same `work-session` hook — but **does not attach**, leaving the current terminal alone; here `--porcelain` is a pure output-format flag (never changes behaviour), emitting `{work, session, created, claudeAction, attached:false}`; attach later with `attach`/`open`/`tmux attach -t mx/`) · `open` (alias `-o`: same, but a NEW terminal window — macOS fullscreen Terminal via osascript, Linux via `$MX_TERMINAL`/known emulators; falls back to printing the `attach` line) · `switch []` (jump between works: with `` it's `attach`; without, an fzf picker over this runtime's live `mx/*` sessions — `attachOrSwitch`) · `gc [--yes]` (prune **orphaned** sessions: live `mx/` sessions whose work — in this runtime, matched by the session's `MX_RUNTIME` — is archived or gone; warns on live panes, confirm or `--yes`; another runtime's sessions are never touched) · `worktree add [] [--branch ] [--base ]` (fires `pre-worktree-create`, then `post-worktree-create`; `` defaults to the repo and is the `wt/` dir + selector — pass a distinct one to hold **multiple worktrees of the same repo**) / `ls` / `rm ` (fires `pre/post-worktree-remove`) / `set-branch []` (metadata-only: after you `git checkout` a different branch in the worktree yourself, re-records its **live** branch in `work.json` — mx never checks out; optional `` guards against a mismatch; `DETACHED`/`BRANCH_MISMATCH`/`NO_WORKTREE` errors) · `port set []` / `unset` / `ls` · `health` / `-n health` (pure-local work-folder health: stray non-mx-native files in the work root, worktree presence vs `work.json`, cross-work port collisions from hand-edits, and archive invariants — an archived work should have its ports freed and worktrees removed; plus the captured `work-health` hook output as `extra`; bare `mx work health` shows every active work, `--all` adds archived) · `archive` (fires `pre/post-work-archive`; removes worktrees **and kills the work's tmux session** — warns first if a pane holds a live foreground process; keeps folder + manifest + sessions + branches; recoverable via `unarchive`; prompts for confirmation — pass `--yes`/`-y` to skip; required for `--porcelain` and non-TTY callers) · `unarchive [=...]` (fires `pre/post-work-unarchive`; re-creates worktrees from `work.json`; positional `=` overrides per-worktree when a recorded branch is missing) · `destroy --force` (PERMANENT: deletes the work folder including session summaries **and kills the tmux session**; branches still kept). `--base` resolves to a commit SHA (trying the ref, then `origin/`) so a bare branch name forks correctly; `worktree rm` / `archive` / `destroy` refuse on uncommitted changes; ports are unique across **all** works (no blocks). `archive` flips `isArchived: true` and stamps `archived_at` in `work.json`; `unarchive` clears them. All lifecycle hooks are central (`/hooks/`); a `pre-*` non-zero exit aborts the op (`HOOK_FAILED`), a `post-*` non-zero exit only warns — see § Hooks. **Bin** (runtime-wide utility executables at `/bin/`, meant for `PATH`) — `mx bin` (alias `mx bins`): `ls` (list bins grouped into **built-in** (mx-shipped) and **user** sections, flag non-executable ones, and note whether `bin/` is on `PATH`; porcelain returns `{dir, onPath, bins[]}` with a `shipped` flag per bin) · `path` (print the `bin/` dir for `export PATH="$(mx bin path):$PATH"`). mx ships `dcs`/`lcs` (delete/list Claude Code sessions by name), `mx-ensure-sessions` (Node bin; build the tmux session for active works **without attaching**, in a chosen order, so they line up in tmux's session picker — via `mx work ensure`; **skips agent-created (`isAgentManaged`) works by default** — `--include-agent` to include them; takes exact names/globs + `-x`/`--except `, `--dry-run`; with no positional args follows an optional ordered config at `/files/mx-ensure-sessions.conf` — work lines, `*` = works not named elsewhere, `exclude:` globs; `--example` prints a starter, `--config`/`$MX_ENSURE_SESSIONS_CONFIG`/`--no-config` control it), and `mx-kill-sessions` (kill every live `mx/*` tmux session at once; confirms unless `-y`) from `templates/bin/`; shipped bins are **re-stamped (overwritten) on every `init`/`sync`** like the runtime `CLAUDE.md` (so updates land), while **user-added bins are never touched**. To customize a shipped bin, copy it to a new name. Distinct from a work's `scripts/` (per-work). Bare `mx bin` defaults to `ls`. diff --git a/apps/cli/src/tmux.ts b/apps/cli/src/tmux.ts index 2c21f4c..4b107fa 100644 --- a/apps/cli/src/tmux.ts +++ b/apps/cli/src/tmux.ts @@ -200,9 +200,9 @@ export interface BuildSessionOpts { * - session named `mx/`, marked with the `@mx_work` option and seeded with * `MX_*` environment (work, paths, runtime, ports) so every pane knows its * context; - * - window `main`: left pane runs the resolved `claude` command, right pane runs - * `nvim wt` (the work's worktrees folder — overridable in the work-session - * hook), focus on claude; + * - window `main`: left pane runs the resolved `claude` command; right side is + * `nvim wt` (top; the work's worktrees folder — overridable in the work-session + * hook) over a short accessory shell (bottom) for ad-hoc commands; focus on claude; * - window `run`: a 2x2 tiled grid of shells for dev servers and ad-hoc work. * * The session is created detached and generously sized so the splits compute @@ -264,6 +264,9 @@ export function buildSession(session: string, opts: BuildSessionOpts): void { // a pane index base. const nvimPane = tmux(['split-window', '-h', '-t', claudePane, '-c', workPath, '-P', '-F', '#{pane_id}']); tmux(['send-keys', '-t', nvimPane, 'nvim wt', 'Enter']); + // A short accessory shell under nvim for ad-hoc commands (git, quick runs) + // without leaving the main window. Kept small so nvim keeps most of the height. + tmux(['split-window', '-v', '-l', '20%', '-t', nvimPane, '-c', workPath]); tmux(['select-pane', '-t', claudePane]); // Run window: 2x2 grid of shells for servers / ad-hoc commands. Three splits // off the active pane then a tiled layout give an even 2x2. diff --git a/docs/commands.md b/docs/commands.md index 8cccdf5..ffb2d16 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -322,7 +322,7 @@ Enter a work — the **primary** way in. Every active work maps to exactly **one The default session mx builds: -- window `main` — LEFT pane: the work's Claude Code session; RIGHT pane: `nvim wt` (the work's `wt/` worktrees folder, not the work-folder root; overridable in the `work-session` hook). Focus starts on the claude pane. +- window `main` — LEFT pane: the work's Claude Code session; TOP-RIGHT pane: `nvim wt` (the work's `wt/` worktrees folder, not the work-folder root; overridable in the `work-session` hook); BOTTOM-RIGHT: a short (20% height) accessory shell for ad-hoc commands. Focus starts on the claude pane. - window `run` — a 2×2 tiled grid of shells for dev servers and ad-hoc commands. - session environment: `MX_WORK`, `MX_WORK_PATH`, `MX_RUNTIME`, `MX_TMUX=1`, `MX_CLAUDE_SESSION_ID`, plus one `MX_PORT__` per allocated port. diff --git a/npm/package.json b/npm/package.json index 868304a..0f33351 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@rousan/mx", - "version": "4.10.1", + "version": "4.11.0", "description": "mx — run several features in parallel across shared repos using git worktrees", "type": "module", "bin": {