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.
- package/README.md +251 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +852 -0
- package/dist/index.js.map +1 -0
- package/package.json +60 -0
- package/templates/.claude/scripts/command-validator/README.md +147 -0
- package/templates/.claude/scripts/command-validator/biome.json +29 -0
- package/templates/.claude/scripts/command-validator/bun.lockb +0 -0
- package/templates/.claude/scripts/command-validator/package.json +27 -0
- package/templates/.claude/scripts/command-validator/src/__tests__/validator.test.ts +148 -0
- package/templates/.claude/scripts/command-validator/src/cli.ts +118 -0
- package/templates/.claude/scripts/command-validator/src/lib/security-rules.ts +172 -0
- package/templates/.claude/scripts/command-validator/src/lib/types.ts +33 -0
- package/templates/.claude/scripts/command-validator/src/lib/validator.ts +360 -0
- package/templates/.claude/scripts/command-validator/vitest.config.ts +7 -0
- package/templates/.claude/scripts/hook-post-file.ts +162 -0
- package/templates/.claude/scripts/statusline/CLAUDE.md +178 -0
- package/templates/.claude/scripts/statusline/README.md +138 -0
- package/templates/.claude/scripts/statusline/biome.json +34 -0
- package/templates/.claude/scripts/statusline/bun.lockb +0 -0
- package/templates/.claude/scripts/statusline/fixtures/test-input.json +25 -0
- package/templates/.claude/scripts/statusline/package.json +19 -0
- package/templates/.claude/scripts/statusline/src/index.ts +111 -0
- package/templates/.claude/scripts/statusline/src/lib/context.ts +82 -0
- package/templates/.claude/scripts/statusline/src/lib/formatters.ts +48 -0
- package/templates/.claude/scripts/statusline/src/lib/git.ts +54 -0
- package/templates/.claude/scripts/statusline/src/lib/types.ts +25 -0
- package/templates/.claude/scripts/statusline/src/lib/usage-limits.ts +105 -0
- package/templates/.claude/scripts/statusline/statusline.config.ts +25 -0
- package/templates/.claude/scripts/statusline/test.ts +20 -0
- package/templates/.claude/scripts/statusline/tsconfig.json +27 -0
- package/templates/.claude/scripts/statusline-ccusage.sh +188 -0
- package/templates/.claude/scripts/statusline.readme.md +194 -0
- package/templates/.claude/scripts/validate-command.js +707 -0
- package/templates/.claude/scripts/validate-command.readme.md +283 -0
- package/templates/.claude/settings.json.template +60 -0
- package/templates/.claude/song/finish.mp3 +0 -0
- 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
|
+
}
|
|
Binary file
|
|
@@ -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();
|