aiblueprint-cli 1.1.7 → 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 (44) 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/fix-grammar.md +49 -0
  6. package/claude-code-config/agents/snipper.md +1 -1
  7. package/claude-code-config/agents/websearch.md +1 -1
  8. package/claude-code-config/commands/commit.md +1 -1
  9. package/claude-code-config/commands/epct/code.md +171 -0
  10. package/claude-code-config/commands/epct/deploy.md +116 -0
  11. package/claude-code-config/commands/epct/explore.md +97 -0
  12. package/claude-code-config/commands/epct/plan.md +132 -0
  13. package/claude-code-config/commands/epct/tasks.md +206 -0
  14. package/claude-code-config/commands/melvynx-plugin.md +1 -0
  15. package/claude-code-config/commands/oneshot.md +57 -0
  16. package/claude-code-config/hooks/hooks.json +15 -0
  17. package/claude-code-config/scripts/statusline/CLAUDE.md +178 -0
  18. package/claude-code-config/scripts/statusline/README.md +105 -0
  19. package/claude-code-config/scripts/statusline/biome.json +34 -0
  20. package/claude-code-config/scripts/statusline/bun.lockb +0 -0
  21. package/claude-code-config/scripts/statusline/data/.gitignore +5 -0
  22. package/claude-code-config/scripts/statusline/fixtures/test-input.json +25 -0
  23. package/claude-code-config/scripts/statusline/package.json +21 -0
  24. package/claude-code-config/scripts/statusline/src/commands/CLAUDE.md +3 -0
  25. package/claude-code-config/scripts/statusline/src/commands/spend-month.ts +60 -0
  26. package/claude-code-config/scripts/statusline/src/commands/spend-today.ts +42 -0
  27. package/claude-code-config/scripts/statusline/src/index.ts +141 -0
  28. package/claude-code-config/scripts/statusline/src/lib/context.ts +103 -0
  29. package/claude-code-config/scripts/statusline/src/lib/formatters.ts +218 -0
  30. package/claude-code-config/scripts/statusline/src/lib/git.ts +100 -0
  31. package/claude-code-config/scripts/statusline/src/lib/spend.ts +119 -0
  32. package/claude-code-config/scripts/statusline/src/lib/types.ts +25 -0
  33. package/claude-code-config/scripts/statusline/src/lib/usage-limits.ts +147 -0
  34. package/claude-code-config/scripts/statusline/statusline.config.ts +122 -0
  35. package/claude-code-config/scripts/statusline/test.ts +20 -0
  36. package/claude-code-config/scripts/statusline/tsconfig.json +27 -0
  37. package/dist/cli.js +153 -69
  38. package/package.json +1 -2
  39. package/claude-code-config/output-styles/assistant.md +0 -15
  40. package/claude-code-config/output-styles/honnest.md +0 -9
  41. package/claude-code-config/output-styles/senior-dev.md +0 -14
  42. package/claude-code-config/scripts/statusline-ccusage.sh +0 -188
  43. package/claude-code-config/scripts/statusline.readme.md +0 -194
  44. /package/claude-code-config/{hooks → scripts}/hook-post-file.ts +0 -0
