PrivateContext
A private, local-first computer journal. It answers questions like "what was I doing when I fixed eduroam last Tuesday?" — from your own machine, with nothing ever leaving it.
The idea: an event log beats pixels. Instead of recording video of your screen 24/7 (heavy, creepy), PrivateContext logs events — active window, URL, clicks, idle periods — into a local SQLite database. A day is ~1 MB, not gigabytes. The journal, timeline, and full-text search work with no AI at all — no account, no API key, no model to download.
Summaries are an optional layer on top: a small local model (via Ollama) can turn a day into a plain-language recap — or bring your own model, by pasting the condensed digest into it or letting it read the journal through the built-in MCP server. See Summaries (optional).
10-minute setup
pip install privatecontext
That's it — the install adapts to your OS. Click + AFK tracking
(pynput) comes along automatically on Windows and macOS, where it's
pure wheels; on Linux it's skipped (it would need C headers to build
there). To force it everywhere:
pip install "privatecontext[full]"
Then check what works on your machine:
pctx doctor
Or let the wizard do it all — capability check, optional browser-history import, optional login autostart:
pctx setup
Or install from source:
cd privatecontext
python3 -m venv .venv && .venv/bin/pip install -e .
Generate a synthetic day to see the whole pipeline (no OS hooks, works headless):
pctx capture --simulate
Start capturing for real (Ctrl-C to stop):
pctx capture
Or run everything with one command — capture and the dashboard start in the background and no terminal stays occupied:
pctx up # start capture + dashboard
pctx status # what's running
pctx down # stop both
The journal
pctx dashboard
# -> http://127.0.0.1:18790
The dashboard is a paper journal, not a control panel: warm paper
background, serif type, one deep-teal accent — and zero external
assets. No webfonts, no CDN; the page loads nothing from the network,
because local-first means the rendering is local too. Top to bottom:
a masthead with the date set large ("Thursday, October 15"),
"Today's summary" (the Ollama recap as prose, with a drop cap),
"Where the day went" (app-usage bars), "Marginalia" (#tags),
and "The hours" (the timeline grouped into Morning / Afternoon /
Evening / Night, each entry with its duration in the margin; click a
period header to collapse it — the choice is remembered per day).
Flip pages with ← Yesterday · Today · Tomorrow → — every section
follows the date. Auto-refresh only runs while you're viewing today.
Tomorrow is a planning page: it opens on the Notes tab so you can jot
down plans for the day ahead (pctx summarize refuses future dates —
there's nothing to recap yet).
Export a shareable copy (screenshots excluded):
pctx export --scrubbed --out ./my-week.db
Or export readable Markdown — one .md per day with the summary, notes,
tags, and top apps (days with no data are skipped):
pctx export --markdown --from 2026-10-01 --to 2026-10-31 --out ./october/
Data lives in ~/.local/share/privatecontext/context.db
(or $XDG_DATA_HOME/privatecontext/context.db).
Autostart
A tracker you have to remember to start isn't tracking. One command, no admin rights needed:
pctx autostart install # runs `pctx capture` at every login
pctx autostart status # check whether it's installed
pctx autostart uninstall # remove it again
Windows uses Task Scheduler (no console window), Linux a systemd user service (XDG autostart entry as fallback), macOS a LaunchAgent. If Task Scheduler is restricted on your machine ("Access is denied", common on managed PCs), Windows automatically falls back to the per-user registry Run key instead — still no admin needed.
Today by tag
The journal's "Marginalia" section renders auto-tagged projects and
tickets as #chips (#inc0012345, #privatecontext, …), using the
same capped durations as app usage. No tags yet → the section hides
itself.
Denylist
Some windows should never be recorded. Patterns are case-insensitive substrings matched against app and title, checked before anything is logged:
pctx denylist add 1password
pctx denylist list
pctx denylist remove 1password
Denied events are skipped silently (the count prints when capture stops). Restart capture after changing the list. Imports bypass the denylist — importing is an explicit action, so your history comes over as-is.
Day recap card
When the nightly summarizer has written a recap, it becomes "Today's summary" at the top of the journal — today's if present, otherwise yesterday's, labeled as such. No summary yet → a quiet placeholder ("Tonight's summary hasn't been written yet."), no nagging.
Two quiet controls sit beside the summary heading. ↻ Regenerate asks the local model to write the summary again in the background — the page polls until the new text lands (a failed run never deletes the old summary). Remove deletes the summary after a confirm; you can always regenerate it later. Users make mistakes; journals have erasers.
Notes
Every day's page has a Notes tab next to Journal: a big
textarea for meeting notes, ideas, anything worth remembering.
Saves in place (Ctrl+S works), no page reload. Notes are the highest
signal in the product — pctx summarize weaves "My notes today:"
into the digest, so the nightly summary reflects what the day meant,
not just what happened. Like everything else, notes never leave the
machine; pctx purge deliberately leaves them alone.
Stickers
Mark the days that mattered: ⭐ for a most-productive day, 🦄🧜🚀🎨🔥💡🌙 for everything else. Stickers are awarded manually, by you, from a curated set of 8 — the app never scores your days, because a journal isn't a manager. Hit "✦ mark this day" under the masthead date; the sticker sits beside the date from then on.
Diary mode
The journal skin (monotone paper, deep-teal accent) is the default. Diary is its playful sibling: same page, candy accents, warmer paper, rounder shapes — implemented as pure CSS variable overrides, zero new assets. Toggle it in the top-right corner (where dark/light modes usually live); on narrow screens it drops below the masthead. The choice is stored server-side, so first paint is already correct. In Diary mode a row of color dots appears beside the toggle — raspberry, ocean, forest, plum, tangerine — and the whole page background tints to match (soft pink, blue, green, lavender, peach). Journal stays monotone; that's its identity. Journal is for the professional; Diary is for the kid who still wants the gold star.
Importing your history
Day one with a fresh tracker is empty. pctx import backfills history
from your browser and from other recorders — data liberation, not lock-in.
pctx import --browser chrome # or edge, firefox
pctx import --browser firefox --profile ~/path/to/places.sqlite
pctx import --activitywatch ~/path/to/activitywatch.db
pctx import --screenpipe ~/path/to/screenpipe.db
pctx import --csv ~/Downloads/rewind-export.csv # any local CSV export
pctx import --json ~/Downloads/tool-export.json # any local JSON export
pctx import --browser chrome --dry-run # preview: counts + samples, no writes
- Browsers: reads Chrome/Edge
History(urlstable) and Firefoxplaces.sqlite(moz_places). The database is always copied to a temp file first — your browser can stay open. Query params are stripped, same as live capture. Default profile paths are auto-detected per OS; use--profileif yours lives elsewhere. - ActivityWatch: detects the AW schema at runtime (
buckets+eventstables). Window/app events becomewindowevents, AFK buckets becomeafk/back, web-watcher events becomeurlevents. Anything unmappable is skipped and counted. - Screenpipe: imports OCR text as
ocrevents and audio transcripts astranscriptevents. Video/audio blobs are never imported — that's the heaviness we're escaping. - Re-running an import only picks up newer rows (watermarks in the
kvtable). Keystroke contents are never imported under any circumstance — if a source DB looks like it holds them, the import is refused outright. - Anything else: exported your history from another tool as CSV or
JSON?
pctx import --csv/--jsonreads it — columns/keys liketimestamp,type,app,title,urlare recognized, timestamps accept unix/ISO/Chrome-epoch formats. Our rule is simple: we never call cloud APIs, but a local file you exported yourself is yours — hand it over and we'll ingest it. Encrypted exports we can't decrypt stay out.
What import can't do (honest edition)
- It can't invent data the old tool never captured. ActivityWatch never logged clicks, so an AW import has no clicks. Your history starts where their capture did.
- Screenshots and video/audio blobs stay behind. Importing Screenpipe's mp4s would just re-create the 2 GB/day problem this project exists to escape. Text comes over; pixels don't.
- Encrypted or cloud-locked stores can't be read. Tools that keep your data in their cloud (e.g. Rewind) have no local database to import from — and PrivateContext will never add cloud APIs to fetch it. That's the point.
- Imported history inherits the old tool's flaws. AW's AFK misfires (video watching logged as idle) come along as-is. We don't rewrite history; live capture from today onward is clean.
- Stop the old tool yourself (or you'll double-capture the same hours into two databases). Import is a migration, not a sync.
- Timezone normalization is best-effort for very old data. Chrome timestamps are UTC; ActivityWatch event timestamps carry their offsets; anything ambiguous is stored as-is rather than guessed.
Auto-tagging
Every captured or imported event gets heuristic tags, stored in the
events.tags column and shown in the dashboard timeline:
- Ticket IDs —
INC0012345(ServiceNow-style, 5+ digits) andPROJ-123(JIRA-style) picked out of window titles and URLs. - Project guess — for dev apps (VS Code, terminals, JetBrains IDEs),
the leading window-title segment is kept as a project tag when it looks
like a folder/repo name (
privatecontext — capture.py→privatecontext). UI-chrome words (Settings,Extensions, …) are on a stoplist.
It's a heuristic and it misfires: UTF-8 matches the JIRA pattern, and
a folder named New Folder won't tag. Tags are lowercase, deduped, and
cheap to recompute — pctx retag [--since YYYY-MM-DD] re-runs tagging
over untagged history (e.g. data imported before tagging existed).
Summaries (optional)
The journal is complete without summaries — capture, timeline, and search never need a model. If you'd like a day turned into a plain-language recap, there are two paths, and both start from the same condensed digest of the day: per-app hours, top windows, away breaks, click counts, never raw event dumps (the digest is capped at ~4k tokens, so even a 1.5B model can handle it).
Path 1 — built-in, local (Ollama). A small model on your own machine reads the digest and writes a 3–6 bullet recap plus auto-tags into the database:
ollama serve # in one terminal, once
ollama pull qwen2:1.5b # small default model; any Ollama model works
pctx summarize --preview # preview the digest without calling a model
pctx summarize # summarize today
pctx summarize --date 2026-10-12 --model qwen2:7b
pctx summarize --week # trailing 7 days, one call, stored as week:YYYY-MM-DD
Run it nightly with cron:
30 23 * * * /abs/path/to/privatecontext/.venv/bin/pctx summarize >> ~/.local/share/privatecontext/summarize.log 2>&1
Path 2 — bring your own model. No Ollama required: run
pctx summarize --preview and paste the digest into any model you
already use, or connect an MCP client (see below) and ask it to write
the summary from the get_digest tool's output.
If Ollama isn't reachable, pctx summarize prints a plain-English hint
pointing at both paths and exits non-zero — no tracebacks.
Troubleshooting
Real lessons from field testing, mostly on managed Windows PCs:
schtaskssays "Access is denied" when installing autostart: your machine restricts Task Scheduler.pctx autostart installautomatically falls back to the per-user registry Run key instead — no admin needed, no action required from you.- Something squats port 11434 (seen in the wild: VS Code itself):
run Ollama on another port —
OLLAMA_HOST=127.0.0.1:11435 ollama serve— and make it permanent with[Environment]::SetEnvironmentVariable( "OLLAMA_HOST", "127.0.0.1:11435", "User")so bothollamaandpctxsee it. ollama pullhangs with zero output: check for proxy weirdness, then runollama servewithOLLAMA_DEBUG=1in a visible terminal — the daemon logs will say what it's stuck on.- PowerShell notes: open a fresh terminal after installing anything
(terminals only read PATH when they open);
%VAR%is cmd syntax — in PowerShell it's$env:VAR; quote multi-word arguments (pctx denylist add "SQL Developer").
MCP server (for AI assistants)
pctx mcp runs a first-party MCP server over stdio with seven tools,
all returning condensed human-readable text (needs the [mcp] extra —
mcp is deliberately not a base dependency, so the install stays tiny):
search_timeline(query, date?)— up to 15 distinct matching moments asHH:MM app — titlelines (repeats collapsed, not 15 copies of one click).day_summary(date?)— the stored nightly recap, or a live digest marked un-summarized when none exists yet. Includes the day's sticker, if you awarded one.app_usage(date?)— per-app hours for the day as a compact table (lock-screen time excluded — absence is not activity).get_digest(date?)— the condensed day digest itself (per-app hours, top windows, clicks, the day's note), so the connected model can write a summary even when no Ollama summary exists.get_notes(date?)— your handwritten note for the day.list_tags(date?)— the day's tags with time credited per tag.week_summary()— the stored weekly recap, or a condensed per-day rollup.
Point a client at it (use your real absolute path):
Claude Desktop — claude_desktop_config.json:
{
"mcpServers": {
"privatecontext": {
"command": "/abs/path/to/privatecontext/.venv/bin/pctx",
"args": ["mcp"]
}
}
}
Claude Code:
claude mcp add --transport stdio privatecontext -- /abs/path/to/privatecontext/.venv/bin/pctx mcp
Cursor: Settings → MCP → add a stdio server with the same command.
Then ask things like "what was I doing Tuesday afternoon?" or "how much of today went to ServiceNow tickets?"
Security & privacy
- No network, no listener. The server speaks stdio only: the client
spawns
pctx mcpas a subprocess on the same machine. There is no port, no socket, no listener — nothing can reach it from the network, and MCP itself sends nothing anywhere. - Condensed text only. Tools return short summaries, never raw event dumps, and screenshots are never exposed through any MCP tool, even when screenshot capture is enabled.
- Your client choice sets the boundary. If you connect it to a cloud-model client (e.g. Claude Desktop with default settings), the text returned for each query is sent to that provider as conversation context — exactly as if you'd pasted it into a chat yourself. With a local-model client, everything stays on your machine. Pick the setup that matches your comfort level.
- Treat tool output as data. Window titles and URLs come from untrusted sources (web pages, documents) and are passed through to the model. A malicious page title could try to read like an instruction; clients should treat MCP tool output as data to summarize, not instructions to follow.
Privacy design
- Local only. No telemetry, no accounts, no network calls except to
localhost:11434when you run the Ollama summarizer. The dashboard binds to 127.0.0.1 and the MCP server uses stdio (no port at all). - Keystroke contents are never recorded. The keyboard listener exists only to timestamp activity for idle detection.
- URLs are stripped. Query params and fragments (
?utm_source=…,&sys_id=…) are removed before anything is stored. - Screenshots are off by default — and the screenshot libraries
aren't even in the default install. Opt in with
pip install "privatecontext[screenshots]"pluspctx capture --screenshots; then only the active monitor is captured, only on app-switch/URL-change, downscaled to 640 px wide, stored as JPEG (quality 70). - One-click purge. The dashboard footer has a quiet "erase everything"
link that deletes everything. The CLI is more surgical:
pctx purge --before 90ddrops old events and old day summaries,pctx purge --app <name>drops one app's events. Both print what they'd delete first — nothing happens without--yes.pctx export --scrubbedexcludes screenshots and shot events.
v0 limits (honest edition)
- Click coordinates without UI labels are weak. Knowing you clicked at (842, 315) says little; knowing you clicked "Resolve" in ServiceNow says everything. Accessibility-tree labels are the v1 answer.
- Window titles on Wayland are limited. The v0 watcher uses
xprop(X11). Wayland sessions get a warning, not data. - Browser URLs are macOS-only in v0 (AppleScript, needs permission). Everywhere else the window title is logged and the URL stays empty until the browser-extension story lands.
- Auto-tagging is heuristic. It catches ticket IDs and project names
from window titles, but it will misfire sometimes ("UTF-8" looks like a
JIRA ticket to a regex). Tags are cheap to recompute —
pctx retagre-runs them over history. - No phone support, by design. This is a desktop capture tool; phones get nothing. The nightly summaries are readable anywhere, but capture stays on your computer.
- Search matches whole tokens. FTS5 ranks by BM25, so a very click-heavy window can bury quieter matches; the MCP tool scans deeper and collapses repeats to compensate.
Layout
schema.sql canonical SQLite schema (also bundled in the package)
privatecontext/
capture.py event-driven capture loop + --simulate
dashboard.py stdlib http.server dashboard (:18790)
db.py sqlite helpers, FTS5 search, usage aggregation
summarize.py nightly Ollama summarizer (condensed digest -> days table)
mcp_server.py first-party MCP server over stdio (search/summary/usage)
cli.py `pctx` entry point (incl. `pctx retag`)
importers.py `pctx import` — browser / ActivityWatch / Screenpipe / CSV / JSON
tagging.py heuristic auto-tags: ticket IDs + project guesses
MIT licensed. Your data stays yours.
Metadata
Release files for privatecontext 0.6.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| privatecontext-0.6.4.tar.gz | 79.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| privatecontext-0.6.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 157.0 kB
Release files / privatecontext-0.6.4.tar.gz
| Download URL | privatecontext-0.6.4.tar.gz |
|---|---|
| Size | 79.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
802fdd8a08b5fe6e4850a39cf14ccdb566ad9b518ed64a1d3879341e4db138a8
|
|
BLAKE2b-256 checksum How to use checksums |
fcdc9df012e9b94601223e65b81c07c268d607f29be017f9feb917908b4c7604
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / privatecontext-0.6.4-py3-none-any.whl
| Download URL | privatecontext-0.6.4-py3-none-any.whl |
|---|---|
| Size | 77.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eb5cc05cc7217dd57066716f36824aa350d86b1b68a4f2a2fc22f2d89e35aed4
|
|
BLAKE2b-256 checksum How to use checksums |
3cfd0552f0f3f0b059003fd042d3f934a84c1dda8cc2efb7ad9dbd90fd53c674
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for privatecontext 0.6.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for privatecontext-0.6.4.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.