"""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)