AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning

This commit is contained in:
Krilly
2026-03-04 13:29:22 +00:00
parent 29a98137a7
commit 57dd294675
13706 changed files with 2114953 additions and 237629 deletions
+331
View 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
View 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
View 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
View 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 "$@"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff