@vibest/claude-code-statusline 0.0.1
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 +64 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +270 -0
- package/dist/colors.d.ts +6 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +299 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/themes.d.ts +36 -0
- package/dist/types.d.ts +46 -0
- package/dist/utils.d.ts +40 -0
- package/dist/widgets.d.ts +61 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# @vibest/claude-code-statusline
|
|
2
|
+
|
|
3
|
+
A customizable statusline for Claude Code CLI.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add -g @vibest/claude-code-statusline
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with npm:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @vibest/claude-code-statusline
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Add to `~/.claude/settings.json`:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"statusLine": {
|
|
24
|
+
"type": "command",
|
|
25
|
+
"command": "claude-code-statusline"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Output
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
[Opus 4.5] █████░░░░░ 45% ∣ $0.52
|
|
34
|
+
~/Code/my-project (main +123 -45)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Programmatic Usage
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import {
|
|
41
|
+
readStdin,
|
|
42
|
+
modelWidget,
|
|
43
|
+
costWidget,
|
|
44
|
+
contextWidget,
|
|
45
|
+
projectGitWidget,
|
|
46
|
+
} from "@vibest/claude-code-statusline";
|
|
47
|
+
|
|
48
|
+
const data = await readStdin();
|
|
49
|
+
console.log(modelWidget(data));
|
|
50
|
+
console.log(projectGitWidget(data));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Available Widgets
|
|
54
|
+
|
|
55
|
+
- `modelWidget` - Model name with brackets (e.g., `[Opus 4.5]`)
|
|
56
|
+
- `contextWidget` - Context window progress bar and percentage
|
|
57
|
+
- `projectGitWidget` - Project path with git branch and changes
|
|
58
|
+
- `costWidget` - Total cost in USD
|
|
59
|
+
- `directoryWidget` - Current directory
|
|
60
|
+
- `gitWidget` - Git branch name
|
|
61
|
+
|
|
62
|
+
## License
|
|
63
|
+
|
|
64
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
9
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
|
+
for (let key of __getOwnPropNames(mod))
|
|
12
|
+
if (!__hasOwnProp.call(to, key))
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: () => mod[key],
|
|
15
|
+
enumerable: true
|
|
16
|
+
});
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
|
+
var __require = import.meta.require;
|
|
21
|
+
|
|
22
|
+
// node_modules/picocolors/picocolors.js
|
|
23
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
24
|
+
var p = process || {};
|
|
25
|
+
var argv = p.argv || [];
|
|
26
|
+
var env = p.env || {};
|
|
27
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
28
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
29
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
30
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
31
|
+
};
|
|
32
|
+
var replaceClose = (string, close, replace, index) => {
|
|
33
|
+
let result = "", cursor = 0;
|
|
34
|
+
do {
|
|
35
|
+
result += string.substring(cursor, index) + replace;
|
|
36
|
+
cursor = index + close.length;
|
|
37
|
+
index = string.indexOf(close, cursor);
|
|
38
|
+
} while (~index);
|
|
39
|
+
return result + string.substring(cursor);
|
|
40
|
+
};
|
|
41
|
+
var createColors = (enabled = isColorSupported) => {
|
|
42
|
+
let f = enabled ? formatter : () => String;
|
|
43
|
+
return {
|
|
44
|
+
isColorSupported: enabled,
|
|
45
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
46
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
47
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
48
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
49
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
50
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
51
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
52
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
53
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
54
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
55
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
56
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
57
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
58
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
59
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
60
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
61
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
62
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
63
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
64
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
65
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
66
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
67
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
68
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
69
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
70
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
71
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
72
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
73
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
74
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
75
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
76
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
77
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
78
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
79
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
80
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
81
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
82
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
83
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
84
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
85
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
module.exports = createColors();
|
|
89
|
+
module.exports.createColors = createColors;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// src/utils.ts
|
|
93
|
+
async function readStdin() {
|
|
94
|
+
const text = await Bun.stdin.text();
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
}
|
|
97
|
+
function formatCost(cost) {
|
|
98
|
+
if (cost === 0) {
|
|
99
|
+
return "$0";
|
|
100
|
+
}
|
|
101
|
+
if (cost < 0.01) {
|
|
102
|
+
return `$${cost.toFixed(4)}`;
|
|
103
|
+
}
|
|
104
|
+
if (cost < 1) {
|
|
105
|
+
return `$${cost.toFixed(2)}`;
|
|
106
|
+
}
|
|
107
|
+
return `$${cost.toFixed(2)}`;
|
|
108
|
+
}
|
|
109
|
+
function getGitBranch(cwd) {
|
|
110
|
+
try {
|
|
111
|
+
const headFile = Bun.file(`${cwd}/.git/HEAD`);
|
|
112
|
+
const content = __require("fs").readFileSync(`${cwd}/.git/HEAD`, "utf8").trim();
|
|
113
|
+
if (content.startsWith("ref: refs/heads/")) {
|
|
114
|
+
return content.replace("ref: refs/heads/", "");
|
|
115
|
+
}
|
|
116
|
+
return content.slice(0, 7);
|
|
117
|
+
} catch {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function truncate(str, maxLength) {
|
|
122
|
+
if (str.length <= maxLength) {
|
|
123
|
+
return str;
|
|
124
|
+
}
|
|
125
|
+
return str.slice(0, maxLength - 1) + "\u2026";
|
|
126
|
+
}
|
|
127
|
+
function shortPath(path) {
|
|
128
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
129
|
+
if (home && path.startsWith(home)) {
|
|
130
|
+
return "~" + path.slice(home.length);
|
|
131
|
+
}
|
|
132
|
+
return path;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/colors.ts
|
|
136
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
137
|
+
var pc = import_picocolors.createColors(true);
|
|
138
|
+
var {
|
|
139
|
+
reset,
|
|
140
|
+
bold,
|
|
141
|
+
dim,
|
|
142
|
+
italic,
|
|
143
|
+
underline,
|
|
144
|
+
inverse,
|
|
145
|
+
hidden,
|
|
146
|
+
strikethrough,
|
|
147
|
+
black,
|
|
148
|
+
red,
|
|
149
|
+
green,
|
|
150
|
+
yellow,
|
|
151
|
+
blue,
|
|
152
|
+
magenta,
|
|
153
|
+
cyan,
|
|
154
|
+
white,
|
|
155
|
+
gray,
|
|
156
|
+
bgBlack,
|
|
157
|
+
bgRed,
|
|
158
|
+
bgGreen,
|
|
159
|
+
bgYellow,
|
|
160
|
+
bgBlue,
|
|
161
|
+
bgMagenta,
|
|
162
|
+
bgCyan,
|
|
163
|
+
bgWhite
|
|
164
|
+
} = pc;
|
|
165
|
+
|
|
166
|
+
// src/widgets.ts
|
|
167
|
+
function modelWidget(data) {
|
|
168
|
+
const name = data.model.display_name;
|
|
169
|
+
return "[" + bold(cyan(name)) + "]";
|
|
170
|
+
}
|
|
171
|
+
function projectGitWidget(data) {
|
|
172
|
+
const dir = shortPath(data.workspace.current_dir);
|
|
173
|
+
const branch = getGitBranch(data.cwd);
|
|
174
|
+
let result = blue(dir);
|
|
175
|
+
if (branch) {
|
|
176
|
+
result += " (" + magenta(truncate(branch, 20)) + ")";
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
function codeChangesWidget(data) {
|
|
181
|
+
const added = data.cost.total_lines_added;
|
|
182
|
+
const removed = data.cost.total_lines_removed;
|
|
183
|
+
if (added === 0 && removed === 0) {
|
|
184
|
+
return "";
|
|
185
|
+
}
|
|
186
|
+
const parts = [];
|
|
187
|
+
if (added > 0) {
|
|
188
|
+
parts.push(green(`+${added}`));
|
|
189
|
+
}
|
|
190
|
+
if (removed > 0) {
|
|
191
|
+
parts.push(red(`-${removed}`));
|
|
192
|
+
}
|
|
193
|
+
return parts.join(" ");
|
|
194
|
+
}
|
|
195
|
+
function costWidget(data) {
|
|
196
|
+
const cost = data.cost.total_cost_usd;
|
|
197
|
+
const formatted = formatCost(cost);
|
|
198
|
+
if (cost < 0.1) {
|
|
199
|
+
return green(formatted);
|
|
200
|
+
}
|
|
201
|
+
if (cost < 0.5) {
|
|
202
|
+
return yellow(formatted);
|
|
203
|
+
}
|
|
204
|
+
return red(formatted);
|
|
205
|
+
}
|
|
206
|
+
function contextWidget(data) {
|
|
207
|
+
const percent = data.context_window.used_percentage;
|
|
208
|
+
const percentInt = Math.round(percent);
|
|
209
|
+
const filled = Math.round(percent / 10);
|
|
210
|
+
const empty = 10 - filled;
|
|
211
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
212
|
+
if (percent < 50) {
|
|
213
|
+
return green(bar) + " " + green(`${percentInt}%`);
|
|
214
|
+
}
|
|
215
|
+
if (percent < 80) {
|
|
216
|
+
return yellow(bar) + " " + yellow(`${percentInt}%`);
|
|
217
|
+
}
|
|
218
|
+
return red(bar) + " " + red(`${percentInt}%`);
|
|
219
|
+
}
|
|
220
|
+
function separator() {
|
|
221
|
+
return gray(" \u2223 ");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/cli.ts
|
|
225
|
+
var args = process.argv.slice(2);
|
|
226
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
227
|
+
console.log(`
|
|
228
|
+
Claude Code Statusline
|
|
229
|
+
|
|
230
|
+
Usage:
|
|
231
|
+
claude-code-statusline
|
|
232
|
+
|
|
233
|
+
Options:
|
|
234
|
+
--help, -h Show this help message
|
|
235
|
+
--version, -v Show version
|
|
236
|
+
|
|
237
|
+
Configuration:
|
|
238
|
+
Add to ~/.claude/settings.json:
|
|
239
|
+
|
|
240
|
+
{
|
|
241
|
+
"statusLine": {
|
|
242
|
+
"type": "command",
|
|
243
|
+
"command": "claude-code-statusline"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
`);
|
|
247
|
+
process.exit(0);
|
|
248
|
+
}
|
|
249
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
250
|
+
const pkg = await Bun.file(new URL("../package.json", import.meta.url)).json();
|
|
251
|
+
console.log(pkg.version);
|
|
252
|
+
process.exit(0);
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const data = await readStdin();
|
|
256
|
+
const changes = codeChangesWidget(data);
|
|
257
|
+
const line1Parts = [
|
|
258
|
+
modelWidget(data) + " " + contextWidget(data),
|
|
259
|
+
costWidget(data)
|
|
260
|
+
];
|
|
261
|
+
if (changes) {
|
|
262
|
+
line1Parts.push(changes);
|
|
263
|
+
}
|
|
264
|
+
const line1 = line1Parts.join(separator());
|
|
265
|
+
const line2 = projectGitWidget(data);
|
|
266
|
+
console.log(line1 + `
|
|
267
|
+
` + line2);
|
|
268
|
+
} catch {
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
package/dist/colors.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color Utilities - using picocolors with forced color support
|
|
3
|
+
*/
|
|
4
|
+
declare const pc: import("picocolors/types").Colors;
|
|
5
|
+
export { pc as colors };
|
|
6
|
+
export declare const reset: import("picocolors/types").Formatter, bold: import("picocolors/types").Formatter, dim: import("picocolors/types").Formatter, italic: import("picocolors/types").Formatter, underline: import("picocolors/types").Formatter, inverse: import("picocolors/types").Formatter, hidden: import("picocolors/types").Formatter, strikethrough: import("picocolors/types").Formatter, black: import("picocolors/types").Formatter, red: import("picocolors/types").Formatter, green: import("picocolors/types").Formatter, yellow: import("picocolors/types").Formatter, blue: import("picocolors/types").Formatter, magenta: import("picocolors/types").Formatter, cyan: import("picocolors/types").Formatter, white: import("picocolors/types").Formatter, gray: import("picocolors/types").Formatter, bgBlack: import("picocolors/types").Formatter, bgRed: import("picocolors/types").Formatter, bgGreen: import("picocolors/types").Formatter, bgYellow: import("picocolors/types").Formatter, bgBlue: import("picocolors/types").Formatter, bgMagenta: import("picocolors/types").Formatter, bgCyan: import("picocolors/types").Formatter, bgWhite: import("picocolors/types").Formatter;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Statusline
|
|
3
|
+
*
|
|
4
|
+
* A customizable statusline for Claude Code CLI
|
|
5
|
+
*/
|
|
6
|
+
export type { StatusLineInput, Model, Workspace, Cost, ContextWindow, CurrentUsage } from "./types.ts";
|
|
7
|
+
export { colors } from "./colors.ts";
|
|
8
|
+
export { bold, dim, italic, underline, red, green, yellow, blue, magenta, cyan, white, gray, } from "./colors.ts";
|
|
9
|
+
export { readStdin, basename, formatCost, formatPercent, formatTokens, formatDuration, getGitBranch, truncate, } from "./utils.ts";
|
|
10
|
+
export { type Widget, modelWidget, directoryWidget, projectWidget, gitWidget, costWidget, contextWidget, tokensWidget, linesWidget, durationWidget, versionWidget, separator, powerlineSeparator, } from "./widgets.ts";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
19
|
+
var __require = import.meta.require;
|
|
20
|
+
|
|
21
|
+
// node_modules/picocolors/picocolors.js
|
|
22
|
+
var require_picocolors = __commonJS((exports, module) => {
|
|
23
|
+
var p = process || {};
|
|
24
|
+
var argv = p.argv || [];
|
|
25
|
+
var env = p.env || {};
|
|
26
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
27
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
28
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
29
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
30
|
+
};
|
|
31
|
+
var replaceClose = (string, close, replace, index) => {
|
|
32
|
+
let result = "", cursor = 0;
|
|
33
|
+
do {
|
|
34
|
+
result += string.substring(cursor, index) + replace;
|
|
35
|
+
cursor = index + close.length;
|
|
36
|
+
index = string.indexOf(close, cursor);
|
|
37
|
+
} while (~index);
|
|
38
|
+
return result + string.substring(cursor);
|
|
39
|
+
};
|
|
40
|
+
var createColors = (enabled = isColorSupported) => {
|
|
41
|
+
let f = enabled ? formatter : () => String;
|
|
42
|
+
return {
|
|
43
|
+
isColorSupported: enabled,
|
|
44
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
45
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
46
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
47
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
48
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
49
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
50
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
51
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
52
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
53
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
54
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
55
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
56
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
57
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
58
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
59
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
60
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
61
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
62
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
63
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
64
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
65
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
66
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
67
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
68
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
69
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
70
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
71
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
72
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
73
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
74
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
75
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
76
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
77
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
78
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
79
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
80
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
81
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
82
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
83
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
84
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
module.exports = createColors();
|
|
88
|
+
module.exports.createColors = createColors;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// src/colors.ts
|
|
92
|
+
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
93
|
+
var pc = import_picocolors.createColors(true);
|
|
94
|
+
var {
|
|
95
|
+
reset,
|
|
96
|
+
bold,
|
|
97
|
+
dim,
|
|
98
|
+
italic,
|
|
99
|
+
underline,
|
|
100
|
+
inverse,
|
|
101
|
+
hidden,
|
|
102
|
+
strikethrough,
|
|
103
|
+
black,
|
|
104
|
+
red,
|
|
105
|
+
green,
|
|
106
|
+
yellow,
|
|
107
|
+
blue,
|
|
108
|
+
magenta,
|
|
109
|
+
cyan,
|
|
110
|
+
white,
|
|
111
|
+
gray,
|
|
112
|
+
bgBlack,
|
|
113
|
+
bgRed,
|
|
114
|
+
bgGreen,
|
|
115
|
+
bgYellow,
|
|
116
|
+
bgBlue,
|
|
117
|
+
bgMagenta,
|
|
118
|
+
bgCyan,
|
|
119
|
+
bgWhite
|
|
120
|
+
} = pc;
|
|
121
|
+
// src/utils.ts
|
|
122
|
+
async function readStdin() {
|
|
123
|
+
const text = await Bun.stdin.text();
|
|
124
|
+
return JSON.parse(text);
|
|
125
|
+
}
|
|
126
|
+
function basename(path) {
|
|
127
|
+
return path.split("/").pop() ?? path;
|
|
128
|
+
}
|
|
129
|
+
function formatCost(cost) {
|
|
130
|
+
if (cost === 0) {
|
|
131
|
+
return "$0";
|
|
132
|
+
}
|
|
133
|
+
if (cost < 0.01) {
|
|
134
|
+
return `$${cost.toFixed(4)}`;
|
|
135
|
+
}
|
|
136
|
+
if (cost < 1) {
|
|
137
|
+
return `$${cost.toFixed(2)}`;
|
|
138
|
+
}
|
|
139
|
+
return `$${cost.toFixed(2)}`;
|
|
140
|
+
}
|
|
141
|
+
function formatPercent(value) {
|
|
142
|
+
return `${value.toFixed(1)}%`;
|
|
143
|
+
}
|
|
144
|
+
function formatDuration(ms) {
|
|
145
|
+
if (ms < 1000) {
|
|
146
|
+
return `${ms}ms`;
|
|
147
|
+
}
|
|
148
|
+
const seconds = ms / 1000;
|
|
149
|
+
if (seconds < 60) {
|
|
150
|
+
return `${seconds.toFixed(1)}s`;
|
|
151
|
+
}
|
|
152
|
+
const minutes = Math.floor(seconds / 60);
|
|
153
|
+
const remainingSeconds = Math.floor(seconds % 60);
|
|
154
|
+
return `${minutes}m${remainingSeconds}s`;
|
|
155
|
+
}
|
|
156
|
+
function formatTokens(tokens) {
|
|
157
|
+
if (tokens < 1000) {
|
|
158
|
+
return tokens.toString();
|
|
159
|
+
}
|
|
160
|
+
if (tokens < 1e6) {
|
|
161
|
+
return `${(tokens / 1000).toFixed(1)}K`;
|
|
162
|
+
}
|
|
163
|
+
return `${(tokens / 1e6).toFixed(2)}M`;
|
|
164
|
+
}
|
|
165
|
+
function getGitBranch(cwd) {
|
|
166
|
+
try {
|
|
167
|
+
const headFile = Bun.file(`${cwd}/.git/HEAD`);
|
|
168
|
+
const content = __require("fs").readFileSync(`${cwd}/.git/HEAD`, "utf8").trim();
|
|
169
|
+
if (content.startsWith("ref: refs/heads/")) {
|
|
170
|
+
return content.replace("ref: refs/heads/", "");
|
|
171
|
+
}
|
|
172
|
+
return content.slice(0, 7);
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function truncate(str, maxLength) {
|
|
178
|
+
if (str.length <= maxLength) {
|
|
179
|
+
return str;
|
|
180
|
+
}
|
|
181
|
+
return str.slice(0, maxLength - 1) + "\u2026";
|
|
182
|
+
}
|
|
183
|
+
// src/widgets.ts
|
|
184
|
+
function modelWidget(data) {
|
|
185
|
+
const name = data.model.display_name;
|
|
186
|
+
return "[" + bold(cyan(name)) + "]";
|
|
187
|
+
}
|
|
188
|
+
function directoryWidget(data) {
|
|
189
|
+
const dir = basename(data.workspace.current_dir);
|
|
190
|
+
return blue(dir);
|
|
191
|
+
}
|
|
192
|
+
function projectWidget(data) {
|
|
193
|
+
const project = basename(data.workspace.project_dir);
|
|
194
|
+
const current = basename(data.workspace.current_dir);
|
|
195
|
+
if (project === current) {
|
|
196
|
+
return blue(project);
|
|
197
|
+
}
|
|
198
|
+
return blue(`${project}/${current}`);
|
|
199
|
+
}
|
|
200
|
+
function gitWidget(data) {
|
|
201
|
+
const branch = getGitBranch(data.cwd);
|
|
202
|
+
if (!branch) {
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
return magenta(` ${truncate(branch, 20)}`);
|
|
206
|
+
}
|
|
207
|
+
function costWidget(data) {
|
|
208
|
+
const cost = data.cost.total_cost_usd;
|
|
209
|
+
const formatted = formatCost(cost);
|
|
210
|
+
if (cost < 0.1) {
|
|
211
|
+
return green(formatted);
|
|
212
|
+
}
|
|
213
|
+
if (cost < 0.5) {
|
|
214
|
+
return yellow(formatted);
|
|
215
|
+
}
|
|
216
|
+
return red(formatted);
|
|
217
|
+
}
|
|
218
|
+
function contextWidget(data) {
|
|
219
|
+
const percent = data.context_window.used_percentage;
|
|
220
|
+
const percentInt = Math.round(percent);
|
|
221
|
+
const filled = Math.round(percent / 10);
|
|
222
|
+
const empty = 10 - filled;
|
|
223
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
224
|
+
if (percent < 50) {
|
|
225
|
+
return green(bar) + " " + green(`${percentInt}%`);
|
|
226
|
+
}
|
|
227
|
+
if (percent < 80) {
|
|
228
|
+
return yellow(bar) + " " + yellow(`${percentInt}%`);
|
|
229
|
+
}
|
|
230
|
+
return red(bar) + " " + red(`${percentInt}%`);
|
|
231
|
+
}
|
|
232
|
+
function tokensWidget(data) {
|
|
233
|
+
const input = formatTokens(data.context_window.total_input_tokens);
|
|
234
|
+
const output = formatTokens(data.context_window.total_output_tokens);
|
|
235
|
+
return gray(`\u2193${input}`) + " " + gray(`\u2191${output}`);
|
|
236
|
+
}
|
|
237
|
+
function linesWidget(data) {
|
|
238
|
+
const added = data.cost.total_lines_added;
|
|
239
|
+
const removed = data.cost.total_lines_removed;
|
|
240
|
+
if (added === 0 && removed === 0) {
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
const parts = [];
|
|
244
|
+
if (added > 0) {
|
|
245
|
+
parts.push(green(`+${added}`));
|
|
246
|
+
}
|
|
247
|
+
if (removed > 0) {
|
|
248
|
+
parts.push(red(`-${removed}`));
|
|
249
|
+
}
|
|
250
|
+
return parts.join(" ");
|
|
251
|
+
}
|
|
252
|
+
function durationWidget(data) {
|
|
253
|
+
const duration = formatDuration(data.cost.total_duration_ms);
|
|
254
|
+
return gray(duration);
|
|
255
|
+
}
|
|
256
|
+
function versionWidget(data) {
|
|
257
|
+
return dim(`v${data.version}`);
|
|
258
|
+
}
|
|
259
|
+
function separator() {
|
|
260
|
+
return gray(" \u2223 ");
|
|
261
|
+
}
|
|
262
|
+
function powerlineSeparator() {
|
|
263
|
+
return gray("");
|
|
264
|
+
}
|
|
265
|
+
export {
|
|
266
|
+
yellow,
|
|
267
|
+
white,
|
|
268
|
+
versionWidget,
|
|
269
|
+
underline,
|
|
270
|
+
truncate,
|
|
271
|
+
tokensWidget,
|
|
272
|
+
separator,
|
|
273
|
+
red,
|
|
274
|
+
readStdin,
|
|
275
|
+
projectWidget,
|
|
276
|
+
powerlineSeparator,
|
|
277
|
+
modelWidget,
|
|
278
|
+
magenta,
|
|
279
|
+
linesWidget,
|
|
280
|
+
italic,
|
|
281
|
+
green,
|
|
282
|
+
gray,
|
|
283
|
+
gitWidget,
|
|
284
|
+
getGitBranch,
|
|
285
|
+
formatTokens,
|
|
286
|
+
formatPercent,
|
|
287
|
+
formatDuration,
|
|
288
|
+
formatCost,
|
|
289
|
+
durationWidget,
|
|
290
|
+
directoryWidget,
|
|
291
|
+
dim,
|
|
292
|
+
cyan,
|
|
293
|
+
costWidget,
|
|
294
|
+
contextWidget,
|
|
295
|
+
pc as colors,
|
|
296
|
+
bold,
|
|
297
|
+
blue,
|
|
298
|
+
basename
|
|
299
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/themes.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statusline Themes
|
|
3
|
+
*/
|
|
4
|
+
import type { StatusLineInput } from "./types.ts";
|
|
5
|
+
export interface Theme {
|
|
6
|
+
name: string;
|
|
7
|
+
render: (data: StatusLineInput) => string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Default theme - balanced information display
|
|
11
|
+
*/
|
|
12
|
+
export declare const defaultTheme: Theme;
|
|
13
|
+
/**
|
|
14
|
+
* Minimal theme - just the essentials
|
|
15
|
+
*/
|
|
16
|
+
export declare const minimalTheme: Theme;
|
|
17
|
+
/**
|
|
18
|
+
* Full theme - all information
|
|
19
|
+
*/
|
|
20
|
+
export declare const fullTheme: Theme;
|
|
21
|
+
/**
|
|
22
|
+
* Developer theme - focus on code changes
|
|
23
|
+
*/
|
|
24
|
+
export declare const devTheme: Theme;
|
|
25
|
+
/**
|
|
26
|
+
* Cost-focused theme
|
|
27
|
+
*/
|
|
28
|
+
export declare const costTheme: Theme;
|
|
29
|
+
/**
|
|
30
|
+
* All available themes
|
|
31
|
+
*/
|
|
32
|
+
export declare const themes: Record<string, Theme>;
|
|
33
|
+
/**
|
|
34
|
+
* Get theme by name
|
|
35
|
+
*/
|
|
36
|
+
export declare function getTheme(name: string): Theme;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Statusline Input Types
|
|
3
|
+
*/
|
|
4
|
+
export interface Model {
|
|
5
|
+
id: string;
|
|
6
|
+
display_name: string;
|
|
7
|
+
}
|
|
8
|
+
export interface Workspace {
|
|
9
|
+
current_dir: string;
|
|
10
|
+
project_dir: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Cost {
|
|
13
|
+
total_cost_usd: number;
|
|
14
|
+
total_duration_ms: number;
|
|
15
|
+
total_api_duration_ms: number;
|
|
16
|
+
total_lines_added: number;
|
|
17
|
+
total_lines_removed: number;
|
|
18
|
+
}
|
|
19
|
+
export interface CurrentUsage {
|
|
20
|
+
input_tokens: number;
|
|
21
|
+
output_tokens: number;
|
|
22
|
+
cache_creation_input_tokens: number;
|
|
23
|
+
cache_read_input_tokens: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ContextWindow {
|
|
26
|
+
total_input_tokens: number;
|
|
27
|
+
total_output_tokens: number;
|
|
28
|
+
context_window_size: number;
|
|
29
|
+
used_percentage: number;
|
|
30
|
+
remaining_percentage: number;
|
|
31
|
+
current_usage: CurrentUsage | null;
|
|
32
|
+
}
|
|
33
|
+
export interface StatusLineInput {
|
|
34
|
+
hook_event_name: "Status";
|
|
35
|
+
session_id: string;
|
|
36
|
+
transcript_path: string;
|
|
37
|
+
cwd: string;
|
|
38
|
+
model: Model;
|
|
39
|
+
workspace: Workspace;
|
|
40
|
+
version: string;
|
|
41
|
+
output_style?: {
|
|
42
|
+
name: string;
|
|
43
|
+
};
|
|
44
|
+
cost: Cost;
|
|
45
|
+
context_window: ContextWindow;
|
|
46
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions
|
|
3
|
+
*/
|
|
4
|
+
import type { StatusLineInput } from "./types.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Read and parse JSON from stdin
|
|
7
|
+
*/
|
|
8
|
+
export declare function readStdin(): Promise<StatusLineInput>;
|
|
9
|
+
/**
|
|
10
|
+
* Get basename of a path
|
|
11
|
+
*/
|
|
12
|
+
export declare function basename(path: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Format cost in USD
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatCost(cost: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Format percentage
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatPercent(value: number): string;
|
|
21
|
+
/**
|
|
22
|
+
* Format duration in ms to human readable
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatDuration(ms: number): string;
|
|
25
|
+
/**
|
|
26
|
+
* Format token count with K/M suffix
|
|
27
|
+
*/
|
|
28
|
+
export declare function formatTokens(tokens: number): string;
|
|
29
|
+
/**
|
|
30
|
+
* Get git branch name (sync, for speed)
|
|
31
|
+
*/
|
|
32
|
+
export declare function getGitBranch(cwd: string): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Truncate string with ellipsis
|
|
35
|
+
*/
|
|
36
|
+
export declare function truncate(str: string, maxLength: number): string;
|
|
37
|
+
/**
|
|
38
|
+
* Convert path to ~/xxx format
|
|
39
|
+
*/
|
|
40
|
+
export declare function shortPath(path: string): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statusline Widgets
|
|
3
|
+
*/
|
|
4
|
+
import type { StatusLineInput } from "./types.ts";
|
|
5
|
+
export type Widget = (data: StatusLineInput) => string;
|
|
6
|
+
/**
|
|
7
|
+
* Model name widget
|
|
8
|
+
*/
|
|
9
|
+
export declare function modelWidget(data: StatusLineInput): string;
|
|
10
|
+
/**
|
|
11
|
+
* Directory widget
|
|
12
|
+
*/
|
|
13
|
+
export declare function directoryWidget(data: StatusLineInput): string;
|
|
14
|
+
/**
|
|
15
|
+
* Project directory widget
|
|
16
|
+
*/
|
|
17
|
+
export declare function projectWidget(data: StatusLineInput): string;
|
|
18
|
+
/**
|
|
19
|
+
* Git branch widget (branch only)
|
|
20
|
+
*/
|
|
21
|
+
export declare function gitWidget(data: StatusLineInput): string;
|
|
22
|
+
/**
|
|
23
|
+
* Project widget with git branch
|
|
24
|
+
*/
|
|
25
|
+
export declare function projectGitWidget(data: StatusLineInput): string;
|
|
26
|
+
/**
|
|
27
|
+
* Code changes widget (lines added/removed by Claude)
|
|
28
|
+
*/
|
|
29
|
+
export declare function codeChangesWidget(data: StatusLineInput): string;
|
|
30
|
+
/**
|
|
31
|
+
* Cost widget
|
|
32
|
+
*/
|
|
33
|
+
export declare function costWidget(data: StatusLineInput): string;
|
|
34
|
+
/**
|
|
35
|
+
* Context usage widget with progress bar
|
|
36
|
+
*/
|
|
37
|
+
export declare function contextWidget(data: StatusLineInput): string;
|
|
38
|
+
/**
|
|
39
|
+
* Token count widget
|
|
40
|
+
*/
|
|
41
|
+
export declare function tokensWidget(data: StatusLineInput): string;
|
|
42
|
+
/**
|
|
43
|
+
* Lines changed widget (standalone, without git)
|
|
44
|
+
*/
|
|
45
|
+
export declare function linesWidget(data: StatusLineInput): string;
|
|
46
|
+
/**
|
|
47
|
+
* Duration widget
|
|
48
|
+
*/
|
|
49
|
+
export declare function durationWidget(data: StatusLineInput): string;
|
|
50
|
+
/**
|
|
51
|
+
* Version widget
|
|
52
|
+
*/
|
|
53
|
+
export declare function versionWidget(data: StatusLineInput): string;
|
|
54
|
+
/**
|
|
55
|
+
* Separator
|
|
56
|
+
*/
|
|
57
|
+
export declare function separator(): string;
|
|
58
|
+
/**
|
|
59
|
+
* Powerline separator (right arrow)
|
|
60
|
+
*/
|
|
61
|
+
export declare function powerlineSeparator(): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vibest/claude-code-statusline",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A customizable statusline for Claude Code CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"claude-code-statusline": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "bun run build:js && bun run build:types",
|
|
23
|
+
"build:js": "bun build ./src/cli.ts --outdir ./dist --target bun && bun build ./src/index.ts --outdir ./dist --target bun",
|
|
24
|
+
"build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
25
|
+
"dev": "bun --watch src/cli.ts",
|
|
26
|
+
"test": "bun test",
|
|
27
|
+
"prepublishOnly": "bun run build",
|
|
28
|
+
"release": "bumpp"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"claude",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"statusline",
|
|
34
|
+
"cli",
|
|
35
|
+
"terminal",
|
|
36
|
+
"anthropic"
|
|
37
|
+
],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/bun": "latest",
|
|
42
|
+
"bumpp": "^10.4.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"bun": ">=1.0.0"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/vibest-dev/claude-code-statusline"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"picocolors": "^1.1.1"
|
|
56
|
+
}
|
|
57
|
+
}
|