Command Reference

Input Modes

Every command follows the same interaction contract, in priority order:

ModeWhenUsage
InteractiveTTY detected (default)Run command with no args
FlagsOne or more flags providedmp <cmd> --flag value
Stdin JSONPiped inputecho '{}' | mp <cmd>
Example--schema flagmp <cmd> --schema

In interactive mode a Bubble Tea wizard walks you through the decisions, using defaults inferred from detected state; commands with several decisions are multi-step wizards. Flags skip the corresponding wizard steps — pass enough flags and the command runs straight through with no wizard. Stdin JSON is the fully programmatic path (JSON in, JSON out): there is no separate “agent mode”, the same CLI is the API. --schema prints an example input document in the same shape stdin JSON expects — edit it and pipe it right back in (mp init --schema | jq '.name = "x"' | mp init); it is not a formal JSON Schema document.

Ambiguity rule: non-interactive invocations (flags or JSON) fail loudly on genuine ambiguity rather than prompting or guessing — there is no human to ask. The wizard is the only place ambiguity gets resolved. (A pure no-op is not ambiguous.)

Output goes to stderr (human-readable) while stdout is reserved for JSON (machine-readable).

Remote execution. Three flags, placed between mp and the verb, route a command somewhere else before any of the above happens: mp --project <name> <cmd> runs it against a registered project (proxied over ssh when the registry entry has a host, from its path when local), and mp --host <ssh-host> [--dir <path>] <cmd> (env: MP_HOST / MP_DIR) is the raw form. Placement matters: after the verb these names belong to the verb (mp init --dir, mp switch --project). The whole invocation is forwarded verbatim to the mp binary on the host, so the remote surface is byte-identical — flags, stdin JSON, JSON out. See Remote development and mp remote doctor.

Multiplexer sessions are optional (the default multiplexer is none). When one is configured, mp manages its sessions only when driven interactively from inside it; see Sessions are interactive-only. Otherwise the verbs that land you in a worktree print its path.


mp shell-init

Print a shell function that makes your shell follow mp into the worktree. See Integrations for how it works.

eval "$(mp shell-init zsh)"       # ~/.zshrc
eval "$(mp shell-init bash)"      # ~/.bashrc
mp shell-init fish | source       # ~/.config/fish/config.fish

The function runs mp with MP_CWD_FILE set to a temp file and cds to the directory mp writes there. It also exports MP_SHELL_INIT=1, which mp doctor checks. Verbs that write the file: switch, go, create, adopt, inbox next/prev, agent focus, and done/abandon/cleanup when they remove the worktree you’re standing in. mp’s stdout and exit code are unchanged.


Shell Completion

Enable tab completion for mp commands:

Bash

source <(mp completion bash)
# Permanent: echo 'source <(mp completion bash)' >> ~/.bashrc

Zsh

source <(mp completion zsh)
# Or: mp completion zsh > "${fpath[1]}/_mp"

Fish

mp completion fish | source
# Permanent: mp completion fish > ~/.config/fish/completions/mp.fish

PowerShell

mp completion powershell | Out-String | Invoke-Expression

What completes

Argument or flagCompletes To
mp switch, mp open, mp status, mp done, mp abandon, mp wait (positional)Piece names
--piece / --parent on the piece verbsPiece names
mp config get / setConfig keys, then values
mp init --pr-providergithub, gitlab
--main on update, sync, merge, cleanupGit branch names

mp init

Initialize monkeypuzzle in current directory.

Usage

mp init                        # Interactive TUI
mp init --name foo             # With flags
echo '{"name":"foo"}' | mp init  # JSON stdin
mp init --schema               # Output schema

Flags

FlagDescriptionDefault
--nameProject nameDirectory name
--pr-providerPR provider (github, gitlab)github
--dirDirectory (relative to repo root) for monkeypuzzle state.monkeypuzzle
--gitignoreRegenerate <dir>/.gitignore only (no other changes)false
--schemaPrint an example input document and exit-
-y, --yesOverwrite existing configfalse

--dir lets you keep all monkeypuzzle state somewhere already ignored by git, e.g. mp init --dir .DONOTCOMMIT/monkeypuzzle. The repo→directory mapping is recorded in ~/.config/monkeypuzzle/project-dirs.json (it can’t live in monkeypuzzle.json, which is inside the directory being relocated). To relocate an existing project later, use mp move.

mp reinit is an alias for mp init — same flags, same behavior — for the explicit “refresh scaffolding in an already-initialized repo” case.

Example input

{
  "name": "project-name",
  "pr_provider": "github",
  "dir": ".monkeypuzzle"
}

Output

Creates the monkeypuzzle directory (default .monkeypuzzle/):

.monkeypuzzle/
├── monkeypuzzle.json    # Configuration
└── .gitignore           # Ignores pieces/ and per-piece metadata

The same per-piece paths are also added to the repo’s local .git/info/exclude, which git honours in every worktree. That keeps a piece’s piece-metadata.json invisible to git even when the piece was branched from a commit that predates the committed .gitignore (e.g. the first pieces after mp init, before that scaffold is committed), so mp cleanup and clean-tree checks never trip over mp’s own state. mp init, mp reinit, mp move and piece creation all refresh it.

Providers

PR Providers:


mp switch

The single switching entry point. Give it whatever you have in your head or clipboard — a piece name, a branch (local, remote, or already adopted), or a brand-new name — and it attaches, adopts, or creates as needed.

Usage

mp switch                                          # Interactive: picker scoped to the current project
mp switch --all                                    # Interactive: every registered project (same as `mp go`)
mp switch fix-x                                    # Piece or branch name; resolves in the current repo
mp switch feat/new-idea --create                   # Brand-new name: create piece "new-idea" on that branch
mp switch --project app                            # Attach app's main worktree
mp switch --project app --piece fix-x              # Attach an existing piece (skips resolution)
mp switch --project app --branch spike-token-rotate  # Adopt branch as piece (or attach if already one)
echo '{"target":"fix-x"}' | mp switch
echo '{"target":"feat/new-idea","create":true}' | mp switch
mp switch --schema

Target resolution

A positional TARGET resolves in order: main/master (attach the main worktree) → an existing piece by name, sanitized name, or the branch checked out in it (attach) → a local branch (adopt, then attach) → a remote ref, pasted verbatim (origin/foo) or by bare name (fetch + adopt tracking) → nothing (create a piece whose branch is TARGET verbatim and whose name is derived from it). A piece always beats an unadopted branch of the same name, so switching stays idempotent; --piece/--branch bypass resolution when you need to be explicit.

