aiblueprint-cli 1.4.12 → 1.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-code-config/scripts/.claude/commands/fix-on-my-computer.md +87 -0
- package/claude-code-config/scripts/CLAUDE.md +50 -0
- package/claude-code-config/scripts/{statusline/biome.json → biome.json} +5 -2
- package/claude-code-config/scripts/bun.lockb +0 -0
- package/claude-code-config/scripts/command-validator/CLAUDE.md +112 -0
- package/claude-code-config/scripts/command-validator/src/__tests__/validator.test.ts +62 -111
- package/claude-code-config/scripts/command-validator/src/cli.ts +5 -3
- package/claude-code-config/scripts/command-validator/src/lib/security-rules.ts +3 -4
- package/claude-code-config/scripts/command-validator/src/lib/types.ts +1 -0
- package/claude-code-config/scripts/command-validator/src/lib/validator.ts +47 -317
- package/claude-code-config/scripts/package.json +39 -0
- package/claude-code-config/scripts/statusline/CLAUDE.md +29 -7
- package/claude-code-config/scripts/statusline/README.md +89 -1
- package/claude-code-config/scripts/statusline/__tests__/context.test.ts +229 -0
- package/claude-code-config/scripts/statusline/__tests__/formatters.test.ts +108 -0
- package/claude-code-config/scripts/statusline/__tests__/statusline.test.ts +309 -0
- package/claude-code-config/scripts/statusline/data/.gitignore +8 -0
- package/claude-code-config/scripts/statusline/data/.gitkeep +0 -0
- package/claude-code-config/scripts/statusline/defaults.json +79 -0
- package/claude-code-config/scripts/statusline/docs/ARCHITECTURE.md +166 -0
- package/claude-code-config/scripts/statusline/fixtures/mock-transcript.jsonl +4 -0
- package/claude-code-config/scripts/statusline/fixtures/test-input.json +12 -2
- package/claude-code-config/scripts/statusline/src/index.ts +175 -24
- package/claude-code-config/scripts/statusline/src/lib/config-types.ts +104 -0
- package/claude-code-config/scripts/statusline/src/lib/config.ts +21 -0
- package/claude-code-config/scripts/statusline/src/lib/context.ts +32 -11
- package/claude-code-config/scripts/statusline/src/lib/formatters.ts +360 -22
- package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
- package/claude-code-config/scripts/statusline/src/lib/menu-factories.ts +224 -0
- package/claude-code-config/scripts/statusline/src/lib/presets.ts +177 -0
- package/claude-code-config/scripts/statusline/src/lib/render-pure.ts +497 -0
- package/claude-code-config/scripts/statusline/src/lib/types.ts +11 -0
- package/claude-code-config/scripts/statusline/src/lib/utils.ts +15 -0
- package/claude-code-config/scripts/statusline/src/tests/spend-v2.test.ts +306 -0
- package/claude-code-config/scripts/statusline/statusline.config.json +79 -0
- package/claude-code-config/scripts/statusline/test-with-fixtures.ts +37 -0
- package/claude-code-config/scripts/tsconfig.json +27 -0
- package/claude-code-config/skills/claude-memory/SKILL.md +689 -0
- package/claude-code-config/skills/claude-memory/references/comprehensive-example.md +175 -0
- package/claude-code-config/skills/claude-memory/references/project-patterns.md +334 -0
- package/claude-code-config/skills/claude-memory/references/prompting-techniques.md +411 -0
- package/claude-code-config/skills/claude-memory/references/section-templates.md +347 -0
- package/claude-code-config/skills/create-slash-commands/SKILL.md +1110 -0
- package/claude-code-config/skills/create-slash-commands/references/arguments.md +273 -0
- package/claude-code-config/skills/create-slash-commands/references/patterns.md +947 -0
- package/claude-code-config/skills/create-slash-commands/references/prompt-examples.md +656 -0
- package/claude-code-config/skills/create-slash-commands/references/tool-restrictions.md +389 -0
- package/claude-code-config/skills/create-subagents/SKILL.md +425 -0
- package/claude-code-config/skills/create-subagents/references/context-management.md +567 -0
- package/claude-code-config/skills/create-subagents/references/debugging-agents.md +714 -0
- package/claude-code-config/skills/create-subagents/references/error-handling-and-recovery.md +502 -0
- package/claude-code-config/skills/create-subagents/references/evaluation-and-testing.md +374 -0
- package/claude-code-config/skills/create-subagents/references/orchestration-patterns.md +591 -0
- package/claude-code-config/skills/create-subagents/references/subagents.md +599 -0
- package/claude-code-config/skills/create-subagents/references/writing-subagent-prompts.md +513 -0
- package/package.json +1 -1
- package/claude-code-config/commands/apex.md +0 -109
- package/claude-code-config/commands/tasks/run-task.md +0 -220
- package/claude-code-config/commands/utils/watch-ci.md +0 -47
- package/claude-code-config/scripts/command-validator/biome.json +0 -29
- package/claude-code-config/scripts/command-validator/bun.lockb +0 -0
- package/claude-code-config/scripts/command-validator/package.json +0 -27
- package/claude-code-config/scripts/command-validator/vitest.config.ts +0 -7
- package/claude-code-config/scripts/hook-post-file.ts +0 -162
- package/claude-code-config/scripts/statusline/bun.lockb +0 -0
- package/claude-code-config/scripts/statusline/package.json +0 -19
- package/claude-code-config/scripts/statusline/statusline.config.ts +0 -25
- package/claude-code-config/scripts/validate-command.js +0 -712
- package/claude-code-config/scripts/validate-command.readme.md +0 -283
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"features": {
|
|
3
|
+
"usageLimits": true,
|
|
4
|
+
"spendTracking": true
|
|
5
|
+
},
|
|
6
|
+
"oneLine": true,
|
|
7
|
+
"showSonnetModel": false,
|
|
8
|
+
"pathDisplayMode": "truncated",
|
|
9
|
+
"git": {
|
|
10
|
+
"enabled": true,
|
|
11
|
+
"showBranch": true,
|
|
12
|
+
"showDirtyIndicator": true,
|
|
13
|
+
"showChanges": false,
|
|
14
|
+
"showStaged": true,
|
|
15
|
+
"showUnstaged": true
|
|
16
|
+
},
|
|
17
|
+
"separator": "•",
|
|
18
|
+
"session": {
|
|
19
|
+
"infoSeparator": null,
|
|
20
|
+
"cost": { "enabled": true, "format": "decimal1" },
|
|
21
|
+
"duration": { "enabled": true },
|
|
22
|
+
"tokens": { "enabled": true, "showMax": false, "showDecimals": false },
|
|
23
|
+
"percentage": {
|
|
24
|
+
"enabled": true,
|
|
25
|
+
"showValue": true,
|
|
26
|
+
"progressBar": {
|
|
27
|
+
"enabled": true,
|
|
28
|
+
"length": 10,
|
|
29
|
+
"style": "braille",
|
|
30
|
+
"color": "progressive",
|
|
31
|
+
"background": "none"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"context": {
|
|
36
|
+
"usePayloadContextWindow": true,
|
|
37
|
+
"maxContextTokens": 200000,
|
|
38
|
+
"autocompactBufferTokens": 45000,
|
|
39
|
+
"useUsableContextOnly": true,
|
|
40
|
+
"overheadTokens": 0
|
|
41
|
+
},
|
|
42
|
+
"limits": {
|
|
43
|
+
"enabled": true,
|
|
44
|
+
"showTimeLeft": true,
|
|
45
|
+
"showPacingDelta": true,
|
|
46
|
+
"cost": { "enabled": false, "format": "decimal1" },
|
|
47
|
+
"percentage": {
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"showValue": true,
|
|
50
|
+
"progressBar": {
|
|
51
|
+
"enabled": true,
|
|
52
|
+
"length": 10,
|
|
53
|
+
"style": "braille",
|
|
54
|
+
"color": "progressive",
|
|
55
|
+
"background": "none"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"weeklyUsage": {
|
|
60
|
+
"enabled": "90%",
|
|
61
|
+
"showTimeLeft": true,
|
|
62
|
+
"showPacingDelta": true,
|
|
63
|
+
"cost": { "enabled": false, "format": "decimal1" },
|
|
64
|
+
"percentage": {
|
|
65
|
+
"enabled": true,
|
|
66
|
+
"showValue": true,
|
|
67
|
+
"progressBar": {
|
|
68
|
+
"enabled": true,
|
|
69
|
+
"length": 10,
|
|
70
|
+
"style": "braille",
|
|
71
|
+
"color": "progressive",
|
|
72
|
+
"background": "none"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"dailySpend": {
|
|
77
|
+
"cost": { "enabled": true, "format": "decimal1" }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Statusline Architecture - Session & Period Tracking
|
|
2
|
+
|
|
3
|
+
## Concepts Fondamentaux
|
|
4
|
+
|
|
5
|
+
### Session Claude Code
|
|
6
|
+
|
|
7
|
+
Une **session** est créée chaque fois qu'on ouvre un chat avec Claude Code.
|
|
8
|
+
|
|
9
|
+
Caractéristiques:
|
|
10
|
+
- **ID unique**: `session_id` (UUID)
|
|
11
|
+
- **Coût cumulatif**: Le coût total de la session qui AUGMENTE au fil du temps
|
|
12
|
+
- **Persistance**: Une session peut durer des heures/jours
|
|
13
|
+
- **Commande /clear**: Efface la conversation mais GARDE la même session
|
|
14
|
+
- **Le coût ne reset jamais**: Si une session a coûté $10, puis on fait /clear, le coût reste $10 et continue d'augmenter
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Session "abc-123"
|
|
18
|
+
├── Début: $0
|
|
19
|
+
├── Après 1h: $5
|
|
20
|
+
├── /clear (conversation effacée)
|
|
21
|
+
├── Après 2h: $12 (coût continue d'augmenter)
|
|
22
|
+
├── Fermeture terminal
|
|
23
|
+
├── Réouverture (même session)
|
|
24
|
+
└── Après 3h: $18 (toujours cumulatif)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Période (5 heures)
|
|
28
|
+
|
|
29
|
+
Une **période** est une fenêtre de 5 heures pour le rate limiting d'Anthropic.
|
|
30
|
+
|
|
31
|
+
Caractéristiques:
|
|
32
|
+
- **resets_at**: Timestamp de fin de période (ex: "2025-12-09T10:00:00.000Z")
|
|
33
|
+
- **Durée fixe**: 5 heures
|
|
34
|
+
- **Indépendant des sessions**: Les sessions peuvent traverser plusieurs périodes
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Période A (05:00-10:00) Période B (10:00-15:00)
|
|
38
|
+
├────────────────────┤ ├────────────────────┤
|
|
39
|
+
Session 1: +$5 Session 1: +$3 (continue)
|
|
40
|
+
Session 2: +$8 Session 3: +$10 (nouvelle)
|
|
41
|
+
───────────── ─────────────
|
|
42
|
+
Total: $13 Total: $13
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Le Problème Actuel
|
|
46
|
+
|
|
47
|
+
### Bug: Double Comptage
|
|
48
|
+
|
|
49
|
+
Quand on calcule le coût d'une période en sommant les coûts totaux des sessions:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
Session X: coût total = $24
|
|
53
|
+
Session X: last_resets_at = période actuelle
|
|
54
|
+
|
|
55
|
+
getCurrentPeriodCost() retourne $24 ❌ FAUX!
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Mais si la session X avait déjà $10 AVANT cette période:
|
|
59
|
+
- Coût réel de cette période = $24 - $10 = $14
|
|
60
|
+
- On affiche $24 au lieu de $14 = **double comptage**
|
|
61
|
+
|
|
62
|
+
### Scénario Réel
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
1. Période A: Session X coûte $10, on compte $10 ✓
|
|
66
|
+
2. Période B commence
|
|
67
|
+
3. Session X continue, coût monte à $24
|
|
68
|
+
4. On calcule période B: on voit session X = $24
|
|
69
|
+
5. On affiche $24 pour période B ❌
|
|
70
|
+
6. Mais on avait déjà compté $10 en période A!
|
|
71
|
+
7. Total affiché: $10 + $24 = $34
|
|
72
|
+
8. Total réel: $24
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Solution: Tracking des Deltas par Session
|
|
76
|
+
|
|
77
|
+
### Nouvelle Structure de Données
|
|
78
|
+
|
|
79
|
+
On doit tracker pour chaque session:
|
|
80
|
+
- Combien on a DÉJÀ compté
|
|
81
|
+
- Dans quelle période on l'a compté
|
|
82
|
+
|
|
83
|
+
```sql
|
|
84
|
+
-- Table: sessions
|
|
85
|
+
session_id TEXT PRIMARY KEY
|
|
86
|
+
total_cost REAL -- Coût total actuel de la session
|
|
87
|
+
cwd TEXT
|
|
88
|
+
date TEXT
|
|
89
|
+
duration_ms INTEGER
|
|
90
|
+
lines_added INTEGER
|
|
91
|
+
lines_removed INTEGER
|
|
92
|
+
|
|
93
|
+
-- Table: session_period_tracking
|
|
94
|
+
session_id TEXT
|
|
95
|
+
period_id TEXT -- resets_at normalisé
|
|
96
|
+
counted_cost REAL -- Combien on a compté pour cette période
|
|
97
|
+
last_update INTEGER -- Timestamp
|
|
98
|
+
PRIMARY KEY (session_id, period_id)
|
|
99
|
+
|
|
100
|
+
-- Table: periods
|
|
101
|
+
period_id TEXT PRIMARY KEY -- resets_at normalisé
|
|
102
|
+
total_cost REAL -- Somme des deltas de toutes les sessions
|
|
103
|
+
utilization INTEGER
|
|
104
|
+
date TEXT
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Algorithme Correct
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Quand saveSession(session_id, new_cost, current_period):
|
|
111
|
+
1. Chercher session dans DB
|
|
112
|
+
2. Si existe:
|
|
113
|
+
old_cost = session.total_cost
|
|
114
|
+
delta = new_cost - old_cost
|
|
115
|
+
Sinon:
|
|
116
|
+
delta = new_cost
|
|
117
|
+
|
|
118
|
+
3. Chercher tracking pour (session_id, current_period)
|
|
119
|
+
4. Si existe:
|
|
120
|
+
// Déjà compté dans cette période, calculer le vrai delta
|
|
121
|
+
already_counted = tracking.counted_cost
|
|
122
|
+
period_delta = delta // Le delta depuis la dernière update
|
|
123
|
+
Sinon:
|
|
124
|
+
// Nouvelle session dans cette période
|
|
125
|
+
period_delta = delta
|
|
126
|
+
|
|
127
|
+
5. Mettre à jour:
|
|
128
|
+
- session.total_cost = new_cost
|
|
129
|
+
- tracking.counted_cost += period_delta
|
|
130
|
+
- period.total_cost += period_delta
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Exemple Concret
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Session X traverse 2 périodes:
|
|
137
|
+
|
|
138
|
+
Période A:
|
|
139
|
+
- Session X: $0 → $10
|
|
140
|
+
- tracking(X, A).counted_cost = $10
|
|
141
|
+
- period(A).total_cost = $10
|
|
142
|
+
|
|
143
|
+
Période B:
|
|
144
|
+
- Session X: $10 → $24
|
|
145
|
+
- delta = $24 - $10 = $14
|
|
146
|
+
- tracking(X, B).counted_cost = $14
|
|
147
|
+
- period(B).total_cost = $14
|
|
148
|
+
|
|
149
|
+
Calcul période B:
|
|
150
|
+
- On lit period(B).total_cost = $14 ✓ CORRECT!
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Avantages SQLite
|
|
154
|
+
|
|
155
|
+
1. **Transactions ACID**: Pas de corruption de données
|
|
156
|
+
2. **Requêtes complexes**: Agrégations, joins faciles
|
|
157
|
+
3. **Index**: Performance pour les lookups par session_id
|
|
158
|
+
4. **Single file**: Toujours portable
|
|
159
|
+
5. **Intégrité référentielle**: FK entre tables
|
|
160
|
+
|
|
161
|
+
## Migration
|
|
162
|
+
|
|
163
|
+
1. Créer nouveau fichier `statusline.db`
|
|
164
|
+
2. Migrer données existantes de spend.json et daily-usage.json
|
|
165
|
+
3. Recalculer les period_costs correctement
|
|
166
|
+
4. Supprimer les anciens fichiers JSON
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{"parentUuid":null,"isSidechain":false,"userType":"external","cwd":"/Users/melvynx/.claude/scripts/statusline","sessionId":"demo-session","version":"2.0.31","gitBranch":"main","timestamp":"2025-11-11T10:00:00.000Z","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_01","type":"message","role":"user","content":[{"type":"text","text":"Hello"}],"usage":{"input_tokens":1000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":100}},"uuid":"uuid-1"}
|
|
2
|
+
{"parentUuid":"uuid-1","isSidechain":false,"userType":"external","cwd":"/Users/melvynx/.claude/scripts/statusline","sessionId":"demo-session","version":"2.0.31","gitBranch":"main","timestamp":"2025-11-11T10:05:00.000Z","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_02","type":"message","role":"assistant","content":[{"type":"text","text":"Hi there!"}],"usage":{"input_tokens":50000,"cache_creation_input_tokens":30000,"cache_read_input_tokens":0,"output_tokens":200}},"uuid":"uuid-2"}
|
|
3
|
+
{"parentUuid":"uuid-2","isSidechain":false,"userType":"external","cwd":"/Users/melvynx/.claude/scripts/statusline","sessionId":"demo-session","version":"2.0.31","gitBranch":"main","timestamp":"2025-11-11T10:10:00.000Z","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_03","type":"message","role":"user","content":[{"type":"text","text":"Can you help me?"}],"usage":{"input_tokens":20000,"cache_creation_input_tokens":0,"cache_read_input_tokens":40000,"output_tokens":150}},"uuid":"uuid-3"}
|
|
4
|
+
{"parentUuid":"uuid-3","isSidechain":false,"userType":"external","cwd":"/Users/melvynx/.claude/scripts/statusline","sessionId":"demo-session","version":"2.0.31","gitBranch":"main","timestamp":"2025-11-11T10:15:00.000Z","message":{"model":"claude-sonnet-4-5-20250929","id":"msg_04","type":"message","role":"assistant","content":[{"type":"text","text":"Of course! What do you need?"}],"usage":{"input_tokens":30000,"cache_creation_input_tokens":0,"cache_read_input_tokens":45000,"output_tokens":300}},"uuid":"uuid-4"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"session_id": "06a7b019-03f8-4083-a9db-410d95cb01e6",
|
|
3
|
-
"transcript_path": "/Users/melvynx/.claude/
|
|
3
|
+
"transcript_path": "/Users/melvynx/.claude/scripts/statusline/fixtures/mock-transcript.jsonl",
|
|
4
4
|
"cwd": "/Users/melvynx/.claude",
|
|
5
5
|
"model": {
|
|
6
6
|
"id": "claude-sonnet-4-5-20250929",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"current_dir": "/Users/melvynx/.claude",
|
|
11
11
|
"project_dir": "/Users/melvynx/.claude"
|
|
12
12
|
},
|
|
13
|
-
"version": "2.0.
|
|
13
|
+
"version": "2.0.68",
|
|
14
14
|
"output_style": {
|
|
15
15
|
"name": "default"
|
|
16
16
|
},
|
|
@@ -21,5 +21,15 @@
|
|
|
21
21
|
"total_lines_added": 185,
|
|
22
22
|
"total_lines_removed": 75
|
|
23
23
|
},
|
|
24
|
+
"context_window": {
|
|
25
|
+
"total_input_tokens": 136349,
|
|
26
|
+
"total_output_tokens": 21612,
|
|
27
|
+
"context_window_size": 200000,
|
|
28
|
+
"current_usage": {
|
|
29
|
+
"input_tokens": 62500,
|
|
30
|
+
"cache_creation_input_tokens": 0,
|
|
31
|
+
"cache_read_input_tokens": 0
|
|
32
|
+
}
|
|
33
|
+
},
|
|
24
34
|
"exceeds_200k_tokens": false
|
|
25
35
|
}
|
|
@@ -1,38 +1,189 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { defaultConfig, type StatuslineConfig } from "./lib/config";
|
|
4
6
|
import { getContextData } from "./lib/context";
|
|
5
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
colors,
|
|
9
|
+
formatBranch,
|
|
10
|
+
formatCost,
|
|
11
|
+
formatDuration,
|
|
12
|
+
formatPath,
|
|
13
|
+
} from "./lib/formatters";
|
|
14
|
+
import { getGitStatus } from "./lib/git";
|
|
15
|
+
import {
|
|
16
|
+
renderStatusline,
|
|
17
|
+
type StatuslineData,
|
|
18
|
+
type UsageLimit,
|
|
19
|
+
} from "./lib/render-pure";
|
|
6
20
|
import type { HookInput } from "./lib/types";
|
|
7
21
|
|
|
22
|
+
// Optional feature imports - just delete the folder to disable!
|
|
23
|
+
let getUsageLimits: any = null;
|
|
24
|
+
let normalizeResetsAt: any = null;
|
|
25
|
+
let getPeriodCost: any = null;
|
|
26
|
+
let getTodayCostV2: any = null;
|
|
27
|
+
let saveSessionV2: any = null;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const limitsModule = await import("./lib/features/limits");
|
|
31
|
+
getUsageLimits = limitsModule.getUsageLimits;
|
|
32
|
+
} catch {
|
|
33
|
+
// Limits feature not available - that's OK!
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const utilsModule = await import("./lib/utils");
|
|
38
|
+
normalizeResetsAt = utilsModule.normalizeResetsAt;
|
|
39
|
+
} catch {
|
|
40
|
+
// Fallback normalizeResetsAt
|
|
41
|
+
normalizeResetsAt = (resetsAt: string) => resetsAt;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const spendModule = await import("./lib/features/spend");
|
|
46
|
+
getPeriodCost = spendModule.getPeriodCost;
|
|
47
|
+
getTodayCostV2 = spendModule.getTodayCostV2;
|
|
48
|
+
saveSessionV2 = spendModule.saveSessionV2;
|
|
49
|
+
} catch {
|
|
50
|
+
// Spend tracking feature not available - that's OK!
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Re-export from render-pure for backwards compatibility
|
|
54
|
+
export {
|
|
55
|
+
renderStatusline,
|
|
56
|
+
type StatuslineData,
|
|
57
|
+
type UsageLimit,
|
|
58
|
+
} from "./lib/render-pure";
|
|
59
|
+
|
|
60
|
+
const CONFIG_FILE_PATH = join(import.meta.dir, "..", "statusline.config.json");
|
|
61
|
+
const LAST_PAYLOAD_PATH = join(
|
|
62
|
+
import.meta.dir,
|
|
63
|
+
"..",
|
|
64
|
+
"data",
|
|
65
|
+
"last_payload.txt",
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
async function loadConfig(): Promise<StatuslineConfig> {
|
|
69
|
+
try {
|
|
70
|
+
const content = await readFile(CONFIG_FILE_PATH, "utf-8");
|
|
71
|
+
return JSON.parse(content);
|
|
72
|
+
} catch {
|
|
73
|
+
return defaultConfig;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
8
77
|
async function main() {
|
|
9
78
|
try {
|
|
10
79
|
const input: HookInput = await Bun.stdin.json();
|
|
11
80
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
81
|
+
// Save last payload for debugging
|
|
82
|
+
await writeFile(LAST_PAYLOAD_PATH, JSON.stringify(input, null, 2));
|
|
83
|
+
|
|
84
|
+
const config = await loadConfig();
|
|
85
|
+
|
|
86
|
+
// Get usage limits (if feature exists)
|
|
87
|
+
const usageLimits = getUsageLimits
|
|
88
|
+
? await getUsageLimits()
|
|
89
|
+
: { five_hour: null, seven_day: null };
|
|
90
|
+
const currentResetsAt = usageLimits.five_hour?.resets_at ?? undefined;
|
|
91
|
+
|
|
92
|
+
// Save session with current period context (if feature exists)
|
|
93
|
+
if (saveSessionV2) {
|
|
94
|
+
await saveSessionV2(input, currentResetsAt);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const git = await getGitStatus();
|
|
98
|
+
|
|
99
|
+
let contextTokens: number | null;
|
|
100
|
+
let contextPercentage: number | null;
|
|
101
|
+
|
|
102
|
+
const usePayloadContext =
|
|
103
|
+
config.context.usePayloadContextWindow && input.context_window;
|
|
104
|
+
|
|
105
|
+
if (usePayloadContext) {
|
|
106
|
+
const current = input.context_window?.current_usage;
|
|
107
|
+
if (current) {
|
|
108
|
+
contextTokens =
|
|
109
|
+
(current.input_tokens || 0) +
|
|
110
|
+
(current.cache_creation_input_tokens || 0) +
|
|
111
|
+
(current.cache_read_input_tokens || 0);
|
|
112
|
+
const maxTokens =
|
|
113
|
+
input.context_window?.context_window_size ||
|
|
114
|
+
config.context.maxContextTokens;
|
|
115
|
+
contextPercentage = Math.min(
|
|
116
|
+
100,
|
|
117
|
+
Math.round((contextTokens / maxTokens) * 100),
|
|
118
|
+
);
|
|
119
|
+
} else {
|
|
120
|
+
// No context data yet - session not started
|
|
121
|
+
contextTokens = null;
|
|
122
|
+
contextPercentage = null;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
const contextData = await getContextData({
|
|
126
|
+
transcriptPath: input.transcript_path,
|
|
127
|
+
maxContextTokens: config.context.maxContextTokens,
|
|
128
|
+
autocompactBufferTokens: config.context.autocompactBufferTokens,
|
|
129
|
+
useUsableContextOnly: config.context.useUsableContextOnly,
|
|
130
|
+
overheadTokens: config.context.overheadTokens,
|
|
131
|
+
});
|
|
132
|
+
contextTokens = contextData.tokens;
|
|
133
|
+
contextPercentage = contextData.percentage;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Get period cost from SQLite (if feature exists)
|
|
137
|
+
let periodCost: number | undefined;
|
|
138
|
+
let todayCost: number | undefined;
|
|
139
|
+
|
|
140
|
+
if (getPeriodCost && getTodayCostV2 && normalizeResetsAt) {
|
|
141
|
+
const normalizedPeriodId = currentResetsAt
|
|
142
|
+
? normalizeResetsAt(currentResetsAt)
|
|
143
|
+
: null;
|
|
144
|
+
periodCost = normalizedPeriodId ? getPeriodCost(normalizedPeriodId) : 0;
|
|
145
|
+
todayCost = getTodayCostV2();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const data: StatuslineData = {
|
|
149
|
+
branch: formatBranch(git, config.git),
|
|
150
|
+
dirPath: formatPath(input.workspace.current_dir, config.pathDisplayMode),
|
|
151
|
+
modelName: input.model.display_name,
|
|
152
|
+
sessionCost: formatCost(
|
|
153
|
+
input.cost.total_cost_usd,
|
|
154
|
+
config.session.cost.format,
|
|
155
|
+
),
|
|
156
|
+
sessionDuration: formatDuration(input.cost.total_duration_ms),
|
|
157
|
+
contextTokens,
|
|
158
|
+
contextPercentage,
|
|
159
|
+
...(getUsageLimits && {
|
|
160
|
+
usageLimits: {
|
|
161
|
+
five_hour: usageLimits.five_hour
|
|
162
|
+
? {
|
|
163
|
+
utilization: usageLimits.five_hour.utilization,
|
|
164
|
+
resets_at: usageLimits.five_hour.resets_at,
|
|
165
|
+
}
|
|
166
|
+
: null,
|
|
167
|
+
seven_day: usageLimits.seven_day
|
|
168
|
+
? {
|
|
169
|
+
utilization: usageLimits.seven_day.utilization,
|
|
170
|
+
resets_at: usageLimits.seven_day.resets_at,
|
|
171
|
+
}
|
|
172
|
+
: null,
|
|
173
|
+
},
|
|
174
|
+
}),
|
|
175
|
+
...((getPeriodCost || getTodayCostV2) && { periodCost, todayCost }),
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const output = renderStatusline(data, config);
|
|
179
|
+
console.log(output);
|
|
180
|
+
if (config.oneLine) {
|
|
181
|
+
console.log("");
|
|
182
|
+
}
|
|
31
183
|
} catch (error) {
|
|
32
184
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
33
|
-
console.log(
|
|
34
|
-
|
|
35
|
-
);
|
|
185
|
+
console.log(`${colors.red("Error:")} ${errorMessage}`);
|
|
186
|
+
console.log(colors.gray("Check statusline configuration"));
|
|
36
187
|
}
|
|
37
188
|
}
|
|
38
189
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export type Separator =
|
|
2
|
+
| "|"
|
|
3
|
+
| "•"
|
|
4
|
+
| "·"
|
|
5
|
+
| "⋅"
|
|
6
|
+
| "●"
|
|
7
|
+
| "◆"
|
|
8
|
+
| "▪"
|
|
9
|
+
| "▸"
|
|
10
|
+
| "›"
|
|
11
|
+
| "→";
|
|
12
|
+
|
|
13
|
+
export type CostFormat = "integer" | "decimal1" | "decimal2";
|
|
14
|
+
export type ProgressBarStyle = "filled" | "rectangle" | "braille";
|
|
15
|
+
export type ProgressBarColor =
|
|
16
|
+
| "progressive"
|
|
17
|
+
| "green"
|
|
18
|
+
| "yellow"
|
|
19
|
+
| "red"
|
|
20
|
+
| "peach"
|
|
21
|
+
| "black"
|
|
22
|
+
| "white";
|
|
23
|
+
export type ProgressBarBackground =
|
|
24
|
+
| "none"
|
|
25
|
+
| "dark"
|
|
26
|
+
| "gray"
|
|
27
|
+
| "light"
|
|
28
|
+
| "blue"
|
|
29
|
+
| "purple"
|
|
30
|
+
| "cyan"
|
|
31
|
+
| "peach";
|
|
32
|
+
|
|
33
|
+
export interface CostConfig {
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
format: CostFormat;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ProgressBarConfig {
|
|
39
|
+
enabled: boolean;
|
|
40
|
+
length: 5 | 10 | 15;
|
|
41
|
+
style: ProgressBarStyle;
|
|
42
|
+
color: ProgressBarColor;
|
|
43
|
+
background: ProgressBarBackground;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface PercentageConfig {
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
showValue: boolean;
|
|
49
|
+
progressBar: ProgressBarConfig;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface StatuslineConfig {
|
|
53
|
+
features?: {
|
|
54
|
+
usageLimits?: boolean;
|
|
55
|
+
spendTracking?: boolean;
|
|
56
|
+
};
|
|
57
|
+
oneLine: boolean;
|
|
58
|
+
showSonnetModel: boolean;
|
|
59
|
+
pathDisplayMode: "full" | "truncated" | "basename";
|
|
60
|
+
git: {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
showBranch: boolean;
|
|
63
|
+
showDirtyIndicator: boolean;
|
|
64
|
+
showChanges: boolean;
|
|
65
|
+
showStaged: boolean;
|
|
66
|
+
showUnstaged: boolean;
|
|
67
|
+
};
|
|
68
|
+
separator: Separator;
|
|
69
|
+
session: {
|
|
70
|
+
infoSeparator: Separator | null;
|
|
71
|
+
cost: CostConfig;
|
|
72
|
+
duration: { enabled: boolean };
|
|
73
|
+
tokens: {
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
showMax: boolean;
|
|
76
|
+
showDecimals: boolean;
|
|
77
|
+
};
|
|
78
|
+
percentage: PercentageConfig;
|
|
79
|
+
};
|
|
80
|
+
context: {
|
|
81
|
+
usePayloadContextWindow: boolean;
|
|
82
|
+
maxContextTokens: number;
|
|
83
|
+
autocompactBufferTokens: number;
|
|
84
|
+
useUsableContextOnly: boolean;
|
|
85
|
+
overheadTokens: number;
|
|
86
|
+
};
|
|
87
|
+
limits: {
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
showTimeLeft: boolean;
|
|
90
|
+
showPacingDelta: boolean;
|
|
91
|
+
cost: CostConfig;
|
|
92
|
+
percentage: PercentageConfig;
|
|
93
|
+
};
|
|
94
|
+
weeklyUsage: {
|
|
95
|
+
enabled: boolean | "90%";
|
|
96
|
+
showTimeLeft: boolean;
|
|
97
|
+
showPacingDelta: boolean;
|
|
98
|
+
cost: CostConfig;
|
|
99
|
+
percentage: PercentageConfig;
|
|
100
|
+
};
|
|
101
|
+
dailySpend: {
|
|
102
|
+
cost: CostConfig;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { StatuslineConfig } from "./config-types";
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = join(import.meta.dir, "..", "..");
|
|
6
|
+
const DEFAULTS_PATH = join(CONFIG_DIR, "defaults.json");
|
|
7
|
+
const CONFIG_PATH = join(CONFIG_DIR, "statusline.config.json");
|
|
8
|
+
|
|
9
|
+
export const defaultConfig: StatuslineConfig = JSON.parse(
|
|
10
|
+
readFileSync(DEFAULTS_PATH, "utf-8"),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export function loadConfig(): StatuslineConfig {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
16
|
+
} catch {
|
|
17
|
+
return JSON.parse(JSON.stringify(defaultConfig));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type { StatuslineConfig } from "./config-types";
|