zele 0.3.16 → 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 +91 -36
- package/dist/api-utils.d.ts +4 -0
- package/dist/api-utils.js +6 -0
- package/dist/api-utils.js.map +1 -1
- package/dist/auth.d.ts +71 -9
- package/dist/auth.js +186 -10
- package/dist/auth.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/draft.js +7 -1
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/filter.js +7 -2
- package/dist/commands/filter.js.map +1 -1
- package/dist/commands/label.js +19 -9
- package/dist/commands/label.js.map +1 -1
- package/dist/commands/mail-actions.js.map +1 -1
- package/dist/commands/mail.js +49 -22
- 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 +14 -0
- package/dist/gmail-client.js +46 -0
- 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.map +1 -1
- package/package.json +5 -2
- package/schema.prisma +7 -5
- package/skills/zele/SKILL.md +50 -21
- package/src/api-utils.ts +6 -0
- package/src/auth.ts +282 -14
- package/src/commands/attachment.ts +1 -0
- package/src/commands/auth-cmd.ts +112 -6
- package/src/commands/draft.ts +5 -1
- package/src/commands/filter.ts +9 -3
- package/src/commands/label.ts +22 -11
- package/src/commands/mail-actions.ts +2 -1
- package/src/commands/mail.ts +52 -22
- 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 +65 -0
- package/src/imap-smtp-client.ts +1381 -0
- package/src/mail-tui.tsx +2 -1
- package/src/schema.sql +2 -0
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
|
}),
|
|
@@ -5,6 +5,7 @@ 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')
|
package/src/commands/mail.ts
CHANGED
|
@@ -11,6 +11,7 @@ import React from 'react'
|
|
|
11
11
|
import { lookup as mimeLookup } from 'mrmime'
|
|
12
12
|
import { getClients, getClient, listAccounts, login } from '../auth.js'
|
|
13
13
|
import type { ThreadListResult } from '../gmail-client.js'
|
|
14
|
+
import type { GmailClient } from '../gmail-client.js'
|
|
14
15
|
import { AuthError } from '../api-utils.js'
|
|
15
16
|
import * as out from '../output.js'
|
|
16
17
|
import { handleCommandError } from '../output.js'
|
|
@@ -66,19 +67,24 @@ export function registerMailCommands(cli: Goke) {
|
|
|
66
67
|
|
|
67
68
|
// Fetch threads and labels from all accounts concurrently
|
|
68
69
|
const results = await Promise.all(
|
|
69
|
-
clients.map(async ({ email, client }) => {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}),
|
|
78
|
-
client.listLabels(),
|
|
79
|
-
])
|
|
70
|
+
clients.map(async ({ email, client, accountType }) => {
|
|
71
|
+
const result = await client.listThreads({
|
|
72
|
+
folder,
|
|
73
|
+
maxResults: max,
|
|
74
|
+
labelIds: options.label ? [options.label] : undefined,
|
|
75
|
+
pageToken: options.page,
|
|
76
|
+
query: options.filter,
|
|
77
|
+
})
|
|
80
78
|
if (result instanceof Error) return result
|
|
81
|
-
|
|
79
|
+
|
|
80
|
+
// Labels are Google-only — skip for IMAP accounts
|
|
81
|
+
let labelMap = new Map<string, string>()
|
|
82
|
+
if (accountType === 'google') {
|
|
83
|
+
const labelsResult = await (client as GmailClient).listLabels()
|
|
84
|
+
if (!(labelsResult instanceof Error)) {
|
|
85
|
+
labelMap = new Map(labelsResult.parsed.map((l) => [l.id, l.name]))
|
|
86
|
+
}
|
|
87
|
+
}
|
|
82
88
|
return { email, result, labelMap }
|
|
83
89
|
}),
|
|
84
90
|
)
|
|
@@ -150,17 +156,21 @@ export function registerMailCommands(cli: Goke) {
|
|
|
150
156
|
|
|
151
157
|
// Search all accounts concurrently (fetch labels alongside for name resolution)
|
|
152
158
|
const results = await Promise.all(
|
|
153
|
-
clients.map(async ({ email, client }) => {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}),
|
|
160
|
-
client.listLabels(),
|
|
161
|
-
])
|
|
159
|
+
clients.map(async ({ email, client, accountType }) => {
|
|
160
|
+
const result = await client.listThreads({
|
|
161
|
+
query,
|
|
162
|
+
maxResults: max,
|
|
163
|
+
pageToken: options.page,
|
|
164
|
+
})
|
|
162
165
|
if (result instanceof Error) return result
|
|
163
|
-
|
|
166
|
+
|
|
167
|
+
let labelMap = new Map<string, string>()
|
|
168
|
+
if (accountType === 'google') {
|
|
169
|
+
const labelsResult = await (client as GmailClient).listLabels()
|
|
170
|
+
if (!(labelsResult instanceof Error)) {
|
|
171
|
+
labelMap = new Map(labelsResult.parsed.map((l) => [l.id, l.name]))
|
|
172
|
+
}
|
|
173
|
+
}
|
|
164
174
|
return { email, result, labelMap }
|
|
165
175
|
}),
|
|
166
176
|
)
|
|
@@ -220,6 +230,7 @@ export function registerMailCommands(cli: Goke) {
|
|
|
220
230
|
.command('mail read [...threadIds]', 'Read full email threads (does not mark as read)')
|
|
221
231
|
.option('--raw', 'Show raw message (first message only, single thread)')
|
|
222
232
|
.option('--raw-html', 'Show raw HTML body per message (no markdown conversion)')
|
|
233
|
+
.option('--verify', 'Show expanded email authentication details (SPF/DKIM/DMARC)')
|
|
223
234
|
.action(async (threadIds, options) => {
|
|
224
235
|
if (threadIds.length === 0) {
|
|
225
236
|
out.error('No thread IDs provided')
|
|
@@ -323,6 +334,24 @@ export function registerMailCommands(cli: Goke) {
|
|
|
323
334
|
}
|
|
324
335
|
console.log(pc.dim(`Date: ${dateStr}`))
|
|
325
336
|
|
|
337
|
+
if (msg.auth) {
|
|
338
|
+
const check = (verdict: string) => {
|
|
339
|
+
return verdict === 'pass'
|
|
340
|
+
? pc.green('✓')
|
|
341
|
+
: pc.red('✗')
|
|
342
|
+
}
|
|
343
|
+
const parts = [
|
|
344
|
+
`${check(msg.auth.spf)} SPF`,
|
|
345
|
+
`${check(msg.auth.dkim)} DKIM`,
|
|
346
|
+
`${check(msg.auth.dmarc)} DMARC`,
|
|
347
|
+
]
|
|
348
|
+
const label = msg.auth.authentic ? pc.green('authentic') : pc.red('UNVERIFIED')
|
|
349
|
+
console.log(`Auth: ${parts.join(' ')} (${label})`)
|
|
350
|
+
if (options.verify) {
|
|
351
|
+
console.log(pc.dim(` Raw: ${msg.auth.raw}`))
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
326
355
|
if (msg.attachments.length > 0) {
|
|
327
356
|
const attList = msg.attachments.map((a) => {
|
|
328
357
|
const size = a.size < 1024 ? `${a.size} B`
|
|
@@ -414,6 +443,7 @@ export function registerMailCommands(cli: Goke) {
|
|
|
414
443
|
fromEmail: options.from,
|
|
415
444
|
attachments,
|
|
416
445
|
})
|
|
446
|
+
if (result instanceof Error) handleCommandError(result)
|
|
417
447
|
|
|
418
448
|
out.printYaml(result)
|
|
419
449
|
out.success(`Sent to ${options.to}`)
|
package/src/commands/profile.ts
CHANGED
|
@@ -5,24 +5,30 @@
|
|
|
5
5
|
|
|
6
6
|
import type { Goke } from 'goke'
|
|
7
7
|
import { getClients } from '../auth.js'
|
|
8
|
+
import type { GmailClient } from '../gmail-client.js'
|
|
8
9
|
import { AuthError } from '../api-utils.js'
|
|
9
10
|
import * as out from '../output.js'
|
|
10
11
|
|
|
11
12
|
export function registerProfileCommands(cli: Goke) {
|
|
12
13
|
cli
|
|
13
|
-
.command('profile', 'Show
|
|
14
|
+
.command('profile', 'Show account info')
|
|
14
15
|
.action(async (options) => {
|
|
15
16
|
const clients = await getClients(options.account)
|
|
16
17
|
|
|
17
18
|
// Fetch all accounts concurrently
|
|
18
19
|
const allResults = await Promise.all(
|
|
19
|
-
clients.map(async ({ client }) => {
|
|
20
|
+
clients.map(async ({ client, accountType }) => {
|
|
20
21
|
const profile = await client.getProfile()
|
|
21
22
|
if (profile instanceof Error) return profile
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
|
|
24
|
+
if (accountType === 'google') {
|
|
25
|
+
// Google accounts have aliases
|
|
26
|
+
const aliases = await (client as GmailClient).getEmailAliases()
|
|
27
|
+
if (aliases instanceof Error) return aliases
|
|
28
|
+
return { profile, aliases, accountType }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { profile, aliases: [{ email: profile.emailAddress, primary: true }], accountType }
|
|
26
32
|
}),
|
|
27
33
|
)
|
|
28
34
|
|
|
@@ -32,18 +38,22 @@ export function registerProfileCommands(cli: Goke) {
|
|
|
32
38
|
return true
|
|
33
39
|
})
|
|
34
40
|
|
|
35
|
-
for (const { profile, aliases } of results) {
|
|
36
|
-
|
|
41
|
+
for (const { profile, aliases, accountType } of results) {
|
|
42
|
+
const data: Record<string, unknown> = {
|
|
37
43
|
email: profile.emailAddress,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
type: accountType,
|
|
45
|
+
}
|
|
46
|
+
if (accountType === 'google') {
|
|
47
|
+
data.messages_total = profile.messagesTotal
|
|
48
|
+
data.threads_total = profile.threadsTotal
|
|
49
|
+
data.history_id = profile.historyId
|
|
50
|
+
}
|
|
51
|
+
data.aliases = aliases.map((a) => ({
|
|
52
|
+
email: a.email,
|
|
53
|
+
name: a.name ?? null,
|
|
54
|
+
primary: a.primary,
|
|
55
|
+
}))
|
|
56
|
+
out.printYaml(data)
|
|
47
57
|
}
|
|
48
58
|
})
|
|
49
59
|
}
|
package/src/db.ts
CHANGED
|
@@ -58,6 +58,10 @@ async function initializePrisma(): Promise<PrismaClient> {
|
|
|
58
58
|
// Run schema.sql — uses CREATE TABLE IF NOT EXISTS so it's idempotent
|
|
59
59
|
await applySchema(prisma)
|
|
60
60
|
|
|
61
|
+
// Add new columns to existing Account tables (idempotent migration).
|
|
62
|
+
// CREATE TABLE IF NOT EXISTS doesn't add columns to pre-existing tables.
|
|
63
|
+
await migrateAccountColumns(prisma)
|
|
64
|
+
|
|
61
65
|
// Secure database files (owner read/write only)
|
|
62
66
|
secureDatabase()
|
|
63
67
|
|
|
@@ -93,6 +97,30 @@ async function applySchema(prisma: PrismaClient): Promise<void> {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Idempotent migration: add accountType and capabilities columns to Account
|
|
102
|
+
* if they don't already exist (for DBs created before IMAP/SMTP support).
|
|
103
|
+
* Also backfill existing Google accounts with their default capabilities.
|
|
104
|
+
*/
|
|
105
|
+
async function migrateAccountColumns(prisma: PrismaClient): Promise<void> {
|
|
106
|
+
const cols = await prisma.$queryRawUnsafe<Array<{ name: string }>>(`PRAGMA table_info("Account")`)
|
|
107
|
+
const colNames = new Set(cols.map((c) => c.name))
|
|
108
|
+
|
|
109
|
+
if (!colNames.has('accountType')) {
|
|
110
|
+
await prisma.$executeRawUnsafe(`ALTER TABLE "Account" ADD COLUMN "accountType" TEXT NOT NULL DEFAULT 'google'`)
|
|
111
|
+
}
|
|
112
|
+
if (!colNames.has('capabilities')) {
|
|
113
|
+
await prisma.$executeRawUnsafe(`ALTER TABLE "Account" ADD COLUMN "capabilities" TEXT NOT NULL DEFAULT ''`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Backfill: existing Google accounts should have capabilities set
|
|
117
|
+
await prisma.$executeRawUnsafe(`
|
|
118
|
+
UPDATE "Account"
|
|
119
|
+
SET "capabilities" = 'gmail,calendar,smtp'
|
|
120
|
+
WHERE "accountType" = 'google' AND ("capabilities" = '' OR "capabilities" IS NULL)
|
|
121
|
+
`)
|
|
122
|
+
}
|
|
123
|
+
|
|
96
124
|
/**
|
|
97
125
|
* Set restrictive permissions on database files.
|
|
98
126
|
* SQLite WAL mode creates additional -wal and -shm files that also need protection.
|
|
@@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = {
|
|
|
20
20
|
"clientVersion": "7.3.0",
|
|
21
21
|
"engineVersion": "9d6ad21cbbceab97458517b147a6a09ff43aa735",
|
|
22
22
|
"activeProvider": "sqlite",
|
|
23
|
-
"inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"./src/generated\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\n// Lifecycle status for account credentials stored in `Account`.\nenum AccountStatus {\n active\n disabled\n}\n\n// Stores one
|
|
23
|
+
"inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"./src/generated\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\n// Lifecycle status for account credentials stored in `Account`.\nenum AccountStatus {\n active\n disabled\n}\n\n// Stores one credential set per (email, appId) pair.\n// appId is the Google OAuth client ID for Google accounts, or 'imap_smtp' for IMAP/SMTP accounts.\n// accountType discriminates the credential format stored in tokens.\n// capabilities is a comma-separated list of features: \"gmail,calendar,smtp\" or \"imap,smtp\".\nmodel Account {\n email String\n appId String\n accountType String @default(\"google\") // \"google\" | \"imap_smtp\"\n capabilities String @default(\"\") // comma-separated: \"gmail,calendar,smtp\" or \"imap,smtp\" or \"imap\"\n accountStatus AccountStatus\n tokens String // JSON: OAuth2 Credentials (google) or ImapSmtpCredentials (imap_smtp)\n createdAt DateTime\n updatedAt DateTime @updatedAt\n\n threads Thread[]\n labels Label?\n profiles Profile?\n syncStates SyncState[]\n calendarLists CalendarList?\n\n @@id([email, appId])\n}\n\n// Caches hydrated thread payloads per account + thread ID.\n// Used by mail read and post-mutation cache invalidation.\n// rawData stores the raw Google gmail_v1.Schema$Thread response (format: full)\n// so the cache is resilient to changes in our own ThreadData type.\n// Indexed columns are extracted for queryability and display.\nmodel Thread {\n id Int @id @default(autoincrement())\n email String\n appId String\n threadId String\n subject String // extracted for display/search\n snippet String // extracted for display\n fromEmail String // extracted for filtering\n fromName String // extracted for display\n date String // extracted for sorting (RFC2822 from header)\n labelIds String // comma-separated, extracted for filtering\n hasUnread Boolean // extracted for filtering\n msgCount Int // extracted for display\n historyId String? // for sync\n rawData String // raw Google API response JSON (gmail_v1.Schema$Thread)\n ttlMs Int\n createdAt DateTime\n\n account Account @relation(fields: [email, appId], references: [email, appId], onDelete: Cascade)\n\n @@unique([email, appId, threadId])\n}\n\n// Caches label metadata per account (label id/name/type payload).\n// Used by label list/get and related command outputs.\n// rawData stores the raw Google gmail_v1.Schema$Label[] response.\nmodel Label {\n email String\n appId String\n rawData String // raw Google API response JSON (gmail_v1.Schema$Label[])\n ttlMs Int\n createdAt DateTime\n\n account Account @relation(fields: [email, appId], references: [email, appId], onDelete: Cascade)\n\n @@id([email, appId])\n}\n\n// Caches Gmail profile payload per account (totals/history id).\n// Used by profile command and account metadata lookups.\n// Fully flattened — no JSON blob needed, only 4 fields from Google.\nmodel Profile {\n email String\n appId String\n emailAddress String // from Gmail API\n messagesTotal Int // from Gmail API\n threadsTotal Int // from Gmail API\n historyId String // from Gmail API\n ttlMs Int\n createdAt DateTime\n\n account Account @relation(fields: [email, appId], references: [email, appId], onDelete: Cascade)\n\n @@id([email, appId])\n}\n\n// Caches calendar list per account.\n// Used by cal list to avoid fetching calendar metadata on every invocation.\n// rawData stores parsed CalendarListItem[] JSON (not raw tsdav — parsed at write time).\nmodel CalendarList {\n email String\n appId String\n rawData String // JSON blob of CalendarListItem[]\n ttlMs Int\n createdAt DateTime\n\n account Account @relation(fields: [email, appId], references: [email, appId], onDelete: Cascade)\n\n @@id([email, appId])\n}\n\n// Stores persistent per-account sync metadata as generic key/value pairs.\n// Use this for lightweight sync cursors and markers that are not cached API\n// payloads, for example `history_id` (incremental Gmail history cursor),\n// `last_full_sync_at`, or other small account-scoped checkpoints.\nmodel SyncState {\n email String\n appId String\n key String\n value String\n\n account Account @relation(fields: [email, appId], references: [email, appId], onDelete: Cascade)\n\n @@id([email, appId, key])\n}\n",
|
|
24
24
|
"runtimeDataModel": {
|
|
25
25
|
"models": {},
|
|
26
26
|
"enums": {},
|
|
@@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
config.runtimeDataModel = JSON.parse("{\"models\":{\"Account\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountStatus\",\"kind\":\"enum\",\"type\":\"AccountStatus\"},{\"name\":\"tokens\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"threads\",\"kind\":\"object\",\"type\":\"Thread\",\"relationName\":\"AccountToThread\"},{\"name\":\"labels\",\"kind\":\"object\",\"type\":\"Label\",\"relationName\":\"AccountToLabel\"},{\"name\":\"profiles\",\"kind\":\"object\",\"type\":\"Profile\",\"relationName\":\"AccountToProfile\"},{\"name\":\"syncStates\",\"kind\":\"object\",\"type\":\"SyncState\",\"relationName\":\"AccountToSyncState\"},{\"name\":\"calendarLists\",\"kind\":\"object\",\"type\":\"CalendarList\",\"relationName\":\"AccountToCalendarList\"}],\"dbName\":null},\"Thread\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"threadId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"subject\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"snippet\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fromEmail\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fromName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"labelIds\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hasUnread\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"msgCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"historyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToThread\"}],\"dbName\":null},\"Label\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToLabel\"}],\"dbName\":null},\"Profile\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"messagesTotal\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"threadsTotal\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"historyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToProfile\"}],\"dbName\":null},\"CalendarList\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToCalendarList\"}],\"dbName\":null},\"SyncState\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToSyncState\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
|
31
|
+
config.runtimeDataModel = JSON.parse("{\"models\":{\"Account\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"capabilities\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"accountStatus\",\"kind\":\"enum\",\"type\":\"AccountStatus\"},{\"name\":\"tokens\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"threads\",\"kind\":\"object\",\"type\":\"Thread\",\"relationName\":\"AccountToThread\"},{\"name\":\"labels\",\"kind\":\"object\",\"type\":\"Label\",\"relationName\":\"AccountToLabel\"},{\"name\":\"profiles\",\"kind\":\"object\",\"type\":\"Profile\",\"relationName\":\"AccountToProfile\"},{\"name\":\"syncStates\",\"kind\":\"object\",\"type\":\"SyncState\",\"relationName\":\"AccountToSyncState\"},{\"name\":\"calendarLists\",\"kind\":\"object\",\"type\":\"CalendarList\",\"relationName\":\"AccountToCalendarList\"}],\"dbName\":null},\"Thread\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"threadId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"subject\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"snippet\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fromEmail\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"fromName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"labelIds\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"hasUnread\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"msgCount\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"historyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToThread\"}],\"dbName\":null},\"Label\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToLabel\"}],\"dbName\":null},\"Profile\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"emailAddress\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"messagesTotal\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"threadsTotal\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"historyId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToProfile\"}],\"dbName\":null},\"CalendarList\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rawData\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ttlMs\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToCalendarList\"}],\"dbName\":null},\"SyncState\":{\"fields\":[{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"appId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"account\",\"kind\":\"object\",\"type\":\"Account\",\"relationName\":\"AccountToSyncState\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
|
32
32
|
|
|
33
33
|
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
|
34
34
|
const { Buffer } = await import('node:buffer')
|
|
@@ -892,6 +892,8 @@ export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof
|
|
|
892
892
|
export const AccountScalarFieldEnum = {
|
|
893
893
|
email: 'email',
|
|
894
894
|
appId: 'appId',
|
|
895
|
+
accountType: 'accountType',
|
|
896
|
+
capabilities: 'capabilities',
|
|
895
897
|
accountStatus: 'accountStatus',
|
|
896
898
|
tokens: 'tokens',
|
|
897
899
|
createdAt: 'createdAt',
|
|
@@ -75,6 +75,8 @@ export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof
|
|
|
75
75
|
export const AccountScalarFieldEnum = {
|
|
76
76
|
email: 'email',
|
|
77
77
|
appId: 'appId',
|
|
78
|
+
accountType: 'accountType',
|
|
79
|
+
capabilities: 'capabilities',
|
|
78
80
|
accountStatus: 'accountStatus',
|
|
79
81
|
tokens: 'tokens',
|
|
80
82
|
createdAt: 'createdAt',
|