Skip to main content
Glama

Argus's Hoard

A private, self-hosted screen memory for your own Windows PC: every few seconds Argus captures the screen, notes the active window, skips frames that have not changed, runs OCR on the new ones and gives you a timeline, full-text search and time-by-app — all stored locally and exposed to an assistant ("Faustus") through MCP. Think of it as a transparent, user-owned take on the "recall" idea: you can see exactly what is stored, pause it, exclude apps, delete ranges and cap its disk usage.

Nothing leaves the machine. There is no cloud, no telemetry and no network call besides 127.0.0.1.

What is captured (be honest with yourself)

While the state is watching, every interval_s seconds (default 5):

  1. The active window is read (title, process name, monitor).

  2. If an exclusion rule matches (process name or window-title regex), nothing is captured or read; only a "hidden" tick is counted for that day.

  3. Otherwise the screen is captured: with capture_scope: active (default) only the monitor that contains the active window (primary monitor when the window position is unknown); with all, every monitor. A frame that looks like the previous one for that monitor (same window, fewer than dedupe_threshold cells of a 384×216 grey grid changed) is not stored: the previous frame's until time is extended instead. That is how durations are computed.

  4. New frames are saved as WebP (max width 1280) plus a 320 px thumbnail under data/frames/YYYY/MM/DD/<id>.webp and queued for OCR. OCR never blocks capture; the queue depth is visible in the status.

  5. OCR text and layout blocks (bounding boxes, confidence) go into SQLite with an FTS5 index (BM25 over text, window title and app).

Not captured: anything while paused, in private mode or with the capture disabled, and anything matching an exclusion. Keystrokes, audio, clipboard and network traffic are never touched.

Related MCP server: cli-history-hub

Privacy controls

  • Pause / resume from the banner, the API or the screen_pause tool.

  • Private mode: a big switch in Ajustes (no hotkey). Nothing is captured until you switch it off; it survives restarts.

  • Exclusions: by process name (keepass*, banco-app) or window-title regex (incógnito|private browsing). Test any app/title in the settings page ("¿esta ventana se excluiría?").

  • Retention: frames older than retention_days (default 30) are deleted — images and text — by a janitor that runs at start and every 10 minutes.

  • Storage cap: when the frames folder exceeds storage_cap_mb the oldest frames are deleted first.

  • Delete range: DELETE /api/frames?from&to, "Borrar este día" in the timeline, or the screen_delete_range tool. Permanent.

Requirements

  • Windows 10/11 (capture + active window). The whole pipeline after capture also runs on Linux/macOS with the fake backend for tests and demos.

  • Python 3.11+ (3.13 works). Node 22 only to build the client.

  • CPU is enough. With ocr_backend: auto the engine is winocr (Windows.Media.Ocr, about 150 ms per 1080p frame, needs requirements-windows.txt) when it is available and rapidocr (ONNX on CPU, cross-platform) otherwise; tesseract is used if pytesseract and the binary are installed. Any of the three can be forced in Ajustes.

Install and run (Windows)

python -m venv venv
venv\Scripts\pip install -r requirements.txt
venv\Scripts\pip install -r requirements-windows.txt   :: optional: winocr, pywin32
npm install
npm run build                                         :: builds the UI into argus\static
venv\Scripts\python scripts\selftest.py               :: one real capture + OCR, prints text + timings
venv\Scripts\python scripts\launch.py                 :: starts the app and opens the browser

python -m argus starts the server on http://127.0.0.1:5183 (env ARGUS_PORT/PORT; PORT_STRICT=1 pins it, otherwise the next free port is used). Data lives in ARGUS_DATA_DIR or <repo>/data (gitignored).

Other env vars: ARGUS_CAPTURE=auto|mss|fake|none, ARGUS_WINDOW=auto|windows|fake|none, ARGUS_AUTOSTART=0 (do not start the recorder thread).

Access from your phone (behind a tunnel)

The server binds 127.0.0.1 and only answers requests whose Host is localhost, 127.0.0.1 or [::1]. To reach it from your phone through a tunnel that fronts the app (a private mesh network, a reverse proxy), list the extra host names in ARGUS_ALLOWED_HOSTS, comma-separated, exact names or *.suffix: ARGUS_ALLOWED_HOSTS=my-pc.example,*.ts.net. Port and letter case are ignored, and the Origin of API calls must resolve to one of those hosts too (any scheme or port). Cross-site fetches are still refused; opening the app from another page (a link, a bookmarklet, the share sheet) is a normal navigation and works.

Development: python scripts/dev.py runs uvicorn --reload plus the Vite dev server (proxying /api).

