ember-assist
v0.1.1
Published
Local MCP bridge and ESP32 mock for Ember physical expressions.
Readme
Ember
Ember is a global CLI for expressing Codex task state through a small display. It installs a Codex hook runner, stores global runtime config, auto-detects a serial device when possible, and optionally exposes an MCP tool for advanced control.
The default transport is a built-in ESP32 mock, so the project can be developed and tested without hardware connected.
Features
- Global CLI:
ember - Interactive setup:
ember setup - Health checks:
ember doctor - Hook install/uninstall:
ember hooks install --global - MCP tool:
set_expressionviaember mcp serve - Mock and USB serial transports
- Global config with serial-path persistence and hook mappings
- Validated expression names
- Simple serial protocol:
EXPR <name>\n - ESP32 mock for local tests and development
Supported Expressions
| Expression | Display behavior |
| --- | --- |
| idle | calm neutral face |
| thinking | animated thinking face |
| success | happy check face |
| error | sad error face |
| working | typing cat keyboard scene |
| warning | alert face with warning marker |
| approval | waiting for approval face |
| happy | celebratory face with sparkles |
Requirements
- Node.js 22.14.0 or newer
- npm
- Optional hardware:
- ESP32-C3 dev board
- GME12864-41 v3 128x64 I2C OLED
- USB serial connection
- PlatformIO CLI for firmware build/upload
Install
npm install
npm install -g .Publish target package name:
ember-assistFor a full system setup from installing Codex CLI through flashing the ESP32-C3 and enabling hooks, see docs/system-setup.md.
Quick Start
- Install the CLI globally:
npm install -g ember-assist- Run one-time setup:
ember setup- Validate the installation and send a test expression:
ember doctor- Send an expression directly:
ember expr thinkingLegacy compatibility is still available:
ember-led happyUsage
Common flows
Set up Ember on a new machine:
ember setup
ember doctorSend a single expression with saved config:
ember expr workingSend a single expression with explicit serial settings:
ember expr success --transport serial --serial-path /dev/tty.usbserial-0001Test without hardware:
ember expr happy --transport mockInstall global Codex hooks:
ember hooks install --globalRemove Ember-managed Codex hooks:
ember hooks uninstall --globalStart the optional MCP server:
ember mcp serveCommand reference
Show help:
ember --helpRun one-time setup:
ember setupRun health checks:
ember doctorSend an expression:
ember expr <expression>Send an expression through the legacy compatibility alias:
ember-led <expression>Install or update global Codex hooks:
ember hooks install --globalRemove Ember-managed hooks:
ember hooks uninstall --globalStart the MCP stdio server:
ember mcp serveCLI options
These options work with commands that resolve runtime config such as expr,
doctor, and mcp serve:
--transport mock|serial
--serial-path <path>
--baud-rate <baud>
--config <path>Examples:
ember doctor --transport mock
ember expr thinking --serial-path /dev/cu.usbmodem12101
ember mcp serve --config /tmp/ember-config.jsonRuntime Config
ember setup writes global config to:
~/.config/ember/config.jsonThe config stores:
- selected transport
- selected serial device path and metadata
- baud rate
- hook event mappings
- throttle window
Resolution precedence is:
- CLI flags such as
--serial-path - Environment variables
- Saved global config
- Auto-detected serial device
Serial Transport
ember setup will prefer a detected serial device and persist it. You can still
override runtime behavior explicitly:
ember expr thinking --transport serial --serial-path /dev/tty.usbserial-0001The baud rate defaults to 115200 and can be overridden with --baud-rate or
EMBER_BAUD_RATE.
If no serial device is configured, Ember falls back to the mock transport only
when the saved config or explicit flags request it. Otherwise it asks you to run
ember setup.
Codex Hooks
Global hooks are installed into:
~/.codex/hooks.jsonThe managed hook runner is invoked through:
ember hook-runnerDefault hook mappings are:
| Hook event | Expression | Purpose |
| --- | --- | --- |
| UserPromptSubmit | thinking | Shows that Codex is considering the prompt |
| PreToolUse | working | Shows that Codex is about to run a tool |
| PermissionRequest | approval | Shows that Codex is paused for approval |
| Stop | happy | Shows a completion celebration when the response ends |
The hook runner keeps runtime state under:
~/.config/ember/Behavior guarantees:
- hook failures still return exit code
0 - a global throttle suppresses high-frequency duplicate sends
approvalbypasses throttle by default- hook execution never prompts the user interactively
Hook behavior can be overridden with environment variables:
EMBER_HOOK_ON_PROMPT=thinking
EMBER_HOOK_ON_TOOL_START=working
EMBER_HOOK_ON_PERMISSION=approval
EMBER_HOOK_ON_STOP=happy
EMBER_HOOK_THROTTLE_SECONDS=1
EMBER_HOOK_THROTTLE_BYPASS=approval,errorMCP Server
The MCP server is optional. Start it when you want an external model client to
call set_expression directly:
ember mcp serveThe server registers one tool:
set_expression({ name: "thinking" })It uses the same runtime resolution stack as the CLI and hook runner, so global config, environment overrides, and auto-detection behave the same way.
Serial Protocol
Ember sends newline-terminated commands:
EXPR thinking
EXPR success
EXPR error
EXPR idle
EXPR working
EXPR warning
EXPR approval
EXPR happyInvalid commands or unsupported expressions are ignored by the mock and should be ignored by firmware.
Development
Run the test suite:
npm testType-check the project:
npm run typecheckBuild the ESP32-C3 firmware:
npm run firmware:buildUpload firmware:
npm run firmware:uploadOpen the serial monitor:
npm run firmware:monitorRun firmware tests on a connected board:
npm run firmware:testProject Structure
src/cli.ts CLI entry point
src/mcp-server.ts MCP stdio server and tool registration
src/service.ts Expression validation and transport orchestration
src/protocol.ts Serial command encoder/parser
src/transports.ts Mock and serial transports
src/esp32-mock.ts Local ESP32 behavior mock
firmware/ ESP32-C3 PlatformIO firmware
tests/ Vitest tests
bin/ember-led Executable CLI wrapper
.codex/hooks.json Codex hook configurationHardware Notes
Current firmware defaults target an ESP32-C3 dev module with a GME12864-41 v3 128x64 I2C OLED:
OLED VCC -> ESP32-C3 3.3V
OLED GND -> ESP32-C3 GND
OLED SDA -> ESP32-C3 GPIO8
OLED SCL -> ESP32-C3 GPIO9Use 3.3V unless the OLED module explicitly supports 5V logic and power.
Firmware defaults live in firmware/include/config.h:
constexpr uint8_t EMBER_DISPLAY_WIDTH = 128;
constexpr uint8_t EMBER_DISPLAY_HEIGHT = 64;
constexpr uint8_t EMBER_DISPLAY_SDA_PIN = 8;
constexpr uint8_t EMBER_DISPLAY_SCL_PIN = 9;
constexpr uint8_t EMBER_DISPLAY_I2C_ADDRESS = 0x3C;If the display does not light, try address 0x3D, verify the controller is
SSD1306-compatible, and confirm SDA/SCL wiring.
