/asciinema-tools:finalize
Finalize orphaned asciinema recordings: stop running processes gracefully, compress, and push to the orphan branch.
Arguments
Argument Description
file
Specific .cast file to finalize
--all
Finalize all unhandled .cast files
--force
Use SIGKILL if graceful stop fails
--no-push
Skip pushing to orphan branch (local only)
--keep-local
Keep local .cast after compression
Workflow
-
Discovery: Find running asciinema processes and unhandled .cast files
-
Selection: AskUserQuestion for which files to process
-
Stop: Gracefully stop running processes (SIGTERM → SIGINT → SIGKILL)
-
Verify: Check file integrity after stop
-
Compress: zstd compress .cast files
-
Push: Push to orphan branch (if configured)
-
Cleanup: Remove local .cast (optional)
Execution
Phase 1: Discovery
/usr/bin/env bash << 'DISCOVER_EOF' echo "=== Running asciinema processes ===" PROCS=$(ps aux | grep -E "asciinema rec" | grep -v grep) if [[ -n "$PROCS" ]]; then echo "$PROCS" | while read -r line; do PID=$(echo "$line" | awk '{print $2}') CAST_FILE=$(echo "$line" | grep -oE '[^ ]+.cast' | head -1) if [[ -n "$CAST_FILE" ]]; then SIZE=$(ls -lh "$CAST_FILE" 2>/dev/null | awk '{print $5}' || echo "?") echo "PID $PID: $CAST_FILE ($SIZE)" else echo "PID $PID: (no file detected)" fi done else echo "No running asciinema processes" fi
echo "" echo "=== Unhandled .cast files ===" find ~/eon -name "*.cast" -size +1M -mtime -30 2>/dev/null | while read -r f; do SIZE=$(ls -lh "$f" | awk '{print $5}') echo "$f ($SIZE)" done DISCOVER_EOF
Phase 2: Selection
AskUserQuestion: question: "Which recordings should be finalized?" header: "Select" multiSelect: true options: - label: "All running processes" description: "Stop all asciinema rec processes and finalize their files" - label: "All unhandled files" description: "Finalize all .cast files found in ~/eon" - label: "Specific file" description: "Enter path to specific .cast file"
Phase 3: Stop Running Processes
/usr/bin/env bash << 'STOP_EOF'
Arguments: PID list
PIDS="$@"
for PID in $PIDS; do echo "Stopping PID $PID..."
Try SIGTERM first (graceful)
kill -TERM "$PID" 2>/dev/null sleep 2
if kill -0 "$PID" 2>/dev/null; then echo " SIGTERM ignored, trying SIGINT..." kill -INT "$PID" 2>/dev/null sleep 2 fi
if kill -0 "$PID" 2>/dev/null; then echo " Process still running. Use --force for SIGKILL" # Only SIGKILL with --force flag if [[ "$FORCE" == "true" ]]; then echo " Sending SIGKILL (file may be truncated)..." kill -9 "$PID" 2>/dev/null sleep 1 fi fi
if ! kill -0 "$PID" 2>/dev/null; then echo " ✓ Process stopped" else echo " ✗ Process still running" fi done STOP_EOF
Phase 4: File Integrity Check
/usr/bin/env bash << 'CHECK_EOF' CAST_FILE="$1"
echo "Checking file integrity: $CAST_FILE"
Check if file exists
if [[ ! -f "$CAST_FILE" ]]; then echo " ✗ File not found" exit 1 fi
Check file size
SIZE=$(stat -f%z "$CAST_FILE" 2>/dev/null || stat -c%s "$CAST_FILE") echo " Size: $(numfmt --to=iec-i "$SIZE" 2>/dev/null || echo "$SIZE bytes")"
Check last line (NDJSON should have complete JSON arrays)
LAST_LINE=$(tail -c 500 "$CAST_FILE" | tail -1) if [[ "$LAST_LINE" == "]" ]]; then echo " ✓ File appears complete (ends with JSON array)" else echo " ⚠ File may be truncated (incomplete JSON)" echo " Note: asciinema 2.0+ streams to disk, so most data is preserved" fi
Test with asciinema cat (quick validation)
if timeout 5 asciinema cat "$CAST_FILE" > /dev/null 2>&1; then echo " ✓ File is playable" else echo " ⚠ File may have issues (but often still usable)" fi CHECK_EOF
Phase 5: Compress
/usr/bin/env bash << 'COMPRESS_EOF' CAST_FILE="$1" ZSTD_LEVEL="${2:-6}"
echo "Compressing: $CAST_FILE"
OUTPUT="${CAST_FILE}.zst" if zstd -"$ZSTD_LEVEL" -f "$CAST_FILE" -o "$OUTPUT"; then ORIG_SIZE=$(stat -f%z "$CAST_FILE" 2>/dev/null || stat -c%s "$CAST_FILE") COMP_SIZE=$(stat -f%z "$OUTPUT" 2>/dev/null || stat -c%s "$OUTPUT") RATIO=$(echo "scale=1; $ORIG_SIZE / $COMP_SIZE" | bc 2>/dev/null || echo "?") echo " ✓ Compressed: $(basename "$OUTPUT")" echo " Compression ratio: ${RATIO}:1" else echo " ✗ Compression failed" exit 1 fi COMPRESS_EOF
Phase 6: Push to Orphan Branch
/usr/bin/env bash << 'PUSH_EOF' COMPRESSED_FILE="$1" RECORDINGS_DIR="$HOME/asciinema_recordings"
Find the local recordings clone
REPO_DIR=$(find "$RECORDINGS_DIR" -maxdepth 1 -type d -name "*" | head -1) if [[ -z "$REPO_DIR" ]] || [[ ! -d "$REPO_DIR/.git" ]]; then echo " ⚠ No orphan branch clone found at $RECORDINGS_DIR" echo " Run /asciinema-tools:bootstrap to set up orphan branch" exit 1 fi
echo "Pushing to orphan branch..."
Copy compressed file
BASENAME=$(basename "$COMPRESSED_FILE") DEST="$REPO_DIR/recordings/$BASENAME" mkdir -p "$(dirname "$DEST")" cp "$COMPRESSED_FILE" "$DEST"
Commit and push
cd "$REPO_DIR" git add -A git commit -m "finalize: $BASENAME" 2>/dev/null || true
Push with token (prefer env var to avoid process spawning)
GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-$(gh auth token 2>/dev/null || echo "")}}" if [[ -n "$GH_TOKEN" ]]; then REMOTE_URL=$(git remote get-url origin)
Convert to token-authenticated URL
TOKEN_URL=$(echo "$REMOTE_URL" | sed "s|https://github.com|https://$GH_TOKEN@github.com|") if git push "$TOKEN_URL" HEAD 2>/dev/null; then echo " ✓ Pushed to orphan branch" else echo " ✗ Push failed (check credentials)" fi else echo " ⚠ No GitHub token, skipping push" fi PUSH_EOF
Phase 7: Cleanup Confirmation
AskUserQuestion: question: "Delete local .cast file after successful compression/push?" header: "Cleanup" options: - label: "Yes, delete local .cast" description: "Remove original .cast file (compressed version preserved)" - label: "No, keep local" description: "Keep both .cast and .cast.zst files"
Example Usage
Interactive mode - discover and select
/asciinema-tools:finalize
Finalize specific file
/asciinema-tools:finalize ~/eon/project/tmp/session.cast
Finalize all with force stop
/asciinema-tools:finalize --all --force
Local only (no push)
/asciinema-tools:finalize session.cast --no-push
Troubleshooting
Issue Cause Solution
Process won't stop Hung asciinema process Use --force flag for SIGKILL
File may be truncated Forced stop interrupted file Most data preserved, try playing it
zstd not found zstd not installed brew install zstd
Push failed No GitHub token Set GH_TOKEN or run gh auth login
No orphan branch Clone not configured Run /asciinema-tools:bootstrap first
File not found Wrong path or already moved Check with /daemon-status
Related Commands
-
/asciinema-tools:daemon-status
-
View status and find unhandled files
-
/asciinema-tools:convert
-
Convert .cast to .txt for analysis
-
/asciinema-tools:summarize
-
AI-powered analysis of recordings