VibeMon

Real-time AI Status Monitor

Display your AI coding assistant's state on ESP32 devices, Desktop App, or cloud service. See when Claude, Codex, Kiro, OpenClaw, or opencode is thinking, working, or done.

Go to VibeMon.io
💻
Install the VibeMon App

Recommended — desktop viewer that also sets up your AI tools for you

$ brew tap opspresso/tap
$ brew install opspresso/tap/vibemon

Install with Homebrew, or download the .dmg from the latest release.

Then open Settings > AI Tools to connect Claude Code, Codex, Kiro, OpenClaw, or opencode with one click.

🤖
I'm an AI Agent

I want to connect to VibeMon and send my status

Tell your AI agent:

Read https://docs.vibemon.io/setup.md and follow the instructions to join VibeMon

Installation

VibeMon App (Recommended)

Install the desktop app, then let it configure everything else for you — no separate script needed.

brew tap opspresso/tap
brew install opspresso/tap/vibemon

Or via npm: npx vibemon@latest

Open the app, go to Settings > AI Tools, and click Install for Claude Code, Codex CLI, Kiro IDE, OpenClaw, or opencode.

The installer and Desktop App honor CLAUDE_CONFIG_DIR, CODEX_HOME, KIRO_HOME, and OPENCODE_CONFIG_DIR (default $XDG_CONFIG_HOME/opencode, falling back to ~/.config/opencode). Hook commands are rewritten to the resolved directory, and Kiro is detected through either kiro or kiro-cli.

Non-interactive Install (AI agents, CI)

For headless setups where a GUI app isn't available:

# Choose exactly one platform + your token
curl -fsSL https://docs.vibemon.io/install.py | python3 - --claude --token my_token
# --codex / --kiro / --openclaw / --opencode for other tools, --all for every detected tool

Interactive prompt version:

curl -fsSL https://docs.vibemon.io/install.py | python3

