zele 0.3.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 (157) hide show
  1. package/README.md +1 -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 +26 -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 +112 -126
  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.js +33 -261
  31. package/dist/commands/watch.js.map +1 -1
  32. package/dist/db.js +12 -13
  33. package/dist/db.js.map +1 -1
  34. package/dist/generated/browser.d.ts +12 -27
  35. package/dist/generated/client.d.ts +13 -28
  36. package/dist/generated/client.js +1 -1
  37. package/dist/generated/commonInputTypes.d.ts +90 -26
  38. package/dist/generated/enums.d.ts +0 -4
  39. package/dist/generated/enums.js +0 -3
  40. package/dist/generated/enums.js.map +1 -1
  41. package/dist/generated/internal/class.d.ts +22 -55
  42. package/dist/generated/internal/class.js +12 -4
  43. package/dist/generated/internal/class.js.map +1 -1
  44. package/dist/generated/internal/prismaNamespace.d.ts +272 -511
  45. package/dist/generated/internal/prismaNamespace.js +54 -66
  46. package/dist/generated/internal/prismaNamespace.js.map +1 -1
  47. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +60 -74
  48. package/dist/generated/internal/prismaNamespaceBrowser.js +50 -62
  49. package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  50. package/dist/generated/models/Account.d.ts +1637 -0
  51. package/dist/generated/models/Account.js +2 -0
  52. package/dist/generated/models/Account.js.map +1 -0
  53. package/dist/generated/models/CalendarList.d.ts +1161 -0
  54. package/dist/generated/models/CalendarList.js +2 -0
  55. package/dist/generated/models/CalendarList.js.map +1 -0
  56. package/dist/generated/models/Label.d.ts +1161 -0
  57. package/dist/generated/models/Label.js +2 -0
  58. package/dist/generated/models/Label.js.map +1 -0
  59. package/dist/generated/models/Profile.d.ts +1269 -0
  60. package/dist/generated/models/Profile.js +2 -0
  61. package/dist/generated/models/Profile.js.map +1 -0
  62. package/dist/generated/models/SyncState.d.ts +1130 -0
  63. package/dist/generated/models/SyncState.js +2 -0
  64. package/dist/generated/models/SyncState.js.map +1 -0
  65. package/dist/generated/models/Thread.d.ts +1608 -0
  66. package/dist/generated/models/Thread.js +2 -0
  67. package/dist/generated/models/Thread.js.map +1 -0
  68. package/dist/generated/models.d.ts +6 -9
  69. package/dist/gmail-client.d.ts +119 -94
  70. package/dist/gmail-client.js +862 -322
  71. package/dist/gmail-client.js.map +1 -1
  72. package/dist/mail-tui.d.ts +1 -0
  73. package/dist/mail-tui.js +517 -0
  74. package/dist/mail-tui.js.map +1 -0
  75. package/dist/output.d.ts +6 -0
  76. package/dist/output.js +124 -11
  77. package/dist/output.js.map +1 -1
  78. package/package.json +39 -11
  79. package/schema.prisma +81 -113
  80. package/src/api-utils.ts +103 -5
  81. package/src/auth.ts +224 -143
  82. package/src/calendar-client.ts +196 -89
  83. package/src/cli.ts +30 -1
  84. package/src/commands/attachment.ts +18 -19
  85. package/src/commands/auth-cmd.ts +19 -9
  86. package/src/commands/calendar.ts +42 -85
  87. package/src/commands/draft.ts +19 -22
  88. package/src/commands/label.ts +21 -57
  89. package/src/commands/mail-actions.ts +11 -19
  90. package/src/commands/mail.ts +102 -147
  91. package/src/commands/profile.ts +12 -28
  92. package/src/commands/watch.ts +37 -304
  93. package/src/db.ts +13 -16
  94. package/src/generated/browser.ts +49 -0
  95. package/src/generated/client.ts +71 -0
  96. package/src/generated/commonInputTypes.ts +332 -0
  97. package/src/generated/enums.ts +17 -0
  98. package/src/generated/internal/class.ts +250 -0
  99. package/src/generated/internal/prismaNamespace.ts +1198 -0
  100. package/src/generated/internal/prismaNamespaceBrowser.ts +169 -0
  101. package/src/generated/models/Account.ts +1848 -0
  102. package/src/generated/models/CalendarList.ts +1331 -0
  103. package/src/generated/models/Label.ts +1331 -0
  104. package/src/generated/models/Profile.ts +1439 -0
  105. package/src/generated/models/SyncState.ts +1300 -0
  106. package/src/generated/models/Thread.ts +1787 -0
  107. package/src/generated/models.ts +17 -0
  108. package/src/gmail-client.test.ts +59 -0
  109. package/src/gmail-client.ts +1034 -429
  110. package/src/mail-tui.tsx +1061 -0
  111. package/src/output.test.ts +1093 -0
  112. package/src/output.ts +128 -13
  113. package/src/schema.sql +58 -68
  114. package/src/test-fixtures/email-html/safe-claude-event.html +28 -0
  115. package/src/test-fixtures/email-html/safe-product-announcement.html +25 -0
  116. package/src/test-fixtures/email-html/safe-tracked-links.html +27 -0
  117. package/src/test-fixtures/email-html-snapshots/safe-claude-event.html.md +9 -0
  118. package/src/test-fixtures/email-html-snapshots/safe-product-announcement.html.md +13 -0
  119. package/src/test-fixtures/email-html-snapshots/safe-tracked-links.html.md +7 -0
  120. package/AGENTS.md +0 -26
  121. package/CHANGELOG.md +0 -43
  122. package/dist/generated/models/accounts.d.ts +0 -2000
  123. package/dist/generated/models/accounts.js +0 -2
  124. package/dist/generated/models/accounts.js.map +0 -1
  125. package/dist/generated/models/calendar_events.d.ts +0 -1433
  126. package/dist/generated/models/calendar_events.js +0 -2
  127. package/dist/generated/models/calendar_events.js.map +0 -1
  128. package/dist/generated/models/calendar_lists.d.ts +0 -1131
  129. package/dist/generated/models/calendar_lists.js +0 -2
  130. package/dist/generated/models/calendar_lists.js.map +0 -1
  131. package/dist/generated/models/label_counts.d.ts +0 -1131
  132. package/dist/generated/models/label_counts.js +0 -2
  133. package/dist/generated/models/label_counts.js.map +0 -1
  134. package/dist/generated/models/labels.d.ts +0 -1131
  135. package/dist/generated/models/labels.js +0 -2
  136. package/dist/generated/models/labels.js.map +0 -1
  137. package/dist/generated/models/profiles.d.ts +0 -1131
  138. package/dist/generated/models/profiles.js +0 -2
  139. package/dist/generated/models/profiles.js.map +0 -1
  140. package/dist/generated/models/sync_states.d.ts +0 -1107
  141. package/dist/generated/models/sync_states.js +0 -2
  142. package/dist/generated/models/sync_states.js.map +0 -1
  143. package/dist/generated/models/thread_lists.d.ts +0 -1404
  144. package/dist/generated/models/thread_lists.js +0 -2
  145. package/dist/generated/models/thread_lists.js.map +0 -1
  146. package/dist/generated/models/threads.d.ts +0 -1247
  147. package/dist/generated/models/threads.js +0 -2
  148. package/dist/generated/models/threads.js.map +0 -1
  149. package/dist/gmail-cache.d.ts +0 -60
  150. package/dist/gmail-cache.js +0 -264
  151. package/dist/gmail-cache.js.map +0 -1
  152. package/docs/gogcli-gmail-implementation.md +0 -599
  153. package/scripts/test-device-code-clients.ts +0 -186
  154. package/scripts/test-micropython-scopes.ts +0 -72
  155. package/scripts/test-oauth-clients.ts +0 -257
  156. package/src/gmail-cache.ts +0 -339
  157. 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
  // ---------------------------------------------------------------------------