@@ -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
+ }
@@ -0,0 +1,218 @@
1
+ import type { Separator, StatuslineConfig } from "../../statusline.config";
2
+ import type { GitStatus } from "./git";
3
+
4
+ export const colors = {
5
+ GREEN: "\x1b[0;32m",
6
+ RED: "\x1b[0;31m",
7
+ PURPLE: "\x1b[0;35m",
8
+ YELLOW: "\x1b[0;33m",
9
+ ORANGE: "\x1b[38;5;208m",
10
+ GRAY: "\x1b[0;90m",
11
+ LIGHT_GRAY: "\x1b[0;37m",
12
+ RESET: "\x1b[0m",
13
+ } as const;
14
+
15
+ export function formatBranch(
16
+ git: GitStatus,
17
+ gitConfig: StatuslineConfig["git"],
18
+ ): string {
19
+ let result = "";
20
+
21
+ if (gitConfig.showBranch) {
22
+ result = git.branch;
23
+ }
24
+
25
+ if (git.hasChanges) {
26
+ const changes: string[] = [];
27
+
28
+ if (gitConfig.showDirtyIndicator) {
29
+ result += `${colors.PURPLE}*${colors.RESET}`;
30
+ }
31
+
32
+ if (gitConfig.showChanges) {
33
+ const totalAdded = git.staged.added + git.unstaged.added;
34
+ const totalDeleted = git.staged.deleted + git.unstaged.deleted;
35
+
36
+ if (totalAdded > 0) {
37
+ changes.push(`${colors.GREEN}+${totalAdded}${colors.RESET}`);
38
+ }
39
+ if (totalDeleted > 0) {
40
+ changes.push(`${colors.RED}-${totalDeleted}${colors.RESET}`);
41
+ }
42
+ }
43
+
44
+ if (gitConfig.showStaged && git.staged.files > 0) {
45
+ changes.push(`${colors.GRAY}~${git.staged.files}${colors.RESET}`);
46
+ }
47
+
48
+ if (gitConfig.showUnstaged && git.unstaged.files > 0) {
49
+ changes.push(`${colors.YELLOW}~${git.unstaged.files}${colors.RESET}`);
50
+ }
51
+
52
+ if (changes.length > 0) {
53
+ result += ` ${changes.join(" ")}`;
54
+ }
55
+ }
56
+
57
+ return result;
58
+ }
59
+
60
+ export function formatPath(
61
+ path: string,
62
+ mode: "full" | "truncated" | "basename" = "truncated",
63
+ ): string {
64
+ const home = process.env.HOME || "";
65
+ let formattedPath = path;
66
+
67
+ if (home && path.startsWith(home)) {
68
+ formattedPath = `~${path.slice(home.length)}`;
69
+ }
70
+
71
+ if (mode === "basename") {
72
+ const segments = path.split("/").filter((s) => s.length > 0);
73
+ return segments[segments.length - 1] || path;
74
+ }
75
+
76
+ if (mode === "truncated") {
77
+ const segments = formattedPath.split("/").filter((s) => s.length > 0);
78
+ if (segments.length > 2) {
79
+ return `/${segments.slice(-2).join("/")}`;
80
+ }
81
+ }
82
+
83
+ return formattedPath;
84
+ }
85
+
86
+ export function formatCost(cost: number): string {
87
+ return cost.toFixed(2);
88
+ }
89
+
90
+ export function formatTokens(tokens: number, showDecimals = true): string {
91
+ if (tokens >= 1000000) {
92
+ const value = tokens / 1000000;
93
+ const number = showDecimals
94
+ ? value.toFixed(1)
95
+ : Math.round(value).toString();
96
+ return `${number}${colors.GRAY}m${colors.LIGHT_GRAY}`;
97
+ }
98
+ if (tokens >= 1000) {
99
+ const value = tokens / 1000;
100
+ const number = showDecimals
101
+ ? value.toFixed(1)
102
+ : Math.round(value).toString();
103
+ return `${number}${colors.GRAY}k${colors.LIGHT_GRAY}`;
104
+ }
105
+ return tokens.toString();
106
+ }
107
+
108
+ export function formatDuration(ms: number): string {
109
+ const minutes = Math.floor(ms / 60000);
110
+ const hours = Math.floor(minutes / 60);
111
+ const mins = minutes % 60;
112
+
113
+ if (hours > 0) {
114
+ return `${hours}h ${mins}m`;
115
+ }
116
+ return `${mins}m`;
117
+ }
118
+
119
+ export function formatResetTime(resetsAt: string): string {
120
+ try {
121
+ const resetDate = new Date(resetsAt);
122
+ const now = new Date();
123
+ const diffMs = resetDate.getTime() - now.getTime();
124
+
125
+ if (diffMs <= 0) {
126
+ return "now";
127
+ }
128
+
129
+ const hours = Math.floor(diffMs / 3600000);
130
+ const minutes = Math.floor((diffMs % 3600000) / 60000);
131
+
132
+ if (hours > 0) {
133
+ return `${hours}h${minutes}m`;
134
+ }
135
+ return `${minutes}m`;
136
+ } catch {
137
+ return "N/A";
138
+ }
139
+ }
140
+
141
+ export function formatProgressBar(
142
+ percentage: number,
143
+ length: number,
144
+ colorMode: "progressive" | "green" | "yellow" | "red",
145
+ ): string {
146
+ const filled = Math.round((percentage / 100) * length);
147
+ const empty = length - filled;
148
+
149
+ const filledBar = "█".repeat(filled);
150
+ const emptyBar = "░".repeat(empty);
151
+
152
+ let barColor: string;
153
+ if (colorMode === "progressive") {
154
+ if (percentage < 50) {
155
+ barColor = colors.GRAY;
156
+ } else if (percentage < 70) {
157
+ barColor = colors.YELLOW;
158
+ } else if (percentage < 90) {
159
+ barColor = colors.ORANGE;
160
+ } else {
161
+ barColor = colors.RED;
162
+ }
163
+ } else if (colorMode === "green") {
164
+ barColor = colors.GREEN;
165
+ } else if (colorMode === "yellow") {
166
+ barColor = colors.YELLOW;
167
+ } else {
168
+ barColor = colors.RED;
169
+ }
170
+
171
+ return `${barColor}${filledBar}${colors.GRAY}${emptyBar}${colors.RESET}`;
172
+ }
173
+
174
+ export interface SessionConfig {
175
+ infoSeparator: Separator | null;
176
+ showCost: boolean;
177
+ showTokens: boolean;
178
+ showMaxTokens: boolean;
179
+ showTokenDecimals: boolean;
180
+ showPercentage: boolean;
181
+ }
182
+
183
+ export function formatSession(
184
+ cost: string,
185
+ tokensUsed: number,
186
+ tokensMax: number,
187
+ percentage: number,
188
+ config: SessionConfig,
189
+ ): string {
190
+ const sessionItems: string[] = [];
191
+
192
+ if (config.showCost) {
193
+ sessionItems.push(`$${cost}`);
194
+ }
195
+ if (config.showTokens) {
196
+ const formattedUsed = formatTokens(tokensUsed, config.showTokenDecimals);
197
+ if (config.showMaxTokens) {
198
+ const formattedMax = formatTokens(tokensMax, config.showTokenDecimals);
199
+ sessionItems.push(
200
+ `${formattedUsed}${colors.GRAY}/${formattedMax}${colors.LIGHT_GRAY}`,
201
+ );
202
+ } else {
203
+ sessionItems.push(formattedUsed);
204
+ }
205
+ }
206
+ if (config.showPercentage) {
207
+ sessionItems.push(`${percentage}${colors.GRAY}%${colors.LIGHT_GRAY}`);
208
+ }
209
+
210
+ if (sessionItems.length === 0) {
211
+ return "";
212
+ }
213
+
214
+ const infoSep = config.infoSeparator
215
+ ? ` ${colors.GRAY}${config.infoSeparator}${colors.LIGHT_GRAY} `
216
+ : " ";
217
+ return `${colors.GRAY}S:${colors.LIGHT_GRAY} ${sessionItems.join(infoSep)}`;
218
+ }