|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
WORKDIR="${1:-$PWD}" |
|
PROMPT_FILE="${PROMPT_FILE:-$WORKDIR/AUTO_LOOP_PROMPT.txt}" |
|
SLEEP_SECS="${SLEEP_SECS:-2}" |
|
LOG_DIR="${LOG_DIR:-$WORKDIR/logs}" |
|
HEARTBEAT_SECS="${HEARTBEAT_SECS:-15}" |
|
SUMMARY_FILE="${SUMMARY_FILE:-$LOG_DIR/run-summary.tsv}" |
|
|
|
cd "$WORKDIR" |
|
|
|
if [[ ! -f "$PROMPT_FILE" ]]; then |
|
echo "Missing prompt file: $PROMPT_FILE" >&2 |
|
exit 1 |
|
fi |
|
|
|
mkdir -p "$LOG_DIR" |
|
|
|
if [[ ! -f "$SUMMARY_FILE" ]]; then |
|
printf "run\tstart_time\telapsed_seconds\texit_code\tprompt_tokens\tcompletion_tokens\ttotal_tokens\tlog_file\n" > "$SUMMARY_FILE" |
|
fi |
|
|
|
run_num=0 |
|
|
|
while true; do |
|
((run_num+=1)) |
|
ts="$(date '+%Y%m%d-%H%M%S')" |
|
start_human="$(date '+%Y-%m-%d %H:%M:%S %Z')" |
|
start_epoch="$(date +%s)" |
|
log_file="$LOG_DIR/claude-run-${run_num}-${ts}.log" |
|
prompt_contents="$(cat "$PROMPT_FILE")" |
|
|
|
{ |
|
echo |
|
echo "============================================================" |
|
echo "Starting fresh non-interactive Claude run" |
|
echo "Run: $run_num" |
|
echo "Timestamp: $start_human" |
|
echo "Workdir: $WORKDIR" |
|
echo "Prompt: $PROMPT_FILE" |
|
echo "Log file: $log_file" |
|
echo "Summary: $SUMMARY_FILE" |
|
echo "============================================================" |
|
echo |
|
echo "Prompt contents:" |
|
echo "------------------------------------------------------------" |
|
printf '%s\n' "$prompt_contents" |
|
echo "------------------------------------------------------------" |
|
echo |
|
echo "Launching Claude with stream-json..." |
|
echo |
|
} | tee -a "$log_file" |
|
|
|
set +e |
|
|
|
( |
|
while true; do |
|
echo "[heartbeat] Claude still running at $(date)" >> "$log_file" |
|
sleep "$HEARTBEAT_SECS" |
|
done |
|
) & |
|
heartbeat_pid=$! |
|
|
|
stdbuf -oL -eL \ |
|
env \ |
|
ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-http://127.0.0.1:8001}" \ |
|
ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-sk-no-key-required}" \ |
|
ANTHROPIC_MODEL="${ANTHROPIC_MODEL:-glm-4.7-flash}" \ |
|
OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://127.0.0.1:8001/v1}" \ |
|
OPENAI_API_KEY="${OPENAI_API_KEY:-sk-no-key-required}" \ |
|
OPENAI_MODEL="${OPENAI_MODEL:-glm-4.7-flash}" \ |
|
CLAUDE_CODE_ATTRIBUTION_HEADER="${CLAUDE_CODE_ATTRIBUTION_HEADER:-0}" \ |
|
claude -p "$prompt_contents" \ |
|
--verbose \ |
|
--output-format stream-json \ |
|
< /dev/null \ |
|
2>&1 \ |
|
| tee -a "$log_file" \ |
|
| jq -Rr ' |
|
def emit_text_blocks: |
|
.message.content[]? |
|
| select(.type == "text") |
|
| .text, "\n"; |
|
|
|
. as $line |
|
| try ($line | fromjson) catch empty |
|
| if (.type == "assistant") then |
|
emit_text_blocks |
|
elif (.type == "stream_event" |
|
and .event.type == "content_block_start" |
|
and .event.content_block.type == "tool_use") then |
|
"\n[tool] " + (.event.content_block.name // "unknown") + "\n" |
|
elif (.type == "result") then |
|
"\n" |
|
else |
|
empty |
|
end |
|
' |
|
|
|
exit_code=${PIPESTATUS[0]} |
|
|
|
kill "$heartbeat_pid" 2>/dev/null || true |
|
wait "$heartbeat_pid" 2>/dev/null || true |
|
|
|
end_epoch="$(date +%s)" |
|
elapsed="$((end_epoch - start_epoch))" |
|
|
|
usage_tsv="$( |
|
jq -sr ' |
|
[ |
|
.[] |
|
| .. |
|
| objects |
|
| select(has("usage")) |
|
| .usage |
|
] |
|
| last // {} |
|
| [ |
|
(.input_tokens // .prompt_tokens // .cache_creation_input_tokens // 0), |
|
(.output_tokens // .completion_tokens // 0), |
|
(.total_tokens // ((.input_tokens // .prompt_tokens // .cache_creation_input_tokens // 0) + (.output_tokens // .completion_tokens // 0))) |
|
] |
|
| @tsv |
|
' "$log_file" 2>/dev/null |
|
)" |
|
|
|
if [[ -n "${usage_tsv:-}" ]]; then |
|
prompt_tokens="$(printf '%s' "$usage_tsv" | cut -f1)" |
|
completion_tokens="$(printf '%s' "$usage_tsv" | cut -f2)" |
|
total_tokens="$(printf '%s' "$usage_tsv" | cut -f3)" |
|
else |
|
prompt_tokens="0" |
|
completion_tokens="0" |
|
total_tokens="0" |
|
fi |
|
|
|
set -e |
|
|
|
{ |
|
echo |
|
echo "------------------------------------------------------------" |
|
echo "Claude run finished" |
|
echo "Run: $run_num" |
|
echo "Exit code: $exit_code" |
|
echo "Elapsed seconds: $elapsed" |
|
echo "Prompt tokens: $prompt_tokens" |
|
echo "Completion tokens: $completion_tokens" |
|
echo "Total tokens: $total_tokens" |
|
echo "Finished: $(date '+%Y-%m-%d %H:%M:%S %Z')" |
|
echo "------------------------------------------------------------" |
|
echo |
|
} | tee -a "$log_file" |
|
|
|
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" \ |
|
"$run_num" \ |
|
"$start_human" \ |
|
"$elapsed" \ |
|
"$exit_code" \ |
|
"$prompt_tokens" \ |
|
"$completion_tokens" \ |
|
"$total_tokens" \ |
|
"$log_file" >> "$SUMMARY_FILE" |
|
|
|
echo "Sleeping ${SLEEP_SECS}s before next fresh run..." | tee -a "$log_file" |
|
sleep "$SLEEP_SECS" |
|
done |