@diversio/pi-cmux
v0.1.0
Published
Shared cmux primitives for Pi extensions — split panes, workspace tabs, notifications, and shell hardening
Maintainers
Readme
@diversio/pi-cmux
Reusable cmux helpers for Pi-aware tooling.
This is a plain TypeScript library, not a Pi package. Install it as a normal npm dependency and import it from TypeScript or JavaScript code that uses Pi's extension APIs.
Why this package exists
This package provides a small, reusable set of cmux helpers for code that needs to:
- detect whether it is running inside cmux
- identify the active workspace and surface
- open splits and workspaces safely
- build hardened Pi and shell launch commands
- send native cmux notifications
The point of the package is simple:
make common cmux automation reliable and easy to reuse
The problem it is solving
One common failure this package helps prevent is:
open split
-> spawn command in fresh pane
-> pane immediately closes
-> no clue what failedThat usually happens because a fresh pane is a weaker environment than the Pi session that launched it:
- PATH may be different
- the
pibinary may not be discoverable by name - a short-lived failure may exit before the human can read it
@diversio/pi-cmux hardens that path by:
- reusing the current session's PATH
- resolving the safest Pi launcher it can find
- keeping failed panes open with a readable message
- centralizing the split/workspace launch flow in one place
Mental model
your code
├─ decides when a split/workspace/notification is useful
└─ calls @diversio/pi-cmux
├─ cmux.ts -> talk to cmux safely
├─ launch.ts -> build reliable shell / Pi commands
├─ split.ts -> open adjacent panes
├─ workspace.ts -> open new workspace tabs
├─ notify.ts -> send native cmux notifications
└─ escape.ts -> safe shell argument escapingAnother way to think about it:
Your application code decides when cmux behavior is useful.
pi-cmux handles the low-level terminal mechanics.Install
npm install @diversio/pi-cmuxThe package is published to the public npm registry. No .npmrc setup or
auth tokens are needed — it installs the same way as any other public npm
package.
Quick usage examples
1. Open a new Pi split
import { buildPiCommand, isInsideCmux, openSplit } from "@diversio/pi-cmux";
if (isInsideCmux()) {
await openSplit(pi, "right", buildPiCommand(process.cwd(), {
prompt: "Review the current diff",
}));
}Mental model:
current Pi session
-> ask cmux for the caller surface
-> create a right-side split
-> respawn that pane with a hardened Pi command2. Open a shell-command split
import { buildShellCommand, openSplit } from "@diversio/pi-cmux";
await openSplit(pi, "down", buildShellCommand(process.cwd(), "npm test"));Use this when you want a helper terminal lane instead of another Pi lane.
3. Open a workspace tab
import { buildPiCommand, openWorkspace } from "@diversio/pi-cmux";
await openWorkspace(pi, {
cwd: process.cwd(),
name: "Reviewer",
command: buildPiCommand(process.cwd(), {
prompt: "Review the migration for safety",
}),
});4. Send a native notification
import { notify } from "@diversio/pi-cmux";
await notify(pi, "Review finished", "backend", "No correctness issues found.");Why the launcher helpers matter
The most important helpers in this package are the command builders in
launch.ts.
resolvePiLauncher()
This answers:
"If I start Pi inside a brand-new shell right now, what command is least likely to fail?"
Resolution order:
1. PI_CLI_PATH override
2. pi binary next to the current Node executable
3. plain `pi`wrapSpawnedCommand()
This answers:
"How do I run a command in a fresh pane without losing the error instantly if it fails?"
It wraps the real command so the new shell:
- restores PATH
- runs the requested command
- prints a readable failure message if needed
- drops into an interactive shell on failure instead of closing immediately
buildPiCommand()
This is the usual choice for a Pi-powered helper lane.
It composes:
cd <cwd>
-> restore PATH
-> launch Pi
-> optionally reuse a seeded session file
-> optionally pass a prompt after `--`buildShellCommand()
This is the usual choice for commands like:
npm testnpm run devlazygitpython manage.py shell
Public API
Environment and cmux execution
isInsideCmux()identifyCaller(pi)execCmux(pi, args, timeout?)CMUX_TIMEOUT_MS
Launch-command building
resolvePiLauncher()wrapSpawnedCommand(cmd, notice)shellEscape(value)buildPiCommand(cwd, { sessionFile?, prompt? })buildShellCommand(cwd, cmd)
Pane and workspace creation
openSplit(pi, direction, command)openWorkspace(pi, { cwd, name?, command })SPLIT_BOOT_DELAY_MS
Notifications
notify(pi, title, subtitle, body, opts?)
Development
npm install
npm run build
npm pack --dry-runUseful checks while editing:
# verify the compiled package surface
node --input-type=module -e 'import("./dist/index.js").then((m) => console.log(Object.keys(m).sort()))'
# inspect the exact tarball contents that would be published
npm pack --dry-runRelease
Releases are triggered by pushing a version tag. The publish workflow
(defined in .github/workflows/publish.yml) runs automatically and does:
- Verify — the git tag (e.g.
v0.1.0) must match theversionfield inpackage.json. This prevents accidentally publishing a mismatched version. - Build — TypeScript is compiled into
dist/. - Idempotency check — if this version already exists on npm, the publish step is skipped. This makes the workflow safe to re-run after transient failures.
- Publish — the package is pushed to the public npm registry
(
registry.npmjs.org) under the@diversioorg. Auth is handled by an npm Automation token stored as theNPM_TOKENGitHub secret. - GitHub Release — a release is created using auto-generated notes (merged PRs since the last tag).
To cut a release:
git tag v0.1.0
git push origin v0.1.0Notes
- The package is published to the public npm registry — no auth required.
- The published tarball intentionally contains only
dist/plus standard package metadata files. - This package is intentionally small. Calling code should own UX policy; this package should own the reusable cmux mechanics.
