Skip to content

terminalbytes/screen-watcher

Repository files navigation

screen-watcher

macOS launchd agent that wires the Mac's screen-lock state and Claude Code's token usage to two physical outputs:

  1. A TP-Link Kasa smart plug / power strip via the python-kasa CLI. Named outlets switch OFF on screen lock and ON on screen unlock. Typical setup: one outlet for a desk lamp, one for a 9V supply feeding an Arduino motor rig (the outlet doubles as a hardware kill switch for the motor).

  2. An Arduino + DC motor speaking a small serial protocol at 115200 baud. Claude Code token activity drives motor RPM via an exponentially-decaying activity level. Bursts spin the motor up, idle periods coast it down.

The Arduino sketch lives in a companion repo: terminalbytes/arduino-motor-control-pwm.

Full build writeup, photos, and the demo video: A motor that runs at the speed of my Claude Code usage on terminalbytes.com.

What it does

  • Mac unlocked, Claude idle → motor stopped, lamp on.
  • Mac unlocked, agent burning tokens → motor spins, faster with sustained activity.
  • Activity tails off → motor decelerates with active reverse braking on the flywheel.
  • Screen locked → lamp off, motor off, Kasa outlets cut so the motor is electrically dead.

Install

Requires macOS (uses Foundation.NSDistributedNotificationCenter for screen-lock events).

git clone https://github.com/terminalbytes/screen-watcher.git
cd screen-watcher
python3 -m venv venv
venv/bin/pip install -r requirements.txt
cp .env.example .env
# edit .env: set KASA_HOST, KASA_OUTLET_NAMES, MOTOR_PORT

Find your Kasa device's IP from your router's DHCP table or:

venv/bin/kasa discover

Find your Arduino's serial port after plugging it in:

ls /dev/cu.usbmodem*

Run manually

set -a && source .env && set +a
venv/bin/python screen_watcher.py

Lock and unlock the Mac to see events; if a Kasa device is configured, the named outlets should toggle. If an Arduino is on MOTOR_PORT and running the motor-control-pwm sketch, it should respond to SPEED= writes.

Run as a launchd agent

com.terminalbytes.screen-watcher.plist.sample is a working template. Copy it to ~/Library/LaunchAgents/, replace <USERNAME> and <REPO_PATH> with real values, then:

launchctl load -w ~/Library/LaunchAgents/com.terminalbytes.screen-watcher.plist

Logs go to screen_watcher.log and screen_watcher.err.log in the repo dir.

Configuration

Env var Default What
KASA_HOST 192.168.1.100 Static LAN IP of the Kasa device
KASA_OUTLET_NAMES Standing Lamp,Fidget Comma-separated outlet names on the device
KASA_CLI ./venv/bin/kasa Path to the python-kasa CLI
MOTOR_PORT /dev/cu.usbmodem8401 Arduino serial device
MOTOR_BAUD 115200 Serial baud rate (match the sketch)
POLL_SECONDS 1.0 Seconds between activity polls
ACTIVITY_HALF_LIFE_SECONDS 30.0 Half-life of the activity-level decay
ACTIVITY_FLOOR 5000 Below this activity level, motor is off
CLAUDE_PROJECTS_DIR ~/.claude/projects Where Claude Code writes JSONL transcripts

Skip the motor (lamp only)

Set MOTOR_PORT to an invalid value (e.g. MOTOR_PORT=disabled) and the motor serial open silently fails; the Kasa side keeps working.

Skip the lamp (motor only)

Set KASA_OUTLET_NAMES= (empty) and run_kasa no-ops.

Activity model

Each poll, the agent:

  1. Decays the running activity level L by 0.5 ** (dt / half_life).
  2. Reads any newly-appended bytes from every JSONL under CLAUDE_PROJECTS_DIR, sums the usage.{input,output,cache_*}_tokens fields, adds to L.
  3. Looks up L in a piecewise threshold table to derive a motor SPEED 0..10.

The thresholds in screen_watcher.py are tuned for the author's workflow. Expect to retune them: heavy users want larger thresholds, lighter users smaller. Run the daemon with tail -f screen_watcher.log for a few hours, watch the L= values that correspond to your normal usage, and adjust.

License

MIT. See LICENSE.

About

macOS launchd agent: Kasa smart plug + Arduino motor controlled by screen lock and Claude Code token activity

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors