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.
- package/README.md +496 -41
- package/claude-code-config/agents/action.md +36 -0
- package/claude-code-config/agents/explore-codebase.md +6 -0
- package/claude-code-config/agents/explore-docs.md +88 -0
- package/claude-code-config/agents/fix-grammar.md +49 -0
- package/claude-code-config/agents/snipper.md +2 -0
- package/claude-code-config/agents/websearch.md +1 -0
- package/claude-code-config/commands/commit.md +1 -1
- package/claude-code-config/commands/debug.md +91 -0
- package/claude-code-config/commands/epct/code.md +171 -0
- package/claude-code-config/commands/epct/deploy.md +116 -0
- package/claude-code-config/commands/epct/explore.md +97 -0
- package/claude-code-config/commands/epct/plan.md +132 -0
- package/claude-code-config/commands/epct/tasks.md +206 -0
- package/claude-code-config/commands/explore.md +45 -0
- package/claude-code-config/commands/melvynx-plugin.md +1 -0
- package/claude-code-config/commands/oneshot.md +57 -0
- package/claude-code-config/hooks/hooks.json +15 -0
- package/claude-code-config/scripts/statusline/CLAUDE.md +178 -0
- package/claude-code-config/scripts/statusline/README.md +105 -0
- package/claude-code-config/scripts/statusline/biome.json +34 -0
- package/claude-code-config/scripts/statusline/bun.lockb +0 -0
- package/claude-code-config/scripts/statusline/data/.gitignore +5 -0
- package/claude-code-config/scripts/statusline/fixtures/test-input.json +25 -0
- package/claude-code-config/scripts/statusline/package.json +21 -0
- package/claude-code-config/scripts/statusline/src/commands/CLAUDE.md +3 -0
- package/claude-code-config/scripts/statusline/src/commands/spend-month.ts +60 -0
- package/claude-code-config/scripts/statusline/src/commands/spend-today.ts +42 -0
- package/claude-code-config/scripts/statusline/src/index.ts +141 -0
- package/claude-code-config/scripts/statusline/src/lib/context.ts +103 -0
- package/claude-code-config/scripts/statusline/src/lib/formatters.ts +218 -0
- package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
- package/claude-code-config/scripts/statusline/src/lib/spend.ts +119 -0
- package/claude-code-config/scripts/statusline/src/lib/types.ts +25 -0
- package/claude-code-config/scripts/statusline/src/lib/usage-limits.ts +147 -0
- package/claude-code-config/scripts/statusline/statusline.config.ts +122 -0
- package/claude-code-config/scripts/statusline/test.ts +20 -0
- package/claude-code-config/scripts/statusline/tsconfig.json +27 -0
- package/dist/cli.js +722 -256
- package/package.json +1 -2
- package/claude-code-config/output-styles/assistant.md +0 -15
- package/claude-code-config/output-styles/honnest.md +0 -9
- package/claude-code-config/output-styles/senior-dev.md +0 -14
- package/claude-code-config/scripts/statusline-ccusage.sh +0 -188
- package/claude-code-config/scripts/statusline.readme.md +0 -194
- /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
|
+
}
|
|
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,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,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
|
+
}
|