cc-statusline-pro 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/QUICK_START.md +190 -0
- package/README.md +118 -0
- package/cli.js +476 -0
- package/install.ps1 +92 -0
- package/install.sh +106 -0
- package/package.json +23 -0
- package/statusline-template.js +458 -0
package/cli.js
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const command = process.argv[2];
|
|
7
|
+
|
|
8
|
+
if (command === 'init') {
|
|
9
|
+
initStatusline();
|
|
10
|
+
} else {
|
|
11
|
+
showHelp();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function showHelp() {
|
|
15
|
+
console.log(`
|
|
16
|
+
Claude Code Statusline Installer
|
|
17
|
+
=================================
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
npx claude-code-statusline init
|
|
21
|
+
|
|
22
|
+
Commands:
|
|
23
|
+
init Install statusline to current project
|
|
24
|
+
|
|
25
|
+
Features:
|
|
26
|
+
- Beautiful colors and progress bars
|
|
27
|
+
- Real-time usage limits from Anthropic API
|
|
28
|
+
- Context window tracking
|
|
29
|
+
- Session time and cost tracking
|
|
30
|
+
- Code changes (lines added/removed)
|
|
31
|
+
- Git branch display
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
cd your-project
|
|
35
|
+
npx claude-code-statusline init
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function initStatusline() {
|
|
40
|
+
const cwd = process.cwd();
|
|
41
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
42
|
+
const statuslineFile = path.join(claudeDir, 'statusline.js');
|
|
43
|
+
const settingsFile = path.join(claudeDir, 'settings.json');
|
|
44
|
+
|
|
45
|
+
console.log('');
|
|
46
|
+
console.log('🚀 Installing Claude Code Statusline...');
|
|
47
|
+
console.log('');
|
|
48
|
+
|
|
49
|
+
// Step 1: Create .claude directory if not exists
|
|
50
|
+
if (!fs.existsSync(claudeDir)) {
|
|
51
|
+
console.log('📁 Creating .claude directory...');
|
|
52
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
53
|
+
console.log(' ✓ Created .claude/');
|
|
54
|
+
} else {
|
|
55
|
+
console.log('📁 .claude directory already exists');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Step 2: Copy statusline.js
|
|
59
|
+
console.log('');
|
|
60
|
+
console.log('📝 Installing statusline.js...');
|
|
61
|
+
|
|
62
|
+
const statuslineContent = getStatuslineTemplate();
|
|
63
|
+
|
|
64
|
+
if (fs.existsSync(statuslineFile)) {
|
|
65
|
+
console.log(' ⚠️ statusline.js already exists');
|
|
66
|
+
console.log(' 💾 Creating backup: statusline.js.backup');
|
|
67
|
+
fs.copyFileSync(statuslineFile, statuslineFile + '.backup');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.writeFileSync(statuslineFile, statuslineContent);
|
|
71
|
+
|
|
72
|
+
// Make executable on Unix systems
|
|
73
|
+
try {
|
|
74
|
+
fs.chmodSync(statuslineFile, '755');
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// Windows doesn't need chmod
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(' ✓ Installed .claude/statusline.js');
|
|
80
|
+
|
|
81
|
+
// Step 3: Update settings.json
|
|
82
|
+
console.log('');
|
|
83
|
+
console.log('⚙️ Updating settings.json...');
|
|
84
|
+
|
|
85
|
+
let settings = {};
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(settingsFile)) {
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(settingsFile, 'utf8');
|
|
90
|
+
settings = JSON.parse(content);
|
|
91
|
+
console.log(' ✓ Found existing settings.json');
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.log(' ⚠️ Could not parse settings.json, creating new one');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Add or update statusLine config with absolute path
|
|
98
|
+
const absoluteStatuslinePath = path.resolve(statuslineFile);
|
|
99
|
+
const command = process.platform === 'win32'
|
|
100
|
+
? `node "${absoluteStatuslinePath}"`
|
|
101
|
+
: absoluteStatuslinePath;
|
|
102
|
+
|
|
103
|
+
settings.statusLine = {
|
|
104
|
+
type: 'command',
|
|
105
|
+
command: command,
|
|
106
|
+
padding: 0
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
|
|
110
|
+
console.log(' ✓ Updated .claude/settings.json');
|
|
111
|
+
console.log(` ✓ Command: ${command}`);
|
|
112
|
+
|
|
113
|
+
// Step 4: Success message
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log('✅ Installation complete!');
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('Your statusline will display:');
|
|
118
|
+
console.log(' • Model name (bold cyan)');
|
|
119
|
+
console.log(' • Context usage with progress bar (green/yellow/red)');
|
|
120
|
+
console.log(' • Session time (blue)');
|
|
121
|
+
console.log(' • Plan usage limits from Anthropic API (green/yellow/red)');
|
|
122
|
+
console.log(' • Cost tracking (yellow)');
|
|
123
|
+
console.log(' • Code changes - lines added/removed (green/red)');
|
|
124
|
+
console.log(' • Git branch (cyan)');
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log('The statusline will automatically appear in Claude Code!');
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getStatuslineTemplate() {
|
|
131
|
+
// Read from the template file in the same directory
|
|
132
|
+
const templatePath = path.join(__dirname, 'statusline-template.js');
|
|
133
|
+
|
|
134
|
+
if (fs.existsSync(templatePath)) {
|
|
135
|
+
return fs.readFileSync(templatePath, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Fallback: return inline template
|
|
139
|
+
return `#!/usr/bin/env node
|
|
140
|
+
|
|
141
|
+
const { execSync } = require('child_process');
|
|
142
|
+
const fs = require('fs');
|
|
143
|
+
const path = require('path');
|
|
144
|
+
const https = require('https');
|
|
145
|
+
const os = require('os');
|
|
146
|
+
|
|
147
|
+
// ANSI Color Codes
|
|
148
|
+
const colors = {
|
|
149
|
+
reset: '\\x1b[0m',
|
|
150
|
+
bold: '\\x1b[1m',
|
|
151
|
+
|
|
152
|
+
// Regular colors
|
|
153
|
+
red: '\\x1b[31m',
|
|
154
|
+
green: '\\x1b[32m',
|
|
155
|
+
yellow: '\\x1b[33m',
|
|
156
|
+
blue: '\\x1b[34m',
|
|
157
|
+
magenta: '\\x1b[35m',
|
|
158
|
+
cyan: '\\x1b[36m',
|
|
159
|
+
|
|
160
|
+
// Bright colors
|
|
161
|
+
brightRed: '\\x1b[91m',
|
|
162
|
+
brightGreen: '\\x1b[92m',
|
|
163
|
+
brightYellow: '\\x1b[93m',
|
|
164
|
+
brightCyan: '\\x1b[96m',
|
|
165
|
+
|
|
166
|
+
// Combined styles
|
|
167
|
+
boldCyan: '\\x1b[1;36m',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Cache for usage limits (5 minutes)
|
|
171
|
+
const CACHE_DURATION = 5 * 60 * 1000;
|
|
172
|
+
let usageLimitsCache = {
|
|
173
|
+
data: null,
|
|
174
|
+
timestamp: 0
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Read input JSON from stdin
|
|
178
|
+
let inputData = '';
|
|
179
|
+
process.stdin.on('data', chunk => {
|
|
180
|
+
inputData += chunk;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
process.stdin.on('end', async () => {
|
|
184
|
+
try {
|
|
185
|
+
const data = JSON.parse(inputData);
|
|
186
|
+
const statusLine = await buildStatusLine(data);
|
|
187
|
+
process.stdout.write(statusLine);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
process.stdout.write('Claude Code');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
async function buildStatusLine(data) {
|
|
194
|
+
const parts = [];
|
|
195
|
+
|
|
196
|
+
// 1. Model name
|
|
197
|
+
const modelName = getModelDisplayName(data);
|
|
198
|
+
parts.push(modelName);
|
|
199
|
+
|
|
200
|
+
// 2. Context usage
|
|
201
|
+
const contextInfo = getContextUsage(data);
|
|
202
|
+
if (contextInfo) parts.push(contextInfo);
|
|
203
|
+
|
|
204
|
+
// 3. Session time
|
|
205
|
+
const sessionTime = getSessionTime(data);
|
|
206
|
+
if (sessionTime) parts.push(sessionTime);
|
|
207
|
+
|
|
208
|
+
// 4. Usage limits
|
|
209
|
+
const usageLimits = await getUsageLimits();
|
|
210
|
+
if (usageLimits) parts.push(usageLimits);
|
|
211
|
+
|
|
212
|
+
// 5. Cost
|
|
213
|
+
const costInfo = getCostInfo(data);
|
|
214
|
+
if (costInfo) parts.push(costInfo);
|
|
215
|
+
|
|
216
|
+
// 6. Code changes
|
|
217
|
+
const codeChanges = getCodeChanges(data);
|
|
218
|
+
if (codeChanges) parts.push(codeChanges);
|
|
219
|
+
|
|
220
|
+
// 7. Git branch
|
|
221
|
+
const gitBranch = getGitBranch(data);
|
|
222
|
+
if (gitBranch) parts.push(gitBranch);
|
|
223
|
+
|
|
224
|
+
return parts.join(' | ');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function getModelDisplayName(data) {
|
|
228
|
+
const name = data.model?.display_name || 'Claude';
|
|
229
|
+
return \`\${colors.boldCyan}\${name}\${colors.reset}\`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function getContextUsage(data) {
|
|
233
|
+
if (!data.context_window?.current_usage) return null;
|
|
234
|
+
|
|
235
|
+
const usage = data.context_window.current_usage;
|
|
236
|
+
const windowSize = data.context_window.context_window_size;
|
|
237
|
+
|
|
238
|
+
const currentTokens = (usage.input_tokens || 0) +
|
|
239
|
+
(usage.cache_creation_input_tokens || 0) +
|
|
240
|
+
(usage.cache_read_input_tokens || 0);
|
|
241
|
+
|
|
242
|
+
const percentage = Math.round((currentTokens / windowSize) * 100);
|
|
243
|
+
|
|
244
|
+
const barColor = percentage < 40 ? colors.green :
|
|
245
|
+
percentage < 70 ? colors.yellow : colors.red;
|
|
246
|
+
const percentColor = barColor;
|
|
247
|
+
|
|
248
|
+
const barLength = 10;
|
|
249
|
+
const filled = Math.round((percentage / 100) * barLength);
|
|
250
|
+
const empty = barLength - filled;
|
|
251
|
+
|
|
252
|
+
const bar = \`\${barColor}\${'▓'.repeat(filled)}\${colors.reset}\${'░'.repeat(empty)}\`;
|
|
253
|
+
|
|
254
|
+
return \`\${bar} \${percentColor}\${percentage}%\${colors.reset}\`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function getSessionTime(data) {
|
|
258
|
+
if (!data.session_id || !data.transcript_path) return null;
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
const stats = fs.statSync(data.transcript_path);
|
|
262
|
+
const startTime = stats.birthtimeMs || stats.ctimeMs;
|
|
263
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
|
264
|
+
|
|
265
|
+
const hours = Math.floor(elapsed / 3600);
|
|
266
|
+
const minutes = Math.floor((elapsed % 3600) / 60);
|
|
267
|
+
const seconds = elapsed % 60;
|
|
268
|
+
|
|
269
|
+
let timeStr;
|
|
270
|
+
if (hours > 0) {
|
|
271
|
+
timeStr = \`⏱ \${hours}h \${minutes}m\`;
|
|
272
|
+
} else if (minutes > 0) {
|
|
273
|
+
timeStr = \`⏱ \${minutes}m \${seconds}s\`;
|
|
274
|
+
} else {
|
|
275
|
+
timeStr = \`⏱ \${seconds}s\`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return \`\${colors.blue}\${timeStr}\${colors.reset}\`;
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function getCostInfo(data) {
|
|
285
|
+
if (!data.cost?.total_cost_usd) return null;
|
|
286
|
+
const cost = data.cost.total_cost_usd.toFixed(4);
|
|
287
|
+
return \`\${colors.yellow}$\${cost}\${colors.reset}\`;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getCodeChanges(data) {
|
|
291
|
+
if (!data.cost) return null;
|
|
292
|
+
|
|
293
|
+
const added = data.cost.total_lines_added || 0;
|
|
294
|
+
const removed = data.cost.total_lines_removed || 0;
|
|
295
|
+
|
|
296
|
+
if (added === 0 && removed === 0) return null;
|
|
297
|
+
|
|
298
|
+
const parts = [];
|
|
299
|
+
if (added > 0) parts.push(\`\${colors.green}+\${added}\${colors.reset}\`);
|
|
300
|
+
if (removed > 0) parts.push(\`\${colors.red}-\${removed}\${colors.reset}\`);
|
|
301
|
+
|
|
302
|
+
return parts.join(' ');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function getUsageLimits() {
|
|
306
|
+
const now = Date.now();
|
|
307
|
+
if (usageLimitsCache.data && (now - usageLimitsCache.timestamp) < CACHE_DURATION) {
|
|
308
|
+
return formatUsageLimits(usageLimitsCache.data);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const usage = await fetchUsageLimitsFromAPI();
|
|
312
|
+
|
|
313
|
+
if (usage) {
|
|
314
|
+
usageLimitsCache.data = usage;
|
|
315
|
+
usageLimitsCache.timestamp = now;
|
|
316
|
+
return formatUsageLimits(usage);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function getAccessToken() {
|
|
323
|
+
const platform = os.platform();
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
if (platform === 'darwin') {
|
|
327
|
+
const token = execSync(
|
|
328
|
+
'security find-generic-password -s "Claude Code-credentials" -w',
|
|
329
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
330
|
+
).trim();
|
|
331
|
+
|
|
332
|
+
const parsed = JSON.parse(token);
|
|
333
|
+
if (parsed.claudeAiOauth?.accessToken) {
|
|
334
|
+
return parsed.claudeAiOauth.accessToken;
|
|
335
|
+
}
|
|
336
|
+
return token;
|
|
337
|
+
} else if (platform === 'win32' || platform === 'linux') {
|
|
338
|
+
const appDataPaths = [
|
|
339
|
+
path.join(os.homedir(), '.claude', '.credentials.json'),
|
|
340
|
+
path.join(process.env.APPDATA || '', 'Claude Code', 'credentials'),
|
|
341
|
+
path.join(process.env.LOCALAPPDATA || '', 'Claude Code', 'credentials'),
|
|
342
|
+
path.join(os.homedir(), '.claude', 'credentials'),
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
for (const credPath of appDataPaths) {
|
|
346
|
+
if (fs.existsSync(credPath)) {
|
|
347
|
+
const content = fs.readFileSync(credPath, 'utf8');
|
|
348
|
+
try {
|
|
349
|
+
const parsed = JSON.parse(content);
|
|
350
|
+
if (parsed.claudeAiOauth?.accessToken) {
|
|
351
|
+
return parsed.claudeAiOauth.accessToken;
|
|
352
|
+
}
|
|
353
|
+
if (parsed.access_token || parsed.token) {
|
|
354
|
+
return parsed.access_token || parsed.token;
|
|
355
|
+
}
|
|
356
|
+
} catch (e) {
|
|
357
|
+
return content.trim();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} catch (error) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function fetchUsageLimitsFromAPI() {
|
|
370
|
+
return new Promise((resolve) => {
|
|
371
|
+
const accessToken = getAccessToken();
|
|
372
|
+
if (!accessToken) {
|
|
373
|
+
resolve(null);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const options = {
|
|
378
|
+
hostname: 'api.anthropic.com',
|
|
379
|
+
path: '/api/oauth/usage',
|
|
380
|
+
method: 'GET',
|
|
381
|
+
headers: {
|
|
382
|
+
'Authorization': \`Bearer \${accessToken}\`,
|
|
383
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
384
|
+
'User-Agent': 'claude-code/2.0.76'
|
|
385
|
+
},
|
|
386
|
+
timeout: 2000
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const req = https.request(options, (res) => {
|
|
390
|
+
let data = '';
|
|
391
|
+
res.on('data', (chunk) => { data += chunk; });
|
|
392
|
+
res.on('end', () => {
|
|
393
|
+
try {
|
|
394
|
+
if (res.statusCode === 200) {
|
|
395
|
+
resolve(JSON.parse(data));
|
|
396
|
+
} else {
|
|
397
|
+
resolve(null);
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
resolve(null);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
req.on('error', () => resolve(null));
|
|
406
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
407
|
+
req.end();
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function formatUsageLimits(usage) {
|
|
412
|
+
if (!usage?.five_hour) return null;
|
|
413
|
+
|
|
414
|
+
const utilization = usage.five_hour.utilization;
|
|
415
|
+
const resetsAt = usage.five_hour.resets_at;
|
|
416
|
+
|
|
417
|
+
if (utilization === undefined || !resetsAt) return null;
|
|
418
|
+
|
|
419
|
+
const usageColor = utilization < 50 ? colors.green :
|
|
420
|
+
utilization < 80 ? colors.yellow : colors.red;
|
|
421
|
+
|
|
422
|
+
const parts = [];
|
|
423
|
+
parts.push(\`\${usageColor}Plan: \${Math.round(utilization)}% used\${colors.reset}\`);
|
|
424
|
+
|
|
425
|
+
const resetTime = new Date(resetsAt);
|
|
426
|
+
const diff = resetTime - new Date();
|
|
427
|
+
|
|
428
|
+
if (diff > 0) {
|
|
429
|
+
const hours = Math.floor(diff / (1000 * 60 * 60));
|
|
430
|
+
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
|
|
431
|
+
|
|
432
|
+
let resetStr;
|
|
433
|
+
if (hours > 0) {
|
|
434
|
+
resetStr = \`Resets in \${hours}h \${minutes}m\`;
|
|
435
|
+
} else if (minutes > 0) {
|
|
436
|
+
resetStr = \`Resets in \${minutes}m\`;
|
|
437
|
+
} else {
|
|
438
|
+
resetStr = \`Resets soon\`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
parts.push(\`\${colors.magenta}\${resetStr}\${colors.reset}\`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return parts.join(' | ');
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getGitBranch(data) {
|
|
448
|
+
try {
|
|
449
|
+
const cwd = data.workspace?.project_dir || data.workspace?.current_dir || process.cwd();
|
|
450
|
+
const gitDir = path.join(cwd, '.git');
|
|
451
|
+
|
|
452
|
+
if (!fs.existsSync(gitDir)) return null;
|
|
453
|
+
|
|
454
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
455
|
+
cwd: cwd,
|
|
456
|
+
encoding: 'utf8',
|
|
457
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
458
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' }
|
|
459
|
+
}).trim();
|
|
460
|
+
|
|
461
|
+
if (branch && branch !== 'HEAD') {
|
|
462
|
+
return \`\${colors.cyan}⎇ \${branch}\${colors.reset}\`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return null;
|
|
466
|
+
} catch (error) {
|
|
467
|
+
return null;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Run init if this is the main module
|
|
474
|
+
if (require.main === module) {
|
|
475
|
+
initStatusline();
|
|
476
|
+
}
|
package/install.ps1
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# PowerShell install script for Claude Code Statusline
|
|
2
|
+
# Usage: .\install.ps1
|
|
3
|
+
|
|
4
|
+
Write-Host ""
|
|
5
|
+
Write-Host "🚀 Installing Claude Code Statusline..." -ForegroundColor Cyan
|
|
6
|
+
Write-Host ""
|
|
7
|
+
|
|
8
|
+
# Check if .claude directory exists
|
|
9
|
+
if (-not (Test-Path ".claude")) {
|
|
10
|
+
Write-Host "📁 Creating .claude directory..." -ForegroundColor Yellow
|
|
11
|
+
New-Item -ItemType Directory -Path ".claude" | Out-Null
|
|
12
|
+
Write-Host " ✓ Created .claude/" -ForegroundColor Green
|
|
13
|
+
} else {
|
|
14
|
+
Write-Host "📁 .claude directory already exists" -ForegroundColor Yellow
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Backup existing statusline if it exists
|
|
18
|
+
if (Test-Path ".claude\statusline.js") {
|
|
19
|
+
Write-Host ""
|
|
20
|
+
Write-Host "⚠️ Backing up existing statusline.js..." -ForegroundColor Yellow
|
|
21
|
+
Copy-Item ".claude\statusline.js" ".claude\statusline.js.backup"
|
|
22
|
+
Write-Host " ✓ Backed up to .claude\statusline.js.backup" -ForegroundColor Green
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Copy statusline.js
|
|
26
|
+
Write-Host ""
|
|
27
|
+
Write-Host "📝 Installing statusline.js..." -ForegroundColor Yellow
|
|
28
|
+
|
|
29
|
+
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
30
|
+
$templatePath = Join-Path $scriptDir "statusline-template.js"
|
|
31
|
+
|
|
32
|
+
if (Test-Path $templatePath) {
|
|
33
|
+
Copy-Item $templatePath ".claude\statusline.js"
|
|
34
|
+
Write-Host " ✓ Installed from local template" -ForegroundColor Green
|
|
35
|
+
} else {
|
|
36
|
+
Write-Host " ❌ Template not found. Please run from installer directory." -ForegroundColor Red
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# Update settings.json
|
|
41
|
+
Write-Host ""
|
|
42
|
+
Write-Host "⚙️ Updating settings.json..." -ForegroundColor Yellow
|
|
43
|
+
|
|
44
|
+
$settingsFile = ".claude\settings.json"
|
|
45
|
+
|
|
46
|
+
# Get absolute path for statusline.js
|
|
47
|
+
$absolutePath = Resolve-Path ".claude\statusline.js" | Select-Object -ExpandProperty Path
|
|
48
|
+
$command = "node `"$absolutePath`""
|
|
49
|
+
|
|
50
|
+
if (Test-Path $settingsFile) {
|
|
51
|
+
# Read and parse existing settings
|
|
52
|
+
$settings = Get-Content $settingsFile | ConvertFrom-Json
|
|
53
|
+
|
|
54
|
+
# Add or update statusLine
|
|
55
|
+
$settings | Add-Member -MemberType NoteProperty -Name "statusLine" -Value @{
|
|
56
|
+
type = "command"
|
|
57
|
+
command = $command
|
|
58
|
+
padding = 0
|
|
59
|
+
} -Force
|
|
60
|
+
|
|
61
|
+
# Write back to file
|
|
62
|
+
$settings | ConvertTo-Json -Depth 10 | Set-Content $settingsFile
|
|
63
|
+
Write-Host " ✓ Updated existing settings.json" -ForegroundColor Green
|
|
64
|
+
} else {
|
|
65
|
+
# Create new settings.json
|
|
66
|
+
@{
|
|
67
|
+
statusLine = @{
|
|
68
|
+
type = "command"
|
|
69
|
+
command = $command
|
|
70
|
+
padding = 0
|
|
71
|
+
}
|
|
72
|
+
} | ConvertTo-Json -Depth 10 | Set-Content $settingsFile
|
|
73
|
+
Write-Host " ✓ Created .claude\settings.json" -ForegroundColor Green
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Write-Host " ✓ Command: $command" -ForegroundColor Green
|
|
77
|
+
|
|
78
|
+
# Success message
|
|
79
|
+
Write-Host ""
|
|
80
|
+
Write-Host "✅ Installation complete!" -ForegroundColor Green
|
|
81
|
+
Write-Host ""
|
|
82
|
+
Write-Host "Your statusline will display:" -ForegroundColor Cyan
|
|
83
|
+
Write-Host " • Model name (bold cyan)"
|
|
84
|
+
Write-Host " • Context usage with progress bar (green/yellow/red)"
|
|
85
|
+
Write-Host " • Session time (blue)"
|
|
86
|
+
Write-Host " • Plan usage limits from Anthropic API (green/yellow/red)"
|
|
87
|
+
Write-Host " • Cost tracking (yellow)"
|
|
88
|
+
Write-Host " • Code changes - lines added/removed (green/red)"
|
|
89
|
+
Write-Host " • Git branch (cyan)"
|
|
90
|
+
Write-Host ""
|
|
91
|
+
Write-Host "The statusline will automatically appear in Claude Code!" -ForegroundColor Green
|
|
92
|
+
Write-Host ""
|
package/install.sh
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Simple install script for Claude Code Statusline
|
|
4
|
+
# Usage: curl -sSL https://your-url/install.sh | bash
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo ""
|
|
9
|
+
echo "🚀 Installing Claude Code Statusline..."
|
|
10
|
+
echo ""
|
|
11
|
+
|
|
12
|
+
# Check if .claude directory exists
|
|
13
|
+
if [ ! -d ".claude" ]; then
|
|
14
|
+
echo "📁 Creating .claude directory..."
|
|
15
|
+
mkdir -p .claude
|
|
16
|
+
echo " ✓ Created .claude/"
|
|
17
|
+
else
|
|
18
|
+
echo "📁 .claude directory already exists"
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
# Backup existing statusline if it exists
|
|
22
|
+
if [ -f ".claude/statusline.js" ]; then
|
|
23
|
+
echo ""
|
|
24
|
+
echo "⚠️ Backing up existing statusline.js..."
|
|
25
|
+
cp .claude/statusline.js .claude/statusline.js.backup
|
|
26
|
+
echo " ✓ Backed up to .claude/statusline.js.backup"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Download or copy statusline.js
|
|
30
|
+
echo ""
|
|
31
|
+
echo "📝 Installing statusline.js..."
|
|
32
|
+
|
|
33
|
+
# If running from local directory
|
|
34
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
35
|
+
if [ -f "$SCRIPT_DIR/statusline-template.js" ]; then
|
|
36
|
+
cp "$SCRIPT_DIR/statusline-template.js" .claude/statusline.js
|
|
37
|
+
chmod +x .claude/statusline.js
|
|
38
|
+
echo " ✓ Installed from local template"
|
|
39
|
+
else
|
|
40
|
+
# Download from URL (if hosted)
|
|
41
|
+
# curl -sSL https://your-url/statusline.js -o .claude/statusline.js
|
|
42
|
+
echo " ❌ Template not found. Please run from installer directory."
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Update settings.json
|
|
47
|
+
echo ""
|
|
48
|
+
echo "⚙️ Updating settings.json..."
|
|
49
|
+
|
|
50
|
+
SETTINGS_FILE=".claude/settings.json"
|
|
51
|
+
|
|
52
|
+
# Get absolute path for statusline.js
|
|
53
|
+
ABSOLUTE_PATH="$(cd "$(dirname ".claude/statusline.js")" && pwd)/$(basename ".claude/statusline.js")"
|
|
54
|
+
|
|
55
|
+
# For macOS/Linux, use the absolute path directly (shebang will handle node)
|
|
56
|
+
# For better compatibility, prefix with node command
|
|
57
|
+
COMMAND="node \"$ABSOLUTE_PATH\""
|
|
58
|
+
|
|
59
|
+
if [ -f "$SETTINGS_FILE" ]; then
|
|
60
|
+
# Check if jq is available
|
|
61
|
+
if command -v jq &> /dev/null; then
|
|
62
|
+
# Use jq to update JSON
|
|
63
|
+
jq --arg cmd "$COMMAND" '.statusLine = {"type": "command", "command": $cmd, "padding": 0}' "$SETTINGS_FILE" > "$SETTINGS_FILE.tmp"
|
|
64
|
+
mv "$SETTINGS_FILE.tmp" "$SETTINGS_FILE"
|
|
65
|
+
echo " ✓ Updated existing settings.json"
|
|
66
|
+
else
|
|
67
|
+
echo " ⚠️ jq not found. Please manually add statusLine config to settings.json"
|
|
68
|
+
echo ""
|
|
69
|
+
echo " Add this to your .claude/settings.json:"
|
|
70
|
+
echo ' "statusLine": {'
|
|
71
|
+
echo ' "type": "command",'
|
|
72
|
+
echo " \"command\": \"$COMMAND\","
|
|
73
|
+
echo ' "padding": 0'
|
|
74
|
+
echo ' }'
|
|
75
|
+
fi
|
|
76
|
+
else
|
|
77
|
+
# Create new settings.json
|
|
78
|
+
cat > "$SETTINGS_FILE" << EOF
|
|
79
|
+
{
|
|
80
|
+
"statusLine": {
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "$COMMAND",
|
|
83
|
+
"padding": 0
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
EOF
|
|
87
|
+
echo " ✓ Created .claude/settings.json"
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
echo " ✓ Command: $COMMAND"
|
|
91
|
+
|
|
92
|
+
# Success message
|
|
93
|
+
echo ""
|
|
94
|
+
echo "✅ Installation complete!"
|
|
95
|
+
echo ""
|
|
96
|
+
echo "Your statusline will display:"
|
|
97
|
+
echo " • Model name (bold cyan)"
|
|
98
|
+
echo " • Context usage with progress bar (green/yellow/red)"
|
|
99
|
+
echo " • Session time (blue)"
|
|
100
|
+
echo " • Plan usage limits from Anthropic API (green/yellow/red)"
|
|
101
|
+
echo " • Cost tracking (yellow)"
|
|
102
|
+
echo " • Code changes - lines added/removed (green/red)"
|
|
103
|
+
echo " • Git branch (cyan)"
|
|
104
|
+
echo ""
|
|
105
|
+
echo "The statusline will automatically appear in Claude Code!"
|
|
106
|
+
echo ""
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-statusline-pro",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Beautiful statusline for Claude Code with usage limits, context tracking, and colors",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cc-statusline": "cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude-code",
|
|
14
|
+
"statusline",
|
|
15
|
+
"cli",
|
|
16
|
+
"anthropic"
|
|
17
|
+
],
|
|
18
|
+
"author": "Your Name",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=14.0.0"
|
|
22
|
+
}
|
|
23
|
+
}
|