Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions claude-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# statusline.js

A [Claude Code](https://claude.com/claude-code) `statusLine` script that shows:

- 📁 current folder
- 🌿 git branch
- 🤖 active model
- context-window usage as a colored progress bar (green/yellow/red)

## Setup

Add to `~/.claude/settings.json` (or your project's `.claude/settings.json`):

```json
{
"statusLine": {
"type": "command",
"command": "node /path/to/claude-code/statusline.js"
}
}
```

Requires Node.js (already a dependency of Claude Code itself) and `git` on `PATH`.

## Context window size

Context usage defaults to a 200K token window. Override with an env var if
your model uses a different window, e.g.:

```json
{
"statusLine": {
"type": "command",
"command": "CLAUDE_CONTEXT_WINDOW=1000000 node /path/to/claude-code/statusline.js"
}
}
```
138 changes: 138 additions & 0 deletions claude-code/statusline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#!/usr/bin/env node
// Claude Code statusLine script.
// Shows: folder, git branch, model, and a context-usage progress bar.
// Configure in ~/.claude/settings.json:
// "statusLine": { "type": "command", "command": "node /path/to/statusline.js" }

const fs = require("fs");
const path = require("path");
const { execSync } = require("child_process");

const RESET = "\x1b[0m";
const DIM = "\x1b[2m";
const CYAN = "\x1b[36m";
const MAGENTA = "\x1b[35m";
const BLUE = "\x1b[34m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const RED = "\x1b[31m";

const CONTEXT_WINDOW =
Number(process.env.CLAUDE_CONTEXT_WINDOW) > 0
? Number(process.env.CLAUDE_CONTEXT_WINDOW)
: 200000;
const BAR_WIDTH = 10;

function readStdin() {
try {
return fs.readFileSync(0, "utf8");
} catch {
return "";
}
}

function getFolder(cwd) {
if (!cwd) return "~";
return path.basename(cwd) || cwd;
}

function getGitBranch(cwd) {
try {
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
cwd,
stdio: ["ignore", "pipe", "ignore"],
})
.toString()
.trim();
return branch === "HEAD" ? "detached" : branch;
} catch {
return null;
}
}

function getContextUsage(transcriptPath) {
if (!transcriptPath || !fs.existsSync(transcriptPath)) return null;

let lines;
try {
lines = fs.readFileSync(transcriptPath, "utf8").split("\n");
} catch {
return null;
}

for (let i = lines.length - 1; i >= 0; i--) {
const line = lines[i].trim();
if (!line) continue;
let entry;
try {
entry = JSON.parse(line);
} catch {
continue;
}
const usage = entry?.message?.usage;
if (usage) {
const used =
(usage.input_tokens || 0) +
(usage.cache_creation_input_tokens || 0) +
(usage.cache_read_input_tokens || 0);
if (used > 0) return used;
}
}
return null;
}

function renderBar(percent) {
const filled = Math.round((clamp(percent, 0, 100) / 100) * BAR_WIDTH);
const empty = BAR_WIDTH - filled;
const color = percent >= 90 ? RED : percent >= 70 ? YELLOW : GREEN;
return `${color}${"█".repeat(filled)}${DIM}${"░".repeat(empty)}${RESET}`;
}

function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}

function formatTokens(n) {
if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
return String(n);
}

function main() {
const raw = readStdin();
let data = {};
try {
data = JSON.parse(raw);
} catch {
data = {};
}

const cwd = data?.workspace?.current_dir || data?.cwd || process.cwd();
const modelName = data?.model?.display_name || data?.model?.id || "unknown";
const transcriptPath = data?.transcript_path;

const folder = getFolder(cwd);
const branch = getGitBranch(cwd);
const used = getContextUsage(transcriptPath);

const parts = [`${CYAN}📁 ${folder}${RESET}`];

if (branch) {
parts.push(`${MAGENTA}🌿 ${branch}${RESET}`);
}

parts.push(`${BLUE}🤖 ${modelName}${RESET}`);

if (used !== null) {
const percent = clamp((used / CONTEXT_WINDOW) * 100, 0, 100);
const bar = renderBar(percent);
parts.push(
`${bar} ${percent.toFixed(0)}% (${formatTokens(used)}/${formatTokens(
CONTEXT_WINDOW
)})`
);
}

process.stdout.write(parts.join(`${DIM} | ${RESET}`) + "\n");
}

main();