@hasna/connectors 0.3.6 → 0.3.8
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/bin/index.js +1 -1
- package/bin/mcp.js +1 -1
- package/connectors/connect-gmail/src/cli/index.ts +5 -1
- package/connectors/connect-google/src/cli/index.ts +77 -61
- package/connectors/connect-googlecalendar/src/cli/index.ts +10 -9
- package/connectors/connect-googlecontacts/src/cli/index.ts +37 -12
- package/connectors/connect-googledocs/src/cli/index.ts +27 -11
- package/connectors/connect-googledrive/src/cli/index.ts +5 -1
- package/connectors/connect-googlesheets/src/cli/index.ts +28 -15
- package/connectors/connect-googletasks/src/cli/index.ts +38 -18
- package/connectors/connect-notion/src/utils/config.ts +2 -1
- package/package.json +1 -1
package/bin/index.js
CHANGED
|
@@ -6701,7 +6701,7 @@ var PRESETS = {
|
|
|
6701
6701
|
commerce: { description: "Commerce and finance", connectors: ["stripe", "shopify", "revolut", "mercury", "pandadoc"] }
|
|
6702
6702
|
};
|
|
6703
6703
|
var program2 = new Command;
|
|
6704
|
-
program2.name("connectors").description("Install API connectors for your project").version("0.3.
|
|
6704
|
+
program2.name("connectors").description("Install API connectors for your project").version("0.3.8");
|
|
6705
6705
|
program2.command("interactive", { isDefault: true }).alias("i").description("Interactive connector browser").action(() => {
|
|
6706
6706
|
if (!isTTY) {
|
|
6707
6707
|
console.log(`Non-interactive environment detected. Use a subcommand:
|
package/bin/mcp.js
CHANGED
|
@@ -20309,7 +20309,7 @@ async function getConnectorCommandHelp(name, command) {
|
|
|
20309
20309
|
loadConnectorVersions();
|
|
20310
20310
|
var server = new McpServer({
|
|
20311
20311
|
name: "connectors",
|
|
20312
|
-
version: "0.3.
|
|
20312
|
+
version: "0.3.8"
|
|
20313
20313
|
});
|
|
20314
20314
|
server.registerTool("search_connectors", {
|
|
20315
20315
|
title: "Search Connectors",
|
|
@@ -75,7 +75,11 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
75
75
|
|
|
76
76
|
// Helper to check authentication
|
|
77
77
|
function requireAuth(): Gmail {
|
|
78
|
-
|
|
78
|
+
// isAuthenticated() checks for both accessToken and refreshToken.
|
|
79
|
+
// If accessToken is missing/expired but refreshToken exists, the client's
|
|
80
|
+
// getValidAccessToken() will handle the refresh automatically.
|
|
81
|
+
const tokens = loadTokens();
|
|
82
|
+
if (!tokens || (!tokens.accessToken && !tokens.refreshToken)) {
|
|
79
83
|
error('Not authenticated. Run "connect-gmail auth login" first.');
|
|
80
84
|
process.exit(1);
|
|
81
85
|
}
|
|
@@ -5,6 +5,8 @@ import { Google } from '../api';
|
|
|
5
5
|
import {
|
|
6
6
|
getAccessToken,
|
|
7
7
|
setAccessToken,
|
|
8
|
+
getRefreshToken,
|
|
9
|
+
getValidAccessToken,
|
|
8
10
|
clearConfig,
|
|
9
11
|
getConfigDir,
|
|
10
12
|
setProfileOverride,
|
|
@@ -55,12 +57,26 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// Helper to get authenticated client
|
|
58
|
-
function getClient(): Google {
|
|
59
|
-
|
|
60
|
+
async function getClient(): Promise<Google> {
|
|
61
|
+
let accessToken = getAccessToken();
|
|
62
|
+
|
|
63
|
+
// If no access token, try to refresh using refresh token
|
|
60
64
|
if (!accessToken) {
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
const refreshToken = getRefreshToken();
|
|
66
|
+
if (refreshToken) {
|
|
67
|
+
try {
|
|
68
|
+
accessToken = await getValidAccessToken();
|
|
69
|
+
} catch {
|
|
70
|
+
// Fall through to error below
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!accessToken) {
|
|
75
|
+
error(`No access token configured. Run "${CONNECTOR_NAME} config set-token <token>" or set GOOGLE_ACCESS_TOKEN environment variable.`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
63
78
|
}
|
|
79
|
+
|
|
64
80
|
return new Google({ accessToken });
|
|
65
81
|
}
|
|
66
82
|
|
|
@@ -208,7 +224,7 @@ gmailMessagesCmd
|
|
|
208
224
|
.option('--labels <labels>', 'Label IDs (comma-separated)')
|
|
209
225
|
.action(async (opts) => {
|
|
210
226
|
try {
|
|
211
|
-
const client = getClient();
|
|
227
|
+
const client = await getClient();
|
|
212
228
|
const result = await client.gmail.listMessages({
|
|
213
229
|
maxResults: parseInt(opts.max),
|
|
214
230
|
q: opts.query,
|
|
@@ -227,7 +243,7 @@ gmailMessagesCmd
|
|
|
227
243
|
.option('--format <format>', 'Message format (minimal, full, raw, metadata)', 'full')
|
|
228
244
|
.action(async (id: string, opts) => {
|
|
229
245
|
try {
|
|
230
|
-
const client = getClient();
|
|
246
|
+
const client = await getClient();
|
|
231
247
|
const result = await client.gmail.getMessage(id, { format: opts.format });
|
|
232
248
|
print(result, getFormat(gmailMessagesCmd));
|
|
233
249
|
} catch (err) {
|
|
@@ -247,7 +263,7 @@ gmailMessagesCmd
|
|
|
247
263
|
.option('--html', 'Send as HTML email')
|
|
248
264
|
.action(async (opts) => {
|
|
249
265
|
try {
|
|
250
|
-
const client = getClient();
|
|
266
|
+
const client = await getClient();
|
|
251
267
|
const result = await client.gmail.sendMessage({
|
|
252
268
|
to: opts.to,
|
|
253
269
|
subject: opts.subject,
|
|
@@ -269,7 +285,7 @@ gmailMessagesCmd
|
|
|
269
285
|
.description('Delete a message permanently')
|
|
270
286
|
.action(async (id: string) => {
|
|
271
287
|
try {
|
|
272
|
-
const client = getClient();
|
|
288
|
+
const client = await getClient();
|
|
273
289
|
await client.gmail.deleteMessage(id);
|
|
274
290
|
success(`Message ${id} deleted`);
|
|
275
291
|
} catch (err) {
|
|
@@ -283,7 +299,7 @@ gmailMessagesCmd
|
|
|
283
299
|
.description('Move a message to trash')
|
|
284
300
|
.action(async (id: string) => {
|
|
285
301
|
try {
|
|
286
|
-
const client = getClient();
|
|
302
|
+
const client = await getClient();
|
|
287
303
|
await client.gmail.trashMessage(id);
|
|
288
304
|
success(`Message ${id} moved to trash`);
|
|
289
305
|
} catch (err) {
|
|
@@ -297,7 +313,7 @@ gmailMessagesCmd
|
|
|
297
313
|
.description('Remove a message from trash')
|
|
298
314
|
.action(async (id: string) => {
|
|
299
315
|
try {
|
|
300
|
-
const client = getClient();
|
|
316
|
+
const client = await getClient();
|
|
301
317
|
await client.gmail.untrashMessage(id);
|
|
302
318
|
success(`Message ${id} removed from trash`);
|
|
303
319
|
} catch (err) {
|
|
@@ -316,7 +332,7 @@ gmailLabelsCmd
|
|
|
316
332
|
.description('List all labels')
|
|
317
333
|
.action(async () => {
|
|
318
334
|
try {
|
|
319
|
-
const client = getClient();
|
|
335
|
+
const client = await getClient();
|
|
320
336
|
const result = await client.gmail.listLabels();
|
|
321
337
|
print(result, getFormat(gmailLabelsCmd));
|
|
322
338
|
} catch (err) {
|
|
@@ -330,7 +346,7 @@ gmailLabelsCmd
|
|
|
330
346
|
.description('Get a label by ID')
|
|
331
347
|
.action(async (id: string) => {
|
|
332
348
|
try {
|
|
333
|
-
const client = getClient();
|
|
349
|
+
const client = await getClient();
|
|
334
350
|
const result = await client.gmail.getLabel(id);
|
|
335
351
|
print(result, getFormat(gmailLabelsCmd));
|
|
336
352
|
} catch (err) {
|
|
@@ -344,7 +360,7 @@ gmailLabelsCmd
|
|
|
344
360
|
.description('Create a new label')
|
|
345
361
|
.action(async (name: string) => {
|
|
346
362
|
try {
|
|
347
|
-
const client = getClient();
|
|
363
|
+
const client = await getClient();
|
|
348
364
|
const result = await client.gmail.createLabel(name);
|
|
349
365
|
success('Label created!');
|
|
350
366
|
print(result, getFormat(gmailLabelsCmd));
|
|
@@ -359,7 +375,7 @@ gmailLabelsCmd
|
|
|
359
375
|
.description('Delete a label')
|
|
360
376
|
.action(async (id: string) => {
|
|
361
377
|
try {
|
|
362
|
-
const client = getClient();
|
|
378
|
+
const client = await getClient();
|
|
363
379
|
await client.gmail.deleteLabel(id);
|
|
364
380
|
success(`Label ${id} deleted`);
|
|
365
381
|
} catch (err) {
|
|
@@ -379,7 +395,7 @@ gmailDraftsCmd
|
|
|
379
395
|
.option('-n, --max <number>', 'Maximum results', '10')
|
|
380
396
|
.action(async (opts) => {
|
|
381
397
|
try {
|
|
382
|
-
const client = getClient();
|
|
398
|
+
const client = await getClient();
|
|
383
399
|
const result = await client.gmail.listDrafts({
|
|
384
400
|
maxResults: parseInt(opts.max),
|
|
385
401
|
});
|
|
@@ -395,7 +411,7 @@ gmailDraftsCmd
|
|
|
395
411
|
.description('Get a draft by ID')
|
|
396
412
|
.action(async (id: string) => {
|
|
397
413
|
try {
|
|
398
|
-
const client = getClient();
|
|
414
|
+
const client = await getClient();
|
|
399
415
|
const result = await client.gmail.getDraft(id);
|
|
400
416
|
print(result, getFormat(gmailDraftsCmd));
|
|
401
417
|
} catch (err) {
|
|
@@ -412,7 +428,7 @@ gmailDraftsCmd
|
|
|
412
428
|
.requiredOption('--body <body>', 'Email body')
|
|
413
429
|
.action(async (opts) => {
|
|
414
430
|
try {
|
|
415
|
-
const client = getClient();
|
|
431
|
+
const client = await getClient();
|
|
416
432
|
const result = await client.gmail.createDraft({
|
|
417
433
|
to: opts.to,
|
|
418
434
|
subject: opts.subject,
|
|
@@ -431,7 +447,7 @@ gmailDraftsCmd
|
|
|
431
447
|
.description('Delete a draft')
|
|
432
448
|
.action(async (id: string) => {
|
|
433
449
|
try {
|
|
434
|
-
const client = getClient();
|
|
450
|
+
const client = await getClient();
|
|
435
451
|
await client.gmail.deleteDraft(id);
|
|
436
452
|
success(`Draft ${id} deleted`);
|
|
437
453
|
} catch (err) {
|
|
@@ -445,7 +461,7 @@ gmailDraftsCmd
|
|
|
445
461
|
.description('Send a draft')
|
|
446
462
|
.action(async (id: string) => {
|
|
447
463
|
try {
|
|
448
|
-
const client = getClient();
|
|
464
|
+
const client = await getClient();
|
|
449
465
|
const result = await client.gmail.sendDraft(id);
|
|
450
466
|
success('Draft sent!');
|
|
451
467
|
print(result, getFormat(gmailDraftsCmd));
|
|
@@ -475,7 +491,7 @@ driveFilesCmd
|
|
|
475
491
|
.option('--folder <id>', 'List files in folder')
|
|
476
492
|
.action(async (opts) => {
|
|
477
493
|
try {
|
|
478
|
-
const client = getClient();
|
|
494
|
+
const client = await getClient();
|
|
479
495
|
let result;
|
|
480
496
|
if (opts.folder) {
|
|
481
497
|
result = await client.drive.listFilesInFolder(opts.folder, {
|
|
@@ -499,7 +515,7 @@ driveFilesCmd
|
|
|
499
515
|
.description('Get file metadata')
|
|
500
516
|
.action(async (id: string) => {
|
|
501
517
|
try {
|
|
502
|
-
const client = getClient();
|
|
518
|
+
const client = await getClient();
|
|
503
519
|
const result = await client.drive.getFile(id);
|
|
504
520
|
print(result, getFormat(driveFilesCmd));
|
|
505
521
|
} catch (err) {
|
|
@@ -517,7 +533,7 @@ driveFilesCmd
|
|
|
517
533
|
.option('--content <content>', 'File content')
|
|
518
534
|
.action(async (opts) => {
|
|
519
535
|
try {
|
|
520
|
-
const client = getClient();
|
|
536
|
+
const client = await getClient();
|
|
521
537
|
const result = await client.drive.createFile({
|
|
522
538
|
name: opts.name,
|
|
523
539
|
parents: opts.parent ? [opts.parent] : undefined,
|
|
@@ -538,7 +554,7 @@ driveFilesCmd
|
|
|
538
554
|
.option('--parent <id>', 'Parent folder ID')
|
|
539
555
|
.action(async (name: string, opts) => {
|
|
540
556
|
try {
|
|
541
|
-
const client = getClient();
|
|
557
|
+
const client = await getClient();
|
|
542
558
|
const result = await client.drive.createFolder(name, {
|
|
543
559
|
parents: opts.parent ? [opts.parent] : undefined,
|
|
544
560
|
});
|
|
@@ -558,7 +574,7 @@ driveFilesCmd
|
|
|
558
574
|
.option('--starred', 'Star the file')
|
|
559
575
|
.action(async (id: string, opts) => {
|
|
560
576
|
try {
|
|
561
|
-
const client = getClient();
|
|
577
|
+
const client = await getClient();
|
|
562
578
|
const result = await client.drive.updateFile(id, {
|
|
563
579
|
name: opts.name,
|
|
564
580
|
description: opts.description,
|
|
@@ -577,7 +593,7 @@ driveFilesCmd
|
|
|
577
593
|
.description('Delete a file permanently')
|
|
578
594
|
.action(async (id: string) => {
|
|
579
595
|
try {
|
|
580
|
-
const client = getClient();
|
|
596
|
+
const client = await getClient();
|
|
581
597
|
await client.drive.deleteFile(id);
|
|
582
598
|
success(`File ${id} deleted`);
|
|
583
599
|
} catch (err) {
|
|
@@ -591,7 +607,7 @@ driveFilesCmd
|
|
|
591
607
|
.description('Move a file to trash')
|
|
592
608
|
.action(async (id: string) => {
|
|
593
609
|
try {
|
|
594
|
-
const client = getClient();
|
|
610
|
+
const client = await getClient();
|
|
595
611
|
await client.drive.trashFile(id);
|
|
596
612
|
success(`File ${id} moved to trash`);
|
|
597
613
|
} catch (err) {
|
|
@@ -606,7 +622,7 @@ driveFilesCmd
|
|
|
606
622
|
.option('--name <name>', 'New name for the copy')
|
|
607
623
|
.action(async (id: string, opts) => {
|
|
608
624
|
try {
|
|
609
|
-
const client = getClient();
|
|
625
|
+
const client = await getClient();
|
|
610
626
|
const result = await client.drive.copyFile(id, { name: opts.name });
|
|
611
627
|
success('File copied!');
|
|
612
628
|
print(result, getFormat(driveFilesCmd));
|
|
@@ -626,7 +642,7 @@ drivePermissionsCmd
|
|
|
626
642
|
.description('List permissions for a file')
|
|
627
643
|
.action(async (fileId: string) => {
|
|
628
644
|
try {
|
|
629
|
-
const client = getClient();
|
|
645
|
+
const client = await getClient();
|
|
630
646
|
const result = await client.drive.listPermissions(fileId);
|
|
631
647
|
print(result, getFormat(drivePermissionsCmd));
|
|
632
648
|
} catch (err) {
|
|
@@ -645,7 +661,7 @@ drivePermissionsCmd
|
|
|
645
661
|
.option('--notify', 'Send notification email')
|
|
646
662
|
.action(async (fileId: string, opts) => {
|
|
647
663
|
try {
|
|
648
|
-
const client = getClient();
|
|
664
|
+
const client = await getClient();
|
|
649
665
|
const result = await client.drive.createPermission(fileId, {
|
|
650
666
|
type: opts.type,
|
|
651
667
|
role: opts.role,
|
|
@@ -666,7 +682,7 @@ drivePermissionsCmd
|
|
|
666
682
|
.description('Remove a permission')
|
|
667
683
|
.action(async (fileId: string, permissionId: string) => {
|
|
668
684
|
try {
|
|
669
|
-
const client = getClient();
|
|
685
|
+
const client = await getClient();
|
|
670
686
|
await client.drive.deletePermission(fileId, permissionId);
|
|
671
687
|
success('Permission deleted');
|
|
672
688
|
} catch (err) {
|
|
@@ -692,7 +708,7 @@ calendarListCmd
|
|
|
692
708
|
.description('List calendars')
|
|
693
709
|
.action(async () => {
|
|
694
710
|
try {
|
|
695
|
-
const client = getClient();
|
|
711
|
+
const client = await getClient();
|
|
696
712
|
const result = await client.calendar.listCalendars();
|
|
697
713
|
print(result, getFormat(calendarListCmd));
|
|
698
714
|
} catch (err) {
|
|
@@ -706,7 +722,7 @@ calendarListCmd
|
|
|
706
722
|
.description('Get a calendar')
|
|
707
723
|
.action(async (id: string) => {
|
|
708
724
|
try {
|
|
709
|
-
const client = getClient();
|
|
725
|
+
const client = await getClient();
|
|
710
726
|
const result = await client.calendar.getCalendar(id);
|
|
711
727
|
print(result, getFormat(calendarListCmd));
|
|
712
728
|
} catch (err) {
|
|
@@ -722,7 +738,7 @@ calendarListCmd
|
|
|
722
738
|
.option('--timezone <tz>', 'Time zone')
|
|
723
739
|
.action(async (summary: string, opts) => {
|
|
724
740
|
try {
|
|
725
|
-
const client = getClient();
|
|
741
|
+
const client = await getClient();
|
|
726
742
|
const result = await client.calendar.createCalendar(summary, {
|
|
727
743
|
description: opts.description,
|
|
728
744
|
timeZone: opts.timezone,
|
|
@@ -740,7 +756,7 @@ calendarListCmd
|
|
|
740
756
|
.description('Delete a calendar')
|
|
741
757
|
.action(async (id: string) => {
|
|
742
758
|
try {
|
|
743
|
-
const client = getClient();
|
|
759
|
+
const client = await getClient();
|
|
744
760
|
await client.calendar.deleteCalendar(id);
|
|
745
761
|
success(`Calendar ${id} deleted`);
|
|
746
762
|
} catch (err) {
|
|
@@ -763,7 +779,7 @@ calendarEventsCmd
|
|
|
763
779
|
.option('-q, --query <query>', 'Search query')
|
|
764
780
|
.action(async (calendarId: string = 'primary', opts) => {
|
|
765
781
|
try {
|
|
766
|
-
const client = getClient();
|
|
782
|
+
const client = await getClient();
|
|
767
783
|
const result = await client.calendar.listEvents(calendarId, {
|
|
768
784
|
maxResults: parseInt(opts.max),
|
|
769
785
|
timeMin: opts.from,
|
|
@@ -784,7 +800,7 @@ calendarEventsCmd
|
|
|
784
800
|
.description('List today\'s events')
|
|
785
801
|
.action(async (calendarId: string = 'primary') => {
|
|
786
802
|
try {
|
|
787
|
-
const client = getClient();
|
|
803
|
+
const client = await getClient();
|
|
788
804
|
const result = await client.calendar.getTodayEvents(calendarId);
|
|
789
805
|
print(result, getFormat(calendarEventsCmd));
|
|
790
806
|
} catch (err) {
|
|
@@ -798,7 +814,7 @@ calendarEventsCmd
|
|
|
798
814
|
.description('List this week\'s events')
|
|
799
815
|
.action(async (calendarId: string = 'primary') => {
|
|
800
816
|
try {
|
|
801
|
-
const client = getClient();
|
|
817
|
+
const client = await getClient();
|
|
802
818
|
const result = await client.calendar.getWeekEvents(calendarId);
|
|
803
819
|
print(result, getFormat(calendarEventsCmd));
|
|
804
820
|
} catch (err) {
|
|
@@ -812,7 +828,7 @@ calendarEventsCmd
|
|
|
812
828
|
.description('Get an event')
|
|
813
829
|
.action(async (eventId: string, calendarId: string = 'primary') => {
|
|
814
830
|
try {
|
|
815
|
-
const client = getClient();
|
|
831
|
+
const client = await getClient();
|
|
816
832
|
const result = await client.calendar.getEvent(calendarId, eventId);
|
|
817
833
|
print(result, getFormat(calendarEventsCmd));
|
|
818
834
|
} catch (err) {
|
|
@@ -833,7 +849,7 @@ calendarEventsCmd
|
|
|
833
849
|
.option('--attendees <emails>', 'Attendee emails (comma-separated)')
|
|
834
850
|
.action(async (calendarId: string = 'primary', opts) => {
|
|
835
851
|
try {
|
|
836
|
-
const client = getClient();
|
|
852
|
+
const client = await getClient();
|
|
837
853
|
let result;
|
|
838
854
|
|
|
839
855
|
if (opts.date) {
|
|
@@ -873,7 +889,7 @@ calendarEventsCmd
|
|
|
873
889
|
.description('Quick add an event using natural language')
|
|
874
890
|
.action(async (text: string, calendarId: string = 'primary') => {
|
|
875
891
|
try {
|
|
876
|
-
const client = getClient();
|
|
892
|
+
const client = await getClient();
|
|
877
893
|
const result = await client.calendar.quickAddEvent(calendarId, text);
|
|
878
894
|
success('Event created!');
|
|
879
895
|
print(result, getFormat(calendarEventsCmd));
|
|
@@ -891,7 +907,7 @@ calendarEventsCmd
|
|
|
891
907
|
.option('--location <location>', 'Event location')
|
|
892
908
|
.action(async (eventId: string, calendarId: string = 'primary', opts) => {
|
|
893
909
|
try {
|
|
894
|
-
const client = getClient();
|
|
910
|
+
const client = await getClient();
|
|
895
911
|
const result = await client.calendar.updateEvent(calendarId, eventId, {
|
|
896
912
|
summary: opts.summary,
|
|
897
913
|
description: opts.description,
|
|
@@ -910,7 +926,7 @@ calendarEventsCmd
|
|
|
910
926
|
.description('Delete an event')
|
|
911
927
|
.action(async (eventId: string, calendarId: string = 'primary') => {
|
|
912
928
|
try {
|
|
913
|
-
const client = getClient();
|
|
929
|
+
const client = await getClient();
|
|
914
930
|
await client.calendar.deleteEvent(calendarId, eventId);
|
|
915
931
|
success('Event deleted');
|
|
916
932
|
} catch (err) {
|
|
@@ -931,7 +947,7 @@ docsCmd
|
|
|
931
947
|
.description('Create a new document')
|
|
932
948
|
.action(async (title: string) => {
|
|
933
949
|
try {
|
|
934
|
-
const client = getClient();
|
|
950
|
+
const client = await getClient();
|
|
935
951
|
const result = await client.docs.createDocument({ title });
|
|
936
952
|
success('Document created!');
|
|
937
953
|
print(result, getFormat(docsCmd));
|
|
@@ -946,7 +962,7 @@ docsCmd
|
|
|
946
962
|
.description('Get a document')
|
|
947
963
|
.action(async (documentId: string) => {
|
|
948
964
|
try {
|
|
949
|
-
const client = getClient();
|
|
965
|
+
const client = await getClient();
|
|
950
966
|
const result = await client.docs.getDocument(documentId);
|
|
951
967
|
print(result, getFormat(docsCmd));
|
|
952
968
|
} catch (err) {
|
|
@@ -960,7 +976,7 @@ docsCmd
|
|
|
960
976
|
.description('Read document content as plain text')
|
|
961
977
|
.action(async (documentId: string) => {
|
|
962
978
|
try {
|
|
963
|
-
const client = getClient();
|
|
979
|
+
const client = await getClient();
|
|
964
980
|
const doc = await client.docs.getDocument(documentId);
|
|
965
981
|
const text = client.docs.extractPlainText(doc);
|
|
966
982
|
console.log(text);
|
|
@@ -975,7 +991,7 @@ docsCmd
|
|
|
975
991
|
.description('Append text to a document')
|
|
976
992
|
.action(async (documentId: string, text: string) => {
|
|
977
993
|
try {
|
|
978
|
-
const client = getClient();
|
|
994
|
+
const client = await getClient();
|
|
979
995
|
await client.docs.appendText(documentId, text);
|
|
980
996
|
success('Text appended to document');
|
|
981
997
|
} catch (err) {
|
|
@@ -992,7 +1008,7 @@ docsCmd
|
|
|
992
1008
|
.option('--match-case', 'Case-sensitive matching')
|
|
993
1009
|
.action(async (documentId: string, opts) => {
|
|
994
1010
|
try {
|
|
995
|
-
const client = getClient();
|
|
1011
|
+
const client = await getClient();
|
|
996
1012
|
const result = await client.docs.replaceAllText(documentId, opts.find, opts.replace, opts.matchCase);
|
|
997
1013
|
success('Text replaced');
|
|
998
1014
|
print(result, getFormat(docsCmd));
|
|
@@ -1010,7 +1026,7 @@ docsCmd
|
|
|
1010
1026
|
.option('--height <height>', 'Image height in points')
|
|
1011
1027
|
.action(async (documentId: string, imageUri: string, opts) => {
|
|
1012
1028
|
try {
|
|
1013
|
-
const client = getClient();
|
|
1029
|
+
const client = await getClient();
|
|
1014
1030
|
await client.docs.insertImage(documentId, imageUri, {
|
|
1015
1031
|
index: opts.index ? parseInt(opts.index) : undefined,
|
|
1016
1032
|
width: opts.width ? parseInt(opts.width) : undefined,
|
|
@@ -1031,7 +1047,7 @@ docsCmd
|
|
|
1031
1047
|
.option('--index <index>', 'Insert position')
|
|
1032
1048
|
.action(async (documentId: string, opts) => {
|
|
1033
1049
|
try {
|
|
1034
|
-
const client = getClient();
|
|
1050
|
+
const client = await getClient();
|
|
1035
1051
|
await client.docs.insertTable(documentId, parseInt(opts.rows), parseInt(opts.cols), {
|
|
1036
1052
|
index: opts.index ? parseInt(opts.index) : undefined,
|
|
1037
1053
|
});
|
|
@@ -1054,7 +1070,7 @@ sheetsCmd
|
|
|
1054
1070
|
.description('Create a new spreadsheet')
|
|
1055
1071
|
.action(async (title: string) => {
|
|
1056
1072
|
try {
|
|
1057
|
-
const client = getClient();
|
|
1073
|
+
const client = await getClient();
|
|
1058
1074
|
const result = await client.sheets.createSpreadsheet({ title });
|
|
1059
1075
|
success('Spreadsheet created!');
|
|
1060
1076
|
print(result, getFormat(sheetsCmd));
|
|
@@ -1069,7 +1085,7 @@ sheetsCmd
|
|
|
1069
1085
|
.description('Get spreadsheet metadata')
|
|
1070
1086
|
.action(async (spreadsheetId: string) => {
|
|
1071
1087
|
try {
|
|
1072
|
-
const client = getClient();
|
|
1088
|
+
const client = await getClient();
|
|
1073
1089
|
const result = await client.sheets.getSpreadsheetMetadata(spreadsheetId);
|
|
1074
1090
|
print(result, getFormat(sheetsCmd));
|
|
1075
1091
|
} catch (err) {
|
|
@@ -1083,7 +1099,7 @@ sheetsCmd
|
|
|
1083
1099
|
.description('List sheet names in a spreadsheet')
|
|
1084
1100
|
.action(async (spreadsheetId: string) => {
|
|
1085
1101
|
try {
|
|
1086
|
-
const client = getClient();
|
|
1102
|
+
const client = await getClient();
|
|
1087
1103
|
const names = await client.sheets.getSheetNames(spreadsheetId);
|
|
1088
1104
|
print(names, getFormat(sheetsCmd));
|
|
1089
1105
|
} catch (err) {
|
|
@@ -1097,7 +1113,7 @@ sheetsCmd
|
|
|
1097
1113
|
.description('Read values from a range')
|
|
1098
1114
|
.action(async (spreadsheetId: string, range: string) => {
|
|
1099
1115
|
try {
|
|
1100
|
-
const client = getClient();
|
|
1116
|
+
const client = await getClient();
|
|
1101
1117
|
const result = await client.sheets.getValues(spreadsheetId, range);
|
|
1102
1118
|
print(result, getFormat(sheetsCmd));
|
|
1103
1119
|
} catch (err) {
|
|
@@ -1112,7 +1128,7 @@ sheetsCmd
|
|
|
1112
1128
|
.requiredOption('--values <json>', 'Values as JSON array (e.g., \'[["A", "B"], ["1", "2"]]\')')
|
|
1113
1129
|
.action(async (spreadsheetId: string, range: string, opts) => {
|
|
1114
1130
|
try {
|
|
1115
|
-
const client = getClient();
|
|
1131
|
+
const client = await getClient();
|
|
1116
1132
|
const values = JSON.parse(opts.values);
|
|
1117
1133
|
const result = await client.sheets.updateValues(spreadsheetId, range, values);
|
|
1118
1134
|
success('Values written!');
|
|
@@ -1129,7 +1145,7 @@ sheetsCmd
|
|
|
1129
1145
|
.requiredOption('--values <json>', 'Values as JSON array')
|
|
1130
1146
|
.action(async (spreadsheetId: string, range: string, opts) => {
|
|
1131
1147
|
try {
|
|
1132
|
-
const client = getClient();
|
|
1148
|
+
const client = await getClient();
|
|
1133
1149
|
const values = JSON.parse(opts.values);
|
|
1134
1150
|
const result = await client.sheets.appendValues(spreadsheetId, range, values);
|
|
1135
1151
|
success('Values appended!');
|
|
@@ -1146,7 +1162,7 @@ sheetsCmd
|
|
|
1146
1162
|
.requiredOption('--row <json>', 'Row values as JSON array (e.g., \'["value1", "value2"]\')')
|
|
1147
1163
|
.action(async (spreadsheetId: string, sheetName: string, opts) => {
|
|
1148
1164
|
try {
|
|
1149
|
-
const client = getClient();
|
|
1165
|
+
const client = await getClient();
|
|
1150
1166
|
const row = JSON.parse(opts.row);
|
|
1151
1167
|
const result = await client.sheets.appendRow(spreadsheetId, sheetName, row);
|
|
1152
1168
|
success('Row appended!');
|
|
@@ -1162,7 +1178,7 @@ sheetsCmd
|
|
|
1162
1178
|
.description('Clear values from a range')
|
|
1163
1179
|
.action(async (spreadsheetId: string, range: string) => {
|
|
1164
1180
|
try {
|
|
1165
|
-
const client = getClient();
|
|
1181
|
+
const client = await getClient();
|
|
1166
1182
|
const result = await client.sheets.clearValues(spreadsheetId, range);
|
|
1167
1183
|
success('Values cleared!');
|
|
1168
1184
|
print(result, getFormat(sheetsCmd));
|
|
@@ -1177,7 +1193,7 @@ sheetsCmd
|
|
|
1177
1193
|
.description('Get a single cell value')
|
|
1178
1194
|
.action(async (spreadsheetId: string, sheetName: string, cell: string) => {
|
|
1179
1195
|
try {
|
|
1180
|
-
const client = getClient();
|
|
1196
|
+
const client = await getClient();
|
|
1181
1197
|
const value = await client.sheets.getCellValue(spreadsheetId, sheetName, cell);
|
|
1182
1198
|
console.log(value);
|
|
1183
1199
|
} catch (err) {
|
|
@@ -1191,7 +1207,7 @@ sheetsCmd
|
|
|
1191
1207
|
.description('Set a single cell value')
|
|
1192
1208
|
.action(async (spreadsheetId: string, sheetName: string, cell: string, value: string) => {
|
|
1193
1209
|
try {
|
|
1194
|
-
const client = getClient();
|
|
1210
|
+
const client = await getClient();
|
|
1195
1211
|
await client.sheets.setCellValue(spreadsheetId, sheetName, cell, value);
|
|
1196
1212
|
success('Cell value set!');
|
|
1197
1213
|
} catch (err) {
|
|
@@ -1205,7 +1221,7 @@ sheetsCmd
|
|
|
1205
1221
|
.description('Get a row by index (1-based)')
|
|
1206
1222
|
.action(async (spreadsheetId: string, sheetName: string, rowIndex: string) => {
|
|
1207
1223
|
try {
|
|
1208
|
-
const client = getClient();
|
|
1224
|
+
const client = await getClient();
|
|
1209
1225
|
const row = await client.sheets.getRow(spreadsheetId, sheetName, parseInt(rowIndex));
|
|
1210
1226
|
print(row, getFormat(sheetsCmd));
|
|
1211
1227
|
} catch (err) {
|
|
@@ -1219,7 +1235,7 @@ sheetsCmd
|
|
|
1219
1235
|
.description('Get a column by letter')
|
|
1220
1236
|
.action(async (spreadsheetId: string, sheetName: string, column: string) => {
|
|
1221
1237
|
try {
|
|
1222
|
-
const client = getClient();
|
|
1238
|
+
const client = await getClient();
|
|
1223
1239
|
const col = await client.sheets.getColumn(spreadsheetId, sheetName, column);
|
|
1224
1240
|
print(col, getFormat(sheetsCmd));
|
|
1225
1241
|
} catch (err) {
|
|
@@ -66,22 +66,17 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
66
66
|
async function getClient(): Promise<GoogleCalendar> {
|
|
67
67
|
let accessToken = getAccessToken();
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Check if token is expired and we have refresh token
|
|
75
|
-
if (isTokenExpired()) {
|
|
69
|
+
// If no access token or token is expired, try to refresh using refresh token
|
|
70
|
+
if (!accessToken || isTokenExpired()) {
|
|
76
71
|
const refreshToken = getRefreshToken();
|
|
77
72
|
const clientId = getClientId();
|
|
78
73
|
const clientSecret = getClientSecret();
|
|
79
74
|
|
|
80
75
|
if (refreshToken && clientId && clientSecret) {
|
|
81
|
-
info('Access token expired, refreshing...');
|
|
76
|
+
info(accessToken ? 'Access token expired, refreshing...' : 'No access token, attempting refresh...');
|
|
82
77
|
try {
|
|
83
78
|
const client = new GoogleCalendar({
|
|
84
|
-
accessToken,
|
|
79
|
+
accessToken: accessToken || '',
|
|
85
80
|
refreshToken,
|
|
86
81
|
clientId,
|
|
87
82
|
clientSecret,
|
|
@@ -94,6 +89,12 @@ async function getClient(): Promise<GoogleCalendar> {
|
|
|
94
89
|
warn(`Failed to refresh token: ${err}. You may need to re-authenticate.`);
|
|
95
90
|
}
|
|
96
91
|
}
|
|
92
|
+
|
|
93
|
+
// After refresh attempt, if still no access token, exit
|
|
94
|
+
if (!accessToken) {
|
|
95
|
+
error(`No access token configured. Run "${CONNECTOR_NAME} auth login" to authenticate or set GOOGLE_CALENDAR_ACCESS_TOKEN environment variable.`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
const refreshToken = getRefreshToken();
|
|
@@ -62,10 +62,10 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
// Helper to get authenticated client
|
|
65
|
-
function getClient(): GoogleContacts {
|
|
65
|
+
async function getClient(): Promise<GoogleContacts> {
|
|
66
66
|
const clientId = getClientId();
|
|
67
67
|
const clientSecret = getClientSecret();
|
|
68
|
-
|
|
68
|
+
let accessToken = getAccessToken();
|
|
69
69
|
const refreshToken = getRefreshToken();
|
|
70
70
|
const redirectUri = getRedirectUri();
|
|
71
71
|
|
|
@@ -74,9 +74,34 @@ function getClient(): GoogleContacts {
|
|
|
74
74
|
process.exit(1);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// If no access token, try to refresh using refresh token
|
|
77
78
|
if (!accessToken) {
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
if (refreshToken) {
|
|
80
|
+
try {
|
|
81
|
+
info('No access token, attempting refresh...');
|
|
82
|
+
const client = new GoogleContacts({
|
|
83
|
+
clientId,
|
|
84
|
+
clientSecret,
|
|
85
|
+
accessToken: '',
|
|
86
|
+
refreshToken,
|
|
87
|
+
redirectUri,
|
|
88
|
+
});
|
|
89
|
+
const tokens = await client.refreshAccessToken();
|
|
90
|
+
saveTokens({
|
|
91
|
+
accessToken: tokens.accessToken,
|
|
92
|
+
refreshToken: tokens.refreshToken,
|
|
93
|
+
expiresIn: tokens.expiresIn,
|
|
94
|
+
});
|
|
95
|
+
accessToken = tokens.accessToken;
|
|
96
|
+
success('Token refreshed successfully');
|
|
97
|
+
} catch (err) {
|
|
98
|
+
error(`Failed to refresh token: ${err}. Run "${CONNECTOR_NAME} auth login" to re-authenticate.`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
error(`No access token. Run "${CONNECTOR_NAME} auth login" to authenticate.`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
80
105
|
}
|
|
81
106
|
|
|
82
107
|
return new GoogleContacts({
|
|
@@ -462,7 +487,7 @@ contactsCmd
|
|
|
462
487
|
.option('--normalize', 'Output in normalized format')
|
|
463
488
|
.action(async (opts) => {
|
|
464
489
|
try {
|
|
465
|
-
const client = getClient();
|
|
490
|
+
const client = await getClient();
|
|
466
491
|
const result = await client.contacts.list({
|
|
467
492
|
pageSize: parseInt(opts.max),
|
|
468
493
|
pageToken: opts.pageToken,
|
|
@@ -491,7 +516,7 @@ contactsCmd
|
|
|
491
516
|
.option('--normalize', 'Output in normalized format')
|
|
492
517
|
.action(async (resourceName: string, opts) => {
|
|
493
518
|
try {
|
|
494
|
-
const client = getClient();
|
|
519
|
+
const client = await getClient();
|
|
495
520
|
const result = await client.contacts.get(resourceName);
|
|
496
521
|
|
|
497
522
|
if (opts.normalize) {
|
|
@@ -516,7 +541,7 @@ contactsCmd
|
|
|
516
541
|
.option('--title <title>', 'Job title')
|
|
517
542
|
.action(async (opts) => {
|
|
518
543
|
try {
|
|
519
|
-
const client = getClient();
|
|
544
|
+
const client = await getClient();
|
|
520
545
|
const result = await client.contacts.create({
|
|
521
546
|
givenName: opts.givenName,
|
|
522
547
|
familyName: opts.familyName,
|
|
@@ -547,7 +572,7 @@ contactsCmd
|
|
|
547
572
|
.option('--title <title>', 'Job title')
|
|
548
573
|
.action(async (resourceName: string, opts) => {
|
|
549
574
|
try {
|
|
550
|
-
const client = getClient();
|
|
575
|
+
const client = await getClient();
|
|
551
576
|
const result = await client.contacts.update(resourceName, {
|
|
552
577
|
givenName: opts.givenName,
|
|
553
578
|
familyName: opts.familyName,
|
|
@@ -572,7 +597,7 @@ contactsCmd
|
|
|
572
597
|
.description('Delete a contact')
|
|
573
598
|
.action(async (resourceName: string) => {
|
|
574
599
|
try {
|
|
575
|
-
const client = getClient();
|
|
600
|
+
const client = await getClient();
|
|
576
601
|
await client.contacts.delete(resourceName);
|
|
577
602
|
success(`Contact ${resourceName} deleted.`);
|
|
578
603
|
} catch (err) {
|
|
@@ -588,7 +613,7 @@ contactsCmd
|
|
|
588
613
|
.option('--normalize', 'Output in normalized format')
|
|
589
614
|
.action(async (query: string, opts) => {
|
|
590
615
|
try {
|
|
591
|
-
const client = getClient();
|
|
616
|
+
const client = await getClient();
|
|
592
617
|
const result = await client.contacts.search({
|
|
593
618
|
query,
|
|
594
619
|
pageSize: parseInt(opts.max),
|
|
@@ -613,7 +638,7 @@ contactsCmd
|
|
|
613
638
|
.option('--normalize', 'Export in normalized format')
|
|
614
639
|
.action(async (opts) => {
|
|
615
640
|
try {
|
|
616
|
-
const client = getClient();
|
|
641
|
+
const client = await getClient();
|
|
617
642
|
const file = Bun.file(opts.output);
|
|
618
643
|
const writer = file.writer();
|
|
619
644
|
|
|
@@ -641,7 +666,7 @@ contactsCmd
|
|
|
641
666
|
.option('-n, --max <number>', 'Maximum results', '100')
|
|
642
667
|
.action(async (opts) => {
|
|
643
668
|
try {
|
|
644
|
-
const client = getClient();
|
|
669
|
+
const client = await getClient();
|
|
645
670
|
const result = await client.contacts.listGroups(parseInt(opts.max));
|
|
646
671
|
print(result, getFormat(contactsCmd));
|
|
647
672
|
} catch (err) {
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
setApiKey,
|
|
8
8
|
getAccessToken,
|
|
9
9
|
setAccessToken,
|
|
10
|
+
getRefreshToken,
|
|
11
|
+
getValidAccessToken,
|
|
10
12
|
clearConfig,
|
|
11
13
|
getConfigDir,
|
|
12
14
|
setProfileOverride,
|
|
@@ -60,13 +62,27 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
// Helper to get authenticated client
|
|
63
|
-
function getClient(): GoogleDocs {
|
|
64
|
-
|
|
65
|
+
async function getClient(): Promise<GoogleDocs> {
|
|
66
|
+
let accessToken = getAccessToken();
|
|
65
67
|
const apiKey = getApiKey();
|
|
68
|
+
|
|
69
|
+
// If no access token, try to refresh using refresh token
|
|
66
70
|
if (!accessToken && !apiKey) {
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
const refreshToken = getRefreshToken();
|
|
72
|
+
if (refreshToken) {
|
|
73
|
+
try {
|
|
74
|
+
accessToken = await getValidAccessToken();
|
|
75
|
+
} catch {
|
|
76
|
+
// Fall through to error below
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!accessToken) {
|
|
81
|
+
error(`No credentials configured. Run "${CONNECTOR_NAME} config set-token <token>" or set GOOGLE_ACCESS_TOKEN environment variable.`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
69
84
|
}
|
|
85
|
+
|
|
70
86
|
return new GoogleDocs({ accessToken, apiKey });
|
|
71
87
|
}
|
|
72
88
|
|
|
@@ -215,7 +231,7 @@ program
|
|
|
215
231
|
.description('Get a document by ID')
|
|
216
232
|
.action(async (documentId: string) => {
|
|
217
233
|
try {
|
|
218
|
-
const client = getClient();
|
|
234
|
+
const client = await getClient();
|
|
219
235
|
const result = await client.documents.get(documentId);
|
|
220
236
|
print(result, getFormat(program));
|
|
221
237
|
} catch (err) {
|
|
@@ -229,7 +245,7 @@ program
|
|
|
229
245
|
.description('Create a new document (requires OAuth token)')
|
|
230
246
|
.action(async (title: string) => {
|
|
231
247
|
try {
|
|
232
|
-
const client = getClient();
|
|
248
|
+
const client = await getClient();
|
|
233
249
|
if (!client.hasWriteAccess()) {
|
|
234
250
|
error('Creating documents requires an OAuth access token. API key provides read-only access.');
|
|
235
251
|
process.exit(1);
|
|
@@ -248,7 +264,7 @@ program
|
|
|
248
264
|
.description('Append text to the end of a document (requires OAuth token)')
|
|
249
265
|
.action(async (documentId: string, text: string) => {
|
|
250
266
|
try {
|
|
251
|
-
const client = getClient();
|
|
267
|
+
const client = await getClient();
|
|
252
268
|
if (!client.hasWriteAccess()) {
|
|
253
269
|
error('Modifying documents requires an OAuth access token. API key provides read-only access.');
|
|
254
270
|
process.exit(1);
|
|
@@ -268,7 +284,7 @@ program
|
|
|
268
284
|
.option('--match-case', 'Match case when finding text')
|
|
269
285
|
.action(async (documentId: string, find: string, replace: string, opts) => {
|
|
270
286
|
try {
|
|
271
|
-
const client = getClient();
|
|
287
|
+
const client = await getClient();
|
|
272
288
|
if (!client.hasWriteAccess()) {
|
|
273
289
|
error('Modifying documents requires an OAuth access token. API key provides read-only access.');
|
|
274
290
|
process.exit(1);
|
|
@@ -292,7 +308,7 @@ program
|
|
|
292
308
|
.description('Insert text at a specific position (requires OAuth token)')
|
|
293
309
|
.action(async (documentId: string, text: string, index: string) => {
|
|
294
310
|
try {
|
|
295
|
-
const client = getClient();
|
|
311
|
+
const client = await getClient();
|
|
296
312
|
if (!client.hasWriteAccess()) {
|
|
297
313
|
error('Modifying documents requires an OAuth access token. API key provides read-only access.');
|
|
298
314
|
process.exit(1);
|
|
@@ -316,7 +332,7 @@ program
|
|
|
316
332
|
.description('Delete content within a range (requires OAuth token)')
|
|
317
333
|
.action(async (documentId: string, startIndex: string, endIndex: string) => {
|
|
318
334
|
try {
|
|
319
|
-
const client = getClient();
|
|
335
|
+
const client = await getClient();
|
|
320
336
|
if (!client.hasWriteAccess()) {
|
|
321
337
|
error('Modifying documents requires an OAuth access token. API key provides read-only access.');
|
|
322
338
|
process.exit(1);
|
|
@@ -341,7 +357,7 @@ program
|
|
|
341
357
|
.description('Insert an image at a specific position (requires OAuth token)')
|
|
342
358
|
.action(async (documentId: string, uri: string, index: string) => {
|
|
343
359
|
try {
|
|
344
|
-
const client = getClient();
|
|
360
|
+
const client = await getClient();
|
|
345
361
|
if (!client.hasWriteAccess()) {
|
|
346
362
|
error('Modifying documents requires an OAuth access token. API key provides read-only access.');
|
|
347
363
|
process.exit(1);
|
|
@@ -56,7 +56,11 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
function requireAuth(): Drive {
|
|
59
|
-
|
|
59
|
+
// isAuthenticated() checks for both accessToken and refreshToken.
|
|
60
|
+
// If accessToken is missing/expired but refreshToken exists, the client's
|
|
61
|
+
// getValidAccessToken() will handle the refresh automatically.
|
|
62
|
+
const tokens = loadTokens();
|
|
63
|
+
if (!tokens || (!tokens.accessToken && !tokens.refreshToken)) {
|
|
60
64
|
error('Not authenticated. Run "connect-googledrive auth login" first.');
|
|
61
65
|
process.exit(1);
|
|
62
66
|
}
|
|
@@ -67,16 +67,29 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// Helper to get authenticated client
|
|
70
|
-
function getClient(): GoogleSheets {
|
|
70
|
+
async function getClient(): Promise<GoogleSheets> {
|
|
71
71
|
const apiKey = getApiKey();
|
|
72
|
-
|
|
72
|
+
let accessToken = getAccessToken();
|
|
73
73
|
const refreshToken = getRefreshToken();
|
|
74
74
|
const clientId = getClientId();
|
|
75
75
|
const clientSecret = getClientSecret();
|
|
76
76
|
|
|
77
|
+
// If no API key and no access token, try to refresh using refresh token
|
|
77
78
|
if (!apiKey && !accessToken) {
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
if (refreshToken && clientId && clientSecret) {
|
|
80
|
+
try {
|
|
81
|
+
info('No access token, attempting refresh...');
|
|
82
|
+
const tempClient = new GoogleSheets({ refreshToken, clientId, clientSecret });
|
|
83
|
+
accessToken = await tempClient.refreshAccessToken();
|
|
84
|
+
success('Token refreshed successfully');
|
|
85
|
+
} catch (err) {
|
|
86
|
+
error(`Failed to refresh token: ${err}. Run "${CONNECTOR_NAME} config set-token <token>" to re-authenticate.`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
error(`No credentials configured. Run "${CONNECTOR_NAME} config set-key <key>" for API key auth or "${CONNECTOR_NAME} config set-token <token>" for OAuth.`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
return new GoogleSheets({
|
|
@@ -265,7 +278,7 @@ program
|
|
|
265
278
|
.option('--formulas', 'Return formulas instead of calculated values')
|
|
266
279
|
.action(async (spreadsheetId: string, range: string, opts) => {
|
|
267
280
|
try {
|
|
268
|
-
const client = getClient();
|
|
281
|
+
const client = await getClient();
|
|
269
282
|
const valueRenderOption = opts.formulas ? 'FORMULA' : opts.raw ? 'UNFORMATTED_VALUE' : 'FORMATTED_VALUE';
|
|
270
283
|
|
|
271
284
|
const result = await client.values.get(spreadsheetId, range, {
|
|
@@ -285,7 +298,7 @@ program
|
|
|
285
298
|
.option('--raw', 'Use RAW input (no formula parsing)')
|
|
286
299
|
.action(async (spreadsheetId: string, range: string, valuesStr: string, opts) => {
|
|
287
300
|
try {
|
|
288
|
-
const client = getClient();
|
|
301
|
+
const client = await getClient();
|
|
289
302
|
|
|
290
303
|
if (client.isApiKeyAuth()) {
|
|
291
304
|
error('Writing requires OAuth authentication. Use "config set-token" to configure OAuth.');
|
|
@@ -314,7 +327,7 @@ program
|
|
|
314
327
|
.option('--overwrite', 'Overwrite existing data instead of inserting rows')
|
|
315
328
|
.action(async (spreadsheetId: string, range: string, valuesStr: string, opts) => {
|
|
316
329
|
try {
|
|
317
|
-
const client = getClient();
|
|
330
|
+
const client = await getClient();
|
|
318
331
|
|
|
319
332
|
if (client.isApiKeyAuth()) {
|
|
320
333
|
error('Writing requires OAuth authentication. Use "config set-token" to configure OAuth.');
|
|
@@ -345,7 +358,7 @@ program
|
|
|
345
358
|
.option('--timezone <tz>', 'Spreadsheet timezone (e.g., "America/New_York")')
|
|
346
359
|
.action(async (title: string, opts) => {
|
|
347
360
|
try {
|
|
348
|
-
const client = getClient();
|
|
361
|
+
const client = await getClient();
|
|
349
362
|
|
|
350
363
|
if (client.isApiKeyAuth()) {
|
|
351
364
|
error('Creating spreadsheets requires OAuth authentication. Use "config set-token" to configure OAuth.');
|
|
@@ -372,7 +385,7 @@ program
|
|
|
372
385
|
.description('List all sheets in a spreadsheet')
|
|
373
386
|
.action(async (spreadsheetId: string) => {
|
|
374
387
|
try {
|
|
375
|
-
const client = getClient();
|
|
388
|
+
const client = await getClient();
|
|
376
389
|
const spreadsheet = await client.spreadsheets.get(spreadsheetId);
|
|
377
390
|
|
|
378
391
|
console.log(chalk.bold(`Spreadsheet: ${spreadsheet.properties.title}`));
|
|
@@ -404,7 +417,7 @@ program
|
|
|
404
417
|
.description('Get spreadsheet metadata')
|
|
405
418
|
.action(async (spreadsheetId: string) => {
|
|
406
419
|
try {
|
|
407
|
-
const client = getClient();
|
|
420
|
+
const client = await getClient();
|
|
408
421
|
const spreadsheet = await client.spreadsheets.get(spreadsheetId);
|
|
409
422
|
|
|
410
423
|
print(spreadsheet, getFormat(program));
|
|
@@ -419,7 +432,7 @@ program
|
|
|
419
432
|
.description('Clear values from a range')
|
|
420
433
|
.action(async (spreadsheetId: string, range: string) => {
|
|
421
434
|
try {
|
|
422
|
-
const client = getClient();
|
|
435
|
+
const client = await getClient();
|
|
423
436
|
|
|
424
437
|
if (client.isApiKeyAuth()) {
|
|
425
438
|
error('Clearing values requires OAuth authentication. Use "config set-token" to configure OAuth.');
|
|
@@ -448,7 +461,7 @@ sheetCmd
|
|
|
448
461
|
.option('--index <index>', 'Position to insert the sheet')
|
|
449
462
|
.action(async (spreadsheetId: string, title: string, opts) => {
|
|
450
463
|
try {
|
|
451
|
-
const client = getClient();
|
|
464
|
+
const client = await getClient();
|
|
452
465
|
|
|
453
466
|
if (client.isApiKeyAuth()) {
|
|
454
467
|
error('Adding sheets requires OAuth authentication.');
|
|
@@ -477,7 +490,7 @@ sheetCmd
|
|
|
477
490
|
.description('Delete a sheet from a spreadsheet')
|
|
478
491
|
.action(async (spreadsheetId: string, sheetIdStr: string) => {
|
|
479
492
|
try {
|
|
480
|
-
const client = getClient();
|
|
493
|
+
const client = await getClient();
|
|
481
494
|
|
|
482
495
|
if (client.isApiKeyAuth()) {
|
|
483
496
|
error('Deleting sheets requires OAuth authentication.');
|
|
@@ -499,7 +512,7 @@ sheetCmd
|
|
|
499
512
|
.description('Rename a sheet')
|
|
500
513
|
.action(async (spreadsheetId: string, sheetIdStr: string, newTitle: string) => {
|
|
501
514
|
try {
|
|
502
|
-
const client = getClient();
|
|
515
|
+
const client = await getClient();
|
|
503
516
|
|
|
504
517
|
if (client.isApiKeyAuth()) {
|
|
505
518
|
error('Renaming sheets requires OAuth authentication.');
|
|
@@ -521,7 +534,7 @@ sheetCmd
|
|
|
521
534
|
.description('Copy a sheet to another spreadsheet')
|
|
522
535
|
.action(async (spreadsheetId: string, sheetIdStr: string, destSpreadsheetId: string) => {
|
|
523
536
|
try {
|
|
524
|
-
const client = getClient();
|
|
537
|
+
const client = await getClient();
|
|
525
538
|
|
|
526
539
|
if (client.isApiKeyAuth()) {
|
|
527
540
|
error('Copying sheets requires OAuth authentication.');
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
setTokens,
|
|
17
17
|
getAccessToken,
|
|
18
18
|
getRefreshToken,
|
|
19
|
+
isTokenExpired,
|
|
19
20
|
clearTokens,
|
|
20
21
|
loadProfile,
|
|
21
22
|
} from '../utils/config';
|
|
@@ -40,11 +41,30 @@ function getFormat(cmd: Command): OutputFormat {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// Helper to get client
|
|
43
|
-
function getClient(): GoogleTasksClient {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
async function getClient(): Promise<GoogleTasksClient> {
|
|
45
|
+
let token = getAccessToken();
|
|
46
|
+
|
|
47
|
+
// If no access token or token is expired, try to refresh using refresh token
|
|
48
|
+
if (!token || isTokenExpired()) {
|
|
49
|
+
const refreshToken = getRefreshToken();
|
|
50
|
+
const clientId = getClientId();
|
|
51
|
+
const clientSecret = getClientSecret();
|
|
52
|
+
|
|
53
|
+
if (refreshToken && clientId && clientSecret) {
|
|
54
|
+
try {
|
|
55
|
+
const tokens = await GoogleTasksClient.refreshAccessToken(refreshToken, clientId, clientSecret);
|
|
56
|
+
setTokens(tokens.access_token, tokens.refresh_token, tokens.expires_in);
|
|
57
|
+
token = tokens.access_token;
|
|
58
|
+
} catch {
|
|
59
|
+
// Fall through to error below if refresh fails
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!token) {
|
|
64
|
+
throw new Error('Not authenticated. Run "connect-googletasks auth login" first.');
|
|
65
|
+
}
|
|
47
66
|
}
|
|
67
|
+
|
|
48
68
|
return new GoogleTasksClient(token);
|
|
49
69
|
}
|
|
50
70
|
|
|
@@ -307,7 +327,7 @@ listsCmd
|
|
|
307
327
|
.description('List all task lists')
|
|
308
328
|
.action(async function(this: Command) {
|
|
309
329
|
try {
|
|
310
|
-
const client = getClient();
|
|
330
|
+
const client = await getClient();
|
|
311
331
|
const response = await client.listTaskLists();
|
|
312
332
|
printTaskLists(response.items || [], getFormat(this));
|
|
313
333
|
} catch (err) {
|
|
@@ -321,7 +341,7 @@ listsCmd
|
|
|
321
341
|
.description('Get a task list by ID')
|
|
322
342
|
.action(async function(this: Command, listId: string) {
|
|
323
343
|
try {
|
|
324
|
-
const client = getClient();
|
|
344
|
+
const client = await getClient();
|
|
325
345
|
const list = await client.getTaskList(listId);
|
|
326
346
|
printTaskList(list, getFormat(this));
|
|
327
347
|
} catch (err) {
|
|
@@ -335,7 +355,7 @@ listsCmd
|
|
|
335
355
|
.description('Create a new task list')
|
|
336
356
|
.action(async function(this: Command, title: string) {
|
|
337
357
|
try {
|
|
338
|
-
const client = getClient();
|
|
358
|
+
const client = await getClient();
|
|
339
359
|
const list = await client.createTaskList({ title });
|
|
340
360
|
success(`Task list "${list.title}" created`);
|
|
341
361
|
printTaskList(list, getFormat(this));
|
|
@@ -350,7 +370,7 @@ listsCmd
|
|
|
350
370
|
.description('Rename a task list')
|
|
351
371
|
.action(async function(this: Command, listId: string, title: string) {
|
|
352
372
|
try {
|
|
353
|
-
const client = getClient();
|
|
373
|
+
const client = await getClient();
|
|
354
374
|
const list = await client.updateTaskList(listId, { title });
|
|
355
375
|
success(`Task list renamed to "${list.title}"`);
|
|
356
376
|
} catch (err) {
|
|
@@ -364,7 +384,7 @@ listsCmd
|
|
|
364
384
|
.description('Delete a task list')
|
|
365
385
|
.action(async function(this: Command, listId: string) {
|
|
366
386
|
try {
|
|
367
|
-
const client = getClient();
|
|
387
|
+
const client = await getClient();
|
|
368
388
|
await client.deleteTaskList(listId);
|
|
369
389
|
success('Task list deleted');
|
|
370
390
|
} catch (err) {
|
|
@@ -389,7 +409,7 @@ tasksCmd
|
|
|
389
409
|
.option('--max <number>', 'Maximum number of tasks', '100')
|
|
390
410
|
.action(async function(this: Command, listId: string, opts) {
|
|
391
411
|
try {
|
|
392
|
-
const client = getClient();
|
|
412
|
+
const client = await getClient();
|
|
393
413
|
const response = await client.listTasks(listId, {
|
|
394
414
|
maxResults: parseInt(opts.max),
|
|
395
415
|
showCompleted: opts.all || opts.completed,
|
|
@@ -408,7 +428,7 @@ tasksCmd
|
|
|
408
428
|
.description('Get a task by ID')
|
|
409
429
|
.action(async function(this: Command, listId: string, taskId: string) {
|
|
410
430
|
try {
|
|
411
|
-
const client = getClient();
|
|
431
|
+
const client = await getClient();
|
|
412
432
|
const task = await client.getTask(listId, taskId);
|
|
413
433
|
printTask(task, getFormat(this));
|
|
414
434
|
} catch (err) {
|
|
@@ -424,7 +444,7 @@ tasksCmd
|
|
|
424
444
|
.option('-d, --due <date>', 'Due date (YYYY-MM-DD)')
|
|
425
445
|
.action(async function(this: Command, listId: string, title: string, opts) {
|
|
426
446
|
try {
|
|
427
|
-
const client = getClient();
|
|
447
|
+
const client = await getClient();
|
|
428
448
|
const task = await client.createTask(listId, {
|
|
429
449
|
title,
|
|
430
450
|
notes: opts.notes,
|
|
@@ -446,7 +466,7 @@ tasksCmd
|
|
|
446
466
|
.option('-d, --due <date>', 'New due date (YYYY-MM-DD)')
|
|
447
467
|
.action(async function(this: Command, listId: string, taskId: string, opts) {
|
|
448
468
|
try {
|
|
449
|
-
const client = getClient();
|
|
469
|
+
const client = await getClient();
|
|
450
470
|
const task = await client.updateTask(listId, taskId, {
|
|
451
471
|
title: opts.title,
|
|
452
472
|
notes: opts.notes,
|
|
@@ -466,7 +486,7 @@ tasksCmd
|
|
|
466
486
|
.description('Mark a task as completed')
|
|
467
487
|
.action(async function(this: Command, listId: string, taskId: string) {
|
|
468
488
|
try {
|
|
469
|
-
const client = getClient();
|
|
489
|
+
const client = await getClient();
|
|
470
490
|
const task = await client.completeTask(listId, taskId);
|
|
471
491
|
success(`Task "${task.title}" marked as completed`);
|
|
472
492
|
} catch (err) {
|
|
@@ -481,7 +501,7 @@ tasksCmd
|
|
|
481
501
|
.description('Mark a task as not completed')
|
|
482
502
|
.action(async function(this: Command, listId: string, taskId: string) {
|
|
483
503
|
try {
|
|
484
|
-
const client = getClient();
|
|
504
|
+
const client = await getClient();
|
|
485
505
|
const task = await client.uncompleteTask(listId, taskId);
|
|
486
506
|
success(`Task "${task.title}" marked as needs action`);
|
|
487
507
|
} catch (err) {
|
|
@@ -496,7 +516,7 @@ tasksCmd
|
|
|
496
516
|
.description('Delete a task')
|
|
497
517
|
.action(async function(this: Command, listId: string, taskId: string) {
|
|
498
518
|
try {
|
|
499
|
-
const client = getClient();
|
|
519
|
+
const client = await getClient();
|
|
500
520
|
await client.deleteTask(listId, taskId);
|
|
501
521
|
success('Task deleted');
|
|
502
522
|
} catch (err) {
|
|
@@ -512,7 +532,7 @@ tasksCmd
|
|
|
512
532
|
.option('--after <taskId>', 'Position after this task')
|
|
513
533
|
.action(async function(this: Command, listId: string, taskId: string, opts) {
|
|
514
534
|
try {
|
|
515
|
-
const client = getClient();
|
|
535
|
+
const client = await getClient();
|
|
516
536
|
const task = await client.moveTask(listId, taskId, {
|
|
517
537
|
parent: opts.parent,
|
|
518
538
|
previous: opts.after,
|
|
@@ -529,7 +549,7 @@ tasksCmd
|
|
|
529
549
|
.description('Clear all completed tasks from a list')
|
|
530
550
|
.action(async function(this: Command, listId: string) {
|
|
531
551
|
try {
|
|
532
|
-
const client = getClient();
|
|
552
|
+
const client = await getClient();
|
|
533
553
|
await client.clearCompleted(listId);
|
|
534
554
|
success('Completed tasks cleared');
|
|
535
555
|
} catch (err) {
|
|
@@ -345,7 +345,8 @@ export function getAccessToken(): string | undefined {
|
|
|
345
345
|
// ============================================
|
|
346
346
|
|
|
347
347
|
export function getApiKey(): string | undefined {
|
|
348
|
-
|
|
348
|
+
const config = loadConfig();
|
|
349
|
+
return process.env.NOTION_API_KEY || config.accessToken || (config as Record<string, unknown>).apiKey as string | undefined;
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
export function setApiKey(apiKey: string): void {
|