Agréger l'usage Claude depuis les transcripts locaux #49
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/vps-health-api#49
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Refs : spec-decisions-suivi-claude.md + spec-plan-suivi-claude.md (dépôt la-compagnie-maximus). Ce corps est autosuffisant : tout ce qu'il faut pour implémenter est ci-dessous.
Module pur de l'agent
claude-usage: lit les transcripts de Claude Code (~/.claude/projects/**/*.jsonl) et produit la série de 30 jours par modèle et le top projets du contrat. Aucune dépendance, aucun réseau.Contrat du snapshot (
schemaVersion: 1, figé)Corps de
POST /claude-usage, partagé entrevps-health-api(agent + serveur) etla-compagnie-maximus(dashboard) :Bornes serveur (
sanitizeClaudeUsage, liste blanche, tout champ inconnu jeté) : corps ≤ 64 Ko ;days≤ 31,date^\d{4}-\d{2}-\d{2}$;models≤ 12 clés^[a-z0-9][a-z0-9.\-]{0,63}$;limits.items≤ 12 ;projects≤ 9 ; nombresNumber.isSafeInteger≥ 0,percent0-100 ;statusdans l'enum ;detail^[a-z0-9_]{1,32}$sinon jeté ; autres chaînes ≤ 128 car. ; dates ISO validées. Le serveur ajoutereceivedAt.Réponse de
GET /claude-usage:{ staleAfterSeconds, receivedAt, ageSeconds, stale, snapshot }, ou{ "status": "no_data" }si le fichier est absent ou illisible.Faits mesurés (2026-09-27, ThinkPad, Claude Code 2.1.283)
<session>/subagents/*.jsonl), ~210 Mo.message.usagesur 30 j pour 7 118 paires (message.id,requestId) uniques (×2,4). Dans un groupe, entrée et cache sont identiques maisoutput_tokenscroît (ex. 8 puis 1 024).cwd,message.id,requestId,timestamp,versionprésents sur 100 % des lignes utiles. Modèle<synthetic>présent (à ignorer).cwd/tmp/spike-defenseur-auto-*(runsdefenseur-auto).Tâches
agent/claude-usage/aggregate.js, pur : entrées = racine des transcripts,frozenDays(objet date →{models, projects}), date du jour Toronto,homedir; sortie ={ openDays, days, projects, claudeCodeVersion }oùopenDayssont les jours recalculés (non figés) etdaysla série fusionnée de 30 jours.**/*.jsonl(sous-agents compris). Seuil de scan = lendemain du plus récent jour figé (aucun seuil s'il n'y en a pas) : ne lire que les fichiers dont le jour Toronto dumtimeest ≥ seuil. Lecture en flux (readline), ligne JSON invalide sautée.type === "assistant"avecmessage.usage; ignorer le modèle<synthetic>; ignorer les lignes dont le jour dutimestampest figé.message.id,requestId) sur tous les fichiers, en gardant l'entrée au plus grandoutput_tokens.input=input_tokens,output=output_tokens,cacheCreation=cache_creation_input_tokens,cacheRead=cache_read_input_tokens,requests= nombre de paires.Intl.DateTimeFormat('en-CA', { timeZone: 'America/Toronto' }); décalages viaDate.UTC(y, m - 1, d - n)sur les parties. Jamais « maintenant − 86 400 000 ».cwd, dans l'ordre : (1)<home>/claude-code/<a>/...→<a>, et si<a>vaut.spikesou.archive→<a>/<b>; (2)<home>/claude-codeexactement →(monorepo); (3)/tmp/spike-defenseur-auto-*→defenseurs; (4)/tmp/claude-<uid>/-home-max-claude-code-<p>/...→<p>; (5) reste →(autre).<home>=os.homedir()(injecté pour les tests).claudeCodeVersion= champversionde la ligne autimestample plus récent lue ;nullsi aucune.worksur la fenêtre puis(autres)(agrégé) s'il reste du volume ; somme = total.other).Fichiers concernés
agent/claude-usage/aggregate.js(nouveau)__tests__/agent-claude-usage.test.js(nouveau, partagé avec l'issue de l'agent)__tests__/fixtures/claude-usage/(nouveau, JSONL synthétiques uniquement, aucun vrai transcript)Critères d'acceptation
subagents/→ un seul compte, au plus grandoutput_tokens<synthetic>, lignesuser, lignes JSON tronquées ignorées sans exception(autres): la somme desworkégale le total de la fenêtreReview caveats
defenseur-autoen/tmp: sans règle dédiée, 9 % du volume tombe en(autre).Décisions prises en planification
claude -p.Spec source
la-compagnie-maximus :
spec-plan-suivi-claude.md, Issue 2