Creation is gated: on a terminal you’re asked to confirm; non-interactively an unmatched target is an error unless --create (or "create": true on stdin) is passed — a typo never silently mints a piece.

Flags

FlagDescription
--projectProject name or path (defaults to the repo you’re standing in)
--pieceExisting piece to attach (skips target resolution)
--branchGit branch to adopt as a piece (attaches if it already is one)
--createAllow an unmatched target to create a new piece
--allInteractive picker across all registered projects
--openAlso open the worktree with your opener (see mp open)
--withOpener command for --open (overrides $MP_OPEN and open_command)
--schemaPrint an example input document and exit

TARGET, --piece, and --branch are mutually exclusive. Omit all of them (with --project) to attach that project’s main worktree. Without --project, mp resolves the project from the current directory — any init’d repo works, registered or not.

Interactive picker

With a terminal and no selectors, opens a fuzzy-filtered list scoped to the current project (bare mp is the same view); --all or running outside a project widens it to every registered project:

Type to filter, ↑/↓ to move, enter to select, esc to cancel. The picker caps the visible rows at 20; narrow the query to surface anything below the cut.

What it does

  1. Resolves the project: the registry for an explicit --project, else the repo the caller is standing in.
  2. Based on the chosen selector:
    • target — runs the resolution above, then attaches / adopts / creates
    • piece — locates the worktree and attaches its multiplexer session
    • branch — attaches the piece holding that branch, else runs AdoptPiece with repo_root set to the project path, then attaches
  3. Attaching only happens when run interactively from inside the configured multiplexer (real TTY on stdin and the adapter’s in-session env var set). When no multiplexer is configured, or when called by an agent/script or from a terminal outside it, it prints the worktree path instead so cd "$(mp switch ...)" works (and the mp shell-init wrapper cds there for you).

Non-interactive shape

mp go --json (and the JSON form of mp switch when stdout isn’t a TTY) includes per-project pieces and branches arrays so callers can build their own pickers.


mp open

Open a piece’s worktree in your editor, IDE, or a new terminal window. See Integrations for recipes.

Usage

mp open                                  # the piece you're in, else the main worktree
mp open add-login                        # a piece or branch, resolved like `mp switch`
mp open add-login --with 'zed {path}'    # opener for this call only
MP_OPEN='code {path}' mp open add-login

TARGET resolves like mp switch (a piece by name or by its branch, or a local/remote branch, adopted first), but mp open never creates a piece. A piece placed on a box is refused with its host and path.

Flags

FlagDescription
--withOpener command for this call (overrides $MP_OPEN and open_command)
--jsonOutput JSON even on a terminal

Opener

The first of --with, $MP_OPEN, or the open_command config key. It is a command template with the placeholders {path}, {piece}, {project} and {branch}; each value is shell-quoted before substitution, and a template with no placeholder gets the path appended (code = code {path}). mp runs it with sh -c in the worktree. With no opener configured, mp open prints the worktree path on stdout and a list of mp config set open_command … recipes on stderr.

mp create --open and mp switch --open run the same opener after landing in the worktree; an opener failure there is a warning, not an error.

Output

{
  "piece": "add-login",
  "project": "demo",
  "path": "/home/user/demo/.monkeypuzzle/pieces/add-login",
  "branch": "add-login",
  "command": "zed '/home/user/demo/.monkeypuzzle/pieces/add-login'",
  "opened": true
}

branch and command are omitted when empty; with no opener configured there is no command and opened is false.


mp move

Relocate the current repository’s monkeypuzzle directory to a new path relative to the repo root, updating the mapping in ~/.config/monkeypuzzle/project-dirs.json.

Usage

mp move .DONOTCOMMIT/monkeypuzzle          # relocate
mp move --path .DONOTCOMMIT/monkeypuzzle   # same, via flag
echo '{"path":".DONOTCOMMIT/monkeypuzzle"}' | mp move
mp move .monkeypuzzle                      # move back to the default (clears the mapping entry)
mp move --schema

What it does

  1. Resolves the repo root (works from a subdirectory or inside a piece worktree).
  2. os.Renames the monkeypuzzle directory to the new location.
  3. Runs git worktree repair on the relocated piece worktrees so git keeps tracking them.
  4. Moves each piece’s per-piece state directory to mirror the new layout.
  5. Records the repo→directory mapping (or removes it when moved back to .monkeypuzzle).

Git tracking is left untouched — if monkeypuzzle.json was committed, run git add -A for the move yourself. The repo’s root .gitignore is not modified; relocating into .DONOTCOMMIT/ assumes that path is already ignored.

Output

JSON to stdout with repo_root, old_dir, new_dir, old_path, new_path, and the list of pieces that were moved/repaired. Human-readable summary to stderr.


mp flatten

Remove all piece worktrees for the current repository, returning it to a flat main-only state. Each piece’s multiplexer session is killed and its worktree removed.

Unlike mp cleanup (which only removes merged pieces), flatten removes every piece regardless of merge status. Branches are kept by default.

Dry-run by default, like the other sweep operations (mp cleanup, mp stack sync): it previews what would be removed and changes nothing. Pass --apply to actually flatten; in an interactive terminal you see the preview and confirm (--yes/-y skips the prompt and applies).

Usage

mp flatten                       # Preview (interactive terminal: preview + confirm)
mp flatten --apply               # Remove all pieces
mp flatten -y                    # Remove all (skip confirmation)
mp flatten --apply --force       # Also discard uncommitted changes
mp flatten --delete-branches     # Also delete each piece's git branch (with --apply)
echo '{"apply":true}' | mp flatten
mp flatten --schema

Flags

FlagDescriptionDefault
--applyApply the flatten (default is a dry-run preview)false
--yes, -ySkip the confirmation prompt (implies --apply)false
--forceForce removal even with uncommitted changesfalse
--delete-branchesAlso delete each piece’s git branchfalse
--dry-runForce a preview even with --apply-style stdinfalse

What it does

  1. Lists all pieces for the repo (works from the main repo or from inside a piece)
  2. Previews; in an interactive terminal, asks for confirmation (skip with --yes), and non-interactive callers only apply with --apply / "apply": true
  3. For each piece: switches you to the main session if you’re inside it, kills the multiplexer session, and removes the worktree (use --force to discard uncommitted changes)
  4. Optionally deletes each piece’s branch (--delete-branches)
  5. Continues past individual failures, reporting them under failed

