Repository navigation
Releases: eliBenven/freemicro
Release list
FreeMicro 0.5.0
[0.5.0] - 2026-07-27
Added
freemicro updateupgrades to the latest release (detects pipx vs pip),
and--checkreports whether one exists without installing. Offline-safe.
Changed
- The Codex coexistence split is now conditional. With
agent_keys.keysset
to a subset, FreeMicro honors it only while the ChatGPT app is running; when
Codex is not running there is no one to share with, so FreeMicro owns all six
keys. It expands and contracts within a few seconds as Codex quits or launches,
with lighting and presses always agreeing on which keys are FreeMicro's.
FreeMicro 0.4.1
[0.4.1] - 2026-07-27
Fixed
- Coexistence split now covers key presses, not just lighting. With
agent_keys.keysset to a subset, pressing a key FreeMicro did not own (a
Codex key) still ran its binding and could open a new terminal on top of
Codex. Un-owned Agent Keys are now inert on FreeMicro's side entirely, so the
three-and-three split with Codex behaves on press too. (Note: the pad's key
presses are read by both apps at once, so this stops FreeMicro acting on
Codex's keys; it cannot stop Codex reacting to FreeMicro's keys - that needs a
setting on the Codex side.)
FreeMicro 0.4.0
[0.4.0] - 2026-07-27
Added
- Coexist with Codex (
agent_keys.keys). FreeMicro can own a subset of the
six Agent Keys and leave the rest for the ChatGPT app, so Codex and Claude
Code can share the pad - three keys each, for example. FreeMicro sends a
partialthstatusfor only its keys (leaving the others as Codex set them)
and auto-reasserts them so the split holds. Default is all six, unchanged.
"agent_keys": {"keys": [3, 4, 5]}.
FreeMicro 0.3.0
[0.3.0] - 2026-07-26
Configurability: the pad adapts to context, and every key can carry more.
Added
- Per-app profiles (
profiles): one key does different things depending on
which app is frontmost. Overrides only the keys it names; matched by app name
(exact, then longest substring). The frontmost app is read off a 300ms cache,
so a keypress is a dict lookup and no profiles means no OS lookup at all. A
holdpressed in one app still releases cleanly if you switch mid-hold. - Layers (
layeraction +layers): hold a key for a second binding set,
Fn-style. Precedence is layer over profile over base. This is what gives the
pointer its click back and puts/efforton a held layer without either
stealing the other. A stuck layer cannot latch on (reuses the hold safety). - Tap-to-click on the joystick: a quick stick tap is a left click, making
the pointer a full trackpad. A tap must return to centre within 0.2s and move
the cursor under 30px, so a fast move never clicks. On by default in pointer
mode. - New terminal on an empty Agent Key: pressing a dark key (no project) opens
a new terminal window instead of doing nothing. On by default; disable with
terminal_app: false.
FreeMicro 0.2.0
[0.2.0] - 2026-07-26
A batch of lighting and awareness features, plus a joystick fix.
Added
- Software
blinkeffect. The firmware has no hard on/off flash (only the
smoothbreath), so FreeMicro blinks in software from the render tick. One
Agent Key can blinkerrorwhile the others stay solid. It is also the
accessibility answer: a distinct effect per state is a redundant channel that
does not depend on colour vision.freemicro lights --effect blinkworks too. - Attention flash on entry (
lighting.flash_on, default waiting/error): one
brief pulse in the state's own colour when an element enters that state. - Named themes (
lighting.theme):factory,nord,solarized, and a
high-contrast colourblind-safe palette that leans on distinct effects.
Explicit per-state colours still win. Picker in the web UI. - Battery cue (off by default): a configurable low-battery look composited
over a zone. Reads the cached status file, never the vendor channel. - Alerts (off by default): a system sound and a Notification Center banner
on agent state transitions, so you are not blind when looking away.
freemicro alerts --test.
Fixed
- The joystick no longer stops mid-hold. The pad falls silent while a
direction is held steady (a 2.1s gap was measured with the stick deflected),
and the 250ms stale-stop read that as a disconnect and froze the cursor. The
window is now 4s, clear of the real gap; release is still instant.
FreeMicro 0.1.2
[0.1.2] - 2026-07-26
Added
- Launch on connect (
freemicro daemon install --on-connect). A LaunchAgent
whoseLaunchEventsIOKit matcher starts FreeMicro the moment the Codex Micro
appears (matched by VID/PID over both BLE and USB), so pressing a button wakes
the connection and the pad comes alive with nothing running while it is away.
Mutually exclusive with the login daemon, swept by uninstall, and it refuses
from a~/Desktopcheckout (a LaunchAgent cannot read that folder). The
install prints the exactlaunchctlsteps to confirm it fires, since IOKit
matching on BLE HID is less documented than on USB. - HID report map in
docs/PROTOCOL.md. The full 216-byte report descriptor
read from a real unit over BLE, decoded: boot keyboard on Report ID 1,
consumer on 2, mouse on 3, and the vendor0xFF00channel on Report ID 6 with
63-byte In/Out/Feature reports. It confirms, from the silicon, the channel
this project was built on. Captured for a firmware author building a
ZMK-compatible board (issue #1).
Also documentation from real use and an interop request.
Changed
- Effort dial documented correctly. Claude Code's
/effortslider is driven
by LEFT/RIGHT arrows, not up/down, and/effortalone opens it on submit. The
shipped default keeps the dial as a pointer companion; the exact rebind for an
effort dial (turn sends left/right, press opens/effort) is now in the
default's notes. Corrects an earlier wrong assumption.
FreeMicro 0.1.1
[0.1.1] - 2026-07-24
Fixes from the first day of real use.
Fixed
- Stuck modifier after push-to-talk. A modifier key-up was posted with the
event's default flags (which for a modifier keycode include its own flag), so
macOS believed e.g. control was still held after the mic released. Enter then
became ctrl+Return and clicks became right-clicks.key_eventnow always sets
the flags it is given, and a chord releases with decreasing flags. Two
backstops added for a genuinely dropped pad event: a repeated key-down
releases the stale hold first, and a hold held past 120s auto-releases. - A long
submitprompt dropped a newline instead of sending. The text is
typed as one fast Unicode burst that a TUI reads as a paste; a Return arriving
inside it became a literal newline. Typed text now settles before the Return.
A per-bindingsubmit_delaytunes it.
Added
double_tapon aholdbinding fires a second, different shortcut on a
double-tap while keeping the physical push-to-talk hold, so the mic key can
drive a hold-mode app on hold and a toggle-mode app on double-tap.
Changed
- The shipped default's REJ cap is now a plain Escape (interrupt Claude,
clear the input, decline a prompt) rather than the gatedanswer_permission reject. Approve stays session-targeted.
FreeMicro 0.1.0
[0.1.0] - 2026-07-24
First public release.
The arc of this release, honestly: FreeMicro began as a state engine and a
renderer registry with no way to drive the actual pad. The Codex Micro's
LED protocol was unknown, so it shipped four fallback renderers while the
investigation ran. It ends with that protocol reverse engineered, published, and
verified on hardware over both USB and Bluetooth - every key, the dial, the
thumbstick and all three LED zones - plus a loop that runs at login without a
terminal open. The fallbacks are gone with it: they insured a risk that no
longer exists, and one of them could abort the process.
Added - talking to the pad
freemicro.device- the pad's0xFF00vendor JSON-RPC channel, over
USB and Bluetooth. Transport-aware write framing (63 bytes with no
report-id prefix on USB, 64 bytes with0x06prefixed on BLE), a pure and
therefore testable framer and decoder, a single shared device handle per
process, and a reconnect loop that treats disconnects as normal rather than
fatal.docs/PROTOCOL.md- the wire protocol, written up as interface facts and
believed to be its first public documentation: methods, key ids, the lighting
objects, the effect enum, and the per-transport framing trap. Plus
docs/CUSTOMIZING.mdanddocs/FACTORY-DEFAULTS.md.
Added - input
- Input bridge (
freemicro.input): every key, the dial press, the dial's
rotation ticks and the analogue thumbstick, routed to user-configured actions.
The pad emits no scancodes, so this is what makes it do anything at all. - CGEvent keystroke backend with AppleScript fallback: adds
fnsupport,
true hold-to-talk, mouse control, and no subprocess per keystroke. - Eight action kinds (
text,key,hold,shell,applescript,app,
mouse,none) in a registry where adding a ninth is one decorated function,
validated at config-load time.
Added - lighting
micro-ledsrenderer: drives the backlight, underglow and the six Agent
Keys from agent state vialights.preview+v.oai.thstatus. Opt-in -freemicro lights --enable- because macOS shares this device and FreeMicro
should not seize LEDs somebody else may be driving.- Factory-matching default colours, and a decay on
done(factory green means
unread, not completed).
Added - the loop is proven, and it runs without you
freemicro selftest: fires a whole synthetic Claude Code session through
the exact command recorded in your settings, as a subprocess with the event
JSON on stdin, against a throwaway state directory - then asserts the resolved
state for each event and the LED protocol messages for each state. This
closes the project's oldest gap: every piece was unit-tested and the assembly
never was.--jsonfor CI,--in-processwhen iterating on the classifier.freemicro start: guided setup end to end - both macOS permissions
(detected, explained, and the right System Settings pane opened on request),
the pad, vendor-app contention, the config, the LED opt-in (default still
no), the hooks (installed and verified), the daemon, and a closing light
show. Idempotent, and every prompt has a default so--yesnever hangs.freemicro daemon install|uninstall|status|logs: a launchd LaunchAgent
that starts at login, restarts if it dies, and logs to a size-capped file. The
pad emits no scancodes, so without something listening the hardware is dead - this is what stops that meaning "keep a terminal open forever".- Pad lock (
~/.freemicro/pad.lock): only one process can usefully hold this
device, sorun,keysand the daemon take anflockand name whoever has
it instead of fighting. Being anflock, a killed process can never leave a
stale lock behind.--take-padoverrides. freemicro.permissions: Input Monitoring viaIOHIDCheckAccessand
Accessibility viaAXIsProcessTrusted- read-only, prompt-free, and they work
with no pad attached, so "did you grant it?" is no longer inferred from a
failure three layers down.doctorreports "macOS hasn't decided yet" as the
distinct third state it really is.freemicro doctor: preflight for both macOS permissions, transport,
battery, and a realdevice.statusround trip - the only honest write test on
a device where success return codes mean nothing.freemicro run: keys in and lights out in one process, one device handle.- User-owned pad config (
~/.freemicro/keymap.json,freemicro keys --init):
every input and every LED colour/effect in one annotated JSON file. JSON
rather than TOML so the core stays dependency-free on Python 3.9.
Added - release readiness
freemicro.compat: records the firmware the protocol was actually
verified on (v0.4.1) and classifies whatever a pad reports as known-good,
newer, older or unparseable. It warns and explains; it never gates.freemicro.diagnostics: a redacted diagnostic bundle for bug reports - OS, Python, transport, firmware, battery, permissions, renderer availability
and config structure, in text and JSON. The contents ofshelland
applescriptbindings and every path outside the config directory are
stripped before anything is printed, so the output is safe to paste into a
public issue.SECURITY.mdanddocs/SECURITY-MODEL.md: what a config can actually
do, what the two macOS permissions really grant and to whom, a threat model
(malicious preset, local process on the web UI, unattended daemon, config
tampering), and a concrete design for preset trust that must ship alongside
shareable presets.docs/RELEASING.md: version bump through PyPI and GitHub release, with
the exact commands.- macOS CI. The product is macOS-only and CI was Linux-only, so every IOKit,
CGEvent and permissions path was untested. There is now amacos-latest
job (3.11 - 3.13, plus 3.9/3.10 on the x86_64 runner) that runs headless - no
hardware, no synthetic keystrokes, and a step that fails the build if anything
imports tkinter. Linux is kept across 3.9 - 3.13 to prove the pure-logic layers stay
portable. Ruff runs as its own job, and a packaging job installs the built
wheel into a clean venv and runs it. - Issue templates for bug reports (asking for
freemicro doctor --report)
and hardware reports (VID/PID and firmware version, feeding the crowdsourced
capability database andcompat.KNOWN_GOOD).
Added - the original scaffold
The layers that predate the hardware work, and are still the backbone:
- State engine (
freemicro.state): Claude Code hooks → five normalized
states, per-session store with priority resolution and TTL expiry. - Renderer registry (
freemicro.renderers): originally five renderers with
a guaranteed non-pad fallback. Four were deleted before release; see
Removed. What survives is the registry andmicro-leds. - Detector (
freemicro.detector): read-only HID capability probe for
Milestone 0. - Sniff & replay (
freemicro.capture,freemicro.protocol,freemicro learn,micro-sniffedrenderer): capture the ChatGPT app's own LED HID
reports per Codex state and replay them from Claude Code for an exact
replication of the Agent-Key behaviour. Parses tshark/Wireshark JSON or plain
hex captures, maps Codex states (thinking/running→working, awaiting→waiting,
…) to FreeMicro states, stores literal per-state frames, and optionally infers
RGB byte offsets from solid-colour captures. Full procedure in
docs/SNIFF-RUNBOOK.md. (Owner-authorized protocol interop; no firmware is
extracted or redistributed.) - LED write-test (
freemicro.verify/freemicro verify-leds): actively
drives the writable LED renderers through the state sequence (Path A from
docs/LED-STRATEGY.md), captures a human verdict (did the Agent Keys move?
per-key or global? app-quit needed?), and writes a report for the capability
DB. Uses only the documented VIA/QMK path - no firmware, no proprietary
protocol. - CLI (
freemicro):detect,install,hook,demo,emit,render,
renderers,status. Hooks are installed with an absolute interpreter path
so they resolve under Claude Code's minimal environment. freemicro demo: plays the full state sequence on the real renderer so
the end-to-end loop is visible with no agent or hardware attached.- Crowdsourced hardware DB scaffold (
hardware/capabilities.json). - Test suite (57 tests) covering the state engine, hook classifier, renderer
selection, hook installer, detector, the LED write-test harness, capture
parsing / learning / replay, and a full-session end-to-end pipeline (hook
events → store → resolved state).
Changed
- Hook installation is now absolute, quoted, and self-repairing. The command
is resolved from the binary you actually invoked (then the interpreter's
bin/, thenPATH) andshlex-quoted, so a virtualenv in a path with a space
no longer produces hooks that silently do nothing. Re-runningfreemicro installafter moving or reinstalling rewrites the stale entry rather than
leaving it. Entries carry atimeout, and--uninstallremoves exactly ours. freemicro installverifies itself by running the selftest, unless you
pass--no-verify. "Installed" and "working" are not the same claim.freemicro doctornow also checks Input Monitoring directly, whether the
hooks are installed and still point at this binary, and the daemon.freemicro daemon installrefuses to claim success it hasn't seen: it
waits for a stable pid (launchd reports one for a process already dying) and,
if the daemon never comes up, reads its log and explains why. It specifically
detects a binary installed under~/Desktop/~/Documents/~/Downloads, which
macOS blocks background agents from reading - an e...