Skip to content

About

Codex status indicator for Moonside Halo lamps over local BLE

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Codex Moonside Lamp

Make a Moonside Halo lamp show local Codex status using Codex lifecycle hooks and BLE.

Tests License: MIT

Codex hook event
  -> codex-moonside-hook
  -> /tmp/codex_moonside_state.json
  -> codex-moonside-daemon
  -> Moonside lamp over local BLE

The hook exits quickly and never talks to BLE. The daemon owns the persistent BLE connection and watches the state file.

Demo

Codex Moonside Lamp demo

Watch the MP4 demo

Moonside Halo attention state

Status

Version 0.2.0 (alpha), tested locally.

Tested setup:

  • macOS
  • Python 3.12
  • Codex local app/CLI hooks with hooks = true
  • Moonside Halo advertising as MOONSIDE-O101

Linux may work if bleak can access the Bluetooth adapter. Windows is not a target.

Behavior

Codex event Lamp state Default visual
SessionStart idle warm solid color
UserPromptSubmit working animated BEAT2
PermissionRequest attention purple gradient approval-needed signal
matching PostToolUse after approval working return to working
ordinary PostToolUse working quiet activity heartbeat
Stop tool_done green finish flash, then idle
SessionEnd idle release session ownership

Tool-use events are intentionally not used for separate tool-running transitions. Ordinary PostToolUse events still refresh working so a premature Stop event or long-running turn does not leave the lamp idle while Codex is active.

When multiple Codex chats are open, the hook uses an active-session lock:

  • the latest prompt claims the lamp
  • other sessions cannot force idle while that session is active
  • permission state cannot be overwritten by another session
  • a new prompt can claim the lamp immediately
  • SessionEnd or lock expiry releases abandoned ownership

Codex currently does not expose a distinct “user clicked approve” hook. This project treats the matching PostToolUse after a permission request as the approval-resolved signal.

Night Mode

Night mode can turn the lamp off during a local quiet window while still allowing Codex activity to wake it back up. It only applies when the daemon believes the lamp is idle or ambient; working, attention, and other active states continue to show normally.

{
  "night_mode_enabled": true,
  "night_mode_off_time": "23:00",
  "night_mode_on_time": "07:00",
  "night_mode_state": "off"
}

When the clock is inside the configured window, idle or ambient states are replaced with night_mode_state. A new Codex prompt, permission request, or tool heartbeat still applies its normal state and turns the lamp back on. At the end of the quiet window, the daemon restores the idle or ambient state that night mode replaced.

Install

git clone https://github.com/ulanenko/codex-moonside-lamp.git
cd codex-moonside-lamp
scripts/setup-local
source .venv/bin/activate

Create the default config:

python install_hooks.py --write-config

This writes ~/.codex-moonside-lamp/config.json if missing.

BLE Setup

Scan nearby BLE devices:

codex-moonside-daemon --scan

Likely Moonside devices are marked with *. On macOS, BLE addresses are usually UUID-like identifiers rather than stable MAC addresses.

If multiple Moonside lamps are nearby, copy the address from --scan into:

{
  "ble_address": "YOUR-SCANNED-ADDRESS"
}

Test the lamp:

codex-moonside-daemon --test-state attention

Raw command testing is useful for tuning effects:

codex-moonside-daemon --raw-command LEDON --raw-command BRIGH070 --raw-command 'THEME.GRADIENT1.180,0,255,40,0,120'

Codex Hooks

Enable Codex hooks in ~/.codex/config.toml:

[features]
hooks = true

Older Codex builds used codex_hooks = true; current desktop builds warn that this name is deprecated.

Generate hook JSON using the absolute path to your local hook executable:

python install_hooks.py --print-hooks

Install global Codex hooks if you do not already have a custom ~/.codex/hooks.json. --trust-hooks also writes the hooks.state trust hashes required by newer Codex builds:

python install_hooks.py --write-hooks --trust-hooks

Use --force only if you want to overwrite the existing hooks file:

python install_hooks.py --write-hooks --trust-hooks --force

If you edit ~/.codex/hooks.json later, rerun:

python install_hooks.py --trust-hooks

Codex only runs hook command configs whose current trusted hash is recorded in ~/.codex/config.toml.

Restart Codex after changing hook configuration.

Doctor

Run the setup doctor when hooks do not fire, after a Codex update, or before filing an issue:

codex-moonside-doctor

It checks:

  • Codex binary and version lookup
  • ~/.codex/config.toml hook feature flags
  • ~/.codex/hooks.json expected events
  • newer Codex hooks.state trusted hashes
  • hook executable paths
  • a safe direct hook smoke test using a temporary state file
  • macOS LaunchAgent status
  • recent daemon and hook logs

If hook trust hashes are missing or stale, apply the safe fix:

codex-moonside-doctor --fix

This updates only the hook trust entries in ~/.codex/config.toml.

For the strongest check, run a real tiny Codex CLI turn:

codex-moonside-doctor --live-codex-test

That can spend a small number of tokens and briefly change the lamp, so it is opt-in.

Run

Foreground daemon:

codex-moonside-daemon

