main
1#!/usr/bin/env bash
2# Helper for pir-fork: list and preview pi sessions.
3# Called by the pir-fork zsh function and by fzf --preview/--bind reload.
4#
5# Usage:
6# _pi-session-helper list [--all] [--days N] [--cwd DIR]
7# _pi-session-helper preview UUID
8set -euo pipefail
9
10SESSION_DIR="${HOME}/.pi/agent/sessions/"
11
12cmd_list() {
13 local show_all="false"
14 local days=""
15 local filter_cwd=""
16
17 while [[ $# -gt 0 ]]; do
18 case "$1" in
19 --all) show_all="true"; shift ;;
20 --days) days="$2"; shift 2 ;;
21 --cwd) filter_cwd="$2"; shift 2 ;;
22 *) shift ;;
23 esac
24 done
25
26 # Compute cutoff date if --days is set
27 local date_cutoff=""
28 if [[ -n "$days" ]]; then
29 date_cutoff=$(date -d "-${days} days" +%Y-%m-%d 2>/dev/null || date -v-${days}d +%Y-%m-%d 2>/dev/null || echo "")
30 fi
31
32 find "$SESSION_DIR" -name '*.jsonl' -type f -print0 \
33 | xargs -0 -P8 -n100 gawk \
34 -v home="$HOME" \
35 -v show_all="$show_all" \
36 -v filter_cwd="$filter_cwd" \
37 -v date_cutoff="$date_cutoff" '
38 FNR==1 {
39 id = ""; ts = ""; cwd = ""; msg = ""; found_user = 0
40 if (match($0, /"id":"([^"]+)"/, m)) id = m[1]
41 if (match($0, /"timestamp":"([^"]+)"/, m)) ts = m[1]
42 if (match($0, /"cwd":"([^"]+)"/, m)) cwd = m[1]
43 if (id == "") nextfile
44 if (show_all != "true" && filter_cwd != "" && index(cwd, filter_cwd) != 1) {
45 id = ""; nextfile
46 }
47 next
48 }
49 !found_user && /"role":"user"/ {
50 found_user = 1
51 if (match($0, /"text":"([^"]{1,80})/, m)) {
52 msg = m[1]
53 gsub(/\\n/, " ", msg)
54 }
55 nextfile
56 }
57 ENDFILE {
58 if (id != "" && ts != "") {
59 date_part = ts
60 sub(/T.*/, "", date_part)
61 if (date_part ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) {
62 if (date_cutoff == "" || date_part >= date_cutoff) {
63 short_cwd = cwd
64 gsub(home, "~", short_cwd)
65 printf "%s\t%s\t%s\t%s\n", date_part, short_cwd, msg, id
66 }
67 }
68 }
69 }
70 ' \
71 | grep -P '^\d{4}-\d{2}-\d{2}\t' \
72 | sort -t$'\t' -rk1,1
73}
74
75cmd_preview() {
76 local uuid="$1"
77 local f
78 f=$(find "$SESSION_DIR" -name "*_${uuid}.jsonl" -type f | head -1)
79
80 [[ -z "$f" ]] && { echo "Session file not found"; return 1; }
81
82 # Session metadata from header
83 local header
84 header=$(head -1 "$f")
85 local ts cwd
86 ts=$(echo "$header" | grep -oP '"timestamp":"\K[^"]+' || true)
87 cwd=$(echo "$header" | grep -oP '"cwd":"\K[^"]+' || true)
88 cwd="${cwd/$HOME/\~}"
89
90 # Count messages
91 local msg_count
92 msg_count=$(grep -c '"type":"message"' "$f" 2>/dev/null || echo 0)
93
94 # Model info
95 local model
96 model=$(grep -oP '"modelId":"\K[^"]+' "$f" 2>/dev/null | tail -1 || true)
97
98 # Print metadata header
99 echo "📅 ${ts}"
100 echo "📁 ${cwd}"
101 echo "💬 ${msg_count} messages"
102 [[ -n "$model" ]] && echo "🤖 ${model}"
103 echo "────────────────────────────────"
104
105 # Show conversation exchanges
106 grep '"type":"message"' "$f" 2>/dev/null \
107 | head -30 \
108 | jq -r '
109 (.message.role // "?") as $role |
110 (
111 if .message.content | type == "array" then
112 [.message.content[] | select(.type == "text") | .text] | join(" ")
113 elif .message.content | type == "string" then
114 .message.content
115 else
116 ""
117 end
118 ) as $text |
119 if $role == "user" then
120 "▶ " + ($text | gsub("\n"; " ") | .[0:200])
121 elif $role == "assistant" then
122 " " + ($text | gsub("\n"; " ") | .[0:200])
123 else
124 empty
125 end
126 ' 2>/dev/null
127}
128
129case "${1:-}" in
130 list) shift; cmd_list "$@" ;;
131 preview) shift; cmd_preview "$@" ;;
132 *) echo "Usage: _pi-session-helper {list|preview} [args...]" >&2; exit 1 ;;
133esac