#!/usr/bin/env bash
# Measure what YOUR Claude Code project costs in startup context.
#
# What it does: starts two throwaway sessions that only say "Reply with only: OK",
# one in an empty temporary directory and one in your project, and subtracts the
# reported token counts. The difference is what your configuration costs.
#
# It does not modify anything and does not read the contents of your project
# files. It does check the byte size of your user-level CLAUDE.md, since that
# loads into every session and is otherwise invisible to the diff. It creates
# one temporary directory and deletes it on exit.
#
# Requires: claude, jq. Usage: bash measure-context.sh [path-to-project]

set -uo pipefail

PROJECT="${1:-$PWD}"

command -v claude >/dev/null || { echo "error: 'claude' not found on PATH" >&2; exit 1; }
command -v jq     >/dev/null || { echo "error: 'jq' not found on PATH" >&2; exit 1; }
[ -d "$PROJECT" ] || { echo "error: not a directory: $PROJECT" >&2; exit 1; }

TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT

# Total prompt tokens for one trivial turn = fresh input + cache written + cache read.
# Reading .usage this way counts the whole prompt regardless of cache state, which
# is what makes the two measurements comparable.
measure() {
  ( cd "$1" && claude -p 'Reply with only: OK' --output-format json </dev/null 2>/dev/null ) \
  | jq -r '(.usage.input_tokens // 0)
         + (.usage.cache_creation_input_tokens // 0)
         + (.usage.cache_read_input_tokens // 0)'
}

# The reported baseline drifts by a couple of hundred tokens over a session, so
# measure it twice. The gap between the two is this run's noise floor, and any
# result smaller than it is not a measurement.
echo "measuring an empty baseline (twice, to find the noise floor)..."
BASE1="$(measure "$TMP")"
BASE2="$(measure "$TMP")"
echo "measuring $PROJECT ..."
MINE="$(measure "$PROJECT")"

case "$BASE1$BASE2$MINE" in
  *[!0-9]*|'') echo "error: could not read token usage; run 'claude -p hi --output-format json' to debug" >&2; exit 1 ;;
esac

BASE=$(( BASE1 < BASE2 ? BASE1 : BASE2 ))
NOISE=$(( BASE1 > BASE2 ? BASE1 - BASE2 : BASE2 - BASE1 ))
[ "$NOISE" -lt 50 ] && NOISE=50
OVERHEAD=$(( MINE - BASE ))

# A user-level CLAUDE.md loads into every session, including the "empty"
# baseline, so it is invisible to the diff above. Surface it, because it is
# often larger than the project config being measured.
USER_MD="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/CLAUDE.md"
USER_BYTES=0
[ -f "$USER_MD" ] && USER_BYTES=$(wc -c < "$USER_MD" | tr -d ' ')

printf '\n  %-34s %s\n' "empty project baseline" "$BASE"
printf '  %-34s %s\n'   "your project at startup" "$MINE"
printf '  %-34s +/-%s\n' "measurement noise this run" "$NOISE"

if [ "$OVERHEAD" -le "$NOISE" ]; then
  printf '  %-34s %s\n\n' "your configuration costs" "below the noise floor"
  echo "  Your project adds less context than this run can reliably measure."
  echo "  That means your configuration is close to free, which is a good result."
  printf '\n'
else
  printf '  %-34s %s\n\n' "your configuration costs" "$OVERHEAD"
fi

if [ "$USER_BYTES" -gt 0 ]; then
  # 0.368 tokens/byte, measured on a real 72,783-byte instruction file. Dense
  # structured markdown tokenises worse than prose, which sits nearer 0.26.
  awk -v b="$USER_BYTES" 'BEGIN{
    printf "  %-34s ~%d tokens (%d bytes)\n", "your user-level CLAUDE.md", 106 + b*0.368, b
  }'
  echo "  Counted inside the baseline, not in the project figure above,"
  echo "  because it loads in every project. Estimated from file size."
  printf '\n'
fi

# Each tool round trip re-sends the whole context, and the context grows as
# results accumulate, so a session costs far more than its startup figure.
# Model validated against measured runs to within 0.45%. See the article below.
for TRIPS in 1 20 100; do
  awk -v s="$MINE" -v n="$TRIPS" 'BEGIN{
    r = 3000 * 0.35; t = 0
    for (k = 0; k <= n; k++) t += s + r * k
    printf "  %-34s %d tokens (%.1fx startup)\n", n " round trips, 3KB results", t, t / s
  }'
done

cat <<EOF

  Assumes an average 3KB tool result. Your numbers will differ; the shape will not.
  Method and published figures: https://aieveryminute.com/method/
EOF
