#!/bin/sh # catchhook — terminal client for CatchHook (https://catchhook.catchhook.workers.dev) # install: curl -s https://catchhook.catchhook.workers.dev/cli -o catchhook && chmod +x catchhook # usage: # ./catchhook new create a capture bin # ./catchhook tail live-tail captures in your terminal (Ctrl-C stops) # ./catchhook relay [--all] # re-deliver captures to a local server, e.g. # target http://localhost:3000 — webhooks to # localhost without a tunnel. --all replays # history too (default: only new captures). # ./catchhook help this text # needs only curl + sed. no account required. ORIGIN="https://catchhook.catchhook.workers.dev" usage() { sed -n '2,13p' "$0" | sed 's/^# \{0,1\}//' } case "$1" in new) curl -sf -H 'Accept: text/plain' "$ORIGIN/new" || { echo "error: could not create bin (rate limit or network)" >&2; exit 1; } ;; tail) arg="$2" [ -n "$arg" ] || { echo "usage: catchhook tail " >&2; exit 1; } # accept a bare id, /h/... or /b/ URL id=$(printf '%s' "$arg" | sed -n 's#.*/[hb]/\([a-z0-9][a-z0-9]*\).*#\1#p') [ -n "$id" ] || id="$arg" hdr=$(mktemp) || exit 1 trap 'rm -f "$hdr"' EXIT trap 'exit 130' INT TERM echo "tailing $ORIGIN/h/$id (Ctrl-C to stop)" last=0 while :; do out=$(curl -sf -D "$hdr" "$ORIGIN/api/bins/$id/tail?after=$last") || { echo "error: bin not found or server unreachable" >&2; exit 1; } new=$(sed -n 's/^[Xx]-[Cc]atchhook-[Ll]ast: *\([0-9]*\).*/\1/p' "$hdr" | tr -d '\r') [ -n "$new" ] && last="$new" [ -n "$out" ] && printf '%s\n' "$out" sleep 2 done ;; relay) arg="$2"; target="$3"; mode="$4" [ -n "$arg" ] && [ -n "$target" ] || { echo "usage: catchhook relay [--all]" >&2; exit 1; } id=$(printf '%s' "$arg" | sed -n 's#.*/[hb]/\([a-z0-9-][a-z0-9-]*\).*#\1#p') [ -n "$id" ] || id="$arg" target=$(printf '%s' "$target" | sed 's#/$##') hdr=$(mktemp) || exit 1 body=$(mktemp) || exit 1 cfg=$(mktemp) || exit 1 trap 'rm -f "$hdr" "$body" "$cfg"' EXIT trap 'exit 130' INT TERM last=0 if [ "$mode" != "--all" ]; then curl -sf -D "$hdr" "$ORIGIN/api/bins/$id/relay-list?peek=1" >/dev/null || { echo "error: bin not found or server unreachable" >&2; exit 1; } last=$(sed -n 's/^[Xx]-[Cc]atchhook-[Cc]ur: *\([0-9]*\).*/\1/p' "$hdr" | tr -d '\r') [ -n "$last" ] || last=0 fi echo "relaying $ORIGIN/h/$id -> $target (Ctrl-C to stop)" TAB=$(printf '\t') while :; do list=$(curl -sf -D "$hdr" "$ORIGIN/api/bins/$id/relay-list?after=$last") || { echo "error: bin not found or server unreachable" >&2; exit 1; } new=$(sed -n 's/^[Xx]-[Cc]atchhook-[Ll]ast: *\([0-9]*\).*/\1/p' "$hdr" | tr -d '\r') [ -n "$new" ] && last="$new" printf '%s\n' "$list" | while IFS="$TAB" read -r rid method pathq trunc; do [ -n "$rid" ] || continue [ "$trunc" = "1" ] && echo "warning: req $rid body was truncated at 100KB \ -- delivering anyway; an HMAC signature over the full body will NOT verify" >&2 curl -sf "$ORIGIN/api/bins/$id/req/$rid/body" -o "$body" || continue curl -sf "$ORIGIN/api/bins/$id/req/$rid/fwd-headers" \ | sed 's/\\/\\\\/g; s/"/\\"/g; s/^/header = "/; s/$/"/' > "$cfg" case "$method" in GET|HEAD) code=$(curl -s -o /dev/null -w '%{http_code}' -K "$cfg" -X "$method" "$target$pathq") ;; *) code=$(curl -s -o /dev/null -w '%{http_code}' -K "$cfg" -X "$method" --data-binary @"$body" "$target$pathq") ;; esac echo "-> $method $pathq => $code" done sleep 2 done ;; help|--help|-h|'') usage ;; *) echo "unknown command: $1" >&2 usage >&2 exit 1 ;; esac