Output

{
  "removed": [
    { "piece_name": "piece-a", "worktree_path": "/home/user/repo/.monkeypuzzle/pieces/piece-a", "branch_name": "piece-a" },
    { "piece_name": "piece-b", "worktree_path": "/home/user/repo/.monkeypuzzle/pieces/piece-b", "branch_name": "piece-b" }
  ],
  "count": 2,
  "main_path": "/home/user/repo"
}

Pieces that could not be removed (e.g. uncommitted changes without --force) appear in a failed array with an error message instead.


mp status

Show a piece’s status. Defaults to the piece you’re standing in (or the main repo); name one positionally or with --piece to inspect it from anywhere in the repo. A piece placed on a box (mp create --remote) is proxied there — see Remote development.

Usage

mp status
mp status my-feature

Output

JSON to stdout:

{
  "in_piece": true,
  "piece_name": "piece-20241226-143022",
  "worktree_path": "/home/user/projects/myproject/.monkeypuzzle/pieces/piece-20241226-143022",
  "repo_root": "/home/user/projects/myproject",
  "parent": "main",
  "stack_depth": 1,
  "can_merge": true
}

Human-readable message to stderr.


mp create

Create a new piece: a git worktree on its own branch (plus a multiplexer session, if you use one). Alias: mp new.

Usage

mp create
mp create --name my-feature
mp create --prompt "add dark mode"        # name auto-generated from the prompt
mp create --parent parent-piece           # stack on another piece
mp create --skip-switch  # Don't auto-switch to new piece
mp create --name my-feature --open        # also open it in your editor
mp create --remote wire --name fix-auth   # place the piece on the ssh box "wire"

Flags

FlagDescriptionDefault
--nameCustom piece nameAuto-generated
--promptCreate from a prompt (name auto-generated)-
-p, --parentParent piece name to branch from (stacks the piece)main
--skip-switchDon’t switch to the new piece after creationfalse
--overwrite-sessionReplace existing main repo multiplexer sessionfalse
--openAlso open the new worktree with your opener (see mp open)false
--withOpener command for --open (overrides $MP_OPEN and open_command)-
--remotePlace the piece on this ssh box: the worktree, hooks, agent and PR live there, the project stays here. First use clones + mp inits the repo on the box under ~/.local/share/mp/<project>. --parent must be main or a piece already on the same box. Also "remote" in stdin JSON. See Remote development-

What it does

  1. Detects current git repository root
  2. Generates piece name: piece-YYYYMMDD-HHMMSS (or uses --name)
  3. Creates git worktree at <repo>/.monkeypuzzle/pieces/<piece-name> on a new branch
  4. Fires the on-piece-create.sh hook (if exists) in the background — see below
  5. Switches to the new piece (unless --skip-switch is set) — but only when run interactively inside a multiplexer (see below); otherwise prints the worktree path

The on-piece-create.sh hook is fire-and-forget: it runs detached in the background so its setup work (dependency installs, submodule init) never blocks piece creation, and mp create returns immediately. The hook’s combined output is redirected to .monkeypuzzle/logs/on-piece-create-<piece-name>.log, and the path is printed when the hook starts. Only a failure to start the hook produces a warning; its exit status is not observed (check the log instead). The worktree is always kept regardless of how the hook fares.

The auto-switch only manages a session when run interactively from inside the configured multiplexer (a real TTY on stdin and the adapter’s in-session env var set): it moves your existing client/tab/workspace to the piece’s session. Run by an agent or script, from a terminal outside the multiplexer, or with no multiplexer configured, it creates no session and prints the worktree path instead (the mp shell-init wrapper cds there) — so --skip-switch is only needed to suppress switching in an interactive session.

Output

JSON to stdout (non-TTY or --json):

{
  "name": "piece-20241226-143022",
  "worktree_path": "/home/user/projects/myproject/.monkeypuzzle/pieces/piece-20241226-143022",
  "session_name": "mp/myproject/piece-20241226-143022"
}

session_name is the name the piece’s session has, or would have, under a multiplexer: mp/<project>/<piece>.

Piece storage

Piece worktrees live inside the repo, at <repo>/.monkeypuzzle/pieces/<piece-name>/ (or under the directory chosen with mp init --dir). mp init gitignores the pieces/ directory.


mp list

List pieces for the current repo as a tree (parent → child) or a flat list.

Usage

mp list           # tree view (human readable)
mp list --flat    # flat list (JSON-friendly)
mp list --all     # across all registered projects

Flags

FlagDescriptionDefault
--flatFlat list instead of the tree viewfalse
--allList pieces across all registered projectsfalse

Output (--flat)

[
  { "name": "feature-auth", "worktree_path": "/path", "parent": "main", "mod_time": "2025-01-04T10:00:00Z" },
  { "name": "auth-oauth", "worktree_path": "/path", "parent": "feature-auth", "mod_time": "2025-01-04T11:00:00Z" },
  { "name": "fix-auth", "worktree_path": "/home/u/.local/share/mp/api/.monkeypuzzle/pieces/fix-auth", "host": "wire", "state": "unknown", "parent": "main" }
]

Pieces placed on a box (mp create --remote, see Remote development) are listed too: host names the box, worktree_path is a path on the box, and state is unknown until refreshed or pending while the remote create is in flight. Local pieces carry neither field.


mp inbox

Your pieces across every registered project as one ordered list. Each row is a piece (project/piece) with the live state mp already knows — agent status, PR state, whether it merged — plus your own rank and note. Every picker and dashboard is a view over mp inbox --json.

Usage

mp inbox                     # table on a terminal (stderr); JSON when piped
mp inbox --json | jq .rows   # {"rows":[…]}
mp inbox --sort urgency      # blocked first, then review/working/idle/merged
mp inbox --refresh           # re-fetch PR state instead of the 2-minute cache

Works from any directory. An init’d repo you are standing in is included even if it was never registered.

Ordering

  1. Rows you have ranked (the order list in the state file), in that order.
  2. Everything else by urgency — blocked > review > working > idle > merged — newest first.
  3. Snoozed rows (snoozed_until in the future) sit at the bottom of every sort mode; they keep their urgency.

--sort urgency puts urgency ahead of your order. Urgency is derived, never stored: an agent waiting on you is blocked; an open non-draft PR or a finished agent is review; a running agent is working; a merged PR is merged; anything else is idle.

Flags

FlagDescriptionDefault
--sortrank (your order, urgency breaks ties) or urgencyrank
--refreshBypass the per-project PR cachefalse
--jsonOutput JSON even on a terminalfalse

