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.
Files changed (69) hide show
  1. package/claude-code-config/scripts/.claude/commands/fix-on-my-computer.md +87 -0
  2. package/claude-code-config/scripts/CLAUDE.md +50 -0
  3. package/claude-code-config/scripts/{statusline/biome.json → biome.json} +5 -2
  4. package/claude-code-config/scripts/bun.lockb +0 -0
  5. package/claude-code-config/scripts/command-validator/CLAUDE.md +112 -0
  6. package/claude-code-config/scripts/command-validator/src/__tests__/validator.test.ts +62 -111
  7. package/claude-code-config/scripts/command-validator/src/cli.ts +5 -3
  8. package/claude-code-config/scripts/command-validator/src/lib/security-rules.ts +3 -4
  9. package/claude-code-config/scripts/command-validator/src/lib/types.ts +1 -0
  10. package/claude-code-config/scripts/command-validator/src/lib/validator.ts +47 -317
  11. package/claude-code-config/scripts/package.json +39 -0
  12. package/claude-code-config/scripts/statusline/CLAUDE.md +29 -7
  13. package/claude-code-config/scripts/statusline/README.md +89 -1
  14. package/claude-code-config/scripts/statusline/__tests__/context.test.ts +229 -0
  15. package/claude-code-config/scripts/statusline/__tests__/formatters.test.ts +108 -0
  16. package/claude-code-config/scripts/statusline/__tests__/statusline.test.ts +309 -0
  17. package/claude-code-config/scripts/statusline/data/.gitignore +8 -0
  18. package/claude-code-config/scripts/statusline/data/.gitkeep +0 -0
  19. package/claude-code-config/scripts/statusline/defaults.json +79 -0
  20. package/claude-code-config/scripts/statusline/docs/ARCHITECTURE.md +166 -0
  21. package/claude-code-config/scripts/statusline/fixtures/mock-transcript.jsonl +4 -0
  22. package/claude-code-config/scripts/statusline/fixtures/test-input.json +12 -2
  23. package/claude-code-config/scripts/statusline/src/index.ts +175 -24
  24. package/claude-code-config/scripts/statusline/src/lib/config-types.ts +104 -0
  25. package/claude-code-config/scripts/statusline/src/lib/config.ts +21 -0
  26. package/claude-code-config/scripts/statusline/src/lib/context.ts +32 -11
  27. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +360 -22
  28. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  29. package/claude-code-config/scripts/statusline/src/lib/menu-factories.ts +224 -0
  30. package/claude-code-config/scripts/statusline/src/lib/presets.ts +177 -0
  31. package/claude-code-config/scripts/statusline/src/lib/render-pure.ts +497 -0
  32. package/claude-code-config/scripts/statusline/src/lib/types.ts +11 -0
  33. package/claude-code-config/scripts/statusline/src/lib/utils.ts +15 -0
  34. package/claude-code-config/scripts/statusline/src/tests/spend-v2.test.ts +306 -0
  35. package/claude-code-config/scripts/statusline/statusline.config.json +79 -0
  36. package/claude-code-config/scripts/statusline/test-with-fixtures.ts +37 -0
  37. package/claude-code-config/scripts/tsconfig.json +27 -0
  38. package/claude-code-config/skills/claude-memory/SKILL.md +689 -0
  39. package/claude-code-config/skills/claude-memory/references/comprehensive-example.md +175 -0
  40. package/claude-code-config/skills/claude-memory/references/project-patterns.md +334 -0
  41. package/claude-code-config/skills/claude-memory/references/prompting-techniques.md +411 -0
  42. package/claude-code-config/skills/claude-memory/references/section-templates.md +347 -0
  43. package/claude-code-config/skills/create-slash-commands/SKILL.md +1110 -0
  44. package/claude-code-config/skills/create-slash-commands/references/arguments.md +273 -0
  45. package/claude-code-config/skills/create-slash-commands/references/patterns.md +947 -0
  46. package/claude-code-config/skills/create-slash-commands/references/prompt-examples.md +656 -0
  47. package/claude-code-config/skills/create-slash-commands/references/tool-restrictions.md +389 -0
  48. package/claude-code-config/skills/create-subagents/SKILL.md +425 -0
  49. package/claude-code-config/skills/create-subagents/references/context-management.md +567 -0
  50. package/claude-code-config/skills/create-subagents/references/debugging-agents.md +714 -0
  51. package/claude-code-config/skills/create-subagents/references/error-handling-and-recovery.md +502 -0
  52. package/claude-code-config/skills/create-subagents/references/evaluation-and-testing.md +374 -0
  53. package/claude-code-config/skills/create-subagents/references/orchestration-patterns.md +591 -0
  54. package/claude-code-config/skills/create-subagents/references/subagents.md +599 -0
  55. package/claude-code-config/skills/create-subagents/references/writing-subagent-prompts.md +513 -0
  56. package/package.json +1 -1
  57. package/claude-code-config/commands/apex.md +0 -109
  58. package/claude-code-config/commands/tasks/run-task.md +0 -220
  59. package/claude-code-config/commands/utils/watch-ci.md +0 -47
  60. package/claude-code-config/scripts/command-validator/biome.json +0 -29
  61. package/claude-code-config/scripts/command-validator/bun.lockb +0 -0
  62. package/claude-code-config/scripts/command-validator/package.json +0 -27
  63. package/claude-code-config/scripts/command-validator/vitest.config.ts +0 -7
  64. package/claude-code-config/scripts/hook-post-file.ts +0 -162
  65. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  66. package/claude-code-config/scripts/statusline/package.json +0 -19
  67. package/claude-code-config/scripts/statusline/statusline.config.ts +0 -25
  68. package/claude-code-config/scripts/validate-command.js +0 -712
  69. 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/projects/-Users-melvynx--claude/06a7b019-03f8-4083-a9db-410d95cb01e6.jsonl",
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.31",
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 { defaultConfig } from "../statusline.config";
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 { colors, formatPath, formatSession } from "./lib/formatters";
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
- const dirPath = formatPath(
13
- input.workspace.current_dir,
14
- defaultConfig.pathDisplayMode,
15
- );
16
-
17
- const contextData = await getContextData({
18
- transcriptPath: input.transcript_path,
19
- maxContextTokens: defaultConfig.context.maxContextTokens,
20
- });
21
-
22
- const sessionInfo = formatSession(
23
- contextData.tokens,
24
- contextData.percentage,
25
- defaultConfig.session,
26
- );
27
-
28
- const sep = ` ${colors.GRAY}${defaultConfig.separator}${colors.LIGHT_GRAY} `;
29
- console.log(`${colors.LIGHT_GRAY}${dirPath}${sep}${sessionInfo}${colors.RESET}`);
30
- console.log("");
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
- `${colors.RED}Error:${colors.LIGHT_GRAY} ${errorMessage}${colors.RESET}`,
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";