claude-blip 1.0.2 → 1.0.4
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 +16 -55
- package/bin/setup.js +31 -69
- package/package.json +1 -1
- package/statusline.js +8 -52
package/README.md
CHANGED
|
@@ -2,87 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
# claude-blip
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
*Zero dependencies. One command. Just a blip.*
|
|
5
|
+
A minimal statusline for Claude Code. One file, zero dependencies.
|
|
7
6
|
|
|
8
7
|
[](https://www.npmjs.com/package/claude-blip)
|
|
9
8
|
[](https://packagephobia.com/result?p=claude-blip)
|
|
10
|
-
[](LICENSE)
|
|
11
9
|
|
|
12
10
|

|
|
13
11
|
|
|
14
|
-
*
|
|
12
|
+
*green when fresh, yellow when warm, red when you're cooked*
|
|
15
13
|
|
|
16
14
|
</div>
|
|
17
15
|
|
|
18
|
-
Five segments. No config files. No themes. No plugins.
|
|
19
|
-
It just shows you what matters and gets out of the way.
|
|
20
|
-
|
|
21
16
|
## Install
|
|
22
17
|
|
|
23
18
|
```sh
|
|
24
19
|
npx claude-blip
|
|
25
20
|
```
|
|
26
21
|
|
|
27
|
-
|
|
22
|
+
Restart Claude Code.
|
|
28
23
|
|
|
29
|
-
##
|
|
24
|
+
## Segments
|
|
30
25
|
|
|
31
|
-
| Segment |
|
|
32
|
-
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
| **Context** | Usage bar + token count | green → yellow → red |
|
|
26
|
+
| Segment | Shows | Style |
|
|
27
|
+
|---------|-------|-------|
|
|
28
|
+
| Project | Directory name | dim |
|
|
29
|
+
| Branch | Current git branch | dim |
|
|
30
|
+
| Model | opus, sonnet, haiku | dim |
|
|
31
|
+
| Context | Usage bar + token count | green / yellow / red |
|
|
38
32
|
|
|
39
|
-
|
|
33
|
+
The context bar scales to 80% - roughly where Claude starts compressing history.
|
|
40
34
|
|
|
41
|
-
Terminal too narrow? Segments drop
|
|
35
|
+
Terminal too narrow? Segments drop from the left. Context bar stays.
|
|
42
36
|
|
|
43
|
-
##
|
|
37
|
+
## Uninstall
|
|
44
38
|
|
|
45
39
|
```sh
|
|
46
|
-
npx claude-blip
|
|
47
|
-
npx claude-blip --project # .claude/settings.json (shareable with team)
|
|
48
|
-
npx claude-blip --local # .claude/settings.local.json (just you)
|
|
49
|
-
npx claude-blip --uninstall # clean removal from all scopes
|
|
40
|
+
npx claude-blip --uninstall
|
|
50
41
|
```
|
|
51
42
|
|
|
52
43
|
## How it works
|
|
53
44
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Claude Code pipes session JSON to your statusline script via stdin. This script reads it, picks out the useful bits, formats them, writes one line to stdout. That's the whole thing.
|
|
57
|
-
|
|
58
|
-
The context bar scales to 80% capacity — that's roughly where Claude starts compressing context, so 100% on the bar means "you're about to lose history."
|
|
59
|
-
|
|
60
|
-
<details>
|
|
61
|
-
<summary><strong>Debug mode</strong></summary>
|
|
62
|
-
|
|
63
|
-
Set `debug: true` in the CONFIG object at top of `statusline.js` to dump the full JSON payload to stderr:
|
|
64
|
-
|
|
65
|
-
```js
|
|
66
|
-
const CONFIG = {
|
|
67
|
-
debug: true, // logs to stderr
|
|
68
|
-
// ...
|
|
69
|
-
};
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
</details>
|
|
73
|
-
|
|
74
|
-
## Contributing
|
|
75
|
-
|
|
76
|
-
Found a bug? Want a feature? PRs welcome. Keep it simple — the whole point is one file with zero dependencies.
|
|
45
|
+
Claude Code pipes session JSON via stdin. This script reads it, formats one line, writes it to stdout. ~140 lines of Node.js.
|
|
77
46
|
|
|
78
47
|
## License
|
|
79
48
|
|
|
80
|
-
MIT
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
<div align="center">
|
|
85
|
-
|
|
86
|
-
*Built for developers with mass context window anxiety.*
|
|
87
|
-
|
|
88
|
-
</div>
|
|
49
|
+
MIT
|
package/bin/setup.js
CHANGED
|
@@ -1,68 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// claude-blip setup
|
|
2
|
+
// claude-blip setup - one command, done.
|
|
3
3
|
//
|
|
4
4
|
// Usage:
|
|
5
|
-
// npx claude-blip (
|
|
6
|
-
// npx claude-blip --
|
|
7
|
-
// npx claude-blip --local (this project, gitignored)
|
|
8
|
-
// npx claude-blip --uninstall (remove from all scopes)
|
|
5
|
+
// npx claude-blip (install)
|
|
6
|
+
// npx claude-blip --uninstall (remove)
|
|
9
7
|
|
|
10
8
|
const fs = require("fs");
|
|
11
9
|
const path = require("path");
|
|
12
10
|
const os = require("os");
|
|
13
11
|
|
|
14
12
|
const HOOK_SOURCE = path.resolve(__dirname, "..", "statusline.js");
|
|
13
|
+
const HOOKS_DIR = path.join(os.homedir(), ".claude", "hooks");
|
|
14
|
+
const SETTINGS_PATH = path.join(os.homedir(), ".claude", "settings.json");
|
|
15
15
|
|
|
16
|
-
const
|
|
17
|
-
global: () => path.join(os.homedir(), ".claude", "settings.json"),
|
|
18
|
-
project: () => path.join(process.cwd(), ".claude", "settings.json"),
|
|
19
|
-
local: () => path.join(process.cwd(), ".claude", "settings.local.json"),
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const args = process.argv.slice(2);
|
|
23
|
-
const uninstall = args.includes("--uninstall");
|
|
24
|
-
const scope = args.includes("--project")
|
|
25
|
-
? "project"
|
|
26
|
-
: args.includes("--local")
|
|
27
|
-
? "local"
|
|
28
|
-
: "global";
|
|
16
|
+
const uninstall = process.argv.includes("--uninstall");
|
|
29
17
|
|
|
30
18
|
const DIM = "\x1b[2m";
|
|
31
19
|
const BOLD = "\x1b[1m";
|
|
32
20
|
const GREEN = "\x1b[32m";
|
|
33
|
-
const RED = "\x1b[31m";
|
|
34
21
|
const RESET = "\x1b[0m";
|
|
35
22
|
|
|
36
23
|
function log(msg) {
|
|
37
24
|
console.log(` ${msg}`);
|
|
38
25
|
}
|
|
39
26
|
|
|
40
|
-
function getInstallDir() {
|
|
41
|
-
if (scope === "global") {
|
|
42
|
-
return path.join(os.homedir(), ".claude", "hooks");
|
|
43
|
-
}
|
|
44
|
-
return path.join(process.cwd(), ".claude", "hooks");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
27
|
function install() {
|
|
48
|
-
const
|
|
49
|
-
const dest = path.join(installDir, "statusline.js");
|
|
50
|
-
const settingsPath = SCOPES[scope]();
|
|
51
|
-
const settingsDir = path.dirname(settingsPath);
|
|
28
|
+
const dest = path.join(HOOKS_DIR, "statusline.js");
|
|
52
29
|
|
|
53
30
|
// 1. Copy the statusline script
|
|
54
|
-
fs.mkdirSync(
|
|
31
|
+
fs.mkdirSync(HOOKS_DIR, { recursive: true });
|
|
55
32
|
fs.copyFileSync(HOOK_SOURCE, dest);
|
|
56
33
|
fs.chmodSync(dest, 0o755);
|
|
57
34
|
|
|
58
35
|
// 2. Update settings.json
|
|
59
|
-
fs.mkdirSync(settingsDir, { recursive: true });
|
|
60
36
|
let settings = {};
|
|
61
|
-
if (fs.existsSync(
|
|
37
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
62
38
|
try {
|
|
63
|
-
settings = JSON.parse(fs.readFileSync(
|
|
39
|
+
settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf8"));
|
|
64
40
|
} catch {
|
|
65
|
-
// Corrupted settings
|
|
41
|
+
// Corrupted settings - start fresh
|
|
66
42
|
}
|
|
67
43
|
}
|
|
68
44
|
|
|
@@ -71,65 +47,51 @@ function install() {
|
|
|
71
47
|
command: dest,
|
|
72
48
|
};
|
|
73
49
|
|
|
74
|
-
fs.writeFileSync(
|
|
50
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
75
51
|
|
|
76
52
|
console.log();
|
|
77
53
|
log(`${GREEN}${BOLD}blip${RESET} ${DIM}installed${RESET}`);
|
|
78
|
-
log(`${DIM}scope: ${RESET}${scope}`);
|
|
79
54
|
log(`${DIM}hook: ${RESET}${dest}`);
|
|
80
|
-
log(`${DIM}config: ${RESET}${
|
|
55
|
+
log(`${DIM}config: ${RESET}${SETTINGS_PATH}`);
|
|
81
56
|
console.log();
|
|
82
57
|
log(`${DIM}Restart Claude Code to see it.${RESET}`);
|
|
83
58
|
console.log();
|
|
84
59
|
}
|
|
85
60
|
|
|
86
|
-
function removeStatusLine(settingsPath) {
|
|
87
|
-
if (!fs.existsSync(settingsPath)) return false;
|
|
88
|
-
try {
|
|
89
|
-
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
90
|
-
if (!settings.statusLine) return false;
|
|
91
|
-
delete settings.statusLine;
|
|
92
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
93
|
-
return true;
|
|
94
|
-
} catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
61
|
function uninstallAll() {
|
|
100
62
|
let removed = false;
|
|
101
63
|
|
|
102
|
-
// Remove from
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
64
|
+
// Remove statusLine from global settings
|
|
65
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
66
|
+
try {
|
|
67
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf8"));
|
|
68
|
+
if (settings.statusLine) {
|
|
69
|
+
delete settings.statusLine;
|
|
70
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + "\n");
|
|
71
|
+
removed = true;
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
// ignore
|
|
107
75
|
}
|
|
108
76
|
}
|
|
109
77
|
|
|
110
|
-
// Remove hook
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (const loc of locations) {
|
|
116
|
-
if (fs.existsSync(loc)) {
|
|
117
|
-
fs.unlinkSync(loc);
|
|
118
|
-
log(`${DIM}Removed ${loc}${RESET}`);
|
|
119
|
-
removed = true;
|
|
120
|
-
}
|
|
78
|
+
// Remove hook file
|
|
79
|
+
const hookPath = path.join(HOOKS_DIR, "statusline.js");
|
|
80
|
+
if (fs.existsSync(hookPath)) {
|
|
81
|
+
fs.unlinkSync(hookPath);
|
|
82
|
+
removed = true;
|
|
121
83
|
}
|
|
122
84
|
|
|
123
85
|
console.log();
|
|
124
86
|
if (removed) {
|
|
125
87
|
log(`${GREEN}${BOLD}blip${RESET} ${DIM}uninstalled${RESET}`);
|
|
126
88
|
} else {
|
|
127
|
-
log(`${DIM}Nothing to remove
|
|
89
|
+
log(`${DIM}Nothing to remove - blip wasn't installed.${RESET}`);
|
|
128
90
|
}
|
|
129
91
|
console.log();
|
|
130
92
|
}
|
|
131
93
|
|
|
132
|
-
//
|
|
94
|
+
// -----------------------------------------------------------------
|
|
133
95
|
|
|
134
96
|
if (uninstall) {
|
|
135
97
|
uninstallAll();
|
package/package.json
CHANGED
package/statusline.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// claude-blip
|
|
3
|
-
// Shows:
|
|
2
|
+
// claude-blip - a single-file statusline for Claude Code
|
|
3
|
+
// Shows: project │ branch │ model │ context usage
|
|
4
4
|
|
|
5
5
|
const { execSync } = require("child_process");
|
|
6
|
-
const fs = require("fs");
|
|
7
6
|
const path = require("path");
|
|
8
|
-
const os = require("os");
|
|
9
7
|
|
|
10
8
|
// ─────────────────────────────────────────────────────────────
|
|
11
9
|
// Config
|
|
@@ -23,7 +21,6 @@ const CONFIG = {
|
|
|
23
21
|
// ─────────────────────────────────────────────────────────────
|
|
24
22
|
const ANSI = {
|
|
25
23
|
dim: "\x1b[2m",
|
|
26
|
-
bold: "\x1b[1m",
|
|
27
24
|
reset: "\x1b[0m",
|
|
28
25
|
green: "\x1b[38;5;108m",
|
|
29
26
|
yellow: "\x1b[38;5;222m",
|
|
@@ -31,7 +28,6 @@ const ANSI = {
|
|
|
31
28
|
};
|
|
32
29
|
|
|
33
30
|
const dim = (s) => `${ANSI.dim}${s}${ANSI.reset}`;
|
|
34
|
-
const bold = (s) => `${ANSI.bold}${s}${ANSI.reset}`;
|
|
35
31
|
const color = (s, c) => `${c}${s}${ANSI.reset}`;
|
|
36
32
|
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
37
33
|
|
|
@@ -51,39 +47,6 @@ function formatTokens(n) {
|
|
|
51
47
|
// Data Fetchers
|
|
52
48
|
// ─────────────────────────────────────────────────────────────
|
|
53
49
|
|
|
54
|
-
function getCurrentTask(sessionId) {
|
|
55
|
-
if (!sessionId) return null;
|
|
56
|
-
|
|
57
|
-
const todosDir = path.join(os.homedir(), ".claude", "todos");
|
|
58
|
-
if (!fs.existsSync(todosDir)) return null;
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
const files = fs
|
|
62
|
-
.readdirSync(todosDir)
|
|
63
|
-
.filter(
|
|
64
|
-
(f) =>
|
|
65
|
-
f.startsWith(sessionId) &&
|
|
66
|
-
f.includes("-agent-") &&
|
|
67
|
-
f.endsWith(".json"),
|
|
68
|
-
)
|
|
69
|
-
.map((f) => ({
|
|
70
|
-
name: f,
|
|
71
|
-
mtime: fs.statSync(path.join(todosDir, f)).mtime,
|
|
72
|
-
}))
|
|
73
|
-
.sort((a, b) => b.mtime - a.mtime);
|
|
74
|
-
|
|
75
|
-
if (files.length === 0) return null;
|
|
76
|
-
|
|
77
|
-
const todos = JSON.parse(
|
|
78
|
-
fs.readFileSync(path.join(todosDir, files[0].name), "utf8"),
|
|
79
|
-
);
|
|
80
|
-
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
81
|
-
return inProgress?.activeForm || null;
|
|
82
|
-
} catch {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
50
|
function getContextDisplay(ctxWindow) {
|
|
88
51
|
const rawUsed = ctxWindow?.used_percentage;
|
|
89
52
|
const maxTokens = ctxWindow?.context_window_size || 200_000;
|
|
@@ -121,21 +84,14 @@ function buildStatusline(input) {
|
|
|
121
84
|
}
|
|
122
85
|
|
|
123
86
|
const dir = data.workspace?.current_dir || process.cwd();
|
|
124
|
-
const sessionId = data.session_id || "";
|
|
125
87
|
|
|
126
88
|
const parts = [];
|
|
127
89
|
|
|
128
|
-
// 1.
|
|
129
|
-
const task = getCurrentTask(sessionId);
|
|
130
|
-
if (task) {
|
|
131
|
-
parts.push(bold(task));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 2. Project name
|
|
90
|
+
// 1. Project name
|
|
135
91
|
const project = path.basename(dir);
|
|
136
92
|
parts.push(dim(project));
|
|
137
93
|
|
|
138
|
-
//
|
|
94
|
+
// 2. Git branch
|
|
139
95
|
try {
|
|
140
96
|
const branch = execSync("git branch --show-current", {
|
|
141
97
|
cwd: dir,
|
|
@@ -147,7 +103,7 @@ function buildStatusline(input) {
|
|
|
147
103
|
// Not a git repo or git not available
|
|
148
104
|
}
|
|
149
105
|
|
|
150
|
-
//
|
|
106
|
+
// 3. Model
|
|
151
107
|
const model = data.model?.display_name;
|
|
152
108
|
if (model) {
|
|
153
109
|
// Extract tier name: "Opus 4.6" → "opus", "Sonnet 4.5" → "sonnet"
|
|
@@ -155,13 +111,13 @@ function buildStatusline(input) {
|
|
|
155
111
|
parts.push(dim(tier));
|
|
156
112
|
}
|
|
157
113
|
|
|
158
|
-
//
|
|
114
|
+
// 4. Context window (bar + token count)
|
|
159
115
|
const ctx = getContextDisplay(data.context_window);
|
|
160
116
|
if (ctx) {
|
|
161
117
|
parts.push(ctx);
|
|
162
118
|
}
|
|
163
119
|
|
|
164
|
-
// Truncate if wider than terminal
|
|
120
|
+
// Truncate if wider than terminal - drops segments from the left
|
|
165
121
|
const sep = dim(" \u00B7 ");
|
|
166
122
|
const cols = process.stdout.columns || 80;
|
|
167
123
|
while (parts.length > 1 && stripAnsi(parts.join(sep)).length > cols) {
|
|
@@ -182,6 +138,6 @@ process.stdin.on("end", () => {
|
|
|
182
138
|
const output = buildStatusline(input);
|
|
183
139
|
process.stdout.write(output);
|
|
184
140
|
} catch {
|
|
185
|
-
// Silent fail
|
|
141
|
+
// Silent fail - a broken statusline should never interrupt your flow
|
|
186
142
|
}
|
|
187
143
|
});
|