zele 0.1.3 → 0.2.0

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 (64) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +112 -0
  3. package/dist/api-utils.d.ts +6 -0
  4. package/dist/api-utils.js +52 -0
  5. package/dist/api-utils.js.map +1 -0
  6. package/dist/auth.d.ts +16 -0
  7. package/dist/auth.js +74 -5
  8. package/dist/auth.js.map +1 -1
  9. package/dist/calendar-client.d.ts +135 -0
  10. package/dist/calendar-client.js +498 -0
  11. package/dist/calendar-client.js.map +1 -0
  12. package/dist/calendar-time.d.ts +24 -0
  13. package/dist/calendar-time.js +245 -0
  14. package/dist/calendar-time.js.map +1 -0
  15. package/dist/cli.js +5 -3
  16. package/dist/cli.js.map +1 -1
  17. package/dist/commands/auth-cmd.js +5 -5
  18. package/dist/commands/auth-cmd.js.map +1 -1
  19. package/dist/commands/calendar.d.ts +2 -0
  20. package/dist/commands/calendar.js +563 -0
  21. package/dist/commands/calendar.js.map +1 -0
  22. package/dist/generated/browser.d.ts +10 -0
  23. package/dist/generated/client.d.ts +10 -0
  24. package/dist/generated/internal/class.d.ts +22 -0
  25. package/dist/generated/internal/class.js +2 -2
  26. package/dist/generated/internal/class.js.map +1 -1
  27. package/dist/generated/internal/prismaNamespace.d.ts +174 -1
  28. package/dist/generated/internal/prismaNamespace.js +21 -0
  29. package/dist/generated/internal/prismaNamespace.js.map +1 -1
  30. package/dist/generated/internal/prismaNamespaceBrowser.d.ts +23 -0
  31. package/dist/generated/internal/prismaNamespaceBrowser.js +21 -0
  32. package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
  33. package/dist/generated/models/accounts.d.ts +281 -0
  34. package/dist/generated/models/calendar_events.d.ts +1433 -0
  35. package/dist/generated/models/calendar_events.js +2 -0
  36. package/dist/generated/models/calendar_events.js.map +1 -0
  37. package/dist/generated/models/calendar_lists.d.ts +1131 -0
  38. package/dist/generated/models/calendar_lists.js +2 -0
  39. package/dist/generated/models/calendar_lists.js.map +1 -0
  40. package/dist/generated/models.d.ts +2 -0
  41. package/dist/gmail-cache.d.ts +22 -0
  42. package/dist/gmail-cache.js +76 -0
  43. package/dist/gmail-cache.js.map +1 -1
  44. package/dist/gmail-client.js +1 -48
  45. package/dist/gmail-client.js.map +1 -1
  46. package/dist/output.d.ts +11 -0
  47. package/dist/output.js +42 -0
  48. package/dist/output.js.map +1 -1
  49. package/package.json +4 -2
  50. package/schema.prisma +39 -6
  51. package/scripts/test-device-code-clients.ts +186 -0
  52. package/scripts/test-micropython-scopes.ts +72 -0
  53. package/scripts/test-oauth-clients.ts +257 -0
  54. package/src/api-utils.ts +60 -0
  55. package/src/auth.ts +92 -5
  56. package/src/calendar-client.ts +758 -0
  57. package/src/calendar-time.ts +299 -0
  58. package/src/cli.ts +5 -3
  59. package/src/commands/auth-cmd.ts +5 -5
  60. package/src/commands/calendar.ts +634 -0
  61. package/src/gmail-cache.ts +96 -0
  62. package/src/gmail-client.ts +1 -57
  63. package/src/output.ts +51 -0
  64. package/src/schema.sql +22 -0
package/src/auth.ts CHANGED
@@ -16,6 +16,7 @@ import fkill from 'fkill'
16
16
  import pc from 'picocolors'
17
17
  import { getPrisma } from './db.js'
18
18
  import { GmailClient } from './gmail-client.js'
19
+ import { CalendarClient } from './calendar-client.js'
19
20
 
20
21
  // ---------------------------------------------------------------------------
21
22
  // Config
@@ -24,12 +25,42 @@ import { GmailClient } from './gmail-client.js'
24
25
  const ZELE_DIR = path.join(os.homedir(), '.zele')
25
26
  const LEGACY_TOKENS_FILE = path.join(ZELE_DIR, 'tokens.json')
26
27
 
