diff --git a/CLAUDE.md b/CLAUDE.md index 2bca100..eb91c26 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -229,9 +229,9 @@ Implemented in `apps/cli` over `@mx/core`. Each command resolves the runtime via The version gate: every runtime-touching command first checks the runtime `mx.json` against the version the CLI supports (CLI major ⇄ runtime version); a mismatch refuses with `RUNTIME_VERSION_MISMATCH`, allowing only `mx migrate`, `mx update`, `mx help`, `mx version`. -**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` (shows container path; 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. +**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 ] [--open|-o]` (creates folder, empty `work.json`, the per-work dirs `wt/`/`scripts/`/`files/`/`tmp/`/`sessions/`, the work `CLAUDE.md`; prints path; **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]` (default: active only; shows folder path / porcelain `path`) · `-n info` · `describe ` · `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; `--porcelain` ensures without attaching) · `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 ] [--open|-o]` (creates folder, empty `work.json`, the per-work dirs `wt/`/`scripts/`/`files/`/`tmp/`/`sessions/`, the work `CLAUDE.md`; prints path; **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) · `-n info` · `describe ` · `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; `--porcelain` ensures without attaching) · `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) 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/args.ts b/apps/cli/src/args.ts index 0924bf9..9359582 100644 --- a/apps/cli/src/args.ts +++ b/apps/cli/src/args.ts @@ -27,6 +27,8 @@ export interface Flags { quick: boolean; /** Run the install step rather than just reporting (currently: `mx doctor --install`). */ install: boolean; + /** Minimal listing — just name + path per entry (currently: `mx work ls --lite`, `mx repo ls --lite`). */ + lite: boolean; /** Explicit runtime path from `--runtime`. */ runtime?: string; /** Target name from `-n`/`--name`. */ @@ -90,6 +92,7 @@ export function parseArgs(argv: string[]): ParsedArgs { dryRun: false, quick: false, install: false, + lite: false, }; for (let i = 0; i < argv.length; i++) { const a = argv[i]; @@ -115,6 +118,8 @@ export function parseArgs(argv: string[]): ParsedArgs { flags.quick = true; } else if (a === '--install') { flags.install = true; + } else if (a === '--lite') { + flags.lite = true; } else if (a === '--port' || a.startsWith('--port=')) { const v = a.startsWith('--port=') ? a.slice('--port='.length) : argv[++i]; const n = Number(v); diff --git a/apps/cli/src/commands/global.ts b/apps/cli/src/commands/global.ts index 46c4b9d..c649d16 100644 --- a/apps/cli/src/commands/global.ts +++ b/apps/cli/src/commands/global.ts @@ -143,10 +143,16 @@ export function runGlobal(positionals: string[], flags: Flags): void { function renderSelfUpdate(info: SelfUpdateInfo): void { const curMajor = Number.parseInt(info.current.split('.')[0], 10) || 0; const manual = `npm i -g ${info.package}@^${curMajor}`; + // When we know the exact target, a failure is most often a lagging mirror that + // can't serve that tarball — so suggest installing it straight from npmjs. + const direct = info.latestInMajor + ? `npm i -g ${info.package}@${info.latestInMajor} --registry https://registry.npmjs.org` + : manual; if (!info.npmAvailable) { console.log(`${warn()} npm not found — update manually: ${bold(manual)}`); } else if (info.installFailed) { - console.log(`${warn()} self-update failed — try: ${bold(manual)}`); + console.log(`${warn()} self-update failed — your npm registry may not have this version yet.`); + console.log(` ${dim('Install it directly:')} ${bold(direct)}`); } else if (info.updated) { // Report the version that actually landed on disk, not the requested target. const to = info.installedVersion ?? info.latestInMajor; diff --git a/apps/cli/src/commands/repo.ts b/apps/cli/src/commands/repo.ts index 8809738..e2a22c0 100644 --- a/apps/cli/src/commands/repo.ts +++ b/apps/cli/src/commands/repo.ts @@ -147,6 +147,15 @@ export function dispatchRepo(positionals: string[], flags: Flags): void { console.log(dim('no repos yet — `mx repo add `')); return; } + // --lite: a compact two-column table — name on the left, path on the + // right, one row per repo. No branch/remote detail. + if (flags.lite) { + const nameW = Math.max(...repos.map((r) => r.name.length)); + for (const r of repos) { + console.log(`${bold(r.name.padEnd(nameW))} ${dim(tildify(r.path))}`); + } + return; + } // Same clean shape as `mx work ls`: bold name, dim path, dim detail, // a blank line between entries. for (let i = 0; i < repos.length; i++) { diff --git a/apps/cli/src/commands/work.ts b/apps/cli/src/commands/work.ts index 535dd2c..4ab3c48 100644 --- a/apps/cli/src/commands/work.ts +++ b/apps/cli/src/commands/work.ts @@ -410,10 +410,7 @@ export function dispatchWork(positionals: string[], flags: Flags): void { onlyArchived: flags.archived, }); emit(() => { - // Human mode: detailed per-work view — header line with name + chip + - // counts; then optional description; then indented worktree rows with - // branches and ports. Active works first, archived after; alphabetical - // within each group. Porcelain consumers see the raw order above. + // Active works first, archived after — the order both views use. const ordered = [ ...works.filter((w) => w.isArchived !== true), ...works.filter((w) => w.isArchived === true), @@ -422,6 +419,22 @@ export function dispatchWork(positionals: string[], flags: Flags): void { console.log(dim('no works yet — `mx work new `')); return; } + // --lite: a compact two-column table — name on the left, path on the + // right, one row per work. No worktrees, descriptions, chips, or ports. + if (flags.lite) { + const nameW = Math.max(...ordered.map((w) => w.name.length)); + for (const w of ordered) { + // Pad the plain name to the column width, then style — active bold, + // archived dim — so the eye still lands on active works first. + const padded = w.name.padEnd(nameW); + const styledName = w.isArchived === true ? dim(padded) : bold(padded); + console.log(`${styledName} ${dim(tildify(w.path))}`); + } + return; + } + // Human mode: detailed per-work view — header line with name + chip + + // counts; then optional description; then indented worktree rows with + // branches and ports. for (let i = 0; i < ordered.length; i++) { if (i > 0) console.log(); const w = ordered[i]; diff --git a/apps/cli/src/help.ts b/apps/cli/src/help.ts index b43edbc..57f00f6 100644 --- a/apps/cli/src/help.ts +++ b/apps/cli/src/help.ts @@ -15,7 +15,7 @@ Global: Repos (pristine clones): mx repo add [--name ] clone a repo into the runtime mx repo new [--quick] [-o] create a fresh local repo (no remote); --quick also makes a dev- work + develop worktree - mx repo ls [--porcelain] + mx repo ls [--lite] [--porcelain] --lite: a compact name + path table, one row per repo mx repo -n path print the repo container path (cd "$(mx repo -n path)") mx repo -n fetch git fetch (+ ff the checked-out and base branches) mx repo fetch --all fetch every repo, one by one @@ -27,7 +27,7 @@ Repos (pristine clones): Works (features): mx work new [[:[:]]]... [--description ] [--branch ] [--base ] [-o|--open] creates the work; extra args are repos to make initial worktrees for (per repo: branch = : else --branch else work name; base = : else --base else pristine HEAD); -o builds the work's tmux session and opens it in a new terminal - mx work ls [--all|--archived] [--porcelain] default: active only; --all includes archived; --archived shows archived only + mx work ls [--all|--archived] [--lite] [--porcelain] default: active only; --all includes archived; --archived shows archived only; --lite: a compact name + path table, one row per work mx work -n info [--porcelain] mx work -n path print the work folder path (cd "$(mx work -n path)") mx work -n attach [--prompt ] build the work's tmux session (mx/) if needed, then attach THIS terminal to it (switch-client when already inside tmux); the primary way to enter a work diff --git a/apps/cli/src/selfupdate.ts b/apps/cli/src/selfupdate.ts index 6fb2cef..8c56c51 100644 --- a/apps/cli/src/selfupdate.ts +++ b/apps/cli/src/selfupdate.ts @@ -170,15 +170,20 @@ export function selfUpdate(porcelain: boolean): SelfUpdateInfo { // stale registry view — as an update and trigger a pointless reinstall that // reports a confusing downgrade ("Updated to v2.3.0 (was v2.5.0)"). if (info.latestInMajor && compareVersions(info.latestInMajor, current) > 0) { - const r = spawnSync('npm', ['i', '-g', `${PKG}@^${curMajor}`], { + // Install the EXACT advertised version, not a `@^` range. With a + // range, a lagging mirror that advertises a version it can't serve resolves + // the range down to the newest tarball it *can* serve — silently DOWNGRADING + // the user (and, with `status 0`, looking like success). Pinning the exact + // version makes npm error (E404 / notarget) when the tarball isn't available, + // which we surface as a failure with a direct-registry hint instead. + const r = spawnSync('npm', ['i', '-g', `${PKG}@${info.latestInMajor}`], { stdio: porcelain ? ['ignore', 'pipe', 'pipe'] : 'inherit', }); if (r.status === 0) { - // npm exited 0, but that alone doesn't prove the target landed — re-read - // the version actually installed. Only a genuinely newer version counts as - // an update; if the same (or older) version came back despite a newer one - // being advertised, the registry served something it couldn't fully deliver - // (a lagging mirror), which we flag separately instead of claiming success. + // Even on exit 0, re-read the version actually on disk rather than assume + // the pin landed. Only a genuinely newer version counts as an update; a + // same/older result despite a newer advertised version means the registry + // couldn't fully deliver (a lagging mirror), flagged separately. info.installedVersion = installedGlobalVersion(); if (info.installedVersion && compareVersions(info.installedVersion, current) > 0) { info.updated = true; diff --git a/docs/commands.md b/docs/commands.md index dfe6b3e..b1a7602 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -90,7 +90,10 @@ A newer major is available: @rousan/mx@3. Crossing a major is always a deliberate user action — `mx update` never does it automatically (a new major implies a runtime migration). `mx update` is **not** subject to the version gate: you may need it precisely because your runtime is a newer major than the CLI on `$PATH`. If `npm` is missing or the install fails, it prints the manual command for you to run instead. -**It reports the version that actually installed** — read back from `npm ls -g`, not the target it requested. This matters behind a lagging registry mirror or virtual-repo proxy, which can *advertise* a version (via `npm view`) whose tarball it can't yet serve: `npm i` then reinstalls the same version while still exiting 0. In that case `mx update` says the registry advertises a newer version but a newer one didn't install, and points you at a direct install (`npm i -g @rousan/mx@ --registry https://registry.npmjs.org`) — it does **not** falsely claim the upgrade, and the auto-sync only runs when a genuinely newer version landed. +**It installs the exact advertised version and reports what actually landed.** Two safeguards, both learned from a lagging registry mirror / virtual-repo proxy that *advertises* a version (via `npm view`) whose tarball it can't yet serve: + +- **Exact pin, not a range.** `mx update` runs `npm i -g @rousan/mx@`, not `@^`. With a range, a mirror that can't serve the advertised version resolves the range *down* to the newest tarball it can serve — silently **downgrading** you while `npm` still exits 0. Pinning the exact version makes `npm` error instead; `mx update` then reports the failure and points you at a direct install: `npm i -g @rousan/mx@ --registry https://registry.npmjs.org`. +- **Report what installed, not what was requested.** The reported version is read back from `npm ls -g` after the install, never assumed from the target. If `npm` somehow exits 0 without a newer version landing, `mx update` says the registry advertises a newer version but a newer one didn't install (and again points at the direct install) rather than falsely claiming the upgrade. The auto-sync only runs when a genuinely newer version actually landed. **Auto-sync after update.** When an in-major update actually installs a newer version, `mx update` then runs **`mx sync`** automatically so the runtime picks up the new version's templates and scaffolding (the runtime `CLAUDE.md`, shipped `bin/` utilities, per-work/per-repo files). It does this by shelling out to the freshly-installed global `mx` — the running process is still the pre-update code, so an in-process sync would stamp the *old* templates. Since the update stays in-major, the runtime version still matches and sync isn't gated. It's best-effort: if the sync can't run (e.g. no runtime at the resolved path) `mx update` prints a hint rather than failing. In `--porcelain` mode the sync runs silently so the update's JSON stays the only object on stdout. (Nothing happens when you're already on the latest in-major version.) @@ -167,10 +170,12 @@ mx repo new exp --quick -o # repo "exp", work "dev-exp", worktree on "develo Note on the branch: the pristine clone holds `main`, and git won't check the same branch out twice, so the worktree forks `main` onto `develop` rather than `main` itself. The pristine stays on `main`, so `mx repo health` stays clean. -### `mx repo ls [--porcelain]` +### `mx repo ls [--lite] [--porcelain]` List all pristine clones in the same clean shape as `mx work ls`: bold name, dim container path, dim `branch remote`, a blank line between entries. Human output collapses `$HOME` to `~`; porcelain adds an absolute `path` field per repo. +`--lite` prints a compact **two-column table** instead — the repo name on the left, its path on the right, one row per repo, nothing else. Handy for a quick name↔path glance or feeding into a script. (`--porcelain` is unaffected by `--lite`; it always returns the full objects.) + ``` • analytics ~/mx/repos/analytics @@ -253,7 +258,7 @@ An empty branch segment (`app::develop`) means "use the default branch". Repos a **`--open` / `-o`**: after creating the work, builds its tmux session and opens it in a **new terminal window** — see [`mx work open`](#mx-work--n-name-open-or-mx-work--n-name--o). Best-effort: if a terminal can't be launched (or the platform lacks one), it warns and points at `mx work -n attach`, which always works in-place — the work is still created either way. -### `mx work ls [--all|--archived] [--porcelain]` +### `mx work ls [--all|--archived] [--lite] [--porcelain]` Detailed listing of works. Default: **active only**. `--all` includes archived. `--archived` filters to archived only. @@ -272,6 +277,16 @@ Per-work block: bullet + work name (dim when archived), optional `[archived YYYY Active works first, archived after. Blank line between works. +`--lite` collapses each work to a single row — a compact **two-column table** of name (left) and path (right), nothing else — combinable with `--all` / `--archived`: + +``` +a-longer-feature-name ~/mx/works/a-longer-feature-name +checkout-revamp ~/mx/works/checkout-revamp +feature-one ~/mx/works/feature-one +``` + +Active works still sort first (and archived names stay dimmed). `--porcelain` is unaffected by `--lite` — it always returns the full work objects. + ### `mx work -n info [--porcelain]` Detail block for one work: name, description, worktree count, and indented worktree rows. diff --git a/npm/package.json b/npm/package.json index 54fcb71..23b09f7 100644 --- a/npm/package.json +++ b/npm/package.json @@ -1,6 +1,6 @@ { "name": "@rousan/mx", - "version": "4.7.1", + "version": "4.8.0", "description": "mx — run several features in parallel across shared repos using git worktrees", "type": "module", "bin": {