aiblueprint-cli 1.1.7 → 1.2.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 (48) hide show
  1. package/README.md +82 -11
  2. package/claude-code-config/agents/action.md +36 -0
  3. package/claude-code-config/agents/explore-codebase.md +6 -1
  4. package/claude-code-config/agents/explore-docs.md +1 -1
  5. package/claude-code-config/agents/websearch.md +1 -1
  6. package/claude-code-config/commands/commit.md +1 -1
  7. package/claude-code-config/commands/oneshot.md +57 -0
  8. package/claude-code-config/hooks/hooks.json +15 -0
  9. package/claude-code-config/scripts/command-validator/README.md +147 -0
  10. package/claude-code-config/scripts/command-validator/biome.json +29 -0
  11. package/claude-code-config/scripts/command-validator/bun.lockb +0 -0
  12. package/claude-code-config/scripts/command-validator/dist/cli.js +544 -0
  13. package/claude-code-config/scripts/command-validator/package.json +27 -0
  14. package/claude-code-config/scripts/command-validator/src/__tests__/validator.test.ts +148 -0
  15. package/claude-code-config/scripts/command-validator/src/cli.ts +118 -0
  16. package/claude-code-config/scripts/command-validator/src/lib/security-rules.ts +172 -0
  17. package/claude-code-config/scripts/command-validator/src/lib/types.ts +33 -0
  18. package/claude-code-config/scripts/command-validator/src/lib/validator.ts +360 -0
  19. package/claude-code-config/scripts/command-validator/vitest.config.ts +7 -0
  20. package/claude-code-config/scripts/statusline/CLAUDE.md +178 -0
  21. package/claude-code-config/scripts/statusline/README.md +105 -0
  22. package/claude-code-config/scripts/statusline/biome.json +34 -0
  23. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  24. package/claude-code-config/scripts/statusline/fixtures/test-input.json +25 -0
  25. package/claude-code-config/scripts/statusline/package.json +19 -0
  26. package/claude-code-config/scripts/statusline/src/index.ts +39 -0
  27. package/claude-code-config/scripts/statusline/src/lib/context.ts +82 -0
  28. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +48 -0
  29. package/claude-code-config/scripts/statusline/src/lib/types.ts +25 -0
  30. package/claude-code-config/scripts/statusline/statusline.config.ts +25 -0
  31. package/claude-code-config/scripts/statusline/test.ts +20 -0
  32. package/claude-code-config/scripts/statusline/tsconfig.json +27 -0
  33. package/dist/cli.js +1086 -76
  34. package/package.json +1 -2
  35. package/claude-code-config/agents/snipper.md +0 -36
  36. package/claude-code-config/commands/claude-memory.md +0 -190
  37. package/claude-code-config/commands/cleanup-context.md +0 -82
  38. package/claude-code-config/commands/debug.md +0 -91
  39. package/claude-code-config/commands/deep-code-analysis.md +0 -87
  40. package/claude-code-config/commands/explain-architecture.md +0 -113
  41. package/claude-code-config/commands/prompt-agent.md +0 -126
  42. package/claude-code-config/commands/prompt-command.md +0 -225
  43. package/claude-code-config/output-styles/assistant.md +0 -15
  44. package/claude-code-config/output-styles/honnest.md +0 -9
  45. package/claude-code-config/output-styles/senior-dev.md +0 -14
  46. package/claude-code-config/scripts/statusline-ccusage.sh +0 -188
  47. package/claude-code-config/scripts/statusline.readme.md +0 -194
  48. /package/claude-code-config/{hooks → scripts}/hook-post-file.ts +0 -0
