-
Notifications
You must be signed in to change notification settings - Fork 21
ops voice
Voice / phone / video interface for claude-ops. All API calls via
curl— no SDK dependencies. Native handlers (Phone, FaceTime, Zoom desktop) require zero credentials; programmatic channels (Twilio, Bland AI, ElevenLabs, Groq, Zoom REST) resolve credentials via the standard claude-ops chain.
Skill: ~/.claude/plugins/data/ops-ops-marketplace/skills/ops-voice/SKILL.md
Wrapper: ~/.claude/plugins/data/ops-ops-marketplace/bin/ops-voice
Routed from: Skills Reference#opscomms for natural-language send flows.
# Native — no credentials, opens the macOS app /ops:voice phone +1234567890 /ops:voice facetime user@example.com /ops:voice zoom start # Smart calendar joiner (v2.9.1) /ops:voice join # joins the meeting that's happening now /ops:voice join --at next # skip current, join the next future meeting /ops:voice join --dry-run # show what would happen, don't launch # Programmatic — credentials required /ops:voice twilio-call +1234567890 +15551234567 --twiml https://demo.twilio.com/docs/voice.xml /ops:voice twilio-sms +1234567890 +15551234567 "your message" /ops:voice bland-call +1234567890 "task prompt for the AI agent" /ops:voice tts "text to speak" --voice <voice_id> --out /tmp/out.mp3 /ops:voice transcribe /path/to/audio.mp3 /ops:voice zoom schedule "<topic>" --start "2026年05月22日T15:00:00Z" --duration 30
| Sub-command | Channel | Needs creds? | Cost | Notes |
|---|---|---|---|---|
phone <num> |
Phone.app via Continuity | No | iPhone plan | macOS + iPhone, same iCloud, Calls on Other Devices ON |
facetime <handle> [--audio] |
FaceTime | No | Free (Apple↔Apple) | Defaults to video; --audio for audio only |
zoom start |
Zoom desktop | No | Zoom plan | Opens zoommtg:// instant meeting |
zoom join <id> [--pwd ...] |
Zoom desktop | No | Zoom plan | Opens existing meeting in desktop app |
zoom schedule "<topic>" ... |
Zoom REST | Yes (ZOOM_API_TOKEN) |
Zoom plan | Server-to-Server OAuth access token |
join |
Auto-detected from calendar | No (uses gog calendar) |
— | v2.9.1 smart joiner — see below |
twilio-call <to> <from> --twiml <url> |
Twilio Voice REST | Yes | Per-minute | Outbound call following a TwiML URL |
twilio-sms <to> <from> "<body>" |
Twilio SMS REST | Yes | Per-message | Plain SMS via Twilio Messaging |
bland-call <num> "<prompt>" |
Bland AI | Yes | Per-minute | AI voice agent calls the number |
tts "<text>" [--voice ...] [--out ...] |
ElevenLabs | Yes | Per-character | Saves MP3, auto-plays on macOS |
transcribe <file> |
Groq Whisper | Yes | Per-second | Returns .text field |
setup |
Configuration | — | — | Scans env / Doppler / keychain, validates each key |
/ops:voice join reads your Google Calendar via gog calendar events --all --today -j --sort start, picks the event that's happening now (within ±window minutes, default 10) or the next future meeting, extracts a conference URL, applies the AV policy below, then hands off to the native opener (which honours Plugin-Rules#rule-7--mobile--ssh-sessions-compact-text-no-tables — SSH/mobile sessions get a copy-able URL block instead of open).
--at now|next|HH:MM default: now
--window MIN default: 10 (±minutes around target time)
--dry-run print plan, don't launch
--json machine-readable output
Walks the event in this order until something resolves:
-
hangoutLink(Google Meet) -
conferenceData.entryPoints[].uri(any provider — Meet / Zoom / Teams / Webex) -
locationfield -
descriptionregex scan:zoom.us/j/<ID>,meet.google.com/<code>,teams.microsoft.com/l/meetup-join/...,webex.com/meet/...
| Attendees | Camera | Microphone |
|---|---|---|
| 1–2 | ON | ON |
| 3–9 | ON | MUTED |
| 10+ | OFF | MUTED |
Tag the event title or description (case-insensitive) to force a setting:
[cam:on] [cam:off] → force camera state
[mic:on] [mic:off] [mic:muted] → force mic state
Example calendar event description:
Quarterly review with the board.
[cam:on] [mic:muted]
Overrides always beat the heuristic. Combine freely.
The script picks the physical mic source based on whether your laptop lid is open or closed:
| OS | Detection | Result |
|---|---|---|
| macOS |
ioreg -r -k AppleClamshellState → "AppleClamshellState" = Yes
|
Closed → external mic; Open → MacBook mic |
| Linux |
/proc/acpi/button/lid/*/state → closed / open
|
Same mapping |
| Windows / WSL / unknown | n/a | Reports default — meeting app keeps its remembered device |
The script reports the chosen source and launches Camera Hub when present, but does not programmatically flip the in-app device picker — Zoom / FaceTime / Meet all remember the last-selected device, so flipping it once per app is permanent.
If Elgato Camera Hub is installed, it's launched before the meeting opens so the virtual cam is registered as a usable device. Detection paths:
| OS | Path |
|---|---|
| macOS |
/Applications/Elgato Camera Hub.app, ~/Applications/Elgato Camera Hub.app, /Applications/Camera Hub.app
|
| Linux |
elgato-camera-hub on $PATH, or AppImage at ~/Applications/Elgato*CameraHub*.AppImage
|
| Windows / WSL | ${PROGRAMFILES}/Elgato/CameraHub/CameraHub.exe |
When the picked event has a https://zoom.us/j/<ID>?pwd=<PWD> link, it's rewritten to:
zoommtg://zoom.us/join?confno=<ID>&pwd=<PWD>
so the desktop app opens directly with no browser middleman. When the AV policy resolves to cam=off, &zc=0 is appended (Zoom's "camera off on join" flag).
$ /ops:voice join --dry-run
dry-run: would join "Weekly Sync" (meet, 3 attendees)
url=https://meet.google.com/abc-defg-hij
cam=on mic=muted
lid=closed mic_source=external
elgato_hub=/Applications/Elgato Camera Hub.app
JSON mode:
/ops:voice join --dry-run --json
# {"ok":true,"channel":"join","detail":"...","picked":{"summary":"...","provider":"meet","attendees":3,"url":"..."},"policy":{"cam":"on","mic":"muted","lid":"closed","mic_source":"external"}}Programmatic channels (Twilio, Bland, ElevenLabs, Groq, Zoom REST) resolve each required key in this order — first hit wins:
-
Environment variable (e.g.
$TWILIO_AUTH_TOKEN) -
ops_cred_get—lib/credential-store.shreads OS-native keychain (macOS Keychain /secret-toolon Linux / Credential Manager on Windows) -
preferences.json— plugin data dir ($PREFS_PATH) -
Doppler —
doppler secrets get <KEY> --plain(scans all projects / configs) - Password manager — Bitwarden / 1Password / Dashlane, if configured
Required keys by channel:
| Channel | Required env / key names |
|---|---|
| Twilio voice/SMS |
TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM_NUMBER
|
| Bland AI | BLAND_AI_API_KEY |
| ElevenLabs |
ELEVENLABS_API_KEY (optional ELEVENLABS_VOICE_ID, defaults to Rachel) |
| Groq Whisper | GROQ_API_KEY |
| Zoom REST |
ZOOM_API_TOKEN (Server-to-Server OAuth access token — generate from Zoom Marketplace) |
| OS | Native phone | FaceTime | Zoom start/join (desktop) | Zoom schedule (REST) | Twilio | Bland | ElevenLabs / Groq |
join smart joiner |
|---|---|---|---|---|---|---|---|---|
| macOS | yes (Continuity) | yes | yes | yes | yes | yes | yes | full (lid + Elgato + ioreg) |
| Linux | no (Apple-only) | no (Apple-only) | yes (Zoom Linux client) | yes | yes | yes | yes | full (/proc/acpi lid + Elgato detection) |
| Windows / WSL | no (Apple-only) | no (Apple-only) | yes (Zoom Windows client) | yes | yes | yes | yes | partial — no lid detection, mic source reports default; Elgato detected via $PROGRAMFILES
|
See OS Compatibility for the broader feature matrix.
When $SSH_CONNECTION, $SSH_CLIENT, $SSH_TTY is set, or $OPS_MOBILE=1 is exported:
- API channels (Twilio, Bland, ElevenLabs, Groq, Zoom REST) work unchanged — pure HTTP, no local UI.
- Native channels (
phone,facetime,zoom start|join,join) detect the SSH context, sourcelib/opener.sh, and callops_open_url, which prints a copy-able URL block instead of spawning the host'sopen/xdg-open(which would launch a browser on the SSH target the user can't see).
This matches the broader Plugin-Rules#rule-7--mobile--ssh-sessions-compact-text-no-tables mobile/SSH convention used across every ops skill.
twilio-call, twilio-sms, and bland-call are 1:1 outbound channels — they ring or text a specific human. They are bound by Plugin-Rules#rule-6--outbound-comms-require-per-message-approval-always:
-
Stage one draft. Show the full
to,from, body, TwiML URL, or prompt. -
Ask —
AskUserQuestionwith[Send]/[Edit]/[Skip], or wait for a plain-chat approval word (ok,send,go,approved,ship it). - Send that one. Then stage the next.
Batch sending is never allowed. This applies equally to direct skill use, /ops:comms routing, subagents, and the daemon. The user's defense-in-depth hook (block-outbound-comms.py with /tmp/.claude-send-ok one-shot, 120s TTL token) backs this up — but the skill itself is the primary gate.
Public-broadcast channels (Reddit / X / LinkedIn / Discord public / blog posts) are not covered — see Rule 6 in Plugin Rules for the full scope.
The natural-language router in /ops:comms maps intent to ops-voice sub-commands:
| User says | Resolves to |
|---|---|
call <name> |
ops-voice phone <number> |
facetime <name> |
ops-voice facetime <handle> |
start a zoom |
ops-voice zoom start |
text <name> "..." |
ops-voice twilio-sms <to> <from> "..." |
have an AI call <name> and tell ... |
ops-voice bland-call <number> "..." |
join my meeting |
ops-voice join |
Contact-number lookup uses the same resolver as WhatsApp (mcp__whatsapp__search_contacts) plus an optional contacts.json map in preferences.json.
/ops:voice setup
Scans all sources in parallel (Rule 4 — run_in_background: true):
- Env vars:
TWILIO_*,BLAND_AI_API_KEY,ELEVENLABS_API_KEY,GROQ_API_KEY,ZOOM_* - Shell profiles:
~/.zshrc,~/.bashrc,~/.zprofile,~/.envrc - Doppler — every project ×ばつ
dev|stg|prdconfig - OS-native keychain (macOS / Linux
secret-tool/ Windows Credential Manager) - Native prerequisites:
/Applications/zoom.us.app,FaceTime.app,Phone.app
Validates each found key with a live probe:
| Channel | Probe |
|---|---|
| Twilio | GET https://api.twilio.com/2010-04-01/Accounts/$SID.json |
| Bland | GET https://api.bland.ai/v1/me |
| ElevenLabs | GET https://api.elevenlabs.io/v1/voices?page_size=1 |
| Groq | GET https://api.groq.com/openai/v1/models |
| Zoom | GET https://api.zoom.us/v2/users/me |
For each unset service, presents AskUserQuestion with [Paste manually] / [Deep hunt — spawn agent] / [Skip] — Rule 3 forbids silent skips.
-
Skills Reference#opscomms — natural-language router that dispatches to
ops-voice -
Skills Reference#opsinbox — when an inbox reply needs a phone call, comms suggests
ops-voice - Integrations — auth setup for Twilio, Bland AI, ElevenLabs, Groq, Zoom
- Plugin Rules — Rule 6 (per-message approval), Rule 7 (mobile/SSH), Rule 3 (no silent skips)
- OS Compatibility — per-OS support for every channel
-
Changelog — v2.9.0
/ops:voiceinitial release, v2.9.1 smartjoin+ AV policy