@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.
Files changed (63) hide show
  1. package/bin/index.js +109 -59
  2. package/bin/mcp.js +236 -182
  3. package/bin/serve.js +77 -33
  4. package/connectors/connect-dropbox/src/api/bulk.ts +285 -0
  5. package/connectors/connect-dropbox/src/api/index.ts +4 -0
  6. package/connectors/connect-dropbox/src/cli/index.ts +151 -0
  7. package/connectors/connect-github/src/api/bulk.ts +328 -0
  8. package/connectors/connect-github/src/api/index.ts +4 -0
  9. package/connectors/connect-github/src/cli/index.ts +145 -0
  10. package/connectors/connect-gmail/src/api/history.ts +82 -0
  11. package/connectors/connect-gmail/src/api/index.ts +12 -0
  12. package/connectors/connect-gmail/src/api/settings.ts +280 -0
  13. package/connectors/connect-gmail/src/api/watch.ts +40 -0
  14. package/connectors/connect-gmail/src/cli/index.ts +237 -0
  15. package/connectors/connect-googleads/src/api/bulk.ts +354 -0
  16. package/connectors/connect-googleads/src/api/index.ts +4 -0
  17. package/connectors/connect-googleads/src/cli/index.ts +192 -0
  18. package/connectors/connect-googlecalendar/src/api/acl.ts +66 -0
  19. package/connectors/connect-googlecalendar/src/api/bulk.ts +452 -0
  20. package/connectors/connect-googlecalendar/src/api/freebusy.ts +35 -0
  21. package/connectors/connect-googlecalendar/src/api/index.ts +9 -0
  22. package/connectors/connect-googlecalendar/src/cli/index.ts +522 -0
  23. package/connectors/connect-googlecalendar/src/types/index.ts +90 -0
  24. package/connectors/connect-googlecontacts/src/api/bulk.ts +365 -0
  25. package/connectors/connect-googlecontacts/src/api/groups.ts +164 -0
  26. package/connectors/connect-googlecontacts/src/api/index.ts +8 -0
  27. package/connectors/connect-googlecontacts/src/cli/index.ts +340 -0
  28. package/connectors/connect-googledocs/src/api/bulk.ts +301 -0
  29. package/connectors/connect-googledocs/src/api/index.ts +4 -0
  30. package/connectors/connect-googledocs/src/cli/index.ts +187 -0
  31. package/connectors/connect-googledrive/src/api/bulk.ts +505 -0
  32. package/connectors/connect-googledrive/src/api/changes.ts +139 -0
  33. package/connectors/connect-googledrive/src/api/client.ts +34 -0
  34. package/connectors/connect-googledrive/src/api/index.ts +12 -0
  35. package/connectors/connect-googledrive/src/api/revisions.ts +132 -0
  36. package/connectors/connect-googledrive/src/cli/index.ts +624 -0
  37. package/connectors/connect-googledrive/src/index.ts +2 -0
  38. package/connectors/connect-googlephotos/src/api/bulk.ts +455 -0
  39. package/connectors/connect-googlephotos/src/api/index.ts +8 -0
  40. package/connectors/connect-googlephotos/src/cli/index.ts +185 -0
  41. package/connectors/connect-googletasks/src/api/bulk.ts +359 -0
  42. package/connectors/connect-googletasks/src/api/index.ts +1 -0
  43. package/connectors/connect-googletasks/src/cli/index.ts +178 -0
  44. package/connectors/connect-linear/src/api/bulk.ts +219 -0
  45. package/connectors/connect-linear/src/api/index.ts +4 -0
  46. package/connectors/connect-linear/src/cli/index.ts +116 -0
  47. package/connectors/connect-shopify/src/api/bulk.ts +288 -0
  48. package/connectors/connect-shopify/src/api/index.ts +4 -0
  49. package/connectors/connect-shopify/src/cli/index.ts +108 -0
  50. package/connectors/connect-slack/src/api/bulk.ts +237 -0
  51. package/connectors/connect-slack/src/api/index.ts +4 -0
  52. package/connectors/connect-slack/src/cli/index.ts +157 -0
  53. package/connectors/connect-stripe/src/api/bulk.ts +488 -0
  54. package/connectors/connect-stripe/src/api/index.ts +4 -0
  55. package/connectors/connect-stripe/src/cli/index.ts +206 -0
  56. package/dist/index.js +75 -33
  57. package/dist/lib/runner.d.ts +15 -5
  58. package/package.json +2 -2
  59. package/connectors/connect-cloudflare/bun.lock +0 -32
  60. package/connectors/connect-gmail/bin/index.js +0 -7027
  61. package/connectors/connect-gmail/dist/cli/index.js +0 -7035
  62. package/connectors/connect-gmail/dist/index.js +0 -2174
  63. package/dist/server/server.test.d.ts +0 -1
@@ -1303,5 +1303,629 @@ drivesCmd
1303
1303
  }
1304
1304
  });
1305
1305
 
1306
+ // ============================================
1307
+ // Bulk Commands
1308
+ // ============================================
1309
+ const bulkCmd = program
1310
+ .command('bulk')
1311
+ .description('Bulk operations on files (using Drive search queries)');
1312
+
1313
+ bulkCmd
1314
+ .command('preview <query>')
1315
+ .description('Preview files matching a Drive search query')
1316
+ .option('-n, --max <number>', 'Maximum files to preview', '20')
1317
+ .action(async (query: string, opts) => {
1318
+ try {
1319
+ const drive = requireAuth();
1320
+ info(`Searching for files matching: ${query}`);
1321
+
1322
+ const result = await drive.bulk.preview(query, parseInt(opts.max));
1323
+
1324
+ if (result.files.length === 0) {
1325
+ info('No files found matching the query');
1326
+ return;
1327
+ }
1328
+
1329
+ success(`Found ${result.total} file(s):`);
1330
+ const output = result.files.map(f => ({
1331
+ id: f.id,
1332
+ name: f.name,
1333
+ mimeType: f.mimeType,
1334
+ size: f.size ? formatBytes(parseInt(f.size)) : '-',
1335
+ }));
1336
+ print(output, getFormat(bulkCmd));
1337
+ } catch (err) {
1338
+ error(String(err));
1339
+ process.exit(1);
1340
+ }
1341
+ });
1342
+
1343
+ bulkCmd
1344
+ .command('trash <query>')
1345
+ .description('Move files matching a query to trash')
1346
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1347
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1348
+ .option('--dry-run', 'Preview changes without applying them')
1349
+ .action(async (query: string, opts) => {
1350
+ try {
1351
+ const drive = requireAuth();
1352
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Moving to trash files matching: ${query}`);
1353
+
1354
+ const result = await drive.bulk.trash({
1355
+ query,
1356
+ maxResults: parseInt(opts.max),
1357
+ concurrency: parseInt(opts.concurrency),
1358
+ dryRun: opts.dryRun,
1359
+ onProgress: (current, total) => {
1360
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1361
+ },
1362
+ });
1363
+ console.log();
1364
+
1365
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk trash complete:`);
1366
+ info(` Total: ${result.total}`);
1367
+ info(` Success: ${result.success}`);
1368
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1369
+ } catch (err) {
1370
+ error(String(err));
1371
+ process.exit(1);
1372
+ }
1373
+ });
1374
+
1375
+ bulkCmd
1376
+ .command('delete <query>')
1377
+ .description('Permanently delete files matching a query (DANGER!)')
1378
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1379
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1380
+ .option('--dry-run', 'Preview changes without applying them')
1381
+ .option('--trash', 'Move to trash instead of permanent delete')
1382
+ .option('--confirm', 'Confirm permanent deletion')
1383
+ .action(async (query: string, opts) => {
1384
+ try {
1385
+ if (!opts.dryRun && !opts.confirm && !opts.trash) {
1386
+ error('Permanent deletion requires --confirm flag');
1387
+ info('Use --dry-run to preview or --trash to move to trash instead');
1388
+ process.exit(1);
1389
+ }
1390
+
1391
+ const drive = requireAuth();
1392
+ if (!opts.trash) {
1393
+ warn(`${opts.dryRun ? '[DRY RUN] ' : ''}PERMANENTLY DELETING files matching: ${query}`);
1394
+ } else {
1395
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Moving to trash files matching: ${query}`);
1396
+ }
1397
+
1398
+ const result = await drive.bulk.delete({
1399
+ query,
1400
+ maxResults: parseInt(opts.max),
1401
+ concurrency: parseInt(opts.concurrency),
1402
+ trash: opts.trash,
1403
+ dryRun: opts.dryRun,
1404
+ onProgress: (current, total) => {
1405
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1406
+ },
1407
+ });
1408
+ console.log();
1409
+
1410
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk delete complete:`);
1411
+ info(` Total: ${result.total}`);
1412
+ info(` Success: ${result.success}`);
1413
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1414
+ } catch (err) {
1415
+ error(String(err));
1416
+ process.exit(1);
1417
+ }
1418
+ });
1419
+
1420
+ bulkCmd
1421
+ .command('untrash <query>')
1422
+ .description('Restore files matching a query from trash')
1423
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1424
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1425
+ .option('--dry-run', 'Preview changes without applying them')
1426
+ .action(async (query: string, opts) => {
1427
+ try {
1428
+ const drive = requireAuth();
1429
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Restoring from trash files matching: ${query}`);
1430
+
1431
+ const result = await drive.bulk.untrash({
1432
+ query,
1433
+ maxResults: parseInt(opts.max),
1434
+ concurrency: parseInt(opts.concurrency),
1435
+ dryRun: opts.dryRun,
1436
+ onProgress: (current, total) => {
1437
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1438
+ },
1439
+ });
1440
+ console.log();
1441
+
1442
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk untrash complete:`);
1443
+ info(` Total: ${result.total}`);
1444
+ info(` Success: ${result.success}`);
1445
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1446
+ } catch (err) {
1447
+ error(String(err));
1448
+ process.exit(1);
1449
+ }
1450
+ });
1451
+
1452
+ bulkCmd
1453
+ .command('move <query> <destinationFolderId>')
1454
+ .description('Move files matching a query to a different folder')
1455
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1456
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1457
+ .option('--dry-run', 'Preview changes without applying them')
1458
+ .action(async (query: string, destinationFolderId: string, opts) => {
1459
+ try {
1460
+ const drive = requireAuth();
1461
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Moving files matching: ${query}`);
1462
+ info(` Destination folder: ${destinationFolderId}`);
1463
+
1464
+ const result = await drive.bulk.move({
1465
+ query,
1466
+ destinationFolderId,
1467
+ maxResults: parseInt(opts.max),
1468
+ concurrency: parseInt(opts.concurrency),
1469
+ dryRun: opts.dryRun,
1470
+ onProgress: (current, total) => {
1471
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1472
+ },
1473
+ });
1474
+ console.log();
1475
+
1476
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk move complete:`);
1477
+ info(` Total: ${result.total}`);
1478
+ info(` Success: ${result.success}`);
1479
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1480
+ } catch (err) {
1481
+ error(String(err));
1482
+ process.exit(1);
1483
+ }
1484
+ });
1485
+
1486
+ bulkCmd
1487
+ .command('rename <query>')
1488
+ .description('Rename files matching a query')
1489
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1490
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1491
+ .option('--dry-run', 'Preview changes without applying them')
1492
+ .option('-m, --mode <mode>', 'Rename mode: prefix, suffix, replace, lowercase, uppercase', 'prefix')
1493
+ .option('--prefix <text>', 'Prefix to add (for mode=prefix)')
1494
+ .option('--suffix <text>', 'Suffix to add before extension (for mode=suffix)')
1495
+ .option('--find <text>', 'Text to find (for mode=replace)')
1496
+ .option('--replace <text>', 'Replacement text (for mode=replace)')
1497
+ .action(async (query: string, opts) => {
1498
+ try {
1499
+ const drive = requireAuth();
1500
+ const mode = opts.mode as 'prefix' | 'suffix' | 'replace' | 'lowercase' | 'uppercase';
1501
+
1502
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Renaming files matching: ${query}`);
1503
+ info(` Mode: ${mode}`);
1504
+ if (opts.prefix) info(` Prefix: ${opts.prefix}`);
1505
+ if (opts.suffix) info(` Suffix: ${opts.suffix}`);
1506
+ if (opts.find) info(` Find: ${opts.find}`);
1507
+ if (opts.replace) info(` Replace: ${opts.replace}`);
1508
+
1509
+ const result = await drive.bulk.rename({
1510
+ query,
1511
+ mode,
1512
+ prefix: opts.prefix,
1513
+ suffix: opts.suffix,
1514
+ find: opts.find,
1515
+ replace: opts.replace,
1516
+ maxResults: parseInt(opts.max),
1517
+ concurrency: parseInt(opts.concurrency),
1518
+ dryRun: opts.dryRun,
1519
+ onProgress: (current, total) => {
1520
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1521
+ },
1522
+ });
1523
+ console.log();
1524
+
1525
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk rename complete:`);
1526
+ info(` Total: ${result.total}`);
1527
+ info(` Success: ${result.success}`);
1528
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1529
+ } catch (err) {
1530
+ error(String(err));
1531
+ process.exit(1);
1532
+ }
1533
+ });
1534
+
1535
+ bulkCmd
1536
+ .command('share <query>')
1537
+ .description('Share files matching a query with a user')
1538
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1539
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1540
+ .option('--dry-run', 'Preview changes without applying them')
1541
+ .option('-e, --email <email>', 'Email address to share with (required)', '')
1542
+ .option('-r, --role <role>', 'Permission role: reader, writer, commenter', 'reader')
1543
+ .option('--no-notification', 'Do not send notification email')
1544
+ .action(async (query: string, opts) => {
1545
+ try {
1546
+ if (!opts.email) {
1547
+ error('--email is required');
1548
+ process.exit(1);
1549
+ }
1550
+
1551
+ const drive = requireAuth();
1552
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Sharing files matching: ${query}`);
1553
+ info(` With: ${opts.email} (${opts.role})`);
1554
+
1555
+ const result = await drive.bulk.share({
1556
+ query,
1557
+ email: opts.email,
1558
+ role: opts.role as 'reader' | 'writer' | 'commenter',
1559
+ sendNotification: opts.notification !== false,
1560
+ maxResults: parseInt(opts.max),
1561
+ concurrency: parseInt(opts.concurrency),
1562
+ dryRun: opts.dryRun,
1563
+ onProgress: (current, total) => {
1564
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1565
+ },
1566
+ });
1567
+ console.log();
1568
+
1569
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk share complete:`);
1570
+ info(` Total: ${result.total}`);
1571
+ info(` Success: ${result.success}`);
1572
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1573
+ } catch (err) {
1574
+ error(String(err));
1575
+ process.exit(1);
1576
+ }
1577
+ });
1578
+
1579
+ bulkCmd
1580
+ .command('make-public <query>')
1581
+ .description('Make files matching a query publicly accessible')
1582
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1583
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1584
+ .option('--dry-run', 'Preview changes without applying them')
1585
+ .action(async (query: string, opts) => {
1586
+ try {
1587
+ const drive = requireAuth();
1588
+ warn(`${opts.dryRun ? '[DRY RUN] ' : ''}MAKING PUBLICLY ACCESSIBLE files matching: ${query}`);
1589
+
1590
+ const result = await drive.bulk.makePublic({
1591
+ query,
1592
+ maxResults: parseInt(opts.max),
1593
+ concurrency: parseInt(opts.concurrency),
1594
+ dryRun: opts.dryRun,
1595
+ onProgress: (current, total) => {
1596
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1597
+ },
1598
+ });
1599
+ console.log();
1600
+
1601
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk make-public complete:`);
1602
+ info(` Total: ${result.total}`);
1603
+ info(` Success: ${result.success}`);
1604
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1605
+ } catch (err) {
1606
+ error(String(err));
1607
+ process.exit(1);
1608
+ }
1609
+ });
1610
+
1611
+ bulkCmd
1612
+ .command('remove-public <query>')
1613
+ .description('Remove public access from files matching a query')
1614
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1615
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1616
+ .option('--dry-run', 'Preview changes without applying them')
1617
+ .action(async (query: string, opts) => {
1618
+ try {
1619
+ const drive = requireAuth();
1620
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Removing public access from files matching: ${query}`);
1621
+
1622
+ const result = await drive.bulk.removePublicAccess({
1623
+ query,
1624
+ maxResults: parseInt(opts.max),
1625
+ concurrency: parseInt(opts.concurrency),
1626
+ dryRun: opts.dryRun,
1627
+ onProgress: (current, total) => {
1628
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1629
+ },
1630
+ });
1631
+ console.log();
1632
+
1633
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk remove-public complete:`);
1634
+ info(` Total: ${result.total}`);
1635
+ info(` Success: ${result.success}`);
1636
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1637
+ } catch (err) {
1638
+ error(String(err));
1639
+ process.exit(1);
1640
+ }
1641
+ });
1642
+
1643
+ bulkCmd
1644
+ .command('star <query>')
1645
+ .description('Star files matching a query')
1646
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1647
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1648
+ .option('--dry-run', 'Preview changes without applying them')
1649
+ .action(async (query: string, opts) => {
1650
+ try {
1651
+ const drive = requireAuth();
1652
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Starring files matching: ${query}`);
1653
+
1654
+ const result = await drive.bulk.star({
1655
+ query,
1656
+ maxResults: parseInt(opts.max),
1657
+ concurrency: parseInt(opts.concurrency),
1658
+ dryRun: opts.dryRun,
1659
+ onProgress: (current, total) => {
1660
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1661
+ },
1662
+ });
1663
+ console.log();
1664
+
1665
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk star complete:`);
1666
+ info(` Total: ${result.total}`);
1667
+ info(` Success: ${result.success}`);
1668
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1669
+ } catch (err) {
1670
+ error(String(err));
1671
+ process.exit(1);
1672
+ }
1673
+ });
1674
+
1675
+ bulkCmd
1676
+ .command('unstar <query>')
1677
+ .description('Remove stars from files matching a query')
1678
+ .option('-n, --max <number>', 'Maximum files to process', '100')
1679
+ .option('-c, --concurrency <number>', 'Maximum concurrent API calls', '10')
1680
+ .option('--dry-run', 'Preview changes without applying them')
1681
+ .action(async (query: string, opts) => {
1682
+ try {
1683
+ const drive = requireAuth();
1684
+ info(`${opts.dryRun ? '[DRY RUN] ' : ''}Removing stars from files matching: ${query}`);
1685
+
1686
+ const result = await drive.bulk.unstar({
1687
+ query,
1688
+ maxResults: parseInt(opts.max),
1689
+ concurrency: parseInt(opts.concurrency),
1690
+ dryRun: opts.dryRun,
1691
+ onProgress: (current, total) => {
1692
+ process.stdout.write(`\r Progress: ${current}/${total}`);
1693
+ },
1694
+ });
1695
+ console.log();
1696
+
1697
+ success(`${opts.dryRun ? '[DRY RUN] ' : ''}Bulk unstar complete:`);
1698
+ info(` Total: ${result.total}`);
1699
+ info(` Success: ${result.success}`);
1700
+ if (result.failed > 0) warn(` Failed: ${result.failed}`);
1701
+ } catch (err) {
1702
+ error(String(err));
1703
+ process.exit(1);
1704
+ }
1705
+ });
1706
+
1707
+ bulkCmd
1708
+ .command('help-query')
1709
+ .description('Show Drive search query syntax examples')
1710
+ .action(() => {
1711
+ info(chalk.bold('\nDrive Search Query Syntax:\n'));
1712
+
1713
+ info(chalk.cyan('Basic filters:'));
1714
+ info(' name = "report" - Files named "report"');
1715
+ info(' name contains "report" - Files with "report" in the name');
1716
+ info(' fullName contains "/Docs/" - Files in a path containing "Docs"\n');
1717
+
1718
+ info(chalk.cyan('Type filters:'));
1719
+ info(' mimeType = "application/pdf" - PDF files only');
1720
+ info(' mimeType = "application/vnd.google-apps.document" - Google Docs');
1721
+ info(' mimeType = "application/vnd.google-apps.folder" - Folders');
1722
+ info(' mimeType contains "image/" - Any image file\n');
1723
+
1724
+ info(chalk.cyan('Status filters:'));
1725
+ info(' trashed = false - Not in trash');
1726
+ info(' trashed = true - In trash');
1727
+ info(' starred = true - Starred files');
1728
+ info(' viewedByMe = true - Files you have viewed\n');
1729
+
1730
+ info(chalk.cyan('Ownership filters:'));
1731
+ info(' "me" in owners - Files you own');
1732
+ info(' sharedWithMe - Files shared with you');
1733
+ info(' "user@example.com" in owners - Files owned by specific user\n');
1734
+
1735
+ info(chalk.cyan('Folder filters:'));
1736
+ info(' "<folderId>" in parents - Direct children of a folder\n');
1737
+
1738
+ info(chalk.cyan('Date filters:'));
1739
+ info(' modifiedTime > "2024-01-01T00:00:00" - Modified after date');
1740
+ info(' modifiedTime < "2024-12-31T23:59:59" - Modified before date');
1741
+ info(' createdTime > "2024-06-01T00:00:00" - Created after date\n');
1742
+
1743
+ info(chalk.cyan('Combining filters:'));
1744
+ info(' mimeType = "application/pdf" and trashed = false');
1745
+ info(' name contains "report" and "me" in owners');
1746
+ info(' mimeType contains "image/" and modifiedTime > "2024-01-01T00:00:00"\n');
1747
+
1748
+ info(chalk.cyan('Examples:'));
1749
+ info(' bulk preview "mimeType=\'application/pdf\'"');
1750
+ info(' bulk trash "name contains \'temp\' and trashed = false" --dry-run');
1751
+ info(' bulk rename "mimeType contains \'image\'" --mode suffix --suffix _v2');
1752
+ info(' bulk star "sharedWithMe" -n 50');
1753
+ });
1754
+
1755
+ // ============================================
1756
+ // Changes Commands
1757
+ // ============================================
1758
+ const changesCmd = program
1759
+ .command('changes')
1760
+ .description('Track changes to Drive files');
1761
+
1762
+ changesCmd
1763
+ .command('start-page-token')
1764
+ .description('Get the current starting page token for change tracking')
1765
+ .option('--drive <driveId>', 'Get token for a specific shared drive')
1766
+ .action(async (opts) => {
1767
+ try {
1768
+ const drive = requireAuth();
1769
+ const result = await drive.changes.getStartPageToken({
1770
+ driveId: opts.drive,
1771
+ supportsAllDrives: !!opts.drive,
1772
+ });
1773
+ success('Current page token: ' + result.nextPageToken);
1774
+ info('Use this token with "changes list" to begin tracking');
1775
+ } catch (err) {
1776
+ error(String(err));
1777
+ process.exit(1);
1778
+ }
1779
+ });
1780
+
1781
+ changesCmd
1782
+ .command('list <pageToken>')
1783
+ .description('List changes since the given page token')
1784
+ .option('-n, --max <number>', 'Maximum changes to return', '50')
1785
+ .option('--include-removed', 'Include removed items')
1786
+ .option('--my-drive-only', 'Only track My Drive changes (exclude shared drives)')
1787
+ .action(async (pageToken: string, opts) => {
1788
+ try {
1789
+ const drive = requireAuth();
1790
+ const result = await drive.changes.list({
1791
+ pageToken,
1792
+ pageSize: parseInt(opts.max),
1793
+ includeRemoved: opts.includeRemoved,
1794
+ restrictToMyDrive: opts.myDriveOnly,
1795
+ includeItemsFromAllDrives: !opts.myDriveOnly,
1796
+ supportsAllDrives: true,
1797
+ });
1798
+
1799
+ if (!result.changes || result.changes.length === 0) {
1800
+ info('No changes found');
1801
+ return;
1802
+ }
1803
+
1804
+ success('Found ' + result.changes.length + ' change(s):');
1805
+ info('New start page token: ' + result.newStartPageToken);
1806
+
1807
+ const changes = result.changes.map((c: any) => ({
1808
+ fileId: c.fileId,
1809
+ type: c.type,
1810
+ removed: c.removed || false,
1811
+ fileName: c.file?.name || '-',
1812
+ mimeType: c.file?.mimeType || '-',
1813
+ }));
1814
+
1815
+ print(changes, getFormat(changesCmd));
1816
+
1817
+ if (result.nextPageToken) {
1818
+ info('Next page token: ' + result.nextPageToken);
1819
+ }
1820
+ } catch (err) {
1821
+ error(String(err));
1822
+ process.exit(1);
1823
+ }
1824
+ });
1825
+
1826
+ // ============================================
1827
+ // Revisions Commands
1828
+ // ============================================
1829
+ const revisionsCmd = program
1830
+ .command('revisions')
1831
+ .description('File revision history commands');
1832
+
1833
+ revisionsCmd
1834
+ .command('list <fileId>')
1835
+ .description('List revisions of a file')
1836
+ .action(async (fileId: string) => {
1837
+ try {
1838
+ const drive = requireAuth();
1839
+ const result = await drive.revisions.list(fileId);
1840
+
1841
+ if (!result.revisions || result.revisions.length === 0) {
1842
+ info('No revisions found');
1843
+ return;
1844
+ }
1845
+
1846
+ success('Found ' + result.revisions.length + ' revision(s):');
1847
+
1848
+ const revisions = result.revisions.map((r: any) => ({
1849
+ id: r.id,
1850
+ mimeType: r.mimeType,
1851
+ size: r.size ? formatBytes(r.size) : '-',
1852
+ modified: r.modifiedTime ? new Date(r.modifiedTime).toLocaleString() : '-',
1853
+ keepForever: r.keepForever || false,
1854
+ }));
1855
+
1856
+ print(revisions, getFormat(revisionsCmd));
1857
+ } catch (err) {
1858
+ error(String(err));
1859
+ process.exit(1);
1860
+ }
1861
+ });
1862
+
1863
+ revisionsCmd
1864
+ .command('get <fileId> <revisionId>')
1865
+ .description('Get details of a specific revision')
1866
+ .action(async (fileId: string, revisionId: string) => {
1867
+ try {
1868
+ const drive = requireAuth();
1869
+ const revision = await drive.revisions.get(fileId, revisionId);
1870
+ print(revision, getFormat(revisionsCmd));
1871
+ } catch (err) {
1872
+ error(String(err));
1873
+ process.exit(1);
1874
+ }
1875
+ });
1876
+
1877
+ revisionsCmd
1878
+ .command('download <fileId> <revisionId> [destination]')
1879
+ .description('Download a specific revision')
1880
+ .action(async (fileId: string, revisionId: string, destination?: string) => {
1881
+ try {
1882
+ const drive = requireAuth();
1883
+ info('Downloading revision...');
1884
+
1885
+ const data = await drive.revisions.download(fileId, revisionId);
1886
+
1887
+ if (destination) {
1888
+ writeFileSync(destination, Buffer.from(data));
1889
+ success('Downloaded revision to: ' + destination);
1890
+ } else {
1891
+ const destPath = join(process.cwd(), 'revision-' + revisionId);
1892
+ writeFileSync(destPath, Buffer.from(data));
1893
+ success('Downloaded revision');
1894
+ info('Saved to: ' + destPath);
1895
+ }
1896
+ } catch (err) {
1897
+ error(String(err));
1898
+ process.exit(1);
1899
+ }
1900
+ });
1901
+
1902
+ revisionsCmd
1903
+ .command('keep-forever <fileId> <revisionId>')
1904
+ .description('Mark a revision to be kept forever (not auto-deleted)')
1905
+ .action(async (fileId: string, revisionId: string) => {
1906
+ try {
1907
+ const drive = requireAuth();
1908
+ await drive.revisions.update(fileId, revisionId, { keepForever: true });
1909
+ success('Revision marked to keep forever');
1910
+ } catch (err) {
1911
+ error(String(err));
1912
+ process.exit(1);
1913
+ }
1914
+ });
1915
+
1916
+ revisionsCmd
1917
+ .command('delete <fileId> <revisionId>')
1918
+ .description('Delete a specific revision')
1919
+ .action(async (fileId: string, revisionId: string) => {
1920
+ try {
1921
+ const drive = requireAuth();
1922
+ await drive.revisions.delete(fileId, revisionId);
1923
+ success('Revision deleted');
1924
+ } catch (err) {
1925
+ error(String(err));
1926
+ process.exit(1);
1927
+ }
1928
+ });
1929
+
1306
1930
  // Parse and execute
1307
1931
  program.parse();
@@ -27,4 +27,6 @@ export {
27
27
  loadTokens,
28
28
  saveTokens,
29
29
  clearConfig,
30
+ setProfileOverride,
31
+ getProfileOverride,
30
32
  } from './utils/config.ts';