Skill: thearchitectit/a2a-register (version 1.3.0)

--- name: a2a-register description: 'Register, deregister, and manage this OpenClaw instance as an A2A agent in the A2A API Gateway. Use when: (1) registering this instance so other agents can discover and route tasks to it, (2) deregistering when going offline, (3) sending heartbeat to signal liveness, (4) checking registration status. NOT for: sending tasks to other agents (use a2a-client), receiving tasks (use a2a-server).' metadata: {"clawdbot":{"emoji":"📝","requires":{"anyBins":["curl","python3"]},"os":["linux","darwin"]}} --- # A2A Register — Agent Registration Management Register and manage this OpenClaw instance as an A2A agent in the A2A API Gateway. Other agents discover this instance through the gateway registry, so registration is required before receiving inbound tasks. ## Sidecar Principle This skill is strictly an **OpenClaw sidecar** — it uses the A2A API Gateway's existing admin API to register, deregister, heartbeat, and check status. The gateway's admin endpoints are the contract; OpenClaw is just a participant. Never assume what other registered agents look like or how they connect — they could be MC2, another OpenClaw, or anything else that speaks A2A. These scripts call the gateway's admin API as-is. ## Configuration All scripts read from a shared `a2a.conf` file (located in the `a2a-client` skill directory). The gateway URL **must** be configured — there are no hardcoded defaults. **Priority order:** CLI flags → env vars → `a2a.conf` → auto-detected defaults | Setting | Auto-detected Default | Description | |---------|-----------------------|-------------| | Gateway URL | *none — required* | A2A API Gateway URL (`A2A_GATEWAY_URL`) | | Agent Name | `hostname -s` (lowercase) | Display name (`AGENT_NAME`) | | Agent Slug | `hostname -s` (lowercase) | URL-safe identifier (`AGENT_SLUG`) | | Agent URL | `http://{Tailscale IP}:{port}` | Agent endpoint (`AGENT_URL`) | | Capabilities | `chat,code,research` | Comma-separated (`AGENT_CAPABILITIES`) | | Auth Type | `bearer` | Auth method (`AGENT_AUTH_TYPE`) | | API Key | *empty* | For listener auth (`A2A_GATEWAY_API_KEY`) | ### Quick Setup ```bash # Interactive setup — creates a2a.conf for you ./a2a-setup.sh # Non-interactive (env vars required) A2A_GATEWAY_URL=http://GATEWAY_IP:8090 A2A_GATEWAY_API_KEY=your-key ./a2a-setup.sh --non-interactive ``` ## When to Use - **Setup** — First time configuring this instance for A2A - **Register** — When this OpenClaw instance needs to be discoverable by other A2A agents - **Deregister** — When going offline or removing this instance from the gateway - **Heartbeat** — Periodically signal that this instance is alive and reachable - **Status** — Check whether this instance is registered and view its current configuration ## Tools ### a2a-setup.sh — Interactive Configuration ```bash ./a2a-setup.sh [--non-interactive] [--conf PATH] ``` Creates or updates the shared `a2a.conf` file. In interactive mode, prompts for all values with auto-detected defaults. In non-interactive mode, reads from environment variables. ```bash # Interactive (recommended first time) ./a2a-setup.sh # Non-interactive (for scripting) A2A_GATEWAY_URL=http://GATEWAY_IP:8090 \ A2A_GATEWAY_API_KEY=your-key \ AGENT_SLUG=my-agent \ ./a2a-setup.sh --non-interactive ``` ### register.sh — Register This Agent ```bash ./register.sh [--gateway-url URL] [--name NAME] [--slug SLUG] [--url URL] [--capabilities CAPS] ``` Registers this OpenClaw instance in the A2A API Gateway. The script: 1. Obtains a JWT via `GET /v0/admin/bootstrap` 2. Checks if the agent slug already exists (`GET /v0/admin/agents`) 3. If new → `POST /v0/admin/agents` to create it 4. If exists → `PUT /v0/admin/agents/{id}` to update it Idempotent — safe to run multiple times. ```bash # Register with defaults (from a2a.conf / auto-detected) ./register.sh # Override specific values ./register.sh --name "My Agent" --slug my-agent --url http://MY_IP:8100 # Override gateway URL ./register.sh --gateway-url http://GATEWAY_LAN_IP:8090 ``` ### deregister.sh — Remove This Agent ```bash ./deregister.sh [--gateway-url URL] [--slug SLUG] ``` Removes this OpenClaw instance from the gateway registry. Other agents will no longer be able to discover or route tasks to it. ```bash ./deregister.sh ``` ### heartbeat.sh — Send Liveness Signal ```bash ./heartbeat.sh [--gateway-url URL] [--slug SLUG] ``` Sends a heartbeat to the gateway (`PATCH /v0/admin/agents/{id}/heartbeat`) to signal that this instance is alive and reachable. Useful for cron jobs or periodic health checks. ```bash # One-time heartbeat ./heartbeat.sh # In a cron (every 5 minutes) */5 * * * * /path/to/skills/a2a-register/heartbeat.sh ``` ### status.sh — Check Registration Status ```bash ./status.sh [--gateway-url URL] [--slug SLUG] ``` Checks whether this OpenClaw instance is registered in the gateway and shows its current configuration (URL, capabilities, status). ```bash ./status.sh ``` ## Typical Workflow 1. **Setup** → `./a2a-setup.sh` to create your `a2a.conf` 2. **Register** → `./register.sh` to add this instance to the gateway 3. **Start listener** → Use `a2a-server` skill's `start.sh` to begin receiving tasks 4. **Heartbeat** → Run `./heartbeat.sh` periodically (or set up a cron) 5. **Status** → `./status.sh` to verify registration 6. **Deregister** → `./deregister.sh` when going offline ## Gateway API | Endpoint | Method | Purpose | |----------|--------|---------| | `/v0/admin/bootstrap` | GET | Obtain admin JWT | | `/v0/admin/agents` | GET | List all registered agents | | `/v0/admin/agents` | POST | Register a new agent | | `/v0/admin/agents/{id}` | PUT | Update an existing agent | | `/v0/admin/agents/{id}` | DELETE | Remove an agent | | `/v0/admin/agents/{id}/heartbeat` | PATCH | Signal liveness | ## Environment Variables | Variable | Description | |----------|-------------| | `A2A_GATEWAY_URL` | Gateway URL (required if not in a2a.conf) | | `A2A_GATEWAY_API_KEY` | API key for listener auth | | `AGENT_NAME` | Agent display name | | `AGENT_SLUG` | Agent identifier | | `AGENT_URL` | Agent endpoint URL | | `AGENT_CAPABILITIES` | Comma-separated capabilities | | `LISTENER_PORT` | Listener port (default: 8100) |

