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.
Files changed (158) hide show
  1. package/README.md +38 -1
  2. package/bin/zele +27 -0
  3. package/dist/api-utils.d.ts +51 -2
  4. package/dist/api-utils.js +89 -3
  5. package/dist/api-utils.js.map +1 -1
  6. package/dist/auth.d.ts +27 -6
  7. package/dist/auth.js +185 -129
  8. package/dist/auth.js.map +1 -1
  9. package/dist/calendar-client.d.ts +16 -9
  10. package/dist/calendar-client.js +163 -59
  11. package/dist/calendar-client.js.map +1 -1
  12. package/dist/cli.js +28 -1
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/attachment.js +17 -15
  15. package/dist/commands/attachment.js.map +1 -1
  16. package/dist/commands/auth-cmd.js +20 -9
  17. package/dist/commands/auth-cmd.js.map +1 -1
  18. package/dist/commands/calendar.js +67 -78
  19. package/dist/commands/calendar.js.map +1 -1
  20. package/dist/commands/draft.js +25 -18
  21. package/dist/commands/draft.js.map +1 -1
  22. package/dist/commands/label.js +33 -45
  23. package/dist/commands/label.js.map +1 -1
  24. package/dist/commands/mail-actions.js +11 -13
  25. package/dist/commands/mail-actions.js.map +1 -1
  26. package/dist/commands/mail.js +114 -128
  27. package/dist/commands/mail.js.map +1 -1
  28. package/dist/commands/profile.js +18 -21
  29. package/dist/commands/profile.js.map +1 -1
  30. package/dist/commands/watch.d.ts +2 -0
  31. package/dist/commands/watch.js +73 -0
  32. package/dist/commands/watch.js.map +1 -0
  33. package/dist/db.js +12 -13
  34. package/dist/db.js.map +1 -1
  35. package/dist/generated/browser.d.ts +12 -27
  36. package/dist/generated/client.d.ts +13 -28
  37. package/dist/generated/client.js +1 -1
  38. package/dist/generated/commonInputTypes.d.ts +90 -26
  39. package/dist/generated/enums.d.ts +0 -4
  40. package/dist/generated/enums.js +0 -3
  41. package/dist/generated/enums.js.map +1 -1
  42. package/dist/generated/internal/class.d.ts +22 -55
  43. package/dist/generated/internal/class.js +12 -4
  44. package/dist/generated/internal/class.js.map +1 -1
  45. package/dist/generated/internal/prismaNamespace.d.ts +272 -511
  46. package/dist/generated/internal/prismaNamespace.js +54 -66
  47. package/dist/generated/internal/prismaNamespace.js.map +1 -1
  48. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +60 -74
  49. package/dist/generated/internal/prismaNamespaceBrowser.js +50 -62
  50. package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  51. package/dist/generated/models/Account.d.ts +1637 -0
  52. package/dist/generated/models/Account.js +2 -0
  53. package/dist/generated/models/Account.js.map +1 -0
  54. package/dist/generated/models/CalendarList.d.ts +1161 -0
  55. package/dist/generated/models/CalendarList.js +2 -0
  56. package/dist/generated/models/CalendarList.js.map +1 -0
  57. package/dist/generated/models/Label.d.ts +1161 -0
  58. package/dist/generated/models/Label.js +2 -0
  59. package/dist/generated/models/Label.js.map +1 -0
  60. package/dist/generated/models/Profile.d.ts +1269 -0
  61. package/dist/generated/models/Profile.js +2 -0
  62. package/dist/generated/models/Profile.js.map +1 -0
  63. package/dist/generated/models/SyncState.d.ts +1130 -0
  64. package/dist/generated/models/SyncState.js +2 -0
  65. package/dist/generated/models/SyncState.js.map +1 -0
  66. package/dist/generated/models/Thread.d.ts +1608 -0
  67. package/dist/generated/models/Thread.js +2 -0
  68. package/dist/generated/models/Thread.js.map +1 -0
  69. package/dist/generated/models.d.ts +6 -9
  70. package/dist/gmail-client.d.ts +119 -94
  71. package/dist/gmail-client.js +862 -315
  72. package/dist/gmail-client.js.map +1 -1
  73. package/dist/mail-tui.d.ts +1 -0
  74. package/dist/mail-tui.js +517 -0
  75. package/dist/mail-tui.js.map +1 -0
  76. package/dist/output.d.ts +6 -4
  77. package/dist/output.js +124 -17
  78. package/dist/output.js.map +1 -1
  79. package/package.json +39 -11
  80. package/schema.prisma +81 -113
  81. package/src/api-utils.ts +103 -5
  82. package/src/auth.ts +224 -143
  83. package/src/calendar-client.ts +196 -89
  84. package/src/cli.ts +32 -1
  85. package/src/commands/attachment.ts +18 -19
  86. package/src/commands/auth-cmd.ts +19 -9
  87. package/src/commands/calendar.ts +42 -85
  88. package/src/commands/draft.ts +19 -22
  89. package/src/commands/label.ts +21 -57
  90. package/src/commands/mail-actions.ts +11 -19
  91. package/src/commands/mail.ts +104 -149
  92. package/src/commands/profile.ts +12 -28
  93. package/src/commands/watch.ts +88 -0
  94. package/src/db.ts +13 -16
  95. package/src/generated/browser.ts +49 -0
  96. package/src/generated/client.ts +71 -0
  97. package/src/generated/commonInputTypes.ts +332 -0
  98. package/src/generated/enums.ts +17 -0
  99. package/src/generated/internal/class.ts +250 -0
  100. package/src/generated/internal/prismaNamespace.ts +1198 -0
  101. package/src/generated/internal/prismaNamespaceBrowser.ts +169 -0
  102. package/src/generated/models/Account.ts +1848 -0
  103. package/src/generated/models/CalendarList.ts +1331 -0
  104. package/src/generated/models/Label.ts +1331 -0
  105. package/src/generated/models/Profile.ts +1439 -0
  106. package/src/generated/models/SyncState.ts +1300 -0
  107. package/src/generated/models/Thread.ts +1787 -0
  108. package/src/generated/models.ts +17 -0
  109. package/src/gmail-client.test.ts +59 -0
  110. package/src/gmail-client.ts +1034 -422
  111. package/src/mail-tui.tsx +1061 -0
  112. package/src/output.test.ts +1093 -0
  113. package/src/output.ts +128 -20
  114. package/src/schema.sql +58 -68
  115. package/src/test-fixtures/email-html/safe-claude-event.html +28 -0
  116. package/src/test-fixtures/email-html/safe-product-announcement.html +25 -0
  117. package/src/test-fixtures/email-html/safe-tracked-links.html +27 -0
  118. package/src/test-fixtures/email-html-snapshots/safe-claude-event.html.md +9 -0
  119. package/src/test-fixtures/email-html-snapshots/safe-product-announcement.html.md +13 -0
  120. package/src/test-fixtures/email-html-snapshots/safe-tracked-links.html.md +7 -0
  121. package/AGENTS.md +0 -26
  122. package/CHANGELOG.md +0 -36
  123. package/dist/generated/models/accounts.d.ts +0 -2000
  124. package/dist/generated/models/accounts.js +0 -2
  125. package/dist/generated/models/accounts.js.map +0 -1
  126. package/dist/generated/models/calendar_events.d.ts +0 -1433
  127. package/dist/generated/models/calendar_events.js +0 -2
  128. package/dist/generated/models/calendar_events.js.map +0 -1
  129. package/dist/generated/models/calendar_lists.d.ts +0 -1131
  130. package/dist/generated/models/calendar_lists.js +0 -2
  131. package/dist/generated/models/calendar_lists.js.map +0 -1
  132. package/dist/generated/models/label_counts.d.ts +0 -1131
  133. package/dist/generated/models/label_counts.js +0 -2
  134. package/dist/generated/models/label_counts.js.map +0 -1
  135. package/dist/generated/models/labels.d.ts +0 -1131
  136. package/dist/generated/models/labels.js +0 -2
  137. package/dist/generated/models/labels.js.map +0 -1
  138. package/dist/generated/models/profiles.d.ts +0 -1131
  139. package/dist/generated/models/profiles.js +0 -2
  140. package/dist/generated/models/profiles.js.map +0 -1
  141. package/dist/generated/models/sync_states.d.ts +0 -1107
  142. package/dist/generated/models/sync_states.js +0 -2
  143. package/dist/generated/models/sync_states.js.map +0 -1
  144. package/dist/generated/models/thread_lists.d.ts +0 -1404
  145. package/dist/generated/models/thread_lists.js +0 -2
  146. package/dist/generated/models/thread_lists.js.map +0 -1
  147. package/dist/generated/models/threads.d.ts +0 -1247
  148. package/dist/generated/models/threads.js +0 -2
  149. package/dist/generated/models/threads.js.map +0 -1
  150. package/dist/gmail-cache.d.ts +0 -60
  151. package/dist/gmail-cache.js +0 -264
  152. package/dist/gmail-cache.js.map +0 -1
  153. package/docs/gogcli-gmail-implementation.md +0 -599
  154. package/scripts/test-device-code-clients.ts +0 -186
  155. package/scripts/test-micropython-scopes.ts +0 -72
  156. package/scripts/test-oauth-clients.ts +0 -257
  157. package/src/gmail-cache.ts +0 -339
  158. package/tsconfig.json +0 -16