28
+ // ---------------------------------------------------------------------------
29
+ // Known open-source Google OAuth clients (Desktop app type).
30
+ // All support localhost + OOB redirects. All have Gmail, Calendar, Drive,
31
+ // Contacts, Tasks, and other Google API scopes enabled.
32
+ // None support device code flow (requires "TVs and Limited Input" client type,
33
+ // which Google restricts — Gmail scopes are blocked from device code entirely).
34
+ // Source: public open-source repos, tested 2026-02-09.
35
+ // ---------------------------------------------------------------------------
36
+ const OAUTH_CLIENTS = {
37
+ // Mozilla Thunderbird — largest user base, highest Google quota.
38
+ // Source: searchfox.org/comm-central/source/mailnews/base/src/OAuth2Providers.sys.mjs
39
+ thunderbird: {
40
+ clientId: '406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com',
41
+ clientSecret: 'kSmqreRr0qwBWJgbf5Y-PjSU',
42
+ },
43
+ // GNOME Online Accounts — used by Evolution, GNOME Calendar, Nautilus (Drive).
44
+ // Source: github.com/GNOME/gnome-online-accounts/blob/master/meson_options.txt
45
+ gnome: {
46
+ clientId: '44438659992-7kgjeitenc16ssihbtdjbgguch7ju55s.apps.googleusercontent.com',
47
+ clientSecret: '-gMLuQyDiI0XrQS_vx_mhuYF',
48
+ },
49
+ // KDE KAccounts — used by KMail, KOrganizer, Kontact.
50
+ // Source: github.com/KDE/kaccounts-providers google.provider.in
51
+ kde: {
52
+ clientId: '317066460457-pkpkedrvt2ldq6g2hj1egfka2n7vpuoo.apps.googleusercontent.com',
53
+ clientSecret: 'Y8eFAaWfcanV3amZdDvtbYUq',
54
+ },
55
+ } as const
56
+
57
+ const ACTIVE_CLIENT = OAUTH_CLIENTS.thunderbird
58
+
27
59
  const CLIENT_ID =
28
- process.env.ZELE_CLIENT_ID ??
29
- '406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com'
60
+ process.env.ZELE_CLIENT_ID ?? ACTIVE_CLIENT.clientId
30
61
 
31
62
  const CLIENT_SECRET =
32
- process.env.ZELE_CLIENT_SECRET ?? 'kSmqreRr0qwBWJgbf5Y-PjSU'
63
+ process.env.ZELE_CLIENT_SECRET ?? ACTIVE_CLIENT.clientSecret
33
64
 
34
65
  const REDIRECT_PORT = 8089
35
66
  const SCOPES = [
@@ -266,7 +297,7 @@ async function authenticateAccount(email: string): Promise<OAuth2Client> {
266
297
  const prisma = await getPrisma()
267
298
  const row = await prisma.accounts.findUnique({ where: { email } })
268
299
  if (!row) {
269
- throw new Error(`No account found for ${email}. Run: zele auth login`)
300
+ throw new Error(`No account found for ${email}. Run: zele login`)
270
301
  }
271
302
 
272
303
  const tokens: Credentials = JSON.parse(row.tokens)
@@ -300,7 +331,7 @@ export async function getClients(
300
331
 
301
332
  const allEmails = await listAccounts()
302
333
  if (allEmails.length === 0) {
303
- throw new Error('No accounts registered. Run: zele auth login')
334
+ throw new Error('No accounts registered. Run: zele login')
304
335
  }
305
336
 
306
337
  const emails = accounts && accounts.length > 0
@@ -340,6 +371,62 @@ export async function getClient(
340
371
  )
341
372
  }
342
373
 
374
+ // ---------------------------------------------------------------------------
375
+ // Calendar client helpers
376
+ // ---------------------------------------------------------------------------
377
+
378
+ /**
379
+ * Get authenticated CalendarClient instances for all accounts (or filtered by email list).
380
+ */
381
+ export async function getCalendarClients(
382
+ accounts?: string[],
383
+ ): Promise<Array<{ email: string; client: CalendarClient }>> {
384
+ await migrateLegacyTokens()
385
+
386
+ const allEmails = await listAccounts()
387
+ if (allEmails.length === 0) {
388
+ throw new Error('No accounts registered. Run: zele login')
389
+ }
390
+
391
+ const emails = accounts && accounts.length > 0
392
+ ? allEmails.filter((e) => accounts.includes(e))
393
+ : allEmails
394
+
395
+ if (emails.length === 0) {
396
+ const available = allEmails.join(', ')
397
+ throw new Error(`No matching accounts. Available: ${available}`)
398
+ }
399
+
400
+ const results = await Promise.all(
401
+ emails.map(async (email) => {
402
+ const auth = await authenticateAccount(email)
403
+ const { token } = await auth.getAccessToken()
404
+ if (!token) throw new Error(`Failed to get access token for ${email}`)
405
+ return { email, client: new CalendarClient({ accessToken: token, email }) }
406
+ }),
407
+ )
408
+
409
+ return results
410
+ }
411
+
412
+ /**
413
+ * Get a single authenticated CalendarClient. Errors if multiple accounts exist
414
+ * and no --account filter was provided.
415
+ */
416
+ export async function getCalendarClient(
417
+ accounts?: string[],
418
+ ): Promise<{ email: string; client: CalendarClient }> {
419
+ const clients = await getCalendarClients(accounts)
420
+ if (clients.length === 1) {
421
+ return clients[0]!
422
+ }
423
+
424
+ const emails = clients.map((c) => c.email).join('\n ')
425
+ throw new Error(
426
+ `Multiple accounts matched. Specify --account:\n ${emails}`,
427
+ )
428
+ }
429
+
343
430
  // ---------------------------------------------------------------------------
344
431
  // Auth status (for auth status command)
345
432
  // ---------------------------------------------------------------------------