Commit 0080be97fd64

Vincent Demeester <vincent@sbr.pm>
2026-07-02 23:53:05
chore(pi): prune unused agent extensions
Removed extensions that were unused or superseded, after auditing each for actual usage: - usage-bar: never invoked via /usage - interactive-shell, shell-completions: never used pi ! bash mode - subagent-commands: /scout and /implement shortcuts unused - review: superseded by code-review skills Trimmed the git extension README's unbuilt Phase 2-5 roadmap and updated docs referencing the removed extensions. Tracking for the git roadmap and a github extension cross-repo bug moved to org TODOs.
1 parent eeec9b7
dots/pi/agent/extensions/git/README.md
@@ -19,10 +19,10 @@ Manage git worktrees easily without cluttering your repository with multiple che
 
 ### Upcoming Features
 
-- **Phase 2:** Smart rebase/fixup workflows
-- **Phase 3:** Commit helpers and validation
-- **Phase 4:** Branch management
-- **Phase 5:** Integration and polish
+See the org TODO tracking whether Phases 2-5 (rebase/fixup, commit helpers,
+branch management, polish) are still wanted. Plan draft:
+`~/.local/share/ai/plans/pi-git-extension.md`. These may be superseded by the
+`git` skill and AGENTS.md commit conventions.
 
 ## Installation
 
@@ -270,27 +270,6 @@ All operations include:
 - Clear error messages
 - Safe defaults
 
-## Roadmap
-
-### Phase 2: Smart Rebase/Fixup (Next)
-- `/fixup <commit>` - Create fixup commit
-- `/rebase-fixups` - Auto-squash fixups
-- Pre-push fixup detection
-
-### Phase 3: Commit Helpers
-- `/commit` - Interactive conventional commit
-- Commit message validation
-- AI-assisted commit messages
-
-### Phase 4: Branch Management
-- `/branch list` - List branches with info
-- `/branch cleanup` - Remove merged branches
-
-### Phase 5: Polish
-- Comprehensive error handling
-- Configuration system
-- Full documentation
-
 ## Related
 
 - Plan: `~/.local/share/ai/plans/pi-git-extension.md`
