mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
Surface Claude subscription plan usage limits (5-hour rolling + 7-day weekly: percent used + reset time) in the header, opt-in via App Settings → Display → "Plan Usage Limits" (default OFF, no behavior change when off). A Codeman-managed Claude statusLine exporter forwards the rate_limits JSON to a new auth-exempt POST /api/status-telemetry (same loopback + hook-secret gate as /api/hook-event); parsed telemetry broadcasts over SSE session:statusTelemetry to a header chip (amber >=80%, red >=95%, reset times on hover). The exporter prints the same summary back as the in-terminal footer (print-through). - src/usage-telemetry.ts: pure parser/formatter (epoch-sec -> ms, clamp, change signature) + test/usage-telemetry.test.ts - hooks-config.ts: generateStatusLineCommand + applyStatusLineConfig (add/remove; never clobbers a user's own statusLine) - session-routes.ts: inject gate (Claude-only, Codeman-managed cases), driven by create-payload statusLineTelemetry (session-ui.js) - schemas.ts: StatusTelemetrySchema + showPlanUsageLimits + payload field - frontend: header chip, applyHeaderVisibilitySettings toggle, renderIndexHtml strip, _onSessionStatusTelemetry handler Schema empirically confirmed against Claude Code 2.1.177 (Claude Max): only five_hour/seven_day windows exist (no Opus-weekly field); rate_limits is absent before the first API response and for non-subscriber auth. Design + verification method in docs/usage-limits-display-plan.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
64 lines
2.3 KiB
TypeScript
64 lines
2.3 KiB
TypeScript
/**
|
|
* @fileoverview Status-telemetry route.
|
|
*
|
|
* Receives Claude Code statusline payloads POSTed by the Codeman-managed
|
|
* statusLine exporter (see `hooks-config.generateStatusLineCommand`) and
|
|
* broadcasts the parsed plan-usage limits (5-hour + weekly) to SSE clients for
|
|
* the header "Plan Usage Limits" chip. Auth-exempt like `/api/hook-event`
|
|
* (localhost-only; hook-secret-gated while a tunnel runs — see middleware/auth).
|
|
*
|
|
* Returns a compact plain-text status string for the exporter to print as the
|
|
* in-terminal footer (print-through), so injecting our statusLine doesn't leave
|
|
* the terminal footer blank.
|
|
*/
|
|
|
|
import { FastifyInstance } from 'fastify';
|
|
import { StatusTelemetrySchema } from '../schemas.js';
|
|
import { parseBody } from '../route-helpers.js';
|
|
import {
|
|
parseStatusTelemetry,
|
|
formatStatusLineText,
|
|
telemetrySignature,
|
|
type RawStatuslinePayload,
|
|
} from '../../usage-telemetry.js';
|
|
import { SessionStatusTelemetry } from '../sse-events.js';
|
|
import type { SessionPort, EventPort } from '../ports/index.js';
|
|
|
|
export function registerStatusTelemetryRoutes(app: FastifyInstance, ctx: SessionPort & EventPort): void {
|
|
// Last broadcast telemetry signature per session — the statusline fires on
|
|
// every assistant message, so we only rebroadcast when the value changes.
|
|
const lastSig = new Map<string, string>();
|
|
|
|
app.post('/api/status-telemetry', async (req, reply) => {
|
|
const { sessionId, data } = parseBody(StatusTelemetrySchema, req.body);
|
|
|
|
reply.type('text/plain; charset=utf-8');
|
|
|
|
// Unknown session — minimal footer, no broadcast.
|
|
if (!ctx.sessions.has(sessionId)) {
|
|
lastSig.delete(sessionId);
|
|
return 'codeman';
|
|
}
|
|
|
|
const telemetry = parseStatusTelemetry(data as RawStatuslinePayload | undefined);
|
|
if (!telemetry) {
|
|
// No plan-limit data yet (pre-first-response or non-subscriber auth).
|
|
return 'codeman';
|
|
}
|
|
|
|
const sig = telemetrySignature(telemetry);
|
|
if (lastSig.get(sessionId) !== sig) {
|
|
lastSig.set(sessionId, sig);
|
|
// Bound the map across long multi-session runs: prune dead sessions.
|
|
if (lastSig.size > 256) {
|
|
for (const id of [...lastSig.keys()]) {
|
|
if (!ctx.sessions.has(id)) lastSig.delete(id);
|
|
}
|
|
}
|
|
ctx.broadcast(SessionStatusTelemetry, { sessionId, ...telemetry });
|
|
}
|
|
|
|
return formatStatusLineText(telemetry);
|
|
});
|
|
}
|