package/src/output.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  // Output formatting utilities for zele CLI.
2
2
  // Handles YAML output with TTY-aware coloring, date formatting,
3
3
  // HTML-to-markdown email conversion (turndown), and stderr hints.
4
- // Follows gogcli pattern: data to stdout, hints/progress to stderr.
4
+ // Data goes to stdout (console.log), hints/progress/errors to stderr (console.error).
5
+ // Prefer console.log/console.error over raw process.stdout/stderr when possible.
5
6
  //
6
7
  // All structured data is output as YAML (js-yaml). In TTY mode, keys are
7
8
  // colored cyan and values are left at the terminal default. In non-TTY mode,
@@ -10,7 +11,9 @@
10
11
 
11
12
  import yaml from 'js-yaml'
12
13
  import TurndownService from 'turndown'
14
+ import { remark } from 'remark'
13
15
  import pc from 'picocolors'
16
+ import EmailReplyParser from 'email-reply-parser'
14
17
 
15
18
  // ---------------------------------------------------------------------------
16
19
  // TTY detection (used for coloring + wrapping decisions)
@@ -42,7 +45,7 @@ turndown.addRule('tracking-pixels', {
42
45
  replacement: () => '',
43
46
  })
44
47
 
45
- // Strip <style> and <head> tags
48
+ // Strip <style>, <head>, <script> tags
46
49
  turndown.addRule('strip-style', {
47
50
  filter: ['style', 'head', 'script'],
48
51
  replacement: () => '',
@@ -57,6 +60,79 @@ turndown.addRule('images', {
57
60
  },
58
61
  })
59
62
 
63
+ // ---------------------------------------------------------------------------
64
+ // Email-specific turndown rules
65
+ // Email HTML heavily relies on layout tables. These are not data tables —
66
+ // they're used for positioning (like a 600px centered wrapper). We unwrap
67
+ // them so only the cell content survives as markdown.
68
+ // ---------------------------------------------------------------------------
69
+
70
+ // Strip common quoted-reply wrappers from Gmail/Outlook.
71
+ turndown.addRule('quoted-replies', {
72
+ filter: (node) => {
73
+ const hasClass = (name: string) => {
74
+ const cls = node.getAttribute('class') ?? ''
75
+ return new RegExp(`(^|\\s)${name}(\\s|$)`).test(cls)
76
+ }
77
+
78
+ if (node.nodeName === 'DIV') {
79
+ if (hasClass('gmail_quote') || hasClass('gmail_extra')) return true
80
+ const id = node.getAttribute('id') ?? ''
81
+ if (id === 'appendonsend' || id === 'divRplyFwdMsg') return true
82
+ }
83
+ if (node.nodeName === 'BLOCKQUOTE') {
84
+ const type = node.getAttribute('type') ?? ''
85
+ if (type === 'cite') return true
86
+ }
87
+ return false
88
+ },
89
+ replacement: () => '',
90
+ })
91
+
92
+ // Unwrap layout tables: tables used for email layout (width, align, role="presentation")
93
+ // just pass through their text content instead of rendering as markdown tables.
94
+ turndown.addRule('layout-tables', {
95
+ filter: (node) => {
96
+ if (node.nodeName !== 'TABLE') return false
97
+ const role = (node.getAttribute('role') ?? '').toLowerCase()
98
+ if (role === 'presentation') return true
99
+
100
+ // Heuristic: tables with explicit width or align are almost always layout
101
+ const width = node.getAttribute('width') ?? ''
102
+ const align = node.getAttribute('align') ?? ''
103
+ if (width || align) return true
104
+
105
+ // Tables with cellpadding/cellspacing/border="0" are layout tables
106
+ const border = (node.getAttribute('border') ?? '').trim()
107
+ const cellpadding = node.getAttribute('cellpadding') ?? ''
108
+ const cellspacing = node.getAttribute('cellspacing') ?? ''
109
+ if (border === '0' || cellpadding || cellspacing) return true
110
+
111
+ return false
112
+ },
113
+ replacement: (content) => {
114
+ // _content already has the inner text converted by turndown
115
+ return content
116
+ },
117
+ })
118
+
119
+ // Strip hidden/preheader elements.
120
+ turndown.addRule('hidden-elements', {
121
+ filter: (node) => {
122
+ const style = node.getAttribute('style') ?? ''
123
+ if (/display\s*:\s*none/i.test(style)) return true
124
+ if (/mso-hide\s*:\s*all/i.test(style)) return true
125
+ if (node.hasAttribute('hidden')) return true
126
+
127
+ const cls = node.getAttribute('class') ?? ''
128
+ if (/(^|\s)preheader(\s|$)/.test(cls)) return true
129
+ if (/(^|\s)preview-text(\s|$)/.test(cls)) return true
130
+
131
+ return false
132
+ },
133
+ replacement: () => '',
134
+ })
135
+
60
136
  // ---------------------------------------------------------------------------
61
137
  // HTML -> Markdown conversion
62
138
  // ---------------------------------------------------------------------------
@@ -66,11 +142,28 @@ export function htmlToMarkdown(html: string): string {
66
142
  const cleaned = html
67
143
  .replace(/<!\-\-[\s\S]*?\-\->/g, '') // HTML comments
68
144
  .replace(/<o:p>[\s\S]*?<\/o:p>/gi, '') // Outlook tags
69
-
70
- const md = turndown.turndown(cleaned)
71
-
72
- // Post-clean: collapse excessive blank lines
73
- return md.replace(/\n{3,}/g, '\n\n').trim()
145
+ .replace(/<!\[if[\s\S]*?<!\[endif\]>/gi, '') // Outlook conditional comments
146
+
147
+ let md = turndown.turndown(cleaned)
148
+
149
+ // Replace non-breaking/zero-width spaces before remark sees them
150
+ md = md.replace(/[\u00A0\u200B\u200C\u200D\uFEFF]/g, ' ')
151
+
152
+ // Parse and re-serialize through remark for stable, normalized markdown.
153
+ // This collapses whitespace, fixes list indentation, and validates structure.
154
+ const normalized = remark().processSync(md).toString()
155
+
156
+ // Remark escapes certain characters for markdown safety. Undo escapes that
157
+ // hurt readability in terminal output:
158
+ // - brackets: only for our synthetic [image: ...] placeholders
159
+ // - ampersands: only inside link destination parens (URL readability)
160
+ return normalized
161
+ .replace(/\\\[image:/g, '[image:')
162
+ .replace(/\(([^\n)]*)\)/g, (whole, inner: string) => {
163
+ if (!/(https?:\/\/|mailto:)/i.test(inner)) return whole
164
+ return `(${inner.replace(/\\&/g, '&')})`
165
+ })
166
+ .trim()
74
167
  }
75
168
 
76
169
  // ---------------------------------------------------------------------------
@@ -78,12 +171,16 @@ export function htmlToMarkdown(html: string): string {
78
171
  // ---------------------------------------------------------------------------
79
172
 
80
173
  export function renderEmailBody(body: string, mimeType: string): string {
81
- if (mimeType === 'text/html') {
82
- return htmlToMarkdown(body)
83
- }
174
+ if (mimeType === 'text/html') return htmlToMarkdown(body)
84
175
  return body.trim()
85
176
  }
86
177
 
178
+ // ---------------------------------------------------------------------------
179
+ // Quoted reply stripping (email-reply-parser)
180
+ // ---------------------------------------------------------------------------
181
+
182
+ export const replyParser = new EmailReplyParser()
183
+
87
184
  // ---------------------------------------------------------------------------
88
185
  // YAML output
89
186
  // ---------------------------------------------------------------------------
@@ -214,13 +311,6 @@ export function formatEventTime(start: string, end: string, allDay = false): { s
214
311
  // ---------------------------------------------------------------------------
215
312
 
216
313
  export function formatSender(sender: { name?: string; email: string }): string {
217
- if (sender.name && sender.name !== sender.email) {
218
- return sender.name
219
- }
220
- return sender.email
221
- }
222
-
223
- export function formatSenderFull(sender: { name?: string; email: string }): string {
224
314
  if (sender.name && sender.name !== sender.email) {
225
315
  return `${sender.name} <${sender.email}>`
226
316
  }
@@ -243,13 +333,31 @@ export function formatFlags(item: { unread?: boolean; starred?: boolean }): stri
243
333
  // ---------------------------------------------------------------------------
244
334
 
245
335
  export function hint(msg: string): void {
246
- process.stderr.write(pc.dim(`# ${msg}`) + '\n')
336
+ console.error(pc.dim(`# ${msg}`))
247
337
  }
248
338
 
249
339
  export function success(msg: string): void {
250
- process.stderr.write(pc.green(msg) + '\n')
340
+ console.error(pc.green(msg))
251
341
  }
252
342
 
253
343
  export function error(msg: string): void {
254
- process.stderr.write(pc.red(msg) + '\n')
344
+ console.error(pc.red(msg))
345
+ }
346
+
347
+ // ---------------------------------------------------------------------------
348
+ // Centralized command error handler (errore pattern)
349
+ // ---------------------------------------------------------------------------
350
+
351
+ import { AuthError } from './api-utils.js'
352
+
353
+ /** Handle any error from a client method in a command context.
354
+ * Prints a user-friendly message to stderr and exits.
355
+ * AuthError gets a "Try: zele login" hint; all others print their message. */
356
+ export function handleCommandError(err: Error): never {
357
+ if (err instanceof AuthError) {
358
+ error(`${err.message}. Try: zele login`)
359
+ } else {
360
+ error(err.message)
361
+ }
362
+ process.exit(1)
255
363
  }
package/src/schema.sql CHANGED
@@ -1,84 +1,74 @@
1
- -- Generated by pnpm generate:sql. Do not edit.
2
- CREATE TABLE IF NOT EXISTS "accounts" (
3
- "email" TEXT NOT NULL PRIMARY KEY,
4
- "account_status" TEXT NOT NULL DEFAULT 'active',
1
+ -- Generated by bun run generate:sql. Do not edit.
2
+ CREATE TABLE IF NOT EXISTS "Account" (
3
+ "email" TEXT NOT NULL,
4
+ "appId" TEXT NOT NULL,
5
+ "accountStatus" TEXT NOT NULL,
5
6
  "tokens" TEXT NOT NULL,
6
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7
- "updated_at" DATETIME NOT NULL
7
+ "createdAt" DATETIME NOT NULL,
8
+ "updatedAt" DATETIME NOT NULL,
9
+
10
+ PRIMARY KEY ("email", "appId")
8
11
  );
9
- CREATE TABLE IF NOT EXISTS "thread_lists" (
12
+ CREATE TABLE IF NOT EXISTS "Thread" (
10
13
  "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
11
14
  "email" TEXT NOT NULL,
12
- "folder" TEXT NOT NULL DEFAULT '',
13
- "query" TEXT NOT NULL DEFAULT '',
14
- "label_ids" TEXT NOT NULL DEFAULT '',
15
- "page_token" TEXT NOT NULL DEFAULT '',
16
- "max_results" INTEGER NOT NULL DEFAULT 0,
17
- "data" TEXT NOT NULL,
18
- "ttl_ms" INTEGER NOT NULL,
19
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
20
- CONSTRAINT "thread_lists_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
15
+ "appId" TEXT NOT NULL,
16
+ "threadId" TEXT NOT NULL,
17
+ "subject" TEXT NOT NULL,
18
+ "snippet" TEXT NOT NULL,
19
+ "fromEmail" TEXT NOT NULL,
20
+ "fromName" TEXT NOT NULL,
21
+ "date" TEXT NOT NULL,
22
+ "labelIds" TEXT NOT NULL,
23
+ "hasUnread" BOOLEAN NOT NULL,
24
+ "msgCount" INTEGER NOT NULL,
25
+ "historyId" TEXT,
26
+ "rawData" TEXT NOT NULL,
27
+ "ttlMs" INTEGER NOT NULL,
28
+ "createdAt" DATETIME NOT NULL,
29
+ CONSTRAINT "Thread_email_appId_fkey" FOREIGN KEY ("email", "appId") REFERENCES "Account" ("email", "appId") ON DELETE CASCADE ON UPDATE CASCADE
21
30
  );
22
31
  CREATE TABLE sqlite_sequence(name,seq);
23
- CREATE TABLE IF NOT EXISTS "threads" (
24
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
32
+ CREATE TABLE IF NOT EXISTS "Label" (
25
33
  "email" TEXT NOT NULL,
26
- "thread_id" TEXT NOT NULL,
27
- "data" TEXT NOT NULL,
28
- "ttl_ms" INTEGER NOT NULL,
29
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
30
- CONSTRAINT "threads_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
31
- );
32
- CREATE TABLE IF NOT EXISTS "labels" (
33
- "email" TEXT NOT NULL PRIMARY KEY,
34
- "data" TEXT NOT NULL,
35
- "ttl_ms" INTEGER NOT NULL,
36
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
- CONSTRAINT "labels_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
38
- );
39
- CREATE TABLE IF NOT EXISTS "label_counts" (
40
- "email" TEXT NOT NULL PRIMARY KEY,
41
- "data" TEXT NOT NULL,
42
- "ttl_ms" INTEGER NOT NULL,
43
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
44
- CONSTRAINT "label_counts_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
45
- );
46
- CREATE TABLE IF NOT EXISTS "profiles" (
47
- "email" TEXT NOT NULL PRIMARY KEY,
48
- "data" TEXT NOT NULL,
49
- "ttl_ms" INTEGER NOT NULL,
50
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
51
- CONSTRAINT "profiles_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
34
+ "appId" TEXT NOT NULL,
35
+ "rawData" TEXT NOT NULL,
36
+ "ttlMs" INTEGER NOT NULL,
37
+ "createdAt" DATETIME NOT NULL,
38
+
39
+ PRIMARY KEY ("email", "appId"),
40
+ CONSTRAINT "Label_email_appId_fkey" FOREIGN KEY ("email", "appId") REFERENCES "Account" ("email", "appId") ON DELETE CASCADE ON UPDATE CASCADE
52
41
  );
53
- CREATE TABLE IF NOT EXISTS "calendar_lists" (
54
- "email" TEXT NOT NULL PRIMARY KEY,
55
- "data" TEXT NOT NULL,
56
- "ttl_ms" INTEGER NOT NULL,
57
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
58
- CONSTRAINT "calendar_lists_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
42
+ CREATE TABLE IF NOT EXISTS "Profile" (
43
+ "email" TEXT NOT NULL,
44
+ "appId" TEXT NOT NULL,
45
+ "emailAddress" TEXT NOT NULL,
46
+ "messagesTotal" INTEGER NOT NULL,
47
+ "threadsTotal" INTEGER NOT NULL,
48
+ "historyId" TEXT NOT NULL,
49
+ "ttlMs" INTEGER NOT NULL,
50
+ "createdAt" DATETIME NOT NULL,
51
+
52
+ PRIMARY KEY ("email", "appId"),
53
+ CONSTRAINT "Profile_email_appId_fkey" FOREIGN KEY ("email", "appId") REFERENCES "Account" ("email", "appId") ON DELETE CASCADE ON UPDATE CASCADE
59
54
  );
60
- CREATE TABLE IF NOT EXISTS "calendar_events" (
61
- "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
55
+ CREATE TABLE IF NOT EXISTS "CalendarList" (
62
56
  "email" TEXT NOT NULL,
63
- "calendar_id" TEXT NOT NULL DEFAULT '',
64
- "time_min" TEXT NOT NULL DEFAULT '',
65
- "time_max" TEXT NOT NULL DEFAULT '',
66
- "query" TEXT NOT NULL DEFAULT '',
67
- "max_results" INTEGER NOT NULL DEFAULT 0,
68
- "page_token" TEXT NOT NULL DEFAULT '',
69
- "data" TEXT NOT NULL,
70
- "ttl_ms" INTEGER NOT NULL,
71
- "created_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
72
- CONSTRAINT "calendar_events_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
57
+ "appId" TEXT NOT NULL,
58
+ "rawData" TEXT NOT NULL,
59
+ "ttlMs" INTEGER NOT NULL,
60
+ "createdAt" DATETIME NOT NULL,
61
+
62
+ PRIMARY KEY ("email", "appId"),
63
+ CONSTRAINT "CalendarList_email_appId_fkey" FOREIGN KEY ("email", "appId") REFERENCES "Account" ("email", "appId") ON DELETE CASCADE ON UPDATE CASCADE
73
64
  );
74
- CREATE TABLE IF NOT EXISTS "sync_states" (
65
+ CREATE TABLE IF NOT EXISTS "SyncState" (
75
66
  "email" TEXT NOT NULL,
67
+ "appId" TEXT NOT NULL,
76
68
  "key" TEXT NOT NULL,
77
69
  "value" TEXT NOT NULL,
78
70
 
79
- PRIMARY KEY ("email", "key"),
80
- CONSTRAINT "sync_states_email_fkey" FOREIGN KEY ("email") REFERENCES "accounts" ("email") ON DELETE CASCADE ON UPDATE CASCADE
71
+ PRIMARY KEY ("email", "appId", "key"),
72
+ CONSTRAINT "SyncState_email_appId_fkey" FOREIGN KEY ("email", "appId") REFERENCES "Account" ("email", "appId") ON DELETE CASCADE ON UPDATE CASCADE
81
73
  );
82
- CREATE UNIQUE INDEX "thread_lists_email_folder_query_label_ids_page_token_max_results_key" ON "thread_lists"("email", "folder", "query", "label_ids", "page_token", "max_results");
83
- CREATE UNIQUE INDEX "threads_email_thread_id_key" ON "threads"("email", "thread_id");
84
- CREATE UNIQUE INDEX "calendar_events_email_calendar_id_time_min_time_max_query_max_results_page_token_key" ON "calendar_events"("email", "calendar_id", "time_min", "time_max", "query", "max_results", "page_token");
74
+ CREATE UNIQUE INDEX "Thread_email_appId_threadId_key" ON "Thread"("email", "appId", "threadId");
@@ -0,0 +1,28 @@
1
+ <!-- Safe shared fixture derived from a real event email with redacted links and identities. -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <body>
5
+ <div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">
6
+ A host has sent you a new message.&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏
7
+ </div>
8
+ <table role="presentation" width="100%">
9
+ <tr>
10
+ <td>
11
+ <img alt="CV Logo" src="https://example.com/logo.png" width="120" />
12
+ <p>New Event Blast</p>
13
+ <p>
14
+ Thank you for applying to Built with Opus
15
+ <a href="https://example.com/opus-46">4.6</a>: a Claude Code Hackathon.
16
+ </p>
17
+ <p>
18
+ We received an incredible number of applications and couldn't include everyone this round.
19
+ </p>
20
+ <p>
21
+ Stay in the loop via our
22
+ <a href="https://example.com/events">Community Events calendar</a>.
23
+ </p>
24
+ </td>
25
+ </tr>
26
+ </table>
27
+ </body>
28
+ </html>
@@ -0,0 +1,25 @@
1
+ <!-- Safe shared fixture derived from a real product update email with redacted links. -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <body>
5
+ <table width="600" align="center" cellpadding="0" cellspacing="0">
6
+ <tr>
7
+ <td>
8
+ <p>Hey there,</p>
9
+ <p>We just shipped Product 1.0. It&#x27;s stable, it&#x27;s production-ready.</p>
10
+ <p>If you&#x27;ve been building with Product, thank you.</p>
11
+ <p>Here&#x27;s what&#x27;s new:</p>
12
+ <ul>
13
+ <li>Stable API in production</li>
14
+ <li>Improved error handling</li>
15
+ <li>Better state management</li>
16
+ </ul>
17
+ <p>
18
+ Read the full announcement:
19
+ <a href="https://safe.example/news/product-1">Introducing Product 1.0</a>
20
+ </p>
21
+ </td>
22
+ </tr>
23
+ </table>
24
+ </body>
25
+ </html>
@@ -0,0 +1,27 @@
1
+ <!-- Safe shared fixture modeled after tracked-link newsletters without private tokens. -->
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <body>
5
+ <table role="presentation" width="100%">
6
+ <tr>
7
+ <td>
8
+ <h1>BentoML Joins Modular</h1>
9
+ <p>
10
+ <a href="https://tracking.example/CL0/https:%2F%2Fmodular.com%2Fblog%2Fbentoml-joins-modular/1/click-token">
11
+ Read the full story
12
+ </a>
13
+ </p>
14
+ <p>
15
+ <a href="https://tracking.example/CL0/https:%2F%2Fforum.modular.com%2Fama/1/click-token">
16
+ Join the AMA
17
+ </a>
18
+ </p>
19
+ <p>
20
+ <img alt="x" src="https://example.com/x.png" />
21
+ <img alt="discord" src="https://example.com/discord.png" />
22
+ </p>
23
+ </td>
24
+ </tr>
25
+ </table>
26
+ </body>
27
+ </html>
@@ -0,0 +1,9 @@
1
+ [image: CV Logo]
2
+
3
+ New Event Blast
4
+
5
+ Thank you for applying to Built with Opus [4.6](https://example.com/opus-46): a Claude Code Hackathon.
6
+
7
+ We received an incredible number of applications and couldn't include everyone this round.
8
+
9
+ Stay in the loop via our [Community Events calendar](https://example.com/events).
@@ -0,0 +1,13 @@
1
+ Hey there,
2
+
3
+ We just shipped Product 1.0. It's stable, it's production-ready.
4
+
5
+ If you've been building with Product, thank you.
6
+
7
+ Here's what's new:
8
+
9
+ * Stable API in production
10
+ * Improved error handling
11
+ * Better state management
12
+
13
+ Read the full announcement: [Introducing Product 1.0](https://safe.example/news/product-1)
@@ -0,0 +1,7 @@
1
+ # BentoML Joins Modular
2
+
3
+ [Read the full story](https://tracking.example/CL0/https:%2F%2Fmodular.com%2Fblog%2Fbentoml-joins-modular/1/click-token)
4
+
5
+ [Join the AMA](https://tracking.example/CL0/https:%2F%2Fforum.modular.com%2Fama/1/click-token)
6
+
7
+ [image: x] [image: discord]
package/AGENTS.md DELETED
@@ -1,26 +0,0 @@
1
- ## development
2
-
3
- to run the cli locally use `tsx src/cli.ts`
4
-
5
- ## goke typing
6
-
7
- do not add manual type annotations to `.action(async ...)` parameters in goke commands; rely on goke option inference.
8
-
9
- ## database
10
-
11
- zele uses a single SQLite database at `~/.zele/zele.db` as the source of truth for CLI state.
12
-
13
- all persistent state is stored in this DB via Prisma models:
14
- - `accounts`: OAuth tokens per email account
15
- - `thread_lists` + `threads`: cached mail list/read payloads
16
- - `labels` + `label_counts`: cached label metadata and unread counters
17
- - `profiles`: cached account profile data
18
- - `sync_states`: misc per-account sync metadata (for example history IDs)
19
-
20
- ## changelog
21
-
22
- keep `CHANGELOG.md` updated when making user-facing changes. bump the version in `package.json` and `src/cli.ts` together.
23
-
24
- ## migrations
25
-
26
- `src/db.ts` runs `src/schema.sql` on startup (idempotent migration) so new tables/indexes are applied automatically on each CLI process start.
package/CHANGELOG.md DELETED
@@ -1,36 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.2.0
4
-
5
- - **Calendar:** Add comprehensive calendar commands (`cal list`, `events`, `get`, `create`, `update`, `delete`, `respond`, `freebusy`)
6
- - **Calendar:** Use CalDAV protocol instead of Google REST API for better compatibility and efficiency
7
- - **Calendar:** Add local caching for calendar lists and events
8
- - **Auth:** Breaking change: `auth` namespace removed. Use `zele login`, `zele logout`, `zele whoami` directly
9
- - **Docs:** Add comprehensive README with install, setup, and command reference
10
- - **Fixes:** Improved error logging with stack traces
11
- - **Internal:** Remove focus/ooo commands (use `cal create` instead)
12
-
13
-
14
- ## 0.1.3
15
-
16
- - Add CHANGELOG.md
17
- - Add changelog guidance to AGENTS.md
18
-
19
- ## 0.1.2
20
-
21
- - Replace monolithic `googleapis` with scoped `@googleapis/gmail` for smaller install size
22
-
23
- ## 0.1.1
24
-
25
- - Rename package from `gtui` to `zele`
26
- - Rename config directory `~/.gtui` to `~/.zele`, database `gtui.db` to `zele.db`
27
- - Rename env vars `GTUI_CLIENT_ID`/`GTUI_CLIENT_SECRET` to `ZELE_CLIENT_ID`/`ZELE_CLIENT_SECRET`
28
- - Style login flow with picocolors (bold steps, cyan URL, dim hints, yellow warnings)
29
-
30
- ## 0.1.0
31
-
32
- - Initial release as `gtui`
33
- - Multi-account Gmail CLI with OAuth2 authentication
34
- - Commands: mail (list, search, read, send, reply, forward), drafts, labels, attachments, profile
35
- - Prisma-backed SQLite cache with TTL-based expiry
36
- - YAML output with TTY-aware coloring