{"id":50309754,"url":"https://github.com/johnib/spoke-cli","last_synced_at":"2026-05-28T20:01:51.298Z","repository":{"id":359042421,"uuid":"1243996184","full_name":"johnib/spoke-cli","owner":"johnib","description":"Command-line interface for Spoke Phone, plus a built-in MCP bridge for AI agents. TypeScript + commander + OAuth2 client_credentials.","archived":false,"fork":false,"pushed_at":"2026-05-20T06:57:23.000Z","size":1138,"stargazers_count":0,"open_issues_count":10,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T08:53:47.688Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnib.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-19T21:41:11.000Z","updated_at":"2026-05-20T06:57:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/johnib/spoke-cli","commit_stats":null,"previous_names":["johnib/spoke-cli"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/johnib/spoke-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnib%2Fspoke-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnib%2Fspoke-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnib%2Fspoke-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnib%2Fspoke-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnib","download_url":"https://codeload.github.com/johnib/spoke-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnib%2Fspoke-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33624221,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-28T02:00:06.440Z","response_time":99,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-28T20:01:48.574Z","updated_at":"2026-05-28T20:01:51.281Z","avatar_url":"https://github.com/johnib.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spoke — Spoke Phone CLI\n\n[![npm version](https://img.shields.io/npm/v/@johnib/spoke-cli.svg)](https://www.npmjs.com/package/@johnib/spoke-cli)\n[![CI](https://github.com/johnib/spoke-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/johnib/spoke-cli/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n[![Node](https://img.shields.io/node/v/@johnib/spoke-cli.svg)](package.json)\n\nA GitHub-style command-line interface for [Spoke Phone](https://www.spokephone.com), plus a built-in MCP bridge that exposes the same surface to AI agents like Claude Code.\n\nBuilt against the [Spoke Developer API](https://developer.spokephone.com/) (OAuth2 client_credentials).\n\n\u003e **Third-party, unofficial.** Not affiliated with Spoke Phone Inc.\n\n## Quickstart\n\n```bash\n# Try without installing\nnpx @johnib/spoke-cli auth status\n\n# Or install globally\nnpm install -g @johnib/spoke-cli\n\n# Authenticate\nspoke auth login --client-id $SPOKE_CLIENT_ID --client-secret $SPOKE_CLIENT_SECRET\n\n# Or use env vars (no profile written)\nexport SPOKE_CLIENT_ID=...\nexport SPOKE_CLIENT_SECRET=...\n\n# Verify\nspoke auth status\n\n# Browse\nspoke directory list\nspoke directory get 1053          # by extension\nspoke directory get \"Alice Cohen\" # by name\nspoke directory search sales\n\n# Users\nspoke user list --available\nspoke user availability 1053\nspoke user redirect-url 1053\n\n# Teams (call groups)\nspoke group list\nspoke group members 1048\nspoke group availability 1048\n\n# Calls\nspoke call list\nspoke call get \u003ccall-id\u003e\nspoke call twiml-url --extension 1053 --organisation-id \u003corg-id\u003e\n\n# Voicemails (derived view over /calls)\nspoke voicemail list\nspoke voicemail transcript \u003ccall-id\u003e\nspoke voicemail download \u003ccall-id\u003e --output vm.wav\n\n# Outbound SMS\nspoke message send --to +1... --from 1053 --body \"Hello\"\n\n# Webhooks\nspoke webhook list\nspoke webhook create --url https://x --events call.started,call.ended\n\n# Raw API escape hatch\nspoke api /directory --jq '$.entries.extension'\nspoke api /calls?limit=5 --json\n\n# MCP for Claude Code\nspoke mcp serve\n```\n\n## Command surface\n\nThe CLI mirrors what the public Spoke API actually exposes. Operations that\nexist only in Spoke's own clients (e.g. setting your own availability,\ntransferring live calls, listing inbound messages) are **not** in the CLI —\nthey aren't in the public API either.\n\n| Resource | Commands |\n|---|---|\n| `auth` | login, logout, status, token, profiles |\n| `directory` | list, get, search |\n| `user` | list, get, availability, redirect-url |\n| `group` (team) | list, get, members, availability, redirect-url |\n| `device` | list, get |\n| `call` | list, get, twiml-url |\n| `message` | send (the API has no read endpoint — subscribe to `conversation.message.created` webhook) |\n| `voicemail` | list, get, transcript, download (derived from `/calls`) |\n| `webhook` | list, create, delete, forward |\n| `config` | get, set, list |\n| `api` | raw HTTP passthrough — `--method`, `--field`, `--header`, `--input`, `--paginate`, `--include` |\n| `mcp` | serve — start the MCP bridge |\n\n## Authentication\n\nOAuth2 client_credentials. Credentials are stored at `~/.spoke/config.yml` (mode\n`0600`). Tokens cache at `~/.spoke/tokens/\u003cprofile\u003e.json`, auto-refreshed.\n\nOverride per-invocation with `--profile \u003cname\u003e` or `SPOKE_PROFILE` env var.\n\n## Output formats\n\n| Flag | Behavior |\n|---|---|\n| (default) for lists | Aligned ASCII table |\n| (default) for single items | Human key:value view |\n| `--json` | Pretty-printed JSON |\n| `--jq \u003cexpr\u003e` | JSONata expression (jq-compatible for simple uses) |\n| `--template \u003ctmpl\u003e` | Go-style `text/template` rendering |\n| `--silent`, `-s` | Suppress stdout (errors still on stderr) |\n\n```bash\nspoke directory list --jq '$.extension'\nspoke call list --template '{{range .}}{{.id}} {{.direction}}\\n{{end}}'\n```\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| `0` | Success |\n| `1` | Generic / validation failure |\n| `2` | Authentication error |\n| `3` | Resource not found (HTTP 404) |\n| `4` | Permission denied (HTTP 403) |\n| `5` | Rate limited (HTTP 429) |\n| `6` | Server-side error (HTTP 5xx) |\n\n## MCP bridge\n\nWire into Claude Code via `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"spoke\": {\n      \"command\": \"npx\",\n      \"args\": [\"@johnib/spoke-cli\", \"mcp\", \"serve\"],\n      \"env\": {\n        \"SPOKE_CLIENT_ID\": \"${SPOKE_CLIENT_ID}\",\n        \"SPOKE_CLIENT_SECRET\": \"${SPOKE_CLIENT_SECRET}\"\n      }\n    }\n  }\n}\n```\n\nTools exposed: `spoke_directory_list`, `spoke_directory_get`,\n`spoke_user_availability`, `spoke_group_availability`, `spoke_group_members`,\n`spoke_call_list`, `spoke_call_get`, `spoke_voicemail_list`,\n`spoke_message_send`, `spoke_webhook_list`, `spoke_api`.\n\n## API quirks worth knowing\n\nThe CLI handles these for you; documenting them in case you reach for `spoke api`.\n\n- **Extension lookup is a query param, not a path:** `GET /directory?extension=1053`. The path-style `/directory/{id}` only accepts UUIDs.\n- **Type discriminator is `\"team\"`**, not `callGroup`. Member list is `teamMembers`.\n- **`Call.duration` is milliseconds**, but `Recording.duration` and `Voicemail.duration` are seconds.\n- **`/conversationMessages` is POST-only.** There is no GET. To receive messages, subscribe to the `conversation.message.created` webhook.\n- **`/voicemails` does not exist as a resource.** Voicemails are a `.voicemail` field nested on Call objects; `spoke voicemail *` projects over `/calls`.\n- **Voicemail recording URLs are signed and expire 6h after recording.** Re-fetch the call to refresh.\n- **`/telephony/redirect` is a TwiML URL** that Twilio fetches when it routes a call — it's not a Spoke REST endpoint. Spoke's public API has no call-transfer / call-hangup endpoints. Use `spoke call twiml-url` to build the URL for your Twilio integration.\n- **Default `limit=100`, max `1000`.** Pagination is cursor-based: `meta.next` carries the next token, pass back as `?next=\u003ctoken\u003e`.\n- **Webhook signature:** `x-spoke-signature: sha256=\u003cHMAC\u003e` of `${ms_timestamp}.${body}`, 5-min window.\n\n## Versioning \u0026 releases\n\nVersions are managed automatically by [semantic-release](https://github.com/semantic-release/semantic-release) on every push to `main`.\n\nThe next version is computed from [Conventional Commit](https://www.conventionalcommits.org/) messages since the last release:\n\n| Commit prefix | Version bump | Example |\n|---|---|---|\n| `fix(scope):` | patch | `1.2.3` → `1.2.4` |\n| `feat(scope):` | minor | `1.2.3` → `1.3.0` |\n| `feat!:` or `BREAKING CHANGE:` footer | major | `1.2.3` → `2.0.0` |\n| `chore:`, `docs:`, `refactor:`, `test:`, `ci:` | none | (no release) |\n\nSee [`CHANGELOG.md`](CHANGELOG.md) for the auto-generated history.\n\n## Development\n\n```bash\nnpm install\nnpm run build\nnpm test\nnpm run test:cov\nnpm run lint\n```\n\nArchitecture:\n- `src/commands/` — one file per CLI subcommand\n- `src/lib/api/` — typed wrappers per Spoke resource\n- `src/lib/auth/` — OAuth2 + token cache\n- `src/lib/output/` — table / human / JSON / JSONata / template renderers\n- `src/mcp/` — MCP server wrapping the API client\n\nAll HTTP is mocked via [nock](https://github.com/nock/nock) in tests. No real\nnetwork is touched by the test suite.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnib%2Fspoke-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnib%2Fspoke-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnib%2Fspoke-cli/lists"}