path: _meta.json
content: ownerId: local
slug: a2a-register
version: 1.1.1
publishedAt: 1745854800

path: a2a-setup.sh
content: #!/usr/bin/env bash
# a2a-setup.sh — Interactive setup script that creates a2a.conf for the A2A skills
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="${SCRIPT_DIR}/../a2a-client"
CONF_FILE="${CLIENT_DIR}/a2a.conf"

# --- Parse args ---
NON_INTERACTIVE=false
while [[ $# -gt 0 ]]; do
  case "$1" in
    --non-interactive) NON_INTERACTIVE=true; shift ;;
    --conf) CONF_FILE="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Auto-detect helpers ---
_auto_tailscale_ip() { tailscale ip -4 2>/dev/null || echo ""; }
_auto_hostname() { hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "openclaw"; }

if [[ "$NON_INTERACTIVE" == "true" ]]; then
  # --- Non-interactive mode: use env vars or fail ---
  GW_URL="${A2A_GATEWAY_URL:-}"
  GW_API_KEY="${A2A_GATEWAY_API_KEY:-}"
  AGENT_NAME_VAL="${AGENT_NAME:-$(_auto_hostname)}"
  AGENT_SLUG_VAL="${AGENT_SLUG:-$(_auto_hostname)}"
  AGENT_URL_VAL="${AGENT_URL:-}"
  AGENT_CAPS="${AGENT_CAPABILITIES:-chat,code,research}"
  LISTENER_PORT="${LISTENER_PORT:-8100}"

  if [[ -z "$GW_URL" ]]; then
    echo "Error: --non-interactive requires A2A_GATEWAY_URL env var" >&2
    exit 1
  fi

  # Auto-detect agent URL if not set
  if [[ -z "$AGENT_URL_VAL" ]]; then
    local_ip="$(_auto_tailscale_ip)"
    [[ -z "$local_ip" ]] && local_ip="$(hostname -I | awk '{print $1}')"
    AGENT_URL_VAL="http://${local_ip}:${LISTENER_PORT}"
  fi
