claude-context-meter 1.0.0 → 1.0.2

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 +29 -21
  2. package/context_meter.js +106 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,23 +3,30 @@
3
3
  A Claude Code plugin that adds a persistent context window usage meter to the status line. After every API response you'll see something like:
4
4
 
5
5
  ```
6
- Context: 24k (18%)
6
+ Context: 24k (18%) · [GitHub/my-project/main]
7
7
  ```
8
8
 
9
9
  Color-coded by pressure level:
10
10
 
11
- | Color | Token range | Example output |
12
- |--------|-------------------|---------------------------------------------------------|
13
- | Green | < 70,000 | `Context: 24k (18%)` |
14
- | Yellow | 70,000 100,000 | `Context: 85k (43%)` |
15
- | Red | > 100,000 | `Context: 112k (56%) · Consider /compact or /clear` |
11
+ | Zone | Token range | Context numbers | VCS segment |
12
+ |--------|------------------|-----------------|---------------------|
13
+ | Green | < 70,000 | Green | Gray (clean) / Cyan (dirty) |
14
+ | Yellow | 70,000–100,000 | Yellow | Gray (clean) / Cyan (dirty) |
15
+ | Red | > 100,000 | Red | Gray (clean) / Cyan (dirty) |
16
16
 
17
- The status line updates automatically no polling or background process required.
17
+ In the red zone a suggestion is appended:
18
18
 
19
- ## Prerequisites
19
+ ```
20
+ Context: 112k (56%) · Consider /compact or /clear · [GitHub/my-project/main]
21
+ ```
20
22
 
21
- - **Claude Code** (any version that supports the `statusLine` setting)
22
- - **Node.js** — already installed as part of Claude Code; no additional installation required
23
+ **VCS segment** if you're inside a git repository, the current platform, repo, and branch are shown at the end of the line. The segment turns cyan with a `*` when there are uncommitted changes:
24
+
25
+ ```
26
+ Context: 24k (18%) · [GitHub/my-project/main*]
27
+ ```
28
+
29
+ The status line updates automatically — no polling or background process required.
23
30
 
24
31
  ## Installation
25
32
 
@@ -27,35 +34,36 @@ The status line updates automatically — no polling or background process requi
27
34
  npx claude-context-meter install
28
35
  ```
29
36
 
30
- This copies the meter script to `~/.claude/plugins/context-meter/` and adds the required `statusLine` entry to `~/.claude/settings.json` automatically. If a `statusLine` entry already exists it is backed up, replaced, and the backup is removed after a successful write.
37
+ This copies the plugin to `~/.claude/plugins/context-meter/` and updates `~/.claude/settings.json` automatically. Open a new Claude Code session to activate it.
31
38
 
32
- Open a new Claude Code session to activate the meter.
33
-
34
- ## Uninstalling
39
+ ## Uninstallation
35
40
 
36
41
  ```sh
37
42
  npx claude-context-meter uninstall
38
43
  ```
39
44
 
40
- Removes the plugin directory and the `statusLine` entry from `~/.claude/settings.json`.
45
+ This removes the plugin files and cleans up the `statusLine` entry from `~/.claude/settings.json`. Open a new Claude Code session to complete the removal.
46
+
47
+ ## Prerequisites
48
+
49
+ - **Claude Code** (any version that supports the `statusLine` setting)
50
+ - **Node.js** ≥ 18 — already installed as part of Claude Code; no additional installation required
41
51
 
42
52
  ## Verifying the installation
43
53
 
44
- After opening a new Claude Code session you should see a status line at the bottom of the terminal that reads:
54
+ After opening a new Claude Code session you should see a status line that reads:
45
55
 
46
56
  ```
47
- Context: 0 (0%)
57
+ Context: 0 (0%) · [GitHub/my-project/main]
48
58
  ```
49
59
 
50
- Send any message to Claude. After the API response the token count and percentage will update to reflect the current session context. If the status line does not appear, check that:
60
+ Send any message to Claude. After the API response the token count and percentage will update. If the status line does not appear, check that:
51
61
 
52
- - `~/.claude/settings.json` contains the correct `statusLine` entry (run `npx claude-context-meter install` to set it)
53
62
  - You opened a **new** Claude Code session after running the installer
63
+ - `~/.claude/settings.json` contains a `statusLine` entry pointing to the plugin
54
64
 
55
65
  ## Running tests
56
66
 
57
67
  ```sh
58
68
  node --test tests/test_context_meter.js
