privatecontext 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PrivateContext contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,269 @@
1
+ Metadata-Version: 2.4
2
+ Name: privatecontext
3
+ Version: 0.1.0
4
+ Summary: Lightweight, local-first activity tracker
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: pynput
10
+ Requires-Dist: mss
11
+ Requires-Dist: pillow
12
+ Requires-Dist: mcp>=2
13
+ Dynamic: license-file
14
+
15
+ # PrivateContext (v0)
16
+
17
+ A lightweight, local-first activity tracker. It answers questions like
18
+ *"what was I doing when I fixed eduroam last Tuesday?"* — from your own
19
+ machine, with nothing ever leaving it.
20
+
21
+ The idea: an **event log beats pixels**. Instead of recording video of your
22
+ screen 24/7 (heavy, creepy), PrivateContext logs *events* — active window,
23
+ URL, clicks, idle periods — into a local SQLite database. A day of tracking
24
+ is ~1 MB, not gigabytes. Full-text search is built in, a nightly Ollama
25
+ summarizer writes plain-language day recaps, and a first-party MCP server
26
+ lets AI assistants query the diary.
27
+
28
+ ## 10-minute setup
29
+
30
+ ```bash
31
+ cd privatecontext
32
+ python3 -m venv .venv && .venv/bin/pip install -e .
33
+ ```
34
+
35
+ Generate a synthetic day to see the whole pipeline (no OS hooks, works headless):
36
+
37
+ ```bash
38
+ pctx capture --simulate
39
+ ```
40
+
41
+ Start capturing for real (Ctrl-C to stop):
42
+
43
+ ```bash
44
+ pctx capture
45
+ ```
46
+
47
+ Open the dashboard:
48
+
49
+ ```bash
50
+ pctx dashboard
51
+ # -> http://127.0.0.1:18790 (auto-refreshes every 2s)
52
+ ```
53
+
54
+ Export a shareable copy (screenshots excluded):
55
+
56
+ ```bash
57
+ pctx export --scrubbed --out ./my-week.db
58
+ ```
59
+
60
+ Data lives in `~/.local/share/privatecontext/context.db`
61
+ (or `$XDG_DATA_HOME/privatecontext/context.db`).
62
+
63
+ ## Importing your history
64
+
65
+ Day one with a fresh tracker is empty. `pctx import` backfills history
66
+ from your browser and from other recorders — data liberation, not lock-in.
67
+
68
+ ```bash
69
+ pctx import --browser chrome # or edge, firefox
70
+ pctx import --browser firefox --profile ~/path/to/places.sqlite
71
+ pctx import --activitywatch ~/path/to/activitywatch.db
72
+ pctx import --screenpipe ~/path/to/screenpipe.db
73
+ pctx import --csv ~/Downloads/rewind-export.csv # any local CSV export
74
+ pctx import --json ~/Downloads/tool-export.json # any local JSON export
75
+ pctx import --browser chrome --dry-run # preview: counts + samples, no writes
76
+ ```
77
+
78
+ - **Browsers:** reads Chrome/Edge `History` (`urls` table) and Firefox
79
+ `places.sqlite` (`moz_places`). The database is always copied to a temp
80
+ file first — your browser can stay open. Query params are stripped, same
81
+ as live capture. Default profile paths are auto-detected per OS; use
82
+ `--profile` if yours lives elsewhere.
83
+ - **ActivityWatch:** detects the AW schema at runtime (`buckets` + `events`
84
+ tables). Window/app events become `window` events, AFK buckets become
85
+ `afk`/`back`, web-watcher events become `url` events. Anything unmappable
86
+ is skipped and counted.
87
+ - **Screenpipe:** imports OCR text as `ocr` events and audio transcripts as
88
+ `transcript` events. **Video/audio blobs are never imported** — that's the
89
+ heaviness we're escaping.
90
+ - Re-running an import only picks up newer rows (watermarks in the `kv`
91
+ table). Keystroke contents are never imported under any circumstance —
92
+ if a source DB looks like it holds them, the import is refused outright.
93
+ - **Anything else:** exported your history from another tool as CSV or
94
+ JSON? `pctx import --csv/--json` reads it — columns/keys like
95
+ `timestamp`, `type`, `app`, `title`, `url` are recognized, timestamps
96
+ accept unix/ISO/Chrome-epoch formats. Our rule is simple: we never call
97
+ cloud APIs, but a local file you exported yourself is yours — hand it
98
+ over and we'll ingest it. Encrypted exports we can't decrypt stay out.
99
+
100
+ ### What import can't do (honest edition)
101
+
102
+ - **It can't invent data the old tool never captured.** ActivityWatch never
103
+ logged clicks, so an AW import has no clicks. Your history starts where
104
+ their capture did.
105
+ - **Screenshots and video/audio blobs stay behind.** Importing Screenpipe's
106
+ mp4s would just re-create the 2 GB/day problem this project exists to
107
+ escape. Text comes over; pixels don't.
108
+ - **Encrypted or cloud-locked stores can't be read.** Tools that keep your
109
+ data in their cloud (e.g. Rewind) have no local database to import from —
110
+ and PrivateContext will never add cloud APIs to fetch it. That's the point.
111
+ - **Imported history inherits the old tool's flaws.** AW's AFK misfires
112
+ (video watching logged as idle) come along as-is. We don't rewrite history;
113
+ live capture from today onward is clean.
114
+ - **Stop the old tool yourself** (or you'll double-capture the same hours
115
+ into two databases). Import is a migration, not a sync.
116
+ - **Timezone normalization is best-effort** for very old data. Chrome
117
+ timestamps are UTC; ActivityWatch event timestamps carry their offsets;
118
+ anything ambiguous is stored as-is rather than guessed.
119
+
120
+ ## Auto-tagging
121
+
122
+ Every captured or imported event gets heuristic tags, stored in the
123
+ `events.tags` column and shown in the dashboard timeline:
124
+
125
+ - **Ticket IDs** — `INC0012345` (ServiceNow-style, 5+ digits) and
126
+ `PROJ-123` (JIRA-style) picked out of window titles and URLs.
127
+ - **Project guess** — for dev apps (VS Code, terminals, JetBrains IDEs),
128
+ the leading window-title segment is kept as a project tag when it looks
129
+ like a folder/repo name (`privatecontext — capture.py` →
130
+ `privatecontext`). UI-chrome words (`Settings`, `Extensions`, …) are
131
+ on a stoplist.
132
+
133
+ It's a heuristic and it misfires: `UTF-8` matches the JIRA pattern, and
134
+ a folder named `New Folder` won't tag. Tags are lowercase, deduped, and
135
+ cheap to recompute — `pctx retag [--since YYYY-MM-DD]` re-runs tagging
136
+ over untagged history (e.g. data imported before tagging existed).
137
+
138
+ ## Nightly summaries (Ollama)
139
+
140
+ Once a day, a small local model reads a *condensed digest* of the day —
141
+ per-app hours, top windows, away breaks, click counts, never raw event dumps
142
+ (the digest is capped at ~4k tokens so a 1.5B model can handle it) — and
143
+ writes a 3–6 bullet recap plus auto-tags into the database.
144
+
145
+ ```bash
146
+ ollama serve # in one terminal, once
147
+ ollama pull qwen2:1.5b # small default model; any Ollama model works
148
+
149
+ pctx summarize --dry-run # preview the digest without calling a model
150
+ pctx summarize # summarize today
151
+ pctx summarize --date 2026-10-12 --model qwen2:7b
152
+ ```
153
+
154
+ Run it nightly with cron:
155
+
156
+ ```cron
157
+ 30 23 * * * /abs/path/to/privatecontext/.venv/bin/pctx summarize >> ~/.local/share/privatecontext/summarize.log 2>&1
158
+ ```
159
+
160
+ If Ollama isn't reachable, `pctx summarize` prints a plain-English hint
161
+ (`ollama serve` / `ollama pull …`) and exits non-zero — no tracebacks.
162
+
163
+ ## MCP server (for AI assistants)
164
+
165
+ `pctx mcp` runs a first-party MCP server over **stdio** with three tools,
166
+ all returning condensed human-readable text:
167
+
168
+ - `search_timeline(query, date?)` — up to 15 distinct matching moments as
169
+ `HH:MM app — title` lines (repeats collapsed, not 15 copies of one click).
170
+ - `day_summary(date?)` — the stored nightly recap, or a live digest marked
171
+ un-summarized when none exists yet.
172
+ - `app_usage(date?)` — per-app hours for the day as a compact table.
173
+
174
+ Point a client at it (use your real absolute path):
175
+
176
+ **Claude Desktop** — `claude_desktop_config.json`:
177
+ ```json
178
+ {
179
+ "mcpServers": {
180
+ "privatecontext": {
181
+ "command": "/abs/path/to/privatecontext/.venv/bin/pctx",
182
+ "args": ["mcp"]
183
+ }
184
+ }
185
+ }
186
+ ```
187
+
188
+ **Claude Code:**
189
+ ```bash
190
+ claude mcp add --transport stdio privatecontext -- /abs/path/to/privatecontext/.venv/bin/pctx mcp
191
+ ```
192
+
193
+ **Cursor:** Settings → MCP → add a stdio server with the same command.
194
+
195
+ Then ask things like *"what was I doing Tuesday afternoon?"* or
196
+ *"how much of today went to ServiceNow tickets?"*
197
+
198
+ ### Security & privacy
199
+
200
+ - **No network, no listener.** The server speaks stdio only: the client
201
+ spawns `pctx mcp` as a subprocess on the same machine. There is no port,
202
+ no socket, no listener — nothing can reach it from the network, and MCP
203
+ itself sends nothing anywhere.
204
+ - **Condensed text only.** Tools return short summaries, never raw event
205
+ dumps, and screenshots are never exposed through any MCP tool, even when
206
+ screenshot capture is enabled.
207
+ - **Your client choice sets the boundary.** If you connect it to a
208
+ cloud-model client (e.g. Claude Desktop with default settings), the text
209
+ returned for each query is sent to that provider as conversation context —
210
+ exactly as if you'd pasted it into a chat yourself. With a local-model
211
+ client, everything stays on your machine. Pick the setup that matches
212
+ your comfort level.
213
+ - **Treat tool output as data.** Window titles and URLs come from untrusted
214
+ sources (web pages, documents) and are passed through to the model. A
215
+ malicious page title could try to read like an instruction; clients should
216
+ treat MCP tool output as data to summarize, not instructions to follow.
217
+
218
+ ## Privacy design
219
+
220
+ - **Local only.** No telemetry, no accounts, no network calls except to
221
+ `localhost:11434` when you run the Ollama summarizer. The dashboard
222
+ binds to 127.0.0.1 and the MCP server uses stdio (no port at all).
223
+ - **Keystroke contents are never recorded.** The keyboard listener exists only
224
+ to timestamp activity for idle detection.
225
+ - **URLs are stripped.** Query params and fragments (`?utm_source=…`,
226
+ `&sys_id=…`) are removed before anything is stored.
227
+ - **Screenshots are off by default.** Opt in with `pctx capture --screenshots`;
228
+ then only the active monitor is captured, only on app-switch/URL-change,
229
+ downscaled to 640 px wide, stored as JPEG (quality 70).
230
+ - **One-click purge.** The dashboard has a big red button that deletes
231
+ everything. `pctx export --scrubbed` excludes screenshots and shot events.
232
+
233
+ ## v0 limits (honest edition)
234
+
235
+ - **Click coordinates without UI labels are weak.** Knowing you clicked at
236
+ (842, 315) says little; knowing you clicked "Resolve" in ServiceNow says
237
+ everything. Accessibility-tree labels are the v1 answer.
238
+ - **Window titles on Wayland are limited.** The v0 watcher uses `xprop`
239
+ (X11). Wayland sessions get a warning, not data.
240
+ - **Browser URLs are macOS-only in v0** (AppleScript, needs permission).
241
+ Everywhere else the window title is logged and the URL stays empty until
242
+ the browser-extension story lands.
243
+ - **Auto-tagging is heuristic.** It catches ticket IDs and project names
244
+ from window titles, but it will misfire sometimes ("UTF-8" looks like a
245
+ JIRA ticket to a regex). Tags are cheap to recompute — `pctx retag`
246
+ re-runs them over history.
247
+ - **No phone support, by design.** This is a desktop capture tool; phones get
248
+ nothing. The nightly summaries are readable anywhere, but capture stays
249
+ on your computer.
250
+ - **Search matches whole tokens.** FTS5 ranks by BM25, so a very click-heavy
251
+ window can bury quieter matches; the MCP tool scans deeper and collapses
252
+ repeats to compensate.
253
+
254
+ ## Layout
255
+
256
+ ```
257
+ schema.sql canonical SQLite schema (also bundled in the package)
258
+ privatecontext/
259
+ capture.py event-driven capture loop + --simulate
260
+ dashboard.py stdlib http.server dashboard (:18790)
261
+ db.py sqlite helpers, FTS5 search, usage aggregation
262
+ summarize.py nightly Ollama summarizer (condensed digest -> days table)
263
+ mcp_server.py first-party MCP server over stdio (search/summary/usage)
264
+ cli.py `pctx` entry point (incl. `pctx retag`)
265
+ importers.py `pctx import` — browser / ActivityWatch / Screenpipe / CSV / JSON
266
+ tagging.py heuristic auto-tags: ticket IDs + project guesses
267
+ ```
268
+
269
+ MIT licensed. Your data stays yours.
@@ -0,0 +1,255 @@
1
+ # PrivateContext (v0)
2
+
3
+ A lightweight, local-first activity tracker. It answers questions like
4
+ *"what was I doing when I fixed eduroam last Tuesday?"* — from your own
5
+ machine, with nothing ever leaving it.
6
+
7
+ The idea: an **event log beats pixels**. Instead of recording video of your
8
+ screen 24/7 (heavy, creepy), PrivateContext logs *events* — active window,
9
+ URL, clicks, idle periods — into a local SQLite database. A day of tracking
10
+ is ~1 MB, not gigabytes. Full-text search is built in, a nightly Ollama
11
+ summarizer writes plain-language day recaps, and a first-party MCP server
12
+ lets AI assistants query the diary.
13
+
14
+ ## 10-minute setup
15
+
16
+ ```bash
17
+ cd privatecontext
18
+ python3 -m venv .venv && .venv/bin/pip install -e .
19
+ ```
20
+
21
+ Generate a synthetic day to see the whole pipeline (no OS hooks, works headless):
22
+
23
+ ```bash
24
+ pctx capture --simulate
25
+ ```
26
+
27
+ Start capturing for real (Ctrl-C to stop):
28
+
29
+ ```bash
30
+ pctx capture
31
+ ```
32
+
33
+ Open the dashboard:
34
+
35
+ ```bash
36
+ pctx dashboard
37
+ # -> http://127.0.0.1:18790 (auto-refreshes every 2s)
38
+ ```
39
+
40
+ Export a shareable copy (screenshots excluded):
41
+
42
+ ```bash
43
+ pctx export --scrubbed --out ./my-week.db
44
+ ```
45
+
46
+ Data lives in `~/.local/share/privatecontext/context.db`
47
+ (or `$XDG_DATA_HOME/privatecontext/context.db`).
48
+
49
+ ## Importing your history
50
+
51
+ Day one with a fresh tracker is empty. `pctx import` backfills history
52
+ from your browser and from other recorders — data liberation, not lock-in.
53
+
54
+ ```bash
55
+ pctx import --browser chrome # or edge, firefox
56
+ pctx import --browser firefox --profile ~/path/to/places.sqlite
57
+ pctx import --activitywatch ~/path/to/activitywatch.db
58
+ pctx import --screenpipe ~/path/to/screenpipe.db
59
+ pctx import --csv ~/Downloads/rewind-export.csv # any local CSV export
60
+ pctx import --json ~/Downloads/tool-export.json # any local JSON export
61
+ pctx import --browser chrome --dry-run # preview: counts + samples, no writes
62
+ ```
63
+
64
+ - **Browsers:** reads Chrome/Edge `History` (`urls` table) and Firefox
65
+ `places.sqlite` (`moz_places`). The database is always copied to a temp
66
+ file first — your browser can stay open. Query params are stripped, same
67
+ as live capture. Default profile paths are auto-detected per OS; use
68
+ `--profile` if yours lives elsewhere.
69
+ - **ActivityWatch:** detects the AW schema at runtime (`buckets` + `events`
70
+ tables). Window/app events become `window` events, AFK buckets become
71
+ `afk`/`back`, web-watcher events become `url` events. Anything unmappable
72
+ is skipped and counted.
73
+ - **Screenpipe:** imports OCR text as `ocr` events and audio transcripts as
74
+ `transcript` events. **Video/audio blobs are never imported** — that's the
75
+ heaviness we're escaping.
76
+ - Re-running an import only picks up newer rows (watermarks in the `kv`
77
+ table). Keystroke contents are never imported under any circumstance —
78
+ if a source DB looks like it holds them, the import is refused outright.
79
+ - **Anything else:** exported your history from another tool as CSV or
80
+ JSON? `pctx import --csv/--json` reads it — columns/keys like
81
+ `timestamp`, `type`, `app`, `title`, `url` are recognized, timestamps
82
+ accept unix/ISO/Chrome-epoch formats. Our rule is simple: we never call
83
+ cloud APIs, but a local file you exported yourself is yours — hand it
84
+ over and we'll ingest it. Encrypted exports we can't decrypt stay out.
85
+
86
+ ### What import can't do (honest edition)
87
+
88
+ - **It can't invent data the old tool never captured.** ActivityWatch never
89
+ logged clicks, so an AW import has no clicks. Your history starts where
90
+ their capture did.
91
+ - **Screenshots and video/audio blobs stay behind.** Importing Screenpipe's
92
+ mp4s would just re-create the 2 GB/day problem this project exists to
93
+ escape. Text comes over; pixels don't.
94
+ - **Encrypted or cloud-locked stores can't be read.** Tools that keep your
95
+ data in their cloud (e.g. Rewind) have no local database to import from —
96
+ and PrivateContext will never add cloud APIs to fetch it. That's the point.
97
+ - **Imported history inherits the old tool's flaws.** AW's AFK misfires
98
+ (video watching logged as idle) come along as-is. We don't rewrite history;
99
+ live capture from today onward is clean.
100
+ - **Stop the old tool yourself** (or you'll double-capture the same hours
101
+ into two databases). Import is a migration, not a sync.
102
+ - **Timezone normalization is best-effort** for very old data. Chrome
103
+ timestamps are UTC; ActivityWatch event timestamps carry their offsets;
104
+ anything ambiguous is stored as-is rather than guessed.
105
+
106
+ ## Auto-tagging
107
+
108
+ Every captured or imported event gets heuristic tags, stored in the
109
+ `events.tags` column and shown in the dashboard timeline:
110
+
111
+ - **Ticket IDs** — `INC0012345` (ServiceNow-style, 5+ digits) and
112
+ `PROJ-123` (JIRA-style) picked out of window titles and URLs.
113
+ - **Project guess** — for dev apps (VS Code, terminals, JetBrains IDEs),
114
+ the leading window-title segment is kept as a project tag when it looks
115
+ like a folder/repo name (`privatecontext — capture.py` →
116
+ `privatecontext`). UI-chrome words (`Settings`, `Extensions`, …) are
117
+ on a stoplist.
118
+
119
+ It's a heuristic and it misfires: `UTF-8` matches the JIRA pattern, and
120
+ a folder named `New Folder` won't tag. Tags are lowercase, deduped, and
121
+ cheap to recompute — `pctx retag [--since YYYY-MM-DD]` re-runs tagging
122
+ over untagged history (e.g. data imported before tagging existed).
123
+
124
+ ## Nightly summaries (Ollama)
125
+
126
+ Once a day, a small local model reads a *condensed digest* of the day —
127
+ per-app hours, top windows, away breaks, click counts, never raw event dumps
128
+ (the digest is capped at ~4k tokens so a 1.5B model can handle it) — and
129
+ writes a 3–6 bullet recap plus auto-tags into the database.
130
+
131
+ ```bash
132
+ ollama serve # in one terminal, once
133
+ ollama pull qwen2:1.5b # small default model; any Ollama model works
134
+
135
+ pctx summarize --dry-run # preview the digest without calling a model
136
+ pctx summarize # summarize today
137
+ pctx summarize --date 2026-10-12 --model qwen2:7b
138
+ ```
139
+
140
+ Run it nightly with cron:
141
+
142
+ ```cron
143
+ 30 23 * * * /abs/path/to/privatecontext/.venv/bin/pctx summarize >> ~/.local/share/privatecontext/summarize.log 2>&1
144
+ ```
145
+
146
+ If Ollama isn't reachable, `pctx summarize` prints a plain-English hint
147
+ (`ollama serve` / `ollama pull …`) and exits non-zero — no tracebacks.
148
+
149
+ ## MCP server (for AI assistants)
150
+
151
+ `pctx mcp` runs a first-party MCP server over **stdio** with three tools,
152
+ all returning condensed human-readable text:
153
+
154
+ - `search_timeline(query, date?)` — up to 15 distinct matching moments as
155
+ `HH:MM app — title` lines (repeats collapsed, not 15 copies of one click).
156
+ - `day_summary(date?)` — the stored nightly recap, or a live digest marked
157
+ un-summarized when none exists yet.
158
+ - `app_usage(date?)` — per-app hours for the day as a compact table.
159
+
160
+ Point a client at it (use your real absolute path):
161
+
162
+ **Claude Desktop** — `claude_desktop_config.json`:
163
+ ```json
164
+ {
165
+ "mcpServers": {
166
+ "privatecontext": {
167
+ "command": "/abs/path/to/privatecontext/.venv/bin/pctx",
168
+ "args": ["mcp"]
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ **Claude Code:**
175
+ ```bash
176
+ claude mcp add --transport stdio privatecontext -- /abs/path/to/privatecontext/.venv/bin/pctx mcp
177
+ ```
178
+
179
+ **Cursor:** Settings → MCP → add a stdio server with the same command.
180
+
181
+ Then ask things like *"what was I doing Tuesday afternoon?"* or
182
+ *"how much of today went to ServiceNow tickets?"*
183
+
184
+ ### Security & privacy
185
+
186
+ - **No network, no listener.** The server speaks stdio only: the client
187
+ spawns `pctx mcp` as a subprocess on the same machine. There is no port,
188
+ no socket, no listener — nothing can reach it from the network, and MCP
189
+ itself sends nothing anywhere.
190
+ - **Condensed text only.** Tools return short summaries, never raw event
191
+ dumps, and screenshots are never exposed through any MCP tool, even when
192
+ screenshot capture is enabled.
193
+ - **Your client choice sets the boundary.** If you connect it to a
194
+ cloud-model client (e.g. Claude Desktop with default settings), the text
195
+ returned for each query is sent to that provider as conversation context —
196
+ exactly as if you'd pasted it into a chat yourself. With a local-model
197
+ client, everything stays on your machine. Pick the setup that matches
198
+ your comfort level.
199
+ - **Treat tool output as data.** Window titles and URLs come from untrusted
200
+ sources (web pages, documents) and are passed through to the model. A
201
+ malicious page title could try to read like an instruction; clients should
202
+ treat MCP tool output as data to summarize, not instructions to follow.
203
+
204
+ ## Privacy design
205
+
206
+ - **Local only.** No telemetry, no accounts, no network calls except to
207
+ `localhost:11434` when you run the Ollama summarizer. The dashboard
208
+ binds to 127.0.0.1 and the MCP server uses stdio (no port at all).
209
+ - **Keystroke contents are never recorded.** The keyboard listener exists only
210
+ to timestamp activity for idle detection.
211
+ - **URLs are stripped.** Query params and fragments (`?utm_source=…`,
212
+ `&sys_id=…`) are removed before anything is stored.
213
+ - **Screenshots are off by default.** Opt in with `pctx capture --screenshots`;
214
+ then only the active monitor is captured, only on app-switch/URL-change,
215
+ downscaled to 640 px wide, stored as JPEG (quality 70).
216
+ - **One-click purge.** The dashboard has a big red button that deletes
217
+ everything. `pctx export --scrubbed` excludes screenshots and shot events.
218
+
219
+ ## v0 limits (honest edition)
220
+
221
+ - **Click coordinates without UI labels are weak.** Knowing you clicked at
222
+ (842, 315) says little; knowing you clicked "Resolve" in ServiceNow says
223
+ everything. Accessibility-tree labels are the v1 answer.
224
+ - **Window titles on Wayland are limited.** The v0 watcher uses `xprop`
225
+ (X11). Wayland sessions get a warning, not data.
226
+ - **Browser URLs are macOS-only in v0** (AppleScript, needs permission).
227
+ Everywhere else the window title is logged and the URL stays empty until
228
+ the browser-extension story lands.
229
+ - **Auto-tagging is heuristic.** It catches ticket IDs and project names
230
+ from window titles, but it will misfire sometimes ("UTF-8" looks like a
231
+ JIRA ticket to a regex). Tags are cheap to recompute — `pctx retag`
232
+ re-runs them over history.
233
+ - **No phone support, by design.** This is a desktop capture tool; phones get
234
+ nothing. The nightly summaries are readable anywhere, but capture stays
235
+ on your computer.
236
+ - **Search matches whole tokens.** FTS5 ranks by BM25, so a very click-heavy
237
+ window can bury quieter matches; the MCP tool scans deeper and collapses
238
+ repeats to compensate.
239
+
240
+ ## Layout
241
+
242
+ ```
243
+ schema.sql canonical SQLite schema (also bundled in the package)
244
+ privatecontext/
245
+ capture.py event-driven capture loop + --simulate
246
+ dashboard.py stdlib http.server dashboard (:18790)
247
+ db.py sqlite helpers, FTS5 search, usage aggregation
248
+ summarize.py nightly Ollama summarizer (condensed digest -> days table)
249
+ mcp_server.py first-party MCP server over stdio (search/summary/usage)
250
+ cli.py `pctx` entry point (incl. `pctx retag`)
251
+ importers.py `pctx import` — browser / ActivityWatch / Screenpipe / CSV / JSON
252
+ tagging.py heuristic auto-tags: ticket IDs + project guesses
253
+ ```
254
+
255
+ MIT licensed. Your data stays yours.
@@ -0,0 +1,3 @@
1
+ """PrivateContext — lightweight, local-first activity tracker (v0)."""
2
+
3
+ __version__ = "0.1.0"