CheddahBot/cheddahbot/tools/report_issue.py

116 lines
3.6 KiB
Python

"""Tool for logging bugs and improvement requests that need Claude Code to fix."""
from __future__ import annotations
import logging
from datetime import UTC, datetime
from pathlib import Path
from . import tool
log = logging.getLogger(__name__)
@tool(
"report_issue",
"Log a bug or improvement that needs Claude Code to fix. "
"Creates a structured entry in memory/improvement_requests.md.",
category="system",
)
def report_issue(
title: str,
description: str,
affected_files: str = "",
suggested_fix: str = "",
ctx: dict | None = None,
) -> str:
"""Append a structured improvement request and index it in memory."""
now = datetime.now(UTC)
timestamp = now.strftime("%Y-%m-%d %H:%M UTC")
entry_lines = [
f"\n## {title}",
f"**Reported:** {timestamp} ",
f"**Status:** pending\n",
description,
]
if affected_files:
entry_lines.append(f"\n**Affected files:** {affected_files}")
if suggested_fix:
entry_lines.append(f"\n**Suggested Claude Code prompt:**\n> {suggested_fix}")
entry_lines.append("\n---")
entry = "\n".join(entry_lines)
# Write to the improvement requests file
requests_path = Path("memory/improvement_requests.md")
requests_path.parent.mkdir(parents=True, exist_ok=True)
if requests_path.exists():
content = requests_path.read_text(encoding="utf-8")
else:
content = "# Improvement Requests\n\nItems here need Claude Code (with codebase access) to fix.\n"
content += entry + "\n"
requests_path.write_text(content, encoding="utf-8")
# Also index into semantic memory for searchability
if ctx and ctx.get("memory"):
ctx["memory"].log_daily(f"Reported issue: {title}{description}")
log.info("Logged improvement request: %s", title)
return f"Logged improvement request: **{title}**. Bryan will see it on the next heartbeat."
@tool(
"check_api_usage",
"Check API token usage and estimated costs for the last N days",
category="system",
)
def check_api_usage(days: int = 30, ctx: dict | None = None) -> str:
"""Return a formatted report of API usage and costs."""
db = ctx.get("db") if ctx else None
if not db:
return "Error: database not available."
summary = db.get_api_usage_summary(days)
daily = db.get_api_usage_daily(min(days, 7))
total_tokens = summary["total_tokens"]
total_cost = summary["total_cost"]
lines = [f"## API Usage Report ({days}-day window)\n"]
lines.append(f"**Total tokens:** {total_tokens:,}")
lines.append(f"**Estimated cost:** ${total_cost:.4f}")
# Budget info
config = ctx.get("config") if ctx else None
if config and hasattr(config, "api_budget"):
limit = config.api_budget.monthly_limit
pct = (total_cost / limit * 100) if limit > 0 else 0
lines.append(f"**Budget:** ${total_cost:.2f} / ${limit:.2f} ({pct:.1f}%)")
if pct >= config.api_budget.alert_threshold * 100:
lines.append(f"\n**WARNING:** Spending is at {pct:.1f}% of monthly budget!")
# Per-model breakdown
if summary["by_model"]:
lines.append("\n### By Model")
for m in summary["by_model"]:
lines.append(
f"- **{m['model']}**: {m['total_tokens']:,} tokens, "
f"${m['total_cost']:.4f}, {m['call_count']} calls"
)
# Daily trend
if daily:
lines.append("\n### Daily Trend (last 7 days)")
for d in daily:
lines.append(
f"- {d['day']}: {d['total_tokens']:,} tokens, "
f"${d['total_cost']:.4f}, {d['call_count']} calls"
)
return "\n".join(lines)