Session 4 step 16 — post-hoc enforcement via PostToolUse hook with PAUSED flag, budget-report.sh aggregates spend against window limit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
2.3 KiB
Bash
84 lines
2.3 KiB
Bash
#!/bin/bash
|
|
# Budget report: summarize cost events and compare against policy.
|
|
# Bash 3.2 compatible. Uses python3 for aggregation.
|
|
#
|
|
# Usage: ./budget-report.sh
|
|
#
|
|
# Placeholders:
|
|
# {{WORKING_DIR}} - absolute path to project directory
|
|
|
|
WORKING_DIR="{{WORKING_DIR}}"
|
|
COST_LOG="$WORKING_DIR/budget/cost-events.jsonl"
|
|
BUDGET_FILE="$WORKING_DIR/BUDGET.md"
|
|
PAUSED_FLAG="$WORKING_DIR/budget/PAUSED"
|
|
|
|
if [ ! -f "$COST_LOG" ]; then
|
|
echo "No cost events recorded yet."
|
|
exit 0
|
|
fi
|
|
|
|
COST_LOG="$COST_LOG" BUDGET_FILE="$BUDGET_FILE" PAUSED_FLAG="$PAUSED_FLAG" python3 -c "
|
|
import json, re, os
|
|
from collections import defaultdict
|
|
|
|
cost_log = os.environ.get('COST_LOG', '')
|
|
budget_file = os.environ.get('BUDGET_FILE', '')
|
|
paused_flag = os.environ.get('PAUSED_FLAG', '')
|
|
|
|
# Read events
|
|
events = []
|
|
with open(cost_log) as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if line:
|
|
try:
|
|
events.append(json.loads(line))
|
|
except:
|
|
pass
|
|
|
|
# Aggregate
|
|
by_agent = defaultdict(int)
|
|
by_day = defaultdict(int)
|
|
by_tool = defaultdict(int)
|
|
|
|
for e in events:
|
|
agent = e.get('agent', 'unknown')
|
|
day = e.get('timestamp', '')[:10]
|
|
tool = e.get('tool_name', 'unknown')
|
|
by_agent[agent] += 1
|
|
by_day[day] += 1
|
|
by_tool[tool] += 1
|
|
|
|
print('BUDGET REPORT')
|
|
print('=' * 50)
|
|
print('Total events: ' + str(len(events)))
|
|
print()
|
|
|
|
# Per-agent breakdown
|
|
print('By Agent:')
|
|
for agent, count in sorted(by_agent.items(), key=lambda x: -x[1]):
|
|
print(' ' + agent + ': ' + str(count) + ' events')
|
|
print()
|
|
|
|
# Per-day breakdown (last 7 days)
|
|
print('By Day (last 7):')
|
|
for day, count in sorted(by_day.items())[-7:]:
|
|
print(' ' + day + ': ' + str(count) + ' events')
|
|
print()
|
|
|
|
# Budget comparison
|
|
if os.path.exists(budget_file):
|
|
content = open(budget_file).read()
|
|
limit_m = re.search(r'limit:\s*(\d+)\s*cents', content)
|
|
if limit_m:
|
|
limit = int(limit_m.group(1))
|
|
est_cents = len(events) # rough proxy
|
|
pct = (est_cents / limit * 100) if limit > 0 else 0
|
|
print('Budget: ~' + str(est_cents) + '/' + str(limit) + ' cents (' + str(round(pct)) + '%)')
|
|
|
|
# Paused status
|
|
if os.path.exists(paused_flag):
|
|
print('')
|
|
print('!! AGENT PAUSED: ' + open(paused_flag).read().strip())
|
|
print(' Remove ' + paused_flag + ' to resume')
|
|
"
|