|
21072
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1
[ENV_SECRET]
[ENV_SECRET]
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
Info: Setting up SSH Host nas: Setting up SSH tunnel
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
description: "What you did, what's next, and any blockers"
icon: "🏢"
featured: true
---
Based on my recordings from the last 24 hours, generate a standup update. Use limit=10 per search, max 3 searches total.
Read screenpipe skill first.
Use this exact format:
## Yesterday
- What I worked on (name specific projects, files, tools, PRs)
## Today
- What I will work on next (based on unfinished tasks and recent activity)
## Blockers
- Issues I hit — errors, slow builds, waiting on someone
- If no blockers, write "None"
Keep it under 150 words. Copy-paste ready for a team standup.
=== /volume1/screenpipe/pipes/time-breakdown/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Time Breakdown
description: "Where your time went — by app, project, and category"
icon: "⏱"
featured: false
---
Analyze my app usage from today (last 12 hours). Use limit=10 per search, max 4 searches. Prefer /raw_sql with COUNT/GROUP BY queries.
Read screenpipe skill first.
Use this exact format with durations and percentages:
## By Application
- List each app with duration and percentage, sorted by time (e.g. "VS Code: 2h 15min (28%)")
## By Category
- Group into: coding, meetings, browsing, writing, communication, other
- Show hours and percentage per category
## By Project
- Group related activities by project/topic. Name specific repos or tasks.
## Productivity Score
- Calculate: focused_work_hours / total_hours as a percentage
- Focused = coding + writing. Unfocused = browsing + switching.
End with: "**Suggestion:** [one specific change to improve tomorrow's productivity]"
=== /volume1/screenpipe/pipes/video-export/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Export Video Clip
description: "Create a video of your recent screen activity"
icon: "🎬"
featured: false
---
Export a video of my screen activity from the last 5 minutes.
Read screenpipe skill first.
Use the POST /frames/export endpoint with the time range and fps=1.0. Then show me the exported video file path as an inline code block so I can watch it.
If the export is large, suggest a lower fps or shorter time range.
Thinking
Thinking
Now I have a clear picture. The pipes are markdown prompt templates in
/volume1/screenpipe/pipes/
, but there's no code to actually load and run them —
consumers/summary.py
has its own hardcoded prompts instead.
Here's what I'll implement:
consumers/pipes.py
— dynamically loads pipe.md files and runs them via Claude (data injected as system message, pipe body as user prompt)
main.py
— two new endpoints:
GET /api/pipes
and
POST /api/pipes/{pipe_id}/run
cli.py
— a
pipe
command to run pipes from the terminal
Let me build it:
Bash
Check browser_urls signature
IN
cat /volume1/screenpipe/app/consumers/activity.py | grep "def browser_urls" -A 5
Copy code to clipboard
OUT
def browser_urls(
for_date: date | None = None,
app_name: str | None = None,
ts_from: str | None = None,
ts_to: str | None = None,
limit: int = 30,
Write
pipes.py
pipes.py
143 lines
"""Dynamic pipe loader and runner."""
import os
import re
from datetime import date
from pathlib import Path
from typing import Any
def _parse_frontmatter(text: str) -> tuple[dict, str]:
if not text.startswith("---"):
return {}, text
try:
end = text.index("---", 3)
except ValueError:
return {}, text
fm_text = text[3:end].strip()
body = text[end + 3:].strip()
meta: dict = {}
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"')
return meta, body
def load_pipes(pipes_dir: Path) -> list[dict]:
if not pipes_dir.exists():
return []
pipes = []
for pipe_dir in sorted(pipes_dir.iterdir()):
if not pipe_dir.is_dir() or pipe_dir.name.startswith("."):
continue
pipe_md = pipe_dir / "pipe.md"
if not pipe_md.exists():
continue
text = pipe_md.read_text(encoding="utf-8")
meta, body = _parse_frontmatter(text)
if meta.get("enabled", "true").lower() == "false":
continue
body = re.sub(r"Read screenpipe skill first\.?\n?", "", body).strip()
pipes.append({
"id": pipe_dir.name,
"title": meta.get("title", pipe_dir.name),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"schedule": meta.get("schedule", "manual"),
"featured": meta.get("featured", "false").lower() == "true",
"prompt": body,
})
return pipes
def _build_system(d: date, stats: dict) -> str:
from consumers.activity import app_time, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
apps = app_time(d)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(d, limit=20)
url_lines = "\n".join(f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls)
ui_rows = ui_event_summary(d)
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(d, 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(d)
if len(audio) > 3000:
audio = audio[:3000]
return f"""You are analyzing screenpipe data — recordings of a person's computer activity.
## App usage (minutes per app):
{app_lines or '(no app data)'}
## Browser URLs visited:
{url_lines or '(no browser data)'}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_lines or '(no UI event data)'}
## Screen content (sampled every 5 minutes):
{ocr_lines or '(no screen data)'}
## Audio / speech:
{audio or '(no audio data)'}
Date: {d}
Total frames recorded: {stats['total_frames']}"""
def run_pipe(
pipe_id: str,
for_date: date | None = None,
api_key: str | None = None,
pipes_dir: Path | None = None,
) -> dict[str, Any]:
import anthropic
from consumers.activity import summary_stats
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.", "date": str(d)}
_pipes_dir = pipes_dir or Path(os.environ.get("PIPES_DIR", "/data/pipes"))
all_pipes = {p["id"]: p for p in load_pipes(_pipes_dir)}
if pipe_id not in all_pipes:
return {"title": "Error", "content": f"Pipe not found: {pipe_id}", "date": str(d)}
pipe = all_pipes[pipe_id]
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {"title": pipe["title"], "content": f"No screen data found for {d}.", "date": str(d)}
system = _build_system(d, stats)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": pipe["prompt"]}],
)
return {
"title": pipe["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Now add the API endpoints to
main.py
:
Edit
main.py
main.py
Added 1 line
Edit
main.py
main.py
Added 1 line
Now add the pipe endpoints at the end of main.py, before the closing summary section:
Edit
main.py
main.py
Added 14 lines
Now add the CLI command to
cli.py
cli.py
:
Edit
cli.py
cli.py
Added 40 lines
Done. Here's how to run pipes:
How to run pipes
How to run pipes
Via Docker (inside the running container):
Copy code to clipboard
docker exec -it screenpipe-app python cli.py pipe --list
docker exec -it screenpipe-app python cli.py pipe day-recap
docker exec -it screenpipe-app python cli.py pipe standup-update --date yesterday
Via the API
(once the container is running on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST "http://localhost:8766/api/pipes/day-recap/run"
curl -X POST "http://localhost:8766/api/pipes/standup-update/run?d=2026-04-25"
Rebuild and restart
to pick up the changes:
Copy code to clipboard...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21072
|
|
21073
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1
[ENV_SECRET]
[ENV_SECRET]
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
Info: Setting up SSH Host nas: Setting up SSH tunnel
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
description: "What you did, what's next, and any blockers"
icon: "🏢"
featured: true
---
Based on my recordings from the last 24 hours, generate a standup update. Use limit=10 per search, max 3 searches total.
Read screenpipe skill first.
Use this exact format:
## Yesterday
- What I worked on (name specific projects, files, tools, PRs)
## Today
- What I will work on next (based on unfinished tasks and recent activity)
## Blockers
- Issues I hit — errors, slow builds, waiting on someone
- If no blockers, write "None"
Keep it under 150 words. Copy-paste ready for a team standup.
=== /volume1/screenpipe/pipes/time-breakdown/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Time Breakdown
description: "Where your time went — by app, project, and category"
icon: "⏱"
featured: false
---
Analyze my app usage from today (last 12 hours). Use limit=10 per search, max 4 searches. Prefer /raw_sql with COUNT/GROUP BY queries.
Read screenpipe skill first.
Use this exact format with durations and percentages:
## By Application
- List each app with duration and percentage, sorted by time (e.g. "VS Code: 2h 15min (28%)")
## By Category
- Group into: coding, meetings, browsing, writing, communication, other
- Show hours and percentage per category
## By Project
- Group related activities by project/topic. Name specific repos or tasks.
## Productivity Score
- Calculate: focused_work_hours / total_hours as a percentage
- Focused = coding + writing. Unfocused = browsing + switching.
End with: "**Suggestion:** [one specific change to improve tomorrow's productivity]"
=== /volume1/screenpipe/pipes/video-export/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Export Video Clip
description: "Create a video of your recent screen activity"
icon: "🎬"
featured: false
---
Export a video of my screen activity from the last 5 minutes.
Read screenpipe skill first.
Use the POST /frames/export endpoint with the time range and fps=1.0. Then show me the exported video file path as an inline code block so I can watch it.
If the export is large, suggest a lower fps or shorter time range.
Thinking
Thinking
Now I have a clear picture. The pipes are markdown prompt templates in
/volume1/screenpipe/pipes/
, but there's no code to actually load and run them —
consumers/summary.py
has its own hardcoded prompts instead.
Here's what I'll implement:
consumers/pipes.py
— dynamically loads pipe.md files and runs them via Claude (data injected as system message, pipe body as user prompt)
main.py
— two new endpoints:
GET /api/pipes
and
POST /api/pipes/{pipe_id}/run
cli.py
— a
pipe
command to run pipes from the terminal
Let me build it:
Bash
Check browser_urls signature
IN
cat /volume1/screenpipe/app/consumers/activity.py | grep "def browser_urls" -A 5
Copy code to clipboard
OUT
def browser_urls(
for_date: date | None = None,
app_name: str | None = None,
ts_from: str | None = None,
ts_to: str | None = None,
limit: int = 30,
Write
pipes.py
pipes.py
143 lines
"""Dynamic pipe loader and runner."""
import os
import re
from datetime import date
from pathlib import Path
from typing import Any
def _parse_frontmatter(text: str) -> tuple[dict, str]:
if not text.startswith("---"):
return {}, text
try:
end = text.index("---", 3)
except ValueError:
return {}, text
fm_text = text[3:end].strip()
body = text[end + 3:].strip()
meta: dict = {}
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"')
return meta, body
def load_pipes(pipes_dir: Path) -> list[dict]:
if not pipes_dir.exists():
return []
pipes = []
for pipe_dir in sorted(pipes_dir.iterdir()):
if not pipe_dir.is_dir() or pipe_dir.name.startswith("."):
continue
pipe_md = pipe_dir / "pipe.md"
if not pipe_md.exists():
continue
text = pipe_md.read_text(encoding="utf-8")
meta, body = _parse_frontmatter(text)
if meta.get("enabled", "true").lower() == "false":
continue
body = re.sub(r"Read screenpipe skill first\.?\n?", "", body).strip()
pipes.append({
"id": pipe_dir.name,
"title": meta.get("title", pipe_dir.name),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"schedule": meta.get("schedule", "manual"),
"featured": meta.get("featured", "false").lower() == "true",
"prompt": body,
})
return pipes
def _build_system(d: date, stats: dict) -> str:
from consumers.activity import app_time, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
apps = app_time(d)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(d, limit=20)
url_lines = "\n".join(f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls)
ui_rows = ui_event_summary(d)
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(d, 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(d)
if len(audio) > 3000:
audio = audio[:3000]
return f"""You are analyzing screenpipe data — recordings of a person's computer activity.
## App usage (minutes per app):
{app_lines or '(no app data)'}
## Browser URLs visited:
{url_lines or '(no browser data)'}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_lines or '(no UI event data)'}
## Screen content (sampled every 5 minutes):
{ocr_lines or '(no screen data)'}
## Audio / speech:
{audio or '(no audio data)'}
Date: {d}
Total frames recorded: {stats['total_frames']}"""
def run_pipe(
pipe_id: str,
for_date: date | None = None,
api_key: str | None = None,
pipes_dir: Path | None = None,
) -> dict[str, Any]:
import anthropic
from consumers.activity import summary_stats
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.", "date": str(d)}
_pipes_dir = pipes_dir or Path(os.environ.get("PIPES_DIR", "/data/pipes"))
all_pipes = {p["id"]: p for p in load_pipes(_pipes_dir)}
if pipe_id not in all_pipes:
return {"title": "Error", "content": f"Pipe not found: {pipe_id}", "date": str(d)}
pipe = all_pipes[pipe_id]
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {"title": pipe["title"], "content": f"No screen data found for {d}.", "date": str(d)}
system = _build_system(d, stats)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": pipe["prompt"]}],
)
return {
"title": pipe["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Now add the API endpoints to
main.py
:
Edit
main.py
main.py
Added 1 line
Edit
main.py
main.py
Added 1 line
Now add the pipe endpoints at the end of main.py, before the closing summary section:
Edit
main.py
main.py
Added 14 lines
Now add the CLI command to
cli.py
cli.py
:
Edit
cli.py
cli.py
Added 40 lines
Done. Here's how to run pipes:
How to run pipes
How to run pipes
Via Docker (inside the running container):
Copy code to clipboard
docker exec -it screenpipe-app python cli.py pipe --list
docker exec -it screenpipe-app python cli.py pipe day-recap
docker exec -it screenpipe-app python cli.py pipe standup-update --date yesterday
Via the API
(once the container is running on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST "http://localhost:8766/api/pipes/day-recap/run"
curl -X POST "http://localhost:8766/api/pipes/standup-update/run?d=2026-04-25"
Rebuild and restart
to pick up the changes:
Copy code to clipboard
cd /volume1/screenpipe/app
docker compose up --build -d
How it works:
Each
pipe.md
file is loaded dynamically — the frontmatter provides metadata and the body becomes the user prompt. Screenpipe data (app usage, browser URLs, OCR, audio) is injected as a Claude system message so the pipe prompt can reference it naturally. The "Read screenpipe skill first" line is stripped automatically.
The pipes directory inside Docker is
/data/pipes
(since
/volume1/screenpipe
→
/data
). You can override it with a
PIPES_DIR
env var if needed.
Message actions
pipe.md
pipe.md
where to place api key and can I use different keys
Thinking
Thinking
Bash
Read .env file
IN
cat /volume1/screenpipe/app/.env
Copy code to clipboard
OUT
[ENV_SECRET]
Where to place the key:
app/.env
app/.env
— replace
your_key_here
with your actual key:
Copy code to clipboard
[ENV_SECRET]
That file is loaded by
load_dotenv()
at startup and is excluded from git via
.gitignore
.
Can you use different keys per pipe?
Not currently — all pipes share the one key from
.env
. But you can add per-pipe key support by adding an
api_key
field to the pipe's frontmatter:
Copy code to clipboard
---
title: Meeting Summary
api_key_env: ANTHROPIC_KEY_MEETINGS
---
Then in
consumers/pipes.py
consumers/pipes.py
, read it in
_parse_frontmatter
and pass it through
run_pipe
. Want me to add that?
Message actions
pipe.md
pipe.md
can I use mistral or other llm? or is is build for antrophic only
Thinking
Thinking
Currently it's Anthropic-only — the code calls the
anthropic
SDK directly in both
consumers/pipes.py
consumers/pipes.py
and
consumers/summary.py
consumers/summary.py
.
Adding other providers is straightforward since Mistral, Ollama, OpenRouter, Groq, and most modern LLMs expose an OpenAI-compatible API. The cleanest approach: add the
openai
package and let you configure the provider via
.env
:
Copy code to clipboard
# Anthropic (default)
[ENV_SECRET]
# OR: any OpenAI-compatible provider
LLM_PROVIDER=openai-compatible
LLM_BASE_URL=https://api.mistral.ai/v1
[ENV_SECRET]
LLM_MODEL=mistral-small-latest
This would cover Mistral, Ollama (
http://localhost:11434/v1
), OpenRouter, Groq, etc. with one code path.
Want me to implement that? It's a small change — a
_call_llm(system, user_prompt)
helper in
pipes.py
that branches on
LLM_PROVIDER...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21073
|
|
21074
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1
[ENV_SECRET]
[ENV_SECRET]
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
Info: Setting up SSH Host nas: Setting up SSH tunnel
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
description: "What you did, what's next, and any blockers"
icon: "🏢"
featured: true
---
Based on my recordings from the last 24 hours, generate a standup update. Use limit=10 per search, max 3 searches total.
Read screenpipe skill first.
Use this exact format:
## Yesterday
- What I worked on (name specific projects, files, tools, PRs)
## Today
- What I will work on next (based on unfinished tasks and recent activity)
## Blockers
- Issues I hit — errors, slow builds, waiting on someone
- If no blockers, write "None"
Keep it under 150 words. Copy-paste ready for a team standup.
=== /volume1/screenpipe/pipes/time-breakdown/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Time Breakdown
description: "Where your time went — by app, project, and category"
icon: "⏱"
featured: false
---
Analyze my app usage from today (last 12 hours). Use limit=10 per search, max 4 searches. Prefer /raw_sql with COUNT/GROUP BY queries.
Read screenpipe skill first.
Use this exact format with durations and percentages:
## By Application
- List each app with duration and percentage, sorted by time (e.g. "VS Code: 2h 15min (28%)")
## By Category
- Group into: coding, meetings, browsing, writing, communication, other
- Show hours and percentage per category
## By Project
- Group related activities by project/topic. Name specific repos or tasks.
## Productivity Score
- Calculate: focused_work_hours / total_hours as a percentage
- Focused = coding + writing. Unfocused = browsing + switching.
End with: "**Suggestion:** [one specific change to improve tomorrow's productivity]"
=== /volume1/screenpipe/pipes/video-export/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Export Video Clip
description: "Create a video of your recent screen activity"
icon: "🎬"
featured: false
---
Export a video of my screen activity from the last 5 minutes.
Read screenpipe skill first.
Use the POST /frames/export endpoint with the time range and fps=1.0. Then show me the exported video file path as an inline code block so I can watch it.
If the export is large, suggest a lower fps or shorter time range.
Thinking
Thinking
Now I have a clear picture. The pipes are markdown prompt templates in
/volume1/screenpipe/pipes/
, but there's no code to actually load and run them —
consumers/summary.py
has its own hardcoded prompts instead.
Here's what I'll implement:
consumers/pipes.py
— dynamically loads pipe.md files and runs them via Claude (data injected as system message, pipe body as user prompt)
main.py
— two new endpoints:
GET /api/pipes
and
POST /api/pipes/{pipe_id}/run
cli.py
— a
pipe
command to run pipes from the terminal
Let me build it:
Bash
Check browser_urls signature
IN
cat /volume1/screenpipe/app/consumers/activity.py | grep "def browser_urls" -A 5
Copy code to clipboard
OUT
def browser_urls(
for_date: date | None = None,
app_name: str | None = None,
ts_from: str | None = None,
ts_to: str | None = None,
limit: int = 30,
Write
pipes.py
pipes.py
143 lines
"""Dynamic pipe loader and runner."""
import os
import re
from datetime import date
from pathlib import Path
from typing import Any
def _parse_frontmatter(text: str) -> tuple[dict, str]:
if not text.startswith("---"):
return {}, text
try:
end = text.index("---", 3)
except ValueError:
return {}, text
fm_text = text[3:end].strip()
body = text[end + 3:].strip()
meta: dict = {}
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"')
return meta, body
def load_pipes(pipes_dir: Path) -> list[dict]:
if not pipes_dir.exists():
return []
pipes = []
for pipe_dir in sorted(pipes_dir.iterdir()):
if not pipe_dir.is_dir() or pipe_dir.name.startswith("."):
continue
pipe_md = pipe_dir / "pipe.md"
if not pipe_md.exists():
continue
text = pipe_md.read_text(encoding="utf-8")
meta, body = _parse_frontmatter(text)
if meta.get("enabled", "true").lower() == "false":
continue
body = re.sub(r"Read screenpipe skill first\.?\n?", "", body).strip()
pipes.append({
"id": pipe_dir.name,
"title": meta.get("title", pipe_dir.name),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"schedule": meta.get("schedule", "manual"),
"featured": meta.get("featured", "false").lower() == "true",
"prompt": body,
})
return pipes
def _build_system(d: date, stats: dict) -> str:
from consumers.activity import app_time, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
apps = app_time(d)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(d, limit=20)
url_lines = "\n".join(f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls)
ui_rows = ui_event_summary(d)
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(d, 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(d)
if len(audio) > 3000:
audio = audio[:3000]
return f"""You are analyzing screenpipe data — recordings of a person's computer activity.
## App usage (minutes per app):
{app_lines or '(no app data)'}
## Browser URLs visited:
{url_lines or '(no browser data)'}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_lines or '(no UI event data)'}
## Screen content (sampled every 5 minutes):
{ocr_lines or '(no screen data)'}
## Audio / speech:
{audio or '(no audio data)'}
Date: {d}
Total frames recorded: {stats['total_frames']}"""
def run_pipe(
pipe_id: str,
for_date: date | None = None,
api_key: str | None = None,
pipes_dir: Path | None = None,
) -> dict[str, Any]:
import anthropic
from consumers.activity import summary_stats
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.", "date": str(d)}
_pipes_dir = pipes_dir or Path(os.environ.get("PIPES_DIR", "/data/pipes"))
all_pipes = {p["id"]: p for p in load_pipes(_pipes_dir)}
if pipe_id not in all_pipes:
return {"title": "Error", "content": f"Pipe not found: {pipe_id}", "date": str(d)}
pipe = all_pipes[pipe_id]
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {"title": pipe["title"], "content": f"No screen data found for {d}.", "date": str(d)}
system = _build_system(d, stats)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": pipe["prompt"]}],
)
return {
"title": pipe["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Now add the API endpoints to
main.py
:
Edit
main.py
main.py
Added 1 line
Edit
main.py
main.py
Added 1 line
Now add the pipe endpoints at the end of main.py, before the closing summary section:
Edit
main.py
main.py
Added 14 lines
Now add the CLI command to
cli.py
cli.py
:
Edit
cli.py
cli.py
Added 40 lines
Done. Here's how to run pipes:
How to run pipes
How to run pipes
Via Docker (inside the running container):
Copy code to clipboard
docker exec -it screenpipe-app python cli.py pipe --list
docker exec -it screenpipe-app python cli.py pipe day-recap
docker exec -it screenpipe-app python cli.py pipe standup-update --date yesterday
Via the API
(once the container is running on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST "http://localhost:8766/api/pipes/day-recap/run"
curl -X POST "http://localhost:8766/api/pipes/standup-update/run?d=2026-04-25"
Rebuild and restart
to pick up the changes:
Copy code to clipboard
cd /volume1/screenpipe/app
docker compose up --build -d
How it works:
Each
pipe.md
file is loaded dynamically — the frontmatter provides metadata and the body becomes the user prompt. Screenpipe data (app usage, browser URLs, OCR, audio) is injected as a Claude system message so the pipe prompt can reference it naturally. The "Read screenpipe skill first" line is stripped automatically.
The pipes directory inside Docker is
/data/pipes
(since
/volume1/screenpipe
→
/data
). You can override it with a
PIPES_DIR
env var if needed.
Message actions
pipe.md
pipe.md
where to place api key and can I use different keys
Thinking
Thinking
Bash
Read .env file
IN
cat /volume1/screenpipe/app/.env
Copy code to clipboard
OUT
[ENV_SECRET]
Where to place the key:
app/.env
app/.env
— replace
your_key_here
with your actual key:
Copy code to clipboard
[ENV_SECRET]
That file is loaded by
load_dotenv()
at startup and is excluded from git via
.gitignore
.
Can you use different keys per pipe?
Not currently — all pipes share the one key from
.env
. But you can add per-pipe key support by adding an
api_key
field to the pipe's frontmatter:
Copy code to clipboard
---
title: Meeting Summary
api_key_env: ANTHROPIC_KEY_MEETINGS
---
Then in
consumers/pipes.py
consumers/pipes.py
, read it in...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21074
|
|
21075
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21075
|
|
21076
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1
[ENV_SECRET]
[ENV_SECRET]
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
Info: Setting up SSH Host nas: Setting up SSH tunnel
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
description: "What you did, what's next, and any blockers"
icon: "🏢"
featured: true
---
Based on my recordings from the last 24 hours, generate a standup update. Use limit=10 per search, max 3 searches total.
Read screenpipe skill first.
Use this exact format:
## Yesterday
- What I worked on (name specific projects, files, tools, PRs)
## Today
- What I will work on next (based on unfinished tasks and recent activity)
## Blockers
- Issues I hit — errors, slow builds, waiting on someone
- If no blockers, write "None"
Keep it under 150 words. Copy-paste ready for a team standup.
=== /volume1/screenpipe/pipes/time-breakdown/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Time Breakdown
description: "Where your time went — by app, project, and category"
icon: "⏱"
featured: false
---
Analyze my app usage from today (last 12 hours). Use limit=10 per search, max 4 searches. Prefer /raw_sql with COUNT/GROUP BY queries.
Read screenpipe skill first.
Use this exact format with durations and percentages:
## By Application
- List each app with duration and percentage, sorted by time (e.g. "VS Code: 2h 15min (28%)")
## By Category
- Group into: coding, meetings, browsing, writing, communication, other
- Show hours and percentage per category
## By Project
- Group related activities by project/topic. Name specific repos or tasks.
## Productivity Score
- Calculate: focused_work_hours / total_hours as a percentage
- Focused = coding + writing. Unfocused = browsing + switching.
End with: "**Suggestion:** [one specific change to improve tomorrow's productivity]"
=== /volume1/screenpipe/pipes/video-export/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Export Video Clip
description: "Create a video of your recent screen activity"
icon: "🎬"
featured: false
---
Export a video of my screen activity from the last 5 minutes.
Read screenpipe skill first.
Use the POST /frames/export endpoint with the time range and fps=1.0. Then show me the exported video file path as an inline code block so I can watch it.
If the export is large, suggest a lower fps or shorter time range.
Thinking
Thinking
Now I have a clear picture. The pipes are markdown prompt templates in
/volume1/screenpipe/pipes/
, but there's no code to actually load and run them —
consumers/summary.py
has its own hardcoded prompts instead.
Here's what I'll implement:
consumers/pipes.py
— dynamically loads pipe.md files and runs them via Claude (data injected as system message, pipe body as user prompt)
main.py
— two new endpoints:
GET /api/pipes
and
POST /api/pipes/{pipe_id}/run
cli.py
— a
pipe
command to run pipes from the terminal
Let me build it:
Bash
Check browser_urls signature
IN
cat /volume1/screenpipe/app/consumers/activity.py | grep "def browser_urls" -A 5
Copy code to clipboard
OUT
def browser_urls(
for_date: date | None = None,
app_name: str | None = None,
ts_from: str | None = None,
ts_to: str | None = None,
limit: int = 30,
Write
pipes.py
pipes.py
143 lines
"""Dynamic pipe loader and runner."""
import os
import re
from datetime import date
from pathlib import Path
from typing import Any
def _parse_frontmatter(text: str) -> tuple[dict, str]:
if not text.startswith("---"):
return {}, text
try:
end = text.index("---", 3)
except ValueError:
return {}, text
fm_text = text[3:end].strip()
body = text[end + 3:].strip()
meta: dict = {}
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"')
return meta, body
def load_pipes(pipes_dir: Path) -> list[dict]:
if not pipes_dir.exists():
return []
pipes = []
for pipe_dir in sorted(pipes_dir.iterdir()):
if not pipe_dir.is_dir() or pipe_dir.name.startswith("."):
continue
pipe_md = pipe_dir / "pipe.md"
if not pipe_md.exists():
continue
text = pipe_md.read_text(encoding="utf-8")
meta, body = _parse_frontmatter(text)
if meta.get("enabled", "true").lower() == "false":
continue
body = re.sub(r"Read screenpipe skill first\.?\n?", "", body).strip()
pipes.append({
"id": pipe_dir.name,
"title": meta.get("title", pipe_dir.name),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"schedule": meta.get("schedule", "manual"),
"featured": meta.get("featured", "false").lower() == "true",
"prompt": body,
})
return pipes
def _build_system(d: date, stats: dict) -> str:
from consumers.activity import app_time, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
apps = app_time(d)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(d, limit=20)
url_lines = "\n".join(f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls)
ui_rows = ui_event_summary(d)
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(d, 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(d)
if len(audio) > 3000:
audio = audio[:3000]
return f"""You are analyzing screenpipe data — recordings of a person's computer activity.
## App usage (minutes per app):
{app_lines or '(no app data)'}
## Browser URLs visited:
{url_lines or '(no browser data)'}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_lines or '(no UI event data)'}
## Screen content (sampled every 5 minutes):
{ocr_lines or '(no screen data)'}
## Audio / speech:
{audio or '(no audio data)'}
Date: {d}
Total frames recorded: {stats['total_frames']}"""
def run_pipe(
pipe_id: str,
for_date: date | None = None,
api_key: str | None = None,
pipes_dir: Path | None = None,
) -> dict[str, Any]:
import anthropic
from consumers.activity import summary_stats
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.", "date": str(d)}
_pipes_dir = pipes_dir or Path(os.environ.get("PIPES_DIR", "/data/pipes"))
all_pipes = {p["id"]: p for p in load_pipes(_pipes_dir)}
if pipe_id not in all_pipes:
return {"title": "Error", "content": f"Pipe not found: {pipe_id}", "date": str(d)}
pipe = all_pipes[pipe_id]
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {"title": pipe["title"], "content": f"No screen data found for {d}.", "date": str(d)}
system = _build_system(d, stats)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": pipe["prompt"]}],
)
return {
"title": pipe["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Now add the API endpoints to
main.py
:
Edit
main.py
main.py
Added 1 line
Edit
main.py
main.py
Added 1 line
Now add the pipe endpoints at the end of main.py, before the closing summary section:
Edit
main.py
main.py
Added 14 lines
Now add the CLI command to
cli.py
cli.py
:
Edit
cli.py
cli.py
Added 40 lines
Done. Here's how to run pipes:
How to run pipes
How to run pipes
Via Docker (inside the running container):
Copy code to clipboard
docker exec -it screenpipe-app python cli.py pipe --list
docker exec -it screenpipe-app python cli.py pipe day-recap
docker exec -it screenpipe-app python cli.py pipe standup-update --date yesterday
Via the API
(once the container is running on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST "http://localhost:8766/api/pipes/day-recap/run"
curl -X POST "http://localhost:8766/api/pipes/standup-update/run?d=2026-04-25"
Rebuild and restart
to pick up the changes:
Copy code to clipboard
cd /volume1/screenpipe/app
docker compose up --build -d
How it works:
Each
pipe.md
file is loaded dynamically — the frontmatter provides metadata and the body becomes the user prompt. Screenpipe data (app usage, browser URLs, OCR, audio) is injected as a Claude system message so the pipe prompt can reference it naturally. The "Read screenpipe skill first" line is stripped automatically.
The pipes directory inside Docker is
/data/pipes
(since
/volume1/screenpipe
→
/data
). You can override it with a
PIPES_DIR
env var if needed.
Message actions
pipe.md
pipe.md
where to place api key and can I use different keys
Thinking
Thinking
Bash
Read .env file
IN
cat /volume1/screenpipe/app/.env
Copy code to clipboard
OUT
[ENV_SECRET]
Where to place the key:
app/.env
app/.env
— replace
your_key_here
with your actual key:
Copy code to clipboard
[ENV_SECRET]
That file is loaded by
load_dotenv()
at startup and is excluded from git via
.gitignore
.
Can you use different keys per pipe?
Not currently — all pipes share the one key from
.env
. But you can add per-pipe key support by adding an
api_key
field to the pipe's frontmatter:
Copy code to clipboard
---
title: Meeting Summary
api_key_env: ANTHROPIC_KEY_MEETINGS
---
Then in
consumers/pipes.py
consumers/pipes.py
, read it in
_parse_frontmatter
and pass it through
run_pipe
. Want me to add that?
Message actions
pipe.md
pipe.md
can I use mistral or other llm? or is is build for antrophic only
Thinking
Thinking
Currently it's Anthropic-only — the code calls the
anthropic
SDK directly in both
consumers/pipes.py
consumers/pipes.py
and
consumers/summary.py
consumers/summary.py
.
Adding other providers is straightforward since Mistral, Ollama, OpenRouter, Groq, and most modern LLMs expose an OpenAI-compatible API. The cleanest approach: add the
openai
package and let you configure the provider via
.env
:
Copy code to clipboard
# Anthropic (default)
[ENV_SECRET]
# OR: any OpenAI-compatible provider
LLM_PROVIDER=openai-compatible
LLM_BASE_URL=https://api.mistral.ai/v1
[ENV_SECRET]
LLM_MODEL=mistral-small-latest
This would cover Mistral, Ollama (
http://localhost:11434/v1
), OpenRouter, Groq, etc. with one code path.
Want me to implement that? It's a small change — a
_call_llm(system, user_prompt)
helper in
pipes.py
that branches on
LLM_PROVIDER
, and
openai
added to
requirements.txt
.
Message actions
lets test it with anthropic first, how to run pipe
Thinking
Thinking
Bash
Check if container is running
IN
cd /volume1/screenpipe/app && docker compose ps 2>/dev/null || docker ps --filter name=screenpipe
Copy code to clipboard
OUT
Exit code 1
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.45/containers/json?filters=%7B%22name%22%3A%7B%22screenpipe%22%3Atrue%7D%7D": dial unix /var/run/docker.sock: connect: permission denied
I don't have Docker socket access, so you'll need to run these yourself. Here are your options:
Option A — via Docker (if container is already running):
Copy code to clipboard
# List available pipes
docker exec -it screenpipe-app python cli.py pipe --list
# Run a specific pipe
docker exec -it screenpipe-app python cli.py pipe day-recap
Option B — rebuild first (since we added new files), then run:
Copy code to clipboard
cd /volume1/screenpipe/app
docker compose up --build -d
docker exec -it screenpipe-app python cli.py pipe day-recap
Option C — via the API
(if container is up on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST http://localhost:8766/api/pipes/day-recap/run
The...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21076
|
|
21077
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21077
|
|
21078
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1
[ENV_SECRET]
[ENV_SECRET]
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
Info: Setting up SSH Host nas: Setting up SSH tunnel
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
description: "What you did, what's next, and any blockers"
icon: "🏢"
featured: true
---
Based on my recordings from the last 24 hours, generate a standup update. Use limit=10 per search, max 3 searches total.
Read screenpipe skill first.
Use this exact format:
## Yesterday
- What I worked on (name specific projects, files, tools, PRs)
## Today
- What I will work on next (based on unfinished tasks and recent activity)
## Blockers
- Issues I hit — errors, slow builds, waiting on someone
- If no blockers, write "None"
Keep it under 150 words. Copy-paste ready for a team standup.
=== /volume1/screenpipe/pipes/time-breakdown/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Time Breakdown
description: "Where your time went — by app, project, and category"
icon: "⏱"
featured: false
---
Analyze my app usage from today (last 12 hours). Use limit=10 per search, max 4 searches. Prefer /raw_sql with COUNT/GROUP BY queries.
Read screenpipe skill first.
Use this exact format with durations and percentages:
## By Application
- List each app with duration and percentage, sorted by time (e.g. "VS Code: 2h 15min (28%)")
## By Category
- Group into: coding, meetings, browsing, writing, communication, other
- Show hours and percentage per category
## By Project
- Group related activities by project/topic. Name specific repos or tasks.
## Productivity Score
- Calculate: focused_work_hours / total_hours as a percentage
- Focused = coding + writing. Unfocused = browsing + switching.
End with: "**Suggestion:** [one specific change to improve tomorrow's productivity]"
=== /volume1/screenpipe/pipes/video-export/pipe.md ===
---
schedule: manual
enabled: true
template: true
title: Export Video Clip
description: "Create a video of your recent screen activity"
icon: "🎬"
featured: false
---
Export a video of my screen activity from the last 5 minutes.
Read screenpipe skill first.
Use the POST /frames/export endpoint with the time range and fps=1.0. Then show me the exported video file path as an inline code block so I can watch it.
If the export is large, suggest a lower fps or shorter time range.
Thinking
Thinking
Now I have a clear picture. The pipes are markdown prompt templates in
/volume1/screenpipe/pipes/
, but there's no code to actually load and run them —
consumers/summary.py
has its own hardcoded prompts instead.
Here's what I'll implement:
consumers/pipes.py
— dynamically loads pipe.md files and runs them via Claude (data injected as system message, pipe body as user prompt)
main.py
— two new endpoints:
GET /api/pipes
and
POST /api/pipes/{pipe_id}/run
cli.py
— a
pipe
command to run pipes from the terminal
Let me build it:
Bash
Check browser_urls signature
IN
cat /volume1/screenpipe/app/consumers/activity.py | grep "def browser_urls" -A 5
Copy code to clipboard
OUT
def browser_urls(
for_date: date | None = None,
app_name: str | None = None,
ts_from: str | None = None,
ts_to: str | None = None,
limit: int = 30,
Write
pipes.py
pipes.py
143 lines
"""Dynamic pipe loader and runner."""
import os
import re
from datetime import date
from pathlib import Path
from typing import Any
def _parse_frontmatter(text: str) -> tuple[dict, str]:
if not text.startswith("---"):
return {}, text
try:
end = text.index("---", 3)
except ValueError:
return {}, text
fm_text = text[3:end].strip()
body = text[end + 3:].strip()
meta: dict = {}
for line in fm_text.splitlines():
if ":" in line:
k, _, v = line.partition(":")
meta[k.strip()] = v.strip().strip('"')
return meta, body
def load_pipes(pipes_dir: Path) -> list[dict]:
if not pipes_dir.exists():
return []
pipes = []
for pipe_dir in sorted(pipes_dir.iterdir()):
if not pipe_dir.is_dir() or pipe_dir.name.startswith("."):
continue
pipe_md = pipe_dir / "pipe.md"
if not pipe_md.exists():
continue
text = pipe_md.read_text(encoding="utf-8")
meta, body = _parse_frontmatter(text)
if meta.get("enabled", "true").lower() == "false":
continue
body = re.sub(r"Read screenpipe skill first\.?\n?", "", body).strip()
pipes.append({
"id": pipe_dir.name,
"title": meta.get("title", pipe_dir.name),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"schedule": meta.get("schedule", "manual"),
"featured": meta.get("featured", "false").lower() == "true",
"prompt": body,
})
return pipes
def _build_system(d: date, stats: dict) -> str:
from consumers.activity import app_time, browser_urls, ui_event_summary
from consumers.ocr import deduplicated_text
from consumers.audio import formatted_transcript
apps = app_time(d)
app_lines = "\n".join(f"- {a['app_name']}: {a['minutes']} min" for a in apps[:20])
urls = browser_urls(d, limit=20)
url_lines = "\n".join(f"- {u['browser_url'][:100]} ({u['minutes']} min)" for u in urls)
ui_rows = ui_event_summary(d)
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(d, 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(d)
if len(audio) > 3000:
audio = audio[:3000]
return f"""You are analyzing screenpipe data — recordings of a person's computer activity.
## App usage (minutes per app):
{app_lines or '(no app data)'}
## Browser URLs visited:
{url_lines or '(no browser data)'}
## UI activity per app (clicks / keystrokes / app-switches):
{ui_lines or '(no UI event data)'}
## Screen content (sampled every 5 minutes):
{ocr_lines or '(no screen data)'}
## Audio / speech:
{audio or '(no audio data)'}
Date: {d}
Total frames recorded: {stats['total_frames']}"""
def run_pipe(
pipe_id: str,
for_date: date | None = None,
api_key: str | None = None,
pipes_dir: Path | None = None,
) -> dict[str, Any]:
import anthropic
from consumers.activity import summary_stats
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.", "date": str(d)}
_pipes_dir = pipes_dir or Path(os.environ.get("PIPES_DIR", "/data/pipes"))
all_pipes = {p["id"]: p for p in load_pipes(_pipes_dir)}
if pipe_id not in all_pipes:
return {"title": "Error", "content": f"Pipe not found: {pipe_id}", "date": str(d)}
pipe = all_pipes[pipe_id]
stats = summary_stats(d)
if stats["total_frames"] == 0:
return {"title": pipe["title"], "content": f"No screen data found for {d}.", "date": str(d)}
system = _build_system(d, stats)
client = anthropic.Anthropic(api_key=key)
message = client.messages.create(
model="claude-haiku-4-5-20251001",
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": pipe["prompt"]}],
)
return {
"title": pipe["title"],
"content": message.content[0].text,
"date": str(d),
"stats": stats,
}
Now add the API endpoints to
main.py
:
Edit
main.py
main.py
Added 1 line
Edit
main.py
main.py
Added 1 line
Now add the pipe endpoints at the end of main.py, before the closing summary section:
Edit
main.py
main.py
Added 14 lines
Now add the CLI command to
cli.py
cli.py
:
Edit
cli.py
cli.py
Added 40 lines
Done. Here's how to run pipes:
How to run pipes
How to run pipes
Via Docker (inside the running container):
Copy code to clipboard
docker exec -it screenpipe-app python cli.py pipe --list
docker exec -it screenpipe-app python cli.py pipe day-recap
docker exec -it screenpipe-app python cli.py pipe standup-update --date yesterday
Via the API
(once the container is running on port 8766):
Copy code to clipboard
# List pipes
curl http://localhost:8766/api/pipes
# Run a pipe
curl -X POST "http://localhost:8766/api/pipes/day-recap/run"
curl -X POST "http://localhost:8766/api/pipes/standup-update/run?d=2026-04-25"
Rebuild and restart
to pick up the changes:
Copy code to clipboard
cd /volume1/screenpipe/app
docker compose up --build -d
How it works:
Each
pipe.md
file is loaded dynamically — the frontmatter provides metadata and the body becomes the user prompt. Screenpipe data (app usage, browser URLs, OCR, audio) is injected as a Claude system message so the pipe prompt can reference it naturally. The "Read screenpipe skill first" line is stripped automatically.
The pipes directory inside Docker is
/data/pipes
(since
/volume1/screenpipe
→
/data
). You can override it with a
PIPES_DIR
env var if needed.
Message actions
pipe.md
pipe.md
where to place api key and can I use different keys
Thinking
Thinking
Bash
Read .env file
IN
cat /volume1/screenpipe/app/.env
Copy code to clipboard
OUT
[ENV_SECRET]
Where to place the key:
app/.env
app/.env
— replace
your_key_here
with your actual key:
Copy code to clipboard
[ENV_SECRET]
That file is loaded by
load_dotenv()
at startup and is excluded from git via
.gitignore
.
Can you use different keys per pipe?
Not currently — all pipes share the one key from
.env
. But you can add per-pipe key support by adding an
api_key
field to the pipe's frontmatter:
Copy code to clipboard
---
title: Meeting Summary
api_key_env: ANTHROPIC_KEY_MEETINGS
---
Then in
consumers/pipes.py
consumers/pipes.py
, read it in
_parse_frontmatter
and pass it through
run_pipe
. Want me to add that?
Message actions
pipe.md
pipe.md
can I use mistral or other llm? or is is build for antrophic only
Thinking
Thinking
Currently it's Anthropic-only — the code calls the
anthropic
SDK directly in both...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21078
|
|
21079
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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
pipe.md, Editor Group 1
.env, Editor Group 1...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21079
|
|
21080
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
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...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21080
|
|
21110
|
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
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. If an alias is provided, columns are
# prefixed (e.g. `o."col"`). Used for both INSERT and SELECT sides; aliased
# form is required when SELECTing from a JOIN where column names collide
# (e.g. ocr_text and frames both have app_name, window_name).
build_col_list() {
local table="$1"
local alias="${2:-}"
local prefix=""
[ -n "$alias" ] && prefix="${alias}."
table_columns "$DB_SRC" "$table" | awk 'NF' | awk -v p="$prefix" '{print p "\"" $0 "\""}' | 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.
# For tables that JOIN frames (ocr_text, elements), the SELECT side needs
# alias-qualified columns because both tables share names (app_name, etc).
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)
# ─── 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_O 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_E 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;
"
# ─── 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
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
# Screenpipe writes daily logs to ~/.screenpipe/screenpipe.YYYY-MM-DD.N.log
# (N is a rotation counter, almost always 0). Mirror them to NAS so log
# analysis can happen on the consumer side without SSH'ing back to the Mac.
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
# Use a glob with nullglob so an empty match doesn't expand to a literal pattern.
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-36s " "rsync logs → NAS"
# No --ignore-existing here: logs grow during the day, so we want rsync's
# default size+mtime check to refresh files that have changed.
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
COPIED=${#LOG_FILES[@]}
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "$COPIED" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-36s %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"
log "========================================"
# Mirror this script's own logbook to NAS so sync health can be monitored
# from the consumer side. Done last so the summary lines above are included.
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
#!/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. If an alias is provided, columns are
# prefixed (e.g. `o."col"`). Used for both INSERT and SELECT sides; aliased
# form is required when SELECTing from a JOIN where column names collide
# (e.g. ocr_text and frames both have app_name, window_name).
build_col_list() {
local table="$1"
local alias="${2:-}"
local prefix=""
[ -n "$alias" ] && prefix="${alias}."
table_columns "$DB_SRC" "$table" | awk 'NF' | awk -v p="$prefix" '{print p "\"" $0 "\""}' | 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.
# For tables that JOIN frames (ocr_text, elements), the SELECT side needs
# alias-qualified columns because both tables share names (app_name, etc).
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)
# ─── 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_O 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_E 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;
"
# ─── 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
# ─── COPY LOGS ────────────────────────────────────────────────────────────────
# Screenpipe writes daily logs to ~/.screenpipe/screenpipe.YYYY-MM-DD.N.log
# (N is a rotation counter, almost always 0). Mirror them to NAS so log
# analysis can happen on the consumer side without SSH'ing back to the Mac.
step "Copying screenpipe logs for $TARGET_DATE"
NAS_LOGS="$NAS_MOUNT/logs"
mkdir -p "$NAS_LOGS"
# Use a glob with nullglob so an empty match doesn't expand to a literal pattern.
shopt -s nullglob
LOG_FILES=( "$HOME/.screenpipe/screenpipe.$TARGET_DATE."*.log )
shopt -u nullglob
if [ ${#LOG_FILES[@]} -gt 0 ]; then
printf " %-36s " "rsync logs → NAS"
# No --ignore-existing here: logs grow during the day, so we want rsync's
# default size+mtime check to refresh files that have changed.
rsync -a "${LOG_FILES[@]}" "$NAS_LOGS/" 2>>"$LOG_FILE"
COPIED=${#LOG_FILES[@]}
TOTAL_SIZE=$(du -ch "${LOG_FILES[@]}" | tail -1 | cut -f1)
printf "✓ %d file(s), %s\n" "$COPIED" "$TOTAL_SIZE" | tee -a "$LOG_FILE"
else
printf " %-36s %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"
log "========================================"
# Mirror this script's own logbook to NAS so sync health can be monitored
# from the consumer side. Done last so the summary lines above are included.
rsync -a "$HOME/.screenpipe/sync.log" "$NAS_LOGS/sync.log" 2>/dev/null || true
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
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 rep...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21110
|
|
21111
|
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...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21111
|
|
21112
|
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
...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21112
|
|
21402
|
iTerm2ShellEditViewSessionScriptsProfilesWindowHel iTerm2ShellEditViewSessionScriptsProfilesWindowHelp-zshDOCKER-rw-r--r---rw-r--r---rw-r--r---W-r--r---rw-r--r---rw-r--r---rw-r--r---rw-r--r---W-r--r-=-rw-r--r---rw-r--r--DEV (docker)$2APP (-zsh)*3-zsh841 lukasstaff562611May09:28soundcore AeroClip(input)_2026-05-11_06-28-15.mp4lukasstaff462011May09:29soundcore AeroClip(input)_2026-05-11_06-28-50.mp4lukasstaff881711May09:29soundcore AeroClip(input)_2026-05-11_06-29-22.mp41lukasstaff581211May09:30soundcore AeroClip(input)_2026-05-11_06-29-52.mp4lukasstaff567111May09:30soundcore AeroClip(input)_2026-05-11_06-30-22.mp41lukasstaff796311May09:31soundcore AeroClip(input)_2026-05-11_06-31-11.mp4lukasstaff6614111lukasstaff560611May09:32soundcore AeroClip(input)_2026-05-11_06-31-43.mp4May09:32soundcore AeroClip(input)_2026-05-11_06-32-20.mp4lukasstaff639011May09:33soundcore AeroCliplukasstaff6031311(input)_2026-05-11_06-32-52.mp41lukasstaff8843311May09:33soundcore AeroClip(input)_2026-05-11_06-33-22.mp4May09:34soundcoreAeroClipinput_2026-05-11_06-33-52.mp4lukasstaff12524911May09:34soundcore AeroClip(input)_2026-05-11_06-34-22.mp4-rw-r--r---=---e-rw-r--r---rw-r--r---rw-r--r---rw-r--r---=---s-rw-r--r---rw-r--r--1lukasstaff10297511May09:35soundcore AeroClip(input)_2026-05-11-06-34-•52.mp4lukasstaff2139911May09:35soundcore AeroClip(input)_2026-05-11_06-35-22.mp4lukasstaff3937911May09:36soundcore AeroClip(input)_2026-05-11_06-35-52.mp41lukasstaff995711May09:37soundcore AeroClip(input)_2026-05-11_.06-36-46.mp4lukasstaff8814811May09:37soundcore AeroClip(input)_2026-05-11_06-37-18.mp41lukasstaff13584011May09:38soundcore AeroClip(input)_2026-05-11_06-37-48.mp41lukasstaff3477011 May09:38soundcore AeroClip (input)_2026-05-11_06-38-18.mp4lukasstaff52737111lukasstaffMay09:39soundcore AeroClip(input)_2026-05-11_06-38-48.mp47007011 May09:39soundcore AeroClip(input)_2026-05-11_06-39-18.mp4lukasstaff5062811May09:40soundcore AeroClip(input)_2026-05-11_06-39-48.mp4-rw-r--r---W-r--T---rw-r--r---rw-r--r---rw-r--r---rw-r--r---V=---s-rw-r--r---rw-r--r--lukasstaff76838111lukasMay09:40soundcore AeroClip (input)_2026-05-11_06-40-18.mp4staff66733 11 May09:41soundcore AeroClip(input)_2026-05-11_06-40-48.mp41lukasstaff7788711May09:41soundcore AeroClip(input)_2026-05-11_06-41-18.mp41lukasstaff6392211May09:42soundcore AeroClip(input)_2026-05-11_06-41-48.mp4lukasstaff1888411May09:42soundcore AeroClip(input)_2026-05-11_06-42-18.mp411lukasstaff1394211May09:43soundcore AeroClip(input)_2026-05-11_06-42-56.mp4lukasstaff565011May09:44soundcore AeroCliplukasstaff462011May09:45(input)_2026-05-11_06-43-53.mp4soundcore AeroClip(input)_2026-05-11_06-44-38.mp41lukasstaff685111May09:45soundcore AeroClipinput_2026-05-11_06-45-10.mp4lukasstaff110711 May09:45soundcore AeroClip-rw-r--r---V=---e-rw-r--r---Ver-=---rw-r--r---rw-r--r---V=--es-rw-r--r---rw-r--r--lukasstaff1780011May19:18(input)_2026-05-11_06-45-22.mp4soundcore AeroClip(input)_2026-05-11_16-18-23.mp4lukasstaff1273211May19:19soundcore AeroClip(input)_2026-05-11_16-18-54.mp411lukasstaff736111May19:19soundcore AeroClip(input)_2026-05-11_16-19-24.mp4lukasstaff1662211May19:20soundcore AeroClip(input)_2026-05-11_16-19-54.mp4lukasstaff15093611May19:21soundcore AeroClip (input)_2026-05-11_16-20-29.mp41lukasstaff13473211May19:21soundcore AeroClip(input)_2026-05-11_16-21-01.mp4lukasstaff2369011May19:22soundcore AeroCliplukasstaff(input)_2026-05-11_16-21-31.mp41665111May19:22soundcore AeroClip(input)_2026-05-11__16-22-01.mp41lukasstaff692211May19:23soundcore AeroClip(input)_2026-05-11_16-22-31.mp4lukasstaff560311May19:23soundcore AeroClip(input)_2026-05-11_16-23-01.mp4-rw-r--r---rw-rlukasstaff4950911May19:24soundcore AeroClip(input)_2026-05-11_.16-23-31.mp41 lukasstaff34462 11May19:24soundcore AeroClip (input)_2026-05-11_16-24-01.mp4.ukas@Lukas-Kovaliks-MacBook-Pro-Jiminny'.screenpipe/data $ 11||-zsh*5screenpipe™100% <78• Mon 11 May 20:48:12181O 886-zshX7...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21402
|
|
21404
|
SioCodeselectionvIewWindowy apo settinas.isonV SCR SioCodeselectionvIewWindowy apo settinas.isonV SCREENPIPE [SSH: NAS]/ #recycleconsumers> static• .env& cli.py# db.pvdocker-compose.vmli# Dockertilemain.py=requirements.txt• data>logs→oinec• aitianore* aoo settinas.ison=archive.db=archive.db-bal= db salite=dh salite.chmE dh calite-wall$ screennine svnc undated sh$ screenpipe_sync.shE screennine dhsscreenpipe sync updated.sh U Xpipe.mc•.env(append-only, no deletions).*Hiso coptes tne days viueofirale dald totuet to the Nho.# Schema-drift tolerant: if Screenpipe migrations add new columns to the source DB,# che NAs archive gets ALTER VABLE'd to match. Inserts use expuicic column Lists,# suncs vesterdav (default)/screennine svnc.sh 2026-04-151# syncs a specific date• cron example (runs at sam dally):* /Users/lukas/.screenpipe/screenpipe_sync.sh > /Users/lukas/.screenpipe/sync.log 2>&1set -euo pipefailSRC="${SCREENPIPE DB:-$HOME/. screenpipe/db.sqlite}"NAS MUUNI= SINAS MOUNI:-/Vo Lumes/screenpiperNAS UB="SNAS MUUNIarchive.db"NAS DATA="SNAS MOUNT data"LOG ?ILE"SHOME.screenoipe/sync.lo0"* Tables that aet schema drift handling. Order matters for Fk-ish referencesSYNC TABLES=video chunks frames elements ocr text ui events meetinas)HEI DEpdSCRIPT START=S(date +%s)msa=" Sdate"+%Y-%m-%d %H:%M:9S") S*echo "Smsa" | tee -a "sLoG FtLE'local now=s date +%s)local elapsed=s now - SCRIPT START ))local mines elapsed / 60 ))local seceselapsed % 60))nrintf "n1+902dme02dsl | %cin" "smin" "ssec" "s*" | tee ca "SLOG FTLE"PROBLEMSOUTPUTAdm1nanypAg00pi lIC_R55g: /volume1/ccreennineescreenpipe SSH: nasl100% L2Mon 11 May 20:48:1208 000*mA .•›* Implement and run pipe I...Implement and run oipe logleAamin@DXP4800PLUS-B5F8:/volumel//screenpipe/apps sudo docker exec -it screenpipe-app python cli.py pipe day-recap -=date 2026-04-20Running pipe 'day-recap' for 2026-04-20...• The container started before you updated the.env , so it's using the old placeholder key. Restart it to pick up the new one:cd /volumel/screenpipe/appsudo docker comoose restart screenoine-aooThen try adain:sudo docker exec -it screenpipe-app python cli.py pipe day-recap --date 2026-04-20Admin@DXP4800PLUS-B5r8:/volumel/screenpipe/apps sudo docker exec -it screenpipe-app python cli.py pipe day-recap --date 2026-04-20Runnina nine 'dav-recan' for 2026-04.20..key is there now but Adm1n@DXP4800PLUS-B5F8:/volume1/screenpipe/app$ sudo docker exec -it screenpipe-app python cli.py pipe day-recap --date 2026-04-20Runnina oioe 'dav-recao' for 2026-04-20.1• The kev works — it authenticated successfullv. The error is iust that vour Anthropic account has no credits. Go to console.anthropic.com → Plans & Billina and add some credits (a few dollars is enouah fotesting).Once topped up, rerun the same command and it should work.tevoм ho asctoenpo. syлo an 1 wan to maxo sur ro aso audeM screenpipe_sync_updated.sh€Ask before edits?bash +vM @ •| xOUTIINE> TIMELINE* SSH: nas 8 master* @@0A0 (@18 Sign In...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
21404
|
|
26189
|
Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧ Explorer (⇧⌘E)
Search (⇧⌘F)
Source Control (⌃⇧G) - 11 pending changes
11
Run and Debug (⇧⌘D)
Remote Explorer
Extensions (⇧⌘X)
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
db.sqlite
db.sqlite-shm
db.sqlite-wal
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_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 (vision + audio) to a NAS archive database.
# Append-only, no deletions.
#
# Key design points
# -----------------
# 1. Multi-install safe via install_id.
# Source IDs reset to 1 on every screenpipe reinstall. To avoid collisions
# in the NAS archive, every synced table gets an extra `install_id` column,
# and the logical primary key becomes (install_id, id) enforced by a
# unique index. The install_id is a UUID stored in
# ~/.screenpipe/.sync_install_id — wiping ~/.screenpipe/ (which is what
# happens on reinstall) discards it, so the next run generates a new one.
#
# 2. Schema-drift tolerant. If screenpipe migrations add new columns to the
# source DB, the NAS gets ALTER TABLE'd to match. Inserts use explicit
# column lists so positional mismatches can't occur.
#
# 3. FTS caveat. FTS tables in the NAS use source `id` as rowid. After a
# reinstall, INSERT OR IGNORE will silently skip rows whose id collides
# with a previous install's id, so FTS only reliably indexes the most
# recent install. Falls back to LIKE queries on the base tables for
# multi-install searches (which can filter by install_id).
#
# Usage
# -----
# ./screenpipe_sync.sh # syncs yesterday
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
# ./screenpipe_sync.sh --reset-install-id # rotate install_id and exit
# ./screenpipe_sync.sh --show-install-id # print install_id and exit
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"
INSTALL_ID_FILE="$HOME/.screenpipe/.sync_install_id"
# Sync table groups. Order matters for FK-ish references
# (parents before children).
VISION_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
AUDIO_TABLES=(speakers speaker_embeddings audio_chunks audio_transcriptions audio_tags)
ALL_SYNC_TABLES=("${VISION_TABLES[@]}" "${AUDIO_TABLES[@]}")
# ──────────────────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
# ─── HELPERS ──────────────────────────────────────────────────────────────────
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 " %-25s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-25s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
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.
# Skips install_id (which is NAS-only and managed separately).
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
if ! echo "$nas_cols" | grep -Fxq "$name"; then
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
}
# Comma-separated, double-quoted column list for a table from source DB.
# Optional alias is prefixed (e.g. `o."col"`) for JOIN selects where column
# names would otherwise collide.
build_col_list() {
local table="$1"
local alias="${2:-}"
local prefix=""
[ -n "$alias" ] && prefix="${alias}."
table_columns "$DB_SRC" "$table" | awk 'NF' | awk -v p="$prefix" '{print p "\"" $0 "\""}' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── ARG HANDLING ─────────────────────────────────────────────────────────────
if [ "${1:-}" = "--reset-install-id" ]; then
if [ -f "$INSTALL_ID_FILE" ]; then
OLD=$(cat "$INSTALL_ID_FILE")
rm -f "$INSTALL_ID_FILE"
echo "Removed install_id: $OLD"
else
echo "No install_id file at $INSTALL_ID_FILE"
fi
echo "A new install_id will be generated on the next sync run."
exit 0
fi
if [ "${1:-}" = "--show-install-id" ]; then
if [ -f "$INSTALL_ID_FILE" ]; then
cat "$INSTALL_ID_FILE"
else
echo "(none — will be generated on next run)"
fi
exit 0
fi
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. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
# ─── INSTALL ID ───────────────────────────────────────────────────────────────
INSTALL_ID=""
if [ -f "$INSTALL_ID_FILE" ]; then
INSTALL_ID=$(tr -d '[:space:]' < "$INSTALL_ID_FILE")
fi
if [ -z "$INSTALL_ID" ]; then
INSTALL_ID=$(uuidgen | tr 'A-Z' 'a-z')
echo "$INSTALL_ID" > "$INSTALL_ID_FILE"
log "Generated new install_id: $INSTALL_ID"
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "install_id: $INSTALL_ID"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-25s %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 " %-25s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date+install_id
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
HAS_INSTALL_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('frames') WHERE name='install_id';")
if [ "$HAS_INSTALL_COL" -gt "0" ]; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp)='$TARGET_DATE' AND install_id='$INSTALL_ID';" 2>/dev/null || echo "0")
else
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp)='$TARGET_DATE';" 2>/dev/null || echo "0")
fi
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE / install $INSTALL_ID already has $EXISTING frames — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-25s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-25s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-25s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date (video frames)
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 " %-25s %s (%s files, %s)\n" "Frame data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-25s %s\n" "Frame data dir:" "not found — skipping"
fi
# Audio files (flat in ~/.screenpipe/data/, dated by filename)
shopt -s nullglob
AUDIO_SRC_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_SRC_FILES[@]} -gt 0 ]; then
AUDIO_TOTAL=$(du -ch "${AUDIO_SRC_FILES[@]}" | tail -1 | cut -f1)
printf " %-25s %s (%s files, %s)\n" "Audio files:" "OK" "${#AUDIO_SRC_FILES[@]}" "$AUDIO_TOTAL"
else
printf " %-25s %s\n" "Audio files:" "none for this date"
fi
# ─── SCHEMA MIGRATION: install_id ─────────────────────────────────────────────
# Adds install_id column to existing NAS tables, backfills NULLs with a
# legacy tag, and creates the (install_id, id) unique index. Idempotent.
if [ -f "$NAS_DB" ]; then
step "Schema migration: install_id"
LEGACY_TAG="legacy-$(date +%Y%m%d)"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
if ! table_exists "$NAS_DB" "$tbl"; then
continue
fi
HAS_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('$tbl') WHERE name='install_id';")
if [ "$HAS_COL" = "0" ]; then
ROW_COUNT=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM $tbl;")
printf " %-36s adding install_id, backfill %s rows → %s\n" "$tbl" "$ROW_COUNT" "$LEGACY_TAG"
sqlite3 "$NAS_DB" "ALTER TABLE $tbl ADD COLUMN install_id TEXT;"
sqlite3 "$NAS_DB" "UPDATE $tbl SET install_id = '$LEGACY_TAG' WHERE install_id IS NULL;"
fi
sqlite3 "$NAS_DB" "CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_install_pk ON ${tbl}(install_id, id);"
done
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';")
SRC_ACHUNKS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ATRANS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_transcriptions WHERE audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE');")
SRC_ATAGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_tags WHERE audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE');")
# speakers + speaker_embeddings are install-global, not per-date; we sync all.
SRC_SPEAKERS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM speakers;")
SRC_SEMB=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM speaker_embeddings;")
printf " %-25s %s\n" "frames:" "$SRC_FRAMES"
printf " %-25s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-25s %s\n" "ui_events:" "$SRC_UI"
printf " %-25s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-25s %s\n" "meetings:" "$SRC_MEETINGS"
printf " %-25s %s\n" "audio_chunks:" "$SRC_ACHUNKS"
printf " %-25s %s\n" "audio_transcriptions:" "$SRC_ATRANS"
printf " %-25s %s\n" "audio_tags:" "$SRC_ATAGS"
printf " %-25s %s (all-time)\n" "speakers:" "$SRC_SPEAKERS"
printf " %-25s %s (all-time)\n" "speaker_embeddings:" "$SRC_SEMB"
if [ "$SRC_FRAMES" -eq "0" ] && [ "$SRC_ACHUNKS" -eq "0" ]; then
log "No frames or audio chunks 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 vision 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;
"
run_sqlite_heredoc "creating audio tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.audio_chunks AS SELECT * FROM main.audio_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.audio_transcriptions AS SELECT * FROM main.audio_transcriptions WHERE 0;
CREATE TABLE IF NOT EXISTS nas.speakers AS SELECT * FROM main.speakers WHERE 0;
CREATE TABLE IF NOT EXISTS nas.speaker_embeddings AS SELECT * FROM main.speaker_embeddings WHERE 0;
CREATE TABLE IF NOT EXISTS nas.audio_tags AS SELECT * FROM main.audio_tags WHERE 0;
DETACH nas;
"
# Re-run install_id + index setup so freshly-created tables get them too.
for tbl in "${ALL_SYNC_TABLES[@]}"; do
HAS_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('$tbl') WHERE name='install_id';")
if [ "$HAS_COL" = "0" ]; then
sqlite3 "$NAS_DB" "ALTER TABLE $tbl ADD COLUMN install_id TEXT;"
fi
sqlite3 "$NAS_DB" "CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_install_pk ON ${tbl}(install_id, id);"
done
# ─── SCHEMA DRIFT ─────────────────────────────────────────────────────────
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(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);
-- 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(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(speaker_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS nas.idx_speaker_emb_speaker_id ON speaker_embeddings(speaker_id);
CREATE INDEX IF NOT EXISTS nas.idx_audio_tags_chunk_id ON audio_tags(audio_chunk_id);
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'
);
CREATE VIRTUAL TABLE IF NOT EXISTS nas.audio_transcriptions_fts USING fts5(
transcription, device, speaker_id UNINDEXED, id UNINDEXED,
tokenize='unicode61'
);
DETACH nas;
"
# ─── BUILD EXPLICIT COLUMN LISTS ──────────────────────────────────────────
# Source columns + install_id appended. Same on both sides of the INSERT.
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)
# ─── 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"
# Speakers + embeddings are install-global, not per-date. Sync everything
# the source currently has; INSERT OR IGNORE handles the duplicate case.
run_sqlite_heredoc "speakers ($SRC_SPEAKERS rows, all-time)" "
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;
"
# ─── FTS UPDATE ───────────────────────────────────────────────────────────
step "Updating FTS indexes"
run_sqlite_heredoc "elements_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE 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 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 "frames_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE 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 install_id = '$INSTALL_ID'
AND full_text IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "ui_events_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE 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 install_id = '$INSTALL_ID'
AND text_content IS NOT NULL;
DETACH nas;
"
run_sqlite_heredoc "audio_transcriptions_fts" "
ATTACH '$NAS_DB' AS nas;
INSERT OR IGNORE INTO nas.audio_transcriptions_fts(rowid, transcription, device, speaker_id, id)
SELECT t.id, t.transcription, COALESCE(t.device,''), t.speaker_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');")
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"
fi
# ─── COPY FRAME DATA FOLDER ──────────────────────────────────────────────────
# Always runs regardless of DB sync status.
step "Copying frame data folder for $TARGET_DATE"
if [ -d "$DATA_SRC" ]; then
mkdir -p "$NAS_DATA/$TARGET_DATE"
RSYNC_START=$(date +%s)
printf " %-36s " "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 %-36s ✓ %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 %-36s ✗ %s / %s files\n" \
"rsync frames → NAS" "$COPIED_FILES" "$SRC_FILES" | tee -a "$LOG_FILE"
fi
else
printf " %-36s %s\n" "rsync frames → NAS" "skipped (no source dir)"
fi
# ─── COPY AUDIO FILES ────────────────────────────────────────────────────────
# Audio is flat in ~/.screenpipe/data/ with the date in the filename, e.g.
# System Audio (output)_2026-05-11_13-48-12.mp4
# soundcore AeroClip (input)_2026-05-10_11-10-32.mp4
# Mirrored 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 " %-36s " "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 %-36s ✓ %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 " %-36s %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 " %-36s " "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 " %-36s %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
#!/bin/bash
# screenpipe_sync.sh
# Syncs Screenpipe SQLite data (vision + audio) to a NAS archive database.
# Append-only, no deletions.
#
# Key design points
# -----------------
# 1. Multi-install safe via install_id.
# Source IDs reset to 1 on every screenpipe reinstall. To avoid collisions
# in the NAS archive, every synced table gets an extra `install_id` column,
# and the logical primary key becomes (install_id, id) enforced by a
# unique index. The install_id is a UUID stored in
# ~/.screenpipe/.sync_install_id — wiping ~/.screenpipe/ (which is what
# happens on reinstall) discards it, so the next run generates a new one.
#
# 2. Schema-drift tolerant. If screenpipe migrations add new columns to the
# source DB, the NAS gets ALTER TABLE'd to match. Inserts use explicit
# column lists so positional mismatches can't occur.
#
# 3. FTS caveat. FTS tables in the NAS use source `id` as rowid. After a
# reinstall, INSERT OR IGNORE will silently skip rows whose id collides
# with a previous install's id, so FTS only reliably indexes the most
# recent install. Falls back to LIKE queries on the base tables for
# multi-install searches (which can filter by install_id).
#
# Usage
# -----
# ./screenpipe_sync.sh # syncs yesterday
# ./screenpipe_sync.sh 2026-04-15 # syncs a specific date
# ./screenpipe_sync.sh today # syncs today so far
# ./screenpipe_sync.sh --reset-install-id # rotate install_id and exit
# ./screenpipe_sync.sh --show-install-id # print install_id and exit
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"
INSTALL_ID_FILE="$HOME/.screenpipe/.sync_install_id"
# Sync table groups. Order matters for FK-ish references
# (parents before children).
VISION_TABLES=(video_chunks frames elements ocr_text ui_events meetings)
AUDIO_TABLES=(speakers speaker_embeddings audio_chunks audio_transcriptions audio_tags)
ALL_SYNC_TABLES=("${VISION_TABLES[@]}" "${AUDIO_TABLES[@]}")
# ──────────────────────────────────────────────────────────────────────────────
SCRIPT_START=$(date +%s)
# ─── HELPERS ──────────────────────────────────────────────────────────────────
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 " %-25s %s / %s ✓\n" "$label:" "$got" "$expected"
else
printf " %-25s %s / %s ✗ MISMATCH\n" "$label:" "$got" "$expected"
fi
}
table_columns_with_types() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2 "|" $3}'
}
table_columns() {
local db="$1" table="$2"
sqlite3 "$db" "PRAGMA table_info($table);" | awk -F'|' '{print $2}'
}
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.
# Skips install_id (which is NAS-only and managed separately).
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
if ! echo "$nas_cols" | grep -Fxq "$name"; then
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
}
# Comma-separated, double-quoted column list for a table from source DB.
# Optional alias is prefixed (e.g. `o."col"`) for JOIN selects where column
# names would otherwise collide.
build_col_list() {
local table="$1"
local alias="${2:-}"
local prefix=""
[ -n "$alias" ] && prefix="${alias}."
table_columns "$DB_SRC" "$table" | awk 'NF' | awk -v p="$prefix" '{print p "\"" $0 "\""}' | paste -sd, -
}
# ──────────────────────────────────────────────────────────────────────────────
# ─── ARG HANDLING ─────────────────────────────────────────────────────────────
if [ "${1:-}" = "--reset-install-id" ]; then
if [ -f "$INSTALL_ID_FILE" ]; then
OLD=$(cat "$INSTALL_ID_FILE")
rm -f "$INSTALL_ID_FILE"
echo "Removed install_id: $OLD"
else
echo "No install_id file at $INSTALL_ID_FILE"
fi
echo "A new install_id will be generated on the next sync run."
exit 0
fi
if [ "${1:-}" = "--show-install-id" ]; then
if [ -f "$INSTALL_ID_FILE" ]; then
cat "$INSTALL_ID_FILE"
else
echo "(none — will be generated on next run)"
fi
exit 0
fi
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. Use YYYY-MM-DD, 'today', or no argument for yesterday."
exit 1
fi
else
TARGET_DATE=$(date -v-1d +%Y-%m-%d)
fi
# ─── INSTALL ID ───────────────────────────────────────────────────────────────
INSTALL_ID=""
if [ -f "$INSTALL_ID_FILE" ]; then
INSTALL_ID=$(tr -d '[:space:]' < "$INSTALL_ID_FILE")
fi
if [ -z "$INSTALL_ID" ]; then
INSTALL_ID=$(uuidgen | tr 'A-Z' 'a-z')
echo "$INSTALL_ID" > "$INSTALL_ID_FILE"
log "Generated new install_id: $INSTALL_ID"
fi
log "========================================"
log "Screenpipe sync starting for: $TARGET_DATE"
log "install_id: $INSTALL_ID"
log "========================================"
# ─── PREFLIGHT ────────────────────────────────────────────────────────────────
step "Preflight checks"
if [ ! -f "$DB_SRC" ]; then
log "ERROR: Source DB not found at $DB_SRC"; exit 1
fi
printf " %-25s %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 " %-25s %s\n" "NAS mount:" "OK $NAS_MOUNT"
# Check if DB already synced for this date+install_id
DB_ALREADY_SYNCED=false
if [ -f "$NAS_DB" ]; then
if table_exists "$NAS_DB" "frames"; then
HAS_INSTALL_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('frames') WHERE name='install_id';")
if [ "$HAS_INSTALL_COL" -gt "0" ]; then
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp)='$TARGET_DATE' AND install_id='$INSTALL_ID';" 2>/dev/null || echo "0")
else
EXISTING=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM frames WHERE date(timestamp)='$TARGET_DATE';" 2>/dev/null || echo "0")
fi
if [ "$EXISTING" -gt "0" ]; then
log "Date $TARGET_DATE / install $INSTALL_ID already has $EXISTING frames — skipping DB sync"
DB_ALREADY_SYNCED=true
else
printf " %-25s %s (%s)\n" "Archive DB:" "exists" "$(du -sh "$NAS_DB" | cut -f1)"
fi
else
printf " %-25s %s\n" "Archive DB:" "exists, no frames table yet"
fi
else
printf " %-25s %s\n" "Archive DB:" "will be created"
fi
# Source data dir for this date (video frames)
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 " %-25s %s (%s files, %s)\n" "Frame data dir:" "OK" "$DATA_FILES" "$DATA_SIZE"
else
printf " %-25s %s\n" "Frame data dir:" "not found — skipping"
fi
# Audio files (flat in ~/.screenpipe/data/, dated by filename)
shopt -s nullglob
AUDIO_SRC_FILES=( "$HOME/.screenpipe/data/"*_"${TARGET_DATE}"_*.mp4 )
shopt -u nullglob
if [ ${#AUDIO_SRC_FILES[@]} -gt 0 ]; then
AUDIO_TOTAL=$(du -ch "${AUDIO_SRC_FILES[@]}" | tail -1 | cut -f1)
printf " %-25s %s (%s files, %s)\n" "Audio files:" "OK" "${#AUDIO_SRC_FILES[@]}" "$AUDIO_TOTAL"
else
printf " %-25s %s\n" "Audio files:" "none for this date"
fi
# ─── SCHEMA MIGRATION: install_id ─────────────────────────────────────────────
# Adds install_id column to existing NAS tables, backfills NULLs with a
# legacy tag, and creates the (install_id, id) unique index. Idempotent.
if [ -f "$NAS_DB" ]; then
step "Schema migration: install_id"
LEGACY_TAG="legacy-$(date +%Y%m%d)"
for tbl in "${ALL_SYNC_TABLES[@]}"; do
if ! table_exists "$NAS_DB" "$tbl"; then
continue
fi
HAS_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('$tbl') WHERE name='install_id';")
if [ "$HAS_COL" = "0" ]; then
ROW_COUNT=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM $tbl;")
printf " %-36s adding install_id, backfill %s rows → %s\n" "$tbl" "$ROW_COUNT" "$LEGACY_TAG"
sqlite3 "$NAS_DB" "ALTER TABLE $tbl ADD COLUMN install_id TEXT;"
sqlite3 "$NAS_DB" "UPDATE $tbl SET install_id = '$LEGACY_TAG' WHERE install_id IS NULL;"
fi
sqlite3 "$NAS_DB" "CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_install_pk ON ${tbl}(install_id, id);"
done
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';")
SRC_ACHUNKS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE';")
SRC_ATRANS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_transcriptions WHERE audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE');")
SRC_ATAGS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM audio_tags WHERE audio_chunk_id IN (SELECT id FROM audio_chunks WHERE date(timestamp) = '$TARGET_DATE');")
# speakers + speaker_embeddings are install-global, not per-date; we sync all.
SRC_SPEAKERS=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM speakers;")
SRC_SEMB=$(sqlite3 "$DB_SRC" "SELECT COUNT(*) FROM speaker_embeddings;")
printf " %-25s %s\n" "frames:" "$SRC_FRAMES"
printf " %-25s %s\n" "elements:" "$SRC_ELEMENTS"
printf " %-25s %s\n" "ui_events:" "$SRC_UI"
printf " %-25s %s\n" "ocr_text:" "$SRC_OCR"
printf " %-25s %s\n" "meetings:" "$SRC_MEETINGS"
printf " %-25s %s\n" "audio_chunks:" "$SRC_ACHUNKS"
printf " %-25s %s\n" "audio_transcriptions:" "$SRC_ATRANS"
printf " %-25s %s\n" "audio_tags:" "$SRC_ATAGS"
printf " %-25s %s (all-time)\n" "speakers:" "$SRC_SPEAKERS"
printf " %-25s %s (all-time)\n" "speaker_embeddings:" "$SRC_SEMB"
if [ "$SRC_FRAMES" -eq "0" ] && [ "$SRC_ACHUNKS" -eq "0" ]; then
log "No frames or audio chunks 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 vision 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;
"
run_sqlite_heredoc "creating audio tables" "
ATTACH '$NAS_DB' AS nas;
CREATE TABLE IF NOT EXISTS nas.audio_chunks AS SELECT * FROM main.audio_chunks WHERE 0;
CREATE TABLE IF NOT EXISTS nas.audio_transcriptions AS SELECT * FROM main.audio_transcriptions WHERE 0;
CREATE TABLE IF NOT EXISTS nas.speakers AS SELECT * FROM main.speakers WHERE 0;
CREATE TABLE IF NOT EXISTS nas.speaker_embeddings AS SELECT * FROM main.speaker_embeddings WHERE 0;
CREATE TABLE IF NOT EXISTS nas.audio_tags AS SELECT * FROM main.audio_tags WHERE 0;
DETACH nas;
"
# Re-run install_id + index setup so freshly-created tables get them too.
for tbl in "${ALL_SYNC_TABLES[@]}"; do
HAS_COL=$(sqlite3 "$NAS_DB" "SELECT COUNT(*) FROM pragma_table_info('$tbl') WHERE name='install_id';")
if [ "$HAS_COL" = "0" ]; then
sqlite3 "$NAS_DB" "ALTER TABLE $tbl ADD COLUMN install_id TEXT;"
fi
sqlite3 "$NAS_DB" "CREATE UNIQUE INDEX IF NOT EXISTS idx_${tbl}_install_pk ON ${tbl}(install_id, id);"
done
# ─── SCHEMA DRIFT ─────────────────────────────────────────────────────────
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(frame_...
|
Code
|
Implement and run pipe l… — screenpipe [SSH: nas]
|
NULL
|
26189
|
|
14955
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 1 unread
Inbox
1
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases 1 unread has menu
Purchases
1
Social 5206 unread has menu
Social
5,206
Updates 8765 unread has menu...
|
Firefox
|
Inbox (1) - kovaliklukas@gmail.com - Gmail — Perso Inbox (1) - kovaliklukas@gmail.com - Gmail — Personal...
|
mail.google.com/mail/u/0/#inbox
|
14955
|
|
14956
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 1 unread
Inbox
1
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases 1 unread has menu
Purchases
1
Social 5206 unread has menu
Social...
|
Firefox
|
Inbox (1) - kovaliklukas@gmail.com - Gmail — Perso Inbox (1) - kovaliklukas@gmail.com - Gmail — Personal...
|
mail.google.com/mail/u/0/#inbox
|
14956
|
|
14957
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 1 unread
Inbox
1
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases 1 unread has menu
Purchases
1
Social 5206 unread has menu
Social
5,206
Updates 8765 unread has menu
Updates
8,765
Forums 6100 unread has menu
Forums
6,100
Promotions 38763 unread has menu
Promotions
38,763
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7487 unread has menu
jiminny-github
7,487
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,254
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 43 new messages,
Social, 43 new messages,
43 new
Social
Updates, 12 new messages,
Updates, 12 new messages,
12 new
Updates
LinkedIn Job Alerts, Back End Software Engineer at MANUAL, 08:32, MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
LinkedIn Job Alerts
Back End Software Engineer at MANUAL - MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Back End Software Engineer at MANUAL
-
MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
08:32
unread, Apple, Your invoice from Apple., 03:49, Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX.
Not starred
Not important
Apple
Your invoice from Apple. - Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Your invoice from Apple.
-
Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Quora Suggested Spa., Genocide in Russia!!, 21:23, Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,.
Not starred
Not important
Quora Suggested Spa.
Genocide in Russia!! - Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
Genocide in Russia!!
-
Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
21:23
RescueTime Team, 15h 39m of Software Development this week - RescueTime Weekly Summary, 10 May, Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'.
Not starred
Important mainly because it was sent directly to you.
RescueTime Team
15h 39m of Software Development this week - RescueTime Weekly Summary - Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
15h 39m of Software Development this week - RescueTime Weekly Summary
-
Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
10 May
Google Maps, Via Dental Studio – Стрелбище replied to your review on Google, 9 May, A business that you reviewed has responded to your post Via Dental Studio – Стрелбище replied to your review Thank you for posting a review on Google. Lukáš You only left a rating Response from the.
Not starred
Not important
Google Maps
Via Dental Studio – Стрелбище replied to your review on Google - A business that you reviewed has responded to your post Via Dental Studio – Стрелбище replied to your review Thank you for posting a review on Google. Lukáš You only left a rating Response from the
Via Dental Studio – Стрелбище replied to your review on Google
-
A business that you reviewed has responded to your post Via Dental Studio – Стрелбище replied to your review Thank you for posting a review on Google. Lukáš You only left a rating Response from the
9 May
Quora Suggested Spa., ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia’s General Staff..., 9 May, ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia's General Staff indicating that, despite heavy battlefield losses.
Not starred
Important according to Google magic.
Quora Suggested Spa.
ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia’s General Staff... - ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia's General Staff indicating that, despite heavy battlefield losses
ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia’s General Staff...
-
ZELENSKYY SAYS RUSSIA PLANS EXPANDED MOBILIZATION Zelenskyy said Ukrainian intelligence has obtained internal documents from Russia's General Staff indicating that, despite heavy battlefield losses
9 May
XDA, Your old PC can run Windows 11 in a VM, but not on bare metal, 9 May, XDA Logo May 9, 2026 The weekend is here! And so is our latest batch of articles for you. You probably know that Windows 11 dropped support for a lot of older PCs, but did you know that those same PCs.
Not starred
Not important
XDA
Your old PC can run Windows 11 in a VM, but not on bare metal - XDA Logo May 9, 2026 The weekend is here! And so is our latest batch of articles for you. You probably know that Windows 11 dropped support for a lot of older PCs, but did you know that those same PCs
Your old PC can run Windows 11 in a VM, but not on bare metal
-
XDA Logo May 9, 2026 The weekend is here! And so is our latest batch of articles for you. You probably know that Windows 11 dropped support for a lot of older PCs, but did you know that those same PCs
9 May
Chelsea with IFTTT, 6 ways people are making their week easier , 9 May, The automations you didn't know you were missing. ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
Chelsea with IFTTT
6 ways people are making their week easier ✨ - The automations you didn't know you were missing. ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
6 ways people are making their week easier
-
The automations you didn't know you were missing. ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
9 May
Google tips and upd., Lukáš, try three tips for easier sign-ins., 9 May, Create strong passwords instantly, add a backup friend and sign in faster with Google. .
Not starred
Not important
Google tips and upd.
Lukáš, try three tips for easier sign-ins. - Create strong passwords instantly, add a backup friend and sign in faster with Google.
Lukáš, try three tips for easier sign-ins.
-
Create strong passwords instantly, add a backup friend and sign in faster with Google.
9 May
Quora Suggested Spa., I was cheating on my husband of 18 years with his own friend and after 2 years, my husband caught me. How can I make this go away from his mind an...?, 8 May, As a psychologist, I'll give you some honest feedback without being crue… The Betrayed Spouse's Space and Community The Betrayed Spouse's Space and Community • 6.6K followers Help, Support.
Not starred
Not important
Quora Suggested Spa.
I was cheating on my husband of 18 years with his own friend and after 2 years, my husband caught me. How can I make this go away from his mind an...? - As a psychologist, I'll give you some honest feedback without being crue… The Betrayed Spouse's Space and Community The Betrayed Spouse's Space and Community • 6.6K followers Help, Support
I was cheating on my husband of 18 years with his own friend and after 2 years, my husband caught me. How can I make this go away from his mind an...?
-
As a psychologist, I'll give you some honest feedback without being crue… The Betrayed Spouse's Space and Community The Betrayed Spouse's Space and Community • 6.6K followers Help, Support
8 May
Google, Security alert, 8 May, A new sign-in on Apple iPhone [EMAIL] We noticed a new sign-in to your Google Account on a Apple iPhone device. If this was you, you don't need to do anything. If not, we'll help.
Not starred
Important according to Google magic.
Google
Security alert - A new sign-in on Apple iPhone [EMAIL] We noticed a new sign-in to your Google Account on a Apple iPhone device. If this was you, you don't need to do anything. If not, we'll help...
|
Firefox
|
Inbox (1) - kovaliklukas@gmail.com - Gmail — Perso Inbox (1) - kovaliklukas@gmail.com - Gmail — Personal...
|
mail.google.com/mail/u/0/#inbox
|
14957
|
|
14958
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox (1) - [EMAIL] - Gmail
Inbox (1) - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox 1 unread
Inbox
1
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases 1 unread has menu
Purchases
1
Social 5206 unread has menu
Social
5,206
Updates 8765 unread has menu
Updates
8,765
Forums 6100 unread has menu
Forums
6,100
Promotions 38763 unread has menu
Promotions
38,763
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7487 unread has menu
jiminny-github
7,487
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,254
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 43 new messages,
Social, 43 new messages,
43 new
Social
Updates, 12 new messages,
Updates, 12 new messages,
12 new
Updates
LinkedIn Job Alerts, Back End Software Engineer at MANUAL, 08:32, MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
LinkedIn Job Alerts
Back End Software Engineer at MANUAL - MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Back End Software Engineer at MANUAL
-
MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
08:32
unread, Apple, Your invoice from Apple., 03:49, Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX.
Not starred
Not important
Apple
Your invoice from Apple. - Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Your invoice from Apple.
-
Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Quora Suggested Spa., Genocide in Russia!!, 21:23, Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,.
Not starred
Not important
Quora Suggested Spa.
Genocide in Russia!! - Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
Genocide in Russia!!
-
Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
21:23
RescueTime Team, 15h 39m of Software Development this week - RescueTime Weekly Summary, 10 May, Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'.
Not starred
Important mainly because it was sent directly to you.
RescueTime Team
15h 39m of Software Development this week - RescueTime Weekly Summary - Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
15h 39m of Software Development this week - RescueTime Weekly Summary
-
Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
10 May...
|
Firefox
|
Inbox (1) - kovaliklukas@gmail.com - Gmail — Perso Inbox (1) - kovaliklukas@gmail.com - Gmail — Personal...
|
mail.google.com/mail/u/0/#inbox
|
14958
|
|
14961
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox - [EMAIL] - Gmail
Inbox - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox
Inbox
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases has menu
Purchases
Social 5206 unread has menu
Social
5,206
Updates 8765 unread has menu
Updates
8,765
Forums 6100 unread has menu
Forums
6,100
Promotions 38763 unread has menu
Promotions
38,763
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7488 unread has menu
jiminny-github
7,488
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,254
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 43 new messages,
Social, 43 new messages,
43 new
Social
Updates, 12 new messages,
Updates, 12 new messages,
12 new
Updates
LinkedIn Job Alerts, Back End Software Engineer at MANUAL, 08:32, MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
LinkedIn Job Alerts
Back End Software Engineer at MANUAL - MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Back End Software Engineer at MANUAL
-
MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
08:32
Apple, Your invoice from Apple., 03:49, Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX.
Not starred
Not important
Apple
Your invoice from Apple. - Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Your invoice from Apple.
-
Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Quora Suggested Spa., Genocide in Russia!!, 21:23, Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,.
Not starred
Not important
Quora Suggested Spa.
Genocide in Russia!! - Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
Genocide in Russia!!
-
Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
21:23
RescueTime Team, 15h 39m of Software Development this week - RescueTime Weekly Summary, 10 May, Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'.
Not starred
Important mainly because it was sent directly to you....
|
Firefox
|
Inbox - kovaliklukas@gmail.com - Gmail — Personal
|
mail.google.com/mail/u/0/#inbox
|
14961
|
|
14962
|
Pull requests · screenpipe/screenpipe · GitHub
Pul Pull requests · screenpipe/screenpipe · GitHub
Pull requests · screenpipe/screenpipe · GitHub
DNS / Nameservers | Hostinger
DNS / Nameservers | Hostinger
Nginx Proxy Manager
Nginx Proxy Manager
Screenpipe — Archive
Screenpipe — Archive
SQLite Web: archive.db
SQLite Web: archive.db
SQLite Web: db.sqlite
SQLite Web: db.sqlite
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
GitHub - screenpipe/screenpipe: Run agents that work based on what you do. 24/7 local screen & mic recording for the superintelligence era · GitHub
DXP4800PLUS-B5F8
DXP4800PLUS-B5F8
AFFiNE - All In One KnowledgeOS
AFFiNE - All In One KnowledgeOS
All docs · AFFiNE
All docs · AFFiNE
Payments Logger
Payments Logger
Inbox - [EMAIL] - Gmail
Inbox - [EMAIL] - Gmail
Close tab
Location Logger
Location Logger
Finance Hub
Finance Hub
Select: transaction_imports - db - Adminer
Select: transaction_imports - db - Adminer
Claude Code | Claude Platform
Claude Code | Claude Platform
April 2026 spending by category - Claude
April 2026 spending by category - Claude
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
lakylak/finance-hub - finance-hub - Gitea: Git with a cup of tea
Applications - Admin - authentik
Applications - Admin - authentik
Електронно банкиране ДСК Директ от Банка ДСК
Електронно банкиране ДСК Директ от Банка ДСК
Location Logger
Location Logger
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Open history (⇧⌘H)
Open bookmarks (⌘B)
Bitwarden
None selected
Skip to content
Skip to content
Using Gmail with screen readers
Using Gmail with screen readers
Main menu
Gmail
Search
Search
Search mail
Advanced search options
Search mail
Support
Settings
Ask Gemini
Google apps
Google Account: Lukáš Koválik ([EMAIL])
Compose
Labels
Labels
Inbox
Inbox
Starred
Starred
Snoozed
Snoozed
Important
Important
Sent
Sent
Drafts 8 unread
Drafts
8
Purchases has menu
Purchases
Social 5206 unread has menu
Social
5,206
Updates 8765 unread has menu
Updates
8,765
Forums 6100 unread has menu
Forums
6,100
Promotions 38763 unread has menu
Promotions
38,763
More labels
More
Labels
Labels
Create new label
Labels
Labels
[Imap]/Nevyžiadaná pošta has menu
[Imap]/Nevyžiadaná pošta
arch has menu
arch
Deleted Items has menu
Deleted Items
Fibank 1229 unread has menu
Fibank
1,229
FL 6 unread has menu
FL
6
Hardware & Software has menu
Hardware & Software
HOSTING 5 unread has menu
HOSTING
5
Infected Items has menu
Infected Items
jiminny-github 7488 unread has menu
jiminny-github
7,488
Junk E-mail 219 unread has menu
Junk E-mail
219
Kontakty has menu
Kontakty
Sent Items has menu
Sent Items
WORK 848 unread has menu
WORK
848
z centra 1274 unread has menu
z centra
1,274
More labels
More
Select
Refresh
More email options
Show more messages
1
–
50
of
21,254
Newer
Older
Toggle split pane mode
Input tools on/off (Ctrl-Shift-K)
Select input tool
Conversations
Conversations
Primary
Primary
Primary
Promotions, 50 new messages,
Promotions, 50 new messages,
50 new
Promotions
Social, 43 new messages,
Social, 43 new messages,
43 new
Social
Updates, 12 new messages,
Updates, 12 new messages,
12 new
Updates
LinkedIn Job Alerts, Back End Software Engineer at MANUAL, 08:32, MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏.
Not starred
Not important
LinkedIn Job Alerts
Back End Software Engineer at MANUAL - MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
Back End Software Engineer at MANUAL
-
MANUAL Back End Software Engineer: VOY is a fast growing Healthtech scale-up, aiming… ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏ ͏
08:32
Apple, Your invoice from Apple., 03:49, Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX.
Not starred
Not important
Apple
Your invoice from Apple. - Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Your invoice from Apple.
-
Invoice APPLE ACCOUNT [EMAIL] BILLED TO Visa .... 4447 (Apple Pay) Lukas Kovalik Inter Expo Center Sofia, 1000 BGR INVOICE DATE 10 May 2026 SEQUENCE NO. 1-13573493610 ORDER ID MLV4N030JX
Quora Suggested Spa., Genocide in Russia!!, 21:23, Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,.
Not starred
Not important
Quora Suggested Spa.
Genocide in Russia!! - Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
Genocide in Russia!!
-
Genocide in Russia!! Only Truth About Russia Only Truth About Russia • 44.9K followers We Challenge Kremlin's Propaganda - And Are Here to Inform NOT to Please Genocide in Russia!! Alan Alan,
21:23
RescueTime Team, 15h 39m of Software Development this week - RescueTime Weekly Summary, 10 May, Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'.
Not starred
Important mainly because it was sent directly to you.
RescueTime Team
15h 39m of Software Development this week - RescueTime Weekly Summary - Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
15h 39m of Software Development this week - RescueTime Weekly Summary
-
Having trouble viewing this email? Click here to view it in your browser. RescueTime personal time management Your weekly summary May 3, 2026 - May 10, 2026 You're missing out! Right now, you'
10 May
Google Maps, Via Dental Studio – Стрелбище replied to your review on Google, 9 May, A business that you reviewed has responded to your post Via Dental Studio – Стрелбище replied to your review Thank you for posting a review on Google. Lukáš You only left a rating Response from the.
Not starred...
|
Firefox
|
Inbox - kovaliklukas@gmail.com - Gmail — Personal
|
mail.google.com/mail/u/0/#inbox/FMfcgzQgLjZlnVMGSv mail.google.com/mail/u/0/#inbox/FMfcgzQgLjZlnVMGSvVXsNgQdhSmRlKp...
|
14962
|
|
22128
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507
|
22128
|
|
22129
|
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
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507
|
22129
|
|
22130
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507
|
22130
|
|
22131
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507
|
22131
|
|
22132
|
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
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22132
|
|
22133
|
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
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22133
|
|
22134
|
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
Close tab
New Tab
Customize sidebar
Open Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
Skip to content
Skip to content
Open menu
Homepage (g then d)
jiminny
jiminny
prophet
prophet
Search or jump to…
Type
/
to search
Chat with Copilot
Open Copilot…
Create new...
All issues
All pull requests
All repositories
You have unread notifications(g then n)
Open user navigation menu
Repository navigation
Repository navigation
Code
Code
Issues
Issues
Pull requests (4)
Pull requests
(
4
)
Agents
Agents
Actions
Actions
Projects
Projects
Wiki
Wiki
Security and quality (5)
Security and quality
(
5
)
Insights
Insights
Settings
Settings
Important update
Important update
On April 24 we'll start using GitHub Copilot interaction data for AI model training unless you opt out.
Review this update
Review this update
and manage your preferences in your
GitHub account settings
GitHub account settings
.
Dismiss banner
JY-20361: Add call scores in Panorama #507 Edit title
JY-20361: Add call scores in Panorama
#
507
Edit title
Preview
Preview
Awaiting approval
Awaiting approval
Code
Code
Open
steliyan-g
steliyan-g
wants to merge 3 commits into
master
master
from
JY-20361-call-scores-in-Panorama
JY-20361-call-scores-in-Panorama
Copy head branch name to clipboard
Lines changed: 140 additions & 13 deletions
Conversation (4)
Conversation
(
4
)
Commits (3)
Commits
(
3
)
Checks (3)
Checks
(
3
)
Files changed (9)
Files changed
(
9
)
Pull Request Toolbar
Pull Request Toolbar
Collapse file tree
Open
JY-20361: Add call scores in Panorama
JY-20361: Add call scores in Panorama
#
507
All commits
All commits
steliyan-g
steliyan-g
wants to merge 3 commits into
master
master
from
JY-20361-call-scores-in-Panorama
JY-20361-call-scores-in-Panorama
Copy head branch name to clipboard
0 of 9 files viewed
Awaiting approval
Awaiting approval
Submit review
Submit
review
Open diff view settings
Open overview panel
Open comments panel
(
0
)
Filter files…
Filter options
File tree
File tree
src/component/ask_anything...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22134
|
|
22135
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplolnmeet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comDaily - Platform - now100% <2Tue 12 May 9:50:23Sот1Nikolay Yankov (Presenting, annotating)ChromeFileViewHistoryProfilesTabWindowHelp*1 SeX PrhnryAtJin* CkС мсhttps://jiminny.atlassian.net/jira/software/c/projects/JY/boards/372selectedlssue=JY-18091© AWS|X) SSH• Home | SalesforceDatadogPlatform TeamQ Sear$ Jу-16322 | JY-18091|READY FLUpgrade to PHP 8.5DescriptionBranchInitial ReleaseActive Support UntilSecurity Support Until8.323 Nov 2023|31 Dec 202531 Dec 2027|8421 Nov 202431 Dec 202631 Dec 20288,520 Nov 202531 Dec 202731 Dec 2029Currenty the Jiminny app is on PHP 8.3 which is 2 versions behind the latest one. It is already stable as was released coupleof months ago, so please proceed with an update.Here are Migration Guides:• ₴ PHP: Migrating from PHP 8.3.x to PHP 8.4.x - Manual• PHP: Migrating from PHP 8.4.x to PHP 8.5.x - ManualSubtasksAdd subtask8 мсJir8 Jirl3 CircleCl|A Sentry8• Tue 12 May 9:501[л|+D All Bookmarks|XNikolay NikolovNikolay IvanovIn Dev v* Improve Technical Story~ DetailsAssigneeNikolay IvanovAssign to meReporter3 Martin PetkovDevelopment|@ Open with VS Code2 branches11 commits1 pull request1 buildComponentsPlatformSub-Product4 othersNikolay Yankovt.meet.google.comNikolay Yankov (You, presenti..9:50 AM | Daily - PlatformLukas Kovalik4:30Lộ3...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22135
|
|
22136
|
New Tab
ActivityLateMorerireroxMistorbookmarksJimi New Tab
ActivityLateMorerireroxMistorbookmarksJiminny... v# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofa-office# support# thank-yous# the_people_of_jimi..^? Direct messages% Galya Dimitrovafe Aneliva Angelovaf Petko KashinskiStefka Stoyanovaa Vasil VasilevNikolay Ivanov(3) Aneliva Angelova…, Covan TanevVesLukas Kovali…::: Apps8 Toast" Jira Cloud(m) Google Cale...$ /2..€ BadKevwordsQuervExcer( ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpE DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen® ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.php@ Handler.php(@ HttnRodDoauoctEycontinA UttnRodDProtllesToolsWindowHelpGalya DimitrovaMessagese FilesF UntitledLukas Kovalik 9:39 AM•може и за interest tracking да намправяедин тикeтGalya Dimitrova 9:39 AMи там ли не паботи.Lukas Kovalik 9:39 AMПетко ми писа че си пристига нещо мулипсваше така че трябва да видя каквода добавя в payloadне знам още какъв точно е проблемGalva Dimitrova 9:41 AMаха, ако можеш направо сега ла гоглелаш че повали различни пооблеми неработи пялата схема с нотификациите и.Плануат от както сме пуснали фичіла. Ивсеки ден след кой го клика за да давамрепорти на CS и много ми се иска даполкапаме автомативациятаLukas Kovallk 9:41 AMдобреGalva Dimitrova 9•42 AMто пьово і ланхат имаха оъг и ги чаках.елна селмица ла го фикснаїMessage Galya Dimitrova In a meeting •+ Aaervice lest.oneRematchActivity(New Tabfoller.pnpC) Hubsoot/Service.onpJy 20820 es reindex stream mode)MatchacuivityermData.png4 [JY-20725] [HubSpot] Optimise CISautomatedkeporc.JY-20725 add HS rate limit handliPipelines - jiminny/app•Pull requests • jiminny/ap:WY-207731 User Pilot not receivina( JY-20773 fix user pilot tracking oftWY-20776) Automated report - se) TypeError: League|Flysystem|Files)A TypeError: Leaque\Flysystem\FilesPlatform Sprint 3 02 - Platform Te:( JY-20361: Add call scores in P: X— New Tal• github.com/finJY-20361: Add call scores in Panorama #507|steliyan-g wants to merge 3 commits into master from JY-20361-call-scores-in-Panorama• Filter files....src/component/ask_anything/streamers/filtered_calls_ask_anything_streamer.py L $@@ -630,6 +630,21 @@ def _build_batch_prompt_template(self, call_ids: listlstr]) →> str:• • src/component/ask_anythingv — prompts/filtered _callsf"===\nin"ask_anything.txt₴ system.txtv E streamersE) filtered calls ask anvthina s.* find_optimal_aa_deal_prompt_..v — tests/componentv F ask anvthina/streamers* test_ask_anything_any_calls_.E) test_filtered calls ask anythi…..• E crm auto populate* test_call_prompt_builder_ai_s...E) test format ai call score co..v • generic|* test_deal_context_ processor_.f"# Response Instructions\n\n"*"Pooulate the JS0N schema provided. Each field should be "f"filled as follows:\n\n'aa -1028.7 +1043.7 aa asvnc def build affiliation text(self. call ids: listlstr]) -> str:10281return DEFAULT AFFILTATION TEXTI1020110301# Extract vendor company name and team id from the ES document1031 -1032first_call_doc = list(call_es_data.values())[0]team data = first call doc.det(" source". {}).aet("userlli. {}).aet("team". {})10221vendor_company_name = team_data.get ("name")10341v src/component/ask_anything/find_optimal_aa_deal_prompt_service.pyaa -25,7 +25,7 @a class FindOptimalAADealPromptService(Find0ptimalAAPromptService):def _init_(self,63163263= a< 40 lil I Daily - Platform -nowБГ100% C7 • Tue 12 May 9:50:23C 0 9 viewec•Awaiting approva+16 -1 99880• Viewedf"===nin'f"## AI call scores (when present in each call JSON) \n\n"# Literal '{ai_call_score}' for the LLM must survive "'str.format"' - usef"- **'{{{{ai_call_score}}}}***: Pre-computed output from your team's AIcall-scoring pipeline "f"(same family as '/call/ai-call-scoring'). Do **not** invent or recalculatescores.\n"tor that ""- xx al scorecard.name*** and kii score **: Scorecard name and overall scoref"scorecard (overall score is typically the average of per-rule scores,possibly a decimal).\n"kx'rule name**k."f"- **'ai_scorecard_rules'**: Per-criterion rows when included:f"***rule_prompt'**, integer **'score'** from 1 (no evidence / not met)through 5"f"(strong evidence on the call), **'justification'**, and optional "("*k' iustification timestamos'** (speaker name + MM:SS in the recording).\n'f"- Use scores when they help answer the user's question; ground statementsf"exact dialoque in the **transcript**.\n\n"fil___\n\n"f"# Response Instructions\n\n"f"filled as follows:\n\n"10431олАI10451046 +1047return DEFAULT AFFILIATION TEXT# Extract vendor comnany name and team id fron the ES documentfirst_call_doc = next(iter(call_es_data.values()))tean data = first call doc.et(" source". {}).aet("user!. {}).aet("team". {})10л9|vendor_company_name = team_data.get ("name")4oлd25 |def init (self....
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22136
|
|
22137
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22137
|
|
22138
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplolnmeet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting, annotating)ChromeViewHistoryProfilesTabWindowHelp*1 SeX PrhnryAt:JinС мсJir8 Jirlhttps://jiminny.atlassian.net/jira/software/c/projects/JY/boards/3788© AWS|X) SSH• Home | Salesforce |DatadogPlatform TeamQ Sear$ Jу-16322 | JY-18091|READY FLUpgrade to PHP 8.5DescriptionBranchInitial ReleaseActive Support UntilSecurity Support Until8.323 Nov 2023|31 Dec 202531 Dec 2027S8421 Nov 202431 Dec 202631 Dec 20288,520 Nov 202531 Dec 202731 Dec 2029Currenty the Jiminny app is on PHP 8.3 which is 2 versions behind the latest one. It is already stable as was released coupleof months ago, so please proceed with an update.Here are Migration Guides:• ₴ PHP: Migrating from PHP 8.3.x to PHP 8.4.x - Manual• PHP: Migrating from PHP 8.4.x to PHP 8.5.x - Manualот1SubtasksAdd subtask8 мс3 CircleciA SentryIn Dev v* Improve Technical Story~ DetailsAssigneeNikolay IvanovAssign to meReporter3 Martin PetkovDevelopment|@ Open with VS Code2 branches11 commits1 pull request1 buildComponentsPlatformSub-ProductDaily - Platform • now100% <2Tue 12 May 9:50:27=8• Tue 12 May 9:501[л|+D All Bookmarks |Nikolay NikolovNikolay Ivanov4 othersNikolay Yankovt.meet.google.comNikolay Yankov (You, presenti..+7Lukas Kovalik4:34Lộ39:50 AM | Daily - Platform...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22138
|
|
22139
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelp1olnmeet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting, annotating)ChromeFileViewHistoryProfiesTabWindowHelp1 SeX PrAthttps://jiminny.atlassian.net/jira/software/c/projects/JY/boards/37© AWS|X) SSH• Home | Salesforce |Platform TeamQ Search board03Epic vType vQuick filters vREADY FOR DEVIN DEV 3CODE REVIEW 2BLOCKEDUpgrade to PH...PHP 8.5 UPGRADEIn Dev3 П •••=AJ Panorama forCall Scoring in ODAUTOMATED AI SCORINGCode Review+ Create2.5 1 =E JY-18091|[ JY-20361|SAI Review - Q1 -Summary/Actionitems/Key PointsGROWTH - MAINTAIN OU...In Dev[HubSpot] OptimiseCRM rematching ondelete hubspot…PLATFORM STABILITYCode Review2 @ .00=[ JY-20566|X* JY-20725[POC)Jiminny MCPConnectorJIMINNY MCP CONNECTORIn Progress10 1 [PASSWORD_DOTS]=@JY-20625At:*JinClС м8 мсJirDatadogCircleClComplete sprintQA 2PO ACCEPTANCESmart InstantNudge Pre-filteringCOST-EFFECTIVE AND FA...Ready for QA|15 П =[ JY-20493Sync opportunitieswithout a localowner (user_id is...PLATFORM STABILITYIn QA9) JY-20352Daily - Platform - now8• Tue 12 May 9:50Jin1[л|+Sentry0 All BookmarksGroup: QueriesDEPLOY 9AI Reports > Emptypage design andpromotionAJ REPORTSDeployed1 1 [PASSWORD_DOTS]=• JY-20372Grok via AzureMAINTENANCEDeployed• •000=.[ JY-20726t.meet.google.comNikolay Yankov (You, presenti.Nikolay NikolovNikolay Yankov+79:50 AM | Daily - PlatformLukas Kovalik4:41Tue 12 May 9:50:34Nikolay Ivanov4 others...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22139
|
|
22140
|
New Tab
New Tab
LateMorerireroxcalJiminny ... ~# c New Tab
New Tab
LateMorerireroxcalJiminny ... ~# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofa-office# support# thank-yous# the_people_of _jimi...^? Direct messages% Galya DimitrovaAneliva Angelovaf Petko KashinskiStefka Stoyanovaa Vasil VasilevNikolay Ivanov(3) Aneliva Angelova…, Covan TanevVesLukas Kovali…::: Apps8 Toast" Jira Cloud(m) Google Cale...$ /2..€ BadKevwordsQuervExcer( ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpC DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen® ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.php© Handler.php(@ HttnRodDoauoctEycontinA UttnRodDMistorbookmarksProtllesToolsWindowHelpe Galya DimitrovaMessagese FilesF UntitledLukas Kovallk 9.39 AM•може и за interest tracking да намправяедин тикетGalya Dimitrova 9:39 AMи там ли не паботи.Lukas Kovalik 0.39 AMIПетко ми писа че си пристига нещо мулипсваше така че трябва да видя каквода добавя в payloadне знам още какъв точно е проблемGalva Dimitrova 9:41 AMаха, ако можеш направо сега ла гоглелаш че повали различни пооблеми неработи пялата схема с нотификациите и.Плануат от както сме пуснали фичіла. Ивсеки ден след кой го клика за да давамрепорти на CS и много ми се иска даполкапаме автомативациятаLukas Kovallk 9:41 AMдобреGalva Dimitrova 9•42 AMто пьово і ланхат имаха оъг и ги чаках.елна селмица ла го фикснаїMessage Galya Dimitrova In a meeting •+ Аa• github.com/jiminny/prophet/pull/507/changestroller.onpNew Tabe Jy 20820 es reindex stream mode4 [JY-20725] [HubSpot] Optimise Cl@ JY-20725 add HS rate limit handlilPipelines - jiminny/app• Pull requests • jiminny/appWY-207731 User Pilot not receivinJY-20773 fix user pilot tracking ofNY-20776 Automated report - seTypeError: League|Flysystem\FilesA TypeError: League\Flysystem|FilesPlatform Sprint 3 02 - Platform TeJY-20361: Add call scores in P: X— New TabGoogle GemintAsk GeminiSummarize page<S0 Wl © | Daily - Platform • nowБГ100% L2• Tue 12 May 9:50:378? OperJY-20361: Add call scores in Panorama #507 |All commits -steliyan-g wants to merge 3 commits into master from JY-20361-call-scores-in-Panorama• 0 9 vieweo•Awaiting approval...component/ask_anything/streamers/filtered_calls_ask_anything_streamer.py [*Viewed• Filter files..str.format — use 11111v • src/component/ask_anythingf"- ***{{{{ai_call_score}}}}***: Pre-computed output fromyour team's AI call-scoring pipeline "• — prompts/filtered_calls636f"(same family as /call/ai-call-scoring*). Do **not**( ask anvthina.txtinvent or recalculate scores.n"system.txt637f"= *k'ai scorecard name'** and *k'score'**: Scorecard nameand overall score for that"v - streamers638f"scorecard (overall score is typically the average of per-E filtered calls ask anvthina s....rule scores, possibly a decimal)..n"* find_optimal_aa_deal_prompt_...639*k'ai scorecard rules'**: Per-criterion rows whenlincluded: ** rule name **,"v = tests/component640f"**' rule pronnt'**. integer **'score'** from 1 (no evidence• F ask anvthing/streamersnot met through s* test_ask_anything_any_calls_f"(strong evidence on the call), **'justification **, andoptional "E test_ filtered calls ask anythi...642f"*k'iustification timestamns'** (sneaker name + MM•SS inlv E crm auto populatel* test_call_prompt_builder_ ai_s...643the recordina).n"ground statements about"E test format ai call score co....f"_ Iise scores when they heln answer the user's question:f"exact dialoque in the *ktranscrint*k.nn"• E generic644645 #* test_ deal_context_processor_..f"===\nln"634f"# Response Instructions\n\n"f"Populate the JSON schema provided. Each field should be "648*"* Resnonse Tnstructionsin\n'f"Populate the JSON schema provided. Each field should be"f"filled as follows:\n\n"f"filled as followsa\n\n"@a -1028,7 +1043,7 @ async def_build affiliation_text(self, call_ids: list[str]) →> str:102810291030return DEFAULT_AFFILIATION_TEXTreturn DEFAULT_AFFILIATION TEXTI# Extract vendor company name and team id from the ES1045# Extract vendor company name and team id from the ESdocumentdocumenti1031 -10321first_call_doc = list(call_es_data.values())[0)tean data = first call doc.det(" source". {}).aet("user".1046 +1047|tirst call doc = next(1ter(call es_data.values)))team_data = first_call_doc.get("_source", (}).get("user",(}).qet ("team", {}){).qet("team",‹›)10331034.vendor_company_name = team_data.get("name")10481vendor comnanv name = team data.aet ("name")src/component/ask anvthing/find ontimal aa deal promot service.rv (+1-20441 Viewed0@ -25,7 +25,7 @@ class FindOptimalAADealPromptService(FindOptimalAAPromptService):def init (self.raw nronnt. strdef init (self.raw nromnt: str.token count: TokenCounter.token count: TokenCounter....
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22140
|
|
22141
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22141
|
|
22142
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22142
|
|
22143
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22143
|
|
22144
|
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
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22144
|
|
22145
|
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
Close tab
New Tab
Customize sidebar
Close Google Gemini (⌃X)
Tabs from other devices
Open history (⇧⌘H)
Open bookmarks (⌘B)
AI Chat settings
Close...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22145
|
|
22146
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22146
|
|
22147
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22147
|
|
22148
|
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
Close tab
New Tab...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22148
|
|
22149
|
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
Close tab...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22149
|
|
22150
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22150
|
|
22151
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22151
|
|
22152
|
New Tab
New Tab
FirefoxFileEditViewHistoryBookmark New Tab
New Tab
FirefoxFileEditViewHistoryBookmarksProfilesTools|WindowHelp(ollmeet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting, annotating)ChromeFileViewHistoryProtiesTabWindowHelp*XPrinry4 AtCo+ ATTС мс3 мс8 Jinhttps://jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedlssue=JY-20566E::x) SSH Home | SalesforceDatadog* Claude |3 CircleciA SentryPlatform TeamЯ.Q Sear• Jy-12251 / П Jy-20566|READY FIAI Review - Q1 - Summary/Action items/Key PointsIn Dev v4* Improve StoryDetailsDescriptionRun AI evaluations for all Al services against the latest versions of trending LLMs and store the results in Google Drive. Wewant to see how our features are tracking comparing to new models that are coming out.AssigneeSteliyan GeorgievAssign to me• Summary on Call|• Action Items on Call• Key Points on Call|• AA on Call• AA on Deal• Identify participants for mono calls• ensure the evaluations are done with a smarter model and the scores are accurateReporter8 Galya DimitrovaDevelopment|@ Open with VS Code1 branch14 commitsUpdate the test-set. Remove the deleted activities.1 pull request4 buildsSubtasks•••@D +66% DoneReleasesA› Production› See all deploymePriorityAssigneeComponentsDaily - Platform - now100% 128•Tue 12 May 9:51:1498• Tue 12 May 9:51All BookmarksXSteliyan GeorgievNikolay Ivanov4 othersNikolay Yankov7 days agoB3meet.google.comNikolay Yankov (You, presenti.9:51 AM | Daily - PlatformLukas Kovalik5:21...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22152
|
|
22166
|
ActivityFllesLateMoreSlackcalVIewJiminny ...# curi ActivityFllesLateMoreSlackcalVIewJiminny ...# curiosity lab# engineering# general# jiminny-bg# platform-tickets# product launches# random# releases# sofa-office# support# thank-yous# the_people_of_jimi..^? Direct messagesGalya DimitrovaIe Aneliva Angelovaf Petko KashinskiStefka Stoyanovaa Vasil VasilevNikolay Ivanov(3) Aneliva Angelova., Covan TanevVesLukas Kovali…::: Apps8 Toast" Jira Cloud(m) Google Cale...€ BadKevwordsQuervExcer(-) ConfiqurationExceotion.o© CrmException.php-crmUndateSycention.ohr© DatabaseException.phpC DealRisksEycention.nhn© DomainException.phpC EmailActivitvimnortEycen® ErrorDownloadingEmptyF) ErrorDownloadina Sile Syci© FileNotFoundException.pl© FileSystemException.phpA Handlor nhn(@ HttnRodDoauoctEycontinA UttnRodDMistonWindowhelp@ Describe what you are looking for* # engineering8 24Messagest Canvase FilesMoreecfix/npm20260507)circlec APP 9-10 ANO 2 PRe with vulnerabli fivec are readu torreviewPlease take a look ar the code and conhrmthar evervthing works nronerlv on vourlocal machine or on a nianet environmentPull requests (liminny aoo)• 412049 hx cecurimv: comnoserdenendencv undates - 2026-05-07secfix/comnoser-20260507• #12048 fx(security): npm dependencyupdates - 2026-05-07 (secfix/npm-20260507)llan Kvuchukov 9.50AMClaude's limits are getaing ridiculous. whatare you using from OpenAI? GPT-5.3Codey?• 2 replies Last reply today at 9:51.Message #engineering+ Aalfoller.pnpSautomatNew Tabe Jy 20820 es reindex stream mode4 [JY-20725] [HubSpot] Optimise Cl@ JY-20725 add HS rate limit handlilPipelines - jiminny/app• Pull requests • jiminny/appWY-207731 User Pilot not receivinJY-20773 fix user pilot tracking ofiNY-20776 Automated report - seTypeError: League\Flysystem|FilesA TypeError: League\Flysystem|FilesPlatform Sprint 3 02 - Platform TeJY-20361: Add call scores in P: X— New Tabi• github.conGoogle Gemint• Context Breakdown: * Short Context:Now aisolays tne Al scorecaro name anothe overall Al Score (the average of per-rule scores).Full Context: Detalls theai call score object, breakingdown Individual rules. Lach rulelincludes a title. oromot. a 1-5 score. arier lustricarion. and Uo to unreeuimestamoeo soeaker rererences(MM:SS).• Application Rules: Instructs the systemto use these scores for coachingsummaries, trends, and comparisons, butdictates that it must still rely on the actualtranscript for direct quotesGrammar and Formatting Cleanup• Typo Corrections: Fixed minorgrammatical errors, such as changing"contexts" to "context" and "utlizes" to"utilizes".• String Formatting: Cleaned up themarkdown link instructions by removingtrailing newline and quotation artifacts(\n" ) from the teyt strinasO Enter a prompt for GeminiPro vYour Jiminny chats aren't used to improve our models. Gemini iwwcanmikomicnkos colcolliolechocktYottr.orivacvtromSummarize pageJY-20361: Add call scores in Panorama #5071o Opersteliyan-g wants to merge 3 commits into master from JY-20361-call-scores-in-Panorama• Filter files..v src/component/ask_anything/prompts/filtered_calls/system.txt L *- clarity or resocve ampiguities It the question aepenas on priorv • src/component/ask_anything• — prompts/filtered_calls( ask anvthina.txt...component/ask_anything/streamers/filtered_calls_ask_anything_streamer.py Mt* system.txt@a -630,6 +630,21 @ def_build_batch_prompt_template(self, call_ids: list(str]) →> str:|v E streamersf"==-nin"E filtered calls ask anvthina s....* find_optimal_aa_deal_prompt_...v = tests/component• $ ask_anything/streamers635* test_ask_anything_any_calls_E test_ filtered calls ask anythi...636v E crm auto populatel* test_call_prompt_builder_ ai_s...E test format ai call score co....• E generic* test_ deal_context_processor_..63510291030.1931 16456466471'# Response Instructions n.n"f"Populate the JSON schema provided. Each field should be "649t"tilled as follows: n\n"650aa -1028.7 ÷1043.7 @a asvnc def build affiliation text(self. call ids: list[strl) -> str:Ireturn DEFAULT AFFTITATTON TEXTI10431# Fxtract vendor comnanv name and team id from the Fo10451documentfirst call doc = list(call es data.values())(911046team data = first call doc.aet(" source". <).aet("user".<→0 lhl • | Daily - Platform • now100% LzP• Tue 12 May 9:52:10• 0 9 vieweo•Awaiting approva+4 -1 00002Viewed...- clarity or resouve ampiguities It the question aepenas on priormessages.+16 -1 m000 ^7 Viewedf"==-\nn"f"## AI call scores (when present in each call JSON)\n\n"# Literal "ai call score, for the LLM must survivel"str.format'1-use {{{{"1"- *k111ai call scorer **: Pre-computed output fromyour team's AI call-scoring pipeline"1"(same family as /call/ai-call-scorina ). Do **not**invent or recalculate scores.\n't"- * ai scorecard name ** and ** score **: Scorecard nameand overall score for that"t"scorecard overall score is tvpically the average of per-rule scores, possibly a decimal).\n"f"- *k ai scorecard rules **: Per-criterion rows whenincluded: **'rule_name'**, "f"** rule prompt **, integer ** score ** from 1 (no evidence/ not met) through 5"1"«strona evidence on the calu, ** 1ustitication **, andontional"1"** 1ustitication timestamps ** speaker name + MM:SS 1nithe recordina)..n'1"- Use scores when they nelp answer the user's question:ground statements about "t"exact dialoque in the **transcriot**.nn'f"'_--\n\n"f"# Response Instructions\n\n"f"Populate the JSON schema provided. Each field should be "1"tilled as tollows:n.n"return DEFAULT AFFTITATTON TEXT# Fxtract vendor comnanv name and team id from the Fdfirst call doc = next(iter(call es data.values()))team data = first call doc.aet(" source", <).aet("user".....
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22166
|
|
22167
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22167
|
|
22168
|
FirefoxFileEditViewHistoryBookmarksProfilesToolsWi FirefoxFileEditViewHistoryBookmarksProfilesToolsWindowHelplolnmeet.google.com/mie-gawc-dsi?authuser=lukas.kovalik%40jiminny.comNikolay Yankov (Presenting, annotating)ChromeFileViewHistoryTabWindowHelp*$8• Tue 12 May 9:52X Prinry• At|Co4 At:a Jin ClС мс | E3 мс• Jin8 Jinhttps://jiminny.atlassian.net/jira/software/c/projects/JY/boards/37?selectedlssue=JY-20566E::Home | SalesforceDatadog3 CircleCiA SentryD All BookmarksPlatform TeamQ Sear$ Jy-12251 / П JY-20566|READY FIAI Review - Q1 - Summary/Action items/Key PointsIn Dev v* Improve StoryDetailsDescriptionRun AI evaluations for all Al services against the latest versions of trending LLMs and store the results in Google Drive. Wewant to see how our features are tracking comparing to new models that are coming out.Assignee3 Steliyan GeorgievAssign to meReporterGalya Dimitrova• *• Summary on Call |• Action Items on Call|• Key Points on Call |• AA on Call• AA on Deal• Identify participants for mono callis• ensure the evaluations are done with a smarter model and the scores are accurateUpdate the test-set. Remove the deleted activities.Development|@ Open with VS Code1 branch14 commits1 pull request4 builds7 days agot:meet.google.comNikolay Yankov (You, presenti..Subtasks|•••@+66% DoneReleasesA Production› See all deployme0+7PniortyAssigneeComponentsDaily - Platform - now100% L28•Tue 12 May 9:52:129Steliyan GeorgievStefka Stoyanova4 othersGalya Dimitrova9:52 AM | Daily - PlatformLukas Kovalik6:19...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22168
|
|
22169
|
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...
|
Firefox
|
JY-20361: Add call scores in Panorama by steliyan- JY-20361: Add call scores in Panorama by steliyan-g · Pull Request #507 · jiminny/prophet — Work...
|
github.com/jiminny/prophet/pull/507/changes
|
22169
|