The UI (Spanish)

  • Línea de tiempo: day picker, the day grouped into sessions (one app + window seen continuously: time range, duration, count, preview thumbnails); a session expands into its frames with a scrubber; a frame opens as a big image with the OCR blocks overlaid as selectable text, prev/next (arrow keys).

  • Buscar: query + date range + app; one row per moment (repeated frames of a window collapsed, with a thumbnail strip), times as "hoy 17:32–17:41" / "ayer 09:10".

  • Actividad: time by app (bars), top window titles, per-day table.

  • Ajustes: private mode, enabled, interval, change sensitivity, monitors, OCR engine (with availability of each), image width, retention, storage cap, exclusions (add/remove/toggle + test box) and a status card (queue, disk, last capture, backends, errors).

A banner on every page says Argus está mirando / en pausa / modo privado / captura desactivada.

API

All routes are bound to 127.0.0.1 and refuse other hosts/origins.

Route

Purpose

GET /api/health

{service, version, dataDirConfigured} (no auth)

GET /api/status

state, queue depth, last capture, disk usage, retention, backends, capture_scope, monitors, idle_since/idle_s (active screen unchanged since then)

GET/PUT /api/settings

settings (PUT accepts a partial object)

POST /api/pause, POST /api/resume, POST /api/private

recording controls (private toggles; body {private: bool} sets)

GET/POST /api/exclusions, PATCH/DELETE /api/exclusions/{id}, POST /api/exclusions/test

exclusion rules

GET /api/timeline?from&to&app&q&title&limit&cursor&order

frames with duration and excerpt, cursor pagination

GET /api/sessions?day (or from&to) &app

consecutive frames of one app + window grouped into sessions (start, end, duration, count, preview thumbs)

GET /api/frames/{id} · /image · /thumb

full text + blocks + prev/next; WebP files

GET /api/search?q&from&to&app&limit

FTS5 candidates re-ranked with BM25 (title 3 / app 2 / text 1, smoothed IDF so rank is always a real negative), snippet() with [ ] markers; consecutive near-identical hits of one window within 10 min collapse into one moment (first_at, last_at, count, frame_ids best first); limit counts moments

GET /api/apps?from&to

time by app + top window titles

GET /api/days

days with data (frames, hidden ticks, seconds)

DELETE /api/frames?from&to

delete a range (permanent)

GET /api/agent/tools, POST /api/agent/call

tool catalog and the MCP bridge (Bearer token)

from/to accept ISO datetimes, YYYY-MM-DD (whole day) and phrases: hoy, ayer, anteayer, esta mañana, esta tarde, anoche, hace 2 horas, hace 15 minutos, últimos 30 minutos, esta semana, la semana pasada, ayer a las 9, and the English equivalents (today, yesterday, 2 hours ago, this morning, last week, today at 3pm).

MCP tools

The bridge (mcp_server.py) fetches the tool list from the running app and proxies every call to POST /api/agent/call with the token from data/mcp-token; it never opens the database.

Tool

What it does

screen_status

recording state, queue, last capture, disk, retention

screen_search

full-text search with snippets; each hit is a moment (first_at, last_at, count, frame_ids)

screen_timeline

what was on screen when, with durations

screen_frame_text

full OCR text of a frame (optionally blocks)

screen_recent

the last N minutes' text, deduplicated, most recent first

screen_activity

time by app + top windows + longest sessions + one-line summary

screen_days

days that have data

screen_pause / screen_resume

only when the user asks

screen_delete_range

permanent deletion of a range (destructive)

Connect it from Faustus with faustus-plugin.json, or by hand: {PYTHON} mcp_server.py with env ARGUS_URL=http://127.0.0.1:5183 and ARGUS_TOKEN_FILE=<repo>/data/mcp-token.

Tests

venv\Scripts\python -m pytest -q

Covers change detection, exclusions, the time-phrase resolver, real OCR on rendered fixtures, storage/FTS/snippets, timeline durations, retention and storage cap, the API through TestClient with the fake backend, agent auth, and an end-to-end test that boots python -m argus in a subprocess and lists the tools through the MCP stdio bridge.

Verifying on the real machine

scripts/selftest.py takes one real capture, reads the active window, runs the OCR engine and prints the text and timings, without starting the app. Pass an engine name (rapidocr, winocr, tesseract) to force one, --fake to use fixtures. This is the first thing to run on Windows: the Windows-only backends (mss capture, ctypes active window, winocr) cannot be exercised on a headless Linux build box.

License

MIT — see LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    Read-only MCP server for searching your local Retrace screen-history database, with tools for full-text search, segment listing, frame details, app usage, and tags.
    6
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Local-first memory MCP server that captures macOS screen and app context into persistent Markdown, enabling any tool-capable LLM agent to query its working history.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables MCP clients to start and stop local read-only Windows 11 screen observation sessions, then inspect redacted screen state, UI trees, frames, and wait for changes, title matches, or idle periods through stdio tools.
    Apache 2.0