#!/usr/bin/env bash set -euo pipefail usage() { cat <<'EOF' Usage: scripts/call-action.sh [action] [--args '{"k":"v"}'] [--args-file args.json] [--request-id ] [--path openclaw-action] [--test] [--pretty] [--dry-run] Notes: - `action` is optional when --args/--args-file contains a full payload with top-level `action` + `args`. - `--args` / `--args-file` may contain either: 1) a plain args object, or 2) a full payload object: {"action":"...","args":{...},"request_id":"..."} Environment: N8N_ACTION_PATH Default router webhook path (default: openclaw-action) N8N_WEBHOOK_SECRET Shared secret for header auth N8N_BASE_URL Base n8n URL (default: http://192.168.153.113:18808) N8N_SECRET_HEADER Header name (default: x-openclaw-secret) Examples: scripts/call-action.sh append_log --args '{"text":"backup complete"}' --request-id auto scripts/call-action.sh --args-file assets/test-send-email-draft.json --pretty scripts/call-action.sh notify --args-file notify.json --test --pretty EOF } SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" CALL_WEBHOOK="$SCRIPT_DIR/call-webhook.sh" ACTION_PATH="${N8N_ACTION_PATH:-openclaw-action}" ACTION="" ARGS='{}' ARGS_FILE="" REQUEST_ID="" MODE_FLAG="" PRETTY=0 DRY_RUN=0 EXTRA_ARGS=() while [[ $# -gt 0 ]]; do case "$1" in --help|-h) usage exit 0 ;; --args) ARGS="${2:?missing value for --args}" shift 2 ;; --args-file) ARGS_FILE="${2:?missing value for --args-file}" shift 2 ;; --request-id) REQUEST_ID="${2:?missing value for --request-id}" shift 2 ;; --path) ACTION_PATH="${2:?missing value for --path}" shift 2 ;; --test) MODE_FLAG="--test" shift ;; --prod) MODE_FLAG="--prod" shift ;; --pretty) PRETTY=1 shift ;; --dry-run) DRY_RUN=1 shift ;; --*) echo "Unknown option: $1" >&2 usage >&2 exit 2 ;; *) if [[ -z "$ACTION" ]]; then ACTION="$1" else EXTRA_ARGS+=("$1") fi shift ;; esac done if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then echo "Unexpected extra arguments: ${EXTRA_ARGS[*]}" >&2 exit 2 fi if [[ -n "$ARGS_FILE" ]]; then ARGS="$(cat "$ARGS_FILE")" fi if [[ "$REQUEST_ID" == "auto" ]]; then REQUEST_ID="$(python3 - <<'PY' import uuid print(uuid.uuid4()) PY )" fi PAYLOAD="$({ python3 - <<'PY' "$ACTION" "$ARGS" "$REQUEST_ID" import json, sys cli_action = sys.argv[1] raw = json.loads(sys.argv[2]) cli_request_id = sys.argv[3] if not isinstance(raw, dict): raise SystemExit('Action args must decode to a JSON object.') if 'action' in raw and 'args' in raw: file_action = raw.get('action', '') file_args = raw.get('args', {}) file_request_id = raw.get('request_id', '') if not isinstance(file_args, dict): raise SystemExit('Full payload args must decode to a JSON object.') if cli_action and file_action and cli_action != file_action: raise SystemExit(f'CLI action {cli_action!r} does not match payload action {file_action!r}.') action = cli_action or file_action args = file_args request_id = cli_request_id or file_request_id else: action = cli_action args = raw request_id = cli_request_id if not action: raise SystemExit('Action is required unless provided inside --args/--args-file full payload.') payload = { 'action': action, 'args': args, } if request_id: payload['request_id'] = request_id print(json.dumps(payload, separators=(',', ':'))) PY } )" CMD=("$CALL_WEBHOOK" "$ACTION_PATH" --data "$PAYLOAD") [[ -n "$MODE_FLAG" ]] && CMD+=("$MODE_FLAG") [[ "$PRETTY" -eq 1 ]] && CMD+=(--pretty) [[ "$DRY_RUN" -eq 1 ]] && CMD+=(--dry-run) exec "${CMD[@]}"