feat: Archipelago demo stack (lightweight)
This commit is contained in:
187
neode-ui/loop/loop.sh
Executable file
187
neode-ui/loop/loop.sh
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env sh
|
||||
# Headless loop script for overnight Claude Code automation.
|
||||
# Rate-limit aware: detects limits, sleeps until reset, and retries automatically.
|
||||
set -u
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
PROMPT_FILE="${PROMPT_FILE:-$PROJECT_DIR/loop/prompt.md}"
|
||||
LOG_FILE="${LOG_FILE:-$PROJECT_DIR/loop/loop.log}"
|
||||
ITERATION_COUNT="${ITERATION_COUNT:-10}"
|
||||
ITERATION_DELAY="${ITERATION_DELAY:-30}"
|
||||
CLAUDE_BIN="${CLAUDE_BIN:-claude}"
|
||||
RATE_LIMIT_WAIT="${RATE_LIMIT_WAIT:-3600}"
|
||||
MAX_RATE_LIMIT_RETRIES="${MAX_RATE_LIMIT_RETRIES:-5}"
|
||||
CLAUDE_EXIT=0
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
log() {
|
||||
echo "$1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
banner() {
|
||||
log ""
|
||||
log "================================================================"
|
||||
log " $1"
|
||||
log " $(date '+%Y-%m-%d %H:%M:%S')"
|
||||
log "================================================================"
|
||||
log ""
|
||||
}
|
||||
|
||||
section() {
|
||||
log ""
|
||||
log "----------------------------------------"
|
||||
log " $1"
|
||||
log "----------------------------------------"
|
||||
log ""
|
||||
}
|
||||
|
||||
plan_has_tasks() {
|
||||
grep -q '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null
|
||||
}
|
||||
|
||||
remaining_tasks() {
|
||||
grep -c '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
next_task() {
|
||||
grep -m1 '^\- \[ \]' "$PROJECT_DIR/loop/plan.md" 2>/dev/null | sed 's/^- \[ \] //' || echo "(none)"
|
||||
}
|
||||
|
||||
check_rate_limit() {
|
||||
[ "${CLAUDE_EXIT:-0}" -eq 0 ] && return 1
|
||||
tail -50 "$LOG_FILE" 2>/dev/null | grep -v "^Rate limit detected" | grep -v "^Sleeping" | grep -v "^=" | grep -v "^-" | grep -qi \
|
||||
-e "rate.limit" \
|
||||
-e "too.many.requests" \
|
||||
-e "429" \
|
||||
-e "quota.exceeded" \
|
||||
-e "usage.limit" \
|
||||
-e "limit.reached" 2>/dev/null
|
||||
}
|
||||
|
||||
banner "NEODE-UI OVERNIGHT AUTOMATION STARTED"
|
||||
log " Project: $PROJECT_DIR"
|
||||
log " Prompt: $PROMPT_FILE"
|
||||
log " Autonomous: ${CLAUDE_AUTONOMOUS:-0}"
|
||||
log " Iterations: $ITERATION_COUNT (${ITERATION_DELAY}s between each)"
|
||||
log " Rate limit: wait ${RATE_LIMIT_WAIT}s, retry up to ${MAX_RATE_LIMIT_RETRIES}x"
|
||||
log " Tasks left: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
log ""
|
||||
|
||||
i=1
|
||||
rate_limit_retries=0
|
||||
while [ "$i" -le "$ITERATION_COUNT" ]; do
|
||||
|
||||
if ! plan_has_tasks; then
|
||||
banner "ALL TASKS COMPLETE"
|
||||
log " No remaining tasks in plan.md. Stopping."
|
||||
break
|
||||
fi
|
||||
|
||||
section "ITERATION $i/$ITERATION_COUNT"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
log ""
|
||||
|
||||
export CLAUDE_PROJECT_DIR="$PROJECT_DIR"
|
||||
export CLAUDE_AUTONOMOUS="${CLAUDE_AUTONOMOUS:-1}"
|
||||
|
||||
if [ -f "$PROMPT_FILE" ]; then
|
||||
log " Starting Claude session..."
|
||||
log ""
|
||||
"$CLAUDE_BIN" -p --dangerously-skip-permissions \
|
||||
< "$PROMPT_FILE" 2>&1 | tee -a "$LOG_FILE"
|
||||
CLAUDE_EXIT=$?
|
||||
log ""
|
||||
log " Claude exited with code: $CLAUDE_EXIT"
|
||||
else
|
||||
log " ERROR: $PROMPT_FILE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if check_rate_limit; then
|
||||
rate_limit_retries=$((rate_limit_retries + 1))
|
||||
if [ "$rate_limit_retries" -ge "$MAX_RATE_LIMIT_RETRIES" ]; then
|
||||
section "RATE LIMITED — SCHEDULING LAUNCHD RETRY"
|
||||
log " Hit rate limit $rate_limit_retries times. Creating launchd job to retry later."
|
||||
|
||||
PLIST_LABEL="com.neode-ui.overnight-retry"
|
||||
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
|
||||
RETRY_TIME=$(date -v+${RATE_LIMIT_WAIT}S '+%H:%M' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M')
|
||||
RETRY_HOUR=$(echo "$RETRY_TIME" | cut -d: -f1)
|
||||
RETRY_MIN=$(echo "$RETRY_TIME" | cut -d: -f2)
|
||||
|
||||
cat > "$PLIST_PATH" <<PLIST
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${PLIST_LABEL}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/bin/sh</string>
|
||||
<string>-c</string>
|
||||
<string>cd ${PROJECT_DIR} && caffeinate -i ./loop/loop.sh >> ${LOG_FILE} 2>&1; launchctl unload ${PLIST_PATH}; rm -f ${PLIST_PATH}</string>
|
||||
</array>
|
||||
<key>StartCalendarInterval</key>
|
||||
<dict>
|
||||
<key>Hour</key>
|
||||
<integer>${RETRY_HOUR}</integer>
|
||||
<key>Minute</key>
|
||||
<integer>${RETRY_MIN}</integer>
|
||||
</dict>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>CLAUDE_AUTONOMOUS</key>
|
||||
<string>1</string>
|
||||
<key>CLAUDE_PROJECT_DIR</key>
|
||||
<string>${PROJECT_DIR}</string>
|
||||
<key>PATH</key>
|
||||
<string>/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin</string>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>${LOG_FILE}</string>
|
||||
</dict>
|
||||
</plist>
|
||||
PLIST
|
||||
|
||||
launchctl load "$PLIST_PATH" 2>/dev/null || true
|
||||
log " Scheduled retry at ~${RETRY_TIME}"
|
||||
log " Plist: $PLIST_PATH (auto-removes after running)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
section "RATE LIMITED — WAITING"
|
||||
log " Attempt $rate_limit_retries/$MAX_RATE_LIMIT_RETRIES"
|
||||
log " Sleeping ${RATE_LIMIT_WAIT}s until $(date -v+${RATE_LIMIT_WAIT}S '+%H:%M:%S' 2>/dev/null || date -d "+${RATE_LIMIT_WAIT} seconds" '+%H:%M:%S')..."
|
||||
sleep "$RATE_LIMIT_WAIT"
|
||||
|
||||
if ! plan_has_tasks; then
|
||||
banner "ALL TASKS COMPLETE (during rate limit wait)"
|
||||
break
|
||||
fi
|
||||
log " Retrying..."
|
||||
continue
|
||||
fi
|
||||
|
||||
rate_limit_retries=0
|
||||
|
||||
section "ITERATION $i COMPLETE"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log " Next task: $(next_task)"
|
||||
|
||||
i=$((i + 1))
|
||||
if [ "$i" -le "$ITERATION_COUNT" ] && [ "$ITERATION_DELAY" -gt 0 ]; then
|
||||
log " Pausing ${ITERATION_DELAY}s before next iteration..."
|
||||
sleep "$ITERATION_DELAY"
|
||||
fi
|
||||
done
|
||||
|
||||
banner "LOOP FINISHED"
|
||||
log " Completed $((i - 1)) iterations"
|
||||
log " Tasks remaining: $(remaining_tasks)"
|
||||
log ""
|
||||
3
neode-ui/loop/plan.md
Normal file
3
neode-ui/loop/plan.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Overnight Plan -- neode-ui
|
||||
|
||||
> Tasks will be generated during setup.
|
||||
38
neode-ui/loop/prepare.sh
Executable file
38
neode-ui/loop/prepare.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env sh
|
||||
# Pre-run script: verify repo state and create overnight branch.
|
||||
set -eu
|
||||
|
||||
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(cd "$(dirname "$0")/.." && pwd)}"
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
DATE=$(date '+%Y-%m-%d')
|
||||
BRANCH="overnight/${DATE}"
|
||||
|
||||
echo "=== neode-ui overnight pre-run check @ $(date '+%Y-%m-%dT%H:%M:%S') ==="
|
||||
|
||||
# 1. Check git status is clean
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
echo "Error: Working tree not clean. Commit or stash changes first." >&2
|
||||
git status --short >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Check we're not already on an overnight branch
|
||||
current=$(git branch --show-current 2>/dev/null || true)
|
||||
if [ -n "$current" ] && [ "$current" = "$BRANCH" ]; then
|
||||
echo "Already on $BRANCH. Ready to run." >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 3. Create date-stamped branch
|
||||
if git rev-parse --verify "$BRANCH" >/dev/null 2>&1; then
|
||||
echo "Branch $BRANCH already exists. Checkout or use a different date." >&2
|
||||
exit 1
|
||||
fi
|
||||
git checkout -b "$BRANCH"
|
||||
echo "Created branch $BRANCH"
|
||||
|
||||
echo ""
|
||||
echo "Reminder: Push before starting overnight run: git push -u origin $BRANCH"
|
||||
echo "Then run: caffeinate -i ./loop/loop.sh"
|
||||
echo "=== Ready ==="
|
||||
26
neode-ui/loop/prompt.md
Normal file
26
neode-ui/loop/prompt.md
Normal file
@@ -0,0 +1,26 @@
|
||||
You are working through an overnight automation plan for the neode-ui project. Read these files first:
|
||||
|
||||
1. `loop/plan.md` -- Your task checklist (mark items `- [x]` as you complete them)
|
||||
2. `CLAUDE.md` -- Project conventions, architecture, and coding standards
|
||||
|
||||
## Working Process
|
||||
|
||||
For each task in `loop/plan.md`:
|
||||
|
||||
1. Find the first unchecked `- [ ]` item
|
||||
2. Read the task description carefully
|
||||
3. Read the relevant source files before making changes
|
||||
4. Implement following CLAUDE.md conventions
|
||||
5. Run any test/build commands specified in the task
|
||||
6. Fix all errors before continuing
|
||||
7. Commit with conventional format: `type: description`
|
||||
8. Mark it done `- [x]` in `loop/plan.md`
|
||||
9. Move to the next unchecked task immediately
|
||||
|
||||
## Rules
|
||||
|
||||
- Never skip a testing gate -- if tests fail, fix before moving on
|
||||
- If a task is proving difficult, make at least 10 genuine attempts before moving on
|
||||
- Always read source files before editing them
|
||||
- Do not stop until all tasks are checked or you are rate limited
|
||||
- Commit after each completed task
|
||||
Reference in New Issue
Block a user