@hasna/connectors 1.3.21 → 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 (64) hide show
  1. package/bin/index.js +274 -71
  2. package/bin/mcp.js +458 -197
  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/dist/server/auth.d.ts +4 -0
  59. package/package.json +2 -2
  60. package/connectors/connect-cloudflare/bun.lock +0 -32
  61. package/connectors/connect-gmail/bin/index.js +0 -7027
  62. package/connectors/connect-gmail/dist/cli/index.js +0 -7035
  63. package/connectors/connect-gmail/dist/index.js +0 -2174
  64. package/dist/server/server.test.d.ts +0 -1
package/bin/serve.js CHANGED
@@ -17,7 +17,7 @@ var __export = (target, all) => {
17
17
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
18
  var __require = import.meta.require;
19
19
 
20
- // node_modules/@hasna/cloud/dist/index.js
20
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/@hasna/cloud/dist/index.js
21
21
  import { createRequire } from "module";
22
22
  import { Database } from "bun:sqlite";
23
23
  import {
@@ -10327,7 +10327,7 @@ import { existsSync as existsSync7 } from "fs";
10327
10327
  import { dirname as dirname3, join as join8 } from "path";
10328
10328
  import { fileURLToPath, pathToFileURL } from "url";
10329
10329
 
10330
- // node_modules/zod/v3/external.js
10330
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/external.js
10331
10331
  var exports_external2 = {};
10332
10332
  __export(exports_external2, {
10333
10333
  void: () => voidType2,
@@ -10439,7 +10439,7 @@ __export(exports_external2, {
10439
10439
  BRAND: () => BRAND2
10440
10440
  });
10441
10441
 
10442
- // node_modules/zod/v3/helpers/util.js
10442
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/util.js
10443
10443
  var util2;
10444
10444
  (function(util3) {
10445
10445
  util3.assertEqual = (_) => {};
@@ -10570,7 +10570,7 @@ var getParsedType2 = (data) => {
10570
10570
  }
10571
10571
  };
10572
10572
 
10573
- // node_modules/zod/v3/ZodError.js
10573
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/ZodError.js
10574
10574
  var ZodIssueCode2 = util2.arrayToEnum([
10575
10575
  "invalid_type",
10576
10576
  "invalid_literal",
@@ -10689,7 +10689,7 @@ ZodError2.create = (issues) => {
10689
10689
  return error;
10690
10690
  };
10691
10691
 
10692
- // node_modules/zod/v3/locales/en.js
10692
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/locales/en.js
10693
10693
  var errorMap2 = (issue, _ctx) => {
10694
10694
  let message;
10695
10695
  switch (issue.code) {
@@ -10792,7 +10792,7 @@ var errorMap2 = (issue, _ctx) => {
10792
10792
  };
10793
10793
  var en_default2 = errorMap2;
10794
10794
 
10795
- // node_modules/zod/v3/errors.js
10795
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/errors.js
10796
10796
  var overrideErrorMap2 = en_default2;
10797
10797
  function setErrorMap2(map) {
10798
10798
  overrideErrorMap2 = map;
@@ -10800,7 +10800,7 @@ function setErrorMap2(map) {
10800
10800
  function getErrorMap2() {
10801
10801
  return overrideErrorMap2;
10802
10802
  }
10803
- // node_modules/zod/v3/helpers/parseUtil.js
10803
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/parseUtil.js
10804
10804
  var makeIssue2 = (params) => {
10805
10805
  const { data, path, errorMaps, issueData } = params;
10806
10806
  const fullPath = [...path, ...issueData.path || []];
@@ -10906,14 +10906,14 @@ var isAborted2 = (x) => x.status === "aborted";
10906
10906
  var isDirty2 = (x) => x.status === "dirty";
10907
10907
  var isValid2 = (x) => x.status === "valid";
10908
10908
  var isAsync2 = (x) => typeof Promise !== "undefined" && x instanceof Promise;
10909
- // node_modules/zod/v3/helpers/errorUtil.js
10909
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/helpers/errorUtil.js
10910
10910
  var errorUtil2;
10911
10911
  (function(errorUtil3) {
10912
10912
  errorUtil3.errToObj = (message) => typeof message === "string" ? { message } : message || {};
10913
10913
  errorUtil3.toString = (message) => typeof message === "string" ? message : message?.message;
10914
10914
  })(errorUtil2 || (errorUtil2 = {}));
10915
10915
 
10916
- // node_modules/zod/v3/types.js
10916
+ // ../../../home/hasna/workspace/hasna/opensource/open-connectors/node_modules/zod/v3/types.js
10917
10917
  class ParseInputLazyPath2 {
10918
10918
  constructor(parent, value, path, key) {
10919
10919
  this._cachedPath = [];
@@ -15160,7 +15160,7 @@ var githubConnector = defineConnector({
15160
15160
  // package.json
15161
15161
  var package_default = {
15162
15162
  name: "@hasna/connectors",
15163
- version: "1.3.20",
15163
+ version: "1.3.23",
15164
15164
  description: "Open source connector library - Install API connectors with a single command",
15165
15165
  type: "module",
15166
15166
  bin: {
@@ -15191,7 +15191,7 @@ var package_default = {
15191
15191
  typecheck: "tsc --noEmit",
15192
15192
  test: "bun test",
15193
15193
  "sdk:build": "cd sdk && bun run build",
15194
- prepublishOnly: "bun test && bun run build"
15194
+ prepublishOnly: "bun run build && bun test"
15195
15195
  },
15196
15196
  keywords: [
15197
15197
  "connectors",
@@ -23528,6 +23528,53 @@ function getConnectorCliPath(name) {
23528
23528
  function hasConnectorCommandSurface(name) {
23529
23529
  return Boolean(getInternalCommandRuntime(name)?.commands.length || getConnectorCliPath(name));
23530
23530
  }
23531
+ function normalizeCommanderUsage(usage) {
23532
+ const commandToken = usage.trim().split(/\s+/)[0];
23533
+ if (!commandToken || commandToken === "help")
23534
+ return null;
23535
+ const aliases = commandToken.split("|").filter(Boolean);
23536
+ const [name, ...rest] = aliases;
23537
+ if (!name)
23538
+ return null;
23539
+ return {
23540
+ name,
23541
+ aliases: rest,
23542
+ usage: usage.trim()
23543
+ };
23544
+ }
23545
+ function parseCommanderHelpOperations(helpText) {
23546
+ const operations = [];
23547
+ const lines = helpText.split(`
23548
+ `);
23549
+ let inCommands = false;
23550
+ for (const line of lines) {
23551
+ if (line.trim().startsWith("Commands:")) {
23552
+ inCommands = true;
23553
+ continue;
23554
+ }
23555
+ if (!inCommands)
23556
+ continue;
23557
+ if (line.trim() === "") {
23558
+ if (operations.length > 0)
23559
+ inCommands = false;
23560
+ continue;
23561
+ }
23562
+ const row = line.match(/^ {2}(\S.*?)(?: {2,}(.+))?$/);
23563
+ if (!row)
23564
+ continue;
23565
+ const usage = row[1].trim();
23566
+ const summary = (row[2] ?? "").trim();
23567
+ const normalized = normalizeCommanderUsage(usage);
23568
+ if (!normalized)
23569
+ continue;
23570
+ operations.push({
23571
+ ...normalized,
23572
+ summary,
23573
+ source: "cli"
23574
+ });
23575
+ }
23576
+ return operations;
23577
+ }
23531
23578
  function runConnectorCommand2(name, args, timeoutMs = 30000) {
23532
23579
  getConnectorsHome();
23533
23580
  const internalRuntime = getInternalCommandRuntime(name);
@@ -23594,38 +23641,33 @@ function runLegacyConnectorCommand(name, args, timeoutMs = 30000) {
23594
23641
  async function getConnectorOperations(name) {
23595
23642
  const internalRuntime = getInternalCommandRuntime(name);
23596
23643
  if (internalRuntime?.commands.length) {
23644
+ const operations2 = internalRuntime.commands.map((command) => ({
23645
+ name: command.name,
23646
+ aliases: [],
23647
+ usage: command.name,
23648
+ summary: command.summary,
23649
+ source: "internal"
23650
+ }));
23597
23651
  return {
23598
- commands: internalRuntime.commands.map((command) => command.name),
23652
+ commands: operations2.map((operation) => operation.name),
23653
+ operations: operations2,
23599
23654
  helpText: internalRuntime.helpText ?? buildInternalHelpText(name, internalRuntime.commands),
23600
23655
  hasCli: true
23601
23656
  };
23602
23657
  }
23603
23658
  const cliPath = getConnectorCliPath(name);
23604
23659
  if (!cliPath) {
23605
- return { commands: [], helpText: "", hasCli: false };
23660
+ return { commands: [], operations: [], helpText: "", hasCli: false };
23606
23661
  }
23607
23662
  const result = await runConnectorCommand2(name, ["--help"]);
23608
23663
  const helpText = result.stdout || result.stderr;
23609
- const commands = [];
23610
- const lines = helpText.split(`
23611
- `);
23612
- let inCommands = false;
23613
- for (const line of lines) {
23614
- if (line.trim().startsWith("Commands:")) {
23615
- inCommands = true;
23616
- continue;
23617
- }
23618
- if (inCommands) {
23619
- const match = line.match(/^\s{2,}(\S+)/);
23620
- if (match && match[1] !== "help") {
23621
- commands.push(match[1]);
23622
- }
23623
- if (line.trim() === "" && commands.length > 0) {
23624
- inCommands = false;
23625
- }
23626
- }
23627
- }
23628
- return { commands, helpText, hasCli: true };
23664
+ const operations = parseCommanderHelpOperations(helpText);
23665
+ return {
23666
+ commands: operations.map((operation) => operation.name),
23667
+ operations,
23668
+ helpText,
23669
+ hasCli: true
23670
+ };
23629
23671
  }
23630
23672
  async function getConnectorCommandHelp(name, command) {
23631
23673
  const internalRuntime = getInternalCommandRuntime(name);
@@ -24349,7 +24391,9 @@ Dashboard not found at: ${dashboardDir}`);
24349
24391
  return json({
24350
24392
  connector: name,
24351
24393
  displayName: meta.displayName,
24394
+ auth: getAuthStatus(name),
24352
24395
  commands: ops.commands,
24396
+ operations: ops.operations,
24353
24397
  helpText: ops.helpText
24354
24398
  }, 200, port);
24355
24399
  }
@@ -0,0 +1,285 @@
1
+ import type { DropboxClient } from './client';
2
+ import type { Metadata, FileMetadata, RelocationBatchV2Result } from '../types';
3
+
4
+ // ============================================
5
+ // Bulk Operation Types
6
+ // ============================================
7
+
8
+ export interface BulkOperationOptions {
9
+ /** Maximum concurrent API calls (default: 10) */
10
+ concurrency?: number;
11
+ /** Dry run - don't actually modify */
12
+ dryRun?: boolean;
13
+ /** Progress callback */
14
+ onProgress?: (current: number, total: number, item: unknown) => void;
15
+ /** Error callback */
16
+ onError?: (error: Error, item: unknown) => void;
17
+ }
18
+
19
+ export interface PathSummary {
20
+ path: string;
21
+ displayName?: string;
22
+ }
23
+
24
+ export interface BulkDeleteOptions extends BulkOperationOptions {
25
+ /** Paths to delete */
26
+ paths: string[];
27
+ }
28
+
29
+ export interface BulkDeleteResult {
30
+ total: number;
31
+ success: number;
32
+ failed: number;
33
+ errors: Array<{ path: string; error: string }>;
34
+ deletedPaths: string[];
35
+ }
36
+
37
+ export interface CopyEntry {
38
+ fromPath: string;
39
+ toPath: string;
40
+ }
41
+
42
+ export interface BulkCopyOptions extends BulkOperationOptions {
43
+ /** Source/destination path pairs */
44
+ entries: CopyEntry[];
45
+ /** Autorename if destination exists */
46
+ autorename?: boolean;
47
+ /** Allow shared folders */
48
+ allowSharedFolder?: boolean;
49
+ }
50
+
51
+ export interface CopyResult {
52
+ fromPath: string;
53
+ toPath: string;
54
+ metadata?: Metadata;
55
+ }
56
+
57
+ export interface BulkCopyResult {
58
+ total: number;
59
+ success: number;
60
+ failed: number;
61
+ errors: Array<{ fromPath: string; toPath: string; error: string }>;
62
+ results: CopyResult[];
63
+ }
64
+
65
+ export interface MoveEntry {
66
+ fromPath: string;
67
+ toPath: string;
68
+ }
69
+
70
+ export interface BulkMoveOptions extends BulkOperationOptions {
71
+ /** Source/destination path pairs */
72
+ entries: MoveEntry[];
73
+ /** Autorename if destination exists */
74
+ autorename?: boolean;
75
+ /** Allow shared folders */
76
+ allowSharedFolder?: boolean;
77
+ }
78
+
79
+ export interface MoveResult {
80
+ fromPath: string;
81
+ toPath: string;
82
+ metadata?: Metadata;
83
+ }
84
+
85
+ export interface BulkMoveResult {
86
+ total: number;
87
+ success: number;
88
+ failed: number;
89
+ errors: Array<{ fromPath: string; toPath: string; error: string }>;
90
+ results: MoveResult[];
91
+ }
92
+
93
+ // ============================================
94
+ // Bulk Operations API
95
+ // ============================================
96
+
97
+ export class BulkApi {
98
+ private readonly client: DropboxClient;
99
+
100
+ constructor(client: DropboxClient) {
101
+ this.client = client;
102
+ }
103
+
104
+ // ============================================
105
+ // Delete Batch
106
+ // ============================================
107
+
108
+ /**
109
+ * Bulk delete files and folders
110
+ */
111
+ async delete(options: BulkDeleteOptions): Promise<BulkDeleteResult> {
112
+ const { paths, concurrency = 10, dryRun = false, onProgress, onError } = options;
113
+
114
+ const result: BulkDeleteResult = {
115
+ total: paths.length,
116
+ success: 0,
117
+ failed: 0,
118
+ errors: [],
119
+ deletedPaths: [],
120
+ };
121
+
122
+ if (paths.length === 0) return result;
123
+
124
+ const chunks = this.chunkArray(paths, concurrency);
125
+
126
+ for (const chunk of chunks) {
127
+ await Promise.all(
128
+ chunk.map(async (path) => {
129
+ try {
130
+ if (dryRun) {
131
+ result.success++;
132
+ } else {
133
+ await this.client.post('/files/delete_v2', { path });
134
+ result.success++;
135
+ result.deletedPaths.push(path);
136
+ }
137
+
138
+ if (onProgress) onProgress(result.success + result.failed, result.total, path);
139
+ } catch (err) {
140
+ result.failed++;
141
+ const errorMessage = err instanceof Error ? err.message : String(err);
142
+ result.errors.push({ path, error: errorMessage });
143
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), path);
144
+ }
145
+ })
146
+ );
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ // ============================================
153
+ // Copy Batch
154
+ // ============================================
155
+
156
+ /**
157
+ * Bulk copy files and folders
158
+ */
159
+ async copy(options: BulkCopyOptions): Promise<BulkCopyResult> {
160
+ const { entries, concurrency = 10, dryRun = false, autorename = false, allowSharedFolder = false, onProgress, onError } = options;
161
+
162
+ const result: BulkCopyResult = {
163
+ total: entries.length,
164
+ success: 0,
165
+ failed: 0,
166
+ errors: [],
167
+ results: [],
168
+ };
169
+
170
+ if (entries.length === 0) return result;
171
+
172
+ const chunks = this.chunkArray(entries, concurrency);
173
+
174
+ for (const chunk of chunks) {
175
+ await Promise.all(
176
+ chunk.map(async (entry) => {
177
+ try {
178
+ if (dryRun) {
179
+ result.success++;
180
+ } else {
181
+ const response = await this.client.post<RelocationBatchV2Result>('/files/copy_v2', {
182
+ from_path: entry.fromPath,
183
+ to_path: entry.toPath,
184
+ autorename,
185
+ allow_shared_folder: allowSharedFolder,
186
+ });
187
+ result.success++;
188
+ result.results.push({
189
+ fromPath: entry.fromPath,
190
+ toPath: entry.toPath,
191
+ metadata: (response as unknown as { metadata: Metadata }).metadata,
192
+ });
193
+ }
194
+
195
+ if (onProgress) onProgress(result.success + result.failed, result.total, entry);
196
+ } catch (err) {
197
+ result.failed++;
198
+ const errorMessage = err instanceof Error ? err.message : String(err);
199
+ result.errors.push({
200
+ fromPath: entry.fromPath,
201
+ toPath: entry.toPath,
202
+ error: errorMessage,
203
+ });
204
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
205
+ }
206
+ })
207
+ );
208
+ }
209
+
210
+ return result;
211
+ }
212
+
213
+ // ============================================
214
+ // Move Batch
215
+ // ============================================
216
+
217
+ /**
218
+ * Bulk move files and folders
219
+ */
220
+ async move(options: BulkMoveOptions): Promise<BulkMoveResult> {
221
+ const { entries, concurrency = 10, dryRun = false, autorename = false, allowSharedFolder = false, onProgress, onError } = options;
222
+
223
+ const result: BulkMoveResult = {
224
+ total: entries.length,
225
+ success: 0,
226
+ failed: 0,
227
+ errors: [],
228
+ results: [],
229
+ };
230
+
231
+ if (entries.length === 0) return result;
232
+
233
+ const chunks = this.chunkArray(entries, concurrency);
234
+
235
+ for (const chunk of chunks) {
236
+ await Promise.all(
237
+ chunk.map(async (entry) => {
238
+ try {
239
+ if (dryRun) {
240
+ result.success++;
241
+ } else {
242
+ const response = await this.client.post<RelocationBatchV2Result>('/files/move_v2', {
243
+ from_path: entry.fromPath,
244
+ to_path: entry.toPath,
245
+ autorename,
246
+ allow_shared_folder: allowSharedFolder,
247
+ });
248
+ result.success++;
249
+ result.results.push({
250
+ fromPath: entry.fromPath,
251
+ toPath: entry.toPath,
252
+ metadata: (response as unknown as { metadata: Metadata }).metadata,
253
+ });
254
+ }
255
+
256
+ if (onProgress) onProgress(result.success + result.failed, result.total, entry);
257
+ } catch (err) {
258
+ result.failed++;
259
+ const errorMessage = err instanceof Error ? err.message : String(err);
260
+ result.errors.push({
261
+ fromPath: entry.fromPath,
262
+ toPath: entry.toPath,
263
+ error: errorMessage,
264
+ });
265
+ if (onError) onError(err instanceof Error ? err : new Error(errorMessage), entry);
266
+ }
267
+ })
268
+ );
269
+ }
270
+
271
+ return result;
272
+ }
273
+
274
+ // ============================================
275
+ // Helper Methods
276
+ // ============================================
277
+
278
+ private chunkArray<T>(array: T[], size: number): T[][] {
279
+ const chunks: T[][] = [];
280
+ for (let i = 0; i < array.length; i += size) {
281
+ chunks.push(array.slice(i, i + size));
282
+ }
283
+ return chunks;
284
+ }
285
+ }
@@ -15,15 +15,18 @@ import type {
15
15
  DeleteResult,
16
16
  } from '../types';
17
17
  import { DropboxClient } from './client';
18
+ import { BulkApi } from './bulk';
18
19
 
19
20
  /**
20
21
  * Dropbox API wrapper
21
22
  */
22
23
  export class Dropbox {
23
24
  private readonly client: DropboxClient;
25
+ public readonly bulk: BulkApi;
24
26
 
25
27
  constructor(config: DropboxConfig) {
26
28
  this.client = new DropboxClient(config);
29
+ this.bulk = new BulkApi(this.client);
27
30
  }
28
31
 
29
32
  /**
@@ -393,3 +396,4 @@ export class Dropbox {
393
396
  }
394
397
 
395
398
  export { DropboxClient } from './client';
399
+ export { BulkApi } from './bulk';
@@ -623,5 +623,156 @@ accountCmd
623
623
  }
624
624
  });
625
625
 
626
+ // ============================================
627
+ // Bulk Commands
628
+ // ============================================
629
+
630
+ const bulkCmd = program.command('bulk').description('Bulk operations on files and folders');
631
+
632
+ bulkCmd
633
+ .command('delete')
634
+ .description('Bulk delete files and folders')
635
+ .requiredOption('--paths <paths>', 'Comma-separated list of paths')
636
+ .option('--concurrency <number>', 'Max concurrent requests', '10')
637
+ .option('--dry-run', 'Preview without making changes')
638
+ .action(async (opts) => {
639
+ const paths = opts.paths.split(',').map((p: string) => p.trim()).filter(Boolean);
640
+ const concurrency = parseInt(opts.concurrency, 10);
641
+
642
+ if (paths.length === 0) {
643
+ console.error(chalk.red('Error: No paths provided'));
644
+ process.exit(1);
645
+ }
646
+
647
+ const client = getClient();
648
+ console.log(chalk.blue(`Deleting ${paths.length} path(s) (concurrency: ${concurrency})...`));
649
+ if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
650
+
651
+ const result = await client.bulk.delete({
652
+ paths,
653
+ concurrency,
654
+ dryRun: opts.dryRun,
655
+ onProgress: (current, total) => {
656
+ process.stdout.write(`\rProgress: ${current}/${total}`);
657
+ },
658
+ onError: (err, path) => {
659
+ console.error(`\n Failed: ${path} - ${err.message}`);
660
+ },
661
+ });
662
+
663
+ console.log();
664
+ console.log(chalk.green(`Done: ${result.success} deleted, ${result.failed} failed`));
665
+ if (result.errors.length > 0) {
666
+ console.log(chalk.yellow('Errors:'));
667
+ for (const e of result.errors) {
668
+ console.log(` - ${e.path}: ${e.error}`);
669
+ }
670
+ }
671
+ });
672
+
673
+ bulkCmd
674
+ .command('copy')
675
+ .description('Bulk copy files and folders')
676
+ .requiredOption('--entries <entries>', 'JSON array of {fromPath, toPath}')
677
+ .option('--concurrency <number>', 'Max concurrent requests', '10')
678
+ .option('--dry-run', 'Preview without making changes')
679
+ .option('--autorename', 'Autorename if destination exists')
680
+ .option('--allow-shared-folder', 'Allow shared folders')
681
+ .action(async (opts) => {
682
+ let entries: Array<{ fromPath: string; toPath: string }>;
683
+ try {
684
+ entries = JSON.parse(opts.entries) as Array<{ fromPath: string; toPath: string }>;
685
+ } catch {
686
+ console.error(chalk.red('Error: Invalid JSON for --entries'));
687
+ process.exit(1);
688
+ }
689
+
690
+ if (entries.length === 0) {
691
+ console.error(chalk.red('Error: No entries provided'));
692
+ process.exit(1);
693
+ }
694
+
695
+ const client = getClient();
696
+ const concurrency = parseInt(opts.concurrency, 10);
697
+ console.log(chalk.blue(`Copying ${entries.length} item(s)...`));
698
+ if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
699
+
700
+ const result = await client.bulk.copy({
701
+ entries,
702
+ concurrency,
703
+ dryRun: opts.dryRun,
704
+ autorename: opts.autorename,
705
+ allowSharedFolder: opts.allowSharedFolder,
706
+ onProgress: (current, total) => {
707
+ process.stdout.write(`\rProgress: ${current}/${total}`);
708
+ },
709
+ onError: (err, entry) => {
710
+ const e = entry as { fromPath: string };
711
+ console.error(`\n Failed: ${e.fromPath} - ${err.message}`);
712
+ },
713
+ });
714
+
715
+ console.log();
716
+ console.log(chalk.green(`Done: ${result.success} copied, ${result.failed} failed`));
717
+ if (result.errors.length > 0) {
718
+ console.log(chalk.yellow('Errors:'));
719
+ for (const e of result.errors) {
720
+ console.log(` - ${e.fromPath} -> ${e.toPath}: ${e.error}`);
721
+ }
722
+ }
723
+ });
724
+
725
+ bulkCmd
726
+ .command('move')
727
+ .description('Bulk move files and folders')
728
+ .requiredOption('--entries <entries>', 'JSON array of {fromPath, toPath}')
729
+ .option('--concurrency <number>', 'Max concurrent requests', '10')
730
+ .option('--dry-run', 'Preview without making changes')
731
+ .option('--autorename', 'Autorename if destination exists')
732
+ .option('--allow-shared-folder', 'Allow shared folders')
733
+ .action(async (opts) => {
734
+ let entries: Array<{ fromPath: string; toPath: string }>;
735
+ try {
736
+ entries = JSON.parse(opts.entries) as Array<{ fromPath: string; toPath: string }>;
737
+ } catch {
738
+ console.error(chalk.red('Error: Invalid JSON for --entries'));
739
+ process.exit(1);
740
+ }
741
+
742
+ if (entries.length === 0) {
743
+ console.error(chalk.red('Error: No entries provided'));
744
+ process.exit(1);
745
+ }
746
+
747
+ const client = getClient();
748
+ const concurrency = parseInt(opts.concurrency, 10);
749
+ console.log(chalk.blue(`Moving ${entries.length} item(s)...`));
750
+ if (opts.dryRun) console.log(chalk.yellow('(dry run)'));
751
+
752
+ const result = await client.bulk.move({
753
+ entries,
754
+ concurrency,
755
+ dryRun: opts.dryRun,
756
+ autorename: opts.autorename,
757
+ allowSharedFolder: opts.allowSharedFolder,
758
+ onProgress: (current, total) => {
759
+ process.stdout.write(`\rProgress: ${current}/${total}`);
760
+ },
761
+ onError: (err, entry) => {
762
+ const e = entry as { fromPath: string };
763
+ console.error(`\n Failed: ${e.fromPath} - ${err.message}`);
764
+ },
765
+ });
766
+
767
+ console.log();
768
+ console.log(chalk.green(`Done: ${result.success} moved, ${result.failed} failed`));
769
+ if (result.errors.length > 0) {
770
+ console.log(chalk.yellow('Errors:'));
771
+ for (const e of result.errors) {
772
+ console.log(` - ${e.fromPath} -> ${e.toPath}: ${e.error}`);
773
+ }
774
+ }
775
+ });
776
+
626
777
  // Parse and execute
627
778
  program.parse();