@@ -236,13 +333,31 @@ export function formatFlags(item: { unread?: boolean; starred?: boolean }): stri
236
333
  // ---------------------------------------------------------------------------
237
334
 
238
335
  export function hint(msg: string): void {
239
- process.stderr.write(pc.dim(`# ${msg}`) + '\n')
336
+ console.error(pc.dim(`# ${msg}`))
240
337
  }
241
338
 
242
339
  export function success(msg: string): void {
243
- process.stderr.write(pc.green(msg) + '\n')
340
+ console.error(pc.green(msg))
244
341
  }
245
342
 
246
343
  export function error(msg: string): void {
247
- 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)
248
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,43 +0,0 @@
1
- # Changelog
2
-
3
- ## 0.3.0
4
-
5
- - **Mail:** Add `mail watch` command for polling new emails using Gmail History API (incremental sync)
6
- - **Mail:** Add support for Gmail search operators in `watch --query` (e.g., `from:github`, `is:unread`)
7
- - **Mail:** Show sender email address in `from` fields for better clarity
8
- - **Fixes:** Improve query parsing and handle server-only operators correctly
9
-
10
- ## 0.2.0
11
-
12
- - **Calendar:** Add comprehensive calendar commands (`cal list`, `events`, `get`, `create`, `update`, `delete`, `respond`, `freebusy`)
13
- - **Calendar:** Use CalDAV protocol instead of Google REST API for better compatibility and efficiency
14
- - **Calendar:** Add local caching for calendar lists and events
15
- - **Auth:** Breaking change: `auth` namespace removed. Use `zele login`, `zele logout`, `zele whoami` directly
16
- - **Docs:** Add comprehensive README with install, setup, and command reference
17
- - **Fixes:** Improved error logging with stack traces
18
- - **Internal:** Remove focus/ooo commands (use `cal create` instead)
19
-
20
-
21
- ## 0.1.3
22
-
23
- - Add CHANGELOG.md
24
- - Add changelog guidance to AGENTS.md
25
-
26
- ## 0.1.2
27
-
28
- - Replace monolithic `googleapis` with scoped `@googleapis/gmail` for smaller install size
29
-
30
- ## 0.1.1
31
-
32
- - Rename package from `gtui` to `zele`
33
- - Rename config directory `~/.gtui` to `~/.zele`, database `gtui.db` to `zele.db`
34
- - Rename env vars `GTUI_CLIENT_ID`/`GTUI_CLIENT_SECRET` to `ZELE_CLIENT_ID`/`ZELE_CLIENT_SECRET`
35
- - Style login flow with picocolors (bold steps, cyan URL, dim hints, yellow warnings)
36
-
37
- ## 0.1.0
38
-
39
- - Initial release as `gtui`
40
- - Multi-account Gmail CLI with OAuth2 authentication
41
- - Commands: mail (list, search, read, send, reply, forward), drafts, labels, attachments, profile
42
- - Prisma-backed SQLite cache with TTL-based expiry
43
- - YAML output with TTY-aware coloring