else
  # --- Interactive mode ---
  echo "═══════════════════════════════════════════════════"
  echo "  A2A Configuration Setup"
  echo "═══════════════════════════════════════════════════"
  echo ""
  echo "This will create/update ${CONF_FILE}"
  echo "Press Enter to accept [default] values."
  echo ""

  # Load existing config if present
  if [[ -f "$CONF_FILE" ]]; then
    echo "📝 Existing config found — current values shown as defaults."
    # shellcheck source=a2a.conf
    source "$CONF_FILE"
  fi

  # Gateway URL (REQUIRED)
  default_gw="${A2A_GATEWAY_URL:-}"
  read -rp "Gateway URL (e.g. http://GATEWAY_IP:8090) [${default_gw:-(required)}]: " GW_URL
  GW_URL="${GW_URL:-$default_gw}"
  if [[ -z "$GW_URL" ]]; then
    echo "Error: Gateway URL is required" >&2
    exit 1
  fi

  # API Key
  default_key="${A2A_GATEWAY_API_KEY:-}"
  read -rp "Gateway API Key [${default_key:-(empty — auth checks disabled)}]: " GW_API_KEY
  GW_API_KEY="${GW_API_KEY:-$default_key}"

  # Agent Name
  default_name="${AGENT_NAME:-$(_auto_hostname)}"
  read -rp "Agent Name [${default_name}]: " AGENT_NAME_VAL
  AGENT_NAME_VAL="${AGENT_NAME_VAL:-$default_name}"

  # Agent Slug
  default_slug="${AGENT_SLUG:-$(_auto_hostname)}"
  read -rp "Agent Slug [${default_slug}]: " AGENT_SLUG_VAL
  AGENT_SLUG_VAL="${AGENT_SLUG_VAL:-$default_slug}"

  # Listener Port
  default_port="${LISTENER_PORT:-8100}"
  read -rp "Listener Port [${default_port}]: " LISTENER_PORT
  LISTENER_PORT="${LISTENER_PORT:-$default_port}"

  # Agent URL
  local_ip="$(_auto_tailscale_ip)"
  [[ -z "$local_ip" ]] && local_ip="$(hostname -I | awk '{print $1}')"
  default_url="${AGENT_URL:-http://${local_ip}:${LISTENER_PORT}}"
  read -rp "Agent URL [${default_url}]: " AGENT_URL_VAL
  AGENT_URL_VAL="${AGENT_URL_VAL:-$default_url}"

  # Capabilities
  default_caps="${AGENT_CAPABILITIES:-chat,code,research}"
  read -rp "Capabilities [${default_caps}]: " AGENT_CAPS
  AGENT_CAPS="${AGENT_CAPS:-$default_caps}"

  # Auth Type
  default_auth="${AGENT_AUTH_TYPE:-bearer}"
  read -rp "Auth Type [${default_auth}]: " AGENT_AUTH_VAL
  AGENT_AUTH_VAL="${AGENT_AUTH_VAL:-$default_auth}"
fi

# --- Write the config file ---
mkdir -p "$(dirname "$CONF_FILE")"

