@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.
Files changed (3) hide show
  1. package/README.md +17 -0
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naarang/glancebar",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "A customizable statusline for Claude Code - display calendar events, tasks, and more at a glance",
5
5
  "author": "Vishal Dubey",
6
6
  "license": "MIT",
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 = ["ZohoCalendar.calendar.READ", "ZohoCalendar.event.READ"];
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; api: string }> = {
168
- "com": { accounts: "https://accounts.zoho.com", api: "https://calendar.zoho.com" },
169
- "eu": { accounts: "https://accounts.zoho.eu", api: "https://calendar.zoho.eu" },
170
- "in": { accounts: "https://accounts.zoho.in", api: "https://calendar.zoho.in" },
171
- "com.au": { accounts: "https://accounts.zoho.com.au", api: "https://calendar.zoho.com.au" },
172
- "com.cn": { accounts: "https://accounts.zoho.com.cn", api: "https://calendar.zoho.com.cn" },
173
- "jp": { accounts: "https://accounts.zoho.jp", api: "https://calendar.zoho.jp" },
174
- "zohocloud.ca": { accounts: "https://accounts.zohocloud.ca", api: "https://calendar.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.api,
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.api;
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 getUpcomingEvents(config);
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) {