@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.
Files changed (3) hide show
  1. package/README.md +133 -0
  2. package/bin/install.js +263 -0
  3. 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
+ }