zele 0.3.15 → 0.3.17
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 +113 -33
- package/dist/api-utils.d.ts +7 -0
- package/dist/api-utils.js +12 -0
- package/dist/api-utils.js.map +1 -1
- package/dist/auth.d.ts +71 -9
- package/dist/auth.js +187 -11
- package/dist/auth.js.map +1 -1
- package/dist/calendar-time.js +6 -0
- package/dist/calendar-time.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/attachment.js +2 -0
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/auth-cmd.js +104 -6
- package/dist/commands/auth-cmd.js.map +1 -1
- package/dist/commands/calendar.js +1 -1
- package/dist/commands/calendar.js.map +1 -1
- package/dist/commands/draft.js +9 -3
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/filter.d.ts +2 -0
- package/dist/commands/filter.js +64 -0
- package/dist/commands/filter.js.map +1 -0
- package/dist/commands/label.js +19 -9
- package/dist/commands/label.js.map +1 -1
- package/dist/commands/mail-actions.js +12 -2
- package/dist/commands/mail-actions.js.map +1 -1
- package/dist/commands/mail.js +194 -84
- package/dist/commands/mail.js.map +1 -1
- package/dist/commands/profile.js +25 -18
- package/dist/commands/profile.js.map +1 -1
- package/dist/db.js +24 -0
- package/dist/db.js.map +1 -1
- package/dist/generated/internal/class.js +2 -2
- package/dist/generated/internal/class.js.map +1 -1
- package/dist/generated/internal/prismaNamespace.d.ts +2 -0
- package/dist/generated/internal/prismaNamespace.js +2 -0
- package/dist/generated/internal/prismaNamespace.js.map +1 -1
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +2 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +2 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/generated/models/Account.d.ts +97 -1
- package/dist/gmail-client.d.ts +42 -0
- package/dist/gmail-client.js +175 -9
- package/dist/gmail-client.js.map +1 -1
- package/dist/imap-smtp-client.d.ts +235 -0
- package/dist/imap-smtp-client.js +1225 -0
- package/dist/imap-smtp-client.js.map +1 -0
- package/dist/mail-tui.js +3 -2
- package/dist/mail-tui.js.map +1 -1
- package/dist/output.d.ts +2 -0
- package/dist/output.js +4 -0
- package/dist/output.js.map +1 -1
- package/package.json +9 -5
- package/schema.prisma +7 -5
- package/skills/zele/SKILL.md +141 -0
- package/src/api-utils.ts +13 -0
- package/src/auth.ts +283 -15
- package/src/calendar-time.test.ts +35 -0
- package/src/calendar-time.ts +5 -0
- package/src/cli.ts +6 -1
- package/src/commands/attachment.ts +1 -0
- package/src/commands/auth-cmd.ts +112 -6
- package/src/commands/calendar.ts +1 -1
- package/src/commands/draft.ts +7 -3
- package/src/commands/filter.ts +74 -0
- package/src/commands/label.ts +22 -11
- package/src/commands/mail-actions.ts +16 -3
- package/src/commands/mail.ts +207 -89
- package/src/commands/profile.ts +27 -17
- package/src/db.ts +28 -0
- package/src/generated/internal/class.ts +2 -2
- package/src/generated/internal/prismaNamespace.ts +2 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +2 -0
- package/src/generated/models/Account.ts +97 -1
- package/src/gmail-client.test.ts +155 -2
- package/src/gmail-client.ts +221 -16
- package/src/imap-smtp-client.ts +1381 -0
- package/src/mail-tui.tsx +3 -3
- package/src/output.ts +8 -1
- package/src/schema.sql +2 -0
package/src/commands/calendar.ts
CHANGED
|
@@ -78,7 +78,7 @@ export function registerCalendarCommands(cli: Goke) {
|
|
|
78
78
|
.option('--all', 'Fetch from all calendars')
|
|
79
79
|
.option('--query <query>', 'Free text search')
|
|
80
80
|
.option('--max [max]', 'Max results (default: 20)')
|
|
81
|
-
.option('--page <page>', 'Pagination token')
|
|
81
|
+
.option('--page <page>', 'Pagination token (requires --account, only works for a single account)')
|
|
82
82
|
.action(async (options) => {
|
|
83
83
|
const max = options.max ? Number(options.max) : 20
|
|
84
84
|
const calendarId = options.calendar ?? 'primary'
|
package/src/commands/draft.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { z } from 'zod'
|
|
|
8
8
|
import fs from 'node:fs'
|
|
9
9
|
import { getClients, getClient } from '../auth.js'
|
|
10
10
|
import type { GmailClient } from '../gmail-client.js'
|
|
11
|
+
import type { ImapSmtpClient } from '../imap-smtp-client.js'
|
|
11
12
|
import { AuthError } from '../api-utils.js'
|
|
12
13
|
import * as out from '../output.js'
|
|
13
14
|
import { handleCommandError } from '../output.js'
|
|
@@ -21,7 +22,7 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
21
22
|
cli
|
|
22
23
|
.command('draft list', 'List drafts')
|
|
23
24
|
.option('--max <max>', z.number().default(20).describe('Max results'))
|
|
24
|
-
.option('--page <page>', z.string().describe('Pagination token'))
|
|
25
|
+
.option('--page <page>', z.string().describe('Pagination token (requires --account, only works for a single account)'))
|
|
25
26
|
.option('--query <query>', z.string().describe('Search query'))
|
|
26
27
|
.action(async (options) => {
|
|
27
28
|
const clients = await getClients(options.account)
|
|
@@ -72,7 +73,7 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
72
73
|
subject: d.subject,
|
|
73
74
|
date: out.formatDate(d.date),
|
|
74
75
|
})),
|
|
75
|
-
{ summary: `${merged.length} draft(s)
|
|
76
|
+
{ summary: `${merged.length} draft(s)`, nextPage: allResults[0]?.result.nextPageToken },
|
|
76
77
|
)
|
|
77
78
|
})
|
|
78
79
|
|
|
@@ -154,6 +155,7 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
154
155
|
threadId: options.thread,
|
|
155
156
|
fromEmail: options.from,
|
|
156
157
|
})
|
|
158
|
+
if (result instanceof Error) handleCommandError(result)
|
|
157
159
|
|
|
158
160
|
out.printYaml(result)
|
|
159
161
|
out.success('Draft created')
|
|
@@ -168,6 +170,7 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
168
170
|
.action(async (draftId, options) => {
|
|
169
171
|
const { client } = await getClient(options.account)
|
|
170
172
|
const result = await client.sendDraft({ draftId })
|
|
173
|
+
if (result instanceof Error) handleCommandError(result)
|
|
171
174
|
|
|
172
175
|
out.printYaml(result)
|
|
173
176
|
out.success('Draft sent')
|
|
@@ -197,7 +200,8 @@ export function registerDraftCommands(cli: Goke) {
|
|
|
197
200
|
|
|
198
201
|
const { client } = await getClient(options.account)
|
|
199
202
|
|
|
200
|
-
await client.deleteDraft({ draftId })
|
|
203
|
+
const deleteResult = await client.deleteDraft({ draftId })
|
|
204
|
+
if (deleteResult instanceof Error) handleCommandError(deleteResult)
|
|
201
205
|
|
|
202
206
|
out.printYaml({ draft_id: draftId, deleted: true })
|
|
203
207
|
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Filter commands: list, create, delete Gmail filters.
|
|
2
|
+
// Multi-account support via getClients/getClient like label.ts.
|
|
3
|
+
|
|
4
|
+
import type { Goke } from 'goke'
|
|
5
|
+
import { getClients } from '../auth.js'
|
|
6
|
+
import { AuthError, UnsupportedError, isScopeError } from '../api-utils.js'
|
|
7
|
+
import type { GmailClient } from '../gmail-client.js'
|
|
8
|
+
import * as out from '../output.js'
|
|
9
|
+
import { handleCommandError } from '../output.js'
|
|
10
|
+
|
|
11
|
+
export function registerFilterCommands(cli: Goke) {
|
|
12
|
+
// =========================================================================
|
|
13
|
+
// filter list
|
|
14
|
+
// =========================================================================
|
|
15
|
+
|
|
16
|
+
cli
|
|
17
|
+
.command('mail filter list', 'List all Gmail filters')
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
const clients = await getClients(options.account)
|
|
20
|
+
const googleClients = clients.filter((c) => c.accountType === 'google')
|
|
21
|
+
if (googleClients.length === 0) {
|
|
22
|
+
handleCommandError(new UnsupportedError({ feature: 'Filters', accountType: 'IMAP/SMTP', hint: 'Filters are a Gmail-specific feature.' }))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const results = await Promise.all(
|
|
26
|
+
googleClients.map(async ({ email, client }) => {
|
|
27
|
+
const res = await (client as GmailClient).listFilters()
|
|
28
|
+
if (res instanceof Error) return res
|
|
29
|
+
return { email, filters: res.parsed }
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
const allResults = results.filter((r): r is Exclude<typeof r, Error> => {
|
|
34
|
+
if (r instanceof AuthError) {
|
|
35
|
+
if (isScopeError(r)) {
|
|
36
|
+
out.error(`Missing required OAuth scopes. Run: zele login to grant updated permissions`)
|
|
37
|
+
} else {
|
|
38
|
+
out.error(`${r.message}. Try: zele login`)
|
|
39
|
+
}
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
if (r instanceof Error) { out.error(`Failed to fetch filters: ${r.message}`); return false }
|
|
43
|
+
return true
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const merged = allResults.flatMap(({ email, filters }) =>
|
|
47
|
+
filters.map((f) => ({ ...f, account: email })),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
if (merged.length === 0) {
|
|
51
|
+
out.hint('No filters found')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const showAccount = clients.length > 1
|
|
56
|
+
for (const f of merged) {
|
|
57
|
+
out.printYaml({
|
|
58
|
+
...(showAccount ? { account: f.account } : {}),
|
|
59
|
+
id: f.id,
|
|
60
|
+
criteria: f.criteria,
|
|
61
|
+
action: f.action,
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
out.hint(`${merged.length} filter(s)`)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// TODO: add `mail filter create` and `mail filter delete` commands once
|
|
69
|
+
// gmail.settings.basic scope is added to the GCP OAuth consent screen.
|
|
70
|
+
// The https://mail.google.com/ scope covers reading filters but Google
|
|
71
|
+
// enforces the narrower scope for write operations (create/delete).
|
|
72
|
+
// The gmail-client.ts methods (createFilter, deleteFilter, resolveLabel)
|
|
73
|
+
// are already implemented and ready to use.
|
|
74
|
+
}
|
package/src/commands/label.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Goke } from 'goke'
|
|
7
7
|
import { z } from 'zod'
|
|
8
|
-
import { getClients,
|
|
9
|
-
import { AuthError } from '../api-utils.js'
|
|
8
|
+
import { getClients, getGmailClient } from '../auth.js'
|
|
9
|
+
import { AuthError, UnsupportedError } from '../api-utils.js'
|
|
10
|
+
import type { GmailClient } from '../gmail-client.js'
|
|
10
11
|
import * as out from '../output.js'
|
|
12
|
+
import { handleCommandError } from '../output.js'
|
|
11
13
|
|
|
12
14
|
export function registerLabelCommands(cli: Goke) {
|
|
13
15
|
// =========================================================================
|
|
@@ -18,11 +20,16 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
18
20
|
.command('label list', 'List all labels')
|
|
19
21
|
.action(async (options) => {
|
|
20
22
|
const clients = await getClients(options.account)
|
|
23
|
+
// Labels are Google-only — filter to Google accounts
|
|
24
|
+
const googleClients = clients.filter((c) => c.accountType === 'google')
|
|
25
|
+
if (googleClients.length === 0) {
|
|
26
|
+
handleCommandError(new UnsupportedError({ feature: 'Labels', accountType: 'IMAP/SMTP', hint: 'IMAP accounts use folders. Use --folder to browse different mailboxes.' }))
|
|
27
|
+
}
|
|
21
28
|
|
|
22
|
-
// Fetch from all accounts concurrently
|
|
29
|
+
// Fetch from all Google accounts concurrently
|
|
23
30
|
const results = await Promise.all(
|
|
24
|
-
|
|
25
|
-
const labelsResult = await client.listLabels()
|
|
31
|
+
googleClients.map(async ({ email, client }) => {
|
|
32
|
+
const labelsResult = await (client as GmailClient).listLabels()
|
|
26
33
|
if (labelsResult instanceof Error) return labelsResult
|
|
27
34
|
return { email, labels: labelsResult.parsed }
|
|
28
35
|
}),
|
|
@@ -69,7 +76,7 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
69
76
|
cli
|
|
70
77
|
.command('label get <labelId>', 'Get label details with counts')
|
|
71
78
|
.action(async (labelId, options) => {
|
|
72
|
-
const { client } = await
|
|
79
|
+
const { client } = await getGmailClient(options.account)
|
|
73
80
|
|
|
74
81
|
const label = await client.getLabel({ labelId })
|
|
75
82
|
|
|
@@ -93,7 +100,7 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
93
100
|
.option('--bg-color <bgColor>', z.string().describe('Background color (hex, e.g. #4986e7)'))
|
|
94
101
|
.option('--text-color <textColor>', z.string().describe('Text color (hex, e.g. #ffffff)'))
|
|
95
102
|
.action(async (name, options) => {
|
|
96
|
-
const { client } = await
|
|
103
|
+
const { client } = await getGmailClient(options.account)
|
|
97
104
|
|
|
98
105
|
const result = await client.createLabel({
|
|
99
106
|
name,
|
|
@@ -128,7 +135,7 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
128
135
|
}
|
|
129
136
|
}
|
|
130
137
|
|
|
131
|
-
const { client } = await
|
|
138
|
+
const { client } = await getGmailClient(options.account)
|
|
132
139
|
await client.deleteLabel({ labelId })
|
|
133
140
|
|
|
134
141
|
out.printYaml({ label_id: labelId, deleted: true })
|
|
@@ -142,11 +149,15 @@ export function registerLabelCommands(cli: Goke) {
|
|
|
142
149
|
.command('label counts', 'Show unread counts per label')
|
|
143
150
|
.action(async (options) => {
|
|
144
151
|
const clients = await getClients(options.account)
|
|
152
|
+
const googleClients = clients.filter((c) => c.accountType === 'google')
|
|
153
|
+
if (googleClients.length === 0) {
|
|
154
|
+
handleCommandError(new UnsupportedError({ feature: 'Label counts', accountType: 'IMAP/SMTP', hint: 'IMAP accounts use folders, not labels.' }))
|
|
155
|
+
}
|
|
145
156
|
|
|
146
|
-
// Fetch from all accounts concurrently
|
|
157
|
+
// Fetch from all Google accounts concurrently
|
|
147
158
|
const results = await Promise.all(
|
|
148
|
-
|
|
149
|
-
const countsResult = await client.getLabelCounts()
|
|
159
|
+
googleClients.map(async ({ email, client }) => {
|
|
160
|
+
const countsResult = await (client as GmailClient).getLabelCounts()
|
|
150
161
|
if (countsResult instanceof Error) return countsResult
|
|
151
162
|
return { email, counts: countsResult.parsed }
|
|
152
163
|
}),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
// Mail action commands: star, unstar, archive, trash, untrash, mark read/unread, label modify.
|
|
1
|
+
// Mail action commands: star, unstar, archive, trash, untrash, mark read/unread, spam, unspam, label modify.
|
|
2
2
|
// Bulk operations on threads — cache invalidation is handled by the client methods.
|
|
3
3
|
|
|
4
4
|
import type { Goke } from 'goke'
|
|
5
5
|
import { z } from 'zod'
|
|
6
6
|
import { getClient } from '../auth.js'
|
|
7
7
|
import type { GmailClient } from '../gmail-client.js'
|
|
8
|
+
import type { ImapSmtpClient } from '../imap-smtp-client.js'
|
|
8
9
|
import * as out from '../output.js'
|
|
9
10
|
import { handleCommandError } from '../output.js'
|
|
10
11
|
|
|
@@ -16,7 +17,7 @@ async function bulkAction(
|
|
|
16
17
|
threadIds: string[],
|
|
17
18
|
actionName: string,
|
|
18
19
|
accountFilter: string[] | undefined,
|
|
19
|
-
fn: (client: GmailClient, ids: string[]) => Promise<void | Error>,
|
|
20
|
+
fn: (client: GmailClient | ImapSmtpClient, ids: string[]) => Promise<void | Error>,
|
|
20
21
|
) {
|
|
21
22
|
if (threadIds.length === 0) {
|
|
22
23
|
out.error('No thread IDs provided')
|
|
@@ -66,7 +67,7 @@ export function registerMailActionCommands(cli: Goke) {
|
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
cli
|
|
69
|
-
.command('mail read-mark [...threadIds]', 'Mark threads as read')
|
|
70
|
+
.command('mail read-mark [...threadIds]', 'Mark threads as read (removes UNREAD label)')
|
|
70
71
|
.action(async (threadIds, options) => {
|
|
71
72
|
await bulkAction(threadIds, 'Marked as read', options.account, (c, ids) => c.markAsRead({ threadIds: ids }))
|
|
72
73
|
})
|
|
@@ -77,6 +78,18 @@ export function registerMailActionCommands(cli: Goke) {
|
|
|
77
78
|
await bulkAction(threadIds, 'Marked as unread', options.account, (c, ids) => c.markAsUnread({ threadIds: ids }))
|
|
78
79
|
})
|
|
79
80
|
|
|
81
|
+
cli
|
|
82
|
+
.command('mail spam [...threadIds]', 'Mark threads as spam')
|
|
83
|
+
.action(async (threadIds, options) => {
|
|
84
|
+
await bulkAction(threadIds, 'Marked as spam', options.account, (c, ids) => c.markAsSpam({ threadIds: ids }))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
cli
|
|
88
|
+
.command('mail unspam [...threadIds]', 'Remove threads from spam')
|
|
89
|
+
.action(async (threadIds, options) => {
|
|
90
|
+
await bulkAction(threadIds, 'Removed from spam', options.account, (c, ids) => c.unmarkSpam({ threadIds: ids }))
|
|
91
|
+
})
|
|
92
|
+
|
|
80
93
|
cli
|
|
81
94
|
.command('mail label [...threadIds]', 'Add or remove labels from threads')
|
|
82
95
|
.option('--add <add>', z.string().describe('Labels to add (comma-separated)'))
|