116 lines
3.6 KiB
Python
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)
|