@naarang/glancebar 1.0.6 → 1.0.7
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 +17 -0
- package/package.json +1 -1
- package/src/cli.ts +181 -13
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ A customizable statusline for [Claude Code](https://claude.com/product/claude-co
|
|
|
10
10
|
- **Session info** - Project name, git branch, model, cost, lines changed, and context usage
|
|
11
11
|
- **System stats** - CPU and memory usage (optional)
|
|
12
12
|
- **Calendar events** - Upcoming events from Google Calendar and Zoho Calendar
|
|
13
|
+
- **Zoho Tasks** - Display top pending tasks from Zoho Mail
|
|
13
14
|
- **Meeting warnings** - Red alert when a meeting is 5 minutes away
|
|
14
15
|
- **Health reminders** - Water, stretch, and eye break reminders
|
|
15
16
|
- **Color-coded** - Everything has distinct colors for quick scanning
|
|
@@ -205,6 +206,10 @@ glancebar config --eye-reminder true
|
|
|
205
206
|
glancebar config --cpu-usage true
|
|
206
207
|
glancebar config --memory-usage true
|
|
207
208
|
|
|
209
|
+
# Enable/disable Zoho tasks display
|
|
210
|
+
glancebar config --zoho-tasks true
|
|
211
|
+
glancebar config --max-tasks 3
|
|
212
|
+
|
|
208
213
|
# Reset to defaults
|
|
209
214
|
glancebar config --reset
|
|
210
215
|
```
|
|
@@ -244,6 +249,16 @@ Context usage color changes based on percentage:
|
|
|
244
249
|
| Later | `HH:MM AM/PM: Title (account)` | `2:30 PM: Meeting (work)` |
|
|
245
250
|
| No events | `No upcoming events` | |
|
|
246
251
|
|
|
252
|
+
### Zoho Tasks
|
|
253
|
+
|
|
254
|
+
| State | Color | Example |
|
|
255
|
+
|-------|-------|---------|
|
|
256
|
+
| Overdue task | Red | `Overdue task title` |
|
|
257
|
+
| High priority | Yellow | `High priority task` |
|
|
258
|
+
| Normal task | White | `Normal task title` |
|
|
259
|
+
|
|
260
|
+
Tasks are displayed as: `Tasks: Task 1, Task 2, Task 3`
|
|
261
|
+
|
|
247
262
|
### Health Reminders (~5% chance)
|
|
248
263
|
|
|
249
264
|
| Type | Color | Example |
|
|
@@ -279,6 +294,8 @@ All configuration is stored in `~/.glancebar/`:
|
|
|
279
294
|
| `eyeReminderEnabled` | true | Enable random eye break reminders (20-20-20 rule) |
|
|
280
295
|
| `showCpuUsage` | false | Show CPU usage percentage |
|
|
281
296
|
| `showMemoryUsage` | false | Show memory usage |
|
|
297
|
+
| `showZohoTasks` | true | Show pending Zoho tasks |
|
|
298
|
+
| `maxTasksToShow` | 3 | Maximum number of tasks to display |
|
|
282
299
|
|
|
283
300
|
## Building from Source
|
|
284
301
|
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -22,6 +22,8 @@ interface Config {
|
|
|
22
22
|
eyeReminderEnabled: boolean;
|
|
23
23
|
showCpuUsage: boolean;
|
|
24
24
|
showMemoryUsage: boolean;
|
|
25
|
+
showZohoTasks: boolean;
|
|
26
|
+
maxTasksToShow: number;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
interface ZohoAccount {
|
|
@@ -64,6 +66,8 @@ const DEFAULT_CONFIG: Config = {
|
|
|
64
66
|
eyeReminderEnabled: true,
|
|
65
67
|
showCpuUsage: false,
|
|
66
68
|
showMemoryUsage: false,
|
|
69
|
+
showZohoTasks: true,
|
|
70
|
+
maxTasksToShow: 3,
|
|
67
71
|
};
|
|
68
72
|
|
|
69
73
|
const WATER_REMINDERS = [
|
|
@@ -160,18 +164,22 @@ interface GoogleCredentials {
|
|
|
160
164
|
// Zoho OAuth Authentication
|
|
161
165
|
// ============================================================================
|
|
162
166
|
|
|
163
|
-
const ZOHO_SCOPES = [
|
|
167
|
+
const ZOHO_SCOPES = [
|
|
168
|
+
"ZohoCalendar.calendar.READ",
|
|
169
|
+
"ZohoCalendar.event.READ",
|
|
170
|
+
"ZohoMail.tasks.READ",
|
|
171
|
+
];
|
|
164
172
|
const ZOHO_REDIRECT_URI = "http://localhost:3000/callback";
|
|
165
173
|
|
|
166
174
|
// Zoho datacenter mappings
|
|
167
|
-
const ZOHO_DATACENTERS: Record<string, { accounts: string;
|
|
168
|
-
"com": { accounts: "https://accounts.zoho.com",
|
|
169
|
-
"eu": { accounts: "https://accounts.zoho.eu",
|
|
170
|
-
"in": { accounts: "https://accounts.zoho.in",
|
|
171
|
-
"com.au": { accounts: "https://accounts.zoho.com.au",
|
|
172
|
-
"com.cn": { accounts: "https://accounts.zoho.com.cn",
|
|
173
|
-
"jp": { accounts: "https://accounts.zoho.jp",
|
|
174
|
-
"zohocloud.ca": { accounts: "https://accounts.zohocloud.ca",
|
|
175
|
+
const ZOHO_DATACENTERS: Record<string, { accounts: string; calendar: string; mail: string }> = {
|
|
176
|
+
"com": { accounts: "https://accounts.zoho.com", calendar: "https://calendar.zoho.com", mail: "https://mail.zoho.com" },
|
|
177
|
+
"eu": { accounts: "https://accounts.zoho.eu", calendar: "https://calendar.zoho.eu", mail: "https://mail.zoho.eu" },
|
|
178
|
+
"in": { accounts: "https://accounts.zoho.in", calendar: "https://calendar.zoho.in", mail: "https://mail.zoho.in" },
|
|
179
|
+
"com.au": { accounts: "https://accounts.zoho.com.au", calendar: "https://calendar.zoho.com.au", mail: "https://mail.zoho.com.au" },
|
|
180
|
+
"com.cn": { accounts: "https://accounts.zoho.com.cn", calendar: "https://calendar.zoho.com.cn", mail: "https://mail.zoho.com.cn" },
|
|
181
|
+
"jp": { accounts: "https://accounts.zoho.jp", calendar: "https://calendar.zoho.jp", mail: "https://mail.zoho.jp" },
|
|
182
|
+
"zohocloud.ca": { accounts: "https://accounts.zohocloud.ca", calendar: "https://calendar.zohocloud.ca", mail: "https://mail.zohocloud.ca" },
|
|
175
183
|
};
|
|
176
184
|
|
|
177
185
|
interface ZohoCredentials {
|
|
@@ -352,7 +360,7 @@ async function authenticateZohoAccount(account: ZohoAccount): Promise<void> {
|
|
|
352
360
|
access_token: tokenData.access_token,
|
|
353
361
|
refresh_token: tokenData.refresh_token,
|
|
354
362
|
expires_at: Date.now() + (tokenData.expires_in * 1000),
|
|
355
|
-
api_domain: tokenData.api_domain || dc.
|
|
363
|
+
api_domain: tokenData.api_domain || dc.calendar,
|
|
356
364
|
};
|
|
357
365
|
|
|
358
366
|
const tokensDir = getTokensDir();
|
|
@@ -592,7 +600,7 @@ async function getZohoEvents(config: Config, now: Date, timeMax: Date): Promise<
|
|
|
592
600
|
|
|
593
601
|
const dc = ZOHO_DATACENTERS[account.datacenter];
|
|
594
602
|
// Always use the calendar-specific API domain, not the generic api_domain
|
|
595
|
-
const apiBase = dc.
|
|
603
|
+
const apiBase = dc.calendar;
|
|
596
604
|
|
|
597
605
|
// First get list of calendars
|
|
598
606
|
const calendarsResponse = await fetch(`${apiBase}/api/v1/calendars?category=own`, {
|
|
@@ -704,6 +712,122 @@ async function getUpcomingEvents(config: Config): Promise<CalendarEvent[]> {
|
|
|
704
712
|
return allEvents;
|
|
705
713
|
}
|
|
706
714
|
|
|
715
|
+
// ============================================================================
|
|
716
|
+
// Zoho Tasks
|
|
717
|
+
// ============================================================================
|
|
718
|
+
|
|
719
|
+
interface ZohoTask {
|
|
720
|
+
id: string;
|
|
721
|
+
title: string;
|
|
722
|
+
description: string;
|
|
723
|
+
dueDate: Date | null;
|
|
724
|
+
priority: "High" | "Normal" | "Low";
|
|
725
|
+
status: string;
|
|
726
|
+
isOverdue: boolean;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
async function getZohoTasks(config: Config): Promise<ZohoTask[]> {
|
|
730
|
+
if (!config.zohoAccounts || config.zohoAccounts.length === 0) return [];
|
|
731
|
+
if (!config.showZohoTasks) return [];
|
|
732
|
+
|
|
733
|
+
const allTasks: ZohoTask[] = [];
|
|
734
|
+
|
|
735
|
+
for (const account of config.zohoAccounts) {
|
|
736
|
+
try {
|
|
737
|
+
const token = await refreshZohoToken(account);
|
|
738
|
+
if (!token) continue;
|
|
739
|
+
|
|
740
|
+
const dc = ZOHO_DATACENTERS[account.datacenter];
|
|
741
|
+
const mailBase = dc.mail;
|
|
742
|
+
|
|
743
|
+
// Fetch tasks assigned to user
|
|
744
|
+
const response = await fetch(
|
|
745
|
+
`${mailBase}/api/tasks/?view=assignedtome&action=view&limit=10&from=0`,
|
|
746
|
+
{
|
|
747
|
+
headers: {
|
|
748
|
+
Authorization: `Zoho-oauthtoken ${token.access_token}`,
|
|
749
|
+
Accept: "application/json",
|
|
750
|
+
},
|
|
751
|
+
}
|
|
752
|
+
);
|
|
753
|
+
|
|
754
|
+
if (!response.ok) continue;
|
|
755
|
+
|
|
756
|
+
const data = await response.json();
|
|
757
|
+
const tasks = data.data?.tasks || [];
|
|
758
|
+
|
|
759
|
+
const now = new Date();
|
|
760
|
+
|
|
761
|
+
for (const task of tasks) {
|
|
762
|
+
// Skip completed tasks
|
|
763
|
+
if (task.status === "Completed" || task.status === "completed") continue;
|
|
764
|
+
|
|
765
|
+
let dueDate: Date | null = null;
|
|
766
|
+
let isOverdue = false;
|
|
767
|
+
|
|
768
|
+
if (task.dueDate) {
|
|
769
|
+
// Parse DD/MM/YYYY format
|
|
770
|
+
const parts = task.dueDate.split("/");
|
|
771
|
+
if (parts.length === 3) {
|
|
772
|
+
dueDate = new Date(+parts[2], +parts[1] - 1, +parts[0]);
|
|
773
|
+
isOverdue = dueDate < now;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
allTasks.push({
|
|
778
|
+
id: task.id || "",
|
|
779
|
+
title: task.title || "(No title)",
|
|
780
|
+
description: task.description || "",
|
|
781
|
+
dueDate,
|
|
782
|
+
priority: task.priority || "Normal",
|
|
783
|
+
status: task.status || "Open",
|
|
784
|
+
isOverdue,
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
} catch {
|
|
788
|
+
// Silently continue on error
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Sort: overdue first, then by due date (soonest first), then by priority
|
|
793
|
+
allTasks.sort((a, b) => {
|
|
794
|
+
// Overdue tasks first
|
|
795
|
+
if (a.isOverdue && !b.isOverdue) return -1;
|
|
796
|
+
if (!a.isOverdue && b.isOverdue) return 1;
|
|
797
|
+
|
|
798
|
+
// Then by due date (null dates go last)
|
|
799
|
+
if (a.dueDate && b.dueDate) {
|
|
800
|
+
return a.dueDate.getTime() - b.dueDate.getTime();
|
|
801
|
+
}
|
|
802
|
+
if (a.dueDate && !b.dueDate) return -1;
|
|
803
|
+
if (!a.dueDate && b.dueDate) return 1;
|
|
804
|
+
|
|
805
|
+
// Then by priority
|
|
806
|
+
const priorityOrder = { High: 0, Normal: 1, Low: 2 };
|
|
807
|
+
return (priorityOrder[a.priority] || 1) - (priorityOrder[b.priority] || 1);
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
return allTasks.slice(0, config.maxTasksToShow);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function formatTasks(tasks: ZohoTask[]): string | null {
|
|
814
|
+
if (tasks.length === 0) return null;
|
|
815
|
+
|
|
816
|
+
const formatted = tasks.map((task) => {
|
|
817
|
+
const title = task.title.length > 25 ? task.title.slice(0, 24) + "…" : task.title;
|
|
818
|
+
|
|
819
|
+
if (task.isOverdue) {
|
|
820
|
+
return `${COLORS.red}${title}${COLORS.reset}`;
|
|
821
|
+
} else if (task.priority === "High") {
|
|
822
|
+
return `${COLORS.yellow}${title}${COLORS.reset}`;
|
|
823
|
+
} else {
|
|
824
|
+
return `${COLORS.white}${title}${COLORS.reset}`;
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
return `${COLORS.cyan}Tasks:${COLORS.reset} ${formatted.join(", ")}`;
|
|
829
|
+
}
|
|
830
|
+
|
|
707
831
|
function extractAccountName(email: string): string {
|
|
708
832
|
const atIndex = email.indexOf("@");
|
|
709
833
|
if (atIndex === -1) return email;
|
|
@@ -871,6 +995,8 @@ Usage:
|
|
|
871
995
|
glancebar config --eye-reminder <true|false> Enable/disable eye break reminders (default: true)
|
|
872
996
|
glancebar config --cpu-usage <true|false> Show CPU usage (default: false)
|
|
873
997
|
glancebar config --memory-usage <true|false> Show memory usage (default: false)
|
|
998
|
+
glancebar config --zoho-tasks <true|false> Show Zoho tasks (default: true)
|
|
999
|
+
glancebar config --max-tasks <number> Max tasks to show (default: 3)
|
|
874
1000
|
glancebar config --reset Reset to default configuration
|
|
875
1001
|
glancebar setup Show setup instructions
|
|
876
1002
|
|
|
@@ -1322,6 +1448,34 @@ function handleConfig(args: string[]) {
|
|
|
1322
1448
|
return;
|
|
1323
1449
|
}
|
|
1324
1450
|
|
|
1451
|
+
// Handle --zoho-tasks
|
|
1452
|
+
const zohoTasksIndex = args.indexOf("--zoho-tasks");
|
|
1453
|
+
if (zohoTasksIndex !== -1) {
|
|
1454
|
+
const value = args[zohoTasksIndex + 1]?.toLowerCase();
|
|
1455
|
+
if (value !== "true" && value !== "false") {
|
|
1456
|
+
console.error("Error: --zoho-tasks must be 'true' or 'false'");
|
|
1457
|
+
process.exit(1);
|
|
1458
|
+
}
|
|
1459
|
+
config.showZohoTasks = value === "true";
|
|
1460
|
+
saveConfig(config);
|
|
1461
|
+
console.log(`Zoho tasks display ${value === "true" ? "enabled" : "disabled"}`);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
// Handle --max-tasks
|
|
1466
|
+
const maxTasksIndex = args.indexOf("--max-tasks");
|
|
1467
|
+
if (maxTasksIndex !== -1) {
|
|
1468
|
+
const value = parseInt(args[maxTasksIndex + 1], 10);
|
|
1469
|
+
if (isNaN(value) || value < 1 || value > 10) {
|
|
1470
|
+
console.error("Error: --max-tasks must be between 1 and 10");
|
|
1471
|
+
process.exit(1);
|
|
1472
|
+
}
|
|
1473
|
+
config.maxTasksToShow = value;
|
|
1474
|
+
saveConfig(config);
|
|
1475
|
+
console.log(`Max tasks to show set to ${value}`);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1325
1479
|
// Show current config
|
|
1326
1480
|
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1327
1481
|
const googleStr = gmailAccounts.length > 0 ? gmailAccounts.join(", ") : "(none)";
|
|
@@ -1352,6 +1506,10 @@ Reminders:
|
|
|
1352
1506
|
System Stats:
|
|
1353
1507
|
CPU usage: ${config.showCpuUsage ? "enabled" : "disabled"}
|
|
1354
1508
|
Memory usage: ${config.showMemoryUsage ? "enabled" : "disabled"}
|
|
1509
|
+
|
|
1510
|
+
Zoho Tasks:
|
|
1511
|
+
Show tasks: ${config.showZohoTasks ? "enabled" : "disabled"}
|
|
1512
|
+
Max tasks to show: ${config.maxTasksToShow}
|
|
1355
1513
|
`);
|
|
1356
1514
|
}
|
|
1357
1515
|
|
|
@@ -1551,12 +1709,16 @@ async function outputStatusline() {
|
|
|
1551
1709
|
parts.push(reminder);
|
|
1552
1710
|
}
|
|
1553
1711
|
|
|
1554
|
-
// Get calendar events
|
|
1712
|
+
// Get calendar events and tasks in parallel
|
|
1555
1713
|
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1556
1714
|
const hasAccounts = gmailAccounts.length > 0 || config.zohoAccounts.length > 0;
|
|
1557
1715
|
|
|
1558
1716
|
if (hasAccounts) {
|
|
1559
|
-
const events = await
|
|
1717
|
+
const [events, tasks] = await Promise.all([
|
|
1718
|
+
getUpcomingEvents(config),
|
|
1719
|
+
getZohoTasks(config),
|
|
1720
|
+
]);
|
|
1721
|
+
|
|
1560
1722
|
const event = getCurrentOrNextEvent(events);
|
|
1561
1723
|
|
|
1562
1724
|
// Check for meeting warning (within 5 minutes)
|
|
@@ -1565,6 +1727,12 @@ async function outputStatusline() {
|
|
|
1565
1727
|
parts.push(meetingWarning);
|
|
1566
1728
|
}
|
|
1567
1729
|
|
|
1730
|
+
// Add tasks if available
|
|
1731
|
+
const tasksStr = formatTasks(tasks);
|
|
1732
|
+
if (tasksStr) {
|
|
1733
|
+
parts.push(tasksStr);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1568
1736
|
if (event) {
|
|
1569
1737
|
parts.push(formatEvent(event, config));
|
|
1570
1738
|
} else if (parts.length === 0) {
|