freemicro 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. freemicro-0.1.0/.gitignore +29 -0
  2. freemicro-0.1.0/CHANGELOG.md +262 -0
  3. freemicro-0.1.0/CODE_OF_CONDUCT.md +44 -0
  4. freemicro-0.1.0/CONTRIBUTING.md +89 -0
  5. freemicro-0.1.0/LICENSE +21 -0
  6. freemicro-0.1.0/PKG-INFO +566 -0
  7. freemicro-0.1.0/README.md +502 -0
  8. freemicro-0.1.0/SPEC.md +353 -0
  9. freemicro-0.1.0/docs/AGENT-KEYS.md +364 -0
  10. freemicro-0.1.0/docs/AUDIT.md +1064 -0
  11. freemicro-0.1.0/docs/CUSTOMIZING.md +626 -0
  12. freemicro-0.1.0/docs/FACTORY-DEFAULTS.md +853 -0
  13. freemicro-0.1.0/docs/FEATURE-OPPORTUNITIES.md +900 -0
  14. freemicro-0.1.0/docs/LAUNCH.md +109 -0
  15. freemicro-0.1.0/docs/LED-STRATEGY.md +187 -0
  16. freemicro-0.1.0/docs/MENUBAR.md +299 -0
  17. freemicro-0.1.0/docs/PATH-B-CAPTURE.md +63 -0
  18. freemicro-0.1.0/docs/PRODUCT-REVIEW.md +950 -0
  19. freemicro-0.1.0/docs/PROTOCOL.md +296 -0
  20. freemicro-0.1.0/docs/RELEASING.md +443 -0
  21. freemicro-0.1.0/docs/SECURITY-MODEL.md +443 -0
  22. freemicro-0.1.0/docs/WEB-UI.md +890 -0
  23. freemicro-0.1.0/hardware/capabilities.json +207 -0
  24. freemicro-0.1.0/hardware/probes/codex-micro-303a-8360_2026-07-23.json +261 -0
  25. freemicro-0.1.0/hardware/probes/codex-report-descriptor.bin +0 -0
  26. freemicro-0.1.0/hooks/README.md +27 -0
  27. freemicro-0.1.0/pyproject.toml +94 -0
  28. freemicro-0.1.0/src/freemicro/__init__.py +18 -0
  29. freemicro-0.1.0/src/freemicro/__main__.py +6 -0
  30. freemicro-0.1.0/src/freemicro/agentkeys.py +590 -0
  31. freemicro-0.1.0/src/freemicro/cli.py +2368 -0
  32. freemicro-0.1.0/src/freemicro/compat.py +273 -0
  33. freemicro-0.1.0/src/freemicro/config.py +107 -0
  34. freemicro-0.1.0/src/freemicro/daemon.py +553 -0
  35. freemicro-0.1.0/src/freemicro/default_keymap.json +365 -0
  36. freemicro-0.1.0/src/freemicro/detector/__init__.py +5 -0
  37. freemicro-0.1.0/src/freemicro/detector/probe.py +190 -0
  38. freemicro-0.1.0/src/freemicro/device/__init__.py +192 -0
  39. freemicro-0.1.0/src/freemicro/device/codex_micro.py +678 -0
  40. freemicro-0.1.0/src/freemicro/device/lighting.py +347 -0
  41. freemicro-0.1.0/src/freemicro/diagnostics.py +628 -0
  42. freemicro-0.1.0/src/freemicro/focus.py +491 -0
  43. freemicro-0.1.0/src/freemicro/hooks_install.py +317 -0
  44. freemicro-0.1.0/src/freemicro/input/__init__.py +19 -0
  45. freemicro-0.1.0/src/freemicro/input/actions.py +999 -0
  46. freemicro-0.1.0/src/freemicro/input/bridge.py +1131 -0
  47. freemicro-0.1.0/src/freemicro/input/keys.py +273 -0
  48. freemicro-0.1.0/src/freemicro/input/latch.py +176 -0
  49. freemicro-0.1.0/src/freemicro/input/pointer.py +501 -0
  50. freemicro-0.1.0/src/freemicro/input/quartz.py +434 -0
  51. freemicro-0.1.0/src/freemicro/lighting_owner.py +836 -0
  52. freemicro-0.1.0/src/freemicro/menubar/__init__.py +34 -0
  53. freemicro-0.1.0/src/freemicro/menubar/__main__.py +15 -0
  54. freemicro-0.1.0/src/freemicro/menubar/app.py +670 -0
  55. freemicro-0.1.0/src/freemicro/menubar/checks.py +234 -0
  56. freemicro-0.1.0/src/freemicro/menubar/cocoa.py +381 -0
  57. freemicro-0.1.0/src/freemicro/menubar/model.py +438 -0
  58. freemicro-0.1.0/src/freemicro/menubar/status.py +560 -0
  59. freemicro-0.1.0/src/freemicro/onboarding.py +513 -0
  60. freemicro-0.1.0/src/freemicro/padconfig.py +1383 -0
  61. freemicro-0.1.0/src/freemicro/permission_prompt.py +449 -0
  62. freemicro-0.1.0/src/freemicro/permissions.py +212 -0
  63. freemicro-0.1.0/src/freemicro/renderers/__init__.py +37 -0
  64. freemicro-0.1.0/src/freemicro/renderers/base.py +157 -0
  65. freemicro-0.1.0/src/freemicro/renderers/micro_leds.py +1045 -0
  66. freemicro-0.1.0/src/freemicro/selftest.py +538 -0
  67. freemicro-0.1.0/src/freemicro/staleness.py +1133 -0
  68. freemicro-0.1.0/src/freemicro/state/__init__.py +43 -0
  69. freemicro-0.1.0/src/freemicro/state/engine.py +1374 -0
  70. freemicro-0.1.0/src/freemicro/state/hooks.py +256 -0
  71. freemicro-0.1.0/src/freemicro/state/slots.py +91 -0
  72. freemicro-0.1.0/src/freemicro/uninstall.py +926 -0
  73. freemicro-0.1.0/src/freemicro/webui/__init__.py +40 -0
  74. freemicro-0.1.0/src/freemicro/webui/api.py +479 -0
  75. freemicro-0.1.0/src/freemicro/webui/apps.py +102 -0
  76. freemicro-0.1.0/src/freemicro/webui/configio.py +529 -0
  77. freemicro-0.1.0/src/freemicro/webui/keycaps.py +375 -0
  78. freemicro-0.1.0/src/freemicro/webui/layout.py +401 -0
  79. freemicro-0.1.0/src/freemicro/webui/layouts.py +209 -0
  80. freemicro-0.1.0/src/freemicro/webui/padlink.py +664 -0
  81. freemicro-0.1.0/src/freemicro/webui/server.py +421 -0
  82. freemicro-0.1.0/src/freemicro/webui/starters.py +420 -0
  83. freemicro-0.1.0/src/freemicro/webui/static/app.css +1041 -0
  84. freemicro-0.1.0/src/freemicro/webui/static/app.js +3168 -0
  85. freemicro-0.1.0/src/freemicro/webui/static/index.html +101 -0
  86. freemicro-0.1.0/tests/conftest.py +27 -0
  87. freemicro-0.1.0/tests/test_actions.py +452 -0
  88. freemicro-0.1.0/tests/test_activity_light.py +799 -0
  89. freemicro-0.1.0/tests/test_agentkeys.py +982 -0
  90. freemicro-0.1.0/tests/test_bridge.py +886 -0
  91. freemicro-0.1.0/tests/test_cli.py +477 -0
  92. freemicro-0.1.0/tests/test_compat.py +175 -0
  93. freemicro-0.1.0/tests/test_daemon.py +191 -0
  94. freemicro-0.1.0/tests/test_diagnostics.py +242 -0
  95. freemicro-0.1.0/tests/test_e2e.py +182 -0
  96. freemicro-0.1.0/tests/test_hook_log_rotation.py +69 -0
  97. freemicro-0.1.0/tests/test_hooks.py +128 -0
  98. freemicro-0.1.0/tests/test_hooks_install.py +205 -0
  99. freemicro-0.1.0/tests/test_install_and_detect.py +96 -0
  100. freemicro-0.1.0/tests/test_latch.py +530 -0
  101. freemicro-0.1.0/tests/test_lighting_owner.py +756 -0
  102. freemicro-0.1.0/tests/test_menubar.py +594 -0
  103. freemicro-0.1.0/tests/test_micro_leds.py +717 -0
  104. freemicro-0.1.0/tests/test_onboarding.py +114 -0
  105. freemicro-0.1.0/tests/test_one_state_store.py +320 -0
  106. freemicro-0.1.0/tests/test_padconfig.py +545 -0
  107. freemicro-0.1.0/tests/test_permission_prompt.py +665 -0
  108. freemicro-0.1.0/tests/test_permissions.py +114 -0
  109. freemicro-0.1.0/tests/test_pointer.py +660 -0
  110. freemicro-0.1.0/tests/test_protocol.py +269 -0
  111. freemicro-0.1.0/tests/test_renderers.py +73 -0
  112. freemicro-0.1.0/tests/test_selftest.py +181 -0
  113. freemicro-0.1.0/tests/test_staleness.py +607 -0
  114. freemicro-0.1.0/tests/test_state_engine.py +1103 -0
  115. freemicro-0.1.0/tests/test_uninstall.py +645 -0
  116. freemicro-0.1.0/tests/test_webui.py +1541 -0
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+ env/
11
+
12
+ # Tooling
13
+ .pytest_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+ .mypy_cache/
18
+
19
+ # Editors / OS
20
+ .vscode/
21
+ .idea/
22
+ *.swp
23
+ .DS_Store
24
+
25
+ # FreeMicro runtime state (never commit a user's session store)
26
+ .freemicro/
27
+
28
+ # Browser-automation artifacts from UI verification
29
+ .playwright-mcp/
@@ -0,0 +1,262 @@
1
+ # Changelog
2
+
3
+ All notable changes to FreeMicro are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-07-24
8
+
9
+ First public release.
10
+
11
+ The arc of this release, honestly: FreeMicro began as a state engine and a
12
+ renderer registry with **no way to drive the actual pad**. The Codex Micro's
13
+ LED protocol was unknown, so it shipped four fallback renderers while the
14
+ investigation ran. It ends with that protocol reverse engineered, published, and
15
+ **verified on hardware over both USB and Bluetooth** - every key, the dial, the
16
+ thumbstick and all three LED zones - plus a loop that runs at login without a
17
+ terminal open. The fallbacks are gone with it: they insured a risk that no
18
+ longer exists, and one of them could abort the process.
19
+
20
+ ### Added - talking to the pad
21
+
22
+ - **`freemicro.device`** - the pad's `0xFF00` vendor JSON-RPC channel, over
23
+ **USB and Bluetooth**. Transport-aware write framing (63 bytes with no
24
+ report-id prefix on USB, 64 bytes with `0x06` prefixed on BLE), a pure and
25
+ therefore testable framer and decoder, a single shared device handle per
26
+ process, and a reconnect loop that treats disconnects as normal rather than
27
+ fatal.
28
+ - **`docs/PROTOCOL.md`** - the wire protocol, written up as interface facts and
29
+ believed to be its first public documentation: methods, key ids, the lighting
30
+ objects, the effect enum, and the per-transport framing trap. Plus
31
+ `docs/CUSTOMIZING.md` and `docs/FACTORY-DEFAULTS.md`.
32
+
33
+ ### Added - input
34
+
35
+ - **Input bridge** (`freemicro.input`): every key, the dial press, the dial's
36
+ rotation ticks and the analogue thumbstick, routed to user-configured actions.
37
+ The pad emits no scancodes, so this is what makes it do anything at all.
38
+ - **CGEvent keystroke backend** with AppleScript fallback: adds `fn` support,
39
+ true hold-to-talk, mouse control, and no subprocess per keystroke.
40
+ - **Eight action kinds** (`text`, `key`, `hold`, `shell`, `applescript`, `app`,
41
+ `mouse`, `none`) in a registry where adding a ninth is one decorated function,
42
+ validated at config-load time.
43
+
44
+ ### Added - lighting
45
+
46
+ - **`micro-leds` renderer**: drives the backlight, underglow and the six Agent
47
+ Keys from agent state via `lights.preview` + `v.oai.thstatus`. **Opt-in** - `freemicro lights --enable` - because macOS shares this device and FreeMicro
48
+ should not seize LEDs somebody else may be driving.
49
+ - Factory-matching default colours, and a decay on `done` (factory green means
50
+ *unread*, not *completed*).
51
+
52
+ ### Added - the loop is proven, and it runs without you
53
+
54
+ - **`freemicro selftest`**: fires a whole synthetic Claude Code session through
55
+ the *exact command recorded in your settings*, as a subprocess with the event
56
+ JSON on stdin, against a throwaway state directory - then asserts the resolved
57
+ state for each event **and** the LED protocol messages for each state. This
58
+ closes the project's oldest gap: every piece was unit-tested and the assembly
59
+ never was. `--json` for CI, `--in-process` when iterating on the classifier.
60
+ - **`freemicro start`**: guided setup end to end - both macOS permissions
61
+ (detected, explained, and the right System Settings pane opened on request),
62
+ the pad, vendor-app contention, the config, the LED opt-in (default still
63
+ **no**), the hooks (installed *and verified*), the daemon, and a closing light
64
+ show. Idempotent, and every prompt has a default so `--yes` never hangs.
65
+ - **`freemicro daemon install|uninstall|status|logs`**: a launchd LaunchAgent
66
+ that starts at login, restarts if it dies, and logs to a size-capped file. The
67
+ pad emits no scancodes, so without something listening the hardware is dead - this is what stops that meaning "keep a terminal open forever".
68
+ - **Pad lock** (`~/.freemicro/pad.lock`): only one process can usefully hold this
69
+ device, so `run`, `keys` and the daemon take an `flock` and *name whoever has
70
+ it* instead of fighting. Being an `flock`, a killed process can never leave a
71
+ stale lock behind. `--take-pad` overrides.
72
+ - **`freemicro.permissions`**: Input Monitoring via `IOHIDCheckAccess` and
73
+ Accessibility via `AXIsProcessTrusted` - read-only, prompt-free, and they work
74
+ with no pad attached, so "did you grant it?" is no longer inferred from a
75
+ failure three layers down. `doctor` reports "macOS hasn't decided yet" as the
76
+ distinct third state it really is.
77
+ - **`freemicro doctor`**: preflight for both macOS permissions, transport,
78
+ battery, and a real `device.status` round trip - the only honest write test on
79
+ a device where success return codes mean nothing.
80
+ - **`freemicro run`**: keys in and lights out in one process, one device handle.
81
+ - **User-owned pad config** (`~/.freemicro/keymap.json`, `freemicro keys --init`):
82
+ every input and every LED colour/effect in one annotated JSON file. JSON
83
+ rather than TOML so the core stays dependency-free on Python 3.9.
84
+
85
+ ### Added - release readiness
86
+
87
+ - **`freemicro.compat`**: records the firmware the protocol was actually
88
+ verified on (v0.4.1) and classifies whatever a pad reports as known-good,
89
+ newer, older or unparseable. It warns and explains; it never gates.
90
+ - **`freemicro.diagnostics`**: a redacted diagnostic bundle for bug reports - OS, Python, transport, firmware, battery, permissions, renderer availability
91
+ and config *structure*, in text and JSON. The contents of `shell` and
92
+ `applescript` bindings and every path outside the config directory are
93
+ stripped before anything is printed, so the output is safe to paste into a
94
+ public issue.
95
+ - **`SECURITY.md`** and **`docs/SECURITY-MODEL.md`**: what a config can actually
96
+ do, what the two macOS permissions really grant and to whom, a threat model
97
+ (malicious preset, local process on the web UI, unattended daemon, config
98
+ tampering), and a concrete design for preset trust that must ship alongside
99
+ shareable presets.
100
+ - **`docs/RELEASING.md`**: version bump through PyPI and GitHub release, with
101
+ the exact commands.
102
+ - **macOS CI.** The product is macOS-only and CI was Linux-only, so every IOKit,
103
+ CGEvent and permissions path was untested. There is now a `macos-latest`
104
+ job (3.11 - 3.13, plus 3.9/3.10 on the x86_64 runner) that runs headless - no
105
+ hardware, no synthetic keystrokes, and a step that fails the build if anything
106
+ imports tkinter. Linux is kept across 3.9 - 3.13 to prove the pure-logic layers stay
107
+ portable. Ruff runs as its own job, and a packaging job installs the built
108
+ wheel into a clean venv and runs it.
109
+ - **Issue templates** for bug reports (asking for `freemicro doctor --report`)
110
+ and hardware reports (VID/PID *and firmware version*, feeding the crowdsourced
111
+ capability database and `compat.KNOWN_GOOD`).
112
+
113
+ ### Added - the original scaffold
114
+
115
+ The layers that predate the hardware work, and are still the backbone:
116
+
117
+ - **State engine** (`freemicro.state`): Claude Code hooks → five normalized
118
+ states, per-session store with priority resolution and TTL expiry.
119
+ - **Renderer registry** (`freemicro.renderers`): originally five renderers with
120
+ a guaranteed non-pad fallback. Four were deleted before release; see
121
+ **Removed**. What survives is the registry and `micro-leds`.
122
+ - **Detector** (`freemicro.detector`): read-only HID capability probe for
123
+ Milestone 0.
124
+ - **Sniff & replay** (`freemicro.capture`, `freemicro.protocol`, `freemicro
125
+ learn`, `micro-sniffed` renderer): capture the ChatGPT app's own LED HID
126
+ reports per Codex state and replay them from Claude Code for an exact
127
+ replication of the Agent-Key behaviour. Parses tshark/Wireshark JSON or plain
128
+ hex captures, maps Codex states (thinking/running→working, awaiting→waiting,
129
+ …) to FreeMicro states, stores literal per-state frames, and optionally infers
130
+ RGB byte offsets from solid-colour captures. Full procedure in
131
+ `docs/SNIFF-RUNBOOK.md`. (Owner-authorized protocol interop; no firmware is
132
+ extracted or redistributed.)
133
+ - **LED write-test** (`freemicro.verify` / `freemicro verify-leds`): actively
134
+ drives the writable LED renderers through the state sequence (Path A from
135
+ `docs/LED-STRATEGY.md`), captures a human verdict (did the Agent Keys move?
136
+ per-key or global? app-quit needed?), and writes a report for the capability
137
+ DB. Uses only the documented VIA/QMK path - no firmware, no proprietary
138
+ protocol.
139
+ - **CLI** (`freemicro`): `detect`, `install`, `hook`, `demo`, `emit`, `render`,
140
+ `renderers`, `status`. Hooks are installed with an absolute interpreter path
141
+ so they resolve under Claude Code's minimal environment.
142
+ - **`freemicro demo`**: plays the full state sequence on the real renderer so
143
+ the end-to-end loop is visible with no agent or hardware attached.
144
+ - **Crowdsourced hardware DB** scaffold (`hardware/capabilities.json`).
145
+ - Test suite (57 tests) covering the state engine, hook classifier, renderer
146
+ selection, hook installer, detector, the LED write-test harness, capture
147
+ parsing / learning / replay, and a full-session end-to-end pipeline (hook
148
+ events → store → resolved state).
149
+
150
+ ### Changed
151
+
152
+ - **Hook installation is now absolute, quoted, and self-repairing.** The command
153
+ is resolved from the binary you actually invoked (then the interpreter's
154
+ `bin/`, then `PATH`) and `shlex`-quoted, so a virtualenv in a path with a space
155
+ no longer produces hooks that silently do nothing. Re-running `freemicro
156
+ install` after moving or reinstalling *rewrites* the stale entry rather than
157
+ leaving it. Entries carry a `timeout`, and `--uninstall` removes exactly ours.
158
+ - **`freemicro install` verifies itself** by running the selftest, unless you
159
+ pass `--no-verify`. "Installed" and "working" are not the same claim.
160
+ - **`freemicro doctor`** now also checks Input Monitoring directly, whether the
161
+ hooks are installed *and still point at this binary*, and the daemon.
162
+ - **`freemicro daemon install` refuses to claim success it hasn't seen**: it
163
+ waits for a stable pid (launchd reports one for a process already dying) and,
164
+ if the daemon never comes up, reads its log and explains why. It specifically
165
+ detects a binary installed under `~/Desktop`/`~/Documents`/`~/Downloads`, which
166
+ macOS blocks background agents from reading - an error whose actual message
167
+ (`PermissionError … pyvenv.cfg`) is unguessable.
168
+ - **Packaging**: `Development Status :: 4 - Beta` (the loop is verified on
169
+ hardware and covered by a re-runnable self-test; not Stable until more than one
170
+ unit and firmware have been through it), macOS and Python 3.9 - 3.13 classifiers,
171
+ project URLs, and an explicit `artifacts` entry so the shipped keymap can never
172
+ be dropped from the wheel. `pipx` is now the recommended install.
173
+ - Lighting defaults to `lights.preview` rather than `v.oai.rgbcfg`, with the
174
+ choice exposed as one config key (`lighting.method`), because the two paths
175
+ genuinely disagree - see the open question in `docs/PROTOCOL.md`.
176
+
177
+ ### Removed
178
+
179
+ - **Four of the five renderers, and the positioning that justified them.**
180
+ `screen`, `busylight`, `micro-via` and `micro-qmk` are gone, with `--no-screen`,
181
+ `freemicro watch`, the `busylight` extra, the Tk probe and its
182
+ `~/.freemicro/tk-probe.json` cache, and doctor's Tk check. They existed so
183
+ that "the alert never depends on the pad", written while the LED path was
184
+ unproven; it is now verified on hardware over both transports. They were not
185
+ free: `screen`'s window could not open on **any** machine (nothing ever called
186
+ the probe that would have let it), it had already aborted a process outright,
187
+ and doctor warned about it with a blank reason. `busylight` had never been run
188
+ by anyone; blink(1) and Luxafor owners are better served by VibeSignal, which
189
+ does that job properly. `micro-via` and `micro-qmk` targeted VIA/QMK pads: the
190
+ Codex Micro exposes no `0xFF60` channel and QMK does not run on its ESP32.
191
+ `freemicro run` prints each state change to the terminal, which is all the
192
+ `screen` renderer ever actually delivered. A `renderers.prefer` entry naming a
193
+ removed renderer, and `freemicro watch` or `--no-screen` on a command line,
194
+ each get a sentence saying what replaced them.
195
+ - **`firmware/qmk-keymap/`**: firmware for a reflash this silicon cannot take.
196
+ - **`presets/`**: a VIA skeleton still carrying `"vendorId": "0x0000"` and a
197
+ Work Louder Input layout whose bindings predate being able to read the pad
198
+ directly. Both were fossils; the web UI (`freemicro config --web`) is how you
199
+ configure the pad now.
200
+ - `micro_sniffed`: its premise (sniffing an opaque binary RGB protocol and
201
+ replaying it) is obsolete now that the channel is known to speak JSON-RPC.
202
+ - `docs/PATH-B-CAPTURE.md` retired to a decision record; no USB capture was
203
+ needed.
204
+
205
+ ### Fixed
206
+
207
+ - **Every surface now decays a session by the same clocks.** The CLI, the menu
208
+ bar, the web UI and the pad each built their own `StateStore` from a
209
+ hand-written list of TTLs, and three of the lists were short, so
210
+ `working_ttl_seconds: 0` and `tool_ttl_seconds` were ignored by the console
211
+ line while the pad honoured them, and the two could disagree about the same
212
+ session in the same process at the same instant. The timers now travel as one
213
+ `DecayPolicy` and there is a single construction, `default_store()`, that
214
+ every reader calls. `working_ttl_seconds: 0` switches the check off
215
+ everywhere, exactly as the docs have always said.
216
+ - **"LEDs require USB" was wrong.** That conclusion came from malformed
217
+ Bluetooth writes, not from an incapable transport: BLE needs a 64-byte buffer
218
+ with the report id prefixed. With correct framing, input, lighting and RPC all
219
+ work wirelessly - confirmed by eye with the cable unplugged.
220
+ - **Encoder rotation is no longer silently swallowed.** Ticks have been seen
221
+ carrying `act` values other than `1`, so `ENC_CW`/`ENC_CC` now fire on any
222
+ value. Filtering on `act == 1` dropped every dial turn and looked exactly like
223
+ a dead dial.
224
+ - **Tk can no longer take the process down**, because FreeMicro no longer
225
+ imports Tk at all. On some macOS/Python combinations `tkinter.Tk()` calls
226
+ `abort()`, which no `try`/`except` can catch; the subprocess probe written to
227
+ survive that went out with the renderer it defended.
228
+ - **The IOKit input-report callback keeps a strong module-level reference**, so
229
+ a pad that drops mid-callback can no longer segfault the interpreter.
230
+
231
+ ### Security
232
+
233
+ - LED control is **off by default**. FreeMicro does not take over a shared
234
+ device uninvited.
235
+ - The web UI refuses to bind anything that is not loopback - a `ValueError`,
236
+ with no flag to override it - requires a per-run bearer token on every
237
+ request, and validates the `Host` header to close DNS rebinding.
238
+ - Diagnostic reports redact `shell` and `applescript` contents and personal
239
+ paths, so they are safe to paste into a public issue.
240
+ - `sys.bootloader`, which reboots the pad into DFU and disconnects it, is
241
+ documented and deliberately never sent.
242
+ - The security model and the preset-trust design are written down *before*
243
+ shareable presets exist, because the day presets land, downloading one becomes
244
+ remote code execution.
245
+
246
+ ### Known limitations
247
+
248
+ - **Pad support is macOS-only.** The vendor channel is reached through IOKit;
249
+ `hidapi` cannot open this device. There is no non-pad display to fall back to,
250
+ so on Linux and Windows FreeMicro currently shows you nothing. Support is
251
+ unimplemented, not impossible.
252
+ - **One unit, one firmware.** Everything was verified on a single pad running
253
+ v0.4.1. `freemicro.compat` says so out loud when yours differs, and a
254
+ [Hardware Report](.github/ISSUE_TEMPLATE/hardware_report.yml) is the single
255
+ most valuable contribution anyone can make.
256
+ - **`v.oai.rgbcfg` is unresolved.** It acknowledges without any visible change
257
+ on our hardware, while the vendor app appears to use it for everything.
258
+ - **Synthetic `fn` is unverified** against third-party dictation apps.
259
+ - **There is no preset trust check yet.** A config you did not write is a
260
+ program you did not read - see `docs/SECURITY-MODEL.md`.
261
+
262
+ [0.1.0]: https://github.com/eliBenven/freemicro/releases/tag/v0.1.0
@@ -0,0 +1,44 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment:
18
+
19
+ - Demonstrating empathy and kindness toward other people
20
+ - Being respectful of differing opinions, viewpoints, and experiences
21
+ - Giving and gracefully accepting constructive feedback
22
+ - Accepting responsibility and apologizing to those affected by our mistakes
23
+ - Focusing on what is best for the overall community
24
+
25
+ Examples of unacceptable behavior:
26
+
27
+ - The use of sexualized language or imagery, and sexual attention or advances
28
+ - Trolling, insulting or derogatory comments, and personal or political attacks
29
+ - Public or private harassment
30
+ - Publishing others' private information without explicit permission
31
+ - Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Enforcement
35
+
36
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
37
+ reported to the project maintainers. All complaints will be reviewed and
38
+ investigated promptly and fairly.
39
+
40
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
41
+ version 2.1, available at
42
+ https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
43
+
44
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,89 @@
1
+ # Contributing to FreeMicro
2
+
3
+ Thanks for helping turn macro pads into agent-status surfaces! There are two
4
+ kinds of contribution and **one of them needs no code at all.**
5
+
6
+ ## 🥇 The highest-value contribution: a hardware report
7
+
8
+ FreeMicro's whole LED path hinges on facts we can only learn from physical
9
+ hardware (see [`SPEC.md` §4](SPEC.md)). If you own a Codex Micro - or *any*
10
+ VIA/QMK RGB pad - run the read-only probe and share the result:
11
+
12
+ ```sh
13
+ pip install "freemicro[detect]"
14
+ freemicro detect --json
15
+ ```
16
+
17
+ Then open a **Hardware Report** issue and paste the output. That single
18
+ paste materially advances the project and grows
19
+ [`hardware/capabilities.json`](hardware/capabilities.json). The probe never
20
+ writes to your device.
21
+
22
+ ## 🛠️ Code contributions
23
+
24
+ ### Setup
25
+
26
+ ```sh
27
+ git clone https://github.com/eliBenven/freemicro && cd freemicro
28
+ python -m venv .venv && source .venv/bin/activate
29
+ pip install -e ".[dev,all]"
30
+ pytest # should be green in under a second
31
+ ruff check . # lint
32
+ ```
33
+
34
+ ### Project shape
35
+
36
+ ```
37
+ src/freemicro/
38
+ state/ hooks -> normalized AgentState, per-session store (pure, tested)
39
+ renderers/ base registry + micro-leds (the pad's own LEDs)
40
+ device/ the pad's 0xFF00 vendor channel, shared by input and lighting
41
+ detector/ read-only HID probe
42
+ input/ pad events -> your bound actions
43
+ cli.py the `freemicro` command
44
+ ```
45
+
46
+ ### Principles
47
+
48
+ 1. **The pad is the display, and it must say so.** FreeMicro once shipped four
49
+ fallback renderers so that "the alert never depends on the pad". The LEDs are
50
+ now verified on hardware over USB and Bluetooth, the fallbacks were deleted,
51
+ and no surface may claim to show agent state unless someone has watched it
52
+ do so. `freemicro run` prints each state change to the terminal; that line is
53
+ the whole of the non-pad output and it is not a fallback.
54
+ 2. **Core stays dependency-free.** `hidapi` is an *optional* extra, used only by
55
+ `freemicro detect`. Importing `freemicro` must work without it.
56
+ 3. **Hooks must never break Claude Code.** The `hook` command swallows its own
57
+ errors and exits 0. A status light is not worth interrupting someone's
58
+ session.
59
+ 4. **New agent? New file.** Adding Codex CLI / Cursor support should mean a new
60
+ classifier in `state/`, not changes to the engine or renderers.
61
+ 5. **Experimental means experimental.** A hardware renderer stays `experimental
62
+ = True` and dormant (`available()` False) until validated on real hardware
63
+ with a capability-DB entry - and if it never gets validated, it gets deleted
64
+ rather than listed.
65
+
66
+ ### Adding a renderer
67
+
68
+ Subclass `freemicro.renderers.base.Renderer`, implement `available()` and
69
+ `render(state)`, and decorate the class with `@register`. Pick a `priority`
70
+ (higher = preferred as primary). Add a row to the renderer table in the README.
71
+
72
+ ### Adding a new agent harness
73
+
74
+ Write a `classify(event) -> AgentState | None` for that agent's events, mirror
75
+ the tests in `tests/test_hooks.py`, and wire it into the CLI. The state engine
76
+ and every renderer come along for free.
77
+
78
+ ### Commit / PR
79
+
80
+ - Keep PRs focused; one concern each.
81
+ - Add or update tests - `pytest` must stay green.
82
+ - Run `ruff check .` before pushing.
83
+ - Be kind. See [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).
84
+
85
+ ## Reporting bugs / ideas
86
+
87
+ Use the issue templates. For anything hardware-shaped, include your
88
+ `freemicro detect --json` output - it's almost always the first thing we'll ask
89
+ for.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eli Benveniste
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.