59
69
  ```
60
-
61
- All 26 tests pass with no external dependencies — Node.js standard library only.
package/context_meter.js CHANGED
@@ -1,17 +1,26 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
+ const { execSync } = require('child_process');
5
+
4
6
  const GREEN = '\x1b[32m';
5
7
  const YELLOW = '\x1b[33m';
6
8
  const RED = '\x1b[31m';
9
+ const GRAY = '\x1b[2m';
10
+ const CYAN = '\x1b[36m';
7
11
  const RESET = '\x1b[0m';
8
12
 
9
13
  const GREEN_THRESHOLD = 70_000;
10
14
  const YELLOW_THRESHOLD = 100_000;
11
- const RED_ZONE_MSG = ' · Consider /compact or /clear';
12
15
 
13
16
  const ANSI = { green: GREEN, yellow: YELLOW, red: RED };
14
17
 
18
+ const KNOWN_PLATFORMS = {
19
+ 'github.com': 'GitHub',
20
+ 'bitbucket.org': 'Bitbucket',
21
+ 'gitlab.com': 'GitLab',
22
+ };
23
+
15
24
  function formatTokens(count) {
16
25
  if (count >= 1_000_000) return `${Math.round(count / 1_000_000)}M`;
17
26
  if (count >= 1_000) return `${Math.round(count / 1_000)}k`;
@@ -36,13 +45,103 @@ function parseInput(data) {
36
45
  }
37
46
  }
38
47
 
39
- function render(tokens, pct) {
48
+ function parseRemoteUrl(url) {
49
+ let host = null;
50
+ let repo = null;
51
+
52
+ const sshMatch = url.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
53
+ if (sshMatch) {
54
+ host = sshMatch[1];
55
+ repo = sshMatch[2].split('/').pop() || null;
56
+ } else {
57
+ try {
58
+ const u = new URL(url);
59
+ host = u.hostname || null;
60
+ const segments = u.pathname.replace(/^\//, '').replace(/\.git$/, '').split('/');
61
+ repo = segments.pop() || null;
62
+ } catch {
63
+ // unparseable remote URL
64
+ }
65
+ }
66
+
67
+ const platform = host ? (KNOWN_PLATFORMS[host] || host) : null;
68
+ return { platform, repo };
69
+ }
70
+
71
+ function detectVcs(cwd = process.cwd(), exec = execSync) {
72
+ const run = cmd => exec(cmd, { cwd, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
73
+
74
+ let branch;
75
+ try {
76
+ branch = run('git branch --show-current');
77
+ } catch {
78
+ return { type: 'none' };
79
+ }
80
+
81
+ let platform = null;
82
+ let repo = null;
83
+ try {
84
+ ({ platform, repo } = parseRemoteUrl(run('git remote get-url origin')));
85
+ } catch {
86
+ // no remote configured
87
+ }
88
+
89
+ let dirty = false;
90
+ try {
91
+ dirty = run('git status --porcelain').length > 0;
92
+ } catch {
93
+ // default to clean if git status fails
94
+ }
95
+
96
+ if (!branch) {
97
+ let hash = 'HEAD';
98
+ try { hash = run('git rev-parse --short HEAD'); } catch { /* use fallback */ }
99
+ return { type: 'detached', platform, repo, hash, dirty };
100
+ }
101
+
102
+ return { type: 'branch', platform, repo, branch, dirty };
103
+ }
104
+
105
+ function formatVcs(vcsState) {
106
+ if (vcsState.type === 'none') {
107
+ return `${GRAY} · [No version control]${RESET}`;
108
+ }
109
+
110
+ const { dirty } = vcsState;
111
+ const color = dirty ? CYAN : GRAY;
112
+ const star = dirty ? '*' : '';
113
+
114
+ if (vcsState.type === 'detached') {
115
+ const bracketParts = [vcsState.platform, vcsState.repo].filter(Boolean);
116
+ const bracket = bracketParts.length ? `[${bracketParts.join('/')}] ` : '';
117
+ const content = `${bracket}(HEAD detached at ${vcsState.hash})${star}`;
118
+ return dirty
119
+ ? `${GRAY} · ${RESET}${color}${content}${RESET}`
120
+ : `${GRAY} · ${content}${RESET}`;
121
+ }
122
+
123
+ const parts = [vcsState.platform, vcsState.repo, vcsState.branch].filter(Boolean);
124
+ const content = `[${parts.join('/')}${star}]`;
125
+ return dirty
126
+ ? `${GRAY} · ${RESET}${color}${content}${RESET}`
127
+ : `${GRAY} · ${content}${RESET}`;
128
+ }
129
+
130
+ function render(tokens, pct, vcsState = { type: 'none' }) {
40
131
  const formatted = formatTokens(tokens);
41
132
  const pctInt = Math.round(pct);
42
133
  const category = classify(tokens);
43
- let line = `Context: ${formatted} (${pctInt}%)`;
44
- if (category === 'red') line += RED_ZONE_MSG;
45
- return `${ANSI[category]}${line}${RESET}`;
134
+ const zoneColor = ANSI[category];
135
+
136
+ let line = `${GRAY}Context: ${RESET}${zoneColor}${formatted} (${pctInt}%)${RESET}`;
137
+
138
+ if (category === 'red') {
139
+ line += `${GRAY} · Consider /compact or /clear${RESET}`;
140
+ }
141
+
142
+ line += formatVcs(vcsState);
143
+
144
+ return line;
46
145
  }
47
146
 
48
147
  if (require.main === module) {
@@ -51,8 +150,8 @@ if (require.main === module) {
51
150
  process.stdin.on('data', chunk => { data += chunk; });
52
151
  process.stdin.on('end', () => {
53
152
  const [tokens, pct] = parseInput(data);
54
- console.log(render(tokens, pct));
153
+ console.log(render(tokens, pct, detectVcs()));
55
154
  });
56
155
  }
57
156
 
58
- module.exports = { formatTokens, classify, parseInput, render };
157
+ module.exports = { formatTokens, classify, parseInput, detectVcs, formatVcs, render };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-context-meter",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Persistent context window usage meter for the Claude Code status line",
5
5
  "bin": {
6
6
  "claude-context-meter": "cli.js"