feat(templates): add budget tracking templates (Paperclip pattern)
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>
This commit is contained in:
parent
506f532f88
commit
ec6f7c150e
4 changed files with 246 additions and 0 deletions
84
scripts/templates/budget/budget-report.sh
Normal file
84
scripts/templates/budget/budget-report.sh
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
#!/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')
|
||||
"
|
||||
Loading…
Add table
Add a link
Reference in a new issue