summaryrefslogtreecommitdiff
path: root/internal/api/budget.go
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-26 20:34:09 +0000
committerClaude <noreply@anthropic.com>2026-05-26 20:34:09 +0000
commitab4b364954af08fa602388495ca425eaef0abf74 (patch)
treec0806a57295270b4d86f097c3d0b6a0e1d631201 /internal/api/budget.go
parent32715355fe2eed321df4f7083dfe580d35f8a62a (diff)
feat(api,web): budget headroom endpoint + UI chips (Phase 6)
Adds GET /api/budget returning per-provider rolling-window headroom (empty when budget gating is unconfigured), wired from serve.go via SetBudget. The web UI polls it and renders a chip per limited provider in the header, flagging any under 20% remaining. New pure JS helpers formatBudgetHeadroom/ renderBudgetHeadroom are unit-tested; the endpoint is covered by Go handler tests (empty/reports/500). UI render not browser-verified in this environment. Completes Phase 6: spend accounting + dispatcher gating + observability surface. https://claude.ai/code/session_01SESwn7kQ7oP62trWw6pc39
Diffstat (limited to 'internal/api/budget.go')
-rw-r--r--internal/api/budget.go36
1 files changed, 36 insertions, 0 deletions
diff --git a/internal/api/budget.go b/internal/api/budget.go
new file mode 100644
index 0000000..eee5d09
--- /dev/null
+++ b/internal/api/budget.go
@@ -0,0 +1,36 @@
+package api
+
+import (
+ "net/http"
+
+ "github.com/thepeterstone/claudomator/internal/budget"
+)
+
+// budgetReporter exposes per-provider spend headroom. Satisfied by
+// *budget.Accountant; an interface keeps the handler testable.
+type budgetReporter interface {
+ All() ([]budget.Headroom, error)
+}
+
+// SetBudget wires the budget accountant so GET /api/budget can report headroom.
+func (s *Server) SetBudget(b budgetReporter) {
+ s.budget = b
+}
+
+// handleGetBudget returns per-provider rolling-window spend headroom. When no
+// budget is configured it returns an empty list so the UI simply shows nothing.
+func (s *Server) handleGetBudget(w http.ResponseWriter, _ *http.Request) {
+ if s.budget == nil {
+ writeJSON(w, http.StatusOK, []budget.Headroom{})
+ return
+ }
+ hs, err := s.budget.All()
+ if err != nil {
+ writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
+ return
+ }
+ if hs == nil {
+ hs = []budget.Headroom{}
+ }
+ writeJSON(w, http.StatusOK, hs)
+}