#!/usr/bin/env bash # burn — a token-burning harness around Claude Code. # Usage: burn TOKENS [--model MODEL] set -euo pipefail FILLER_TOKENS=1000 SYSTEM_OVERHEAD_TOKENS=9000 # measured: claude -p system prompt + tool defs TOKENS_PER_CALL=$(( SYSTEM_OVERHEAD_TOKENS + FILLER_TOKENS )) MIN_TOKENS=$TOKENS_PER_CALL # asking for less than one call can't be honored FILLER_LINE="Burn baby burn, this is a disco inferno, burning tokens is for great engineers. " usage() { cat <&2; exit 2; fi model="$2"; shift 2 ;; --model=*) model="${1#*=}"; shift ;; --) shift; break ;; -*) echo "error: unknown flag: $1" >&2; usage >&2; exit 2 ;; *) if [[ -n "$tokens" ]]; then echo "error: unexpected extra argument: $1" >&2 exit 2 fi tokens="$1"; shift ;; esac done if [[ -z "$tokens" ]]; then usage >&2; exit 2 fi if ! [[ "$tokens" =~ ^[0-9]+$ ]]; then echo "error: TOKENS must be a positive integer (got '$tokens')" >&2; exit 2 fi if (( tokens < MIN_TOKENS )); then echo "error: tokens must be at least ${MIN_TOKENS} (got ${tokens})" >&2; exit 2 fi if ! command -v claude >/dev/null 2>&1; then echo "error: 'claude' CLI not found on PATH." >&2 echo " install Claude Code first: https://docs.claude.com/claude-code" >&2 exit 1 fi if ! command -v jq >/dev/null 2>&1; then echo "error: 'jq' not found on PATH (used to parse Claude Code's JSON output)." >&2 echo " install with: brew install jq" >&2 exit 1 fi # ~4 chars/token is the standard rule of thumb for English text. target_chars=$(( FILLER_TOKENS * 4 )) filler_len=${#FILLER_LINE} repeats=$(( target_chars / filler_len )) (( repeats < 1 )) && repeats=1 filler="" for ((i=0; i 100 )) && pct=100 filled=$(( pct * BAR_WIDTH / 100 )) empty=$(( BAR_WIDTH - filled )) for ((i=0; i/dev/null || true wait "$claude_pid" 2>/dev/null || true fi echo printf "stopped. burned %s tokens across %s call(s), spent \$%s.\n" \ "$burned" "$iteration" "$total_cost" exit 130 } trap on_interrupt INT # Run one claude call in the background while animating flames. On success # leaves the parsed stdout in $last_output. Returns the claude exit status. run_claude() { local output_file output_file=$(mktemp) "${claude_cmd[@]}" "$prompt" > "$output_file" 2>/dev/null & claude_pid=$! local frame=0 while kill -0 "$claude_pid" 2>/dev/null; do redraw_flames $(( frame % 3 )) frame=$(( frame + 1 )) sleep 0.25 done if wait "$claude_pid" 2>/dev/null; then last_output=$(<"$output_file") rm -f "$output_file" claude_pid="" return 0 else rm -f "$output_file" claude_pid="" return 1 fi } printf '%s' "$HIDE_CURSOR" echo draw_fireplace 0 echo draw_bar while (( burned < tokens )); do iteration=$(( iteration + 1 )) if ! run_claude; then draw_bar " (retrying)" if ! run_claude; then printf "\nerror: two consecutive claude calls failed; aborting.\n" >&2 exit 1 fi fi read -r this_call this_cost < <(jq -r ' [ ((.usage.input_tokens // 0) + (.usage.cache_creation_input_tokens // 0) + (.usage.cache_read_input_tokens // 0) + (.usage.output_tokens // 0)), (.total_cost_usd // 0) ] | @tsv' <<<"$last_output") if ! [[ "$this_call" =~ ^[0-9]+$ ]]; then printf "\nerror: could not parse token usage from claude output\n" >&2 exit 1 fi burned=$(( burned + this_call )) total_cost=$(awk -v a="$total_cost" -v b="$this_cost" 'BEGIN{printf "%.6f", a+b}') draw_bar done echo printf "done. burned %s tokens across %s call(s) (target was %s), spent \$%s.\n" \ "$burned" "$iteration" "$tokens" "$total_cost"