Pixel Ops is an event-driven runtime for ambient pixel-art worlds. Integrations translate outside activity into neutral events, a selected game engine owns its projections and visual rules, and output drivers deliver frames to files, windows, or hardware. Pokemon is the default game engine, not the platform model; Spaceship is the first independent persistent world.
The display is intentionally ambient. Meetings become encounters, pull requests become world events, Discord voice state becomes map companions, and operational pressure becomes mood instead of a notification wall.
python pixel_ops/main.py --plugin pokemon --output preview
python pixel_ops/main.py --plugin spaceship --output preview
python pixel_ops/main.py --plugin pokemon --output window --forever
python pixel_ops/main.py --plugin pokemon --output gif --seconds 8
python pixel_ops/main.py --plugin pokemon --output turzx --forever --fps 10 --offline
python pixel_ops/main.py --plugin pokemon --output thermalright --forever --fps 2 --offline
python pixel_ops/main.py --plugin pokemon --output lcd --forever --fps 10 --offlineWindow mode:
python pixel_ops/main.py --plugin pokemon --window --forever
python pixel_ops/main.py --plugin pokemon --window --window-scale 1 --forever
python pixel_ops/main.py --plugin pokemon --window --window-scale 3 --foreverLocal outputs:
pixel_ops/output/preview.png
pixel_ops/output/preview.gif
Platform setup and USB display notes:
Supported development/runtime targets:
- Linux:
preview,gif,window, and TURZX USB with libusb plus udev permissions. - Windows:
preview,gif,window, and TURZX USB with a WinUSB/libusb driver such as Zadig. - macOS: useful for development and preview/window output, but USB display support is not the primary target.
Run the platform check before testing hardware:
python scripts/linux_check.py
python scripts/windows_check.pyBoth checks validate Python dependencies, local system tools, PC stats availability, and expected TURZX USB setup. CI runs Linux and Windows workflows for Python tests, Config Studio build, and offline preview rendering.
Config Studio is the local React UI for editing runtime JSON config.
cd config-studio
npm install
npm run devThe dev server exposes local endpoints that read and write repository JSON files. The packaged Electron app starts the same local server against the bundled Pixel OPs runtime; on Windows the installer creates Start Menu and desktop shortcuts for Pixel OPs Config Studio.
Config Studio detects available visual plugins from pixel_ops/plugins/*/plugin.py, loads plugin-owned JSON only when the plugin is selected, and detects integrations from pixel_ops/integrations/*/plugin.py.
JSON is the primary runtime config format. Matching YAML files are fallback only.
Core config:
pixel_ops/config/display.json: display size, FPS, output target, AI decision config, splash, and HUD layout.pixel_ops/config/people.json: people and time zones.pixel_ops/config/integrations.json: integration enables and non-secret provider settings.
Pokemon plugin config:
pixel_ops/plugins/pokemon/game.json: scene timing, HUD/game layout, events, and Pokemon selection behavior.pixel_ops/plugins/pokemon/pokemon.json: PokeAPI, cache, sprite, and offline settings.pixel_ops/plugins/pokemon/companions.json: Pokemon-specific visual mapping for Discord companions.
Spaceship plugin config:
pixel_ops/plugins/spaceship/game.json: procedural layout seed, active-time progression, save cadence, palette, and event settings.pixel_ops/plugins/spaceship/assets/: local PixelLab-generated ship, crew, interior, and asteroid sprites.
Integration sidecars:
pixel_ops/config/discord_people.json: recent Discord users and nicknames observed from voice state.
Secrets stay in .env. Runtime toggles, guild IDs, repo lists, city names, sprite choices, and other UI-editable values stay in JSON.
Secret env vars currently used:
PIXEL_OPS_SLACK_APP_TOKEN=xapp-...
PIXEL_OPS_SLACK_BOT_TOKEN=xoxb-...
PIXEL_OPS_KITE_TOKEN=...
PIXEL_OPS_DISCORD_BOT_TOKEN=...
PIXEL_OPS_ZOOM_ACCOUNT_ID=...
PIXEL_OPS_ZOOM_CLIENT_ID=...
PIXEL_OPS_ZOOM_CLIENT_SECRET=...
PIXEL_OPS_GITHUB_TOKEN=github_pat_...
PIXEL_OPS_CLICKUP_TOKEN=pk_...
OPENAI_API_KEY=sk-...
OPENAI_ADMIN_KEY=sk-admin-...
OPENWEATHERMAP_API_KEY=...provider transport
-> integration event source
-> WorkEvent / PixelOpsEvent
-> selected GameEngine
-> game-owned projection and renderer
-> output driver
PixelOpsApp never queries provider-specific state or assembles a scene-shaped render call. It delivers events, emits runtime.tick, and requests a frame. The selected engine decides how calendar, GitHub, weather, social presence, tasks, local metrics, media, and AI usage affect its world.
Snapshot APIs are adapted into observation events. Important event types include calendar.today_updated, github.pull_requests_updated, weather.conditions_updated, system.metrics_updated, tasks.snapshot_updated, and social.companions_updated.
See ADR 0026 for the boundary and migration constraints.
Pixel OPs has three plugin-style boundaries:
- game engines consume events, own projections, and render a world;
- integration plugins collect outside activity and emit neutral events;
- AI decision plugins provide optional structured model decisions.
Game plugins live in pixel_ops/plugins/<name>/. They own a complete display experience, event projections, world rules, and rendering.
Games consume WorkEvent and PixelOpsEvent through the GameEngine contract. They must not make provider transport calls or receive provider source objects directly.
The visual plugin object contract is duck-typed by pixel_ops/main.py:
name: stable CLI/config key.display_name: human-readable name for UI tooling.add_arguments(parser): optional CLI flags.load_config(plugin_dir, load_config): load plugin-owned JSON config.maybe_handle_command(args, root_dir, config): handle one-shot commands.fps(config, display_fps): choose render FPS.event_config(config): expose event settings to the runtime.build_app(...): construct aGameEngineand return aPixelOpsAppwired only to event sources.
To create a visual plugin:
- Create
pixel_ops/plugins/<name>/plugin.py. - Add plugin JSON files under that directory.
- Implement the plugin class.
- Register it in
pixel_ops/plugins/registry.py. - Build game-owned projections from events instead of importing provider transports or sources.
- Add documentation and an ADR when changing runtime boundaries or event semantics.
Game-specific docs:
Integration plugins live in pixel_ops/integrations/<name>/plugin.py. They own provider setup, polling, sockets, local file reads, and normalization.
Integration plugins implement the protocol in pixel_ops/integration_plugins/base.py:
name: stable key underpixel_ops/config/integrations.json.enabled(ctx): decide whether the plugin should load.build(ctx): return anIntegrationContribution.
IntegrationContribution can provide only event and lifecycle boundaries:
event_sources;calendar_paths;starters;warmers;closers;
To create an integration plugin:
- Create
pixel_ops/integrations/<name>/plugin.py. - Add a class with
name,enabled(ctx), andbuild(ctx). - Return normalized contributions only.
- Add the module to
PLUGIN_MODULESand env fallback toPLUGIN_ENABLES. - Add non-secret config under
pixel_ops/config/integrations.json. - Keep secrets in
.envand reference them from JSON by env var name. - Normalize social/meeting activity through
AmbientSignalandWorkEvent.
Provider integrations must not render raw messages, provider payloads, or chat feeds.
AI decision plugins live under pixel_ops/plugins/ai/. They are not visual plugins.
The protocol in pixel_ops/plugins/ai/plugin.py is:
enabled: boolean.decide_json(request): return a JSON object matching the request schema, orNone.
Rules:
- calls are optional;
- successful decisions should be cached when practical;
- return
Noneon disabled config, missing keys, API errors, or invalid JSON; - keep provider-specific API code in the AI plugin, not visual scenes.
To add an AI provider, implement the protocol, extend build_ai_plugin(), add JSON config under display.ai, and document any new secret env vars.
All providers normalize into ambient state. Social/meeting providers first produce AmbientSignal, then WorkEvent. The common vocabulary lives in pixel_ops/events/ambient_signals.py.
Local ICS file:
python pixel_ops/main.py --ics path/to/calendar.icsICS config:
{
"integrations": {
"ics": {
"enabled": true,
"paths": ["/path/to/calendar.ics"],
"poll_seconds": 300
}
}
}Google Calendar via private ICS URL:
{
"integrations": {
"google_calendar": {
"enabled": true,
"ics_urls": ["https://calendar.google.com/calendar/ical/..."],
"poll_seconds": 300
}
}
}{
"integrations": {
"github": {
"enabled": true,
"token_env": "PIXEL_OPS_GITHUB_TOKEN",
"repos": ["owner/repo"],
"poll_seconds": 60,
"max_pull_requests": 4,
"fetch_deployments": true,
"deployment_workflows": []
}
}
}GitHub pull requests feed the compact HUD. When fetch_deployments is enabled, recent GitHub Actions workflow runs are normalized into ambient deploy/build events. Leave deployment_workflows empty to observe all workflows.
{
"integrations": {
"clickup": {
"enabled": true,
"token_env": "PIXEL_OPS_CLICKUP_TOKEN",
"team_id": "",
"assignee_id": "",
"poll_seconds": 120,
"max_tasks": 5,
"due_within_days": 14
}
}
}ClickUp tasks feed the optional tasks HUD window with assigned task names, due dates, and remaining time. Leave team_id and assignee_id empty to resolve the first authorized Workspace and current API user.
Capacities can feed the optional project_radar HUD with one active project, one project that needs to resurface, and compact Inbox/Review pressure counts. Create a custom Capacities object type named Projeto or Project with any of these properties (Portuguese and English names are both discovered):
Estado/StateorStatusÁrea/AreaPróxima ação/Next actionRevisitar em/Review dateÚltimo toque/Last touchedImportância/Importance
Create a read-only personal token under Capacities Settings → Capacities API and store it in .env:
PIXEL_OPS_CAPACITIES_TOKEN=cap-api-...Enable capacities in pixel_ops/config/integrations.json, then add a project_radar HUD window in Config Studio. The Capacities MCP connection used by AI clients is separate from this unattended runtime token.
{
"integrations": {
"weather": {
"enabled": true,
"provider": "open_meteo",
"city": "Porto Alegre",
"country_code": "BR",
"poll_seconds": 900,
"timeout_seconds": 8,
"api_key_env": "OPENWEATHERMAP_API_KEY"
}
}
}Supported providers:
open_meteo: default, no key.wttr_in: no key.openweathermap: requires the configured API key env var.
{
"integrations": {
"ai_usage": {
"enabled": true,
"providers": ["codex", "claude", "openai_api"],
"poll_seconds": 300,
"codex_home": "~/.codex",
"claude_projects_path": "~/.claude/projects",
"openai_admin_key_env": "OPENAI_ADMIN_KEY",
"thresholds": [75, 90]
}
}
}Supported sources:
- Codex local JSONL sessions under
CODEX_HOMEor~/.codex. - Claude local JSONL project logs under
~/.claude/projects. - OpenAI Admin API usage/cost endpoints via
OPENAI_ADMIN_KEY.
Pixel OPs renders this as ambient gauges and threshold events, not billing tables or token logs.
{
"integrations": {
"slack": {
"enabled": true,
"app_token_env": "PIXEL_OPS_SLACK_APP_TOKEN",
"bot_token_env": "PIXEL_OPS_SLACK_BOT_TOKEN",
"bot_user_id": "U123456",
"socket_reconnect_seconds": 10
}
}
}Slack uses Socket Mode. Enable Socket Mode in the Slack app, create an app-level token with connections:write, and install the bot with event scopes for mentions, messages, reactions, and presence-like activity.
{
"integrations": {
"discord": {
"enabled": true,
"bot_token_env": "PIXEL_OPS_DISCORD_BOT_TOKEN",
"guild_id": "1133891225209024633",
"focus_user_id": "242829666488942593",
"max_companions": 30,
"gateway_reconnect_seconds": 10
}
}
}Discord runs a local Gateway client. It tracks voice state for one guild, emits provider-neutral voice activity events on voice channel joins/switches, and exposes a current voice snapshot for visual plugins. Recent Discord identities are stored in discord_people.json; Pokemon sprite choices are stored separately in pixel_ops/plugins/pokemon/companions.json.
Download/cache PokeAPI metadata plus front and animated sprites:
python pixel_ops/main.py --plugin pokemon --warm-cache
python pixel_ops/main.py --plugin pokemon --warm-cache --pokemon-limit 25Run without network access:
python pixel_ops/main.py --plugin pokemon --output preview --offlineDisplay transport is isolated in pixel_ops/hardware/. The current reference device is a TURZX/Turing Smart Screen-style 3.5-inch USB display:
- Size: 3.5-inch portrait display.
- Resolution: 320x480.
- Protocol: Rev. A-compatible USB bulk transport (
1a86:5722). - Backend:
usb_bulk_rev_a.
Purchase links:
- Brazil: TURZX 3.5-inch secondary USB monitor on Mercado Livre
- United States: TURZX 3.5-inch USB monitor search on Amazon
Listings change frequently. Match a 3.5-inch TURZX/Turing Smart Screen/USB monitor with 320x480 resolution before buying.
Architecture decisions live in docs/adr. Add or update an ADR when changing provider/plugin boundaries, config ownership, event semantics, AI policy, hot reload behavior, or renderer product principles.
- CodexBar, by Peter Steinberger, inspired the provider-normalized AI usage gauges and local/API usage tracking approach used by the
ai_usageintegration. - Pixel OPs was originally built from a local fork of
mathoudebine/turing-smart-screen-python. The upstream project provided the original USB display protocol research and Python driver foundation for Turing Smart Screen style devices. The current repository keeps a minimal adapted USB bulk transport and RGB565 serializer.