|
26621
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26621
|
|
26622
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26622
|
|
26623
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26623
|
|
26624
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26624
|
|
26625
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26625
|
|
26626
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26626
|
|
26635
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26635
|
|
26643
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26643
|
|
26644
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26644
|
|
26645
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26645
|
|
26646
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26646
|
|
26647
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26647
|
|
26648
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26648
|
|
26649
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26649
|
|
26650
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26650
|
|
26651
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26651
|
|
26652
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26652
|
|
26653
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26653
|
|
26654
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26654
|
|
26655
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26655
|
|
26656
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update, 1 requires restart
3
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
archive.db.bak-pre-installid
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_fts_migrate.sh
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
M
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_fts_migrate.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
done
step "Reconciling NAS schema with source"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
-- vision
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(install_id, frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(install_id, frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(install_id, frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
-- audio
CREATE INDEX IF NOT EXISTS nas.idx_audio_chunks_timestamp ON audio_chunks(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_chunk_id ON audio_transcriptions(install_id, audio_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_timestamp ON audio_transcriptions(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_audio_trans_speaker ON audio_transcriptions(install_id, speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(install_id, speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(install_id, audio_chunk_id);
DETACH nas;
"
# ─── FTS TABLES (contentless, install-safe) ───────────────────────────────
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role,
install_id UNINDEXED, source_id UNINDEXED, frame_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content, app_name, window_title, element_name,
install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device,
speaker_id UNINDEXED, install_id UNINDEXED, source_id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD COLUMN LISTS ───────────────────────────────────────────────────
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
ELEMENTS_COLS_E=$(build_col_list elements e)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
OCR_TEXT_COLS_O=$(build_col_list ocr_text o)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
ACHUNKS_COLS=$(build_col_list audio_chunks)
ATRANS_COLS=$(build_col_list audio_transcriptions)
ATRANS_COLS_T=$(build_col_list audio_transcriptions t)
SPEAKERS_COLS=$(build_col_list speakers)
SEMB_COLS=$(build_col_list speaker_embeddings)
ATAGS_COLS=$(build_col_list audio_tags)
ATAGS_COLS_AT=$(build_col_list audio_tags at)
TAGS_COLS=$(build_col_list tags)
VTAGS_COLS=$(build_col_list vision_tags)
VTAGS_COLS_VT=$(build_col_list vision_tags vt)
# ─── SYNC VISION DATA ─────────────────────────────────────────────────────
step "Syncing vision data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS, install_id)
SELECT $VIDEO_CHUNKS_COLS, '$INSTALL_ID' FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS, install_id)
SELECT $FRAMES_COLS, '$INSTALL_ID' FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS, install_id)
SELECT $OCR_TEXT_COLS_O, '$INSTALL_ID' FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS, install_id)
SELECT $UI_EVENTS_COLS, '$INSTALL_ID' FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS, install_id)
SELECT $ELEMENTS_COLS_E, '$INSTALL_ID' FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS, install_id)
SELECT $MEETINGS_COLS, '$INSTALL_ID' FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC AUDIO DATA ──────────────────────────────────────────────────────
step "Syncing audio data for $TARGET_DATE"
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speakers ($SPEAKERS_COLS, install_id)
SELECT $SPEAKERS_COLS, '$INSTALL_ID' FROM main.speakers;
DETACH nas;
"
run_sqlite_heredoc "speaker_embeddings ($SRC_SEMB rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.speaker_embeddings ($SEMB_COLS, install_id)
SELECT $SEMB_COLS, '$INSTALL_ID' FROM main.speaker_embeddings;
DETACH nas;
"
run_sqlite_heredoc "audio_chunks ($SRC_ACHUNKS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_chunks ($ACHUNKS_COLS, install_id)
SELECT $ACHUNKS_COLS, '$INSTALL_ID' FROM main.audio_chunks WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions ($SRC_ATRANS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions ($ATRANS_COLS, install_id)
SELECT $ATRANS_COLS_T, '$INSTALL_ID' FROM main.audio_transcriptions t
JOIN main.audio_chunks c ON t.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "audio_tags ($SRC_ATAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_tags ($ATAGS_COLS, install_id)
SELECT $ATAGS_COLS_AT, '$INSTALL_ID' FROM main.audio_tags at
JOIN main.audio_chunks c ON at.audio_chunk_id = c.id
WHERE date(c.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── SYNC SHARED ──────────────────────────────────────────────────────────
step "Syncing shared tables (tags, vision_tags)"
run_sqlite_heredoc "tags ($SRC_TAGS rows, all)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.tags ($TAGS_COLS, install_id)
SELECT $TAGS_COLS, '$INSTALL_ID' FROM main.tags;
DETACH nas;
"
run_sqlite_heredoc "vision_tags ($SRC_VTAGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.vision_tags ($VTAGS_COLS, install_id)
SELECT $VTAGS_COLS_VT, '$INSTALL_ID' FROM main.vision_tags vt
JOIN main.frames f ON vt.vision_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
# ─── FTS UPDATE (contentless, auto-rowid, no collisions) ──────────────────
# No `rowid` specified; SQLite assigns a fresh one. install_id + source_id
# are UNINDEXED columns so JOIN-back-to-base queries work.
step "Updating FTS indexes"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(full_text, app_name, window_name, browser_url, install_id, source_id)
SELECT full_text, app_name, window_name, browser_url, install_id, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND full_text IS NOT NULL AND full_text != '';
DETACH nas;
"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(text, role, install_id, source_id, frame_id)
SELECT e.text, e.role, e.install_id, e.id, e.frame_id
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id AND e.install_id = f.install_id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.install_id = '$INSTALL_ID'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(text_content, app_name, window_title, element_name, install_id, source_id)
SELECT text_content, app_name, window_title, element_name, install_id, id
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.audio_transcriptions_fts(transcription, device, speaker_id, install_id, source_id)
SELECT t.transcription, COALESCE(t.device,''), t.speaker_id, t.install_id, t.id
FROM nas.audio_transcriptions t
JOIN nas.audio_chunks c ON t.audio_chunk_id = c.id AND t.install_id = c.install_id
WHERE date(c.timestamp) = '$TARGET_DATE'
AND t.install_id = '$INSTALL_ID'
AND t.transcription IS NOT NULL AND t.transcription != '';
DETACH nas;
"
# ─── VERIFY ───────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE install_id='$INSTALL_ID' AND frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ACHUNKS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID';")
V_ATRANS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_transcriptions WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_ATAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM audio_tags WHERE install_id='$INSTALL_ID' AND audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
V_VTAGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM vision_tags WHERE install_id='$INSTALL_ID' AND vision_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE' AND install_id='$INSTALL_ID');")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
check "audio_chunks" "$V_ACHUNKS" "$SRC_ACHUNKS"
check "audio_transcriptions" "$V_ATRANS" "$SRC_ATRANS"
check "audio_tags" "$V_ATAGS" "$SRC_ATAGS"
check "vision_tags" "$V_VTAGS" "$SRC_VTAGS"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync frames → NAS"
rsync -a --ignore-existing "$DATA_SRC/" "$NAS_DATA/$TARGET_DATE/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" 2>/dev/null | grep -v '^audio$' | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -ge "$SRC_FILES" ]; then
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync frames → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-40s ✗ %s / %s files\n" "rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-40s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with date in filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirror to $NAS_DATA/<date>/audio/ so each day's archive is self-contained.
step "Copying audio files for $TARGET_DATE"
shopt -s nullglob
AUDIO_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_FILES[@]} -gt 0 ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE/audio"
RSYNC_START=$(date +%s)
printf " %-40s " "rsync audio → NAS"
rsync -a --ignore-existing "${AUDIO_FILES[@]}" "$NAS_DATA/$TARGET_DATE/audio/" 2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_AUDIO=$(ls "$NAS_DATA/$TARGET_DATE/audio" | wc -l | tr -d ' ')
AUDIO_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE/audio" | cut -f1)
printf "\r %-40s ✓ %dm%02ds (%s files, %s)\n" \
"rsync audio → NAS" "$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_AUDIO" "$AUDIO_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync audio → NAS" "skipped (no audio for date)"
fi
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-40s " "rsync logs → NAS"
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "${#LOG_FILES[@]}" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-40s %s\n" "rsync logs → NAS" "skipped (no matching logs)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE (install $INSTALL_ID)"
log "========================================"
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
Implement and run pipe l…, Editor Group 2
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
Shell Script
Editor Language Status: Inline Suggestions, next: $(copilot) No inline suggestion available, Inline suggestions
LF
UTF-8
Spaces: 2
Ln 846, Col 59
Info: Downloading VS Code Server...
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
cli.py
consumers
db.py
docker-compose.yml
Dockerfile
main.py
__pycache__
requirements.txt
static
services:
screenpipe-app:
build: .
container_name: screenpipe-app
restart: unless-stopped
ports:
- "8766:8000"
volumes:
- /volume1/screenpipe:/data
env_file:
- .env
environment:
TZ: Europe/Sofia
DB_PATH: /data/db.sqlite
sqlite-web-archive:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-archive
restart: unless-stopped
ports:
- "8767:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/archive.db"]
sqlite-web-live:
image: ghcr.io/coleifer/sqlite-web:latest
container_name: screenpipe-sqlweb-live
restart: unless-stopped
ports:
- "8768:8080"
volumes:
- /volume1/screenpipe:/data
command: ["-H", "[IP_ADDRESS]", "-r", "/data/db.sqlite"]
Read
requirements.txt
requirements.txt
Bash
Inspect a pipe directory
IN
ls /volume1/screenpipe/pipes/day-recap/ && cat /volume1/screenpipe/pipes/day-recap/pipe.json 2>/dev/null || ls /volume1/screenpipe/pipes/day-recap/
Copy code to clipboard
OUT
pipe.md
pipe.md
Read
cli.py
cli.py
Bash
List all pipes and their contents
IN
ls /volume1/screenpipe/pipes/ && for d in /volume1/screenpipe/pipes/*/; do echo "=== $d ==="; ls "$d"; done
Copy code to clipboard
OUT
ai-habits
day-recap
meeting-summary
standup-update
time-breakdown
video-export
=== /volume1/screenpipe/pipes/ai-habits/ ===
pipe.md
=== /volume1/screenpipe/pipes/day-recap/ ===
pipe.md
=== /volume1/screenpipe/pipes/meeting-summary/ ===
pipe.md
=== /volume1/screenpipe/pipes/standup-update/ ===
pipe.md
=== /volume1/screenpipe/pipes/time-breakdown/ ===
pipe.md
=== /volume1/screenpipe/pipes/video-export/ ===
pipe.md
Bash
Read pipe markdown files
IN
cat /volume1/screenpipe/pipes/day-recap/pipe.md && echo "===" && cat /volume1/screenpipe/pipes/meeting-summary/pipe.md 2>/dev/null | head -50
Copy code to clipboard
OUT
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
Thinking
Thinking
Bash
Read summary consumer
IN
ls /volume1/screenpipe/app/consumers/ && cat /volume1/screenpipe/app/consumers/summary.py
Copy code to clipboard
OUT
activity.py
audio.py
elements.py
__init__.py
interactions.py
ocr.py
__pycache__
report.py
summary.py
"""AI summary generator using Claude."""
import os
from datetime import date
from typing import Any
from consumers.activity import app_time, summary_stats, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
SUMMARY_TYPES = {
"day_recap": {
"title": "Day Recap",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a concise day recap using EXACTLY this format:
## Summary
One sentence: what I mainly worked on today.
## Accomplishments
- Top 3 things I finished, with approximate times
- Name specific apps, files, or projects
## Key Moments
- Important things seen or heard, with timestamps
## Unfinished Work
- What to continue tomorrow
## Patterns
- Apps used most, topics that came up repeatedly
Only report what you can verify from the data. End with:
**Next step:** [most important thing to continue]""",
},
"standup": {
"title": "Standup Update",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
## Audio/speech:
{audio}
Write a brief standup update:
**Yesterday:** What I worked on (2-3 bullets)
**Today:** What I plan to continue (1-2 bullets)
**Blockers:** Any issues observed (or "None")
Keep it under 150 words. Only report what's verifiable from the data.""",
},
"focus_time": {
"title": "Focus & Distraction Analysis",
"prompt": """\
You are analyzing a person's computer activity for the day.
## App usage (minutes per app):
{app_time}
## Browser URLs visited:
{browser_urls}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_events}
## Screen content (sampled every 5 minutes):
{ocr_text}
Analyze focus vs distraction patterns:
## Deep Work Periods
- When and how long were sustained focus sessions?
- What was being worked on?
## Distractions
- Which apps/sites were used recreationally?
- How much time was lost to context switching?
## Recommendations
- 2-3 concrete suggestions to improve focus tomorrow
Be specific with times and app names.""",
},
}
def _build_context(for_date: date) -> dict[str, str]:
apps = app_time(for_date)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(for_date, limit=20)
url_lines = "\n".join(
f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls
)
ui_rows = ui_event_summary(for_date)
ui_lines = "\n".join(
f"- {r['app_name']}: {r['clicks']} clicks, {r['keystrokes']} keys, {r['switches']} switches"
for r in ui_rows[:15]
)
ocr_rows = deduplicated_text(for_date, chunk_minutes=5)
ocr_lines = "\n".join(
f"[{r['bucket']}] {r['app_name']} / {r['window_name'] or ''}: {r['text'][:200]}"
for r in ocr_rows[:100]
)
audio = formatted_transcript(for_date)
return {
"app_time": app_lines or "(no app data)",
"browser_urls": url_lines or "(no browser data)",
"ui_events": ui_lines or "(no UI event data)",
"ocr_text": ocr_lines or "(no screen data)",
"audio": audio[:3000] if len(audio) > 3000 else audio,
}
def generate(
summary_type: str = "day_recap",
for_date: date | None = None,
api_key: str | None = None,
) -> dict[str, Any]:
"""Generate an AI summary. Returns dict with 'title', 'content', 'date'."""
import anthropic
d = for_date or date.today()
key = api_key or os.environ.get("ANTHROPIC_API_KEY", "")
if not key:
return {
"title": "Error",
"content": "ANTHROPIC_API_KEY not set. Add it to your .env file.",
"date": str(d),
}
spec = SUMMARY_TYPES.get(summary_type)
if not spec:
return {"title": "Error", "content": f"Unknown summary type: {summary_type}", "date": str(d)}
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {
"title": spec["title"],
"content": f"No screen data found for {d}.",
"date": str(d),
}
ctx = _build_context(d)
prompt = spec["prompt"].format(**ctx)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
return {
"title": spec["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Thinking
Thinking
Bash
Read all pipe.md files
IN
for f in /volume1/screenpipe/pipes/*/pipe.md; do echo "=== $f ==="; cat "$f"; echo; done
Copy code to clipboard
OUT
=== /volume1/screenpipe/pipes/ai-habits/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: AI Habits
description: "How you use AI tools — patterns and insights"
icon: "🤖"
featured: true
---
Search my recordings from the last 24 hours for AI tool usage. Use app_name filter for each tool separately: ChatGPT, Claude, Copilot, Cursor, Gemini, Perplexity. Use limit=5 per search, max 6 searches total.
Read screenpipe skill first.
Use this exact format:
## AI Tools Used
- List each tool with approximate time spent (e.g. "Claude: ~45min")
## What I Used Them For
- For each tool: coding, writing, research, or brainstorming
## Usage Patterns
- Do I switch between tools? Use them in bursts or steadily?
## Effectiveness
- Which tool appeared alongside completed work vs. abandoned attempts
If no AI usage is found, say so clearly. End with: "**Tip:** [one suggestion to use AI tools more effectively]"
=== /volume1/screenpipe/pipes/day-recap/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Day Recap
description: "Today's accomplishments, key moments, and unfinished work"
icon: "📋"
featured: true
---
Analyze my screen and audio recordings from today (last 16 hours only).
Read screenpipe skill first.
Use this exact format:
## Summary
One sentence: what I mainly did today.
## Accomplishments
- Top 3 things I finished, with timestamps (e.g. "2:30 PM")
- Name specific apps, files, or projects
## Key Moments
- Important things I saw, said, or heard — with timestamps
## Unfinished Work
- What I should continue tomorrow — name the app/file/task
## Patterns
- Apps I used most, topics that came up repeatedly
Only report what you can verify from the data. End with: "**Next step:** [most important thing to continue]"
=== /volume1/screenpipe/pipes/meeting-summary/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Meeting Summary
description: "Summarize meeting transcript with key takeaways and action items"
icon: "🤝"
featured: false
---
Summarize the meeting transcript provided in the context. Include key takeaways and action items. If the meeting is marked as ongoing, note that and summarize what's available so far.
Read screenpipe skill first.
Use this exact format:
## Meeting Summary
One sentence: what this meeting was about.
## Key Takeaways
- Top 3-5 important points discussed
- Include who said what when relevant
## Action Items
- [ ] Task — assigned to whom, deadline if mentioned
- [ ] Task — assigned to whom
## Decisions Made
- List any decisions or agreements reached
## Open Questions
- Anything unresolved or needing follow-up
Keep it concise and actionable.
=== /volume1/screenpipe/pipes/standup-update/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Standup Update
de...
|
Code
|
screenpipe_sync.sh — screenpipe [SSH: nas] — Modif screenpipe_sync.sh — screenpipe [SSH: nas] — Modified...
|
NULL
|
26656
|
|
21113
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 10 pending changes
10
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
__pycache__
consumers
static
.env
cli.py
M
db.py
docker-compose.yml
Dockerfile
M
main.py
M
requirements.txt
M
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1...
|
Code
|
screenpipe_sync_updated.sh — screenpipe [SSH: nas] screenpipe_sync_updated.sh — screenpipe [SSH: nas] — Untracked...
|
NULL
|
21113
|
|
21114
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 10 pending changes
10
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
__pycache__
consumers
static
.env
cli.py
M
db.py
docker-compose.yml
Dockerfile
M
main.py
M
requirements.txt
M
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
...
|
Code
|
screenpipe_sync_updated.sh — screenpipe [SSH: nas] screenpipe_sync_updated.sh — screenpipe [SSH: nas] — Untracked...
|
NULL
|
21114
|
|
21115
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 10 pending changes
10
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
__pycache__
consumers
static
.env
cli.py
M
db.py
docker-compose.yml
Dockerfile
M
main.py
M
requirements.txt
M
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
#!/bin/bash
# screenpipe_sync.sh
# Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions).
# Also copies the day's video/frame data folder to the NAS.
#
# Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB,
# the NAS archive gets ALTER TABLE'd to match. Inserts use explicit column lists,
# so positional mismatches can't occur.
#
# Usage:
# ./screenpipe_sync.sh # syncs yesterday (default)
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
#
# Cron example (runs at 3am daily):
# 0 3 * * * /Users/lukas/.screenpipe/screenpipe_sync.sh >> /Users/lukas/.screenpipe/sync.log 2>&1
set -euo pipefail
# ─── CONFIG ───────────────────────────────────────────────────────────────────
DB_SRC="${SCREENPIPE_DB:-$HOME/.screenpipe/db.sqlite}"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/screenpipe}"
NAS_DB="$NAS_MOUNT/archive.db"
NAS_DATA="$NAS_MOUNT/data"
LOG_FILE="$HOME/.screenpipe/sync.log"
# Tables that get schema drift handling. Order matters for FK-ish references
# (parents before children: video_chunks → frames → elements/ocr_text/ui_events).
SYNC_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
# ──────────────────────────────────────────────────────────────────────────────
# ─── HELPERS ──────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$LOG_FILE"
}
step() {
local now=$(date +%s)
local elapsed=$(( now - SCRIPT_START ))
local min=$(( elapsed / 60 ))
local sec=$(( elapsed % 60 ))
printf "\n[+%02dm%02ds] ▶ %s\n" "$min" "$sec" "$*" | tee -a "$LOG_FILE"
}
run_sqlite_heredoc() {
local label="$1"
local sql="$2"
local start=$(date +%s)
printf " %-36s " "$label"
sqlite3 "$DB_SRC" <<< "$sql" &
local pid=$!
local spin=[PASSWORD] '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r %-36s %s " "$label" "${spin[$i]}"
i=$(( (i + 1) % 10 ))
sleep 0.2
done
wait "$pid"
local rc=$?
if [ $rc -ne 0 ]; then
printf "\r %-36s ✗ FAILED\n" "$label" | tee -a "$LOG_FILE"
exit $rc
fi
local dur=$(( $(date +%s) - start ))
printf "\r %-36s ✓ %dm%02ds\n" "$label" "$(( dur / 60 ))" "$(( dur % 60 ))" | tee -a "$LOG_FILE"
}
check() {
local label="$1" got="$2" expected="$3"
if [ "$got" -eq "$expected" ]; then
printf " %-20s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-20s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
# ─── SCHEMA HELPERS ───────────────────────────────────────────────────────────
# Echoes "name|type" lines for a table from a given DB.
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
# Echoes just column names, one per line.
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
# Returns true (0) if a table exists in the given DB.
table_exists() {
local db="$1" table="$2"
local count
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$table';")
[ "$count" -gt 0 ]
}
# Adds any columns present in source but missing in NAS for the given table.
# Uses ALTER TABLE ADD COLUMN, which is a fast metadata-only operation in SQLite.
ensure_columns() {
local table="$1"
local label="schema: $table"
printf " %-36s " "$label"
if ! table_exists "$DB_SRC" "$table"; then
printf "✗ source missing — skipping\n"
return 0
fi
if ! table_exists "$NAS_DB" "$table"; then
printf "✓ fresh (created above)\n"
return 0
fi
local src_cols
src_cols=$(table_columns_with_types "$DB_SRC" "$table")
local nas_cols
nas_cols=$(table_columns "$NAS_DB" "$table")
local added=0
local added_names=""
while IFS='|' read -r name type; do
[ -z "$name" ] && continue
# Use grep -F -x for fixed-string exact match (safe against regex chars).
if ! echo "$nas_cols" | grep -Fxq "$name"; then
# SQLite ALTER TABLE ADD COLUMN: type is optional, no NOT NULL allowed
# without a constant default. Default-NULL is exactly what we want for archive.
sqlite3 "$NAS_DB" "ALTER TABLE $table ADD COLUMN \"$name\" $type;"
added=$((added + 1))
added_names="$added_names $name"
fi
done <<< "$src_cols"
if [ "$added" -gt 0 ]; then
printf "✓ added %d:%s\n" "$added" "$added_names"
else
printf "✓ in sync\n"
fi
}
# Echoes a comma-separated list of source columns for a table, double-quoted
# so reserved words and hyphens are safe. Used for both INSERT and SELECT sides.
build_col_list() {
local table="$1"
table_columns "$DB_SRC" "$table" | awk 'NF' | sed 's/.*/"&"/' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── DATE ARGUMENT ────────────────────────────────────────────────────────────
if [ "${1:-}" = "today" ]; then
TARGET_DATE=$(date +%Y-%m-%d)
elif [ -n "${1:-}" ]; then
TARGET_DATE="$1"
if ! [[ "$TARGET_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "ERROR: Invalid date format. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-20s %s (%s)\n" "Source DB:" "OK" "$(du -sh "$DB_SRC" | cut -f1)"
if [ ! -d "$NAS_MOUNT" ]; then
log "ERROR: NAS not mounted at $NAS_MOUNT"; exit 1
fi
printf " %-20s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';" 2>/dev/null || echo "0")
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE already has $EXISTING frames in archive — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-20s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-20s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-20s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date
DATA_SRC="$HOME/.screenpipe/data/data/$TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
DATA_SIZE=$(du -sh "$DATA_SRC" | cut -f1)
DATA_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
printf " %-20s %s (%s files, %s)\n" "Data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-20s %s\n" "Data dir:" "not found — skipping file copy"
fi
# ─── DB SYNC ──────────────────────────────────────────────────────────────────
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── COUNT SOURCE ROWS ────────────────────────────────────────────────────
step "Counting source rows for $TARGET_DATE"
SRC_FRAMES=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ELEMENTS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_UI=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
SRC_OCR=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_MEETINGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE';")
printf " %-20s %s\n" "frames:" "$SRC_FRAMES"
printf " %-20s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-20s %s\n" "ui_events:" "$SRC_UI"
printf " %-20s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-20s %s\n" "meetings:" "$SRC_MEETINGS"
if [ "$SRC_FRAMES" -eq "0" ]; then
log "No frames found for $TARGET_DATE — skipping DB sync"
DB_ALREADY_SYNCED=true
fi
fi
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── INIT TABLES ──────────────────────────────────────────────────────────
step "Initialising tables (CREATE IF NOT EXISTS)"
run_sqlite_heredoc "creating tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.frames AS SELECT * FROM main.frames WHERE 0;
CREATE TABLE IF NOT EXISTS nas.elements AS SELECT * FROM main.elements WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ui_events AS SELECT * FROM main.ui_events WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ocr_text AS SELECT * FROM main.ocr_text WHERE 0;
CREATE TABLE IF NOT EXISTS nas.video_chunks AS SELECT * FROM main.video_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.meetings AS SELECT * FROM main.meetings WHERE 0;
DETACH nas;
"
# ─── SCHEMA DRIFT FIX ─────────────────────────────────────────────────────
# Runs AFTER CREATE IF NOT EXISTS so newly-created tables are no-ops here,
# and pre-existing tables get any missing columns added.
step "Reconciling NAS schema with source"
for tbl in "${SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
DETACH nas;
"
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role, frame_id UNINDEXED,
content='elements', content_rowid='id', tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url, id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content,
app_name,
window_title,
element_name,
content='ui_events',
content_rowid='id',
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD EXPLICIT COLUMN LISTS ──────────────────────────────────────────
# After ensure_columns, source ⊆ NAS for every synced table, so source's
# column list is a safe subset to use on both sides of the INSERT.
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
# ─── SYNC DATA ────────────────────────────────────────────────────────────
step "Syncing data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS)
SELECT $VIDEO_CHUNKS_COLS FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS)
SELECT $FRAMES_COLS FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS)
SELECT $OCR_TEXT_COLS FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS)
SELECT $UI_EVENTS_COLS FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS)
SELECT $ELEMENTS_COLS FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS)
SELECT $MEETINGS_COLS FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# NOTE: the elements/ocr_text inserts use unqualified column names while
# selecting from a JOIN. SQLite resolves them against the leftmost table
# that has the column, which is what we want here (e.* / o.* implicitly),
# because none of the column names collide with frames'.
# ─── FTS UPDATE ───────────────────────────────────────────────────────────
step "Updating FTS indexes"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(rowid, text, role)
SELECT e.id, e.text, e.role
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(rowid, full_text, app_name, window_name, browser_url, id)
SELECT id, full_text, app_name, window_name, browser_url, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND full_text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(rowid, text_content, app_name, window_title, element_name)
SELECT id, text_content, app_name, window_title, element_name
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND text_content IS NOT NULL;
DETACH nas;
"
# ─── VERIFY DB ────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start)= '$TARGET_DATE';")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
fi
# ─── COPY DATA FOLDER ─────────────────────────────────────────────────────────
# Always runs regardless of DB sync status
step "Copying data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-36s " "rsync $TARGET_DATE/ → NAS"
rsync -a --ignore-existing \
"$DATA_SRC/" \
"$NAS_DATA/$TARGET_DATE/" \
2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -eq "$SRC_FILES" ]; then
printf "\r %-36s ✓ %dm%02ds (%s files, %s)\n" \
"rsync $TARGET_DATE/ → NAS" \
"$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-36s ✗ %s / %s files\n" \
"rsync $TARGET_DATE/ → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-36s %s\n" "rsync $TARGET_DATE/ → NAS" "skipped (no source dir)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE"
log "========================================"
#!/bin/bash
# screenpipe_sync.sh
# Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions).
# Also copies the day's video/frame data folder to the NAS.
#
# Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB,
# the NAS archive gets ALTER TABLE'd to match. Inserts use explicit column lists,
# so positional mismatches can't occur.
#
# Usage:
# ./screenpipe_sync.sh # syncs yesterday (default)
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
#
# Cron example (runs at 3am daily):
# 0 3 * * * /Users/lukas/.screenpipe/screenpipe_sync.sh >> /Users/lukas/.screenpipe/sync.log 2>&1
set -euo pipefail
# ─── CONFIG ───────────────────────────────────────────────────────────────────
DB_SRC="${SCREENPIPE_DB:-$HOME/.screenpipe/db.sqlite}"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/screenpipe}"
NAS_DB="$NAS_MOUNT/archive.db"
NAS_DATA="$NAS_MOUNT/data"
LOG_FILE="$HOME/.screenpipe/sync.log"
# Tables that get schema drift handling. Order matters for FK-ish references
# (parents before children: video_chunks → frames → elements/ocr_text/ui_events).
SYNC_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
# ──────────────────────────────────────────────────────────────────────────────
# ─── HELPERS ──────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$LOG_FILE"
}
step() {
local now=$(date +%s)
local elapsed=$(( now - SCRIPT_START ))
local min=$(( elapsed / 60 ))
local sec=$(( elapsed % 60 ))
printf "\n[+%02dm%02ds] ▶ %s\n" "$min" "$sec" "$*" | tee -a "$LOG_FILE"
}
run_sqlite_heredoc() {
local label="$1"
local sql="$2"
local start=$(date +%s)
printf " %-36s " "$label"
sqlite3 "$DB_SRC" <<< "$sql" &
local pid=$!
local spin=[PASSWORD] '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r %-36s %s " "$label" "${spin[$i]}"
i=$(( (i + 1) % 10 ))
sleep 0.2
done
wait "$pid"
local rc=$?
if [ $rc -ne 0 ]; then
printf "\r %-36s ✗ FAILED\n" "$label" | tee -a "$LOG_FILE"
exit $rc
fi
local dur=$(( $(date +%s) - start ))
printf "\r %-36s ✓ %dm%02ds\n" "$label" "$(( dur / 60 ))" "$(( dur % 60 ))" | tee -a "$LOG_FILE"
}
check() {
local label="$1" got="$2" expected="$3"
if [ "$got" -eq "$expected" ]; then
printf " %-20s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-20s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
# ─── SCHEMA HELPERS ───────────────────────────────────────────────────────────
# Echoes "name|type" lines for a table from a given DB.
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
# Echoes just column names, one per line.
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
# Returns true (0) if a table exists in the given DB.
table_exists() {
local db="$1" table="$2"
local count
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$table';")
[ "$count" -gt 0 ]
}
# Adds any columns present in source but missing in NAS for the given table.
# Uses ALTER TABLE ADD COLUMN, which is a fast metadata-only operation in SQLite.
ensure_columns() {
local table="$1"
local label="schema: $table"
printf " %-36s " "$label"
if ! table_exists "$DB_SRC" "$table"; then
printf "✗ source missing — skipping\n"
return 0
fi
if ! table_exists "$NAS_DB" "$table"; then
printf "✓ fresh (created above)\n"
return 0
fi
local src_cols
src_cols=$(table_columns_with_types "$DB_SRC" "$table")
local nas_cols
nas_cols=$(table_columns "$NAS_DB" "$table")
local added=0
local added_names=""
while IFS='|' read -r name type; do
[ -z "$name" ] && continue
# Use grep -F -x for fixed-string exact match (safe against regex chars).
if ! echo "$nas_cols" | grep -Fxq "$name"; then
# SQLite ALTER TABLE ADD COLUMN: type is optional, no NOT NULL allowed
# without a constant default. Default-NULL is exactly what we want for archive.
sqlite3 "$NAS_DB" "ALTER TABLE $table ADD COLUMN \"$name\" $type;"
added=$((added + 1))
added_names="$added_names $name"
fi
done <<< "$src_cols"
if [ "$added" -gt 0 ]; then
printf "✓ added %d:%s\n" "$added" "$added_names"
else
printf "✓ in sync\n"
fi
}
# Echoes a comma-separated list of source columns for a table, double-quoted
# so reserved words and hyphens are safe. Used for both INSERT and SELECT sides.
build_col_list() {
local table="$1"
table_columns "$DB_SRC" "$table" | awk 'NF' | sed 's/.*/"&"/' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── DATE ARGUMENT ────────────────────────────────────────────────────────────
if [ "${1:-}" = "today" ]; then
TARGET_DATE=$(date +%Y-%m-%d)
elif [ -n "${1:-}" ]; then
TARGET_DATE="$1"
if ! [[ "$TARGET_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "ERROR: Invalid date format. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-20s %s (%s)\n" "Source DB:" "OK" "$(du -sh "$DB_SRC" | cut -f1)"
if [ ! -d "$NAS_MOUNT" ]; then
log "ERROR: NAS not mounted at $NAS_MOUNT"; exit 1
fi
printf " %-20s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';" 2>/dev/null || echo "0")
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE already has $EXISTING frames in archive — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-20s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-20s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-20s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date
DATA_SRC="$HOME/.screenpipe/data/data/$TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
DATA_SIZE=$(du -sh "$DATA_SRC" | cut -f1)
DATA_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
printf " %-20s %s (%s files, %s)\n" "Data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-20s %s\n" "Data dir:" "not found — skipping file copy"
fi
# ─── DB SYNC ──────────────────────────────────────────────────────────────────
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── COUNT SOURCE ROWS ────────────────────────────────────────────────────
step "Counting source rows for $TARGET_DATE"
SRC_FRAMES=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ELEMENTS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_UI=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
SRC_OCR=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_MEETINGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE';")
printf " %-20s %s\n" "frames:" "$SRC_FRAMES"
printf " %-20s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-20s %s\n" "ui_events:" "$SRC_UI"
printf " %-20s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-20s %s\n" "meetings:" "$SRC_MEETINGS"
if [ "$SRC_FRAMES" -eq "0" ]; then
log "No frames found for $TARGET_DATE — skipping DB sync"
DB_ALREADY_SYNCED=true
fi
fi
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── INIT TABLES ──────────────────────────────────────────────────────────
step "Initialising tables (CREATE IF NOT EXISTS)"
run_sqlite_heredoc "creating tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.frames AS SELECT * FROM main.frames WHERE 0;
CREATE TABLE IF NOT EXISTS nas.elements AS SELECT * FROM main.elements WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ui_events AS SELECT * FROM main.ui_events WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ocr_text AS SELECT * FROM main.ocr_text WHERE 0;
CREATE TABLE IF NOT EXISTS nas.video_chunks AS SELECT * FROM main.video_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.meetings AS SELECT * FROM main.meetings WHERE 0;
DETACH nas;
"
# ─── SCHEMA DRIFT FIX ─────────────────────────────────────────────────────
# Runs AFTER CREATE IF NOT EXISTS so newly-created tables are no-ops here,
# and pre-existing tables get any missing columns added.
step "Reconciling NAS schema with source"
for tbl in "${SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
DETACH nas;
"
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role, frame_id UNINDEXED,
content='elements', content_rowid='id', tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url, id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content,
app_name,
window_title,
element_name,
content='ui_events',
content_rowid='id',
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD EXPLICIT COLUMN LISTS ──────────────────────────────────────────
# After ensure_columns, source ⊆ NAS for every synced table, so source's
# column list is a safe subset to use on both sides of the INSERT.
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
# ─── SYNC DATA ────────────────────────────────────────────────────────────
step "Syncing data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS)
SELECT $VIDEO_CHUNKS_COLS FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS)
SELECT $FRAMES_COLS FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS)
SELECT $OCR_TEXT_COLS FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS)
SELECT $UI_EVENTS_COLS FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS)
SELECT $ELEMENTS_COLS FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS)
SELECT $MEETINGS_COLS FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# NOTE: the elements/ocr_text inserts use unqualified column names while
# selecting from a JOIN. SQLite resolves them against the leftmost table
# that has the column, which is what we want here (e.* / o.* implicitly),
# because none of the column names collide with frames'.
# ─── FTS UPDATE ───────────────────────────────────────────────────────────
step "Updating FTS indexes"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(rowid, text, role)
SELECT e.id, e.text, e.role
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(rowid, full_text, app_name, window_name, browser_url, id)
SELECT id, full_text, app_name, window_name, browser_url, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND full_text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(rowid, text_content, app_name, window_title, element_name)
SELECT id, text_content, app_name, window_title, element_name
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND text_content IS NOT NULL;
DETACH nas;
"
# ─── VERIFY DB ────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start)= '$TARGET_DATE';")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
fi
# ─── COPY DATA FOLDER ─────────────────────────────────────────────────────────
# Always runs regardless of DB sync status
step "Copying data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-36s " "rsync $TARGET_DATE/ → NAS"
rsync -a --ignore-existing \
"$DATA_SRC/" \
"$NAS_DATA/$TARGET_DATE/" \
2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -eq "$SRC_FILES" ]; then
printf "\r %-36s ✓ %dm%02ds (%s files, %s)\n" \
"rsync $TARGET_DATE/ → NAS" \
"$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-36s ✗ %s / %s files\n" \
"rsync $TARGET_DATE/ → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-36s %s\n" "rsync $TARGET_DATE/ → NAS" "skipped (no source dir)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE"
log "========================================"
Implement and run pipe l…, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports - 1 forwarded port
PORTS
1
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
No results found
Implement and run pipe logic
Session history
New session
Message actions
main.py
main.py
lets implement logic with pipes. how can I run it?
Thinking
Thinking
Let me look at the current project structure to understand what's already in place.
Bash
List project files
IN
find /volume1/screenpipe -type f | head -50 && echo "---" && ls /volume1/screenpipe
Copy code to clipboard
OUT
/volume1/screenpipe/.DS_Store
/volume1/screenpipe/screenpipe.db
/volume1/screenpipe/screenpipe_sync.sh
/volume1/screenpipe/app_settings.json
/volume1/screenpipe/.gitignore
/volume1/screenpipe/archive.db
/volume1/screenpipe/db.sqlite
/volume1/screenpipe/db.sqlite-wal
/volume1/screenpipe/db.sqlite-shm
/volume1/screenpipe/#recycle/desktop.ini
/volume1/screenpipe/#recycle/db.sqlite
/volume1/screenpipe/#recycle/Copy #1 of db.sqlite
/volume1/screenpipe/#recycle/.DS_Store
/volume1/screenpipe/#recycle/Copy #2 of db.sqlite
/volume1/screenpipe/#recycle/archive.db
/volume1/screenpipe/#recycle/Copy #1 of archive.db
/volume1/screenpipe/#recycle/Copy #2 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of archive.db
/volume1/screenpipe/#recycle/Copy #4 of archive.db
/volume1/screenpipe/#recycle/Copy #5 of archive.db
/volume1/screenpipe/#recycle/Copy #3 of db.sqlite
/volume1/screenpipe/#recycle/Copy #4 of db.sqlite
/volume1/screenpipe/#recycle/Copy #6 of archive.db
/volume1/screenpipe/#recycle/test.txt
/volume1/screenpipe/#recycle/Copy #7 of archive.db
/volume1/screenpipe/#recycle/Copy #8 of archive.db
/volume1/screenpipe/#recycle/archive.db-journal
/volume1/screenpipe/#recycle/Copy #1 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #2 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #3 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #4 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #6 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #7 of archive.db-journal
/volume1/screenpipe/#recycle/Copy #5 of db.sqlite
/volume1/screenpipe/pipes/.DS_Store
/volume1/screenpipe/app/.env
/volume1/screenpipe/app/cli.py
/volume1/screenpipe/app/.DS_Store
/volume1/screenpipe/app/db.py
/volume1/screenpipe/app/docker-compose.yml
/volume1/screenpipe/app/main.py
/volume1/screenpipe/app/requirements.txt
/volume1/screenpipe/app/Dockerfile
/volume1/screenpipe/data/.DS_Store
/volume1/screenpipe/.git/description
/volume1/screenpipe/.git/HEAD
/volume1/screenpipe/.git/index
/volume1/screenpipe/.git/COMMIT_EDITMSG
/volume1/screenpipe/#recycle/data/.DS_Store
---
app
app_settings.json
archive.db
data
db.sqlite
db.sqlite-shm
db.sqlite-wal
pipes
#recycle
screenpipe.db
screenpipe_sync.sh
Read
main.py
main.py
Bash
Check pipes directory and docker-compose
IN
ls /volume1/screenpipe/pipes/ && ls /volume1/screenpipe/app/ && cat /volume1/screenpipe/app/docker-compose.yml
Copy code to clipboard...
|
Code
|
screenpipe_sync_updated.sh — screenpipe [SSH: nas] screenpipe_sync_updated.sh — screenpipe [SSH: nas] — Untracked...
|
NULL
|
21115
|
|
21116
|
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Explorer (⇧⌘E) - 1 unsaved file
1
Search (⇧⌘F)
Source Control (⌃⇧G) - 10 pending changes
10
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X) - 2 require update
2
Claude Code
Containers
EXPLORER
EXPLORER
Explorer Section: screenpipe [SSH: nas]
Explorer Section: screenpipe [SSH: nas]
SCREENPIPE [SSH: NAS]
#recycle
app
__pycache__
consumers
static
.env
cli.py
M
db.py
docker-compose.yml
Dockerfile
M
main.py
M
requirements.txt
M
data
logs
pipes
.gitignore
app_settings.json
archive.db
archive.db-bak
U
db.sqlite
db.sqlite-shm
db.sqlite-wal
screenpipe_sync_updated.sh
U
screenpipe_sync.sh
screenpipe.db
Outline Section
OUTLINE
OUTLINE
Timeline Section
TIMELINE
TIMELINE
app_settings.json, Editor Group 1
screenpipe_sync.sh, Editor Group 1
screenpipe_sync_updated.sh, preview, Editor Group 1
pipe.md, Editor Group 1
.env, Editor Group 1
#!/bin/bash
# screenpipe_sync.sh
# Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions).
# Also copies the day's video/frame data folder to the NAS.
#
# Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB,
# the NAS archive gets ALTER TABLE'd to match. Inserts use explicit column lists,
# so positional mismatches can't occur.
#
# Usage:
# ./screenpipe_sync.sh # syncs yesterday (default)
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
#
# Cron example (runs at 3am daily):
# 0 3 * * * /Users/lukas/.screenpipe/screenpipe_sync.sh >> /Users/lukas/.screenpipe/sync.log 2>&1
set -euo pipefail
# ─── CONFIG ───────────────────────────────────────────────────────────────────
DB_SRC="${SCREENPIPE_DB:-$HOME/.screenpipe/db.sqlite}"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/screenpipe}"
NAS_DB="$NAS_MOUNT/archive.db"
NAS_DATA="$NAS_MOUNT/data"
LOG_FILE="$HOME/.screenpipe/sync.log"
# Tables that get schema drift handling. Order matters for FK-ish references
# (parents before children: video_chunks → frames → elements/ocr_text/ui_events).
SYNC_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
# ──────────────────────────────────────────────────────────────────────────────
# ─── HELPERS ──────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$LOG_FILE"
}
step() {
local now=$(date +%s)
local elapsed=$(( now - SCRIPT_START ))
local min=$(( elapsed / 60 ))
local sec=$(( elapsed % 60 ))
printf "\n[+%02dm%02ds] ▶ %s\n" "$min" "$sec" "$*" | tee -a "$LOG_FILE"
}
run_sqlite_heredoc() {
local label="$1"
local sql="$2"
local start=$(date +%s)
printf " %-36s " "$label"
sqlite3 "$DB_SRC" <<< "$sql" &
local pid=$!
local spin=[PASSWORD] '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r %-36s %s " "$label" "${spin[$i]}"
i=$(( (i + 1) % 10 ))
sleep 0.2
done
wait "$pid"
local rc=$?
if [ $rc -ne 0 ]; then
printf "\r %-36s ✗ FAILED\n" "$label" | tee -a "$LOG_FILE"
exit $rc
fi
local dur=$(( $(date +%s) - start ))
printf "\r %-36s ✓ %dm%02ds\n" "$label" "$(( dur / 60 ))" "$(( dur % 60 ))" | tee -a "$LOG_FILE"
}
check() {
local label="$1" got="$2" expected="$3"
if [ "$got" -eq "$expected" ]; then
printf " %-20s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-20s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
# ─── SCHEMA HELPERS ───────────────────────────────────────────────────────────
# Echoes "name|type" lines for a table from a given DB.
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
# Echoes just column names, one per line.
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
# Returns true (0) if a table exists in the given DB.
table_exists() {
local db="$1" table="$2"
local count
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$table';")
[ "$count" -gt 0 ]
}
# Adds any columns present in source but missing in NAS for the given table.
# Uses ALTER TABLE ADD COLUMN, which is a fast metadata-only operation in SQLite.
ensure_columns() {
local table="$1"
local label="schema: $table"
printf " %-36s " "$label"
if ! table_exists "$DB_SRC" "$table"; then
printf "✗ source missing — skipping\n"
return 0
fi
if ! table_exists "$NAS_DB" "$table"; then
printf "✓ fresh (created above)\n"
return 0
fi
local src_cols
src_cols=$(table_columns_with_types "$DB_SRC" "$table")
local nas_cols
nas_cols=$(table_columns "$NAS_DB" "$table")
local added=0
local added_names=""
while IFS='|' read -r name type; do
[ -z "$name" ] && continue
# Use grep -F -x for fixed-string exact match (safe against regex chars).
if ! echo "$nas_cols" | grep -Fxq "$name"; then
# SQLite ALTER TABLE ADD COLUMN: type is optional, no NOT NULL allowed
# without a constant default. Default-NULL is exactly what we want for archive.
sqlite3 "$NAS_DB" "ALTER TABLE $table ADD COLUMN \"$name\" $type;"
added=$((added + 1))
added_names="$added_names $name"
fi
done <<< "$src_cols"
if [ "$added" -gt 0 ]; then
printf "✓ added %d:%s\n" "$added" "$added_names"
else
printf "✓ in sync\n"
fi
}
# Echoes a comma-separated list of source columns for a table, double-quoted
# so reserved words and hyphens are safe. Used for both INSERT and SELECT sides.
build_col_list() {
local table="$1"
table_columns "$DB_SRC" "$table" | awk 'NF' | sed 's/.*/"&"/' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── DATE ARGUMENT ────────────────────────────────────────────────────────────
if [ "${1:-}" = "today" ]; then
TARGET_DATE=$(date +%Y-%m-%d)
elif [ -n "${1:-}" ]; then
TARGET_DATE="$1"
if ! [[ "$TARGET_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "ERROR: Invalid date format. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-20s %s (%s)\n" "Source DB:" "OK" "$(du -sh "$DB_SRC" | cut -f1)"
if [ ! -d "$NAS_MOUNT" ]; then
log "ERROR: NAS not mounted at $NAS_MOUNT"; exit 1
fi
printf " %-20s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';" 2>/dev/null || echo "0")
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE already has $EXISTING frames in archive — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-20s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-20s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-20s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date
DATA_SRC="$HOME/.screenpipe/data/data/$TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
DATA_SIZE=$(du -sh "$DATA_SRC" | cut -f1)
DATA_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
printf " %-20s %s (%s files, %s)\n" "Data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-20s %s\n" "Data dir:" "not found — skipping file copy"
fi
# ─── DB SYNC ──────────────────────────────────────────────────────────────────
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── COUNT SOURCE ROWS ────────────────────────────────────────────────────
step "Counting source rows for $TARGET_DATE"
SRC_FRAMES=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ELEMENTS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_UI=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
SRC_OCR=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_MEETINGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE';")
printf " %-20s %s\n" "frames:" "$SRC_FRAMES"
printf " %-20s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-20s %s\n" "ui_events:" "$SRC_UI"
printf " %-20s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-20s %s\n" "meetings:" "$SRC_MEETINGS"
if [ "$SRC_FRAMES" -eq "0" ]; then
log "No frames found for $TARGET_DATE — skipping DB sync"
DB_ALREADY_SYNCED=true
fi
fi
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── INIT TABLES ──────────────────────────────────────────────────────────
step "Initialising tables (CREATE IF NOT EXISTS)"
run_sqlite_heredoc "creating tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.frames AS SELECT * FROM main.frames WHERE 0;
CREATE TABLE IF NOT EXISTS nas.elements AS SELECT * FROM main.elements WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ui_events AS SELECT * FROM main.ui_events WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ocr_text AS SELECT * FROM main.ocr_text WHERE 0;
CREATE TABLE IF NOT EXISTS nas.video_chunks AS SELECT * FROM main.video_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.meetings AS SELECT * FROM main.meetings WHERE 0;
DETACH nas;
"
# ─── SCHEMA DRIFT FIX ─────────────────────────────────────────────────────
# Runs AFTER CREATE IF NOT EXISTS so newly-created tables are no-ops here,
# and pre-existing tables get any missing columns added.
step "Reconciling NAS schema with source"
for tbl in "${SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
DETACH nas;
"
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role, frame_id UNINDEXED,
content='elements', content_rowid='id', tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url, id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content,
app_name,
window_title,
element_name,
content='ui_events',
content_rowid='id',
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD EXPLICIT COLUMN LISTS ──────────────────────────────────────────
# After ensure_columns, source ⊆ NAS for every synced table, so source's
# column list is a safe subset to use on both sides of the INSERT.
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
# ─── SYNC DATA ────────────────────────────────────────────────────────────
step "Syncing data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS)
SELECT $VIDEO_CHUNKS_COLS FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS)
SELECT $FRAMES_COLS FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS)
SELECT $OCR_TEXT_COLS FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS)
SELECT $UI_EVENTS_COLS FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS)
SELECT $ELEMENTS_COLS FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS)
SELECT $MEETINGS_COLS FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# NOTE: the elements/ocr_text inserts use unqualified column names while
# selecting from a JOIN. SQLite resolves them against the leftmost table
# that has the column, which is what we want here (e.* / o.* implicitly),
# because none of the column names collide with frames'.
# ─── FTS UPDATE ───────────────────────────────────────────────────────────
step "Updating FTS indexes"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(rowid, text, role)
SELECT e.id, e.text, e.role
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(rowid, full_text, app_name, window_name, browser_url, id)
SELECT id, full_text, app_name, window_name, browser_url, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND full_text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(rowid, text_content, app_name, window_title, element_name)
SELECT id, text_content, app_name, window_title, element_name
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND text_content IS NOT NULL;
DETACH nas;
"
# ─── VERIFY DB ────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start)= '$TARGET_DATE';")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
fi
# ─── COPY DATA FOLDER ─────────────────────────────────────────────────────────
# Always runs regardless of DB sync status
step "Copying data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-36s " "rsync $TARGET_DATE/ → NAS"
rsync -a --ignore-existing \
"$DATA_SRC/" \
"$NAS_DATA/$TARGET_DATE/" \
2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -eq "$SRC_FILES" ]; then
printf "\r %-36s ✓ %dm%02ds (%s files, %s)\n" \
"rsync $TARGET_DATE/ → NAS" \
"$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-36s ✗ %s / %s files\n" \
"rsync $TARGET_DATE/ → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-36s %s\n" "rsync $TARGET_DATE/ → NAS" "skipped (no source dir)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE"
log "========================================"
#!/bin/bash
# screenpipe_sync.sh
# Syncs Screenpipe SQLite data to a NAS archive database (append-only, no deletions).
# Also copies the day's video/frame data folder to the NAS.
#
# Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB,
# the NAS archive gets ALTER TABLE'd to match. Inserts use explicit column lists,
# so positional mismatches can't occur.
#
# Usage:
# ./screenpipe_sync.sh # syncs yesterday (default)
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
#
# Cron example (runs at 3am daily):
# 0 3 * * * /Users/lukas/.screenpipe/screenpipe_sync.sh >> /Users/lukas/.screenpipe/sync.log 2>&1
set -euo pipefail
# ─── CONFIG ───────────────────────────────────────────────────────────────────
DB_SRC="${SCREENPIPE_DB:-$HOME/.screenpipe/db.sqlite}"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/screenpipe}"
NAS_DB="$NAS_MOUNT/archive.db"
NAS_DATA="$NAS_MOUNT/data"
LOG_FILE="$HOME/.screenpipe/sync.log"
# Tables that get schema drift handling. Order matters for FK-ish references
# (parents before children: video_chunks → frames → elements/ocr_text/ui_events).
SYNC_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
# ──────────────────────────────────────────────────────────────────────────────
# ─── HELPERS ──────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
log() {
local msg="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
echo "$msg" | tee -a "$LOG_FILE"
}
step() {
local now=$(date +%s)
local elapsed=$(( now - SCRIPT_START ))
local min=$(( elapsed / 60 ))
local sec=$(( elapsed % 60 ))
printf "\n[+%02dm%02ds] ▶ %s\n" "$min" "$sec" "$*" | tee -a "$LOG_FILE"
}
run_sqlite_heredoc() {
local label="$1"
local sql="$2"
local start=$(date +%s)
printf " %-36s " "$label"
sqlite3 "$DB_SRC" <<< "$sql" &
local pid=$!
local spin=[PASSWORD] '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
local i=0
while kill -0 "$pid" 2>/dev/null; do
printf "\r %-36s %s " "$label" "${spin[$i]}"
i=$(( (i + 1) % 10 ))
sleep 0.2
done
wait "$pid"
local rc=$?
if [ $rc -ne 0 ]; then
printf "\r %-36s ✗ FAILED\n" "$label" | tee -a "$LOG_FILE"
exit $rc
fi
local dur=$(( $(date +%s) - start ))
printf "\r %-36s ✓ %dm%02ds\n" "$label" "$(( dur / 60 ))" "$(( dur % 60 ))" | tee -a "$LOG_FILE"
}
check() {
local label="$1" got="$2" expected="$3"
if [ "$got" -eq "$expected" ]; then
printf " %-20s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-20s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
# ─── SCHEMA HELPERS ───────────────────────────────────────────────────────────
# Echoes "name|type" lines for a table from a given DB.
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
# Echoes just column names, one per line.
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
# Returns true (0) if a table exists in the given DB.
table_exists() {
local db="$1" table="$2"
local count
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='$table';")
[ "$count" -gt 0 ]
}
# Adds any columns present in source but missing in NAS for the given table.
# Uses ALTER TABLE ADD COLUMN, which is a fast metadata-only operation in SQLite.
ensure_columns() {
local table="$1"
local label="schema: $table"
printf " %-36s " "$label"
if ! table_exists "$DB_SRC" "$table"; then
printf "✗ source missing — skipping\n"
return 0
fi
if ! table_exists "$NAS_DB" "$table"; then
printf "✓ fresh (created above)\n"
return 0
fi
local src_cols
src_cols=$(table_columns_with_types "$DB_SRC" "$table")
local nas_cols
nas_cols=$(table_columns "$NAS_DB" "$table")
local added=0
local added_names=""
while IFS='|' read -r name type; do
[ -z "$name" ] && continue
# Use grep -F -x for fixed-string exact match (safe against regex chars).
if ! echo "$nas_cols" | grep -Fxq "$name"; then
# SQLite ALTER TABLE ADD COLUMN: type is optional, no NOT NULL allowed
# without a constant default. Default-NULL is exactly what we want for archive.
sqlite3 "$NAS_DB" "ALTER TABLE $table ADD COLUMN \"$name\" $type;"
added=$((added + 1))
added_names="$added_names $name"
fi
done <<< "$src_cols"
if [ "$added" -gt 0 ]; then
printf "✓ added %d:%s\n" "$added" "$added_names"
else
printf "✓ in sync\n"
fi
}
# Echoes a comma-separated list of source columns for a table, double-quoted
# so reserved words and hyphens are safe. Used for both INSERT and SELECT sides.
build_col_list() {
local table="$1"
table_columns "$DB_SRC" "$table" | awk 'NF' | sed 's/.*/"&"/' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── DATE ARGUMENT ────────────────────────────────────────────────────────────
if [ "${1:-}" = "today" ]; then
TARGET_DATE=$(date +%Y-%m-%d)
elif [ -n "${1:-}" ]; then
TARGET_DATE="$1"
if ! [[ "$TARGET_DATE" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then
echo "ERROR: Invalid date format. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-20s %s (%s)\n" "Source DB:" "OK" "$(du -sh "$DB_SRC" | cut -f1)"
if [ ! -d "$NAS_MOUNT" ]; then
log "ERROR: NAS not mounted at $NAS_MOUNT"; exit 1
fi
printf " %-20s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';" 2>/dev/null || echo "0")
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE already has $EXISTING frames in archive — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-20s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-20s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-20s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date
DATA_SRC="$HOME/.screenpipe/data/data/$TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
DATA_SIZE=$(du -sh "$DATA_SRC" | cut -f1)
DATA_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
printf " %-20s %s (%s files, %s)\n" "Data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-20s %s\n" "Data dir:" "not found — skipping file copy"
fi
# ─── DB SYNC ──────────────────────────────────────────────────────────────────
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── COUNT SOURCE ROWS ────────────────────────────────────────────────────
step "Counting source rows for $TARGET_DATE"
SRC_FRAMES=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ELEMENTS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_UI=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
SRC_OCR=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
SRC_MEETINGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start) = '$TARGET_DATE';")
printf " %-20s %s\n" "frames:" "$SRC_FRAMES"
printf " %-20s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-20s %s\n" "ui_events:" "$SRC_UI"
printf " %-20s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-20s %s\n" "meetings:" "$SRC_MEETINGS"
if [ "$SRC_FRAMES" -eq "0" ]; then
log "No frames found for $TARGET_DATE — skipping DB sync"
DB_ALREADY_SYNCED=true
fi
fi
if [ "$DB_ALREADY_SYNCED" = false ]; then
# ─── INIT TABLES ──────────────────────────────────────────────────────────
step "Initialising tables (CREATE IF NOT EXISTS)"
run_sqlite_heredoc "creating tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.frames AS SELECT * FROM main.frames WHERE 0;
CREATE TABLE IF NOT EXISTS nas.elements AS SELECT * FROM main.elements WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ui_events AS SELECT * FROM main.ui_events WHERE 0;
CREATE TABLE IF NOT EXISTS nas.ocr_text AS SELECT * FROM main.ocr_text WHERE 0;
CREATE TABLE IF NOT EXISTS nas.video_chunks AS SELECT * FROM main.video_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.meetings AS SELECT * FROM main.meetings WHERE 0;
DETACH nas;
"
# ─── SCHEMA DRIFT FIX ─────────────────────────────────────────────────────
# Runs AFTER CREATE IF NOT EXISTS so newly-created tables are no-ops here,
# and pre-existing tables get any missing columns added.
step "Reconciling NAS schema with source"
for tbl in "${SYNC_TABLES[@]}"; do
ensure_columns "$tbl"
done
run_sqlite_heredoc "creating indexes" "
ATTACH '$NAS_DB' AS nas;
CREATE INDEX IF NOT EXISTS nas.idx_frames_timestamp ON frames(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_frames_app_name ON frames(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_window_name ON frames(window_name);
CREATE INDEX IF NOT EXISTS nas.idx_frames_video_chunk_id ON frames(video_chunk_id);
CREATE INDEX IF NOT EXISTS nas.idx_frames_document_path ON frames(document_path) WHERE document_path IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_id ON elements(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_elements_frame_src_role ON elements(frame_id, source, role) WHERE text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_elements_onscreen_frame ON elements(frame_id) WHERE on_screen = 1 AND text IS NOT NULL;
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_timestamp ON ui_events(timestamp);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_app_name ON ui_events(app_name);
CREATE INDEX IF NOT EXISTS nas.idx_ui_events_frame_id ON ui_events(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_ocr_text_frame_id ON ocr_text(frame_id);
CREATE INDEX IF NOT EXISTS nas.idx_meetings_start ON meetings(meeting_start);
CREATE INDEX IF NOT EXISTS nas.idx_video_chunks_device ON video_chunks(device_name);
DETACH nas;
"
run_sqlite_heredoc "creating FTS tables" "
ATTACH '$NAS_DB' AS nas;
CREATE VIRTUAL TABLE IF NOT EXISTS nas.elements_fts USING fts5(
text, role, frame_id UNINDEXED,
content='elements', content_rowid='id', tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.frames_fts USING fts5(
full_text, app_name, window_name, browser_url, id UNINDEXED,
tokenize='unicode61'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.ui_events_fts USING fts5(
text_content,
app_name,
window_title,
element_name,
content='ui_events',
content_rowid='id',
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD EXPLICIT COLUMN LISTS ──────────────────────────────────────────
# After ensure_columns, source ⊆ NAS for every synced table, so source's
# column list is a safe subset to use on both sides of the INSERT.
FRAMES_COLS=$(build_col_list frames)
ELEMENTS_COLS=$(build_col_list elements)
UI_EVENTS_COLS=$(build_col_list ui_events)
OCR_TEXT_COLS=$(build_col_list ocr_text)
VIDEO_CHUNKS_COLS=$(build_col_list video_chunks)
MEETINGS_COLS=$(build_col_list meetings)
# ─── SYNC DATA ────────────────────────────────────────────────────────────
step "Syncing data for $TARGET_DATE"
run_sqlite_heredoc "video_chunks" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.video_chunks ($VIDEO_CHUNKS_COLS)
SELECT $VIDEO_CHUNKS_COLS FROM main.video_chunks
WHERE id IN (
SELECT DISTINCT video_chunk_id FROM main.frames
WHERE date(timestamp) = '$TARGET_DATE' AND video_chunk_id IS NOT NULL
);
DETACH nas;
"
run_sqlite_heredoc "frames ($SRC_FRAMES rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.frames ($FRAMES_COLS)
SELECT $FRAMES_COLS FROM main.frames WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ocr_text ($SRC_OCR rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ocr_text ($OCR_TEXT_COLS)
SELECT $OCR_TEXT_COLS FROM main.ocr_text o
JOIN main.frames f ON o.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "ui_events ($SRC_UI rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.ui_events ($UI_EVENTS_COLS)
SELECT $UI_EVENTS_COLS FROM main.ui_events WHERE date(timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "elements ($SRC_ELEMENTS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.elements ($ELEMENTS_COLS)
SELECT $ELEMENTS_COLS FROM main.elements e
JOIN main.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE';
DETACH nas;
"
run_sqlite_heredoc "meetings ($SRC_MEETINGS rows)" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.meetings ($MEETINGS_COLS)
SELECT $MEETINGS_COLS FROM main.meetings WHERE date(meeting_start) = '$TARGET_DATE';
DETACH nas;
"
# NOTE: the elements/ocr_text inserts use unqualified column names while
# selecting from a JOIN. SQLite resolves them against the leftmost table
# that has the column, which is what we want here (e.* / o.* implicitly),
# because none of the column names collide with frames'.
# ─── FTS UPDATE ───────────────────────────────────────────────────────────
step "Updating FTS indexes"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.elements_fts(rowid, text, role)
SELECT e.id, e.text, e.role
FROM nas.elements e
JOIN nas.frames f ON e.frame_id = f.id
WHERE date(f.timestamp) = '$TARGET_DATE'
AND e.text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.frames_fts(rowid, full_text, app_name, window_name, browser_url, id)
SELECT id, full_text, app_name, window_name, browser_url, id
FROM nas.frames
WHERE date(timestamp) = '$TARGET_DATE'
AND full_text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT INTO nas.ui_events_fts(rowid, text_content, app_name, window_title, element_name)
SELECT id, text_content, app_name, window_title, element_name
FROM nas.ui_events
WHERE date(timestamp) = '$TARGET_DATE'
AND text_content IS NOT NULL;
DETACH nas;
"
# ─── VERIFY DB ────────────────────────────────────────────────────────────
step "Verifying DB"
V_FRAMES=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp) = '$TARGET_DATE';")
V_ELEMENTS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM elements WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_UI=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ui_events WHERE date(timestamp) = '$TARGET_DATE';")
V_OCR=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM ocr_text WHERE frame_id IN (SELECT id FROM frames WHERE date(timestamp) = '$TARGET_DATE');")
V_MEETINGS=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM meetings WHERE date(meeting_start)= '$TARGET_DATE';")
check "frames" "$V_FRAMES" "$SRC_FRAMES"
check "elements" "$V_ELEMENTS" "$SRC_ELEMENTS"
check "ui_events" "$V_UI" "$SRC_UI"
check "ocr_text" "$V_OCR" "$SRC_OCR"
check "meetings" "$V_MEETINGS" "$SRC_MEETINGS"
fi
# ─── COPY DATA FOLDER ─────────────────────────────────────────────────────────
# Always runs regardless of DB sync status
step "Copying data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-36s " "rsync $TARGET_DATE/ → NAS"
rsync -a --ignore-existing \
"$DATA_SRC/" \
"$NAS_DATA/$TARGET_DATE/" \
2>>"$LOG_FILE"
RSYNC_DUR=$(( $(date +%s) - RSYNC_START ))
COPIED_FILES=$(ls "$NAS_DATA/$TARGET_DATE" | wc -l | tr -d ' ')
SRC_FILES=$(ls "$DATA_SRC" | wc -l | tr -d ' ')
COPIED_SIZE=$(du -sh "$NAS_DATA/$TARGET_DATE" | cut -f1)
if [ "$COPIED_FILES" -eq "$SRC_FILES" ]; then
printf "\r %-36s ✓ %dm%02ds (%s files, %s)\n" \
"rsync $TARGET_DATE/ → NAS" \
"$(( RSYNC_DUR / 60 ))" "$(( RSYNC_DUR % 60 ))" \
"$COPIED_FILES" "$COPIED_SIZE" | tee -a "$LOG_FILE"
else
printf "\r %-36s ✗ %s / %s files\n" \
"rsync $TARGET_DATE/ → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-36s %s\n" "rsync $TARGET_DATE/ → NAS" "skipped (no source dir)"
fi
# ─── SUMMARY ──────────────────────────────────────────────────────────────────
TOTAL_ELAPSED=$(( $(date +%s) - SCRIPT_START ))
DB_SIZE=$(du -sh "$NAS_DB" | cut -f1)
echo ""
log "Archive DB size: $DB_SIZE"
log "Total time: $(( TOTAL_ELAPSED / 60 ))m$(( TOTAL_ELAPSED % 60 ))s"
log "Sync complete for $TARGET_DATE"
log "========================================"
Implement and run pipe l…, Editor Group 2
Problems (⇧⌘M)
PROBLEMS
Output (⇧⌘U)
OUTPUT
Debug Console (⇧⌘Y)
DEBUG CONSOLE
Terminal (⌃`)
TERMINAL
Ports - 1 forwarded port
PORTS
1
remote SSH: nas
SSH: nas
screenpipe (Git) - master*, Checkout Branch/Tag...
master*
screenpipe (Git) - Synchronize Changes
No Problems
0
0
Forwarded Ports: 8766
1
Notifications
Sign In
Sign In
No results found
Implement and run pipe logic...
|
Code
|
screenpipe_sync_updated.sh — screenpipe [SSH: nas] screenpipe_sync_updated.sh — screenpipe [SSH: nas] — Untracked...
|
NULL
|
21116
|
|
22251
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
simonw/llm-prices: Prices of various LLMs
simonw/llm-prices: Prices of various LLMs
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Show thinking
Gemini said
Gemini said
Here is a summary of the changes in the provided code diff:
AI Call Scoring Additions
AI Call Scoring Additions
Pre-computed Scores:
Added strict instructions to treat the
ai_call_score
as pre-computed data. The system must interpret and summarize the provided scores rather than inventing or recalculating them.
Context Breakdown:
*
Short Context:
Now displays the
AI Scorecard
name and the overall
AI Score
(the average of per-rule scores).
Full Context:
Details the
ai_call_score
object, breaking down individual rules. Each rule includes a title, prompt, a 1–5 score, a brief justification, and up to three timestamped speaker references (MM:SS).
Application Rules:
Instructs the system to use these scores for coaching summaries, trends, and comparisons, but dictates that it must still rely on the actual transcript for direct quotes.
Grammar and Formatting Cleanup
Grammar and Formatting Cleanup
Typo Corrections:
Fixed minor grammatical errors, such as changing "contexts" to "context" and "utlizes" to "utilizes".
String Formatting:
Cleaned up the markdown link instructions by removing trailing newline and quotation artifacts (
\n"
) from the text strings.
Good response
Bad response
Share & export
Copy
Show more options
Copy prompt
Edit
You said are there some issues and bottlenecks
You said
are there some issues and bottlenecks...
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
github.com/simonw/llm-prices
|
22251
|
|
22252
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
simonw/llm-prices: Prices of various LLMs
simonw/llm-prices: Prices of various LLMs
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand...
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
github.com/simonw/llm-prices
|
22252
|
|
22253
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
simonw/llm-prices: Prices of various LLMs
simonw/llm-prices: Prices of various LLMs
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]
Main menu
New Chat
Open menu for conversation actions.
Conversation with Gemini
Conversation with Gemini
Copy prompt
You said I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected. Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
You said
I’m on page “<tabTitle>JY-20361: Add call scores in Panorama by steliyan-</tabTitle>” with “<selection>@@ -4,7 +4,7 @@ Today is {date_today}.445System instructions:5System instructions:6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.6- You must answer using markdown. Do not use html tags in your response even if requested by the user's question.7-- The contexts includes data for call shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.7+- The context includes data for calls shortlisted through user-applied filters. Note that the calls might not relate to the same deal or not even be by the same sales rep.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.8 - The full call context includes all calls data available while the short call context contains only an overview of the calls that are analysed. Refer to the short call context for quick reference and to see the overall picture.9- The calls are ordered in chronological order. 9- The calls are ordered in chronological order. 10- Here’s how to use the call context:10- Here’s how to use the call context:@@ -16,6 +16,15 @@ System instructions:16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.16 - Frame answers with awareness of the company’s ICP, deal cycle, and sales motion.17 - Evaluate statements or objections based on how the team operates and what success looks like.17 - Evaluate statements or objections based on how the team operates and what success looks like.18 - Position responses in light of known competitors and market dynamics.18 - Position responses in light of known competitors and market dynamics.19+ - AI call score (`ai_call_score` in the full call JSON): When present, treat it as **pre-computed** output from your team’s AI call-scoring pipeline (the same kind of scoring as the `/call/ai-call-scoring` endpoint). **Do not** invent, override, or recalculate scores; interpret and summarize what is given.20+ - **Short call context:** **AI Scorecard** is the name of the scorecard applied to that call; **AI Score** is the **overall** score for that scorecard (the average of its per-rule scores, possibly shown as a decimal).21+ - **Full call context:** The `ai_call_score` object may include `ai_scorecard_name`, `score` (overall for that scorecard), and `ai_scorecard_rules`. For each rule when listed:22+ - `rule_name`: Title of the criterion.23+ - `rule_prompt`: The criterion text that was evaluated.24+ - `score`: Whole number **1–5** measuring how well the call satisfied that criterion against `rule_prompt` (1 = no evidence / not discussed or contrary; 5 = strong, clear evidence on the call).25+ - `justification`: Short rationale grounded in what happened on the call.26+ - `justification_timestamps`: Up to three entries with speaker **name** and **timestamp** (MM:SS in the recording) highlighting where the justification is supported.27+ - Use scores for coaching summaries, trends across calls, or quick comparisons when relevant. For **what was actually said**, still rely on the **transcript** (you may cross-reference rule timestamps when helpful).19- Use the Message History to:28- Use the Message History to:20 - Maintain continuity if the current question refers to previous exchanges.29 - Maintain continuity if the current question refers to previous exchanges.21 - Clarify or resolve ambiguities if the question depends on prior messages.30 - Clarify or resolve ambiguities if the question depends on prior messages.@@ -28,10 +37,10 @@ System instructions:28 - All factual claims must be supported by one or more references to relevant calls.37 - All factual claims must be supported by one or more references to relevant calls.29 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.38 - Use Markdown link syntax ([link text](URL)) and place links inline within sentences. Do not use footnotes, reference sections, or bare URLs. Integrate the reference links directly within the relevant parts of your response, rather than in a separate section.30 - Use markdown links in the format [link text](/playback/{{call_id}})39 - Use markdown links in the format [link text](/playback/{{call_id}})31- - Use descriptive link that utlizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.\n"40+ - Use descriptive link text that utilizes the call name. Do not use call time stamps in the link text even if the link itself contains a time stamp, e.g. use 'Call Name' instead of 'Call Name at 10:00'. Never use call ids as link text.32- - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)\n"41+ - Good example of link text: [Call Name](/playback/1234567890?apFrom=123)33- - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)\n"42+ - Bad example of link text: [Call Name at 12:34](/playback/1234567890?apFrom=123) (do not use call time stamps in the link text)34- - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)\n\n"43+ - Bad example of links: [Call Name on November 12, 2025](/playback/1234567890?apFrom=123) (do not use dates in the link text)35 - You might back your statements with examples from the provided call transcripts in addition to the reference links.44 - You might back your statements with examples from the provided call transcripts in addition to the reference links.36- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.45- Be specific. Use names of accounts, clients and persons involved. Don't just say "one client" or "a client"! If no account is available, use the call title as a reference.37- If the question or context is unclear, request clarification or highlight ambiguities.46- If the question or context is unclear, request clarification or highlight ambiguities.</selection>” selected.
Please summarize the selection using precise and concise language. Use headers and bulleted lists in the summary, to make it scannable. Maintain the meaning and factual accuracy.
Expand
Listen
Show thinking
Gemini said
Gemini said
Here is a summary of the changes in the provided code diff:
AI Call Scoring Additions
AI Call Scoring Additions
Pre-computed Scores:
Added strict instructions to treat the
ai_call_score
as pre-computed data. The system must interpret and summarize the provided scores rather than inventing or recalculating them.
Context Breakdown:
*
Short Context:
Now displays the
AI Scorecard
name and the overall
AI Score
(the average of per-rule scores).
Full Context:
Details the
ai_call_score
object, breaking down individual rules. Each rule includes a title, prompt, a 1–5 score, a brief justification, and up to three timestamped speaker references (MM:SS).
Application Rules:
Instructs the system to use these scores for coaching summaries, trends, and comparisons, but dictates that it must still rely on the actual transcript for direct quotes....
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
github.com/simonw/llm-prices
|
22253
|
|
22254
|
New Tab
New Tab
Jy 20820 es reindex stream model h New Tab
New Tab
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
Jy 20820 es reindex stream model hydration by Vasil-Jiminny · Pull Request #12059 · jiminny/app
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
[JY-20725] [HubSpot] Optimise CRM rematching on delete hubspot accounts/contacts - Jira
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
JY-20725 add HS rate limit handling on activities rematching by LakyLak · Pull Request #12066 · jiminny/app
Pipelines - jiminny/app
Pipelines - jiminny/app
Pull requests · jiminny/app
Pull requests · jiminny/app
[JY-20773] User Pilot not receiving events on report generated - Jira
[JY-20773] User Pilot not receiving events on report generated - Jira
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
JY-20773 fix user pilot tracking ofr automated report generated by LakyLak · Pull Request #12024 · jiminny/app
[JY-20776] Automated report - sentry - Jira
[JY-20776] Automated report - sentry - Jira
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
TypeError: League\Flysystem\Filesystem::has(): Argument #1 ($location) must be of type string, null given, called in /home/jiminny/vendor/laravel/framework/src/Illuminate/Filesystem/FilesystemAdapter.php on line 218 — jiminny — app
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
Platform Sprint 3 Q2 - Platform Team - Scrum Board - Jira
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet
simonw/llm-prices: Prices of various LLMs
simonw/llm-prices: Prices of various LLMs
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close
WORK, Google Account: [EMAIL]...
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
github.com/simonw/llm-prices
|
22254
|
|
22255
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp(allDaily - Platform • 8 m left)100% <2Tue 12 May 9:57:42meet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting, annotating)ChromeFileViewHistory1 SeProfilesTabWindowHelpPrNnry4 AtAt:https://jiminny.atlassian.net/jira/software/c/projects/JY/boards/37© AWS|X) SSH• Home | Salesforce |Platform TeamQ Search board•.Epic vType vQuick filters vREADY FOR DEVINDEV 3CODE REVIEW 2BLOCKEDUpgrade to PHP 8.5PHP 8.5 UPGRADEIn Dev3 П •••=AJ Panorama forCall Scoring in ODAUTOMATED AI SCORINGCode Review2.5 1 =E JY-18091|[ JY-20361|SAI Review - Q1 -Summary/Actionitems/Key PointsGROWTH - MAINTAIN OU...In Dev20.000=(HubSpot] OptimiseCRM rematching ondelete hubspot….PLATFORM STABILITYCode Review[ JY-20566XỐ JY-20725[POC)Jiminny MCPConnectorJIMINNY MCP CONNECTORIn Progress+ Create10 1 •.••=@JY-20625*JinClС мс3 мс$8• Tue 12 May 9:57Jin8 Jie1[л|+Datadog* Claude|3 CircleciSentryD All BookmarksComplete sprintGroup: Queries|QA 2PO ACCEPTANCEDEPLOY 9Smart Instant|Nudge Pre-filteringCOST-EFFECTIVE AND FA...Ready for QA|15 П =AI Reports > Emptypage design andpromotionAJ REPORTSDeployed1 П [PASSWORD_DOTS]=[ JY-20493|Д JY-20372Sync opportunitieswithout a localowner (user_id is..PLATFORM STABILITYIn QAn9 JY-20352Grok via AzureMAINTENANCEDeployed• •••.=Д JY-20726:meet.google.comNikolay Yankov (You, presenti..+7Galya DimitrovaNikolay Nikolov9Stefka Stoyanova4 others9:57 AM | Daily - PlatformLukas Kovalik11:49...
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
NULL
|
22255
|
|
22256
|
HomeActivityFllesLaterMoreFirefoxMIStoMJiminny... HomeActivityFllesLaterMoreFirefoxMIStoMJiminny... ~# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofia-office# support# thank-yous# the_people_of jimi...^? Direct messages. Galya Dimitrova MA. Aneliya Angelova @f Petko Kashinski@ Stefka Stoyanova€. Vasil VasilevCP. Nikolay Ivanov(3) Aneliva Angelova…Stoyan Tanev. Vese. Lukas Kovali... M o::: Apps& Toast4" Jira Cloud(m) Google Cale...© BadKeywordsQueryExcer( ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpE DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen© ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.php© Handler.php(@ HttnRodDoauoctEycontinA HttnRaNDebookmarksProtllesToolsWindow& # engineering• Messages©Canvas20260507)Help8 24e FilesMoreatecfix/npm-circlec APP 9-10 ANO 2 PRs with v Pnerability fixes are ready forreviewPlease take a look at the code and confirmthat everything works properly on yourlocal machine or on a planet environmentPull requests (jiminny/app)• 412049 hx cecurimv: comnoserdependency updates - 2026-05-07(secfix/composer-20260507)• #12048 fix(security): npm dependencyupdates - 2026-05-07 (secfix/npm-20260507)llan Kvuchukov 9.50AMClaude's limits are getang ridiculous. whatare you using from OpenAI? GPT-5.3Codey?e A 5 replies Last reply todayat 9-5...Message #engineering+ Aatroller.onpSautomatNew Tabe Jy 20820 es reindex stream mode[UY-20725] [HubSpot] Optimise CF@ JY-20725 add HS rate limit handlilO'Pipelines - jiminny/app• Pull requests • jiminny/appWY-207731 User Pilot not receivin@JY-20773 fix user pilot tracking ofrNY-20776 Automated report - seTypeError: LeaguelFlysystem|FilesA TypeError: League\Flysystem|FilesPlatform Sprint 3 02 - Platform TeJY-20361: Add call scores in P. Xsimonw/lim-prices: Prices of va X+ New Tab© github.com/simc~ Google Geminiintroduced by these additions. When you ask aLarge Language Model (LLM) to juggle largetranscripts alongside complex JsON scoringobjects, it creates specific friction points.Here is a breakdown of the potential issues andbottlenecks based on the changes:1. Token Consumption and Context Bloat(Bottleneck)• Massive Payload Size: By injecting thefull ai_call_score object into thecontext (which includes rule names,prompts, scores, text justifications, andup to three timestamps per rule), thetoken count per call will skyrocket.• Context Window Limits: The promptstates: "The full call context includes allcalls data available..." If a user filters for 5or 10 calls, injecting the transcripts plusthese extensive Al scoring arrays couldeasily hit the model's maximum contextwindow, leading to truncated data ordegraded recall (the "lost in the middle"effect).? 11 M Inctruction Conflicte (Iccme)• Pre-computed vs. Raw Transcript: Theprompt explicitly instructs the model: "Donot invent, override, or recalculatescores" but later says, "For what was• Enter a prompt for GeminiPro vYour Jiminny chats aren't used to improveJlrcan mite micnkes colcolliale chocktYottr orivaevdoSummarize pagea<40 hil © I Daily - Platform - 8mleft A 100% C/7 &• Tue 12 May 9:57:42OO READMEExample:"orices":"id": "amazon-nova-lite","vendor":amazon","name": "Amazon Nova Lite","input": 0.06,"output":0.24,"input_cached": null,"from_date": null,"to_date": null"id": "amazon-nova-micro","Amazon Nova Micro","1ndut": 0.035."output": 0.14,"input_cached": null,"from_date": null,"to date": nuluHow the data files workThe pricing data is maintained in individual JSON files in the data/ directory, with one file per vendor (e.g., data/anthropic.json, data/openal.json )•Each vendor file has the following structure:"vendor": "anthropic","id": "claude-3.7-sonnet","name": "Claude 3.7 Sonnet","orice history"."input": 3,"output": 15,"inout cachedi: null."from_date": null,"to_date": null...
|
Firefox
|
simonw/llm-prices: Prices of various LLMs — Work
|
NULL
|
22256
|
|
28221
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28221
|
|
28222
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ⠙
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28222
|
|
28223
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠧
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28223
|
|
28224
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠋
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28224
|
|
28225
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠹
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28225
|
|
28226
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠴
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28226
|
|
28237
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠇
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1...
|
iTerm2
|
sqlite3
|
NULL
|
28237
|
|
28238
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠇
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1...
|
iTerm2
|
sqlite3
|
NULL
|
28238
|
|
28239
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠧
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28239
|
|
28240
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠹
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28240
|
|
28241
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠦
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28241
|
|
28242
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠦
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28242
|
|
28243
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠋
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28243
|
|
28244
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠙
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28244
|
|
28378
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ✓ 2m37s
ocr_text (2332 rows) ⠋ Parse error near line 3: ambiguous column name: app_name
acted_at") SELECT "frame_id","text","text_json","app_name","ocr_engine","win
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ rm screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849272
drwxr-xr-x 21 lukas staff 672 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3651 lukas staff 116832 12 May 21:02 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273267 12 May 21:02 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849304
drwxr-xr-x 22 lukas staff 704 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3653 lukas staff 116896 12 May 21:03 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273781 12 May 21:03 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 15135 12 May 21:03 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:25] ========================================
[2026-05-12 21:06:25] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:25] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:25] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:46] ========================================
[2026-05-12 21:06:46] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:46] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:46] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:07:36] ========================================
[2026-05-12 21:07:36] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:07:36] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables, indexes, FTS
creating tables ✓ 0m00s
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m01s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠹
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28378
|
|
28379
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ✓ 2m37s
ocr_text (2332 rows) ⠋ Parse error near line 3: ambiguous column name: app_name
acted_at") SELECT "frame_id","text","text_json","app_name","ocr_engine","win
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ rm screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849272
drwxr-xr-x 21 lukas staff 672 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3651 lukas staff 116832 12 May 21:02 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273267 12 May 21:02 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849304
drwxr-xr-x 22 lukas staff 704 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3653 lukas staff 116896 12 May 21:03 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273781 12 May 21:03 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 15135 12 May 21:03 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:25] ========================================
[2026-05-12 21:06:25] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:25] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:25] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:46] ========================================
[2026-05-12 21:06:46] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:46] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:46] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:07:36] ========================================
[2026-05-12 21:07:36] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:07:36] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables, indexes, FTS
creating tables ✓ 0m00s
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m01s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠏
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28379
|
|
28380
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ✓ 2m37s
ocr_text (2332 rows) ⠋ Parse error near line 3: ambiguous column name: app_name
acted_at") SELECT "frame_id","text","text_json","app_name","ocr_engine","win
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ rm screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849272
drwxr-xr-x 21 lukas staff 672 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3651 lukas staff 116832 12 May 21:02 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273267 12 May 21:02 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849304
drwxr-xr-x 22 lukas staff 704 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3653 lukas staff 116896 12 May 21:03 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273781 12 May 21:03 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 15135 12 May 21:03 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:25] ========================================
[2026-05-12 21:06:25] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:25] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:25] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:46] ========================================
[2026-05-12 21:06:46] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:46] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:46] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:07:36] ========================================
[2026-05-12 21:07:36] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:07:36] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables, indexes, FTS
creating tables ✓ 0m00s
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m01s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠦
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28380
|
|
28381
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ✓ 2m37s
ocr_text (2332 rows) ⠋ Parse error near line 3: ambiguous column name: app_name
acted_at") SELECT "frame_id","text","text_json","app_name","ocr_engine","win
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ rm screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849272
drwxr-xr-x 21 lukas staff 672 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3651 lukas staff 116832 12 May 21:02 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273267 12 May 21:02 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849304
drwxr-xr-x 22 lukas staff 704 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3653 lukas staff 116896 12 May 21:03 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273781 12 May 21:03 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 15135 12 May 21:03 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:25] ========================================
[2026-05-12 21:06:25] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:25] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:25] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:46] ========================================
[2026-05-12 21:06:46] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:46] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:46] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:07:36] ========================================
[2026-05-12 21:07:36] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:07:36] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables, indexes, FTS
creating tables ✓ 0m00s
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m01s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠼
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28381
|
|
28382
|
Last login: Tue May 12 20:12:05 on ttys007
Poetry Last login: Tue May 12 20:12:05 on ttys007
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
Poetry could not find a pyproject.toml file in /Users/lukas/jiminny/app or its parents
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ open ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/jiminny/app (JY-20725-handle-HS-search-rate-limit) $ cd ~/.screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8720288
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3660 lukas staff 117120 12 May 17:41 data
-rw-r--r--@ 1 lukas staff 4457357312 12 May 17:41 db.sqlite
-rw-r--r-- 1 lukas staff 65536 12 May 09:26 db.sqlite-shm
-rw-r--r-- 1 lukas staff 5001712 12 May 17:41 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 245484 12 May 17:39 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 8541 11 May 20:54 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ cat screenpipe_sync.sh | pbcopy
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
^X Exit Help ^V Next Pg
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: ]
^X Exit Help ^V Next Pg
UW PICO 5.09 File: screenpipe_sync.sh Modified
Pico Help Text
Pico is designed to be a simple, easy-to-use text editor with a
layout very similar to the Alpine mailer. The status line at the
top of the display shows pico's version, the current file being
edited and whether or not there are outstanding modifications
that have not been saved. The third line from the bottom is used
to report informational messages and for additional command input.
The bottom two lines list the available editing commands.
Each character typed is automatically inserted into the buffer
at the current cursor position. Editing commands and cursor
movement (besides arrow keys) are given to pico by typing
special control-key sequences. A caret, '^', is used to denote
the control key, sometimes marked "CTRL", so the CTRL-q key
combination is written as ^Q.
The following functions are available in pico (where applicable,
corresponding function key commands are in parentheses).
^G (F1) Display this help text.
^F move Forward a character.
^B move Backward a character.
^P move to the Previous line.
^N move to the Next line.
^A move to the beginning of the current line.
^E move to the End of the current line.
^V (F8) move forward a page of text.
^Y (F7) move backward a page of text.
^W (F6) Search for (where is) text, neglecting case.
^L Refresh the display.
^D Delete the character at the cursor position.
^^ Mark cursor position as beginning of selected text.
Note: Setting mark when already set unselects text.
^K (F9) Cut selected text (displayed in inverse characters).
Note: The selected text's boundary on the cursor side
ends at the left edge of the cursor. So, with
[ Unknown Command: Next Page ]
^X Exit Help ^V Next Pg
[2026-05-12 20:19:37] ========================================
UW PICO 5.09 New Buffer
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] Screenpipe sync starting for: 2026-05-11
UW PICO 5.09 New Buffer
[ Read 695 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[2026-05-12 20:19:37] install_id: 2ff6574c-4272-4dbf-a20b-434b024c65fb
[2026-05-12 20:19:37] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Frame data dir: OK (283 files, 318M)
Audio files: OK (2507 files, 267M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
audio_chunks: 2507
audio_transcriptions: 226
audio_tags: 0
speakers: 15 (all-time)
speaker_embeddings: 58 (all-time)
[+00m01s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating vision tables ✓ 0m00s
creating audio tables ✓ 0m01s
Error: in prepare, no such column: id
S idx_ocr_text_install_pk ON ocr_text(install_id, id);
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ sqlite3 ~/.screenpipe/db.sqlite "PRAGMA table_info(ocr_text);"
0|frame_id|INTEGER|1||0
1|text|TEXT|1||0
2|text_json|TEXT|0||0
3|app_name|TEXT|1|''|0
4|ocr_engine|TEXT|1|'unknown'|0
5|window_name|TEXT|0||0
6|focused|BOOLEAN|0|FALSE|0
7|text_length|INTEGER|0||0
8|sync_id|TEXT|0||0
9|synced_at|DATETIME|0||0
10|redacted_at|INTEGER|0||0
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano --version
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh-bakk
usage: mv [-f | -i | -n] [-hv] source target
mv [-f | -i | -n] [-v] source ... directory
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8801488
drwxr-xr-x 21 lukas staff 672 12 May 09:21 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3613 lukas staff 115616 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4488413184 12 May 20:51 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 15721952 12 May 20:53 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270317 12 May 20:53 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ mv screenpipe_sync.sh screenpipe_sync.sh-bakk
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810840
drwxr-xr-x 21 lukas staff 672 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3615 lukas staff 115680 12 May 20:53 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8810880
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3619 lukas staff 115808 12 May 20:54 data
-rw-r--r--@ 1 lukas staff 4492152832 12 May 20:54 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16615992 12 May 20:54 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 270865 12 May 20:54 screenpipe.2026-05-12.0.log
-rw-r--r-- 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8799288
drwxr-xr-x 22 lukas staff 704 12 May 20:54 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3629 lukas staff 116128 12 May 20:57 data
-rw-r--r--@ 1 lukas staff 4495654912 12 May 20:56 db.sqlite
-rw-r--r--@ 1 lukas staff 32768 12 May 20:21 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 6357192 12 May 20:57 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 271226 12 May 20:56 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 19714 12 May 20:54 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9058 12 May 20:19 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 20:58:45] ========================================
[2026-05-12 20:58:45] Screenpipe sync starting for: 2026-05-11
[2026-05-12 20:58:45] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
UW PICO 5.09 New Buffer
[ New file ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
[+00m00s] ▶ Initialising tables (CREATE IF NOT EXISTS)
creating tables ✓ 0m00s
[+00m00s] ▶ Reconciling NAS schema with source
schema: video_chunks ✓ in sync
schema: frames ✓ in sync
schema: elements ✓ in sync
schema: ocr_text ✓ in sync
schema: ui_events ✓ in sync
schema: meetings ✓ in sync
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m02s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ✓ 2m37s
ocr_text (2332 rows) ⠋ Parse error near line 3: ambiguous column name: app_name
acted_at") SELECT "frame_id","text","text_json","app_name","ocr_engine","win
error here ---^
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ rm screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849272
drwxr-xr-x 21 lukas staff 672 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3651 lukas staff 116832 12 May 21:02 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273267 12 May 21:02 screenpipe.2026-05-12.0.log
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
UW PICO 5.09 New Buffer
[ Read 353 lines ]
^G Get Help ^O WriteOut ^R Read File ^Y Prev Pg ^K Cut Text ^C Cur Pos
^X Exit ^J Justify ^W Where is ^V Next Pg ^U UnCut Text ^T To Spell
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ chmod +x screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ll
total 8849304
drwxr-xr-x 22 lukas staff 704 12 May 21:03 .
drwx------+ 96 lukas staff 3072 12 May 20:12 ..
-rw-r--r--@ 1 lukas staff 6148 12 May 20:14 .DS_Store
-rw-r--r--@ 1 lukas staff 37 11 May 20:54 .sync_install_id
-rw-r--r-- 1 lukas staff 0 10 May 14:43 clipboard-disabled-after-crash
drwxr-xr-x 3653 lukas staff 116896 12 May 21:03 data
-rw-r--r--@ 1 lukas staff 4511797248 12 May 21:03 db.sqlite
-rw-r--r--@ 1 lukas staff 65536 12 May 21:01 db.sqlite-shm
-rw-r--r--@ 1 lukas staff 16665432 12 May 21:03 db.sqlite-wal
drwxr-xr-x 9 lukas staff 288 10 May 11:39 pipes
-rw-r--r-- 1 lukas staff 28408 6 May 21:02 screenpipe.2026-05-06.0.log
-rw-r--r-- 1 lukas staff 566164 7 May 21:50 screenpipe.2026-05-07.0.log
-rw-r--r-- 1 lukas staff 382102 8 May 22:20 screenpipe.2026-05-08.0.log
-rw-r--r-- 1 lukas staff 167023 9 May 23:04 screenpipe.2026-05-09.0.log
-rw-r--r-- 1 lukas staff 88266 10 May 23:51 screenpipe.2026-05-10.0.log
-rw-r--r-- 1 lukas staff 528943 11 May 22:54 screenpipe.2026-05-11.0.log
-rw-r--r-- 1 lukas staff 273781 12 May 21:03 screenpipe.2026-05-12.0.log
-rwxr-xr-x 1 lukas staff 15135 12 May 21:03 screenpipe_sync.sh
-rwxr-xr-x@ 1 lukas staff 32005 11 May 20:54 screenpipe_sync.sh-bakk
-rwxr-xr-x@ 1 lukas staff 14994 6 May 20:26 screenpipe_sync.sh.bak
-rwxr-xr-x@ 1 lukas staff 21485 10 May 13:34 screenpipe_sync.sh.bak2
-rw-r--r--@ 1 lukas staff 9771 12 May 21:01 sync.log
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:25] ========================================
[2026-05-12 21:06:25] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:25] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:25] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:06:46] ========================================
[2026-05-12 21:06:46] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:06:46] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
[2026-05-12 21:06:46] ERROR: NAS not mounted at /Volumes/Test/screenpipe
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ nano screenpipe_sync.sh
lukas@Lukas-Kovaliks-MacBook-Pro-Jiminny ~/.screenpipe $ ~/.screenpipe/screenpipe_sync.sh 2026-05-11
[2026-05-12 21:07:36] ========================================
[2026-05-12 21:07:36] Screenpipe sync starting for: 2026-05-11
[2026-05-12 21:07:36] ========================================
[+00m00s] ▶ Preflight checks
Source DB: OK (4.2G)
NAS mount: OK /Volumes/screenpipe
Archive DB: will be created
Data dir: OK (283 files, 318M)
[+00m00s] ▶ Counting source rows for 2026-05-11
frames: 6857
elements: 672129
ui_events: 7063
ocr_text: 2332
meetings: 1
[+00m00s] ▶ Initialising tables, indexes, FTS
creating tables ✓ 0m00s
creating indexes ✓ 0m01s
creating FTS tables ✓ 0m00s
[+00m01s] ▶ Syncing data for 2026-05-11
video_chunks ✓ 0m00s
frames (6857 rows) ⠙
DOCKER
Close Tab
DEV (-zsh)
Close Tab
APP (screenpipe")
Close Tab
sqlite3
Close Tab
⌥⌘1
sqlite3...
|
iTerm2
|
sqlite3
|
NULL
|
28382
|