Setting the file. One moment. Filter Sessions · Extracting Session Data · bitwarden/ai-plugins · Skills Docs19
Running Work Transitions
scripts/filter-sessions.sh
scripts/filter-sessions.sh
Shell·304 lines·9 KB
jq
&
>
/dev/null;
then
13 echo "Error: jq is required but not installed. Install with: brew install jq" >&2
14 exit 1
15fi
16
17# Default options
18PROJECT_DIR="${PWD}"
19SINCE=""
20UNTIL=""
21BRANCH=""
22MIN_SIZE=""
23MAX_SIZE=""
24MIN_LINES=""
25MAX_LINES=""
26HAS_ERRORS=""
27KEYWORD=""
28OUTPUT_FORMAT="list"
29
30# Parse arguments
31while [[ $# -gt 0 ]]; do
32 case $1 in
33 --project)
34 PROJECT_DIR="$2"
35 shift 2
36 ;;
37 --since)
38 SINCE="$2"
39 shift 2
40 ;;
41 --until)
42 UNTIL="$2"
43 shift 2
44 ;;
45 --branch)
46 BRANCH="$2"
47 shift 2
48 ;;
49 --min-size)
50 MIN_SIZE="$2"
51 shift 2
52 ;;
53 --max-size)
54 MAX_SIZE="$2"
55 shift 2
56 ;;
57 --min-lines)
58 MIN_LINES="$2"
59 shift 2
60 ;;
61 --max-lines)
62 MAX_LINES="$2"
63 shift 2
64 ;;
65 --has-errors)
66 HAS_ERRORS="true"
67 shift
68 ;;
69 --keyword)
70 KEYWORD="$2"
71 shift 2
72 ;;
73 --format)
74 OUTPUT_FORMAT="$2"
75 shift 2
76 ;;
77 --help)
78 echo "Usage: $0 [options]"
79 echo ""
80 echo "Options:"
81 echo " --project DIR Project directory (default: current directory)"
82 echo " --since DATE Sessions modified since date (YYYY-MM-DD or 'N days ago')"
83 echo " --until DATE Sessions modified until date (YYYY-MM-DD)"
84 echo " --branch BRANCH Filter by git branch name"
85 echo " --min-size SIZE Minimum file size (e.g., 1M, 500K)"
86 echo " --max-size SIZE Maximum file size"
87 echo " --min-lines N Minimum line count"
88 echo " --max-lines N Maximum line count"
89 echo " --has-errors Only sessions with errors"
90 echo " --keyword WORD Sessions containing keyword"
91 echo " --format FORMAT Output format: list (default), paths, json"
92 echo " --help Show this help message"
93 echo ""
94 echo "Examples:"
95 echo " $0 --since '2 days ago' # Recent sessions"
96 echo " $0 --branch main --has-errors # Errors on main branch"
97 echo " $0 --min-lines 500 --keyword 'test' # Large sessions with 'test'"
98 exit 0
99 ;;
100 *)
101 echo "Error: Unknown argument: $1" >&2
102 exit 1
103 ;;
104 esac
105done
106
107# Get logs directory
108LOGS_DIR=$("${SCRIPT_DIR}/locate-logs.sh" "${PROJECT_DIR}")
109
110if [ ! -d "${LOGS_DIR}" ]; then
111 echo "Error: Logs directory not found: ${LOGS_DIR}" >&2
112 exit 1
113fi
114
115# Find all session files
116ALL_FILES=()
117while IFS= read -r file; do
118 ALL_FILES+=("$file")
119done < <(find "${LOGS_DIR}" -maxdepth 1 -name "*.jsonl" -type f)
120
121if [ ${#ALL_FILES[@]} -eq 0 ]; then
122 echo "No session files found" >&2
123 exit 0
124fi
125
126# Convert date string to timestamp for comparison
127date_to_timestamp() {
128 local date_str="$1"
129 if [[ "${date_str}" =~ ago ]]; then
130 # Handle relative dates like "2 days ago"
131 date -j -v-$(echo "${date_str}" | sed 's/ ago//') +%s 2>/dev/null || date -d "${date_str}" +%s 2>/dev/null
132 else
133 # Handle absolute dates like "2025-10-24"
134 date -j -f "%Y-%m-%d" "${date_str}" +%s 2>/dev/null || date -d "${date_str}" +%s 2>/dev/null
135 fi
136}
137
138# Convert size string to bytes
139size_to_bytes() {
140 local size_str="$1"
141 local num=$(echo "${size_str}" | sed 's/[^0-9.]//g')
142 local unit=$(echo "${size_str}" | sed 's/[0-9.]//g' | tr '[:lower:]' '[:upper:]')
143
144 case ${unit} in
145 K|KB)
146 echo $(echo "${num} * 1024" | bc | cut -d'.' -f1)
147 ;;
148 M|MB)
149 echo $(echo "${num} * 1024 * 1024" | bc | cut -d'.' -f1)
150 ;;
151 G|GB)
152 echo $(echo "${num} * 1024 * 1024 * 1024" | bc | cut -d'.' -f1)
153 ;;
154 *)
155 echo "${num}"
156 ;;
157 esac
158}
159
160# Filter sessions
161declare -a FILTERED_FILES
162
163for file in "${ALL_FILES[@]}"; do
164 INCLUDE=true
165
166 # Time filter
167 if [ -n "${SINCE}" ] || [ -n "${UNTIL}" ]; then
168 FILE_TIMESTAMP=$(stat -f%m "${file}" 2>/dev/null || stat -c%Y "${file}" 2>/dev/null)
169
170 if [ -n "${SINCE}" ]; then
171 SINCE_TIMESTAMP=$(date_to_timestamp "${SINCE}")
172 if [ "${FILE_TIMESTAMP}" -lt "${SINCE_TIMESTAMP}" ]; then
173 INCLUDE=false
174 fi
175 fi
176
177 if [ -n "${UNTIL}" ]; then
178 UNTIL_TIMESTAMP=$(date_to_timestamp "${UNTIL}")
179 if [ "${FILE_TIMESTAMP}" -gt "${UNTIL_TIMESTAMP}" ]; then
180 INCLUDE=false
181 fi
182 fi
183 fi
184
185 # Branch filter
186 if [ -n "${BRANCH}" ] && [ "${INCLUDE}" = true ]; then
187 FILE_BRANCH=$(head -1 "${file}" | jq -r '.gitBranch // "unknown"' 2>/dev/null)
188 if [ "${FILE_BRANCH}" != "${BRANCH}" ]; then
189 INCLUDE=false
190 fi
191 fi
192
193 # Size filter
194 if [ "${INCLUDE}" = true ] && { [ -n "${MIN_SIZE}" ] || [ -n "${MAX_SIZE}" ]; }; then
195 FILE_SIZE=$(stat -f%z "${file}" 2>/dev/null || stat -c%s "${file}" 2>/dev/null)
196
197 if [ -n "${MIN_SIZE}" ]; then
198 MIN_BYTES=$(size_to_bytes "${MIN_SIZE}")
199 if [ "${FILE_SIZE}" -lt "${MIN_BYTES}" ]; then
200 INCLUDE=false
201 fi
202 fi
203
204 if [ -n "${MAX_SIZE}" ]; then
205 MAX_BYTES=$(size_to_bytes "${MAX_SIZE}")
206 if [ "${FILE_SIZE}" -gt "${MAX_BYTES}" ]; then
207 INCLUDE=false
208 fi
209 fi
210 fi
211
212 # Line count filter
213 if [ "${INCLUDE}" = true ] && { [ -n "${MIN_LINES}" ] || [ -n "${MAX_LINES}" ]; }; then
214 LINE_COUNT=$(wc -l < "${file}" | tr -d ' ')
215
216 if [ -n "${MIN_LINES}" ] && [ "${LINE_COUNT}" -lt "${MIN_LINES}" ]; then
217 INCLUDE=false
218 fi
219
220 if [ -n "${MAX_LINES}" ] && [ "${LINE_COUNT}" -gt "${MAX_LINES}" ]; then
221 INCLUDE=false
222 fi
223 fi
224
225 # Errors filter
226 if [ "${HAS_ERRORS}" = true ] && [ "${INCLUDE}" = true ]; then
227 ERROR_COUNT=$(grep -c '"is_error":true' "${file}" 2>/dev/null || echo "0")
228 if [ "${ERROR_COUNT}" -eq 0 ]; then
229 INCLUDE=false
230 fi
231 fi
232
233 # Keyword filter
234 if [ -n "${KEYWORD}" ] && [ "${INCLUDE}" = true ]; then
235 if ! grep -q "${KEYWORD}" "${file}" 2>/dev/null; then
236 INCLUDE=false
237 fi
238 fi
239
240 # Add to filtered list
241 if [ "${INCLUDE}" = true ]; then
242 FILTERED_FILES+=("${file}")
243 fi
244done
245
246# Output results
247if [ ${#FILTERED_FILES[@]} -eq 0 ]; then
248 echo "No sessions match the specified criteria" >&2
249 exit 0
250fi
251
252case ${OUTPUT_FORMAT} in
253 paths)
254 for file in "${FILTERED_FILES[@]}"; do
255 echo "${file}"
256 done
257 ;;
258 json)
259 echo "["
260 FIRST=true
261 for file in "${FILTERED_FILES[@]}"; do
262 SESSION_ID=$(basename "${file}" .jsonl)
263 FILE_SIZE=$(du -h "${file}" | cut -f1)
264 LINE_COUNT=$(wc -l < "${file}" | tr -d ' ')
265 MODIFIED_DATE=$(stat -f%Sm -t "%Y-%m-%d %H:%M:%S" "${file}" 2>/dev/null || stat -c%y "${file}" 2>/dev/null | cut -d'.' -f1)
266 GIT_BRANCH=$(head -1 "${file}" | jq -r '.gitBranch // "unknown"' 2>/dev/null || echo "unknown")
267
268 if [ "${FIRST}" = true ]; then
269 FIRST=false
270 else
271 echo ","
272 fi
273 echo " {"
274 echo " \"sessionId\": \"${SESSION_ID}\","
275 echo " \"path\": \"${file}\","
276 echo " \"size\": \"${FILE_SIZE}\","
277 echo " \"lines\": ${LINE_COUNT},"
278 echo " \"modifiedDate\": \"${MODIFIED_DATE}\","
279 echo " \"gitBranch\": \"${GIT_BRANCH}\""
280 echo -n " }"
281 done
282 echo ""
283 echo "]"
284 ;;
285 list|*)
286 echo "Found ${#FILTERED_FILES[@]} matching session(s):"
287 echo ""
288 for file in "${FILTERED_FILES[@]}"; do
289 SESSION_ID=$(basename "${file}" .jsonl)
290 FILE_SIZE=$(du -h "${file}" | cut -f1)
291 LINE_COUNT=$(wc -l < "${file}" | tr -d ' ')
292 MODIFIED_DATE=$(stat -f%Sm -t "%Y-%m-%d %H:%M:%S" "${file}" 2>/dev/null || stat -c%y "${file}" 2>/dev/null | cut -d'.' -f1)
293 GIT_BRANCH=$(head -1 "${file}" | jq -r '.gitBranch // "unknown"' 2>/dev/null || echo "unknown")
294
295 echo "Session: ${SESSION_ID}"
296 echo " Path: ${file}"
297 echo " Size: ${FILE_SIZE}"
298 echo " Lines: ${LINE_COUNT}"
299 echo " Modified: ${MODIFIED_DATE}"
300 echo " Branch: ${GIT_BRANCH}"
301 echo ""
302 done
303 ;;
304esac