Files

229 lines
7.6 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# claim-task-api.sh — Create a remote branch + started.md + draft PR
# entirely via GitHub REST API, bypassing 'git push'.
#
# This solves the Jules sandbox restriction where git push is blocked
# but curl/HTTP requests are allowed.
#
# Usage:
# ./scripts/agent/claim-task-api.sh \
# --agent <agent-name> \
# --cadence <daily|weekly> \
# [--base <branch>] \
# [--run-id <id>]
#
# Environment:
# GITHUB_TOKEN — Required. A token with repo write access.
# Falls back to GH_TOKEN if GITHUB_TOKEN is unset.
#
# Outputs (stdout):
# CLAIM_BRANCH=<branch-name>
# CLAIM_PR_NUMBER=<number>
# CLAIM_PR_URL=<url>
#
# Exit codes:
# 0 — Claim created successfully
# 1 — Usage error or missing token
# 2 — API error (branch/file/PR creation failed)
OWNER="PR0M3TH3AN"
REPO="bitvid"
API_BASE="https://api.github.com/repos/${OWNER}/${REPO}"
# ---------------------------------------------------------------------------
# Parse arguments
# ---------------------------------------------------------------------------
AGENT=""
CADENCE=""
BASE_BRANCH="unstable"
RUN_ID=""
while [[ $# -gt 0 ]]; do
case "$1" in
--agent) AGENT="$2"; shift 2 ;;
--cadence) CADENCE="$2"; shift 2 ;;
--base) BASE_BRANCH="$2"; shift 2 ;;
--run-id) RUN_ID="$2"; shift 2 ;;
*)
echo "ERROR: Unknown argument: $1" >&2
echo "Usage: $0 --agent <name> --cadence <daily|weekly> [--base <branch>] [--run-id <id>]" >&2
exit 1
;;
esac
done
if [[ -z "$AGENT" || -z "$CADENCE" ]]; then
echo "ERROR: --agent and --cadence are required." >&2
exit 1
fi
if [[ "$CADENCE" != "daily" && "$CADENCE" != "weekly" ]]; then
echo "ERROR: --cadence must be 'daily' or 'weekly'." >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Resolve token
# ---------------------------------------------------------------------------
TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}"
if [[ -z "$TOKEN" ]]; then
echo "ERROR: GITHUB_TOKEN (or GH_TOKEN) must be set for API access." >&2
echo "Without a token, remote task locking is impossible." >&2
exit 1
fi
# ---------------------------------------------------------------------------
# Helper: authenticated GitHub API call
# ---------------------------------------------------------------------------
gh_api() {
local method="$1"
local endpoint="$2"
local data="${3:-}"
local args=(
-s -f
-X "$method"
-H "Authorization: Bearer ${TOKEN}"
-H "Accept: application/vnd.github+json"
-H "X-GitHub-Api-Version: 2022-11-28"
)
if [[ -n "$data" ]]; then
args+=(-H "Content-Type: application/json" -d "$data")
fi
curl "${args[@]}" "${API_BASE}${endpoint}"
}
# ---------------------------------------------------------------------------
# Generate identifiers
# ---------------------------------------------------------------------------
TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S")
DATE_ONLY=$(date -u +"%Y-%m-%d")
RUN_ID="${RUN_ID:-$(date -u +"%s")}"
BRANCH_NAME="agents/${CADENCE}/${AGENT}/${DATE_ONLY}-run-${RUN_ID}"
LOG_DIR="task-logs/${CADENCE}"
LOG_FILE="${TIMESTAMP}_${AGENT}_started.md"
LOG_PATH="${LOG_DIR}/${LOG_FILE}"
echo "Claiming task: ${AGENT} (${CADENCE})" >&2
echo "Branch: ${BRANCH_NAME}" >&2
echo "Log file: ${LOG_PATH}" >&2
# ---------------------------------------------------------------------------
# Step 1: Get the latest commit SHA of the base branch
# ---------------------------------------------------------------------------
echo "Step 1: Fetching base branch SHA (${BASE_BRANCH})..." >&2
BASE_REF_JSON=$(gh_api GET "/git/refs/heads/${BASE_BRANCH}")
BASE_SHA=$(echo "$BASE_REF_JSON" | jq -r '.object.sha // empty')
if [[ -z "$BASE_SHA" ]]; then
echo "ERROR: Could not resolve base branch '${BASE_BRANCH}'." >&2
echo "API response: ${BASE_REF_JSON}" >&2
exit 2
fi
echo " Base SHA: ${BASE_SHA}" >&2
# ---------------------------------------------------------------------------
# Step 2: Create the remote branch
# ---------------------------------------------------------------------------
echo "Step 2: Creating remote branch..." >&2
CREATE_REF_JSON=$(gh_api POST "/git/refs" "$(jq -n \
--arg ref "refs/heads/${BRANCH_NAME}" \
--arg sha "$BASE_SHA" \
'{ref: $ref, sha: $sha}')")
CREATED_REF=$(echo "$CREATE_REF_JSON" | jq -r '.ref // empty')
if [[ -z "$CREATED_REF" ]]; then
# Check if it's a 422 (already exists) — another agent may have claimed it
ERROR_MSG=$(echo "$CREATE_REF_JSON" | jq -r '.message // empty')
if [[ "$ERROR_MSG" == *"Reference already exists"* ]]; then
echo "RACE DETECTED: Branch '${BRANCH_NAME}' already exists. Another agent claimed first." >&2
exit 2
fi
echo "ERROR: Failed to create branch." >&2
echo "API response: ${CREATE_REF_JSON}" >&2
exit 2
fi
echo " Created: ${CREATED_REF}" >&2
# ---------------------------------------------------------------------------
# Step 3: Create the started.md log file on the branch
# ---------------------------------------------------------------------------
echo "Step 3: Creating started.md log file..." >&2
LOG_CONTENT=$(cat <<LOGEOF
# Started ${AGENT} run
Agent: ${AGENT}
Date: ${DATE_ONLY}
Branch: ${BRANCH_NAME}
Claimed-via: GitHub API (claim-task-api.sh)
LOGEOF
)
# Base64 encode the content (compatible with both GNU and BSD base64)
ENCODED_CONTENT=$(echo "$LOG_CONTENT" | base64 -w 0 2>/dev/null || echo "$LOG_CONTENT" | base64)
CREATE_FILE_JSON=$(gh_api PUT "/contents/${LOG_PATH}" "$(jq -n \
--arg message "chore(agents): claim ${CADENCE} task for ${AGENT}" \
--arg content "$ENCODED_CONTENT" \
--arg branch "$BRANCH_NAME" \
'{message: $message, content: $content, branch: $branch}')")
FILE_PATH_CREATED=$(echo "$CREATE_FILE_JSON" | jq -r '.content.path // empty')
if [[ -z "$FILE_PATH_CREATED" ]]; then
echo "ERROR: Failed to create log file." >&2
echo "API response: ${CREATE_FILE_JSON}" >&2
# Clean up: delete the branch we created
gh_api DELETE "/git/refs/heads/${BRANCH_NAME}" >/dev/null 2>&1 || true
exit 2
fi
echo " Created: ${FILE_PATH_CREATED}" >&2
# ---------------------------------------------------------------------------
# Step 4: Create a draft PR
# ---------------------------------------------------------------------------
echo "Step 4: Creating draft PR..." >&2
PR_TITLE="[${CADENCE}] ${AGENT}: ${DATE_ONLY} run"
PR_BODY="Automated task claim for **${AGENT}** (${CADENCE} cadence).
Branch: \`${BRANCH_NAME}\`
Claimed at: ${TIMESTAMP} UTC
Method: GitHub API (claim-task-api.sh)
---
*This draft PR serves as a distributed lock. Do not merge until the agent completes its work.*"
CREATE_PR_JSON=$(gh_api POST "/pulls" "$(jq -n \
--arg title "$PR_TITLE" \
--arg body "$PR_BODY" \
--arg head "$BRANCH_NAME" \
--arg base "$BASE_BRANCH" \
'{title: $title, body: $body, head: $head, base: $base, draft: true}')")
PR_NUMBER=$(echo "$CREATE_PR_JSON" | jq -r '.number // empty')
PR_URL=$(echo "$CREATE_PR_JSON" | jq -r '.html_url // empty')
if [[ -z "$PR_NUMBER" ]]; then
echo "WARNING: Draft PR creation failed. Branch and log file exist as partial lock." >&2
echo "API response: ${CREATE_PR_JSON}" >&2
# Don't exit — the branch itself is still a visible claim
PR_NUMBER="FAILED"
PR_URL="FAILED"
else
echo " PR #${PR_NUMBER}: ${PR_URL}" >&2
fi
# ---------------------------------------------------------------------------
# Output machine-readable results
# ---------------------------------------------------------------------------
echo "CLAIM_BRANCH=${BRANCH_NAME}"
echo "CLAIM_PR_NUMBER=${PR_NUMBER}"
echo "CLAIM_PR_URL=${PR_URL}"
echo "CLAIM_LOG_FILE=${LOG_PATH}"
echo ""
echo "Task claimed successfully." >&2