zele 0.3.13 → 0.3.15

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 (44) hide show
  1. package/README.md +8 -1
  2. package/dist/auth.js +1 -1
  3. package/dist/auth.js.map +1 -1
  4. package/dist/cli.d.ts +1 -1
  5. package/dist/cli.js +10 -8
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/attachment.js +44 -5
  8. package/dist/commands/attachment.js.map +1 -1
  9. package/dist/commands/auth-cmd.js +1 -2
  10. package/dist/commands/auth-cmd.js.map +1 -1
  11. package/dist/commands/calendar.js +4 -6
  12. package/dist/commands/calendar.js.map +1 -1
  13. package/dist/commands/draft.js +2 -3
  14. package/dist/commands/draft.js.map +1 -1
  15. package/dist/commands/label.js +4 -5
  16. package/dist/commands/label.js.map +1 -1
  17. package/dist/commands/mail.js +24 -21
  18. package/dist/commands/mail.js.map +1 -1
  19. package/dist/db.js +24 -1
  20. package/dist/db.js.map +1 -1
  21. package/dist/gmail-client.js +39 -4
  22. package/dist/gmail-client.js.map +1 -1
  23. package/dist/mail-tui.js +47 -12
  24. package/dist/mail-tui.js.map +1 -1
  25. package/dist/output.d.ts +3 -1
  26. package/dist/output.js +7 -2
  27. package/dist/output.js.map +1 -1
  28. package/package.json +11 -5
  29. package/src/app.log +9 -0
  30. package/src/auth.ts +1 -1
  31. package/src/cli.ts +31 -30
  32. package/src/commands/attachment.ts +49 -6
  33. package/src/commands/auth-cmd.ts +1 -2
  34. package/src/commands/calendar.ts +4 -7
  35. package/src/commands/draft.ts +2 -3
  36. package/src/commands/label.ts +4 -4
  37. package/src/commands/mail.ts +24 -19
  38. package/src/db.ts +26 -1
  39. package/src/gmail-client.ts +46 -4
  40. package/src/mail-tui.test.ts +170 -0
  41. package/src/mail-tui.tsx +87 -20
  42. package/src/output.ts +8 -3
  43. package/bin/zele +0 -27
  44. package/src/opentui-react.d.ts +0 -9
package/src/mail-tui.tsx CHANGED
@@ -34,8 +34,9 @@ import {
34
34
  useNavigation,
35
35
  showFailureToast,
36
36
  } from 'termcast'
37
+ // @ts-expect-error https://github.com/anomalyco/opentui/pull/614
37
38
  import { useTerminalDimensions } from '@opentui/react'
38
- import { useCachedPromise } from '@termcast/utils'
39
+ import { useCachedPromise, useCachedState } from '@termcast/utils'
39
40
  import { useState, useMemo, useCallback, useEffect } from 'react'
40
41
 