Output

{
  "rows": [
    {
      "key": "api/fix-auth", "project": "api", "piece": "fix-auth", "rank": 1,
      "branch": "fix-auth", "parent": "main",
      "worktree_path": "/code/api/.monkeypuzzle/pieces/fix-auth",
      "session_name": "mp/api/fix-auth", "has_session": true,
      "agent_status": "blocked", "agent_counts": { "blocked": 1 },
      "pr": { "number": 12, "url": "https://github.com/o/api/pull/12", "state": "open", "draft": true },
      "merged": false, "urgency": "blocked",
      "note": "waiting on review", "snoozed_until": "2026-09-10T09:00:00Z",
      "snoozed": true,
      "updated_at": "2026-09-09T11:42:00Z"
    }
  ]
}

host, pr, note and snoozed_until are omitted when empty. snoozed is snoozed_until evaluated at list time, so pickers never compare timestamps.

State file

$MP_CONFIG_DIR/inbox.json (default ~/.config/monkeypuzzle/inbox.json), next to the user config:

{
  "version": 1,
  "order": ["monkeypuzzle/inbox-list", "api/fix-auth"],
  "notes": { "api/fix-auth": "waiting on review" },
  "snoozed": { "api/fix-auth": "2026-09-10T09:00:00Z" },
  "cache": { "api/fix-auth": { "pr": { "number": 12, "…": "…" }, "fetched_at": "…" } }
}

Keys are project/piece. Every list drops keys whose piece no longer exists and refreshes stale cache entries (one forge call per project, reused for 120s). A forge that is unreachable warns on stderr and leaves pr empty; it never fails the list.

Subcommands

Every subcommand takes flags or stdin JSON (--schema prints the shape), writes JSON to stdout and a one-liner to stderr on a terminal, and records a history event (inbox.moved, inbox.noted, inbox.snoozed).

SubcommandWhat it does
mp inbox move PIECE --top|--bottom|--up [N]|--down [N]|--before PIECE|--after PIECERe-rank; exactly one placement. The first move pins every row’s current rank. Prints {"key","rank","from_rank"}.
mp inbox note PIECE [text]Set the note; empty text or --clear removes it. Prints {"key","note"}.
mp inbox snooze PIECE --for 2d|--until RFC3339|--clearPark the row at the bottom (next/prev skip it) until then. Prints {"key","snoozed_until"} (null after --clear).
mp inbox next / mp inbox prevSwitch to the row after/before the piece you stand in, wrapping; snoozed rows skipped; --sort urgency respected. Same switch as mp switch (multiplexer, else the path on stdout); --json prints the switch result. Outside a piece next is rank 1, prev the last row.
mp inbox refreshmp inbox --refresh.

PIECE is project/piece, or a bare piece name: inside a repo that means this project’s piece first; elsewhere it must be unique across projects, or the command fails naming the candidates.

mp inbox move fix-auth --top             # inside api/: api/fix-auth
mp inbox move web/fix-auth --after nav   # explicit project; nav is unique
mp inbox note fix-auth "waiting on review"
mp inbox snooze fix-auth --for 2d
echo '{"piece":"api/fix-auth","up":2}' | mp inbox move
cd "$(mp inbox next)"                    # outside a multiplexer

mp update

Merge main branch into current piece.

Usage

mp update                  # Merge from 'main'
mp update --main develop  # Merge from 'develop'

Flags

FlagDescriptionDefault
--mainBranch to merge from (--main-branch is a deprecated alias)main

Requirements

What it does

  1. Verifies you’re in a piece worktree
  2. Runs before-piece-update.sh hook (if exists)
  3. Merges specified branch into current piece branch
  4. Runs after-piece-update.sh hook (if exists)
  5. Reports success/failure

If any hook fails, the operation is aborted.


mp sync

Sync the current piece with its parent (from piece metadata — another piece, or main for root pieces). Defaults to origin’s version of the parent: origin/<parent> is fetched and merged. The local parent branch is used only when origin doesn’t have it (or no origin is configured), or with --local.

For whole-stack syncing, see mp stack sync.

Usage

mp sync                        # Merge origin/<parent> into the piece
mp sync --local                # Merge the local parent branch instead
mp sync --from upstream/main   # Merge an explicit ref
echo '{"local":true}' | mp sync

Flags

FlagDescriptionDefault
--mainTrunk branch name, used when the piece’s parent is main (--main-branch is a deprecated alias)main
--fromExplicit ref to sync from (fetched when remote)—
--localUse the local parent branch, skip originfalse

Requirements

What it does

  1. Reads the piece’s parent from metadata (main → --main)
  2. Resolves the ref: --from override, else origin/<parent> when origin has the branch (fetched first), else the local parent branch
  3. Runs before-piece-update.sh hook (if exists)
  4. Merges the resolved ref into the current piece branch
  5. Runs after-piece-update.sh hook (if exists)

Output

{
  "piece_name": "auth-oauth",
  "parent": "feature-auth",
  "merged_ref": "origin/feature-auth",
  "source": "origin",
  "status": "synced"
}

source is origin, local, or override (--from). If origin is configured but unreachable, mp sync warns and falls back to the local parent.


mp merge

Merge piece back to main branch.

By default mp merge refuses when the target branch has commits the piece lacks (run mp update to pull them in). The gate is a policy, not a rule — bypass it any of three ways; the merge then proceeds with a warning and any conflicts surface from git:

BypassSpellingScope
CLI flagmp merge --no-update-checkthis call
stdin JSONecho '{"no_update_check":true}' | mp mergethis call
user configmp config set merge_require_updated falsealways

The result carries "update_check_skipped": true when the gate was bypassed.

Usage

mp merge                   # Merge to 'main'
mp merge --main develop  # Merge to 'develop'
mp merge --no-update-check       # target ahead: merge anyway
echo '{"no_update_check":true}' | mp merge

Flags

FlagDescriptionDefault
--mainBranch to merge into (--main-branch is a deprecated alias)main
--forceMerge even if the piece has child pieces (children are not re-homed)false
--reparent-childrenMerge a piece with children, re-homing them onto the merge targetfalse
--reparent-strategyHow to re-home children: rebase (rewrites history) or merge (no force-push)rebase
--no-update-checkMerge even if the target has commits not in the piece (conflicts surface from git)false

Requirements

What it does

  1. Verifies you’re in a piece worktree
  2. Runs before-piece-merge.sh hook (if exists)
  3. Checks main branch isn’t ahead (safety check)
  4. Switches to main branch in main repository
  5. Merges piece branch into main
  6. Runs after-piece-merge.sh hook (if exists)
  7. Reports success/failure

If any hook fails, the operation is aborted.

Safety check

If main has commits not in the piece, merge fails by default. Run mp update first to incorporate those changes, or bypass the check as above.


mp cleanup

Remove worktrees for merged pieces and prune deleted projects. Aliased as mp repair.

Dry-run by default. With no --apply, cleanup only previews what would be removed. Pass --apply (or "apply": true) to actually clean up. In an interactive terminal you are shown the preview and asked to confirm.

Usage

mp cleanup              # Preview what would be cleaned (dry-run)
mp cleanup --apply      # Actually remove merged pieces / prune projects
mp cleanup --dry-run    # Explicit preview (also suppresses the confirm prompt)
echo '{"apply":true}' | mp cleanup

Flags

FlagDescriptionDefault
--applyApply the cleanup (default is a dry-run preview)false
--yes, -ySkip the confirmation prompt (implies --apply)false
--dry-runPreview only; never prompt, never change anythingfalse
--mainMain branch to check merge status against (--main-branch deprecated)main

--force was removed: on cleanup it used to mean “apply”, clashing with its meaning everywhere else (override a safety check). Use --apply or --yes.

What it does

  1. Scans pieces directory for worktrees
  2. Checks if each piece’s branch is merged (via git branch, PR, or remote)
  3. Previews the merged pieces and stale projects that would be removed
  4. With --apply (or an interactive confirmation): removes each worktree, kills its multiplexer session, and prunes registry entries for deleted projects
  5. Child pieces of a removed piece are re-homed onto its parent (usually main) so they never become orphans — metadata only; run mp stack sync to restack them. Re-homed children are listed under reparented_children.
  6. Checks each placement (mp create --remote) against its box: drops stale links (gone on the box) and pending links (create never finished) the box has nothing for, heals a pending link whose piece the box does have, and drops the box’s hidden registry row once no links to it remain. Unreachable boxes keep their links. JSON: links[] with present/pending/healed/dropped.

mp abandon

Remove an unmerged piece (worktree, multiplexer session, optionally branch).

Usage

mp abandon                              # The piece you're standing in
mp abandon my-feature                   # By name
mp abandon my-feature --force           # Discard uncommitted changes
mp abandon foo --delete-branch          # Also delete git branch

Flags

FlagDescriptionDefault
--piecePiece to abandon (or pass it positionally)current
--nameDeprecated alias for --piece-
--forceForce removal even with uncommitted changesfalse
--delete-branchAlso delete the git branchfalse

What it does

  1. Finds the piece by the positional/--piece selector, else the piece the caller is standing in
  2. Kills the multiplexer session if it exists
  3. Removes the git worktree (use --force to discard uncommitted changes)
  4. Optionally deletes the git branch (--delete-branch)

Output

{
  "piece_name": "my-feature",
  "worktree_path": "/home/user/repo/.monkeypuzzle/pieces/my-feature",
  "branch_name": "my-feature",
  "branch_deleted": true
}

mp pr create

Create a pull/merge request for the current piece. Pushes the branch to origin and opens the PR/MR via the configured provider’s CLI — gh for GitHub or glab for GitLab (set pr_provider at mp init). Run from within a piece worktree.

Usage

mp pr create                                  # title/body from piece name
mp pr create --title "Add login" --body "..."
mp pr create --base develop                   # override the base branch
echo '{"title":"Add login","body":"..."}' | mp pr create
mp pr create --schema

Flags

FlagDescriptionDefault
--titlePR titlePiece name
--bodyPR description-
--baseBase branch to merge intoAuto-detect from parent piece

For a stacked piece, the base auto-detects to the parent piece’s branch so the PR targets the right branch in the stack.

Output

{ "url": "https://github.com/owner/repo/pull/123", "number": 123 }

mp pr ready

Flip the current piece’s draft PR/MR to ready-for-review. Reads the PR number from .monkeypuzzle/pr-metadata.json and fires the before-pr-ready / after-pr-ready hooks around the provider call. Run from within a piece worktree.

Usage

mp pr ready
echo '{}' | mp pr ready     # stdin accepted for uniformity (no fields)
mp pr ready --schema        # prints {} — ready takes no input
FlagDescription
--jsonOutput {"status":"ready"} even on a terminal
--schemaPrint an example input document and exit

mp done

Clean up a piece (worktree + multiplexer session) after its branch has been merged. Defaults to the piece you’re standing in; name one positionally or with --piece to finish it from anywhere in the repo.

By default mp done refuses a piece whose branch is not merged. The gate is a policy, not a rule — bypass it any of three ways; all keep the branch (only the worktree and session go), warn that the piece is unmerged, and warn how many commits its upstream lacks:

BypassSpellingScope
CLI flagmp done --forcethis call
stdin JSONecho '{"force":true}' | mp donethis call
user configmp config set done_require_merged falsealways

The result carries "forced": true when the gate was bypassed. To drop the branch as well, use mp abandon.

Child pieces of the finished piece are re-homed onto its parent (metadata only, listed under reparented_children); run mp stack sync to restack them. The same happens on mp abandon and mp cleanup.

Naming a piece placed on a box runs done there and drops the placement (same for abandon).

Usage

mp done
mp done my-feature
mp done --main develop
mp done --force                  # unmerged: remove worktree, keep branch
echo '{"force":true}' | mp done

Flags

FlagDescriptionDefault
--piecePiece to finish (or pass it positionally)current
--mainMain branch to check merge status against (--main-branch deprecated)main
--forceClean up even if not merged (branch kept locally)false

mp adopt

Convert an existing git branch into a piece worktree. Accepts a local branch name or a remote ref like origin/foo (remote refs are fetched and a tracking branch is created). Run from the main repo with no branch to adopt the current branch; from inside a piece worktree --branch is required.

Adopt does not require a clean working directory: it always creates a separate worktree, so uncommitted changes in the main checkout are left untouched. When the branch being adopted is the one currently checked out in the main worktree, adopt frees it by resetting the main worktree back to its main branch (main, or master), carrying any uncommitted work-in-progress along into the new piece worktree.

