zele 0.2.0 → 0.3.5
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/README.md +38 -1
- package/bin/zele +27 -0
- package/dist/api-utils.d.ts +51 -2
- package/dist/api-utils.js +89 -3
- package/dist/api-utils.js.map +1 -1
- package/dist/auth.d.ts +27 -6
- package/dist/auth.js +185 -129
- package/dist/auth.js.map +1 -1
- package/dist/calendar-client.d.ts +16 -9
- package/dist/calendar-client.js +163 -59
- package/dist/calendar-client.js.map +1 -1
- package/dist/cli.js +28 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/attachment.js +17 -15
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/auth-cmd.js +20 -9
- package/dist/commands/auth-cmd.js.map +1 -1
- package/dist/commands/calendar.js +67 -78
- package/dist/commands/calendar.js.map +1 -1
- package/dist/commands/draft.js +25 -18
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/label.js +33 -45
- package/dist/commands/label.js.map +1 -1
- package/dist/commands/mail-actions.js +11 -13
- package/dist/commands/mail-actions.js.map +1 -1
- package/dist/commands/mail.js +114 -128
- package/dist/commands/mail.js.map +1 -1
- package/dist/commands/profile.js +18 -21
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +73 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/db.js +12 -13
- package/dist/db.js.map +1 -1
- package/dist/generated/browser.d.ts +12 -27
- package/dist/generated/client.d.ts +13 -28
- package/dist/generated/client.js +1 -1
- package/dist/generated/commonInputTypes.d.ts +90 -26
- package/dist/generated/enums.d.ts +0 -4
- package/dist/generated/enums.js +0 -3
- package/dist/generated/enums.js.map +1 -1
- package/dist/generated/internal/class.d.ts +22 -55
- package/dist/generated/internal/class.js +12 -4
- package/dist/generated/internal/class.js.map +1 -1
- package/dist/generated/internal/prismaNamespace.d.ts +272 -511
- package/dist/generated/internal/prismaNamespace.js +54 -66
- package/dist/generated/internal/prismaNamespace.js.map +1 -1
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +60 -74
- package/dist/generated/internal/prismaNamespaceBrowser.js +50 -62
- package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/generated/models/Account.d.ts +1637 -0
- package/dist/generated/models/Account.js +2 -0
- package/dist/generated/models/Account.js.map +1 -0
- package/dist/generated/models/CalendarList.d.ts +1161 -0
- package/dist/generated/models/CalendarList.js +2 -0
- package/dist/generated/models/CalendarList.js.map +1 -0
- package/dist/generated/models/Label.d.ts +1161 -0
- package/dist/generated/models/Label.js +2 -0
- package/dist/generated/models/Label.js.map +1 -0
- package/dist/generated/models/Profile.d.ts +1269 -0
- package/dist/generated/models/Profile.js +2 -0
- package/dist/generated/models/Profile.js.map +1 -0
- package/dist/generated/models/SyncState.d.ts +1130 -0
- package/dist/generated/models/SyncState.js +2 -0
- package/dist/generated/models/SyncState.js.map +1 -0
- package/dist/generated/models/Thread.d.ts +1608 -0
- package/dist/generated/models/Thread.js +2 -0
- package/dist/generated/models/Thread.js.map +1 -0
- package/dist/generated/models.d.ts +6 -9
- package/dist/gmail-client.d.ts +119 -94
- package/dist/gmail-client.js +862 -315
- package/dist/gmail-client.js.map +1 -1
- package/dist/mail-tui.d.ts +1 -0
- package/dist/mail-tui.js +517 -0
- package/dist/mail-tui.js.map +1 -0
- package/dist/output.d.ts +6 -4
- package/dist/output.js +124 -17
- package/dist/output.js.map +1 -1
- package/package.json +39 -11
- package/schema.prisma +81 -113
- package/src/api-utils.ts +103 -5
- package/src/auth.ts +224 -143
- package/src/calendar-client.ts +196 -89
- package/src/cli.ts +32 -1
- package/src/commands/attachment.ts +18 -19
- package/src/commands/auth-cmd.ts +19 -9
- package/src/commands/calendar.ts +42 -85
- package/src/commands/draft.ts +19 -22
- package/src/commands/label.ts +21 -57
- package/src/commands/mail-actions.ts +11 -19
- package/src/commands/mail.ts +104 -149
- package/src/commands/profile.ts +12 -28
- package/src/commands/watch.ts +88 -0
- package/src/db.ts +13 -16
- package/src/generated/browser.ts +49 -0
- package/src/generated/client.ts +71 -0
- package/src/generated/commonInputTypes.ts +332 -0
- package/src/generated/enums.ts +17 -0
- package/src/generated/internal/class.ts +250 -0
- package/src/generated/internal/prismaNamespace.ts +1198 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +169 -0
- package/src/generated/models/Account.ts +1848 -0
- package/src/generated/models/CalendarList.ts +1331 -0
- package/src/generated/models/Label.ts +1331 -0
- package/src/generated/models/Profile.ts +1439 -0
- package/src/generated/models/SyncState.ts +1300 -0
- package/src/generated/models/Thread.ts +1787 -0
- package/src/generated/models.ts +17 -0
- package/src/gmail-client.test.ts +59 -0
- package/src/gmail-client.ts +1034 -422
- package/src/mail-tui.tsx +1061 -0
- package/src/output.test.ts +1093 -0
- package/src/output.ts +128 -20
- package/src/schema.sql +58 -68
- package/src/test-fixtures/email-html/safe-claude-event.html +28 -0
- package/src/test-fixtures/email-html/safe-product-announcement.html +25 -0
- package/src/test-fixtures/email-html/safe-tracked-links.html +27 -0
- package/src/test-fixtures/email-html-snapshots/safe-claude-event.html.md +9 -0
- package/src/test-fixtures/email-html-snapshots/safe-product-announcement.html.md +13 -0
- package/src/test-fixtures/email-html-snapshots/safe-tracked-links.html.md +7 -0
- package/AGENTS.md +0 -26
- package/CHANGELOG.md +0 -36
- package/dist/generated/models/accounts.d.ts +0 -2000
- package/dist/generated/models/accounts.js +0 -2
- package/dist/generated/models/accounts.js.map +0 -1
- package/dist/generated/models/calendar_events.d.ts +0 -1433
- package/dist/generated/models/calendar_events.js +0 -2
- package/dist/generated/models/calendar_events.js.map +0 -1
- package/dist/generated/models/calendar_lists.d.ts +0 -1131
- package/dist/generated/models/calendar_lists.js +0 -2
- package/dist/generated/models/calendar_lists.js.map +0 -1
- package/dist/generated/models/label_counts.d.ts +0 -1131
- package/dist/generated/models/label_counts.js +0 -2
- package/dist/generated/models/label_counts.js.map +0 -1
- package/dist/generated/models/labels.d.ts +0 -1131
- package/dist/generated/models/labels.js +0 -2
- package/dist/generated/models/labels.js.map +0 -1
- package/dist/generated/models/profiles.d.ts +0 -1131
- package/dist/generated/models/profiles.js +0 -2
- package/dist/generated/models/profiles.js.map +0 -1
- package/dist/generated/models/sync_states.d.ts +0 -1107
- package/dist/generated/models/sync_states.js +0 -2
- package/dist/generated/models/sync_states.js.map +0 -1
- package/dist/generated/models/thread_lists.d.ts +0 -1404
- package/dist/generated/models/thread_lists.js +0 -2
- package/dist/generated/models/thread_lists.js.map +0 -1
- package/dist/generated/models/threads.d.ts +0 -1247
- package/dist/generated/models/threads.js +0 -2
- package/dist/generated/models/threads.js.map +0 -1
- package/dist/gmail-cache.d.ts +0 -60
- package/dist/gmail-cache.js +0 -264
- package/dist/gmail-cache.js.map +0 -1
- package/docs/gogcli-gmail-implementation.md +0 -599
- package/scripts/test-device-code-clients.ts +0 -186
- package/scripts/test-micropython-scopes.ts +0 -72
- package/scripts/test-oauth-clients.ts +0 -257
- package/src/gmail-cache.ts +0 -339
- package/tsconfig.json +0 -16
package/src/commands/auth-cmd.ts
CHANGED
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
import type { Goke } from 'goke'
|
|
6
6
|
import { login, logout, listAccounts, getAuthStatuses } from '../auth.js'
|
|
7
|
+
import { closePrisma } from '../db.js'
|
|
7
8
|
import * as out from '../output.js'
|
|
9
|
+
import { handleCommandError } from '../output.js'
|
|
8
10
|
|
|
9
11
|
export function registerAuthCommands(cli: Goke) {
|
|
10
12
|
cli
|
|
11
|
-
.command('login', 'Authenticate with Google (opens browser)')
|
|
13
|
+
.command('login', 'Authenticate with Google (opens browser). Run in background via tmux for remote/headless environments. The command prints an authorization URL — show it to the user, ask them to complete consent in their browser, then paste back the localhost redirect URL containing the auth code.')
|
|
12
14
|
.action(async () => {
|
|
13
|
-
const
|
|
15
|
+
const result = await login()
|
|
16
|
+
if (result instanceof Error) handleCommandError(result)
|
|
17
|
+
const { email } = result
|
|
14
18
|
out.success(`Authenticated as ${email}`)
|
|
19
|
+
await closePrisma()
|
|
20
|
+
process.exit(0)
|
|
15
21
|
})
|
|
16
22
|
|
|
17
23
|
cli
|
|
@@ -25,21 +31,23 @@ export function registerAuthCommands(cli: Goke) {
|
|
|
25
31
|
return
|
|
26
32
|
}
|
|
27
33
|
|
|
34
|
+
const emails = [...new Set(accounts.map((a) => a.email))]
|
|
35
|
+
|
|
28
36
|
// If no email specified and multiple accounts: error with list
|
|
29
|
-
if (!email &&
|
|
37
|
+
if (!email && emails.length > 1) {
|
|
30
38
|
out.error('Multiple accounts logged in. Specify which to remove:')
|
|
31
|
-
for (const
|
|
32
|
-
|
|
39
|
+
for (const e of emails) {
|
|
40
|
+
console.error(` ${e}`)
|
|
33
41
|
}
|
|
34
42
|
process.exit(1)
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
// If no email and only one account, use that one
|
|
38
|
-
const targetEmail = email ??
|
|
46
|
+
const targetEmail = email ?? emails[0]!
|
|
39
47
|
|
|
40
|
-
if (!
|
|
48
|
+
if (!emails.includes(targetEmail)) {
|
|
41
49
|
out.error(`Account not found: ${targetEmail}`)
|
|
42
|
-
out.hint(`Logged in accounts: ${
|
|
50
|
+
out.hint(`Logged in accounts: ${emails.join(', ')}`)
|
|
43
51
|
process.exit(1)
|
|
44
52
|
}
|
|
45
53
|
|
|
@@ -62,7 +70,8 @@ export function registerAuthCommands(cli: Goke) {
|
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
await logout(targetEmail)
|
|
73
|
+
const logoutResult = await logout(targetEmail)
|
|
74
|
+
if (logoutResult instanceof Error) handleCommandError(logoutResult)
|
|
66
75
|
out.success(`Credentials removed for ${targetEmail}`)
|
|
67
76
|
})
|
|
68
77
|
|
|
@@ -79,6 +88,7 @@ export function registerAuthCommands(cli: Goke) {
|
|
|
79
88
|
out.printList(
|
|
80
89
|
statuses.map((s) => ({
|
|
81
90
|
email: s.email,
|
|
91
|
+
app_id: s.appId,
|
|
82
92
|
status: 'Authenticated',
|
|
83
93
|
expires: s.expiresAt?.toISOString() ?? 'unknown',
|
|
84
94
|
})),
|
package/src/commands/calendar.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// Calendar commands: list, events, get, create, update, delete, respond, freebusy.
|
|
2
|
-
// Manages Google Calendar with YAML output
|
|
2
|
+
// Manages Google Calendar with YAML output.
|
|
3
|
+
// Cache is handled by the client — commands just call methods and use data.
|
|
3
4
|
// Multi-account: list/events fetch all accounts concurrently and merge by start time.
|
|
4
|
-
// Improved UX over gogcli: multi-account by default, +duration syntax, cleaner output.
|
|
5
5
|
|
|
6
6
|
import type { Goke } from 'goke'
|
|
7
7
|
import { z } from 'zod'
|
|
8
8
|
import readline from 'node:readline'
|
|
9
9
|
import { getCalendarClients, getCalendarClient } from '../auth.js'
|
|
10
10
|
import type { CalendarClient, CalendarEvent, CalendarListItem, EventListResult } from '../calendar-client.js'
|
|
11
|
-
import
|
|
11
|
+
import { AuthError } from '../api-utils.js'
|
|
12
12
|
import * as out from '../output.js'
|
|
13
|
+
import { handleCommandError } from '../output.js'
|
|
13
14
|
import { resolveTimeRange, parseTimeExpression, parseDuration, isDateOnly } from '../calendar-time.js'
|
|
14
15
|
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
@@ -23,41 +24,22 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
23
24
|
|
|
24
25
|
cli
|
|
25
26
|
.command('cal list', 'List calendars')
|
|
26
|
-
.option('--no-cache', 'Skip cache')
|
|
27
27
|
.action(async (options) => {
|
|
28
28
|
const clients = await getCalendarClients(options.account)
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const results = await Promise.all(
|
|
31
31
|
clients.map(async ({ email, client }) => {
|
|
32
|
-
if (!options.noCache) {
|
|
33
|
-
const cached = await cache.getCachedCalendarList<CalendarListItem[]>(email)
|
|
34
|
-
if (cached) return { email, calendars: cached }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
32
|
const calendars = await client.listCalendars()
|
|
38
|
-
|
|
39
|
-
if (!options.noCache) {
|
|
40
|
-
await cache.cacheCalendarList(email, calendars)
|
|
41
|
-
}
|
|
42
|
-
|
|
33
|
+
if (calendars instanceof Error) return calendars
|
|
43
34
|
return { email, calendars }
|
|
44
35
|
}),
|
|
45
36
|
)
|
|
46
37
|
|
|
47
|
-
const allResults =
|
|
48
|
-
|
|
49
|
-
if (r.
|
|
50
|
-
const msg = String(r.reason)
|
|
51
|
-
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
52
|
-
out.error('CalDAV authentication failed. Try: zele login')
|
|
53
|
-
} else {
|
|
54
|
-
out.error(`Failed to fetch calendars: ${msg}`)
|
|
55
|
-
}
|
|
56
|
-
return false
|
|
57
|
-
}
|
|
38
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
39
|
+
if (r instanceof AuthError) { out.error(`${r.message}. Try: zele login`); return false }
|
|
40
|
+
if (r instanceof Error) { out.error(`Failed to fetch calendars: ${r.message}`); return false }
|
|
58
41
|
return true
|
|
59
42
|
})
|
|
60
|
-
.map((r) => r.value)
|
|
61
43
|
|
|
62
44
|
const showAccount = clients.length > 1
|
|
63
45
|
const merged = allResults.flatMap(({ email, calendars }) =>
|
|
@@ -98,7 +80,6 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
98
80
|
.option('--query <query>', 'Free text search')
|
|
99
81
|
.option('--max [max]', 'Max results (default: 20)')
|
|
100
82
|
.option('--page <page>', 'Pagination token')
|
|
101
|
-
.option('--no-cache', 'Skip cache')
|
|
102
83
|
.action(async (options) => {
|
|
103
84
|
const max = options.max ? Number(options.max) : 20
|
|
104
85
|
const calendarId = options.calendar ?? 'primary'
|
|
@@ -115,9 +96,10 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
115
96
|
process.exit(1)
|
|
116
97
|
}
|
|
117
98
|
|
|
118
|
-
const
|
|
99
|
+
const results = await Promise.all(
|
|
119
100
|
clients.map(async ({ email, client }) => {
|
|
120
101
|
const tz = await client.getTimezone(calendarId)
|
|
102
|
+
if (tz instanceof Error) return tz
|
|
121
103
|
const { timeMin, timeMax } = resolveTimeRange({
|
|
122
104
|
from: options.from,
|
|
123
105
|
to: options.to,
|
|
@@ -127,28 +109,15 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
127
109
|
days: options.days,
|
|
128
110
|
}, tz)
|
|
129
111
|
|
|
130
|
-
const cacheParams = {
|
|
131
|
-
calendarId: options.all ? '__all__' : calendarId,
|
|
132
|
-
timeMin,
|
|
133
|
-
timeMax,
|
|
134
|
-
query: options.query,
|
|
135
|
-
maxResults: max,
|
|
136
|
-
pageToken: options.page,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!options.noCache) {
|
|
140
|
-
const cached = await cache.getCachedCalendarEvents<EventListResult>(email, cacheParams)
|
|
141
|
-
if (cached) return { email, result: cached, tz }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
112
|
let result: EventListResult
|
|
145
113
|
|
|
146
114
|
if (options.all) {
|
|
147
115
|
// Fetch from all calendars
|
|
148
116
|
const calendars = await client.listCalendars()
|
|
117
|
+
if (calendars instanceof Error) return calendars
|
|
149
118
|
const allEvents: CalendarEvent[] = []
|
|
150
119
|
|
|
151
|
-
const perCalResults = await Promise.
|
|
120
|
+
const perCalResults = await Promise.all(
|
|
152
121
|
calendars.map(async (cal) => {
|
|
153
122
|
const r = await client.listEvents({
|
|
154
123
|
calendarId: cal.id,
|
|
@@ -157,16 +126,14 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
157
126
|
query: options.query,
|
|
158
127
|
maxResults: max,
|
|
159
128
|
})
|
|
129
|
+
if (r instanceof Error) return r
|
|
160
130
|
return r.events.map((e) => ({ ...e, calendarId: cal.id }))
|
|
161
131
|
}),
|
|
162
132
|
)
|
|
163
133
|
|
|
164
134
|
for (const r of perCalResults) {
|
|
165
|
-
if (r.
|
|
166
|
-
|
|
167
|
-
} else {
|
|
168
|
-
out.error(`Calendar fetch failed: ${r.reason}`)
|
|
169
|
-
}
|
|
135
|
+
if (r instanceof Error) { out.error(r instanceof AuthError ? `${r.message}. Try: zele login` : r.message); continue }
|
|
136
|
+
allEvents.push(...r)
|
|
170
137
|
}
|
|
171
138
|
|
|
172
139
|
result = {
|
|
@@ -175,7 +142,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
175
142
|
timezone: tz,
|
|
176
143
|
}
|
|
177
144
|
} else {
|
|
178
|
-
|
|
145
|
+
const r = await client.listEvents({
|
|
179
146
|
calendarId,
|
|
180
147
|
timeMin,
|
|
181
148
|
timeMax,
|
|
@@ -183,30 +150,19 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
183
150
|
maxResults: max,
|
|
184
151
|
pageToken: options.page,
|
|
185
152
|
})
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (!options.noCache) {
|
|
189
|
-
await cache.cacheCalendarEvents(email, cacheParams, result)
|
|
153
|
+
if (r instanceof Error) return r
|
|
154
|
+
result = r
|
|
190
155
|
}
|
|
191
156
|
|
|
192
157
|
return { email, result, tz }
|
|
193
158
|
}),
|
|
194
159
|
)
|
|
195
160
|
|
|
196
|
-
const allResults =
|
|
197
|
-
|
|
198
|
-
if (r.
|
|
199
|
-
const msg = String(r.reason)
|
|
200
|
-
if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
|
|
201
|
-
out.error('CalDAV authentication failed. Try: zele login')
|
|
202
|
-
} else {
|
|
203
|
-
out.error(`Failed to fetch events: ${msg}`)
|
|
204
|
-
}
|
|
205
|
-
return false
|
|
206
|
-
}
|
|
161
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
162
|
+
if (r instanceof AuthError) { out.error(`${r.message}. Try: zele login`); return false }
|
|
163
|
+
if (r instanceof Error) { out.error(`Failed to fetch events: ${r.message}`); return false }
|
|
207
164
|
return true
|
|
208
165
|
})
|
|
209
|
-
.map((r) => r.value)
|
|
210
166
|
|
|
211
167
|
const showAccount = clients.length > 1
|
|
212
168
|
|
|
@@ -254,6 +210,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
254
210
|
const { client } = await getCalendarClient(options.account)
|
|
255
211
|
|
|
256
212
|
const event = await client.getEvent({ calendarId, eventId })
|
|
213
|
+
if (event instanceof Error) handleCommandError(event)
|
|
257
214
|
const time = out.formatEventTime(event.start, event.end, event.allDay)
|
|
258
215
|
|
|
259
216
|
const doc: Record<string, unknown> = {
|
|
@@ -318,8 +275,9 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
318
275
|
}
|
|
319
276
|
|
|
320
277
|
const calendarId = options.calendar ?? 'primary'
|
|
321
|
-
const {
|
|
278
|
+
const { client } = await getCalendarClient(options.account)
|
|
322
279
|
const tz = await client.getTimezone(calendarId)
|
|
280
|
+
if (tz instanceof Error) handleCommandError(tz)
|
|
323
281
|
|
|
324
282
|
const allDay = options.allDay || (isDateOnly(options.from) && isDateOnly(options.to))
|
|
325
283
|
const start = allDay ? options.from : parseTimeExpression(options.from, tz)
|
|
@@ -346,7 +304,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
346
304
|
|
|
347
305
|
const reminders = options.reminder ? [parseReminder(options.reminder)] : undefined
|
|
348
306
|
|
|
349
|
-
const
|
|
307
|
+
const eventResult = await client.createEvent({
|
|
350
308
|
calendarId,
|
|
351
309
|
summary: options.summary,
|
|
352
310
|
start,
|
|
@@ -362,9 +320,8 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
362
320
|
visibility: options.visibility,
|
|
363
321
|
})
|
|
364
322
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
printEventDetail(event)
|
|
323
|
+
if (eventResult instanceof Error) handleCommandError(eventResult)
|
|
324
|
+
printEventDetail(eventResult)
|
|
368
325
|
out.success('Event created')
|
|
369
326
|
})
|
|
370
327
|
|
|
@@ -387,7 +344,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
387
344
|
.option('--visibility <visibility>', 'Event visibility')
|
|
388
345
|
.action(async (eventId, options) => {
|
|
389
346
|
const calendarId = options.calendar ?? 'primary'
|
|
390
|
-
const {
|
|
347
|
+
const { client } = await getCalendarClient(options.account)
|
|
391
348
|
|
|
392
349
|
const addAttendees = options.addAttendees
|
|
393
350
|
? options.addAttendees.split(',').map((e: string) => e.trim()).filter(Boolean)
|
|
@@ -417,6 +374,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
417
374
|
}
|
|
418
375
|
} else {
|
|
419
376
|
const tz = await client.getTimezone(calendarId)
|
|
377
|
+
if (tz instanceof Error) handleCommandError(tz)
|
|
420
378
|
if (options.from) start = parseTimeExpression(options.from, tz)
|
|
421
379
|
if (options.to) {
|
|
422
380
|
const durationMs = parseDuration(options.to)
|
|
@@ -429,7 +387,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
429
387
|
}
|
|
430
388
|
}
|
|
431
389
|
|
|
432
|
-
const
|
|
390
|
+
const updateResult = await client.updateEvent({
|
|
433
391
|
calendarId,
|
|
434
392
|
eventId,
|
|
435
393
|
summary: options.summary,
|
|
@@ -445,9 +403,8 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
445
403
|
visibility: options.visibility,
|
|
446
404
|
})
|
|
447
405
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
printEventDetail(event)
|
|
406
|
+
if (updateResult instanceof Error) handleCommandError(updateResult)
|
|
407
|
+
printEventDetail(updateResult)
|
|
451
408
|
out.success('Event updated')
|
|
452
409
|
})
|
|
453
410
|
|
|
@@ -461,7 +418,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
461
418
|
.option('--force', 'Skip confirmation')
|
|
462
419
|
.action(async (eventId, options) => {
|
|
463
420
|
const calendarId = options.calendar ?? 'primary'
|
|
464
|
-
const {
|
|
421
|
+
const { client } = await getCalendarClient(options.account)
|
|
465
422
|
|
|
466
423
|
if (!options.force && process.stdin.isTTY) {
|
|
467
424
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
|
|
@@ -476,8 +433,8 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
476
433
|
}
|
|
477
434
|
}
|
|
478
435
|
|
|
479
|
-
await client.deleteEvent({ calendarId, eventId })
|
|
480
|
-
|
|
436
|
+
const deleteResult = await client.deleteEvent({ calendarId, eventId })
|
|
437
|
+
if (deleteResult instanceof Error) handleCommandError(deleteResult)
|
|
481
438
|
|
|
482
439
|
out.printYaml({ deleted: true, id: eventId })
|
|
483
440
|
out.success('Event deleted')
|
|
@@ -505,20 +462,19 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
505
462
|
}
|
|
506
463
|
|
|
507
464
|
const calendarId = options.calendar ?? 'primary'
|
|
508
|
-
const {
|
|
465
|
+
const { client } = await getCalendarClient(options.account)
|
|
509
466
|
|
|
510
|
-
const
|
|
467
|
+
const respondResult = await client.respondToEvent({
|
|
511
468
|
calendarId,
|
|
512
469
|
eventId,
|
|
513
470
|
status: options.status as 'accepted' | 'declined' | 'tentative',
|
|
514
471
|
comment: options.comment,
|
|
515
472
|
})
|
|
516
|
-
|
|
517
|
-
await cache.invalidateCalendarEvents(email)
|
|
473
|
+
if (respondResult instanceof Error) handleCommandError(respondResult)
|
|
518
474
|
|
|
519
475
|
out.printYaml({
|
|
520
|
-
id:
|
|
521
|
-
summary:
|
|
476
|
+
id: respondResult.id,
|
|
477
|
+
summary: respondResult.summary,
|
|
522
478
|
status: options.status,
|
|
523
479
|
...(options.comment ? { comment: options.comment } : {}),
|
|
524
480
|
})
|
|
@@ -545,6 +501,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
545
501
|
|
|
546
502
|
const { client } = await getCalendarClient(options.account)
|
|
547
503
|
const tz = await client.getTimezone()
|
|
504
|
+
if (tz instanceof Error) handleCommandError(tz)
|
|
548
505
|
|
|
549
506
|
const timeMin = parseTimeExpression(options.from, tz)
|
|
550
507
|
const timeMax = parseTimeExpression(options.to, tz)
|
package/src/commands/draft.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// Draft commands: list, create, get, send, delete.
|
|
2
2
|
// Manages Gmail drafts with YAML output for list views.
|
|
3
|
+
// Cache invalidation is handled by the client (sendDraft invalidates threadLists).
|
|
3
4
|
// Multi-account: list fetches all accounts concurrently and merges by date.
|
|
4
5
|
|
|
5
6
|
import type { Goke } from 'goke'
|
|
6
7
|
import { z } from 'zod'
|
|
7
8
|
import fs from 'node:fs'
|
|
8
9
|
import { getClients, getClient } from '../auth.js'
|
|
9
|
-
import { GmailClient } from '../gmail-client.js'
|
|
10
|
-
import
|
|
10
|
+
import type { GmailClient } from '../gmail-client.js'
|
|
11
|
+
import { AuthError } from '../api-utils.js'
|
|
11
12
|
import * as out from '../output.js'
|
|
13
|
+
import { handleCommandError } from '../output.js'
|
|
12
14
|
import pc from 'picocolors'
|
|
13
15
|
|
|
14
16
|
export function registerDraftCommands(cli: Goke) {
|
|
@@ -29,27 +31,24 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
29
31
|
process.exit(1)
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
// Fetch from all accounts concurrently
|
|
33
|
-
const
|
|
34
|
+
// Fetch from all accounts concurrently
|
|
35
|
+
const results = await Promise.all(
|
|
34
36
|
clients.map(async ({ email, client }) => {
|
|
35
37
|
const result = await client.listDrafts({
|
|
36
38
|
query: options.query,
|
|
37
39
|
maxResults: options.max,
|
|
38
40
|
pageToken: options.page,
|
|
39
41
|
})
|
|
42
|
+
if (result instanceof Error) return result
|
|
40
43
|
return { email, result }
|
|
41
44
|
}),
|
|
42
45
|
)
|
|
43
46
|
|
|
44
|
-
const allResults =
|
|
45
|
-
|
|
46
|
-
if (r.
|
|
47
|
-
out.error(`Failed to fetch drafts: ${r.reason}`)
|
|
48
|
-
return false
|
|
49
|
-
}
|
|
47
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
48
|
+
if (r instanceof AuthError) { out.error(`${r.message}. Try: zele login`); return false }
|
|
49
|
+
if (r instanceof Error) { out.error(`Failed to fetch drafts: ${r.message}`); return false }
|
|
50
50
|
return true
|
|
51
51
|
})
|
|
52
|
-
.map((r) => r.value)
|
|
53
52
|
|
|
54
53
|
// Merge drafts from all accounts, sorted by date descending, capped at max
|
|
55
54
|
const merged = allResults
|
|
@@ -88,20 +87,21 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
88
87
|
const { client } = await getClient(options.account)
|
|
89
88
|
|
|
90
89
|
const draft = await client.getDraft({ draftId })
|
|
90
|
+
if (draft instanceof Error) handleCommandError(draft)
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
console.log(pc.bold(`Draft: ${draft.message.subject}`))
|
|
93
|
+
console.log(pc.dim(`Draft ID: ${draft.id}`))
|
|
94
|
+
console.log(`To: ${draft.to.join(', ') || '(none)'}`)
|
|
95
95
|
if (draft.cc.length > 0) {
|
|
96
|
-
|
|
96
|
+
console.log(`Cc: ${draft.cc.join(', ')}`)
|
|
97
97
|
}
|
|
98
98
|
if (draft.bcc.length > 0) {
|
|
99
|
-
|
|
99
|
+
console.log(`Bcc: ${draft.bcc.join(', ')}`)
|
|
100
100
|
}
|
|
101
|
-
|
|
101
|
+
console.log()
|
|
102
102
|
|
|
103
103
|
const body = out.renderEmailBody(draft.message.body, draft.message.mimeType)
|
|
104
|
-
|
|
104
|
+
console.log(body)
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
// =========================================================================
|
|
@@ -167,12 +167,9 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
167
167
|
cli
|
|
168
168
|
.command('draft send <draftId>', 'Send a draft')
|
|
169
169
|
.action(async (draftId, options) => {
|
|
170
|
-
const {
|
|
171
|
-
|
|
170
|
+
const { client } = await getClient(options.account)
|
|
172
171
|
const result = await client.sendDraft({ draftId })
|
|
173
172
|
|
|
174
|
-
await cache.invalidateThreadLists(email)
|
|
175
|
-
|
|
176
173
|
out.printYaml(result)
|
|
177
174
|
out.success('Draft sent')
|
|
178
175
|
})
|
package/src/commands/label.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// Label commands: list, get, create, delete, counts.
|
|
2
|
-
// Manages Gmail labels with YAML output
|
|
2
|
+
// Manages Gmail labels with YAML output.
|
|
3
|
+
// Cache is handled by the client — commands just call methods and use data.
|
|
3
4
|
// Multi-account: list and counts fetch all accounts concurrently and merge.
|
|
4
5
|
|
|
5
6
|
import type { Goke } from 'goke'
|
|
6
7
|
import { z } from 'zod'
|
|
7
8
|
import { getClients, getClient } from '../auth.js'
|
|
8
|
-
import {
|
|
9
|
-
import * as cache from '../gmail-cache.js'
|
|
9
|
+
import { AuthError } from '../api-utils.js'
|
|
10
10
|
import * as out from '../output.js'
|
|
11
11
|
|
|
12
12
|
export function registerLabelCommands(cli: Goke) {
|
|
@@ -16,38 +16,23 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
16
16
|
|
|
17
17
|
cli
|
|
18
18
|
.command('label list', 'List all labels')
|
|
19
|
-
.option('--no-cache', 'Skip cache')
|
|
20
19
|
.action(async (options) => {
|
|
21
20
|
const clients = await getClients(options.account)
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// Fetch from all accounts concurrently, tolerating individual failures
|
|
26
|
-
const settled = await Promise.allSettled(
|
|
22
|
+
// Fetch from all accounts concurrently
|
|
23
|
+
const results = await Promise.all(
|
|
27
24
|
clients.map(async ({ email, client }) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const labels = await client.listLabels()
|
|
34
|
-
if (!options.noCache) {
|
|
35
|
-
await cache.cacheLabels(email, labels)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return { email, labels }
|
|
25
|
+
const labelsResult = await client.listLabels()
|
|
26
|
+
if (labelsResult instanceof Error) return labelsResult
|
|
27
|
+
return { email, labels: labelsResult.parsed }
|
|
39
28
|
}),
|
|
40
29
|
)
|
|
41
30
|
|
|
42
|
-
const allResults =
|
|
43
|
-
|
|
44
|
-
if (r.
|
|
45
|
-
out.error(`Failed to fetch labels: ${r.reason}`)
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
31
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
32
|
+
if (r instanceof AuthError) { out.error(`${r.message}. Try: zele login`); return false }
|
|
33
|
+
if (r instanceof Error) { out.error(`Failed to fetch labels: ${r.message}`); return false }
|
|
48
34
|
return true
|
|
49
35
|
})
|
|
50
|
-
.map((r) => r.value)
|
|
51
36
|
|
|
52
37
|
// Merge labels from all accounts
|
|
53
38
|
const merged = allResults.flatMap(({ email, labels }) =>
|
|
@@ -109,7 +94,7 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
109
94
|
.option('--bg-color <bgColor>', z.string().describe('Background color (hex, e.g. #4986e7)'))
|
|
110
95
|
.option('--text-color <textColor>', z.string().describe('Text color (hex, e.g. #ffffff)'))
|
|
111
96
|
.action(async (name, options) => {
|
|
112
|
-
const {
|
|
97
|
+
const { client } = await getClient(options.account)
|
|
113
98
|
|
|
114
99
|
const result = await client.createLabel({
|
|
115
100
|
name,
|
|
@@ -118,8 +103,6 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
118
103
|
: undefined,
|
|
119
104
|
})
|
|
120
105
|
|
|
121
|
-
await cache.invalidateLabels(email)
|
|
122
|
-
|
|
123
106
|
out.printYaml(result)
|
|
124
107
|
out.success(`Label created: "${result.name}"`)
|
|
125
108
|
})
|
|
@@ -146,13 +129,9 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
146
129
|
}
|
|
147
130
|
}
|
|
148
131
|
|
|
149
|
-
const {
|
|
150
|
-
|
|
132
|
+
const { client } = await getClient(options.account)
|
|
151
133
|
await client.deleteLabel({ labelId })
|
|
152
134
|
|
|
153
|
-
await cache.invalidateLabels(email)
|
|
154
|
-
await cache.invalidateLabelCounts(email)
|
|
155
|
-
|
|
156
135
|
out.printYaml({ label_id: labelId, deleted: true })
|
|
157
136
|
})
|
|
158
137
|
|
|
@@ -162,38 +141,23 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
162
141
|
|
|
163
142
|
cli
|
|
164
143
|
.command('label counts', 'Show unread counts per label')
|
|
165
|
-
.option('--no-cache', 'Skip cache')
|
|
166
144
|
.action(async (options) => {
|
|
167
145
|
const clients = await getClients(options.account)
|
|
168
146
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Fetch from all accounts concurrently, tolerating individual failures
|
|
172
|
-
const settled = await Promise.allSettled(
|
|
147
|
+
// Fetch from all accounts concurrently
|
|
148
|
+
const results = await Promise.all(
|
|
173
149
|
clients.map(async ({ email, client }) => {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const counts = await client.getLabelCounts()
|
|
180
|
-
if (!options.noCache) {
|
|
181
|
-
await cache.cacheLabelCounts(email, counts)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { email, counts }
|
|
150
|
+
const countsResult = await client.getLabelCounts()
|
|
151
|
+
if (countsResult instanceof Error) return countsResult
|
|
152
|
+
return { email, counts: countsResult.parsed }
|
|
185
153
|
}),
|
|
186
154
|
)
|
|
187
155
|
|
|
188
|
-
const allResults =
|
|
189
|
-
|
|
190
|
-
if (r.
|
|
191
|
-
out.error(`Failed to fetch counts: ${r.reason}`)
|
|
192
|
-
return false
|
|
193
|
-
}
|
|
156
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
157
|
+
if (r instanceof AuthError) { out.error(`${r.message}. Try: zele login`); return false }
|
|
158
|
+
if (r instanceof Error) { out.error(`Failed to fetch counts: ${r.message}`); return false }
|
|
194
159
|
return true
|
|
195
160
|
})
|
|
196
|
-
.map((r) => r.value)
|
|
197
161
|
|
|
198
162
|
// Merge counts from all accounts
|
|
199
163
|
const merged = allResults.flatMap(({ email, counts }) =>
|