claude-context-meter 1.0.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.
- package/README.md +61 -0
- package/cli.js +97 -0
- package/context_meter.js +58 -0
- package/package.json +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# claude-context-meter
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
```
|
|
6
|
+
Context: 24k (18%)
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Color-coded by pressure level:
|
|
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` |
|
|
16
|
+
|
|
17
|
+
The status line updates automatically — no polling or background process required.
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
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
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx claude-context-meter install
|
|
28
|
+
```
|
|
29
|
+
|
|
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.
|
|
31
|
+
|
|
32
|
+
Open a new Claude Code session to activate the meter.
|
|
33
|
+
|
|
34
|
+
## Uninstalling
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx claude-context-meter uninstall
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Removes the plugin directory and the `statusLine` entry from `~/.claude/settings.json`.
|
|
41
|
+
|
|
42
|
+
## Verifying the installation
|
|
43
|
+
|
|
44
|
+
After opening a new Claude Code session you should see a status line at the bottom of the terminal that reads:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
Context: 0 (0%)
|
|
48
|
+
```
|
|
49
|
+
|
|
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:
|
|
51
|
+
|
|
52
|
+
- `~/.claude/settings.json` contains the correct `statusLine` entry (run `npx claude-context-meter install` to set it)
|
|
53
|
+
- You opened a **new** Claude Code session after running the installer
|
|
54
|
+
|
|
55
|
+
## Running tests
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
node --test tests/test_context_meter.js
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
All 26 tests pass with no external dependencies — Node.js standard library only.
|
package/cli.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
const INSTALL_DIR = path.join(os.homedir(), '.claude', 'plugins', 'context-meter');
|
|
9
|
+
const SCRIPT_DEST = path.join(INSTALL_DIR, 'context_meter.js');
|
|
10
|
+
const SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json');
|
|
11
|
+
const SETTINGS_BACKUP = SETTINGS_FILE + '.bak';
|
|
12
|
+
|
|
13
|
+
function readSettings() {
|
|
14
|
+
if (!fs.existsSync(SETTINGS_FILE)) return {};
|
|
15
|
+
return JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function writeSettings(settings) {
|
|
19
|
+
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(settings, null, 2) + '\n');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function backupSettings() {
|
|
23
|
+
if (fs.existsSync(SETTINGS_FILE)) {
|
|
24
|
+
fs.copyFileSync(SETTINGS_FILE, SETTINGS_BACKUP);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function removeBackup() {
|
|
29
|
+
if (fs.existsSync(SETTINGS_BACKUP)) {
|
|
30
|
+
fs.unlinkSync(SETTINGS_BACKUP);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function install() {
|
|
35
|
+
fs.mkdirSync(INSTALL_DIR, { recursive: true });
|
|
36
|
+
fs.copyFileSync(path.join(__dirname, 'context_meter.js'), SCRIPT_DEST);
|
|
37
|
+
|
|
38
|
+
const settings = readSettings();
|
|
39
|
+
if (settings.statusLine) {
|
|
40
|
+
console.log('Existing statusLine found — backing up settings.json before overwriting.');
|
|
41
|
+
}
|
|
42
|
+
backupSettings();
|
|
43
|
+
|
|
44
|
+
settings.statusLine = {
|
|
45
|
+
type: 'command',
|
|
46
|
+
command: `node "${SCRIPT_DEST}"`
|
|
47
|
+
};
|
|
48
|
+
writeSettings(settings);
|
|
49
|
+
|
|
50
|
+
const written = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
51
|
+
if (written.statusLine && written.statusLine.command === `node "${SCRIPT_DEST}"`) {
|
|
52
|
+
removeBackup();
|
|
53
|
+
console.log('Installed to ' + SCRIPT_DEST);
|
|
54
|
+
console.log('Updated ~/.claude/settings.json');
|
|
55
|
+
console.log('\nOpen a new Claude Code session to see the context meter.');
|
|
56
|
+
} else {
|
|
57
|
+
console.error('Verification failed — backup preserved at ' + SETTINGS_BACKUP);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function uninstall() {
|
|
63
|
+
const settings = readSettings();
|
|
64
|
+
if (!settings.statusLine) {
|
|
65
|
+
console.log('No statusLine entry found in settings.json — nothing to remove.');
|
|
66
|
+
} else {
|
|
67
|
+
backupSettings();
|
|
68
|
+
delete settings.statusLine;
|
|
69
|
+
writeSettings(settings);
|
|
70
|
+
|
|
71
|
+
const written = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf8'));
|
|
72
|
+
if (!written.statusLine) {
|
|
73
|
+
removeBackup();
|
|
74
|
+
console.log('Removed statusLine from ~/.claude/settings.json');
|
|
75
|
+
} else {
|
|
76
|
+
console.error('Verification failed — backup preserved at ' + SETTINGS_BACKUP);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (fs.existsSync(INSTALL_DIR)) {
|
|
82
|
+
fs.rmSync(INSTALL_DIR, { recursive: true });
|
|
83
|
+
console.log('Removed ' + INSTALL_DIR);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log('\nOpen a new Claude Code session to complete the uninstall.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const command = process.argv[2];
|
|
90
|
+
if (command === 'install') {
|
|
91
|
+
install();
|
|
92
|
+
} else if (command === 'uninstall') {
|
|
93
|
+
uninstall();
|
|
94
|
+
} else {
|
|
95
|
+
console.error('Usage: npx claude-context-meter <install|uninstall>');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
package/context_meter.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const GREEN = '\x1b[32m';
|
|
5
|
+
const YELLOW = '\x1b[33m';
|
|
6
|
+
const RED = '\x1b[31m';
|
|
7
|
+
const RESET = '\x1b[0m';
|
|
8
|
+
|
|
9
|
+
const GREEN_THRESHOLD = 70_000;
|
|
10
|
+
const YELLOW_THRESHOLD = 100_000;
|
|
11
|
+
const RED_ZONE_MSG = ' · Consider /compact or /clear';
|
|
12
|
+
|
|
13
|
+
const ANSI = { green: GREEN, yellow: YELLOW, red: RED };
|
|
14
|
+
|
|
15
|
+
function formatTokens(count) {
|
|
16
|
+
if (count >= 1_000_000) return `${Math.round(count / 1_000_000)}M`;
|
|
17
|
+
if (count >= 1_000) return `${Math.round(count / 1_000)}k`;
|
|
18
|
+
return String(count);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function classify(count) {
|
|
22
|
+
if (count < GREEN_THRESHOLD) return 'green';
|
|
23
|
+
if (count <= YELLOW_THRESHOLD) return 'yellow';
|
|
24
|
+
return 'red';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseInput(data) {
|
|
28
|
+
try {
|
|
29
|
+
const payload = JSON.parse(data);
|
|
30
|
+
const cw = payload.context_window || {};
|
|
31
|
+
const tokens = parseInt(cw.total_input_tokens ?? 0, 10) || 0;
|
|
32
|
+
const pct = parseFloat(cw.used_percentage ?? 0) || 0;
|
|
33
|
+
return [tokens, pct];
|
|
34
|
+
} catch {
|
|
35
|
+
return [0, 0];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function render(tokens, pct) {
|
|
40
|
+
const formatted = formatTokens(tokens);
|
|
41
|
+
const pctInt = Math.round(pct);
|
|
42
|
+
const category = classify(tokens);
|
|
43
|
+
let line = `Context: ${formatted} (${pctInt}%)`;
|
|
44
|
+
if (category === 'red') line += RED_ZONE_MSG;
|
|
45
|
+
return `${ANSI[category]}${line}${RESET}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (require.main === module) {
|
|
49
|
+
let data = '';
|
|
50
|
+
process.stdin.setEncoding('utf8');
|
|
51
|
+
process.stdin.on('data', chunk => { data += chunk; });
|
|
52
|
+
process.stdin.on('end', () => {
|
|
53
|
+
const [tokens, pct] = parseInput(data);
|
|
54
|
+
console.log(render(tokens, pct));
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { formatTokens, classify, parseInput, render };
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-context-meter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Persistent context window usage meter for the Claude Code status line",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claude-context-meter": "cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"cli.js",
|
|
10
|
+
"context_meter.js"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/zbshadow/claude-context-meter.git"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"context",
|
|
24
|
+
"status-line"
|
|
25
|
+
]
|
|
26
|
+
}
|