howmuchleft 0.2.0 → 0.2.1
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 +20 -88
- package/lib/statusline.js +49 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,103 +3,32 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/howmuchleft)
|
|
4
4
|
[](https://www.npmjs.com/package/howmuchleft)
|
|
5
5
|
[](https://github.com/smm-h/howmuchleft/blob/main/LICENSE)
|
|
6
|
-
[](https://nodejs.org)
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
**Know exactly how much context and usage you have left, right in your Claude Code statusline.**
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|---|---|
|
|
12
|
-
|  |  |
|
|
9
|
+

|
|
13
10
|
|
|
14
|
-
|
|
11
|
+

|
|
15
12
|
|
|
16
|
-
Three bars with sub-cell precision
|
|
13
|
+
Three progress bars with sub-cell precision that shift from green to red as you approach your limits:
|
|
17
14
|
|
|
18
|
-
| Bar |
|
|
19
|
-
|
|
20
|
-
| Context window | How full your conversation is
|
|
21
|
-
| 5-hour usage | Rolling rate limit
|
|
22
|
-
| Weekly usage | Rolling 7-day rate limit
|
|
15
|
+
| Bar | What it tracks |
|
|
16
|
+
|---|---|
|
|
17
|
+
| **Context window** | How full your conversation is, plus subscription tier and model |
|
|
18
|
+
| **5-hour usage** | Rolling rate limit, time until reset, git branch and diff stats |
|
|
19
|
+
| **Weekly usage** | Rolling 7-day rate limit, time until reset, current directory |
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
Works with Pro, Max 5x, Max 20x, and Team subscriptions. API key users see context bar only.
|
|
25
22
|
|
|
26
23
|
## Install
|
|
27
24
|
|
|
25
|
+
Two commands and you're done:
|
|
26
|
+
|
|
28
27
|
```bash
|
|
29
28
|
npm install -g howmuchleft
|
|
30
29
|
howmuchleft --install
|
|
31
30
|
```
|
|
32
31
|
|
|
33
|
-
For multiple Claude Code config directories:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
howmuchleft --install ~/.claude-work
|
|
37
|
-
howmuchleft --install ~/.claude-personal
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Configuration
|
|
41
|
-
|
|
42
|
-
Config file: `~/.config/howmuchleft.json` (JSONC -- comments and trailing commas are allowed).
|
|
43
|
-
|
|
44
|
-
```jsonc
|
|
45
|
-
{
|
|
46
|
-
"progressLength": 12,
|
|
47
|
-
"colorMode": "auto",
|
|
48
|
-
"colors": [
|
|
49
|
-
// Truecolor dark: RGB gradient + RGB background
|
|
50
|
-
{ "dark-mode": true, "true-color": true, "bg": [48, 48, 48], "gradient": [[0,215,0], [255,255,0], [255,0,0]] },
|
|
51
|
-
// 256-color dark: index gradient + index background
|
|
52
|
-
{ "dark-mode": true, "true-color": false, "bg": 236, "gradient": [46, 226, 196] }
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
### Top-level settings
|
|
58
|
-
|
|
59
|
-
| Field | Default | Description |
|
|
60
|
-
|---|---|---|
|
|
61
|
-
| `progressLength` | `12` | Bar width in characters (3--40) |
|
|
62
|
-
| `colorMode` | `"auto"` | `"auto"` (detect via `COLORTERM`), `"truecolor"`, or `"256"` |
|
|
63
|
-
| `colors` | built-in | Array of color entries (see below) |
|
|
64
|
-
|
|
65
|
-
### Color entries
|
|
66
|
-
|
|
67
|
-
Each entry in the `colors` array is matched against the current terminal. First match wins. Omit a condition to match both modes.
|
|
68
|
-
|
|
69
|
-
| Field | Required | Description |
|
|
70
|
-
|---|---|---|
|
|
71
|
-
| `gradient` | Yes | Color stops: `[R,G,B]` arrays for truecolor, or integers (0--255) for 256-color |
|
|
72
|
-
| `bg` | No | Empty bar background: `[R,G,B]` for truecolor, or integer (0--255) for 256-color |
|
|
73
|
-
| `dark-mode` | No | Match dark (`true`) or light (`false`) terminals only |
|
|
74
|
-
| `true-color` | No | Match truecolor (`true`) or 256-color (`false`) terminals only |
|
|
75
|
-
|
|
76
|
-
Truecolor gradients are smoothly interpolated between stops -- 3 stops (green, yellow, red) is enough for a smooth bar. 256-color gradients snap to the nearest stop.
|
|
77
|
-
|
|
78
|
-
To preview your current gradient: `howmuchleft --test-colors`
|
|
79
|
-
|
|
80
|
-
## CLI
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
howmuchleft [config-dir] Run the statusline (called by Claude Code)
|
|
84
|
-
howmuchleft --install [config-dir] Add to Claude Code settings.json
|
|
85
|
-
howmuchleft --uninstall [config-dir] Remove from Claude Code settings.json
|
|
86
|
-
howmuchleft --config Show config path and current settings
|
|
87
|
-
howmuchleft --demo [seconds] Time-lapse animation (default 60s)
|
|
88
|
-
howmuchleft --test-colors Preview gradient at seven sample levels
|
|
89
|
-
howmuchleft --version Show version
|
|
90
|
-
howmuchleft --help Show help
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## How it works
|
|
94
|
-
|
|
95
|
-
Claude Code invokes `howmuchleft` as a child process on each statusline render, piping a JSON object to stdin with model info, context window usage, cwd, and cost data. The script:
|
|
96
|
-
|
|
97
|
-
1. Parses the JSON from stdin
|
|
98
|
-
2. Fetches usage data from Anthropic's OAuth API (cached 60s, stale-data fallback on failure)
|
|
99
|
-
3. Auto-refreshes expired OAuth tokens
|
|
100
|
-
4. Runs `git status --porcelain=v2` in parallel for branch/change info
|
|
101
|
-
5. Renders three lines of ANSI-colored progress bars to stdout
|
|
102
|
-
|
|
103
32
|
## Uninstall
|
|
104
33
|
|
|
105
34
|
```bash
|
|
@@ -107,9 +36,12 @@ howmuchleft --uninstall
|
|
|
107
36
|
npm uninstall -g howmuchleft
|
|
108
37
|
```
|
|
109
38
|
|
|
110
|
-
##
|
|
39
|
+
## Customize
|
|
40
|
+
|
|
41
|
+
Config lives at `~/.config/howmuchleft.json` (JSONC -- comments allowed). See [`config.example.json`](./config.example.json) for all options.
|
|
42
|
+
|
|
43
|
+
- **`progressLength`** -- bar width in characters (default 12)
|
|
44
|
+
- **`colorMode`** -- `"auto"`, `"truecolor"`, or `"256"`
|
|
45
|
+
- **`colors`** -- custom gradient stops and background colors per theme/color-depth combo
|
|
111
46
|
|
|
112
|
-
|
|
113
|
-
- Claude Code with OAuth login (Pro, Max, or Team subscription)
|
|
114
|
-
- `git` (optional, for branch/change display)
|
|
115
|
-
- `gsettings` (Linux/GNOME) or `defaults` (macOS) for dark/light mode detection
|
|
47
|
+
Preview your current gradient: `howmuchleft --test-colors`
|
package/lib/statusline.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
const path = require('path');
|
|
19
|
+
const crypto = require('crypto');
|
|
19
20
|
const https = require('https');
|
|
20
21
|
const { execFile, execFileSync } = require('child_process');
|
|
21
22
|
const { promisify } = require('util');
|
|
@@ -238,12 +239,57 @@ function getClaudeDir() {
|
|
|
238
239
|
return resolvePath(arg);
|
|
239
240
|
}
|
|
240
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Read credentials from the macOS Keychain.
|
|
244
|
+
* Claude Code stores OAuth tokens in the Keychain on macOS instead of
|
|
245
|
+
* (or in addition to) the .credentials.json file. The service name includes
|
|
246
|
+
* a hash of the config dir path to support multiple accounts.
|
|
247
|
+
*/
|
|
248
|
+
function readKeychainCredentials(claudeDir) {
|
|
249
|
+
if (process.platform !== 'darwin') return null;
|
|
250
|
+
const hash = crypto.createHash('sha256').update(claudeDir).digest('hex').slice(0, 8);
|
|
251
|
+
// Try current hashed service name, then legacy unhashed name
|
|
252
|
+
for (const svc of [`Claude Code-credentials-${hash}`, 'Claude Code-credentials']) {
|
|
253
|
+
try {
|
|
254
|
+
const raw = execFileSync('security', [
|
|
255
|
+
'find-generic-password', '-s', svc, '-w',
|
|
256
|
+
], { encoding: 'utf8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
257
|
+
const parsed = JSON.parse(raw.trim());
|
|
258
|
+
if (parsed?.claudeAiOauth?.accessToken) return parsed;
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Cached per-process: credentials won't change during a single render cycle.
|
|
265
|
+
let _credentialsRead = false;
|
|
266
|
+
let _credentialsCache = null;
|
|
267
|
+
|
|
241
268
|
function readCredentialsFile(claudeDir) {
|
|
269
|
+
if (_credentialsRead) return _credentialsCache;
|
|
270
|
+
_credentialsRead = true;
|
|
271
|
+
|
|
272
|
+
// Try file first (fast, no system prompt)
|
|
273
|
+
let fileData = null;
|
|
242
274
|
try {
|
|
243
|
-
|
|
244
|
-
} catch {
|
|
245
|
-
|
|
275
|
+
fileData = JSON.parse(fs.readFileSync(path.join(claudeDir, '.credentials.json'), 'utf8'));
|
|
276
|
+
} catch {}
|
|
277
|
+
if (fileData?.claudeAiOauth?.accessToken) {
|
|
278
|
+
_credentialsCache = fileData;
|
|
279
|
+
return fileData;
|
|
246
280
|
}
|
|
281
|
+
|
|
282
|
+
// macOS: try Keychain when file lacks OAuth credentials
|
|
283
|
+
const keychainData = readKeychainCredentials(claudeDir);
|
|
284
|
+
if (keychainData) {
|
|
285
|
+
// Merge: preserve file data (mcpOAuth etc.) with keychain OAuth
|
|
286
|
+
const merged = { ...(fileData || {}), claudeAiOauth: keychainData.claudeAiOauth };
|
|
287
|
+
_credentialsCache = merged;
|
|
288
|
+
return merged;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
_credentialsCache = fileData;
|
|
292
|
+
return fileData;
|
|
247
293
|
}
|
|
248
294
|
|
|
249
295
|
/**
|
package/package.json
CHANGED