Python client + CLI for building, managing and voice-calling OpenHome agents (app.openhome.com).
Project description
OpenHome Client + CLI
Link this abilities repo with your app.openhome.com account and drive the same actions the dashboard live editor does — from code or the terminal:
- Create an ability from a template (uses this repo's
templates/+official/) - Save / commit an ability to your account (zips the folder, uploads it)
- Auto-install it into an agent's voice call flow (pass an agent id on save)
- Set trigger words (persisted in the OpenHome database)
- Voice-to-voice call an agent directly, no dashboard needed
- Run a Local Link bridge so an agent can execute requests on your own machine
It is a thin CLI over a reusable Python library (openhome).
Install
Run these from the abilities repo root:
python3 -m venv cli/.venv && source cli/.venv/bin/activate
pip install -e cli
cp .env.example .env # at the repo ROOT — then fill in OPENHOME_API_KEY
The openhome command then works from any directory (the repo root is the
natural place to run it). .env is discovered from the repo root automatically,
so you don't need to cd cli. New/synced abilities land in user/ at the root.
For the voice call (openhome call) you also need the mpv player and PortAudio:
# macOS
brew install mpv portaudio
# Linux
sudo apt install mpv portaudio19-dev
Authentication
Put your API key in .env (or run openhome login):
OPENHOME_API_KEY=... # from app.openhome.com → Settings → API Keys
OPENHOME_JWT=... # optional — only for a real browser session
The API key alone is enough for everything — create, push, list, sync, delete, and the voice call. The client sends it the way each endpoint expects:
/api/sdk/*(agents, verify) → api_key in the JSON body- everything else →
X-API-KEY: <api_key>header
OPENHOME_JWT is optional: if set, the capability endpoints use it as a
Bearer token (a browser session); if empty, they use X-API-KEY. Either works.
The api_key is never sent as a Bearer token (the server runs Bearer through
SimpleJWT and would reject it).
CLI — full command reference
Setup / account
openhome login --api-key <KEY> --jwt <JWT> # verify + save credentials to ~/.openhome
openhome agents # list your agents (id + name)
openhome templates # list templates you can scaffold from
openhome list # list abilities on your account
Create a new ability (auto-pushes to your account)
# interactive — prompts for trigger words + description:
openhome create myskill -t basic-template
# non-interactive:
openhome create myskill -t basic-template \
--triggers "run my skill, my skill" \
--description "what it does" \
--agent <AGENT_ID> # optional: install into an agent (omit = global)
openhome create myskill -t basic-template --no-push # scaffold only, don't push
Scaffolds user/myskill/, then saves it to your account. --name is derived
alphanumeric (hyphens stripped — the backend requires it).
Edit, then push (updates in place — same id, never delete+recreate)
# …edit user/myskill/main.py…
openhome push user/myskill # save a DRAFT update
openhome push user/myskill --commit -m "v2 fix" # COMMIT a version (v1 → v2)
Trigger words / enable / disable
openhome set-triggers myskill "weather, forecast"
openhome enable myskill
openhome disable myskill
Voice (no dashboard)
openhome call # 🎙 real voice call to the DEFAULT agent (0) — mic in, speaker out
openhome call 238371 # voice call a specific agent
openhome call --say "run my skill" # one-shot TEXT trigger to default agent, prints the reply
openhome call 238371 --say "hi" # one-shot text to a specific agent
openhome chat 238371 # interactive TEXT chat (type, /quit to exit)
The real voice call streams your mic to the agent and plays its reply through mpv.
Speak after the greeting; Ctrl-C hangs up. Agent resolution: arg → OPENHOME_AGENT_ID → 0.
It runs half-duplex — the mic is muted while the bot is speaking, because a
laptop has no hardware echo cancellation (an open mic would capture the speaker
audio and either falsely "interrupt" the bot or get its words transcribed back as
your input). bot-speak-end is sent only once mpv's IPC reports playback has
actually drained, so the mic re-opens at the right moment. (Hardware with echo
cancellation — e.g. a DevKit — can run full-duplex with mic barge-in.)
Interrupt with SPACE: press the spacebar while the bot is talking to cut it off
— this stops mpv instantly, sends interrupt-event + bot-speak-end, and discards
any remaining audio chunks until the next response begins. Server logs stream live,
colored by level (coloredlogs).
Local Link (run requests on your own machine)
openhome local runs a small bridge on your computer that stays connected to your
OpenHome agent. When the agent needs something done locally, it sends the request to
the bridge, the bridge runs it, and the reply goes back to the agent — so a voice
agent can reach your machine without the cloud sandbox. Each request is routed to
whichever local agent is available:
- local-link — a raw shell executor (first-class on macOS/Linux, best-effort on Windows). Always available.
- hermes — used when Hermes is installed and configured.
- openclaw — used when OpenClaw is installed and its gateway is running.
The bridge detects which of these are ready and tells the agent. Anything installed but not yet usable comes back with a short hint on how to enable it (e.g. start the OpenClaw gateway).
openhome local start # start the bridge in the background
openhome local status # is it running?
openhome local logs # stream requests and responses live (Ctrl-C to stop)
openhome local stop # stop it
openhome local run # run in the foreground for debugging (Ctrl-C to quit)
Options for start / run:
| Flag | Default | Meaning |
|---|---|---|
--client-id |
laptop |
a name for this device |
--role |
agent |
connection role |
--timeout |
30 |
per-request timeout, in seconds |
openhome local logs shows recent history and then live-tails; add --no-follow to
print and exit, or -n/--lines N to change how much history it shows first. The
background bridge reconnects on its own if the connection drops; openhome local run --once connects a single time without reconnecting (handy while debugging).
Sync (account → local) and delete
openhome sync # download code + effective triggers into user/ (keeps local edits)
openhome sync --force # overwrite local code with the account version
openhome sync --prune # also delete local folders for abilities removed on the account
openhome delete myskill # remove from the account AND delete the local user/ folder
openhome delete myskill --keep-local # remove from the account only
Contribute an ability to the community
openhome push_to_community myskill # copy user/myskill → community/myskill
openhome push_to_community myskill --overwrite # replace an existing community/ copy
Copies your finished ability from user/ into the repo's community/ folder
(stripping the personal .openhome.json manifest + build junk), runs the repo
validator, and prints the git/PR steps. Community folder names must use hyphens
(not underscores/spaces). This is separate from your account — it stages the
ability for a pull request; it does not touch app.openhome.com.
Direction at a glance
| Direction | Command |
|---|---|
| account → local | sync · sync --force · sync --prune |
| local → account (new) | create (auto-push) · push (first time) |
| local → account (edit, same id) | push (draft) · push --commit -m "…" (version) |
| voice | call (real voice, mic+speaker) · call --say "…" (one-shot text) · chat (interactive text) |
| remove | delete (account + local folder) · delete --keep-local |
| contribute | push_to_community <name> (user/ → community/ for a PR) |
| local bridge | local start · local status · local logs · local stop (run agent requests on your machine) |
The user/ workspace
Your own abilities live in user/ (gitignored — never committed). Each folder
carries a .openhome.json manifest linking it to the remote ability (capability id,
effective trigger words, release history), so push works from the folder without
re-typing flags. The manifest is excluded from the upload zip.
openhome sync downloads the source of every ability on your account into user/
and records its effective (overridden) trigger words. Local code is preserved unless
you pass --force. Sync only adds/updates — it never deletes on its own. To mirror
deletions (remove local folders whose ability was deleted on the account), add
--prune; folders never pushed (no capability_id) are always kept.
Create vs. update (push is smart)
push looks at the folder's manifest and does the right thing — it never
deletes + re-creates:
- New ability (no
capability_idyet) → creates it viaadd-capability. - Existing ability → updates the code in place via
validate/release-code, keeping the samecapability_id.
openhome push user/myhelper # existing → saves a draft update
openhome push user/myhelper --commit -m "v2: fix triggers" # commit a version
Committing bumps the release (v1 → v2) and the manifest auto-refreshes to the new
editable release. Saving a draft (default) updates the current release in place.
Library
from openhome import OpenHomeClient
oh = OpenHomeClient.from_env()
# 1. create from template
folder = oh.create_from_template("my-weather", "api-template")
# 2–4. save + install + trigger words
agents = oh.list_agents()
result = oh.save_ability(
folder,
name="my-weather",
description="Current weather by city",
category="skill",
trigger_words=["what's the weather", "weather"],
personality_id=agents[0].id, # auto-installs into the call flow
)
# 5. voice-to-voice
print(oh.call(agents[0].id, "what's the weather in Tokyo"))
API contract
See openhome/endpoints.py for the endpoint registry and
openhome/transport.py for the auth-mode handling. These
capability/release endpoints accept X-API-KEY: <api_key> (or a Bearer JWT
for a browser session):
| Action | Method + Path |
|---|---|
| Create ability | POST /api/capabilities/add-capability/ (multipart, nested zip) |
| Update ability in place (save/commit) | POST /api/capabilities/validate/release-code/{release_id}/ (multipart, flat zip, committed, commit_message) |
| List user abilities | GET /api/capabilities/get-all-capabilities/ |
| Download ability source (zip) | GET /api/capabilities/get/template-file/{capability_id}/ |
| Installed detail (effective triggers + releases) | GET /api/capabilities/get/installed-capability/by-capability/{capability_id}/ |
| Delete ability(ies) | POST /api/capabilities/delete-capability/ (JSON {"capability_ids": [...]}, batch) |
Also api-key based: verify_apikey & get_personalities (api_key in body),
edit-installed-capability & edit-personality (X-API-KEY), and the voice
voice-stream WebSocket (api_key in the URL).
Note: the download endpoint returns a flat zip (files at the root). The installed-detail endpoint also exposes per-version
zip_filemedia paths andis_committed/commit_message— the basis for the in-place commit flow.
Voice WebSocket (openhome call)
wss://app.openhome.com/websocket/voice-stream/<api_key>/<agent_id>
- Use the api-key-in-URL path, not the dashboard's
web/0path — the latter is browser-only (authenticates via session cookies) and rejects scripts with1008. - A browser-like
User-Agentheader is required on the handshake, or the server closes with1008 (policy violation). - Client → server:
{"type":"audio","data":<b64 16-bit PCM 16kHz>},ack/audio-received,bot-speaking/bot-speak-end,interrupt-event,ping. - Server → client:
{"type":"message","data":{role,content,live,final}},text(audio-init/audio-end/interrupt), and{"type":"audio",...}— the audio is MP3 (piped intompv), despite PCM being the input format.
Known limitations
- Templates must follow the backend's import policy (e.g. no
import osoutside the allowed block) or the upload validator rejects them. - Updating code keeps the same
capability_id; there is no separate "update metadata only" beyondset-triggers/enable/disable. openhome call(real voice) needsmpv+ PortAudio installed (see Install).
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openhome_client-0.1.1.tar.gz.
File metadata
- Download URL: openhome_client-0.1.1.tar.gz
- Upload date:
- Size: 54.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec1aa89439a2f7ebd2827c25fb30e0c753fef0aced6310fe1f55d67913d110c4
|
|
| MD5 |
498252c4fe4f96bb692673b61343d984
|
|
| BLAKE2b-256 |
4f02957c8cb4e791960922f16a5b75ff83e133e5393c9eb2c077ec6e0fbfdb3e
|
Provenance
The following attestation bundles were made for openhome_client-0.1.1.tar.gz:
Publisher:
publish-cli.yml on openhome-dev/abilities
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openhome_client-0.1.1.tar.gz -
Subject digest:
ec1aa89439a2f7ebd2827c25fb30e0c753fef0aced6310fe1f55d67913d110c4 - Sigstore transparency entry: 2190146232
- Sigstore integration time:
-
Permalink:
openhome-dev/abilities@bf5f2dfd071d27f179c0524db2864a1ad0f7b4b9 -
Branch / Tag:
refs/tags/cli-v0.1.1 - Owner: https://github.com/openhome-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@bf5f2dfd071d27f179c0524db2864a1ad0f7b4b9 -
Trigger Event:
release
-
Statement type:
File details
Details for the file openhome_client-0.1.1-py3-none-any.whl.
File metadata
- Download URL: openhome_client-0.1.1-py3-none-any.whl
- Upload date:
- Size: 55.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3f8dbd2f26d9fd3752f987be46bcd56561dc2fd880f83d28c7f0d29f5ac1805b
|
|
| MD5 |
a437bc7717246b713ad9e35e6ea25e9a
|
|
| BLAKE2b-256 |
7c4237cf02a6734ca427924a22be4dd353ab84c3f1d6834cacf50687737a8d11
|
Provenance
The following attestation bundles were made for openhome_client-0.1.1-py3-none-any.whl:
Publisher:
publish-cli.yml on openhome-dev/abilities
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openhome_client-0.1.1-py3-none-any.whl -
Subject digest:
3f8dbd2f26d9fd3752f987be46bcd56561dc2fd880f83d28c7f0d29f5ac1805b - Sigstore transparency entry: 2190146258
- Sigstore integration time:
-
Permalink:
openhome-dev/abilities@bf5f2dfd071d27f179c0524db2864a1ad0f7b4b9 -
Branch / Tag:
refs/tags/cli-v0.1.1 - Owner: https://github.com/openhome-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-cli.yml@bf5f2dfd071d27f179c0524db2864a1ad0f7b4b9 -
Trigger Event:
release
-
Statement type: