@marcfargas/go-easy 0.0.1 → 0.3.0

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 (113) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/LICENSE +21 -0
  3. package/README.md +224 -0
  4. package/dist/auth-flow.d.ts +50 -0
  5. package/dist/auth-flow.d.ts.map +1 -0
  6. package/dist/auth-flow.js +219 -0
  7. package/dist/auth-flow.js.map +1 -0
  8. package/dist/auth-server.d.ts +18 -0
  9. package/dist/auth-server.d.ts.map +1 -0
  10. package/dist/auth-server.js +327 -0
  11. package/dist/auth-server.js.map +1 -0
  12. package/dist/auth-store.d.ts +81 -0
  13. package/dist/auth-store.d.ts.map +1 -0
  14. package/dist/auth-store.js +185 -0
  15. package/dist/auth-store.js.map +1 -0
  16. package/dist/auth.d.ts +47 -0
  17. package/dist/auth.d.ts.map +1 -0
  18. package/dist/auth.js +131 -0
  19. package/dist/auth.js.map +1 -0
  20. package/dist/bin/calendar.d.ts +17 -0
  21. package/dist/bin/calendar.d.ts.map +1 -0
  22. package/dist/bin/calendar.js +224 -0
  23. package/dist/bin/calendar.js.map +1 -0
  24. package/dist/bin/drive.d.ts +18 -0
  25. package/dist/bin/drive.d.ts.map +1 -0
  26. package/dist/bin/drive.js +205 -0
  27. package/dist/bin/drive.js.map +1 -0
  28. package/dist/bin/easy.d.ts +11 -0
  29. package/dist/bin/easy.d.ts.map +1 -0
  30. package/dist/bin/easy.js +140 -0
  31. package/dist/bin/easy.js.map +1 -0
  32. package/dist/bin/gmail.d.ts +25 -0
  33. package/dist/bin/gmail.d.ts.map +1 -0
  34. package/dist/bin/gmail.js +243 -0
  35. package/dist/bin/gmail.js.map +1 -0
  36. package/dist/bin/tasks.d.ts +17 -0
  37. package/dist/bin/tasks.d.ts.map +1 -0
  38. package/dist/bin/tasks.js +190 -0
  39. package/dist/bin/tasks.js.map +1 -0
  40. package/dist/calendar/helpers.d.ts +35 -0
  41. package/dist/calendar/helpers.d.ts.map +1 -0
  42. package/dist/calendar/helpers.js +178 -0
  43. package/dist/calendar/helpers.js.map +1 -0
  44. package/dist/calendar/index.d.ts +64 -0
  45. package/dist/calendar/index.d.ts.map +1 -0
  46. package/dist/calendar/index.js +210 -0
  47. package/dist/calendar/index.js.map +1 -0
  48. package/dist/calendar/types.d.ts +191 -0
  49. package/dist/calendar/types.d.ts.map +1 -0
  50. package/dist/calendar/types.js +12 -0
  51. package/dist/calendar/types.js.map +1 -0
  52. package/dist/drive/helpers.d.ts +22 -0
  53. package/dist/drive/helpers.d.ts.map +1 -0
  54. package/dist/drive/helpers.js +85 -0
  55. package/dist/drive/helpers.js.map +1 -0
  56. package/dist/drive/index.d.ts +114 -0
  57. package/dist/drive/index.d.ts.map +1 -0
  58. package/dist/drive/index.js +418 -0
  59. package/dist/drive/index.js.map +1 -0
  60. package/dist/drive/types.d.ts +91 -0
  61. package/dist/drive/types.d.ts.map +1 -0
  62. package/dist/drive/types.js +5 -0
  63. package/dist/drive/types.js.map +1 -0
  64. package/dist/errors.d.ts +58 -0
  65. package/dist/errors.d.ts.map +1 -0
  66. package/dist/errors.js +73 -0
  67. package/dist/errors.js.map +1 -0
  68. package/dist/gmail/helpers.d.ts +59 -0
  69. package/dist/gmail/helpers.d.ts.map +1 -0
  70. package/dist/gmail/helpers.js +308 -0
  71. package/dist/gmail/helpers.js.map +1 -0
  72. package/dist/gmail/index.d.ts +95 -0
  73. package/dist/gmail/index.d.ts.map +1 -0
  74. package/dist/gmail/index.js +465 -0
  75. package/dist/gmail/index.js.map +1 -0
  76. package/dist/gmail/markdown.d.ts +22 -0
  77. package/dist/gmail/markdown.d.ts.map +1 -0
  78. package/dist/gmail/markdown.js +30 -0
  79. package/dist/gmail/markdown.js.map +1 -0
  80. package/dist/gmail/types.d.ts +154 -0
  81. package/dist/gmail/types.d.ts.map +1 -0
  82. package/dist/gmail/types.js +5 -0
  83. package/dist/gmail/types.js.map +1 -0
  84. package/dist/index.d.ts +27 -0
  85. package/dist/index.d.ts.map +1 -0
  86. package/dist/index.js +29 -0
  87. package/dist/index.js.map +1 -0
  88. package/dist/safety.d.ts +58 -0
  89. package/dist/safety.d.ts.map +1 -0
  90. package/dist/safety.js +61 -0
  91. package/dist/safety.js.map +1 -0
  92. package/dist/scopes.d.ts +16 -0
  93. package/dist/scopes.d.ts.map +1 -0
  94. package/dist/scopes.js +28 -0
  95. package/dist/scopes.js.map +1 -0
  96. package/dist/tasks/helpers.d.ts +10 -0
  97. package/dist/tasks/helpers.d.ts.map +1 -0
  98. package/dist/tasks/helpers.js +33 -0
  99. package/dist/tasks/helpers.js.map +1 -0
  100. package/dist/tasks/index.d.ts +63 -0
  101. package/dist/tasks/index.d.ts.map +1 -0
  102. package/dist/tasks/index.js +253 -0
  103. package/dist/tasks/index.js.map +1 -0
  104. package/dist/tasks/types.d.ts +79 -0
  105. package/dist/tasks/types.d.ts.map +1 -0
  106. package/dist/tasks/types.js +5 -0
  107. package/dist/tasks/types.js.map +1 -0
  108. package/package.json +73 -4
  109. package/skills/go-easy/SKILL.md +146 -0
  110. package/skills/go-easy/calendar.md +366 -0
  111. package/skills/go-easy/drive.md +309 -0
  112. package/skills/go-easy/gmail.md +478 -0
  113. package/skills/go-easy/tasks.md +260 -0
@@ -0,0 +1,366 @@
1
+ # go-easy: Calendar Reference
2
+
3
+ ## Gateway CLI: `npx go-calendar`
4
+
5
+ ```
6
+ npx go-calendar <account> <command> [args...] [--flags]
7
+ ```
8
+
9
+ All commands output JSON to stdout. Errors output JSON to stderr with exit code 1.
10
+
11
+ ### Commands
12
+
13
+ #### calendars
14
+ List all calendars for the account.
15
+ ```bash
16
+ npx go-calendar <account> calendars
17
+ ```
18
+ Returns: `Array<{ id, summary, description?, primary?, timeZone?, backgroundColor? }>` (bare array)
19
+
20
+ Use `primary` as calendarId for the main calendar in other commands.
21
+
22
+ #### events
23
+ List events on a calendar. By default returns ALL event types (regular, out-of-office,
24
+ working location, focus time, birthdays).
25
+ ```bash
26
+ # Upcoming events (all types)
27
+ npx go-calendar <account> events primary
28
+
29
+ # Date range
30
+ npx go-calendar <account> events primary \
31
+ --from=2026-02-01T00:00:00Z \
32
+ --to=2026-02-28T23:59:59Z
33
+
34
+ # With text search and pagination
35
+ npx go-calendar <account> events primary --query="meeting" --max=10
36
+ npx go-calendar <account> events primary --max=50 --page-token=<token>
37
+
38
+ # Filter by event type
39
+ npx go-calendar <account> events primary --event-types=workingLocation
40
+ npx go-calendar <account> events primary --event-types=default,outOfOffice
41
+ ```
42
+ Returns: `{ items: CalendarEvent[], nextPageToken? }`
43
+
44
+ **Defaults:**
45
+ - `--max`: 20 per page
46
+ - `--from`: now (if omitted, returns events from current time onward)
47
+ - `--to`: no upper bound (if omitted, returns all future events up to `--max`)
48
+ - Recurring events are expanded into individual instances (singleEvents=true)
49
+ - All event types included (the Google API hides workingLocation and birthday by default — go-easy includes them)
50
+
51
+ **Event types**: `default`, `outOfOffice`, `workingLocation`, `focusTime`, `birthday`
52
+
53
+ #### event
54
+ Get a single event by ID.
55
+ ```bash
56
+ npx go-calendar <account> event <calendarId> <eventId>
57
+ ```
58
+ Returns: `CalendarEvent`
59
+
60
+ For recurring events, this returns the recurring event definition, not individual instances.
61
+ Use the `recurringEventId` from a listed instance to find its parent.
62
+
63
+ #### create (WRITE)
64
+ Create a new event.
65
+
66
+ **Required flags:** `--summary`, `--start`, `--end`
67
+ **Optional flags:** `--description`, `--location`, `--tz`, `--attendees`, `--all-day`, `--type` + type-specific flags
68
+
69
+ ```bash
70
+ # Regular timed event
71
+ npx go-calendar <account> create primary \
72
+ --summary="Team Meeting" \
73
+ --start=2026-02-10T10:00:00+01:00 \
74
+ --end=2026-02-10T11:00:00+01:00 \
75
+ --description="Weekly sync" \
76
+ --location="Office"
77
+
78
+ # All-day event (end date is EXCLUSIVE — Feb 14 only)
79
+ npx go-calendar <account> create primary \
80
+ --summary="Company Holiday" \
81
+ --start=2026-02-14 \
82
+ --end=2026-02-15 \
83
+ --all-day
84
+
85
+ # With attendees (invitation emails sent automatically)
86
+ npx go-calendar <account> create primary \
87
+ --summary="Project Review" \
88
+ --start=2026-02-12T14:00:00+01:00 \
89
+ --end=2026-02-12T15:00:00+01:00 \
90
+ --attendees=alice@example.com,bob@example.com
91
+
92
+ # Working location — home office
93
+ npx go-calendar <account> create primary \
94
+ --type=workingLocation \
95
+ --summary="Home" \
96
+ --start=2026-02-10 --end=2026-02-11 --all-day \
97
+ --wl-type=homeOffice
98
+
99
+ # Working location — office
100
+ npx go-calendar <account> create primary \
101
+ --type=workingLocation \
102
+ --summary="Barcelona" \
103
+ --start=2026-02-10 --end=2026-02-11 --all-day \
104
+ --wl-type=officeLocation --wl-label="Barcelona Office"
105
+
106
+ # Out of office ⚠️ --auto-decline sends decline emails to existing invitations
107
+ npx go-calendar <account> create primary \
108
+ --type=outOfOffice \
109
+ --summary="Vacation" \
110
+ --start=2026-02-14T00:00:00+01:00 \
111
+ --end=2026-02-21T00:00:00+01:00 \
112
+ --auto-decline=declineAllConflictingInvitations \
113
+ --decline-message="On vacation, back Feb 21"
114
+
115
+ # Focus time ⚠️ --auto-decline sends decline emails to existing invitations
116
+ npx go-calendar <account> create primary \
117
+ --type=focusTime \
118
+ --summary="Deep Work" \
119
+ --start=2026-02-10T09:00:00+01:00 \
120
+ --end=2026-02-10T12:00:00+01:00 \
121
+ --auto-decline=declineOnlyNewConflictingInvitations \
122
+ --chat-status=doNotDisturb
123
+ ```
124
+ Returns: `{ ok: true, id, htmlLink? }`
125
+
126
+ #### update (WRITE)
127
+ Update an existing event. Uses PATCH — only provided fields are changed, others are preserved.
128
+
129
+ ⚠️ If the event has attendees, update notifications will be sent automatically.
130
+
131
+ ```bash
132
+ # Update just the summary (other fields unchanged)
133
+ npx go-calendar <account> update primary <eventId> \
134
+ --summary="Updated Meeting"
135
+
136
+ # Reschedule
137
+ npx go-calendar <account> update primary <eventId> \
138
+ --summary="Updated Meeting" \
139
+ --start=2026-02-10T11:00:00+01:00 \
140
+ --end=2026-02-10T12:00:00+01:00
141
+
142
+ # Update attendees
143
+ npx go-calendar <account> update primary <eventId> \
144
+ --summary="Review" \
145
+ --start=2026-02-10T14:00:00+01:00 \
146
+ --end=2026-02-10T15:00:00+01:00 \
147
+ --attendees=alice@example.com,bob@example.com,carol@example.com
148
+ ```
149
+ Returns: `{ ok: true, id, htmlLink? }`
150
+
151
+ **Required flags:** `--summary`, `--start`, `--end` (always required even for PATCH — Google API needs them)
152
+ **Optional flags:** `--description`, `--location`, `--tz`, `--attendees`, `--all-day`
153
+
154
+ #### delete ⚠️ DESTRUCTIVE
155
+ Delete an event. Requires `--confirm`.
156
+ ```bash
157
+ npx go-calendar <account> delete primary <eventId> --confirm
158
+ ```
159
+ Returns: `{ ok: true, id }`
160
+
161
+ ⚠️ If the event has attendees, cancellation emails will be sent automatically.
162
+
163
+ Without `--confirm`:
164
+ ```json
165
+ { "blocked": true, "operation": "calendar.delete", "description": "Delete event \"Meeting\" with 3 attendees — cancellation emails will be sent", "hint": "Add --confirm" }
166
+ ```
167
+
168
+ #### freebusy
169
+ Check availability across calendars.
170
+ ```bash
171
+ # Single calendar
172
+ npx go-calendar <account> freebusy primary \
173
+ --from=2026-02-10T00:00:00Z \
174
+ --to=2026-02-10T23:59:59Z
175
+
176
+ # Multiple calendars
177
+ npx go-calendar <account> freebusy primary,colleague@example.com \
178
+ --from=2026-02-10T08:00:00Z \
179
+ --to=2026-02-10T18:00:00Z
180
+ ```
181
+ Returns: `Array<{ calendarId, busy: [{ start, end }] }>`
182
+
183
+ **Required flags:** `--from`, `--to`
184
+
185
+ ## Library API
186
+
187
+ ```typescript
188
+ import { getAuth } from '@marcfargas/go-easy/auth';
189
+ import { listCalendars, listEvents, getEvent, createEvent,
190
+ updateEvent, deleteEvent, queryFreeBusy
191
+ } from '@marcfargas/go-easy/calendar';
192
+ import { setSafetyContext } from '@marcfargas/go-easy';
193
+
194
+ const auth = await getAuth('calendar', '<account>');
195
+
196
+ // List calendars
197
+ const cals = await listCalendars(auth);
198
+
199
+ // List events (with pagination)
200
+ const page1 = await listEvents(auth, 'primary', {
201
+ timeMin: '2026-02-01T00:00:00Z',
202
+ timeMax: '2026-02-28T23:59:59Z',
203
+ maxResults: 50,
204
+ });
205
+ if (page1.nextPageToken) {
206
+ const page2 = await listEvents(auth, 'primary', {
207
+ timeMin: '2026-02-01T00:00:00Z',
208
+ timeMax: '2026-02-28T23:59:59Z',
209
+ maxResults: 50,
210
+ pageToken: page1.nextPageToken,
211
+ });
212
+ }
213
+
214
+ // Get single event
215
+ const event = await getEvent(auth, 'primary', 'eventId');
216
+
217
+ // Create event (WRITE — no safety gate)
218
+ const created = await createEvent(auth, 'primary', {
219
+ summary: 'Meeting',
220
+ start: '2026-02-10T10:00:00+01:00',
221
+ end: '2026-02-10T11:00:00+01:00',
222
+ timeZone: 'Europe/Madrid',
223
+ location: 'Office',
224
+ attendees: ['alice@example.com'],
225
+ });
226
+
227
+ // Update event (WRITE — PATCH semantics)
228
+ await updateEvent(auth, 'primary', 'eventId', {
229
+ summary: 'Updated',
230
+ start: '2026-02-10T11:00:00+01:00',
231
+ end: '2026-02-10T12:00:00+01:00',
232
+ });
233
+
234
+ // Delete event (DESTRUCTIVE — needs safety context)
235
+ setSafetyContext({ confirm: async (op) => { /* ... */ return true; } });
236
+ await deleteEvent(auth, 'primary', 'eventId');
237
+
238
+ // Free/busy
239
+ const availability = await queryFreeBusy(
240
+ auth,
241
+ ['primary', 'colleague@example.com'],
242
+ '2026-02-10T08:00:00Z',
243
+ '2026-02-10T18:00:00Z'
244
+ );
245
+ ```
246
+
247
+ ## Date/Time Formats
248
+
249
+ - **Timed events**: ISO 8601 with offset — `2026-02-10T10:00:00+01:00` or UTC `2026-02-10T09:00:00Z`
250
+ - **All-day events**: Date only — `2026-02-14` (with `--all-day` flag in CLI)
251
+ - **Timezone (`--tz`)**: IANA format — `Europe/Madrid`, `America/New_York`, etc.
252
+
253
+ ### Timezone semantics
254
+
255
+ - If `--start`/`--end` include an offset (e.g. `+01:00`), that offset is used directly
256
+ - `--tz` sets the calendar display timezone (e.g. for recurring events or DST transitions)
257
+ - If `--start`/`--end` are UTC (`Z`) and `--tz` is set, the event displays in that timezone
258
+
259
+ ### All-day event end date
260
+
261
+ All-day end dates are **exclusive** (Google Calendar convention):
262
+ - One day event on Feb 14: `--start=2026-02-14 --end=2026-02-15`
263
+ - Three day event Feb 14–16: `--start=2026-02-14 --end=2026-02-17`
264
+
265
+ ### Recurring events
266
+
267
+ Recurring events are not directly supported for creation/update.
268
+ When listing events, recurring events are expanded into individual instances by default (`singleEvents=true`).
269
+ Each instance has a `recurringEventId` pointing to the parent definition.
270
+
271
+ ## Types
272
+
273
+ ```typescript
274
+ type EventType = 'default' | 'outOfOffice' | 'workingLocation' | 'focusTime' | 'birthday';
275
+
276
+ interface CalendarEvent {
277
+ id: string;
278
+ summary: string;
279
+ description?: string;
280
+ start: string; // ISO 8601 datetime or date
281
+ end: string;
282
+ timeZone?: string;
283
+ location?: string;
284
+ attendees?: Attendee[];
285
+ status?: 'confirmed' | 'tentative' | 'cancelled';
286
+ htmlLink?: string;
287
+ recurringEventId?: string; // parent recurring event (for instances)
288
+ allDay?: boolean;
289
+ organizer?: { email: string; displayName?: string };
290
+ creator?: { email: string; displayName?: string };
291
+ eventType?: EventType;
292
+ workingLocation?: WorkingLocationProperties;
293
+ outOfOffice?: OutOfOfficeProperties;
294
+ focusTime?: FocusTimeProperties;
295
+ birthday?: BirthdayProperties; // read-only
296
+ }
297
+
298
+ interface Attendee {
299
+ email: string;
300
+ displayName?: string;
301
+ responseStatus?: 'needsAction' | 'declined' | 'tentative' | 'accepted';
302
+ organizer?: boolean;
303
+ self?: boolean;
304
+ }
305
+
306
+ interface WorkingLocationProperties {
307
+ type: 'homeOffice' | 'officeLocation' | 'customLocation';
308
+ homeOffice?: true;
309
+ officeLocation?: { buildingId?; deskId?; floorId?; floorSectionId?; label? };
310
+ customLocation?: { label? };
311
+ }
312
+
313
+ interface OutOfOfficeProperties {
314
+ autoDeclineMode?: 'declineNone' | 'declineAllConflictingInvitations' | 'declineOnlyNewConflictingInvitations';
315
+ declineMessage?: string;
316
+ }
317
+
318
+ interface FocusTimeProperties {
319
+ autoDeclineMode?: 'declineNone' | 'declineAllConflictingInvitations' | 'declineOnlyNewConflictingInvitations';
320
+ chatStatus?: string; // 'available' or 'doNotDisturb'
321
+ declineMessage?: string;
322
+ }
323
+
324
+ interface BirthdayProperties { // read-only, cannot be created
325
+ contact?: string;
326
+ type?: 'birthday' | 'anniversary' | 'custom' | 'self';
327
+ customTypeName?: string;
328
+ }
329
+
330
+ interface CalendarInfo {
331
+ id: string;
332
+ summary: string;
333
+ description?: string;
334
+ primary?: boolean;
335
+ timeZone?: string;
336
+ backgroundColor?: string;
337
+ }
338
+
339
+ interface FreeBusyResult {
340
+ calendarId: string;
341
+ busy: { start: string; end: string }[];
342
+ }
343
+ ```
344
+
345
+ ## Error Codes
346
+
347
+ | Code | Meaning | Exit Code |
348
+ |------|---------|-----------|
349
+ | `AUTH_NO_ACCOUNT` | Account not configured | 1 |
350
+ | `AUTH_MISSING_SCOPE` | Account exists but missing Calendar scope | 1 |
351
+ | `AUTH_TOKEN_REVOKED` | Refresh token revoked — re-auth needed | 1 |
352
+ | `AUTH_NO_CREDENTIALS` | OAuth credentials missing | 1 |
353
+ | `NOT_FOUND` | Event not found (404) | 1 |
354
+ | `QUOTA_EXCEEDED` | Calendar API rate limit (429) — wait 30s and retry | 1 |
355
+ | `SAFETY_BLOCKED` | Destructive op without `--confirm` | 2 |
356
+ | `CALENDAR_ERROR` | Other Calendar API error | 1 |
357
+
358
+ Auth errors include a `fix` field: `{ "error": "AUTH_NO_ACCOUNT", "fix": "npx go-easy auth add <email>" }`
359
+
360
+ ## Available Accounts
361
+
362
+ ```bash
363
+ npx go-easy auth list
364
+ ```
365
+
366
+ If an account is missing, add it: `npx go-easy auth add <email>` (see [SKILL.md](SKILL.md) for the full auth workflow).
@@ -0,0 +1,309 @@
1
+ # go-easy: Drive Reference
2
+
3
+ ## Gateway CLI: `npx go-drive`
4
+
5
+ ```
6
+ npx go-drive <account> <command> [args...] [--flags]
7
+ ```
8
+
9
+ All commands output JSON to stdout. Errors output JSON to stderr with exit code 1.
10
+ Shared Drives are supported transparently.
11
+
12
+ ### Commands
13
+
14
+ #### ls
15
+ List files in a folder or by metadata query.
16
+ ```bash
17
+ # List root folder (most recently modified first)
18
+ npx go-drive <account> ls
19
+
20
+ # List specific folder
21
+ npx go-drive <account> ls <folderId>
22
+
23
+ # With metadata query
24
+ npx go-drive <account> ls --query="name contains 'report'"
25
+
26
+ # Combine folder + query + pagination
27
+ npx go-drive <account> ls <folderId> --query="mimeType = 'application/pdf'" --max=10
28
+ npx go-drive <account> ls --max=50 --page-token=<token>
29
+
30
+ # Order by name
31
+ npx go-drive <account> ls --order="name"
32
+ ```
33
+ Returns: `{ items: DriveFile[], nextPageToken? }`
34
+
35
+ **Defaults:**
36
+ - `--max`: 20 per page
37
+ - `--order`: `modifiedTime desc` (most recently modified first)
38
+
39
+ **Valid `--order` values:** `name`, `modifiedTime`, `modifiedTime desc`, `createdTime`, `createdTime desc`, `folder`, `quotaBytesUsed`, `recency`
40
+
41
+ #### search
42
+ Full-text content search (searches inside file contents).
43
+ ```bash
44
+ npx go-drive <account> search "quarterly revenue"
45
+ npx go-drive <account> search "contract clause 5" --max=5
46
+ npx go-drive <account> search "budget" --page-token=<token>
47
+ ```
48
+ Returns: `{ items: DriveFile[], nextPageToken? }`
49
+
50
+ - `--max`: no default (returns all matches, Drive API decides page size)
51
+
52
+ **Note**: `search` searches file *contents*. Use `ls --query` to search by filename/metadata.
53
+
54
+ #### get
55
+ Get file metadata by ID.
56
+ ```bash
57
+ npx go-drive <account> get <fileId>
58
+ ```
59
+ Returns: `DriveFile`
60
+
61
+ #### download
62
+ Download a file (writes to disk).
63
+ ```bash
64
+ npx go-drive <account> download <fileId> # saves as original filename in CWD
65
+ npx go-drive <account> download <fileId> ./output.pdf # saves to specific path
66
+ ```
67
+ Returns: `{ ok: true, path, size, mimeType }`
68
+
69
+ ⚠️ Without a destination path, the file is saved to the **current working directory**. Use `$TEMP` or a specific path for agent workflows:
70
+ ```bash
71
+ npx go-drive <account> download <fileId> "$TEMP/downloaded.pdf"
72
+ ```
73
+
74
+ **Note**: Cannot download Google Workspace files (Docs/Sheets/Slides) — they have no binary content. Use `export` instead. Attempting to download one throws `DRIVE_EXPORT_REQUIRED`.
75
+
76
+ #### export
77
+ Export Google Workspace files to standard formats.
78
+ ```bash
79
+ npx go-drive <account> export <fileId> pdf
80
+ npx go-drive <account> export <fileId> docx ./output.docx
81
+ npx go-drive <account> export <fileId> xlsx
82
+ ```
83
+ Returns: `{ ok: true, path, size, mimeType }`
84
+
85
+ ⚠️ Without a destination path, the file is saved to the **current working directory**.
86
+
87
+ **Export format matrix:**
88
+
89
+ | Source type | Available formats |
90
+ |-------------|-------------------|
91
+ | Google Docs | `pdf`, `docx`, `txt`, `html` |
92
+ | Google Sheets | `pdf`, `xlsx`, `csv` |
93
+ | Google Slides | `pdf`, `pptx` |
94
+ | Google Drawings | `pdf` |
95
+
96
+ Not all combinations work — the table shows supported ones. Unsupported combos return `DRIVE_ERROR`.
97
+
98
+ #### upload (WRITE)
99
+ Upload a file.
100
+ ```bash
101
+ npx go-drive <account> upload ./report.pdf
102
+ npx go-drive <account> upload ./report.pdf --folder=<folderId>
103
+ npx go-drive <account> upload ./report.pdf --name="Q1 Report.pdf"
104
+ ```
105
+ Returns: `{ ok: true, id, name, webViewLink? }`
106
+
107
+ #### mkdir (WRITE)
108
+ Create a folder.
109
+ ```bash
110
+ npx go-drive <account> mkdir "New Folder"
111
+ npx go-drive <account> mkdir "Subfolder" --parent=<folderId>
112
+ ```
113
+ Returns: `{ ok: true, id, name, webViewLink? }`
114
+
115
+ #### move (WRITE)
116
+ Move a file to a different folder.
117
+ ```bash
118
+ npx go-drive <account> move <fileId> <newParentId>
119
+ ```
120
+ Returns: `{ ok: true, id, name, webViewLink? }`
121
+
122
+ #### rename (WRITE)
123
+ Rename a file.
124
+ ```bash
125
+ npx go-drive <account> rename <fileId> "new-name.pdf"
126
+ ```
127
+ Returns: `{ ok: true, id, name, webViewLink? }`
128
+
129
+ #### copy (WRITE)
130
+ Copy a file.
131
+ ```bash
132
+ npx go-drive <account> copy <fileId>
133
+ npx go-drive <account> copy <fileId> --name="Copy of report" --parent=<folderId>
134
+ ```
135
+ Returns: `{ ok: true, id, name, webViewLink? }`
136
+
137
+ Without `--name`, the copy is named "Copy of \<original name\>". Without `--parent`, it stays in the same folder.
138
+
139
+ #### trash ⚠️ DESTRUCTIVE
140
+ Trash a file. Requires `--confirm`.
141
+ ```bash
142
+ npx go-drive <account> trash <fileId> --confirm
143
+ ```
144
+ Returns: `{ ok: true, id, name }`
145
+
146
+ #### permissions
147
+ List sharing permissions on a file.
148
+ ```bash
149
+ npx go-drive <account> permissions <fileId>
150
+ ```
151
+ Returns: `Array<{ id, type, role, emailAddress?, displayName? }>` (bare array)
152
+
153
+ #### share ⚠️ DESTRUCTIVE (when type=anyone or type=domain)
154
+ Share a file. Requires `--confirm` when sharing with "anyone" or "domain" (exposes data broadly).
155
+ ```bash
156
+ # Share with specific user (WRITE — no --confirm needed)
157
+ npx go-drive <account> share <fileId> --type=user --role=reader --email=other@example.com
158
+
159
+ # Share publicly (DESTRUCTIVE — needs --confirm)
160
+ npx go-drive <account> share <fileId> --type=anyone --role=reader --confirm
161
+
162
+ # Share with domain (DESTRUCTIVE — needs --confirm)
163
+ npx go-drive <account> share <fileId> --type=domain --role=reader --domain=example.com --confirm
164
+ ```
165
+ Returns: `{ ok: true, id, name, webViewLink? }`
166
+
167
+ **Roles:** `reader`, `commenter`, `writer`
168
+
169
+ #### unshare ⚠️ DESTRUCTIVE
170
+ Remove a permission. Requires `--confirm`.
171
+ ```bash
172
+ npx go-drive <account> unshare <fileId> <permissionId> --confirm
173
+ ```
174
+ Returns: `{ ok: true, id, name }`
175
+
176
+ Get the `permissionId` from the `permissions` command.
177
+
178
+ ## Library API
179
+
180
+ ```typescript
181
+ import { getAuth } from '@marcfargas/go-easy/auth';
182
+ import { listFiles, searchFiles, getFile, downloadFile, exportFile,
183
+ uploadFile, createFolder, moveFile, renameFile, copyFile,
184
+ trashFile, listPermissions, shareFile, unshareFile
185
+ } from '@marcfargas/go-easy/drive';
186
+
187
+ const auth = await getAuth('drive', '<account>');
188
+
189
+ // List with pagination
190
+ const page1 = await listFiles(auth, { folderId: 'folder-id', maxResults: 50 });
191
+ if (page1.nextPageToken) {
192
+ const page2 = await listFiles(auth, { folderId: 'folder-id', maxResults: 50, pageToken: page1.nextPageToken });
193
+ }
194
+
195
+ // Full-text search
196
+ const results = await searchFiles(auth, { query: 'contract' });
197
+
198
+ // Get metadata
199
+ const file = await getFile(auth, 'file-id');
200
+
201
+ // Download (returns Buffer — library does NOT write to disk)
202
+ const { data, name, mimeType } = await downloadFile(auth, 'file-id');
203
+ // data is a Buffer — write it yourself: fs.writeFileSync('out.pdf', data)
204
+
205
+ // Export (returns Buffer — library does NOT write to disk)
206
+ const exported = await exportFile(auth, 'doc-id', 'pdf');
207
+ // exported.data is a Buffer
208
+
209
+ // Write operations
210
+ const uploaded = await uploadFile(auth, './file.pdf', { folderId: 'folder-id' });
211
+ const folder = await createFolder(auth, 'New Folder', 'parent-id');
212
+ await moveFile(auth, 'file-id', 'new-parent-id');
213
+ await renameFile(auth, 'file-id', 'new-name.pdf');
214
+ await copyFile(auth, 'file-id', 'Copy', 'parent-id');
215
+
216
+ // Destructive (needs safety context)
217
+ await trashFile(auth, 'file-id');
218
+ await shareFile(auth, 'file-id', { type: 'anyone', role: 'reader' });
219
+ await unshareFile(auth, 'file-id', 'permission-id');
220
+
221
+ // Permissions
222
+ const perms = await listPermissions(auth, 'file-id');
223
+ ```
224
+
225
+ **Library vs CLI difference:** `downloadFile` and `exportFile` return `{ data: Buffer, ... }` in the library. The CLI writes to disk and returns `{ path, size }`.
226
+
227
+ ## Drive Query Syntax (for ls --query)
228
+
229
+ Same as Drive API v3 query syntax. **Shell quoting**: wrap the whole query in double quotes; use single quotes for string values inside:
230
+
231
+ ```bash
232
+ npx go-drive <account> ls --query="name contains 'report'"
233
+ npx go-drive <account> ls --query="mimeType = 'application/pdf' and name contains 'invoice'"
234
+ ```
235
+
236
+ Common queries:
237
+ - `name = 'report.pdf'` — exact name match
238
+ - `name contains 'report'` — name contains text
239
+ - `mimeType = 'application/pdf'` — by MIME type
240
+ - `modifiedTime > '2026-01-01'` — modified after date
241
+ - `'me' in owners` — owned by me
242
+ - `sharedWithMe` — shared with me
243
+ - `trashed = false` — not in trash (default)
244
+
245
+ Combine with `and`/`or`:
246
+ ```
247
+ name contains 'report' and mimeType = 'application/pdf'
248
+ ```
249
+
250
+ ### Google Workspace MIME types
251
+
252
+ | Type | MIME type |
253
+ |------|-----------|
254
+ | Folder | `application/vnd.google-apps.folder` |
255
+ | Document | `application/vnd.google-apps.document` |
256
+ | Spreadsheet | `application/vnd.google-apps.spreadsheet` |
257
+ | Presentation | `application/vnd.google-apps.presentation` |
258
+ | Drawing | `application/vnd.google-apps.drawing` |
259
+ | Form | `application/vnd.google-apps.form` |
260
+
261
+ ## Types
262
+
263
+ ```typescript
264
+ interface DriveFile {
265
+ id: string;
266
+ name: string;
267
+ mimeType: string;
268
+ size?: number; // not present for Google Workspace files
269
+ createdTime?: string;
270
+ modifiedTime?: string;
271
+ parents?: string[];
272
+ webViewLink?: string;
273
+ driveId?: string; // Shared Drive ID (present for Shared Drive files)
274
+ shared?: boolean;
275
+ trashed?: boolean;
276
+ }
277
+
278
+ interface DrivePermission {
279
+ id: string;
280
+ type: 'user' | 'group' | 'domain' | 'anyone';
281
+ role: 'owner' | 'organizer' | 'fileOrganizer' | 'writer' | 'commenter' | 'reader';
282
+ emailAddress?: string;
283
+ displayName?: string;
284
+ }
285
+ ```
286
+
287
+ ## Error Codes
288
+
289
+ | Code | Meaning | Exit Code |
290
+ |------|---------|-----------|
291
+ | `AUTH_NO_ACCOUNT` | Account not configured | 1 |
292
+ | `AUTH_MISSING_SCOPE` | Account exists but missing Drive scope | 1 |
293
+ | `AUTH_TOKEN_REVOKED` | Refresh token revoked — re-auth needed | 1 |
294
+ | `AUTH_NO_CREDENTIALS` | OAuth credentials missing | 1 |
295
+ | `NOT_FOUND` | File not found (404) | 1 |
296
+ | `QUOTA_EXCEEDED` | Drive API rate limit (429) — wait 30s and retry | 1 |
297
+ | `SAFETY_BLOCKED` | Destructive op without `--confirm` | 2 |
298
+ | `DRIVE_ERROR` | Other Drive API error | 1 |
299
+ | `DRIVE_EXPORT_REQUIRED` | Tried to download a Google Workspace file — use `export` | 1 |
300
+
301
+ Auth errors include a `fix` field: `{ "error": "AUTH_NO_ACCOUNT", "fix": "npx go-easy auth add <email>" }`
302
+
303
+ ## Available Accounts
304
+
305
+ ```bash
306
+ npx go-easy auth list
307
+ ```
308
+
309
+ If an account is missing, add it: `npx go-easy auth add <email>` (see [SKILL.md](SKILL.md) for the full auth workflow).