vibe-academy-cli 1.0.0

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 (38) hide show
  1. package/README.md +251 -0
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.js +852 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +60 -0
  6. package/templates/.claude/scripts/command-validator/README.md +147 -0
  7. package/templates/.claude/scripts/command-validator/biome.json +29 -0
  8. package/templates/.claude/scripts/command-validator/bun.lockb +0 -0
  9. package/templates/.claude/scripts/command-validator/package.json +27 -0
  10. package/templates/.claude/scripts/command-validator/src/__tests__/validator.test.ts +148 -0
  11. package/templates/.claude/scripts/command-validator/src/cli.ts +118 -0
  12. package/templates/.claude/scripts/command-validator/src/lib/security-rules.ts +172 -0
  13. package/templates/.claude/scripts/command-validator/src/lib/types.ts +33 -0
  14. package/templates/.claude/scripts/command-validator/src/lib/validator.ts +360 -0
  15. package/templates/.claude/scripts/command-validator/vitest.config.ts +7 -0
  16. package/templates/.claude/scripts/hook-post-file.ts +162 -0
  17. package/templates/.claude/scripts/statusline/CLAUDE.md +178 -0
  18. package/templates/.claude/scripts/statusline/README.md +138 -0
  19. package/templates/.claude/scripts/statusline/biome.json +34 -0
  20. package/templates/.claude/scripts/statusline/bun.lockb +0 -0
  21. package/templates/.claude/scripts/statusline/fixtures/test-input.json +25 -0
  22. package/templates/.claude/scripts/statusline/package.json +19 -0
  23. package/templates/.claude/scripts/statusline/src/index.ts +111 -0
  24. package/templates/.claude/scripts/statusline/src/lib/context.ts +82 -0
  25. package/templates/.claude/scripts/statusline/src/lib/formatters.ts +48 -0
  26. package/templates/.claude/scripts/statusline/src/lib/git.ts +54 -0
  27. package/templates/.claude/scripts/statusline/src/lib/types.ts +25 -0
  28. package/templates/.claude/scripts/statusline/src/lib/usage-limits.ts +105 -0
  29. package/templates/.claude/scripts/statusline/statusline.config.ts +25 -0
  30. package/templates/.claude/scripts/statusline/test.ts +20 -0
  31. package/templates/.claude/scripts/statusline/tsconfig.json +27 -0
  32. package/templates/.claude/scripts/statusline-ccusage.sh +188 -0
  33. package/templates/.claude/scripts/statusline.readme.md +194 -0
  34. package/templates/.claude/scripts/validate-command.js +707 -0
  35. package/templates/.claude/scripts/validate-command.readme.md +283 -0
  36. package/templates/.claude/settings.json.template +60 -0
  37. package/templates/.claude/song/finish.mp3 +0 -0
  38. package/templates/.claude/song/need-human.mp3 +0 -0
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env bun
2
+ // @ts-nocheck
3
+
4
+ interface HookInput {
5
+ session_id: string;
6
+ transcript_path: string;
7
+ cwd: string;
8
+ hook_event_name: string;
9
+ tool_name: string;
10
+ tool_input: {
11
+ file_path: string;
12
+ content: string;
13
+ };
14
+ tool_response: {
15
+ filePath: string;
16
+ success: boolean;
17
+ };
18
+ }
19
+
20
+ interface HookOutput {
21
+ hookSpecificOutput: {
22
+ hookEventName: string;
23
+ additionalContext: string;
24
+ };
25
+ }
26
+
27
+ // Check for debug mode
28
+ const DEBUG = process.argv.includes("--debug");
29
+
30
+ function log(message: string, ...args: unknown[]) {
31
+ if (DEBUG) {
32
+ console.log(message, ...args);
33
+ }
34
+ }
35
+
36
+ async function runCommand(
37
+ command: string[],
38
+ ): Promise<{ stdout: string; stderr: string; success: boolean }> {
39
+ try {
40
+ const proc = Bun.spawn(command, {
41
+ stdout: "pipe",
42
+ stderr: "pipe",
43
+ });
44
+
45
+ const stdout = await new Response(proc.stdout).text();
46
+ const stderr = await new Response(proc.stderr).text();
47
+ const success = (await proc.exited) === 0;
48
+
49
+ return { stdout, stderr, success };
50
+ } catch (error) {
51
+ return { stdout: "", stderr: String(error), success: false };
52
+ }
53
+ }
54
+
55
+ async function main() {
56
+ log("Hook started for file processing");
57
+
58
+ // Lire l'input JSON depuis stdin
59
+ const input = await Bun.stdin.text();
60
+ log("Input received, length:", input.length);
61
+
62
+ let hookData: HookInput;
63
+ try {
64
+ hookData = JSON.parse(input);
65
+ } catch (error) {
66
+ log("Error parsing JSON input:", error);
67
+ process.exit(0);
68
+ }
69
+
70
+ const filePath = hookData.tool_input?.file_path;
71
+ if (!filePath) {
72
+ log("Unable to extract file path from input");
73
+ process.exit(0);
74
+ }
75
+
76
+ // Vérifier que c'est un fichier .ts ou .tsx uniquement
77
+ if (!filePath.endsWith(".ts") && !filePath.endsWith(".tsx")) {
78
+ log(`Skipping ${filePath}: not a TypeScript file`);
79
+ process.exit(0);
80
+ }
81
+
82
+ log("Processing file:", filePath);
83
+
84
+ // Vérifier que le fichier existe
85
+ const file = Bun.file(filePath);
86
+ if (!(await file.exists())) {
87
+ log("File not found:", filePath);
88
+ process.exit(1);
89
+ }
90
+
91
+ // 1. Exécuter Prettier
92
+ log("Running Prettier formatting");
93
+ const prettierResult = await runCommand([
94
+ "bun",
95
+ "x",
96
+ "prettier",
97
+ "--write",
98
+ filePath,
99
+ ]);
100
+ if (!prettierResult.success) {
101
+ log("Prettier failed:", prettierResult.stderr);
102
+ }
103
+
104
+ // 2. ESLint --fix
105
+ log("Running ESLint --fix");
106
+ await runCommand(["bun", "x", "eslint", "--fix", filePath]);
107
+
108
+ // 3. Run ESLint check and TypeScript check in parallel
109
+ log("Running ESLint and TypeScript checks in parallel");
110
+ const [eslintCheckResult, tscResult] = await Promise.all([
111
+ runCommand(["bun", "x", "eslint", filePath]),
112
+ runCommand(["bun", "x", "tsc", "--noEmit", "--pretty", "false"]),
113
+ ]);
114
+
115
+ const eslintErrors = (
116
+ eslintCheckResult.stdout + eslintCheckResult.stderr
117
+ ).trim();
118
+
119
+ const tsErrors = tscResult.stderr
120
+ .split("\n")
121
+ .filter((line) => line.includes(filePath))
122
+ .join("\n");
123
+
124
+ // Construire le message d'erreurs
125
+ let errorMessage = "";
126
+
127
+ if (tsErrors || eslintErrors) {
128
+ errorMessage = `Fix NOW the following errors AND warning detected in ${filePath
129
+ .split("/")
130
+ .pop()}:\\n`;
131
+
132
+ if (tsErrors) {
133
+ errorMessage += `\\n TypeScript errors:\\n${tsErrors}\\n`;
134
+ }
135
+
136
+ if (eslintErrors) {
137
+ errorMessage += `\\n ESLint errors:\\n${eslintErrors}\\n`;
138
+ }
139
+ }
140
+
141
+ log("Error message", errorMessage);
142
+
143
+ // Sortir le résultat
144
+ if (errorMessage) {
145
+ const output: HookOutput = {
146
+ hookSpecificOutput: {
147
+ hookEventName: "PostToolUse",
148
+ additionalContext: errorMessage,
149
+ },
150
+ };
151
+
152
+ log("Output", JSON.stringify(output, null, 2));
153
+ console.log(JSON.stringify(output, null, 2));
154
+ } else {
155
+ console.error(`No errors detected in ${filePath.split("/").pop()}`);
156
+ }
157
+ }
158
+
159
+ main().catch((error) => {
160
+ log("Error in hook:", error);
161
+ process.exit(1);
162
+ });
@@ -0,0 +1,178 @@
1
+ # Claude Code Statusline - Project Memory
2
+
3
+ ## Overview
4
+
5
+ Clean, type-safe statusline implementation for Claude Code using Bun + TypeScript. Displays real-time session information, git status, context usage, and Claude API rate limits.
6
+
7
+ ## Project Setup & Configuration
8
+
9
+ ### Dependencies
10
+ - **Bun**: Runtime (uses `$` for shell commands)
11
+ - **@biomejs/biome**: Linting & formatting
12
+ - **TypeScript**: Type safety
13
+
14
+ No external npm packages required - pure Bun APIs.
15
+
16
+ ### Configuration in Claude Code
17
+
18
+ Add to `~/.claude/settings.json`:
19
+
20
+ ```json
21
+ {
22
+ "statusLine": {
23
+ "type": "command",
24
+ "command": "bun /Users/melvynx/.claude/scripts/statusline/src/index.ts",
25
+ "padding": 0
26
+ }
27
+ }
28
+ ```
29
+
30
+ ### Authentication
31
+
32
+ OAuth token stored in macOS Keychain:
33
+ - **Service**: `Claude Code-credentials`
34
+ - **Format**: JSON with `claudeAiOauth.accessToken`
35
+ - **Token type**: `sk-ant-oat01-...` (OAuth token, not API key)
36
+ - **Access**: `security find-generic-password -s "Claude Code-credentials" -w`
37
+
38
+ ## Architecture
39
+
40
+ ### Modular Design
41
+
42
+ The project follows a clean architecture with separated concerns:
43
+
44
+ ```
45
+ src/
46
+ ├── index.ts # Main entry - orchestrates all components
47
+ └── lib/
48
+ ├── types.ts # TypeScript interfaces (HookInput)
49
+ ├── git.ts # Git operations (branch, changes)
50
+ ├── context.ts # Transcript parsing & context calculation
51
+ ├── usage-limits.ts # Claude OAuth API integration
52
+ └── formatters.ts # Display utilities & colors
53
+ ```
54
+
55
+ ### Data Flow
56
+
57
+ ```
58
+ Claude Code Hook → stdin JSON → index.ts
59
+
60
+ ┌───────────────┴───────────────┐
61
+ ↓ ↓
62
+ [Get Git Status] [Get Context Data]
63
+ ↓ ↓
64
+ [Format Branch] [Get Usage Limits]
65
+ ↓ ↓
66
+ └───────────────┬───────────────┘
67
+
68
+ [Build Output Lines]
69
+
70
+ stdout (2 lines)
71
+ ```
72
+
73
+ ## Component Specifications
74
+
75
+ ### Context Calculation (`lib/context.ts`)
76
+ - **Purpose**: Calculate token usage from Claude Code transcript files
77
+ - **Algorithm**: Parses `.jsonl` transcript, finds most recent main-chain entry
78
+ - **Tokens counted**: `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`
79
+ - **Excludes**: Sidechain entries (agent calls), API error messages
80
+ - **Output**: `{ tokens: number, percentage: number }` (0-100% of 200k context)
81
+
82
+ ### Usage Limits (`lib/usage-limits.ts`)
83
+ - **Purpose**: Fetch Claude API rate limits from OAuth endpoint
84
+ - **Auth**: Retrieves OAuth token from macOS Keychain (`Claude Code-credentials`)
85
+ - **API**: `https://api.anthropic.com/api/oauth/usage`
86
+ - **Data**: Five-hour window utilization + reset time
87
+ - **Error handling**: Fails silently, returns null on errors
88
+
89
+ ### Git Status (`lib/git.ts`)
90
+ - **Purpose**: Show current branch and uncommitted changes
91
+ - **Detection**: Checks both staged and unstaged changes
92
+ - **Output**: Branch name + line additions/deletions
93
+ - **Display**: `main* (+123 -45)` with color coding
94
+
95
+ ### Formatters (`lib/formatters.ts`)
96
+ - **Colors**: ANSI color codes for terminal output
97
+ - **Token display**: `62.5K`, `1.2M` format
98
+ - **Time formatting**: `3h21m`, `45m` for countdowns
99
+ - **Reset time**: Calculates difference between API reset time and now
100
+
101
+ ## Output Specification
102
+
103
+ ### Line 1: Session Info
104
+ ```
105
+ main* (+123 -45) | ~/.claude | Sonnet 4.5
106
+ ```
107
+
108
+ ### Line 2: Metrics
109
+ ```
110
+ $0.17 (6m) | 62.5K tokens | 31% | 15% (3h27m)
111
+ ```
112
+
113
+ **Components:**
114
+ - `$0.17` - Session cost (USD)
115
+ - `(6m)` - Session duration
116
+ - `62.5K tokens` - Context tokens used (from transcript)
117
+ - `31%` - Context percentage (tokens / 200k)
118
+ - `15%` - Five-hour usage (from Claude API)
119
+ - `(3h27m)` - Time until rate limit resets
120
+
121
+ ## Development
122
+
123
+ ### Testing
124
+
125
+ ```bash
126
+ # Run test with fixture
127
+ bun run test
128
+
129
+ # Use custom fixture
130
+ bun run test fixtures/custom.json
131
+
132
+ # Manual test
133
+ echo '{ ... }' | bun run start
134
+ ```
135
+
136
+ ### Code Conventions
137
+
138
+ - **ALWAYS** use camelCase for variables and functions
139
+ - Use TypeScript strict mode
140
+ - Follow Biome formatting rules
141
+
142
+ ### Error Handling & Performance
143
+
144
+ **Error Handling** - All components fail silently:
145
+ - Missing transcript → 0 tokens, 0%
146
+ - API failure → No usage limits shown
147
+ - Git errors → "no-git" branch
148
+ - Keychain access denied → No usage limits
149
+
150
+ This ensures statusline never crashes Claude Code.
151
+
152
+ **Performance Benchmarks:**
153
+ - Context calculation: ~10-50ms (depends on transcript size)
154
+ - API call: ~100-300ms (cached by Claude API)
155
+ - Git operations: ~20-50ms
156
+ - Total: < 500ms typical
157
+
158
+ ## Maintenance Guide
159
+
160
+ ### Adding New Metrics
161
+
162
+ 1. Add interface to `lib/types.ts`
163
+ 2. Create fetcher in `lib/*.ts`
164
+ 3. Import in `index.ts`
165
+ 4. Add to `buildSecondLine()`
166
+
167
+ ### Modifying Display
168
+
169
+ - Colors: Edit `lib/formatters.ts` colors constant
170
+ - Layout: Modify `buildFirstLine()` / `buildSecondLine()`
171
+ - Formatting: Add functions to `lib/formatters.ts`
172
+
173
+ ## Known Limitations
174
+
175
+ - macOS only (uses Keychain)
176
+ - Requires `git` CLI for git status
177
+ - Requires Claude Code OAuth (not API key)
178
+ - Transcript must be accessible (permissions)
@@ -0,0 +1,138 @@
1
+ # Claude Code Status Line
2
+
3
+ nouvelle version de la status line qui combine les features de l'ancienne et de la nouvelle version avec synchronisation Claude Code.
4
+
5
+ ## features
6
+
7
+ ### ligne 1 : informations contextuelles
8
+ - 🌿 **branche git** avec changements (vert pour ajouts, rouge pour suppressions)
9
+ - 💄 **output style** (le style de réponse claude configuré)
10
+ - 📁 **dossier actuel** (chemin avec ~ pour home)
11
+ - 🤖 **modèle utilisé** (sonnet 4.5, opus, etc.)
12
+
13
+ ### ligne 2 : métriques et limites
14
+ - 📅 **coût journalier** (via ccusage)
15
+ - 🧩 **tokens utilisés** avec pourcentage du contexte (200k max)
16
+ - ⏱️ **temps restant** avant reset de session (3pm heure locale)
17
+
18
+ ## prérequis **obligatoires**
19
+
20
+ ### ccusage (impératif)
21
+
22
+ **installation** :
23
+ ```bash
24
+ npm install -g @erinreily/ccusage
25
+ ```
26
+
27
+ **pourquoi ccusage est obligatoire** :
28
+ - calcul du coût journalier
29
+ - tracking du temps restant avant reset
30
+ - sans ccusage, la status line affichera $0.00 et pas de temps restant
31
+
32
+ **vérification** :
33
+ ```bash
34
+ ccusage --version # doit afficher 17.0.2 ou plus
35
+ ```
36
+
37
+ ### git (optionnel)
38
+
39
+ pour afficher la branche et les changements. si git n'est pas disponible, affichera "no-git".
40
+
41
+ ## activation
42
+
43
+ la status line v2 est déjà active si tu as suivi la configuration précédente.
44
+
45
+ pour vérifier dans `~/.claude/settings.json` :
46
+
47
+ ```json
48
+ {
49
+ "statusLine": {
50
+ "type": "command",
51
+ "command": "bun /Users/matthieucousin/.claude/scripts/statusline/src/index.ts",
52
+ "padding": 0
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## format de sortie
58
+
59
+ ```
60
+ 🌿 main • 💄 senior-dev • 📁 ~/Documents/project • 🤖 Claude Sonnet 4.5
61
+ 📅 $12.76 • 🧩 124.4K tokens (62%) • ⏱️ (2h22m left)
62
+ ```
63
+
64
+ ## sources de données
65
+
66
+ - **git** : commandes git locales (branche + changements)
67
+ - **output style** : `input.output_style.name` depuis Claude Code
68
+ - **modèle** : `input.model.display_name` depuis Claude Code
69
+ - **dossier** : `input.workspace.current_dir` depuis Claude Code
70
+ - **tokens** : transcript Claude Code (input + cache + cache_read, précis)
71
+ - **coût jour** : ccusage daily (nécessite ccusage)
72
+ - **temps restant** : calculé jusqu'à 3pm heure locale (reset de session Claude Code)
73
+
74
+ ## synchronisation avec claude code
75
+
76
+ le temps restant affiché dans la status bar est **synchronisé avec l'interface Claude Code** :
77
+ - claude code : "resets 3pm (America/Guadeloupe)"
78
+ - status bar : calcule le temps jusqu'à 3pm heure locale
79
+ - toujours cohérent avec `/usage` dans claude code
80
+
81
+ ## différences avec l'ancienne version
82
+
83
+ **l'ancienne version bash** (`statusline-ccusage.sh`, supprimée) avait :
84
+ - ✅ branche git (conservé)
85
+ - ✅ output style (conservé)
86
+ - ✅ modèle (conservé)
87
+ - ✅ coût journalier (conservé)
88
+ - ❌ bloc actif ccusage (enlevé, utilisait projection incorrecte)
89
+ - ❌ coût de session (enlevé, redondant avec journalier)
90
+
91
+ **la version v2 ajoute** :
92
+ - synchronisation précise avec claude code (3pm reset)
93
+ - pourcentage du contexte utilisé
94
+ - tokens depuis transcript (plus précis que ccusage)
95
+ - architecture modulaire typescript (git.ts, usage-limits.ts, etc.)
96
+
97
+ ## architecture
98
+
99
+ ```
100
+ src/
101
+ ├── index.ts # entry point principal
102
+ └── lib/
103
+ ├── types.ts # interfaces typescript
104
+ ├── context.ts # calcul tokens depuis transcript
105
+ ├── git.ts # statut git
106
+ ├── usage-limits.ts # temps restant + coût bloc
107
+ └── formatters.ts # formatage affichage
108
+ ```
109
+
110
+ ## troubleshooting
111
+
112
+ ### temps restant incorrect
113
+
114
+ si le temps affiché ne correspond pas à `/usage` dans claude code :
115
+ 1. vérifie ta timezone système : `date`
116
+ 2. vérifie que claude code affiche "resets 3pm"
117
+ 3. redémarre claude code
118
+
119
+ ### coût à $0.00
120
+
121
+ si le coût journalier est toujours $0.00 :
122
+ 1. vérifie ccusage : `ccusage --version`
123
+ 2. vérifie les données : `ccusage daily --json`
124
+ 3. réinstalle si besoin : `npm install -g @erinreily/ccusage`
125
+
126
+ ### pas de temps restant affiché
127
+
128
+ si ⏱️ n'apparaît pas :
129
+ 1. ccusage n'est pas installé ou ne fonctionne pas
130
+ 2. pas de bloc actif : `ccusage blocks --active --json`
131
+
132
+ ## notes techniques
133
+
134
+ - écrit en typescript avec bun runtime
135
+ - réutilise les types et helpers de la nouvelle status line
136
+ - gère les erreurs gracieusement (fallback sur valeurs par défaut)
137
+ - compatible avec toutes les timezones (utilise timezone système)
138
+ - ne dépend **pas** de l'API OAuth anthropic (qui ne fonctionne pas avec credentials claude code)
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.2/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "tab"
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "recommended": true
19
+ }
20
+ },
21
+ "javascript": {
22
+ "formatter": {
23
+ "quoteStyle": "double"
24
+ }
25
+ },
26
+ "assist": {
27
+ "enabled": true,
28
+ "actions": {
29
+ "source": {
30
+ "organizeImports": "on"
31
+ }
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "session_id": "06a7b019-03f8-4083-a9db-410d95cb01e6",
3
+ "transcript_path": "/Users/melvynx/.claude/projects/-Users-melvynx--claude/06a7b019-03f8-4083-a9db-410d95cb01e6.jsonl",
4
+ "cwd": "/Users/melvynx/.claude",
5
+ "model": {
6
+ "id": "claude-sonnet-4-5-20250929",
7
+ "display_name": "Sonnet 4.5"
8
+ },
9
+ "workspace": {
10
+ "current_dir": "/Users/melvynx/.claude",
11
+ "project_dir": "/Users/melvynx/.claude"
12
+ },
13
+ "version": "2.0.31",
14
+ "output_style": {
15
+ "name": "default"
16
+ },
17
+ "cost": {
18
+ "total_cost_usd": 0.17468000000000003,
19
+ "total_duration_ms": 385160,
20
+ "total_api_duration_ms": 252694,
21
+ "total_lines_added": 185,
22
+ "total_lines_removed": 75
23
+ },
24
+ "exceeds_200k_tokens": false
25
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "statusline",
3
+ "version": "2.0.0",
4
+ "module": "src/index.ts",
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "bun run src/index.ts",
8
+ "test": "bun run test.ts",
9
+ "lint": "biome check --write .",
10
+ "format": "biome format --write ."
11
+ },
12
+ "devDependencies": {
13
+ "@biomejs/biome": "^2.3.2",
14
+ "@types/bun": "latest"
15
+ },
16
+ "peerDependencies": {
17
+ "typescript": "^5.0.0"
18
+ }
19
+ }
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { defaultConfig } from "../statusline.config";
4
+ import { getContextData } from "./lib/context";
5
+ import { colors, formatPath } from "./lib/formatters";
6
+ import { getGitInfo } from "./lib/git";
7
+ import type { HookInput } from "./lib/types";
8
+ import { getUsageLimits } from "./lib/usage-limits";
9
+
10
+ // Helper functions for formatting
11
+ function formatCost(cost: number): string {
12
+ return cost.toFixed(2);
13
+ }
14
+
15
+ function formatTokens(tokens: number): string {
16
+ if (tokens >= 1000000) {
17
+ const value = (tokens / 1000000).toFixed(1);
18
+ return `${value}M`;
19
+ }
20
+ if (tokens >= 1000) {
21
+ const value = (tokens / 1000).toFixed(1);
22
+ return `${value}K`;
23
+ }
24
+ return tokens.toString();
25
+ }
26
+
27
+ async function getDailyCost(): Promise<number> {
28
+ try {
29
+ const today = new Date().toISOString().split("T")[0].replace(/-/g, "");
30
+ const dailyProc = Bun.spawn([
31
+ "ccusage",
32
+ "daily",
33
+ "--json",
34
+ "--since",
35
+ today,
36
+ ]);
37
+ const dailyText = await new Response(dailyProc.stdout).text();
38
+ const dailyData = JSON.parse(dailyText);
39
+
40
+ if (dailyData && dailyData.totals && dailyData.totals.totalCost) {
41
+ return dailyData.totals.totalCost;
42
+ }
43
+ return 0;
44
+ } catch {
45
+ return 0;
46
+ }
47
+ }
48
+
49
+ async function main() {
50
+ try {
51
+ const input: HookInput = await Bun.stdin.json();
52
+
53
+ // Get basic info from Claude Code
54
+ const dirPath = formatPath(
55
+ input.workspace.current_dir,
56
+ defaultConfig.pathDisplayMode,
57
+ );
58
+ const modelName = input.model.display_name || "unknown";
59
+ const outputStyle = input.output_style.name || "unknown";
60
+
61
+ // Get context data (tokens from transcript - most accurate)
62
+ const contextData = await getContextData({
63
+ transcriptPath: input.transcript_path,
64
+ maxContextTokens: defaultConfig.context.maxContextTokens,
65
+ });
66
+
67
+ // Get git info
68
+ const gitBranch = await getGitInfo();
69
+
70
+ // Get daily cost from ccusage (optional)
71
+ const dailyCost = await getDailyCost();
72
+
73
+ // Get usage limits (five-hour window)
74
+ const usageLimits = await getUsageLimits();
75
+
76
+ // Format tokens
77
+ const formattedTokens = formatTokens(contextData.tokens);
78
+
79
+ // Build output
80
+ const sep = ` ${colors.GRAY}${defaultConfig.separator}${colors.LIGHT_GRAY} `;
81
+
82
+ // First line: branch / style / folder / model
83
+ const firstLine = `${colors.LIGHT_GRAY}🌿 ${gitBranch}${sep}💄 ${outputStyle}${sep}📁 ${dirPath}${sep}🤖 ${modelName}${colors.RESET}`;
84
+
85
+ // Second line: daily cost / tokens (percentage) / usage limits
86
+ let secondLineParts = [
87
+ `📅 $${formatCost(dailyCost)}`,
88
+ `🧩 ${formattedTokens} ${colors.GRAY}tokens (${contextData.percentage}%)${colors.LIGHT_GRAY}`,
89
+ ];
90
+
91
+ // Add usage limits if available (from Anthropic API)
92
+ if (usageLimits) {
93
+ secondLineParts.push(
94
+ `⏱️ ${colors.GRAY}(${usageLimits.utilization}%)${colors.LIGHT_GRAY}`,
95
+ `⏳ ${colors.GRAY}(${usageLimits.remainingTime} left)${colors.LIGHT_GRAY}`,
96
+ );
97
+ }
98
+
99
+ const secondLine = `${colors.LIGHT_GRAY}${secondLineParts.join(sep)}${colors.RESET}`;
100
+
101
+ console.log(firstLine);
102
+ console.log(secondLine);
103
+ console.log("");
104
+ } catch (error) {
105
+ const errorMessage = error instanceof Error ? error.message : String(error);
106
+ console.log(`${colors.LIGHT_GRAY}Error: ${errorMessage}${colors.RESET}`);
107
+ console.log("");
108
+ }
109
+ }
110
+
111
+ main();