ctxline-claude 0.0.1 → 0.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.
- package/LICENSE +21 -21
- package/README.md +154 -167
- package/bin/install.js +135 -135
- package/package.json +39 -39
- package/statusline.js +394 -345
- package/preview.png +0 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Mithun Wijayasiri
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mithun Wijayasiri
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,167 +1,154 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
##
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
- Fails gracefully (no statusline breakage)
|
|
156
|
-
|
|
157
|
-
## License
|
|
158
|
-
|
|
159
|
-
MIT
|
|
160
|
-
|
|
161
|
-
## Credits
|
|
162
|
-
|
|
163
|
-
Originally created by [@TahaSabir0](https://github.com/TahaSabir0).
|
|
164
|
-
|
|
165
|
-
Built for the [Claude Code](https://github.com/anthropics/claude-code) community.
|
|
166
|
-
|
|
167
|
-
---
|
|
1
|
+
<h1 align="center">Claude Code Statusline</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
A lightweight, zero-config statusline for Claude Code.
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/ctxline-claude">
|
|
9
|
+
<img src="https://img.shields.io/npm/v/ctxline-claude" alt="npm version">
|
|
10
|
+
</a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/ctxline-claude">
|
|
12
|
+
<img src="https://img.shields.io/npm/dm/ctxline-claude" alt="npm downloads">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="LICENSE">
|
|
15
|
+
<img src="https://img.shields.io/github/license/MithunWijayasiri/claudecode-statusline" alt="license">
|
|
16
|
+
</a>
|
|
17
|
+
<a href="https://github.com/MithunWijayasiri/claudecode-statusline/stargazers">
|
|
18
|
+
<img src="https://img.shields.io/github/stars/MithunWijayasiri/claudecode-statusline" alt="stars">
|
|
19
|
+
</a>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img src="preview.svg" alt="Claude Code Statusline">
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
Monitor context usage, session limits, and weekly allowance without leaving Claude Code.
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
See your **current directory**, **active model**, **context window usage**, and **Claude usage limits** at a glance — including both your **current 5-hour session** and **weekly allowance**.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx ctxline-claude # or: bunx ctxline-claude
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then restart Claude Code or start a new session. That's it.
|
|
39
|
+
|
|
40
|
+
<details>
|
|
41
|
+
<summary>Other install methods</summary>
|
|
42
|
+
|
|
43
|
+
**Clone & run the installer:**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/MithunWijayasiri/claudecode-statusline.git
|
|
47
|
+
cd claudecode-statusline
|
|
48
|
+
./install.sh # macOS / Linux
|
|
49
|
+
./install.ps1 # Windows (PowerShell)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Manual:** download the script, then point `~/.claude/settings.json` at it.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl -o ~/.claude/hooks/statusline.js https://raw.githubusercontent.com/MithunWijayasiri/claudecode-statusline/main/statusline.js
|
|
56
|
+
chmod +x ~/.claude/hooks/statusline.js
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"statusLine": {
|
|
62
|
+
"type": "command",
|
|
63
|
+
"command": "node ~/.claude/hooks/statusline.js"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
</details>
|
|
69
|
+
|
|
70
|
+
## Uninstall
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx ctxline-claude uninstall
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Removes the `statusLine` entry from `settings.json` (backed up first, other settings untouched), deletes the hook script, and clears the usage cache. If `settings.json` points at a different statusline, it's left alone.
|
|
77
|
+
|
|
78
|
+
<details>
|
|
79
|
+
<summary>Manual uninstall</summary>
|
|
80
|
+
|
|
81
|
+
Undo the two things the installer did — remove the `statusLine` block from `~/.claude/settings.json` (a timestamped `settings.json.backup.<n>` exists if you'd rather restore), then delete the script:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# macOS / Linux
|
|
85
|
+
rm ~/.claude/hooks/statusline.js
|
|
86
|
+
rm -f ~/.claude/cache/usage-cache.json # optional: clears cached usage
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```powershell
|
|
90
|
+
# Windows (PowerShell)
|
|
91
|
+
Remove-Item "$env:USERPROFILE\.claude\hooks\statusline.js"
|
|
92
|
+
Remove-Item "$env:USERPROFILE\.claude\cache\usage-cache.json" -ErrorAction SilentlyContinue
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
</details>
|
|
96
|
+
|
|
97
|
+
## What it shows
|
|
98
|
+
|
|
99
|
+
| Segment | Detail |
|
|
100
|
+
|---|---|
|
|
101
|
+
| **Directory** | Current working directory |
|
|
102
|
+
| **Model** | Active Claude model (Opus / Sonnet / Haiku) |
|
|
103
|
+
| **Context** | Visual bar of context-window usage |
|
|
104
|
+
| **Current** | Live 5-hour session limit + reset countdown (subscription users) |
|
|
105
|
+
| **Weekly** | Weekly usage allowance + time until the weekly reset (subscription users) |
|
|
106
|
+
| **Task** | The in-progress todo, when there is one |
|
|
107
|
+
|
|
108
|
+
> \[!NOTE\] Usage bars change color automatically as you approach your
|
|
109
|
+
> limits.
|
|
110
|
+
|
|
111
|
+
## How it works
|
|
112
|
+
|
|
113
|
+
- **Source** — context comes from Claude Code's session data; both usage bars are fetched from `https://api.anthropic.com/api/oauth/usage` (the `/usage` data — 5-hour and weekly limits). API-key users skip the usage fetch.
|
|
114
|
+
- **Adaptive timing** — 1.5s timeout on the first prompt (cold start), 1.2s after (connection reused).
|
|
115
|
+
- **Caching** — usage is cached at `~/.claude/cache/usage-cache.json`, shared across sessions. Within 30s the cache renders directly (the API call is skipped); if a live call fails, the last value (up to 10 min old) is shown so the bar never vanishes. The reset countdown recomputes every render.
|
|
116
|
+
- **Never breaks** — every failure path falls back silently; the statusline always prints.
|
|
117
|
+
|
|
118
|
+
## FAQ
|
|
119
|
+
|
|
120
|
+
<details>
|
|
121
|
+
<summary>Does this use the same data as /usage?</summary>
|
|
122
|
+
|
|
123
|
+
Yes. Usage information comes directly from Anthropic's usage API.
|
|
124
|
+
|
|
125
|
+
</details>
|
|
126
|
+
|
|
127
|
+
<details>
|
|
128
|
+
<summary>Does it work with API keys?</summary>
|
|
129
|
+
|
|
130
|
+
Yes. The statusline automatically detects subscription vs API-key usage.
|
|
131
|
+
|
|
132
|
+
</details>
|
|
133
|
+
|
|
134
|
+
<details>
|
|
135
|
+
<summary>Can it break Claude Code?</summary>
|
|
136
|
+
|
|
137
|
+
No. All failures are handled silently and the statusline always renders.
|
|
138
|
+
|
|
139
|
+
</details>
|
|
140
|
+
|
|
141
|
+
<details>
|
|
142
|
+
<summary>Does it expose my API keys / auth tokens?</summary>
|
|
143
|
+
|
|
144
|
+
No. Your credentials never leave your machine. The OAuth token is read locally (from `~/.claude/.credentials.json` or the macOS keychain) only to authenticate the request to Anthropic's own usage API — the same endpoint `/usage` uses. Nothing is sent to any third party, logged, or cached; only the resulting usage percentages are stored locally.
|
|
145
|
+
|
|
146
|
+
</details>
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT
|
|
151
|
+
|
|
152
|
+
## Credits
|
|
153
|
+
|
|
154
|
+
Thanks to [@TahaSabir0](https://github.com/TahaSabir0) for the base config.
|
package/bin/install.js
CHANGED
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
const claudeDir = path.join(os.homedir(), '.claude');
|
|
8
|
-
const hooksDir = path.join(claudeDir, 'hooks');
|
|
9
|
-
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
10
|
-
const scriptDest = path.join(hooksDir, 'statusline.js');
|
|
11
|
-
const scriptSrc = path.join(__dirname, '..', 'statusline.js');
|
|
12
|
-
|
|
13
|
-
const green = '\x1b[32m';
|
|
14
|
-
const red = '\x1b[31m';
|
|
15
|
-
const yellow = '\x1b[33m';
|
|
16
|
-
const cyan = '\x1b[36m';
|
|
17
|
-
const reset = '\x1b[0m';
|
|
18
|
-
|
|
19
|
-
// Uninstall mode: `npx ctxline-claude uninstall` (additive — plain install is unchanged)
|
|
20
|
-
const mode = (process.argv[2] || '').toLowerCase();
|
|
21
|
-
if (mode === 'uninstall' || mode === 'remove') {
|
|
22
|
-
runUninstall();
|
|
23
|
-
process.exit(0);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
console.log(`${cyan}======================================${reset}`);
|
|
27
|
-
console.log(`${cyan} Claude Code Statusline Installer${reset}`);
|
|
28
|
-
console.log(`${cyan}======================================${reset}\n`);
|
|
29
|
-
|
|
30
|
-
// Check Claude Code is installed
|
|
31
|
-
if (!fs.existsSync(claudeDir)) {
|
|
32
|
-
console.log(`${red}Error: Claude Code not found!${reset}`);
|
|
33
|
-
console.log('Please install Claude Code first: https://github.com/anthropics/claude-code');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Create hooks directory
|
|
38
|
-
if (!fs.existsSync(hooksDir)) {
|
|
39
|
-
fs.mkdirSync(hooksDir, { recursive: true });
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Copy statusline script
|
|
43
|
-
console.log(`${yellow}Installing statusline...${reset}`);
|
|
44
|
-
fs.copyFileSync(scriptSrc, scriptDest);
|
|
45
|
-
fs.chmodSync(scriptDest, 0o755);
|
|
46
|
-
console.log(`${green}✓ Installed statusline.js${reset}`);
|
|
47
|
-
|
|
48
|
-
// Update settings.json
|
|
49
|
-
console.log(`${yellow}Updating settings...${reset}`);
|
|
50
|
-
|
|
51
|
-
let settings = {};
|
|
52
|
-
if (fs.existsSync(settingsFile)) {
|
|
53
|
-
// Backup existing settings
|
|
54
|
-
const backup = `${settingsFile}.backup.${Date.now()}`;
|
|
55
|
-
fs.copyFileSync(settingsFile, backup);
|
|
56
|
-
console.log(`${green}✓ Backed up existing settings${reset}`);
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
60
|
-
} catch (e) {
|
|
61
|
-
settings = {};
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
settings.statusLine = {
|
|
66
|
-
type: 'command',
|
|
67
|
-
command: `node ${scriptDest.replace(/\\/g, '/')}`
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
71
|
-
console.log(`${green}✓ Updated settings.json${reset}`);
|
|
72
|
-
|
|
73
|
-
console.log(`\n${green}======================================${reset}`);
|
|
74
|
-
console.log(`${green} Installation Complete!${reset}`);
|
|
75
|
-
console.log(`${green}======================================${reset}`);
|
|
76
|
-
console.log('\nRestart Claude Code or start a new session.');
|
|
77
|
-
console.log('The statusline will auto-detect your setup (subscription vs API key).\n');
|
|
78
|
-
|
|
79
|
-
function runUninstall() {
|
|
80
|
-
console.log(`${cyan}======================================${reset}`);
|
|
81
|
-
console.log(`${cyan} Claude Code Statusline Uninstaller${reset}`);
|
|
82
|
-
console.log(`${cyan}======================================${reset}\n`);
|
|
83
|
-
|
|
84
|
-
if (!fs.existsSync(claudeDir)) {
|
|
85
|
-
console.log(`${yellow}Nothing to remove — ~/.claude was not found.${reset}\n`);
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 1. Remove our statusLine entry from settings.json (preserving everything else)
|
|
90
|
-
if (fs.existsSync(settingsFile)) {
|
|
91
|
-
try {
|
|
92
|
-
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
93
|
-
const command = ((settings.statusLine && settings.statusLine.command) || '').replace(/\\/g, '/');
|
|
94
|
-
// Match our hook path only (covers absolute + manual `~` installs), not any statusline.js.
|
|
95
|
-
const isOurs = command.includes('/.claude/hooks/statusline.js');
|
|
96
|
-
if (settings.statusLine && isOurs) {
|
|
97
|
-
const backup = `${settingsFile}.backup.${Date.now()}`;
|
|
98
|
-
fs.copyFileSync(settingsFile, backup);
|
|
99
|
-
delete settings.statusLine;
|
|
100
|
-
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
101
|
-
console.log(`${green}✓ Removed statusLine from settings.json (backup: ${path.basename(backup)})${reset}`);
|
|
102
|
-
} else if (settings.statusLine) {
|
|
103
|
-
console.log(`${yellow}! settings.json has a different statusLine — leaving it untouched.${reset}`);
|
|
104
|
-
} else {
|
|
105
|
-
console.log(`${green}✓ No statusLine entry in settings.json${reset}`);
|
|
106
|
-
}
|
|
107
|
-
} catch (e) {
|
|
108
|
-
console.log(`${red}✗ Could not parse settings.json — remove the "statusLine" block manually.${reset}`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// 2. Delete the hook script
|
|
113
|
-
if (fs.existsSync(scriptDest)) {
|
|
114
|
-
try {
|
|
115
|
-
fs.unlinkSync(scriptDest);
|
|
116
|
-
console.log(`${green}✓ Deleted ${scriptDest}${reset}`);
|
|
117
|
-
} catch (e) {
|
|
118
|
-
console.log(`${red}✗ Could not delete ${scriptDest}: ${e.message}${reset}`);
|
|
119
|
-
console.log(`${yellow} Remove it manually.${reset}`);
|
|
120
|
-
}
|
|
121
|
-
} else {
|
|
122
|
-
console.log(`${green}✓ No statusline.js found in hooks${reset}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 3. Clear cached usage data (best-effort)
|
|
126
|
-
const cacheFile = path.join(claudeDir, 'cache', 'usage-cache.json');
|
|
127
|
-
if (fs.existsSync(cacheFile)) {
|
|
128
|
-
try {
|
|
129
|
-
fs.unlinkSync(cacheFile);
|
|
130
|
-
console.log(`${green}✓ Cleared usage cache${reset}`);
|
|
131
|
-
} catch (e) {}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log(`\n${green}Uninstall complete.${reset} Restart Claude Code or start a new session.\n`);
|
|
135
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const claudeDir = path.join(os.homedir(), '.claude');
|
|
8
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
9
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
10
|
+
const scriptDest = path.join(hooksDir, 'statusline.js');
|
|
11
|
+
const scriptSrc = path.join(__dirname, '..', 'statusline.js');
|
|
12
|
+
|
|
13
|
+
const green = '\x1b[32m';
|
|
14
|
+
const red = '\x1b[31m';
|
|
15
|
+
const yellow = '\x1b[33m';
|
|
16
|
+
const cyan = '\x1b[36m';
|
|
17
|
+
const reset = '\x1b[0m';
|
|
18
|
+
|
|
19
|
+
// Uninstall mode: `npx ctxline-claude uninstall` (additive — plain install is unchanged)
|
|
20
|
+
const mode = (process.argv[2] || '').toLowerCase();
|
|
21
|
+
if (mode === 'uninstall' || mode === 'remove') {
|
|
22
|
+
runUninstall();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
console.log(`${cyan}======================================${reset}`);
|
|
27
|
+
console.log(`${cyan} Claude Code Statusline Installer${reset}`);
|
|
28
|
+
console.log(`${cyan}======================================${reset}\n`);
|
|
29
|
+
|
|
30
|
+
// Check Claude Code is installed
|
|
31
|
+
if (!fs.existsSync(claudeDir)) {
|
|
32
|
+
console.log(`${red}Error: Claude Code not found!${reset}`);
|
|
33
|
+
console.log('Please install Claude Code first: https://github.com/anthropics/claude-code');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Create hooks directory
|
|
38
|
+
if (!fs.existsSync(hooksDir)) {
|
|
39
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Copy statusline script
|
|
43
|
+
console.log(`${yellow}Installing statusline...${reset}`);
|
|
44
|
+
fs.copyFileSync(scriptSrc, scriptDest);
|
|
45
|
+
fs.chmodSync(scriptDest, 0o755);
|
|
46
|
+
console.log(`${green}✓ Installed statusline.js${reset}`);
|
|
47
|
+
|
|
48
|
+
// Update settings.json
|
|
49
|
+
console.log(`${yellow}Updating settings...${reset}`);
|
|
50
|
+
|
|
51
|
+
let settings = {};
|
|
52
|
+
if (fs.existsSync(settingsFile)) {
|
|
53
|
+
// Backup existing settings
|
|
54
|
+
const backup = `${settingsFile}.backup.${Date.now()}`;
|
|
55
|
+
fs.copyFileSync(settingsFile, backup);
|
|
56
|
+
console.log(`${green}✓ Backed up existing settings${reset}`);
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
60
|
+
} catch (e) {
|
|
61
|
+
settings = {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
settings.statusLine = {
|
|
66
|
+
type: 'command',
|
|
67
|
+
command: `node ${scriptDest.replace(/\\/g, '/')}`
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
71
|
+
console.log(`${green}✓ Updated settings.json${reset}`);
|
|
72
|
+
|
|
73
|
+
console.log(`\n${green}======================================${reset}`);
|
|
74
|
+
console.log(`${green} Installation Complete!${reset}`);
|
|
75
|
+
console.log(`${green}======================================${reset}`);
|
|
76
|
+
console.log('\nRestart Claude Code or start a new session.');
|
|
77
|
+
console.log('The statusline will auto-detect your setup (subscription vs API key).\n');
|
|
78
|
+
|
|
79
|
+
function runUninstall() {
|
|
80
|
+
console.log(`${cyan}======================================${reset}`);
|
|
81
|
+
console.log(`${cyan} Claude Code Statusline Uninstaller${reset}`);
|
|
82
|
+
console.log(`${cyan}======================================${reset}\n`);
|
|
83
|
+
|
|
84
|
+
if (!fs.existsSync(claudeDir)) {
|
|
85
|
+
console.log(`${yellow}Nothing to remove — ~/.claude was not found.${reset}\n`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 1. Remove our statusLine entry from settings.json (preserving everything else)
|
|
90
|
+
if (fs.existsSync(settingsFile)) {
|
|
91
|
+
try {
|
|
92
|
+
const settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
93
|
+
const command = ((settings.statusLine && settings.statusLine.command) || '').replace(/\\/g, '/');
|
|
94
|
+
// Match our hook path only (covers absolute + manual `~` installs), not any statusline.js.
|
|
95
|
+
const isOurs = command.includes('/.claude/hooks/statusline.js');
|
|
96
|
+
if (settings.statusLine && isOurs) {
|
|
97
|
+
const backup = `${settingsFile}.backup.${Date.now()}`;
|
|
98
|
+
fs.copyFileSync(settingsFile, backup);
|
|
99
|
+
delete settings.statusLine;
|
|
100
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
101
|
+
console.log(`${green}✓ Removed statusLine from settings.json (backup: ${path.basename(backup)})${reset}`);
|
|
102
|
+
} else if (settings.statusLine) {
|
|
103
|
+
console.log(`${yellow}! settings.json has a different statusLine — leaving it untouched.${reset}`);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(`${green}✓ No statusLine entry in settings.json${reset}`);
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(`${red}✗ Could not parse settings.json — remove the "statusLine" block manually.${reset}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 2. Delete the hook script
|
|
113
|
+
if (fs.existsSync(scriptDest)) {
|
|
114
|
+
try {
|
|
115
|
+
fs.unlinkSync(scriptDest);
|
|
116
|
+
console.log(`${green}✓ Deleted ${scriptDest}${reset}`);
|
|
117
|
+
} catch (e) {
|
|
118
|
+
console.log(`${red}✗ Could not delete ${scriptDest}: ${e.message}${reset}`);
|
|
119
|
+
console.log(`${yellow} Remove it manually.${reset}`);
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
console.log(`${green}✓ No statusline.js found in hooks${reset}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 3. Clear cached usage data (best-effort)
|
|
126
|
+
const cacheFile = path.join(claudeDir, 'cache', 'usage-cache.json');
|
|
127
|
+
if (fs.existsSync(cacheFile)) {
|
|
128
|
+
try {
|
|
129
|
+
fs.unlinkSync(cacheFile);
|
|
130
|
+
console.log(`${green}✓ Cleared usage cache${reset}`);
|
|
131
|
+
} catch (e) {}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
console.log(`\n${green}Uninstall complete.${reset} Restart Claude Code or start a new session.\n`);
|
|
135
|
+
}
|