dots/pi/agent/extensions/shell-completions/scripts/bash-complete.bash
@@ -1,49 +0,0 @@
-#!/bin/bash
-# Gets completions using bash's native completion system
-# Usage: bash-complete.bash "command line" "/path/to/cwd"
-
-__cmdline="$1"
-__cwd="$2"
-
-cd "$__cwd" 2>/dev/null || exit 1
-
-# Extract command name
-__cmd=${__cmdline%% *}
-
-# Source bash-completion framework if available
-for f in /usr/share/bash-completion/bash_completion /etc/bash_completion /opt/homebrew/etc/bash_completion /opt/homebrew/share/bash-completion/bash_completion; do
-    # shellcheck disable=SC1090
-    [[ -f "$f" ]] && { source "$f" 2>/dev/null; break; }
-done
-
-# Also try to source command-specific completions directly (macOS/Homebrew)
-for dir in /opt/homebrew/etc/bash_completion.d /usr/share/bash-completion/completions /etc/bash_completion.d; do
-    for f in "$dir/$__cmd" "$dir/$__cmd.bash" "$dir/${__cmd}-completion.bash"; do
-        # shellcheck disable=SC1090
-        [[ -f "$f" ]] && source "$f" 2>/dev/null
-    done
-done
-
-# Set up completion environment
-COMP_LINE="$__cmdline"
-COMP_POINT=${#COMP_LINE}
-eval set -- "$COMP_LINE"
-COMP_WORDS=("$@")
-
-# Add empty word if line ends with space (completing new word)
-[[ "${COMP_LINE: -1}" = ' ' ]] && COMP_WORDS+=('')
-
-COMP_CWORD=$(( ${#COMP_WORDS[@]} - 1 ))
-
-# Load completion for the command if available
-declare -F _completion_loader &>/dev/null && _completion_loader "$__cmd" 2>/dev/null
-
-# Get the completion function
-completion=$(complete -p "$__cmd" 2>/dev/null | awk '{print $(NF-1)}')
-
-if [[ -n "$completion" ]] && declare -F "$completion" &>/dev/null; then
-    # Call the completion function
-    "$completion" 2>/dev/null
-    # Output unique results
-    printf '%s\n' "${COMPREPLY[@]}" | sort -u | head -30
-fi
dots/pi/agent/extensions/shell-completions/scripts/zsh-capture.zsh
@@ -1,149 +0,0 @@
-#!/usr/bin/env zsh
-# shellcheck disable=all
-# Simple zsh completion capture using _complete_help
-# Usage: zsh-capture.zsh "command line" "/path/to/cwd"
-
-emulate -L zsh
-setopt no_beep
-
-local cmdline="$1"
-local cwd="$2"
-
-cd "$cwd" 2>/dev/null || exit 1
-
-# Initialize completion system (use user's zcompdump)
-autoload -Uz compinit
-compinit -C 2>/dev/null
-
-# Parse command line
-local -a words
-words=("${(@Q)${(z)cmdline}}")
-
-# If line ends with space, we're completing a new word
-if [[ "$cmdline" == *" " ]]; then
-    words+=("")
-fi
-
-local cmd="${words[1]}"
-local current="${words[-1]}"
-
-# Helper to output completions
-output() {
-    local val="$1" desc="$2"
-    if [[ -n "$desc" ]]; then
-        print -r -- "${val}"$'\t'"${desc}"
-    else
-        print -r -- "${val}"
-    fi
-}
-
-# Git completions
-if [[ "$cmd" == "git" ]]; then
-    if (( ${#words} == 2 )); then
-        # Git subcommands
-        git --list-cmds=main,others 2>/dev/null | while read -r subcmd; do
-            [[ -z "$current" || "$subcmd" == "$current"* ]] && output "$subcmd"
-        done
-    else
-        local subcmd="${words[2]}"
-        case "$subcmd" in
-            checkout|switch|merge|rebase|branch|log)
-                # Branches
-                git for-each-ref --format='%(refname:short)' refs/heads 2>/dev/null | while read -r b; do
-                    [[ -z "$current" || "$b" == "$current"* ]] && output "$b" "branch"
-                done
-                git for-each-ref --format='%(refname:short)' refs/remotes 2>/dev/null | while read -r b; do
-                    [[ "$b" == */HEAD ]] && continue
-                    local short="${b#*/}"
-                    [[ -z "$current" || "$short" == "$current"* ]] && output "$short" "remote"
-                done
-                ;;
-            add|diff|restore|reset)
-                # Modified files
-                git diff --name-only 2>/dev/null | while read -r f; do
-                    [[ -z "$current" || "$f" == "$current"* ]] && output "$f" "modified"
-                done
-                git diff --cached --name-only 2>/dev/null | while read -r f; do
-                    [[ -z "$current" || "$f" == "$current"* ]] && output "$f" "staged"
-                done
-                ;;
-            push|pull|fetch)
-                if (( ${#words} == 3 )); then
-                    git remote 2>/dev/null | while read -r r; do
-                        [[ -z "$current" || "$r" == "$current"* ]] && output "$r" "remote"
-                    done
-                fi
-                ;;
-            stash)
-                for sub in apply drop list pop show push; do
-                    [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
-                done
-                ;;
-        esac
-    fi
-    exit 0
-fi
-
-# SSH/SCP completions - hosts
-if [[ "$cmd" == "ssh" || "$cmd" == "scp" || "$cmd" == "sftp" ]]; then
-    {
-        [[ -f ~/.ssh/config ]] && awk '/^Host / && !/\*/{for(i=2;i<=NF;i++)print $i}' ~/.ssh/config
-        [[ -f ~/.ssh/known_hosts ]] && awk -F'[, ]' '{print $1}' ~/.ssh/known_hosts
-    } 2>/dev/null | sort -u | while read -r h; do
-        [[ -z "$current" || "$h" == "$current"* ]] && output "$h" "host"
-    done
-    exit 0
-fi
-
-# Make completions
-if [[ "$cmd" == "make" ]]; then
-    local mf
-    for f in GNUmakefile Makefile makefile; do
-        [[ -f "$f" ]] && mf="$f" && break
-    done
-    if [[ -n "$mf" ]]; then
-        awk -F: '/^[a-zA-Z_][a-zA-Z0-9_-]*:/ && !/^\./{print $1}' "$mf" 2>/dev/null | while read -r t; do
-            [[ -z "$current" || "$t" == "$current"* ]] && output "$t" "target"
-        done
-    fi
-    exit 0
-fi
-
-# NPM/Yarn/PNPM completions
-if [[ "$cmd" == "npm" || "$cmd" == "yarn" || "$cmd" == "pnpm" ]]; then
-    if (( ${#words} == 2 )); then
-        for sub in install add remove run build test start dev publish; do
-            [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
-        done
-    elif [[ "${words[2]}" == "run" && -f package.json ]]; then
-        jq -r '.scripts // {} | keys[]' package.json 2>/dev/null | while read -r s; do
-            [[ -z "$current" || "$s" == "$current"* ]] && output "$s" "script"
-        done
-    fi
-    exit 0
-fi
-
-# Docker completions
-if [[ "$cmd" == "docker" ]]; then
-    if (( ${#words} == 2 )); then
-        for sub in build compose exec images logs ps pull push rm rmi run start stop; do
-            [[ -z "$current" || "$sub" == "$current"* ]] && output "$sub"
-        done
-    fi
-    exit 0
-fi
-
-# Fallback: file completion
-if [[ -n "$current" ]]; then
-    local -a matches
-    matches=( ${current}*(N) )
-    for f in "${matches[@]:0:20}"; do
-        [[ -d "$f" ]] && output "${f}/" "directory" || output "$f" "file"
-    done
-else
-    local -a matches
-    matches=( *(N) )
-    for f in "${matches[@]:0:20}"; do
-        [[ -d "$f" ]] && output "${f}/" "directory" || output "$f" "file"
-    done
-fi
dots/pi/agent/extensions/shell-completions/bash.ts
@@ -1,125 +0,0 @@
-/**
- * Bash shell completion provider.
- *
- * Uses bash's native completion system by running a script that sets up
- * COMP_* environment variables and calls the registered completion function.
- *
- * Philosophy: Only provide completions if the user has bash-completion available.
- */
-
-import type { AutocompleteItem } from "@earendil-works/pi-tui";
-import { spawnSync } from "node:child_process";
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { fileURLToPath } from "node:url";
-import type { CompletionResult, ShellCompletionProvider } from "./types.js";
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const COMPLETE_SCRIPT = path.join(__dirname, "scripts", "bash-complete.bash");
-
-/**
- * Check if bash-completion is available.
- * We check for the presence of completion scripts in standard locations.
- */
-let completionCheckCache: boolean | null = null;
-
-function userHasBashCompletions(bashPath: string): boolean {
-	if (completionCheckCache !== null) {
-		return completionCheckCache;
-	}
-
-	try {
-		// Check if bash-completion framework or git completion exists
-		const result = spawnSync(
-			bashPath,
-			[
-				"-c",
-				`
-				for f in /usr/share/bash-completion/bash_completion /etc/bash_completion /opt/homebrew/etc/bash_completion /opt/homebrew/share/bash-completion/bash_completion; do
-					[[ -f "$f" ]] && { echo yes; exit 0; }
-				done
-				# Also check for individual completion files
-				for f in /opt/homebrew/etc/bash_completion.d/git* /usr/share/bash-completion/completions/git; do
-					[[ -f "$f" ]] && { echo yes; exit 0; }
-				done
-				echo no
-			`,
-			],
-			{
-				encoding: "utf-8",
-				timeout: 500,
-			}
-		);
-
-		completionCheckCache = result.stdout?.trim() === "yes";
-		return completionCheckCache;
-	} catch {
-		completionCheckCache = false;
-		return false;
-	}
-}
-
-/**
- * Get completions using bash's native completion system.
- */
-export function getBashCompletions(
-	commandLine: string,
-	cwd: string,
-	bashPath: string
-): CompletionResult | null {
-	// Check if bash completions are available
-	if (!userHasBashCompletions(bashPath)) {
-		return null;
-	}
-
-	// Check if completion script exists
-	if (!fs.existsSync(COMPLETE_SCRIPT)) {
-		return null;
-	}
-
-	// Extract prefix
-	const trimmed = commandLine.trimStart();
-	let prefix = "";
-	if (!trimmed.endsWith(" ")) {
-		const words = trimmed.split(/\s+/);
-		prefix = words[words.length - 1] || "";
-	}
-
-	try {
-		const result = spawnSync(bashPath, [COMPLETE_SCRIPT, commandLine, cwd], {
-			encoding: "utf-8",
-			timeout: 500,
-			maxBuffer: 1024 * 100,
-			cwd,
-		});
-
-		if (result.error || !result.stdout) {
-			return null;
-		}
-
-		const items: AutocompleteItem[] = result.stdout
-			.trim()
-			.split("\n")
-			.filter(Boolean)
-			.map((line) => {
-				// Remove trailing space that bash completion adds
-				const value = line.trimEnd();
-				return { value, label: value };
-			});
-
-		if (items.length === 0) {
-			return null;
-		}
-
-		return {
-			items: items.slice(0, 30),
-			prefix,
-		};
-	} catch {
-		return null;
-	}
-}
-
-export const bashCompletionProvider: ShellCompletionProvider = {
-	getCompletions: getBashCompletions,
-};
dots/pi/agent/extensions/shell-completions/fish.ts
@@ -1,84 +0,0 @@
-/**
- * Fish shell completion provider.
- *
- * Uses fish's native `complete -C` command which provides excellent completions
- * for most tools automatically.
- * 
- * Fish always has completions available (it's a core feature), so this never
- * returns null for "user hasn't configured completions".
- */
-
-import type { AutocompleteItem } from "@earendil-works/pi-tui";
-import { spawnSync } from "node:child_process";
-import type { CompletionResult, ShellCompletionProvider } from "./types.js";
-
-/**
- * Get completions using fish's native `complete -C` command.
- * Fish completions are excellent and cover most tools automatically.
- */
-export function getFishCompletions(
-	commandLine: string,
-	cwd: string,
-	fishPath: string
-): CompletionResult | null {
-	// Extract prefix
-	const trimmed = commandLine.trimStart();
-	let prefix = "";
-	if (!trimmed.endsWith(" ")) {
-		const words = trimmed.split(/\s+/);
-		prefix = words[words.length - 1] || "";
-	}
-
-	try {
-		// Fish's complete -C gives us completions directly
-		const result = spawnSync(
-			fishPath,
-			["-c", `complete -C ${JSON.stringify(commandLine)}`],
-			{
-				encoding: "utf-8",
-				timeout: 500,
-				maxBuffer: 1024 * 100,
-				cwd,
-			}
-		);
-
-		if (result.error || !result.stdout) {
-			return null;
-		}
-
-		// Fish output format: "completion\tdescription" (tab-separated)
-		const lines = result.stdout.trim().split("\n").filter(Boolean);
-		const items: AutocompleteItem[] = [];
-
-		for (const line of lines) {
-			const tabIndex = line.indexOf("\t");
-			if (tabIndex >= 0) {
-				const value = line.slice(0, tabIndex).trim();
-				const description = line.slice(tabIndex + 1).trim();
-				if (value) {
-					items.push({ value, label: value, description });
-				}
-			} else {
-				const value = line.trim();
-				if (value) {
-					items.push({ value, label: value });
-				}
-			}
-		}
-
-		if (items.length === 0) {
-			return null;
-		}
-
-		return {
-			items: items.slice(0, 30),
-			prefix,
-		};
-	} catch {
-		return null;
-	}
-}
-
-export const fishCompletionProvider: ShellCompletionProvider = {
-	getCompletions: getFishCompletions,
-};
dots/pi/agent/extensions/shell-completions/index.ts
@@ -1,338 +0,0 @@
-/**
- * Shell Completions Extension for Pi
- *
- * Adds native shell completions (fish/zsh/bash) to pi's `!` and `!!` bash mode.
- * Uses the user's actual shell completion configuration - if they haven't
- * set up completions, we don't provide them (no magic).
- *
- * Usage: Place in ~/.pi/agent/extensions/shell-completions/index.ts
- *
- * Shell priority:
- * 1. User's $SHELL (if fish/zsh/bash) - uses their configured completions
- * 2. Fish (if available) - always has completions (core feature)
- * 3. Zsh (if compinit is configured)
- * 4. Bash (if bash-completion is installed)
- *
- * Philosophy: Don't magically provide completions the user hasn't configured.
- * This means completions respect the user's shell setup, aliases, and customizations.
- */
-
-import { CustomEditor, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
-import type { AutocompleteItem, AutocompleteProvider } from "@earendil-works/pi-tui";
-import * as fs from "node:fs";
-import * as path from "node:path";
-
-import type { ShellInfo, ShellType, CompletionResult } from "./types.js";
-import { getFishCompletions } from "./fish.js";
-import { getBashCompletions } from "./bash.js";
-import { getZshCompletions } from "./zsh.js";
-
-// ============================================================================
-// Shell Detection
-// ============================================================================
-
-/**
- * Detect shell type from path.
- */
-function detectShellType(shellPath: string): ShellType {
-	const name = path.basename(shellPath);
-	if (name === "fish" || name.startsWith("fish")) return "fish";
-	if (name === "zsh" || name.startsWith("zsh")) return "zsh";
-	return "bash";
-}
-
-/**
- * Find a shell suitable for running completion scripts.
- *
- * Priority:
- * 1. User's $SHELL if it's fish/zsh/bash (respects user's configured completions)
- * 2. Fish if available (best completion UX)
- * 3. Zsh if available
- * 4. Bash as fallback
- */
-function findCompletionShell(): ShellInfo {
-	// First, try user's $SHELL - they've configured their completions there
-	const userShell = process.env.SHELL;
-	if (userShell && fs.existsSync(userShell)) {
-		const shellType = detectShellType(userShell);
-		// Only use it if it's a shell we support (fish/zsh/bash)
-		if (shellType === "fish" || shellType === "zsh" || shellType === "bash") {
-			return { path: userShell, type: shellType };
-		}
-	}
-
-	// If user's shell isn't suitable, prefer fish for best completions
-	const fishPaths = [
-		"/opt/homebrew/bin/fish",
-		"/usr/local/bin/fish",
-		"/usr/bin/fish",
-		"/bin/fish",
-	];
-	for (const fishPath of fishPaths) {
-		if (fs.existsSync(fishPath)) {
-			return { path: fishPath, type: "fish" };
-		}
-	}
-
-	// Then zsh
-	const zshPaths = [
-		"/bin/zsh",
-		"/usr/bin/zsh",
-		"/usr/local/bin/zsh",
-		"/opt/homebrew/bin/zsh",
-	];
-	for (const zshPath of zshPaths) {
-		if (fs.existsSync(zshPath)) {
-			return { path: zshPath, type: "zsh" };
-		}
-	}
-
-	// Bash fallback
-	const bashPaths = [
-		"/bin/bash",
-		"/usr/bin/bash",
-		"/usr/local/bin/bash",
-		"/opt/homebrew/bin/bash",
-	];
-	for (const bashPath of bashPaths) {
-		if (fs.existsSync(bashPath)) {
-			return { path: bashPath, type: "bash" };
-		}
-	}
-
-	// Try resolving from PATH (NixOS compat)
-	try {
-		const { execSync } = require("node:child_process");
-		const which = execSync("which bash", { encoding: "utf-8", timeout: 5000 }).trim();
-		if (which) return { path: which, type: "bash" };
-	} catch { /* ignore */ }
-
-	return { path: "/bin/bash", type: "bash" };
-}
-
-// ============================================================================
-// Completion Context Extraction
-// ============================================================================
-
-/**
- * Extract the command line and completion prefix from editor text.
- */
-function extractCompletionContext(text: string): {
-	commandLine: string;
-	prefix: string;
-} {
-	// Remove ! or !! prefix
-	let commandLine = text.trimStart();
-	if (commandLine.startsWith("!!")) {
-		commandLine = commandLine.slice(2);
-	} else if (commandLine.startsWith("!")) {
-		commandLine = commandLine.slice(1);
-	}
-
-	const trimmed = commandLine.trimStart();
-
-	// If ends with space, completing a new word
-	if (trimmed.endsWith(" ")) {
-		return { commandLine: trimmed, prefix: "" };
-	}
-
-	// Last word is the prefix
-	const words = trimmed.split(/\s+/);
-	const prefix = words[words.length - 1] || "";
-
-	return { commandLine: trimmed, prefix };
-}
-
-// ============================================================================
-// Shell Completion Dispatcher
-// ============================================================================
-
-/**
- * Get shell completions for a command line.
- * Returns null if the user hasn't configured completions for their shell.
- */
-function getShellCompletions(
-	text: string,
-	cwd: string,
-	shell: ShellInfo
-): CompletionResult | null {
-	const { commandLine } = extractCompletionContext(text);
-
-	if (!commandLine.trim()) {
-		return null;
-	}
-
-	// Each shell provider checks if user has completions configured
-	// and returns null if not
-	switch (shell.type) {
-		case "fish":
-			// Fish always has completions (it's a core feature)
-			return getFishCompletions(commandLine, cwd, shell.path);
-		case "bash":
-			// Bash: only works if bash-completion is available
-			return getBashCompletions(commandLine, cwd, shell.path);
-		case "zsh":
-			// Zsh: only works if user has compinit in their .zshrc
-			return getZshCompletions(commandLine, cwd, shell.path);
-		default:
-			return null;
-	}
-}
-
-// ============================================================================
-// Shell-Aware Autocomplete Provider Wrapper
-// ============================================================================
-
-/**
- * Wraps an existing autocomplete provider to add shell completion support
- * when in bash mode (text starts with ! or !!).
- */
-function wrapWithShellCompletion(
-	baseProvider: AutocompleteProvider,
-	shell: ShellInfo
-): AutocompleteProvider {
-	const isBashMode = (lines: string[]): boolean => {
-		const text = lines.join("\n").trimStart();
-		return text.startsWith("!") || text.startsWith("!!");
-	};
-
-	const getTextUpToCursor = (
-		lines: string[],
-		cursorLine: number,
-		cursorCol: number
-	): string => {
-		const textLines = lines.slice(0, cursorLine + 1);
-		if (textLines.length > 0) {
-			textLines[textLines.length - 1] = textLines[textLines.length - 1].slice(0, cursorCol);
-		}
-		return textLines.join("\n");
-	};
-
-	return {
-		getSuggestions(
-			lines: string[],
-			cursorLine: number,
-			cursorCol: number,
-			options?: any
-		): { items: AutocompleteItem[]; prefix: string } | null {
-			if (isBashMode(lines)) {
-				const text = getTextUpToCursor(lines, cursorLine, cursorCol);
-				const result = getShellCompletions(text, process.cwd(), shell);
-				if (result && result.items.length > 0) {
-					return result;
-				}
-			}
-			return baseProvider.getSuggestions(lines, cursorLine, cursorCol, options);
-		},
-
-		applyCompletion(
-			lines: string[],
-			cursorLine: number,
-			cursorCol: number,
-			item: AutocompleteItem,
-			prefix: string
-		): { lines: string[]; cursorLine: number; cursorCol: number } {
-			if (isBashMode(lines)) {
-				const currentLine = lines[cursorLine] || "";
-				const prefixStart = cursorCol - prefix.length;
-				const beforePrefix = currentLine.slice(0, prefixStart);
-				const afterCursor = currentLine.slice(cursorCol);
-
-				// Don't add space after directories
-				const isDirectory = item.value.endsWith("/");
-				const suffix = isDirectory ? "" : " ";
-
-				const newLine = beforePrefix + item.value + suffix + afterCursor;
-				const newLines = [...lines];
-				newLines[cursorLine] = newLine;
-
-				return {
-					lines: newLines,
-					cursorLine,
-					cursorCol: prefixStart + item.value.length + suffix.length,
-				};
-			}
-
-			return baseProvider.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
-		},
-
-		// Forward optional methods
-		getForceFileSuggestions(
-			lines: string[],
-			cursorLine: number,
-			cursorCol: number
-		): { items: AutocompleteItem[]; prefix: string } | null {
-			if (isBashMode(lines)) {
-				const text = getTextUpToCursor(lines, cursorLine, cursorCol);
-				return getShellCompletions(text, process.cwd(), shell);
-			}
-			if ("getForceFileSuggestions" in baseProvider) {
-				return (baseProvider as any).getForceFileSuggestions(lines, cursorLine, cursorCol);
-			}
-			return this.getSuggestions(lines, cursorLine, cursorCol);
-		},
-
-		shouldTriggerFileCompletion(
-			lines: string[],
-			cursorLine: number,
-			cursorCol: number
-		): boolean {
-			if (isBashMode(lines)) {
-				return true;
-			}
-			if ("shouldTriggerFileCompletion" in baseProvider) {
-				return (baseProvider as any).shouldTriggerFileCompletion(lines, cursorLine, cursorCol);
-			}
-			return true;
-		},
-	};
-}
-
-// ============================================================================
-// Custom Editor with Shell Completion
-// ============================================================================
-
-/**
- * Custom editor that intercepts setAutocompleteProvider to wrap with shell completion.
- */
-class ShellCompletionEditor extends CustomEditor {
-	private shell: ShellInfo;
-	private wrappedProvider = false;
-
-	constructor(tui: any, theme: any, keybindings: any, shell: ShellInfo) {
-		super(tui, theme, keybindings);
-		this.shell = shell;
-	}
-
-	// Override setAutocompleteProvider to wrap the base provider
-	setAutocompleteProvider(provider: AutocompleteProvider): void {
-		if (!this.wrappedProvider && provider) {
-			// Wrap the provider with shell completion support
-			const wrapped = wrapWithShellCompletion(provider, this.shell);
-			super.setAutocompleteProvider(wrapped);
-			this.wrappedProvider = true;
-		} else {
-			super.setAutocompleteProvider(provider);
-		}
-	}
-}
-
-// ============================================================================
-// Extension Entry Point
-// ============================================================================
-
-export default function (pi: ExtensionAPI) {
-	const shell = findCompletionShell();
-	const shellName = path.basename(shell.path);
-
-	pi.on("session_start", (_event, ctx) => {
-		ctx.ui.setEditorComponent((tui, theme, keybindings) => {
-			return new ShellCompletionEditor(tui, theme, keybindings, shell);
-		});
-
-		ctx.ui.notify(`Shell completions enabled (${shellName})`, "info");
-	});
-}
-
-// Re-export types for potential external use
-export type { ShellInfo, ShellType, CompletionResult } from "./types.js";
dots/pi/agent/extensions/shell-completions/package.json
@@ -1,20 +0,0 @@
-{
-  "name": "pi-shell-completions",
-  "version": "0.2.0",
-  "description": "Pi extension that adds native shell completions (fish/zsh/bash) to ! and !! bash mode commands",
-  "type": "module",
-  "keywords": ["pi-package"],
-  "license": "MIT",
-  "author": "laulauland",
-  "repository": {
-    "type": "git",
-    "url": "https://github.com/laulauland/dotfiles"
-  },
-  "pi": {
-    "extensions": ["./index.ts"]
-  },
-  "peerDependencies": {
-    "@earendil-works/pi-coding-agent": "*",
-    "@earendil-works/pi-tui": "*"
-  }
-}
dots/pi/agent/extensions/shell-completions/README.md
@@ -1,70 +0,0 @@
-# pi-shell-completions
-
-Adds native shell completions to pi's `!` and `!!` bash mode commands.
-
-## Installation
-
-```bash
-pi install npm:pi-shell-completions
-```
-
-Or for local development, place in `~/.pi/agent/extensions/shell-completions/`
-
-## How it works
-
-When you type `!git checkout ` in pi's prompt, this extension queries your shell's completion system and shows suggestions.
-
-### Shell support
-
-| Shell | How it works | Quality |
-|-------|--------------|---------|
-| **Fish** | Native `complete -C` command | ⭐⭐⭐ Excellent - all completions work |
-| **Bash** | Sources bash-completion scripts | ⭐⭐ Good - if bash-completion is installed |
-| **Zsh** | Fallback script for common tools | ⭐ Basic - see limitations |
-
-### Fish (recommended)
-
-Fish's completion system is designed to be queried programmatically via `complete -C "command "`. This means:
-
-- All your fish completions work automatically
-- Git branches, docker containers, ssh hosts, npm scripts — everything
-- Descriptions are included
-- Fast (10-30ms)
-
-Even if fish isn't your primary shell, installing it gives you great completions in pi.
-
-### Bash
-
-Bash-completion can be queried by setting up `COMP_*` environment variables and calling completion functions. This extension:
-
-- Sources completion scripts from standard locations (`/opt/homebrew/etc/bash_completion.d/`, `/usr/share/bash-completion/completions/`, etc.)
-- Calls the registered completion function for each command
-- Works if you have bash-completion installed
-
-### Zsh (limited)
-
-Zsh's completion system is tightly coupled to its line editor (ZLE) and cannot be easily queried programmatically. The `zpty` pseudo-terminal approach is complex and unreliable.
-
-**Current limitations:**
-- Does NOT use your full zsh completion config
-- Only handles common tools: git, ssh, make, npm/yarn/pnpm, docker
-- Falls back to file completion for other commands
-
-**Recommendation:** If you use zsh and want good completions in pi, install fish as a secondary shell. The extension will automatically prefer fish when available.
-
-## Shell priority
-
-1. Your `$SHELL` (if fish/zsh/bash)
-2. Fish (if available) — even if not your primary shell
-3. Zsh
-4. Bash
-
-## Requirements
-
-- One of: fish, zsh, or bash
-- For bash: bash-completion package installed
-- For best experience: fish
-
-## License
-
-MIT
dots/pi/agent/extensions/shell-completions/tsconfig.json
@@ -1,12 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "ES2022",
-    "module": "ES2022",
-    "moduleResolution": "bundler",
-    "types": [],
-    "skipLibCheck": true,
-    "noEmit": true,
-    "allowImportingTsExtensions": true
-  },
-  "include": ["*.ts"]
-}
dots/pi/agent/extensions/shell-completions/types.ts
@@ -1,30 +0,0 @@
-/**
- * Shared types for shell completions extension.
- */
-
-import type { AutocompleteItem } from "@earendil-works/pi-tui";
-
-export type ShellType = "fish" | "zsh" | "bash";
-
-export interface ShellInfo {
-	path: string;
-	type: ShellType;
-}
-
-export interface CompletionContext {
-	commandLine: string;
-	prefix: string;
-}
-
-export interface CompletionResult {
-	items: AutocompleteItem[];
-	prefix: string;
-}
-
-/**
- * Interface for shell-specific completion providers.
- * Returns null if the user hasn't configured completions for this shell.
- */
-export interface ShellCompletionProvider {
-	getCompletions(commandLine: string, cwd: string, shellPath: string): CompletionResult | null;
-}
dots/pi/agent/extensions/shell-completions/zsh.ts
@@ -1,110 +0,0 @@
-/**
- * Zsh completion support
- *
- * Unfortunately, zsh's completion system is tightly coupled to its line editor
- * (ZLE) and cannot be easily queried programmatically without a pseudo-terminal.
- * The zpty approach is complex and fragile.
- *
- * This implementation uses a simple fallback script that handles common cases
- * (git, ssh, make, npm, docker) but does NOT tap into the user's full zsh
- * completion configuration.
- *
- * For the best experience, install fish (even as a secondary shell) - its
- * `complete -C` command provides excellent completions without complexity.
- */
-
-import { spawnSync } from "node:child_process";
-import * as fs from "node:fs";
-import * as path from "node:path";
-import { fileURLToPath } from "node:url";
-import type { CompletionResult } from "./types.js";
-import type { AutocompleteItem } from "@earendil-works/pi-tui";
-
-const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const CAPTURE_SCRIPT = path.join(__dirname, "scripts", "zsh-capture.zsh");
-
-/**
- * Parse completion output (tab-separated: value\tdescription)
- */
-function parseOutput(output: string): AutocompleteItem[] {
-	const lines = output.trim().split("\n").filter(Boolean);
-	const items: AutocompleteItem[] = [];
-	const seen = new Set<string>();
-
-	for (const line of lines) {
-		const tabIndex = line.indexOf("\t");
-		let value: string;
-		let description: string | undefined;
-
-		if (tabIndex >= 0) {
-			value = line.slice(0, tabIndex).trim();
-			description = line.slice(tabIndex + 1).trim() || undefined;
-		} else {
-			value = line.trim();
-		}
-
-		if (!value || seen.has(value)) continue;
-		seen.add(value);
-
-		// Skip internal refs
-		if (value.startsWith("refs/jj/keep/")) continue;
-
-		items.push({ value, label: value, description });
-	}
-
-	return items;
-}
-
-/**
- * Get completions using zsh fallback script.
- * Note: This does NOT use the user's full zsh completion config.
- */
-export function getZshCompletions(
-	commandLine: string,
-	cwd: string,
-	zshPath: string
-): CompletionResult | null {
-	// Check if capture script exists
-	if (!fs.existsSync(CAPTURE_SCRIPT)) {
-		return null;
-	}
-
-	// Extract prefix
-	const trimmed = commandLine.trimStart();
-	let prefix = "";
-	if (!trimmed.endsWith(" ")) {
-		const words = trimmed.split(/\s+/);
-		prefix = words[words.length - 1] || "";
-	}
-
-	try {
-		const result = spawnSync(zshPath, [CAPTURE_SCRIPT, commandLine, cwd], {
-			encoding: "utf-8",
-			timeout: 500,
-			maxBuffer: 1024 * 100,
-			cwd,
-		});
-
-		if (result.error || !result.stdout) {
-			return null;
-		}
-
-		const items = parseOutput(result.stdout);
-
-		if (items.length === 0) {
-			return null;
-		}
-
-		return {
-			items: items.slice(0, 30),
-			prefix,
-		};
-	} catch {
-		return null;
-	}
-}
-
-export const zshCompletionProvider = {
-	name: "zsh" as const,
-	getCompletions: getZshCompletions,
-};
dots/pi/agent/extensions/subagent/README.md
@@ -114,17 +114,6 @@ This might use:
 - planner → `claude-sonnet-4-5@20250929` via `google-vertex-claude`
 - worker → `claude-sonnet-4-5@20250929` via `google-vertex-claude`
 
-### Slash Commands
-
-The `subagent-commands.ts` extension provides shortcuts:
-
-```
-/scout find all authentication code
-/implement add caching to session store
-/scout-and-plan refactor database layer
-/review-code check security in API handlers
-```
-
 ## Example Agents
 
 ### Scout (Fast, Cheap)
dots/pi/agent/extensions/interactive-shell.ts
@@ -1,196 +0,0 @@
-/**
- * Interactive Shell Commands Extension
- *
- * Enables running interactive commands (vim, git rebase -i, htop, etc.)
- * with full terminal access. The TUI suspends while they run.
- *
- * Usage:
- *   pi -e examples/extensions/interactive-shell.ts
- *
- *   !vim file.txt        # Auto-detected as interactive
- *   !i any-command       # Force interactive mode with !i prefix
- *   !git rebase -i HEAD~3
- *   !htop
- *
- * Configuration via environment variables:
- *   INTERACTIVE_COMMANDS - Additional commands (comma-separated)
- *   INTERACTIVE_EXCLUDE  - Commands to exclude (comma-separated)
- *
- * Note: This only intercepts user `!` commands, not agent bash tool calls.
- * If the agent runs an interactive command, it will fail (which is fine).
- */
-
-import { spawnSync } from "node:child_process";
-import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-
-// Default interactive commands - editors, pagers, git ops, TUIs
-const DEFAULT_INTERACTIVE_COMMANDS = [
-	// Editors
-	"vim",
-	"nvim",
-	"vi",
-	"nano",
-	"emacs",
-	"pico",
-	"micro",
-	"helix",
-	"hx",
-	"kak",
-	// Pagers
-	"less",
-	"more",
-	"most",
-	// Git interactive
-	"git commit",
-	"git rebase",
-	"git merge",
-	"git cherry-pick",
-	"git revert",
-	"git add -p",
-	"git add --patch",
-	"git add -i",
-	"git add --interactive",
-	"git stash -p",
-	"git stash --patch",
-	"git reset -p",
-	"git reset --patch",
-	"git checkout -p",
-	"git checkout --patch",
-	"git difftool",
-	"git mergetool",
-	// System monitors
-	"htop",
-	"top",
-	"btop",
-	"glances",
-	// File managers
-	"ranger",
-	"nnn",
-	"lf",
-	"mc",
-	"vifm",
-	// Git TUIs
-	"tig",
-	"lazygit",
-	"gitui",
-	// Fuzzy finders
-	"fzf",
-	"sk",
-	// Remote sessions
-	"ssh",
-	"telnet",
-	"mosh",
-	// Database clients
-	"psql",
-	"mysql",
-	"sqlite3",
-	"mongosh",
-	"redis-cli",
-	// Kubernetes/Docker
-	"kubectl edit",
-	"kubectl exec -it",
-	"docker exec -it",
-	"docker run -it",
-	// Other
-	"tmux",
-	"screen",
-	"ncdu",
-];
-
-function getInteractiveCommands(): string[] {
-	const additional =
-		process.env.INTERACTIVE_COMMANDS?.split(",")
-			.map((s) => s.trim())
-			.filter(Boolean) ?? [];
-	const excluded = new Set(process.env.INTERACTIVE_EXCLUDE?.split(",").map((s) => s.trim().toLowerCase()) ?? []);
-	return [...DEFAULT_INTERACTIVE_COMMANDS, ...additional].filter((cmd) => !excluded.has(cmd.toLowerCase()));
-}
-
-function isInteractiveCommand(command: string): boolean {
-	const trimmed = command.trim().toLowerCase();
-	const commands = getInteractiveCommands();
-
-	for (const cmd of commands) {
-		const cmdLower = cmd.toLowerCase();
-		// Match at start
-		if (trimmed === cmdLower || trimmed.startsWith(`${cmdLower} `) || trimmed.startsWith(`${cmdLower}\t`)) {
-			return true;
-		}
-		// Match after pipe: "cat file | less"
-		const pipeIdx = trimmed.lastIndexOf("|");
-		if (pipeIdx !== -1) {
-			const afterPipe = trimmed.slice(pipeIdx + 1).trim();
-			if (afterPipe === cmdLower || afterPipe.startsWith(`${cmdLower} `)) {
-				return true;
-			}
-		}
-	}
-	return false;
-}
-
-export default function (pi: ExtensionAPI) {
-	pi.on("user_bash", async (event, ctx) => {
-		let command = event.command;
-		let forceInteractive = false;
-
-		// Check for !i prefix (command comes without the leading !)
-		// The prefix parsing happens before this event, so we check if command starts with "i "
-		if (command.startsWith("i ") || command.startsWith("i\t")) {
-			forceInteractive = true;
-			command = command.slice(2).trim();
-		}
-
-		const shouldBeInteractive = forceInteractive || isInteractiveCommand(command);
-		if (!shouldBeInteractive) {
-			return; // Let normal handling proceed
-		}
-
-		// No UI available (print mode, RPC, etc.)
-		if (!ctx.hasUI) {
-			return {
-				result: { output: "(interactive commands require TUI)", exitCode: 1, cancelled: false, truncated: false },
-			};
-		}
-
-		// Use ctx.ui.custom() to get TUI access, then run the command
-		const exitCode = await ctx.ui.custom<number | null>((tui, _theme, _kb, done) => {
-			// Stop TUI to release terminal
-			tui.stop();
-
-			// Clear screen
-			process.stdout.write("\x1b[2J\x1b[H");
-
-			// Run command with full terminal access
-			const shell = process.env.SHELL || "/bin/sh";
-			const result = spawnSync(shell, ["-c", command], {
-				stdio: "inherit",
-				env: process.env,
-			});
-
-			// Restart TUI
-			tui.start();
-			tui.requestRender(true);
-
-			// Signal completion
-			done(result.status);
-
-			// Return empty component (immediately disposed since done() was called)
-			return { render: () => [], invalidate: () => {} };
-		});
-
-		// Return result to prevent default bash handling
-		const output =
-			exitCode === 0
-				? "(interactive command completed successfully)"
-				: `(interactive command exited with code ${exitCode})`;
-
-		return {
-			result: {
-				output,
-				exitCode: exitCode ?? 1,
-				cancelled: false,
-				truncated: false,
-			},
-		};
-	});
-}
dots/pi/agent/extensions/review.ts
@@ -1,1886 +0,0 @@
-/**
- * Code Review Extension (inspired by Codex's review feature)
- *
- * Provides a `/review` command that prompts the agent to review code changes.
- * Supports multiple review modes:
- * - Review a GitHub pull request (checks out the PR locally)
- * - Review against a base branch (PR style)
- * - Review uncommitted changes
- * - Review a specific commit
- * - Custom review instructions
- *
- * Usage:
- * - `/review` - show interactive selector
- * - `/review 123` - review PR #123 (checks out locally)
- * - `/review tektoncd/pipeline#123` - review PR #123 from tektoncd/pipeline
- * - `/review https://github.com/owner/repo/pull/123` - review PR from URL
- * - `/review pr 123` - review PR #123 (alternative syntax)
- * - `/review uncommitted` - review uncommitted changes directly
- * - `/review branch main` - review against main branch
- * - `/review commit abc123` - review specific commit
- * - `/review folder src docs` - review specific folders/files (snapshot, not diff)
- * - `/review custom "check for security issues"` - custom instructions
- *
- * Project-specific review guidelines:
- * - If a REVIEW_GUIDELINES.md file exists in the same directory as .pi,
- *   its contents are appended to the review prompt.
- *
- * Note: PR review requires a clean working tree (no uncommitted changes to tracked files).
- */
-
-import type { ExtensionAPI, ExtensionContext, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
-import { DynamicBorder, BorderedLoader } from "@earendil-works/pi-coding-agent";
-import { Container, type SelectItem, SelectList, Text, Key } from "@earendil-works/pi-tui";
-import path from "node:path";
-import { promises as fs, readdirSync, readFileSync } from "node:fs";
-import { execSync } from "node:child_process";
-
-// =============================================================================
-// Helper Functions for Saving Reviews
-// =============================================================================
-
-function getYearMonth(): string {
-	const now = new Date();
-	const year = now.getFullYear();
-	const month = String(now.getMonth() + 1).padStart(2, "0");
-	return `${year}-${month}`;
-}
-
-function formatTimestamp(date: Date): string {
-	return date.toISOString().replace("T", " ").slice(0, 19);
-}
-
-function extractOrgRepo(remoteUrl: string): { org: string; repo: string } | null {
-	// Extract org and repo from various git remote formats
-	// Examples:
-	//   https://github.com/NixOS/nixpkgs.git -> { org: "nixos", repo: "nixpkgs" }
-	//   git@github.com:tektoncd/pipeline.git -> { org: "tektoncd", repo: "pipeline" }
-	//   https://github.com/kubernetes/kubernetes -> { org: "kubernetes", repo: "kubernetes" }
-	
-	// Remove .git suffix
-	const cleanUrl = remoteUrl.replace(/\.git$/, "");
-	
-	// Extract org/repo pattern
-	const match = cleanUrl.match(/[:/]([^/]+)\/([^/]+)$/);
-	if (match) {
-		// Lowercase for case-insensitive matching
-		const org = match[1].toLowerCase();
-		const repo = match[2].toLowerCase();
-		return { org, repo };
-	}
-	
-	return null;
-}
-
-async function loadRepositorySpecificRules(cwd: string): Promise<string | null> {
-	try {
-		// Get git remote URL
-		const remoteUrl = execSync("git remote get-url origin", {
-			cwd,
-			encoding: "utf-8",
-		}).trim();
-
-		const orgRepo = extractOrgRepo(remoteUrl);
-		if (!orgRepo) {
-			return null;
-		}
-
-		const { org, repo } = orgRepo;
-		const home = process.env.HOME || process.env.USERPROFILE || "";
-		const skillsDir = path.join(home, ".config", "claude", "skills");
-
-		// Priority 1: Review skill with repo-specific file
-		// ~/.config/claude/skills/Review/repositories/<org>-<repo>.md
-		const reviewSkillDir = path.join(skillsDir, "Review");
-		const repoSpecificFile = path.join(reviewSkillDir, "repositories", `${org}-${repo}.md`);
-		
-		try {
-			const content = await fs.readFile(repoSpecificFile, "utf-8");
-			const filename = path.relative(skillsDir, repoSpecificFile);
-			return `
-
-# Repository-Specific Review Guidelines: ${org}/${repo}
-
-From: ~/.config/claude/skills/${filename}
-
-${content}
-`;
-		} catch {
-			// File doesn't exist, continue to next priority
-		}
-
-		// Priority 2: Review skill general workflow
-		// ~/.config/claude/skills/Review/workflows/Review.md
-		const reviewWorkflow = path.join(reviewSkillDir, "workflows", "Review.md");
-		try {
-			const content = await fs.readFile(reviewWorkflow, "utf-8");
-			const filename = path.relative(skillsDir, reviewWorkflow);
-			return `
-
-# General Review Guidelines
-
-From: ~/.config/claude/skills/${filename}
-
-${content}
-`;
-		} catch {
-			// File doesn't exist, continue to next priority
-		}
-
-		// Priority 3: Review skill general guidelines
-		// ~/.config/claude/skills/Review/SKILL.md
-		const reviewSkill = path.join(reviewSkillDir, "SKILL.md");
-		try {
-			const content = await fs.readFile(reviewSkill, "utf-8");
-			const filename = path.relative(skillsDir, reviewSkill);
-			return `
-
-# General Review Guidelines
-
-From: ~/.config/claude/skills/${filename}
-
-${content}
-`;
-		} catch {
-			// File doesn't exist, continue to fallback
-		}
-
-		// Fallback: Legacy repo-specific skill (e.g., Nixpkgs skill)
-		// Try to find a skill directory matching the repository name
-		const skillDirs = await fs.readdir(skillsDir);
-		const matchingSkill = skillDirs.find(
-			(dir) => dir.toLowerCase() === repo
-		);
-
-		if (!matchingSkill) {
-			return null;
-		}
-
-		const skillDir = path.join(skillsDir, matchingSkill);
-		
-		// Try to load review-specific files in priority order
-		const reviewFiles = [
-			path.join(skillDir, "review-checklist.md"),
-			path.join(skillDir, "workflows", "Review.md"),
-			path.join(skillDir, "SKILL.md"),
-		];
-		
-		for (const filepath of reviewFiles) {
-			try {
-				const content = await fs.readFile(filepath, "utf-8");
-				const filename = path.relative(skillsDir, filepath);
-				
-				// For SKILL.md, try to extract review section
-				if (filepath.endsWith("SKILL.md")) {
-					const reviewSection = content.match(/## Review Best Practices[\s\S]*?(?=##\s+[A-Z]|$)/);
-					if (reviewSection) {
-						return `
-
-# Repository-Specific Review Guidelines: ${repo}
-
-From: ~/.config/claude/skills/${filename}
-
-${reviewSection[0]}
-`;
-					}
-				}
-				
-				// For other files, include the whole content
-				return `
-
-# Repository-Specific Review Guidelines: ${repo}
-
-From: ~/.config/claude/skills/${filename}
-
-${content}
-`;
-			} catch {
-				// Try next file
-				continue;
-			}
-		}
-		
-		// None found
-		return null;
-	} catch {
-		return null;
-	}
-}
-
-async function saveReviewMetadata(reviewData: {
-	type: string;
-	target: string;
-	sessionFile: string;
-	cwd: string;
-}): Promise<string> {
-	const home = process.env.HOME || process.env.USERPROFILE || "";
-	const yearMonth = getYearMonth();
-	const reviewsDir = path.join(home, ".local", "share", "ai", "reviews", yearMonth);
-	
-	// Ensure directory exists
-	await fs.mkdir(reviewsDir, { recursive: true });
-	
-	const timestamp = Date.now();
-	const filename = `review-${timestamp}.md`;
-	const filepath = path.join(reviewsDir, filename);
-	
-	const now = new Date();
-	
-	// Try to get git context
-	let gitBranch = "unknown";
-	let gitCommit = "unknown";
-	try {
-		gitBranch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: reviewData.cwd, encoding: "utf-8" }).trim();
-		gitCommit = execSync("git rev-parse HEAD", { cwd: reviewData.cwd, encoding: "utf-8" }).trim().slice(0, 8);
-	} catch {
-		// Ignore errors
-	}
-	
-	const markdown = `# Code Review: ${reviewData.target}
-
-**Date:** ${formatTimestamp(now)}
-**Type:** ${reviewData.type}
-**Session:** ${reviewData.sessionFile}
-**Branch:** ${gitBranch}
-**Commit:** ${gitCommit}
-
-## Review Target
-
-${reviewData.target}
-
-## Notes
-
-Review session created. See session file for AI-generated review findings.
-
-To extract findings and create TODOs, use the org-todos extension.
-
-## Next Actions
-
-- [ ] Review AI findings
-- [ ] Create TODOs for critical/high issues
-- [ ] Apply suggested fixes
-- [ ] Re-review after changes
-`;
-
-	await fs.writeFile(filepath, markdown, "utf-8");
-	return filepath;
-}
-
-// State to track fresh session review (where we branched from).
-// Module-level state means only one review can be active at a time.
-// This is intentional - the UI and /end-review command assume a single active review.
-let reviewOriginId: string | undefined = undefined;
-// Temporary clone directory for cross-repo PR reviews (cleaned up on /end-review)
-let reviewCloneDir: string | undefined = undefined;
-
-const REVIEW_STATE_TYPE = "review-session";
-
-type ReviewSessionState = {
-	active: boolean;
-	originId?: string;
-	cloneDir?: string;
-};
-
-function setReviewWidget(ctx: ExtensionContext, active: boolean) {
-	if (!ctx.hasUI) return;
-	if (!active) {
-		ctx.ui.setWidget("review", undefined);
-		return;
-	}
-
-	ctx.ui.setWidget("review", (_tui, theme) => {
-		const text = new Text(theme.fg("warning", "Review session active, return with /end-review"), 0, 0);
-		return {
-			render(width: number) {
-				return text.render(width);
-			},
-			invalidate() {
-				text.invalidate();
-			},
-		};
-	});
-}
-
-function getReviewState(ctx: ExtensionContext): ReviewSessionState | undefined {
-	let state: ReviewSessionState | undefined;
-	for (const entry of ctx.sessionManager.getBranch()) {
-		if (entry.type === "custom" && entry.customType === REVIEW_STATE_TYPE) {
-			state = entry.data as ReviewSessionState | undefined;
-		}
-	}
-
-	return state;
-}
-
-function applyReviewState(ctx: ExtensionContext) {
-	const state = getReviewState(ctx);
-
-	if (state?.active && state.originId) {
-		reviewOriginId = state.originId;
-		reviewCloneDir = state.cloneDir;
-		setReviewWidget(ctx, true);
-		return;
-	}
-
-	reviewOriginId = undefined;
-	reviewCloneDir = undefined;
-	setReviewWidget(ctx, false);
-}
-
-/**
- * Clean up review state and optionally remove the temporary clone directory.
- */
-async function clearReviewState(ctx: ExtensionContext, pi: ExtensionAPI) {
-	setReviewWidget(ctx, false);
-	reviewOriginId = undefined;
-
-	// Clean up temporary clone directory for cross-repo PR reviews
-	if (reviewCloneDir) {
-		try {
-			await fs.rm(reviewCloneDir, { recursive: true, force: true });
-			ctx.ui.notify(`Cleaned up temporary clone: ${reviewCloneDir}`, "info");
-		} catch {
-			ctx.ui.notify(`Warning: Failed to clean up ${reviewCloneDir}`, "warning");
-		}
-		reviewCloneDir = undefined;
-	}
-
-	pi.appendEntry(REVIEW_STATE_TYPE, { active: false });
-}
-
-// Review target types (matching Codex's approach)
-type ReviewTarget =
-	| { type: "uncommitted" }
-	| { type: "baseBranch"; branch: string }
-	| { type: "commit"; sha: string; title?: string }
-	| { type: "custom"; instructions: string }
-	| { type: "pullRequest"; prNumber: number; baseBranch: string; title: string; repo?: string; cloneDir?: string }
-	| { type: "folder"; paths: string[] };
-
-// Prompts (adapted from Codex)
-const UNCOMMITTED_PROMPT =
-	"Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings.";
-
-const BASE_BRANCH_PROMPT_WITH_MERGE_BASE =
-	"Review the code changes against the base branch '{baseBranch}'. The merge base commit for this comparison is {mergeBaseSha}. Run `git diff {mergeBaseSha}` to inspect the changes relative to {baseBranch}. Provide prioritized, actionable findings.";
-
-const BASE_BRANCH_PROMPT_FALLBACK =
-	"Review the code changes against the base branch '{branch}'. Start by finding the merge diff between the current branch and {branch}'s upstream e.g. (`git merge-base HEAD \"$(git rev-parse --abbrev-ref \"{branch}@{upstream}\")\"`), then run `git diff` against that SHA to see what changes we would merge into the {branch} branch. Provide prioritized, actionable findings.";
-
-const COMMIT_PROMPT_WITH_TITLE =
-	'Review the code changes introduced by commit {sha} ("{title}"). Provide prioritized, actionable findings.';
-
-const COMMIT_PROMPT = "Review the code changes introduced by commit {sha}. Provide prioritized, actionable findings.";
-
-const PULL_REQUEST_PROMPT =
-	'Review pull request #{prNumber} ("{title}") against the base branch \'{baseBranch}\'. The merge base commit for this comparison is {mergeBaseSha}. Run `git diff {mergeBaseSha}` to inspect the changes that would be merged. Provide prioritized, actionable findings.';
-
-const PULL_REQUEST_PROMPT_FALLBACK =
-	'Review pull request #{prNumber} ("{title}") against the base branch \'{baseBranch}\'. Start by finding the merge base between the current branch and {baseBranch} (e.g., `git merge-base HEAD {baseBranch}`), then run `git diff` against that SHA to see the changes that would be merged. Provide prioritized, actionable findings.';
-
-const FOLDER_REVIEW_PROMPT =
-	"Review the code in the following paths: {paths}. This is a snapshot review (not a diff). Read the files directly in these paths and provide prioritized, actionable findings.";
-
-// =============================================================================
-// Review Focus Areas (dynamically discovered from reviewer agents)
-// =============================================================================
-
-interface ReviewerAgent {
-	/** Focus key, e.g. "general", "security", "go" */
-	focus: string;
-	/** Agent name from frontmatter, e.g. "reviewer-security" */
-	agent: string;
-	/** Human-readable label, e.g. "Security" */
-	label: string;
-	/** Description from agent frontmatter */
-	description: string;
-}
-
-/**
- * Discover reviewer agents from ~/.pi/agent/agents/reviewer*.md
- * The base `reviewer.md` is treated as "general".
- * Agents named `reviewer-<focus>.md` become focus areas.
- * Descriptions are read from the agent frontmatter.
- */
-function discoverReviewerAgents(): ReviewerAgent[] {
-	const home = process.env.HOME || process.env.USERPROFILE || "";
-	const agentsDir = path.join(home, ".pi", "agent", "agents");
-
-	let entries: string[];
-	try {
-		entries = readdirSync(agentsDir).filter(
-			(f) => f.startsWith("reviewer") && f.endsWith(".md"),
-		);
-	} catch {
-		return [];
-	}
-
-	const agents: ReviewerAgent[] = [];
-	for (const filename of entries.sort()) {
-		const filePath = path.join(agentsDir, filename);
-		let content: string;
-		try {
-			content = readFileSync(filePath, "utf-8");
-		} catch {
-			continue;
-		}
-
-		// Parse YAML frontmatter
-		const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
-		if (!fmMatch) continue;
-
-		const fm = fmMatch[1];
-		const nameMatch = fm.match(/^name:\s*(.+)$/m);
-		const descMatch = fm.match(/^description:\s*(.+)$/m);
-		if (!nameMatch) continue;
-
-		const agentName = nameMatch[1].trim();
-		const description = descMatch ? descMatch[1].trim() : agentName;
-
-		// Derive focus key: "reviewer" → "general", "reviewer-security" → "security"
-		let focus: string;
-		let label: string;
-		if (filename === "reviewer.md") {
-			focus = "general";
-			label = "General";
-		} else {
-			focus = filename.replace(/^reviewer-/, "").replace(/\.md$/, "");
-			label = focus.charAt(0).toUpperCase() + focus.slice(1);
-		}
-
-		agents.push({ focus, agent: agentName, label, description });
-	}
-
-	return agents;
-}
-
-async function loadProjectReviewGuidelines(cwd: string): Promise<string | null> {
-	let currentDir = path.resolve(cwd);
-
-	while (true) {
-		const piDir = path.join(currentDir, ".pi");
-		const guidelinesPath = path.join(currentDir, "REVIEW_GUIDELINES.md");
-
-		const piStats = await fs.stat(piDir).catch(() => null);
-		if (piStats?.isDirectory()) {
-			const guidelineStats = await fs.stat(guidelinesPath).catch(() => null);
-			if (guidelineStats?.isFile()) {
-				try {
-					const content = await fs.readFile(guidelinesPath, "utf8");
-					const trimmed = content.trim();
-					return trimmed ? trimmed : null;
-				} catch {
-					return null;
-				}
-			}
-			return null;
-		}
-
-		const parentDir = path.dirname(currentDir);
-		if (parentDir === currentDir) {
-			return null;
-		}
-		currentDir = parentDir;
-	}
-}
-
-/**
- * Get the merge base between HEAD and a branch
- */
-async function getMergeBase(
-	pi: ExtensionAPI,
-	branch: string,
-	cwd?: string,
-): Promise<string | null> {
-	const execOpts = cwd ? { cwd } : undefined;
-	try {
-		// First try to get the upstream tracking branch
-		const { stdout: upstream, code: upstreamCode } = await pi.exec("git", [
-			"rev-parse",
-			"--abbrev-ref",
-			`${branch}@{upstream}`,
-		], execOpts);
-
-		if (upstreamCode === 0 && upstream.trim()) {
-			const { stdout: mergeBase, code } = await pi.exec("git", ["merge-base", "HEAD", upstream.trim()], execOpts);
-			if (code === 0 && mergeBase.trim()) {
-				return mergeBase.trim();
-			}
-		}
-
-		// Fall back to using the branch directly
-		const { stdout: mergeBase, code } = await pi.exec("git", ["merge-base", "HEAD", branch], execOpts);
-		if (code === 0 && mergeBase.trim()) {
-			return mergeBase.trim();
-		}
-
-		return null;
-	} catch {
-		return null;
-	}
-}
-
-/**
- * Get list of local branches
- */
-async function getLocalBranches(pi: ExtensionAPI): Promise<string[]> {
-	const { stdout, code } = await pi.exec("git", ["branch", "--format=%(refname:short)"]);
-	if (code !== 0) return [];
-	return stdout
-		.trim()
-		.split("\n")
-		.filter((b) => b.trim());
-}
-
-/**
- * Get list of recent commits
- */
-async function getRecentCommits(pi: ExtensionAPI, limit: number = 10): Promise<Array<{ sha: string; title: string }>> {
-	const { stdout, code } = await pi.exec("git", ["log", `--oneline`, `-n`, `${limit}`]);
-	if (code !== 0) return [];
-
-	return stdout
-		.trim()
-		.split("\n")
-		.filter((line) => line.trim())
-		.map((line) => {
-			const [sha, ...rest] = line.trim().split(" ");
-			return { sha, title: rest.join(" ") };
-		});
-}
-
-/**
- * Check if there are uncommitted changes (staged, unstaged, or untracked)
- */
-async function hasUncommittedChanges(pi: ExtensionAPI): Promise<boolean> {
-	const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
-	return code === 0 && stdout.trim().length > 0;
-}
-
-/**
- * Check if there are changes that would prevent switching branches
- * (staged or unstaged changes to tracked files - untracked files are fine)
- */
-async function hasPendingChanges(pi: ExtensionAPI): Promise<boolean> {
-	// Check for staged or unstaged changes to tracked files
-	const { stdout, code } = await pi.exec("git", ["status", "--porcelain"]);
-	if (code !== 0) return false;
-
-	// Filter out untracked files (lines starting with ??)
-	const lines = stdout.trim().split("\n").filter((line) => line.trim());
-	const trackedChanges = lines.filter((line) => !line.startsWith("??"));
-	return trackedChanges.length > 0;
-}
-
-/**
- * Parsed PR reference with optional repository context
- */
-type PrReference = {
-	number: number;
-	repo?: string; // "owner/repo" format, undefined means current repo
-};
-
-/**
- * Parse a PR reference (URL, number, or owner/repo#number) and return the PR number + optional repo
- */
-function parsePrReference(ref: string): PrReference | null {
-	const trimmed = ref.trim();
-
-	// Try as a number first
-	const num = parseInt(trimmed, 10);
-	if (!isNaN(num) && num > 0) {
-		return { number: num };
-	}
-
-	// Try to extract from GitHub URL
-	// Formats: https://github.com/owner/repo/pull/123
-	//          github.com/owner/repo/pull/123
-	const urlMatch = trimmed.match(/github\.com\/([^/]+\/[^/]+)\/pull\/(\d+)/);
-	if (urlMatch) {
-		return { number: parseInt(urlMatch[2], 10), repo: urlMatch[1] };
-	}
-
-	// Try to extract from owner/repo#number format
-	// Format: tektoncd/pipeline#1234
-	const repoMatch = trimmed.match(/^([^/]+\/[^#]+)#(\d+)$/);
-	if (repoMatch) {
-		return { number: parseInt(repoMatch[2], 10), repo: repoMatch[1] };
-	}
-
-	return null;
-}
-
-/**
- * Get PR information from GitHub CLI
- */
-async function getPrInfo(pi: ExtensionAPI, prNumber: number, repo?: string): Promise<{ baseBranch: string; title: string; headBranch: string } | null> {
-	const args = ["pr", "view", String(prNumber), "--json", "baseRefName,title,headRefName"];
-	if (repo) {
-		args.push("-R", repo);
-	}
-	const { stdout, code } = await pi.exec("gh", args);
-
-	if (code !== 0) return null;
-
-	try {
-		const data = JSON.parse(stdout);
-		return {
-			baseBranch: data.baseRefName,
-			title: data.title,
-			headBranch: data.headRefName,
-		};
-	} catch {
-		return null;
-	}
-}
-
-/**
- * Checkout a PR using GitHub CLI
- */
-async function checkoutPr(pi: ExtensionAPI, prNumber: number, repo?: string): Promise<{ success: boolean; error?: string }> {
-	const args = ["pr", "checkout", String(prNumber)];
-	if (repo) {
-		args.push("-R", repo);
-	}
-	const { stdout, stderr, code } = await pi.exec("gh", args);
-
-	if (code !== 0) {
-		return { success: false, error: stderr || stdout || "Failed to checkout PR" };
-	}
-
-	return { success: true };
-}
-
-/**
- * Get the current branch name
- */
-async function getCurrentBranch(pi: ExtensionAPI): Promise<string | null> {
-	const { stdout, code } = await pi.exec("git", ["branch", "--show-current"]);
-	if (code === 0 && stdout.trim()) {
-		return stdout.trim();
-	}
-	return null;
-}
-
-/**
- * Get the default branch (main or master)
- */
-async function getDefaultBranch(pi: ExtensionAPI): Promise<string> {
-	// Try to get from remote HEAD
-	const { stdout, code } = await pi.exec("git", ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
-	if (code === 0 && stdout.trim()) {
-		return stdout.trim().replace("origin/", "");
-	}
-
-	// Fall back to checking if main or master exists
-	const branches = await getLocalBranches(pi);
-	if (branches.includes("main")) return "main";
-	if (branches.includes("master")) return "master";
-
-	return "main"; // Default fallback
-}
-
-/**
- * Build the review prompt based on target
- */
-async function buildReviewPrompt(pi: ExtensionAPI, target: ReviewTarget): Promise<string> {
-	switch (target.type) {
-		case "uncommitted":
-			return UNCOMMITTED_PROMPT;
-
-		case "baseBranch": {
-			const mergeBase = await getMergeBase(pi, target.branch);
-			if (mergeBase) {
-				return BASE_BRANCH_PROMPT_WITH_MERGE_BASE.replace(/{baseBranch}/g, target.branch).replace(
-					/{mergeBaseSha}/g,
-					mergeBase,
-				);
-			}
-			return BASE_BRANCH_PROMPT_FALLBACK.replace(/{branch}/g, target.branch);
-		}
-
-		case "commit":
-			if (target.title) {
-				return COMMIT_PROMPT_WITH_TITLE.replace("{sha}", target.sha).replace("{title}", target.title);
-			}
-			return COMMIT_PROMPT.replace("{sha}", target.sha);
-
-		case "custom":
-			return target.instructions;
-
-		case "pullRequest": {
-			const mergeBase = await getMergeBase(pi, target.baseBranch, target.cloneDir);
-			let prompt: string;
-			if (mergeBase) {
-				prompt = PULL_REQUEST_PROMPT
-					.replace(/{prNumber}/g, String(target.prNumber))
-					.replace(/{title}/g, target.title)
-					.replace(/{baseBranch}/g, target.baseBranch)
-					.replace(/{mergeBaseSha}/g, mergeBase);
-			} else {
-				prompt = PULL_REQUEST_PROMPT_FALLBACK
-					.replace(/{prNumber}/g, String(target.prNumber))
-					.replace(/{title}/g, target.title)
-					.replace(/{baseBranch}/g, target.baseBranch);
-			}
-			if (target.cloneDir) {
-				prompt += `\n\nIMPORTANT: This is a cross-repository PR. The repository has been cloned to \`${target.cloneDir}\`. You MUST \`cd ${target.cloneDir}\` before running any git or file commands. All file paths are relative to that directory.`;
-			}
-			return prompt;
-		}
-
-		case "folder":
-			return FOLDER_REVIEW_PROMPT.replace("{paths}", target.paths.join(", "));
-	}
-}
-
-/**
- * Get user-facing hint for the review target
- */
-function getUserFacingHint(target: ReviewTarget): string {
-	switch (target.type) {
-		case "uncommitted":
-			return "current changes";
-		case "baseBranch":
-			return `changes against '${target.branch}'`;
-		case "commit": {
-			const shortSha = target.sha.slice(0, 7);
-			return target.title ? `commit ${shortSha}: ${target.title}` : `commit ${shortSha}`;
-		}
-		case "custom":
-			return target.instructions.length > 40 ? target.instructions.slice(0, 37) + "..." : target.instructions;
-
-		case "pullRequest": {
-			const shortTitle = target.title.length > 30 ? target.title.slice(0, 27) + "..." : target.title;
-			const repoPrefix = target.repo ? `${target.repo}#` : "PR #";
-			return `${repoPrefix}${target.prNumber}: ${shortTitle}`;
-		}
-
-		case "folder": {
-			const joined = target.paths.join(", ");
-			return joined.length > 40 ? `folders: ${joined.slice(0, 37)}...` : `folders: ${joined}`;
-		}
-	}
-}
-
-// Review preset options for the selector
-const REVIEW_PRESETS = [
-	{ value: "pullRequest", label: "Review a pull request", description: "(GitHub PR)" },
-	{ value: "baseBranch", label: "Review against a base branch", description: "(local)" },
-	{ value: "uncommitted", label: "Review uncommitted changes", description: "" },
-	{ value: "commit", label: "Review a commit", description: "" },
-	{ value: "folder", label: "Review a folder (or more)", description: "(snapshot, not diff)" },
-	{ value: "custom", label: "Custom review instructions", description: "" },
-] as const;
-
-export default function reviewExtension(pi: ExtensionAPI) {
-	pi.on("session_start", (_event, ctx) => {
-		applyReviewState(ctx);
-	});
-
-	pi.on("session_switch", (_event, ctx) => {
-		applyReviewState(ctx);
-	});
-
-	pi.on("session_tree", (_event, ctx) => {
-		applyReviewState(ctx);
-	});
-
-	/**
-	 * Determine the smart default review type based on git state
-	 */
-	async function getSmartDefault(): Promise<"uncommitted" | "baseBranch" | "commit"> {
-		// Priority 1: If there are uncommitted changes, default to reviewing them
-		if (await hasUncommittedChanges(pi)) {
-			return "uncommitted";
-		}
-
-		// Priority 2: If on a feature branch (not the default branch), default to PR-style review
-		const currentBranch = await getCurrentBranch(pi);
-		const defaultBranch = await getDefaultBranch(pi);
-		if (currentBranch && currentBranch !== defaultBranch) {
-			return "baseBranch";
-		}
-
-		// Priority 3: Default to reviewing a specific commit
-		return "commit";
-	}
-
-	/**
-	 * Show the review preset selector
-	 */
-	async function showReviewSelector(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		// Determine smart default and reorder items
-		const smartDefault = await getSmartDefault();
-		const items: SelectItem[] = REVIEW_PRESETS
-			.slice() // copy to avoid mutating original
-			.sort((a, b) => {
-				// Put smart default first
-				if (a.value === smartDefault) return -1;
-				if (b.value === smartDefault) return 1;
-				return 0;
-			})
-			.map((preset) => ({
-				value: preset.value,
-				label: preset.label,
-				description: preset.description,
-			}));
-
-		while (true) {
-			const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
-				const container = new Container();
-				container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-				container.addChild(new Text(theme.fg("accent", theme.bold("Select a review preset"))));
-
-				const selectList = new SelectList(items, Math.min(items.length, 10), {
-					selectedPrefix: (text) => theme.fg("accent", text),
-					selectedText: (text) => theme.fg("accent", text),
-					description: (text) => theme.fg("muted", text),
-					scrollInfo: (text) => theme.fg("dim", text),
-					noMatch: (text) => theme.fg("warning", text),
-				});
-
-				selectList.onSelect = (item) => done(item.value);
-				selectList.onCancel = () => done(null);
-
-				container.addChild(selectList);
-				container.addChild(new Text(theme.fg("dim", "Press enter to confirm or esc to go back")));
-				container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-				return {
-					render(width: number) {
-						return container.render(width);
-					},
-					invalidate() {
-						container.invalidate();
-					},
-					handleInput(data: string) {
-						selectList.handleInput(data);
-						tui.requestRender();
-					},
-				};
-			});
-
-			if (!result) return null;
-
-			// Handle each preset type
-			switch (result) {
-				case "uncommitted":
-					return { type: "uncommitted" };
-
-				case "baseBranch": {
-					const target = await showBranchSelector(ctx);
-					if (target) return target;
-					break;
-				}
-
-				case "commit": {
-					const target = await showCommitSelector(ctx);
-					if (target) return target;
-					break;
-				}
-
-				case "custom": {
-					const target = await showCustomInput(ctx);
-					if (target) return target;
-					break;
-				}
-
-				case "folder": {
-					const target = await showFolderInput(ctx);
-					if (target) return target;
-					break;
-				}
-
-				case "pullRequest": {
-					const target = await showPrInput(ctx);
-					if (target) return target;
-					break;
-				}
-
-				default:
-					return null;
-			}
-		}
-	}
-
-	/**
-	 * Show branch selector for base branch review
-	 */
-	async function showBranchSelector(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		const branches = await getLocalBranches(pi);
-		const defaultBranch = await getDefaultBranch(pi);
-
-		if (branches.length === 0) {
-			ctx.ui.notify("No branches found", "error");
-			return null;
-		}
-
-		// Sort branches with default branch first
-		const sortedBranches = branches.sort((a, b) => {
-			if (a === defaultBranch) return -1;
-			if (b === defaultBranch) return 1;
-			return a.localeCompare(b);
-		});
-
-		const items: SelectItem[] = sortedBranches.map((branch) => ({
-			value: branch,
-			label: branch,
-			description: branch === defaultBranch ? "(default)" : "",
-		}));
-
-		const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
-			const container = new Container();
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-			container.addChild(new Text(theme.fg("accent", theme.bold("Select base branch"))));
-
-			const selectList = new SelectList(items, Math.min(items.length, 10), {
-				selectedPrefix: (text) => theme.fg("accent", text),
-				selectedText: (text) => theme.fg("accent", text),
-				description: (text) => theme.fg("muted", text),
-				scrollInfo: (text) => theme.fg("dim", text),
-				noMatch: (text) => theme.fg("warning", text),
-			});
-
-			// Enable search
-			selectList.searchable = true;
-
-			selectList.onSelect = (item) => done(item.value);
-			selectList.onCancel = () => done(null);
-
-			container.addChild(selectList);
-			container.addChild(new Text(theme.fg("dim", "Type to filter • enter to select • esc to cancel")));
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-			return {
-				render(width: number) {
-					return container.render(width);
-				},
-				invalidate() {
-					container.invalidate();
-				},
-				handleInput(data: string) {
-					selectList.handleInput(data);
-					tui.requestRender();
-				},
-			};
-		});
-
-		if (!result) return null;
-		return { type: "baseBranch", branch: result };
-	}
-
-	/**
-	 * Show commit selector
-	 */
-	async function showCommitSelector(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		const commits = await getRecentCommits(pi, 20);
-
-		if (commits.length === 0) {
-			ctx.ui.notify("No commits found", "error");
-			return null;
-		}
-
-		const items: SelectItem[] = commits.map((commit) => ({
-			value: commit.sha,
-			label: `${commit.sha.slice(0, 7)} ${commit.title}`,
-			description: "",
-		}));
-
-		const result = await ctx.ui.custom<{ sha: string; title: string } | null>((tui, theme, _kb, done) => {
-			const container = new Container();
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-			container.addChild(new Text(theme.fg("accent", theme.bold("Select commit to review"))));
-
-			const selectList = new SelectList(items, Math.min(items.length, 10), {
-				selectedPrefix: (text) => theme.fg("accent", text),
-				selectedText: (text) => theme.fg("accent", text),
-				description: (text) => theme.fg("muted", text),
-				scrollInfo: (text) => theme.fg("dim", text),
-				noMatch: (text) => theme.fg("warning", text),
-			});
-
-			// Enable search
-			selectList.searchable = true;
-
-			selectList.onSelect = (item) => {
-				const commit = commits.find((c) => c.sha === item.value);
-				if (commit) {
-					done(commit);
-				} else {
-					done(null);
-				}
-			};
-			selectList.onCancel = () => done(null);
-
-			container.addChild(selectList);
-			container.addChild(new Text(theme.fg("dim", "Type to filter • enter to select • esc to cancel")));
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-			return {
-				render(width: number) {
-					return container.render(width);
-				},
-				invalidate() {
-					container.invalidate();
-				},
-				handleInput(data: string) {
-					selectList.handleInput(data);
-					tui.requestRender();
-				},
-			};
-		});
-
-		if (!result) return null;
-		return { type: "commit", sha: result.sha, title: result.title };
-	}
-
-	/**
-	 * Show custom instructions input
-	 */
-	async function showCustomInput(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		const result = await ctx.ui.editor(
-			"Enter review instructions:",
-			"Review the code for security vulnerabilities and potential bugs...",
-		);
-
-		if (!result?.trim()) return null;
-		return { type: "custom", instructions: result.trim() };
-	}
-
-	function parseReviewPaths(value: string): string[] {
-		return value
-			.split(/\s+/)
-			.map((item) => item.trim())
-			.filter((item) => item.length > 0);
-	}
-
-	/**
-	 * Show folder input
-	 */
-	async function showFolderInput(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		const result = await ctx.ui.editor(
-			"Enter folders/files to review (space-separated or one per line):",
-			".",
-		);
-
-		if (!result?.trim()) return null;
-		const paths = parseReviewPaths(result);
-		if (paths.length === 0) return null;
-
-		return { type: "folder", paths };
-	}
-
-	/**
-	 * Show focus area selector for the review.
-	 * Dynamically discovers reviewer-*.md agents from ~/.pi/agent/agents/.
-	 * Returns the focus key ("general", "security", "full", etc.) or null if cancelled.
-	 */
-	async function showFocusSelector(ctx: ExtensionContext): Promise<{ focus: string; agents: ReviewerAgent[] } | null> {
-		// Discover all reviewer agents fresh each time (allows adding agents mid-session)
-		const allAgents = discoverReviewerAgents();
-
-		if (allAgents.length === 0) {
-			ctx.ui.notify("No reviewer agents found in ~/.pi/agent/agents/", "error");
-			return null;
-		}
-
-		const items: SelectItem[] = allAgents.map((ra) => ({
-			value: ra.focus,
-			label: ra.label,
-			description: ra.description,
-		}));
-
-		// Add "full" option at the end
-		items.push({
-			value: "full",
-			label: "Full review",
-			description: `All ${allAgents.length} reviewers in parallel`,
-		});
-
-		const result = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
-			const container = new Container();
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-			container.addChild(new Text(theme.fg("accent", theme.bold("Select review focus"))));
-
-			const selectList = new SelectList(items, Math.min(items.length, 10), {
-				selectedPrefix: (text) => theme.fg("accent", text),
-				selectedText: (text) => theme.fg("accent", text),
-				description: (text) => theme.fg("muted", text),
-				scrollInfo: (text) => theme.fg("dim", text),
-				noMatch: (text) => theme.fg("warning", text),
-			});
-
-			selectList.onSelect = (item) => done(item.value);
-			selectList.onCancel = () => done(null);
-
-			container.addChild(selectList);
-			container.addChild(new Text(theme.fg("dim", "Press enter to confirm or esc to go back")));
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-			return {
-				render(width: number) {
-					return container.render(width);
-				},
-				invalidate() {
-					container.invalidate();
-				},
-				handleInput(data: string) {
-					selectList.handleInput(data);
-					tui.requestRender();
-				},
-			};
-		});
-
-		if (!result) return null;
-		return { focus: result, agents: allAgents };
-	}
-
-	/**
-	 * Fetch open PRs from GitHub for the selector
-	 */
-	async function fetchOpenPRs(): Promise<Array<{ number: number; title: string; author: string; branch: string; base: string; isDraft: boolean; reviewDecision: string }>> {
-		const { stdout, code } = await pi.exec("gh", [
-			"pr", "list",
-			"--state", "open",
-			"--json", "number,title,author,headRefName,baseRefName,isDraft,reviewDecision",
-			"--limit", "30",
-		]);
-
-		if (code !== 0) return [];
-
-		try {
-			const data = JSON.parse(stdout);
-			if (!Array.isArray(data)) return [];
-			return data.map((item: any) => ({
-				number: item.number ?? 0,
-				title: item.title ?? "",
-				author: item.author?.login ?? "",
-				branch: item.headRefName ?? "",
-				base: item.baseRefName ?? "",
-				isDraft: item.isDraft ?? false,
-				reviewDecision: item.reviewDecision ?? "",
-			}));
-		} catch {
-			return [];
-		}
-	}
-
-	/**
-	 * Get review decision label for display
-	 */
-	function reviewDecisionLabel(decision: string): string {
-		switch (decision) {
-			case "APPROVED": return " ✓approved";
-			case "CHANGES_REQUESTED": return " ✗changes requested";
-			case "REVIEW_REQUIRED": return " ⏳review needed";
-			default: return "";
-		}
-	}
-
-	/**
-	 * Show PR selector with list of open PRs + manual entry option
-	 */
-	async function showPrInput(ctx: ExtensionContext): Promise<ReviewTarget | null> {
-		// Fetch open PRs
-		ctx.ui.notify("Fetching open PRs...", "info");
-		const prs = await fetchOpenPRs();
-
-		// Try to get current user for smart ordering
-		let currentUser = "";
-		const { stdout: userOut, code: userCode } = await pi.exec("gh", ["api", "user", "--jq", ".login"], { timeout: 5000 });
-		if (userCode === 0) currentUser = userOut.trim();
-
-		// Sort: PRs needing your review first, then your own, then others
-		const sorted = prs.slice().sort((a, b) => {
-			const aScore = a.reviewDecision === "REVIEW_REQUIRED" && a.author !== currentUser ? 0
-				: a.author === currentUser ? 1
-				: 2;
-			const bScore = b.reviewDecision === "REVIEW_REQUIRED" && b.author !== currentUser ? 0
-				: b.author === currentUser ? 1
-				: 2;
-			return aScore - bScore;
-		});
-
-		// Build select items
-		const allItems: SelectItem[] = sorted.map((pr) => {
-			const draft = pr.isDraft ? " [draft]" : "";
-			const review = reviewDecisionLabel(pr.reviewDecision);
-			return {
-				value: String(pr.number),
-				label: `#${pr.number} ${pr.title}${draft}${review}`,
-				description: `@${pr.author}  ${pr.branch} → ${pr.base}`,
-			};
-		});
-
-		// Add manual entry option at the bottom
-		const manualItem: SelectItem = {
-			value: "__manual__",
-			label: "Enter PR number manually…",
-			description: "(for cross-repo or closed PRs)",
-		};
-
-		/** Fuzzy-match: all terms (split by space) must appear somewhere in the searchable text */
-		function fuzzyMatch(item: SelectItem, query: string): boolean {
-			if (!query) return true;
-			const searchable = `${item.label} ${item.description || ""}`.toLowerCase();
-			const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
-			return terms.every((term) => searchable.includes(term));
-		}
-
-		// Show selector with search-as-you-type
-		const selected = await ctx.ui.custom<string | null>((tui, theme, _kb, done) => {
-			let searchQuery = "";
-
-			function getFilteredItems(): SelectItem[] {
-				if (!searchQuery) return [...allItems, manualItem];
-				const filtered = allItems.filter((item) => fuzzyMatch(item, searchQuery));
-				filtered.push(manualItem);
-				return filtered;
-			}
-
-			let currentItems = getFilteredItems();
-
-			const container = new Container();
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-			const headerText = new Text("", 0, 0);
-			function updateHeader() {
-				const title = theme.fg("accent", theme.bold("Select a pull request to review"));
-				if (searchQuery) {
-					headerText.setText(`${title}  ${theme.fg("warning", `filter: ${searchQuery}`)}`);
-				} else {
-					headerText.setText(title);
-				}
-			}
-			updateHeader();
-			container.addChild(headerText);
-
-			let selectList = new SelectList(currentItems, Math.min(currentItems.length, 12), {
-				selectedPrefix: (text) => theme.fg("accent", text),
-				selectedText: (text) => theme.fg("accent", text),
-				description: (text) => theme.fg("muted", text),
-				scrollInfo: (text) => theme.fg("dim", text),
-				noMatch: (text) => theme.fg("warning", text),
-			});
-			selectList.onSelect = (item) => done(item.value);
-			selectList.onCancel = () => done(null);
-
-			container.addChild(selectList);
-			container.addChild(new Text(theme.fg("dim", "Type to filter • enter to select • esc to cancel")));
-			container.addChild(new DynamicBorder((str) => theme.fg("accent", str)));
-
-			function rebuildList() {
-				currentItems = getFilteredItems();
-				const newList = new SelectList(currentItems, Math.min(currentItems.length, 12), {
-					selectedPrefix: (text) => theme.fg("accent", text),
-					selectedText: (text) => theme.fg("accent", text),
-					description: (text) => theme.fg("muted", text),
-					scrollInfo: (text) => theme.fg("dim", text),
-					noMatch: (text) => theme.fg("warning", text),
-				});
-				newList.onSelect = (item) => done(item.value);
-				newList.onCancel = () => done(null);
-				// Replace in container (index 2: after border + header)
-				const idx = container.children.indexOf(selectList);
-				if (idx !== -1) container.children[idx] = newList;
-				selectList = newList;
-				updateHeader();
-			}
-
-			return {
-				render(width: number) { return container.render(width); },
-				invalidate() { container.invalidate(); },
-				handleInput(data: string) {
-					// Backspace: remove last char from search
-					if (data === "\x7f" || data === "\b") {
-						if (searchQuery.length > 0) {
-							searchQuery = searchQuery.slice(0, -1);
-							rebuildList();
-							tui.requestRender();
-						}
-						return;
-					}
-
-					// Printable characters: append to search
-					if (data.length === 1 && data >= " " && data <= "~") {
-						searchQuery += data;
-						rebuildList();
-						tui.requestRender();
-						return;
-					}
-
-					// Everything else (arrows, enter, escape): pass to SelectList
-					selectList.handleInput(data);
-					tui.requestRender();
-				},
-			};
-		});
-
-		if (!selected) return null;
-
-		// Build the ref string for handlePrCheckout
-		let prRef: string;
-		let prRepo: string | undefined;
-		if (selected === "__manual__") {
-			const input = await ctx.ui.editor(
-				"Enter PR number, owner/repo#number, or URL (e.g. 123, tektoncd/pipeline#456, or https://github.com/owner/repo/pull/123):",
-				"",
-			);
-			if (!input?.trim()) return null;
-
-			const parsed = parsePrReference(input);
-			if (!parsed) {
-				ctx.ui.notify("Invalid PR reference. Enter a number, owner/repo#number, or GitHub PR URL.", "error");
-				return null;
-			}
-			prRef = input.trim();
-			prRepo = parsed.repo;
-		} else {
-			prRef = selected;
-		}
-
-		// Delegate to handlePrCheckout which handles both local and cross-repo PRs
-		return await handlePrCheckout(ctx, prRef, prRepo);
-	}
-
-	/**
-	 * Execute the review
-	 */
-	async function executeReview(ctx: ExtensionCommandContext, target: ReviewTarget, useFreshSession: boolean, focus: string, reviewerAgents: ReviewerAgent[]): Promise<void> {
-		// Check if we're already in a review
-		if (reviewOriginId) {
-			ctx.ui.notify("Already in a review. Use /end-review to finish first.", "warning");
-			return;
-		}
-
-		// Track clone directory for cross-repo PR reviews
-		const cloneDir = target.type === "pullRequest" ? target.cloneDir : undefined;
-		reviewCloneDir = cloneDir;
-
-		// Handle fresh session mode
-		if (useFreshSession) {
-			// Store current position (where we'll return to)
-			const originId = ctx.sessionManager.getLeafId() ?? undefined;
-			if (!originId) {
-				ctx.ui.notify("Failed to determine review origin. Try again from a session with messages.", "error");
-				reviewCloneDir = undefined;
-				return;
-			}
-			reviewOriginId = originId;
-
-			// Keep a local copy so session_tree events during navigation don't wipe it
-			const lockedOriginId = originId;
-
-			// Find the first user message in the session
-			const entries = ctx.sessionManager.getEntries();
-			const firstUserMessage = entries.find(
-				(e) => e.type === "message" && e.message.role === "user",
-			);
-
-			if (!firstUserMessage) {
-				ctx.ui.notify("No user message found in session", "error");
-				reviewOriginId = undefined;
-				reviewCloneDir = undefined;
-				return;
-			}
-
-			// Navigate to first user message to create a new branch from that point
-			// Label it as "code-review" so it's visible in the tree
-			try {
-				const result = await ctx.navigateTree(firstUserMessage.id, { summarize: false, label: "code-review" });
-				if (result.cancelled) {
-					reviewOriginId = undefined;
-					reviewCloneDir = undefined;
-					return;
-				}
-			} catch (error) {
-				// Clean up state if navigation fails
-				reviewOriginId = undefined;
-				reviewCloneDir = undefined;
-				ctx.ui.notify(`Failed to start review: ${error instanceof Error ? error.message : String(error)}`, "error");
-				return;
-			}
-
-			// Restore origin after navigation events (session_tree can reset it)
-			reviewOriginId = lockedOriginId;
-
-			// Clear the editor (navigating to user message fills it with the message text)
-			ctx.ui.setEditorText("");
-
-			// Show widget indicating review is active
-			setReviewWidget(ctx, true);
-
-			// Persist review state so tree navigation can restore/reset it
-			pi.appendEntry(REVIEW_STATE_TYPE, { active: true, originId: lockedOriginId, cloneDir });
-		}
-
-		const reviewPrompt = await buildReviewPrompt(pi, target);
-		const hint = getUserFacingHint(target);
-		const projectGuidelines = await loadProjectReviewGuidelines(ctx.cwd);
-		const repoRules = await loadRepositorySpecificRules(ctx.cwd);
-
-		// Build the task context that will be sent to subagent(s)
-		let taskContext = reviewPrompt;
-
-		if (projectGuidelines) {
-			taskContext += `\n\nThis project has additional instructions for code reviews:\n\n${projectGuidelines}`;
-		}
-
-		if (repoRules) {
-			taskContext += repoRules;
-		}
-
-		// Look up the selected agent (or all agents for "full")
-		const selectedAgent = reviewerAgents.find((ra) => ra.focus === focus);
-		const focusLabel = focus === "full" ? "Full" : (selectedAgent?.label ?? focus);
-		const modeHint = useFreshSession ? " (fresh session)" : "";
-		ctx.ui.notify(`Starting ${focusLabel} review: ${hint}${modeHint}`, "info");
-
-		// Save review metadata to ai-storage
-		try {
-			const reviewPath = await saveReviewMetadata({
-				type: target.type,
-				target: `[${focusLabel}] ${hint}`,
-				sessionFile: ctx.sessionManager.getSessionFile(),
-				cwd: ctx.cwd,
-			});
-
-			ctx.ui.notify(`Review metadata saved: ${path.basename(reviewPath)}`, "success");
-		} catch (error: any) {
-			// Don't fail the review if saving fails
-			ctx.ui.notify(`Warning: Failed to save review metadata: ${error.message}`, "warning");
-		}
-
-		// Dispatch to subagent(s) based on focus area
-		if (focus === "full") {
-			// Run all discovered reviewer agents in parallel
-			const subagentTasks = reviewerAgents.map((ra) => ({
-				agent: ra.agent,
-				task: `${ra.label}-focused review: ${taskContext}`,
-			}));
-
-			const focusNames = reviewerAgents.map((ra) => ra.focus).join("/");
-			const tasksJson = JSON.stringify(subagentTasks);
-			const subagentPrompt = `Run a full code review using parallel subagents. Dispatch the following reviewers using the subagent tool in parallel mode:
-
-${tasksJson}
-
-After all reviewers complete, present a **consolidated review report**:
-1. Group findings by file, deduplicating overlapping issues (keep the most specific)
-2. Note which reviewer (${focusNames}) flagged each issue
-3. Use the highest priority tag when reviewers disagree
-4. Provide a unified verdict: "correct" if no P0/P1 issues, "needs attention" otherwise`;
-
-			pi.sendUserMessage(subagentPrompt);
-		} else if (selectedAgent) {
-			// Single focused review — dispatch to one subagent
-			const subagentPrompt = `Run a ${focusLabel.toLowerCase()}-focused code review using the subagent tool.
-
-Dispatch to the \`${selectedAgent.agent}\` agent with this task:
-
-${taskContext}
-
-Present the subagent's findings directly to the user.`;
-
-			pi.sendUserMessage(subagentPrompt);
-		} else {
-			ctx.ui.notify(`No reviewer agent found for focus "${focus}"`, "error");
-		}
-	}
-
-	/**
-	 * Parse command arguments for direct invocation
-	 * Returns the target or a special marker for PR that needs async handling
-	 */
-	function parseArgs(args: string | undefined): ReviewTarget | { type: "pr"; ref: string; repo?: string } | null {
-		if (!args?.trim()) return null;
-
-		const trimmed = args.trim();
-
-		// Check if it looks like a PR reference (URL, number, or owner/repo#number)
-		// This allows `/review 123`, `/review https://...`, or `/review tektoncd/pipeline#1234`
-		const prRef = parsePrReference(trimmed);
-		if (prRef !== null) {
-			return { type: "pr", ref: trimmed, repo: prRef.repo };
-		}
-
-		const parts = trimmed.split(/\s+/);
-		const subcommand = parts[0]?.toLowerCase();
-
-		switch (subcommand) {
-			case "uncommitted":
-				return { type: "uncommitted" };
-
-			case "branch": {
-				const branch = parts[1];
-				if (!branch) return null;
-				return { type: "baseBranch", branch };
-			}
-
-			case "commit": {
-				const sha = parts[1];
-				if (!sha) return null;
-				const title = parts.slice(2).join(" ") || undefined;
-				return { type: "commit", sha, title };
-			}
-
-			case "custom": {
-				const instructions = parts.slice(1).join(" ");
-				if (!instructions) return null;
-				return { type: "custom", instructions };
-			}
-
-			case "folder": {
-				const paths = parseReviewPaths(parts.slice(1).join(" "));
-				if (paths.length === 0) return null;
-				return { type: "folder", paths };
-			}
-
-			case "pr": {
-				const ref = parts[1];
-				if (!ref) return { type: "pr", ref: "__select__" };
-				const parsed = parsePrReference(ref);
-				return { type: "pr", ref, repo: parsed?.repo };
-			}
-
-			default:
-				return null;
-		}
-	}
-
-	/**
-	 * Handle PR checkout and return a ReviewTarget (or null on failure)
-	 *
-	 * For cross-repo PRs (when repo is set), clones the repository to a temp
-	 * directory and checks out the PR there instead of in the current repo.
-	 */
-	async function handlePrCheckout(ctx: ExtensionContext, ref: string, repo?: string): Promise<ReviewTarget | null> {
-		const prRef = parsePrReference(ref);
-		if (!prRef) {
-			ctx.ui.notify("Invalid PR reference. Enter a number or GitHub PR URL.", "error");
-			return null;
-		}
-
-		// Use repo from the parsed reference if not explicitly provided
-		const effectiveRepo = repo ?? prRef.repo;
-		const repoLabel = effectiveRepo ? ` (${effectiveRepo})` : "";
-		const isCrossRepo = !!effectiveRepo;
-
-		// For local repo PRs, check for pending changes (cross-repo clones to temp dir so no conflict)
-		if (!isCrossRepo && await hasPendingChanges(pi)) {
-			ctx.ui.notify("Cannot checkout PR: you have uncommitted changes. Please commit or stash them first.", "error");
-			return null;
-		}
-
-		// Get PR info
-		ctx.ui.notify(`Fetching PR #${prRef.number} info${repoLabel}...`, "info");
-		const prInfo = await getPrInfo(pi, prRef.number, effectiveRepo);
-
-		if (!prInfo) {
-			ctx.ui.notify(`Could not find PR #${prRef.number}${repoLabel}. Make sure gh is authenticated and the PR exists.`, "error");
-			return null;
-		}
-
-		let cloneDir: string | undefined;
-
-		if (isCrossRepo) {
-			// Clone the repository to a temp directory for cross-repo PRs
-			const tmpBase = path.join(process.env.TMPDIR || "/tmp", "pi-review");
-			await fs.mkdir(tmpBase, { recursive: true });
-			const sanitizedRepo = effectiveRepo!.replace(/\//g, "-");
-			cloneDir = path.join(tmpBase, `${sanitizedRepo}-pr-${prRef.number}`);
-
-			// Remove existing clone if present (stale from previous review)
-			try {
-				await fs.rm(cloneDir, { recursive: true, force: true });
-			} catch {
-				// Ignore
-			}
-
-			ctx.ui.notify(`Cloning ${effectiveRepo} to ${cloneDir}...`, "info");
-			const { stderr: cloneErr, code: cloneCode } = await pi.exec("gh", [
-				"repo", "clone", effectiveRepo!, cloneDir,
-			]);
-
-			if (cloneCode !== 0) {
-				ctx.ui.notify(`Failed to clone ${effectiveRepo}: ${cloneErr}`, "error");
-				return null;
-			}
-
-			// Checkout the PR inside the clone
-			ctx.ui.notify(`Checking out PR #${prRef.number} in clone...`, "info");
-			const { stderr: coErr, code: coCode } = await pi.exec("gh", [
-				"pr", "checkout", String(prRef.number), "-R", effectiveRepo!,
-			], { cwd: cloneDir });
-
-			if (coCode !== 0) {
-				ctx.ui.notify(`Failed to checkout PR: ${coErr}`, "error");
-				// Clean up failed clone
-				await fs.rm(cloneDir, { recursive: true, force: true }).catch(() => {});
-				return null;
-			}
-
-			ctx.ui.notify(`Checked out PR #${prRef.number} (${prInfo.headBranch}) in ${cloneDir}`, "info");
-		} else {
-			// Local repo checkout
-			ctx.ui.notify(`Checking out PR #${prRef.number}...`, "info");
-			const checkoutResult = await checkoutPr(pi, prRef.number);
-
-			if (!checkoutResult.success) {
-				ctx.ui.notify(`Failed to checkout PR: ${checkoutResult.error}`, "error");
-				return null;
-			}
-
-			ctx.ui.notify(`Checked out PR #${prRef.number} (${prInfo.headBranch})`, "info");
-		}
-
-		return {
-			type: "pullRequest",
-			prNumber: prRef.number,
-			baseBranch: prInfo.baseBranch,
-			title: prInfo.title,
-			repo: effectiveRepo,
-			cloneDir,
-		};
-	}
-
-	// Register the /review command
-	pi.registerCommand("review", {
-		description: "Review code changes (PR, uncommitted, branch, commit, folder, or custom)",
-		handler: async (args, ctx) => {
-			if (!ctx.hasUI) {
-				ctx.ui.notify("Review requires interactive mode", "error");
-				return;
-			}
-
-			// Check if we're already in a review
-			if (reviewOriginId) {
-				ctx.ui.notify("Already in a review. Use /end-review to finish first.", "warning");
-				return;
-			}
-
-			// Check if we're in a git repository
-			const { code } = await pi.exec("git", ["rev-parse", "--git-dir"]);
-			if (code !== 0) {
-				ctx.ui.notify("Not a git repository", "error");
-				return;
-			}
-
-			// Try to parse direct arguments
-			let target: ReviewTarget | null = null;
-			let fromSelector = false;
-			const parsed = parseArgs(args);
-
-			if (parsed) {
-				if (parsed.type === "pr") {
-					if (parsed.ref === "__select__") {
-						// `/review pr` with no number — show PR selector
-						target = await showPrInput(ctx);
-					} else {
-						// `/review pr 123` or `/review 123` — direct checkout
-						target = await handlePrCheckout(ctx, parsed.ref, parsed.repo);
-					}
-					if (!target) {
-						ctx.ui.notify("PR review failed. Returning to review menu.", "warning");
-					}
-				} else {
-					target = parsed;
-				}
-			}
-
-			// If no args or invalid args, show selector
-			if (!target) {
-				fromSelector = true;
-			}
-
-			while (true) {
-				if (!target && fromSelector) {
-					target = await showReviewSelector(ctx);
-				}
-
-				if (!target) {
-					ctx.ui.notify("Review cancelled", "info");
-					return;
-				}
-
-				// Select review focus area
-				const focusResult = await showFocusSelector(ctx);
-
-				if (!focusResult) {
-					if (fromSelector) {
-						target = null;
-						continue;
-					}
-					ctx.ui.notify("Review cancelled", "info");
-					return;
-				}
-
-				// Determine if we should use fresh session mode
-				// Check if this is a new session (no messages yet)
-				const entries = ctx.sessionManager.getEntries();
-				const messageCount = entries.filter((e) => e.type === "message").length;
-
-				let useFreshSession = false;
-
-				if (messageCount > 0) {
-					// Existing session - ask user which mode they want
-					const choice = await ctx.ui.select("Start review in:", ["Empty branch", "Current session"]);
-
-					if (choice === undefined) {
-						if (fromSelector) {
-							target = null;
-							continue;
-						}
-						ctx.ui.notify("Review cancelled", "info");
-						return;
-					}
-
-					useFreshSession = choice === "Empty branch";
-				}
-				// If messageCount === 0, useFreshSession stays false (current session mode)
-
-				await executeReview(ctx, target, useFreshSession, focusResult.focus, focusResult.agents);
-				return;
-			}
-		},
-	});
-
-	// Custom prompt for review summaries - focuses on capturing review findings
-	const REVIEW_SUMMARY_PROMPT = `We are switching to a coding session to continue working on the code. 
-Create a structured summary of this review branch for context when returning later.
-	
-You MUST summarize the code review that was performed in this branch so that the user can act on it.
-
-1. What was reviewed (files, changes, scope)
-2. Key findings and their priority levels (P0-P3)
-3. The overall verdict (correct vs needs attention)
-4. Any action items or recommendations
-
-YOU MUST append a message with this EXACT format at the end of your summary:
-
-## Next Steps
-1. [What should happen next to act on the review]
-
-## Constraints & Preferences
-- [Any constraints, preferences, or requirements mentioned]
-- [Or "(none)" if none were mentioned]
-
-## Code Review Findings
-
-[P0] Short Title
-
-File: path/to/file.ext:line_number
-
-\`\`\`
-affected code snippet
-\`\`\`
-
-Preserve exact file paths, function names, and error messages.
-`;
-
-	// Register the /end-review command
-	pi.registerCommand("end-review", {
-		description: "Complete review and return to original position",
-		handler: async (args, ctx) => {
-			if (!ctx.hasUI) {
-				ctx.ui.notify("End-review requires interactive mode", "error");
-				return;
-			}
-
-			// Check if we're in a fresh session review
-			if (!reviewOriginId) {
-				const state = getReviewState(ctx);
-				if (state?.active && state.originId) {
-					reviewOriginId = state.originId;
-					reviewCloneDir = state.cloneDir;
-				} else if (state?.active) {
-					await clearReviewState(ctx, pi);
-					ctx.ui.notify("Review state was missing origin info; cleared review status.", "warning");
-					return;
-				} else {
-					ctx.ui.notify("Not in a review branch (use /review first, or review was started in current session mode)", "info");
-					return;
-				}
-			}
-
-			// Ask about summarization (Summarize is default/first option)
-			const summaryChoice = await ctx.ui.select("Summarize review branch?", [
-				"Summarize",
-				"No summary",
-			]);
-
-			if (summaryChoice === undefined) {
-				// User cancelled - keep state so they can call /end-review again
-				ctx.ui.notify("Cancelled. Use /end-review to try again.", "info");
-				return;
-			}
-
-			const wantsSummary = summaryChoice === "Summarize";
-			const originId = reviewOriginId;
-
-			if (wantsSummary) {
-				// Show spinner while summarizing
-				const result = await ctx.ui.custom<{ cancelled: boolean; error?: string } | null>((tui, theme, _kb, done) => {
-					const loader = new BorderedLoader(tui, theme, "Summarizing review branch...");
-					loader.onAbort = () => done(null);
-
-					ctx.navigateTree(originId!, {
-						summarize: true,
-						customInstructions: REVIEW_SUMMARY_PROMPT,
-						replaceInstructions: true,
-					})
-						.then(done)
-						.catch((err) => done({ cancelled: false, error: err instanceof Error ? err.message : String(err) }));
-
-					return loader;
-				});
-
-				if (result === null) {
-					// User aborted - keep state so they can try again
-					ctx.ui.notify("Summarization cancelled. Use /end-review to try again.", "info");
-					return;
-				}
-
-				if (result.error) {
-					// Real error - keep state so they can try again
-					ctx.ui.notify(`Summarization failed: ${result.error}`, "error");
-					return;
-				}
-
-				// Clear state only on success
-				await clearReviewState(ctx, pi);
-
-				if (result.cancelled) {
-					ctx.ui.notify("Navigation cancelled", "info");
-					return;
-				}
-
-				// Pre-fill prompt if editor is empty
-				if (!ctx.ui.getEditorText().trim()) {
-					ctx.ui.setEditorText("Act on the code review");
-				}
-
-				ctx.ui.notify("Review complete! Returned to original position.", "info");
-			} else {
-				// No summary - just navigate back
-				try {
-					const result = await ctx.navigateTree(originId!, { summarize: false });
-
-					if (result.cancelled) {
-						// Keep state so they can try again
-						ctx.ui.notify("Navigation cancelled. Use /end-review to try again.", "info");
-						return;
-					}
-
-					// Clear state only on success
-					await clearReviewState(ctx, pi);
-					ctx.ui.notify("Review complete! Returned to original position.", "info");
-				} catch (error) {
-					// Keep state so they can try again
-					ctx.ui.notify(`Failed to return: ${error instanceof Error ? error.message : String(error)}`, "error");
-				}
-			}
-		},
-	});
-}
dots/pi/agent/extensions/subagent-commands.ts
@@ -1,170 +0,0 @@
-/**
- * Subagent Commands - Slash commands for common subagent workflows
- * 
- * Provides easy-to-use slash commands that invoke the subagent tool:
- * - /scout <query> - Fast reconnaissance with scout agent
- * - /implement <query> - Full workflow: scout → planner → worker
- * - /scout-and-plan <query> - Planning workflow: scout → planner
- * - /review-code <query> - Review workflow: scout → reviewer
- */
-
-import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-
-export default function (pi: ExtensionAPI) {
-	// /scout - Quick reconnaissance
-	pi.registerCommand("scout", {
-		description: "Quick codebase reconnaissance with scout agent",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /scout <query>", "error");
-				return;
-			}
-
-			// Send a message that will cause the AI to use the subagent tool
-			ctx.ui.notify("Dispatching scout agent...", "info");
-			pi.sendUserMessage(`Use the subagent tool to run the scout agent with this task: ${args}`);
-		},
-	});
-
-	// /implement - Full workflow
-	pi.registerCommand("implement", {
-		description: "Full implementation workflow: scout → planner → worker",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /implement <query>", "error");
-				return;
-			}
-
-			ctx.ui.notify("Starting implementation workflow...", "info");
-			pi.sendUserMessage(
-				`Use the subagent tool with chain mode to implement: ${args}\n\n` +
-				`Chain:\n` +
-				`1. scout agent: find all code relevant to: ${args}\n` +
-				`2. planner agent: create implementation plan using {previous}\n` +
-				`3. worker agent: implement the plan from {previous}`
-			);
-		},
-	});
-
-	// /scout-and-plan - Planning workflow
-	pi.registerCommand("scout-and-plan", {
-		description: "Planning workflow: scout → planner (no implementation)",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /scout-and-plan <query>", "error");
-				return;
-			}
-
-			ctx.ui.notify("Starting planning workflow...", "info");
-			pi.sendUserMessage(
-				`Use the subagent tool with chain mode for planning: ${args}\n\n` +
-				`Chain:\n` +
-				`1. scout agent: find all code relevant to: ${args}\n` +
-				`2. planner agent: create implementation plan using {previous}`
-			);
-		},
-	});
-
-	// /review-code - Review workflow
-	pi.registerCommand("review-code", {
-		description: "Code review workflow: scout → reviewer",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /review-code <query>", "error");
-				return;
-			}
-
-			ctx.ui.notify("Starting code review...", "info");
-			pi.sendUserMessage(
-				`Use the subagent tool with chain mode to review: ${args}\n\n` +
-				`Chain:\n` +
-				`1. scout agent: find all code relevant to: ${args}\n` +
-				`2. reviewer agent: review the code from {previous}`
-			);
-		},
-	});
-
-	// /oracle - Deep analysis and debugging
-	pi.registerCommand("oracle", {
-		description: "Deep analysis, debugging, and architecture investigation with oracle agent",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /oracle <question or problem>", "error");
-				return;
-			}
-
-			ctx.ui.notify("Dispatching oracle agent...", "info");
-			pi.sendUserMessage(`Use the subagent tool to run the oracle agent with this task: ${args}`);
-		},
-	});
-
-	// /research - Technical research with web search
-	pi.registerCommand("research", {
-		description: "Technical research with web search, GitHub, and Stack Overflow via researcher agent",
-		handler: async (args, ctx) => {
-			if (!args.trim()) {
-				ctx.ui.notify("Usage: /research <topic or question>", "error");
-				return;
-			}
-
-			ctx.ui.notify("Dispatching researcher agent...", "info");
-			pi.sendUserMessage(`Use the subagent tool to run the researcher agent with this task: ${args}`);
-		},
-	});
-
-	// /subagent-help - Show available agents
-	pi.registerCommand("subagent-help", {
-		description: "Show available subagents and usage",
-		handler: async (_args, ctx) => {
-			const help = `
-# Subagent Extension Help
-
-## Available Slash Commands
-
-- \`/scout <query>\` - Quick reconnaissance (uses Haiku, fast & cheap)
-- \`/implement <query>\` - Full workflow: scout → planner → worker
-- \`/scout-and-plan <query>\` - Planning only: scout → planner
-- \`/review-code <query>\` - Code review: scout → reviewer
-- \`/oracle <query>\` - Deep analysis, debugging, architecture investigation
-- \`/research <query>\` - Technical research with web search
-
-## Available Agents
-
-- **scout** (Haiku) - Fast recon, returns compressed context
-- **planner** (Sonnet) - Creates implementation plans
-- **reviewer** (Sonnet) - Code review
-- **worker** (Sonnet) - General purpose, full capabilities
-- **oracle** (Opus) - Deep analysis, debugging, architecture decisions
-- **researcher** (Sonnet) - Technical research with web/GitHub/SO search
-
-## Direct Tool Usage
-
-You can also ask the AI to use the subagent tool directly:
-
-**Single agent:**
-> Use scout to find all authentication code
-
-**Parallel:**
-> Run scouts in parallel: find DB schemas, find API endpoints
-
-**Chain:**
-> Chain: scout finds auth code, then planner suggests improvements
-
-## Examples
-
-\`\`\`
-/scout find all Tekton Task definitions
-/implement add Redis caching to session store
-/scout-and-plan refactor authentication module
-/review-code check security in auth handlers
-\`\`\`
-
-## Agent Details
-
-Location: ~/.pi/agent/agents/*.md
-`;
-			ctx.ui.notify("Subagent help displayed", "info");
-			console.log(help);
-		},
-	});
-}
dots/pi/agent/extensions/usage-bar.ts
@@ -1,1080 +0,0 @@
-/**
- * Usage Bar Extension - Shows AI provider usage stats like CodexBar
- * Run /usage to see usage for Claude, Copilot, Gemini, and Codex
- * 
- * Features:
- * - Usage stats with progress bars
- * - Provider status (outages/incidents)
- * - Reset countdowns
- */
-
-import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
-import { visibleWidth } from "@earendil-works/pi-tui";
-import * as fs from "node:fs";
-import * as path from "node:path";
-import * as os from "node:os";
-import { execSync } from "node:child_process";
-
-// ============================================================================
-// Types
-// ============================================================================
-
-interface RateWindow {
-	label: string;
-	usedPercent: number;
-	resetDescription?: string;
-	resetsAt?: Date;
-}
-
-interface ProviderStatus {
-	indicator: "none" | "minor" | "major" | "critical" | "maintenance" | "unknown";
-	description?: string;
-}
-
-interface UsageSnapshot {
-	provider: string;
-	displayName: string;
-	windows: RateWindow[];
-	plan?: string;
-	error?: string;
-	status?: ProviderStatus;
-}
-
-// ============================================================================
-// Status Polling
-// ============================================================================
-
-const STATUS_URLS: Record<string, string> = {
-	anthropic: "https://status.anthropic.com/api/v2/status.json",
-	codex: "https://status.openai.com/api/v2/status.json",
-	copilot: "https://www.githubstatus.com/api/v2/status.json",
-};
-
-async function fetchProviderStatus(provider: string): Promise<ProviderStatus> {
-	const url = STATUS_URLS[provider];
-	if (!url) return { indicator: "none" };
-	
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-		
-		const res = await fetch(url, { signal: controller.signal });
-		if (!res.ok) return { indicator: "unknown" };
-		
-		const data = await res.json() as any;
-		const indicator = data.status?.indicator || "none";
-		const description = data.status?.description;
-		
-		return {
-			indicator: indicator as ProviderStatus["indicator"],
-			description,
-		};
-	} catch {
-		return { indicator: "unknown" };
-	}
-}
-
-async function fetchGeminiStatus(): Promise<ProviderStatus> {
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-		
-		const res = await fetch("https://www.google.com/appsstatus/dashboard/incidents.json", {
-			signal: controller.signal,
-		});
-		if (!res.ok) return { indicator: "unknown" };
-		
-		const incidents = await res.json() as any[];
-		
-		// Look for active Gemini incidents (product ID: npdyhgECDJ6tB66MxXyo)
-		const geminiProductId = "npdyhgECDJ6tB66MxXyo";
-		const activeIncidents = incidents.filter((inc: any) => {
-			if (inc.end) return false; // Not active
-			const affected = inc.currently_affected_products || inc.affected_products || [];
-			return affected.some((p: any) => p.id === geminiProductId);
-		});
-		
-		if (activeIncidents.length === 0) {
-			return { indicator: "none" };
-		}
-		
-		// Find most severe
-		let worstIndicator: ProviderStatus["indicator"] = "minor";
-		let description: string | undefined;
-		
-		for (const inc of activeIncidents) {
-			const status = inc.most_recent_update?.status || inc.status_impact;
-			if (status === "SERVICE_OUTAGE") {
-				worstIndicator = "critical";
-				description = inc.external_desc;
-			} else if (status === "SERVICE_DISRUPTION" && worstIndicator !== "critical") {
-				worstIndicator = "major";
-				description = inc.external_desc;
-			}
-		}
-		
-		return { indicator: worstIndicator, description };
-	} catch {
-		return { indicator: "unknown" };
-	}
-}
-
-// ============================================================================
-// Claude Usage
-// ============================================================================
-
-function loadClaudeToken(): string | undefined {
-	// Try pi's auth.json first (has user:profile scope)
-	const piAuthPath = path.join(os.homedir(), ".pi", "agent", "auth.json");
-	try {
-		if (fs.existsSync(piAuthPath)) {
-			const data = JSON.parse(fs.readFileSync(piAuthPath, "utf-8"));
-			if (data.anthropic?.access) return data.anthropic.access;
-		}
-	} catch {}
-
-	// Fallback to Claude CLI keychain (macOS)
-	try {
-		const keychainData = execSync(
-			'security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null',
-			{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
-		).trim();
-		if (keychainData) {
-			const parsed = JSON.parse(keychainData);
-			const scopes = parsed.claudeAiOauth?.scopes || [];
-			if (scopes.includes("user:profile") && parsed.claudeAiOauth?.accessToken) {
-				return parsed.claudeAiOauth.accessToken;
-			}
-		}
-	} catch {}
-
-	return undefined;
-}
-
-async function fetchClaudeUsage(): Promise<UsageSnapshot> {
-	const token = loadClaudeToken();
-	if (!token) {
-		return { provider: "anthropic", displayName: "Claude", windows: [], error: "No credentials" };
-	}
-
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		const res = await fetch("https://api.anthropic.com/api/oauth/usage", {
-			headers: {
-				Authorization: `Bearer ${token}`,
-				"anthropic-beta": "oauth-2025-04-20",
-			},
-			signal: controller.signal,
-		});
-
-		if (!res.ok) {
-			return { provider: "anthropic", displayName: "Claude", windows: [], error: `HTTP ${res.status}` };
-		}
-
-		const data = await res.json() as any;
-		const windows: RateWindow[] = [];
-
-		if (data.five_hour?.utilization !== undefined) {
-			windows.push({
-				label: "5h",
-				usedPercent: data.five_hour.utilization,
-				resetDescription: data.five_hour.resets_at ? formatReset(new Date(data.five_hour.resets_at)) : undefined,
-			});
-		}
-
-		if (data.seven_day?.utilization !== undefined) {
-			windows.push({
-				label: "Week",
-				usedPercent: data.seven_day.utilization,
-				resetDescription: data.seven_day.resets_at ? formatReset(new Date(data.seven_day.resets_at)) : undefined,
-			});
-		}
-
-		const modelWindow = data.seven_day_sonnet || data.seven_day_opus;
-		if (modelWindow?.utilization !== undefined) {
-			windows.push({
-				label: data.seven_day_sonnet ? "Sonnet" : "Opus",
-				usedPercent: modelWindow.utilization,
-			});
-		}
-
-		return { provider: "anthropic", displayName: "Claude", windows };
-	} catch (e) {
-		return { provider: "anthropic", displayName: "Claude", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Copilot Usage
-// ============================================================================
-
-function loadCopilotRefreshToken(): string | undefined {
-	// The copilot_internal/user endpoint needs the GitHub OAuth token (ghu_*),
-	// NOT the Copilot session token (tid=*). The refresh token IS the GitHub OAuth token.
-	const authPath = path.join(os.homedir(), ".pi", "agent", "auth.json");
-	try {
-		if (fs.existsSync(authPath)) {
-			const data = JSON.parse(fs.readFileSync(authPath, "utf-8"));
-			// Use refresh token (GitHub OAuth token ghu_*) for the usage API
-			if (data["github-copilot"]?.refresh) return data["github-copilot"].refresh;
-		}
-	} catch {}
-
-	return undefined;
-}
-
-async function fetchCopilotUsage(_modelRegistry: any): Promise<UsageSnapshot> {
-	const token = loadCopilotRefreshToken();
-	if (!token) {
-		return { provider: "copilot", displayName: "Copilot", windows: [], error: "No token" };
-	}
-
-	const headersBase = {
-		"Editor-Version": "vscode/1.96.2",
-		"User-Agent": "GitHubCopilotChat/0.26.7",
-		"X-Github-Api-Version": "2025-04-01",
-		Accept: "application/json",
-	};
-
-	const tryFetch = async (authHeader: string) => {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		const res = await fetch("https://api.github.com/copilot_internal/user", {
-			headers: {
-				...headersBase,
-				Authorization: authHeader,
-			},
-			signal: controller.signal,
-		});
-		return res;
-	};
-
-	try {
-		// Copilot access tokens (from /login github-copilot) expect Bearer. PATs accept "token".
-		// GitHub OAuth token (ghu_*) requires "token" prefix, not Bearer
-		const attempts = [`token ${token}`];
-		let lastStatus: number | undefined;
-		let res: Response | undefined;
-
-		for (const auth of attempts) {
-			res = await tryFetch(auth);
-			lastStatus = res.status;
-			if (res.ok) break;
-			if (res.status === 401 || res.status === 403) continue; // try next scheme
-			break;
-		}
-
-		if (!res || !res.ok) {
-			const status = lastStatus ?? 0;
-			return { provider: "copilot", displayName: "Copilot", windows: [], error: `HTTP ${status}` };
-		}
-
-		const data = await res.json() as any;
-		const windows: RateWindow[] = [];
-
-		// Parse reset date for display
-		const resetDate = data.quota_reset_date_utc ? new Date(data.quota_reset_date_utc) : undefined;
-		const resetDesc = resetDate ? formatReset(resetDate) : undefined;
-
-		// Premium interactions (e.g., Claude, o1 models) - has a cap
-		if (data.quota_snapshots?.premium_interactions) {
-			const pi = data.quota_snapshots.premium_interactions;
-			const remaining = pi.remaining ?? 0;
-			const entitlement = pi.entitlement ?? 0;
-			const usedPercent = Math.max(0, 100 - (pi.percent_remaining || 0));
-			windows.push({
-				label: `Premium`,
-				usedPercent,
-				resetDescription: resetDesc ? `${resetDesc} (${remaining}/${entitlement})` : `${remaining}/${entitlement}`,
-			});
-		}
-
-		// Chat quota - often unlimited, only show if limited
-		if (data.quota_snapshots?.chat && !data.quota_snapshots.chat.unlimited) {
-			const chat = data.quota_snapshots.chat;
-			windows.push({
-				label: "Chat",
-				usedPercent: Math.max(0, 100 - (chat.percent_remaining || 0)),
-				resetDescription: resetDesc,
-			});
-		}
-
-		return {
-			provider: "copilot",
-			displayName: "Copilot",
-			windows,
-			plan: data.copilot_plan,
-		};
-	} catch (e) {
-		return { provider: "copilot", displayName: "Copilot", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Gemini Usage
-// ============================================================================
-
-async function fetchGeminiUsage(_modelRegistry: any): Promise<UsageSnapshot> {
-	let token: string | undefined;
-	
-	// Read directly from pi's auth.json
-	const piAuthPath = path.join(os.homedir(), ".pi", "agent", "auth.json");
-	try {
-		if (fs.existsSync(piAuthPath)) {
-			const data = JSON.parse(fs.readFileSync(piAuthPath, "utf-8"));
-			token = data["google-gemini-cli"]?.access;
-		}
-	} catch {}
-	
-	// Fallback to ~/.gemini/oauth_creds.json
-	if (!token) {
-		const credPath = path.join(os.homedir(), ".gemini", "oauth_creds.json");
-		try {
-			if (fs.existsSync(credPath)) {
-				const data = JSON.parse(fs.readFileSync(credPath, "utf-8"));
-				token = data.access_token;
-			}
-		} catch {}
-	}
-	
-	if (!token) {
-		return { provider: "gemini", displayName: "Gemini", windows: [], error: "No credentials" };
-	}
-
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		const res = await fetch("https://cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota", {
-			method: "POST",
-			headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
-			body: "{}",
-			signal: controller.signal,
-		});
-
-		if (!res.ok) {
-			return { provider: "gemini", displayName: "Gemini", windows: [], error: `HTTP ${res.status}` };
-		}
-
-		const data = await res.json() as any;
-		const quotas: Record<string, number> = {};
-
-		for (const bucket of data.buckets || []) {
-			const model = bucket.modelId || "unknown";
-			const frac = bucket.remainingFraction ?? 1;
-			if (!quotas[model] || frac < quotas[model]) quotas[model] = frac;
-		}
-
-		const windows: RateWindow[] = [];
-		let proMin = 1, flashMin = 1;
-		let hasProModel = false, hasFlashModel = false;
-
-		for (const [model, frac] of Object.entries(quotas)) {
-			if (model.toLowerCase().includes("pro")) {
-				hasProModel = true;
-				if (frac < proMin) proMin = frac;
-			}
-			if (model.toLowerCase().includes("flash")) {
-				hasFlashModel = true;
-				if (frac < flashMin) flashMin = frac;
-			}
-		}
-
-		// Always show windows if model exists (even at 0% usage)
-		if (hasProModel) windows.push({ label: "Pro", usedPercent: (1 - proMin) * 100 });
-		if (hasFlashModel) windows.push({ label: "Flash", usedPercent: (1 - flashMin) * 100 });
-
-		return { provider: "gemini", displayName: "Gemini", windows };
-	} catch (e) {
-		return { provider: "gemini", displayName: "Gemini", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Antigravity Usage
-// ============================================================================
-
-type AntigravityAuth = {
-	accessToken: string;
-	refreshToken?: string;
-	expiresAt?: number;
-	projectId?: string;
-};
-
-function loadAntigravityAuthFromPiAuthJson(): AntigravityAuth | undefined {
-	const piAuthPath = path.join(os.homedir(), ".pi", "agent", "auth.json");
-	try {
-		if (!fs.existsSync(piAuthPath)) return undefined;
-		const data = JSON.parse(fs.readFileSync(piAuthPath, "utf-8"));
-
-		// Provider is called "google-antigravity" in pi.
-		const cred = data["google-antigravity"] ?? data["antigravity"] ?? data["anti-gravity"];
-		if (!cred) return undefined;
-
-		const accessToken = typeof cred.access === "string" ? cred.access : undefined;
-		if (!accessToken) return undefined;
-
-		return {
-			accessToken,
-			refreshToken: typeof cred.refresh === "string" ? cred.refresh : undefined,
-			expiresAt: typeof cred.expires === "number" ? cred.expires : undefined,
-			projectId: typeof cred.projectId === "string" ? cred.projectId : typeof cred.project_id === "string" ? cred.project_id : undefined,
-		};
-	} catch {
-		return undefined;
-	}
-}
-
-async function loadAntigravityAuth(modelRegistry: any): Promise<AntigravityAuth | undefined> {
-	// Prefer model registry auth storage first (may auto-refresh).
-	try {
-		const accessToken = await Promise.resolve(modelRegistry?.authStorage?.getApiKey?.("google-antigravity"));
-		const raw = await Promise.resolve(modelRegistry?.authStorage?.get?.("google-antigravity"));
-
-		const projectId = typeof raw?.projectId === "string" ? raw.projectId : undefined;
-		const refreshToken = typeof raw?.refresh === "string" ? raw.refresh : undefined;
-		const expiresAt = typeof raw?.expires === "number" ? raw.expires : undefined;
-
-		if (typeof accessToken === "string" && accessToken.length > 0) {
-			return { accessToken, projectId, refreshToken, expiresAt };
-		}
-	} catch {}
-
-	// Fallback to pi auth.json
-	const fromPi = loadAntigravityAuthFromPiAuthJson();
-	if (fromPi) return fromPi;
-
-	// Last resort: env var (won't have projectId; request will likely fail)
-	if (process.env.ANTIGRAVITY_API_KEY) {
-		return { accessToken: process.env.ANTIGRAVITY_API_KEY };
-	}
-
-	return undefined;
-}
-
-async function refreshAntigravityAccessToken(refreshToken: string): Promise<{ accessToken: string; expiresAt?: number } | null> {
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		// From the reference snippet in CodexBar issue #129.
-		const clientId = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com";
-		const clientSecret = "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";
-
-		const res = await fetch("https://oauth2.googleapis.com/token", {
-			method: "POST",
-			headers: { "Content-Type": "application/x-www-form-urlencoded" },
-			body: new URLSearchParams({
-				client_id: clientId,
-				client_secret: clientSecret,
-				refresh_token: refreshToken,
-				grant_type: "refresh_token",
-			}).toString(),
-			signal: controller.signal,
-		});
-
-		if (!res.ok) return null;
-		const data = (await res.json()) as any;
-		const accessToken = typeof data.access_token === "string" ? data.access_token : undefined;
-		if (!accessToken) return null;
-		const expiresIn = typeof data.expires_in === "number" ? data.expires_in : undefined;
-		return {
-			accessToken,
-			expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : undefined,
-		};
-	} catch {
-		return null;
-	}
-}
-
-async function fetchAntigravityUsage(modelRegistry: any): Promise<UsageSnapshot> {
-	const auth = await loadAntigravityAuth(modelRegistry);
-	if (!auth?.accessToken) {
-		return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "No credentials" };
-	}
-
-	if (!auth.projectId) {
-		return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Missing projectId" };
-	}
-
-	let accessToken = auth.accessToken;
-
-	// Refresh if likely expired.
-	if (auth.refreshToken && auth.expiresAt && auth.expiresAt < Date.now() + 5 * 60 * 1000) {
-		const refreshed = await refreshAntigravityAccessToken(auth.refreshToken);
-		if (refreshed?.accessToken) accessToken = refreshed.accessToken;
-	}
-
-	const fetchModels = async (token: string): Promise<Response> => {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		return fetch("https://cloudcode-pa.googleapis.com/v1internal:fetchAvailableModels", {
-			method: "POST",
-			headers: {
-				Authorization: `Bearer ${token}`,
-				"Content-Type": "application/json",
-				"User-Agent": "antigravity/1.12.4",
-				"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
-				Accept: "application/json",
-			},
-			body: JSON.stringify({ project: auth.projectId }),
-			signal: controller.signal,
-		});
-	};
-
-	try {
-		let res = await fetchModels(accessToken);
-
-		if ((res.status === 401 || res.status === 403) && auth.refreshToken) {
-			const refreshed = await refreshAntigravityAccessToken(auth.refreshToken);
-			if (refreshed?.accessToken) {
-				accessToken = refreshed.accessToken;
-				res = await fetchModels(accessToken);
-			}
-		}
-
-		if (res.status === 401 || res.status === 403) {
-			return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Unauthorized" };
-		}
-
-		if (!res.ok) {
-			return { provider: "antigravity", displayName: "Antigravity", windows: [], error: `HTTP ${res.status}` };
-		}
-
-		const data = (await res.json()) as any;
-		const models: Record<string, any> = data.models || {};
-
-		const getQuotaInfo = (modelKeys: string[]): { usedPercent: number; resetDescription?: string } | null => {
-			for (const key of modelKeys) {
-				const qi = models?.[key]?.quotaInfo;
-				if (!qi) continue;
-				// In practice (CodexBar issue #129), some models only provide resetTime.
-				// Treat missing remainingFraction as 0% remaining (100% used), which matches Antigravity's behavior when quota is exhausted.
-				const remainingFraction = typeof qi.remainingFraction === "number" ? qi.remainingFraction : 0;
-				const usedPercent = Math.min(100, Math.max(0, (1 - remainingFraction) * 100));
-				const resetTime = qi.resetTime ? new Date(qi.resetTime) : undefined;
-				return { usedPercent, resetDescription: resetTime ? formatReset(resetTime) : undefined };
-			}
-			return null;
-		};
-
-		// Quota groups from the reference snippet in CodexBar issue #129.
-		const windows: RateWindow[] = [];
-
-		const claudeOrGptOss = getQuotaInfo([
-			"claude-sonnet-4-6",
-			"claude-sonnet-4-6-thinking",
-			"claude-sonnet-4-5",
-			"claude-sonnet-4-5-thinking",
-			"claude-opus-4-5-thinking",
-			"gpt-oss-120b-medium",
-		]);
-		if (claudeOrGptOss) {
-			windows.push({ label: "Claude", usedPercent: claudeOrGptOss.usedPercent, resetDescription: claudeOrGptOss.resetDescription });
-		}
-
-		const gemini3Pro = getQuotaInfo(["gemini-3-pro-high", "gemini-3-pro-low", "gemini-3-pro-preview"]);
-		if (gemini3Pro) {
-			windows.push({ label: "G3 Pro", usedPercent: gemini3Pro.usedPercent, resetDescription: gemini3Pro.resetDescription });
-		}
-
-		const gemini3Flash = getQuotaInfo(["gemini-3-flash"]);
-		if (gemini3Flash) {
-			windows.push({ label: "G3 Flash", usedPercent: gemini3Flash.usedPercent, resetDescription: gemini3Flash.resetDescription });
-		}
-
-		if (windows.length === 0) {
-			return { provider: "antigravity", displayName: "Antigravity", windows: [], error: "No quota data" };
-		}
-
-		return { provider: "antigravity", displayName: "Antigravity", windows };
-	} catch (e) {
-		return { provider: "antigravity", displayName: "Antigravity", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Codex (OpenAI) Usage
-// ============================================================================
-
-async function fetchCodexUsage(modelRegistry: any): Promise<UsageSnapshot> {
-	// Try to get token from pi's auth storage first
-	let accessToken: string | undefined;
-	let accountId: string | undefined;
-	
-	try {
-		// Try openai-codex provider first (pi's built-in)
-		accessToken = await modelRegistry?.authStorage?.getApiKey?.("openai-codex");
-		
-		// Get account ID if available from OAuth credentials
-		const cred = modelRegistry?.authStorage?.get?.("openai-codex");
-		if (cred?.type === "oauth") {
-			accountId = (cred as any).accountId;
-		}
-	} catch {}
-	
-	// Fallback to ~/.codex/auth.json if not in pi's auth
-	if (!accessToken) {
-		const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
-		const authPath = path.join(codexHome, "auth.json");
-		
-		try {
-			if (fs.existsSync(authPath)) {
-				const data = JSON.parse(fs.readFileSync(authPath, "utf-8"));
-				
-				if (data.OPENAI_API_KEY) {
-					accessToken = data.OPENAI_API_KEY;
-				} else if (data.tokens?.access_token) {
-					accessToken = data.tokens.access_token;
-					accountId = data.tokens.account_id;
-				}
-			}
-		} catch {}
-	}
-	
-	if (!accessToken) {
-		return { provider: "codex", displayName: "Codex", windows: [], error: "No credentials" };
-	}
-
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		const headers: Record<string, string> = {
-			Authorization: `Bearer ${accessToken}`,
-			"User-Agent": "CodexBar",
-			Accept: "application/json",
-		};
-		
-		if (accountId) {
-			headers["ChatGPT-Account-Id"] = accountId;
-		}
-
-		const res = await fetch("https://chatgpt.com/backend-api/wham/usage", {
-			method: "GET",
-			headers,
-			signal: controller.signal,
-		});
-
-		if (res.status === 401 || res.status === 403) {
-			return { provider: "codex", displayName: "Codex", windows: [], error: "Token expired" };
-		}
-
-		if (!res.ok) {
-			return { provider: "codex", displayName: "Codex", windows: [], error: `HTTP ${res.status}` };
-		}
-
-		const data = await res.json() as any;
-		const windows: RateWindow[] = [];
-
-		// Primary window (usually 3-hour)
-		if (data.rate_limit?.primary_window) {
-			const pw = data.rate_limit.primary_window;
-			const resetDate = pw.reset_at ? new Date(pw.reset_at * 1000) : undefined;
-			const windowHours = Math.round((pw.limit_window_seconds || 10800) / 3600);
-			windows.push({
-				label: `${windowHours}h`,
-				usedPercent: pw.used_percent || 0,
-				resetDescription: resetDate ? formatReset(resetDate) : undefined,
-			});
-		}
-
-		// Secondary window (usually daily)
-		if (data.rate_limit?.secondary_window) {
-			const sw = data.rate_limit.secondary_window;
-			const resetDate = sw.reset_at ? new Date(sw.reset_at * 1000) : undefined;
-			const windowHours = Math.round((sw.limit_window_seconds || 86400) / 3600);
-			const label = windowHours >= 24 ? "Day" : `${windowHours}h`;
-			windows.push({
-				label,
-				usedPercent: sw.used_percent || 0,
-				resetDescription: resetDate ? formatReset(resetDate) : undefined,
-			});
-		}
-
-		// Credits info
-		let plan = data.plan_type;
-		if (data.credits?.balance !== undefined && data.credits.balance !== null) {
-			const balance = typeof data.credits.balance === 'number' 
-				? data.credits.balance 
-				: parseFloat(data.credits.balance) || 0;
-			plan = plan ? `${plan} ($${balance.toFixed(2)})` : `$${balance.toFixed(2)}`;
-		}
-
-		return { provider: "codex", displayName: "Codex", windows, plan };
-	} catch (e) {
-		return { provider: "codex", displayName: "Codex", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Kiro (AWS)
-// ============================================================================
-
-function stripAnsi(text: string): string {
-	return text.replace(/\x1B\[[0-9;?]*[A-Za-z]|\x1B\].*?\x07/g, "");
-}
-
-function whichSync(cmd: string): string | null {
-	try {
-		return execSync(`which ${cmd}`, { encoding: "utf-8" }).trim();
-	} catch {
-		return null;
-	}
-}
-
-async function fetchKiroUsage(): Promise<UsageSnapshot> {
-	const kiroBinary = whichSync("kiro-cli");
-	if (!kiroBinary) {
-		return { provider: "kiro", displayName: "Kiro", windows: [], error: "kiro-cli not found" };
-	}
-
-	try {
-		// Check if logged in
-		try {
-			execSync("kiro-cli whoami", { encoding: "utf-8", timeout: 5000 });
-		} catch {
-			return { provider: "kiro", displayName: "Kiro", windows: [], error: "Not logged in" };
-		}
-
-		// Get usage
-		const output = execSync("kiro-cli chat --no-interactive /usage", { 
-			encoding: "utf-8", 
-			timeout: 10000,
-			env: { ...process.env, TERM: "xterm-256color" }
-		});
-
-		const stripped = stripAnsi(output);
-		const windows: RateWindow[] = [];
-
-		// Parse plan name from "| KIRO FREE" or similar
-		let planName = "Kiro";
-		const planMatch = stripped.match(/\|\s*(KIRO\s+\w+)/i);
-		if (planMatch) {
-			planName = planMatch[1].trim();
-		}
-
-		// Parse credits percentage from "████...█ X%"
-		let creditsPercent = 0;
-		const percentMatch = stripped.match(/█+\s*(\d+)%/);
-		if (percentMatch) {
-			creditsPercent = parseInt(percentMatch[1], 10);
-		}
-
-		// Parse credits used/total from "(X.XX of Y covered in plan)"
-		let creditsUsed = 0;
-		let creditsTotal = 50;
-		const creditsMatch = stripped.match(/\((\d+\.?\d*)\s+of\s+(\d+)\s+covered/);
-		if (creditsMatch) {
-			creditsUsed = parseFloat(creditsMatch[1]);
-			creditsTotal = parseFloat(creditsMatch[2]);
-			if (!percentMatch && creditsTotal > 0) {
-				creditsPercent = (creditsUsed / creditsTotal) * 100;
-			}
-		}
-
-		// Parse reset date from "resets on 01/01"
-		let resetsAt: Date | undefined;
-		const resetMatch = stripped.match(/resets on (\d{2}\/\d{2})/);
-		if (resetMatch) {
-			const [month, day] = resetMatch[1].split("/").map(Number);
-			const now = new Date();
-			const year = now.getFullYear();
-			resetsAt = new Date(year, month - 1, day);
-			if (resetsAt < now) resetsAt.setFullYear(year + 1);
-		}
-
-		windows.push({
-			label: "Credits",
-			usedPercent: creditsPercent,
-			resetDescription: resetsAt ? formatReset(resetsAt) : undefined,
-		});
-
-		// Parse bonus credits
-		const bonusMatch = stripped.match(/Bonus credits:\s*(\d+\.?\d*)\/(\d+)/);
-		if (bonusMatch) {
-			const bonusUsed = parseFloat(bonusMatch[1]);
-			const bonusTotal = parseFloat(bonusMatch[2]);
-			const bonusPercent = bonusTotal > 0 ? (bonusUsed / bonusTotal) * 100 : 0;
-			const expiryMatch = stripped.match(/expires in (\d+) days?/);
-			windows.push({
-				label: "Bonus",
-				usedPercent: bonusPercent,
-				resetDescription: expiryMatch ? `${expiryMatch[1]}d left` : undefined,
-			});
-		}
-
-		return { provider: "kiro", displayName: "Kiro", windows, plan: planName };
-	} catch (e) {
-		return { provider: "kiro", displayName: "Kiro", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// z.ai
-// ============================================================================
-
-async function fetchZaiUsage(): Promise<UsageSnapshot> {
-	// Check for API key in environment or pi auth
-	let apiKey = process.env.Z_AI_API_KEY;
-	
-	if (!apiKey) {
-		// Try pi auth storage
-		try {
-			const authPath = path.join(os.homedir(), ".pi", "agent", "auth.json");
-			if (fs.existsSync(authPath)) {
-				const auth = JSON.parse(fs.readFileSync(authPath, "utf-8"));
-				apiKey = auth["z-ai"]?.access || auth["zai"]?.access;
-			}
-		} catch {}
-	}
-
-	if (!apiKey) {
-		return { provider: "zai", displayName: "z.ai", windows: [], error: "No API key" };
-	}
-
-	try {
-		const controller = new AbortController();
-		setTimeout(() => controller.abort(), 5000);
-
-		const res = await fetch("https://api.z.ai/api/monitor/usage/quota/limit", {
-			method: "GET",
-			headers: {
-				Authorization: `Bearer ${apiKey}`,
-				Accept: "application/json",
-			},
-			signal: controller.signal,
-		});
-
-		if (!res.ok) {
-			return { provider: "zai", displayName: "z.ai", windows: [], error: `HTTP ${res.status}` };
-		}
-
-		const data = await res.json() as any;
-		if (!data.success || data.code !== 200) {
-			return { provider: "zai", displayName: "z.ai", windows: [], error: data.msg || "API error" };
-		}
-
-		const windows: RateWindow[] = [];
-		const limits = data.data?.limits || [];
-
-		for (const limit of limits) {
-			const type = limit.type;
-			const usage = limit.usage || 0;
-			const remaining = limit.remaining || 0;
-			const percent = limit.percentage || 0;
-			const nextReset = limit.nextResetTime ? new Date(limit.nextResetTime) : undefined;
-
-			// Unit: 1=days, 3=hours, 5=minutes
-			let windowLabel = "Limit";
-			if (limit.unit === 1) windowLabel = `${limit.number}d`;
-			else if (limit.unit === 3) windowLabel = `${limit.number}h`;
-			else if (limit.unit === 5) windowLabel = `${limit.number}m`;
-
-			if (type === "TOKENS_LIMIT") {
-				windows.push({
-					label: `Tokens (${windowLabel})`,
-					usedPercent: percent,
-					resetDescription: nextReset ? formatReset(nextReset) : undefined,
-				});
-			} else if (type === "TIME_LIMIT") {
-				windows.push({
-					label: "Monthly",
-					usedPercent: percent,
-					resetDescription: nextReset ? formatReset(nextReset) : undefined,
-				});
-			}
-		}
-
-		const planName = data.data?.planName || data.data?.plan || undefined;
-		return { provider: "zai", displayName: "z.ai", windows, plan: planName };
-	} catch (e) {
-		return { provider: "zai", displayName: "z.ai", windows: [], error: String(e) };
-	}
-}
-
-// ============================================================================
-// Helpers
-// ============================================================================
-
-function formatReset(date: Date): string {
-	const diffMs = date.getTime() - Date.now();
-	if (diffMs < 0) return "now";
-	
-	const diffMins = Math.floor(diffMs / 60000);
-	if (diffMins < 60) return `${diffMins}m`;
-	
-	const hours = Math.floor(diffMins / 60);
-	const mins = diffMins % 60;
-	if (hours < 24) return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
-	
-	const days = Math.floor(hours / 24);
-	if (days < 7) return `${days}d ${hours % 24}h`;
-	
-	return new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric" }).format(date);
-}
-
-function getStatusEmoji(status?: ProviderStatus): string {
-	if (!status) return "";
-	switch (status.indicator) {
-		case "none": return "✅";
-		case "minor": return "⚠️";
-		case "major": return "🟠";
-		case "critical": return "🔴";
-		case "maintenance": return "🔧";
-		default: return "";
-	}
-}
-
-// ============================================================================
-// UI Component
-// ============================================================================
-
-class UsageComponent {
-	private usages: UsageSnapshot[] = [];
-	private loading = true;
-	private tui: { requestRender: () => void };
-	private theme: any;
-	private onClose: () => void;
-	private modelRegistry: any;
-
-	constructor(tui: { requestRender: () => void }, theme: any, onClose: () => void, modelRegistry: any) {
-		this.tui = tui;
-		this.theme = theme;
-		this.onClose = onClose;
-		this.modelRegistry = modelRegistry;
-		this.load();
-	}
-
-	private async load() {
-		const timeout = <T>(p: Promise<T>, ms: number, fallback: T) =>
-			Promise.race([p, new Promise<T>((r) => setTimeout(() => r(fallback), ms))]);
-
-		// Fetch usage and status in parallel
-		const [claude, copilot, gemini, codex, antigravity, kiro, zai, claudeStatus, copilotStatus, geminiStatus, codexStatus] = await Promise.all([
-			timeout(fetchClaudeUsage(), 6000, { provider: "anthropic", displayName: "Claude", windows: [], error: "Timeout" }),
-			timeout(fetchCopilotUsage(this.modelRegistry), 6000, { provider: "copilot", displayName: "Copilot", windows: [], error: "Timeout" }),
-			timeout(fetchGeminiUsage(this.modelRegistry), 6000, { provider: "gemini", displayName: "Gemini", windows: [], error: "Timeout" }),
-			timeout(fetchCodexUsage(this.modelRegistry), 6000, { provider: "codex", displayName: "Codex", windows: [], error: "Timeout" }),
-			timeout(fetchAntigravityUsage(this.modelRegistry), 6000, { provider: "antigravity", displayName: "Antigravity", windows: [], error: "Timeout" }),
-			timeout(fetchKiroUsage(), 6000, { provider: "kiro", displayName: "Kiro", windows: [], error: "Timeout" }),
-			timeout(fetchZaiUsage(), 6000, { provider: "zai", displayName: "z.ai", windows: [], error: "Timeout" }),
-			timeout(fetchProviderStatus("anthropic"), 3000, { indicator: "unknown" as const }),
-			timeout(fetchProviderStatus("copilot"), 3000, { indicator: "unknown" as const }),
-			timeout(fetchGeminiStatus(), 3000, { indicator: "unknown" as const }),
-			timeout(fetchProviderStatus("codex"), 3000, { indicator: "unknown" as const }),
-		]);
-
-		// Attach status to usage
-		claude.status = claudeStatus;
-		copilot.status = copilotStatus;
-		gemini.status = geminiStatus;
-		codex.status = codexStatus;
-
-		// Filter out providers with no data and no error (not configured)
-		const allUsages = [claude, copilot, gemini, codex, antigravity, kiro, zai];
-		this.usages = allUsages.filter(u => u.windows.length > 0 || u.error !== "No credentials" && u.error !== "kiro-cli not found" && u.error !== "No API key");
-		this.loading = false;
-		this.tui.requestRender();
-	}
-
-	handleInput(_data: string): void {
-		this.onClose();
-	}
-
-	invalidate(): void {}
-
-	render(width: number): string[] {
-		const t = this.theme;
-		const dim = (s: string) => t.fg("muted", s);
-		const bold = (s: string) => t.bold(s);
-		const accent = (s: string) => t.fg("accent", s);
-
-		// Box dimensions: total width includes borders
-		const totalW = Math.min(55, width - 4);
-		const innerW = totalW - 4; // subtract "│ " and " │"
-		const hLine = "─".repeat(totalW - 2); // subtract corners
-
-		const box = (content: string) => {
-			const contentW = visibleWidth(content);
-			const pad = Math.max(0, innerW - contentW);
-			return dim("│ ") + content + " ".repeat(pad) + dim(" │");
-		};
-
-		const lines: string[] = [];
-		lines.push(dim(`╭${hLine}╮`));
-		lines.push(box(bold(accent("AI Usage"))));
-		lines.push(dim(`├${hLine}┤`));
-
-		if (this.loading) {
-			lines.push(box("Loading..."));
-		} else {
-			for (const u of this.usages) {
-				// Provider header with status emoji and plan
-				const statusEmoji = getStatusEmoji(u.status);
-				const planStr = u.plan ? dim(` (${u.plan})`) : "";
-				const statusStr = statusEmoji ? ` ${statusEmoji}` : "";
-				lines.push(box(bold(u.displayName) + planStr + statusStr));
-
-				// Show incident description if any
-				if (u.status?.indicator && u.status.indicator !== "none" && u.status.indicator !== "unknown" && u.status.description) {
-					const desc = u.status.description.length > 40 
-						? u.status.description.substring(0, 37) + "..." 
-						: u.status.description;
-					lines.push(box(t.fg("warning", `  ⚡ ${desc}`)));
-				}
-
-				if (u.error) {
-					lines.push(box(dim(`  ${u.error}`)));
-				} else if (u.windows.length === 0) {
-					lines.push(box(dim("  No data")));
-				} else {
-					for (const w of u.windows) {
-						const remaining = Math.max(0, 100 - w.usedPercent);
-						const barW = 12;
-						const filled = Math.min(barW, Math.round((w.usedPercent / 100) * barW));
-						const empty = barW - filled;
-						const color = remaining <= 10 ? "error" : remaining <= 30 ? "warning" : "success";
-						const bar = t.fg(color, "█".repeat(filled)) + dim("░".repeat(empty));
-						const reset = w.resetDescription ? dim(` ⏱ ${w.resetDescription}`) : "";
-						lines.push(box(`  ${w.label.padEnd(7)} ${bar} ${remaining.toFixed(0).padStart(3)}%${reset}`));
-					}
-				}
-				lines.push(box(""));
-			}
-		}
-
-		lines.push(dim(`├${hLine}┤`));
-		lines.push(box(dim("Press any key to close")));
-		lines.push(dim(`╰${hLine}╯`));
-
-		return lines;
-	}
-
-	dispose(): void {}
-}
-
-// ============================================================================
-// Hook
-// ============================================================================
-
-export default function (pi: ExtensionAPI) {
-	pi.registerCommand("usage", {
-		description: "Show AI provider usage statistics",
-		handler: async (_args, ctx) => {
-			if (!ctx.hasUI) {
-				ctx.ui.notify("Usage requires interactive mode", "error");
-				return;
-			}
-
-			const modelRegistry = ctx.modelRegistry;
-			await ctx.ui.custom((tui, theme, _kb, done) => {
-				return new UsageComponent(tui, theme, () => done(), modelRegistry);
-			});
-		},
-	});
-}
dots/pi/agent/README.md
@@ -140,9 +140,7 @@ Custom extensions in `extensions/`:
 - **`org-todos/`** - Org-mode TODO integration
 - **`vertex-claude/`** - Google Vertex AI Claude models
 - **`defaults/`** - Default extensions wrapper
-- **`lsp/`** - LSP integration
 - **`filter-output/`** - Output filtering
-- **`shell-completions/`** - Shell completion enhancements
 - **`threads/`** - Thread/session management
 
 Extensions are symlinked to `~/.pi/agent/extensions/` and npm dependencies are auto-installed.