aiblueprint-cli 1.1.4 → 1.1.8

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 (46) hide show
  1. package/README.md +496 -41
  2. package/claude-code-config/agents/action.md +36 -0
  3. package/claude-code-config/agents/explore-codebase.md +6 -0
  4. package/claude-code-config/agents/explore-docs.md +88 -0
  5. package/claude-code-config/agents/fix-grammar.md +49 -0
  6. package/claude-code-config/agents/snipper.md +2 -0
  7. package/claude-code-config/agents/websearch.md +1 -0
  8. package/claude-code-config/commands/commit.md +1 -1
  9. package/claude-code-config/commands/debug.md +91 -0
  10. package/claude-code-config/commands/epct/code.md +171 -0
  11. package/claude-code-config/commands/epct/deploy.md +116 -0
  12. package/claude-code-config/commands/epct/explore.md +97 -0
  13. package/claude-code-config/commands/epct/plan.md +132 -0
  14. package/claude-code-config/commands/epct/tasks.md +206 -0
  15. package/claude-code-config/commands/explore.md +45 -0
  16. package/claude-code-config/commands/melvynx-plugin.md +1 -0
  17. package/claude-code-config/commands/oneshot.md +57 -0
  18. package/claude-code-config/hooks/hooks.json +15 -0
  19. package/claude-code-config/scripts/statusline/CLAUDE.md +178 -0
  20. package/claude-code-config/scripts/statusline/README.md +105 -0
  21. package/claude-code-config/scripts/statusline/biome.json +34 -0
  22. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  23. package/claude-code-config/scripts/statusline/data/.gitignore +5 -0
  24. package/claude-code-config/scripts/statusline/fixtures/test-input.json +25 -0
  25. package/claude-code-config/scripts/statusline/package.json +21 -0
  26. package/claude-code-config/scripts/statusline/src/commands/CLAUDE.md +3 -0
  27. package/claude-code-config/scripts/statusline/src/commands/spend-month.ts +60 -0
  28. package/claude-code-config/scripts/statusline/src/commands/spend-today.ts +42 -0
  29. package/claude-code-config/scripts/statusline/src/index.ts +141 -0
  30. package/claude-code-config/scripts/statusline/src/lib/context.ts +103 -0
  31. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +218 -0
  32. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  33. package/claude-code-config/scripts/statusline/src/lib/spend.ts +119 -0
  34. package/claude-code-config/scripts/statusline/src/lib/types.ts +25 -0
  35. package/claude-code-config/scripts/statusline/src/lib/usage-limits.ts +147 -0
  36. package/claude-code-config/scripts/statusline/statusline.config.ts +122 -0
  37. package/claude-code-config/scripts/statusline/test.ts +20 -0
  38. package/claude-code-config/scripts/statusline/tsconfig.json +27 -0
  39. package/dist/cli.js +722 -256
  40. package/package.json +1 -2
  41. package/claude-code-config/output-styles/assistant.md +0 -15
  42. package/claude-code-config/output-styles/honnest.md +0 -9
  43. package/claude-code-config/output-styles/senior-dev.md +0 -14
  44. package/claude-code-config/scripts/statusline-ccusage.sh +0 -188
  45. package/claude-code-config/scripts/statusline.readme.md +0 -194
  46. /package/claude-code-config/{hooks → scripts}/hook-post-file.ts +0 -0