cat > "$CONF_FILE" <<EOF
# A2A Configuration — auto-generated by a2a-setup.sh or manually edited
# This file is shared by a2a-client, a2a-server, and a2a-register skills.
# Priority order: CLI flags > env vars > a2a.conf > auto-detected defaults
#
# REQUIRED: Set A2A_GATEWAY_URL to your A2A API Gateway instance
# REQUIRED: Set A2A_GATEWAY_API_KEY for task auth (or leave empty to disable auth checks)

A2A_GATEWAY_URL="${GW_URL}"
A2A_GATEWAY_API_KEY="${GW_API_KEY}"

# Agent identity — auto-detected from hostname if empty
AGENT_NAME="${AGENT_NAME_VAL}"
AGENT_SLUG="${AGENT_SLUG_VAL}"

# Agent endpoint — auto-detected from Tailscale/local IP if empty
AGENT_URL="${AGENT_URL_VAL}"

# Agent capabilities (comma-separated)
AGENT_CAPABILITIES="${AGENT_CAPS}"

# Listener bind address — auto-detected from Tailscale/local IP if empty
BIND_ADDR=""

# Listener auth type
AGENT_AUTH_TYPE="${AGENT_AUTH_VAL:-bearer}"

# Listener port
LISTENER_PORT="${LISTENER_PORT}"
EOF

echo ""
echo "═══════════════════════════════════════════════════"
echo "  ✅ Configuration saved"
echo "═══════════════════════════════════════════════════"
echo "  File: ${CONF_FILE}"
echo ""
echo "  Gateway:      ${GW_URL}"
echo "  Agent:        ${AGENT_NAME_VAL} (${AGENT_SLUG_VAL})"
echo "  URL:          ${AGENT_URL_VAL}"
echo "  Port:         ${LISTENER_PORT}"
echo "  Capabilities: ${AGENT_CAPS}"
echo "═══════════════════════════════════════════════════"
echo ""
echo "Next steps:"
echo "  1. Register:    ./register.sh"
echo "  2. Start:       a2a-server/start.sh"
echo "  3. Verify:      curl -s ${AGENT_URL_VAL}/health"


path: deregister.sh
content: #!/usr/bin/env bash
# a2a-register deregister.sh — Remove this OpenClaw instance from the A2A API Gateway registry
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="${SCRIPT_DIR}/../a2a-client"
CONF_FILE="${CLIENT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Auto-detect defaults (agent identity only, NOT gateway URL) ---
_auto_slug() { hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "openclaw"; }

: "${A2A_GATEWAY_URL:=""}"  # REQUIRED — configure via a2a.conf or env var
: "${A2A_GATEWAY_API_KEY:=""}"
: "${AGENT_SLUG:="$(_auto_slug)"}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
AGENT_SLUG_VAL="${AGENT_SLUG}"

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    --slug) AGENT_SLUG_VAL="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "Error: A2A_GATEWAY_URL not configured" >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Get admin JWT ---
echo "Obtaining admin JWT from gateway..."
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Find the agent ID ---
echo "Looking up agent '${AGENT_SLUG_VAL}'..."
EXISTING=$(curl -sS \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

AGENT_ID=$(echo "$EXISTING" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for a in agents:
            if a.get('slug') == slug:
                print(a.get('id', ''))
                break
except: pass
" 2>/dev/null)

if [[ -z "$AGENT_ID" ]]; then
  echo "⚠ Agent '${AGENT_SLUG_VAL}' not found in gateway registry — nothing to deregister"
  exit 0
fi

# --- Delete the agent ---
echo "Deregistering agent '${AGENT_SLUG_VAL}' (ID: ${AGENT_ID})..."
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X DELETE \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents/${AGENT_ID}" 2>/dev/null)

HTTP_CODE=$(echo "$RESPONSE" | tail -1)

if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "204" ]]; then
  BODY=$(echo "$RESPONSE" | sed '$d')
  echo "Error: HTTP ${HTTP_CODE}" >&2
  echo "$BODY" >&2
  exit 1
fi

echo "✅ Agent '${AGENT_SLUG_VAL}' deregistered successfully"
echo "  Other agents will no longer be able to discover or route tasks to this instance"


