config(git,claude,ssh): templated gitconfig; claude settings/commands/hooks/skills; ssh config

gitconfig: name/email from chezmoi data, gh credential helper by PATH
lookup (portable to darwin). claude: no credentials, history, projects,
sessions, or plugin caches. ssh config as private_ (0600).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Prad NukalaandClaude Fable 5 committed 2026-08-09 14:53:46 -04:00
1 parent 3c691e8dc5
commit 20891f3910
476 files changed
+59086 -7

No files matched your search

@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Probes Cloudflare API auth state for the Turnstile Spin agent.
#
# Reads:
# $CLOUDFLARE_API_TOKEN (required)
# $CLOUDFLARE_ACCOUNT_ID (optional; if set, must be one of the token's accounts)
#
# Outputs JSON to stdout, always exits 0. The agent reads `status`:
# "ok" ; selected account passed both Turnstile and Workers scope probes
# "missing_token" ; no token set, or wrangler whoami failed
# "missing_scope" ; token lacks Account.Turnstile:Edit on the selected account
# "missing_workers_scope"; token has Turnstile scope but lacks Workers Scripts on the selected account
# "multiple_accounts" ; token covers >1 accounts and $CLOUDFLARE_ACCOUNT_ID is unset; agent must ask user to pick, set it, and re-run
# "account_mismatch" ; $CLOUDFLARE_ACCOUNT_ID is set but is not in the token's accounts list
#
# Human-readable diagnostics go to stderr. The agent surfaces them to the user.
set -uo pipefail
emit() {
echo "$1"
exit 0
}
token="${CLOUDFLARE_API_TOKEN:-}"
declared_account="${CLOUDFLARE_ACCOUNT_ID:-}"
if [ -z "$token" ]; then
echo "auth-probe: \$CLOUDFLARE_API_TOKEN is not set." >&2
emit '{"status":"missing_token","reason":"no_env_var"}'
fi
whoami_json=$(npx wrangler whoami --json 2>/dev/null || true)
if [ -z "$whoami_json" ] || [ "$(echo "$whoami_json" | head -c 1)" != "{" ]; then
echo "auth-probe: wrangler whoami returned no JSON. Token may be invalid or expired." >&2
emit '{"status":"missing_token","reason":"whoami_failed"}'
fi
# Extract the accounts array. Fall back to python3 if jq is missing.
accounts_json=$(echo "$whoami_json" | (jq -c '.accounts' 2>/dev/null || python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)['accounts']))"))
account_count=$(echo "$accounts_json" | (jq 'length' 2>/dev/null || python3 -c "import sys,json; print(len(json.load(sys.stdin)))"))
if [ -z "$account_count" ] || [ "$account_count" = "0" ] || [ "$account_count" = "null" ]; then
echo "auth-probe: wrangler whoami succeeded but no accounts found on the token." >&2
emit '{"status":"missing_token","reason":"no_accounts"}'
fi
# Pick the account to probe:
# - $CLOUDFLARE_ACCOUNT_ID set: must be in the token's accounts list, else account_mismatch
# - unset, exactly 1 account: use it silently
# - unset, >1 accounts: emit multiple_accounts; agent picks and re-runs
if [ -n "$declared_account" ]; then
in_list=$(echo "$accounts_json" | (jq --arg id "$declared_account" 'map(.id) | index($id) != null' 2>/dev/null || python3 -c "import sys,json; print('true' if any(a['id']==sys.argv[1] for a in json.load(sys.stdin)) else 'false')" "$declared_account"))
if [ "$in_list" != "true" ]; then
echo "auth-probe: \$CLOUDFLARE_ACCOUNT_ID ($declared_account) is not one of the token's accounts." >&2
echo "auth-probe: Either unset \$CLOUDFLARE_ACCOUNT_ID, or set it to an account included in the token's Account Resources." >&2
emit "{\"status\":\"account_mismatch\",\"declared\":\"$declared_account\",\"accounts\":$accounts_json}"
fi
account_id="$declared_account"
elif [ "$account_count" = "1" ]; then
account_id=$(echo "$accounts_json" | (jq -r '.[0].id' 2>/dev/null || python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])"))
else
echo "auth-probe: token covers $account_count accounts; ask the user to pick one, then export \$CLOUDFLARE_ACCOUNT_ID and re-run." >&2
emit "{\"status\":\"multiple_accounts\",\"accounts\":$accounts_json}"
fi
# Probe Turnstile scope on the selected account
tmp=$(mktemp)
http_code=$(curl -sS -w "%{http_code}" -o "$tmp" \
"https://api.cloudflare.com/client/v4/accounts/$account_id/challenges/widgets" \
-H "Authorization: Bearer $token" 2>/dev/null || echo "000")
body=$(cat "$tmp"); rm -f "$tmp"
success=$(echo "$body" | (jq -r '.success' 2>/dev/null || echo "false"))
if [ "$success" != "true" ]; then
echo "auth-probe: token cannot read /challenges/widgets on account $account_id (HTTP $http_code). Missing Account.Turnstile:Edit." >&2
emit "{\"status\":\"missing_scope\",\"account_id\":\"$account_id\",\"http_code\":$http_code}"
fi
# Probe Workers scope on the selected account. GET /workers/scripts requires
# Account.Workers Scripts:Read, which is a best-effort proxy for Edit. Tokens
# granted Edit-only (without Read) will fail this probe and emit a confusing
# missing_workers_scope; the agent should suggest adding Read alongside Edit.
tmp=$(mktemp)
workers_code=$(curl -sS -w "%{http_code}" -o "$tmp" \
"https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts" \
-H "Authorization: Bearer $token" 2>/dev/null || echo "000")
workers_body=$(cat "$tmp"); rm -f "$tmp"
workers_success=$(echo "$workers_body" | (jq -r '.success' 2>/dev/null || echo "false"))
if [ "$workers_success" != "true" ]; then
echo "auth-probe: token cannot read /workers/scripts on account $account_id (HTTP $workers_code). Missing Account.Workers Scripts:Edit." >&2
emit "{\"status\":\"missing_workers_scope\",\"account_id\":\"$account_id\",\"http_code\":$workers_code}"
fi
emit "{\"status\":\"ok\",\"account_id\":\"$account_id\",\"accounts\":$accounts_json}"
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Retrieves the secret for an existing Turnstile widget via the Cloudflare API.
# Used by the recovery flow when binding the secret to a freshly deployed Worker.
#
# Reads:
# $CLOUDFLARE_API_TOKEN (required)
#
# Args:
# --account-id <id> Cloudflare account ID
# --sitekey <key> Widget sitekey to look up
#
# Outputs JSON. Exit 0 on success, 1 on failure.
# ok: {"status":"ok","secret":"<secret>","clearance_level":"<level>","domains":[<list>]}
# no_scope: {"status":"missing_read_scope","detail":"token lacks Account.Turnstile:Read"}
# not_found: {"status":"error","reason":"widget_not_found","http_code":<code>}
#
# The agent uses clearance_level to enforce the pre-clearance scope boundary
# (Spin only applies to widgets where clearance_level == "no_clearance"; for
# other levels siteverify is optional and the recovery flow should exit).
#
# Never propose recreating the widget to get a fresh secret; that breaks
# the existing sitekey everywhere the user has it deployed in their frontend.
set -uo pipefail
while [[ $# -gt 0 ]]; do
case $1 in
--account-id) ACCOUNT_ID="$2"; shift 2 ;;
--sitekey) SITEKEY="$2"; shift 2 ;;
*) echo "fetch-secret: unknown arg $1" >&2; exit 2 ;;
esac
done
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN must be set}"
: "${ACCOUNT_ID:?--account-id required}"
: "${SITEKEY:?--sitekey required}"
tmp=$(mktemp)
http_code=$(curl -sS -w "%{http_code}" -o "$tmp" \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/challenges/widgets/$SITEKEY" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" 2>/dev/null || echo "000")
body=$(cat "$tmp"); rm -f "$tmp"
if [ "$http_code" = "200" ]; then
secret=$(echo "$body" | (jq -r '.result.secret' 2>/dev/null || python3 -c "import sys,json; print(json.load(sys.stdin)['result']['secret'])"))
clearance=$(echo "$body" | (jq -r '.result.clearance_level // "no_clearance"' 2>/dev/null || python3 -c "import sys,json; print(json.load(sys.stdin)['result'].get('clearance_level','no_clearance'))"))
domains=$(echo "$body" | (jq -c '.result.domains // []' 2>/dev/null || python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)['result'].get('domains',[])))"))
if [ -n "$secret" ] && [ "$secret" != "null" ]; then
echo "{\"status\":\"ok\",\"secret\":\"$secret\",\"clearance_level\":\"$clearance\",\"domains\":$domains}"
exit 0
fi
fi
if [ "$http_code" = "403" ]; then
code=$(echo "$body" | (jq -r '.errors[0].code // 0' 2>/dev/null || echo "0"))
if [ "$code" = "10000" ]; then
echo "fetch-secret: token can edit Turnstile widgets but cannot read this one's secret." >&2
echo "fetch-secret: add Account.Turnstile:Read to the token, or fall back to user paste." >&2
echo "{\"status\":\"missing_read_scope\",\"detail\":\"token lacks Account.Turnstile:Read\"}"
exit 1
fi
fi
echo "fetch-secret: widget lookup failed (HTTP $http_code)." >&2
echo "{\"status\":\"error\",\"reason\":\"widget_not_found\",\"http_code\":$http_code}"
exit 1
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Persists the canonical Spin skill bundle (SKILL.md + scripts/ + references/)
# from cloudflare/skills to the user's repo so the agent can re-load it on
# follow-up tasks without re-pasting the bootstrap prompt.
#
# Args:
# --path <path> SKILL.md destination, e.g. .claude/skills/turnstile-spin/SKILL.md.
# The bundle is extracted into the parent directory of <path>,
# so scripts land at e.g. .claude/skills/turnstile-spin/scripts/.
#
# Outputs JSON. Exit 0 if the bundle was written, 1 on failure.
# ok: {"status":"ok","path":"<path>","bundle_root":"<dir>","scripts":[<list>]}
# fail: {"status":"error","reason":"<reason>"}
set -uo pipefail
PATH_ARG=""
while [[ $# -gt 0 ]]; do
case $1 in
--path) PATH_ARG="$2"; shift 2 ;;
*) echo "persist-skill: unknown arg $1" >&2; exit 2 ;;
esac
done
: "${PATH_ARG:?--path required}"
TARGET_DIR=$(dirname "$PATH_ARG")
mkdir -p "$TARGET_DIR"
# Install the canonical bundle from cloudflare/skills via degit. This writes
# SKILL.md, scripts/, references/, templates/, tests/ into $TARGET_DIR.
if ! npx --yes degit cloudflare/skills/skills/turnstile-spin "$TARGET_DIR" >/dev/null 2>&1; then
echo "persist-skill: degit failed; cannot fetch cloudflare/skills/skills/turnstile-spin." >&2
echo "persist-skill: ensure your network can reach github.com and try again, or install manually." >&2
echo "{\"status\":\"error\",\"reason\":\"degit_failed\"}"
exit 1
fi
if [ ! -f "$TARGET_DIR/SKILL.md" ]; then
echo "persist-skill: bundle extracted but SKILL.md is missing at $TARGET_DIR/SKILL.md." >&2
echo "{\"status\":\"error\",\"reason\":\"skill_missing\"}"
exit 1
fi
# Make scripts executable so the agent can invoke them directly.
if [ -d "$TARGET_DIR/scripts" ]; then
chmod +x "$TARGET_DIR/scripts"/*.sh 2>/dev/null || true
fi
scripts_list=$(ls "$TARGET_DIR/scripts" 2>/dev/null | sed 's/.*/"&"/' | paste -sd, -)
echo "persist-skill: wrote bundle to $TARGET_DIR" >&2
echo "{\"status\":\"ok\",\"path\":\"$PATH_ARG\",\"bundle_root\":\"$TARGET_DIR\",\"scripts\":[$scripts_list]}"
exit 0
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# Validates a deployed Turnstile siteverify Worker end-to-end.
#
# Reads:
# $CLOUDFLARE_API_TOKEN (required for hostname check)
#
# Args:
# --worker-url <url> Deployed Worker URL (from worker-deploy.sh)
# --account-id <id> Cloudflare account ID
# --sitekey <key> Widget sitekey (from widget-create.sh)
# --expected-domains <a,b,c> Comma-separated domains that must appear in the widget's domains array
#
# Outputs JSON. Exit 0 if all three checks pass, 1 otherwise.
# ok: {"status":"ok"}
# fail: {"status":"error","check":"health|dummy_siteverify|worker_metadata|hostname","detail":"<msg>"}
set -uo pipefail
while [[ $# -gt 0 ]]; do
case $1 in
--worker-url) WORKER_URL="$2"; shift 2 ;;
--account-id) ACCOUNT_ID="$2"; shift 2 ;;
--sitekey) SITEKEY="$2"; shift 2 ;;
--expected-domains) EXPECTED_DOMAINS="$2"; shift 2 ;;
*) echo "validate: unknown arg $1" >&2; exit 2 ;;
esac
done
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN must be set}"
: "${WORKER_URL:?--worker-url required}"
: "${ACCOUNT_ID:?--account-id required}"
: "${SITEKEY:?--sitekey required}"
: "${EXPECTED_DOMAINS:?--expected-domains required}"
# Check 1: health endpoint
health=$(curl -sSf "${WORKER_URL}/health" 2>/dev/null || echo "")
if [ -z "$health" ] || ! echo "$health" | grep -q '"ok":true'; then
echo "validate: health check failed; $WORKER_URL/health did not return {ok:true,version:...}" >&2
echo "{\"status\":\"error\",\"check\":\"health\",\"detail\":\"worker /health did not respond ok:true\"}"
exit 1
fi
# Check 2: dummy siteverify; should return success:false + error-codes array
dummy=$(curl -sS -X POST "${WORKER_URL}/" \
-H "Content-Type: application/json" \
-d '{"token":"XXXX.DUMMY.TOKEN.XXXX"}' 2>/dev/null || echo "")
success=$(echo "$dummy" | (jq -r '.success // "missing"' 2>/dev/null || echo "missing"))
errors=$(echo "$dummy" | (jq -r '.["error-codes"] | length // 0' 2>/dev/null || echo "0"))
if [ "$success" != "false" ] || [ "$errors" = "0" ]; then
echo "validate: dummy siteverify check failed; expected success:false + error-codes; got: $dummy" >&2
echo "{\"status\":\"error\",\"check\":\"dummy_siteverify\",\"detail\":\"unexpected response shape\"}"
exit 1
fi
# Check 2b: confirm the Worker is the managed template (not a customer-written
# replacement) by looking for the _worker metadata field. If absent, the user
# deployed a custom Worker; surface it so the agent can alert them.
worker_meta=$(echo "$dummy" | (jq -r '._worker.worker_version // "missing"' 2>/dev/null || echo "missing"))
if [ "$worker_meta" = "missing" ]; then
echo "validate: _worker metadata missing from response; this is not the managed Spin Worker template." >&2
echo "{\"status\":\"error\",\"check\":\"worker_metadata\",\"detail\":\"_worker field missing; user may have deployed a custom Worker\"}"
exit 1
fi
# Check 3: hostname / widget domains registered
widget=$(curl -sS \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/challenges/widgets/$SITEKEY" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" 2>/dev/null)
registered=$(echo "$widget" | (jq -r '.result.domains[]' 2>/dev/null || python3 -c "import sys,json; [print(d) for d in json.load(sys.stdin)['result']['domains']]"))
missing=""
IFS=',' read -ra DOMS <<< "$EXPECTED_DOMAINS"
for d in "${DOMS[@]}"; do
if ! echo "$registered" | grep -qFx "$d"; then
missing="${missing}${d} "
fi
done
if [ -n "$missing" ]; then
echo "validate: hostname check failed; domains not on widget: $missing" >&2
echo "{\"status\":\"error\",\"check\":\"hostname\",\"detail\":\"missing domains: ${missing% }\"}"
exit 1
fi
echo '{"status":"ok"}'
@@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Creates a Turnstile widget via the Cloudflare API.
#
# Reads:
# $CLOUDFLARE_API_TOKEN (required)
#
# Args:
# --account-id <id> Cloudflare account ID
# --name <name> Widget name (e.g. "myproject (Spin)")
# --domains <a,b,c> Comma-separated domain list (include localhost,127.0.0.1)
# --mode <managed|invisible|non-interactive> Default: managed
#
# Outputs JSON to stdout. Exit 0 on success, 1 on failure. Diagnostics on stderr.
# ok: {"status":"ok","sitekey":"<key>","secret":"<secret>"}
# error: {"status":"error","code":<code>,"message":"<msg>"}
# code 10000 → token lacks Account.Turnstile:Edit
set -uo pipefail
MODE="managed"
while [[ $# -gt 0 ]]; do
case $1 in
--account-id) ACCOUNT_ID="$2"; shift 2 ;;
--name) NAME="$2"; shift 2 ;;
--domains) DOMAINS="$2"; shift 2 ;;
--mode) MODE="$2"; shift 2 ;;
*) echo "widget-create: unknown arg $1" >&2; exit 2 ;;
esac
done
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN must be set}"
: "${ACCOUNT_ID:?--account-id required}"
: "${NAME:?--name required}"
: "${DOMAINS:?--domains required}"
domains_json=$(python3 -c "import sys; print(__import__('json').dumps(sys.argv[1].split(',')))" "$DOMAINS")
body=$(curl -sS -X POST \
"https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/challenges/widgets" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\":\"$NAME\",\"domains\":$domains_json,\"mode\":\"$MODE\"}" 2>/dev/null)
success=$(echo "$body" | (jq -r '.success' 2>/dev/null || python3 -c "import sys,json; print(str(json.load(sys.stdin).get('success',False)).lower())"))
if [ "$success" = "true" ]; then
sitekey=$(echo "$body" | (jq -r '.result.sitekey' 2>/dev/null || python3 -c "import sys,json; print(json.load(sys.stdin)['result']['sitekey'])"))
secret=$(echo "$body" | (jq -r '.result.secret' 2>/dev/null || python3 -c "import sys,json; print(json.load(sys.stdin)['result']['secret'])"))
echo "{\"status\":\"ok\",\"sitekey\":\"$sitekey\",\"secret\":\"$secret\"}"
exit 0
fi
code=$(echo "$body" | (jq -r '.errors[0].code // 0' 2>/dev/null || echo "0"))
message=$(echo "$body" | (jq -r '.errors[0].message // "unknown"' 2>/dev/null || echo "unknown") | tr -d '"')
echo "widget-create: failed (code=$code): $message" >&2
echo "{\"status\":\"error\",\"code\":$code,\"message\":\"$message\"}"
exit 1
@@ -0,0 +1,102 @@
#!/usr/bin/env bash
# Deploys the managed siteverify Worker template to the user's account
# and sets TURNSTILE_SECRET_KEY as a Worker secret.
#
# Reads:
# $CLOUDFLARE_API_TOKEN (required)
# $WIDGET_SECRET (required; secret captured from widget-create.sh)
#
# Args:
# --name <worker-name> Base name; appends a hash suffix if taken
# --deploy-dir <path> Where to extract the template. Default: /tmp/turnstile-siteverify-deploy
#
# Outputs JSON. Exit 0 on success, non-zero on failure.
# ok: {"status":"ok","worker_url":"<url>","worker_name":"<name>"}
# conflict: {"status":"error","reason":"name_conflict_after_retry"}
# deploy_failed: {"status":"error","reason":"deploy_failed"}
# set_secret: {"status":"error","reason":"set_secret_failed","worker_name":"<name>"}
# url_parse: {"status":"error","reason":"url_parse_failed","worker_name":"<name>"}
set -uo pipefail
NAME="${WORKER_NAME:-turnstile-siteverify}"
DEPLOY_DIR="/tmp/turnstile-siteverify-deploy"
while [[ $# -gt 0 ]]; do
case $1 in
--name) NAME="$2"; shift 2 ;;
--deploy-dir) DEPLOY_DIR="$2"; shift 2 ;;
*) echo "worker-deploy: unknown arg $1" >&2; exit 2 ;;
esac
done
: "${CLOUDFLARE_API_TOKEN:?CLOUDFLARE_API_TOKEN must be set}"
: "${WIDGET_SECRET:?WIDGET_SECRET must be set}"
deploy_log=$(mktemp)
deploy() {
local target_name="$1"
rm -rf "$DEPLOY_DIR"
npx --yes degit cloudflare/skills/skills/turnstile-spin/templates/worker "$DEPLOY_DIR" >/dev/null 2>&1
# Capture both streams. Wrangler prints the success URL and version ID on
# stdout; progress indicators on stderr. Capturing only stderr loses the URL.
(cd "$DEPLOY_DIR" && npx wrangler deploy --name "$target_name") >"$deploy_log" 2>&1
}
if ! deploy "$NAME"; then
if grep -q "script name already in use" "$deploy_log"; then
NAME="${NAME}-$(openssl rand -hex 3 2>/dev/null || date +%s | tail -c 5)"
echo "worker-deploy: name conflict; retrying as $NAME" >&2
if ! deploy "$NAME"; then
echo "worker-deploy: deploy failed even with new name" >&2
cat "$deploy_log" >&2
rm -f "$deploy_log"
echo "{\"status\":\"error\",\"reason\":\"name_conflict_after_retry\"}"
exit 1
fi
else
cat "$deploy_log" >&2
rm -f "$deploy_log"
echo "{\"status\":\"error\",\"reason\":\"deploy_failed\"}"
exit 1
fi
fi
# Set the secret. Use `echo` (not `printf '%s'`); wrangler secret put expects
# newline-terminated stdin; printf without a trailing newline lands an empty
# secret in the runtime even though wrangler reports success.
secret_log=$(mktemp)
set_secret() {
echo "$WIDGET_SECRET" | (cd "$DEPLOY_DIR" && npx wrangler secret put TURNSTILE_SECRET_KEY --name "$NAME") >"$secret_log" 2>&1
}
if ! set_secret; then
echo "worker-deploy: failed to set TURNSTILE_SECRET_KEY on $NAME" >&2
cat "$secret_log" >&2
detail=$(tail -3 "$secret_log" | tr '\n' ' ' | sed 's/"/\\"/g' | head -c 200)
rm -f "$deploy_log" "$secret_log"
echo "{\"status\":\"error\",\"reason\":\"set_secret_failed\",\"worker_name\":\"$NAME\",\"detail\":\"$detail\"}"
exit 1
fi
rm -f "$secret_log"
sleep 5
# Extract the deployed URL. Try workers.dev first, then any https URL in the
# log that is not the well-known cloudflare.com host (custom domain deploys
# and Workers for Platforms don't always land at a workers.dev hostname).
worker_url=$(grep -oE 'https://[a-zA-Z0-9._-]+\.workers\.dev' "$deploy_log" | head -1)
if [ -z "$worker_url" ]; then
worker_url=$(grep -oE 'https://[a-zA-Z0-9.-]+(/[^[:space:]]*)?' "$deploy_log" \
| grep -v -E 'cloudflare\.com|workers-sdk|github\.com' \
| head -1)
fi
rm -f "$deploy_log"
if [ -z "$worker_url" ]; then
echo "worker-deploy: deployed but could not parse Worker URL from wrangler output" >&2
echo "worker-deploy: ask the user for the URL printed by wrangler deploy and pass it to validate.sh manually" >&2
echo "{\"status\":\"error\",\"reason\":\"url_parse_failed\",\"worker_name\":\"$NAME\"}"
exit 1
fi
echo "{\"status\":\"ok\",\"worker_url\":\"$worker_url\",\"worker_name\":\"$NAME\"}"