diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/check-token | 78 | ||||
| -rw-r--r-- | scripts/ct-submit | 62 | ||||
| -rw-r--r-- | scripts/ct-task | 169 | ||||
| -rwxr-xr-x | scripts/debug-execution | 34 | ||||
| -rwxr-xr-x | scripts/deploy | 31 | ||||
| -rw-r--r-- | scripts/drain-failed-tasks | 22 | ||||
| -rw-r--r-- | scripts/fix-permissions | 43 | ||||
| -rw-r--r-- | scripts/hooks/pre-commit | 7 | ||||
| -rw-r--r-- | scripts/hooks/pre-push | 6 | ||||
| -rw-r--r-- | scripts/install-hooks | 23 | ||||
| -rwxr-xr-x | scripts/next-task | 5 | ||||
| -rwxr-xr-x | scripts/reset-failed-tasks | 48 | ||||
| -rw-r--r-- | scripts/sync-credentials | 49 | ||||
| -rw-r--r-- | scripts/verify | 17 |
14 files changed, 565 insertions, 29 deletions
diff --git a/scripts/check-token b/scripts/check-token new file mode 100644 index 0000000..40a3116 --- /dev/null +++ b/scripts/check-token @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# check-token: Verify Claude OAuth token is valid against the Anthropic API. +# Usage: check-token [--refresh] [--retry-task <id-prefix>] +# --refresh re-authenticate via claude CLI if token is bad +# --retry-task <id> after a successful token check/refresh, retry that task +# +# Exit codes: 0=valid, 1=expired/invalid, 2=credentials file missing + +set -euo pipefail + +CREDS="/root/.claude/.credentials.json" +REFRESH=0 +RETRY_TASK="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --refresh) REFRESH=1; shift ;; + --retry-task) RETRY_TASK="$2"; shift 2 ;; + *) echo "Unknown arg: $1" >&2; exit 2 ;; + esac +done + +if [[ ! -f "$CREDS" ]]; then + echo "ERROR: credentials file not found: $CREDS" >&2 + exit 2 +fi + +ACCESS_TOKEN=$(python3 -c " +import json, sys +d = json.load(open('$CREDS')) +tok = d.get('claudeAiOauth', {}).get('accessToken', '') +if not tok: + print('MISSING', file=sys.stderr) + sys.exit(1) +print(tok) +") + +# Test token against the API with a minimal request +HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \ + -X POST https://api.anthropic.com/v1/messages \ + -H "anthropic-version: 2023-06-01" \ + -H "anthropic-beta: oauth-2025-04-20" \ + -H "Authorization: Bearer $ACCESS_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"model":"claude-haiku-4-5-20251001","max_tokens":1,"messages":[{"role":"user","content":"hi"}]}') + +if [[ "$HTTP_STATUS" == "200" ]]; then + echo "OK: token is valid (HTTP $HTTP_STATUS)" + if [[ -n "$RETRY_TASK" ]]; then + /workspace/claudomator/scripts/ct-task "$RETRY_TASK" retry + fi + exit 0 +elif [[ "$HTTP_STATUS" == "401" ]]; then + echo "EXPIRED: token rejected by API (HTTP 401)" + if [[ "$REFRESH" == "1" ]]; then + echo "Re-authenticating via claude CLI..." + claude --dangerously-skip-permissions /dev/null 2>&1 || true + # Check if creds were updated + NEW_TOKEN=$(python3 -c "import json; print(json.load(open('$CREDS')).get('claudeAiOauth',{}).get('accessToken',''))") + if [[ "$NEW_TOKEN" != "$ACCESS_TOKEN" ]]; then + echo "New token obtained. Syncing credentials..." + /workspace/claudomator/scripts/sync-credentials + if [[ -n "$RETRY_TASK" ]]; then + /workspace/claudomator/scripts/ct-task "$RETRY_TASK" retry + fi + exit 0 + else + echo "Token unchanged — manual re-auth required: run 'claude' in a terminal" >&2 + exit 1 + fi + else + echo "Run: check-token --refresh or re-authenticate via 'claude'" >&2 + exit 1 + fi +else + echo "WARN: unexpected HTTP $HTTP_STATUS from API (token may still be valid)" + exit 1 +fi diff --git a/scripts/ct-submit b/scripts/ct-submit new file mode 100644 index 0000000..26213c5 --- /dev/null +++ b/scripts/ct-submit @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# ct-submit — Create and immediately run a Claudomator task +# +# Usage: +# ct-submit --name "task name" --repo "/site/git.terst.org/repos/doot.git" --instructions "..." +# ct-submit --name "task name" --instructions-file /tmp/instructions.txt +# +# Reads instructions from --instructions or --instructions-file. +# Prints the task ID on success. + +set -euo pipefail + +API="http://localhost:8484" +NAME="" +REPO="/site/git.terst.org/repos/claudomator.git" +INSTRUCTIONS="" +INSTRUCTIONS_FILE="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --name) NAME="$2"; shift 2 ;; + --repo) REPO="$2"; shift 2 ;; + --instructions) INSTRUCTIONS="$2"; shift 2 ;; + --instructions-file) INSTRUCTIONS_FILE="$2"; shift 2 ;; + *) echo "Unknown arg: $1" >&2; exit 1 ;; + esac +done + +if [[ -z "$NAME" ]]; then + echo "Error: --name is required" >&2 + exit 1 +fi + +if [[ -n "$INSTRUCTIONS_FILE" ]]; then + INSTRUCTIONS="$(cat "$INSTRUCTIONS_FILE")" +fi + +if [[ -z "$INSTRUCTIONS" ]]; then + echo "Error: --instructions or --instructions-file is required" >&2 + exit 1 +fi + +PAYLOAD="$(jq -n \ + --arg name "$NAME" \ + --arg repo "$REPO" \ + --arg inst "$INSTRUCTIONS" \ + '{name: $name, repository_url: $repo, agent: {type: "claude", instructions: $inst}}')" + +RESPONSE="$(curl -s -X POST "$API/api/tasks" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD")" + +TASK_ID="$(echo "$RESPONSE" | jq -r '.id // empty')" +if [[ -z "$TASK_ID" ]]; then + echo "Error creating task:" >&2 + echo "$RESPONSE" >&2 + exit 1 +fi + +curl -s -X POST "$API/api/tasks/$TASK_ID/run" > /dev/null + +echo "$TASK_ID" diff --git a/scripts/ct-task b/scripts/ct-task new file mode 100644 index 0000000..cd3388a --- /dev/null +++ b/scripts/ct-task @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# ct-task: Query, retry, cancel, or reset a Claudomator task by ID prefix. +# Usage: +# ct-task <id-prefix> — show task state + latest execution error +# ct-task <id-prefix> retry — reset to PENDING and queue +# ct-task <id-prefix> run — queue (must already be PENDING) +# ct-task <id-prefix> reset — reset state to PENDING only +# ct-task <id-prefix> cancel — cancel task (works from any state) +# ct-task list [state] — list tasks (optional: filter by state) +# ct-task cancel-all-failed — cancel every FAILED task +# +# Examples: +# ct-task f8829d6f +# ct-task f8829d6f retry +# ct-task f8829d6f cancel +# ct-task list FAILED +# ct-task cancel-all-failed + +set -euo pipefail + +API="http://localhost:8484" +DB="/site/doot.terst.org/data/claudomator.db" + +_jq() { python3 -c "import sys,json; d=json.load(sys.stdin); $1"; } + +task_id_from_prefix() { + local prefix="$1" + local id + id=$(sqlite3 "$DB" "SELECT id FROM tasks WHERE id LIKE '${prefix}%' ORDER BY created_at DESC LIMIT 1;") + if [[ -z "$id" ]]; then + echo "ERROR: no task found matching '${prefix}'" >&2 + exit 1 + fi + echo "$id" +} + +cmd_show() { + local id + id=$(task_id_from_prefix "$1") + local task + task=$(curl -sf "$API/api/tasks/$id") + echo "$task" | _jq " +t=d +print('ID: ', t['id']) +print('Name: ', t['name']) +print('State: ', t['state']) +print('Project:', t.get('project','')) +summary = t.get('summary','') +if summary: print('Summary:', summary) +rejection = t.get('rejection_comment','') +if rejection: print('Rejection:', rejection) +" + + # Latest execution error + local execs + execs=$(curl -sf "$API/api/tasks/$id/executions" 2>/dev/null || echo "[]") + local count + count=$(echo "$execs" | _jq "print(len(d))") + if [[ "$count" -gt 0 ]]; then + echo "" + echo "$execs" | _jq " +e=d[0] +print('Execution:', e['ID']) +print('Status: ', e['Status']) +print('Exit code:', e['ExitCode']) +err = e.get('ErrorMsg','') +if err: print('Error: ', err) +cost = e.get('CostUSD',0) +if cost: print('Cost: ', f'\${cost:.4f}') +" + fi +} + +cmd_reset() { + local id + id=$(task_id_from_prefix "$1") + sqlite3 "$DB" "UPDATE tasks SET state='PENDING' WHERE id='$id';" + echo "Reset $id to PENDING" +} + +cmd_run() { + local id + id=$(task_id_from_prefix "$1") + local resp + resp=$(curl -sf -X POST "$API/api/tasks/$id/run" || true) + local state + state=$(curl -sf "$API/api/tasks/$id" | _jq "print(d['state'])") + echo "$id → $state" +} + +cmd_retry() { + local id + id=$(task_id_from_prefix "$1") + sqlite3 "$DB" "UPDATE tasks SET state='PENDING' WHERE id='$id';" + curl -sf -X POST "$API/api/tasks/$id/run" >/dev/null || true + local state + state=$(curl -sf "$API/api/tasks/$id" | _jq "print(d['state'])") + echo "$id → $state" +} + +cmd_cancel() { + local id + id=$(task_id_from_prefix "$1") + # Try API cancel first (works for PENDING/QUEUED/RUNNING/BLOCKED) + local http_status + http_status=$(curl -sf -o /dev/null -w "%{http_code}" -X POST "$API/api/tasks/$id/cancel" 2>/dev/null || echo "000") + if [[ "$http_status" == "200" ]]; then + echo "$id → CANCELLED" + return + fi + # Terminal states (FAILED, TIMED_OUT, etc.) can't transition via API — force via DB. + sqlite3 "$DB" "UPDATE tasks SET state='CANCELLED' WHERE id='$id';" + echo "$id → CANCELLED (forced)" +} + +cmd_cancel_all_failed() { + local ids + ids=$(sqlite3 "$DB" "SELECT id FROM tasks WHERE state='FAILED';") + if [[ -z "$ids" ]]; then + echo "No FAILED tasks." + return + fi + while IFS= read -r id; do + sqlite3 "$DB" "UPDATE tasks SET state='CANCELLED' WHERE id='$id';" + echo "${id:0:8}… → CANCELLED" + done <<< "$ids" +} + +cmd_list() { + local filter="${1:-}" + local tasks + tasks=$(curl -sf "$API/api/tasks") + if [[ -n "$filter" ]]; then + echo "$tasks" | _jq " +for t in d: + if t['state'] == '${filter}'.upper() or '${filter}'.upper() in t['state']: + print(t['id'][:8], t['state'].ljust(12), t['name'][:60]) +" + else + echo "$tasks" | _jq " +for t in d: + print(t['id'][:8], t['state'].ljust(12), t['name'][:60]) +" + fi +} + +# Dispatch +PREFIX="${1:-}" +SUBCMD="${2:-show}" + +if [[ -z "$PREFIX" ]]; then + echo "Usage: ct-task <id-prefix> [show|retry|run|reset|cancel] OR ct-task list [state] OR ct-task cancel-all-failed" >&2 + exit 1 +fi + +case "$PREFIX" in + list) cmd_list "${2:-}" ;; + cancel-all-failed) cmd_cancel_all_failed ;; + *) + case "$SUBCMD" in + show|"") cmd_show "$PREFIX" ;; + retry) cmd_retry "$PREFIX" ;; + run) cmd_run "$PREFIX" ;; + reset) cmd_reset "$PREFIX" ;; + cancel) cmd_cancel "$PREFIX" ;; + *) echo "Unknown subcommand: $SUBCMD" >&2; exit 1 ;; + esac + ;; +esac diff --git a/scripts/debug-execution b/scripts/debug-execution index 87540b7..b4873b9 100755 --- a/scripts/debug-execution +++ b/scripts/debug-execution @@ -1,13 +1,14 @@ #!/usr/bin/env bash # debug-execution: Show details for a failed task execution from the production DB. -# Usage: ./scripts/debug-execution <execution-id-or-prefix> +# Usage: ./scripts/debug-execution [execution-id-or-prefix] # Example: ./scripts/debug-execution c74c877f +# If no ID is given, defaults to the most recent execution. set -euo pipefail DB="/site/doot.terst.org/data/claudomator.db" DATA_DIR="/site/doot.terst.org/data" -PREFIX="${1:?Usage: $0 <execution-id-or-prefix>}" +PREFIX="${1:-}" if [[ ! -f "$DB" ]]; then echo "ERROR: DB not found at $DB" >&2 @@ -15,16 +16,29 @@ if [[ ! -f "$DB" ]]; then fi # Look up execution -ROW=$(sqlite3 "$DB" " - SELECT id, task_id, exit_code, status, stdout_path, stderr_path, error_msg - FROM executions - WHERE id LIKE '${PREFIX}%' - ORDER BY start_time DESC - LIMIT 1; -") +if [[ -z "$PREFIX" ]]; then + ROW=$(sqlite3 "$DB" " + SELECT id, task_id, exit_code, status, stdout_path, stderr_path, error_msg + FROM executions + ORDER BY start_time DESC + LIMIT 1; + ") +else + ROW=$(sqlite3 "$DB" " + SELECT id, task_id, exit_code, status, stdout_path, stderr_path, error_msg + FROM executions + WHERE id LIKE '${PREFIX}%' + ORDER BY start_time DESC + LIMIT 1; + ") +fi if [[ -z "$ROW" ]]; then - echo "ERROR: No execution found matching '${PREFIX}'" >&2 + if [[ -z "$PREFIX" ]]; then + echo "ERROR: No executions found in DB" >&2 + else + echo "ERROR: No execution found matching '${PREFIX}'" >&2 + fi exit 1 fi diff --git a/scripts/deploy b/scripts/deploy index c7ff734..2161535 100755 --- a/scripts/deploy +++ b/scripts/deploy @@ -21,7 +21,7 @@ REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" cd "${REPO_DIR}" echo "==> Pulling latest from bare repo..." -git pull --ff-only local master +git pull --ff-only local main STASHED=false if [ "$DIRTY" = false ] && [ -n "$(git status --porcelain)" ]; then @@ -31,6 +31,9 @@ if [ "$DIRTY" = false ] && [ -n "$(git status --porcelain)" ]; then trap 'if [ "$STASHED" = true ]; then echo "==> Popping stash..."; git stash pop; fi' EXIT fi +echo "==> Verifying (build + tests)..." +"${REPO_DIR}/scripts/verify" + echo "==> Building claudomator..." export GOCACHE="${SITE_DIR}/cache/go-build" export GOPATH="${SITE_DIR}/cache/gopath" @@ -39,23 +42,23 @@ go build -o "${BIN_DIR}/claudomator" ./cmd/claudomator/ echo "==> Copying scripts..." mkdir -p "${SITE_DIR}/scripts" -cp "${REPO_DIR}/scripts/"* "${SITE_DIR}/scripts/" -chown -R www-data:www-data "${SITE_DIR}/scripts" -chmod +x "${SITE_DIR}/scripts/"* +find "${REPO_DIR}/scripts" -maxdepth 1 -type f -exec cp -p {} "${SITE_DIR}/scripts/" \; + +echo "==> Installing to /usr/local/bin..." +install -m 755 "${BIN_DIR}/claudomator" /usr/local/bin/claudomator + +echo "==> Verifying system CLI version..." +/usr/local/bin/claudomator version echo "==> Fixing permissions..." -chown www-data:www-data "${BIN_DIR}/claudomator" -chmod +x "${BIN_DIR}/claudomator" +"${REPO_DIR}/scripts/fix-permissions" -if [ -f "${BIN_DIR}/claude" ]; then - echo "==> Fixing Claude permissions..." - chown www-data:www-data "${BIN_DIR}/claude" - chmod +x "${BIN_DIR}/claude" -fi +echo "==> Syncing credentials..." +"${REPO_DIR}/scripts/sync-credentials" -echo "==> Installing to /usr/local/bin..." -cp "${BIN_DIR}/claudomator" /usr/local/bin/claudomator -chmod +x /usr/local/bin/claudomator +echo "==> Ensuring binary and scripts are executable..." +chmod +x "${BIN_DIR}/claudomator" /usr/local/bin/claudomator +find "${SITE_DIR}/scripts" -maxdepth 1 -type f -exec chmod +x {} + echo "==> Restarting service..." sudo systemctl restart "${SERVICE}" diff --git a/scripts/drain-failed-tasks b/scripts/drain-failed-tasks new file mode 100644 index 0000000..4bb6992 --- /dev/null +++ b/scripts/drain-failed-tasks @@ -0,0 +1,22 @@ +#!/bin/bash +# drain-failed-tasks — retry failed tasks by running start-next-task every 5 minutes +# Usage: ./scripts/drain-failed-tasks [iterations] +# Default: 29 iterations + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ITERATIONS="${1:-29}" +INTERVAL=300 # 5 minutes + +echo "Running start-next-task every ${INTERVAL}s for ${ITERATIONS} iterations" + +for ((i=1; i<=ITERATIONS; i++)); do + echo "[$(date '+%H:%M:%S')] Iteration ${i}/${ITERATIONS}" + "$SCRIPT_DIR/start-next-task" || true + if [[ $i -lt $ITERATIONS ]]; then + sleep "$INTERVAL" + fi +done + +echo "[$(date '+%H:%M:%S')] Done." diff --git a/scripts/fix-permissions b/scripts/fix-permissions new file mode 100644 index 0000000..408a23e --- /dev/null +++ b/scripts/fix-permissions @@ -0,0 +1,43 @@ +#!/bin/bash +# claudomator-fix-perms — Fix ownership and permissions for Claudomator components +set -euo pipefail + +SITE_DIR="/site/doot.terst.org" +GIT_REPOS_DIR="/site/git.terst.org/repos" +WORKSPACE_DIR="/workspace" + +echo "==> Fixing site ownership (www-data:www-data)..." +chown -R www-data:www-data "${SITE_DIR}" + +echo "==> Ensuring binaries are executable..." +if [ -d "${SITE_DIR}/bin" ]; then + find "${SITE_DIR}/bin" -type f -exec chmod +x {} + +fi +if [ -f "/usr/local/bin/claudomator" ]; then + chmod +x /usr/local/bin/claudomator +fi + +echo "==> Ensuring scripts are executable..." +if [ -d "${SITE_DIR}/scripts" ]; then + find "${SITE_DIR}/scripts" -type f -exec chmod +x {} + +fi +if [ -d "${WORKSPACE_DIR}/claudomator/scripts" ]; then + find "${WORKSPACE_DIR}/claudomator/scripts" -type f -exec chmod +x {} + +fi + +echo "==> Fixing git bare repo permissions..." +# Specifically fix object permissions that might be corrupted by root runs +if [ -d "${GIT_REPOS_DIR}" ]; then + chown -R www-data:www-data "${GIT_REPOS_DIR}" + find "${GIT_REPOS_DIR}" -type d -exec chmod 775 {} + + find "${GIT_REPOS_DIR}" -type f -exec chmod 664 {} + +fi + +echo "==> Fixing database permissions..." +if [ -f "${SITE_DIR}/data/claudomator.db" ]; then + chmod 664 "${SITE_DIR}/data/claudomator.db" + # Ensure the data directory is writable for WAL mode + chmod 775 "${SITE_DIR}/data" +fi + +echo "==> Done!" diff --git a/scripts/hooks/pre-commit b/scripts/hooks/pre-commit new file mode 100644 index 0000000..faf91fc --- /dev/null +++ b/scripts/hooks/pre-commit @@ -0,0 +1,7 @@ +#!/bin/bash +# pre-commit — Reject commits that don't compile. +set -euo pipefail +REPO_DIR="$(git rev-parse --show-toplevel)" +echo "pre-commit: go build ./..." +cd "${REPO_DIR}" +go build ./... diff --git a/scripts/hooks/pre-push b/scripts/hooks/pre-push new file mode 100644 index 0000000..d851332 --- /dev/null +++ b/scripts/hooks/pre-push @@ -0,0 +1,6 @@ +#!/bin/bash +# pre-push — Reject pushes where tests fail. +set -euo pipefail +REPO_DIR="$(git rev-parse --show-toplevel)" +echo "pre-push: running scripts/verify..." +exec "${REPO_DIR}/scripts/verify" diff --git a/scripts/install-hooks b/scripts/install-hooks new file mode 100644 index 0000000..454f3cd --- /dev/null +++ b/scripts/install-hooks @@ -0,0 +1,23 @@ +#!/bin/bash +# install-hooks — Symlink version-controlled hooks into .git/hooks/ +# Usage: ./scripts/install-hooks +# Example: ./scripts/install-hooks + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +HOOKS_SRC="${REPO_DIR}/scripts/hooks" +HOOKS_DST="${REPO_DIR}/.git/hooks" + +for hook in "${HOOKS_SRC}"/*; do + name="$(basename "${hook}")" + target="${HOOKS_DST}/${name}" + if [ -e "${target}" ] && [ ! -L "${target}" ]; then + echo " skipping ${name}: non-symlink already exists at ${target}" + continue + fi + ln -sf "${hook}" "${target}" + echo " installed ${name}" +done + +echo "==> Hooks installed." diff --git a/scripts/next-task b/scripts/next-task index c36fc23..697de11 100755 --- a/scripts/next-task +++ b/scripts/next-task @@ -32,9 +32,8 @@ fi if [ -z "$next_task" ]; then # 4. No child/sibling found: fall back to highest-priority oldest PENDING task - # Exclude tasks that have a rejection comment or have already been executed - # to avoid auto-approving rejected tasks. - FALLBACK_SQL="SELECT id FROM tasks WHERE (state = 'PENDING' AND (rejection_comment IS NULL OR rejection_comment = '') AND id NOT IN (SELECT task_id FROM executions)) OR state = 'QUEUED' + # Exclude tasks with a rejection comment to avoid auto-approving rejected tasks. + FALLBACK_SQL="SELECT id FROM tasks WHERE (state = 'PENDING' AND (rejection_comment IS NULL OR rejection_comment = '')) OR state = 'QUEUED' ORDER BY CASE priority WHEN 'critical' THEN 4 diff --git a/scripts/reset-failed-tasks b/scripts/reset-failed-tasks index eddfff0..1f3b6d5 100755 --- a/scripts/reset-failed-tasks +++ b/scripts/reset-failed-tasks @@ -1,5 +1,49 @@ #!/bin/bash +# Reset FAILED and CANCELLED tasks to PENDING and delete their preserved workspaces. +# Usage: reset-failed-tasks [--dry-run] -DB_PATH="/site/doot.terst.org/data/claudomator.db" +set -euo pipefail -sqlite3 "$DB_PATH" "UPDATE tasks SET state = 'PENDING' WHERE state = 'FAILED';" +DB_PATH="${CLAUDOMATOR_DB:-/site/doot.terst.org/data/claudomator.db}" +DRY_RUN=false +[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=true + +# Collect preserved sandbox dirs before resetting so we can clean them up. +SANDBOX_DIRS=$(sqlite3 "$DB_PATH" " + SELECT DISTINCT e.sandbox_dir + FROM executions e + JOIN tasks t ON t.id = e.task_id + WHERE t.state IN ('FAILED','CANCELLED') + AND e.sandbox_dir IS NOT NULL + AND e.sandbox_dir != ''; +") + +TASK_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM tasks WHERE state IN ('FAILED','CANCELLED');") + +echo "Tasks to reset: $TASK_COUNT" + +if [[ "$DRY_RUN" == "true" ]]; then + echo "[dry-run] Would reset $TASK_COUNT task(s) to PENDING." + if [[ -n "$SANDBOX_DIRS" ]]; then + echo "[dry-run] Workspaces to delete:" + echo "$SANDBOX_DIRS" + else + echo "[dry-run] No preserved workspaces to delete." + fi + exit 0 +fi + +sqlite3 "$DB_PATH" "UPDATE tasks SET state = 'PENDING' WHERE state IN ('FAILED','CANCELLED');" +echo "Reset $TASK_COUNT task(s) to PENDING." + +DELETED=0 +while IFS= read -r dir; do + [[ -z "$dir" ]] && continue + if [[ -d "$dir" ]]; then + rm -rf "$dir" + echo "Deleted workspace: $dir" + DELETED=$((DELETED + 1)) + fi +done <<< "$SANDBOX_DIRS" + +echo "Deleted $DELETED workspace(s)." diff --git a/scripts/sync-credentials b/scripts/sync-credentials new file mode 100644 index 0000000..22e3f75 --- /dev/null +++ b/scripts/sync-credentials @@ -0,0 +1,49 @@ +#!/bin/bash +# sync-credentials — copies Claude and Gemini credentials to workspace + +set -euo pipefail + +# This script is intended to be run by cron every 10 minutes. +# It copies Claude and Gemini credentials from root home to workspace for claudomator. + +# Source paths +SOURCE_CLAUDE="/root/.claude/.credentials.json" +SOURCE_CLAUDE_SETTINGS="/root/.claude.json" +SOURCE_GEMINI_OAUTH="/root/.gemini/oauth_creds.json" +SOURCE_GEMINI_ACCOUNTS="/root/.gemini/google_accounts.json" + +# Destination paths +DEST_CLAUDE="/workspace/claudomator/credentials/claude/.credentials.json" +DEST_CLAUDE_SETTINGS="/workspace/claudomator/credentials/claude/.claude.json" +DEST_GEMINI_OAUTH="/workspace/claudomator/credentials/gemini/oauth_creds.json" +DEST_GEMINI_ACCOUNTS="/workspace/claudomator/credentials/gemini/google_accounts.json" + +# Sync Claude +if [[ -f "$SOURCE_CLAUDE" ]]; then + mkdir -p "$(dirname "$DEST_CLAUDE")" + cp "$SOURCE_CLAUDE" "$DEST_CLAUDE" + chown root:www-data "$DEST_CLAUDE" 2>/dev/null || true + chmod 640 "$DEST_CLAUDE" + echo "Synced Claude credentials." +fi + +if [[ -f "$SOURCE_CLAUDE_SETTINGS" ]]; then + cp "$SOURCE_CLAUDE_SETTINGS" "$DEST_CLAUDE_SETTINGS" + chmod 644 "$DEST_CLAUDE_SETTINGS" + echo "Synced Claude settings." +fi + +# Sync Gemini +if [[ -f "$SOURCE_GEMINI_OAUTH" ]]; then + mkdir -p "$(dirname "$DEST_GEMINI_OAUTH")" + cp "$SOURCE_GEMINI_OAUTH" "$DEST_GEMINI_OAUTH" + chmod 600 "$DEST_GEMINI_OAUTH" + echo "Synced Gemini OAuth credentials." +fi + +if [[ -f "$SOURCE_GEMINI_ACCOUNTS" ]]; then + mkdir -p "$(dirname "$DEST_GEMINI_ACCOUNTS")" + cp "$SOURCE_GEMINI_ACCOUNTS" "$DEST_GEMINI_ACCOUNTS" + chmod 600 "$DEST_GEMINI_ACCOUNTS" + echo "Synced Gemini Google accounts." +fi diff --git a/scripts/verify b/scripts/verify new file mode 100644 index 0000000..4f9c52f --- /dev/null +++ b/scripts/verify @@ -0,0 +1,17 @@ +#!/bin/bash +# verify — Build and test the claudomator codebase +# Usage: ./scripts/verify +# Example: ./scripts/verify + +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "${REPO_DIR}" + +echo "==> Building..." +go build ./... + +echo "==> Testing (race detector on)..." +go test -race ./... + +echo "==> All checks passed." |