A branch checked out in a worktree mp doesn’t manage — e.g. one created by an agent’s worktree isolation (Claude Code) or a manual git worktree add — is adopted by relocating that whole worktree into the pieces dir (git worktree move), uncommitted changes and all. A stale worktree record whose directory has been deleted is pruned automatically before adopting. Two cases still refuse: a branch that is already a piece (use mp switch), and a branch held by a locked worktree (git worktree unlock it first).

When run interactively from inside the configured multiplexer, adopt creates and switches to the new piece’s session (like mp switch). For agents/automation it leaves the multiplexer untouched and reports the worktree path in the result JSON.

Usage

mp adopt                       # adopt the current branch (from main repo)
mp adopt my-spike              # adopt a local branch
mp adopt --branch origin/foo   # fetch + adopt a remote branch
mp adopt my-spike --parent feature-a   # adopt as a child of another piece
echo '{}' | mp adopt

Flags

FlagDescriptionDefault
-b, --branchBranch to adopt; local name or remote ref origin/fooCurrent branch on main
--nameOverride piece nameBranch name
-p, --parentParent piece namemain

mp stack

Whole-stack operations over pieces stacked via --parent (git-town-style). All operations are non-interactive: anything risky aborts cleanly and prints plain-English next steps (e.g. which PR base to change on GitHub).

mp stack status

Show the stack tree, PR/MR state, and drift vs the forge’s PR/MR list.

mp stack status
mp stack status --from-remote   # rebuild local lineage from open PR/MR bases
mp stack status --apply-bases   # edit PR/MR bases on the forge to match local lineage
FlagDescriptionDefault
--from-remoteRebuild local lineage from open PR/MR basesfalse
--apply-basesEdit PR/MR bases on the forge to match local lineagefalse
--mainMain branch namemain

(--from-github remains as a hidden, deprecated alias for --from-remote.)

mp stack sync

Propagate main and each parent down through the stack.

Dry-run by default. With no --apply, sync only previews which pieces would be synced. Pass --apply (or "apply": true) to actually sync. In an interactive terminal you are shown the preview and asked to confirm.

Sync source. Sync first updates local main from an upstream ref (a fetch and fast-forward) before propagating it down the stack. That ref is --from, defaulting to origin/<main>. In an interactive terminal you are prompted for it (enter to accept the default); non-interactive callers use the default. A ref whose remote isn’t configured (e.g. origin/main in a local-only repo) makes the main update a no-op.

mp stack sync                     # preview which pieces would be synced (dry-run)
mp stack sync --apply             # actually sync the stack
mp stack sync --from upstream/main --apply   # sync main from a different remote
mp stack sync --strategy rebase --apply   # rebase instead of the default merge
mp stack sync --push --apply      # push each branch after syncing
mp stack sync --stack             # limit the preview to the current piece's stack
FlagDescriptionDefault
--applyApply the sync (default is a dry-run preview)false
--dry-runPreview only; never prompt, never change anythingfalse
--fromUpstream ref to update main from (fetch + fast-forward)origin/<main>
--strategySync strategy: merge or rebasemerge
--pushPush each branch after syncing; already-merged pieces are skipped (listed under merged)false
--stackLimit to the current piece’s stack (run from a piece)false
--mainMain branch namemain

mp stack continue

Resume a conflicted rebase started by mp stack sync --strategy rebase (after resolving conflicts).

mp stack continue

mp stack undo

Restore every piece branch to the snapshot mp stack sync took right before its last run. Local only — remote branches are untouched; force-push with lease afterwards if you’d already pushed. Refuses to run if an affected worktree has uncommitted changes.

mp stack undo

mp stack set-parent

Re-point a piece onto a different parent — metadata only; run mp stack sync afterwards to actually restack the branches onto the new lineage. Defaults to the current piece when run from inside one.

mp stack set-parent --parent other-piece
mp stack set-parent --piece child-feat --parent main   # make it a root piece
FlagDescriptionDefault
--piecePiece to re-parentCurrent piece
--parentNew parent piece name, or main-

mp stack graph

Reconstruct a repository’s stacked-PR forest straight from the forge’s open PRs’ base→head edges — no local clone required. Auth comes from the ambient GH_TOKEN/GITHUB_TOKEN (or GITLAB_TOKEN) environment, so a server can run this as a specific user. This is the same forest the hosted dashboard renders — both go through the shared stackgraph builder.

mp stack graph --repo owner/name
mp stack graph --repo owner/name --provider gitlab --default-branch develop
FlagDescriptionDefault
--repoRepository as owner/name (required)-
--default-branchTrunk branchAuto-detected from the forge
--providerForge provider: github or gitlabgithub
--limitMax PRs to fetch200

mp stack append / prepend

append creates a new piece as a child of the current piece; prepend inserts a new piece between the current piece and its parent.

mp stack append --name child-feat
mp stack append --prompt "add caching layer"
mp stack prepend --name base-feat
FlagDescriptionDefault
--namePiece nameAuto-generated
--promptPiece prompt (recorded in piece metadata; used to name the piece)-

mp project

Manage the global registry of monkeypuzzle projects. A “project” is any git repo initialised with mp init (which registers it automatically). Registering projects lets mp list pieces across all of them and jump between their multiplexer sessions. Aliases: projects, proj.

Usage

mp project add                       # register the current directory
mp project add /path/to/repo
mp project add wire:code/api         # scp-style: a project on an ssh host
echo '{"path":"/repo"}' | mp project add
echo '{"host":"wire","path":"code/api"}' | mp project add

mp project list                      # human-readable table (alias: ls, status)
mp project list --json               # machine output
mp project list --all                # include hidden rows (box-side clones of placed pieces)

mp project remove my-project         # unregister (alias: rm); repo on disk untouched
mp project remove --target /path/to/repo

mp project list shows best-effort live state per project (current branch, number of pieces). Remote projects show as (remote) with a host:path location; their JSON rows carry a "host" field. Rows with "hidden": true are bookkeeping for placed pieces (mp create --remote, see Remote development) — shown as (hidden) only with --all; their "linked_from" is the controller-side repo root. The HOST:PATH form resolves the path to an absolute path on the host at add time and requires the repo to already be mp init-ed there — see Remote development.


mp go

A repo switcher: jump to any registered project’s worktree from anywhere. With a terminal it opens an interactive fuzzy picker where each repo starts collapsed (one row per repo). Press → to expand a repo and reveal its pieces and branches; ← collapses it again. Pressing Enter on a collapsed repo jumps straight to its main worktree. Typing filters across everything (collapsed or not), and the list scrolls (↑/↓, PgUp/PgDn), sizing its window to the terminal height. A single registered repo starts expanded.

