#!/usr/bin/env bash # gstack-timeline-read — read and format project timeline # Usage: gstack-timeline-read [--since "7 days ago"] [--limit N] [--branch NAME] # # Session timeline: local-only, never sent anywhere. # Reads ~/.gstack/projects/$SLUG/timeline.jsonl, filters, formats. # Exit 0 silently if no timeline file exists. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" eval "$("$SCRIPT_DIR/gstack-slug" 2>/dev/null)" GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}" SINCE="" LIMIT=20 BRANCH="" while [[ $# -gt 0 ]]; do case "$1" in --since) SINCE="$2"; shift 2 ;; --limit) LIMIT="$2"; shift 2 ;; --branch) BRANCH="$2"; shift 2 ;; *) shift ;; esac done TIMELINE_FILE="$GSTACK_HOME/projects/$SLUG/timeline.jsonl" if [ ! -f "$TIMELINE_FILE" ]; then exit 0 fi cat "$TIMELINE_FILE" 2>/dev/null | bun -e " const lines = (await Bun.stdin.text()).trim().split('\n').filter(Boolean); const since = '${SINCE}'; const branch = '${BRANCH}'; const limit = ${LIMIT}; let sinceMs = 0; if (since) { // Parse relative time like '7 days ago' const match = since.match(/(\d+)\s*(day|hour|minute|week|month)s?\s*ago/i); if (match) { const n = parseInt(match[1]); const unit = match[2].toLowerCase(); const ms = { minute: 60000, hour: 3600000, day: 86400000, week: 604800000, month: 2592000000 }; sinceMs = Date.now() - n * (ms[unit] || 86400000); } } const entries = []; for (const line of lines) { try { const e = JSON.parse(line); if (sinceMs && new Date(e.ts).getTime() < sinceMs) continue; if (branch && e.branch !== branch) continue; entries.push(e); } catch {} } if (entries.length === 0) process.exit(0); // Take last N entries const recent = entries.slice(-limit); // Skill counts (completed events only) const counts = {}; const branches = new Set(); for (const e of entries) { if (e.event === 'completed') { counts[e.skill] = (counts[e.skill] || 0) + 1; } if (e.branch) branches.add(e.branch); } // Output summary const countStr = Object.entries(counts) .sort((a, b) => b[1] - a[1]) .map(([s, n]) => n + ' /' + s) .join(', '); if (countStr) { console.log('TIMELINE: ' + countStr + ' across ' + branches.size + ' branch' + (branches.size !== 1 ? 'es' : '')); } // Output recent events console.log(''); console.log('## Recent Events'); for (const e of recent) { const ts = (e.ts || '').replace('T', ' ').replace(/\.\d+Z$/, 'Z'); const dur = e.duration_s ? ' (' + e.duration_s + 's)' : ''; const outcome = e.outcome ? ' [' + e.outcome + ']' : ''; console.log('- ' + ts + ' /' + e.skill + ' ' + e.event + outcome + dur + (e.branch ? ' on ' + e.branch : '')); } " 2>/dev/null || exit 0