@naarang/glancebar 1.0.5 → 1.0.6
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 +76 -18
- package/package.json +1 -1
- package/src/cli.ts +574 -78
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ A customizable statusline for [Claude Code](https://claude.com/product/claude-co
|
|
|
9
9
|
|
|
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
|
-
- **Calendar events** - Upcoming events from
|
|
12
|
+
- **Calendar events** - Upcoming events from Google Calendar and Zoho Calendar
|
|
13
13
|
- **Meeting warnings** - Red alert when a meeting is 5 minutes away
|
|
14
14
|
- **Health reminders** - Water, stretch, and eye break reminders
|
|
15
15
|
- **Color-coded** - Everything has distinct colors for quick scanning
|
|
@@ -19,7 +19,8 @@ A customizable statusline for [Claude Code](https://claude.com/product/claude-co
|
|
|
19
19
|
## Requirements
|
|
20
20
|
|
|
21
21
|
- [Bun](https://bun.sh/) >= 1.0.0
|
|
22
|
-
- Google Cloud project with Calendar API enabled
|
|
22
|
+
- Google Cloud project with Calendar API enabled (for Google Calendar)
|
|
23
|
+
- Zoho API Console credentials (for Zoho Calendar)
|
|
23
24
|
|
|
24
25
|
## Installation
|
|
25
26
|
|
|
@@ -49,8 +50,9 @@ npm install -g @naarang/glancebar
|
|
|
49
50
|
# 1. Run setup guide
|
|
50
51
|
glancebar setup
|
|
51
52
|
|
|
52
|
-
# 2. Add your
|
|
53
|
+
# 2. Add your account (after setting up credentials)
|
|
53
54
|
glancebar auth --add your-email@gmail.com
|
|
55
|
+
# Select Google (1) or Zoho (2) when prompted
|
|
54
56
|
|
|
55
57
|
# 3. Test it
|
|
56
58
|
glancebar
|
|
@@ -58,7 +60,9 @@ glancebar
|
|
|
58
60
|
|
|
59
61
|
## Setup
|
|
60
62
|
|
|
61
|
-
###
|
|
63
|
+
### Google Calendar Setup
|
|
64
|
+
|
|
65
|
+
#### 1. Create Google Cloud Project
|
|
62
66
|
|
|
63
67
|
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
64
68
|
2. Create a new project or select an existing one
|
|
@@ -66,30 +70,82 @@ glancebar
|
|
|
66
70
|
- Go to "APIs & Services" > "Library"
|
|
67
71
|
- Search for "Google Calendar API" and enable it
|
|
68
72
|
|
|
69
|
-
|
|
73
|
+
#### 2. Create OAuth Credentials
|
|
70
74
|
|
|
71
75
|
1. Go to "APIs & Services" > "Credentials"
|
|
72
76
|
2. Click "Create Credentials" > "OAuth client ID"
|
|
73
77
|
3. Select "Desktop app" as application type
|
|
74
|
-
4.
|
|
75
|
-
5.
|
|
78
|
+
4. Give it a name and click "Create"
|
|
79
|
+
5. Download the JSON file
|
|
80
|
+
6. Rename to `credentials.json` and save to `~/.glancebar/credentials.json`
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
#### 3. Add Redirect URI
|
|
78
83
|
|
|
79
|
-
|
|
84
|
+
Desktop app credentials don't show a redirect URI field in the console UI. You need to manually edit the downloaded `credentials.json` file:
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
1. Open `~/.glancebar/credentials.json` in a text editor
|
|
87
|
+
2. Find the `"redirect_uris"` array and ensure it contains:
|
|
88
|
+
```json
|
|
89
|
+
"redirect_uris": ["http://localhost:3000/callback"]
|
|
90
|
+
```
|
|
91
|
+
3. Save the file
|
|
92
|
+
|
|
93
|
+
Alternatively, you can add it via Google Cloud Console:
|
|
94
|
+
1. Go to "APIs & Services" > "Credentials"
|
|
95
|
+
2. Click on your OAuth client to edit it
|
|
96
|
+
3. Under "Authorized redirect URIs", click "Add URI"
|
|
97
|
+
4. Enter `http://localhost:3000/callback`
|
|
98
|
+
5. Save and re-download the JSON file
|
|
99
|
+
|
|
100
|
+
### Zoho Calendar Setup
|
|
101
|
+
|
|
102
|
+
#### 1. Register Application
|
|
103
|
+
|
|
104
|
+
1. Go to [Zoho API Console](https://api-console.zoho.com/)
|
|
105
|
+
2. Click "Add Client" > "Server-based Applications"
|
|
106
|
+
3. Enter application details
|
|
107
|
+
|
|
108
|
+
#### 2. Configure Client
|
|
109
|
+
|
|
110
|
+
1. Set Authorized Redirect URI: `http://localhost:3000/callback`
|
|
111
|
+
2. Note your Client ID and Client Secret
|
|
112
|
+
|
|
113
|
+
#### 3. Save Credentials
|
|
114
|
+
|
|
115
|
+
Create `~/.glancebar/zoho_credentials.json`:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"client_id": "YOUR_CLIENT_ID",
|
|
120
|
+
"client_secret": "YOUR_CLIENT_SECRET"
|
|
121
|
+
}
|
|
83
122
|
```
|
|
84
123
|
|
|
85
|
-
###
|
|
124
|
+
### Add Accounts
|
|
86
125
|
|
|
87
126
|
```bash
|
|
127
|
+
# Add Google Calendar account
|
|
88
128
|
glancebar auth --add your-email@gmail.com
|
|
89
|
-
|
|
129
|
+
# Select "1" for Google Calendar
|
|
130
|
+
|
|
131
|
+
# Add Zoho Calendar account
|
|
132
|
+
glancebar auth --add your-email@zoho.com
|
|
133
|
+
# Select "2" for Zoho Calendar
|
|
134
|
+
# Then select your datacenter region (1-7)
|
|
90
135
|
```
|
|
91
136
|
|
|
92
|
-
|
|
137
|
+
**Zoho Datacenters:**
|
|
138
|
+
| Choice | Region | Domain |
|
|
139
|
+
|--------|--------|--------|
|
|
140
|
+
| 1 | United States | zoho.com |
|
|
141
|
+
| 2 | Europe | zoho.eu |
|
|
142
|
+
| 3 | India | zoho.in |
|
|
143
|
+
| 4 | Australia | zoho.com.au |
|
|
144
|
+
| 5 | China | zoho.com.cn |
|
|
145
|
+
| 6 | Japan | zoho.jp |
|
|
146
|
+
| 7 | Canada | zohocloud.ca |
|
|
147
|
+
|
|
148
|
+
### Configure Claude Code
|
|
93
149
|
|
|
94
150
|
Update `~/.claude/settings.json`:
|
|
95
151
|
|
|
@@ -202,10 +258,12 @@ All configuration is stored in `~/.glancebar/`:
|
|
|
202
258
|
|
|
203
259
|
```
|
|
204
260
|
~/.glancebar/
|
|
205
|
-
├── config.json
|
|
206
|
-
├── credentials.json
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
├── config.json # User settings
|
|
262
|
+
├── credentials.json # Google OAuth credentials (you provide)
|
|
263
|
+
├── zoho_credentials.json # Zoho OAuth credentials (you provide)
|
|
264
|
+
└── tokens/ # OAuth tokens per account
|
|
265
|
+
├── google_<email>.json # Google tokens
|
|
266
|
+
└── zoho_<email>.json # Zoho tokens
|
|
209
267
|
```
|
|
210
268
|
|
|
211
269
|
### Default Settings
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -10,7 +10,9 @@ import { createInterface } from "readline";
|
|
|
10
10
|
// ============================================================================
|
|
11
11
|
|
|
12
12
|
interface Config {
|
|
13
|
-
accounts: string[];
|
|
13
|
+
accounts: string[]; // Legacy - for backwards compatibility
|
|
14
|
+
gmailAccounts: string[]; // Google accounts
|
|
15
|
+
zohoAccounts: ZohoAccount[]; // Zoho accounts
|
|
14
16
|
lookaheadHours: number;
|
|
15
17
|
showCalendarName: boolean;
|
|
16
18
|
countdownThresholdMinutes: number;
|
|
@@ -22,6 +24,11 @@ interface Config {
|
|
|
22
24
|
showMemoryUsage: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
interface ZohoAccount {
|
|
28
|
+
email: string;
|
|
29
|
+
datacenter: string; // com, eu, in, com.au, com.cn, jp, zohocloud.ca
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
const COLORS: Record<string, string> = {
|
|
26
33
|
reset: "\x1b[0m",
|
|
27
34
|
red: "\x1b[31m",
|
|
@@ -45,7 +52,9 @@ const COLORS: Record<string, string> = {
|
|
|
45
52
|
const ACCOUNT_COLORS = ["cyan", "magenta", "brightGreen", "orange", "brightBlue", "pink", "yellow", "purple"];
|
|
46
53
|
|
|
47
54
|
const DEFAULT_CONFIG: Config = {
|
|
48
|
-
accounts: [],
|
|
55
|
+
accounts: [], // Legacy
|
|
56
|
+
gmailAccounts: [],
|
|
57
|
+
zohoAccounts: [],
|
|
49
58
|
lookaheadHours: 8,
|
|
50
59
|
showCalendarName: true,
|
|
51
60
|
countdownThresholdMinutes: 60,
|
|
@@ -117,7 +126,14 @@ function loadConfig(): Config {
|
|
|
117
126
|
try {
|
|
118
127
|
const content = readFileSync(configPath, "utf-8");
|
|
119
128
|
const userConfig = JSON.parse(content);
|
|
120
|
-
|
|
129
|
+
const config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
130
|
+
|
|
131
|
+
// Migrate legacy accounts to gmailAccounts
|
|
132
|
+
if (config.accounts && config.accounts.length > 0 && (!config.gmailAccounts || config.gmailAccounts.length === 0)) {
|
|
133
|
+
config.gmailAccounts = [...config.accounts];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return config;
|
|
121
137
|
} catch {
|
|
122
138
|
return { ...DEFAULT_CONFIG };
|
|
123
139
|
}
|
|
@@ -129,23 +145,54 @@ function saveConfig(config: Config): void {
|
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
// ============================================================================
|
|
132
|
-
// OAuth Authentication
|
|
148
|
+
// Google OAuth Authentication
|
|
133
149
|
// ============================================================================
|
|
134
150
|
|
|
135
|
-
const
|
|
151
|
+
const GOOGLE_SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"];
|
|
136
152
|
const REDIRECT_URI = "http://localhost:3000/callback";
|
|
137
153
|
|
|
138
|
-
interface
|
|
154
|
+
interface GoogleCredentials {
|
|
139
155
|
installed?: { client_id: string; client_secret: string };
|
|
140
156
|
web?: { client_id: string; client_secret: string };
|
|
141
157
|
}
|
|
142
158
|
|
|
143
|
-
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// Zoho OAuth Authentication
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
const ZOHO_SCOPES = ["ZohoCalendar.calendar.READ", "ZohoCalendar.event.READ"];
|
|
164
|
+
const ZOHO_REDIRECT_URI = "http://localhost:3000/callback";
|
|
165
|
+
|
|
166
|
+
// 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
|
+
};
|
|
176
|
+
|
|
177
|
+
interface ZohoCredentials {
|
|
178
|
+
client_id: string;
|
|
179
|
+
client_secret: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface ZohoToken {
|
|
183
|
+
access_token: string;
|
|
184
|
+
refresh_token: string;
|
|
185
|
+
expires_at: number;
|
|
186
|
+
api_domain?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Google credentials
|
|
190
|
+
function getGoogleCredentialsPath(): string {
|
|
144
191
|
return join(getConfigDir(), "credentials.json");
|
|
145
192
|
}
|
|
146
193
|
|
|
147
|
-
function
|
|
148
|
-
const credPath =
|
|
194
|
+
function loadGoogleCredentials(): GoogleCredentials {
|
|
195
|
+
const credPath = getGoogleCredentialsPath();
|
|
149
196
|
if (!existsSync(credPath)) {
|
|
150
197
|
throw new Error(
|
|
151
198
|
`credentials.json not found at ${credPath}\n\nPlease download OAuth credentials from Google Cloud Console and save to:\n${credPath}\n\nRun 'glancebar setup' for detailed instructions.`
|
|
@@ -154,23 +201,55 @@ function loadCredentials(): Credentials {
|
|
|
154
201
|
return JSON.parse(readFileSync(credPath, "utf-8"));
|
|
155
202
|
}
|
|
156
203
|
|
|
157
|
-
function
|
|
204
|
+
function getGoogleTokenPath(account: string): string {
|
|
205
|
+
const safeAccount = account.replace(/[^a-zA-Z0-9@.-]/g, "_");
|
|
206
|
+
return join(getTokensDir(), `google_${safeAccount}.json`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Legacy token path (for migration)
|
|
210
|
+
function getLegacyTokenPath(account: string): string {
|
|
158
211
|
const safeAccount = account.replace(/[^a-zA-Z0-9@.-]/g, "_");
|
|
159
212
|
return join(getTokensDir(), `${safeAccount}.json`);
|
|
160
213
|
}
|
|
161
214
|
|
|
162
|
-
|
|
215
|
+
// Zoho credentials
|
|
216
|
+
function getZohoCredentialsPath(): string {
|
|
217
|
+
return join(getConfigDir(), "zoho_credentials.json");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function loadZohoCredentials(): ZohoCredentials {
|
|
221
|
+
const credPath = getZohoCredentialsPath();
|
|
222
|
+
if (!existsSync(credPath)) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`zoho_credentials.json not found at ${credPath}\n\nPlease create OAuth credentials in Zoho API Console and save to:\n${credPath}\n\nRun 'glancebar setup' for detailed instructions.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
return JSON.parse(readFileSync(credPath, "utf-8"));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function getZohoTokenPath(account: string): string {
|
|
231
|
+
const safeAccount = account.replace(/[^a-zA-Z0-9@.-]/g, "_");
|
|
232
|
+
return join(getTokensDir(), `zoho_${safeAccount}.json`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function createGoogleOAuth2Client(credentials: GoogleCredentials) {
|
|
163
236
|
const { client_id, client_secret } = credentials.installed || credentials.web!;
|
|
164
237
|
return new google.auth.OAuth2(client_id, client_secret, REDIRECT_URI);
|
|
165
238
|
}
|
|
166
239
|
|
|
167
|
-
function
|
|
168
|
-
const credentials =
|
|
169
|
-
const oauth2Client =
|
|
170
|
-
const tokenPath = getTokenPath(account);
|
|
240
|
+
function getGoogleAuthenticatedClient(account: string) {
|
|
241
|
+
const credentials = loadGoogleCredentials();
|
|
242
|
+
const oauth2Client = createGoogleOAuth2Client(credentials);
|
|
171
243
|
|
|
244
|
+
// Try new path first, then legacy path
|
|
245
|
+
let tokenPath = getGoogleTokenPath(account);
|
|
172
246
|
if (!existsSync(tokenPath)) {
|
|
173
|
-
|
|
247
|
+
const legacyPath = getLegacyTokenPath(account);
|
|
248
|
+
if (existsSync(legacyPath)) {
|
|
249
|
+
tokenPath = legacyPath;
|
|
250
|
+
} else {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
174
253
|
}
|
|
175
254
|
|
|
176
255
|
const token = JSON.parse(readFileSync(tokenPath, "utf-8"));
|
|
@@ -185,13 +264,13 @@ function getAuthenticatedClient(account: string) {
|
|
|
185
264
|
return oauth2Client;
|
|
186
265
|
}
|
|
187
266
|
|
|
188
|
-
async function
|
|
189
|
-
const credentials =
|
|
190
|
-
const oauth2Client =
|
|
267
|
+
async function authenticateGoogleAccount(account: string): Promise<void> {
|
|
268
|
+
const credentials = loadGoogleCredentials();
|
|
269
|
+
const oauth2Client = createGoogleOAuth2Client(credentials);
|
|
191
270
|
|
|
192
271
|
const authUrl = oauth2Client.generateAuthUrl({
|
|
193
272
|
access_type: "offline",
|
|
194
|
-
scope:
|
|
273
|
+
scope: GOOGLE_SCOPES,
|
|
195
274
|
prompt: "consent",
|
|
196
275
|
login_hint: account,
|
|
197
276
|
});
|
|
@@ -211,11 +290,132 @@ async function authenticateAccount(account: string): Promise<void> {
|
|
|
211
290
|
mkdirSync(tokensDir, { recursive: true });
|
|
212
291
|
}
|
|
213
292
|
|
|
214
|
-
const tokenPath =
|
|
293
|
+
const tokenPath = getGoogleTokenPath(account);
|
|
215
294
|
writeFileSync(tokenPath, JSON.stringify(tokens, null, 2));
|
|
216
295
|
console.log(`Token saved for ${account}`);
|
|
217
296
|
}
|
|
218
297
|
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// Zoho OAuth Flow
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
async function authenticateZohoAccount(account: ZohoAccount): Promise<void> {
|
|
303
|
+
const credentials = loadZohoCredentials();
|
|
304
|
+
const dc = ZOHO_DATACENTERS[account.datacenter];
|
|
305
|
+
|
|
306
|
+
if (!dc) {
|
|
307
|
+
throw new Error(`Invalid datacenter: ${account.datacenter}. Valid options: ${Object.keys(ZOHO_DATACENTERS).join(", ")}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const params = new URLSearchParams({
|
|
311
|
+
response_type: "code",
|
|
312
|
+
client_id: credentials.client_id,
|
|
313
|
+
scope: ZOHO_SCOPES.join(","),
|
|
314
|
+
redirect_uri: ZOHO_REDIRECT_URI,
|
|
315
|
+
access_type: "offline",
|
|
316
|
+
prompt: "consent",
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const authUrl = `${dc.accounts}/oauth/v2/auth?${params.toString()}`;
|
|
320
|
+
|
|
321
|
+
console.log(`\nAuthenticating Zoho: ${account.email}`);
|
|
322
|
+
console.log(`Datacenter: ${account.datacenter}`);
|
|
323
|
+
console.log(`Opening browser...`);
|
|
324
|
+
|
|
325
|
+
const code = await startServerAndGetCode(authUrl);
|
|
326
|
+
|
|
327
|
+
console.log(`Exchanging code for tokens...`);
|
|
328
|
+
|
|
329
|
+
// Exchange code for tokens
|
|
330
|
+
const tokenParams = new URLSearchParams({
|
|
331
|
+
grant_type: "authorization_code",
|
|
332
|
+
client_id: credentials.client_id,
|
|
333
|
+
client_secret: credentials.client_secret,
|
|
334
|
+
redirect_uri: ZOHO_REDIRECT_URI,
|
|
335
|
+
code: code,
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
const tokenResponse = await fetch(`${dc.accounts}/oauth/v2/token`, {
|
|
339
|
+
method: "POST",
|
|
340
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
341
|
+
body: tokenParams.toString(),
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
if (!tokenResponse.ok) {
|
|
345
|
+
const errorText = await tokenResponse.text();
|
|
346
|
+
throw new Error(`Failed to get Zoho tokens: ${errorText}`);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const tokenData = await tokenResponse.json();
|
|
350
|
+
|
|
351
|
+
const token: ZohoToken = {
|
|
352
|
+
access_token: tokenData.access_token,
|
|
353
|
+
refresh_token: tokenData.refresh_token,
|
|
354
|
+
expires_at: Date.now() + (tokenData.expires_in * 1000),
|
|
355
|
+
api_domain: tokenData.api_domain || dc.api,
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const tokensDir = getTokensDir();
|
|
359
|
+
if (!existsSync(tokensDir)) {
|
|
360
|
+
mkdirSync(tokensDir, { recursive: true });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const tokenPath = getZohoTokenPath(account.email);
|
|
364
|
+
writeFileSync(tokenPath, JSON.stringify(token, null, 2));
|
|
365
|
+
console.log(`Token saved for ${account.email}`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function refreshZohoToken(account: ZohoAccount): Promise<ZohoToken | null> {
|
|
369
|
+
const tokenPath = getZohoTokenPath(account.email);
|
|
370
|
+
if (!existsSync(tokenPath)) return null;
|
|
371
|
+
|
|
372
|
+
const token: ZohoToken = JSON.parse(readFileSync(tokenPath, "utf-8"));
|
|
373
|
+
|
|
374
|
+
// Check if token is still valid (with 5 minute buffer)
|
|
375
|
+
if (token.expires_at > Date.now() + 300000) {
|
|
376
|
+
return token;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Refresh the token
|
|
380
|
+
try {
|
|
381
|
+
const credentials = loadZohoCredentials();
|
|
382
|
+
const dc = ZOHO_DATACENTERS[account.datacenter];
|
|
383
|
+
|
|
384
|
+
const params = new URLSearchParams({
|
|
385
|
+
grant_type: "refresh_token",
|
|
386
|
+
client_id: credentials.client_id,
|
|
387
|
+
client_secret: credentials.client_secret,
|
|
388
|
+
refresh_token: token.refresh_token,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const response = await fetch(`${dc.accounts}/oauth/v2/token`, {
|
|
392
|
+
method: "POST",
|
|
393
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
394
|
+
body: params.toString(),
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
if (!response.ok) return null;
|
|
398
|
+
|
|
399
|
+
const data = await response.json();
|
|
400
|
+
const updatedToken: ZohoToken = {
|
|
401
|
+
...token,
|
|
402
|
+
access_token: data.access_token,
|
|
403
|
+
expires_at: Date.now() + (data.expires_in * 1000),
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
writeFileSync(tokenPath, JSON.stringify(updatedToken, null, 2));
|
|
407
|
+
return updatedToken;
|
|
408
|
+
} catch {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function getZohoAuthenticatedToken(account: ZohoAccount): ZohoToken | null {
|
|
414
|
+
const tokenPath = getZohoTokenPath(account.email);
|
|
415
|
+
if (!existsSync(tokenPath)) return null;
|
|
416
|
+
return JSON.parse(readFileSync(tokenPath, "utf-8"));
|
|
417
|
+
}
|
|
418
|
+
|
|
219
419
|
function startServerAndGetCode(authUrl: string): Promise<string> {
|
|
220
420
|
return new Promise((resolve, reject) => {
|
|
221
421
|
let server: Server;
|
|
@@ -313,16 +513,24 @@ interface CalendarEvent {
|
|
|
313
513
|
account: string;
|
|
314
514
|
accountEmail: string;
|
|
315
515
|
accountIndex: number;
|
|
516
|
+
provider: "google" | "zoho";
|
|
316
517
|
}
|
|
317
518
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
const
|
|
519
|
+
// Get all accounts combined for indexing
|
|
520
|
+
function getAllAccounts(config: Config): string[] {
|
|
521
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
522
|
+
const zohoEmails = config.zohoAccounts.map(z => z.email);
|
|
523
|
+
return [...gmailAccounts, ...zohoEmails];
|
|
524
|
+
}
|
|
322
525
|
|
|
323
|
-
|
|
526
|
+
async function getGoogleEvents(config: Config, now: Date, timeMax: Date): Promise<CalendarEvent[]> {
|
|
527
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
528
|
+
const allAccounts = getAllAccounts(config);
|
|
529
|
+
|
|
530
|
+
const eventPromises = gmailAccounts.map(async (account) => {
|
|
531
|
+
const accountIndex = allAccounts.indexOf(account);
|
|
324
532
|
try {
|
|
325
|
-
const auth =
|
|
533
|
+
const auth = getGoogleAuthenticatedClient(account);
|
|
326
534
|
if (!auth) return [];
|
|
327
535
|
|
|
328
536
|
const calendar = google.calendar({ version: "v3", auth });
|
|
@@ -358,6 +566,7 @@ async function getUpcomingEvents(config: Config): Promise<CalendarEvent[]> {
|
|
|
358
566
|
account: extractAccountName(account),
|
|
359
567
|
accountEmail: account,
|
|
360
568
|
accountIndex,
|
|
569
|
+
provider: "google" as const,
|
|
361
570
|
};
|
|
362
571
|
});
|
|
363
572
|
} catch {
|
|
@@ -366,10 +575,131 @@ async function getUpcomingEvents(config: Config): Promise<CalendarEvent[]> {
|
|
|
366
575
|
});
|
|
367
576
|
|
|
368
577
|
const results = await Promise.all(eventPromises);
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
578
|
+
return results.flat();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function getZohoEvents(config: Config, now: Date, timeMax: Date): Promise<CalendarEvent[]> {
|
|
582
|
+
if (!config.zohoAccounts || config.zohoAccounts.length === 0) return [];
|
|
583
|
+
|
|
584
|
+
const allAccounts = getAllAccounts(config);
|
|
585
|
+
const gmailCount = (config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts).length;
|
|
586
|
+
|
|
587
|
+
const eventPromises = config.zohoAccounts.map(async (account, idx) => {
|
|
588
|
+
const accountIndex = gmailCount + idx;
|
|
589
|
+
try {
|
|
590
|
+
const token = await refreshZohoToken(account);
|
|
591
|
+
if (!token) return [];
|
|
592
|
+
|
|
593
|
+
const dc = ZOHO_DATACENTERS[account.datacenter];
|
|
594
|
+
// Always use the calendar-specific API domain, not the generic api_domain
|
|
595
|
+
const apiBase = dc.api;
|
|
596
|
+
|
|
597
|
+
// First get list of calendars
|
|
598
|
+
const calendarsResponse = await fetch(`${apiBase}/api/v1/calendars?category=own`, {
|
|
599
|
+
headers: {
|
|
600
|
+
Authorization: `Zoho-oauthtoken ${token.access_token}`,
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
if (!calendarsResponse.ok) return [];
|
|
605
|
+
|
|
606
|
+
const calendarsData = await calendarsResponse.json();
|
|
607
|
+
const calendars = calendarsData.calendars || [];
|
|
608
|
+
|
|
609
|
+
if (calendars.length === 0) return [];
|
|
610
|
+
|
|
611
|
+
// Use the first (primary) calendar
|
|
612
|
+
const primaryCalendar = calendars.find((c: any) => c.isdefault) || calendars[0];
|
|
613
|
+
const calendarUid = primaryCalendar.uid;
|
|
614
|
+
|
|
615
|
+
// Format dates for Zoho API (yyyyMMdd'T'HHmmss'Z')
|
|
616
|
+
const formatZohoDate = (date: Date): string => {
|
|
617
|
+
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
|
618
|
+
};
|
|
619
|
+
|
|
620
|
+
const range = JSON.stringify({
|
|
621
|
+
start: formatZohoDate(now),
|
|
622
|
+
end: formatZohoDate(timeMax),
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
const eventsResponse = await fetch(
|
|
626
|
+
`${apiBase}/api/v1/calendars/${encodeURIComponent(calendarUid)}/events?range=${encodeURIComponent(range)}`,
|
|
627
|
+
{
|
|
628
|
+
headers: {
|
|
629
|
+
Authorization: `Zoho-oauthtoken ${token.access_token}`,
|
|
630
|
+
},
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
if (!eventsResponse.ok) return [];
|
|
635
|
+
|
|
636
|
+
const eventsData = await eventsResponse.json();
|
|
637
|
+
const events = eventsData.events || [];
|
|
638
|
+
|
|
639
|
+
return events.map((event: any) => {
|
|
640
|
+
const isAllDay = event.isallday === true;
|
|
641
|
+
let start: Date, end: Date;
|
|
642
|
+
|
|
643
|
+
// Parse Zoho date format: "20260109T163000+0530" or "20260109T163000Z"
|
|
644
|
+
const parseZohoDate = (dateStr: string): Date => {
|
|
645
|
+
// Format: YYYYMMDDTHHmmss+HHMM or YYYYMMDDTHHmmssZ
|
|
646
|
+
const match = dateStr.match(/^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})([Z]|([+-])(\d{2})(\d{2}))?$/);
|
|
647
|
+
if (match) {
|
|
648
|
+
const [, year, month, day, hour, min, sec, tz, sign, tzHour, tzMin] = match;
|
|
649
|
+
if (tz === "Z") {
|
|
650
|
+
return new Date(Date.UTC(+year, +month - 1, +day, +hour, +min, +sec));
|
|
651
|
+
} else if (sign && tzHour && tzMin) {
|
|
652
|
+
const offsetMinutes = (+tzHour * 60 + +tzMin) * (sign === "+" ? -1 : 1);
|
|
653
|
+
const utc = Date.UTC(+year, +month - 1, +day, +hour, +min, +sec);
|
|
654
|
+
return new Date(utc + offsetMinutes * 60000);
|
|
655
|
+
}
|
|
656
|
+
// No timezone, assume local
|
|
657
|
+
return new Date(+year, +month - 1, +day, +hour, +min, +sec);
|
|
658
|
+
}
|
|
659
|
+
// Fallback to standard parsing
|
|
660
|
+
return new Date(dateStr);
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
if (event.dateandtime) {
|
|
664
|
+
start = parseZohoDate(event.dateandtime.start);
|
|
665
|
+
end = parseZohoDate(event.dateandtime.end);
|
|
666
|
+
} else {
|
|
667
|
+
start = parseZohoDate(event.start);
|
|
668
|
+
end = parseZohoDate(event.end);
|
|
669
|
+
}
|
|
372
670
|
|
|
671
|
+
return {
|
|
672
|
+
id: event.uid || "",
|
|
673
|
+
title: event.title || "(No title)",
|
|
674
|
+
start,
|
|
675
|
+
end,
|
|
676
|
+
isAllDay,
|
|
677
|
+
account: extractAccountName(account.email),
|
|
678
|
+
accountEmail: account.email,
|
|
679
|
+
accountIndex,
|
|
680
|
+
provider: "zoho" as const,
|
|
681
|
+
};
|
|
682
|
+
});
|
|
683
|
+
} catch {
|
|
684
|
+
return [];
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
const results = await Promise.all(eventPromises);
|
|
689
|
+
return results.flat();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
async function getUpcomingEvents(config: Config): Promise<CalendarEvent[]> {
|
|
693
|
+
const now = new Date();
|
|
694
|
+
const timeMax = new Date(now.getTime() + config.lookaheadHours * 60 * 60 * 1000);
|
|
695
|
+
|
|
696
|
+
// Fetch from both providers in parallel
|
|
697
|
+
const [googleEvents, zohoEvents] = await Promise.all([
|
|
698
|
+
getGoogleEvents(config, now, timeMax),
|
|
699
|
+
getZohoEvents(config, now, timeMax),
|
|
700
|
+
]);
|
|
701
|
+
|
|
702
|
+
const allEvents = [...googleEvents, ...zohoEvents];
|
|
373
703
|
allEvents.sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
374
704
|
return allEvents;
|
|
375
705
|
}
|
|
@@ -528,9 +858,9 @@ Display calendar events, tasks, and more at a glance.
|
|
|
528
858
|
Usage:
|
|
529
859
|
glancebar Output statusline (for Claude Code)
|
|
530
860
|
glancebar auth Authenticate all configured accounts
|
|
531
|
-
glancebar auth --add <email> Add and authenticate a new account
|
|
861
|
+
glancebar auth --add <email> Add and authenticate a new account (prompts for provider)
|
|
532
862
|
glancebar auth --remove <email> Remove an account
|
|
533
|
-
glancebar auth --list List configured accounts
|
|
863
|
+
glancebar auth --list List all configured accounts
|
|
534
864
|
glancebar config Show current configuration
|
|
535
865
|
glancebar config --lookahead <hours> Set lookahead hours (default: 8)
|
|
536
866
|
glancebar config --countdown-threshold <mins> Set countdown threshold in minutes (default: 60)
|
|
@@ -545,7 +875,8 @@ Usage:
|
|
|
545
875
|
glancebar setup Show setup instructions
|
|
546
876
|
|
|
547
877
|
Examples:
|
|
548
|
-
glancebar auth --add user@gmail.com
|
|
878
|
+
glancebar auth --add user@gmail.com # Will prompt for Google or Zoho
|
|
879
|
+
glancebar auth --add user@zoho.com # Will prompt for Google or Zoho
|
|
549
880
|
glancebar config --lookahead 12
|
|
550
881
|
glancebar config --stretch-reminder false
|
|
551
882
|
|
|
@@ -558,6 +889,9 @@ function printSetup() {
|
|
|
558
889
|
Glancebar - Setup Instructions
|
|
559
890
|
==============================
|
|
560
891
|
|
|
892
|
+
GOOGLE CALENDAR SETUP
|
|
893
|
+
---------------------
|
|
894
|
+
|
|
561
895
|
Step 1: Create Google Cloud Project
|
|
562
896
|
- Go to https://console.cloud.google.com/
|
|
563
897
|
- Create a new project or select existing one
|
|
@@ -574,17 +908,41 @@ Step 3: Create OAuth Credentials
|
|
|
574
908
|
|
|
575
909
|
Step 4: Save credentials
|
|
576
910
|
- Rename downloaded file to "credentials.json"
|
|
577
|
-
- Save it to: ${
|
|
911
|
+
- Save it to: ${getGoogleCredentialsPath()}
|
|
578
912
|
|
|
579
913
|
Step 5: Add redirect URI
|
|
580
|
-
-
|
|
581
|
-
|
|
914
|
+
- Edit credentials.json and ensure redirect_uris contains:
|
|
915
|
+
"redirect_uris": ["http://localhost:3000/callback"]
|
|
916
|
+
|
|
917
|
+
ZOHO CALENDAR SETUP
|
|
918
|
+
-------------------
|
|
919
|
+
|
|
920
|
+
Step 1: Register Application
|
|
921
|
+
- Go to https://api-console.zoho.com/
|
|
922
|
+
- Click "Add Client" > "Server-based Applications"
|
|
923
|
+
|
|
924
|
+
Step 2: Configure Client
|
|
925
|
+
- Set Authorized Redirect URI: http://localhost:3000/callback
|
|
926
|
+
- Note your Client ID and Client Secret
|
|
927
|
+
|
|
928
|
+
Step 3: Save credentials
|
|
929
|
+
- Create file: ${getZohoCredentialsPath()}
|
|
930
|
+
- Add content:
|
|
931
|
+
{
|
|
932
|
+
"client_id": "YOUR_CLIENT_ID",
|
|
933
|
+
"client_secret": "YOUR_CLIENT_SECRET"
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
ADDING ACCOUNTS
|
|
937
|
+
---------------
|
|
582
938
|
|
|
583
|
-
Step 6: Add your Google accounts
|
|
584
939
|
glancebar auth --add your-email@gmail.com
|
|
585
|
-
|
|
940
|
+
# Select "Google" or "Zoho" when prompted
|
|
941
|
+
# For Zoho, select your datacenter region
|
|
942
|
+
|
|
943
|
+
CONFIGURE CLAUDE CODE
|
|
944
|
+
---------------------
|
|
586
945
|
|
|
587
|
-
Step 7: Configure Claude Code statusline
|
|
588
946
|
Update ~/.claude/settings.json:
|
|
589
947
|
{
|
|
590
948
|
"statusLine": {
|
|
@@ -599,19 +957,40 @@ For more info: https://github.com/vishal-android-freak/glancebar
|
|
|
599
957
|
}
|
|
600
958
|
|
|
601
959
|
async function handleAuth(args: string[]) {
|
|
960
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
961
|
+
const prompt = (q: string): Promise<string> => new Promise((r) => rl.question(q, r));
|
|
962
|
+
|
|
602
963
|
// Handle --list
|
|
603
964
|
if (args.includes("--list")) {
|
|
604
965
|
const config = loadConfig();
|
|
605
|
-
|
|
966
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
967
|
+
const hasAny = gmailAccounts.length > 0 || config.zohoAccounts.length > 0;
|
|
968
|
+
|
|
969
|
+
if (!hasAny) {
|
|
606
970
|
console.log("No accounts configured.");
|
|
607
971
|
} else {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
972
|
+
if (gmailAccounts.length > 0) {
|
|
973
|
+
console.log("\nGoogle Calendar accounts:");
|
|
974
|
+
gmailAccounts.forEach((acc, i) => {
|
|
975
|
+
let tokenPath = getGoogleTokenPath(acc);
|
|
976
|
+
if (!existsSync(tokenPath)) {
|
|
977
|
+
tokenPath = getLegacyTokenPath(acc);
|
|
978
|
+
}
|
|
979
|
+
const status = existsSync(tokenPath) ? "authenticated" : "not authenticated";
|
|
980
|
+
console.log(` ${i + 1}. ${acc} (${status})`);
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
if (config.zohoAccounts.length > 0) {
|
|
985
|
+
console.log("\nZoho Calendar accounts:");
|
|
986
|
+
config.zohoAccounts.forEach((acc, i) => {
|
|
987
|
+
const tokenPath = getZohoTokenPath(acc.email);
|
|
988
|
+
const status = existsSync(tokenPath) ? "authenticated" : "not authenticated";
|
|
989
|
+
console.log(` ${i + 1}. ${acc.email} [${acc.datacenter}] (${status})`);
|
|
990
|
+
});
|
|
991
|
+
}
|
|
614
992
|
}
|
|
993
|
+
rl.close();
|
|
615
994
|
return;
|
|
616
995
|
}
|
|
617
996
|
|
|
@@ -621,25 +1000,89 @@ async function handleAuth(args: string[]) {
|
|
|
621
1000
|
const email = args[addIndex + 1];
|
|
622
1001
|
if (!email || email.startsWith("--")) {
|
|
623
1002
|
console.error("Error: Please provide an email address after --add");
|
|
1003
|
+
rl.close();
|
|
624
1004
|
process.exit(1);
|
|
625
1005
|
}
|
|
626
1006
|
|
|
627
1007
|
if (!email.includes("@")) {
|
|
628
1008
|
console.error("Error: Invalid email address");
|
|
1009
|
+
rl.close();
|
|
629
1010
|
process.exit(1);
|
|
630
1011
|
}
|
|
631
1012
|
|
|
1013
|
+
// Prompt for provider
|
|
1014
|
+
console.log("\nSelect calendar provider:");
|
|
1015
|
+
console.log(" 1. Google Calendar");
|
|
1016
|
+
console.log(" 2. Zoho Calendar");
|
|
1017
|
+
const providerChoice = await prompt("\nEnter choice (1 or 2): ");
|
|
1018
|
+
|
|
632
1019
|
const config = loadConfig();
|
|
633
|
-
|
|
634
|
-
|
|
1020
|
+
|
|
1021
|
+
if (providerChoice === "1") {
|
|
1022
|
+
// Google Calendar
|
|
1023
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1024
|
+
if (gmailAccounts.includes(email)) {
|
|
1025
|
+
console.log(`\nGoogle account ${email} already exists. Re-authenticating...`);
|
|
1026
|
+
} else {
|
|
1027
|
+
if (config.gmailAccounts.length === 0 && config.accounts.length > 0) {
|
|
1028
|
+
config.gmailAccounts = [...config.accounts];
|
|
1029
|
+
}
|
|
1030
|
+
config.gmailAccounts.push(email);
|
|
1031
|
+
saveConfig(config);
|
|
1032
|
+
console.log(`\nAdded ${email} to Google accounts.`);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
await authenticateGoogleAccount(email);
|
|
1036
|
+
console.log("\nDone!");
|
|
1037
|
+
} else if (providerChoice === "2") {
|
|
1038
|
+
// Zoho Calendar
|
|
1039
|
+
console.log("\nSelect Zoho datacenter:");
|
|
1040
|
+
console.log(" 1. com - United States");
|
|
1041
|
+
console.log(" 2. eu - Europe");
|
|
1042
|
+
console.log(" 3. in - India");
|
|
1043
|
+
console.log(" 4. com.au - Australia");
|
|
1044
|
+
console.log(" 5. com.cn - China");
|
|
1045
|
+
console.log(" 6. jp - Japan");
|
|
1046
|
+
console.log(" 7. zohocloud.ca - Canada");
|
|
1047
|
+
|
|
1048
|
+
const dcChoice = await prompt("\nEnter choice (1-7): ");
|
|
1049
|
+
const dcMap: Record<string, string> = {
|
|
1050
|
+
"1": "com",
|
|
1051
|
+
"2": "eu",
|
|
1052
|
+
"3": "in",
|
|
1053
|
+
"4": "com.au",
|
|
1054
|
+
"5": "com.cn",
|
|
1055
|
+
"6": "jp",
|
|
1056
|
+
"7": "zohocloud.ca",
|
|
1057
|
+
};
|
|
1058
|
+
|
|
1059
|
+
const datacenter = dcMap[dcChoice];
|
|
1060
|
+
if (!datacenter) {
|
|
1061
|
+
console.error("Error: Invalid datacenter choice");
|
|
1062
|
+
rl.close();
|
|
1063
|
+
process.exit(1);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
const existingZoho = config.zohoAccounts.find((z) => z.email === email);
|
|
1067
|
+
if (existingZoho) {
|
|
1068
|
+
console.log(`\nZoho account ${email} already exists. Re-authenticating...`);
|
|
1069
|
+
existingZoho.datacenter = datacenter;
|
|
1070
|
+
saveConfig(config);
|
|
1071
|
+
} else {
|
|
1072
|
+
config.zohoAccounts.push({ email, datacenter });
|
|
1073
|
+
saveConfig(config);
|
|
1074
|
+
console.log(`\nAdded ${email} to Zoho accounts.`);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
await authenticateZohoAccount({ email, datacenter });
|
|
1078
|
+
console.log("\nDone!");
|
|
635
1079
|
} else {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1080
|
+
console.error("Error: Invalid choice. Please enter 1 or 2.");
|
|
1081
|
+
rl.close();
|
|
1082
|
+
process.exit(1);
|
|
639
1083
|
}
|
|
640
1084
|
|
|
641
|
-
|
|
642
|
-
console.log("\nDone!");
|
|
1085
|
+
rl.close();
|
|
643
1086
|
return;
|
|
644
1087
|
}
|
|
645
1088
|
|
|
@@ -649,52 +1092,92 @@ async function handleAuth(args: string[]) {
|
|
|
649
1092
|
const email = args[removeIndex + 1];
|
|
650
1093
|
if (!email || email.startsWith("--")) {
|
|
651
1094
|
console.error("Error: Please provide an email address after --remove");
|
|
1095
|
+
rl.close();
|
|
652
1096
|
process.exit(1);
|
|
653
1097
|
}
|
|
654
1098
|
|
|
655
1099
|
const config = loadConfig();
|
|
656
|
-
const
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
1100
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1101
|
+
|
|
1102
|
+
// Check Google accounts
|
|
1103
|
+
const googleIdx = gmailAccounts.indexOf(email);
|
|
1104
|
+
if (googleIdx !== -1) {
|
|
1105
|
+
if (config.gmailAccounts.length > 0) {
|
|
1106
|
+
config.gmailAccounts.splice(googleIdx, 1);
|
|
1107
|
+
} else {
|
|
1108
|
+
config.accounts.splice(googleIdx, 1);
|
|
1109
|
+
}
|
|
1110
|
+
saveConfig(config);
|
|
1111
|
+
|
|
1112
|
+
// Remove token files
|
|
1113
|
+
const tokenPath = getGoogleTokenPath(email);
|
|
1114
|
+
const legacyPath = getLegacyTokenPath(email);
|
|
1115
|
+
if (existsSync(tokenPath)) unlinkSync(tokenPath);
|
|
1116
|
+
if (existsSync(legacyPath)) unlinkSync(legacyPath);
|
|
1117
|
+
|
|
1118
|
+
console.log(`Removed Google account ${email}.`);
|
|
1119
|
+
rl.close();
|
|
1120
|
+
return;
|
|
660
1121
|
}
|
|
661
1122
|
|
|
662
|
-
|
|
663
|
-
|
|
1123
|
+
// Check Zoho accounts
|
|
1124
|
+
const zohoIdx = config.zohoAccounts.findIndex((z) => z.email === email);
|
|
1125
|
+
if (zohoIdx !== -1) {
|
|
1126
|
+
config.zohoAccounts.splice(zohoIdx, 1);
|
|
1127
|
+
saveConfig(config);
|
|
664
1128
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
1129
|
+
const tokenPath = getZohoTokenPath(email);
|
|
1130
|
+
if (existsSync(tokenPath)) unlinkSync(tokenPath);
|
|
1131
|
+
|
|
1132
|
+
console.log(`Removed Zoho account ${email}.`);
|
|
1133
|
+
rl.close();
|
|
1134
|
+
return;
|
|
668
1135
|
}
|
|
669
1136
|
|
|
670
|
-
console.
|
|
671
|
-
|
|
1137
|
+
console.error(`Error: Account ${email} not found.`);
|
|
1138
|
+
rl.close();
|
|
1139
|
+
process.exit(1);
|
|
672
1140
|
}
|
|
673
1141
|
|
|
674
1142
|
// Default: authenticate all accounts
|
|
675
1143
|
const config = loadConfig();
|
|
1144
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1145
|
+
const hasAny = gmailAccounts.length > 0 || config.zohoAccounts.length > 0;
|
|
676
1146
|
|
|
677
|
-
if (
|
|
1147
|
+
if (!hasAny) {
|
|
678
1148
|
console.log("No accounts configured.\n");
|
|
679
1149
|
console.log("Add an account using:");
|
|
680
1150
|
console.log(" glancebar auth --add your-email@gmail.com\n");
|
|
1151
|
+
rl.close();
|
|
681
1152
|
return;
|
|
682
1153
|
}
|
|
683
1154
|
|
|
684
|
-
console.log("Glancebar -
|
|
685
|
-
console.log("
|
|
1155
|
+
console.log("Glancebar - Calendar Authentication");
|
|
1156
|
+
console.log("====================================\n");
|
|
686
1157
|
|
|
687
|
-
|
|
688
|
-
|
|
1158
|
+
// Authenticate Google accounts
|
|
1159
|
+
for (const account of gmailAccounts) {
|
|
1160
|
+
let tokenPath = getGoogleTokenPath(account);
|
|
1161
|
+
if (!existsSync(tokenPath)) {
|
|
1162
|
+
tokenPath = getLegacyTokenPath(account);
|
|
1163
|
+
}
|
|
1164
|
+
if (existsSync(tokenPath)) {
|
|
1165
|
+
console.log(`Google: ${account} - Already authenticated`);
|
|
1166
|
+
const answer = await prompt(`Re-authenticate? (y/N): `);
|
|
1167
|
+
if (answer.toLowerCase() !== "y") continue;
|
|
1168
|
+
}
|
|
1169
|
+
await authenticateGoogleAccount(account);
|
|
1170
|
+
}
|
|
689
1171
|
|
|
690
|
-
|
|
691
|
-
|
|
1172
|
+
// Authenticate Zoho accounts
|
|
1173
|
+
for (const account of config.zohoAccounts) {
|
|
1174
|
+
const tokenPath = getZohoTokenPath(account.email);
|
|
692
1175
|
if (existsSync(tokenPath)) {
|
|
693
|
-
console.log(
|
|
694
|
-
const answer = await prompt(`Re-authenticate
|
|
1176
|
+
console.log(`Zoho: ${account.email} [${account.datacenter}] - Already authenticated`);
|
|
1177
|
+
const answer = await prompt(`Re-authenticate? (y/N): `);
|
|
695
1178
|
if (answer.toLowerCase() !== "y") continue;
|
|
696
1179
|
}
|
|
697
|
-
await
|
|
1180
|
+
await authenticateZohoAccount(account);
|
|
698
1181
|
}
|
|
699
1182
|
|
|
700
1183
|
rl.close();
|
|
@@ -706,8 +1189,9 @@ function handleConfig(args: string[]) {
|
|
|
706
1189
|
|
|
707
1190
|
// Handle --reset
|
|
708
1191
|
if (args.includes("--reset")) {
|
|
709
|
-
|
|
710
|
-
|
|
1192
|
+
// Preserve all account types
|
|
1193
|
+
const { accounts, gmailAccounts, zohoAccounts } = config;
|
|
1194
|
+
saveConfig({ ...DEFAULT_CONFIG, accounts, gmailAccounts, zohoAccounts });
|
|
711
1195
|
console.log("Configuration reset to defaults (accounts preserved).");
|
|
712
1196
|
return;
|
|
713
1197
|
}
|
|
@@ -839,11 +1323,20 @@ function handleConfig(args: string[]) {
|
|
|
839
1323
|
}
|
|
840
1324
|
|
|
841
1325
|
// Show current config
|
|
1326
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1327
|
+
const googleStr = gmailAccounts.length > 0 ? gmailAccounts.join(", ") : "(none)";
|
|
1328
|
+
const zohoStr = config.zohoAccounts.length > 0
|
|
1329
|
+
? config.zohoAccounts.map((z) => `${z.email} [${z.datacenter}]`).join(", ")
|
|
1330
|
+
: "(none)";
|
|
1331
|
+
|
|
842
1332
|
console.log(`
|
|
843
1333
|
Glancebar Configuration
|
|
844
1334
|
=======================
|
|
845
1335
|
Config directory: ${getConfigDir()}
|
|
846
|
-
|
|
1336
|
+
|
|
1337
|
+
Accounts:
|
|
1338
|
+
Google Calendar: ${googleStr}
|
|
1339
|
+
Zoho Calendar: ${zohoStr}
|
|
847
1340
|
|
|
848
1341
|
Calendar Settings:
|
|
849
1342
|
Lookahead hours: ${config.lookaheadHours}
|
|
@@ -1059,7 +1552,10 @@ async function outputStatusline() {
|
|
|
1059
1552
|
}
|
|
1060
1553
|
|
|
1061
1554
|
// Get calendar events
|
|
1062
|
-
|
|
1555
|
+
const gmailAccounts = config.gmailAccounts.length > 0 ? config.gmailAccounts : config.accounts;
|
|
1556
|
+
const hasAccounts = gmailAccounts.length > 0 || config.zohoAccounts.length > 0;
|
|
1557
|
+
|
|
1558
|
+
if (hasAccounts) {
|
|
1063
1559
|
const events = await getUpcomingEvents(config);
|
|
1064
1560
|
const event = getCurrentOrNextEvent(events);
|
|
1065
1561
|
|