claude-simple-status 1.0.1 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +21 -2
  2. package/package.json +2 -2
  3. package/statusline.mjs +51 -32
package/README.md CHANGED
@@ -19,7 +19,8 @@ A simple, no-frills statusline for [Claude Code](https://docs.anthropic.com/en/d
19
19
  - **Cross-platform** — works on macOS, Linux, and Windows
20
20
  - **Non-blocking** — returns cached data instantly, refreshes quota in the background
21
21
  - **Color-coded** — green/orange/red percentages at a glance
22
- - **Git-aware** — shows the current branch name in repos
22
+ - **Stale-aware** — shows `--` for quota values when cache is outdated, real values appear after first refresh
23
+ - **Git-aware** — shows the current branch name in repos (cached 30s to reduce overhead)
23
24
  - **Timezone-smart** — quota reset time converted to your local timezone
24
25
 
25
26
  If the quota API is unreachable, a red `ERR` indicator appears at the end and clears automatically once the connection recovers.
@@ -32,9 +33,10 @@ npm install -g claude-simple-status
32
33
 
33
34
  That's it — Claude Code is configured automatically. The statusline appears immediately.
34
35
 
35
- To uninstall (also cleans up the Claude Code config):
36
+ To uninstall:
36
37
 
37
38
  ```bash
39
+ claude-simple-status --uninstall
38
40
  npm uninstall -g claude-simple-status
39
41
  ```
40
42
 
@@ -101,6 +103,11 @@ Quota data is cached to the system temp directory and refreshed every 2 minutes.
101
103
 
102
104
  ## Troubleshooting
103
105
 
106
+ **Indicators:**
107
+ - `--` for quota values means the cache is stale (>5 minutes old) — values appear after the first background refresh
108
+ - `?` means quota data has never been fetched yet
109
+ - `ERR` (red) means the last quota fetch failed — clears automatically on recovery
110
+
104
111
  If the statusline shows `ERR`, check the error log:
105
112
 
106
113
  ```bash
@@ -121,6 +128,18 @@ rm /tmp/claude-statusline-quota.json
121
128
  Remove-Item $env:TEMP\claude-statusline-quota.json
122
129
  ```
123
130
 
131
+ ## Related projects
132
+
133
+ ### [claude-rig](https://github.com/edimuj/claude-rig)
134
+
135
+ Run multiple isolated Claude Code configurations simultaneously — each with its own plugins, skills, MCP servers, and settings. When a session is launched through claude-rig, the active profile name appears in the statusline in bold magenta as the first segment:
136
+
137
+ ```
138
+ minimal | main | Opus 4.6 | 12% | 14:30 | 5h:34% | 7d:12%
139
+ ```
140
+
141
+ No configuration needed — claude-simple-status detects claude-rig automatically. Users not using claude-rig are unaffected.
142
+
124
143
  ## Contributing
125
144
 
126
145
  Contributions are welcome! This project follows a few principles:
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "claude-simple-status",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "A simple statusline for Claude Code — git branch, model, context usage, and quota at a glance",
5
5
  "type": "module",
6
6
  "bin": {
7
- "claude-simple-status": "./statusline.mjs"
7
+ "claude-simple-status": "statusline.mjs"
8
8
  },
9
9
  "scripts": {
10
10
  "postinstall": "node scripts/setup.mjs install",
package/statusline.mjs CHANGED
@@ -2,17 +2,35 @@
2
2
  // Claude Code Statusline - Shows Branch | Model | Context % | Next Reset | 5h Quota % | 7d Quota %
3
3
  // Cross-platform Node.js version (no dependencies)
4
4
 
5
- import { readFileSync, writeFileSync, mkdirSync, rmdirSync, statSync, existsSync, appendFileSync } from 'fs';
5
+ import { readFileSync, writeFileSync, mkdirSync, rmdirSync, statSync, existsSync } from 'fs';
6
6
  import { homedir, tmpdir } from 'os';
7
7
  import { join } from 'path';
8
8
  import { spawn, execSync } from 'child_process';
9
- import { request } from 'https';
9
+
10
+ // Handle --uninstall flag (workaround: npm doesn't run preuninstall for global packages)
11
+ if (process.argv.includes('--uninstall')) {
12
+ const settingsFile = join(homedir(), '.claude', 'settings.json');
13
+ try {
14
+ const settings = JSON.parse(readFileSync(settingsFile, 'utf8'));
15
+ if (settings.statusLine?.command === 'claude-simple-status') {
16
+ delete settings.statusLine;
17
+ writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
18
+ console.log('claude-simple-status removed from Claude Code settings.');
19
+ } else {
20
+ console.log('Nothing to remove (statusLine not managed by claude-simple-status).');
21
+ }
22
+ } catch {
23
+ console.log('Nothing to remove (~/.claude/settings.json not found).');
24
+ }
25
+ process.exit(0);
26
+ }
10
27
 
11
28
  // ANSI color codes
12
29
  const GREEN = '\x1b[0;32m';
13
30
  const ORANGE = '\x1b[0;33m';
14
31
  const RED = '\x1b[0;31m';
15
32
  const CYAN = '\x1b[0;36m';
33
+ const MAGENTA_BOLD = '\x1b[1;35m';
16
34
  const YELLOW_BOLD = '\x1b[1;33m';
17
35
  const RESET = '\x1b[0m';
18
36
 
@@ -22,7 +40,10 @@ const CACHE_FILE = join(tmpdir(), 'claude-statusline-quota.json');
22
40
  const LOCK_DIR = join(tmpdir(), 'claude-statusline-quota.lock');
23
41
  const ERROR_FILE = join(tmpdir(), 'claude-statusline-error');
24
42
  const LOG_FILE = join(tmpdir(), 'claude-statusline.log');
25
- const CACHE_MAX_AGE = 120; // seconds
43
+ const CACHE_MAX_AGE = 120; // seconds - when to fetch
44
+ const CACHE_STALE_AGE = 300; // seconds - when to show "--" instead of old values
45
+ const GIT_BRANCH_CACHE = join(tmpdir(), 'claude-statusline-branches.json');
46
+ const GIT_BRANCH_MAX_AGE = 30; // seconds
26
47
 
27
48
  // Color a percentage value based on thresholds
28
49
  function colorPct(val) {
@@ -71,30 +92,6 @@ function acquireLock() {
71
92
  }
72
93
  }
73
94
 
74
- // Release lock
75
- function releaseLock() {
76
- try { rmdirSync(LOCK_DIR); } catch {}
77
- }
78
-
79
- // Log error with timestamp
80
- function logError(msg) {
81
- const ts = new Date().toISOString().replace('T', ' ').slice(0, 19);
82
- try {
83
- appendFileSync(LOG_FILE, `[${ts}] ${msg}\n`);
84
- writeFileSync(ERROR_FILE, msg);
85
- // Trim log to last 50 lines
86
- const lines = readFileSync(LOG_FILE, 'utf8').split('\n').filter(Boolean);
87
- if (lines.length > 50) {
88
- writeFileSync(LOG_FILE, lines.slice(-50).join('\n') + '\n');
89
- }
90
- } catch {}
91
- }
92
-
93
- // Clear error state
94
- function clearError() {
95
- try { writeFileSync(ERROR_FILE, ''); } catch {}
96
- }
97
-
98
95
  // Spawn background refresh process
99
96
  function refreshInBackground(token) {
100
97
  const child = spawn(process.execPath, [
@@ -169,13 +166,22 @@ function toLocalTime(isoString) {
169
166
  }
170
167
  }
171
168
 
172
- // Get current git branch name
169
+ // Get current git branch name (cached per cwd, 30s TTL)
173
170
  function getGitBranch() {
171
+ const cwd = process.cwd();
174
172
  try {
175
- return execSync('git rev-parse --abbrev-ref HEAD', {
173
+ const cache = readJsonFile(GIT_BRANCH_CACHE) || {};
174
+ const entry = cache[cwd];
175
+ if (entry && (Date.now() - entry.ts) < GIT_BRANCH_MAX_AGE * 1000) {
176
+ return entry.branch;
177
+ }
178
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', {
176
179
  timeout: 1000,
177
180
  stdio: ['ignore', 'pipe', 'ignore']
178
181
  }).toString().trim();
182
+ cache[cwd] = { branch, ts: Date.now() };
183
+ try { writeFileSync(GIT_BRANCH_CACHE, JSON.stringify(cache)); } catch {}
184
+ return branch;
179
185
  } catch {
180
186
  return null;
181
187
  }
@@ -219,12 +225,17 @@ async function main() {
219
225
  }
220
226
  }
221
227
 
222
- // Parse quota data
228
+ // Parse quota data (show "--" if cache is too stale)
223
229
  let fiveHourPct = '?';
224
230
  let sevenDayPct = '?';
225
231
  let resetLocal = '--:--';
232
+ const cacheIsStale = !quotaData || getFileAge(CACHE_FILE) > CACHE_STALE_AGE;
226
233
 
227
- if (quotaData) {
234
+ if (cacheIsStale) {
235
+ fiveHourPct = '--';
236
+ sevenDayPct = '--';
237
+ resetLocal = '--:--';
238
+ } else if (quotaData) {
228
239
  if (quotaData.five_hour === null || quotaData.seven_day === null) {
229
240
  // Organization/team plan without individual quota
230
241
  fiveHourPct = 'N/A';
@@ -244,11 +255,19 @@ async function main() {
244
255
  hasError = errContent.length > 0;
245
256
  } catch {}
246
257
 
258
+ // Get rig profile (claude-rig sets CLAUDE_CONFIG_DIR to ~/.claude-rig/profiles/<name>)
259
+ const rigProfile = (() => {
260
+ const configDir = process.env.CLAUDE_CONFIG_DIR;
261
+ if (!configDir) return null;
262
+ const match = configDir.match(/\.claude-rig\/profiles\/([^/]+)\/?$/);
263
+ return match ? match[1] : null;
264
+ })();
265
+
247
266
  // Get git branch
248
267
  const branch = getGitBranch();
249
268
 
250
269
  // Build output
251
- let output = `${branch ? `${YELLOW_BOLD}${branch}${RESET} | ` : ''}${CYAN}${model}${RESET} | ${colorPct(contextUsed)}`;
270
+ let output = `${rigProfile ? `${MAGENTA_BOLD}${rigProfile}${RESET} | ` : ''}${branch ? `${YELLOW_BOLD}${branch}${RESET} | ` : ''}${CYAN}${model}${RESET} | ${colorPct(contextUsed)}`;
252
271
  if (token) {
253
272
  output += ` | ${resetLocal} | 5h:${colorPct(fiveHourPct)} | 7d:${colorPct(sevenDayPct)}`;
254
273
  if (hasError) {