Logs:

~/.codex-moonside-lamp/daemon.log
~/.codex-moonside-lamp/hook.log

macOS Autostart

Install a user LaunchAgent so the daemon starts when you log in:

scripts/install-macos-service

The installer copies runtime code to ~/.codex-moonside-lamp/runtime because macOS may block LaunchAgents from executing code inside protected folders such as Documents.

Check service status:

launchctl print gui/$(id -u)/local.codex-moonside-lamp
tail -f ~/.codex-moonside-lamp/daemon.log

Uninstall the LaunchAgent:

scripts/uninstall-macos-service

If daemon code changes after installing the LaunchAgent, rerun:

scripts/install-macos-service

macOS Menu Bar App

The macOS menu bar app is a local wrapper around the Python daemon. It does not talk to BLE directly. It reads the existing config, state, and log files, then calls the same local scripts and CLI commands you would run from Terminal.

Build, install, launch, and start the local unsigned app at login:

scripts/install-macos-menu-app

Remove it with:

scripts/uninstall-macos-menu-app

The menu shows daemon status, current lamp state, configured lamp target, and quick actions for testing attention, testing ambient, turning the lamp off, scanning BLE devices, restarting the daemon, and opening logs/config.

Current limitations:

  • macOS only
  • local unsigned app
  • expects the project checkout and .venv to remain available
  • generated .app bundles are not committed to Git

Config

Default config path:

~/.codex-moonside-lamp/config.json

Important fields:

{
  "state_file": "/tmp/codex_moonside_state.json",
  "permission_file": "/tmp/codex_moonside_permission_pending.json",
  "session_lock_file": "/tmp/codex_moonside_active_session.json",
  "session_lock_enabled": true,
  "session_lock_max_age_seconds": 43200,
  "permission_marker_max_age_seconds": 43200,
  "lamp_name_contains": "Moonside",
  "ble_address": null,
  "poll_interval_seconds": 0.2,
  "reconnect_interval_seconds": 3,
  "command_delay_seconds": 0.25,
  "write_response": true,
  "skip_redundant_commands": true,
  "minimum_attention_seconds": 1.0,
  "ambient_after_idle_seconds": 1800,
  "ambient_state": "ambient",
  "codex_process_tracking_enabled": false,
  "codex_process_names": ["ChatGPT", "Codex"],
  "codex_process_poll_interval_seconds": 15,
  "codex_process_timeout_seconds": 5,
  "codex_process_warning_interval_seconds": 300,
  "codex_process_missing_seconds": 60,
  "codex_process_missing_state": "ambient",
  "codex_process_return_state": "idle",
  "log_max_bytes": 2097152,
  "log_backup_count": 3
}

Optional macOS process tracking lets the daemon switch the lamp to ambient, or to any other configured state, when the Codex desktop app has been closed for a while:

{
  "codex_process_tracking_enabled": true,
  "codex_process_missing_seconds": 60,
  "codex_process_missing_state": "ambient",
  "codex_process_return_state": "idle"
}

This watches the current ChatGPT.app name and the legacy Codex.app name with a lightweight exact pgrep check, so it does not require the menu bar app. Process checks run outside the BLE event loop and warnings are rate-limited. Leave tracking disabled if you mainly use Codex CLI without the desktop app.

Default attention effect:

{
  "attention": {
    "commands": ["LEDON", "BRIGH070", "THEME.GRADIENT1.180,0,255,40,0,120"]
  }
}

Default ambient effect after 30 minutes idle:

{
  "ambient": {
    "commands": ["LEDON", "BRIGH070", "THEME.RAINBOW3.0"]
  }
}

Moonside command strings are reverse-engineered and empirical. If a theme does not work, switch that state to simple commands such as LEDON, BRIGH060, and COLOR255120040.

Privacy

This project is local-only:

  • no Moonside cloud API
  • no external server
  • no prompt text in state files
  • no command output in state files
  • no file contents in state files

The hook stores only minimal state metadata such as event name, session id, tool name, working directory, and timestamp.

Troubleshooting

If scanning prints Bluetooth is not available to Bleak, verify:

  • Bluetooth is enabled
  • the terminal/app running Python has Bluetooth permission in macOS Privacy & Security
  • you are running on the host macOS session, not a remote/containerized shell

If the LaunchAgent is loaded but the lamp does not connect, check:

tail -f ~/.codex-moonside-lamp/launchd.stderr.log
tail -f ~/.codex-moonside-lamp/daemon.log

Only one daemon should own the lamp BLE connection. Stop any foreground codex-moonside-daemon before relying on the LaunchAgent.

Development

python3 -B -m unittest discover -s tests
python3 -B -m py_compile codex_moonside/*.py install_hooks.py
codex-moonside-doctor --skip-direct-hook-test

Prior Art

  • bobek-balinek/claude-lamp: Claude Code status integration for Moonside lamps.
  • TheGreyDiamond Moonside reverse-engineering notes and BLE connector examples.
  • Codex hooks documentation and the hooks feature flag.

License

MIT

About

Codex status indicator for Moonside Halo lamps over local BLE

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages