@hasna/connectors 1.3.22 → 1.3.23
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 +109 -59
- package/bin/mcp.js +236 -182
- package/bin/serve.js +77 -33
- package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
- package/connectors/connect-dropbox/src/api/index.ts +4 -0
- package/connectors/connect-dropbox/src/cli/index.ts +151 -0
- package/connectors/connect-github/src/api/bulk.ts +328 -0
- package/connectors/connect-github/src/api/index.ts +4 -0
- package/connectors/connect-github/src/cli/index.ts +145 -0
- package/connectors/connect-gmail/src/api/history.ts +82 -0
- package/connectors/connect-gmail/src/api/index.ts +12 -0
- package/connectors/connect-gmail/src/api/settings.ts +280 -0
- package/connectors/connect-gmail/src/api/watch.ts +40 -0
- package/connectors/connect-gmail/src/cli/index.ts +237 -0
- package/connectors/connect-googleads/src/api/bulk.ts +354 -0
- package/connectors/connect-googleads/src/api/index.ts +4 -0
- package/connectors/connect-googleads/src/cli/index.ts +192 -0
- package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
- package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
- package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
- package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
- package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
- package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
- package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
- package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
- package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
- package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
- package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
- package/connectors/connect-googledocs/src/api/index.ts +4 -0
- package/connectors/connect-googledocs/src/cli/index.ts +187 -0
- package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
- package/connectors/connect-googledrive/src/api/changes.ts +139 -0
- package/connectors/connect-googledrive/src/api/client.ts +34 -0
- package/connectors/connect-googledrive/src/api/index.ts +12 -0
- package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
- package/connectors/connect-googledrive/src/cli/index.ts +624 -0
- package/connectors/connect-googledrive/src/index.ts +2 -0
- package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
- package/connectors/connect-googlephotos/src/api/index.ts +8 -0
- package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
- package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
- package/connectors/connect-googletasks/src/api/index.ts +1 -0
- package/connectors/connect-googletasks/src/cli/index.ts +178 -0
- package/connectors/connect-linear/src/api/bulk.ts +219 -0
- package/connectors/connect-linear/src/api/index.ts +4 -0
- package/connectors/connect-linear/src/cli/index.ts +116 -0
- package/connectors/connect-shopify/src/api/bulk.ts +288 -0
- package/connectors/connect-shopify/src/api/index.ts +4 -0
- package/connectors/connect-shopify/src/cli/index.ts +108 -0
- package/connectors/connect-slack/src/api/bulk.ts +237 -0
- package/connectors/connect-slack/src/api/index.ts +4 -0
- package/connectors/connect-slack/src/cli/index.ts +157 -0
- package/connectors/connect-stripe/src/api/bulk.ts +488 -0
- package/connectors/connect-stripe/src/api/index.ts +4 -0
- package/connectors/connect-stripe/src/cli/index.ts +206 -0
- package/dist/index.js +75 -33
- package/dist/lib/runner.d.ts +15 -5
- package/package.json +2 -2
- package/connectors/connect-cloudflare/bun.lock +0 -32
- package/connectors/connect-gmail/bin/index.js +0 -7027
- package/connectors/connect-gmail/dist/cli/index.js +0 -7035
- package/connectors/connect-gmail/dist/index.js +0 -2174
- package/dist/server/server.test.d.ts +0 -1
|
@@ -816,5 +816,527 @@ eventCmd
|
|
|
816
816
|
}
|
|
817
817
|
});
|
|
818
818
|
|
|
819
|
+
// ============================================
|
|
820
|
+
// Bulk Commands
|
|
821
|
+
// ============================================
|
|
822
|
+
const bulkCmd = program
|
|
823
|
+
.command('bulk')
|
|
824
|
+
.description('Bulk operations on events');
|
|
825
|
+
|
|
826
|
+
bulkCmd
|
|
827
|
+
.command('preview')
|
|
828
|
+
.description('Preview events matching a query')
|
|
829
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
830
|
+
.option('-q, --query <text>', 'Search query')
|
|
831
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
832
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
833
|
+
.option('-n, --max <number>', 'Maximum events to preview', '20')
|
|
834
|
+
.action(async (opts) => {
|
|
835
|
+
try {
|
|
836
|
+
const client = await getClient();
|
|
837
|
+
const label = opts.query ? `matching: ${opts.query}` : `on calendar: ${opts.calendar}`;
|
|
838
|
+
info(`Previewing events ${label}`);
|
|
839
|
+
|
|
840
|
+
const result = await client.bulk.preview(opts.calendar, {
|
|
841
|
+
query: opts.query,
|
|
842
|
+
timeMin: opts.timeMin,
|
|
843
|
+
timeMax: opts.timeMax,
|
|
844
|
+
maxResults: parseInt(opts.max),
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
if (result.events.length === 0) {
|
|
848
|
+
info('No events found');
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
success(`Found ${result.total} event(s):`);
|
|
853
|
+
const output = result.events.map(e => ({
|
|
854
|
+
id: e.id,
|
|
855
|
+
summary: e.summary,
|
|
856
|
+
start: e.start,
|
|
857
|
+
end: e.end,
|
|
858
|
+
status: e.status,
|
|
859
|
+
location: e.location || '-',
|
|
860
|
+
}));
|
|
861
|
+
print(output, getFormat(bulkCmd));
|
|
862
|
+
} catch (err) {
|
|
863
|
+
error(String(err));
|
|
864
|
+
process.exit(1);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
bulkCmd
|
|
869
|
+
.command('delete')
|
|
870
|
+
.description('Delete events matching a query (DANGER!)')
|
|
871
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
872
|
+
.option('-q, --query <text>', 'Search query')
|
|
873
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
874
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
875
|
+
.option('-n, --max <number>', 'Maximum events to process', '100')
|
|
876
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
877
|
+
.option('--dry-run', 'Preview changes without applying them')
|
|
878
|
+
.option('--confirm', 'Confirm permanent deletion')
|
|
879
|
+
.action(async (opts) => {
|
|
880
|
+
try {
|
|
881
|
+
if (!opts.dryRun && !opts.confirm) {
|
|
882
|
+
error('Permanent deletion requires --confirm flag');
|
|
883
|
+
info('Use --dry-run to preview what would be deleted');
|
|
884
|
+
process.exit(1);
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const client = await getClient();
|
|
888
|
+
warn(`${opts.dryRun ? '[DRY RUN] ' : ''}PERMANENTLY DELETING events on ${opts.calendar}`);
|
|
889
|
+
if (opts.query) info(` Query: ${opts.query}`);
|
|
890
|
+
if (opts.timeMin) info(` From: ${opts.timeMin}`);
|
|
891
|
+
if (opts.timeMax) info(` To: ${opts.timeMax}`);
|
|
892
|
+
|
|
893
|
+
const result = await client.bulk.delete({
|
|
894
|
+
calendarId: opts.calendar,
|
|
895
|
+
query: opts.query,
|
|
896
|
+
timeMin: opts.timeMin,
|
|
897
|
+
timeMax: opts.timeMax,
|
|
898
|
+
maxResults: parseInt(opts.max),
|
|
899
|
+
concurrency: parseInt(opts.concurrency),
|
|
900
|
+
dryRun: opts.dryRun,
|
|
901
|
+
onProgress: (current, total) => {
|
|
902
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
console.log();
|
|
906
|
+
|
|
907
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk delete complete:`);
|
|
908
|
+
info(` Total: ${result.total}`);
|
|
909
|
+
info(` Success: ${result.success}`);
|
|
910
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
911
|
+
} catch (err) {
|
|
912
|
+
error(String(err));
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
bulkCmd
|
|
918
|
+
.command('create')
|
|
919
|
+
.description('Create multiple events from a JSON file')
|
|
920
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
921
|
+
.option('-f, --file <path>', 'Path to JSON file with events array')
|
|
922
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
923
|
+
.option('--dry-run', 'Preview without creating')
|
|
924
|
+
.option('--notify', 'Send notifications to attendees')
|
|
925
|
+
.action(async (opts) => {
|
|
926
|
+
try {
|
|
927
|
+
const { readFileSync } = await import('fs');
|
|
928
|
+
|
|
929
|
+
if (!opts.file) {
|
|
930
|
+
error('--file is required');
|
|
931
|
+
process.exit(1);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const events = JSON.parse(readFileSync(opts.file, 'utf-8'));
|
|
935
|
+
if (!Array.isArray(events)) {
|
|
936
|
+
error('JSON file must contain an array of events');
|
|
937
|
+
process.exit(1);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const client = await getClient();
|
|
941
|
+
info(`${opts.dryRun ? '[DRY RUN] ' : ''}Creating ${events.length} event(s) on ${opts.calendar}`);
|
|
942
|
+
|
|
943
|
+
const result = await client.bulk.create({
|
|
944
|
+
calendarId: opts.calendar,
|
|
945
|
+
events,
|
|
946
|
+
concurrency: parseInt(opts.concurrency),
|
|
947
|
+
dryRun: opts.dryRun,
|
|
948
|
+
sendNotifications: opts.notify === true,
|
|
949
|
+
onProgress: (current, total) => {
|
|
950
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
951
|
+
},
|
|
952
|
+
});
|
|
953
|
+
console.log();
|
|
954
|
+
|
|
955
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk create complete:`);
|
|
956
|
+
info(` Total: ${result.total}`);
|
|
957
|
+
info(` Success: ${result.success}`);
|
|
958
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
959
|
+
} catch (err) {
|
|
960
|
+
error(String(err));
|
|
961
|
+
process.exit(1);
|
|
962
|
+
}
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
bulkCmd
|
|
966
|
+
.command('update')
|
|
967
|
+
.description('Update events matching a query with partial data')
|
|
968
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
969
|
+
.option('-q, --query <text>', 'Search query')
|
|
970
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
971
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
972
|
+
.option('-n, --max <number>', 'Maximum events to process', '100')
|
|
973
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
974
|
+
.option('--dry-run', 'Preview changes without applying them')
|
|
975
|
+
.option('--updates <json>', 'JSON object with fields to update (e.g. \'{"location":"Remote"}\')')
|
|
976
|
+
.action(async (opts) => {
|
|
977
|
+
try {
|
|
978
|
+
if (!opts.updates) {
|
|
979
|
+
error('--updates is required (JSON object)');
|
|
980
|
+
process.exit(1);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
const updates = JSON.parse(opts.updates);
|
|
984
|
+
const client = await getClient();
|
|
985
|
+
info(`${opts.dryRun ? '[DRY RUN] ' : ''}Updating events on ${opts.calendar}`);
|
|
986
|
+
if (opts.query) info(` Query: ${opts.query}`);
|
|
987
|
+
info(` Updates: ${JSON.stringify(updates)}`);
|
|
988
|
+
|
|
989
|
+
const result = await client.bulk.update({
|
|
990
|
+
calendarId: opts.calendar,
|
|
991
|
+
query: opts.query,
|
|
992
|
+
timeMin: opts.timeMin,
|
|
993
|
+
timeMax: opts.timeMax,
|
|
994
|
+
maxResults: parseInt(opts.max),
|
|
995
|
+
concurrency: parseInt(opts.concurrency),
|
|
996
|
+
updates,
|
|
997
|
+
dryRun: opts.dryRun,
|
|
998
|
+
onProgress: (current, total) => {
|
|
999
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
1000
|
+
},
|
|
1001
|
+
});
|
|
1002
|
+
console.log();
|
|
1003
|
+
|
|
1004
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk update complete:`);
|
|
1005
|
+
info(` Total: ${result.total}`);
|
|
1006
|
+
info(` Success: ${result.success}`);
|
|
1007
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
error(String(err));
|
|
1010
|
+
process.exit(1);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
bulkCmd
|
|
1015
|
+
.command('accept')
|
|
1016
|
+
.description('Accept event invitations matching a query')
|
|
1017
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
1018
|
+
.option('-q, --query <text>', 'Search query')
|
|
1019
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
1020
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
1021
|
+
.option('-n, --max <number>', 'Maximum events to process', '100')
|
|
1022
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
1023
|
+
.option('--dry-run', 'Preview changes without applying them')
|
|
1024
|
+
.option('--comment <text>', 'Optional comment')
|
|
1025
|
+
.action(async (opts) => {
|
|
1026
|
+
try {
|
|
1027
|
+
const client = await getClient();
|
|
1028
|
+
info(`${opts.dryRun ? '[DRY RUN] ' : ''}Accepting events on ${opts.calendar}`);
|
|
1029
|
+
if (opts.query) info(` Query: ${opts.query}`);
|
|
1030
|
+
|
|
1031
|
+
const result = await client.bulk.accept({
|
|
1032
|
+
calendarId: opts.calendar,
|
|
1033
|
+
query: opts.query,
|
|
1034
|
+
timeMin: opts.timeMin,
|
|
1035
|
+
timeMax: opts.timeMax,
|
|
1036
|
+
maxResults: parseInt(opts.max),
|
|
1037
|
+
concurrency: parseInt(opts.concurrency),
|
|
1038
|
+
dryRun: opts.dryRun,
|
|
1039
|
+
comment: opts.comment,
|
|
1040
|
+
onProgress: (current, total) => {
|
|
1041
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1044
|
+
console.log();
|
|
1045
|
+
|
|
1046
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk accept complete:`);
|
|
1047
|
+
info(` Total: ${result.total}`);
|
|
1048
|
+
info(` Success: ${result.success}`);
|
|
1049
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
1050
|
+
} catch (err) {
|
|
1051
|
+
error(String(err));
|
|
1052
|
+
process.exit(1);
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
bulkCmd
|
|
1057
|
+
.command('decline')
|
|
1058
|
+
.description('Decline event invitations matching a query')
|
|
1059
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
1060
|
+
.option('-q, --query <text>', 'Search query')
|
|
1061
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
1062
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
1063
|
+
.option('-n, --max <number>', 'Maximum events to process', '100')
|
|
1064
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
1065
|
+
.option('--dry-run', 'Preview changes without applying them')
|
|
1066
|
+
.option('--comment <text>', 'Optional comment')
|
|
1067
|
+
.action(async (opts) => {
|
|
1068
|
+
try {
|
|
1069
|
+
const client = await getClient();
|
|
1070
|
+
info(`${opts.dryRun ? '[DRY RUN] ' : ''}Declining events on ${opts.calendar}`);
|
|
1071
|
+
if (opts.query) info(` Query: ${opts.query}`);
|
|
1072
|
+
|
|
1073
|
+
const result = await client.bulk.decline({
|
|
1074
|
+
calendarId: opts.calendar,
|
|
1075
|
+
query: opts.query,
|
|
1076
|
+
timeMin: opts.timeMin,
|
|
1077
|
+
timeMax: opts.timeMax,
|
|
1078
|
+
maxResults: parseInt(opts.max),
|
|
1079
|
+
concurrency: parseInt(opts.concurrency),
|
|
1080
|
+
dryRun: opts.dryRun,
|
|
1081
|
+
comment: opts.comment,
|
|
1082
|
+
onProgress: (current, total) => {
|
|
1083
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
1084
|
+
},
|
|
1085
|
+
});
|
|
1086
|
+
console.log();
|
|
1087
|
+
|
|
1088
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk decline complete:`);
|
|
1089
|
+
info(` Total: ${result.total}`);
|
|
1090
|
+
info(` Success: ${result.success}`);
|
|
1091
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
error(String(err));
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
bulkCmd
|
|
1099
|
+
.command('tentative')
|
|
1100
|
+
.description('Mark events as tentative matching a query')
|
|
1101
|
+
.option('-c, --calendar <id>', 'Calendar ID', 'primary')
|
|
1102
|
+
.option('-q, --query <text>', 'Search query')
|
|
1103
|
+
.option('--time-min <date>', 'Start date (RFC3339)')
|
|
1104
|
+
.option('--time-max <date>', 'End date (RFC3339)')
|
|
1105
|
+
.option('-n, --max <number>', 'Maximum events to process', '100')
|
|
1106
|
+
.option('-k, --concurrency <number>', 'Maximum concurrent API calls', '10')
|
|
1107
|
+
.option('--dry-run', 'Preview changes without applying them')
|
|
1108
|
+
.option('--comment <text>', 'Optional comment')
|
|
1109
|
+
.action(async (opts) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const client = await getClient();
|
|
1112
|
+
info(`${opts.dryRun ? '[DRY RUN] ' : ''}Marking events as tentative on ${opts.calendar}`);
|
|
1113
|
+
if (opts.query) info(` Query: ${opts.query}`);
|
|
1114
|
+
|
|
1115
|
+
const result = await client.bulk.tentative({
|
|
1116
|
+
calendarId: opts.calendar,
|
|
1117
|
+
query: opts.query,
|
|
1118
|
+
timeMin: opts.timeMin,
|
|
1119
|
+
timeMax: opts.timeMax,
|
|
1120
|
+
maxResults: parseInt(opts.max),
|
|
1121
|
+
concurrency: parseInt(opts.concurrency),
|
|
1122
|
+
dryRun: opts.dryRun,
|
|
1123
|
+
comment: opts.comment,
|
|
1124
|
+
onProgress: (current, total) => {
|
|
1125
|
+
process.stdout.write(`\r Progress: ${current}/${total}`);
|
|
1126
|
+
},
|
|
1127
|
+
});
|
|
1128
|
+
console.log();
|
|
1129
|
+
|
|
1130
|
+
success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk tentative complete:`);
|
|
1131
|
+
info(` Total: ${result.total}`);
|
|
1132
|
+
info(` Success: ${result.success}`);
|
|
1133
|
+
if (result.failed > 0) warn(` Failed: ${result.failed}`);
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
error(String(err));
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// ============================================
|
|
1141
|
+
// Free/Busy Commands
|
|
1142
|
+
// ============================================
|
|
1143
|
+
const freebusyCmd = program
|
|
1144
|
+
.command('freebusy')
|
|
1145
|
+
.description('Query free/busy information');
|
|
1146
|
+
|
|
1147
|
+
freebusyCmd
|
|
1148
|
+
.command('query')
|
|
1149
|
+
.description('Query free/busy for one or more calendars')
|
|
1150
|
+
.requiredOption('-c, --calendar <ids>', 'Comma-separated calendar IDs')
|
|
1151
|
+
.requiredOption('--time-min <date>', 'Start time (RFC3339)')
|
|
1152
|
+
.requiredOption('--time-max <date>', 'End time (RFC3339)')
|
|
1153
|
+
.option('--timezone <tz>', 'Timezone')
|
|
1154
|
+
.action(async (opts) => {
|
|
1155
|
+
try {
|
|
1156
|
+
const client = await getClient();
|
|
1157
|
+
const calendarIds = opts.calendar.split(',').map((s: string) => s.trim());
|
|
1158
|
+
|
|
1159
|
+
const result = await client.freebusy.query({
|
|
1160
|
+
timeMin: opts.timeMin,
|
|
1161
|
+
timeMax: opts.timeMax,
|
|
1162
|
+
items: calendarIds.map((id: string) => ({ id })),
|
|
1163
|
+
timeZone: opts.timezone,
|
|
1164
|
+
});
|
|
1165
|
+
|
|
1166
|
+
const format = getFormat(freebusyCmd);
|
|
1167
|
+
if (format === 'json') {
|
|
1168
|
+
print(result, format);
|
|
1169
|
+
} else {
|
|
1170
|
+
for (const [calId, calInfo] of Object.entries(result.calendars)) {
|
|
1171
|
+
console.log(chalk.bold(` ${calId}`));
|
|
1172
|
+
if (calInfo.busy.length === 0) {
|
|
1173
|
+
info(' Free');
|
|
1174
|
+
} else {
|
|
1175
|
+
for (const slot of calInfo.busy) {
|
|
1176
|
+
const start = new Date(slot.start).toLocaleString();
|
|
1177
|
+
const end = new Date(slot.end).toLocaleString();
|
|
1178
|
+
console.log(` ${chalk.yellow('Busy:')} ${start} → ${end}`);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
if (calInfo.errors && calInfo.errors.length > 0) {
|
|
1182
|
+
for (const e of calInfo.errors) {
|
|
1183
|
+
warn(` Error: ${e.message}`);
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
} catch (err) {
|
|
1189
|
+
error(String(err));
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
freebusyCmd
|
|
1195
|
+
.command('check')
|
|
1196
|
+
.description('Quick check if a calendar is free during a time range')
|
|
1197
|
+
.requiredOption('-c, --calendar <id>', 'Calendar ID')
|
|
1198
|
+
.requiredOption('--time-min <date>', 'Start time (RFC3339)')
|
|
1199
|
+
.requiredOption('--time-max <date>', 'End time (RFC3339)')
|
|
1200
|
+
.action(async (opts) => {
|
|
1201
|
+
try {
|
|
1202
|
+
const client = await getClient();
|
|
1203
|
+
const free = await client.freebusy.isFree(opts.calendar, opts.timeMin, opts.timeMax);
|
|
1204
|
+
|
|
1205
|
+
const format = getFormat(freebusyCmd);
|
|
1206
|
+
if (format === 'json') {
|
|
1207
|
+
print({ calendar: opts.calendar, free, timeMin: opts.timeMin, timeMax: opts.timeMax }, format);
|
|
1208
|
+
} else {
|
|
1209
|
+
if (free) {
|
|
1210
|
+
success(`${opts.calendar} is free from ${new Date(opts.timeMin).toLocaleString()} to ${new Date(opts.timeMax).toLocaleString()}`);
|
|
1211
|
+
} else {
|
|
1212
|
+
warn(`${opts.calendar} is busy during ${new Date(opts.timeMin).toLocaleString()} to ${new Date(opts.timeMax).toLocaleString()}`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
error(String(err));
|
|
1217
|
+
process.exit(1);
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
|
|
1221
|
+
// ============================================
|
|
1222
|
+
// ACL Commands
|
|
1223
|
+
// ============================================
|
|
1224
|
+
const aclCmd = program
|
|
1225
|
+
.command('acl')
|
|
1226
|
+
.description('Manage calendar access control lists');
|
|
1227
|
+
|
|
1228
|
+
aclCmd
|
|
1229
|
+
.command('list')
|
|
1230
|
+
.description('List ACL rules for a calendar')
|
|
1231
|
+
.requiredOption('-c, --calendar <id>', 'Calendar ID')
|
|
1232
|
+
.action(async (opts) => {
|
|
1233
|
+
try {
|
|
1234
|
+
const client = await getClient();
|
|
1235
|
+
const result = await client.acl.list(opts.calendar);
|
|
1236
|
+
|
|
1237
|
+
const format = getFormat(aclCmd);
|
|
1238
|
+
if (format === 'json') {
|
|
1239
|
+
print(result, format);
|
|
1240
|
+
} else {
|
|
1241
|
+
if (result.items.length === 0) {
|
|
1242
|
+
info(`No ACL rules found for ${opts.calendar}`);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
success(`ACL rules for ${opts.calendar}:`);
|
|
1246
|
+
for (const rule of result.items) {
|
|
1247
|
+
const scopeStr = rule.scope.type === 'default'
|
|
1248
|
+
? 'default'
|
|
1249
|
+
: `${rule.scope.type}:${rule.scope.value}`;
|
|
1250
|
+
console.log(` ${rule.role.padEnd(18)} ${scopeStr}`);
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
} catch (err) {
|
|
1254
|
+
error(String(err));
|
|
1255
|
+
process.exit(1);
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
aclCmd
|
|
1260
|
+
.command('add')
|
|
1261
|
+
.description('Add an ACL rule to a calendar')
|
|
1262
|
+
.requiredOption('-c, --calendar <id>', 'Calendar ID')
|
|
1263
|
+
.requiredOption('-r, --role <role>', 'Role: freeBusyReader, reader, writer, owner')
|
|
1264
|
+
.requiredOption('--scope-type <type>', 'Scope type: user, group, domain, default')
|
|
1265
|
+
.option('--scope-value <value>', 'Scope value (email for user/group, domain for domain)')
|
|
1266
|
+
.action(async (opts) => {
|
|
1267
|
+
try {
|
|
1268
|
+
const role = opts.role as 'freeBusyReader' | 'reader' | 'writer' | 'owner';
|
|
1269
|
+
const validRoles = ['freeBusyReader', 'reader', 'writer', 'owner'];
|
|
1270
|
+
if (!validRoles.includes(role)) {
|
|
1271
|
+
error(`Invalid role: ${role}. Must be one of: ${validRoles.join(', ')}`);
|
|
1272
|
+
process.exit(1);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
const scopeType = opts.scopeType as 'user' | 'group' | 'domain' | 'default';
|
|
1276
|
+
const validTypes = ['user', 'group', 'domain', 'default'];
|
|
1277
|
+
if (!validTypes.includes(scopeType)) {
|
|
1278
|
+
error(`Invalid scope type: ${scopeType}. Must be one of: ${validTypes.join(', ')}`);
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
if (scopeType !== 'default' && !opts.scopeValue) {
|
|
1283
|
+
error('--scope-value is required for user/group/domain scope types');
|
|
1284
|
+
process.exit(1);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
const client = await getClient();
|
|
1288
|
+
const rule = await client.acl.insert(opts.calendar, {
|
|
1289
|
+
role,
|
|
1290
|
+
scope: {
|
|
1291
|
+
type: scopeType,
|
|
1292
|
+
value: opts.scopeValue,
|
|
1293
|
+
},
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
success(`ACL rule added:`);
|
|
1297
|
+
info(` Role: ${rule.role}`);
|
|
1298
|
+
info(` Scope: ${rule.scope.type}${rule.scope.value ? `:${rule.scope.value}` : ''}`);
|
|
1299
|
+
} catch (err) {
|
|
1300
|
+
error(String(err));
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
aclCmd
|
|
1306
|
+
.command('remove')
|
|
1307
|
+
.description('Remove an ACL rule from a calendar')
|
|
1308
|
+
.requiredOption('-c, --calendar <id>', 'Calendar ID')
|
|
1309
|
+
.requiredOption('-i, --rule-id <id>', 'ACL rule ID to remove')
|
|
1310
|
+
.action(async (opts) => {
|
|
1311
|
+
try {
|
|
1312
|
+
const client = await getClient();
|
|
1313
|
+
await client.acl.delete(opts.calendar, opts.ruleId);
|
|
1314
|
+
success(`ACL rule "${opts.ruleId}" removed from ${opts.calendar}`);
|
|
1315
|
+
} catch (err) {
|
|
1316
|
+
error(String(err));
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
aclCmd
|
|
1322
|
+
.command('share')
|
|
1323
|
+
.description('Share a calendar with a user')
|
|
1324
|
+
.requiredOption('-c, --calendar <id>', 'Calendar ID')
|
|
1325
|
+
.requiredOption('--with <email>', 'Email address to share with')
|
|
1326
|
+
.option('-r, --role <role>', 'Access level: reader (default), writer, freeBusyReader', 'reader')
|
|
1327
|
+
.action(async (opts) => {
|
|
1328
|
+
try {
|
|
1329
|
+
const client = await getClient();
|
|
1330
|
+
const rule = await client.acl.insert(opts.calendar, {
|
|
1331
|
+
role: opts.role,
|
|
1332
|
+
scope: { type: 'user', value: opts.with },
|
|
1333
|
+
});
|
|
1334
|
+
success(`Shared ${opts.calendar} with ${opts.with} as ${rule.role}`);
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
error(String(err));
|
|
1337
|
+
process.exit(1);
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
819
1341
|
// Parse and execute
|
|
820
1342
|
program.parse();
|
|
@@ -345,3 +345,93 @@ export class GoogleCalendarApiError extends Error {
|
|
|
345
345
|
this.errors = errors;
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
|
+
|
|
349
|
+
// ============================================
|
|
350
|
+
// Free/Busy Types
|
|
351
|
+
// ============================================
|
|
352
|
+
|
|
353
|
+
export interface FreeBusyTimeRange {
|
|
354
|
+
start: string; // RFC3339
|
|
355
|
+
end: string; // RFC3339
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export interface FreeBusyGroup {
|
|
359
|
+
id: string;
|
|
360
|
+
members?: { id: string }[];
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export interface FreeBusyCalendarRequest {
|
|
364
|
+
id: string;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export interface FreeBusyGroupRequest {
|
|
368
|
+
id: string;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export interface FreeBusyRequest {
|
|
372
|
+
timeMin?: string; // RFC3339
|
|
373
|
+
timeMax?: string; // RFC3339
|
|
374
|
+
items: (FreeBusyCalendarRequest | FreeBusyGroupRequest)[];
|
|
375
|
+
groupExpansionMax?: number;
|
|
376
|
+
timeZone?: string;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export interface FreeBusyBusyPeriod {
|
|
380
|
+
start: string;
|
|
381
|
+
end: string;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export interface FreeBusyCalendar {
|
|
385
|
+
busy: FreeBusyBusyPeriod[];
|
|
386
|
+
errors?: ApiErrorDetail[];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export interface FreeBusyCalendarResponse {
|
|
390
|
+
calendars: { [calendarId: string]: FreeBusyCalendar };
|
|
391
|
+
groups?: { [groupId: string]: FreeBusyGroup };
|
|
392
|
+
kind: 'calendar#freeBusy';
|
|
393
|
+
timeMax: string;
|
|
394
|
+
timeMin: string;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ============================================
|
|
398
|
+
// ACL Types
|
|
399
|
+
// ============================================
|
|
400
|
+
|
|
401
|
+
export type CalendarAclRole =
|
|
402
|
+
| 'none'
|
|
403
|
+
| 'freeBusyReader'
|
|
404
|
+
| 'reader'
|
|
405
|
+
| 'writer'
|
|
406
|
+
| 'owner';
|
|
407
|
+
|
|
408
|
+
export type CalendarAclScopeType =
|
|
409
|
+
| 'default'
|
|
410
|
+
| 'user'
|
|
411
|
+
| 'group'
|
|
412
|
+
| 'domain';
|
|
413
|
+
|
|
414
|
+
export interface CalendarAclScope {
|
|
415
|
+
type: CalendarAclScopeType;
|
|
416
|
+
value?: string; // email for user/group, domain for domain
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export interface CalendarAcl {
|
|
420
|
+
kind: 'calendar#aclRule';
|
|
421
|
+
etag: string;
|
|
422
|
+
id: string;
|
|
423
|
+
role: CalendarAclRole;
|
|
424
|
+
scope: CalendarAclScope;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export interface CalendarAclListResponse {
|
|
428
|
+
kind: 'calendar#acl';
|
|
429
|
+
etag: string;
|
|
430
|
+
nextPageToken?: string;
|
|
431
|
+
items: CalendarAcl[];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export interface CalendarAclInsert {
|
|
435
|
+
role: CalendarAclRole;
|
|
436
|
+
scope: CalendarAclScope;
|
|
437
|
+
}
|