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 CHANGED
@@ -2,87 +2,48 @@
2
2
 
3
3
  # claude-blip
4
4
 
5
- **A single-file statusline for Claude Code.**
6
- *Zero dependencies. One command. Just a blip.*
5
+ A minimal statusline for Claude Code. One file, zero dependencies.
7
6
 
8
7
  [![npm](https://img.shields.io/npm/v/claude-blip)](https://www.npmjs.com/package/claude-blip)
9
8
  [![install size](https://packagephobia.com/badge?p=claude-blip)](https://packagephobia.com/result?p=claude-blip)
10
- [![license](https://img.shields.io/npm/l/claude-blip)](LICENSE)
11
9
 
12
10
  ![variants](variants.gif)
13
11
 
14
- *dim when chill, yellow when warm, red when you should probably wrap up*
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
- That's it. Restart Claude Code. Done.
22
+ Restart Claude Code.
28
23
 
29
- ## What you get
24
+ ## Segments
30
25
 
31
- | Segment | What it shows | Style |
32
- |---------|--------------|-------|
33
- | **Task** | What Claude is working on right now | **bold** |
34
- | **Project** | Directory name | dim |
35
- | **Branch** | Current git branch | dim |
36
- | **Model** | opus, sonnet, haiku | dim |
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
- Everything is dim except the task your eyes go to the thing that matters.
33
+ The context bar scales to 80% - roughly where Claude starts compressing history.
40
34
 
41
- Terminal too narrow? Segments drop off the left. Context bar always stays.
35
+ Terminal too narrow? Segments drop from the left. Context bar stays.
42
36
 
43
- ## Scopes
37
+ ## Uninstall
44
38
 
45
39
  ```sh
46
- npx claude-blip # global (recommended)
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
- One file. ~150 lines. Node.js only (ships with Claude Code nothing to install).
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 — do whatever you want with it.
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 one command, done.
2
+ // claude-blip setup - one command, done.
3
3
  //
4
4
  // Usage:
5
- // npx claude-blip (global — recommended)
6
- // npx claude-blip --project (this project, shareable)
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 SCOPES = {
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 installDir = getInstallDir();
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(installDir, { recursive: true });
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(settingsPath)) {
37
+ if (fs.existsSync(SETTINGS_PATH)) {
62
38
  try {
63
- settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
39
+ settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, "utf8"));
64
40
  } catch {
65
- // Corrupted settings start fresh
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(settingsPath, JSON.stringify(settings, null, 2) + "\n");
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}${settingsPath}`);
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 all settings scopes
103
- for (const [name, getPath] of Object.entries(SCOPES)) {
104
- if (removeStatusLine(getPath())) {
105
- log(`${DIM}Removed statusLine from ${name} settings${RESET}`);
106
- removed = true;
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 files
111
- const locations = [
112
- path.join(os.homedir(), ".claude", "hooks", "statusline.js"),
113
- path.join(process.cwd(), ".claude", "hooks", "statusline.js"),
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 blip wasn't installed.${RESET}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-blip",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "A single-file statusline for Claude Code. Zero dependencies. Just a blip.",
5
5
  "license": "MIT",
6
6
  "author": "davebream",
package/statusline.js CHANGED
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env node
2
- // claude-blip a single-file statusline for Claude Code
3
- // Shows: task │ project │ branch │ model │ context usage
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. Current task (bold — only segment that stands out)
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
- // 3. Git branch
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
- // 4. Model
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
- // 5. Context window (bar + token count)
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 drops segments from the left (task first)
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 a broken statusline should never interrupt your flow
141
+ // Silent fail - a broken statusline should never interrupt your flow
186
142
  }
187
143
  });