Or point your AI agent at docs/setup.md (https://docs.vibemon.io/setup.md) and have it follow the instructions directly.

Local Install

git clone https://github.com/opspresso/vibemon-docs.git
cd vibemon-docs
python3 docs/install.py

Configuration

After installation, edit ~/.vibemon/config.json to configure your targets:

{
  "debug": false,
  "cache_path": "~/.vibemon/cache/projects.json",
  "auto_launch": true,
  "http_urls": [],
  "serial_port": null,
  "vibemon_token": "",
  "vibemon_url": "https://vibemon.io"
}
FieldDescriptionExample
debugEnable debug loggingtrue
cache_pathCache file path for project metadata~/.vibemon/cache/projects.json
auto_launchAuto-launch Desktop App on session starttrue
http_urlsHTTP targets (Desktop App, ESP32 WiFi)["http://127.0.0.1:19280"]
serial_portESP32 USB serial port (wildcard supported)/dev/cu.usbmodem*
vibemon_urlVibeMon cloud API URLhttps://vibemon.io
vibemon_tokenVibeMon API access token (from dashboard)

Claude Code's statusline reads a separate ~/.vibemon/statusline.json for display toggles (e.g. show_cost, show_git, show_model, show_tokens) and the fallback token_reset_hours setting — see statusline.example.json for the full set of defaults. This file is optional; statusline.py falls back to sensible defaults (and to any matching keys still in config.json) when it's absent.

The Claude Code installer also places a standalone refresher at ~/.vibemon/usage.py. For Claude, it fetches plan usage directly from Anthropic's OAuth usage API using the local Claude Code login token (no active session required), falling back to a claude -p "/usage" subprocess when a token isn't available or the API call fails. For Codex, it queries the same account-level usage API Codex CLI's own /status polls using the local Codex login token, falling back to the newest local session log. Either way it writes the shared ~/.vibemon/cache/usage.json, so the Desktop app can run it (python3 ~/.vibemon/usage.py --max-age 600) on startup or on a schedule to keep usage data fresh even when no Claude Code or Codex session is active. Since the claude -p "/usage" fallback is itself a real Claude Code session, the Desktop app sets VIBEMON_SUPPRESS_HOOKS=1 in its environment so the spawned session's own hooks don't report status back. Independently of that env var, the hooks also skip any session whose cwd is ~/.vibemon itself, so spawners that don't set the variable (older Desktop app versions, manual usage.py runs) can't surface a phantom .vibemon project either.

The reset-countdown fields the hooks attach (usage5hResetsIn/usageWeekResetsIn/usageWeekModelResetsIn) are populated whenever the cache was refreshed via a resets_at epoch — either an active Claude Code session's statusline (the official rate_limits path), usage.py's direct Anthropic/Codex API queries, or a Codex session log. Only the last-resort claude -p "/usage" text fallback lacks a machine-parseable reset time, so the reset countdown is omitted in that case while the usage percentages still update.

Note that the plan-usage fields (usage5h/usageWeek/usageWeekModel and their reset countdowns) are sent by the Claude Code and Codex hooks, since they both read from the same usage.py-refreshed cache (under separate claude/codex cache keys). The Kiro and opencode hooks don't report usage, and OpenClaw reports context-window usage as memory instead.

Codex Configuration

Codex uses the same ~/.vibemon/config.json as Claude Code, Kiro, the OpenClaw plugin, and the opencode plugin. Hooks are enabled by default; the installer preserves an explicit [features].hooks = false in ~/.codex/config.toml. Merge codex/hooks.json into your existing ~/.codex/hooks.json (do not overwrite), then open /hooks and review/trust the new definitions.

Kiro Configuration

Kiro IDE 1.x and CLI 3.x discover the global v1 hook at ~/.kiro/hooks/vibemon.json. It applies to every local project without selecting a custom agent. The installer removes only VibeMon's legacy agent hooks and .kiro.hook files during migration.

OpenClaw Configuration

The OpenClaw plugin reads transmission settings (http_urls, serial_port, vibemon_url, vibemon_token) from the same ~/.vibemon/config.json as the other tools. It only needs to be registered and enabled in ~/.openclaw/openclaw.json — OpenClaw doesn't auto-discover extension directories, so the plugin path must also be registered under plugins.load.paths or the manifest/entries config alone won't load it:

{
  "plugins": {
    "load": {
      "paths": ["~/.openclaw/extensions/vibemon-bridge"]
    },
    "entries": {
      "vibemon-bridge": {
        "enabled": true,
        "hooks": { "allowConversationAccess": true }
      }
    }
  }
}

To override the shared settings for OpenClaw only, add a config object to the entry (projectName, character, httpEnabled, httpUrls, serialEnabled, vibemonUrl, vibemonToken, autoLaunch, debug) — plugin config always wins over ~/.vibemon/config.json.

After installing or updating the plugin, rebuild OpenClaw's persisted plugin registry and restart the gateway (openclaw plugins registry --refresh && openclaw gateway restart) — the gateway boots from a registry snapshot and won't pick up the plugin's hooks otherwise. The installer runs the refresh automatically when the openclaw CLI is available.

opencode Configuration

The opencode plugin reads transmission settings (http_urls, serial_port, vibemon_url, vibemon_token) from the same ~/.vibemon/config.json as the other tools. opencode has no Claude Code-style hooks, so the plugin at ~/.config/opencode/plugins/vibemon.js bridges opencode events to VibeMon's hook pipeline: it spawns the adapter at ~/.config/opencode/hooks/vibemon.py, which feeds vibemon_core.py. opencode auto-discovers plugins in ~/.config/opencode/plugins/ at startup, so no config registration is needed — install the plugin file and restart opencode.

The installer honors an OPENCODE_CONFIG_DIR override (default $XDG_CONFIG_HOME/opencode, falling back to ~/.config/opencode); on Windows it also pins the plugin's interpreter to the Python that ran the installer, since python3 isn't on PATH there.

CLI Commands

The hook script supports these commands:

# Lock monitor to current project
python3 ~/.claude/hooks/vibemon.py --lock [project_name]

# Unlock monitor
python3 ~/.claude/hooks/vibemon.py --unlock

# Get current status
python3 ~/.claude/hooks/vibemon.py --status

# Get/set lock mode (first-project, on-thinking)
python3 ~/.claude/hooks/vibemon.py --lock-mode [mode]

# Reboot ESP32 device
python3 ~/.claude/hooks/vibemon.py --reboot

Apps

Desktop App

Electron app with system tray for macOS, Windows, Linux. See Installation above to install.

Token can be configured via the system tray menu.

It shows a single character window with a speech bubble that follows it. The window retargets to whichever project is currently active instead of opening one window per project.

Features: frameless floating window, always on top, system tray integration, snap to screen corners, click to focus terminal (macOS).

ESP32 Hardware

Dedicated LCD display (172×320, ST7789V2). Hardware: ESP32-C6-LCD-1.47 board, USB-C cable.

Required libraries: LovyanGFX (lovyan03), ArduinoJson (Benoit Blanchon), WebSockets (Markus Sattler, for WebSocket mode).

Arduino IDE setup: add the ESP32 Board Manager URL below, install the ESP32 board and required libraries, select the ESP32C6 Dev Module, then upload.

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

WiFi configuration (credentials.h):

#define USE_WIFI
#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSWORD "YOUR_PASSWORD"

Optional WebSocket mode:

#define USE_WEBSOCKET
#define WS_HOST "ws.vibemon.io"
#define WS_PORT 443
#define WS_PATH "/"
#define WS_USE_SSL true
#define WS_TOKEN "your-access-token"

For SSL, change the Partition Scheme to "Huge APP (3MB No OTA/1MB SPIFFS)".

Testing via serial:

# macOS
echo '{"state":"working","tool":"Bash","project":"my-project"}' > /dev/cu.usbmodem1101

# Linux (set baud rate first)
stty -F /dev/ttyACM0 115200
echo '{"state":"working","tool":"Bash","project":"my-project"}' > /dev/ttyACM0

API

WebSocket

wss://ws.vibemon.io?token=your-access-token

Message types:

// Status update
{
  "type": "status",
  "data": {
    "state": "working",
    "tool": "Bash",
    "project": "my-project",
    "model": "opus",
    "memory": 45,
    "character": "clawd",
    "usage5h": 36,
    "usageWeek": 37,
    "usage5hResetsIn": 154,
    "usageWeekResetsIn": 4381,
    "createdAt": "2026-01-01T00:00:00.000Z",
    "updatedAt": "2026-01-01T00:00:00.000Z"
  }
}

// Project deleted
{
  "type": "delete",
  "data": { "project": "my-project" }
}

HTTP API

curl -X POST https://vibemon.io/api/status \
  -H "Authorization: Bearer your-token" \
  -H "Content-Type: application/json" \
  -d '{
    "state": "working",
    "project": "my-project",
    "character": "clawd",
    "tool": "Bash",
    "model": "opus",
    "memory": 45
  }'
