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.
- privatecontext-0.1.0/LICENSE +21 -0
- privatecontext-0.1.0/PKG-INFO +269 -0
- privatecontext-0.1.0/README.md +255 -0
- privatecontext-0.1.0/privatecontext/__init__.py +3 -0
- privatecontext-0.1.0/privatecontext/capture.py +350 -0
- privatecontext-0.1.0/privatecontext/cli.py +228 -0
- privatecontext-0.1.0/privatecontext/dashboard.py +168 -0
- privatecontext-0.1.0/privatecontext/db.py +140 -0
- privatecontext-0.1.0/privatecontext/importers.py +572 -0
- privatecontext-0.1.0/privatecontext/mcp_server.py +123 -0
- privatecontext-0.1.0/privatecontext/schema.sql +53 -0
- privatecontext-0.1.0/privatecontext/summarize.py +260 -0
- privatecontext-0.1.0/privatecontext/tagging.py +83 -0
- privatecontext-0.1.0/privatecontext.egg-info/PKG-INFO +269 -0
- privatecontext-0.1.0/privatecontext.egg-info/SOURCES.txt +19 -0
- privatecontext-0.1.0/privatecontext.egg-info/dependency_links.txt +1 -0
- privatecontext-0.1.0/privatecontext.egg-info/entry_points.txt +2 -0
- privatecontext-0.1.0/privatecontext.egg-info/requires.txt +4 -0
- privatecontext-0.1.0/privatecontext.egg-info/top_level.txt +1 -0
- privatecontext-0.1.0/pyproject.toml +27 -0
- privatecontext-0.1.0/setup.cfg +4 -0
|
@@ -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.
|