Commit eb8e005f2beb
Changed files (3)
dots
config
zsh
tools
pi
agent
dots/config/zsh/tools/_pi-session-helper
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+# Helper for pir-fork: list and preview pi sessions.
+# Called by the pir-fork zsh function and by fzf --preview/--bind reload.
+#
+# Usage:
+# _pi-session-helper list [--all] [--days N] [--cwd DIR]
+# _pi-session-helper preview UUID
+set -euo pipefail
+
+SESSION_DIR="${HOME}/.pi/agent/sessions/"
+
+cmd_list() {
+ local show_all="false"
+ local days=""
+ local filter_cwd=""
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --all) show_all="true"; shift ;;
+ --days) days="$2"; shift 2 ;;
+ --cwd) filter_cwd="$2"; shift 2 ;;
+ *) shift ;;
+ esac
+ done
+
+ # Compute cutoff date if --days is set
+ local date_cutoff=""
+ if [[ -n "$days" ]]; then
+ date_cutoff=$(date -d "-${days} days" +%Y-%m-%d 2>/dev/null || date -v-${days}d +%Y-%m-%d 2>/dev/null || echo "")
+ fi
+
+ find "$SESSION_DIR" -name '*.jsonl' -type f -print0 \
+ | xargs -0 -P8 -n100 gawk \
+ -v home="$HOME" \
+ -v show_all="$show_all" \
+ -v filter_cwd="$filter_cwd" \
+ -v date_cutoff="$date_cutoff" '
+ FNR==1 {
+ id = ""; ts = ""; cwd = ""; msg = ""; found_user = 0
+ if (match($0, /"id":"([^"]+)"/, m)) id = m[1]
+ if (match($0, /"timestamp":"([^"]+)"/, m)) ts = m[1]
+ if (match($0, /"cwd":"([^"]+)"/, m)) cwd = m[1]
+ if (id == "") nextfile
+ if (show_all != "true" && filter_cwd != "" && index(cwd, filter_cwd) != 1) {
+ id = ""; nextfile
+ }
+ next
+ }
+ !found_user && /"role":"user"/ {
+ found_user = 1
+ if (match($0, /"text":"([^"]{1,80})/, m)) {
+ msg = m[1]
+ gsub(/\\n/, " ", msg)
+ }
+ nextfile
+ }
+ ENDFILE {
+ if (id != "" && ts != "") {
+ date_part = ts
+ sub(/T.*/, "", date_part)
+ if (date_part ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
+ if (date_cutoff == "" || date_part >= date_cutoff) {
+ short_cwd = cwd
+ gsub(home, "~", short_cwd)
+ printf "%s\t%s\t%s\t%s\n", date_part, short_cwd, msg, id
+ }
+ }
+ }
+ }
+ ' \
+ | grep -P '^\d{4}-\d{2}-\d{2}\t' \
+ | sort -t$'\t' -rk1,1
+}
+
+cmd_preview() {
+ local uuid="$1"
+ local f
+ f=$(find "$SESSION_DIR" -name "*_${uuid}.jsonl" -type f | head -1)
+
+ [[ -z "$f" ]] && { echo "Session file not found"; return 1; }
+
+ # Session metadata from header
+ local header
+ header=$(head -1 "$f")
+ local ts cwd
+ ts=$(echo "$header" | grep -oP '"timestamp":"\K[^"]+' || true)
+ cwd=$(echo "$header" | grep -oP '"cwd":"\K[^"]+' || true)
+ cwd="${cwd/$HOME/\~}"
+
+ # Count messages
+ local msg_count
+ msg_count=$(grep -c '"type":"message"' "$f" 2>/dev/null || echo 0)
+
+ # Model info
+ local model
+ model=$(grep -oP '"modelId":"\K[^"]+' "$f" 2>/dev/null | tail -1 || true)
+
+ # Print metadata header
+ echo "๐
${ts}"
+ echo "๐ ${cwd}"
+ echo "๐ฌ ${msg_count} messages"
+ [[ -n "$model" ]] && echo "๐ค ${model}"
+ echo "โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ"
+
+ # Show conversation exchanges
+ grep '"type":"message"' "$f" 2>/dev/null \
+ | head -30 \
+ | jq -r '
+ (.message.role // "?") as $role |
+ (
+ if .message.content | type == "array" then
+ [.message.content[] | select(.type == "text") | .text] | join(" ")
+ elif .message.content | type == "string" then
+ .message.content
+ else
+ ""
+ end
+ ) as $text |
+ if $role == "user" then
+ "โถ " + ($text | gsub("\n"; " ") | .[0:200])
+ elif $role == "assistant" then
+ " " + ($text | gsub("\n"; " ") | .[0:200])
+ else
+ empty
+ end
+ ' 2>/dev/null
+}
+
+case "${1:-}" in
+ list) shift; cmd_list "$@" ;;
+ preview) shift; cmd_preview "$@" ;;
+ *) echo "Usage: _pi-session-helper {list|preview} [args...]" >&2; exit 1 ;;
+esac
dots/config/zsh/tools/pi.zsh
@@ -0,0 +1,100 @@
+# Pi coding agent helpers
+has pi || return
+
+# Path to the session helper script (sourced from same directory as this file)
+_PI_SESSION_HELPER="${0:A:h}/_pi-session-helper"
+
+# pir-fork: fuzzy-pick a pi session and fork it into a new session
+#
+# By default shows sessions from the current project directory, all time.
+# --all / -a show sessions from all projects
+# --week / -w last 7 days only
+# --month / -m last 30 days only
+# --days N last N days
+#
+# Inside fzf:
+# ctrl-a show all projects
+# ctrl-p show current project only
+# alt-w show last 7 days (week)
+# alt-m show last 30 days (month)
+# alt-t show all time (total)
+function pir-fork() {
+ local show_all=false
+ local days=""
+ local filter_cwd="$(pwd)"
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --all|-a) show_all=true; shift ;;
+ --week|-w) days=7; shift ;;
+ --month|-m) days=30; shift ;;
+ --days) days="$2"; shift 2 ;;
+ *) shift ;;
+ esac
+ done
+
+ [[ -d "${HOME}/.pi/agent/sessions/" ]] || { echo "No sessions found" >&2; return 1; }
+
+ # Build initial list command
+ local list_base="${_PI_SESSION_HELPER} list"
+ local list_cmd="$list_base"
+ [[ "$show_all" == "true" ]] && list_cmd+=" --all" || list_cmd+=" --cwd ${(q)filter_cwd}"
+ [[ -n "$days" ]] && list_cmd+=" --days $days"
+
+ local entries
+ entries=$(eval "$list_cmd")
+
+ if [[ -z "$entries" ]]; then
+ if [[ "$show_all" == "false" ]]; then
+ echo "No sessions found for $(pwd). Try: pir-fork --all" >&2
+ else
+ echo "No sessions found" >&2
+ fi
+ return 1
+ fi
+
+ # Precompute reload commands for fzf bindings
+ # Scope toggles (preserve no time filter โ user can combine with ctrl-1/2/3)
+ local rld_all="${list_base} --all"
+ local rld_project="${list_base} --cwd ${(q)filter_cwd}"
+ local rld_all_7d="${rld_all} --days 7"
+ local rld_all_30d="${rld_all} --days 30"
+ local rld_proj_7d="${rld_project} --days 7"
+ local rld_proj_30d="${rld_project} --days 30"
+
+ # Initial state for header
+ local scope_label
+ [[ "$show_all" == "true" ]] && scope_label="all projects" || scope_label="${filter_cwd/$HOME/\~}"
+ local time_label
+ [[ -n "$days" ]] && time_label="last ${days}d" || time_label="all time"
+
+ local selected
+ selected=$(printf "%s\n" "$entries" \
+ | fzf --delimiter='\t' \
+ --with-nth=1..3 \
+ --preview="${_PI_SESSION_HELPER} preview {4}" \
+ --preview-window=right:50%:wrap \
+ --header="scope: ${scope_label} โ time: ${time_label}
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork" \
+ --prompt="fork> " \
+ --bind="ctrl-j:accept" \
+ --bind="ctrl-a:reload(${rld_all})+change-header(scope: all projects โ time: all
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork)" \
+ --bind="ctrl-p:reload(${rld_project})+change-header(scope: ${scope_label} โ time: all
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork)" \
+ --bind="alt-w:reload(${rld_proj_7d})+change-header(scope: ${scope_label} โ time: last 7d
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork)" \
+ --bind="alt-m:reload(${rld_proj_30d})+change-header(scope: ${scope_label} โ time: last 30d
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork)" \
+ --bind="alt-t:reload(${rld_project})+change-header(scope: ${scope_label} โ time: all
+ctrl-a/p: scope โ alt-w/m/t: week/month/all โ ctrl-j: fork)"
+ )
+
+ [[ -z "$selected" ]] && return 0
+
+ local uuid
+ uuid=$(printf "%s" "$selected" | awk -F'\t' '{print $NF}')
+
+ echo "Forking session ${uuid}..."
+ pi --fork "$uuid"
+}
dots/pi/agent/keybindings.json
@@ -1,13 +1,14 @@
{
- "cursorUp": ["up", "ctrl+p"],
- "cursorDown": ["down", "ctrl+n"],
- "cursorLeft": ["left", "ctrl+b"],
- "cursorRight": ["right", "ctrl+f"],
- "cursorWordLeft": ["alt+left", "alt+b"],
- "cursorWordRight": ["alt+right", "alt+f"],
- "deleteCharForward": ["delete", "ctrl+d"],
- "deleteCharBackward": ["backspace", "ctrl+h"],
- "newLine": ["shift+enter", "ctrl+j"],
- "cycleModelForward": ["alt+m"],
- "cycleModelBackward": ["alt+shift+m"]
+ "tui.editor.cursorUp": ["up", "ctrl+p"],
+ "tui.editor.cursorDown": ["down", "ctrl+n"],
+ "tui.editor.cursorLeft": ["left", "ctrl+b"],
+ "tui.editor.cursorRight": ["right", "ctrl+f"],
+ "tui.editor.cursorWordLeft": ["alt+left", "alt+b"],
+ "tui.editor.cursorWordRight": ["alt+right", "alt+f"],
+ "tui.editor.deleteCharForward": ["delete", "ctrl+d"],
+ "tui.editor.deleteCharBackward": ["backspace", "ctrl+h"],
+ "tui.input.newLine": ["shift+enter", "ctrl+j"],
+ "app.model.cycleForward": ["alt+m"],
+ "app.model.cycleBackward": ["alt+shift+m"],
+ "app.session.fork": ["alt+shift+k"]
}