@babarot/c-c-statusline 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +133 -0
- package/bin/install.js +263 -0
- package/package.json +27 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# c-c-statusline
|
|
2
|
+
|
|
3
|
+
A Deno-powered status line for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI.
|
|
4
|
+
|
|
5
|
+
Shows model info, context usage, rate limits, git status, session duration, and more — right in your terminal.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### Binary (recommended)
|
|
10
|
+
|
|
11
|
+
Downloads a precompiled binary from GitHub Releases. No runtime dependencies needed.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Deno
|
|
15
|
+
deno run -A https://raw.githubusercontent.com/babarot/c-c-statusline/main/bin/install.ts
|
|
16
|
+
|
|
17
|
+
# npx
|
|
18
|
+
npx @babarot/c-c-statusline
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Options
|
|
22
|
+
|
|
23
|
+
Pass options during install to customize the statusline:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
deno run -A https://raw.githubusercontent.com/babarot/c-c-statusline/main/bin/install.ts \
|
|
27
|
+
--bar-style block --path-style short --theme tokyo-night
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Option | Values | Default | Description |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| `--bar-style` | `dot`, `block`, `fill` | `dot` | Progress bar style |
|
|
33
|
+
| `--path-style` | `parent`, `full`, `short`, `basename` | `parent` | Directory display style |
|
|
34
|
+
| `--theme` | See [Themes](#themes) | `default` | Color theme |
|
|
35
|
+
| `--time-style` | `absolute`, `relative` | `absolute` | Reset time format |
|
|
36
|
+
| `--ctx-label` | `ctx`, `unicode`, `minimal` | `ctx` | Context window label |
|
|
37
|
+
|
|
38
|
+
**Bar styles:**
|
|
39
|
+
|
|
40
|
+
| Style | Example |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `dot` | `●●●●○○○○○○` |
|
|
43
|
+
| `block` | `▰▰▰▰▱▱▱▱▱▱` |
|
|
44
|
+
| `fill` | `████░░░░░░` |
|
|
45
|
+
|
|
46
|
+
**Path styles** (for `/Users/you/src/github.com/you/project`):
|
|
47
|
+
|
|
48
|
+
| Style | Example |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `parent` | `you/project` |
|
|
51
|
+
| `full` | `~/src/github.com/you/project` |
|
|
52
|
+
| `short` | `~/s/g/you/project` |
|
|
53
|
+
| `basename` | `project` |
|
|
54
|
+
|
|
55
|
+
**Time styles:**
|
|
56
|
+
|
|
57
|
+
| Style | Example |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `absolute` | `8:00pm`, `Mar 12, 2:00pm` |
|
|
60
|
+
| `relative` | `1h 30m left`, `2d 5h left` |
|
|
61
|
+
|
|
62
|
+
**Context window labels:**
|
|
63
|
+
|
|
64
|
+
| Style | Example |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `ctx` | `ctx 32%` |
|
|
67
|
+
| `unicode` | `◈ 32%` |
|
|
68
|
+
| `minimal` | `32%` |
|
|
69
|
+
|
|
70
|
+
### Themes
|
|
71
|
+
|
|
72
|
+
Built-in color themes using 24-bit True Color (RGB). Each theme defines 8 semantic color roles (`primary`, `secondary`, `success`, `warning`, `caution`, `danger`, `muted`, `accent`), so every theme can map any color to any role.
|
|
73
|
+
|
|
74
|
+
| Theme | Description |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `default` | Original palette |
|
|
77
|
+
| `tokyo-night` | [Tokyo Night](https://github.com/enkia/tokyo-night-vscode-theme) Dark |
|
|
78
|
+
| `tokyo-night-storm` | Tokyo Night Storm |
|
|
79
|
+
| `tokyo-night-light` | Tokyo Night Light |
|
|
80
|
+
| `catppuccin-mocha` | [Catppuccin](https://github.com/catppuccin/catppuccin) Mocha |
|
|
81
|
+
| `dracula` | [Dracula](https://draculatheme.com/) |
|
|
82
|
+
| `solarized-dark` | [Solarized](https://ethanschoonover.com/solarized/) Dark |
|
|
83
|
+
| `gruvbox-dark` | [Gruvbox](https://github.com/morhetz/gruvbox) Dark |
|
|
84
|
+
| `nord` | [Nord](https://www.nordtheme.com/) |
|
|
85
|
+
| `one-dark` | [One Dark](https://github.com/Binaryify/OneDark-Pro) |
|
|
86
|
+
| `github-dark` | [GitHub Dark](https://github.com/primer/primitives) |
|
|
87
|
+
| `kanagawa` | [Kanagawa](https://github.com/rebelot/kanagawa.nvim) |
|
|
88
|
+
| `rose-pine` | [Rosé Pine](https://rosepinetheme.com/) |
|
|
89
|
+
|
|
90
|
+
## What it shows
|
|
91
|
+
|
|
92
|
+
**Line 1:** Model name, context usage %, directory, git status, session duration, effort level
|
|
93
|
+
|
|
94
|
+
**Lines 2+:** Rate limit usage (current 5-hour window, weekly, extra credits when active)
|
|
95
|
+
|
|
96
|
+
### Git status
|
|
97
|
+
|
|
98
|
+
Inspired by [git-prompt.sh](https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh). Displays branch name and rich status indicators:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
(main *+$% ↑1↓2|REBASE 3/5)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Symbol | Meaning |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `*` | Unstaged changes |
|
|
107
|
+
| `+` | Staged changes |
|
|
108
|
+
| `$` | Stash entries exist |
|
|
109
|
+
| `%` | Untracked files |
|
|
110
|
+
| `↑N` | N commits ahead of upstream |
|
|
111
|
+
| `↓N` | N commits behind upstream |
|
|
112
|
+
| `\|REBASE` | Rebase in progress (with step/total) |
|
|
113
|
+
| `\|MERGING` | Merge in progress |
|
|
114
|
+
| `\|CHERRY-PICKING` | Cherry-pick in progress |
|
|
115
|
+
| `\|REVERTING` | Revert in progress |
|
|
116
|
+
| `\|BISECTING` | Bisect in progress |
|
|
117
|
+
| `\|AM` | `git am` in progress |
|
|
118
|
+
|
|
119
|
+
Detached HEAD is shown in red with a tag or short SHA.
|
|
120
|
+
|
|
121
|
+
## Uninstall
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# Deno
|
|
125
|
+
deno run -A https://raw.githubusercontent.com/babarot/c-c-statusline/main/bin/install.ts --uninstall
|
|
126
|
+
|
|
127
|
+
# npx
|
|
128
|
+
npx @babarot/c-c-statusline --uninstall
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
MIT
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
|
|
8
|
+
const HOME = os.homedir();
|
|
9
|
+
const CLAUDE_DIR = path.join(HOME, ".claude");
|
|
10
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, "settings.json");
|
|
11
|
+
const BINARY_NAME = "c-c-statusline";
|
|
12
|
+
const BINARY_DEST = path.join(CLAUDE_DIR, BINARY_NAME);
|
|
13
|
+
const REPO = "babarot/c-c-statusline";
|
|
14
|
+
|
|
15
|
+
const blue = "\x1b[38;2;0;153;255m";
|
|
16
|
+
const green = "\x1b[38;2;0;175;80m";
|
|
17
|
+
const red = "\x1b[38;2;255;85;85m";
|
|
18
|
+
const yellow = "\x1b[38;2;230;200;0m";
|
|
19
|
+
const dim = "\x1b[2m";
|
|
20
|
+
const reset = "\x1b[0m";
|
|
21
|
+
|
|
22
|
+
const log = (msg) => console.log(` ${msg}`);
|
|
23
|
+
const success = (msg) => console.log(` ${green}✓${reset} ${msg}`);
|
|
24
|
+
const warn = (msg) => console.log(` ${yellow}!${reset} ${msg}`);
|
|
25
|
+
const fail = (msg) => console.error(` ${red}✗${reset} ${msg}`);
|
|
26
|
+
|
|
27
|
+
function detectPlatform() {
|
|
28
|
+
const platform = os.platform();
|
|
29
|
+
const arch = os.arch();
|
|
30
|
+
|
|
31
|
+
const osMap = { darwin: "darwin", linux: "linux" };
|
|
32
|
+
const archMap = { arm64: "arm64", x64: "x86_64" };
|
|
33
|
+
|
|
34
|
+
const osName = osMap[platform];
|
|
35
|
+
const archName = archMap[arch];
|
|
36
|
+
if (!osName || !archName) return null;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
os: osName,
|
|
40
|
+
arch: archName,
|
|
41
|
+
asset: `${BINARY_NAME}-${osName}-${archName}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function getLatestRelease() {
|
|
46
|
+
const resp = await fetch(
|
|
47
|
+
`https://api.github.com/repos/${REPO}/releases/latest`,
|
|
48
|
+
{ headers: { Accept: "application/vnd.github.v3+json" } },
|
|
49
|
+
);
|
|
50
|
+
if (!resp.ok) throw new Error(`GitHub API error: ${resp.status}`);
|
|
51
|
+
const data = await resp.json();
|
|
52
|
+
|
|
53
|
+
const assets = {};
|
|
54
|
+
for (const asset of data.assets) {
|
|
55
|
+
assets[asset.name] = asset.browser_download_url;
|
|
56
|
+
}
|
|
57
|
+
return { tag: data.tag_name, assets };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function downloadAndVerify(url, checksumUrl, assetName) {
|
|
61
|
+
const resp = await fetch(url);
|
|
62
|
+
if (!resp.ok) throw new Error(`Download failed: ${resp.status}`);
|
|
63
|
+
const binary = Buffer.from(await resp.arrayBuffer());
|
|
64
|
+
|
|
65
|
+
if (!checksumUrl) {
|
|
66
|
+
warn("Checksum file not found, skipping verification");
|
|
67
|
+
return binary;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const csResp = await fetch(checksumUrl);
|
|
71
|
+
if (!csResp.ok) {
|
|
72
|
+
warn("Checksum file not found, skipping verification");
|
|
73
|
+
return binary;
|
|
74
|
+
}
|
|
75
|
+
const checksumText = await csResp.text();
|
|
76
|
+
|
|
77
|
+
const line = checksumText.split("\n").find((l) => l.includes(assetName));
|
|
78
|
+
if (!line) {
|
|
79
|
+
warn("Checksum entry not found, skipping verification");
|
|
80
|
+
return binary;
|
|
81
|
+
}
|
|
82
|
+
const expectedHash = line.split(/\s+/)[0];
|
|
83
|
+
|
|
84
|
+
const actualHash = crypto.createHash("sha256").update(binary).digest("hex");
|
|
85
|
+
if (actualHash !== expectedHash) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Checksum mismatch!\n expected: ${expectedHash}\n actual: ${actualHash}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return binary;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readSettings() {
|
|
95
|
+
try {
|
|
96
|
+
return JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8"));
|
|
97
|
+
} catch {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function writeSettings(settings) {
|
|
103
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function uninstall() {
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(` ${blue}Claude Code Statusline Uninstaller${reset}`);
|
|
109
|
+
console.log(` ${dim}──────────────────────────────────${reset}`);
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
const backup = BINARY_DEST + ".bak";
|
|
113
|
+
|
|
114
|
+
if (fs.existsSync(backup)) {
|
|
115
|
+
fs.renameSync(backup, BINARY_DEST);
|
|
116
|
+
success(`Restored previous statusline from ${dim}backup${reset}`);
|
|
117
|
+
} else if (fs.existsSync(BINARY_DEST)) {
|
|
118
|
+
fs.unlinkSync(BINARY_DEST);
|
|
119
|
+
success(`Removed ${dim}${BINARY_NAME}${reset}`);
|
|
120
|
+
} else {
|
|
121
|
+
warn("No statusline binary found — nothing to remove");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Clean up old statusline.ts
|
|
125
|
+
const oldTs = path.join(CLAUDE_DIR, "statusline.ts");
|
|
126
|
+
if (fs.existsSync(oldTs)) {
|
|
127
|
+
fs.unlinkSync(oldTs);
|
|
128
|
+
success(`Removed old ${dim}statusline.ts${reset}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const settings = readSettings();
|
|
133
|
+
if (settings.statusLine) {
|
|
134
|
+
delete settings.statusLine;
|
|
135
|
+
writeSettings(settings);
|
|
136
|
+
success(`Removed statusLine from ${dim}settings.json${reset}`);
|
|
137
|
+
} else {
|
|
138
|
+
success("Settings already clean");
|
|
139
|
+
}
|
|
140
|
+
} catch {
|
|
141
|
+
fail(`Could not parse ${SETTINGS_FILE} — fix it manually`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log();
|
|
146
|
+
log(`${green}Done!${reset} Restart Claude Code to apply changes.`);
|
|
147
|
+
console.log();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function install(extraArgs) {
|
|
151
|
+
console.log();
|
|
152
|
+
console.log(` ${blue}Claude Code Statusline Installer${reset}`);
|
|
153
|
+
console.log(` ${dim}────────────────────────────────${reset}`);
|
|
154
|
+
console.log();
|
|
155
|
+
|
|
156
|
+
const platform = detectPlatform();
|
|
157
|
+
if (!platform) {
|
|
158
|
+
fail(`Unsupported platform: ${os.platform()} ${os.arch()}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
success(`Platform: ${platform.os}/${platform.arch}`);
|
|
162
|
+
|
|
163
|
+
let release;
|
|
164
|
+
try {
|
|
165
|
+
release = await getLatestRelease();
|
|
166
|
+
} catch (e) {
|
|
167
|
+
fail(`Failed to fetch latest release: ${e}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
success(`Latest release: ${release.tag}`);
|
|
171
|
+
|
|
172
|
+
const downloadUrl = release.assets[platform.asset];
|
|
173
|
+
if (!downloadUrl) {
|
|
174
|
+
fail(`No binary found for ${platform.asset}`);
|
|
175
|
+
log(` Available: ${Object.keys(release.assets).join(", ")}`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
log(`Downloading ${dim}${platform.asset}${reset}...`);
|
|
180
|
+
let binary;
|
|
181
|
+
try {
|
|
182
|
+
binary = await downloadAndVerify(
|
|
183
|
+
downloadUrl,
|
|
184
|
+
release.assets["checksums.txt"],
|
|
185
|
+
platform.asset,
|
|
186
|
+
);
|
|
187
|
+
} catch (e) {
|
|
188
|
+
fail(`${e}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
success("Downloaded and verified");
|
|
192
|
+
|
|
193
|
+
if (!fs.existsSync(CLAUDE_DIR)) {
|
|
194
|
+
fs.mkdirSync(CLAUDE_DIR, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (fs.existsSync(BINARY_DEST)) {
|
|
198
|
+
fs.renameSync(BINARY_DEST, BINARY_DEST + ".bak");
|
|
199
|
+
warn(`Backed up existing binary to ${dim}${BINARY_NAME}.bak${reset}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(BINARY_DEST, binary, { mode: 0o755 });
|
|
203
|
+
success(`Installed to ${dim}${BINARY_DEST}${reset}`);
|
|
204
|
+
|
|
205
|
+
let command = `"$HOME/.claude/${BINARY_NAME}"`;
|
|
206
|
+
if (extraArgs.length > 0) {
|
|
207
|
+
command += " " + extraArgs.join(" ");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const settings = readSettings();
|
|
211
|
+
const statusLineConfig = { type: "command", command };
|
|
212
|
+
|
|
213
|
+
const existing = settings.statusLine;
|
|
214
|
+
if (existing?.type === "command" && existing?.command === command) {
|
|
215
|
+
success("Settings already configured");
|
|
216
|
+
} else {
|
|
217
|
+
settings.statusLine = statusLineConfig;
|
|
218
|
+
writeSettings(settings);
|
|
219
|
+
success(`Updated ${dim}settings.json${reset}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log();
|
|
223
|
+
log(`${green}Done!${reset} Restart Claude Code to see your new status line.`);
|
|
224
|
+
if (extraArgs.length > 0) {
|
|
225
|
+
log(`${dim}Options: ${extraArgs.join(" ")}${reset}`);
|
|
226
|
+
}
|
|
227
|
+
console.log();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Main ────────────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
const argv = process.argv.slice(2);
|
|
233
|
+
|
|
234
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
235
|
+
console.log(`
|
|
236
|
+
${blue}Claude Code Statusline${reset}
|
|
237
|
+
|
|
238
|
+
${dim}Usage:${reset}
|
|
239
|
+
npx @babarot/c-c-statusline Install with defaults
|
|
240
|
+
npx @babarot/c-c-statusline --uninstall Uninstall
|
|
241
|
+
|
|
242
|
+
${dim}Options:${reset}
|
|
243
|
+
--bar-style <dot|block|fill> Bar style (default: dot)
|
|
244
|
+
--path-style <parent|full|short|basename> Path style (default: parent)
|
|
245
|
+
--uninstall Remove statusline
|
|
246
|
+
--help Show this help
|
|
247
|
+
`);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (argv.includes("--uninstall")) {
|
|
252
|
+
uninstall();
|
|
253
|
+
} else {
|
|
254
|
+
const extraArgs = [];
|
|
255
|
+
for (let i = 0; i < argv.length; i++) {
|
|
256
|
+
if (argv[i] === "--bar-style" && argv[i + 1]) {
|
|
257
|
+
extraArgs.push("--bar-style", argv[++i]);
|
|
258
|
+
} else if (argv[i] === "--path-style" && argv[i + 1]) {
|
|
259
|
+
extraArgs.push("--path-style", argv[++i]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
install(extraArgs);
|
|
263
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@babarot/c-c-statusline",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A Deno-powered status line for Claude Code CLI showing model, context usage, rate limits, and more",
|
|
5
|
+
"bin": {
|
|
6
|
+
"c-c-statusline": "./bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"claude",
|
|
10
|
+
"claude-code",
|
|
11
|
+
"statusline",
|
|
12
|
+
"deno",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": "babarot",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/babarot/c-c-statusline"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"bin/install.js"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
}
|
|
27
|
+
}
|