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.
- package/README.md +8 -1
- package/dist/auth.js +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +10 -8
- package/dist/cli.js.map +1 -1
- package/dist/commands/attachment.js +44 -5
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/auth-cmd.js +1 -2
- package/dist/commands/auth-cmd.js.map +1 -1
- package/dist/commands/calendar.js +4 -6
- package/dist/commands/calendar.js.map +1 -1
- package/dist/commands/draft.js +2 -3
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/label.js +4 -5
- package/dist/commands/label.js.map +1 -1
- package/dist/commands/mail.js +24 -21
- package/dist/commands/mail.js.map +1 -1
- package/dist/db.js +24 -1
- package/dist/db.js.map +1 -1
- package/dist/gmail-client.js +39 -4
- package/dist/gmail-client.js.map +1 -1
- package/dist/mail-tui.js +47 -12
- package/dist/mail-tui.js.map +1 -1
- package/dist/output.d.ts +3 -1
- package/dist/output.js +7 -2
- package/dist/output.js.map +1 -1
- package/package.json +11 -5
- package/src/app.log +9 -0
- package/src/auth.ts +1 -1
- package/src/cli.ts +31 -30
- package/src/commands/attachment.ts +49 -6
- package/src/commands/auth-cmd.ts +1 -2
- package/src/commands/calendar.ts +4 -7
- package/src/commands/draft.ts +2 -3
- package/src/commands/label.ts +4 -4
- package/src/commands/mail.ts +24 -19
- package/src/db.ts +26 -1
- package/src/gmail-client.ts +46 -4
- package/src/mail-tui.test.ts +170 -0
- package/src/mail-tui.tsx +87 -20
- package/src/output.ts +8 -3
- package/bin/zele +0 -27
- 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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
|
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] =
|
|
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=
|
|
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={
|
|
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
|
|
1169
|
+
<Action
|
|
1121
1170
|
title='Open Thread'
|
|
1122
1171
|
icon={Icon.Eye}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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> = {
|
|
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
|
package/src/opentui-react.d.ts
DELETED
|
@@ -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
|
-
}
|