With --json (or no TTY) it prints the full per-project detail (pieces, branches) so automation can build its own pickers.

Bare mp opens a fuzzy picker scoped to the current project (repo-local) — it shows the pieces and branches of the project you’re standing in. When run outside a monkeypuzzle project, bare mp prints context-aware guidance to stderr and then falls through to the cross-project picker (when you have registered projects to jump to):

Use mp go (or mp switch --all) for the explicit all-projects view from anywhere.

Usage

mp               # picker scoped to the current project (guidance if outside one)
mp go            # repo switcher: collapsed repos, → to expand (full JSON when not a TTY)
mp go --json     # force JSON output
mp --json        # JSON for the current project

The JSON form includes per-project pieces and branches arrays so callers can build their own pickers (see mp switch). Each piece carries a branch field (the branch checked out in its worktree — differs from the name for adopted branches), and the top level carries in_project / current_project so consumers can hard-scope to the caller’s repo. The branches array includes both local branches and remote-only refs (e.g. origin/foo, marked "remote": true) that have no local branch yet — selecting one fetches the remote and adopts it as a piece.


mp remote

Remote-host utilities for the ssh proxy (see Remote development).

Usage

mp remote doctor wire     # probe one ssh host
mp remote doctor          # probe every box in the project registry (hidden rows included)

Per box the JSON also carries pending_links — project/piece placements whose mp create --remote never finished (mp cleanup in that project heals or drops them) — and, when a path was probed, dir/init (is it an mp project there).

Like mp config, doctor uses positional args — there is no JSON-stdin mode. It reports, per host: key-based (BatchMode) ssh reachability, the remote mp version vs the local one, git and gh presence plus gh auth state, and whether tmux/herdr are installed (only relevant if you use one there). Human summary on stderr, JSON array on stdout; exits non-zero if a host is unreachable or missing mp/git. Run it once after setting up a host, and first whenever a proxied command misbehaves.


mp doctor

Check this machine’s mp setup. Changes nothing. For a remote host, use mp remote doctor.

mp doctor          # report on stderr (a terminal); JSON on stdout when piped
mp doctor --json   # JSON even on a terminal

It checks, in order: the user config file, the configured multiplexer (installed? is this terminal inside it?), whether the mp shell-init wrapper is loaded, what mp open will run, git, and, inside a project, the forge CLI for its provider (gh or glab, installed and authenticated) and whether Claude Code hooks report to mp. Each check is ok, warn or info, with a fix when there is one:

mp 5bc4f64
  ✓ config           /home/user/.config/monkeypuzzle/config.json
  ✓ multiplexer      none — mp prints worktree paths instead of managing sessions
  ✓ shell-init       loaded — mp follows you into the worktree
  · open_command     unset — `mp open` only prints the path
      mp config set open_command 'code {path}'
  ✓ git              present
  · project          /home/user/demo is a git repo but not an mp project
      mp init

JSON: {"version": "…", "checks": [{"name", "status", "detail", "fix"}]}.


mp config

Get and set user-level configuration (config.json in the user config directory, $MP_CONFIG_DIR if set; mp doctor prints the path). Uses positional args, not JSON stdin.

Until the file exists, mp refuses to run most commands (help, completion, config, shell-init, doctor and --schema work without it): on a terminal the first mp command offers a setup wizard that picks the multiplexer; elsewhere it asks you to run mp config set multiplexer ….

Usage

mp config get multiplexer
mp config set multiplexer none   # tmux, zellij, cmux, herdr, or none
mp config set open_command 'code {path}'  # what `mp open` runs
mp config set done_require_merged false   # let `mp done` clean up unmerged pieces
mp config set merge_require_updated false # let `mp merge` proceed when the target is ahead

Keys

KeyDescriptionValues
multiplexerTerminal multiplexer for piece sessions (Integrations)none (default), tmux, zellij, cmux, herdr
open_commandCommand template mp open runse.g. code {path}; placeholders {path} {piece} {project} {branch}; unset by default
done_require_mergedWhether mp done refuses unmerged pieces (--force bypasses per call)true (default), false
merge_require_updatedWhether mp merge refuses when the target is ahead (--no-update-check bypasses per call)true (default), false

Hooks

Hooks are executable shell scripts in .monkeypuzzle/hooks/ that run at key points during piece operations.

Available Hooks

HookTriggerExecution
on-piece-create.shAfter piece creationDetached (fire-and-forget); output logged to .monkeypuzzle/logs/
before-piece-update.shBefore mp update / mp syncBlocking
after-piece-update.shAfter successful update/syncBlocking
before-piece-merge.shBefore mp mergeBlocking (non-zero exit aborts the merge)
after-piece-merge.shAfter successful mergeBlocking
before-pr-create.sh / after-pr-create.shAround mp pr createBlocking
before-pr-ready.sh / after-pr-ready.shAround mp pr readyBlocking
is-piece-done.shConsulted by mp cleanup / mp merge’s merge-status checkBlocking; exit 0 means “treat as merged” — use to recognise squash-merges a plain branch-ancestry check would miss
agent-blocked.shPiece’s aggregate agent status transitions to blockedDetached
agent-done.shPiece’s aggregate agent status transitions to doneDetached
on-box-connect.shFirst mp create --remote=<box> of a project on a box (controller-side)Blocking; replaces the built-in clone + mp init + hook rsync entirely; non-zero aborts the placement. Env: MP_BOX, MP_REMOTE_PATH, MP_REPO_URL, MP_PROJECT, MP_HOOKS_DIR

Environment Variables

All hooks receive these environment variables:

VariableDescription
MP_PIECE_NAMEName of the piece
MP_WORKTREE_PATHAbsolute path to worktree
MP_REPO_ROOTAbsolute path to main repo
MP_MAIN_BRANCHMain branch name (merge/update)
MP_SESSION_NAMEMultiplexer session name (create; set only when a multiplexer is configured)
MP_PR_NUMBERPR/MR number (PR hooks)
MP_PR_URLPR/MR URL (PR hooks)
MP_PR_BASE_BRANCHPR/MR base branch (PR hooks)
MP_AGENT_IDReporting agent id (agent hooks)
MP_AGENT_KINDAgent kind, e.g. claude (agent hooks)
MP_AGENT_STATUSNew piece aggregate status (agent hooks)
MP_AGENT_PANEMultiplexer pane the agent runs in (agent hooks)
MP_PLACEMENT_HOSTBox name as the controller knows it (box-side hooks in a placed piece’s worktree; read from the piece metadata the box-side create wrote, so any invocation there sees it)
MP_REMOTE1 when the hook runs on a box for a placed piece
MP_BOXssh destination being connected (on-box-connect.sh)
MP_REMOTE_PATHIntended clone path on the box, unexpanded $HOME/.local/share/mp/<project> (on-box-connect.sh)
MP_REPO_URLThe project’s origin URL (on-box-connect.sh)
MP_PROJECTProject name (on-box-connect.sh)
MP_HOOKS_DIRController-side .monkeypuzzle/hooks/ (on-box-connect.sh)

