- Vibe coding Abilities with Claude — terminal-based, CLI-driven, Python on the OpenHome sandbox
- Vibe coding dashboards with the Replit Agent — browser-based, Flask + single-file HTML
The rule of two tabs
When building, keep two browser tabs open side by side. You need to see both sides of the pipe at once.- Tab 1: OpenHome Live Editor → Ability logs
- Tab 2: Your terminal (Claude + CLI) or Replit (server logs)
Vibe coding Abilities with Claude
The OpenHome CLI was designed so an AI coding agent — Claude, Cursor, anything that supports tool use — can drive the build loop end-to-end.Setup
Install the CLI
Point Claude at the project
In your terminal, launch Claude Code (or your preferred agent) in the directory where your Ability lives. Claude has full tool-use access to the CLI.
Give Claude context
Share the SDK Reference and Simple Abilities Cookbook up front. These two documents are the entire operating context for Ability development.
The context you must give Claude
Before asking Claude to write any Ability code, paste or reference these four things:- SDK Reference — every method, every sandbox rule, every prompt pattern
- Simple Abilities Cookbook — the 100+ examples so Claude understands the Ability shape
- Voice-First Best Practices — the UX rules that distinguish a demo from a product
- What you want to build — one paragraph, plain English, with concrete triggers and behaviors
Scaffolding prompt for a new Ability
Copy and adapt:Two-shot development
The worst failure of AI-assisted coding is generating a plausible implementation of the wrong design. Two-shot it:Shot 1 — architecture in prose
Before any code: “Walk me through the architecture. What files, what loops, what prompts, what storage patterns? Flag any tradeoffs.”
Sandbox rules to pre-brief
Claude doesn’t know OpenHome’s sandbox until you tell it. The ones it gets wrong most often:- No
asyncio.create_task— useself.worker.session_tasks.create() - No
asyncio.sleep— useself.worker.session_tasks.sleep() - No
print()— useself.worker.editor_logging_handler.info() / .error() - No raw
open()— use the file storage API (write_file,read_file,check_if_file_exists) - No top-level
import os,import signal,import jsonoutside the register block #{{register capability}}is a literal comment tag, not a function call- Every
main.pyexit path must callresume_normal_flow()— even exception handlers and timeouts
Log-driven iteration
When something breaks, paste the live Live Editor logs directly into Claude and ask it to diagnose.- Don’t summarize
- Don’t filter
- Don’t try to identify the problem yourself first
Deploy and test in a loop
The CLI’s superpower is that Claude can build → deploy → test without you clicking anything:Vibe coding dashboards with the Replit Agent
Replit’s Agent is good, but only as good as the brief you give it. Be explicit about three things: what is POSTing in, what the frontend will poll, and that state lives in memory.Master prompt for the backend
Copy this, adapt the<ability> placeholder, paste it into the Replit Agent:
Frontend-specific prompt
Run this as a second prompt after the backend is working. Mixing backend and frontend in one prompt produces muddled output.OpenHome Live Editor patterns
The Live Editor is where most of your Ability development happens. A few patterns will save you hours.Prompts at the top of the file
Every configurable LLM prompt, every URL, every constant belongs at the top ofmain.py as a named constant. When you want to fork an Ability for a new persona, you change the constants — not the logic. This rule is absolute.
Use the register-capability tag, not a function call
Inside yourMatchingCapability class, use the literal comment tag #{{register capability}} — not register_capability(). The platform rewrites the tag at load time. Writing the function call manually throws sandbox errors.
Log generously in dev, sparely in production
During development, log every cycle, every POST, every API response.editor_logging_handler is your only window into the Ability’s behavior.
Before shipping to the Marketplace, trim log lines to the bare minimum — one line per cycle, plus errors. A silent Ability in production is a good Ability.
Shared patterns and guardrails
These apply whether you’re vibe coding the Ability or the dashboard.Forbidden imports and patterns
| Don’t | Use instead |
|---|---|
import os, import signal, raw open() | Platform helpers — play_from_audio_file(), file storage API |
import redis, RedisHandler, connection_manager | (direct infra access is blocked) |
print() | self.worker.editor_logging_handler.info() / .error() |
asyncio.sleep, asyncio.create_task | session_tasks.sleep(), session_tasks.create() |
The session_tasks pattern
All background work — heartbeats, parallel LLM calls, dashboard POSTs — goes through self.worker.session_tasks.create(coro). This guarantees clean shutdown when the session ends.
The two feedback loops
| Layer | Feedback mechanism |
|---|---|
| Ability code in the Live Editor | editor_logging_handler.info() — visible live in the Editor’s log pane |
| Dashboard POSTs | Replit’s server logs — every request dumped to console |
| OpenHome CLI | Local terminal output + openhome-cli log lines |
Never send raw audio to a dashboard
Keep audio local to the Ability. Send transcripts, summaries, tone descriptors, metadata — never PCM or WAV bytes.- 1 minute of 16kHz 16-bit audio = 2 megabytes
- 1 minute of transcript = 2 kilobytes
See also
- OpenHome CLI — terminal-based Ability development
- SDK Reference — methods, prompts, patterns, sandbox rules
- Simple Abilities Cookbook — 100+ copy-remix-ship ideas
- Build a Companion Dashboard — architecture + snippets for the dashboard side
- Voice-First Best Practices — the UX rules LLMs forget

