DEV Community

abdullah85
abdullah85

Posted on Fully Autonomous

Le Laissez-Log: Record what you learn everyday, Lazily in the Background

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

What I Built

Le Laissez-Log is a small terminal-session watcher for ActivityWatch, the open-source, local-first time tracker.

ActivityWatch already knows when you are at the keyboard (aw-watcher-afk) and which window is active (aw-watcher-window). What it doesn't know is what you actually did in the terminal, which is where a lot of daily learning happens: the commands you tried, the errors you hit, the fix that finally worked.

My watcher wraps your shell in a PTY and writes one ActivityWatch event per command: the command, how long it ran, and a short, redacted snippet of its output. You keep working as usual. The log builds itself in the background, so at the end of the day there is a searchable record of what you learned, with no note-taking.

The lazy part: no habit to maintain and nothing to write down. You work and it records.

Demo

Start a throwaway ActivityWatch server, run the wrapper, type a few commands, then exit:

# terminal 1
aw-server --testing

# terminal 2
python aw_watcher_terminal.py --testing
# ...type a few commands, then exit...

# terminal 3: read the events back
curl -s "localhost:5666/api/0/buckets/aw-watcher-terminal_$(hostname)/events" | jq -c '.[] | {timestamp, duration, data}'
Enter fullscreen mode Exit fullscreen mode

--testing must match how the server was started: aw-server --testing listens on port 5666 with a separate database, while a plain aw-server uses port 5600 and your real data. If they don't match, the watcher tells you which port it couldn't reach.

A real event from my machine, for the command ls:

{
  "timestamp": "2026-10-26T06:53:00.822000+00:00",
  "duration": 0.004932,
  "data": {
    "cmd": "ls",
    "approx": false,
    "output_bytes": 149,
    "output_head": "aw_watcher_terminal.py  LICENSE    requirements.txt\nlaissez-log-post.md     README.md  typescript\n"
  }
}
Enter fullscreen mode Exit fullscreen mode

Code

The whole watcher is one file, aw_watcher_terminal.py, built on aw-client. To run it:

pip install aw-client aw-server
aw-server --testing &
python aw_watcher_terminal.py --testing
Enter fullscreen mode Exit fullscreen mode

Options: --max-output N (bytes of output kept per command, 0 to disable), --settle SECONDS, --shell PATH, and --debug FILE to log terminal state without logging keystrokes.

How I Built It

Learning the system first

Before writing anything, I ran aw-server, aw-watcher-afk and aw-watcher-window as separate processes instead of using the bundled tray launcher, and poked at the REST API with curl. Three things became clear:

  • Everything is events inside buckets. A bucket is a named stream from one source on one host, and an event is {timestamp, duration, data} with arbitrary JSON data. The server doesn't know what the data means; meaning is applied at query time.
  • Watchers send heartbeats, not one event per poll. If a heartbeat has the same data and arrives within pulsetime of the last event's end, the server extends that event. Otherwise it starts a new one. My AFK bucket showed this directly: a single not-afk event whose duration grew in steps of about 5 seconds (the watcher's poll interval) while its timestamp never changed.
  • I confirmed the rule by hand. From IPython, I created my own bucket and sent heartbeats with a 10-second pulsetime. Two identical heartbeats a few milliseconds apart produced one event with a duration of 0.029s. Identical heartbeats more than 10 seconds apart produced separate events, and different data always produced a new event.

The watcher

There is no official watcher for terminal content. The closest one, aw-watcher-input, counts keypresses only and deliberately doesn't record which keys. So I built one:

  • It runs your $SHELL inside a PTY, relays bytes both ways, and rebuilds each command from the keystrokes you type.
  • It writes events with client.insert_event. A command is a discrete event rather than a heartbeat stream, so it's written once the shell prompt has returned and output has gone quiet.
  • The window watcher keeps recording which window was active, so the two buckets can be joined by timestamp at query time.

Privacy decisions that shaped the design

A terminal recorder is sensitive by nature, so I made these choices on purpose:

  • One session, not a global keylogger. A system-wide key hook would see every app. A PTY wrapper sees only the session it wraps.
  • Record only when the shell owns the terminal. Keystrokes count only while the shell is the foreground process of the PTY, which means password prompts (sudo, ssh), editors, pagers and REPLs are never stored:
  fg = os.tcgetpgrp(master) == pid            # is the shell itself in the foreground?
  recordable = fg and (echo_on or not canonical_mode)
Enter fullscreen mode Exit fullscreen mode
  • Redaction and truncation. password=, token=, api_key= patterns are redacted in both the command and its output, and output is cut to 500 bytes by default.
  • Everything stays local. The data goes into ActivityWatch's own SQLite database on your machine, where you can inspect it, change retention, or delete it.

Bugs I hit along the way

Each of these came out of reading real events and noticing something was off:

  1. My first password-prompt check looked at terminal echo flags and discarded every command, because readline turns echo off at a normal prompt too. The fix was to distinguish readline (echo off, canonical mode off) from password prompts (echo off, canonical mode on).
  2. Events were only written when the next command was entered or the shell exited. I switched to writing once the shell regains the foreground and the output settles. A subtle race here: the shell takes back the terminal slightly before it prints its prompt, so checking too early lost the output and recorded a 2-second sleep as 0 seconds.
  3. Pasting several lines leaked bracketed-paste codes ([200~) into commands and split the paste into separate commands. I now parse escape sequences and treat a pasted newline as text.
  4. Output carried the trailing prompt and the next command's echo. These are now trimmed.

AI use

The watcher has no model at runtime. I used an AI coding assistant (Claude) as a pair programmer while building and debugging it.

Limitations

  • Commands are rebuilt from keystrokes, so history recall and tab completion can't be reconstructed. Those events get approx: true.
  • Full-screen programs (like info) paint with cursor movements rather than lines, so their output_head is empty even though output_bytes is not.
  • output_head records what programs print, which can include text you typed (a program that echoes its input will echo it into the log). Treat the database as sensitive.
  • Only the wrapped terminal is recorded, and only the first part of each command's output.
  • I tested with scripted PTY sessions (echo, a silent sleep, a multi-line paste, a password prompt and cat) and by hand on my own machine. Bash only; zsh and fish are untested.

Why Does Open Innovation Matter?

Because the thing I wanted is something a closed service wouldn't let me build.

  • I could read how it works. Understanding heartbeat merging came from the open API and from experimenting against my own server, not from a vendor's description of a black box.
  • I could extend it. ActivityWatch exposes an open REST API and a client library, so a watcher for a data source nobody had covered was an afternoon of work, not a feature request.
  • The data stays mine. Command history and output are about as personal as data gets. With an open, local-first tool I can inspect the database, set my own retention and delete it. With a closed cloud tracker, I'd be trusting a company with my terminal.
  • It leaves room for local AI. Because the log lives in an open local store, a locally run model could later summarize "what I learned today" without any of it leaving my machine. That isn't built yet, but it's the direction this opens.

The Lazy Payoff

I ran it on my own machine while building this post, and it recorded real sessions: ls, man pages, an info browser session and exit. The payoff I care about isn't a number of minutes saved. It's that I no longer have to remember to write down what I tried. The terminal history I'd normally lose is now an event stream I can query by day.

Top comments (0)