# Muse Porcelain Commands
> **Layer guide:** Muse commands are organised into three tiers.
> This document covers **Tier 2 — Core Porcelain**: the high-level,
> human-friendly commands that build on the Tier 1 plumbing layer.
> Tier 3 commands (MIDI, Bitcoin, Code) live in their own reference docs.
All porcelain commands accept `--format json` where documented below. JSON is
printed to `stdout`; human text goes to `stdout` too; error messages always go
to `stderr`. Exit codes follow the same convention as the plumbing layer:
`0` success · `1` user error · `3` internal error.
---
## Quick Index
| Command | Description |
|---------|-------------|
| [`init`](#init) | Initialise a new Muse repository |
| [`commit`](#commit) | Record the working tree as a new version |
| [`status`](#status) | Show working-tree drift against HEAD |
| [`log`](#log) | Display commit history |
| [`diff`](#diff) | Compare working tree or two commits |
| [`show`](#show) | Inspect a commit — metadata, diff, files |
| [`branch`](#branch) | List, create, or delete branches |
| [`checkout`](#checkout) | Switch branches or restore a snapshot |
| [`merge`](#merge) | Three-way merge a branch into the current branch |
| [`rebase`](#rebase) | Replay commits onto a new base |
| [`reset`](#reset) | Move HEAD to a prior commit |
| [`revert`](#revert) | Undo a commit by creating a new one |
| [`cherry-pick`](#cherry-pick) | Apply a single commit's changes |
| [`stash`](#stash) | Shelve and restore uncommitted changes |
| [`tag`](#tag) | Attach and query semantic tags on commits |
| [`blame`](#blame) | Line-level attribution for any text file |
| [`reflog`](#reflog) | History of HEAD and branch-ref movements |
| [`rerere`](#rerere) | Reuse recorded conflict resolutions |
| [`gc`](#gc) | Garbage-collect unreachable objects |
| [`archive`](#archive) | Export a snapshot as tar.gz or zip |
| [`bisect`](#bisect) | Binary-search through history for a regression |
| [`worktree`](#worktree) | Multiple simultaneous branch checkouts |
| [`clean`](#clean) | Remove untracked files from the working tree |
| [`describe`](#describe) | Label a commit by its nearest tag |
| [`shortlog`](#shortlog) | Commit summary grouped by author or agent |
| [`verify`](#verify) | Whole-repository integrity check |
| [`snapshot`](#snapshot) | Explicit snapshot management |
| [`bundle`](#bundle) | Pack and unpack commits for offline transfer |
| [`content-grep`](#content-grep) | Full-text search across tracked file content |
| [`whoami`](#whoami) | Show the current identity |
| [`config`](#config) | Read and write repository configuration |
---
## Established Core Porcelain
### `init` — initialise a repository
Create a fresh `.muse/` directory in the current folder.
```
muse init # initialise in current dir
muse init --domain midi # set the active domain
muse init -d code # short flag
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--domain` | `-d` | `midi` | Domain plugin to activate for this repo |
**Exit codes:** `0` success · `1` already initialised
---
### `commit` — record the working tree
Snapshot the working tree and write a commit pointing to it.
```
muse commit -m "verse melody"
muse commit --message "Add chorus" --author "gabriel"
muse commit --allow-empty
muse commit --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--message` | `-m` | `""` | Commit message |
| `--author` | `-a` | config value | Override the author name |
| `--allow-empty` | `-e` | off | Commit even when nothing changed |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"commit_id": "a3f2...c8d1",
"branch": "main",
"message": "Add verse melody",
"author": "gabriel",
"committed_at": "2026-03-21T12:00:00+00:00",
"snapshot_id": "b7e4...f912",
"sem_ver_bump": "minor"
}
```
`sem_ver_bump` is `"none"`, `"patch"`, `"minor"`, or `"major"` depending on the
domain plugin's assessment of the change.
**Exit codes:** `0` committed · `1` nothing to commit (and `--allow-empty` not given)
---
### `status` — show drift against HEAD
```
muse status
muse status --short
muse status --json # machine-readable
muse status -s -j
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--short` | `-s` | off | Compact one-line-per-file output |
| `--json` | `-j` | off | JSON output (equivalent to `--format json`) |
**Text output:**
```
On branch main
Modified:
tracks/bass.mid
Added:
tracks/lead.mid
```
`clean` when working tree matches HEAD.
**JSON output**
```json
{
"branch": "main",
"clean": false,
"modified": ["tracks/bass.mid"],
"added": ["tracks/lead.mid"],
"deleted": []
}
```
**Exit codes:** `0` always (non-zero drift is shown, not signalled)
---
### `log` — display commit history
```
muse log
muse log --limit 20
muse log --branch feat/audio
muse log --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--branch` | `-b` | current | Branch to walk |
| `--limit` | `-n` | 50 | Max commits to emit |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output** — an array of commit records, newest first:
```json
[
{
"commit_id": "a3f2...c8d1",
"branch": "main",
"message": "Add verse melody",
"author": "gabriel",
"committed_at": "2026-03-21T12:00:00+00:00",
"snapshot_id": "b7e4...f912",
"parent_commit_id": "ff01...23ab",
"sem_ver_bump": "minor"
}
]
```
**Exit codes:** `0` always
---
### `diff` — compare working tree or two commits
```
muse diff # working tree vs HEAD
muse diff --from HEAD~3
muse diff --from v1.0 --to v2.0
muse diff --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--from` | `-f` | HEAD | Ref or commit to diff from |
| `--to` | `-t` | working tree | Ref or commit to diff to |
| `--format` | — | `text` | `text` or `json` |
**JSON output**
```json
{
"from": "ff01...23ab",
"to": "a3f2...c8d1",
"added": ["tracks/lead.mid"],
"removed": ["tracks/old.mid"],
"modified": ["tracks/bass.mid"],
"total_changes": 3
}
```
> **Note:** The JSON field is `"total_changes"` (not `"ops"` or `"changes"`).
**Exit codes:** `0` always
---
### `show` — inspect a commit
```
muse show HEAD
muse show abc123
muse show --format json
muse show --stat HEAD # files changed, not full diff
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--ref` | `-r` | HEAD | Commit or branch to inspect |
| `--stat` | `-s` | off | Show file-level summary instead of raw diff |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output — full diff mode**
```json
{
"commit_id": "a3f2...c8d1",
"branch": "main",
"message": "Add verse melody",
"author": "gabriel",
"committed_at": "2026-03-21T12:00:00+00:00",
"snapshot_id": "b7e4...f912",
"parent_commit_id": "ff01...23ab",
"delta": {
"added": ["tracks/lead.mid"],
"removed": [],
"modified": ["tracks/bass.mid"]
}
}
```
**JSON output — `--stat` mode**
```json
{
"commit_id": "a3f2...c8d1",
"branch": "main",
"message": "Add verse melody",
"author": "gabriel",
"committed_at": "2026-03-21T12:00:00+00:00",
"snapshot_id": "b7e4...f912",
"parent_commit_id": "ff01...23ab",
"files_added": 1,
"files_removed": 0,
"files_modified": 1
}
```
**Exit codes:** `0` found · `1` commit not found
---
### `branch` — list, create, or delete branches
```
muse branch # list all
muse branch feat/reverb # create
muse branch --delete feat/reverb
muse branch -d feat/reverb # short flag
muse branch --format json # structured list
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--delete` | `-d` | off | Delete a branch |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output — list**
```json
{
"current": "main",
"branches": [
{"name": "dev", "commit_id": "ff01...23ab", "is_current": false},
{"name": "main", "commit_id": "a3f2...c8d1", "is_current": true}
]
}
```
**JSON output — create / delete**
```json
{"action": "created", "branch": "feat/reverb"}
{"action": "deleted", "branch": "feat/reverb"}
```
**Exit codes:** `0` success · `1` branch already exists (create) or not found (delete)
---
### `checkout` — switch branches or restore snapshot
```
muse checkout main
muse checkout feat/guitar
muse checkout --create feat/new-idea # create and switch
muse checkout -c feat/new-idea # short flag
muse checkout --format json # machine-readable result
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--create` | `-c` | off | Create branch then switch |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"action": "switched",
"branch": "feat/guitar",
"commit_id": "a3f2...c8d1"
}
```
`"action"` is one of `"switched"`, `"created"`, or `"already_on"` (when you
check out the branch that is already active).
**Exit codes:** `0` success · `1` branch not found (and `--create` not given)
---
### `merge` — three-way merge
```
muse merge feat/audio
muse merge --message "Merge audio feature"
muse merge --abort
muse merge --continue
muse merge --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--message` | `-m` | auto | Override merge commit message |
| `--abort` | `-a` | off | Abort an in-progress merge |
| `--continue` | `-c` | off | Resume after resolving conflicts |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output — clean merge**
```json
{
"action": "merged",
"branch": "feat/audio",
"commit_id": "a3f2...c8d1",
"message": "Merge feat/audio into main"
}
```
**JSON output — conflict**
```json
{
"action": "conflict",
"branch": "feat/audio",
"conflicts": ["tracks/bass.mid"]
}
```
**Conflict flow:**
1. `muse merge ` → conflict reported, writes `MERGE_STATE.json`
2. Resolve files manually
3. `muse merge --continue` → commit the merge
4. Or `muse merge --abort` → restore original HEAD
**Exit codes:** `0` merged · `1` conflict or bad arguments
---
### `rebase` — replay commits onto a new base
Muse rebase replays a sequence of commits onto a new base using the same
three-way merge engine as `muse merge`. Because commits are content-addressed,
each replayed commit gets a **new ID** — the originals are untouched in the store.
```
muse rebase main # replay current branch onto main
muse rebase --onto newbase upstream # replay onto a specific base
muse rebase --squash main # collapse all commits into one
muse rebase --squash -m "feat: all in" # squash with custom message
muse rebase --abort # restore original HEAD
muse rebase --continue # resume after conflict resolution
muse rebase --format json
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--onto [` | `-o` | New base commit |
| `--squash` | `-s` | Collapse all commits into one |
| `--message ` | `-m` | Message for squash commit |
| `--abort` | `-a` | Abort and restore original HEAD |
| `--continue` | `-c` | Resume after resolving a conflict |
| `--format ` | `-f` | `text` or `json` |
**JSON output — squash rebase**
```json
{
"action": "squash_rebase",
"onto": "main",
"new_commit_id": "a3f2...c8d1",
"commits_squashed": 4,
"branch": "feat/audio"
}
```
**JSON output — normal rebase**
```json
{
"action": "rebase",
"onto": "main",
"branch": "feat/audio",
"commits_replayed": 4,
"new_tip": "a3f2...c8d1"
}
```
**Conflict flow:**
1. `muse rebase main` → conflict reported, writes `REBASE_STATE.json` and `MERGE_STATE.json`
2. Resolve files manually
3. `muse rebase --continue` → commit the resolved state and continue
4. Or `muse rebase --abort` → restore the original branch pointer
**State file:** `.muse/REBASE_STATE.json` — tracks remaining/completed commits
and the `onto` base. Cleared automatically on successful completion or `--abort`.
**Exit codes:** `0` clean · `1` conflict or bad arguments
---
### `reset` — move HEAD to a prior commit
```
muse reset HEAD~1 # move back one commit
muse reset abc123 # move to specific commit
muse reset --hard # also reset working tree
muse reset --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--hard` | `-H` | off | Also update the working tree to match |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"action": "reset",
"branch": "main",
"previous_commit": "a3f2...c8d1",
"new_commit": "ff01...23ab",
"hard": false
}
```
**Exit codes:** `0` success · `1` commit not found
---
### `revert` — undo a commit by creating a new one
Non-destructive: the original commit remains in history. A new commit is
created whose effect is the inverse of the target commit.
```
muse revert HEAD
muse revert abc123
muse revert --message "Undo broken change"
muse revert --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--message` | `-m` | auto | Override revert commit message |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"action": "reverted",
"reverted_commit": "a3f2...c8d1",
"new_commit_id": "b7e4...f912",
"branch": "main",
"message": "Revert \"Add verse melody\""
}
```
**Exit codes:** `0` success · `1` commit not found or nothing to revert
---
### `cherry-pick` — apply a single commit's changes
```
muse cherry-pick abc123
muse cherry-pick abc123 --message "Cherry: verse fix"
muse cherry-pick abc123 --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--message` | `-m` | auto | Override the cherry-picked commit message |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"action": "cherry_picked",
"source_commit": "abc1...2345",
"new_commit_id": "a3f2...c8d1",
"branch": "main",
"message": "Cherry: verse fix"
}
```
**Exit codes:** `0` success · `1` commit not found or conflict
---
### `stash` — shelve and restore changes
```
muse stash push -m "WIP: bridge section"
muse stash list
muse stash list --format json
muse stash pop
muse stash drop 0
```
**Subcommands**
| Subcommand | Description |
|------------|-------------|
| `push` | Stash current working-tree changes |
| `list` | List saved stashes |
| `pop` | Restore the most recent stash and drop it |
| `apply ` | Restore stash N without dropping it |
| `drop ` | Delete stash N |
| `show ` | Show what a stash contains |
**Flags — `push`**
| Flag | Short | Description |
|------|-------|-------------|
| `--message` | `-m` | Label for the stash entry |
**Flags — `list` / `show`**
| Flag | Short | Description |
|------|-------|-------------|
| `--format` | `-f` | `text` or `json` |
**JSON output — `list`**
```json
[
{
"index": 0,
"message": "WIP: bridge section",
"created_at": "2026-03-21T12:00:00+00:00",
"branch": "feat/audio"
}
]
```
**Exit codes:** `0` success · `1` stash index out of range or nothing to stash
---
### `tag` — semantic tags on commits
```
muse tag v1.0.0
muse tag v1.0.0 --commit abc123
muse tag list
muse tag list --format json
muse tag delete v0.9.0
muse tag show v1.0.0 --format json
```
**Subcommands**
| Subcommand | Description |
|------------|-------------|
| `` | Create a tag on HEAD (or `--commit`) |
| `list` | List all tags |
| `show ` | Inspect a tag |
| `delete ` | Delete a tag |
**Flags — create**
| Flag | Short | Description |
|------|-------|-------------|
| `--commit` | `-c` | Attach tag to a specific commit ID |
| `--message` | `-m` | Optional annotation |
| `--format` | `-f` | `text` or `json` |
**JSON output — create**
```json
{"action": "created", "name": "v1.0.0", "commit_id": "a3f2...c8d1"}
```
**JSON output — `list`**
```json
[
{"name": "v1.0.0", "commit_id": "a3f2...c8d1", "created_at": "2026-03-21T12:00:00+00:00"}
]
```
**Exit codes:** `0` success · `1` tag or commit not found
---
### `blame` — line-level attribution
```
muse blame song.mid
muse blame --format json song.mid
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--format` | `-f` | `text` | `text` or `json` |
| `--ref` | `-r` | HEAD | Branch or commit to blame against |
**JSON output**
```json
[
{
"line": 1,
"content": "tempo: 120",
"commit_id": "a3f2...c8d1",
"author": "gabriel",
"committed_at": "2026-03-21T12:00:00+00:00",
"message": "Add verse melody"
}
]
```
**Exit codes:** `0` success · `1` file or ref not found
---
### `reflog` — HEAD and branch movement history
```
muse reflog
muse reflog --branch feat/audio
muse reflog --limit 50
muse reflog --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--branch` | `-b` | HEAD | Branch to show reflog for |
| `--limit` | `-n` | 50 | Max entries to show |
| `--format` | `-f` | `text` | `text` or `json` |
The reflog is the "undo safety net" — every ref movement is recorded so
you can recover from accidental resets, force-pushes, or botched rebases.
**JSON output**
```json
[
{
"index": 0,
"commit_id": "a3f2...c8d1",
"action": "commit",
"message": "Add verse melody",
"author": "gabriel",
"moved_at": "2026-03-21T12:00:00+00:00"
}
]
```
`"index"` is 0-based, with 0 being the most recent entry. `"action"` describes
what caused the ref movement: `"commit"`, `"merge"`, `"rebase"`, `"reset"`, `"checkout"`, etc.
**Exit codes:** `0` always
---
### `rerere` — reuse recorded resolutions
```
muse rerere list # show cached resolutions
muse rerere apply # auto-apply cached fixes to current conflicts
muse rerere forget abc123 # remove a cached resolution
muse rerere status # show which conflicts have cached resolutions
```
**How it works:** After a successful merge, Muse records the resolution in
`.muse/rerere/`. On future conflicts with the same "conflict fingerprint",
`rerere apply` replays the resolution automatically.
**Exit codes:** `0` resolution applied or listed · `1` no matching resolution
---
### `gc` — garbage collect
Removes objects that are not reachable from any branch or tag ref. Orphaned
commits (e.g. after a reset), dangling snapshots, and unreferenced blobs are
all eligible.
```
muse gc # remove unreachable objects
muse gc --dry-run # preview what would be removed
muse gc -n # short flag for --dry-run
muse gc --format json
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--dry-run` | `-n` | off | Preview without deleting |
| `--format` | `-f` | `text` | `text` or `json` |
**JSON output**
```json
{
"commits_removed": 2,
"snapshots_removed": 2,
"objects_removed": 11,
"bytes_freed": 204800,
"dry_run": false
}
```
**Exit codes:** `0` always
---
### `archive` — export a snapshot
```
muse archive HEAD
muse archive HEAD --format zip --output release.zip
muse archive v1.0.0 --prefix project/
```
**Flags**
| Flag | Short | Default | Description |
|------|-------|---------|-------------|
| `--format` | `-f` | `tar.gz` | Archive format: `tar.gz` or `zip` |
| `--output` | `-o` | `.tar.gz` | Output file path |
| `--prefix` | `-p` | `""` | Directory prefix inside the archive |
All archive entries use the `prefix/` directory. Tar-slip / zip-slip are
prevented: entry paths are validated to stay within the prefix.
**Exit codes:** `0` archive written · `1` ref not found · `3` I/O error
---
### `bisect` — binary-search for a regression
Muse bisect works on any domain — not just code. Use it to find which commit
introduced a melody change, a tuning drift, or a data regression.
```
muse bisect start
muse bisect bad HEAD # mark HEAD as bad (broken)
muse bisect good v1.0.0 # mark v1.0.0 as good (working)
muse bisect bad # mark the currently-tested commit as bad
muse bisect good # mark the currently-tested commit as good
muse bisect skip # skip an untestable commit
muse bisect log # show the current session log
muse bisect reset # end the session and restore HEAD
muse bisect run pytest # automated bisect: run command, 0=good, 1=bad
```
**Subcommands**
| Subcommand | Description |
|------------|-------------|
| `start` | Begin a new bisect session |
| `bad [][]` | Mark a commit as bad; omit to mark current |
| `good [][]` | Mark a commit as good; omit to mark current |
| `skip [][]` | Skip a commit that cannot be tested |
| `log` | Print the current session state |
| `reset` | End the session; restore the original branch |
| `run ` | Automate: run command, exit 0 = good, exit 1 = bad |
Bisect narrows the search range using binary search. On each step, Muse
checks out the midpoint commit and waits for a verdict (`good`/`bad`/`skip`).
The search converges in O(log N) steps regardless of domain.
**Exit codes:** `0` session active or found · `1` bad arguments · `3` I/O error
---
### `worktree` — multiple simultaneous checkouts
```
muse worktree add /path/to/dir feat/audio
muse worktree list
muse worktree list --format json
muse worktree remove feat/audio
muse worktree prune
```
**Subcommands**
| Subcommand | Description |
|------------|-------------|
| `add ` | Check out `branch` into `path` |
| `list` | List registered worktrees |
| `remove ` | Remove a linked worktree |
| `prune` | Remove entries for deleted directories |
**Flags — `list`**
| Flag | Short | Description |
|------|-------|-------------|
| `--format` | `-f` | `text` or `json` |
**JSON output — `list`**
```json
[
{"path": "/home/g/muse-main", "branch": "main", "is_main": true},
{"path": "/home/g/muse-audio", "branch": "feat/audio", "is_main": false}
]
```
**Exit codes:** `0` success · `1` path or branch conflict
---
### `clean` — remove untracked files
Scans the working tree against the HEAD snapshot and removes files not tracked
in any commit. `--force` is required to actually delete files (safety guard).
```
muse clean -n # dry-run: show what would be removed
muse clean -f # delete untracked files
muse clean -f -d # also delete empty directories
muse clean -f -x # also delete .museignore-excluded files
muse clean -f -d -x # everything untracked + ignored + empty dirs
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--dry-run` | `-n` | Preview without deleting |
| `--force` | `-f` | Required to actually delete |
| `--directories` | `-d` | Remove empty untracked directories |
| `--include-ignored` | `-x` | Also remove .museignore-excluded files |
**Exit codes:** `0` clean or cleaned · `1` untracked exist but `--force` not given
---
### `describe` — label by nearest tag
Walks backward from a commit and finds the nearest tag. Returns `~N`
where N is the hop count. N=0 gives the bare tag name.
```
muse describe # → v1.0.0~3
muse describe --ref feat/audio # describe the tip of a branch
muse describe --long # → v1.0.0-3-gabc123456789
muse describe --require-tag # exit 1 if no tags exist
muse describe --format json # machine-readable
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--ref ][` | `-r` | Branch or commit to describe |
| `--long` | `-l` | Always show `--g` |
| `--require-tag` | `-t` | Fail if no tag found |
| `--format ` | `-f` | `text` or `json` |
**JSON output schema:**
```json
{
"commit_id": "string (full SHA-256)",
"tag": "string | null",
"distance": 0,
"short_sha": "string (12 chars)",
"name": "string (e.g. v1.0.0~3)"
}
```
**Exit codes:** `0` description produced · `1` ref not found or `--require-tag` with no tags
---
### `shortlog` — commit summary by author or agent
Groups commits by `author` or `agent_id` and prints a count + message list.
Especially expressive in Muse because both human and agent contributions are
tracked with full metadata.
```
muse shortlog # current branch
muse shortlog --all # all branches
muse shortlog --numbered # sort by commit count (most active first)
muse shortlog --email # include agent_id alongside author name
muse shortlog --limit 100 # cap commit walk at 100
muse shortlog --format json # JSON for agent consumption
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--branch ]
` | `-b` | Branch to summarise |
| `--all` | `-a` | Summarise all branches |
| `--numbered` | `-n` | Sort by commit count |
| `--email` | `-e` | Include agent_id |
| `--limit ` | `-l` | Max commits to walk |
| `--format ` | `-f` | `text` or `json` |
**JSON output schema:**
```json
[
{
"author": "string",
"count": 12,
"commits": [
{"commit_id": "...", "message": "...", "committed_at": "..."}
]
}
]
```
**Exit codes:** `0` always
---
### `verify` — whole-repository integrity check
Walks every reachable commit from every branch ref and performs a three-tier check:
1. Every branch ref points to an existing commit.
2. Every commit's snapshot exists.
3. Every object referenced by every snapshot exists, and (unless `--no-objects`)
its SHA-256 is recomputed to detect silent data corruption.
This is Muse's equivalent of `git fsck`.
```
muse verify # full integrity check (re-hashes all objects)
muse verify --no-objects # existence-only check (faster)
muse verify --quiet # exit code only — no output
muse verify -q && echo "healthy"
muse verify --format json | jq '.failures'
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--quiet` | `-q` | No output; exit 0 = clean, 1 = failure |
| `--no-objects` | `-O` | Skip SHA-256 re-hashing |
| `--format ` | `-f` | `text` or `json` |
**JSON output schema:**
```json
{
"refs_checked": 3,
"commits_checked": 42,
"snapshots_checked": 42,
"objects_checked": 210,
"all_ok": true,
"failures": [
{
"kind": "object",
"id": "abc123...",
"error": "hash mismatch — data corruption detected"
}
]
}
```
**Failure kinds:** `ref` · `commit` · `snapshot` · `object`
**Exit codes:** `0` all checks passed · `1` one or more failures
---
### `snapshot` — explicit snapshot management
A snapshot is Muse's fundamental unit of state: an immutable, content-addressed
record mapping workspace paths to their SHA-256 object IDs.
`muse snapshot` exposes snapshots as a first-class operation — capture, list,
show, and export them independently of commits. Useful for mid-work checkpoints
in agent pipelines.
#### `snapshot create`
```
muse snapshot create
muse snapshot create -m "WIP: before refactor"
muse snapshot create --format json # prints snapshot_id
```
**JSON output:**
```json
{
"snapshot_id": "string",
"file_count": 42,
"note": "string",
"created_at": "ISO8601"
}
```
#### `snapshot list`
```
muse snapshot list
muse snapshot list --limit 5
muse snapshot list --format json
```
#### `snapshot show`
```
muse snapshot show
muse snapshot show abc123 # prefix lookup
muse snapshot show abc123 --format text
```
#### `snapshot export`
```
muse snapshot export
muse snapshot export abc123 --format zip --output release.zip
muse snapshot export abc123 --prefix project/
```
**Archive formats:** `tar.gz` (default) · `zip`
**Flags (export):**
| Flag | Short | Description |
|------|-------|-------------|
| `--format ` | `-f` | `tar.gz` or `zip` |
| `--output ` | `-o` | Output file path |
| `--prefix ` | | Directory prefix inside archive |
**Exit codes:** `0` success · `1` snapshot not found
---
### `bundle` — offline commit transfer
A bundle is a self-contained JSON file carrying commits, snapshots, and objects.
Copy it over SSH, USB, or email — no network connection required.
The bundle format is identical to the plumbing `PackBundle` JSON and is
human-inspectable.
#### `bundle create`
```
muse bundle create out.bundle # bundle from HEAD
muse bundle create out.bundle feat/audio # bundle a specific branch
muse bundle create out.bundle HEAD --have old-sha # delta bundle
```
#### `bundle unbundle`
```
muse bundle unbundle repo.bundle # apply and update branch refs
muse bundle unbundle repo.bundle --no-update-refs # objects only
```
#### `bundle verify`
```
muse bundle verify repo.bundle
muse bundle verify repo.bundle --quiet
muse bundle verify repo.bundle --format json
```
#### `bundle list-heads`
```
muse bundle list-heads repo.bundle
muse bundle list-heads repo.bundle --format json
```
**Bundle value-add over plumbing:** `unbundle` updates local branch refs from
the bundle's `branch_heads` map, so the receiver's repo reflects the sender's
branch state automatically.
**Exit codes:** `0` success · `1` file not found, corrupt, or bad args
---
### `content-grep` — full-text search across tracked files
Searches every file in the HEAD snapshot for a pattern. Files are read from
the content-addressed object store. Binary files and non-UTF-8 files are
silently skipped.
Muse-specific: the search target is the **immutable object store** — you're
searching a specific point in history, not the working tree.
```
muse content-grep --pattern "Cm7"
muse content-grep --pattern "TODO|FIXME" --files-only
muse content-grep --pattern "verse" --ignore-case
muse content-grep --pattern "tempo" --format json
muse content-grep --pattern "chord" --ref feat/harmony
muse content-grep --pattern "hit" --count
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--pattern ` | `-p` | Python regex to search for |
| `--ref [` | `-r` | Branch or commit to search |
| `--ignore-case` | `-i` | Case-insensitive matching |
| `--files-only` | `-l` | Print only matching file paths |
| `--count` | `-c` | Print match count per file |
| `--format ` | `-f` | `text` or `json` |
**JSON output schema:**
```json
[
{
"path": "song.txt",
"object_id": "abc123...",
"match_count": 3,
"matches": [
{"line_number": 4, "text": "chord: Cm7"}
]
}
]
```
**Exit codes:** `0` at least one match · `1` no matches
---
### `whoami` — show the current identity
A shortcut for `muse auth whoami`.
```
muse whoami
muse whoami --json # JSON output for agent consumers
muse whoami --all # show identities for all configured hubs
```
**Flags**
| Flag | Short | Description |
|------|-------|-------------|
| `--json` | `-j` | JSON output |
| `--all` | `-a` | Show all hub identities |
**Text output:**
```
hub: app.musehub.ai
type: agent
name: mozart-agent-v2
id: usr_abc123
token: set
```
**JSON output:**
```json
{
"hub": "app.musehub.ai",
"type": "agent",
"name": "mozart-agent-v2",
"id": "usr_abc123",
"token_set": true,
"capabilities": ["push", "pull", "share"]
}
```
`"token_set"` is a boolean. `"capabilities"` lists the operations the current
token is authorised for on the configured hub.
**Exit codes:** `0` identity found · `1` no identity stored (not authenticated)
---
## Domain Auth & Config
### `auth` — identity management
```
muse auth login --hub app.musehub.ai
muse auth login --agent --agent-id mozart-v2 --model gpt-4.5
muse auth whoami
muse auth logout
```
---
### `config` — repository configuration
Read and write repository configuration stored in `.muse/config.toml`.
```
muse config show # full config as text
muse config show --format json # machine-readable
muse config get core.author # single value
muse config set core.author "Gabriel" # write a value
```
**Subcommands**
| Subcommand | Description |
|------------|-------------|
| `show` | Print the entire config |
| `get ` | Print a single key's value |
| `set ` | Write a key/value pair |
**Flags — `show`**
| Flag | Short | Description |
|------|-------|-------------|
| `--format` | `-f` | `text` or `json` |
**JSON output — `show`**
```json
{
"user": {
"author": "gabriel",
"email": ""
},
"hub": {
"url": "https://app.musehub.ai"
},
"remotes": {
"origin": "https://app.musehub.ai/repos/my-repo"
},
"domain": {
"my.key": "value"
}
}
```
Top-level sections:
- `user` — local author identity
- `hub` — MuseHub connection (HTTPS URLs only)
- `remotes` — named remote URLs
- `domain` — domain-specific key/value pairs (any `domain.*` key is permitted)
**Blocked namespaces:** `auth.*` and `remotes.*` keys cannot be written via
`config set` — use dedicated commands (`muse auth login`, `muse remote add`).
**Exit codes:** `0` success · `1` key not found, blocked namespace, or bad format
---
### `hub` — MuseHub connection
```
muse hub connect https://app.musehub.ai
muse hub status
muse hub disconnect
```
---
## Composability Patterns
Muse porcelain commands are designed to compose cleanly in pipelines.
**Check integrity before every push:**
```bash
muse verify --quiet || { echo "repo corrupt!"; exit 1; }
muse push
```
**Offline collaboration via bundle:**
```bash
# Sender:
muse bundle create session.bundle
scp session.bundle colleague:/tmp/
# Receiver:
muse bundle verify /tmp/session.bundle --quiet
muse bundle unbundle /tmp/session.bundle
```
**Generate a release label in CI:**
```bash
VERSION=$(muse describe --format json | jq -r .name)
echo "Building $VERSION..."
muse snapshot export HEAD --output "${VERSION}.tar.gz"
```
**Find which commits touched a melody line:**
```bash
muse content-grep --pattern "tempo: 120" --format json | jq '.[].path'
```
**Agent activity summary:**
```bash
muse shortlog --all --numbered --email --format json \
| jq '.[] | select(.author | test("agent")) | {agent: .author, count: .count}'
```
**Checkpoint before a risky refactor:**
```bash
SNAP=$(muse snapshot create -m "pre-refactor" --format json | jq -r .snapshot_id)
# ... do the work ...
muse snapshot show "$SNAP" --format json | jq '.manifest | keys'
```
**Binary-search for the broken commit (automated):**
```bash
muse bisect start
muse bisect bad HEAD
muse bisect good v1.0.0
muse bisect run pytest tests/regression.py
# Muse prints the first bad commit and resets automatically.
```
**Audit all recent changes by agents:**
```bash
muse log --format json | jq '[.[] | select(.author | startswith("agent-"))]'
```
]