Skip to content

Releases: tabcomputing/camelot

camelot 0.4.0

Choose a tag to compare

@trans trans released this 23 Sep 01:32

Camelot can see now, as well as read.

camelot shot — one frame through the XDG desktop portal. No PipeWire negotiation, permission remembered after the first grant, and the desktop announces every capture its own way (on GNOME, the shutter flash and sound — a consent signal worth keeping). A 2560x1440 screen arrives as a ~145 kB JPEG scaled to fit --max-edge (1568 by default, as much as a vision model uses), in about 0.65s. --pick hands the choice of window or region to the desktop's own picker.

Capture is a pull, never a push. A frame is taken when something asks, handed over and dropped: nothing image-shaped enters the event stream or the daemon's history, where frames would cost more memory in a minute than the whole event log does in a day. screenshots: false switches it off entirely.

MCP — shot is a tool, answering with the image itself. Verified end to end with a real agent session: given one frame at 1000px, it read window titles, tab names and the text of a conversation. Tool calls now return MCP content blocks, so text and image tools share one path. What an agent still is not given: the daemon, the raw event stream, and your controls over being recorded.

Fixes

  • The daemon crashed, reproducibly, whenever a context call followed a screenshot: the shell takes focus, and gnome-shell answers the AT-SPI Collection interface with a GArray whose length is not a length. The length is clamped and the caller falls back to walking the tree. A client fiber now also rescues anything that escapes — an exception leaving a fiber ends the process, and one malformed desktop should not take the service down.
  • docs/ was gitignored by crystal init, so the design note the README links to had never been in a clone.

Upgrade if you run 0.3.1 as a service; the crash fix matters.

Known limitation, and the plan. The screenshot portal flashes per frame, which is obstructive for an agent taking many — and there is no setting for it. The answer is org.freedesktop.portal.ScreenCast: approved once, a persistent "screen is being shared" indicator, frames pulled quietly from PipeWire. That makes capture a three-way choice — off, on demand, session — and it is written up in docs/daemon.md.

camelot 0.3.1

Choose a tag to compare

@trans trans released this 22 Sep 13:56

A bug-fix release, and an important one if you run the daemon.

The daemon could spin a core. Left running (paused, overnight), it reached 100% CPU and stopped answering commands. Its GLib pump had paths that returned to the top of the loop with no yield point, starving every other fiber — including the one that answers the socket.

  • Dispatch is now bounded: "iterate until nothing is ready" is not finite in general, because libatspi arms an idle source while draining its own queue.
  • Watchers are retired when GLib stops polling an fd. A dead application's connection stays readable at EOF forever and would wake the pump for eternity — the likely trigger for an overnight run.
  • Every path ends in a wait or a real 1 ms sleep, never Fiber.yield: while the pump is runnable Crystal never runs its own event loop, so timer-based fibers would never wake.
  • Pausing now gates event resolution, so a paused daemon really does no work.

Measured over a minute: 0.67% CPU recording, 0.28% paused, responsive in both. A regression spec covers it: an always-ready GLib source must not stop a plain Crystal fiber from ticking.

Upgrade if you are running 0.3.0 as a service.

camelot 0.3.0

Choose a tag to compare

@trans trans released this 22 Sep 03:07

The control panel.

camelot-gtk (new package) — a GTK4/libadwaita switchboard over the daemon: what is being recorded, one switch per capability.

  • Background service: start/stop the daemon
  • Recording: activity on/off, typed text, how long to keep history, durable log
  • Access: browser accessibility, ignored applications (with a running-app picker)
  • Activity log: the recent digest as a live list, on its own page — there when you want it

Every change writes the config file and the daemon reloads in place. Installs a desktop entry; single-instance.

Daemon

  • subscribe: a push stream of recorded events and state changes (backfill first). The panel is built on it and never polls.
  • recent folds edits per widget across interleaving activity, caps snippets to one line, and reports terminal repaints as "output".
  • Events are resolved off libatspi's dispatch path via a bounded queue, with per-widget location caching: ~0% CPU under a terminal flood (was ~5%).
  • Hardening found by wedging the daemon from the panel: the event callback is fenced against exceptions, and child/parent/application lookups are null-safe.

Packages: camelot (CLI, daemon, unit, completions) and camelot-gtk (panel, desktop entry) for Arch, Debian/Ubuntu and Fedora.

camelot 0.2.0

Choose a tag to compare

@trans trans released this 22 Sep 00:29

The daemon: camelot now remembers what you've been doing, not just what's on screen.

New commands

  • daemon — background service (systemd user unit included: systemctl --user enable --now camelot). Keeps the bus connection warm, records window switches, focus changes and edits into an in-memory history, and answers every command over a private socket. When it's running, the CLI and the MCP tools go through it; when it isn't, everything works as in 0.1.
  • recent — a digest of the last minute (or -s N): windows, focus, edits folded per widget with count, span and the last text typed. Also an MCP tool, so an agent can ask "what was I just doing?"
  • status — daemon state: uptime, history fill, recording on/paused, log location, ignore list.
  • pause / resume — stop and restart recording without stopping the daemon. reload — re-read the config in place. These are user controls and deliberately not MCP tools.

Privacy

  • ~/.config/camelot/config.yaml: ignore (app-name globs: redacted everywhere, never recorded), retention (default 30m — history is time-bounded as well as count-bounded, and in memory only), text: false (record that you typed, not what), and log (opt-in durable JSONL record, off by default, 0600).
  • Password fields stay redacted in snapshots and in the event stream.

Browsers work out of the box when the daemon runs: it turns on toolkit-accessibility at startup if needed (logged; accessibility: false opts out; never turned off).

Under the hood

  • One command runner behind the CLI, MCP and the daemon socket, so they cannot drift.
  • The event pump waits on GLib's own fds inside Crystal's event loop — no polling, no second thread, ~0.2% CPU idle.
  • Builds against both current (2.60) and LTS (2.52) libatspi.

camelot 0.1.0

Choose a tag to compare

@trans trans released this 21 Sep 21:05

First release: the semantic layer of a desktop-to-AI context bridge.

Commands — apps, windows, tree, focus, at, context read the AT-SPI2 accessibility tree and report as text, JSON or YAML; watch streams window/focus/text events; mcp serves the commands as Model Context Protocol tools over stdio (register with claude mcp add --scope user camelot -- camelot mcp).

Highlights

  • Trees are pruned of anonymous layout containers by default; hidden widgets are skipped; every node keeps its real index path so pid + path addresses the widget again
  • Password fields are always redacted, in snapshots and in the event stream
  • Browsers (Firefox, Chromium) expose page content once toolkit-accessibility is on — see the README
  • Wayland-aware: window-relative coordinates, since toolkits report no screen position there
  • The event stream drives GLib's main context from inside Crystal's event loop — no polling, no second thread

Packages for Arch, Debian/Ubuntu and Fedora are attached (built by the Package workflow). Runtime dependency is just at-spi2-core.

Built with Jargon (JSON Schema CLI) and gi-crystal.