joakimp c624eafe64 ext-toggle: refuse to disable ssh-controlmaster during --ssh session
Disabling ssh-controlmaster mid --ssh session would tear down the
ControlMaster (if we own it) and silently redirect read/write/edit/bash
back to the local filesystem while the system prompt still claims we're
on the remote. Now blocked with an explanatory dialog.

Implementation: a DISABLE_GUARDS map keyed by bare extension name lets
specific extensions register a refusal predicate. ssh-controlmaster's
guard checks process.argv for --ssh and refuses if present. Easy to
extend with similar foot-guns later.
2026-05-07 20:43:20 +02:00

pi-extensions

Custom and modified extensions for the pi coding-agent.

This repo is the single source of truth for extensions that aren't suitable for general publishing — personal workflow tweaks, modified versions of built-in examples, and extensions written for specific infrastructure. Symlinked into ~/.pi/agent/extensions/ so pi loads them automatically.

Part of the same family as pi-toolkit (bring-up) and skillset (agent skills).


Install

git clone ssh://git@gitea.jordbo.se:2222/joakimp/pi-extensions.git ~/src/src_local/pi-extensions
cd ~/src/src_local/pi-extensions
chmod +x install.sh
./install.sh

Each .ts file in extensions/ is symlinked into ~/.pi/agent/extensions/. Existing real files are backed up with a timestamp. Re-runs are idempotent.

Install a subset:

./install.sh --only ssh-controlmaster          # just this one
./install.sh --only "ssh-controlmaster,other"  # explicit list
./install.sh --skip "git-checkpoint"           # all except these

--only and --skip accept comma-separated names without the .ts suffix. --only takes precedence if both are given.

Alternative: pi install (local path)

Because package.json declares a pi manifest, you can also register this repo as a pi package:

pi install ~/src/src_local/pi-extensions

This makes pi manage the extension loading directly. The install.sh approach (symlinks) and pi install are mutually exclusive for the same extension — pick one per machine.

Uninstall

./install.sh --uninstall

Removes symlinks that point into this repo. Your own files in ~/.pi/agent/extensions/ are never touched.


Extensions

ssh-controlmaster.ts

Transparent SSH remote execution via a persistent ControlMaster socket.

When launched with --ssh user@host, all of pi's native file and shell tools (read, write, edit, bash) are transparently redirected to execute on the remote machine. One SSH connection is established at session start; all subsequent tool calls multiplex over it via a Unix socket. Much faster than the plain ssh.ts example which opens a new connection per tool call.

Use cases:

  • Diagnose and fix issues on a remote server without installing pi there
  • Work on Proxmox hosts, LXC containers, or ephemeral VMs
  • Any machine you have SSH key access to but don't own

Usage:

# Key-based auth (normal)
pi --ssh user@192.168.1.10

# Explicit remote path (skips the initial pwd call)
pi --ssh root@proxmox-node:/etc/pve

# Password auth — prompts before connecting
pi --ssh user@host --ssh-ask-pass

# Try without modifying your global install
pi -e ~/src/src_local/pi-extensions/extensions/ssh-controlmaster.ts --ssh user@host

Requirements:

  • SSH key-based auth (preferred), or password auth via --ssh-ask-pass (see below)
  • bash available on the remote

Note on --ssh-ask-pass: The password is prompted via pi's input dialog before the SSH connection is opened. Input is not masked — the password is visible while typing. It is passed to SSH via a temporary SSH_ASKPASS script (/tmp/pi-askpass-<pid>.sh, chmod 700) which is deleted immediately after the master is established.

How it works:

  1. On session_start, runs ssh -G <host> to read the effective config for that host
  2. If ~/.ssh/config already configures ControlMaster auto or yes for the host, the existing system socket is reused — no second connection is opened and pi does not tear down the master on exit (it was the system's to manage)
  3. Otherwise pi establishes its own master: ssh -fN -o ControlMaster=yes -o ControlPersist=yes -o ControlPath=/tmp/pi-cm-<pid>.sock <remote> and shuts it down cleanly on exit
  4. All tool calls multiplex over the socket with -o ControlMaster=no -o ControlPath=<socket> — near-zero per-call overhead
  5. The system prompt is patched to tell the LLM it's operating on <remoteCwd> (via SSH ControlMaster: <remote>)
  6. User ! shell commands are also routed over SSH

The status bar shows ⚡ own master or ⚡ system master so you can see which path was taken.

Status bar: Shows SSH ⚡ user@host:/path when the master is ready, ⟳ connecting… during setup, and an error state if the master fails to start.

Path mapping: Paths are rewritten by replacing the local cwd with the remote cwd. This means pi should be started from a directory that maps cleanly to a path on the remote. Use the user@host:/explicit/path form when the remote path differs significantly from your local working directory.


confirm-destructive.ts

Confirmation gates for dangerous bash commands and destructive session actions. Always-on — no flag needed.

Bash commands intercepted:

  • Recursive removes (rm -rf, rm -r, etc.)
  • Any sudo command
  • chmod/chown 777
  • dd if= (disk operations)
  • mkfs (format filesystem)
  • git push --force / git push -f
  • Writes to /dev/*
  • truncate --size 0

In non-interactive mode (e.g. pi -p) dangerous commands are blocked outright rather than prompted.

Session actions gated:

  • /new — confirms before clearing the session
  • /resume — confirms before switching away if the current session has messages
  • /fork — always confirms

git-checkpoint.ts

Creates a git stash checkpoint at the start of each turn, keyed to the session entry ID. If you /fork from a past entry, you're offered the option to restore the code to that point.

Silently skips when the working directory isn't inside a git repo, or when there are no changes to stash. Status bar shows ⎇ N checkpoints during active sessions.

Notes:

  • Uses git stash create — non-destructive, doesn't touch your working tree
  • Stash objects persist in the git repo even after pi exits, so you can apply them manually with git stash apply <ref> if needed
  • Checkpoints are in-memory per session — the entry→ref mapping is lost on restart, but the underlying stash objects remain

notify.ts

Sends a native terminal notification when the agent finishes and is waiting for input. Only fires when the agent ran for longer than the threshold (default 8 seconds) — quick responses are silently skipped.

Terminal support:

  • Kitty (KITTY_WINDOW_ID) → OSC 99
  • Windows Terminal / WSL (WT_SESSION) → Windows toast
  • Everything else (iTerm2, WezTerm, Ghostty) → OSC 777

Flag:

pi --notify-min-secs 15   # only notify for tasks over 15 seconds
pi --notify-min-secs 0    # notify on every agent completion

ext-toggle.ts

Registers /ext — a slash command that lists extensions in ~/.pi/agent/extensions/ and toggles individual ones on/off without leaving the TUI.

How it works: pi auto-discovers *.ts only. Toggling renames a file (or symlink) between name.ts and name.ts.off, so a disabled extension is invisible to the loader. After a toggle, the extension calls ctx.reload() so the change takes effect immediately — no restart needed.

Usage:

/ext                # opens a picker; ● = active, ○ = disabled

Notes:

  • Subdirectory-style extensions (name/index.ts) are listed read-only — v1 doesn't toggle them. Move the directory aside manually if needed.
  • install.sh --uninstall cleans up both .ts and .ts.off symlinks pointing into this repo, so a disabled extension won't be left behind.
  • Re-running ./install.sh respects a prior /ext disable: if <name>.ts.off already exists, the installer leaves it alone instead of silently re-enabling.
  • ssh-controlmaster cannot be disabled via /ext while pi was launched with --ssh — disabling mid-session would silently revert tool calls to the local filesystem. Exit pi and relaunch without --ssh instead.

Adding a new extension

  1. Drop a .ts file into extensions/
  2. Re-run ./install.sh — it picks up the new file and symlinks it
  3. In a running pi session, /reload is enough; no restart needed
  4. (or, with ext-toggle installed: /ext to disable noisy ones at runtime)

Each extension is a TypeScript module loaded by jiti — no compilation step. See the pi extensions docs and the built-in examples for the API surface.


Deploying on a new machine

# 1. Prerequisites: pi installed, SSH key auth working
pi --help           # creates ~/.pi/agent/ on first run

# 2. Clone and install
git clone ssh://git@gitea.jordbo.se:2222/joakimp/pi-extensions.git ~/src/src_local/pi-extensions
cd ~/src/src_local/pi-extensions && ./install.sh

# 3. Verify
ls -la ~/.pi/agent/extensions/   # should show symlinks into this repo

  • pi-toolkit — pi bring-up: settings template, keybindings, shell env loader
  • mempalace-toolkit — persistent memory layer for pi via MemPalace MCP
  • skillset — agent skills for pi, opencode, and Claude

License

MIT — see LICENSE.

S
Description
No description provided
Readme MIT 295 KiB
Languages
TypeScript 91.3%
Shell 8.7%