FieldTypeDescription
statestringstart, idle, thinking, planning, working, packing, notification, done, sleep, alert (required)
projectstringProject name (required)
characterstringvibemon, clawd, codex, kiro, claw, opencode, or daangni (required; an unrecognized value falls back to vibemon rather than being rejected). daangni is manual selection only (no tool maps to it)
toolstringTool name (Bash, Read, Edit, etc.) (optional)
modelstringModel name (opus, sonnet, etc.) (optional)
memorynumberContext window usage 0-100 (optional)
usage5h / usageWeeknumberPlan-usage percentage 0-100 (optional; Claude Code and Codex hooks only — see above)
usage5hResetsIn / usageWeekResetsInnumberMinutes until the usage window resets (optional; see above)
usageWeekModelnumberModel-scoped weekly plan-usage percentage 0-100, e.g. the Fable weekly limit (optional)
usageWeekModelResetsInnumberMinutes until the model-scoped weekly window resets (optional)
usageWeekModelLabelstringDisplay label of the scoped model, e.g. "Fable" (optional)
# Delete agent status
curl -X DELETE "https://vibemon.io/api/status?project=my-project" \
  -H "Authorization: Bearer your-token"

# Aggregated metrics
curl "https://vibemon.io/api/metrics?granularity=HOUR&range=24h" \
  -H "Authorization: Bearer your-token"

Token format: a-z, 0-9, _, -, 8-64 characters (e.g. my_token_123).

State Mapping

State reporting is edge-driven: a new state start replaces the previous state. Completion hooks are retained only when no later start reliably restores the state (PostToolUse/PostCompact) or an explicit ending must be reported (Stop/SessionEnd). Claude and Codex subagent Agent calls already pass through the tool hooks, so separate subagent hooks are omitted.

Claude Code

EventState
SessionStartstart
UserPromptSubmitthinking
PreToolUseworking
PostToolUsethinking
PostToolUseFailurethinking
PermissionDeniedthinking
PreCompactpacking
PostCompactthinking
Notificationnotification
PermissionRequestnotification
SessionEnddone
Stopdone
StopFailuredone

Plan Mode: when Claude Code is in plan mode, thinking and working states automatically become planning.

Codex CLI

EventState
SessionStartstart
UserPromptSubmitthinking
PreToolUseworking
PostToolUsethinking
PermissionRequestnotification
PreCompactpacking
PostCompactthinking
Stopdone
Interruptdone
SessionEnddone

SessionStart covers startup, resume, and clear. Matcher-free PreToolUse/PostToolUse hooks observe every supported local tool call. Informational hooks run in the background; SessionEnd and Interrupt run synchronously with a three-second timeout.

After installation, open /hooks and review/trust new or changed VibeMon hook definitions.

Kiro IDE

EventState
SessionStartstart
UserPromptSubmitthinking
PreToolUseworking
PostToolUsethinking
Stopdone

OpenClaw

EventState
gateway_startstart
before_agent_run (fallback: before_agent_start)thinking
before_tool_callworking
after_tool_callthinking (working while another tool runs)
before_compaction / after_compactionpacking / thinking
subagent_spawnedworking
agent_end (success or failure)done (3s delay, after all runs finish)
message_sentdone fallback (only with no active run)
session_end / gateway_stopdone

opencode

EventState
SessionStartstart
UserPromptSubmitthinking
PreToolUseworking
PostToolUsethinking
PermissionRequestnotification
PreCompactpacking
PostCompactthinking
Stopdone
SessionEnddone

opencode has no Claude Code-style hooks, so the plugin bridges its events to these hook events:

  • session.created → SessionStart
  • chat.message → UserPromptSubmit
  • tool.execute.before → PreToolUse
  • tool.execute.after → PostToolUse
  • permission.asked (bus event; legacy permission.ask hook) → PermissionRequest
  • experimental.session.compacting → PreCompact
  • session.compacted → PostCompact
  • session.status (busy/retry) → UserPromptSubmit
  • session.idle, session.status (idle), or session.error → Stop
  • session.deleted → SessionEnd