Skip to content

Commit abfdfc8

Browse files
Stop requiring a hardcoded Telnyx outbound voice profile name.
Use the profile attached to the PhoneZero TeXML app (any name, any whitelist). Create a profile only when the account has none. Co-authored-by: Bryan @ FnFirst <function1st@users.noreply.github.com>
1 parent 5fb2aad commit abfdfc8

15 files changed

Lines changed: 159 additions & 58 deletions

File tree

‎.cursor-plugin/marketplace.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
},
66
"metadata": {
77
"description": "Phone skills for voice tasks (restaurant booking first). Need: Telnyx KYC + one US number, xAI team with ZDR off, API keys, Voice Agent Builder.",
8-
"version": "0.4.1"
8+
"version": "0.4.2"
99
},
1010
"plugins": [
1111
{

‎AGENTS.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,26 +97,26 @@ Use that `organization_id`. (Developer curl `GET /v2/whoami` is the same value.
9797
2. **`PHONEZERO_TEXML_APP_ID`** — `invoke_api_endpoint` with `endpoint_name` `list_texml_applications` and args:
9898

9999
```json
100-
{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name}" }
100+
{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name, outbound}" }
101101
```
102102

103103
If a row named `PhoneZero` exists, use its `id`. If not, create it in **7**.
104104

105105
3. **From** — `get_call_config`. If `from_wired` is false, Telnyx `list_phone_numbers` for the DID on the PhoneZero TeXML app.
106106

107-
4. **Destinations** — read Telnyx, do not invent a PhoneZero field. `invoke_api_endpoint` `list_outbound_voice_profiles` args:
107+
4. **Destinations** — the outbound voice profile **attached to the PhoneZero TeXML app**, whatever it is named. Do **not** invent a PhoneZero field. Do **not** filter by `PhoneZero US-only`. Read `outbound.outbound_voice_profile_id` from the TeXML app (step 2). Then `invoke_api_endpoint` `list_outbound_voice_profiles` args:
108108

109109
```json
110-
{ "filter": { "name": { "contains": "PhoneZero" } }, "jq_filter": ".data[] | select(.name==\"PhoneZero US-only\") | {id, name, whitelisted_destinations}" }
110+
{ "jq_filter": ".data[] | {id, name, whitelisted_destinations}" }
111111
```
112112

113-
Show the codes in chat: “Telnyx outbound voice profile **PhoneZero US-only** currently allows: … . Change this in Telnyx Mission Control → Voice → Outbound voice profiles (or ask me to PATCH). It is not a PhoneZero plugin setting.” Only PATCH if they ask to add/remove countries. Do not give legal advice.
113+
Pick the row whose `id` matches that profile id. Show the codes in chat: “Telnyx outbound voice profile **{name}** currently allows: … . Change this in Telnyx Mission Control → Voice → Outbound voice profiles (or ask me to PATCH). It is not a PhoneZero plugin setting.” If the TeXML app has no profile yet: same list, no name filter — one profile → use it; several → ask which; none → create in **7**. Only PATCH if they ask to add/remove countries. Do not give legal advice.
114114

115115
### 7. Provision only if missing
116116

117-
If profile **PhoneZero US-only**, TeXML app **PhoneZero**, and the DID is already attached: skip create. Do **not** overwrite an existing `whitelisted_destinations` unless they asked to change countries. Approve each credentialed write. Never echo keys.
117+
If a TeXML app **PhoneZero** already has an outbound voice profile attached and the DID is on that app: skip create. Use that profile’s name and whitelist as-is. Do **not** overwrite an existing `whitelisted_destinations` unless they asked to change countries. Approve each credentialed write. Never echo keys.
118118

119-
If something is missing, skill **Setup** via MCP names (not REST path names): `list_outbound_voice_profiles` / `create_outbound_voice_profiles` (name `PhoneZero US-only`, `traffic_type=conversational`, `service_plan=global`, `usage_payment_method=rate-deck`, `whitelisted_destinations` default `["US"]` on create, `daily_spend_limit="5.00"`, `daily_spend_limit_enabled=true`); `create_texml_applications` / `update_texml_applications` (`voice_url` = `https://raw.githubusercontent.com/function1st/PhoneZero/main/texml/inbound.xml`, verify HTTP 200, `voice_method=get`); `update_phone_numbers` `connection_id` = TeXML app id.
119+
If something is missing, skill **Setup** via MCP names (not REST path names): `list_outbound_voice_profiles` (no name filter) / `create_outbound_voice_profiles` only when they have **no** profile (create-default name `PhoneZero` unless they pick another, `traffic_type=conversational`, `service_plan=global`, `usage_payment_method=rate-deck`, `whitelisted_destinations` default `["US"]` on create, `daily_spend_limit="5.00"`, `daily_spend_limit_enabled=true`); `create_texml_applications` / `update_texml_applications` (`voice_url` = `https://raw.githubusercontent.com/function1st/PhoneZero/main/texml/inbound.xml`, verify HTTP 200, `voice_method=get`, attach the chosen profile); `update_phone_numbers` `connection_id` = TeXML app id.
120120

121121
xAI: `list_phone_numbers` → `register_byo_number` if the DID is not `byo_trunk` → `attach_agent` onto **your** DID. Skip Builder if they said already set up.
122122

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,13 @@ Fallback only if stdio will not start: Bearer on `https://api.x.ai/v1` and `/v2`
7676
Keep in session. Do not look these up after yes.
7777

7878
1. `TELNYX_ACCOUNT_SID` — `invoke_api_endpoint` `list_billing_groups` args `{ "jq_filter": "[.data[].organization_id] | unique" }`
79-
2. `PHONEZERO_TEXML_APP_ID` — `invoke_api_endpoint` `list_texml_applications` args `{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name}" }`
79+
2. `PHONEZERO_TEXML_APP_ID` — `invoke_api_endpoint` `list_texml_applications` args `{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name, outbound}" }`
8080
3. From — `get_call_config` (last-4 in chat)
81-
4. Destinations — `invoke_api_endpoint` `list_outbound_voice_profiles` (name `PhoneZero US-only` → `whitelisted_destinations`). Show the list. This is **Telnyx** (Mission Control → Voice → Outbound voice profiles), not a PhoneZero field. PATCH only if they ask to add/remove countries.
81+
4. Destinations — the profile **attached to the PhoneZero TeXML app** (any name). `list_texml_applications` → `outbound.outbound_voice_profile_id`, then `invoke_api_endpoint` `list_outbound_voice_profiles` `{ "jq_filter": ".data[] | {id, name, whitelisted_destinations}" }` and pick that `id`. **No name filter.** Show the actual name + codes. This is **Telnyx** (Mission Control → Voice → Outbound voice profiles), not a PhoneZero field. PATCH only if they ask to add/remove countries.
8282

8383
### 7. Provision only if missing
8484

85-
If profile **PhoneZero US-only**, app **PhoneZero**, and the DID is attached: skip create. Do not overwrite an existing whitelist unless they asked. Else skill Setup via MCP names: `list_outbound_voice_profiles` / `create_outbound_voice_profiles` (create default `["US"]`), `create_texml_applications` (`voice_url` = `https://raw.githubusercontent.com/function1st/PhoneZero/main/texml/inbound.xml`, verify 200), `update_phone_numbers` `connection_id`. xAI: `register_byo_number` + `attach_agent` on **your** DID if needed.
85+
If app **PhoneZero** already has an outbound voice profile attached and the DID is on that app: skip create. Use that profile’s name and whitelist as-is. Do not overwrite an existing whitelist unless they asked. Else skill Setup via MCP names: `list_outbound_voice_profiles` (no name filter) / `create_outbound_voice_profiles` only if they have none (create-default name `PhoneZero` unless they pick another; whitelist default `["US"]` on create), `create_texml_applications` (`voice_url` = `https://raw.githubusercontent.com/function1st/PhoneZero/main/texml/inbound.xml`, verify 200, attach the chosen profile), `update_phone_numbers` `connection_id`. xAI: `register_byo_number` + `attach_agent` on **your** DID if needed.
8686

8787
### 8. First call
8888

@@ -146,7 +146,7 @@ Keys live in Configure / MCP headers only — not the agent shell.
146146

147147
This sample ships with:
148148

149-
- **Destinations:** Telnyx outbound voice profile **PhoneZero US-only**, `whitelisted_destinations` default `US` on create (Mission Control → Voice → Outbound voice profiles)
149+
- **Destinations:** whichever Telnyx outbound voice profile is attached to the PhoneZero TeXML app (`whitelisted_destinations`; default `US` only when PhoneZero **creates** a new profile). Name and country list are the user’s. Mission Control → Voice → Outbound voice profiles.
150150
- **AI disclosure:** default **on** in each task (`disclose_ai`) and in the one-time Builder paste
151151
- **Spoken name:** `PhoneZero`
152152
- **Recording:** dual-channel Telnyx recording on every call; the opener says the call is on a recorded line

‎docs/SETUP.md‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The Telnyx MCP cannot run until `TELNYX_API_KEY` is saved as a plugin variable.
4747

4848
The Configure card must match [`plugins/phonezero/.cursor-plugin/plugin.json`](../plugins/phonezero/.cursor-plugin/plugin.json). Cursor injects those values into the plugin MCP processes only — **not** the agent shell. After save, start a **new** conversation and check Telnyx MCP plus the PhoneZero xAI MCP (`get_call_config`). Never `source ~/.phonezero/env` in chat. Never ask the agent to `curl` with `$XAI_API_KEY`.
4949

50-
**Defaults.** Destinations are the Telnyx profile **PhoneZero US-only** (`whitelisted_destinations`, default `US` on create) — Mission Control → Voice → Outbound voice profiles. PhoneZero has no plugin field for this. AI disclosure defaults on. Calls are recorded. You are solely responsible for lawful use; see [DISCLAIMER.md](../DISCLAIMER.md).
50+
**Defaults.** Destinations are the Telnyx outbound voice profile **attached to the PhoneZero TeXML app** (`whitelisted_destinations`; default `US` only when a new profile is created). Name and country list are yours — Mission Control → Voice → Outbound voice profiles. PhoneZero has no plugin field for this. AI disclosure defaults on. Calls are recorded. You are solely responsible for lawful use; see [DISCLAIMER.md](../DISCLAIMER.md).
5151

5252
To add a country (e.g. Japan): change that Telnyx whitelist in Mission Control, or ask the Bot to PATCH `whitelisted_destinations`. Telnyx rejects calls outside that list.
5353

@@ -57,7 +57,7 @@ To add a country (e.g. Japan): change that Telnyx whitelist in Mission Control,
5757
| `PHONEZERO_FROM_NUMBER` | plugin variable | A | Telnyx US DID (E.164): outbound caller ID and SIP bridge target (`sip:{PHONEZERO_FROM_NUMBER}@sip.voice.x.ai;transport=tls`). |
5858
| `XAI_API_KEY` | plugin variable (secret) | A | From a team with **ZDR off**. Injected into the PhoneZero xAI MCP only (Files, collections, STT, phone-numbers). Not in the agent shell. |
5959
| Spoken name / disclose | per-task (`phonezero-task.json`) | chat | `spoken_name` and `disclose_ai`. Default PhoneZero / true. Not on the Configure card. `{disclosure_clause}` is pasted once into the Builder prompt. |
60-
| Destinations | Telnyx voice profile **PhoneZero US-only** | Telnyx | `whitelisted_destinations`. Mission Control → Voice → Outbound voice profiles. Not a PhoneZero plugin variable. |
60+
| Destinations | Telnyx voice profile on the PhoneZero TeXML app | Telnyx | `whitelisted_destinations` of whatever profile is attached (`outbound.outbound_voice_profile_id`). Any name, any country list. Mission Control → Voice → Outbound voice profiles. Not a PhoneZero plugin variable. |
6161
| `TELNYX_ACCOUNT_SID` | resolved in session | B | TeXML REST account SID. MCP: `list_billing_groups` → `data[].organization_id` (no `whoami` tool). Developer curl: `GET /v2/whoami` → `data.organization_id`. Not on the Configure card. |
6262
| `PHONEZERO_TEXML_APP_ID` | resolved in session | B | TeXML application SID. Not on the Configure card. |
6363
| `PHONEZERO_XAI_COLLECTION_ID` | resolved in session | B | Collection for `phonezero-task.json` and optional `phonezero-template-*.json`. Find-or-create name `PhoneZero bookings`. |
@@ -68,7 +68,7 @@ Account, KYC, and the DID stay manual. Everything after that is API-automatable.
6868

6969
12. **End-user path.** Run `/setup-phone-calling` (or ask *"Set up phone calling."*). The first message is a vendor checklist — stop if anything is missing (including ZDR off). Then the Bot uses the Telnyx hosted MCP (`list_api_endpoints` → `get_api_endpoint_schema` → `invoke_api_endpoint`) and `XAI_API_KEY` for xAI Files + phone-numbers. Approve each credentialed step. It will:
7070
- Resolve `TELNYX_ACCOUNT_SID` from Telnyx MCP `list_billing_groups` → `data[].organization_id` (developer curl: `GET /v2/whoami`).
71-
- Find-or-create outbound voice profile **PhoneZero US-only**: `traffic_type=conversational`, `service_plan=global`, `usage_payment_method=rate-deck` (the only accepted combo today; error 10015 otherwise), `whitelisted_destinations` default `["US"]` on create, `daily_spend_limit="5.00"`, `daily_spend_limit_enabled=true`. If the profile exists, keep its current whitelist. PATCH only if the user asks to add or remove countries.
71+
- Resolve the outbound voice profile **already attached** to the PhoneZero TeXML app (any name). If none: use the account’s only profile, or ask which of several to attach, or create one (create-default name `PhoneZero` unless they pick another; `traffic_type=conversational`, `service_plan=global`, `usage_payment_method=rate-deck` — the only accepted combo today; error 10015 otherwise; `whitelisted_destinations` default `["US"]` on create; `daily_spend_limit="5.00"`, `daily_spend_limit_enabled=true`). Keep an existing whitelist and name. PATCH only if the user asks to add or remove countries.
7272
- Find-or-create TeXML application **PhoneZero** with `voice_url` = [`texml/inbound.xml`](../texml/inbound.xml) (default raw URL: `https://raw.githubusercontent.com/function1st/PhoneZero/main/texml/inbound.xml`) and `voice_method=get`. **Verify that URL returns HTTP 200 before writing it.** That URL is fetched **only for inbound** calls to the DID. Outbound calls carry inline `Texml` (template [`plugins/phonezero/texml/bridge.xml`](../plugins/phonezero/texml/bridge.xml)).
7373
- Attach the DID: `PATCH /v2/phone_numbers/{phone_number_id}` `{"connection_id":"<texml_app_id>"}`.
7474
- Register the DID with xAI if it is not already registered: `POST https://api.x.ai/v2/phone-numbers` `{"name":"PhoneZero","phoneNumber":"+1…","origin":"byo_trunk"}`.
@@ -79,6 +79,7 @@ Account, KYC, and the DID stay manual. Everything after that is API-automatable.
7979
- `XAI_API_KEY` — register the DID with xAI. The SIP bridge target is `PHONEZERO_FROM_NUMBER`.
8080
- `PHONEZERO_XAI_AGENT_ID` — attach a Builder agent (step 15) via the fieldMask PATCH.
8181
- `PHONEZERO_ALLOWED_COUNTRIES` — developer-only env. If set, writes that ISO list onto the Telnyx profile `whitelisted_destinations`. If unset, create uses `["US"]` and an existing profile is left as-is. Not a plugin variable.
82+
- `PHONEZERO_PROFILE_NAME` — developer-only env. Pin a Telnyx outbound voice profile by name (find or create). If unset: use the profile already attached to the PhoneZero TeXML app, or the only profile on the account. Not a plugin variable.
8283

8384
14. **Create the Voice Agent (once, in Builder).** There is no public create API (`/v1/agents` is not enabled). Preferred: the Bot opens [https://console.x.ai](https://console.x.ai) with your approved session. Fallback: you do this and give the Bot the `agentId`. On the **same ZDR-off team** as the API key:
8485

@@ -107,7 +108,7 @@ Account, KYC, and the DID stay manual. Everything after that is API-automatable.
107108
- Telnyx auth works (a real `tools/call` succeeds);
108109
- `get_call_config` returns From last-4 (or Telnyx `list_phone_numbers` shows the DID);
109110
- a TeXML application named PhoneZero exists and the DID's `connection_id` matches it;
110-
- Telnyx outbound voice profile **PhoneZero US-only** `whitelisted_destinations` is listed (change countries there, not on the Configure card);
111+
- the outbound voice profile **attached to the PhoneZero TeXML app** is listed (`name` + `whitelisted_destinations`; change countries there, not on the Configure card);
111112
- xAI MCP `ensure_collection` finds **PhoneZero bookings** (403 + "Zero Data Retention" → wrong team);
112113
- xAI MCP `list_phone_numbers` shows the DID as `origin=byo_trunk` with an `agentId` (missing agent is a warning — finish step 14–15).
113114
20. **Optional developer path.** [`scripts/setup-check.sh`](../scripts/setup-check.sh) is developer-only. Run it on a **personal machine that is allowed to hold keys**, never on the Bot computer. It verifies Telnyx auth, that the number is on the account, that the TeXML application exists, and that the DID is attached to that app. If `XAI_API_KEY` is set, it also checks xAI BYO registration. It does **not** verify that the agent answers.

‎plugins/phonezero-grok/.grok-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "phonezero-grok",
33
"displayName": "PhoneZero (Grok)",
4-
"version": "0.4.1",
4+
"version": "0.4.2",
55
"description": "Grok Bot: phone skills (restaurant first). Telnyx hosted HTTP + PhoneZero xAI stdio. Edit Values for keys. Do not click Authenticate on Telnyx.",
66
"author": {
77
"name": "Function1st"

‎plugins/phonezero-grok/commands/setup-phone-calling.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ After the vendor gate, **ask in chat and wait** before wiring. Do not silently k
1212
- Spoken name (default PhoneZero) — what the callee hears
1313
- AI disclaimer ON or OFF — they may turn it OFF
1414

15-
**Do not ask for destination countries.** Read them from Telnyx after the HTTP MCP is proven: `list_outbound_voice_profiles` → profile **PhoneZero US-only** → `whitelisted_destinations`. Show the list. That setting is Telnyx Mission Control → Voice → Outbound voice profiles. PATCH only if they ask to add or remove countries. Do not give legal advice.
15+
**Do not ask for destination countries.** Read them from Telnyx after the HTTP MCP is proven: the outbound voice profile **attached to the PhoneZero TeXML app** (any name) → `whitelisted_destinations`. `list_outbound_voice_profiles` with **no name filter**; pick the id on that TeXML app. Show the actual name + codes. That setting is Telnyx Mission Control → Voice → Outbound voice profiles. PATCH only if they ask to add or remove countries. Do not give legal advice.
1616

1717
Do not search Telnyx MCP for `whoami`. After Telnyx HTTP + xAI stdio are proven, resolve session ids immediately:
1818

‎plugins/phonezero-grok/skills/phonezero-telnyx-canary/SKILL.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ Computer-use. Drive the UI. Do these steps **in order**. Same playbook: repo-roo
1414
5. If `put_task` / `put_booking` is missing: add stdio `xai` from [`plugins/phonezero/mcp.json`](../../../phonezero/mcp.json) with **real** env (not `${…}`). Then `get_call_config` + `ensure_collection`. If they said xAI is already set up, do not open the Builder. Ignore the wizard number. If they turned disclaimer OFF and Builder already exists, or the agent still searches `phonezero-booking.json`, re-paste `prompts/voice-agent.md` and `prompts/end_call.md` once.
1515
6. Session ids **now**, before any plan:
1616
- `TELNYX_ACCOUNT_SID` = `invoke_api_endpoint` `list_billing_groups` args `{ "jq_filter": "[.data[].organization_id] | unique" }`
17-
- `PHONEZERO_TEXML_APP_ID` = `invoke_api_endpoint` `list_texml_applications` args `{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name}" }`
17+
- `PHONEZERO_TEXML_APP_ID` = `invoke_api_endpoint` `list_texml_applications` args `{ "filter": { "friendly_name": "PhoneZero" }, "jq_filter": ".data[] | {id, friendly_name, outbound}" }`
1818
- From = `get_call_config`
19-
- Destinations = `list_outbound_voice_profiles` → **PhoneZero US-only** → `whitelisted_destinations`. Show them. This is Telnyx Mission Control → Voice → Outbound voice profiles, not a PhoneZero field. PATCH only if they ask to change countries.
19+
- Destinations = TeXML app `outbound.outbound_voice_profile_id` → `list_outbound_voice_profiles` (no name filter) → that row’s `name` + `whitelisted_destinations`. Show them. This is Telnyx Mission Control → Voice → Outbound voice profiles, not a PhoneZero field. PATCH only if they ask to change countries.
2020
7. Provision only if profile / TeXML app / DID attach is missing. Do not overwrite an existing whitelist unless they asked.
2121
8. On yes: `put_task` (or `put_booking` alias) → `calls_accounts_texml_calls` (session ids, do not look up SID again) → poll → recordings → `transcribe` → classify → `delete_booking` (live brief only).

‎plugins/phonezero/.cursor-plugin/plugin.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "phonezero",
33
"displayName": "PhoneZero",
4-
"version": "0.4.1",
4+
"version": "0.4.2",
55
"description": "Phone skills for voice tasks (restaurant booking first). Need: Telnyx KYC + one US number, xAI team with ZDR off, API keys, Voice Agent Builder. Then /setup-phone-calling.",
66
"author": {
77
"name": "Function1st"

0 commit comments

Comments
 (0)