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.
- freemicro-0.1.0/.gitignore +29 -0
- freemicro-0.1.0/CHANGELOG.md +262 -0
- freemicro-0.1.0/CODE_OF_CONDUCT.md +44 -0
- freemicro-0.1.0/CONTRIBUTING.md +89 -0
- freemicro-0.1.0/LICENSE +21 -0
- freemicro-0.1.0/PKG-INFO +566 -0
- freemicro-0.1.0/README.md +502 -0
- freemicro-0.1.0/SPEC.md +353 -0
- freemicro-0.1.0/docs/AGENT-KEYS.md +364 -0
- freemicro-0.1.0/docs/AUDIT.md +1064 -0
- freemicro-0.1.0/docs/CUSTOMIZING.md +626 -0
- freemicro-0.1.0/docs/FACTORY-DEFAULTS.md +853 -0
- freemicro-0.1.0/docs/FEATURE-OPPORTUNITIES.md +900 -0
- freemicro-0.1.0/docs/LAUNCH.md +109 -0
- freemicro-0.1.0/docs/LED-STRATEGY.md +187 -0
- freemicro-0.1.0/docs/MENUBAR.md +299 -0
- freemicro-0.1.0/docs/PATH-B-CAPTURE.md +63 -0
- freemicro-0.1.0/docs/PRODUCT-REVIEW.md +950 -0
- freemicro-0.1.0/docs/PROTOCOL.md +296 -0
- freemicro-0.1.0/docs/RELEASING.md +443 -0
- freemicro-0.1.0/docs/SECURITY-MODEL.md +443 -0
- freemicro-0.1.0/docs/WEB-UI.md +890 -0
- freemicro-0.1.0/hardware/capabilities.json +207 -0
- freemicro-0.1.0/hardware/probes/codex-micro-303a-8360_2026-07-23.json +261 -0
- freemicro-0.1.0/hardware/probes/codex-report-descriptor.bin +0 -0
- freemicro-0.1.0/hooks/README.md +27 -0
- freemicro-0.1.0/pyproject.toml +94 -0
- freemicro-0.1.0/src/freemicro/__init__.py +18 -0
- freemicro-0.1.0/src/freemicro/__main__.py +6 -0
- freemicro-0.1.0/src/freemicro/agentkeys.py +590 -0
- freemicro-0.1.0/src/freemicro/cli.py +2368 -0
- freemicro-0.1.0/src/freemicro/compat.py +273 -0
- freemicro-0.1.0/src/freemicro/config.py +107 -0
- freemicro-0.1.0/src/freemicro/daemon.py +553 -0
- freemicro-0.1.0/src/freemicro/default_keymap.json +365 -0
- freemicro-0.1.0/src/freemicro/detector/__init__.py +5 -0
- freemicro-0.1.0/src/freemicro/detector/probe.py +190 -0
- freemicro-0.1.0/src/freemicro/device/__init__.py +192 -0
- freemicro-0.1.0/src/freemicro/device/codex_micro.py +678 -0
- freemicro-0.1.0/src/freemicro/device/lighting.py +347 -0
- freemicro-0.1.0/src/freemicro/diagnostics.py +628 -0
- freemicro-0.1.0/src/freemicro/focus.py +491 -0
- freemicro-0.1.0/src/freemicro/hooks_install.py +317 -0
- freemicro-0.1.0/src/freemicro/input/__init__.py +19 -0
- freemicro-0.1.0/src/freemicro/input/actions.py +999 -0
- freemicro-0.1.0/src/freemicro/input/bridge.py +1131 -0
- freemicro-0.1.0/src/freemicro/input/keys.py +273 -0
- freemicro-0.1.0/src/freemicro/input/latch.py +176 -0
- freemicro-0.1.0/src/freemicro/input/pointer.py +501 -0
- freemicro-0.1.0/src/freemicro/input/quartz.py +434 -0
- freemicro-0.1.0/src/freemicro/lighting_owner.py +836 -0
- freemicro-0.1.0/src/freemicro/menubar/__init__.py +34 -0
- freemicro-0.1.0/src/freemicro/menubar/__main__.py +15 -0
- freemicro-0.1.0/src/freemicro/menubar/app.py +670 -0
- freemicro-0.1.0/src/freemicro/menubar/checks.py +234 -0
- freemicro-0.1.0/src/freemicro/menubar/cocoa.py +381 -0
- freemicro-0.1.0/src/freemicro/menubar/model.py +438 -0
- freemicro-0.1.0/src/freemicro/menubar/status.py +560 -0
- freemicro-0.1.0/src/freemicro/onboarding.py +513 -0
- freemicro-0.1.0/src/freemicro/padconfig.py +1383 -0
- freemicro-0.1.0/src/freemicro/permission_prompt.py +449 -0
- freemicro-0.1.0/src/freemicro/permissions.py +212 -0
- freemicro-0.1.0/src/freemicro/renderers/__init__.py +37 -0
- freemicro-0.1.0/src/freemicro/renderers/base.py +157 -0
- freemicro-0.1.0/src/freemicro/renderers/micro_leds.py +1045 -0
- freemicro-0.1.0/src/freemicro/selftest.py +538 -0
- freemicro-0.1.0/src/freemicro/staleness.py +1133 -0
- freemicro-0.1.0/src/freemicro/state/__init__.py +43 -0
- freemicro-0.1.0/src/freemicro/state/engine.py +1374 -0
- freemicro-0.1.0/src/freemicro/state/hooks.py +256 -0
- freemicro-0.1.0/src/freemicro/state/slots.py +91 -0
- freemicro-0.1.0/src/freemicro/uninstall.py +926 -0
- freemicro-0.1.0/src/freemicro/webui/__init__.py +40 -0
- freemicro-0.1.0/src/freemicro/webui/api.py +479 -0
- freemicro-0.1.0/src/freemicro/webui/apps.py +102 -0
- freemicro-0.1.0/src/freemicro/webui/configio.py +529 -0
- freemicro-0.1.0/src/freemicro/webui/keycaps.py +375 -0
- freemicro-0.1.0/src/freemicro/webui/layout.py +401 -0
- freemicro-0.1.0/src/freemicro/webui/layouts.py +209 -0
- freemicro-0.1.0/src/freemicro/webui/padlink.py +664 -0
- freemicro-0.1.0/src/freemicro/webui/server.py +421 -0
- freemicro-0.1.0/src/freemicro/webui/starters.py +420 -0
- freemicro-0.1.0/src/freemicro/webui/static/app.css +1041 -0
- freemicro-0.1.0/src/freemicro/webui/static/app.js +3168 -0
- freemicro-0.1.0/src/freemicro/webui/static/index.html +101 -0
- freemicro-0.1.0/tests/conftest.py +27 -0
- freemicro-0.1.0/tests/test_actions.py +452 -0
- freemicro-0.1.0/tests/test_activity_light.py +799 -0
- freemicro-0.1.0/tests/test_agentkeys.py +982 -0
- freemicro-0.1.0/tests/test_bridge.py +886 -0
- freemicro-0.1.0/tests/test_cli.py +477 -0
- freemicro-0.1.0/tests/test_compat.py +175 -0
- freemicro-0.1.0/tests/test_daemon.py +191 -0
- freemicro-0.1.0/tests/test_diagnostics.py +242 -0
- freemicro-0.1.0/tests/test_e2e.py +182 -0
- freemicro-0.1.0/tests/test_hook_log_rotation.py +69 -0
- freemicro-0.1.0/tests/test_hooks.py +128 -0
- freemicro-0.1.0/tests/test_hooks_install.py +205 -0
- freemicro-0.1.0/tests/test_install_and_detect.py +96 -0
- freemicro-0.1.0/tests/test_latch.py +530 -0
- freemicro-0.1.0/tests/test_lighting_owner.py +756 -0
- freemicro-0.1.0/tests/test_menubar.py +594 -0
- freemicro-0.1.0/tests/test_micro_leds.py +717 -0
- freemicro-0.1.0/tests/test_onboarding.py +114 -0
- freemicro-0.1.0/tests/test_one_state_store.py +320 -0
- freemicro-0.1.0/tests/test_padconfig.py +545 -0
- freemicro-0.1.0/tests/test_permission_prompt.py +665 -0
- freemicro-0.1.0/tests/test_permissions.py +114 -0
- freemicro-0.1.0/tests/test_pointer.py +660 -0
- freemicro-0.1.0/tests/test_protocol.py +269 -0
- freemicro-0.1.0/tests/test_renderers.py +73 -0
- freemicro-0.1.0/tests/test_selftest.py +181 -0
- freemicro-0.1.0/tests/test_staleness.py +607 -0
- freemicro-0.1.0/tests/test_state_engine.py +1103 -0
- freemicro-0.1.0/tests/test_uninstall.py +645 -0
- 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.
|
freemicro-0.1.0/LICENSE
ADDED
|
@@ -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.
|