AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning
This commit is contained in:
69
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
69
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: 🐛 Bug Report
|
||||
description: Something isn't working right
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting! The more detail you give, the faster we can fix it.
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: ClawdTalk Client Version
|
||||
description: "Run `cat skills/clawdtalk-client/SKILL.md | head -5` to find it"
|
||||
placeholder: "e.g. 1.3.0"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Component
|
||||
options:
|
||||
- Voice Calls (Inbound)
|
||||
- Voice Calls (Outbound)
|
||||
- SMS
|
||||
- WebSocket Connection
|
||||
- Setup / Installation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
placeholder: "Describe the bug. What did you expect vs what actually happened?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
placeholder: |
|
||||
1. Run `./scripts/connect.sh start`
|
||||
2. Call the number
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs / Error output
|
||||
description: "Run with `DEBUG=1` if possible"
|
||||
render: shell
|
||||
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: OS
|
||||
placeholder: "e.g. macOS 15.3, Ubuntu 24.04"
|
||||
|
||||
- type: input
|
||||
id: openclaw-version
|
||||
attributes:
|
||||
label: OpenClaw Version
|
||||
placeholder: "Run `openclaw --version`"
|
||||
5
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 💬 OpenClaw Community
|
||||
url: https://discord.gg/StGHNHNc
|
||||
about: Chat with the community on Discord
|
||||
51
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
51
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: 💡 Feature Request
|
||||
description: Suggest a new feature or improvement
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Got an idea? We'd love to hear it.
|
||||
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: What problem does this solve?
|
||||
placeholder: "I want to... but currently I can't because..."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: solution
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
placeholder: "Describe what you'd like to happen"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
placeholder: "Any workarounds you've tried or other approaches?"
|
||||
|
||||
- type: dropdown
|
||||
id: component
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Voice Calls
|
||||
- SMS
|
||||
- WebSocket / Connection
|
||||
- Authentication / Security
|
||||
- CLI / Scripts
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional context
|
||||
placeholder: "Screenshots, links, examples, anything helpful"
|
||||
22
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
22
skills/clawdtalk-client/.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: ❓ Question
|
||||
description: Ask a question about setup, usage, or anything else
|
||||
labels: ["question"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
No dumb questions. Ask away.
|
||||
|
||||
- type: textarea
|
||||
id: question
|
||||
attributes:
|
||||
label: Your question
|
||||
placeholder: "What do you want to know?"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Context
|
||||
placeholder: "What are you trying to do? What have you tried?"
|
||||
76
skills/clawdtalk-client/CHANGELOG.md
Normal file
76
skills/clawdtalk-client/CHANGELOG.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.0
|
||||
- **New**: Approval requests — request user confirmation for sensitive actions
|
||||
- `./scripts/approval.sh` to send push notifications and wait for approve/deny
|
||||
- Supports biometric verification requirement for high-security actions
|
||||
- Configurable timeout and details field
|
||||
- Perfect for voice call flows: "I've sent the flight details to your phone for approval"
|
||||
|
||||
## 1.4.0
|
||||
- `./scripts/missions.sh` remove this
|
||||
- Missions are now supported over Telnyx Missions API
|
||||
|
||||
## 1.4.0
|
||||
- **New**: Re-enabled missions — AI-powered outreach via voice calls and SMS
|
||||
- `./scripts/missions.sh` restored with create, list, get, events, status, and cancel commands
|
||||
- Supports voice, SMS, and combined (both) channels
|
||||
- Scheduling options: immediate, business hours, or custom datetime
|
||||
- Fixed shell syntax bug in missions script (broken SERVER fallback)
|
||||
|
||||
## 1.3.0
|
||||
- **New**: Log `call_control_id` from deep tool requests when present
|
||||
- Enables asking your agent "what was the call control ID of my last call?"
|
||||
- Backwards-compatible: works with servers that don't yet send the field
|
||||
|
||||
## 1.2.9
|
||||
- **Fix**: Use correct `/tools/invoke` endpoint for sessions_send
|
||||
- Previous version used non-existent `/v1/sessions/send` (405 error)
|
||||
|
||||
## 1.2.8
|
||||
- **Fix**: Use `sessions_send` to route call outcomes to main persistent session
|
||||
- Call reports now appear in your Telegram/Discord/etc. session instead of ephemeral sessions
|
||||
|
||||
## 1.2.7
|
||||
- **New**: `update.sh` script for easy self-updates from GitHub
|
||||
- **New**: `dist/clawdtalk-client-latest.zip` in repo for direct downloads
|
||||
- Run `./update.sh` to check for and install updates
|
||||
|
||||
## 1.2.6
|
||||
- **Fix**: Route call outcomes to `main` agent instead of legacy `voice` agent
|
||||
- Call reports now reach your primary session correctly
|
||||
|
||||
## 1.2.5
|
||||
- **Call outcome reporting**: After calls end, skill reports what happened to the user
|
||||
- Voicemail left (with message preview)
|
||||
- Call completed (with duration)
|
||||
- No answer / silence detected
|
||||
- Fax machine detected
|
||||
- Voicemail failed (no beep)
|
||||
- Enhanced call.ended event handling with detailed outcome info
|
||||
|
||||
## 1.2.2
|
||||
- **Outbound calls**: New `call.sh` script to have your bot call you
|
||||
- **Missions removed**: Batch outreach feature disabled (not ready for production)
|
||||
- Updated docs and removed missions.sh
|
||||
|
||||
## 1.2.0
|
||||
- **SMS Reply Support**: Incoming SMS messages now route to your main agent session
|
||||
- Agent generates response and automatically sends reply via ClawdTalk
|
||||
- SMS replies truncated to fit SMS limits
|
||||
|
||||
## 1.1.0
|
||||
- Deep tool requests now route to main agent session for full context/memory access
|
||||
- Auto-detect owner and agent names from USER.md and IDENTITY.md
|
||||
- Personalized greetings using detected owner name
|
||||
- Added "drip progress updates" - brief spoken updates during tool execution
|
||||
- Added `--server <url>` flag to connect.sh for server override
|
||||
- Removed hardcoded model - uses gateway's configured model
|
||||
- Better timeout handling with specific error messages
|
||||
- Sends owner/agent names during auth for assistant personalization
|
||||
|
||||
## 1.0.0
|
||||
- Initial release
|
||||
- Voice calling with full tool execution
|
||||
- SMS messaging support
|
||||
- Missions support
|
||||
209
skills/clawdtalk-client/README.md
Normal file
209
skills/clawdtalk-client/README.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# ClawdTalk Client
|
||||
|
||||
Give your OpenClaw bot a phone number.
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://github.com/team-telnyx/clawdtalk-client)
|
||||
|
||||
Voice calling and SMS messaging for [Clawdbot](https://clawdbot.com). Talk to your bot by phone or exchange texts. Powered by [Telnyx](https://telnyx.com).
|
||||
|
||||
## What's New in 2.0
|
||||
|
||||
- **Approval requests**: Voice callers can request bot approval for actions that require confirmation
|
||||
- **Missions**: Configure AI-powered outbound call campaigns for automated outreach
|
||||
- **Call control ID logging**: Track individual call legs with unique control IDs for better debugging
|
||||
|
||||
## Changelog
|
||||
|
||||
See [CHANGELOG.md](./CHANGELOG.md) for the full release history.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Phone → Telnyx (STT) → ClawdTalk Server → WebSocket → OpenClaw Gateway → Agent → TTS → Phone
|
||||
│ │
|
||||
└── Speech-to-text └── Routes to /v1/chat/completions
|
||||
│ │
|
||||
└── Text-to-speech └── Bot processes like any message
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Voice calls**: Real-time conversations with your bot via phone
|
||||
- **SMS messaging**: Send and receive text messages
|
||||
- **Tool integration**: Your bot's full capabilities, accessible by voice
|
||||
- **Approval requests**: Get caller confirmation for sensitive actions
|
||||
- **Missions**: Run AI-driven outbound call campaigns
|
||||
- **Call control IDs**: Debug calls with unique leg identifiers
|
||||
|
||||
## Updating
|
||||
|
||||
To update to the latest version, run:
|
||||
|
||||
```bash
|
||||
./update.sh
|
||||
```
|
||||
|
||||
Or just ask your bot to update by pasting the repo URL:
|
||||
|
||||
> Update clawdtalk to the latest version: https://github.com/team-telnyx/clawdtalk-client
|
||||
|
||||
The update script pulls the latest changes and restarts the connection if needed.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Clawdbot or OpenClaw with gateway running
|
||||
- Node.js, bash, jq
|
||||
- ClawdTalk account ([clawdtalk.com](https://clawdtalk.com))
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone or download to your skills directory
|
||||
cd ~/clawd/skills/clawdtalk-client
|
||||
|
||||
# Run setup
|
||||
./setup.sh
|
||||
|
||||
# Start the WebSocket connection
|
||||
./scripts/connect.sh start
|
||||
```
|
||||
|
||||
The setup script will ask for your API key, configure the voice agent in your gateway, and create `skill-config.json`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Voice Calls
|
||||
|
||||
Start the connection, then call your ClawdTalk number:
|
||||
|
||||
```bash
|
||||
./scripts/connect.sh start # Start (run in background or via cron)
|
||||
./scripts/connect.sh stop # Stop
|
||||
./scripts/connect.sh status # Check status
|
||||
./scripts/connect.sh restart # Restart
|
||||
```
|
||||
|
||||
Keep it running via crontab:
|
||||
|
||||
> **Note:** This keeps a persistent WebSocket connection to clawdtalk.com. Voice transcripts are transmitted in real-time when calls are active.
|
||||
|
||||
```bash
|
||||
# Add to crontab (crontab -e):
|
||||
@reboot cd ~/clawd/skills/clawdtalk-client && ./scripts/connect.sh start
|
||||
```
|
||||
|
||||
### Outbound Calls
|
||||
|
||||
Have the bot call you:
|
||||
|
||||
```bash
|
||||
./scripts/call.sh # Call with default greeting
|
||||
./scripts/call.sh "Hey, what's up?" # Custom greeting
|
||||
./scripts/call.sh status <call_id> # Check status
|
||||
./scripts/call.sh end <call_id> # End call
|
||||
```
|
||||
|
||||
### SMS
|
||||
|
||||
```bash
|
||||
./scripts/sms.sh send +15551234567 "Hello from ClawdTalk!"
|
||||
./scripts/sms.sh send +15551234567 "With image" --media https://example.com/photo.jpg
|
||||
./scripts/sms.sh list
|
||||
./scripts/sms.sh list --contact +15551234567
|
||||
./scripts/sms.sh conversations
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`skill-config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_key": "cc_live_xxx",
|
||||
"server": "https://clawdtalk.com"
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `api_key` | Your API key from clawdtalk.com |
|
||||
| `server` | ClawdTalk server URL (default: `https://clawdtalk.com`) |
|
||||
|
||||
### Environment Variable Support
|
||||
|
||||
Instead of storing credentials in plaintext, use `${ENV_VAR}` references:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_key": "${CLAWDTALK_API_KEY}",
|
||||
"server": "https://clawdtalk.com"
|
||||
}
|
||||
```
|
||||
|
||||
Set the variable in one of these locations (checked in order):
|
||||
- `~/.openclaw/.env`
|
||||
- `~/.clawdbot/.env`
|
||||
- `<skill-dir>/.env`
|
||||
|
||||
Example `.env` file:
|
||||
|
||||
```bash
|
||||
CLAWDTALK_API_KEY=cc_live_xxx
|
||||
```
|
||||
|
||||
The gateway auth token in `openclaw.json`/`clawdbot.json` also supports this:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"auth": {
|
||||
"token": "${GATEWAY_TOKEN}"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Gateway Tools (Required)
|
||||
|
||||
The voice assistant uses `sessions_send` to proxy questions to your Clawdbot. You must allow it on the gateway's `/tools/invoke` endpoint:
|
||||
|
||||
```json
|
||||
{
|
||||
"gateway": {
|
||||
"tools": {
|
||||
"allow": ["sessions_send"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Without this, the voice assistant will handle calls on its own but won't be able to forward questions to your bot (you'll see `sessions_send failed: 404` in the logs).
|
||||
|
||||
Run `./scripts/connect.sh status` to check if this is configured correctly.
|
||||
|
||||
## How It Works
|
||||
|
||||
**Voice:** Phone calls connect via Telnyx to the ClawdTalk server. The WebSocket client (`ws-client.js`) routes transcribed speech to your gateway's `/v1/chat/completions` endpoint. Your bot processes it like any other message with the same tools and context. The response is converted to speech and played back.
|
||||
|
||||
**SMS:** Messages route through the ClawdTalk API. Inbound messages can trigger your bot via webhooks.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Fix |
|
||||
|-------|-----|
|
||||
| Auth failed | Regenerate API key at clawdtalk.com |
|
||||
| Empty responses | Run `./setup.sh`, then `clawdbot gateway restart` |
|
||||
| `sessions_send failed: 404` | Add `sessions_send` to `gateway.tools.allow` in your OpenClaw config (see Gateway Tools above) |
|
||||
| Connection drops | Check `tail -f .connect.log` for errors |
|
||||
| Debug mode | `DEBUG=1 ./scripts/connect.sh restart` |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Links
|
||||
|
||||
- [ClawdTalk](https://clawdtalk.com) — Sign up and manage your account
|
||||
- [Clawdbot](https://clawdbot.com) — AI assistant framework
|
||||
- [Telnyx](https://telnyx.com) — Voice and messaging infrastructure
|
||||
263
skills/clawdtalk-client/SETUP.md
Normal file
263
skills/clawdtalk-client/SETUP.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# ClawdTalk Skill — Agent Setup Guide
|
||||
|
||||
This document tells you (the agent) how to configure the `clawdtalk-client` skill after installation. The goal: get the user's ClawdTalk working with zero manual config from them beyond providing an API key.
|
||||
|
||||
**Important:** After running `clawdhub install`, the output tells you where the skill was installed (e.g. `Installed clawdtalk-client -> /path/to/skills/clawdtalk-client`). Use that exact path for all subsequent commands. This document uses `<skill_directory>` as a placeholder for that path.
|
||||
|
||||
## Setup Steps (Summary)
|
||||
|
||||
| # | What | How | Notes |
|
||||
|---|------|-----|-------|
|
||||
| 1 | Install the skill | `clawdhub install clawdtalk-client --registry https://clawhub.ai` | User triggers this. You run the command. |
|
||||
| 2 | Get the API key | Ask the user if they didn't provide one | **Mandatory. Do not proceed without it.** |
|
||||
| 3 | Validate the API key | `python3 scripts/telnyx_api.py check-key` | Confirms the key is valid before proceeding. |
|
||||
| 4 | Run `setup.sh` | `bash ./setup.sh` (interactive, needs PTY) | Handles config, gateway, deps, names. |
|
||||
| 5 | Trim agent name | Edit `skill-config.json` if name has extra text | `setup.sh` sometimes pulls full IDENTITY.md line. |
|
||||
| 6 | Start the WebSocket | `bash ./scripts/connect.sh start` | Enables voice calls. |
|
||||
| 7 | Verify | `bash ./status.sh` | Confirm everything is green. |
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install the Skill
|
||||
|
||||
The user will ask you to run:
|
||||
|
||||
```bash
|
||||
clawdhub install clawdtalk-client --registry https://clawhub.ai
|
||||
```
|
||||
|
||||
This installs the skill into your workspace at `skills/clawdtalk-client/`. Note the exact install path from the output. Once installed, read this document before doing anything else.
|
||||
|
||||
## Step 2: Get the API Key
|
||||
|
||||
**You cannot proceed without an API key.** If the user did not include their API key in the install message, you MUST stop and ask them for it now. Do not skip this step. Do not attempt setup without a key.
|
||||
|
||||
Ask something like: *"I need your ClawdTalk API key to finish setup. You can get one from clawdtalk.com → Dashboard. Paste it here when you have it."*
|
||||
|
||||
The key looks like `cc_live_...` (a long hex string). Once you have it, move to step 3.
|
||||
|
||||
## Step 3: Validate the API Key
|
||||
|
||||
Before running setup, verify the key actually works:
|
||||
|
||||
```bash
|
||||
cd <skill_directory>
|
||||
CLAWDTALK_API_KEY="<the_key>" python3 scripts/telnyx_api.py check-key
|
||||
```
|
||||
|
||||
You should see: `API key configured: cc_live_...xxx`
|
||||
|
||||
If it fails or returns an auth error, the key is invalid. Ask the user to regenerate it at [clawdtalk.com](https://clawdtalk.com) → Dashboard. **Do not proceed with setup until the key validates.**
|
||||
|
||||
## Step 4: Run `setup.sh` (Interactive)
|
||||
|
||||
**This is the main setup script. Use it. Do not write `skill-config.json` manually.**
|
||||
|
||||
The script handles:
|
||||
- Saving the API key
|
||||
- Auto-detecting gateway config (port, token, agent ID)
|
||||
- Adding a `voice` agent to the gateway config
|
||||
- Installing Node.js dependencies
|
||||
- Auto-detecting owner and agent names from `USER.md` / `IDENTITY.md`
|
||||
- Checking `sessions_send` is in `gateway.tools.allow`
|
||||
- Writing `skill-config.json` with all the correct values
|
||||
|
||||
### How to Run It
|
||||
|
||||
The script uses `read -p` for interactive prompts. **It must run under `bash` with a PTY**, not `zsh`:
|
||||
|
||||
```bash
|
||||
cd <skill_directory>
|
||||
bash ./setup.sh
|
||||
# Must use: pty=true, background=true
|
||||
```
|
||||
|
||||
### Interactive Prompts (In Order)
|
||||
|
||||
The script will ask these questions. Here's how to answer each:
|
||||
|
||||
| Prompt | What to answer | Notes |
|
||||
|--------|---------------|-------|
|
||||
| `Do you want to reconfigure? (y/N):` | `y` | Only appears if `skill-config.json` already exists. |
|
||||
| `Enter your API key:` | Paste the user's API key | The `cc_live_...` key from the user. |
|
||||
| `Auto-detect names from workspace? (Y/n):` | `Y` | Pulls owner name from `USER.md`, agent name from `IDENTITY.md`. |
|
||||
| `Add sessions_send to gateway.tools.allow? (Y/n):` | `Y` | Only appears if not already allowed. Required for voice calls. |
|
||||
|
||||
If the script asks for a name manually (auto-detect failed), use the owner's name from `USER.md` and your agent name from `IDENTITY.md`.
|
||||
|
||||
### What Can Go Wrong
|
||||
|
||||
| Problem | Symptom | Fix |
|
||||
|---------|---------|-----|
|
||||
| Ran with `zsh` instead of `bash` | `read:XX: -p: no coprocess` error | Re-run with `bash ./setup.sh` |
|
||||
| No PTY | Script exits immediately or hangs | Use `pty=true` when spawning the process |
|
||||
| No API key provided | Setup completes but key is `null` | Edit `skill-config.json` and add the key, or re-run setup |
|
||||
| Gateway config not found | Warning about manual steps | Check `~/.openclaw/openclaw.json` or `~/.clawdbot/clawdbot.json` exists |
|
||||
| `jq` not installed | Script fails at requirements check | Install jq: `brew install jq` (macOS) or `apt install jq` (Linux) |
|
||||
| `node` not installed | Script fails at requirements check | Install Node.js |
|
||||
|
||||
## Step 5: Verify and Clean Up `skill-config.json`
|
||||
|
||||
After setup completes, read `skill-config.json` and check:
|
||||
|
||||
1. **`api_key`** — should be the user's key (not `null`, not `YOUR_API_KEY_HERE`)
|
||||
2. **`agent_name`** — should be clean (e.g. `"PAL-01"`, not `"PAL-01 (some extra description)"`)
|
||||
3. **`gateway_url`** — should be `http://127.0.0.1:<port>` matching the gateway config
|
||||
4. **`gateway_token`** — should be populated
|
||||
5. **`agent_id`** — should be `"main"` (or the default agent's ID)
|
||||
|
||||
If `agent_name` has extra text from IDENTITY.md, trim it to just the name. The setup script's grep can be greedy.
|
||||
|
||||
### Example of a Clean Config
|
||||
|
||||
```json
|
||||
{
|
||||
"api_key": "cc_live_xxxxxxxxxxxxxxxxxxxx",
|
||||
"server": "https://clawdtalk.com",
|
||||
"owner_name": "Ciaran",
|
||||
"agent_name": "PAL-01",
|
||||
"greeting": "Hey Ciaran, what's up?",
|
||||
"gateway_url": "http://127.0.0.1:18789",
|
||||
"gateway_token": "xxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"agent_id": "main"
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Start the WebSocket Connection
|
||||
|
||||
```bash
|
||||
cd <skill_directory>
|
||||
bash ./scripts/connect.sh start
|
||||
```
|
||||
|
||||
This starts a background Node.js process that maintains a WebSocket connection to the ClawdTalk server. It enables:
|
||||
- **Inbound voice calls** — user calls their ClawdTalk number, speech is transcribed and routed to your main agent session
|
||||
- **Outbound voice calls** — you can initiate calls via the scripts
|
||||
- **Approval requests** — biometric/push approvals during voice calls
|
||||
|
||||
The process runs in the background and writes logs to `.connect.log` in the skill directory.
|
||||
|
||||
### What Can Go Wrong
|
||||
|
||||
| Problem | Symptom | Fix |
|
||||
|---------|---------|-----|
|
||||
| Config missing | `❌ Configuration not found. Run './setup.sh' first.` | Run setup.sh first |
|
||||
| API key invalid | Connection drops immediately, auth errors in `.connect.log` | User needs to regenerate key at clawdtalk.com |
|
||||
| `sessions_send` not allowed | Voice calls connect but agent can't process requests (silent 404s) | Add `sessions_send` to `gateway.tools.allow` and restart gateway |
|
||||
| Port/token changed | Connection fails or agent responses don't work | Re-run `setup.sh` to update config |
|
||||
| Node modules missing | `Cannot find module 'ws'` in logs | Run `npm install --production` in the skill directory |
|
||||
| Already running | `⚠️ Connection already running` | That's fine, it's idempotent. Use `restart` if you need to refresh. |
|
||||
|
||||
## Step 7: Verify Everything
|
||||
|
||||
Run the status script:
|
||||
|
||||
```bash
|
||||
cd <skill_directory>
|
||||
bash ./status.sh
|
||||
```
|
||||
|
||||
You should see:
|
||||
- **API Key**: ✅ with a masked key
|
||||
- **WebSocket Connection**: ✅ CONNECTED with a PID
|
||||
- **Gateway Status**: ✅ RUNNING
|
||||
|
||||
If anything shows ❌, check the troubleshooting table above for that component.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting: "Bot not connected" / Connection Errors
|
||||
|
||||
If the user reports a connection error from the ClawdTalk portal (e.g. "Bot not connected. Make sure your Clawdbot is running with the ClawdTalk skill installed and your API key configured."), the problem is almost always the WebSocket connection.
|
||||
|
||||
### Step 1: Check WebSocket Status
|
||||
|
||||
```bash
|
||||
cd <skill_directory>
|
||||
bash ./scripts/connect.sh status
|
||||
```
|
||||
|
||||
If it shows `DISCONNECTED` or a stale PID, restart it:
|
||||
|
||||
```bash
|
||||
bash ./scripts/connect.sh restart
|
||||
```
|
||||
|
||||
### Step 2: Check WebSocket Logs
|
||||
|
||||
The logs are the single best source of truth for connection issues:
|
||||
|
||||
```bash
|
||||
# Last 30 lines
|
||||
tail -30 <skill_directory>/.connect.log
|
||||
|
||||
# Follow live (useful during debugging)
|
||||
tail -f <skill_directory>/.connect.log
|
||||
```
|
||||
|
||||
### Common Log Errors and Fixes
|
||||
|
||||
| Log error | Cause | Fix |
|
||||
|-----------|-------|-----|
|
||||
| `Authentication failed` / `401` | API key is invalid or expired | User regenerates key at clawdtalk.com, re-run `setup.sh` |
|
||||
| `ECONNREFUSED 127.0.0.1:<port>` | Gateway is not running | Start the gateway: `openclaw gateway start` |
|
||||
| `Cannot find module 'ws'` | Node dependencies not installed | `cd <skill_dir> && npm install --production` |
|
||||
| `Token mismatch` / `403` from gateway | Gateway token in config doesn't match actual token | Re-run `setup.sh` to refresh gateway details |
|
||||
| `Connection closed` / repeated reconnects | Server-side issue or network instability | Check the backoff pattern in logs. If it stabilises, it's transient. If not, check the API key. |
|
||||
| `sessions_send` 404 | Tool not allowed on gateway HTTP API | Add `sessions_send` to `gateway.tools.allow` and restart gateway |
|
||||
|
||||
### Step 3: Verify from the Portal
|
||||
|
||||
After fixing the issue and confirming `connect.sh status` shows `CONNECTED`, have the user go back to the ClawdTalk portal and click **Test Connection** (or **Retry**). It should go green.
|
||||
|
||||
---
|
||||
|
||||
## Gateway Requirements (Important)
|
||||
|
||||
The voice system works by routing transcribed speech to your main agent session via the Gateway HTTP tools API. Two things must be true:
|
||||
|
||||
### 1. `sessions_send` Must Be Allowed
|
||||
|
||||
The gateway blocks `sessions_send` over HTTP by default (security measure). Without it, voice calls connect but the AI hears you and can't do anything.
|
||||
|
||||
`setup.sh` handles this automatically. If it didn't (or was skipped), add it manually:
|
||||
|
||||
```bash
|
||||
openclaw config patch '{"gateway":{"tools":{"allow":["sessions_send"]}}}'
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
**WARNING**: This goes under `gateway.tools.allow`, NOT top-level `tools.allow`. Putting it at the top level restricts your agent to ONLY that tool, breaking everything.
|
||||
|
||||
### 2. Voice Agent Must Exist
|
||||
|
||||
`setup.sh` adds a `voice` agent to the gateway's `agents.list`. If it wasn't added (check with `jq '.agents.list[] | select(.id == "voice")' ~/.openclaw/openclaw.json`), the gateway needs a restart after setup adds it.
|
||||
|
||||
---
|
||||
|
||||
## After Setup: What's Available
|
||||
|
||||
Once setup is complete and the WebSocket is connected, the user can:
|
||||
- **Receive voice calls** on their ClawdTalk number (calls route to you, the agent)
|
||||
- **Make outbound calls** via `./scripts/call.sh`
|
||||
- **Send SMS** via `./scripts/sms.sh`
|
||||
- **Run AI Missions** via `python3 scripts/telnyx_api.py` (multi-step outreach campaigns)
|
||||
|
||||
Full usage docs are in `SKILL.md` within the skill directory.
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: Key Paths
|
||||
|
||||
| What | Path |
|
||||
|------|------|
|
||||
| Skill directory | `<workspace>/skills/clawdtalk-client/` |
|
||||
| Config | `<skill_dir>/skill-config.json` |
|
||||
| Setup script | `<skill_dir>/setup.sh` |
|
||||
| Connect script | `<skill_dir>/scripts/connect.sh` |
|
||||
| Status script | `<skill_dir>/status.sh` |
|
||||
| WebSocket logs | `<skill_dir>/.connect.log` |
|
||||
| PID file | `<skill_dir>/.connect.pid` |
|
||||
| Full skill docs | `<skill_dir>/SKILL.md` |
|
||||
| Gateway config (OpenClaw) | `~/.openclaw/openclaw.json` |
|
||||
| Gateway config (Clawdbot) | `~/.clawdbot/clawdbot.json` |
|
||||
824
skills/clawdtalk-client/SKILL.md
Normal file
824
skills/clawdtalk-client/SKILL.md
Normal file
@@ -0,0 +1,824 @@
|
||||
---
|
||||
name: clawdtalk-client
|
||||
version: 2.0.0
|
||||
description: ClawdTalk — Voice calls, SMS, and AI Missions for Clawdbot
|
||||
metadata: {"clawdbot":{"emoji":"📞","primaryEnv":"CLAWDTALK_API_KEY","homepage":"https://github.com/team-telnyx/clawdtalk-client","requires":{"env":["CLAWDTALK_API_KEY"],"bins":["bash","node","jq","python3"],"config":["skill-config.json","~/.openclaw/openclaw.json","~/.clawdbot/clawdbot.json"]}}}
|
||||
---
|
||||
|
||||
# ClawdTalk
|
||||
|
||||
> ⚠️ **First time setup?** Read `SETUP.md` in this directory before anything else. It walks you through the complete configuration flow step by step.
|
||||
|
||||
Voice calling, SMS messaging, and AI Missions for Clawdbot. Call your bot by phone, send texts, or run autonomous multi-step outreach campaigns — powered by ClawdTalk.
|
||||
|
||||
> **Trust:** By using this skill, voice transcripts, SMS messages, and mission data are sent to clawdtalk.com (operated by Telnyx). Only install if you trust this service with your conversation data.
|
||||
|
||||
## External Endpoints
|
||||
|
||||
| Endpoint | Used by | Data sent |
|
||||
|----------|---------|-----------|
|
||||
| `https://clawdtalk.com` (WebSocket) | `ws-client.js` | Voice transcripts, tool results, conversation state |
|
||||
| `https://clawdtalk.com/v1/*` | `telnyx_api.py` | Mission state, events, scheduled calls/SMS, assistant configs |
|
||||
| `http://127.0.0.1:<port>` | `ws-client.js` | Transcribed speech (local gateway only) |
|
||||
| `https://raw.githubusercontent.com/team-telnyx/clawdtalk-client/...` | `update.sh` | None (download only) |
|
||||
|
||||
## Security & Privacy
|
||||
|
||||
- Voice transcripts and SMS content are transmitted to clawdtalk.com.
|
||||
- Mission state and events are stored server-side for tracking and insights.
|
||||
- `setup.sh` reads gateway config to extract connection details; with confirmation it adds a voice agent and `sessions_send` to `gateway.tools.allow`.
|
||||
- API key is stored in `skill-config.json` — use env var `CLAWDTALK_API_KEY` or a `${CLAWDTALK_API_KEY}` reference to avoid plaintext storage.
|
||||
|
||||
---
|
||||
|
||||
# ⚠️ CRITICAL: SLUG CONSISTENCY
|
||||
|
||||
`init` auto-generates a slug from the mission name (lowercased, spaces → hyphens).
|
||||
**Every command that takes a slug (`setup-agent`, `save-memory`, `complete`) MUST use the EXACT same slug.**
|
||||
|
||||
Mismatched slugs = agent not linked = scheduled events invisible on the frontend.
|
||||
|
||||
```bash
|
||||
# After init, ALWAYS confirm the slug:
|
||||
python scripts/telnyx_api.py list-state
|
||||
# Output: find-window-washing-contractors: Find window washing contractors [running]
|
||||
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ COPY-PASTE THIS. NEVER ABBREVIATE.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# ⚠️ CRITICAL: YOU OWN THE MISSION LIFECYCLE
|
||||
|
||||
**The server does NOT automatically update plan steps or mission status.** That is YOUR job as the bot. If you don't update steps and complete the mission, the UI will show "Running" forever with all steps "Pending".
|
||||
|
||||
## Your Responsibilities
|
||||
|
||||
For every mission, YOU must:
|
||||
1. **Update plan steps** — mark each step `in_progress` → `completed` (or `failed`)
|
||||
2. **Log events** — after every significant action
|
||||
3. **Poll for completion** — check if scheduled calls/SMS finished
|
||||
4. **Complete the mission** — mark the run as `succeeded` or `failed`
|
||||
5. **Clean up** — remove any polling cron jobs when the mission ends
|
||||
|
||||
The UI reflects exactly what you tell it. No updates from you = no updates on screen.
|
||||
|
||||
---
|
||||
|
||||
# 🚨 MANDATORY: SAVE TO MEMORY AFTER EVERY ACTION
|
||||
|
||||
**Every significant action MUST be persisted using `save-memory` or `append-memory` IMMEDIATELY after the action succeeds.** The frontend reads from server memory. If you don't save it, it doesn't show up. `log-event` alone is NOT enough.
|
||||
|
||||
> **Rule: If you did something, save it to memory. No exceptions. No "I'll do it later." Do it NOW.**
|
||||
|
||||
Example (scheduling an SMS):
|
||||
```bash
|
||||
# 1. Schedule it
|
||||
python scripts/telnyx_api.py schedule-sms $AID "$TO" "$FROM" "$DATETIME" "$MESSAGE" $MID $RID $STEP_ID
|
||||
|
||||
# 2. IMMEDIATELY save to memory
|
||||
python scripts/telnyx_api.py append-memory "$SLUG" "scheduled_events" \
|
||||
'{"event_id": "<id>", "type": "sms", "to": "<to>", "message": "<msg>", "scheduled_at": "<dt>", "step_id": "<step>"}'
|
||||
|
||||
# 3. Then log the event
|
||||
python scripts/telnyx_api.py log-event $MID $RID custom "Scheduled SMS event_id=<id>" $STEP_ID
|
||||
```
|
||||
|
||||
**Skipping step 2 is the #1 cause of "nothing shows on the frontend" bugs.**
|
||||
|
||||
---
|
||||
|
||||
# 🚨 MANDATORY: CHECK MISSION STATUS AFTER EVERY STEP
|
||||
|
||||
**After completing or failing ANY step, you MUST check whether the mission should be completed or failed.** Never leave a mission in "running" state when it's actually done or dead.
|
||||
|
||||
> **Rule: After every step change, ask yourself: is this mission finished?**
|
||||
|
||||
### Decision tree (run after EVERY step):
|
||||
|
||||
```
|
||||
Step finished →
|
||||
├── Succeeded?
|
||||
│ ├── All steps done? → COMPLETE MISSION (update-run succeeded)
|
||||
│ ├── More steps remain? → Continue to next step
|
||||
│ └── Only verify left? → Set up polling cron
|
||||
└── Failed?
|
||||
├── Recoverable (retry/reschedule)? → Retry
|
||||
└── Unrecoverable? → FAIL MISSION NOW:
|
||||
1. update-step <step_id> failed
|
||||
2. log-event error "Failed: <reason>" <step_id>
|
||||
3. save-memory "$SLUG" "error_<step_id>" '{"error": "...", "recoverable": false}'
|
||||
4. update-run $MID $RID failed
|
||||
5. save-memory "$SLUG" "result" '{"status": "failed", "reason": "...", "failed_step": "..."}'
|
||||
6. Clean up any polling cron jobs
|
||||
```
|
||||
|
||||
**A mission stuck in "running" when it's actually done or dead is a bug. The user sees it and thinks work is still happening.**
|
||||
|
||||
---
|
||||
|
||||
# Architecture Overview
|
||||
|
||||
## How It Fits Together
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
|
||||
│ You (Bot) │────▶│ ClawdTalk Server │────▶│ Telnyx API │
|
||||
│ │ │ (dev/prod) │ │ (cloud) │
|
||||
│ telnyx_api.py│ │ Local DB + proxy │ │ Executes │
|
||||
│ │ │ to Telnyx │ │ calls/SMS │
|
||||
└──────────────┘ └──────────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
- **telnyx_api.py** — Python CLI script. Every command you run goes through this. It talks to the ClawdTalk server, never directly to Telnyx.
|
||||
- **ClawdTalk Server** — Node.js backend. Stores missions, assistants, and events in a local Postgres DB. Proxies requests to the Telnyx API.
|
||||
- **Telnyx API** — Cloud service. Actually makes the calls and sends the SMS at the scheduled time.
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `scripts/telnyx_api.py` | CLI tool for all mission/assistant/event operations |
|
||||
| `scripts/connect.sh` | WebSocket client for inbound voice call routing |
|
||||
| `skill-config.json` | API key and server URL |
|
||||
| `.missions_state.json` | Local state tracking for active missions |
|
||||
| `.connect.log` | WebSocket connection logs |
|
||||
|
||||
## Two Sets of IDs
|
||||
|
||||
Every entity exists in two places with different IDs:
|
||||
- **Local DB ID** — what the ClawdTalk server returns (e.g. `3df24dde-...`)
|
||||
- **Telnyx ID** — what the Telnyx API uses internally
|
||||
|
||||
The script always works with local IDs. You don't need to worry about Telnyx IDs.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Sign up** at [clawdtalk.com](https://clawdtalk.com)
|
||||
2. **Add your phone** in Settings
|
||||
3. **Get API key** from Dashboard
|
||||
4. **Run setup**: `./setup.sh`
|
||||
> `setup.sh` reads your gateway config to extract connection details, adds a voice agent to `agents.list`, and (with confirmation) adds `sessions_send` to `gateway.tools.allow`. Gateway config is at `~/.openclaw/openclaw.json` or `~/.clawdbot/clawdbot.json`.
|
||||
5. **Start connection**: `./scripts/connect.sh start`
|
||||
|
||||
## Voice Calls
|
||||
|
||||
The WebSocket client routes calls to your gateway's main agent session, giving full access to memory, tools, and context.
|
||||
|
||||
```bash
|
||||
./scripts/connect.sh start # Start connection
|
||||
./scripts/connect.sh stop # Stop
|
||||
./scripts/connect.sh status # Check status
|
||||
```
|
||||
|
||||
### Outbound Calls
|
||||
|
||||
Have the bot call you or others:
|
||||
|
||||
```bash
|
||||
./scripts/call.sh # Call your phone
|
||||
./scripts/call.sh "Hey, what's up?" # Call with greeting
|
||||
./scripts/call.sh --to +15551234567 # Call external number*
|
||||
./scripts/call.sh --to +15551234567 "Hello!" # External with greeting
|
||||
./scripts/call.sh status <call_id> # Check call status
|
||||
./scripts/call.sh end <call_id> # End call
|
||||
```
|
||||
|
||||
*External calls require a paid account with a dedicated number. The AI will operate in privacy mode when calling external numbers (won't reveal your private info).
|
||||
|
||||
## SMS
|
||||
|
||||
Send and receive text messages:
|
||||
|
||||
```bash
|
||||
./scripts/sms.sh send +15551234567 "Hello!"
|
||||
./scripts/sms.sh list
|
||||
./scripts/sms.sh conversations
|
||||
```
|
||||
|
||||
## AI Missions (Full Tracking via Python)
|
||||
|
||||
For complex, multi-step missions with full tracking, state persistence, retries, and conversation insights, use the Python-based missions API.
|
||||
|
||||
**Required**: Python 3.7+, `CLAWDTALK_API_KEY` environment variable. Optionally set `CLAWDTALK_API_URL` to override the default endpoint (defaults to `https://clawdtalk.com/v1`).
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py check-key # Verify setup
|
||||
```
|
||||
|
||||
# CRITICAL: SAVE STATE FREQUENTLY
|
||||
|
||||
**You MUST save your progress after EVERY significant action.** If the session crashes or restarts, unsaved work is LOST.
|
||||
|
||||
## Two-Layer Persistence: Memory + Events
|
||||
|
||||
Always save to BOTH:
|
||||
1. **Local Memory** (`.missions_state.json`) - Fast, survives restarts
|
||||
2. **Events API** (cloud) - Permanent audit trail, survives local file loss
|
||||
|
||||
## When to Save (After EVERY action!)
|
||||
|
||||
| Action | Save Memory | Log Event |
|
||||
|--------|-------------|-----------|
|
||||
| Web search returns results | append-memory | log-event (tool_call) |
|
||||
| Found a contractor/lead | append-memory | log-event (custom) |
|
||||
| Created assistant | save-memory | log-event (custom) |
|
||||
| Assigned phone number | save-memory | log-event (custom) |
|
||||
| Scheduled a call/SMS | append-memory | log-event (custom) |
|
||||
| Call completed | save-memory | log-event (custom) |
|
||||
| Got quote/insight | save-memory | log-event (custom) |
|
||||
| Made a decision | save-memory | log-event (message) |
|
||||
| Step started | save-memory | update-step (in_progress) + log-event (step_started) |
|
||||
| Step completed | save-memory | update-step (completed) + log-event (step_completed) |
|
||||
| Step failed | save-memory | update-step (failed) + log-event (error) |
|
||||
| Error occurred | save-memory | log-event (error) |
|
||||
|
||||
## Memory Commands (Local Backup)
|
||||
|
||||
```bash
|
||||
# Save a single value
|
||||
python scripts/telnyx_api.py save-memory "<slug>" "key" '{"data": "value"}'
|
||||
|
||||
# Append to a list (great for collecting multiple items)
|
||||
python scripts/telnyx_api.py append-memory "<slug>" "contractors" '{"name": "ABC Co", "phone": "+1234567890"}'
|
||||
|
||||
# Retrieve memory
|
||||
python scripts/telnyx_api.py get-memory "<slug>" # Get all memory
|
||||
python scripts/telnyx_api.py get-memory "<slug>" "key" # Get specific key
|
||||
```
|
||||
|
||||
## Event Commands (Cloud Backup)
|
||||
|
||||
```bash
|
||||
# Log an event (step_id is REQUIRED - links event to a plan step)
|
||||
python scripts/telnyx_api.py log-event <mission_id> <run_id> <type> "<summary>" <step_id> '[payload_json]'
|
||||
|
||||
# Event types: tool_call, custom, message, error, step_started, step_completed
|
||||
# step_id: Use the step_id from your plan (e.g., "research", "setup", "calls")
|
||||
# Use "-" if event doesn't belong to a specific step
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## When to Use Missions
|
||||
|
||||
This skill has two modes: **full missions** (tracked, multi-step) and **simple calls** (one-off, no mission overhead). Pick the right one.
|
||||
|
||||
### Use a Full Mission When:
|
||||
- The task involves **multiple calls or SMS** (batch outreach, surveys, sweeps)
|
||||
- You need a **complete audit trail** with events, plans, and state tracking
|
||||
- The task is **multi-step** and takes significant effort across phases
|
||||
- **Retries and failure tracking** matter
|
||||
- You need to **compare results** across multiple calls
|
||||
|
||||
Examples:
|
||||
- "Find me window washing contractors in Chicago, call them and negotiate rates"
|
||||
- "Contact all leads in this list and schedule demos"
|
||||
- "Call 10 weather stations and find the hottest one"
|
||||
|
||||
### Do NOT Use a Mission When:
|
||||
- The task is a **single outbound call** — just create an assistant (or reuse one) and schedule the call directly
|
||||
- It's a **one-off SMS** — schedule it and done
|
||||
- The task doesn't need tracking, plans, or state recovery
|
||||
- You'd be creating a mission with one step and one call — that's overengineering
|
||||
|
||||
**For simple calls, just:**
|
||||
```bash
|
||||
# Reuse or create an assistant
|
||||
python scripts/telnyx_api.py list-assistants --name=<relevant>
|
||||
# Schedule the call
|
||||
python scripts/telnyx_api.py schedule-call <assistant_id> <to> <from> <datetime> <mission_id> <run_id>
|
||||
# Poll for completion
|
||||
python scripts/telnyx_api.py get-event <assistant_id> <event_id>
|
||||
# Get insights
|
||||
python scripts/telnyx_api.py get-insights <conversation_id>
|
||||
```
|
||||
|
||||
No mission, no run, no plan. Keep it simple.
|
||||
|
||||
## State Persistence
|
||||
|
||||
The script automatically manages state in `.missions_state.json`. This survives restarts and supports multiple concurrent missions.
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py list-state # List all active missions
|
||||
python scripts/telnyx_api.py get-state "find-window-washing-contractors" # Get state for specific mission
|
||||
python scripts/telnyx_api.py remove-state "find-window-washing-contractors" # Remove mission from state
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Core Workflow
|
||||
|
||||
## Phase 1: Initialize Tracking
|
||||
|
||||
### Step 1.1: Create a Mission
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py create-mission "Brief descriptive name" "Full description of the task"
|
||||
```
|
||||
|
||||
**Save the returned `mission_id`** - you'll need it for all subsequent calls.
|
||||
|
||||
### Step 1.2: Start a Run
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py create-run <mission_id> '{"original_request": "The exact user request", "context": "Any relevant context"}'
|
||||
```
|
||||
|
||||
**Save the returned `run_id`**.
|
||||
|
||||
### Step 1.3: Create a Plan
|
||||
|
||||
Before executing, outline your plan:
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py create-plan <mission_id> <run_id> '[
|
||||
{"step_id": "step_1", "description": "Research contractors online", "sequence": 1},
|
||||
{"step_id": "step_2", "description": "Create voice agent for calls", "sequence": 2},
|
||||
{"step_id": "step_3", "description": "Schedule calls to each contractor", "sequence": 3},
|
||||
{"step_id": "step_4", "description": "Monitor call completions", "sequence": 4},
|
||||
{"step_id": "step_5", "description": "Analyze results and select best options", "sequence": 5}
|
||||
]'
|
||||
```
|
||||
|
||||
### Step 1.4: Set Run to Running
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py update-run <mission_id> <run_id> running
|
||||
```
|
||||
|
||||
### High-Level Alternative: Initialize Everything at Once
|
||||
|
||||
Use the `init` command to create mission, run, plan, and set status in one step:
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py init "Find window washing contractors" "Find contractors in Chicago, call them, negotiate rates" "User wants window washing quotes" '[
|
||||
{"step_id": "research", "description": "Find contractors online", "sequence": 1},
|
||||
{"step_id": "setup", "description": "Create voice agent", "sequence": 2},
|
||||
{"step_id": "calls", "description": "Schedule and make calls", "sequence": 3},
|
||||
{"step_id": "analyze", "description": "Analyze results", "sequence": 4}
|
||||
]'
|
||||
```
|
||||
|
||||
This also automatically resumes if a mission with the same name already exists.
|
||||
|
||||
**⚠️ Immediately after `init`, run `list-state` and copy the exact slug. Use it for ALL subsequent commands.**
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Voice/SMS Agent Setup
|
||||
|
||||
When your task requires making calls or sending SMS, create an AI assistant first.
|
||||
|
||||
### Step 2.1: Create a Voice/SMS Assistant
|
||||
|
||||
**For phone calls:**
|
||||
```bash
|
||||
python scripts/telnyx_api.py create-assistant "Contractor Outreach Agent" "You are calling on behalf of [COMPANY]. Your goal is to [SPECIFIC GOAL]. Be professional and concise. Collect: [WHAT TO COLLECT]. If they cannot talk now, ask for a good callback time." "Hi, this is an AI assistant calling on behalf of [COMPANY]. Is this [BUSINESS NAME]? I am calling to inquire about your services. Do you have a moment?" '["telephony", "messaging"]'
|
||||
```
|
||||
|
||||
**For SMS:**
|
||||
```bash
|
||||
python scripts/telnyx_api.py create-assistant "SMS Outreach Agent" "You send SMS messages to collect information. Keep messages brief and professional." "Hi! I am reaching out on behalf of [COMPANY] regarding [PURPOSE]. Could you please reply with [REQUESTED INFO]?" '["telephony", "messaging"]'
|
||||
```
|
||||
|
||||
**Save the returned `assistant_id`**.
|
||||
|
||||
### Step 2.2: Find and Assign a Phone Number
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py get-available-phone # Get first available
|
||||
python scripts/telnyx_api.py get-connection-id <assistant_id> telephony # Get connection ID
|
||||
python scripts/telnyx_api.py assign-phone <phone_number_id> <connection_id> voice # Assign
|
||||
```
|
||||
|
||||
### High-Level Alternative: Setup Agent in One Step
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py setup-agent "find-window-washing-contractors" "Contractor Caller" "You are calling to get quotes for commercial window washing. Ask about: rates per floor, availability, insurance. Be professional." "Hi, I am calling to inquire about your commercial window washing services. Do you have a moment to discuss rates?"
|
||||
```
|
||||
|
||||
This automatically creates the assistant, links it to the mission run, finds an available phone number, assigns it, and saves all IDs to the state file.
|
||||
|
||||
**⚠️ The slug MUST match what `init` created. If it doesn't, the agent won't be linked and scheduled events won't appear on the frontend.**
|
||||
|
||||
**Verify linking worked immediately after:**
|
||||
```bash
|
||||
python scripts/telnyx_api.py list-linked-agents <mission_id> <run_id>
|
||||
# Must show your assistant_id. If empty → slug was wrong. Fix with:
|
||||
python scripts/telnyx_api.py link-agent <mission_id> <run_id> <assistant_id>
|
||||
```
|
||||
|
||||
### Step 2.3: Link Agent to Mission Run
|
||||
|
||||
**If using `setup-agent`**: Linking is done automatically (only if slug matches `init`).
|
||||
|
||||
**If setting up manually**:
|
||||
```bash
|
||||
python scripts/telnyx_api.py link-agent <mission_id> <run_id> <assistant_id>
|
||||
python scripts/telnyx_api.py list-linked-agents <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py unlink-agent <mission_id> <run_id> <assistant_id>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Scheduling Calls/SMS
|
||||
|
||||
### Business Hours Consideration
|
||||
|
||||
**CRITICAL**: Before scheduling calls, consider business hours (9 AM - 5 PM local time). `scheduled_at` must be in the future (at least 1 minute from now).
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py schedule-call <assistant_id> "+15551234567" "+15559876543" "2024-12-01T14:30:00Z" <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py schedule-sms <assistant_id> "+15551234567" "+15559876543" "2024-12-01T14:30:00Z" "Your message here"
|
||||
```
|
||||
|
||||
**Save the returned event `id`**.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Monitoring Call Completion
|
||||
|
||||
### ⚠️ THIS IS THE MOST IMPORTANT PART
|
||||
|
||||
After scheduling a call or SMS, **Telnyx executes it autonomously** at the scheduled time. You need to **poll** to find out when it's done, then update the mission accordingly.
|
||||
|
||||
### Check Scheduled Event Status
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py get-event <assistant_id> <event_id>
|
||||
```
|
||||
|
||||
### Use Cron Jobs to Poll
|
||||
|
||||
Use your bot's cron system to schedule polling. **Do NOT block the main session waiting.** Match the poll interval to the expected wait time:
|
||||
|
||||
| Expected completion | Poll interval | Example |
|
||||
|---|---|---|
|
||||
| < 5 minutes | Every 30 seconds | SMS sent 1 min from now |
|
||||
| 5–30 minutes | Every 2–5 minutes | Call scheduled in 15 min |
|
||||
| 1–24 hours | Every 15–30 minutes | Call scheduled for tonight |
|
||||
| Days/weeks | Every 4–8 hours | Call scheduled for next week |
|
||||
|
||||
**If you know the exact scheduled time**, don't start polling until after that time. Schedule your first poll for `scheduled_time + 2 minutes`.
|
||||
|
||||
### Cron Job Pattern
|
||||
|
||||
When you schedule a call/SMS, create a cron job to poll for it:
|
||||
|
||||
```
|
||||
Create cron: poll at appropriate interval
|
||||
→ Run get-event <assistant_id> <event_id>
|
||||
→ If completed: update step, log event, complete mission if last step, DELETE THIS CRON
|
||||
→ If failed: update step as failed, log error, DELETE THIS CRON
|
||||
→ If pending/in_progress: do nothing, cron runs again at next interval
|
||||
```
|
||||
|
||||
**⚠️ ALWAYS clean up cron jobs when a mission reaches a terminal state (completed, failed, cancelled). Never leave polling crons running after a mission ends.**
|
||||
|
||||
### Adjusting Poll Frequency
|
||||
|
||||
You can update the cron interval as circumstances change:
|
||||
- Scheduled for 2 weeks from now? Start with 8-hour polling.
|
||||
- As the scheduled time approaches (within 1 hour), tighten to 5-minute intervals.
|
||||
- After the scheduled time passes, tighten to 30-second intervals.
|
||||
|
||||
### Event Status Values
|
||||
|
||||
| Status | Meaning | Action |
|
||||
|--------|---------|--------|
|
||||
| `pending` | Waiting for scheduled time | Keep polling |
|
||||
| `in_progress` | Call/SMS in progress | Keep polling |
|
||||
| `completed` | Finished successfully | Update step, get insights if call |
|
||||
| `failed` | Failed after retries | Update step as failed, consider retry |
|
||||
|
||||
### Call Status Values (Phone Calls Only)
|
||||
|
||||
| call_status | Meaning | Action |
|
||||
|-------------|---------|--------|
|
||||
| `ringing` | Phone is ringing | Poll again in 1-2 minutes |
|
||||
| `in-progress` | Call is active | Poll again in 2-3 minutes |
|
||||
| `completed` | Call finished normally | Get insights |
|
||||
| `no-answer` | Nobody picked up | **Retryable** — reschedule |
|
||||
| `busy` | Line is busy | **Retryable** — retry in 10-15 min |
|
||||
| `canceled` | Call was canceled | Check if intentional |
|
||||
| `failed` | Network/system error | **Retryable** — retry in 5-10 min |
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Getting Conversation Insights
|
||||
|
||||
Once a call completes with a `conversation_id`, retrieve insights. **Poll until status is "completed"** (wait 10 seconds between retries).
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py get-insights <conversation_id>
|
||||
```
|
||||
|
||||
Telnyx automatically creates default insight templates when an assistant is created. You don't need to manage these — just read the results.
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Complete the Mission
|
||||
|
||||
```bash
|
||||
python scripts/telnyx_api.py update-run <mission_id> <run_id> succeeded
|
||||
|
||||
# Or with full results:
|
||||
python scripts/telnyx_api.py complete "find-window-washing-contractors" <mission_id> <run_id> "Summary of results" '{"key": "payload"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Event Logging Reference
|
||||
|
||||
**Log EVERY action as an event.** Always update step status via `update-step` AND log corresponding events.
|
||||
|
||||
```bash
|
||||
# When STARTING a step:
|
||||
python scripts/telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "research" "in_progress"
|
||||
python scripts/telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" step_started "Starting: Research" "research"
|
||||
|
||||
# When COMPLETING a step:
|
||||
python scripts/telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "research" "completed"
|
||||
python scripts/telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" step_completed "Completed: Research" "research"
|
||||
|
||||
# When a step FAILS:
|
||||
python scripts/telnyx_api.py update-step "$MISSION_ID" "$RUN_ID" "calls" "failed"
|
||||
python scripts/telnyx_api.py log-event "$MISSION_ID" "$RUN_ID" error "Failed: Could not reach contractors" "calls"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Quick Reference: All Python Commands
|
||||
|
||||
```bash
|
||||
# Check setup
|
||||
python scripts/telnyx_api.py check-key
|
||||
|
||||
# Missions
|
||||
python scripts/telnyx_api.py create-mission <name> <instructions>
|
||||
python scripts/telnyx_api.py get-mission <mission_id>
|
||||
python scripts/telnyx_api.py list-missions
|
||||
|
||||
# Runs
|
||||
python scripts/telnyx_api.py create-run <mission_id> <input_json>
|
||||
python scripts/telnyx_api.py get-run <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py update-run <mission_id> <run_id> <status>
|
||||
python scripts/telnyx_api.py list-runs <mission_id>
|
||||
|
||||
# Plan
|
||||
python scripts/telnyx_api.py create-plan <mission_id> <run_id> <steps_json>
|
||||
python scripts/telnyx_api.py get-plan <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py update-step <mission_id> <run_id> <step_id> <status>
|
||||
|
||||
# Events
|
||||
python scripts/telnyx_api.py log-event <mission_id> <run_id> <type> <summary> <step_id> [payload_json]
|
||||
python scripts/telnyx_api.py list-events <mission_id> <run_id>
|
||||
|
||||
# Assistants
|
||||
python scripts/telnyx_api.py list-assistants [--name=<filter>] [--page=<n>] [--size=<n>]
|
||||
python scripts/telnyx_api.py create-assistant <name> <instructions> <greeting> [options_json]
|
||||
python scripts/telnyx_api.py get-assistant <assistant_id>
|
||||
python scripts/telnyx_api.py update-assistant <assistant_id> <updates_json>
|
||||
python scripts/telnyx_api.py get-connection-id <assistant_id> [telephony|messaging]
|
||||
|
||||
# Phone Numbers
|
||||
python scripts/telnyx_api.py list-phones [--available]
|
||||
python scripts/telnyx_api.py get-available-phone
|
||||
python scripts/telnyx_api.py assign-phone <phone_id> <connection_id> [voice|sms]
|
||||
|
||||
# Scheduled Events
|
||||
python scripts/telnyx_api.py schedule-call <assistant_id> <to> <from> <datetime> <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py schedule-sms <assistant_id> <to> <from> <datetime> <text>
|
||||
python scripts/telnyx_api.py get-event <assistant_id> <event_id>
|
||||
python scripts/telnyx_api.py cancel-scheduled-event <assistant_id> <event_id>
|
||||
python scripts/telnyx_api.py list-events-assistant <assistant_id>
|
||||
|
||||
# Insights
|
||||
python scripts/telnyx_api.py get-insights <conversation_id>
|
||||
|
||||
# Mission Run Agents
|
||||
python scripts/telnyx_api.py link-agent <mission_id> <run_id> <telnyx_agent_id>
|
||||
python scripts/telnyx_api.py list-linked-agents <mission_id> <run_id>
|
||||
python scripts/telnyx_api.py unlink-agent <mission_id> <run_id> <telnyx_agent_id>
|
||||
|
||||
# State Management
|
||||
python scripts/telnyx_api.py list-state
|
||||
python scripts/telnyx_api.py get-state <slug>
|
||||
python scripts/telnyx_api.py remove-state <slug>
|
||||
|
||||
# Memory
|
||||
python scripts/telnyx_api.py save-memory <slug> <key> <value_json>
|
||||
python scripts/telnyx_api.py get-memory <slug> [key]
|
||||
python scripts/telnyx_api.py append-memory <slug> <key> <item_json>
|
||||
|
||||
# High-Level Workflows
|
||||
python scripts/telnyx_api.py init <name> <instructions> <request> [steps_json]
|
||||
python scripts/telnyx_api.py setup-agent <slug> <name> <instructions> <greeting>
|
||||
python scripts/telnyx_api.py complete <slug> <mission_id> <run_id> <summary> [payload_json]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Complete Example: SMS Mission with Cron Polling
|
||||
|
||||
Here's the full flow for an SMS mission with proper lifecycle tracking and cron-based polling:
|
||||
|
||||
```bash
|
||||
# 1. Init mission
|
||||
python scripts/telnyx_api.py init "SMS Test 003" \
|
||||
"Send a test SMS to +13322200013" \
|
||||
"SMS test with full tracking" \
|
||||
'[{"step_id": "setup", "description": "Create SMS agent", "sequence": 1},
|
||||
{"step_id": "sms", "description": "Schedule SMS", "sequence": 2},
|
||||
{"step_id": "verify", "description": "Verify delivery", "sequence": 3}]'
|
||||
# Save: mission_id, run_id
|
||||
|
||||
# 2. Step 1: Setup agent
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID setup in_progress
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_started "Starting: Create SMS agent" setup
|
||||
|
||||
python scripts/telnyx_api.py setup-agent "sms-test-003" "SMS Agent" "Send test messages" "Test from bot"
|
||||
# Save: assistant_id, phone_number
|
||||
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID setup completed
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_completed "Completed: Created assistant $ASSISTANT_ID" setup
|
||||
|
||||
# 3. Step 2: Schedule SMS
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID sms in_progress
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_started "Starting: Schedule SMS" sms
|
||||
|
||||
python scripts/telnyx_api.py schedule-sms $ASSISTANT_ID "+13322200013" "$PHONE" "2026-02-19T18:43:00Z" \
|
||||
"What do you call a bear with no teeth? A gummy bear!" \
|
||||
$MISSION_ID $RUN_ID sms
|
||||
# Save: event_id
|
||||
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID sms completed
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_completed "Completed: SMS scheduled" sms
|
||||
|
||||
# 4. Step 3: Verify delivery — CREATE A CRON JOB TO POLL
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID verify in_progress
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_started "Starting: Poll for delivery" verify
|
||||
|
||||
# >>> Create a cron job that fires AFTER the scheduled time <<<
|
||||
# >>> Cron runs: get-event $ASSISTANT_ID $EVENT_ID <<<
|
||||
# >>> On completed: update-step verify completed, log-event, update-run succeeded, DELETE CRON <<<
|
||||
# >>> On failed: update-step verify failed, log-event, update-run failed, DELETE CRON <<<
|
||||
# >>> On pending/in_progress: do nothing, cron fires again next interval <<<
|
||||
|
||||
# 5. (Cron fires, detects completion)
|
||||
python scripts/telnyx_api.py update-step $MISSION_ID $RUN_ID verify completed
|
||||
python scripts/telnyx_api.py log-event $MISSION_ID $RUN_ID step_completed "Completed: SMS delivered" verify
|
||||
python scripts/telnyx_api.py update-run $MISSION_ID $RUN_ID succeeded
|
||||
# DELETE the polling cron job!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Mission Classes
|
||||
|
||||
Not all missions are the same. Identify which class before planning.
|
||||
|
||||
```
|
||||
Does call N depend on results of call N-1?
|
||||
YES -> Is it negotiation (leveraging previous results)?
|
||||
YES -> Class 3: Sequential Negotiation
|
||||
NO -> Does it have distinct rounds with human approval?
|
||||
YES -> Class 4: Multi-Round / Follow-up
|
||||
NO -> Class 5: Information Gathering -> Action
|
||||
NO -> Do you need structured scoring/ranking?
|
||||
YES -> Class 2: Parallel Screening with Rubric
|
||||
NO -> Class 1: Parallel Sweep
|
||||
```
|
||||
|
||||
## Class 1: Parallel Sweep
|
||||
Fan out calls in parallel batches. Same question to many targets. Schedule all calls in one batch (stagger by 1-2 min). Analysis happens after ALL calls complete.
|
||||
|
||||
## Class 2: Parallel Screening with Rubric
|
||||
Fan out calls in parallel with structured scoring criteria. Results are ranked post-hoc via insights.
|
||||
|
||||
## Class 3: Sequential Negotiation
|
||||
Calls MUST run serially. Each call's strategy depends on previous results. Use `update-assistant` between calls to inject context. **Never parallelize these.**
|
||||
|
||||
## Class 4: Multi-Round / Follow-up
|
||||
Two or more distinct phases. Round 1 is broad outreach, human approval gate, then Round 2 targets a subset.
|
||||
|
||||
## Class 5: Information Gathering -> Action
|
||||
Call to find something, then act on it. Early termination when goal is met — cancel remaining calls.
|
||||
|
||||
---
|
||||
|
||||
# Operational Guide
|
||||
|
||||
## Default Tools
|
||||
The `send_dtmf` tool is included by default. Most outbound calls hit an IVR first.
|
||||
|
||||
## IVR Navigation
|
||||
Expect IVRs even when calling businesses. Instruct the assistant to press 0 or say 'representative'.
|
||||
|
||||
## Call Limits and Throttling
|
||||
Stagger calls in batches of 5-10, space scheduled times 1-2 minutes apart, monitor for 429 errors.
|
||||
|
||||
## Answering Machine Detection (AMD)
|
||||
- **Enable** for human contacts (leave voicemail or skip machines)
|
||||
- **Disable** for IVR systems, businesses with phone trees — set action to `continue_assistant`
|
||||
|
||||
## Polling for Results: Use Cron Jobs
|
||||
After scheduling calls, set up a cron job to poll periodically. Don't block the main session.
|
||||
|
||||
## Retry Strategy
|
||||
Track every number's status in mission memory. Retry based on recipient type:
|
||||
- **Automated systems**: retry in 5-15 min, up to 3 times
|
||||
- **Service industry**: retry in 30 min - 2 hours, avoid peak hours
|
||||
- **Professionals**: retry next business day, leave one voicemail max
|
||||
|
||||
---
|
||||
|
||||
## Approval Requests (Sensitive Actions)
|
||||
|
||||
For destructive or sensitive actions during voice calls, request user approval first:
|
||||
|
||||
```bash
|
||||
./scripts/approval.sh request "Delete GitHub repo myproject"
|
||||
./scripts/approval.sh request "Send $500 to John" --biometric
|
||||
./scripts/approval.sh request "Post tweet about X" --details "Full text: ..."
|
||||
```
|
||||
|
||||
**When to request approval:**
|
||||
- Deleting repos, files, or data
|
||||
- Sending money or making purchases
|
||||
- Posting to social media
|
||||
- Sending emails/messages to others
|
||||
- Any irreversible action
|
||||
|
||||
**Response values:**
|
||||
- `approved` → Execute the action, confirm completion
|
||||
- `denied` → Tell user "Okay, I won't do that"
|
||||
- `timeout` → "I didn't get a response, should I try again?"
|
||||
- `no_devices` → Skip approval, action not executed (no mobile app)
|
||||
|
||||
**Example flow in voice call:**
|
||||
1. User: "Delete my test-repo on GitHub"
|
||||
2. You: "I'll need your approval for that. Check your phone."
|
||||
3. Run: `approval.sh request "Delete GitHub repo test-repo"`
|
||||
4. If approved: Delete the repo, then say "Done, test-repo has been deleted"
|
||||
5. If denied: "Got it, I won't delete it"
|
||||
|
||||
## Gateway Requirements
|
||||
|
||||
Voice calls route requests to the main agent via `sessions_send`. This tool is **blocked by default** on the Gateway HTTP tools API. You must explicitly allow it:
|
||||
|
||||
```json5
|
||||
// In openclaw.json → gateway.tools
|
||||
{
|
||||
"gateway": {
|
||||
"tools": {
|
||||
"allow": ["sessions_send"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or via CLI: `openclaw config patch '{"gateway":{"tools":{"allow":["sessions_send"]}}}'`
|
||||
|
||||
Without this, voice calls will connect but the agent won't be able to process any requests (deep tool calls return 404).
|
||||
|
||||
> ⚠️ **WARNING:** This MUST go under `gateway.tools.allow`, NOT top-level `tools.allow`. The top-level `tools.allow` is the agent's tool allowlist — putting `sessions_send` there will restrict your agent to ONLY that tool, breaking everything. If you accidentally did this, remove the top-level `tools.allow` entry and restart.
|
||||
|
||||
## ❌ Common Pitfalls
|
||||
|
||||
| Mistake | Symptom | Fix |
|
||||
|---------|---------|-----|
|
||||
| Different slug for `init` vs `setup-agent` | Scheduled events missing from frontend | `list-state` after `init`, copy-paste slug |
|
||||
| Forgetting `save-memory` after actions | Frontend shows nothing | Save immediately after every action |
|
||||
| Not checking mission status after step changes | Mission stuck "running" forever | Run decision tree after every step |
|
||||
| Leaving polling crons running | Wasted resources, stale polls | Delete cron on any terminal state |
|
||||
| Not verifying `list-linked-agents` after `setup-agent` | Agent not linked, events invisible | Always verify, fix with `link-agent` |
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `skill-config.json`:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `api_key` | API key from clawdtalk.com |
|
||||
| `server` | Server URL (default: `https://clawdtalk.com`) |
|
||||
| `owner_name` | Your name (auto-detected from USER.md) |
|
||||
| `agent_name` | Agent name (auto-detected from IDENTITY.md) |
|
||||
| `greeting` | Custom greeting for inbound calls |
|
||||
|
||||
Environment variables for the Python missions API:
|
||||
- `CLAWDTALK_API_KEY` — your ClawdTalk API key (required for missions)
|
||||
- `CLAWDTALK_API_URL` — override the API endpoint (default: `https://clawdtalk.com/v1`)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Auth failed**: Regenerate API key at clawdtalk.com
|
||||
- **Gateway token/port changed**: Re-run `./setup.sh` to update skill-config.json with the new values
|
||||
- **Empty responses**: Run `./setup.sh` and restart gateway
|
||||
- **Slow responses**: Try a faster model in your gateway config
|
||||
- **Debug mode**: `DEBUG=1 ./scripts/connect.sh restart`
|
||||
- **Missions API key**: Run `python scripts/telnyx_api.py check-key` to verify
|
||||
- **JSON parsing errors**: Use single quotes around JSON arguments
|
||||
37
skills/clawdtalk-client/_meta.json
Normal file
37
skills/clawdtalk-client/_meta.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"owner": "dcasem",
|
||||
"slug": "clawdtalk-client",
|
||||
"displayName": "ClawdTalk",
|
||||
"latest": {
|
||||
"version": "2.0.2",
|
||||
"publishedAt": 1772142334743,
|
||||
"commit": "https://github.com/openclaw/skills/commit/e2df5967d2e34ca277cc9a111569c38668fe46f4"
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"version": "2.0.1",
|
||||
"publishedAt": 1771880820667,
|
||||
"commit": "https://github.com/openclaw/skills/commit/b94ed6adb959f5c30e85a7000539833bcc67fb25"
|
||||
},
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"publishedAt": 1770615361575,
|
||||
"commit": "https://github.com/openclaw/skills/commit/96da5b533188905206644c9d391c652e2deba3b6"
|
||||
},
|
||||
{
|
||||
"version": "1.1.2",
|
||||
"publishedAt": 1770565932432,
|
||||
"commit": "https://github.com/openclaw/skills/commit/6f4e51cde5f18e0fd4c8df147e9f613d2371fa8d"
|
||||
},
|
||||
{
|
||||
"version": "1.1.10",
|
||||
"publishedAt": 1770515331593,
|
||||
"commit": "https://github.com/openclaw/skills/commit/ebda0539c94acd7373b7e7f2f8ce83122f62efaa"
|
||||
},
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"publishedAt": 1770441680124,
|
||||
"commit": "https://github.com/openclaw/skills/commit/8ea8be7afc1447758ddce88f4f8b85fe3d289aae"
|
||||
}
|
||||
]
|
||||
}
|
||||
37
skills/clawdtalk-client/package-lock.json
generated
Normal file
37
skills/clawdtalk-client/package-lock.json
generated
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "clawdtalk-client",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "clawdtalk-client",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
skills/clawdtalk-client/package.json
Normal file
14
skills/clawdtalk-client/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "clawdtalk-client",
|
||||
"version": "2.0.1",
|
||||
"description": "Voice calling and SMS for Clawdbot",
|
||||
"author": "Telnyx",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/team-telnyx/clawdtalk-client.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
331
skills/clawdtalk-client/scripts/approval.sh
Normal file
331
skills/clawdtalk-client/scripts/approval.sh
Normal file
@@ -0,0 +1,331 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk Approval Requests
|
||||
#
|
||||
# Request user approval for sensitive actions during voice calls.
|
||||
# Sends push notification to user's phone and waits for response.
|
||||
#
|
||||
# Usage:
|
||||
# ./approval.sh request "Book flight LAX→JFK for $450"
|
||||
# ./approval.sh request "Send email to john@example.com" --details "Subject: Meeting tomorrow"
|
||||
# ./approval.sh request "Delete 50 files" --biometric --timeout 120
|
||||
# ./approval.sh status <request_id>
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: https://clawdtalk.com
|
||||
# Reads: skill-config.json
|
||||
# Writes: none
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$SKILL_DIR/skill-config.json"
|
||||
|
||||
# Default timeout for waiting on approval (seconds)
|
||||
DEFAULT_TIMEOUT=300
|
||||
POLL_INTERVAL=2
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
check_config() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo -e "${RED}Error: Configuration not found. Run ./setup.sh first.${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if user has any registered devices (from ws-client cache)
|
||||
check_devices() {
|
||||
local status_file="$SKILL_DIR/.device-status"
|
||||
if [ -f "$status_file" ]; then
|
||||
local has_devices
|
||||
has_devices=$(jq -r '.has_devices // false' "$status_file" 2>/dev/null)
|
||||
if [ "$has_devices" = "false" ]; then
|
||||
return 1 # No devices
|
||||
fi
|
||||
fi
|
||||
return 0 # Has devices (or unknown - assume yes)
|
||||
}
|
||||
|
||||
get_config() {
|
||||
local key="$1"
|
||||
local value
|
||||
value=$(jq -r ".$key // empty" "$CONFIG_FILE" 2>/dev/null)
|
||||
|
||||
# Resolve ${ENV_VAR} references
|
||||
if [[ "$value" =~ ^\$\{([A-Z_][A-Z0-9_]*)\}$ ]]; then
|
||||
local env_var="${BASH_REMATCH[1]}"
|
||||
value="${!env_var:-$value}"
|
||||
fi
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
request_approval() {
|
||||
local action=""
|
||||
local details=""
|
||||
local require_biometric=false
|
||||
local timeout=$DEFAULT_TIMEOUT
|
||||
local wait=true
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--details)
|
||||
details="$2"
|
||||
shift 2
|
||||
;;
|
||||
--biometric)
|
||||
require_biometric=true
|
||||
shift
|
||||
;;
|
||||
--timeout)
|
||||
timeout="$2"
|
||||
shift 2
|
||||
;;
|
||||
--no-wait)
|
||||
wait=false
|
||||
shift
|
||||
;;
|
||||
-*)
|
||||
echo -e "${RED}Unknown option: $1${NC}" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [ -z "$action" ]; then
|
||||
action="$1"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$action" ]; then
|
||||
echo -e "${RED}Error: Action description required${NC}" >&2
|
||||
echo "Usage: $0 request \"Description of action\" [--details \"More info\"] [--biometric] [--timeout 300]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if user has devices - skip API call entirely if not
|
||||
if ! check_devices; then
|
||||
echo "no_devices"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local api_key
|
||||
api_key=$(get_config "api_key")
|
||||
local server
|
||||
server=$(get_config "server")
|
||||
server="${server:-https://clawdtalk.com}"
|
||||
|
||||
if [ -z "$api_key" ]; then
|
||||
echo -e "${RED}Error: No API key configured${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build request body
|
||||
local body
|
||||
body=$(jq -n \
|
||||
--arg action "$action" \
|
||||
--arg details "$details" \
|
||||
--argjson biometric "$require_biometric" \
|
||||
--argjson expires_in "$timeout" \
|
||||
'{
|
||||
action: $action,
|
||||
require_biometric: $biometric,
|
||||
expires_in: $expires_in
|
||||
} + (if $details != "" then {details: $details} else {} end)'
|
||||
)
|
||||
|
||||
# Create approval request
|
||||
local response
|
||||
response=$(curl -s -X POST "$server/v1/approvals" \
|
||||
-H "Authorization: Bearer $api_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$body")
|
||||
|
||||
local request_id
|
||||
request_id=$(echo "$response" | jq -r '.request_id // empty')
|
||||
|
||||
if [ -z "$request_id" ]; then
|
||||
local error
|
||||
error=$(echo "$response" | jq -r '.message // .error // "Unknown error"')
|
||||
echo -e "${RED}Error creating approval request: $error${NC}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local devices_notified
|
||||
devices_notified=$(echo "$response" | jq -r '.devices_notified // 0')
|
||||
|
||||
if [ "$devices_notified" -eq 0 ]; then
|
||||
# No devices registered — return immediately
|
||||
echo "no_devices"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$wait" = false ]; then
|
||||
# Just return the request ID, don't wait
|
||||
echo "$request_id"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Wait for response
|
||||
echo -e "Waiting for approval (timeout: ${timeout}s)..." >&2
|
||||
|
||||
local start_time
|
||||
start_time=$(date +%s)
|
||||
local end_time=$((start_time + timeout))
|
||||
|
||||
while true; do
|
||||
local current_time
|
||||
current_time=$(date +%s)
|
||||
|
||||
if [ "$current_time" -ge "$end_time" ]; then
|
||||
echo "timeout"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check status
|
||||
local status_response
|
||||
status_response=$(curl -s "$server/v1/approvals/$request_id" \
|
||||
-H "Authorization: Bearer $api_key")
|
||||
|
||||
local status
|
||||
status=$(echo "$status_response" | jq -r '.status // "pending"')
|
||||
|
||||
case "$status" in
|
||||
approved)
|
||||
echo "approved"
|
||||
exit 0
|
||||
;;
|
||||
denied)
|
||||
echo "denied"
|
||||
exit 0
|
||||
;;
|
||||
expired)
|
||||
echo "expired"
|
||||
exit 0
|
||||
;;
|
||||
pending)
|
||||
# Still waiting
|
||||
sleep "$POLL_INTERVAL"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unexpected status: $status${NC}" >&2
|
||||
echo "error"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
check_status() {
|
||||
local request_id="$1"
|
||||
|
||||
if [ -z "$request_id" ]; then
|
||||
echo -e "${RED}Error: Request ID required${NC}" >&2
|
||||
echo "Usage: $0 status <request_id>" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local api_key
|
||||
api_key=$(get_config "api_key")
|
||||
local server
|
||||
server=$(get_config "server")
|
||||
server="${server:-https://clawdtalk.com}"
|
||||
|
||||
local response
|
||||
response=$(curl -s "$server/v1/approvals/$request_id" \
|
||||
-H "Authorization: Bearer $api_key")
|
||||
|
||||
local status
|
||||
status=$(echo "$response" | jq -r '.status // "unknown"')
|
||||
|
||||
echo "$status"
|
||||
}
|
||||
|
||||
list_approvals() {
|
||||
local status="${1:-pending}"
|
||||
|
||||
local api_key
|
||||
api_key=$(get_config "api_key")
|
||||
local server
|
||||
server=$(get_config "server")
|
||||
server="${server:-https://clawdtalk.com}"
|
||||
|
||||
curl -s "$server/v1/approvals?status=$status" \
|
||||
-H "Authorization: Bearer $api_key" | jq '.'
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
ClawdTalk Approval Requests
|
||||
|
||||
Request user approval for sensitive actions. Sends a push notification
|
||||
to the user's phone and waits for their response.
|
||||
|
||||
COMMANDS:
|
||||
request <action> Create approval request and wait for response
|
||||
status <id> Check status of an existing request
|
||||
list [status] List approval requests (default: pending)
|
||||
|
||||
OPTIONS (for request):
|
||||
--details "text" Additional details to show user
|
||||
--biometric Require biometric auth (fingerprint/face) to approve
|
||||
--timeout <secs> How long to wait for response (default: 300)
|
||||
--no-wait Return request ID immediately, don't wait
|
||||
|
||||
EXAMPLES:
|
||||
# Simple approval
|
||||
./approval.sh request "Send email to boss@company.com"
|
||||
|
||||
# With details
|
||||
./approval.sh request "Book flight" --details "Delta 123, LAX→JFK, $450, Feb 15"
|
||||
|
||||
# Require biometric for sensitive action
|
||||
./approval.sh request "Transfer $5000 to external account" --biometric
|
||||
|
||||
# Quick check without waiting
|
||||
id=$(./approval.sh request "Delete files" --no-wait)
|
||||
# ... do other things ...
|
||||
status=$(./approval.sh status "$id")
|
||||
|
||||
OUTPUT:
|
||||
approved - User approved the action
|
||||
denied - User denied the action
|
||||
timeout - No response within timeout period
|
||||
expired - Request expired before user responded
|
||||
no_devices - User has no mobile app installed (no registered devices)
|
||||
pending - Still waiting (for status command)
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main
|
||||
check_config
|
||||
|
||||
case "${1:-}" in
|
||||
request)
|
||||
shift
|
||||
request_approval "$@"
|
||||
;;
|
||||
status)
|
||||
shift
|
||||
check_status "$@"
|
||||
;;
|
||||
list)
|
||||
shift
|
||||
list_approvals "$@"
|
||||
;;
|
||||
help|--help|-h)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
212
skills/clawdtalk-client/scripts/call.sh
Normal file
212
skills/clawdtalk-client/scripts/call.sh
Normal file
@@ -0,0 +1,212 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# ClawdTalk Outbound Call Script
|
||||
# Initiates an outbound call to user's phone or an external number
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/call.sh # Call your phone
|
||||
# ./scripts/call.sh "Hey, what's up?" # Call with greeting
|
||||
# ./scripts/call.sh --to +15551234567 # Call external (paid only)
|
||||
# ./scripts/call.sh --to +1555... --purpose "Schedule meeting" # External with purpose
|
||||
# ./scripts/call.sh status <call_id> # Check call status
|
||||
# ./scripts/call.sh end <call_id> # End an active call
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: https://clawdtalk.com
|
||||
# Reads: skill-config.json
|
||||
# Writes: none
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$SKILL_DIR/skill-config.json"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
error() { echo -e "${RED}Error:${NC} $1" >&2; exit 1; }
|
||||
info() { echo -e "${GREEN}$1${NC}"; }
|
||||
warn() { echo -e "${YELLOW}$1${NC}"; }
|
||||
|
||||
# Load config
|
||||
[[ -f "$CONFIG_FILE" ]] || error "Config not found. Run ./setup.sh first."
|
||||
|
||||
# Resolve env vars in config
|
||||
resolve_config() {
|
||||
local config
|
||||
config=$(cat "$CONFIG_FILE")
|
||||
|
||||
# Find .env files
|
||||
local env_files=(
|
||||
"$HOME/.openclaw/.env"
|
||||
"$HOME/.clawdbot/.env"
|
||||
"$SKILL_DIR/.env"
|
||||
)
|
||||
|
||||
for env_file in "${env_files[@]}"; do
|
||||
if [[ -f "$env_file" ]]; then
|
||||
while IFS='=' read -r key value; do
|
||||
[[ -z "$key" || "$key" =~ ^# ]] && continue
|
||||
value="${value%\"}"
|
||||
value="${value#\"}"
|
||||
config="${config//\$\{$key\}/$value}"
|
||||
done < "$env_file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "$config"
|
||||
}
|
||||
|
||||
CONFIG=$(resolve_config)
|
||||
API_KEY=$(echo "$CONFIG" | jq -r '.api_key // empty')
|
||||
SERVER=$(echo "$CONFIG" | jq -r '.server // "https://clawdtalk.com"')
|
||||
|
||||
[[ -n "$API_KEY" ]] || error "API key not configured. Run ./setup.sh"
|
||||
|
||||
# API helper
|
||||
api() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local args=(-s -X "$method" -H "Authorization: Bearer $API_KEY" -H "Content-Type: application/json")
|
||||
[[ -n "$data" ]] && args+=(-d "$data")
|
||||
|
||||
curl "${args[@]}" "${SERVER}${endpoint}"
|
||||
}
|
||||
|
||||
# Commands
|
||||
cmd_call() {
|
||||
local greeting=""
|
||||
local to_number=""
|
||||
local purpose=""
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--to)
|
||||
to_number="$2"
|
||||
shift 2
|
||||
;;
|
||||
--purpose|--context)
|
||||
purpose="$2"
|
||||
shift 2
|
||||
;;
|
||||
-*)
|
||||
error "Unknown option: $1"
|
||||
;;
|
||||
*)
|
||||
greeting="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Build payload
|
||||
local payload='{}'
|
||||
|
||||
# Smart detection: if greeting looks like a phone number and no --to provided, treat it as --to
|
||||
if [[ -z "$to_number" && -n "$greeting" && "$greeting" =~ ^\+?[0-9]{10,15}$ ]]; then
|
||||
warn "Detected phone number in greeting, treating as --to target"
|
||||
to_number="$greeting"
|
||||
greeting=""
|
||||
fi
|
||||
|
||||
# Start with base object
|
||||
if [[ -n "$to_number" ]]; then
|
||||
payload=$(jq -n --arg t "$to_number" '{to: $t}')
|
||||
fi
|
||||
|
||||
# Add greeting if provided
|
||||
if [[ -n "$greeting" ]]; then
|
||||
payload=$(echo "$payload" | jq --arg g "$greeting" '. + {greeting: $g}')
|
||||
fi
|
||||
|
||||
# Add context with purpose for external calls
|
||||
if [[ -n "$purpose" ]]; then
|
||||
payload=$(echo "$payload" | jq --arg p "$purpose" '. + {context: {purpose: $p}}')
|
||||
fi
|
||||
|
||||
if [[ -n "$to_number" ]]; then
|
||||
info "Initiating outbound call to $to_number..."
|
||||
else
|
||||
info "Initiating outbound call to your phone..."
|
||||
fi
|
||||
|
||||
local result
|
||||
result=$(api POST "/v1/calls" "$payload")
|
||||
|
||||
local status
|
||||
status=$(echo "$result" | jq -r '.status // .error.code // "unknown"')
|
||||
|
||||
if [[ "$status" == "initiating" || "$status" == "ringing" ]]; then
|
||||
local call_id
|
||||
call_id=$(echo "$result" | jq -r '.call_id')
|
||||
info "Call initiated: $call_id"
|
||||
echo "$result" | jq .
|
||||
else
|
||||
error "Failed to initiate call: $(echo "$result" | jq -r '.error.message // .message // "Unknown error"')"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_status() {
|
||||
local call_id="$1"
|
||||
[[ -n "$call_id" ]] || error "Usage: $0 status <call_id>"
|
||||
|
||||
api GET "/v1/calls/$call_id" | jq .
|
||||
}
|
||||
|
||||
cmd_end() {
|
||||
local call_id="$1"
|
||||
local reason="${2:-user_ended}"
|
||||
[[ -n "$call_id" ]] || error "Usage: $0 end <call_id> [reason]"
|
||||
|
||||
local payload
|
||||
payload=$(jq -n --arg r "$reason" '{reason: $r}')
|
||||
|
||||
info "Ending call $call_id..."
|
||||
api POST "/v1/calls/$call_id/end" "$payload" | jq .
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
cat <<EOF
|
||||
ClawdTalk Outbound Call
|
||||
|
||||
Usage:
|
||||
$0 Call your own phone (default)
|
||||
$0 "Hello!" Call with custom greeting
|
||||
$0 --to +15551234567 Call an external number (paid only)
|
||||
$0 --to +1555... --purpose "Schedule mtg" Call external with purpose
|
||||
$0 --to +1555... "Hi!" --purpose "..." External + greeting + purpose
|
||||
$0 status <call_id> Check call status
|
||||
$0 end <call_id> End an active call
|
||||
|
||||
Options:
|
||||
--to <number> Call external number instead of your own
|
||||
--purpose <text> Tell the AI why you're calling (critical for external calls)
|
||||
|
||||
Without --to: calls your verified phone number.
|
||||
With --to: calls the specified number (requires paid account with dedicated number).
|
||||
The --purpose flag tells the AI what the call is about so it knows what to do.
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main
|
||||
case "${1:-}" in
|
||||
status)
|
||||
cmd_status "${2:-}"
|
||||
;;
|
||||
end)
|
||||
cmd_end "${2:-}" "${3:-}"
|
||||
;;
|
||||
help|--help|-h)
|
||||
cmd_help
|
||||
;;
|
||||
*)
|
||||
cmd_call "$@"
|
||||
;;
|
||||
esac
|
||||
330
skills/clawdtalk-client/scripts/connect.sh
Normal file
330
skills/clawdtalk-client/scripts/connect.sh
Normal file
@@ -0,0 +1,330 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk - WebSocket Connection Manager
|
||||
#
|
||||
# Manages the WebSocket connection to ClawdTalk server for receiving
|
||||
# voice transcriptions and sending responses.
|
||||
# Works with both Clawdbot and OpenClaw.
|
||||
#
|
||||
# Usage: ./connect.sh {start|stop|status|restart} [--server <url>]
|
||||
#
|
||||
# Env vars: via .env
|
||||
# Endpoints: none (launches ws-client.js)
|
||||
# Reads: skill-config.json, .env
|
||||
# Writes: .connect.pid, .connect.log
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$SKILL_DIR/skill-config.json"
|
||||
PID_FILE="$SKILL_DIR/.connect.pid"
|
||||
LOG_FILE="$SKILL_DIR/.connect.log"
|
||||
|
||||
# Parse server override from args
|
||||
SERVER_FLAG=""
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--server)
|
||||
SERVER_FLAG="--server $2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
CMD="${CMD:-$1}"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_status() {
|
||||
echo -e "${BLUE}📞 Clawd Talk Connection Manager${NC}"
|
||||
echo "================================="
|
||||
echo ""
|
||||
}
|
||||
|
||||
check_config() {
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo -e "${RED}❌ Configuration not found. Run './setup.sh' first.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if we have API key
|
||||
local api_key=$(jq -r '.api_key // empty' "$CONFIG_FILE" 2>/dev/null || echo "")
|
||||
if [ -z "$api_key" ] || [ "$api_key" = "null" ] || [ "$api_key" = "YOUR_API_KEY_HERE" ]; then
|
||||
echo -e "${RED}❌ No API key configured.${NC}"
|
||||
echo ""
|
||||
echo "Get your API key from https://clawdtalk.com → Dashboard"
|
||||
echo "Then add it to skill-config.json"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
check_gateway_tools() {
|
||||
# Check if sessions_send is allowed on the gateway /tools/invoke endpoint
|
||||
# Without this, the voice assistant can't proxy questions to the Clawdbot
|
||||
local config_paths=(
|
||||
"$HOME/.openclaw/openclaw.json"
|
||||
"$HOME/.clawdbot/clawdbot.json"
|
||||
)
|
||||
|
||||
for cfg in "${config_paths[@]}"; do
|
||||
if [ -f "$cfg" ]; then
|
||||
local tools_allow=$(jq -r '.gateway.tools.allow // [] | join(",")' "$cfg" 2>/dev/null)
|
||||
if [ -z "$tools_allow" ] || ! echo "$tools_allow" | grep -q "sessions_send"; then
|
||||
echo -e "${YELLOW}⚠️ Gateway missing 'sessions_send' in tools allowlist${NC}"
|
||||
echo ""
|
||||
echo " The voice assistant needs sessions_send to proxy questions to your Clawdbot."
|
||||
echo " Add it to your config ($cfg):"
|
||||
echo ""
|
||||
echo ' "gateway": { "tools": { "allow": ["sessions_send"] } }'
|
||||
echo ""
|
||||
echo " Or ask your Clawdbot to run:"
|
||||
echo " openclaw config set gateway.tools.allow '[\"sessions_send\"]'"
|
||||
echo ""
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e "${YELLOW}⚠️ No OpenClaw/Clawdbot config found. Gateway tools check skipped.${NC}"
|
||||
return 0
|
||||
}
|
||||
|
||||
check_dependencies() {
|
||||
for tool in node jq; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
echo -e "${RED}❌ Required tool '$tool' is not installed.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Check node_modules exist
|
||||
if [ ! -d "$SKILL_DIR/node_modules/ws" ]; then
|
||||
echo -e "${YELLOW}📦 Installing dependencies...${NC}"
|
||||
(cd "$SKILL_DIR" && npm install --production 2>/dev/null)
|
||||
if [ ! -d "$SKILL_DIR/node_modules/ws" ]; then
|
||||
echo -e "${RED}❌ Failed to install dependencies. Run 'npm install' in $SKILL_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e " ${GREEN}✓ Dependencies installed${NC}"
|
||||
fi
|
||||
}
|
||||
|
||||
is_running() {
|
||||
if [ -f "$PID_FILE" ]; then
|
||||
local pid=$(cat "$PID_FILE")
|
||||
if ps -p "$pid" &> /dev/null; then
|
||||
return 0
|
||||
else
|
||||
# Stale PID file
|
||||
rm -f "$PID_FILE"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
start_connection() {
|
||||
if is_running; then
|
||||
echo -e "${YELLOW}⚠️ Connection already running (PID: $(cat "$PID_FILE"))${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "🚀 Starting WebSocket connection..."
|
||||
|
||||
# Source skill's own .env if it exists (for skill-specific env vars only)
|
||||
[ -f "$SKILL_DIR/.env" ] && . "$SKILL_DIR/.env"
|
||||
|
||||
# Rotate log if it's too big (> 1MB)
|
||||
if [ -f "$LOG_FILE" ] && [ $(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null || echo 0) -gt 1048576 ]; then
|
||||
echo "🔄 Rotating large log file..."
|
||||
mv "$LOG_FILE" "${LOG_FILE}.1" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Start the WebSocket client in background (append to log)
|
||||
nohup node "$SCRIPT_DIR/ws-client.js" $SERVER_FLAG >> "$LOG_FILE" 2>&1 &
|
||||
local pid=$!
|
||||
echo $pid > "$PID_FILE"
|
||||
|
||||
# Give it a moment to start
|
||||
sleep 2
|
||||
|
||||
# Check if it's still running
|
||||
if ps -p "$pid" &> /dev/null; then
|
||||
echo -e " ✓ ${GREEN}WebSocket client started (PID: $pid)${NC}"
|
||||
echo ""
|
||||
echo "Use './scripts/connect.sh status' to check connection health"
|
||||
echo "Logs: $LOG_FILE"
|
||||
else
|
||||
rm -f "$PID_FILE"
|
||||
echo -e " ❌ ${RED}Failed to start WebSocket client${NC}"
|
||||
echo ""
|
||||
echo "Check logs: $LOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
stop_connection() {
|
||||
if ! is_running; then
|
||||
echo -e "${YELLOW}⚠️ Connection not running${NC}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local pid=$(cat "$PID_FILE")
|
||||
echo "🛑 Stopping WebSocket connection (PID: $pid)..."
|
||||
|
||||
# Try graceful shutdown first
|
||||
if kill "$pid" 2>/dev/null; then
|
||||
# Wait up to 5 seconds for graceful shutdown
|
||||
for i in {1..5}; do
|
||||
if ! ps -p "$pid" &> /dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Force kill if still running
|
||||
if ps -p "$pid" &> /dev/null; then
|
||||
kill -9 "$pid" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$PID_FILE"
|
||||
echo -e " ✓ ${GREEN}WebSocket client stopped${NC}"
|
||||
}
|
||||
|
||||
show_status() {
|
||||
print_status
|
||||
|
||||
if is_running; then
|
||||
local pid=$(cat "$PID_FILE")
|
||||
echo -e "Status: ${GREEN}CONNECTED${NC} (PID: $pid)"
|
||||
|
||||
# Show recent log lines
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
echo ""
|
||||
echo "Recent activity:"
|
||||
echo "================"
|
||||
tail -n 5 "$LOG_FILE" 2>/dev/null | while IFS= read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo -e "Status: ${RED}DISCONNECTED${NC}"
|
||||
|
||||
if [ -f "$LOG_FILE" ]; then
|
||||
echo ""
|
||||
echo "Last error (if any):"
|
||||
echo "==================="
|
||||
tail -n 3 "$LOG_FILE" 2>/dev/null | while IFS= read -r line; do
|
||||
if [[ "$line" =~ (ERROR|Error|error|FAILED|Failed|failed) ]]; then
|
||||
echo -e " ${RED}$line${NC}"
|
||||
else
|
||||
echo " $line"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check gateway tools
|
||||
echo ""
|
||||
check_gateway_tools 2>/dev/null && echo -e "Gateway tools: ${GREEN}sessions_send allowed${NC}" || true
|
||||
|
||||
echo ""
|
||||
echo "Configuration:"
|
||||
echo "============="
|
||||
local server_url=$(jq -r '.server // "https://clawdtalk.com"' "$CONFIG_FILE" 2>/dev/null)
|
||||
|
||||
echo " Server: $server_url"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo "========="
|
||||
echo " start - Start WebSocket connection"
|
||||
echo " stop - Stop WebSocket connection"
|
||||
echo " restart - Restart WebSocket connection"
|
||||
echo " status - Show this status"
|
||||
echo " watchdog - Check if running and restart if needed"
|
||||
echo ""
|
||||
echo "Flags:"
|
||||
echo " --server <url> - Override server URL"
|
||||
echo ""
|
||||
}
|
||||
|
||||
restart_connection() {
|
||||
echo "🔄 Restarting WebSocket connection..."
|
||||
stop_connection
|
||||
sleep 1
|
||||
start_connection
|
||||
}
|
||||
|
||||
watchdog_check() {
|
||||
# Silent watchdog - only log when taking action
|
||||
if ! is_running; then
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WATCHDOG: Process not running, restarting..." >> "$SKILL_DIR/.watchdog.log"
|
||||
check_config 2>/dev/null || {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WATCHDOG: Config check failed, skipping restart" >> "$SKILL_DIR/.watchdog.log"
|
||||
return 1
|
||||
}
|
||||
check_dependencies 2>/dev/null || {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WATCHDOG: Dependencies check failed, skipping restart" >> "$SKILL_DIR/.watchdog.log"
|
||||
return 1
|
||||
}
|
||||
start_connection >> "$SKILL_DIR/.watchdog.log" 2>&1
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WATCHDOG: Restart completed" >> "$SKILL_DIR/.watchdog.log"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main command handling
|
||||
case "${CMD:-}" in
|
||||
start)
|
||||
print_status
|
||||
check_config
|
||||
check_dependencies
|
||||
check_gateway_tools || true
|
||||
start_connection
|
||||
;;
|
||||
stop)
|
||||
print_status
|
||||
stop_connection
|
||||
;;
|
||||
restart)
|
||||
print_status
|
||||
check_config
|
||||
check_dependencies
|
||||
check_gateway_tools || true
|
||||
restart_connection
|
||||
;;
|
||||
status)
|
||||
check_config
|
||||
show_status
|
||||
;;
|
||||
watchdog)
|
||||
# Silent watchdog mode - used by cron
|
||||
watchdog_check
|
||||
;;
|
||||
*)
|
||||
print_status
|
||||
echo -e "${RED}❌ Invalid command${NC}"
|
||||
echo ""
|
||||
echo "Usage: $0 {start|stop|status|restart|watchdog} [--server <url>]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start - Start WebSocket connection to ClawdTalk server"
|
||||
echo " stop - Stop WebSocket connection"
|
||||
echo " restart - Restart WebSocket connection"
|
||||
echo " status - Show connection status and configuration"
|
||||
echo " watchdog - Check if running and restart if needed (for cron)"
|
||||
echo ""
|
||||
echo "Flags:"
|
||||
echo " --server <url> - Override server URL"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
245
skills/clawdtalk-client/scripts/sms.sh
Normal file
245
skills/clawdtalk-client/scripts/sms.sh
Normal file
@@ -0,0 +1,245 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk SMS — Send and list SMS messages
|
||||
#
|
||||
# Usage:
|
||||
# sms.sh send +1234567890 "Hello, world!"
|
||||
# sms.sh send +1234567890 "Message" --media https://example.com/image.jpg
|
||||
# sms.sh list [--limit 20] [--contact +1234567890]
|
||||
# sms.sh conversations
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: https://clawdtalk.com
|
||||
# Reads: skill-config.json
|
||||
# Writes: none
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
CONFIG_FILE="$SKILL_DIR/skill-config.json"
|
||||
|
||||
# ─── Load config ────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "Error: skill-config.json not found. Run setup.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API_KEY=$(jq -r '.api_key // empty' "$CONFIG_FILE")
|
||||
SERVER=$(jq -r '.server // "https://clawdtalk.com"' "$CONFIG_FILE")
|
||||
|
||||
if [[ -z "$API_KEY" || "$API_KEY" == "null" ]]; then
|
||||
echo "Error: No API key configured. Add api_key to skill-config.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ─── Helper functions ───────────────────────────────────────────────────────
|
||||
|
||||
api_call() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local url="${SERVER}${endpoint}"
|
||||
local args=(
|
||||
-s -S
|
||||
-X "$method"
|
||||
-H "Authorization: Bearer $API_KEY"
|
||||
-H "Content-Type: application/json"
|
||||
)
|
||||
|
||||
if [[ -n "$data" ]]; then
|
||||
args+=(-d "$data")
|
||||
fi
|
||||
|
||||
curl "${args[@]}" "$url"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << 'EOF'
|
||||
ClawdTalk SMS — Send and receive text messages
|
||||
|
||||
USAGE:
|
||||
sms.sh send <to> <message> [--media <url>]
|
||||
sms.sh list [--limit N] [--contact +1xxx]
|
||||
sms.sh conversations
|
||||
|
||||
COMMANDS:
|
||||
send Send an SMS/MMS message
|
||||
list List message history
|
||||
conversations List conversation threads
|
||||
|
||||
EXAMPLES:
|
||||
# Send a text
|
||||
sms.sh send +13125551234 "Hey, what's up?"
|
||||
|
||||
# Send with image (MMS)
|
||||
sms.sh send +13125551234 "Check this out" --media https://example.com/photo.jpg
|
||||
|
||||
# List recent messages
|
||||
sms.sh list --limit 10
|
||||
|
||||
# List messages with a specific contact
|
||||
sms.sh list --contact +13125551234
|
||||
|
||||
# Get conversation threads
|
||||
sms.sh conversations
|
||||
EOF
|
||||
}
|
||||
|
||||
# ─── Commands ───────────────────────────────────────────────────────────────
|
||||
|
||||
cmd_send() {
|
||||
local to=""
|
||||
local message=""
|
||||
local media_urls=()
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--media)
|
||||
media_urls+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$to" ]]; then
|
||||
to="$1"
|
||||
elif [[ -z "$message" ]]; then
|
||||
message="$1"
|
||||
else
|
||||
message="$message $1"
|
||||
fi
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$to" || -z "$message" ]]; then
|
||||
echo "Usage: sms.sh send <to> <message> [--media <url>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build JSON payload
|
||||
local payload
|
||||
if [[ ${#media_urls[@]} -gt 0 ]]; then
|
||||
local media_json
|
||||
media_json=$(printf '%s\n' "${media_urls[@]}" | jq -R . | jq -s .)
|
||||
payload=$(jq -n --arg to "$to" --arg msg "$message" --argjson media "$media_json" \
|
||||
'{to: $to, message: $msg, media_urls: $media}')
|
||||
else
|
||||
payload=$(jq -n --arg to "$to" --arg msg "$message" '{to: $to, message: $msg}')
|
||||
fi
|
||||
|
||||
local response
|
||||
response=$(api_call POST "/v1/messages/send" "$payload")
|
||||
|
||||
# Check for error
|
||||
if echo "$response" | jq -e '.error' >/dev/null 2>&1; then
|
||||
local err_msg
|
||||
err_msg=$(echo "$response" | jq -r '.error.message // .error')
|
||||
echo "Error: $err_msg" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Success output
|
||||
local msg_id from_num
|
||||
msg_id=$(echo "$response" | jq -r '.id // "unknown"')
|
||||
from_num=$(echo "$response" | jq -r '.from // "unknown"')
|
||||
|
||||
echo "✓ Message sent"
|
||||
echo " ID: $msg_id"
|
||||
echo " From: $from_num"
|
||||
echo " To: $to"
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
local limit=20
|
||||
local contact=""
|
||||
local direction=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--limit)
|
||||
limit="$2"
|
||||
shift 2
|
||||
;;
|
||||
--contact)
|
||||
contact="$2"
|
||||
shift 2
|
||||
;;
|
||||
--direction)
|
||||
direction="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
local query="?limit=$limit"
|
||||
[[ -n "$contact" ]] && query="$query&contact=$contact"
|
||||
[[ -n "$direction" ]] && query="$query&direction=$direction"
|
||||
|
||||
local response
|
||||
response=$(api_call GET "/v1/messages$query")
|
||||
|
||||
if echo "$response" | jq -e '.error' >/dev/null 2>&1; then
|
||||
echo "Error: $(echo "$response" | jq -r '.error.message // .error')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Format output
|
||||
echo "$response" | jq -r '.messages[] | "\(.direction | if . == "inbound" then "←" else "→" end) \(.created_at | split("T")[0]) \(if .direction == "inbound" then .from else .to end): \(.body[:60])\(if (.body | length) > 60 then "..." else "" end)"'
|
||||
|
||||
local total
|
||||
total=$(echo "$response" | jq -r '.pagination.total')
|
||||
echo ""
|
||||
echo "Total: $total messages"
|
||||
}
|
||||
|
||||
cmd_conversations() {
|
||||
local response
|
||||
response=$(api_call GET "/v1/messages/conversations")
|
||||
|
||||
if echo "$response" | jq -e '.error' >/dev/null 2>&1; then
|
||||
echo "Error: $(echo "$response" | jq -r '.error.message // .error')" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$response" | jq -r '.conversations[] | "\(.contact): \(.last_message[:50])\(if (.last_message | length) > 50 then "..." else "" end)"'
|
||||
}
|
||||
|
||||
# ─── Main ───────────────────────────────────────────────────────────────────
|
||||
|
||||
main() {
|
||||
if [[ $# -eq 0 ]]; then
|
||||
show_help
|
||||
exit 0
|
||||
fi
|
||||
|
||||
local cmd="$1"
|
||||
shift
|
||||
|
||||
case "$cmd" in
|
||||
send)
|
||||
cmd_send "$@"
|
||||
;;
|
||||
list)
|
||||
cmd_list "$@"
|
||||
;;
|
||||
conversations)
|
||||
cmd_conversations "$@"
|
||||
;;
|
||||
-h|--help|help)
|
||||
show_help
|
||||
;;
|
||||
*)
|
||||
echo "Unknown command: $cmd" >&2
|
||||
echo "Run 'sms.sh --help' for usage." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
1104
skills/clawdtalk-client/scripts/telnyx_api.py
Normal file
1104
skills/clawdtalk-client/scripts/telnyx_api.py
Normal file
File diff suppressed because it is too large
Load Diff
1053
skills/clawdtalk-client/scripts/ws-client.js
Normal file
1053
skills/clawdtalk-client/scripts/ws-client.js
Normal file
File diff suppressed because it is too large
Load Diff
373
skills/clawdtalk-client/setup.sh
Normal file
373
skills/clawdtalk-client/setup.sh
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk - Setup Script (v1.1)
|
||||
#
|
||||
# Interactive setup for voice calling integration.
|
||||
# Asks for API key, auto-detects names, and configures the gateway.
|
||||
# Uses jq for all JSON manipulation (no python3 dependency).
|
||||
#
|
||||
# Usage: ./setup.sh
|
||||
#
|
||||
# Env vars: none directly
|
||||
# Endpoints: none
|
||||
# Reads: ~/.openclaw/openclaw.json, ~/.clawdbot/clawdbot.json, USER.md, IDENTITY.md
|
||||
# Writes: skill-config.json, gateway config
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="$SCRIPT_DIR/skill-config.json"
|
||||
|
||||
echo ""
|
||||
echo "📞 ClawdTalk Setup"
|
||||
echo "==================="
|
||||
echo ""
|
||||
echo "This will set up voice calling for your Clawdbot/OpenClaw instance."
|
||||
echo ""
|
||||
|
||||
# Check for required tools
|
||||
echo "📋 Checking requirements..."
|
||||
for tool in node jq; do
|
||||
if ! command -v "$tool" &> /dev/null; then
|
||||
echo "❌ Required tool '$tool' is not installed."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
echo " ✓ All required tools found"
|
||||
|
||||
# Check if already configured
|
||||
if [ -f "$CONFIG_FILE" ]; then
|
||||
echo ""
|
||||
echo "⚠️ Configuration already exists!"
|
||||
echo ""
|
||||
echo "Current config: $CONFIG_FILE"
|
||||
echo ""
|
||||
read -p "Do you want to reconfigure? (y/N): " reconfigure
|
||||
if [[ ! "$reconfigure" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo "Setup cancelled. Run './status.sh' to see current configuration."
|
||||
exit 0
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Ask for API key
|
||||
echo "🔑 API Key"
|
||||
echo "==========="
|
||||
echo ""
|
||||
echo "You need an API key from ClawdTalk."
|
||||
echo ""
|
||||
echo " 1. Go to https://clawdtalk.com and sign in with Google"
|
||||
echo " 2. Set up your phone number in Settings"
|
||||
echo " 3. Generate an API key from the Dashboard"
|
||||
echo ""
|
||||
read -s -p "Enter your API key (or press Enter to skip for now): " api_key
|
||||
echo ""
|
||||
|
||||
if [ -n "$api_key" ]; then
|
||||
echo " ✓ API key saved"
|
||||
else
|
||||
echo " ⚠️ No API key entered — you can add it to skill-config.json later"
|
||||
fi
|
||||
|
||||
# Auto-detect gateway config (support both clawdbot and openclaw)
|
||||
echo ""
|
||||
echo "🔧 Configuring voice agent..."
|
||||
|
||||
GATEWAY_CONFIG=""
|
||||
CLI_NAME=""
|
||||
|
||||
if [ -f "${HOME}/.clawdbot/clawdbot.json" ]; then
|
||||
GATEWAY_CONFIG="${HOME}/.clawdbot/clawdbot.json"
|
||||
CLI_NAME="clawdbot"
|
||||
elif [ -f "${HOME}/.openclaw/openclaw.json" ]; then
|
||||
GATEWAY_CONFIG="${HOME}/.openclaw/openclaw.json"
|
||||
CLI_NAME="openclaw"
|
||||
fi
|
||||
|
||||
# Auto-detect CLI name
|
||||
if [ -z "$CLI_NAME" ]; then
|
||||
if command -v clawdbot &> /dev/null; then
|
||||
CLI_NAME="clawdbot"
|
||||
elif command -v openclaw &> /dev/null; then
|
||||
CLI_NAME="openclaw"
|
||||
else
|
||||
CLI_NAME="clawdbot"
|
||||
fi
|
||||
fi
|
||||
|
||||
voice_agent_added=false
|
||||
main_agent_workspace=""
|
||||
|
||||
if [ -n "$GATEWAY_CONFIG" ] && [ -f "$GATEWAY_CONFIG" ]; then
|
||||
# Check if voice agent already exists (using jq)
|
||||
has_voice=$(jq -r '[.agents.list[]? | select(.id == "voice")] | length > 0' "$GATEWAY_CONFIG" 2>/dev/null || echo "false")
|
||||
|
||||
# Read the main agent's name and workspace using jq
|
||||
main_agent_name=$(jq -r '(.agents.list[]? | select(.default == true or .id == "main") | .name) // "Assistant"' "$GATEWAY_CONFIG" 2>/dev/null || echo "Assistant")
|
||||
main_agent_workspace=$(jq -r '(.agents.list[]? | select(.default == true or .id == "main") | .workspace) // .agents.defaults.workspace // "/home/node/clawd"' "$GATEWAY_CONFIG" 2>/dev/null || echo "/home/node/clawd")
|
||||
|
||||
# Extract gateway connection details for skill-config.json (so ws-client doesn't need to read gateway config at runtime)
|
||||
gateway_port=$(jq -r '.gateway.port // 18789' "$GATEWAY_CONFIG" 2>/dev/null || echo "18789")
|
||||
gateway_token=$(jq -r '.gateway.auth.token // ""' "$GATEWAY_CONFIG" 2>/dev/null || echo "")
|
||||
gateway_url="http://127.0.0.1:${gateway_port}"
|
||||
main_agent_id=$(jq -r '(.agents.list[]? | select(.default == true) | .id) // (.agents.list[0]?.id) // "main"' "$GATEWAY_CONFIG" 2>/dev/null || echo "main")
|
||||
|
||||
if [ "$has_voice" = "true" ]; then
|
||||
echo " ✓ Voice agent already configured in gateway"
|
||||
voice_agent_added=true
|
||||
else
|
||||
# Build the voice agent object (no systemPrompt — injected by ws-client via messages)
|
||||
voice_agent=$(jq -n \
|
||||
--arg name "${main_agent_name} Voice" \
|
||||
--arg workspace "$main_agent_workspace" \
|
||||
'{
|
||||
id: "voice",
|
||||
name: $name,
|
||||
workspace: $workspace
|
||||
}')
|
||||
|
||||
# Add voice agent to agents.list
|
||||
tmp_config=$(mktemp)
|
||||
if jq --argjson agent "$voice_agent" '
|
||||
.agents.list = (.agents.list // []) + [$agent]
|
||||
' "$GATEWAY_CONFIG" > "$tmp_config" 2>/dev/null; then
|
||||
mv "$tmp_config" "$GATEWAY_CONFIG"
|
||||
echo " ✓ Added '${main_agent_name} Voice' agent to gateway config"
|
||||
voice_agent_added=true
|
||||
|
||||
# Tell user to restart gateway (skill shouldn't restart the gateway itself)
|
||||
echo " ⚠️ Run '$CLI_NAME gateway restart' to apply the new agent config"
|
||||
else
|
||||
rm -f "$tmp_config"
|
||||
echo " ⚠️ Could not auto-configure — see manual steps below"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Gateway config not found — see manual steps below"
|
||||
echo " Checked: ~/.clawdbot/clawdbot.json and ~/.openclaw/openclaw.json"
|
||||
fi
|
||||
|
||||
# Install Node dependencies
|
||||
echo ""
|
||||
echo "📦 Installing dependencies..."
|
||||
if [ -f "$SCRIPT_DIR/package.json" ]; then
|
||||
(cd "$SCRIPT_DIR" && npm install --production 2>/dev/null) && echo " ✓ Dependencies installed" || echo " ⚠️ npm install failed — run 'npm install' manually in the skill directory"
|
||||
else
|
||||
echo " ⚠️ No package.json found"
|
||||
fi
|
||||
|
||||
# Detect user and agent names
|
||||
echo ""
|
||||
echo "👤 Setting up names..."
|
||||
|
||||
WORKSPACE="${main_agent_workspace:-$HOME/.openclaw/workspace}"
|
||||
owner_name=""
|
||||
agent_name=""
|
||||
|
||||
# Offer to auto-detect from workspace files (opt-in to address security scanner flag)
|
||||
auto_detect="y"
|
||||
if [ -f "$WORKSPACE/USER.md" ] || [ -f "$WORKSPACE/IDENTITY.md" ]; then
|
||||
read -p " Auto-detect names from workspace? (Y/n): " auto_detect
|
||||
auto_detect="${auto_detect:-y}"
|
||||
fi
|
||||
|
||||
if [[ "$auto_detect" =~ ^[Yy]$ ]]; then
|
||||
# Try to get owner name from USER.md ("What to call them:" or "Name:")
|
||||
if [ -f "$WORKSPACE/USER.md" ]; then
|
||||
owner_name=$(grep -i "what to call them:" "$WORKSPACE/USER.md" 2>/dev/null | head -1 | sed 's/.*:\s*//' | tr -d '*' | xargs)
|
||||
if [ -z "$owner_name" ]; then
|
||||
owner_name=$(grep -i "^- \*\*Name:" "$WORKSPACE/USER.md" 2>/dev/null | head -1 | sed 's/.*:\s*//' | tr -d '*' | xargs)
|
||||
owner_name=$(echo "$owner_name" | awk '{print $1}')
|
||||
fi
|
||||
fi
|
||||
|
||||
# Try to get agent name from IDENTITY.md
|
||||
if [ -f "$WORKSPACE/IDENTITY.md" ]; then
|
||||
agent_name=$(grep -i "^- \*\*Name:" "$WORKSPACE/IDENTITY.md" 2>/dev/null | head -1 | sed 's/.*:\s*//' | tr -d '*' | xargs)
|
||||
fi
|
||||
fi
|
||||
|
||||
# If auto-detect didn't find names (or was skipped), ask manually
|
||||
if [ -z "$owner_name" ]; then
|
||||
read -p " Your name (for greeting): " owner_name
|
||||
fi
|
||||
if [ -z "$agent_name" ]; then
|
||||
read -p " Agent name (optional, press Enter to skip): " agent_name
|
||||
fi
|
||||
|
||||
if [ -n "$owner_name" ]; then
|
||||
echo " ✓ Owner name: $owner_name"
|
||||
fi
|
||||
|
||||
if [ -n "$agent_name" ]; then
|
||||
echo " ✓ Agent name: $agent_name"
|
||||
fi
|
||||
|
||||
# Create skill-config.json
|
||||
echo ""
|
||||
echo "💾 Creating skill configuration..."
|
||||
|
||||
# Build values
|
||||
if [ -n "$api_key" ]; then
|
||||
api_key_json="\"$api_key\""
|
||||
else
|
||||
api_key_json="null"
|
||||
fi
|
||||
|
||||
owner_name_json="null"
|
||||
agent_name_json="null"
|
||||
if [ -n "$owner_name" ]; then
|
||||
owner_name_json="\"$owner_name\""
|
||||
fi
|
||||
if [ -n "$agent_name" ]; then
|
||||
agent_name_json="\"$agent_name\""
|
||||
fi
|
||||
|
||||
# Build greeting with name if available
|
||||
if [ -n "$owner_name" ]; then
|
||||
greeting="Hey $owner_name, what's up?"
|
||||
else
|
||||
greeting="Hey, what's up?"
|
||||
fi
|
||||
|
||||
gateway_url_json="null"
|
||||
gateway_token_json="null"
|
||||
agent_id_json="null"
|
||||
if [ -n "$gateway_url" ]; then
|
||||
gateway_url_json="\"$gateway_url\""
|
||||
fi
|
||||
if [ -n "$gateway_token" ]; then
|
||||
gateway_token_json="\"$gateway_token\""
|
||||
fi
|
||||
if [ -n "$main_agent_id" ]; then
|
||||
agent_id_json="\"$main_agent_id\""
|
||||
fi
|
||||
|
||||
cat > "$CONFIG_FILE" << EOF
|
||||
{
|
||||
"api_key": $api_key_json,
|
||||
"server": "https://clawdtalk.com",
|
||||
"owner_name": $owner_name_json,
|
||||
"agent_name": $agent_name_json,
|
||||
"greeting": "$greeting",
|
||||
"gateway_url": $gateway_url_json,
|
||||
"gateway_token": $gateway_token_json,
|
||||
"agent_id": $agent_id_json
|
||||
}
|
||||
EOF
|
||||
|
||||
echo " ✓ Configuration saved to: $CONFIG_FILE"
|
||||
echo " ⚠️ Note: If you change your gateway token or port later, re-run setup.sh to update."
|
||||
|
||||
# Display next steps
|
||||
echo ""
|
||||
echo "🎉 Setup Complete!"
|
||||
echo "=================="
|
||||
echo ""
|
||||
|
||||
if [ -z "$api_key" ]; then
|
||||
echo "Next steps:"
|
||||
echo ""
|
||||
echo "1. Get your API key from https://clawdtalk.com"
|
||||
echo " • Sign in with Google"
|
||||
echo " • Set up your phone number in Settings"
|
||||
echo " • Generate an API key from the Dashboard"
|
||||
echo ""
|
||||
echo "2. Add it to skill-config.json:"
|
||||
echo " Set the api_key field"
|
||||
echo ""
|
||||
echo "3. Start the connection:"
|
||||
echo " ./scripts/connect.sh start"
|
||||
else
|
||||
echo "Next steps:"
|
||||
echo ""
|
||||
echo "1. Make sure your phone number is set up at https://clawdtalk.com → Settings"
|
||||
echo ""
|
||||
echo "2. Start the connection:"
|
||||
echo " ./scripts/connect.sh start"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Check gateway.tools.allow for sessions_send
|
||||
echo ""
|
||||
echo "🔐 Checking gateway tools policy..."
|
||||
sessions_send_allowed=false
|
||||
if [ -n "$GATEWAY_CONFIG" ] && [ -f "$GATEWAY_CONFIG" ]; then
|
||||
has_allow=$(jq -r '(.gateway.tools.allow // []) | map(select(. == "sessions_send")) | length > 0' "$GATEWAY_CONFIG" 2>/dev/null || echo "false")
|
||||
if [ "$has_allow" = "true" ]; then
|
||||
echo " ✓ sessions_send is allowed on the Gateway HTTP tools API"
|
||||
sessions_send_allowed=true
|
||||
else
|
||||
echo ""
|
||||
echo " ⚠️ sessions_send is NOT allowed on the Gateway HTTP tools API"
|
||||
echo ""
|
||||
echo " Voice calls route requests to your main agent via sessions_send."
|
||||
echo " OpenClaw blocks this tool over HTTP by default for security."
|
||||
echo " Without it, voice calls connect but the AI can't process any requests —"
|
||||
echo " it hears you, but can't act (all tool calls silently fail with 404)."
|
||||
echo ""
|
||||
read -p " Add sessions_send to gateway.tools.allow? (Y/n): " add_allow
|
||||
if [[ ! "$add_allow" =~ ^[Nn]$ ]]; then
|
||||
tmp_config=$(mktemp)
|
||||
if jq '.gateway.tools.allow = ((.gateway.tools.allow // []) + ["sessions_send"] | unique)' "$GATEWAY_CONFIG" > "$tmp_config" 2>/dev/null; then
|
||||
mv "$tmp_config" "$GATEWAY_CONFIG"
|
||||
echo " ✓ Added sessions_send to gateway.tools.allow"
|
||||
sessions_send_allowed=true
|
||||
echo " ⚠️ Run '$CLI_NAME gateway restart' to apply changes"
|
||||
else
|
||||
rm -f "$tmp_config"
|
||||
echo " ⚠️ Could not auto-configure — add it manually (see below)"
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Skipped — voice call requests won't work until this is added"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$voice_agent_added" = true ]; then
|
||||
echo ""
|
||||
echo "✅ Voice agent is configured and ready."
|
||||
else
|
||||
echo "⚠️ Voice agent not auto-configured. Add it manually:"
|
||||
echo ""
|
||||
config_path="~/.clawdbot/clawdbot.json"
|
||||
if [ "$CLI_NAME" = "openclaw" ]; then
|
||||
config_path="~/.openclaw/openclaw.json"
|
||||
fi
|
||||
echo " Edit $config_path and add to agents.list[]:"
|
||||
echo ' { "id": "voice", "name": "Voice" }'
|
||||
echo ""
|
||||
echo " Also ensure chatCompletions is enabled:"
|
||||
echo ' "gateway": { "http": { "endpoints": { "chatCompletions": { "enabled": true } } } }'
|
||||
echo ""
|
||||
echo " Then restart: $CLI_NAME gateway restart"
|
||||
fi
|
||||
|
||||
if [ "$sessions_send_allowed" != true ]; then
|
||||
echo ""
|
||||
echo "⚠️ Gateway tools policy: sessions_send must be allowed for voice calls."
|
||||
echo ""
|
||||
echo " Voice calls work by routing your spoken requests to the main agent session"
|
||||
echo " via the Gateway HTTP tools API (/tools/invoke → sessions_send). OpenClaw"
|
||||
echo " blocks sessions_send over HTTP by default as a security measure. Without"
|
||||
echo " this, the AI connects to your call but can't do anything — all requests"
|
||||
echo " silently fail."
|
||||
echo ""
|
||||
config_path="~/.openclaw/openclaw.json"
|
||||
if [ "$CLI_NAME" = "clawdbot" ]; then
|
||||
config_path="~/.clawdbot/clawdbot.json"
|
||||
fi
|
||||
echo " Add to $config_path:"
|
||||
echo ' { "gateway": { "tools": { "allow": ["sessions_send"] } } }'
|
||||
echo ""
|
||||
echo " Or via CLI:"
|
||||
echo " $CLI_NAME config patch '{\"gateway\":{\"tools\":{\"allow\":[\"sessions_send\"]}}}'"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📋 Voice calls will use your main agent's full context and memory."
|
||||
echo " All tools available to your agent work on voice calls too."
|
||||
echo ""
|
||||
echo "To check status: ./status.sh"
|
||||
echo ""
|
||||
8
skills/clawdtalk-client/skill-config-sample.md
Normal file
8
skills/clawdtalk-client/skill-config-sample.md
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"api_key": "YOUR_API_KEY_HERE",
|
||||
"server": "https://clawdtalk.com",
|
||||
"owner_name": null,
|
||||
"agent_name": null,
|
||||
"greeting": "Hey, what's up?",
|
||||
"max_conversation_turns": 20
|
||||
}
|
||||
135
skills/clawdtalk-client/status.sh
Normal file
135
skills/clawdtalk-client/status.sh
Normal file
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk - Status Script
|
||||
#
|
||||
# Shows connection status, gateway status, and config summary.
|
||||
#
|
||||
# Usage: ./status.sh
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: none
|
||||
# Reads: skill-config.json, .connect.log, .connect.pid
|
||||
# Writes: none
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="$SCRIPT_DIR/skill-config.json"
|
||||
|
||||
# Auto-detect CLI name
|
||||
CLI_NAME="clawdbot"
|
||||
if command -v openclaw &> /dev/null && ! command -v clawdbot &> /dev/null; then
|
||||
CLI_NAME="openclaw"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📞 ClawdTalk Status (v1.2.4)"
|
||||
echo "============================"
|
||||
echo ""
|
||||
|
||||
# Check if configuration exists
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "❌ No configuration found."
|
||||
echo ""
|
||||
echo "Run './setup.sh' to set up ClawdTalk for the first time."
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for jq
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "⚠️ 'jq' not found - showing raw config instead of parsed status"
|
||||
echo ""
|
||||
cat "$CONFIG_FILE"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Parse configuration
|
||||
api_key=$(jq -r '.api_key // empty' "$CONFIG_FILE" 2>/dev/null || echo "")
|
||||
server=$(jq -r '.server // "https://clawdtalk.com"' "$CONFIG_FILE" 2>/dev/null || echo "https://clawdtalk.com")
|
||||
|
||||
# Display config summary
|
||||
echo "📋 Configuration"
|
||||
echo "----------------"
|
||||
echo "Server: $server"
|
||||
|
||||
if [ -z "$api_key" ] || [ "$api_key" = "null" ] || [ "$api_key" = "YOUR_API_KEY_HERE" ]; then
|
||||
echo "API Key: ❌ NOT SET"
|
||||
echo ""
|
||||
echo "Get your API key from https://clawdtalk.com → Dashboard"
|
||||
echo "Then add it to skill-config.json"
|
||||
else
|
||||
masked_key="${api_key:0:6}...${api_key: -4}"
|
||||
echo "API Key: ✅ $masked_key"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# WebSocket connection status
|
||||
echo "🔌 WebSocket Connection"
|
||||
echo "----------------------"
|
||||
|
||||
if [ -f "$SCRIPT_DIR/.connect.pid" ]; then
|
||||
ws_pid=$(cat "$SCRIPT_DIR/.connect.pid")
|
||||
if ps -p "$ws_pid" &> /dev/null; then
|
||||
echo "Status: ✅ CONNECTED (PID: $ws_pid)"
|
||||
if [ -f "$SCRIPT_DIR/.connect.log" ]; then
|
||||
echo ""
|
||||
echo "Recent activity:"
|
||||
tail -n 3 "$SCRIPT_DIR/.connect.log" 2>/dev/null | while IFS= read -r line; do
|
||||
echo " $line"
|
||||
done
|
||||
fi
|
||||
else
|
||||
echo "Status: ❌ DISCONNECTED (stale PID)"
|
||||
rm -f "$SCRIPT_DIR/.connect.pid"
|
||||
echo "Start with: ./scripts/connect.sh start"
|
||||
fi
|
||||
else
|
||||
echo "Status: ❌ NOT STARTED"
|
||||
echo "Start with: ./scripts/connect.sh start"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Gateway status
|
||||
echo "🌐 Gateway Status"
|
||||
echo "----------------"
|
||||
|
||||
# Try multiple detection methods
|
||||
gateway_running=false
|
||||
|
||||
# Method 1: Check for gateway process directly
|
||||
if pgrep -f "clawdbot.*gateway" &>/dev/null || pgrep -f "openclaw.*gateway" &>/dev/null; then
|
||||
gateway_running=true
|
||||
fi
|
||||
|
||||
# Method 2: Check for node process with gateway in cwd
|
||||
if ! $gateway_running && pgrep -f "node.*clawd" &>/dev/null; then
|
||||
gateway_running=true
|
||||
fi
|
||||
|
||||
# Method 3: Try the CLI status command
|
||||
if ! $gateway_running; then
|
||||
gateway_status=$($CLI_NAME gateway status 2>/dev/null || echo "")
|
||||
if [[ "$gateway_status" =~ "running" ]] || [[ "$gateway_status" =~ "Gateway" ]] || [[ "$gateway_status" =~ "pid" ]]; then
|
||||
gateway_running=true
|
||||
fi
|
||||
fi
|
||||
|
||||
if $gateway_running; then
|
||||
echo "Status: ✅ RUNNING"
|
||||
else
|
||||
echo "Status: ❌ NOT RUNNING"
|
||||
echo "Start with: $CLI_NAME gateway start"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Management commands
|
||||
echo "🔧 Commands"
|
||||
echo "-----------"
|
||||
echo "Reconfigure: ./setup.sh"
|
||||
echo "WebSocket: ./scripts/connect.sh start|stop|status|restart"
|
||||
echo "Gateway: $CLI_NAME gateway status|start|stop|restart"
|
||||
echo "Config: cat $CONFIG_FILE"
|
||||
echo "Logs: tail -f .connect.log"
|
||||
echo ""
|
||||
110
skills/clawdtalk-client/uninstall.sh
Normal file
110
skills/clawdtalk-client/uninstall.sh
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk - Uninstall Script (v1.0)
|
||||
#
|
||||
# Removes the voice agent from gateway config, stops the WebSocket connection,
|
||||
# and optionally deletes the skill-config.json.
|
||||
#
|
||||
# Usage: ./uninstall.sh
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: none
|
||||
# Reads: skill-config.json, gateway config
|
||||
# Writes: gateway config
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CONFIG_FILE="$SCRIPT_DIR/skill-config.json"
|
||||
|
||||
echo ""
|
||||
echo "📞 ClawdTalk Uninstall"
|
||||
echo "======================"
|
||||
echo ""
|
||||
|
||||
# Auto-detect gateway config
|
||||
GATEWAY_CONFIG=""
|
||||
CLI_NAME=""
|
||||
|
||||
if [ -f "${HOME}/.clawdbot/clawdbot.json" ]; then
|
||||
GATEWAY_CONFIG="${HOME}/.clawdbot/clawdbot.json"
|
||||
CLI_NAME="clawdbot"
|
||||
elif [ -f "${HOME}/.openclaw/openclaw.json" ]; then
|
||||
GATEWAY_CONFIG="${HOME}/.openclaw/openclaw.json"
|
||||
CLI_NAME="openclaw"
|
||||
fi
|
||||
|
||||
if [ -z "$CLI_NAME" ]; then
|
||||
if command -v clawdbot &> /dev/null; then
|
||||
CLI_NAME="clawdbot"
|
||||
elif command -v openclaw &> /dev/null; then
|
||||
CLI_NAME="openclaw"
|
||||
else
|
||||
CLI_NAME="clawdbot"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 1. Stop WebSocket connection
|
||||
echo "🔌 Stopping WebSocket connection..."
|
||||
if [ -f "$SCRIPT_DIR/scripts/connect.sh" ]; then
|
||||
bash "$SCRIPT_DIR/scripts/connect.sh" stop 2>/dev/null || true
|
||||
echo " ✓ Connection stopped"
|
||||
else
|
||||
echo " ⚠️ connect.sh not found, skipping"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 2. Remove voice agent from gateway config
|
||||
echo "🔧 Removing voice agent from gateway config..."
|
||||
gateway_changed=false
|
||||
|
||||
if [ -n "$GATEWAY_CONFIG" ] && [ -f "$GATEWAY_CONFIG" ]; then
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo " ⚠️ jq not found — please remove the voice agent manually from $GATEWAY_CONFIG"
|
||||
else
|
||||
has_voice=$(jq -r '[.agents.list[]? | select(.id == "voice")] | length > 0' "$GATEWAY_CONFIG" 2>/dev/null || echo "false")
|
||||
|
||||
if [ "$has_voice" = "true" ]; then
|
||||
tmp_config=$(mktemp)
|
||||
if jq '.agents.list = [.agents.list[]? | select(.id != "voice")]' "$GATEWAY_CONFIG" > "$tmp_config" 2>/dev/null; then
|
||||
mv "$tmp_config" "$GATEWAY_CONFIG"
|
||||
echo " ✓ Voice agent removed from gateway config"
|
||||
gateway_changed=true
|
||||
else
|
||||
rm -f "$tmp_config"
|
||||
echo " ⚠️ Could not update gateway config — remove voice agent manually"
|
||||
fi
|
||||
else
|
||||
echo " ✓ No voice agent found (already clean)"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo " ⚠️ Gateway config not found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# 3. Tell user to restart gateway if we changed the config
|
||||
if [ "$gateway_changed" = true ]; then
|
||||
echo " ⚠️ Run '$CLI_NAME gateway restart' to apply changes"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 4. Clean up local files
|
||||
echo "🗑️ Cleaning up..."
|
||||
rm -f "$SCRIPT_DIR/.connect.pid"
|
||||
rm -f "$SCRIPT_DIR/.connect.log"
|
||||
echo " ✓ Removed PID and log files"
|
||||
|
||||
read -p "Delete skill-config.json (contains your API key)? (y/N): " delete_config
|
||||
if [[ "$delete_config" =~ ^[Yy]$ ]]; then
|
||||
rm -f "$CONFIG_FILE"
|
||||
echo " ✓ skill-config.json deleted"
|
||||
else
|
||||
echo " ✓ skill-config.json kept"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
echo "✅ ClawdTalk uninstalled."
|
||||
echo ""
|
||||
echo "To reinstall later, run: ./setup.sh"
|
||||
echo ""
|
||||
120
skills/clawdtalk-client/update.sh
Normal file
120
skills/clawdtalk-client/update.sh
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# ClawdTalk Client Update Script
|
||||
# Downloads and installs the latest version from GitHub
|
||||
#
|
||||
# Env vars: none
|
||||
# Endpoints: https://raw.githubusercontent.com, https://api.github.com
|
||||
# Reads: package.json
|
||||
# Writes: skill files (overwrites on update)
|
||||
|
||||
set -e
|
||||
|
||||
REPO_URL="https://github.com/team-telnyx/clawdtalk-client"
|
||||
RAW_URL="https://raw.githubusercontent.com/team-telnyx/clawdtalk-client/main"
|
||||
SKILL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BACKUP_DIR="${SKILL_DIR}/.backup"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}ClawdTalk Client Updater${NC}"
|
||||
echo "========================="
|
||||
echo
|
||||
|
||||
# Get current version
|
||||
CURRENT_VERSION=$(grep '"version"' "$SKILL_DIR/package.json" 2>/dev/null | head -1 | sed 's/.*"version": "\([^"]*\)".*/\1/')
|
||||
echo "Current version: ${CURRENT_VERSION:-unknown}"
|
||||
|
||||
# Check latest version from GitHub
|
||||
echo "Checking for updates..."
|
||||
LATEST_VERSION=$(curl -s "${RAW_URL}/package.json" | grep '"version"' | head -1 | sed 's/.*"version": "\([^"]*\)".*/\1/')
|
||||
|
||||
if [ -z "$LATEST_VERSION" ]; then
|
||||
echo -e "${RED}Error: Could not fetch latest version from GitHub${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Latest version: ${LATEST_VERSION}"
|
||||
echo
|
||||
|
||||
if [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then
|
||||
echo -e "${GREEN}✓ Already up to date!${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Update available: ${CURRENT_VERSION} → ${LATEST_VERSION}${NC}"
|
||||
echo
|
||||
|
||||
# Confirm update
|
||||
read -p "Do you want to update? [y/N] " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "Update cancelled."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Stop the client if running
|
||||
echo "Stopping ClawdTalk client..."
|
||||
"$SKILL_DIR/scripts/connect.sh" stop 2>/dev/null || true
|
||||
|
||||
# Backup current installation
|
||||
echo "Backing up current installation..."
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_FILE="${BACKUP_DIR}/backup-${CURRENT_VERSION:-old}-$(date +%Y%m%d%H%M%S).tar.gz"
|
||||
tar -czf "$BACKUP_FILE" -C "$SKILL_DIR" \
|
||||
--exclude='.git' \
|
||||
--exclude='node_modules' \
|
||||
--exclude='.backup' \
|
||||
--exclude='skill-config.json' \
|
||||
. 2>/dev/null || true
|
||||
echo "Backup saved to: $BACKUP_FILE"
|
||||
|
||||
# Download latest
|
||||
echo "Downloading latest version..."
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
curl -sL "${RAW_URL}/dist/clawdtalk-client-latest.zip" -o "${TEMP_DIR}/latest.zip"
|
||||
|
||||
if [ ! -f "${TEMP_DIR}/latest.zip" ] || [ ! -s "${TEMP_DIR}/latest.zip" ]; then
|
||||
echo -e "${RED}Error: Download failed${NC}"
|
||||
rm -rf "$TEMP_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract and update
|
||||
echo "Installing update..."
|
||||
cd "$TEMP_DIR"
|
||||
unzip -q latest.zip
|
||||
|
||||
# Copy new files (preserve skill-config.json)
|
||||
cp -r clawdtalk-client/* "$SKILL_DIR/" 2>/dev/null || true
|
||||
|
||||
# Restore config if it was overwritten
|
||||
if [ -f "$SKILL_DIR/skill-config.json.bak" ]; then
|
||||
mv "$SKILL_DIR/skill-config.json.bak" "$SKILL_DIR/skill-config.json"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -rf "$TEMP_DIR"
|
||||
|
||||
# Install dependencies if needed
|
||||
if [ -f "$SKILL_DIR/package.json" ]; then
|
||||
echo "Installing dependencies..."
|
||||
cd "$SKILL_DIR"
|
||||
npm install --production 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Make scripts executable
|
||||
chmod +x "$SKILL_DIR"/*.sh "$SKILL_DIR/scripts"/*.sh 2>/dev/null || true
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}✓ Updated to version ${LATEST_VERSION}!${NC}"
|
||||
echo
|
||||
echo "To start the client:"
|
||||
echo " ./scripts/connect.sh start"
|
||||
echo
|
||||
echo "To restore previous version:"
|
||||
echo " tar -xzf $BACKUP_FILE -C $SKILL_DIR"
|
||||
Reference in New Issue
Block a user