path: heartbeat.sh
content: #!/usr/bin/env bash
# a2a-register heartbeat.sh — Send a liveness heartbeat to the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="${SCRIPT_DIR}/../a2a-client"
CONF_FILE="${CLIENT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Auto-detect defaults (agent identity only, NOT gateway URL) ---
_auto_slug() { hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "openclaw"; }

: "${A2A_GATEWAY_URL:=""}"  # REQUIRED — configure via a2a.conf or env var
: "${A2A_GATEWAY_API_KEY:=""}"
: "${AGENT_SLUG:="$(_auto_slug)"}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
AGENT_SLUG_VAL="${AGENT_SLUG}"

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    --slug) AGENT_SLUG_VAL="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "Error: A2A_GATEWAY_URL not configured" >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Find the agent ID ---
EXISTING=$(curl -sS \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

AGENT_ID=$(echo "$EXISTING" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for a in agents:
            if a.get('slug') == slug:
                print(a.get('id', ''))
                break
except: pass
" 2>/dev/null)

if [[ -z "$AGENT_ID" ]]; then
  echo "⚠ Agent '${AGENT_SLUG_VAL}' not registered — cannot send heartbeat"
  echo "  Register first: ./register.sh"
  exit 1
fi

# --- Send heartbeat ---
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -X PATCH \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  -d "{\"status\": \"online\"}" \
  "${GATEWAY_URL}/v0/admin/agents/${AGENT_ID}/heartbeat" 2>/dev/null)

HTTP_CODE=$(echo "$RESPONSE" | tail -1)

if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "204" ]]; then
  BODY=$(echo "$RESPONSE" | sed '$d')
  echo "Error: Heartbeat failed (HTTP ${HTTP_CODE})" >&2
  echo "$BODY" >&2
  exit 1
fi

echo "✅ Heartbeat sent for '${AGENT_SLUG_VAL}' (ID: ${AGENT_ID})"


path: register.sh
content: #!/usr/bin/env bash
# a2a-register register.sh — Register this OpenClaw instance as an A2A agent in the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="${SCRIPT_DIR}/../a2a-client"
CONF_FILE="${CLIENT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Auto-detect defaults (only for agent identity, NOT gateway URL) ---
_auto_ip() { tailscale ip -4 2>/dev/null || hostname -I | awk '{print $1}'; }
_auto_slug() { hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "openclaw"; }
_detected_ip="$(_auto_ip)"
_detected_slug="$(_auto_slug)"

: "${A2A_GATEWAY_URL:=""}"  # REQUIRED — configure via a2a.conf or env var
: "${A2A_GATEWAY_API_KEY:=""}"
: "${AGENT_NAME:="${_detected_slug}"}"
: "${AGENT_SLUG:="${_detected_slug}"}"
: "${AGENT_URL:="http://${_detected_ip}:${LISTENER_PORT:-8100}"}"
: "${AGENT_CAPABILITIES:="chat,code,research"}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
AGENT_NAME_VAL="${AGENT_NAME}"
AGENT_SLUG_VAL="${AGENT_SLUG}"
AGENT_URL_VAL="${AGENT_URL}"
CAPABILITIES="${AGENT_CAPABILITIES}"

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    --name) AGENT_NAME_VAL="$2"; shift 2 ;;
    --slug) AGENT_SLUG_VAL="$2"; shift 2 ;;
    --url) AGENT_URL_VAL="$2"; shift 2 ;;
    --capabilities) CAPABILITIES="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "Error: A2A_GATEWAY_URL not configured" >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Get admin JWT ---
echo "Obtaining admin JWT from gateway..."
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

echo "✅ JWT obtained"

