@naarang/glancebar 1.0.2 → 1.0.4
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 +53 -10
- package/package.json +1 -1
- package/src/cli.ts +353 -15
package/README.md
CHANGED
|
@@ -7,12 +7,14 @@ A customizable statusline for [Claude Code](https://claude.com/product/claude-co
|
|
|
7
7
|
|
|
8
8
|
## Features
|
|
9
9
|
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
10
|
+
- **Session info** - Project name, git branch, model, cost, lines changed, and context usage
|
|
11
|
+
- **System stats** - CPU and memory usage (optional)
|
|
12
|
+
- **Calendar events** - Upcoming events from multiple Google accounts
|
|
13
|
+
- **Meeting warnings** - Red alert when a meeting is 5 minutes away
|
|
14
|
+
- **Health reminders** - Water, stretch, and eye break reminders
|
|
15
|
+
- **Color-coded** - Everything has distinct colors for quick scanning
|
|
16
|
+
- **Fully configurable** via CLI
|
|
17
|
+
- **Cross-platform** support (Windows, macOS, Linux)
|
|
16
18
|
|
|
17
19
|
## Requirements
|
|
18
20
|
|
|
@@ -138,8 +140,14 @@ glancebar config --max-title 80
|
|
|
138
140
|
# Toggle calendar name display
|
|
139
141
|
glancebar config --show-calendar false
|
|
140
142
|
|
|
141
|
-
# Enable/disable
|
|
143
|
+
# Enable/disable health reminders
|
|
142
144
|
glancebar config --water-reminder true
|
|
145
|
+
glancebar config --stretch-reminder true
|
|
146
|
+
glancebar config --eye-reminder true
|
|
147
|
+
|
|
148
|
+
# Enable/disable system stats
|
|
149
|
+
glancebar config --cpu-usage true
|
|
150
|
+
glancebar config --memory-usage true
|
|
143
151
|
|
|
144
152
|
# Reset to defaults
|
|
145
153
|
glancebar config --reset
|
|
@@ -147,15 +155,46 @@ glancebar config --reset
|
|
|
147
155
|
|
|
148
156
|
## Display Format
|
|
149
157
|
|
|
158
|
+
Example output:
|
|
159
|
+
```
|
|
160
|
+
glancebar | main* | Opus | $0.12 | +156 -23 | 9.7k/200k (5%) | In 15m: Team Standup (work)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Session Info (from Claude Code)
|
|
164
|
+
|
|
165
|
+
| Field | Color | Example |
|
|
166
|
+
|-------|-------|---------|
|
|
167
|
+
| Project name | Blue | `glancebar`, `my-app` |
|
|
168
|
+
| Git branch | Magenta | `main`, `feature-x*` (asterisk = uncommitted changes) |
|
|
169
|
+
| Model name | Yellow | `Opus`, `Sonnet` |
|
|
170
|
+
| Cost | Green | `$0.01`, `$0.1234` |
|
|
171
|
+
| Lines changed | Green/Red | `+156 -23` |
|
|
172
|
+
| Context usage | Green/Yellow/Red | `9.7k/200k (5%)` |
|
|
173
|
+
| CPU usage | Green/Yellow/Red | `CPU 12%` |
|
|
174
|
+
| Memory usage | Green/Yellow/Red | `Mem 8.2/16.0GB` |
|
|
175
|
+
|
|
176
|
+
Context usage color changes based on percentage:
|
|
177
|
+
- **Green**: < 50%
|
|
178
|
+
- **Yellow**: 50-80%
|
|
179
|
+
- **Red**: > 80%
|
|
180
|
+
|
|
181
|
+
### Calendar Events
|
|
182
|
+
|
|
150
183
|
| State | Format | Example |
|
|
151
184
|
|-------|--------|---------|
|
|
185
|
+
| **Meeting warning** | Red alert when ≤5m away | `Meeting in 3m - wrap up!` |
|
|
152
186
|
| Upcoming (within threshold) | `In Xm: Title (account)` | `In 15m: Team Standup (work)` |
|
|
153
187
|
| Current | `Now: Title (account)` | `Now: Team Standup (work)` |
|
|
154
188
|
| Later | `HH:MM AM/PM: Title (account)` | `2:30 PM: Meeting (work)` |
|
|
155
189
|
| No events | `No upcoming events` | |
|
|
156
|
-
| Water reminder | Random hydration message | `Stay hydrated! Drink some water` |
|
|
157
190
|
|
|
158
|
-
|
|
191
|
+
### Health Reminders (~30% chance)
|
|
192
|
+
|
|
193
|
+
| Type | Color | Example |
|
|
194
|
+
|------|-------|---------|
|
|
195
|
+
| Water | Cyan | `Stay hydrated! Drink some water` |
|
|
196
|
+
| Stretch | Green | `Time to stretch! Stand up and move` |
|
|
197
|
+
| Eye break | Magenta | `Eye break! Look 20ft away for 20s` |
|
|
159
198
|
|
|
160
199
|
## Configuration
|
|
161
200
|
|
|
@@ -177,7 +216,11 @@ All configuration is stored in `~/.glancebar/`:
|
|
|
177
216
|
| `countdownThresholdMinutes` | 60 | Minutes threshold for countdown display |
|
|
178
217
|
| `maxTitleLength` | 120 | Maximum event title length |
|
|
179
218
|
| `showCalendarName` | true | Show account name after event |
|
|
180
|
-
| `waterReminderEnabled` | true | Enable random water break reminders
|
|
219
|
+
| `waterReminderEnabled` | true | Enable random water break reminders |
|
|
220
|
+
| `stretchReminderEnabled` | true | Enable random stretch/posture reminders |
|
|
221
|
+
| `eyeReminderEnabled` | true | Enable random eye break reminders (20-20-20 rule) |
|
|
222
|
+
| `showCpuUsage` | false | Show CPU usage percentage |
|
|
223
|
+
| `showMemoryUsage` | false | Show memory usage |
|
|
181
224
|
|
|
182
225
|
## Building from Source
|
|
183
226
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -16,6 +16,10 @@ interface Config {
|
|
|
16
16
|
countdownThresholdMinutes: number;
|
|
17
17
|
maxTitleLength: number;
|
|
18
18
|
waterReminderEnabled: boolean;
|
|
19
|
+
stretchReminderEnabled: boolean;
|
|
20
|
+
eyeReminderEnabled: boolean;
|
|
21
|
+
showCpuUsage: boolean;
|
|
22
|
+
showMemoryUsage: boolean;
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
const COLORS: Record<string, string> = {
|
|
@@ -47,6 +51,10 @@ const DEFAULT_CONFIG: Config = {
|
|
|
47
51
|
countdownThresholdMinutes: 60,
|
|
48
52
|
maxTitleLength: 120,
|
|
49
53
|
waterReminderEnabled: true,
|
|
54
|
+
stretchReminderEnabled: true,
|
|
55
|
+
eyeReminderEnabled: true,
|
|
56
|
+
showCpuUsage: false,
|
|
57
|
+
showMemoryUsage: false,
|
|
50
58
|
};
|
|
51
59
|
|
|
52
60
|
const WATER_REMINDERS = [
|
|
@@ -60,6 +68,26 @@ const WATER_REMINDERS = [
|
|
|
60
68
|
"Quick reminder: Drink water!",
|
|
61
69
|
];
|
|
62
70
|
|
|
71
|
+
const STRETCH_REMINDERS = [
|
|
72
|
+
"Time to stretch! Stand up and move",
|
|
73
|
+
"Stretch break! Roll your shoulders",
|
|
74
|
+
"Stand up and stretch your legs",
|
|
75
|
+
"Posture check! Sit up straight",
|
|
76
|
+
"Take a quick stretch break",
|
|
77
|
+
"Move your body! Quick stretch",
|
|
78
|
+
"Stretch your neck and shoulders",
|
|
79
|
+
"Stand up! Your body will thank you",
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const EYE_REMINDERS = [
|
|
83
|
+
"Eye break! Look 20ft away for 20s",
|
|
84
|
+
"Rest your eyes - look at something distant",
|
|
85
|
+
"20-20-20: Look away from screen",
|
|
86
|
+
"Give your eyes a break!",
|
|
87
|
+
"Look away from the screen for a moment",
|
|
88
|
+
"Eye rest time! Focus on something far",
|
|
89
|
+
];
|
|
90
|
+
|
|
63
91
|
function getConfigDir(): string {
|
|
64
92
|
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
65
93
|
return join(home, ".glancebar");
|
|
@@ -410,6 +438,75 @@ function formatCountdown(minutes: number): string {
|
|
|
410
438
|
return mins === 0 ? `In ${hours}h` : `In ${hours}h${mins}m`;
|
|
411
439
|
}
|
|
412
440
|
|
|
441
|
+
function getMeetingWarning(event: CalendarEvent | null): string | null {
|
|
442
|
+
if (!event) return null;
|
|
443
|
+
|
|
444
|
+
const now = new Date();
|
|
445
|
+
const minutesUntil = Math.round((event.start.getTime() - now.getTime()) / 60000);
|
|
446
|
+
|
|
447
|
+
// Warning when meeting is 5 minutes or less away
|
|
448
|
+
if (minutesUntil > 0 && minutesUntil <= 5) {
|
|
449
|
+
return `${COLORS.brightRed}Meeting in ${minutesUntil}m - wrap up!${COLORS.reset}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// ============================================================================
|
|
456
|
+
// System Stats
|
|
457
|
+
// ============================================================================
|
|
458
|
+
|
|
459
|
+
function getCpuUsage(): string | null {
|
|
460
|
+
try {
|
|
461
|
+
const os = require("os");
|
|
462
|
+
const cpus = os.cpus();
|
|
463
|
+
|
|
464
|
+
let totalIdle = 0;
|
|
465
|
+
let totalTick = 0;
|
|
466
|
+
|
|
467
|
+
for (const cpu of cpus) {
|
|
468
|
+
for (const type in cpu.times) {
|
|
469
|
+
totalTick += cpu.times[type as keyof typeof cpu.times];
|
|
470
|
+
}
|
|
471
|
+
totalIdle += cpu.times.idle;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const usage = Math.round(100 - (totalIdle / totalTick) * 100);
|
|
475
|
+
|
|
476
|
+
// Color based on usage
|
|
477
|
+
let color = COLORS.green;
|
|
478
|
+
if (usage >= 80) color = COLORS.red;
|
|
479
|
+
else if (usage >= 50) color = COLORS.yellow;
|
|
480
|
+
|
|
481
|
+
return `${color}CPU ${usage}%${COLORS.reset}`;
|
|
482
|
+
} catch {
|
|
483
|
+
return null;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function getMemoryUsage(): string | null {
|
|
488
|
+
try {
|
|
489
|
+
const os = require("os");
|
|
490
|
+
const totalMem = os.totalmem();
|
|
491
|
+
const freeMem = os.freemem();
|
|
492
|
+
const usedMem = totalMem - freeMem;
|
|
493
|
+
const usagePercent = Math.round((usedMem / totalMem) * 100);
|
|
494
|
+
|
|
495
|
+
// Format used memory
|
|
496
|
+
const usedGB = (usedMem / (1024 * 1024 * 1024)).toFixed(1);
|
|
497
|
+
const totalGB = (totalMem / (1024 * 1024 * 1024)).toFixed(1);
|
|
498
|
+
|
|
499
|
+
// Color based on usage
|
|
500
|
+
let color = COLORS.green;
|
|
501
|
+
if (usagePercent >= 80) color = COLORS.red;
|
|
502
|
+
else if (usagePercent >= 50) color = COLORS.yellow;
|
|
503
|
+
|
|
504
|
+
return `${color}Mem ${usedGB}/${totalGB}GB${COLORS.reset}`;
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
413
510
|
function formatTime(date: Date): string {
|
|
414
511
|
const hours = date.getHours();
|
|
415
512
|
const minutes = date.getMinutes();
|
|
@@ -440,13 +537,17 @@ Usage:
|
|
|
440
537
|
glancebar config --max-title <length> Set max title length (default: 120)
|
|
441
538
|
glancebar config --show-calendar <true|false> Show calendar name (default: true)
|
|
442
539
|
glancebar config --water-reminder <true|false> Enable/disable water reminders (default: true)
|
|
540
|
+
glancebar config --stretch-reminder <true|false> Enable/disable stretch reminders (default: true)
|
|
541
|
+
glancebar config --eye-reminder <true|false> Enable/disable eye break reminders (default: true)
|
|
542
|
+
glancebar config --cpu-usage <true|false> Show CPU usage (default: false)
|
|
543
|
+
glancebar config --memory-usage <true|false> Show memory usage (default: false)
|
|
443
544
|
glancebar config --reset Reset to default configuration
|
|
444
545
|
glancebar setup Show setup instructions
|
|
445
546
|
|
|
446
547
|
Examples:
|
|
447
548
|
glancebar auth --add user@gmail.com
|
|
448
549
|
glancebar config --lookahead 12
|
|
449
|
-
glancebar config --
|
|
550
|
+
glancebar config --stretch-reminder false
|
|
450
551
|
|
|
451
552
|
Config location: ${getConfigDir()}
|
|
452
553
|
`);
|
|
@@ -681,6 +782,62 @@ function handleConfig(args: string[]) {
|
|
|
681
782
|
return;
|
|
682
783
|
}
|
|
683
784
|
|
|
785
|
+
// Handle --stretch-reminder
|
|
786
|
+
const stretchReminderIndex = args.indexOf("--stretch-reminder");
|
|
787
|
+
if (stretchReminderIndex !== -1) {
|
|
788
|
+
const value = args[stretchReminderIndex + 1]?.toLowerCase();
|
|
789
|
+
if (value !== "true" && value !== "false") {
|
|
790
|
+
console.error("Error: --stretch-reminder must be 'true' or 'false'");
|
|
791
|
+
process.exit(1);
|
|
792
|
+
}
|
|
793
|
+
config.stretchReminderEnabled = value === "true";
|
|
794
|
+
saveConfig(config);
|
|
795
|
+
console.log(`Stretch reminder ${value === "true" ? "enabled" : "disabled"}`);
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Handle --eye-reminder
|
|
800
|
+
const eyeReminderIndex = args.indexOf("--eye-reminder");
|
|
801
|
+
if (eyeReminderIndex !== -1) {
|
|
802
|
+
const value = args[eyeReminderIndex + 1]?.toLowerCase();
|
|
803
|
+
if (value !== "true" && value !== "false") {
|
|
804
|
+
console.error("Error: --eye-reminder must be 'true' or 'false'");
|
|
805
|
+
process.exit(1);
|
|
806
|
+
}
|
|
807
|
+
config.eyeReminderEnabled = value === "true";
|
|
808
|
+
saveConfig(config);
|
|
809
|
+
console.log(`Eye break reminder ${value === "true" ? "enabled" : "disabled"}`);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// Handle --cpu-usage
|
|
814
|
+
const cpuUsageIndex = args.indexOf("--cpu-usage");
|
|
815
|
+
if (cpuUsageIndex !== -1) {
|
|
816
|
+
const value = args[cpuUsageIndex + 1]?.toLowerCase();
|
|
817
|
+
if (value !== "true" && value !== "false") {
|
|
818
|
+
console.error("Error: --cpu-usage must be 'true' or 'false'");
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
config.showCpuUsage = value === "true";
|
|
822
|
+
saveConfig(config);
|
|
823
|
+
console.log(`CPU usage display ${value === "true" ? "enabled" : "disabled"}`);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Handle --memory-usage
|
|
828
|
+
const memoryUsageIndex = args.indexOf("--memory-usage");
|
|
829
|
+
if (memoryUsageIndex !== -1) {
|
|
830
|
+
const value = args[memoryUsageIndex + 1]?.toLowerCase();
|
|
831
|
+
if (value !== "true" && value !== "false") {
|
|
832
|
+
console.error("Error: --memory-usage must be 'true' or 'false'");
|
|
833
|
+
process.exit(1);
|
|
834
|
+
}
|
|
835
|
+
config.showMemoryUsage = value === "true";
|
|
836
|
+
saveConfig(config);
|
|
837
|
+
console.log(`Memory usage display ${value === "true" ? "enabled" : "disabled"}`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
|
|
684
841
|
// Show current config
|
|
685
842
|
console.log(`
|
|
686
843
|
Glancebar Configuration
|
|
@@ -696,34 +853,209 @@ Calendar Settings:
|
|
|
696
853
|
|
|
697
854
|
Reminders:
|
|
698
855
|
Water reminder: ${config.waterReminderEnabled ? "enabled" : "disabled"}
|
|
856
|
+
Stretch reminder: ${config.stretchReminderEnabled ? "enabled" : "disabled"}
|
|
857
|
+
Eye break reminder: ${config.eyeReminderEnabled ? "enabled" : "disabled"}
|
|
858
|
+
|
|
859
|
+
System Stats:
|
|
860
|
+
CPU usage: ${config.showCpuUsage ? "enabled" : "disabled"}
|
|
861
|
+
Memory usage: ${config.showMemoryUsage ? "enabled" : "disabled"}
|
|
699
862
|
`);
|
|
700
863
|
}
|
|
701
864
|
|
|
702
|
-
function
|
|
703
|
-
|
|
865
|
+
function getRandomReminder(config: Config): string | null {
|
|
866
|
+
const enabledReminders: Array<() => string> = [];
|
|
867
|
+
|
|
868
|
+
if (config.waterReminderEnabled) {
|
|
869
|
+
enabledReminders.push(() => {
|
|
870
|
+
const reminder = WATER_REMINDERS[Math.floor(Math.random() * WATER_REMINDERS.length)];
|
|
871
|
+
return `${COLORS.brightCyan}${reminder}${COLORS.reset}`;
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (config.stretchReminderEnabled) {
|
|
876
|
+
enabledReminders.push(() => {
|
|
877
|
+
const reminder = STRETCH_REMINDERS[Math.floor(Math.random() * STRETCH_REMINDERS.length)];
|
|
878
|
+
return `${COLORS.brightGreen}${reminder}${COLORS.reset}`;
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
if (config.eyeReminderEnabled) {
|
|
883
|
+
enabledReminders.push(() => {
|
|
884
|
+
const reminder = EYE_REMINDERS[Math.floor(Math.random() * EYE_REMINDERS.length)];
|
|
885
|
+
return `${COLORS.brightMagenta}${reminder}${COLORS.reset}`;
|
|
886
|
+
});
|
|
887
|
+
}
|
|
704
888
|
|
|
705
|
-
|
|
706
|
-
|
|
889
|
+
if (enabledReminders.length === 0) return null;
|
|
890
|
+
|
|
891
|
+
// ~30% chance to show any reminder
|
|
892
|
+
if (Math.random() >= 0.3) return null;
|
|
893
|
+
|
|
894
|
+
// Pick a random reminder type from enabled ones
|
|
895
|
+
const randomPicker = enabledReminders[Math.floor(Math.random() * enabledReminders.length)];
|
|
896
|
+
return randomPicker();
|
|
707
897
|
}
|
|
708
898
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
899
|
+
interface ClaudeCodeStatus {
|
|
900
|
+
model?: { display_name?: string };
|
|
901
|
+
cost?: {
|
|
902
|
+
total_cost_usd?: number;
|
|
903
|
+
total_lines_added?: number;
|
|
904
|
+
total_lines_removed?: number;
|
|
905
|
+
};
|
|
906
|
+
cwd?: string;
|
|
907
|
+
workspace?: {
|
|
908
|
+
project_dir?: string;
|
|
909
|
+
};
|
|
910
|
+
context_window?: {
|
|
911
|
+
context_window_size?: number;
|
|
912
|
+
current_usage?: {
|
|
913
|
+
input_tokens?: number;
|
|
914
|
+
output_tokens?: number;
|
|
915
|
+
cache_creation_input_tokens?: number;
|
|
916
|
+
cache_read_input_tokens?: number;
|
|
917
|
+
};
|
|
918
|
+
};
|
|
712
919
|
}
|
|
713
920
|
|
|
714
|
-
|
|
715
|
-
// Consume stdin (Claude Code sends JSON)
|
|
921
|
+
function getGitBranch(cwd?: string): string | null {
|
|
716
922
|
try {
|
|
717
|
-
|
|
718
|
-
|
|
923
|
+
const { execSync } = require("child_process");
|
|
924
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
925
|
+
cwd: cwd || process.cwd(),
|
|
926
|
+
encoding: "utf-8",
|
|
927
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
928
|
+
}).trim();
|
|
929
|
+
|
|
930
|
+
// Check if there are uncommitted changes
|
|
931
|
+
let isDirty = false;
|
|
932
|
+
try {
|
|
933
|
+
const status = execSync("git status --porcelain", {
|
|
934
|
+
cwd: cwd || process.cwd(),
|
|
935
|
+
encoding: "utf-8",
|
|
936
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
937
|
+
}).trim();
|
|
938
|
+
isDirty = status.length > 0;
|
|
939
|
+
} catch {}
|
|
940
|
+
|
|
941
|
+
return isDirty ? `${branch}*` : branch;
|
|
942
|
+
} catch {
|
|
943
|
+
return null;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
function getProjectName(projectDir?: string): string | null {
|
|
948
|
+
if (!projectDir) return null;
|
|
949
|
+
// Extract the last part of the path as project name
|
|
950
|
+
const parts = projectDir.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
951
|
+
return parts.length > 0 ? parts[parts.length - 1] : null;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
async function readStdinJson(): Promise<ClaudeCodeStatus | null> {
|
|
955
|
+
try {
|
|
956
|
+
const chunks: Uint8Array[] = [];
|
|
957
|
+
for await (const chunk of Bun.stdin.stream()) {
|
|
958
|
+
chunks.push(chunk);
|
|
959
|
+
}
|
|
960
|
+
if (chunks.length === 0) return null;
|
|
961
|
+
const text = Buffer.concat(chunks).toString("utf-8").trim();
|
|
962
|
+
if (!text) return null;
|
|
963
|
+
return JSON.parse(text);
|
|
964
|
+
} catch {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function formatSessionInfo(status: ClaudeCodeStatus): string {
|
|
970
|
+
const parts: string[] = [];
|
|
971
|
+
|
|
972
|
+
// Project name
|
|
973
|
+
const projectName = getProjectName(status.workspace?.project_dir);
|
|
974
|
+
if (projectName) {
|
|
975
|
+
parts.push(`${COLORS.brightBlue}${projectName}${COLORS.reset}`);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Git branch
|
|
979
|
+
const gitBranch = getGitBranch(status.cwd || status.workspace?.project_dir);
|
|
980
|
+
if (gitBranch) {
|
|
981
|
+
parts.push(`${COLORS.magenta}${gitBranch}${COLORS.reset}`);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Model name
|
|
985
|
+
if (status.model?.display_name) {
|
|
986
|
+
parts.push(`${COLORS.brightYellow}${status.model.display_name}${COLORS.reset}`);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Cost
|
|
990
|
+
if (status.cost?.total_cost_usd !== undefined) {
|
|
991
|
+
const cost = status.cost.total_cost_usd;
|
|
992
|
+
const costStr = cost < 0.01 ? `$${cost.toFixed(4)}` : `$${cost.toFixed(2)}`;
|
|
993
|
+
parts.push(`${COLORS.green}${costStr}${COLORS.reset}`);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Lines changed
|
|
997
|
+
const linesAdded = status.cost?.total_lines_added || 0;
|
|
998
|
+
const linesRemoved = status.cost?.total_lines_removed || 0;
|
|
999
|
+
if (linesAdded > 0 || linesRemoved > 0) {
|
|
1000
|
+
const linesStr = `${COLORS.green}+${linesAdded}${COLORS.reset} ${COLORS.red}-${linesRemoved}${COLORS.reset}`;
|
|
1001
|
+
parts.push(linesStr);
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Context usage (using current_usage for accurate context window state)
|
|
1005
|
+
if (status.context_window?.current_usage && status.context_window?.context_window_size) {
|
|
1006
|
+
const usage = status.context_window.current_usage;
|
|
1007
|
+
// Sum all token types for total context usage
|
|
1008
|
+
const totalUsed =
|
|
1009
|
+
(usage.input_tokens || 0) +
|
|
1010
|
+
(usage.output_tokens || 0) +
|
|
1011
|
+
(usage.cache_creation_input_tokens || 0) +
|
|
1012
|
+
(usage.cache_read_input_tokens || 0);
|
|
1013
|
+
const windowSize = status.context_window.context_window_size;
|
|
1014
|
+
const percentage = Math.round((totalUsed / windowSize) * 100);
|
|
1015
|
+
const usedK = (totalUsed / 1000).toFixed(1);
|
|
1016
|
+
const windowK = Math.round(windowSize / 1000);
|
|
1017
|
+
|
|
1018
|
+
// Color based on usage: green < 50%, yellow 50-80%, red > 80%
|
|
1019
|
+
let color = COLORS.green;
|
|
1020
|
+
if (percentage >= 80) color = COLORS.red;
|
|
1021
|
+
else if (percentage >= 50) color = COLORS.yellow;
|
|
1022
|
+
|
|
1023
|
+
parts.push(`${color}${usedK}k/${windowK}k (${percentage}%)${COLORS.reset}`);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return parts.join(" | ");
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async function outputStatusline() {
|
|
1030
|
+
// Read and parse stdin from Claude Code
|
|
1031
|
+
const status = await readStdinJson();
|
|
719
1032
|
|
|
720
1033
|
try {
|
|
721
1034
|
const config = loadConfig();
|
|
722
1035
|
const parts: string[] = [];
|
|
723
1036
|
|
|
724
|
-
//
|
|
725
|
-
if (
|
|
726
|
-
|
|
1037
|
+
// Add session info from Claude Code
|
|
1038
|
+
if (status) {
|
|
1039
|
+
const sessionInfo = formatSessionInfo(status);
|
|
1040
|
+
if (sessionInfo) {
|
|
1041
|
+
parts.push(sessionInfo);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// Add system stats if enabled
|
|
1046
|
+
if (config.showCpuUsage) {
|
|
1047
|
+
const cpu = getCpuUsage();
|
|
1048
|
+
if (cpu) parts.push(cpu);
|
|
1049
|
+
}
|
|
1050
|
+
if (config.showMemoryUsage) {
|
|
1051
|
+
const mem = getMemoryUsage();
|
|
1052
|
+
if (mem) parts.push(mem);
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Check for health reminder (water, stretch, eye break)
|
|
1056
|
+
const reminder = getRandomReminder(config);
|
|
1057
|
+
if (reminder) {
|
|
1058
|
+
parts.push(reminder);
|
|
727
1059
|
}
|
|
728
1060
|
|
|
729
1061
|
// Get calendar events
|
|
@@ -731,6 +1063,12 @@ async function outputStatusline() {
|
|
|
731
1063
|
const events = await getUpcomingEvents(config);
|
|
732
1064
|
const event = getCurrentOrNextEvent(events);
|
|
733
1065
|
|
|
1066
|
+
// Check for meeting warning (within 5 minutes)
|
|
1067
|
+
const meetingWarning = getMeetingWarning(event);
|
|
1068
|
+
if (meetingWarning) {
|
|
1069
|
+
parts.push(meetingWarning);
|
|
1070
|
+
}
|
|
1071
|
+
|
|
734
1072
|
if (event) {
|
|
735
1073
|
parts.push(formatEvent(event, config));
|
|
736
1074
|
} else if (parts.length === 0) {
|