@@ -0,0 +1,360 @@
1
+ import { SAFE_COMMANDS, SECURITY_RULES } from "./security-rules";
2
+ import type { ValidationResult } from "./types";
3
+
4
+ export class CommandValidator {
5
+ validate(command: string, toolName = "Unknown"): ValidationResult {
6
+ const result: ValidationResult = {
7
+ isValid: true,
8
+ severity: "LOW",
9
+ violations: [],
10
+ sanitizedCommand: command,
11
+ };
12
+
13
+ if (!command || typeof command !== "string") {
14
+ result.isValid = false;
15
+ result.violations.push("Invalid command format");
16
+ return result;
17
+ }
18
+
19
+ if (command.length > 2000) {
20
+ result.isValid = false;
21
+ result.severity = "MEDIUM";
22
+ result.violations.push("Command too long (potential buffer overflow)");
23
+ return result;
24
+ }
25
+
26
+ if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]/.test(command)) {
27
+ result.isValid = false;
28
+ result.severity = "HIGH";
29
+ result.violations.push("Binary or encoded content detected");
30
+ return result;
31
+ }
32
+
33
+ const normalizedCmd = command.trim().toLowerCase();
34
+ const cmdParts = normalizedCmd.split(/\s+/);
35
+ const mainCommand = cmdParts[0].split("/").pop() || "";
36
+
37
+ if (mainCommand === "source" || mainCommand === "python") {
38
+ return result;
39
+ }
40
+
41
+ for (const pattern of SECURITY_RULES.DANGEROUS_PATTERNS) {
42
+ if (pattern.test(command)) {
43
+ result.isValid = false;
44
+ result.severity = "CRITICAL";
45
+ result.violations.push(`Dangerous pattern detected: ${pattern.source}`);
46
+ }
47
+ }
48
+
49
+ if (SECURITY_RULES.CRITICAL_COMMANDS.includes(mainCommand)) {
50
+ result.isValid = false;
51
+ result.severity = "CRITICAL";
52
+ result.violations.push(`Critical dangerous command: ${mainCommand}`);
53
+ }
54
+
55
+ if (SECURITY_RULES.PRIVILEGE_COMMANDS.includes(mainCommand)) {
56
+ result.isValid = false;
57
+ result.severity = "HIGH";
58
+ result.violations.push(`Privilege escalation command: ${mainCommand}`);
59
+ }
60
+
61
+ if (SECURITY_RULES.NETWORK_COMMANDS.includes(mainCommand)) {
62
+ result.isValid = false;
63
+ result.severity = "HIGH";
64
+ result.violations.push(`Network/remote access command: ${mainCommand}`);
65
+ }
66
+
67
+ if (SECURITY_RULES.SYSTEM_COMMANDS.includes(mainCommand)) {
68
+ result.isValid = false;
69
+ result.severity = "HIGH";
70
+ result.violations.push(`System manipulation command: ${mainCommand}`);
71
+ }
72
+
73
+ if (/rm\s+.*-rf\s/.test(command)) {
74
+ const isRmRfSafe = this.isRmRfCommandSafe(command);
75
+ if (!isRmRfSafe) {
76
+ result.isValid = false;
77
+ result.severity = "CRITICAL";
78
+ result.violations.push("rm -rf command targeting unsafe path");
79
+ }
80
+ }
81
+
82
+ if (SAFE_COMMANDS.includes(mainCommand) && result.violations.length === 0) {
83
+ return result;
84
+ }
85
+
86
+ if (command.includes("&&")) {
87
+ const chainedCommands = this.splitCommandChain(command);
88
+ let allSafe = true;
89
+ for (const chainedCmd of chainedCommands) {
90
+ const trimmedCmd = chainedCmd.trim();
91
+ const cmdParts = trimmedCmd.split(/\s+/);
92
+ const mainCommand = cmdParts[0];
93
+
94
+ if (
95
+ mainCommand === "source" ||
96
+ mainCommand === "python" ||
97
+ SAFE_COMMANDS.includes(mainCommand)
98
+ ) {
99
+ continue;
100
+ }
101
+
102
+ const chainResult = this.validateSingleCommand(trimmedCmd, toolName);
103
+ if (!chainResult.isValid) {
104
+ result.isValid = false;
105
+ result.severity = chainResult.severity;
106
+ result.violations.push(
107
+ `Chained command violation: ${trimmedCmd} - ${chainResult.violations.join(", ")}`,
108
+ );
109
+ allSafe = false;
110
+ }
111
+ }
112
+ if (allSafe) {
113
+ return result;
114
+ }
115
+ }
116
+
117
+ if (command.includes(";") || command.includes("||")) {
118
+ const chainedCommands = this.splitCommandChain(command);
119
+ for (const chainedCmd of chainedCommands) {
120
+ const chainResult = this.validateSingleCommand(
121
+ chainedCmd.trim(),
122
+ toolName,
123
+ );
124
+ if (!chainResult.isValid) {
125
+ result.isValid = false;
126
+ result.severity = chainResult.severity;
127
+ result.violations.push(
128
+ `Chained command violation: ${chainedCmd.trim()} - ${chainResult.violations.join(", ")}`,
129
+ );
130
+ }
131
+ }
132
+ return result;
133
+ }
134
+
135
+ for (const path of SECURITY_RULES.PROTECTED_PATHS) {
136
+ if (command.includes(path)) {
137
+ if (
138
+ path === "/dev/" &&
139
+ (command.includes("/dev/null") ||
140
+ command.includes("/dev/stderr") ||
141
+ command.includes("/dev/stdout"))
142
+ ) {
143
+ continue;
144
+ }
145
+
146
+ const cmdStart = command.trim();
147
+ let isSafeExecutable = false;
148
+ for (const safePath of SECURITY_RULES.SAFE_EXECUTABLE_PATHS) {
149
+ if (cmdStart.startsWith(safePath)) {
150
+ isSafeExecutable = true;
151
+ break;
152
+ }
153
+ }
154
+
155
+ const pathIndex = command.indexOf(path);
156
+ const beforePath = command.substring(0, pathIndex);
157
+ const redirectBeforePath = />\s*$/.test(beforePath.trim());
158
+
159
+ if (!isSafeExecutable && redirectBeforePath) {
160
+ result.isValid = false;
161
+ result.severity = "HIGH";
162
+ result.violations.push(
163
+ `Dangerous operation on protected path: ${path}`,
164
+ );
165
+ }
166
+ }
167
+ }
168
+
169
+ return result;
170
+ }
171
+
172
+ validateSingleCommand(
173
+ command: string,
174
+ _toolName = "Unknown",
175
+ ): ValidationResult {
176
+ const result: ValidationResult = {
177
+ isValid: true,
178
+ severity: "LOW",
179
+ violations: [],
180
+ sanitizedCommand: command,
181
+ };
182
+
183
+ if (!command || typeof command !== "string") {
184
+ result.isValid = false;
185
+ result.violations.push("Invalid command format");
186
+ return result;
187
+ }
188
+
189
+ if (command.length > 2000) {
190
+ result.isValid = false;
191
+ result.severity = "MEDIUM";
192
+ result.violations.push("Command too long (potential buffer overflow)");
193
+ return result;
194
+ }
195
+
196
+ if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\xFF]/.test(command)) {
197
+ result.isValid = false;
198
+ result.severity = "HIGH";
199
+ result.violations.push("Binary or encoded content detected");
200
+ return result;
201
+ }
202
+
203
+ const normalizedCmd = command.trim().toLowerCase();
204
+ const cmdParts = normalizedCmd.split(/\s+/);
205
+ const mainCommand = cmdParts[0].split("/").pop() || "";
206
+
207
+ if (mainCommand === "source" || mainCommand === "python") {
208
+ return result;
209
+ }
210
+
211
+ for (const pattern of SECURITY_RULES.DANGEROUS_PATTERNS) {
212
+ if (pattern.test(command)) {
213
+ result.isValid = false;
214
+ result.severity = "CRITICAL";
215
+ result.violations.push(`Dangerous pattern detected: ${pattern.source}`);
216
+ }
217
+ }
218
+
219
+ if (SECURITY_RULES.CRITICAL_COMMANDS.includes(mainCommand)) {
220
+ result.isValid = false;
221
+ result.severity = "CRITICAL";
222
+ result.violations.push(`Critical dangerous command: ${mainCommand}`);
223
+ }
224
+
225
+ if (SECURITY_RULES.PRIVILEGE_COMMANDS.includes(mainCommand)) {
226
+ result.isValid = false;
227
+ result.severity = "HIGH";
228
+ result.violations.push(`Privilege escalation command: ${mainCommand}`);
229
+ }
230
+
231
+ if (SECURITY_RULES.NETWORK_COMMANDS.includes(mainCommand)) {
232
+ result.isValid = false;
233
+ result.severity = "HIGH";
234
+ result.violations.push(`Network/remote access command: ${mainCommand}`);
235
+ }
236
+
237
+ if (SECURITY_RULES.SYSTEM_COMMANDS.includes(mainCommand)) {
238
+ result.isValid = false;
239
+ result.severity = "HIGH";
240
+ result.violations.push(`System manipulation command: ${mainCommand}`);
241
+ }
242
+
243
+ if (/rm\s+.*-rf\s/.test(command)) {
244
+ const isRmRfSafe = this.isRmRfCommandSafe(command);
245
+ if (!isRmRfSafe) {
246
+ result.isValid = false;
247
+ result.severity = "CRITICAL";
248
+ result.violations.push("rm -rf command targeting unsafe path");
249
+ }
250
+ }
251
+
252
+ if (SAFE_COMMANDS.includes(mainCommand) && result.violations.length === 0) {
253
+ return result;
254
+ }
255
+
256
+ for (const path of SECURITY_RULES.PROTECTED_PATHS) {
257
+ if (command.includes(path)) {
258
+ if (
259
+ path === "/dev/" &&
260
+ (command.includes("/dev/null") ||
261
+ command.includes("/dev/stderr") ||
262
+ command.includes("/dev/stdout"))
263
+ ) {
264
+ continue;
265
+ }
266
+
267
+ const cmdStart = command.trim();
268
+ let isSafeExecutable = false;
269
+ for (const safePath of SECURITY_RULES.SAFE_EXECUTABLE_PATHS) {
270
+ if (cmdStart.startsWith(safePath)) {
271
+ isSafeExecutable = true;
272
+ break;
273
+ }
274
+ }
275
+
276
+ const pathIndex = command.indexOf(path);
277
+ const beforePath = command.substring(0, pathIndex);
278
+ const redirectBeforePath = />\s*$/.test(beforePath.trim());
279
+
280
+ if (!isSafeExecutable && redirectBeforePath) {
281
+ result.isValid = false;
282
+ result.severity = "HIGH";
283
+ result.violations.push(
284
+ `Dangerous operation on protected path: ${path}`,
285
+ );
286
+ }
287
+ }
288
+ }
289
+
290
+ return result;
291
+ }
292
+
293
+ splitCommandChain(command: string): string[] {
294
+ const commands: string[] = [];
295
+ let current = "";
296
+ let inQuotes = false;
297
+ let quoteChar = "";
298
+
299
+ for (let i = 0; i < command.length; i++) {
300
+ const char = command[i];
301
+ const nextChar = command[i + 1];
302
+
303
+ if ((char === '"' || char === "'") && !inQuotes) {
304
+ inQuotes = true;
305
+ quoteChar = char;
306
+ current += char;
307
+ } else if (char === quoteChar && inQuotes) {
308
+ inQuotes = false;
309
+ quoteChar = "";
310
+ current += char;
311
+ } else if (inQuotes) {
312
+ current += char;
313
+ } else if (char === "&" && nextChar === "&") {
314
+ commands.push(current.trim());
315
+ current = "";
316
+ i++;
317
+ } else if (char === "|" && nextChar === "|") {
318
+ commands.push(current.trim());
319
+ current = "";
320
+ i++;
321
+ } else if (char === ";") {
322
+ commands.push(current.trim());
323
+ current = "";
324
+ } else {
325
+ current += char;
326
+ }
327
+ }
328
+
329
+ if (current.trim()) {
330
+ commands.push(current.trim());
331
+ }
332
+
333
+ return commands.filter((cmd) => cmd.length > 0);
334
+ }
335
+
336
+ isRmRfCommandSafe(command: string): boolean {
337
+ const rmRfMatch = command.match(/rm\s+.*-rf\s+([^\s;&|]+)/);
338
+ if (!rmRfMatch) {
339
+ return false;
340
+ }
341
+
342
+ const targetPath = rmRfMatch[1];
343
+
344
+ if (targetPath === "/" || targetPath.endsWith("/")) {
345
+ return false;
346
+ }
347
+
348
+ for (const safePath of SECURITY_RULES.SAFE_RM_PATHS) {
349
+ if (targetPath.startsWith(safePath)) {
350
+ return true;
351
+ }
352
+ }
353
+
354
+ if (!targetPath.startsWith("/")) {
355
+ return true;
356
+ }
357
+
358
+ return false;
359
+ }
360
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ },
7
+ });
@@ -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,105 @@
1
+ # Claude Code Statusline
2
+
3
+ Clean, modular statusline for Claude Code with TypeScript + Bun.
4
+
5
+ ## Features
6
+
7
+ - 🌿 Git branch with changes (+added -deleted)
8
+ - 💰 Session cost and duration
9
+ - 🧩 Context tokens used
10
+ - 📊 Context percentage (0-100%)
11
+ - ⏱️ Five-hour usage limit with reset time
12
+
13
+ ## Structure
14
+
15
+ ```
16
+ src/
17
+ ├── index.ts # Main entry point
18
+ └── lib/
19
+ ├── types.ts # TypeScript interfaces
20
+ ├── git.ts # Git status
21
+ ├── context.ts # Context calculation from transcript
22
+ ├── usage-limits.ts # Claude API usage limits
23
+ └── formatters.ts # Formatting utilities
24
+ ```
25
+
26
+ ## Development
27
+
28
+ ```bash
29
+ # Install dependencies
30
+ bun install
31
+
32
+ # Run the statusline (needs stdin JSON)
33
+ echo '{ ... }' | bun run start
34
+
35
+ # View today's spending
36
+ bun run spend:today
37
+
38
+ # View this month's spending
39
+ bun run spend:month
40
+
41
+ # Format code
42
+ bun run format
43
+
44
+ # Lint code
45
+ bun run lint
46
+ ```
47
+
48
+ ## Spend Tracking
49
+
50
+ The statusline automatically saves session data to `data/spend.json`. You can view your spending with:
51
+
52
+ ```bash
53
+ # Today's sessions and cost
54
+ bun run spend:today
55
+
56
+ # This month's sessions grouped by date
57
+ bun run spend:month
58
+ ```
59
+
60
+ Each session tracks:
61
+ - Cost (USD)
62
+ - Duration
63
+ - Lines added/removed
64
+ - Working directory
65
+
66
+ ## Usage in Claude Code
67
+
68
+ Update your `~/.claude/settings.json`:
69
+
70
+ ```json
71
+ {
72
+ "statusLine": {
73
+ "type": "command",
74
+ "command": "bun /Users/melvynx/.claude/scripts/statusline/src/index.ts",
75
+ "padding": 0
76
+ }
77
+ }
78
+ ```
79
+
80
+ ## Testing
81
+
82
+ ```bash
83
+ echo '{
84
+ "session_id": "test",
85
+ "transcript_path": "/path/to/transcript.jsonl",
86
+ "cwd": "/path",
87
+ "model": {
88
+ "id": "claude-sonnet-4-5",
89
+ "display_name": "Sonnet 4.5"
90
+ },
91
+ "workspace": {
92
+ "current_dir": "/path",
93
+ "project_dir": "/path"
94
+ },
95
+ "version": "2.0.31",
96
+ "output_style": { "name": "default" },
97
+ "cost": {
98
+ "total_cost_usd": 0.15,
99
+ "total_duration_ms": 300000,
100
+ "total_api_duration_ms": 200000,
101
+ "total_lines_added": 100,
102
+ "total_lines_removed": 50
103
+ }
104
+ }' | bun run start
105
+ ```
@@ -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
+ }