Behavior

Example

.monkeypuzzle/hooks/before-piece-merge.sh:

#!/bin/bash
cd "$MP_WORKTREE_PATH"
echo "Running pre-merge checks for $MP_PIECE_NAME..."
go test ./... || exit 1

mp history

Read the append-only history log: one line per lifecycle event, across every repository on this machine. Recording never fails a verb — a write error is a stderr warning.

Usage

mp history                          # last 50 events, table on a terminal
mp history --project mp --since 24h
mp history --event 'pr.*' -n 10
mp history --piece login --json | jq .

Flags

FlagDescriptionDefault
--projectOnly events for this project nameall
--pieceOnly events for this pieceall
--eventExact event name, or a prefix with a trailing * (pr.*)all
--sinceOnly events newer than this Go duration (24h, 30m)all
-n, --limitKeep the last N matching events (0 = all)50
--jsonJSON lines on stdout even on a terminal

File

$MP_HISTORY_FILE, else ${XDG_STATE_HOME:-~/.local/state}/monkeypuzzle/history.jsonl. One JSON object per line:

{"ts":"2026-09-09T10:12:03Z","event":"pr.created","project":"mp","piece":"history-log","branch":"history-log","actor":{"kind":"agent","id":"claude-1"},"data":{"pr_number":83,"pr_url":"https://github.com/…/pull/83","base":"main"}}

actor.kind is agent when CLAUDECODE or MP_AGENT_ID is set, else user. branch, parent, host and data are present only when they carry something.

Events

EventFired by
piece.createdmp create, mp adopt (also piece.adopted)
piece.switchedmp switch
piece.updatedmp update, mp sync
piece.mergedmp merge
piece.donemp done
piece.abandonedmp abandon
piece.cleanedmp cleanup (per removed piece)
pr.createdmp pr create (data: pr_number, pr_url, base)
pr.readymp pr ready
agent.blocked, agent.donemp agent report aggregate transitions
stack.syncedmp stack sync apply (data.pieces: pieces touched)

Hook-backed events fire whether or not the hook script exists. Output follows the usual contract: non-TTY or --json prints JSON lines (oldest first) on stdout; a terminal gets a table on stderr.


Agent commands

Optional: these verbs track coding agents running in piece worktrees. See Integrations for how they fit together. Every other command is already agent-ready: flags or stdin JSON in, JSON out (see Input Modes).

mp agent

Track agent processes (Claude Code, codex, …) running inside piece worktrees. Each piece aggregates its agents’ statuses by severity — blocked > working

done > idle — and the aggregate surfaces as agent_status / agent_counts in mp go --json and mp list output.

Zero-install by default. mp agent list / summary recognize agent processes in a piece session’s panes (tmux) and classify their state from the visible screen: an open permission dialog is blocked, a running spinner is working, a resting prompt is idle. Nothing is written into the agent’s own configuration. Detection is deliberately strict about blocked, so a phrase in conversation text never raises a false alarm. On herdr, mp skips the screen entirely and reads the multiplexer’s own agent tracking, which also covers the done state and agents beyond claude/codex. With no multiplexer there are no panes to read, so agents show up only through the integration hooks below.

Optional precision. mp integration install claude wires Claude Code’s own hooks to mp agent report, which adds what a screen can’t show: the done state, stable session ids, coverage for headless agents (no pane), and the agent-blocked.sh / agent-done.sh lifecycle hooks on transitions. Both sources merge: hook records win identity, the screen wins liveness.

# Called by integration hooks, not usually by hand. Resolves the piece from
# the working directory; outside a piece it is a silent no-op (exit 0).
mp agent report --status blocked --id sess-1 --kind claude

# Claude Code hook mode: derives id + status from the hook payload on stdin
mp agent report --claude-hook --pid $PPID

# Every live agent across the project's pieces, blocked first
mp agent list --json

# Fleet view across all registered projects (implied outside a git repo,
# so status lines work from any cwd)
mp agent list --all --json

# Compact status-line segment, e.g. "🔴1 ⚡2" (empty when no agents)
mp agent summary

Status gone removes the agent’s record (sent on clean exit). Records whose process has died are reaped lazily on the next report.

# Check on an agent without switching focus: print its pane contents.
# Accepts an agent id or piece name (most attention-worthy agent wins).
mp agent read my-piece

# Answer a blocked agent / hand it a follow-up, as if typed into its pane
mp agent send my-piece "yes, and add tests"

read / send need a multiplexer with pane support (tmux, herdr). They are not TTY-gated: an orchestrating agent may drive its workers with them.

# Switch the client straight to an agent's pane — the CLI form of the tmux
# plugin's agent picker and blocked-jump chords.
mp agent focus my-piece            # by piece name (most attention-worthy agent)
mp agent focus sess-1              # by agent id
mp agent focus --blocked           # the most urgent blocked agent, no selector
mp agent focus --blocked --all     # across every registered project

If the agent’s session is no longer live, focus falls back to a plain piece switch (mp switch semantics: attaches an existing worktree, never adopts or creates). --blocked with nothing blocked exits 0 with a warning on stderr and no stdout output — nothing to report.

mp wait

Block until agents settle — no agent working in the target pieces.

# Fan out, then wait for the whole flock
mp create --name a --skip-switch --json   # worktree_path: start a worker there
mp create --name b --skip-switch --json
mp wait && mp agent list

mp wait a b --timeout 30m --interval 5s

Exits 0 when settled; the JSON pieces[].aggregate distinguishes blocked from done. Non-zero on timeout.

mp integration

# Merge mp's agent-report hooks into .claude/settings.json at the repo root.
# Idempotent; preserves existing settings. Run in the main repo and commit so
# every piece worktree gets it.
mp integration install claude

mp claude skill

# Create or regenerate .claude/skills/managing-monkeypuzzle/SKILL.md, the
# Claude Code skill describing the mp CLI. `mp init` writes it too.
mp claude skill