# --- Fetch existing agents to check for slug conflict and get workspaceId ---
echo "Checking if agent '${AGENT_SLUG_VAL}' already exists..."
EXISTING=$(curl -sS -w "\n%{http_code}" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

EXISTING_HTTP=$(echo "$EXISTING" | tail -1)
EXISTING_BODY=$(echo "$EXISTING" | sed '$d')

# Extract existing agent ID and workspace ID
EXISTING_ID=$(echo "$EXISTING_BODY" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        for a in agents:
            if a.get('slug') == slug:
                print(a.get('id', ''))
                break
except: pass
" 2>/dev/null)

WORKSPACE_ID=$(echo "$EXISTING_BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list) and agents:
        print(agents[0].get('workspaceId', ''))
except: pass
" 2>/dev/null)

# --- Build the agent payload (gateway uses agentUrl, not url) ---
PAYLOAD=$(python3 -c "
import json, sys
payload = {
    'name': sys.argv[1],
    'slug': sys.argv[2],
    'agentUrl': sys.argv[3],
    'capabilities': sys.argv[4].split(','),
    'authType': 'bearer',
    'status': 'active'
}
if sys.argv[5]:
    payload['workspaceId'] = sys.argv[5]
print(json.dumps(payload))
" "$AGENT_NAME_VAL" "$AGENT_SLUG_VAL" "$AGENT_URL_VAL" "$CAPABILITIES" "$WORKSPACE_ID")

if [[ -n "$EXISTING_ID" ]]; then
  # --- Update existing agent ---
  echo "Agent '${AGENT_SLUG_VAL}' exists (ID: ${EXISTING_ID}) — updating..."
  RESPONSE=$(curl -sS -w "\n%{http_code}" \
    -X PUT \
    -H "Authorization: Bearer ${AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD" \
    "${GATEWAY_URL}/v0/admin/agents/${EXISTING_ID}" 2>/dev/null)
else
  # --- Create new agent ---
  echo "Agent '${AGENT_SLUG_VAL}' not found — creating..."
  RESPONSE=$(curl -sS -w "\n%{http_code}" \
    -X POST \
    -H "Authorization: Bearer ${AUTH_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$PAYLOAD" \
    "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)
fi

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [[ "$HTTP_CODE" != "200" && "$HTTP_CODE" != "201" ]]; then
  echo "Error: HTTP ${HTTP_CODE}" >&2
  echo "$BODY" >&2
  exit 1
fi

# --- Format output ---
echo ""
echo "═══════════════════════════════════════════════════"
echo "  Agent Registered Successfully"
echo "═══════════════════════════════════════════════════"
echo "$BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    agent = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agent, list) and agent:
        agent = agent[0]
    print('  Name:         ' + agent.get('name', 'N/A'))
    print('  Slug:         ' + agent.get('slug', 'N/A'))
    print('  ID:           ' + agent.get('id', 'N/A'))
    print('  URL:          ' + agent.get('agentUrl', agent.get('url', 'N/A')))
    caps = agent.get('capabilities', [])
    if isinstance(caps, list):
        print('  Capabilities: ' + ', '.join(caps))
    else:
        print('  Capabilities: ' + str(caps))
    print('  Auth Type:    ' + agent.get('authType', 'N/A'))
    print('  Status:       ' + agent.get('status', 'N/A'))
except Exception as e:
    print('  (raw response above)')
    print('  Parse note: ' + str(e))
" 2>/dev/null
echo "═══════════════════════════════════════════════════"
echo ""
echo "Gateway: ${GATEWAY_URL}"
echo "Next: Start the listener with a2a-server/start.sh"


path: status.sh
content: #!/usr/bin/env bash
# a2a-register status.sh — Check this OpenClaw instance's registration status in the A2A API Gateway
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLIENT_DIR="${SCRIPT_DIR}/../a2a-client"
CONF_FILE="${CLIENT_DIR}/a2a.conf"

# --- Source config file if it exists ---
if [[ -f "$CONF_FILE" ]]; then
  # shellcheck source=a2a.conf
  source "$CONF_FILE"
fi

# --- Auto-detect defaults (agent identity only, NOT gateway URL) ---
_auto_slug() { hostname -s 2>/dev/null | tr '[:upper:]' '[:lower:]' || echo "openclaw"; }

: "${A2A_GATEWAY_URL:=""}"  # REQUIRED — configure via a2a.conf or env var
: "${A2A_GATEWAY_API_KEY:=""}"
: "${AGENT_SLUG:="$(_auto_slug)"}"

# --- Apply config ---
GATEWAY_URL="${A2A_GATEWAY_URL}"
[REDACTED_SECRET]"
AGENT_SLUG_VAL="${AGENT_SLUG}"

# --- Parse args (CLI flags override everything) ---
while [[ $# -gt 0 ]]; do
  case "$1" in
    --gateway-url) GATEWAY_URL="$2"; shift 2 ;;
    --api-key) API_KEY="$2"; shift 2 ;;
    --slug) AGENT_SLUG_VAL="$2"; shift 2 ;;
    *) echo "Unknown argument: $1" >&2; exit 1 ;;
  esac
done

# --- Validate gateway URL ---
if [[ -z "$GATEWAY_URL" ]]; then
  echo "Error: A2A_GATEWAY_URL not configured" >&2
  echo "  Set it in a2a.conf, via env var, or use --gateway-url flag" >&2
  exit 1
fi

# --- Get admin JWT ---
AUTH_TOKEN=$(curl -sS "${GATEWAY_URL}/v0/admin/bootstrap" 2>/dev/null | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    print(data.get('accessToken', ''))
except: print('')
" 2>/dev/null)

if [[ -z "$AUTH_TOKEN" ]]; then
  echo "Error: Failed to obtain admin JWT from gateway" >&2
  exit 1
fi

# --- Fetch all agents and find ours ---
RESPONSE=$(curl -sS -w "\n%{http_code}" \
  -H "Authorization: Bearer ${AUTH_TOKEN}" \
  -H "Content-Type: application/json" \
  "${GATEWAY_URL}/v0/admin/agents" 2>/dev/null)

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [[ "$HTTP_CODE" != "200" ]]; then
  echo "Error: Failed to fetch agents (HTTP ${HTTP_CODE})" >&2
  echo "$BODY" >&2
  exit 1
fi

# --- Check if our agent is registered ---
echo "═══════════════════════════════════════════════════"
echo "  Agent Registration Status"
echo "═══════════════════════════════════════════════════"
echo ""

FOUND=$(echo "$BODY" | python3 -c "
import sys, json
slug = '$AGENT_SLUG_VAL'
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        found = False
        for a in agents:
            if a.get('slug') == slug:
                found = True
                print('  Status:       ✅ REGISTERED')
                print('  Name:         ' + a.get('name', 'N/A'))
                print('  Slug:         ' + a.get('slug', 'N/A'))
                print('  ID:           ' + a.get('id', 'N/A'))
                print('  URL:          ' + a.get('agentUrl', a.get('url', 'N/A')))
                caps = a.get('capabilities', [])
                if isinstance(caps, list):
                    print('  Capabilities: ' + ', '.join(caps))
                else:
                    print('  Capabilities: ' + str(caps))
                print('  Auth Type:    ' + a.get('authType', a.get('auth_type', 'N/A')))
                print('  Agent Status: ' + a.get('status', 'N/A'))
                updated = a.get('updatedAt', a.get('updated_at'))
                if updated:
                    print('  Last Updated: ' + updated)
                break
        if not found:
            print('  Status:       ❌ NOT REGISTERED')
            print('  Agent ' + slug + ' not found in gateway registry')
            print('  Run ./register.sh to register')
    else:
        print('  Status:       ⚠ UNEXPECTED RESPONSE FORMAT')
except Exception as e:
    print('  Status:       ⚠ PARSE ERROR')
    print('  Error: ' + str(e))
" 2>/dev/null)

echo "$FOUND"

# --- Also show total agent count ---
TOTAL=$(echo "$BODY" | python3 -c "
import sys, json
try:
    data = json.load(sys.stdin)
    agents = data.get('data', data) if isinstance(data, dict) else data
    if isinstance(agents, list):
        print(len(agents))
    else:
        print('?')
except: print('?')
" 2>/dev/null)

echo ""
echo "Total agents registered: ${TOTAL}"
echo "Gateway: ${GATEWAY_URL}"
echo "═══════════════════════════════════════════════════"


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)