🔧 Where my dotfiles lives in harmony and peace, most of the time

✨ Add pi branch terminal extension

+86 -2
+83
agents/pi/extensions/branch-term.ts
··· 1 + import { spawn } from "node:child_process" 2 + import type { ExtensionAPI } from "@mariozechner/pi-coding-agent" 3 + import { SessionManager } from "@mariozechner/pi-coding-agent" 4 + 5 + const TERMINAL_FLAG = "branch-terminal" 6 + 7 + function normalizeTerminalFlag(value: unknown): string | undefined { 8 + if (typeof value !== "string") return undefined 9 + const trimmed = value.trim() 10 + return trimmed.length > 0 ? trimmed : undefined 11 + } 12 + 13 + function renderTerminalCommand(template: string, sessionFile: string): string { 14 + if (template.includes("{session}")) { 15 + return template.split("{session}").join(sessionFile) 16 + } 17 + return `${template} ${sessionFile}` 18 + } 19 + 20 + function spawnDetached(command: string, args: string[], onError?: (error: Error) => void): void { 21 + const child = spawn(command, args, { detached: true, stdio: "ignore" }) 22 + child.unref() 23 + if (onError) child.on("error", onError) 24 + } 25 + 26 + export default function (pi: ExtensionAPI) { 27 + pi.registerFlag(TERMINAL_FLAG, { 28 + description: "Command to open a new terminal. Use {session} placeholder for the session file path.", 29 + type: "string", 30 + }) 31 + 32 + pi.registerCommand("branch", { 33 + description: "Fork current session into a new terminal", 34 + handler: async (_args, ctx) => { 35 + await ctx.waitForIdle() 36 + 37 + const sessionFile = ctx.sessionManager.getSessionFile() 38 + if (!sessionFile) { 39 + if (ctx.hasUI) ctx.ui.notify("Session is not persisted. Restart without --no-session.", "error") 40 + return 41 + } 42 + 43 + const leafId = ctx.sessionManager.getLeafId() 44 + if (!leafId) { 45 + if (ctx.hasUI) ctx.ui.notify("No messages yet. Nothing to branch.", "error") 46 + return 47 + } 48 + 49 + const forkManager = SessionManager.open(sessionFile) 50 + const forkFile = forkManager.createBranchedSession(leafId) 51 + if (!forkFile) { 52 + throw new Error("Failed to create branched session") 53 + } 54 + 55 + const terminalFlag = normalizeTerminalFlag(pi.getFlag(`--${TERMINAL_FLAG}`)) 56 + if (terminalFlag) { 57 + const command = renderTerminalCommand(terminalFlag, forkFile) 58 + spawnDetached("bash", ["-lc", command], (error) => { 59 + if (ctx.hasUI) ctx.ui.notify(`Terminal command failed: ${error.message}`, "error") 60 + }) 61 + if (ctx.hasUI) ctx.ui.notify("Opened fork in new terminal", "info") 62 + return 63 + } 64 + 65 + if (process.env.TMUX) { 66 + const result = await pi.exec("tmux", ["new-window", "-n", "branch", "pi", "--session", forkFile]) 67 + if (result.code !== 0) { 68 + throw new Error(result.stderr || result.stdout || "tmux new-window failed") 69 + } 70 + if (ctx.hasUI) ctx.ui.notify("Opened fork in new tmux window", "info") 71 + return 72 + } 73 + 74 + spawnDetached("alacritty", ["-e", "pi", "--session", forkFile], (error) => { 75 + if (ctx.hasUI) { 76 + ctx.ui.notify(`Alacritty failed to open: ${error.message}`, "warning") 77 + ctx.ui.notify(`Run: pi --session ${forkFile}`, "info") 78 + } 79 + }) 80 + if (ctx.hasUI) ctx.ui.notify("Opened fork in new Alacritty window", "info") 81 + }, 82 + }) 83 + }
+2 -2
agents/pi/settings.json
··· 10 10 "github-copilot/claude-opus-4.5", 11 11 "github-copilot/gemini-3-pro-preview" 12 12 ], 13 - "lastChangelogVersion": "0.50.1", 14 - "quietStartup": true 13 + "quietStartup": true, 14 + "lastChangelogVersion": "0.50.3" 15 15 }
+1
agents/setup.sh
··· 22 22 mkdir -p "${HOME}/.pi/agent" 23 23 ln -sf "${AGENTS_DIR}/AGENTS.md" "${HOME}/.pi/agent/AGENTS.md" 24 24 ln -sf "${AGENTS_DIR}/pi/settings.json" "${HOME}/.pi/agent/settings.json" 25 + ln -sfT "${AGENTS_DIR}/pi/extensions" "${HOME}/.pi/agent/extensions" 25 26 } 26 27 27 28 setup_amp() {