@@ -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,5 @@
1
+ # Ignore all data and cache files
2
+ *.json
3
+
4
+ # But keep this .gitignore
5
+ !.gitignore
@@ -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,21 @@
1
+ {
2
+ "name": "statusline",
3
+ "version": "1.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
+ "spend:today": "bun run src/commands/spend-today.ts",
10
+ "spend:month": "bun run src/commands/spend-month.ts",
11
+ "lint": "biome check --write .",
12
+ "format": "biome format --write ."
13
+ },
14
+ "devDependencies": {
15
+ "@biomejs/biome": "^2.3.2",
16
+ "@types/bun": "latest"
17
+ },
18
+ "peerDependencies": {
19
+ "typescript": "^5.0.0"
20
+ }
21
+ }
@@ -0,0 +1,3 @@
1
+ Important :
2
+
3
+ This folder only include command that we can run using `/Users/melvynx/.claude/scripts/statusline/package.json` commands. Please don't write anything else here.
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { formatCost, formatDuration } from "../lib/formatters";
4
+ import {
5
+ calculateTotalCost,
6
+ calculateTotalDuration,
7
+ filterSessionsByDate,
8
+ getMonthStart,
9
+ loadSpendData,
10
+ } from "../lib/spend";
11
+
12
+ async function main() {
13
+ const data = await loadSpendData();
14
+ const monthStart = getMonthStart();
15
+ const monthSessions = filterSessionsByDate(data.sessions, monthStart);
16
+
17
+ if (monthSessions.length === 0) {
18
+ console.log("📊 No sessions this month");
19
+ return;
20
+ }
21
+
22
+ const totalCost = calculateTotalCost(monthSessions);
23
+ const totalDuration = calculateTotalDuration(monthSessions);
24
+
25
+ // Group by date
26
+ const sessionsByDate = monthSessions.reduce(
27
+ (acc, session) => {
28
+ if (!acc[session.date]) {
29
+ acc[session.date] = [];
30
+ }
31
+ acc[session.date].push(session);
32
+ return acc;
33
+ },
34
+ {} as Record<string, typeof monthSessions>,
35
+ );
36
+
37
+ const monthName = monthStart.toLocaleString("default", { month: "long" });
38
+
39
+ console.log(`\n📊 ${monthName}'s Spend\n`);
40
+ console.log(`Sessions: ${monthSessions.length}`);
41
+ console.log(`Total Cost: $${formatCost(totalCost)}`);
42
+ console.log(`Total Duration: ${formatDuration(totalDuration)}`);
43
+ console.log(`\n📅 By Date:\n`);
44
+
45
+ const sortedDates = Object.keys(sessionsByDate).sort();
46
+
47
+ for (const date of sortedDates) {
48
+ const sessions = sessionsByDate[date];
49
+ const dayCost = calculateTotalCost(sessions);
50
+ const dayDuration = calculateTotalDuration(sessions);
51
+
52
+ console.log(
53
+ ` ${date}: $${formatCost(dayCost)} (${formatDuration(dayDuration)}) - ${sessions.length} session(s)`,
54
+ );
55
+ }
56
+
57
+ console.log("");
58
+ }
59
+
60
+ main();
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { formatCost, formatDuration } from "../lib/formatters";
4
+ import {
5
+ calculateTotalCost,
6
+ calculateTotalDuration,
7
+ filterSessionsByDate,
8
+ getTodayStart,
9
+ loadSpendData,
10
+ } from "../lib/spend";
11
+
12
+ async function main() {
13
+ const data = await loadSpendData();
14
+ const today = getTodayStart();
15
+ const todaySessions = filterSessionsByDate(data.sessions, today);
16
+
17
+ if (todaySessions.length === 0) {
18
+ console.log("📊 No sessions today");
19
+ return;
20
+ }
21
+
22
+ const totalCost = calculateTotalCost(todaySessions);
23
+ const totalDuration = calculateTotalDuration(todaySessions);
24
+
25
+ console.log(`\n📊 Today's Spend\n`);
26
+ console.log(`Sessions: ${todaySessions.length}`);
27
+ console.log(`Total Cost: $${formatCost(totalCost)}`);
28
+ console.log(`Total Duration: ${formatDuration(totalDuration)}`);
29
+ console.log(`\n📝 Sessions:\n`);
30
+
31
+ for (const session of todaySessions) {
32
+ console.log(
33
+ ` • $${formatCost(session.cost)} (${formatDuration(session.duration_ms)})`,
34
+ );
35
+ console.log(` ${session.cwd}`);
36
+ console.log(
37
+ ` +${session.lines_added} -${session.lines_removed} lines\n`,
38
+ );
39
+ }
40
+ }
41
+
42
+ main();
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import type { StatuslineConfig } from "../statusline.config";
4
+ import { defaultConfig } from "../statusline.config";
5
+ import { getContextData } from "./lib/context";
6
+ import {
7
+ colors,
8
+ formatBranch,
9
+ formatCost,
10
+ formatDuration,
11
+ formatPath,
12
+ formatProgressBar,
13
+ formatResetTime,
14
+ formatSession,
15
+ } from "./lib/formatters";
16
+ import { getGitStatus } from "./lib/git";
17
+ import { saveSession } from "./lib/spend";
18
+ import type { HookInput } from "./lib/types";
19
+ import { getUsageLimits } from "./lib/usage-limits";
20
+
21
+ function buildFirstLine(
22
+ branch: string,
23
+ dirPath: string,
24
+ modelName: string,
25
+ showSonnetModel: boolean,
26
+ separator: string,
27
+ ): string {
28
+ const isSonnet = modelName.toLowerCase().includes("sonnet");
29
+ const sep = `${colors.GRAY}${separator}${colors.LIGHT_GRAY}`;
30
+
31
+ if (isSonnet && !showSonnetModel) {
32
+ return `${colors.LIGHT_GRAY}${branch} ${sep} ${dirPath}${colors.RESET}`;
33
+ }
34
+
35
+ return `${colors.LIGHT_GRAY}${branch} ${sep} ${dirPath} ${sep} ${modelName}${colors.RESET}`;
36
+ }
37
+
38
+ function buildSecondLine(
39
+ sessionCost: string,
40
+ _sessionDuration: string,
41
+ tokensUsed: number,
42
+ tokensMax: number,
43
+ contextPercentage: number,
44
+ fiveHourUtilization: number | null,
45
+ fiveHourReset: string | null,
46
+ sessionConfig: StatuslineConfig["session"],
47
+ limitsConfig: StatuslineConfig["limits"],
48
+ separator: string,
49
+ ): string {
50
+ let line = formatSession(
51
+ sessionCost,
52
+ tokensUsed,
53
+ tokensMax,
54
+ contextPercentage,
55
+ sessionConfig,
56
+ );
57
+
58
+ if (fiveHourUtilization !== null && fiveHourReset) {
59
+ const resetTime = formatResetTime(fiveHourReset);
60
+ const sep = `${colors.GRAY}${separator}`;
61
+
62
+ if (limitsConfig.showProgressBar) {
63
+ const bar = formatProgressBar(
64
+ fiveHourUtilization,
65
+ limitsConfig.progressBarLength,
66
+ limitsConfig.color,
67
+ );
68
+ line += ` ${sep} L: ${bar} ${colors.LIGHT_GRAY}${fiveHourUtilization}${colors.GRAY}% ${colors.GRAY}(${resetTime} left)`;
69
+ } else {
70
+ line += ` ${sep} L:${colors.LIGHT_GRAY} ${fiveHourUtilization}${colors.GRAY}% ${colors.GRAY}(${resetTime} left)`;
71
+ }
72
+ }
73
+
74
+ line += colors.RESET;
75
+
76
+ return line;
77
+ }
78
+
79
+ async function main() {
80
+ try {
81
+ const input: HookInput = await Bun.stdin.json();
82
+
83
+ await saveSession(input);
84
+
85
+ const git = await getGitStatus();
86
+ const branch = formatBranch(git, defaultConfig.git);
87
+ const dirPath = formatPath(
88
+ input.workspace.current_dir,
89
+ defaultConfig.pathDisplayMode,
90
+ );
91
+
92
+ const contextData = await getContextData({
93
+ transcriptPath: input.transcript_path,
94
+ maxContextTokens: defaultConfig.context.maxContextTokens,
95
+ autocompactBufferTokens: defaultConfig.context.autocompactBufferTokens,
96
+ useUsableContextOnly: defaultConfig.context.useUsableContextOnly,
97
+ overheadTokens: defaultConfig.context.overheadTokens,
98
+ });
99
+ const usageLimits = await getUsageLimits();
100
+
101
+ const sessionCost = formatCost(input.cost.total_cost_usd);
102
+ const sessionDuration = formatDuration(input.cost.total_duration_ms);
103
+
104
+ const firstLine = buildFirstLine(
105
+ branch,
106
+ dirPath,
107
+ input.model.display_name,
108
+ defaultConfig.showSonnetModel,
109
+ defaultConfig.separator,
110
+ );
111
+ const secondLine = buildSecondLine(
112
+ sessionCost,
113
+ sessionDuration,
114
+ contextData.tokens,
115
+ defaultConfig.context.maxContextTokens,
116
+ contextData.percentage,
117
+ usageLimits.five_hour?.utilization ?? null,
118
+ usageLimits.five_hour?.resets_at ?? null,
119
+ defaultConfig.session,
120
+ defaultConfig.limits,
121
+ defaultConfig.separator,
122
+ );
123
+
124
+ if (defaultConfig.oneLine) {
125
+ const sep = ` ${colors.GRAY}${defaultConfig.separator}${colors.LIGHT_GRAY} `;
126
+ console.log(`${firstLine}${sep}${secondLine}`);
127
+ console.log(""); // Empty second line for spacing
128
+ } else {
129
+ console.log(firstLine);
130
+ console.log(secondLine);
131
+ }
132
+ } catch (error) {
133
+ const errorMessage = error instanceof Error ? error.message : String(error);
134
+ console.log(
135
+ `${colors.RED}Error:${colors.LIGHT_GRAY} ${errorMessage}${colors.RESET}`,
136
+ );
137
+ console.log(`${colors.GRAY}Check statusline configuration${colors.RESET}`);
138
+ }
139
+ }
140
+
141
+ main();
@@ -0,0 +1,103 @@
1
+ import { existsSync } from "node:fs";
2
+
3
+ export interface TokenUsage {
4
+ input_tokens: number;
5
+ output_tokens: number;
6
+ cache_creation_input_tokens?: number;
7
+ cache_read_input_tokens?: number;
8
+ }
9
+
10
+ export interface TranscriptLine {
11
+ message?: { usage?: TokenUsage };
12
+ timestamp?: string;
13
+ isSidechain?: boolean;
14
+ isApiErrorMessage?: boolean;
15
+ }
16
+
17
+ export interface ContextResult {
18
+ tokens: number;
19
+ percentage: number;
20
+ }
21
+
22
+ export async function getContextLength(
23
+ transcriptPath: string,
24
+ ): Promise<number> {
25
+ try {
26
+ const content = await Bun.file(transcriptPath).text();
27
+ const lines = content.trim().split("\n");
28
+
29
+ if (lines.length === 0) return 0;
30
+
31
+ let mostRecentMainChainEntry: TranscriptLine | null = null;
32
+ let mostRecentTimestamp: Date | null = null;
33
+
34
+ for (const line of lines) {
35
+ try {
36
+ const data = JSON.parse(line) as TranscriptLine;
37
+
38
+ if (!data.message?.usage) continue;
39
+ if (data.isSidechain === true) continue;
40
+ if (data.isApiErrorMessage === true) continue;
41
+ if (!data.timestamp) continue;
42
+
43
+ const entryTime = new Date(data.timestamp);
44
+
45
+ if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
46
+ mostRecentTimestamp = entryTime;
47
+ mostRecentMainChainEntry = data;
48
+ }
49
+ } catch {}
50
+ }
51
+
52
+ if (!mostRecentMainChainEntry?.message?.usage) {
53
+ return 0;
54
+ }
55
+
56
+ const usage = mostRecentMainChainEntry.message.usage;
57
+
58
+ return (
59
+ (usage.input_tokens || 0) +
60
+ (usage.cache_read_input_tokens ?? 0) +
61
+ (usage.cache_creation_input_tokens ?? 0)
62
+ );
63
+ } catch {
64
+ return 0;
65
+ }
66
+ }
67
+
68
+ export interface ContextDataParams {
69
+ transcriptPath: string;
70
+ maxContextTokens: number;
71
+ autocompactBufferTokens: number;
72
+ useUsableContextOnly?: boolean;
73
+ overheadTokens?: number;
74
+ }
75
+
76
+ export async function getContextData({
77
+ transcriptPath,
78
+ maxContextTokens,
79
+ autocompactBufferTokens,
80
+ useUsableContextOnly = false,
81
+ overheadTokens = 0,
82
+ }: ContextDataParams): Promise<ContextResult> {
83
+ if (!transcriptPath || !existsSync(transcriptPath)) {
84
+ return { tokens: 0, percentage: 0 };
85
+ }
86
+
87
+ const contextLength = await getContextLength(transcriptPath);
88
+ let totalTokens = contextLength + overheadTokens;
89
+
90
+ // If useUsableContextOnly is true, add the autocompact buffer to displayed tokens
91
+ if (useUsableContextOnly) {
92
+ totalTokens += autocompactBufferTokens;
93
+ }
94
+
95
+ // Always calculate percentage based on max context window
96
+ // (matching /context display behavior)
97
+ const percentage = Math.min(100, (totalTokens / maxContextTokens) * 100);
98
+
99
+ return {
100
+ tokens: totalTokens,
101
+ percentage: Math.round(percentage),
102
+ };
103
+ }