41
42
  import {
@@ -78,6 +79,17 @@ const ACCOUNT_COLORS = [
78
79
  const ADD_ACCOUNT = '__add_account__'
79
80
  const MANAGE_ACCOUNTS = '__manage_accounts__'
80
81
 
82
+ const FOLDER_OPTIONS = [
83
+ { id: 'inbox', label: 'Inbox', icon: Icon.Envelope },
84
+ { id: 'sent', label: 'Sent', icon: Icon.PaperAirplane },
85
+ { id: 'starred', label: 'Starred', icon: Icon.Star },
86
+ { id: 'drafts', label: 'Drafts', icon: Icon.Pencil },
87
+ { id: 'archive', label: 'Archive', icon: Icon.Tray },
88
+ { id: 'spam', label: 'Spam', icon: Icon.ExclamationMark },
89
+ { id: 'trash', label: 'Trash', icon: Icon.Trash },
90
+ { id: 'all', label: 'All Mail', icon: Icon.List },
91
+ ] as const
92
+
81
93
  // ---------------------------------------------------------------------------
82
94
  // Helpers
83
95
  // ---------------------------------------------------------------------------
@@ -399,10 +411,19 @@ function ComposeForm({ mode, initialAccount, accounts, onSent }: ComposeFormProp
399
411
  ? 'Reply All'
400
412
  : 'Reply'
401
413
 
402
- const bodyPlaceholder =
403
- mode.type === 'forward'
404
- ? 'Add a message (optional)...'
405
- : 'Type your reply...'
414
+ const bodyPlaceholder = mode.type === 'forward'
415
+ ? `Add a message (optional)...
416
+
417
+ ---
418
+
419
+ Best,
420
+ Name`
421
+ : `Type your reply...
422
+
423
+ ---
424
+
425
+ Best,
426
+ Name`
406
427
 
407
428
  const handleSubmit = async (values: { to?: string; body?: string }) => {
408
429
  // Validate based on mode
@@ -691,10 +712,26 @@ function ThreadDetail({
691
712
  // Main Command
692
713
  // ---------------------------------------------------------------------------
693
714
 
715
+ const CACHE_NAMESPACE = 'mail-tui'
716
+
694
717
  export default function Command() {
695
- const [selectedAccount, setSelectedAccount] = useState('all')
718
+ const { push } = useNavigation()
719
+ const [selectedAccount, setSelectedAccount] = useCachedState(
720
+ 'selectedAccount',
721
+ 'all',
722
+ { cacheNamespace: CACHE_NAMESPACE },
723
+ )
724
+ const [activeFolder, setActiveFolder] = useCachedState(
725
+ 'activeFolder',
726
+ 'inbox' as string,
727
+ { cacheNamespace: CACHE_NAMESPACE },
728
+ )
696
729
  const [searchText, setSearchText] = useState('')
697
- const [isShowingDetail, setIsShowingDetail] = useState(true)
730
+ const [isShowingDetail, setIsShowingDetail] = useCachedState(
731
+ 'isShowingDetail',
732
+ true,
733
+ { cacheNamespace: CACHE_NAMESPACE },
734
+ )
698
735
  const [selectedThreads, setSelectedThreads] = useState<string[]>([])
699
736
  const [activeMutations, setActiveMutations] = useState(0)
700
737
  const isMutating = activeMutations > 0
@@ -721,7 +758,7 @@ export default function Command() {
721
758
  pagination,
722
759
  revalidate,
723
760
  } = useCachedPromise(
724
- (query: string, account: string) => {
761
+ (query: string, account: string, folder: string) => {
725
762
  return async ({ cursor }: { page: number; cursor?: MailCursor }) => {
726
763
  const accountFilter = account === 'all' ? undefined : [account]
727
764
  const clients = await getClients(accountFilter)
@@ -733,6 +770,7 @@ export default function Command() {
733
770
  const { email, client } = clients[0]!
734
771
  const result = await client.listThreads({
735
772
  query: query || undefined,
773
+ folder,
736
774
  maxResults: pageSize,
737
775
  pageToken: pageToken || undefined,
738
776
  })
@@ -773,6 +811,7 @@ export default function Command() {
773
811
 
774
812
  const result = await client.listThreads({
775
813
  query: query || undefined,
814
+ folder,
776
815
  maxResults: pageSize,
777
816
  pageToken: previousByAccount[email] ?? undefined,
778
817
  })
@@ -822,7 +861,7 @@ export default function Command() {
822
861
  }
823
862
  }
824
863
  },
825
- [searchText, selectedAccount, pageSize],
864
+ [searchText, selectedAccount, activeFolder, pageSize],
826
865
  { keepPreviousData: true },
827
866
  )
828
867
 
@@ -922,7 +961,7 @@ export default function Command() {
922
961
  isLoading={isLoading || accounts.isLoading || isMutating}
923
962
  isShowingDetail={isShowingDetail}
924
963
  spacingMode={LIST_SPACING_MODE}
925
- searchBarPlaceholder='Search emails...'
964
+ searchBarPlaceholder={`Search ${FOLDER_OPTIONS.find((f) => f.id === activeFolder)?.label ?? 'emails'}...`}
926
965
  onSearchTextChange={setSearchText}
927
966
  throttle
928
967
  pagination={pagination ? { ...pagination, pageSize } : undefined}
@@ -979,7 +1018,7 @@ export default function Command() {
979
1018
  // Detail panel: latest message body as markdown
980
1019
  const detail = isShowingDetail ? (
981
1020
  <List.Item.Detail
982
- markdown={`# ${thread.subject}\n\n${thread.snippet}`}
1021
+ markdown={`\n\n${thread.snippet}`}
983
1022
  metadata={
984
1023
  <List.Item.Detail.Metadata>
985
1024
  <List.Item.Detail.Metadata.Label
@@ -1096,6 +1135,16 @@ export default function Command() {
1096
1135
  onAction={() => setSelectedThreads([])}
1097
1136
  />
1098
1137
  </ActionPanel.Section>
1138
+ <ActionPanel.Section title='Mailbox'>
1139
+ {FOLDER_OPTIONS.map((f) => (
1140
+ <Action
1141
+ key={f.id}
1142
+ title={f.label}
1143
+ icon={activeFolder === f.id ? Icon.CheckCircle : Icon.Circle}
1144
+ onAction={() => setActiveFolder(f.id)}
1145
+ />
1146
+ ))}
1147
+ </ActionPanel.Section>
1099
1148
  <ActionPanel.Section>
1100
1149
  <Action
1101
1150
  title='Refresh'
@@ -1117,17 +1166,25 @@ export default function Command() {
1117
1166
  // ─────────────────────────────────────────────
1118
1167
  <>
1119
1168
  <ActionPanel.Section>
1120
- <Action.Push
1169
+ <Action
1121
1170
  title='Open Thread'
1122
1171
  icon={Icon.Eye}
1123
- target={
1124
- <ThreadDetail
1125
- threadId={thread.id}
1126
- account={thread.account}
1127
- accounts={accountList}
1128
- revalidate={revalidate}
1129
- />
1130
- }
1172
+ onAction={async () => {
1173
+ push(
1174
+ <ThreadDetail
1175
+ threadId={thread.id}
1176
+ account={thread.account}
1177
+ accounts={accountList}
1178
+ revalidate={revalidate}
1179
+ />,
1180
+ )
1181
+ if (thread.unread) {
1182
+ const { client } = await getClient([thread.account])
1183
+ const result = await client.markAsRead({ threadIds: [thread.id] })
1184
+ if (result instanceof Error) return
1185
+ revalidate()
1186
+ }
1187
+ }}
1131
1188
  />
1132
1189
  <Action
1133
1190
  title='Select Thread'
@@ -1266,6 +1323,16 @@ export default function Command() {
1266
1323
  })}
1267
1324
  />
1268
1325
  </ActionPanel.Section>
1326
+ <ActionPanel.Section title='Mailbox'>
1327
+ {FOLDER_OPTIONS.map((f) => (
1328
+ <Action
1329
+ key={f.id}
1330
+ title={f.label}
1331
+ icon={activeFolder === f.id ? Icon.CheckCircle : Icon.Circle}
1332
+ onAction={() => setActiveFolder(f.id)}
1333
+ />
1334
+ ))}
1335
+ </ActionPanel.Section>
1269
1336
  <ActionPanel.Section>
1270
1337
  <Action
1271
1338
  title='Refresh'
package/src/output.ts CHANGED
@@ -212,17 +212,22 @@ export function printYaml(data: unknown): void {
212
212
  }
213
213
 
214
214
  /**
215
- * Print a list of items as YAML with optional pagination.
215
+ * Print a list of items as YAML with optional pagination and summary.
216
216
  * Output shape:
217
+ * summary: "5 threads (inbox)"
217
218
  * items:
218
219
  * - key: value
219
220
  * next_page: "token"
220
221
  */
221
222
  export function printList(
222
223
  items: Record<string, unknown>[],
223
- opts?: { nextPage?: string | null },
224
+ opts?: { nextPage?: string | null; summary?: string },
224
225
  ): void {
225
- const doc: Record<string, unknown> = { items }
226
+ const doc: Record<string, unknown> = {}
227
+ if (opts?.summary) {
228
+ doc.summary = opts.summary
229
+ }
230
+ doc.items = items
226
231
  if (opts?.nextPage) {
227
232
  doc.next_page = opts.nextPage
228
233
  }
package/bin/zele DELETED
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env sh
2
- # Shell launcher for zele CLI.
3
- # Prefers bun (required for the TUI), falls back to node for non-TUI commands.
4
- # Uses exec so no child process is spawned — the shell is replaced in-place.
5
- #
6
- # Shebang uses "#!/usr/bin/env sh" instead of "#!/bin/sh" so that npm's
7
- # cmd-shim on Windows captures "sh" as the program name (looked up via PATH)
8
- # rather than the literal "/bin/sh" path which doesn't exist on Windows.
9
- # Git for Windows provides sh.exe on PATH, making this work cross-platform.
10
-
11
- # Resolve symlink (npm/pnpm create symlinks for bin entries)
12
- SELF="$0"
13
- if [ -L "$SELF" ]; then
14
- LINK="$(readlink "$SELF")"
15
- case "$LINK" in
16
- /*) SELF="$LINK" ;;
17
- *) SELF="$(dirname "$SELF")/$LINK" ;;
18
- esac
19
- fi
20
- DIR="$(cd "$(dirname "$SELF")" && pwd)"
21
- CLI="$DIR/../dist/cli.js"
22
-
23
- if command -v bun >/dev/null 2>&1; then
24
- exec bun "$CLI" "$@"
25
- else
26
- exec node "$CLI" "$@"
27
- fi
@@ -1,9 +0,0 @@
1
- // Type patch for @opentui/react.
2
- //
3
- // The installed package's .d.ts re-exports are missing the hooks entrypoint,
4
- // so TS doesn't see `useTerminalDimensions` even though runtime exports it.
5
- // Keep this minimal and local to avoid sprinkling type assertions in code.
6
-
7
- declare module '@opentui/react' {
8
- export function useTerminalDimensions(): { width: number; height: number }
9
- }