Skip to content

Instantly share code, notes, and snippets.

@astupka
Last active March 23, 2026 20:00
Show Gist options
  • Select an option

  • Save astupka/4e7d85b2d4bf6332da32f2bfefc47b76 to your computer and use it in GitHub Desktop.

Select an option

Save astupka/4e7d85b2d4bf6332da32f2bfefc47b76 to your computer and use it in GitHub Desktop.
Beast dev deployment + CircleCI monitoring scripts for Claude Code

Beast Deploy Scripts for Claude Code

Scripts for automating CustomInk Beast dev instance deployments and CircleCI build monitoring. Designed to be used by Claude Code agents during development workflows.

Setup

mkdir -p ~/claude_scripts
# Download scripts
curl -sL https://gist.github.com/astupka/9866b91e199ed3c292acd9e3aa7b24b3/raw/beast_deploy.sh -o ~/claude_scripts/beast_deploy.sh
curl -sL https://gist.github.com/astupka/9866b91e199ed3c292acd9e3aa7b24b3/raw/check_ci.sh -o ~/claude_scripts/check_ci.sh
curl -sL https://gist.github.com/astupka/9866b91e199ed3c292acd9e3aa7b24b3/raw/README.md -o ~/claude_scripts/README.md
chmod +x ~/claude_scripts/*.sh

Prerequisites

  • bash 4+ — macOS default is bash 3; use brew install bash if needed
  • AWS CLI — with SSO configured: aws sso login --profile techdev-ci-prod
  • GitHub CLI (gh) — authenticated: gh auth login
  • python3 — for JSON response parsing
  • AWS SSO profile — must have access to read /services/k8s-staging-env-manager/BEAST_API_KEY from Parameter Store

Environment Variables (optional)

Variable Default Description
BEAST_USER_EMAIL git config user.email Email for Beast API auth
AWS_CMD aws on PATH Path to AWS CLI binary
AWS_PROFILE techdev-ci-prod AWS SSO profile name

Usage

Standard Development Workflow

After code changes are complete and pushed:

# 1. Wait for CI to build the Docker image
~/claude_scripts/check_ci.sh customink/rails-backend my-feature-branch ktool-build

# 2. Deploy a Beast dev instance (polls until READY)
~/claude_scripts/beast_deploy.sh my-feature-branch

# 3. Connect to the instance
k8s use d    # select the new instance from the list
k8s console  # Rails console
k8s shell    # bash shell

check_ci.sh

Polls a CircleCI build step until it succeeds or fails.

# Check ktool-build (default step)
~/claude_scripts/check_ci.sh customink/rails-backend my-branch

# Check a specific step
~/claude_scripts/check_ci.sh customink/rails-backend my-branch tests

# Check another repo
~/claude_scripts/check_ci.sh customink/ml-ai-gateway my-branch ktool-build

beast_deploy.sh

Creates a Beast dev instance and polls until it's ready.

# Deploy rails-backend (default app, 2 day expiry)
~/claude_scripts/beast_deploy.sh my-feature-branch

# Deploy a different app
~/claude_scripts/beast_deploy.sh my-branch ml-ai-gateway
~/claude_scripts/beast_deploy.sh my-branch rails-frontend

# Custom expiry (seconds)
~/claude_scripts/beast_deploy.sh my-branch rails-backend 86400  # 1 day

Supported Apps

The script maps app names to Beast blueprint IDs. Common apps include:

App Blueprint ID
rails-backend 127
rails-frontend 125
ml-ai-gateway 291
mms 34
catalog 23
profiles 1
sales 56
creative-services 7
promptr 294

Run with an unknown app name to see the full list.

For Claude Code Sessions

Add this to your project's CLAUDE.md to make Claude aware of these scripts:

## Dev Deploy Scripts

Reusable scripts in `~/claude_scripts/` for CI monitoring and Beast dev deployments.
See `~/claude_scripts/README.md` for full docs.

- `~/claude_scripts/check_ci.sh <repo> <branch> [step]` — poll CircleCI build status
- `~/claude_scripts/beast_deploy.sh <branch> [app] [expiry]` — create Beast dev instance

Standard workflow: check CI → deploy Beast → connect → build tophat → mark PR ready.
Requires AWS SSO session: `aws sso login --profile techdev-ci-prod`

How It Works

  1. check_ci.sh uses the GitHub statuses API (gh api repos/.../commits/.../statuses) to poll CircleCI job status
  2. beast_deploy.sh fetches the Beast API key from AWS Parameter Store, then calls the Beast REST API to create a deployment. Beast triggers a CircleCI pipeline that runs ktool to generate Kubernetes manifests, which Argo CD syncs to the dev cluster.
#!/bin/bash
# Beast Dev Deployment Script
# Usage: beast_deploy.sh <branch> [app] [expiry]
# beast_deploy.sh --redeploy <deployment_id>
# beast_deploy.sh --extend <deployment_id> <date>
# beast_deploy.sh --status <deployment_id>
# beast_deploy.sh --delete <deployment_id>
# beast_deploy.sh --list [app]
#
# Examples:
# beast_deploy.sh astupka/rsq-ai-guidance # create new (rails-backend, 2 day)
# beast_deploy.sh astupka/rsq-ai-guidance ml-ai-gateway # create new for different app
# beast_deploy.sh astupka/rsq-ai-guidance rails-backend 86400 # 1 day expiry
# beast_deploy.sh --redeploy 23607 # redeploy existing instance
# beast_deploy.sh --extend 23607 2026-03-25 # extend expiry to date
# beast_deploy.sh --status 23607 # check deployment status
# beast_deploy.sh --delete 23607 # delete instance
# beast_deploy.sh --list # list active rails-backend deployments
# beast_deploy.sh --list ml-ai-gateway # list active ml-ai-gateway deployments
#
# Environment variables (optional):
# BEAST_USER_EMAIL - defaults to git config user.email
# AWS_CMD - defaults to `aws` on PATH
# AWS_PROFILE - defaults to techdev-ci-prod
#
# Requires: AWS SSO session (aws sso login --profile $AWS_PROFILE)
set -eo pipefail
# App name → Blueprint ID lookup
blueprint_for() {
case "$1" in
rails-backend) echo 127 ;;
rails-frontend) echo 125 ;;
ml-ai-gateway) echo 291 ;;
mms) echo 34 ;;
catalog) echo 23 ;;
profiles) echo 1 ;;
sales) echo 56 ;;
checkout) echo 51 ;;
groups) echo 37 ;;
stores) echo 22 ;;
content-review-queue) echo 133 ;;
creative-services) echo 7 ;;
order-fulfillment-service) echo 141 ;;
decorator-gateway-service) echo 121 ;;
decorator-service) echo 110 ;;
shipping-service) echo 89 ;;
production-engine) echo 95 ;;
promptr) echo 294 ;;
langfuse) echo 285 ;;
notes) echo 50 ;;
journal-service) echo 113 ;;
accounts-service) echo 11 ;;
asset-service) echo 19 ;;
fulfillment-products-service) echo 171 ;;
supplier-service) echo 109 ;;
notification-service) echo 111 ;;
schedule-service) echo 192 ;;
order-service) echo 138 ;;
*) echo "" ;;
esac
}
USER_EMAIL="${BEAST_USER_EMAIL:-$(git config user.email)}"
AWS_CMD="${AWS_CMD:-$(command -v aws)}"
AWS_PROFILE="${AWS_PROFILE:-techdev-ci-prod}"
get_api_key() {
"$AWS_CMD" ssm get-parameter \
--name "/services/k8s-staging-env-manager/BEAST_API_KEY" \
--with-decryption --profile "$AWS_PROFILE" \
--query 'Parameter.Value' --output text
}
poll_until_ready() {
local DEPLOYMENT_ID="$1"
local BEAST_API_KEY="$2"
echo "Polling for READY status..."
while true; do
sleep 30
STATE=$(curl -s "https://beast.in.customink.com/deployment/$DEPLOYMENT_ID" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL" | \
python3 -c "import json,sys; d=json.load(sys.stdin); c=d['components'][0]; print(c['state'], c['hostname'])" 2>/dev/null)
COMP_STATE=$(echo "$STATE" | awk '{print $1}')
HOSTNAME=$(echo "$STATE" | awk '{print $2}')
echo " $(date +%H:%M:%S) — $COMP_STATE ($HOSTNAME)"
if [ "$COMP_STATE" = "READY" ]; then
echo ""
echo "Deployment READY!"
echo " Instance: $HOSTNAME"
echo " Console: k8s use d → select $HOSTNAME → k8s console"
exit 0
elif [ "$COMP_STATE" = "ERROR" ] || [ "$COMP_STATE" = "FAILED" ]; then
echo "ERROR: Deployment failed"
exit 1
fi
done
}
# Handle --redeploy
if [ "${1:-}" = "--redeploy" ]; then
DEPLOYMENT_ID="${2:?Usage: beast_deploy.sh --redeploy <deployment_id>}"
BEAST_API_KEY="$(get_api_key)"
echo "Redeploying deployment $DEPLOYMENT_ID..."
RESPONSE=$(curl -s -X PUT "https://beast.in.customink.com/deployment/$DEPLOYMENT_ID/redeploy" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL")
NEW_STATE=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('state', 'UNKNOWN'))" 2>/dev/null)
echo "Redeploy triggered: state=$NEW_STATE"
echo ""
poll_until_ready "$DEPLOYMENT_ID" "$BEAST_API_KEY"
exit 0
fi
# Handle --extend
if [ "${1:-}" = "--extend" ]; then
DEPLOYMENT_ID="${2:?Usage: beast_deploy.sh --extend <deployment_id> <date> (e.g. 2026-03-25)}"
EXTEND_DATE="${3:?Usage: beast_deploy.sh --extend <deployment_id> <date> (e.g. 2026-03-25)}"
BEAST_API_KEY="$(get_api_key)"
EXPIRES="${EXTEND_DATE}T23:59:59.000Z"
echo "Extending deployment $DEPLOYMENT_ID to $EXTEND_DATE..."
RESPONSE=$(curl -s -X PATCH "https://beast.in.customink.com/deployment/$DEPLOYMENT_ID" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL" \
-d "{\"expiresAt\": \"$EXPIRES\"}")
NEW_EXPIRY=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); c=d['components'][0]; print(f'{c[\"hostname\"]}: expires {d[\"expiresAt\"]}')" 2>/dev/null)
echo " $NEW_EXPIRY"
exit 0
fi
# Handle --status
if [ "${1:-}" = "--status" ]; then
DEPLOYMENT_ID="${2:?Usage: beast_deploy.sh --status <deployment_id>}"
BEAST_API_KEY="$(get_api_key)"
curl -s "https://beast.in.customink.com/deployment/$DEPLOYMENT_ID" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL" | \
python3 -c "
import json,sys
d = json.load(sys.stdin)
c = d['components'][0]
print(f'ID: {d[\"id\"]}')
print(f'Host: {c[\"hostname\"]}')
print(f'Branch: {c[\"gitBranch\"]}')
print(f'State: {d[\"state\"]} / {c[\"state\"]}')
print(f'Expires: {d[\"expiresAt\"]}')
"
exit 0
fi
# Handle --list
if [ "${1:-}" = "--list" ]; then
APP_NAME="${2:-rails-backend}"
BLUEPRINT_ID="$(blueprint_for "$APP_NAME")"
if [ -z "$BLUEPRINT_ID" ]; then
echo "ERROR: Unknown app '$APP_NAME'"
exit 1
fi
BEAST_API_KEY="$(get_api_key)"
echo "Active $APP_NAME deployments (blueprint $BLUEPRINT_ID):"
echo ""
curl -s "https://beast.in.customink.com/deployment-blueprint/$BLUEPRINT_ID" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL" | \
python3 -c "
import json, sys
data = json.load(sys.stdin)
deps = data.get('deployments', [])
active = [d for d in deps if d.get('state') in ('READY', 'IN_PROGRESS')]
if not active:
print(' No active deployments.')
sys.exit(0)
for d in active:
comps = d.get('components', [])
for c in comps:
print(f' ID: {d[\"id\"]} {d[\"state\"]:12s} {c[\"hostname\"]:40s} branch: {c[\"gitBranch\"]}')
print(f' expires: {d[\"expiresAt\"]} user: {d.get(\"createdBy\", {}).get(\"email\", \"?\")}')
print()
"
exit 0
fi
# Handle --delete
if [ "${1:-}" = "--delete" ]; then
DEPLOYMENT_ID="${2:?Usage: beast_deploy.sh --delete <deployment_id>}"
BEAST_API_KEY="$(get_api_key)"
echo "Deleting deployment $DEPLOYMENT_ID..."
RESPONSE=$(curl -s -X DELETE "https://beast.in.customink.com/deployment/$DEPLOYMENT_ID" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL")
STATE=$(echo "$RESPONSE" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('state', d))" 2>/dev/null)
echo "Delete triggered: state=$STATE"
exit 0
fi
# Handle create (default)
BRANCH="${1:?Usage: beast_deploy.sh <branch> [app] [expiry] OR --redeploy <id> OR --delete <id>}"
APP_NAME="${2:-rails-backend}"
EXPIRY="${3:-172800}"
BLUEPRINT_ID="$(blueprint_for "$APP_NAME")"
if [ -z "$BLUEPRINT_ID" ]; then
echo "ERROR: Unknown app '$APP_NAME'"
exit 1
fi
BEAST_API_KEY="$(get_api_key)"
echo "Creating Beast deployment..."
echo " App: $APP_NAME (blueprint $BLUEPRINT_ID)"
echo " Branch: $BRANCH"
echo " User: $USER_EMAIL"
echo " Expiry: $((EXPIRY / 3600))h"
RESPONSE=$(curl -s -X POST "https://beast.in.customink.com/deployment" \
-H "Content-Type: application/json" \
-H "Authorization: ApiKey $BEAST_API_KEY" \
-H "X-User-Email: $USER_EMAIL" \
-d "{\"deploymentBlueprintId\": $BLUEPRINT_ID, \"branches\": {\"$APP_NAME\": \"$BRANCH\"}, \"timeToExpire\": $EXPIRY, \"metadata\": {}, \"configuration\": {}}")
DEPLOYMENT_ID=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])" 2>/dev/null)
HOSTNAME=$(echo "$RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['components'][0]['hostname'])" 2>/dev/null)
if [ -z "$DEPLOYMENT_ID" ]; then
echo "ERROR: Failed to create deployment"
echo "$RESPONSE"
exit 1
fi
echo "Deployment created: ID=$DEPLOYMENT_ID, Host=$HOSTNAME"
echo "Beast UI: https://beast.in.customink.com/#/deployment-blueprint/$BLUEPRINT_ID/deployments"
echo ""
echo "Save this ID to redeploy later: beast_deploy.sh --redeploy $DEPLOYMENT_ID"
echo ""
poll_until_ready "$DEPLOYMENT_ID" "$BEAST_API_KEY"
#!/bin/bash
# Check CircleCI build status for a branch
# Usage: check_ci.sh <repo> <branch> [step_name]
#
# Examples:
# check_ci.sh customink/rails-backend astupka/rsq-ai-guidance
# check_ci.sh customink/rails-backend astupka/rsq-ai-guidance ktool-build
#
# Polls every 30s until the step succeeds or fails
set -euo pipefail
REPO="${1:?Usage: check_ci.sh <repo> <branch> [step_name]}"
BRANCH="${2:?Usage: check_ci.sh <repo> <branch> [step_name]}"
STEP="${3:-ktool-build}"
echo "Checking ci/circleci: $STEP for $REPO @ $BRANCH"
while true; do
RESULT=$(/opt/homebrew/bin/gh api "repos/$REPO/commits/$BRANCH/statuses" \
--jq "[.[] | select(.context == \"ci/circleci: $STEP\")] | .[0] | .state" 2>/dev/null)
echo " $(date +%H:%M:%S) — $RESULT"
if [ "$RESULT" = "success" ]; then
echo "CI step '$STEP' succeeded!"
exit 0
elif [ "$RESULT" = "failure" ] || [ "$RESULT" = "error" ]; then
echo "CI step '$STEP' failed!"
exit 1
fi
sleep 30
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment