zele 0.3.16 → 0.3.17
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 +91 -36
- package/dist/api-utils.d.ts +4 -0
- package/dist/api-utils.js +6 -0
- package/dist/api-utils.js.map +1 -1
- package/dist/auth.d.ts +71 -9
- package/dist/auth.js +186 -10
- package/dist/auth.js.map +1 -1
- package/dist/commands/attachment.js +2 -0
- package/dist/commands/attachment.js.map +1 -1
- package/dist/commands/auth-cmd.js +104 -6
- package/dist/commands/auth-cmd.js.map +1 -1
- package/dist/commands/draft.js +7 -1
- package/dist/commands/draft.js.map +1 -1
- package/dist/commands/filter.js +7 -2
- package/dist/commands/filter.js.map +1 -1
- package/dist/commands/label.js +19 -9
- package/dist/commands/label.js.map +1 -1
- package/dist/commands/mail-actions.js.map +1 -1
- package/dist/commands/mail.js +49 -22
- package/dist/commands/mail.js.map +1 -1
- package/dist/commands/profile.js +25 -18
- package/dist/commands/profile.js.map +1 -1
- package/dist/db.js +24 -0
- package/dist/db.js.map +1 -1
- package/dist/generated/internal/class.js +2 -2
- package/dist/generated/internal/class.js.map +1 -1
- package/dist/generated/internal/prismaNamespace.d.ts +2 -0
- package/dist/generated/internal/prismaNamespace.js +2 -0
- package/dist/generated/internal/prismaNamespace.js.map +1 -1
- package/dist/generated/internal/prismaNamespaceBrowser.d.ts +2 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js +2 -0
- package/dist/generated/internal/prismaNamespaceBrowser.js.map +1 -1
- package/dist/generated/models/Account.d.ts +97 -1
- package/dist/gmail-client.d.ts +14 -0
- package/dist/gmail-client.js +46 -0
- package/dist/gmail-client.js.map +1 -1
- package/dist/imap-smtp-client.d.ts +235 -0
- package/dist/imap-smtp-client.js +1225 -0
- package/dist/imap-smtp-client.js.map +1 -0
- package/dist/mail-tui.js.map +1 -1
- package/package.json +5 -2
- package/schema.prisma +7 -5
- package/skills/zele/SKILL.md +50 -21
- package/src/api-utils.ts +6 -0
- package/src/auth.ts +282 -14
- package/src/commands/attachment.ts +1 -0
- package/src/commands/auth-cmd.ts +112 -6
- package/src/commands/draft.ts +5 -1
- package/src/commands/filter.ts +9 -3
- package/src/commands/label.ts +22 -11
- package/src/commands/mail-actions.ts +2 -1
- package/src/commands/mail.ts +52 -22
- package/src/commands/profile.ts +27 -17
- package/src/db.ts +28 -0
- package/src/generated/internal/class.ts +2 -2
- package/src/generated/internal/prismaNamespace.ts +2 -0
- package/src/generated/internal/prismaNamespaceBrowser.ts +2 -0
- package/src/generated/models/Account.ts +97 -1
- package/src/gmail-client.test.ts +155 -2
- package/src/gmail-client.ts +65 -0
- package/src/imap-smtp-client.ts +1381 -0
- package/src/mail-tui.tsx +2 -1
- package/src/schema.sql +2 -0
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
<br/>
|
|
3
3
|
<br/>
|
|
4
4
|
<h3>zele</h3>
|
|
5
|
-
<p>
|
|
5
|
+
<p>Email & Calendar CLI — Gmail, IMAP/SMTP, Google Calendar. For you and your agents</p>
|
|
6
6
|
<br/>
|
|
7
7
|
<br/>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
Multi-account
|
|
12
|
+
Multi-account email and calendar client supporting **Google OAuth** and **IMAP/SMTP** (Fastmail, Outlook, any provider). SQLite cache, YAML output.
|
|
13
13
|
|
|
14
14
|
Requires [bun](https://bun.sh):
|
|
15
15
|
|
|
@@ -24,14 +24,53 @@ bun install -g zele
|
|
|
24
24
|
|
|
25
25
|
## Setup
|
|
26
26
|
|
|
27
|
+
### Google accounts
|
|
28
|
+
|
|
27
29
|
```bash
|
|
28
30
|
zele login
|
|
29
31
|
```
|
|
30
32
|
|
|
31
33
|
Opens a browser for Google OAuth2. Repeat to add more accounts.
|
|
32
34
|
|
|
35
|
+
### IMAP/SMTP accounts
|
|
36
|
+
|
|
37
|
+
For non-Google providers (Fastmail, Outlook, Gmail with app passwords, any IMAP server):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Fastmail
|
|
41
|
+
zele login imap \
|
|
42
|
+
--email you@fastmail.com \
|
|
43
|
+
--imap-host imap.fastmail.com --imap-port 993 \
|
|
44
|
+
--smtp-host smtp.fastmail.com --smtp-port 465 \
|
|
45
|
+
--password "your-app-password"
|
|
46
|
+
|
|
47
|
+
# Gmail (app password)
|
|
48
|
+
zele login imap \
|
|
49
|
+
--email you@gmail.com \
|
|
50
|
+
--imap-host imap.gmail.com --imap-port 993 \
|
|
51
|
+
--smtp-host smtp.gmail.com --smtp-port 465 \
|
|
52
|
+
--password "your-app-password"
|
|
53
|
+
|
|
54
|
+
# Outlook
|
|
55
|
+
zele login imap \
|
|
56
|
+
--email you@outlook.com \
|
|
57
|
+
--imap-host outlook.office365.com --imap-port 993 \
|
|
58
|
+
--smtp-host smtp-mail.outlook.com --smtp-port 587 \
|
|
59
|
+
--password "your-password"
|
|
60
|
+
|
|
61
|
+
# IMAP-only (no sending)
|
|
62
|
+
zele login imap \
|
|
63
|
+
--email reader@example.com \
|
|
64
|
+
--imap-host imap.example.com --imap-port 993 \
|
|
65
|
+
--password "pass"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use `--imap-user` / `--smtp-user` if the login username differs from your email. Omit `--smtp-host` for read-only access.
|
|
69
|
+
|
|
70
|
+
### Account management
|
|
71
|
+
|
|
33
72
|
```bash
|
|
34
|
-
zele whoami # show authenticated accounts
|
|
73
|
+
zele whoami # show authenticated accounts (type, capabilities)
|
|
35
74
|
zele logout # remove credentials
|
|
36
75
|
```
|
|
37
76
|
|
|
@@ -68,44 +107,36 @@ zele mail trash-spam
|
|
|
68
107
|
|
|
69
108
|
### Search query syntax
|
|
70
109
|
|
|
71
|
-
`mail search` and `mail
|
|
72
|
-
|
|
73
|
-
| Operator | Example |
|
|
74
|
-
|
|
75
|
-
| `from:` | `from:github` |
|
|
76
|
-
| `to:` | `to:me@example.com` |
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `is:
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
82
|
-
| `
|
|
83
|
-
|
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
|
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `OR` | `from:a OR from:b` | Match either term (search only) |
|
|
94
|
-
| `{ }` | `{from:a from:b}` | Group OR terms (search only) |
|
|
95
|
-
|
|
96
|
-
Combine multiple operators to narrow results:
|
|
110
|
+
For **Google accounts**, `mail search` and `mail list --filter` use [Gmail search operators](https://support.google.com/mail/answer/7190) server-side. For **IMAP accounts**, queries are translated to IMAP SEARCH criteria (a subset is supported).
|
|
111
|
+
|
|
112
|
+
| Operator | Example | Google | IMAP |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `from:` | `from:github` | yes | yes |
|
|
115
|
+
| `to:` | `to:me@example.com` | yes | yes |
|
|
116
|
+
| `subject:` | `subject:invoice` | yes | yes |
|
|
117
|
+
| `is:unread` | `is:unread` | yes | yes |
|
|
118
|
+
| `is:starred` | `is:starred` | yes | yes |
|
|
119
|
+
| `has:attachment` | `has:attachment` | yes | yes |
|
|
120
|
+
| `newer_than:` | `newer_than:7d` | yes | yes |
|
|
121
|
+
| `older_than:` | `older_than:1m` | yes | yes |
|
|
122
|
+
| `after:` | `after:2024/01/01` | yes | yes |
|
|
123
|
+
| `before:` | `before:2024/12/31` | yes | yes |
|
|
124
|
+
| `cc:` | `cc:team@example.com` | yes | no |
|
|
125
|
+
| `-` (negate) | `-from:noreply` | yes | no |
|
|
126
|
+
| `" "` (quotes) | `"exact phrase"` | yes | no |
|
|
127
|
+
| `label:` | `label:work` | yes | no |
|
|
128
|
+
| `in:` | `in:sent` | yes | no |
|
|
129
|
+
| `filename:` | `filename:pdf` | yes | no |
|
|
130
|
+
| `size:` / `larger:` / `smaller:` | `larger:5M` | yes | no |
|
|
131
|
+
| `OR` / `{ }` | `from:a OR from:b` | yes | no |
|
|
97
132
|
|
|
98
133
|
```bash
|
|
99
134
|
zele mail list --filter "is:unread"
|
|
100
|
-
zele mail list --filter "from:github
|
|
135
|
+
zele mail list --filter "from:github newer_than:7d" --folder sent
|
|
101
136
|
zele mail search "from:github is:unread newer_than:7d"
|
|
102
137
|
zele mail watch --query "from:github has:attachment"
|
|
103
138
|
```
|
|
104
139
|
|
|
105
|
-
`mail list --filter` accepts the same Gmail search operators as `mail search`, and combines with `--folder` and `--label`.
|
|
106
|
-
|
|
107
|
-
Operators marked **(search only)** are handled server-side by Gmail and only available in `mail search`. Using them in `mail watch --query` prints a warning and skips the operator.
|
|
108
|
-
|
|
109
140
|
### Drafts
|
|
110
141
|
|
|
111
142
|
```bash
|
|
@@ -115,7 +146,7 @@ zele draft send <draft-id>
|
|
|
115
146
|
zele draft delete <draft-id>
|
|
116
147
|
```
|
|
117
148
|
|
|
118
|
-
### Labels
|
|
149
|
+
### Labels (Google only)
|
|
119
150
|
|
|
120
151
|
```bash
|
|
121
152
|
zele label list
|
|
@@ -124,7 +155,13 @@ zele label create <name>
|
|
|
124
155
|
zele label delete <label-id>
|
|
125
156
|
```
|
|
126
157
|
|
|
127
|
-
###
|
|
158
|
+
### Filters (Google only)
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
zele mail filter list
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Calendar (Google only)
|
|
128
165
|
|
|
129
166
|
```bash
|
|
130
167
|
zele cal list # list calendars
|
|
@@ -172,6 +209,24 @@ zele profile # show account info
|
|
|
172
209
|
|
|
173
210
|
All commands support `--account <email>` to filter by account. Without it, commands fetch from all accounts and merge results.
|
|
174
211
|
|
|
212
|
+
Google and IMAP/SMTP accounts work side by side — `mail list` merges results from both. Google-only features (labels, filters, calendar) show a helpful error when used with IMAP accounts.
|
|
213
|
+
|
|
214
|
+
### Feature compatibility
|
|
215
|
+
|
|
216
|
+
| Feature | Google | IMAP/SMTP |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| List, read, search emails | yes | yes |
|
|
219
|
+
| Send, reply, forward | yes | yes (requires SMTP) |
|
|
220
|
+
| Star, archive, trash, mark read | yes | yes |
|
|
221
|
+
| Drafts | yes | yes |
|
|
222
|
+
| Attachments | yes | yes |
|
|
223
|
+
| Watch for new emails | yes | yes |
|
|
224
|
+
| Date/sender/subject filters | yes | yes |
|
|
225
|
+
| Labels | yes | no (IMAP uses folders) |
|
|
226
|
+
| Filters | yes | no |
|
|
227
|
+
| Calendar | yes | no |
|
|
228
|
+
| Gmail search operators | full | subset (see table above) |
|
|
229
|
+
|
|
175
230
|
## Output
|
|
176
231
|
|
|
177
232
|
All structured data is output as YAML. In TTY mode, keys are colored for readability. Pipe output to other tools for scripting.
|
package/dist/api-utils.d.ts
CHANGED
|
@@ -46,6 +46,10 @@ declare const ApiError_base: errore.FactoryTaggedErrorClass<"ApiError", "API cal
|
|
|
46
46
|
/** Returned when a non-auth, non-ratelimit API call fails. */
|
|
47
47
|
export declare class ApiError extends ApiError_base {
|
|
48
48
|
}
|
|
49
|
+
declare const UnsupportedError_base: errore.FactoryTaggedErrorClass<"UnsupportedError", "$feature is not available for $accountType accounts. $hint", Error>;
|
|
50
|
+
/** Returned when a command requires a capability (e.g. gmail labels) that the account doesn't support. */
|
|
51
|
+
export declare class UnsupportedError extends UnsupportedError_base {
|
|
52
|
+
}
|
|
49
53
|
/** Detect auth-like errors from underlying libraries (tsdav string errors, googleapis structured errors).
|
|
50
54
|
* Used inside clients to decide whether to return an AuthError.
|
|
51
55
|
* NOTE: String matching here is intentional — this is the boundary layer that converts
|
package/dist/api-utils.js
CHANGED
|
@@ -101,6 +101,12 @@ export class ApiError extends errore.createTaggedError({
|
|
|
101
101
|
message: 'API call failed: $reason',
|
|
102
102
|
}) {
|
|
103
103
|
}
|
|
104
|
+
/** Returned when a command requires a capability (e.g. gmail labels) that the account doesn't support. */
|
|
105
|
+
export class UnsupportedError extends errore.createTaggedError({
|
|
106
|
+
name: 'UnsupportedError',
|
|
107
|
+
message: '$feature is not available for $accountType accounts. $hint',
|
|
108
|
+
}) {
|
|
109
|
+
}
|
|
104
110
|
/** Detect auth-like errors from underlying libraries (tsdav string errors, googleapis structured errors).
|
|
105
111
|
* Used inside clients to decide whether to return an AuthError.
|
|
106
112
|
* NOTE: String matching here is intentional — this is the boundary layer that converts
|
package/dist/api-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-utils.js","sourceRoot":"","sources":["../src/api-utils.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-utils.js","sourceRoot":"","sources":["../src/api-utils.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { OAuth2Client } from 'google-auth-library';
|
|
2
2
|
import { GmailClient } from './gmail-client.js';
|
|
3
3
|
import { CalendarClient } from './calendar-client.js';
|
|
4
|
+
import { ImapSmtpClient } from './imap-smtp-client.js';
|
|
5
|
+
export type AccountType = 'google' | 'imap_smtp';
|
|
6
|
+
export declare const IMAP_SMTP_APP_ID: "imap_smtp";
|
|
7
|
+
export interface ImapCredentials {
|
|
8
|
+
host: string;
|
|
9
|
+
port: number;
|
|
10
|
+
user: string;
|
|
11
|
+
password: string;
|
|
12
|
+
tls: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface SmtpCredentials {
|
|
15
|
+
host: string;
|
|
16
|
+
port: number;
|
|
17
|
+
user: string;
|
|
18
|
+
password: string;
|
|
19
|
+
tls: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Stored in the `tokens` column for imap_smtp accounts. */
|
|
22
|
+
export interface ImapSmtpCredentials {
|
|
23
|
+
imap?: ImapCredentials;
|
|
24
|
+
smtp?: SmtpCredentials;
|
|
25
|
+
}
|
|
26
|
+
/** Capabilities an account can have. */
|
|
27
|
+
export type AccountCapability = 'gmail' | 'calendar' | 'smtp' | 'imap';
|
|
28
|
+
export declare function parseCapabilities(raw: string): AccountCapability[];
|
|
29
|
+
export declare function hasCapability(capabilities: string | AccountCapability[], cap: AccountCapability): boolean;
|
|
4
30
|
/**
|
|
5
31
|
* Create an OAuth2Client. If appId is provided, looks up the matching
|
|
6
32
|
* client credentials from OAUTH_CLIENTS by client ID. Falls back to
|
|
@@ -10,6 +36,8 @@ export declare function createOAuth2Client(appId?: string): OAuth2Client;
|
|
|
10
36
|
export interface AccountId {
|
|
11
37
|
email: string;
|
|
12
38
|
appId: string;
|
|
39
|
+
accountType: AccountType;
|
|
40
|
+
capabilities: AccountCapability[];
|
|
13
41
|
}
|
|
14
42
|
interface BrowserAuthOptions {
|
|
15
43
|
openBrowser?: boolean;
|
|
@@ -25,28 +53,60 @@ export declare function login(appId?: string, options?: BrowserAuthOptions): Pro
|
|
|
25
53
|
appId: string;
|
|
26
54
|
client: GmailClient;
|
|
27
55
|
} | Error>;
|
|
56
|
+
export interface LoginImapOptions {
|
|
57
|
+
email: string;
|
|
58
|
+
imapHost: string;
|
|
59
|
+
imapPort?: number;
|
|
60
|
+
smtpHost?: string;
|
|
61
|
+
smtpPort?: number;
|
|
62
|
+
password?: string;
|
|
63
|
+
imapUser?: string;
|
|
64
|
+
imapPassword?: string;
|
|
65
|
+
smtpUser?: string;
|
|
66
|
+
smtpPassword?: string;
|
|
67
|
+
tls?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Login with IMAP/SMTP credentials. Tests connections before saving.
|
|
71
|
+
* Returns an error value if validation or connection test fails.
|
|
72
|
+
*/
|
|
73
|
+
export declare function loginImap(options: LoginImapOptions): Promise<{
|
|
74
|
+
email: string;
|
|
75
|
+
appId: string;
|
|
76
|
+
} | Error>;
|
|
28
77
|
export declare function logout(email: string): Promise<void | Error>;
|
|
29
78
|
export declare function listAccounts(): Promise<AccountId[]>;
|
|
79
|
+
/** Entry returned by getClients — client is GmailClient for Google accounts, ImapSmtpClient for IMAP/SMTP. */
|
|
80
|
+
export interface ClientEntry {
|
|
81
|
+
email: string;
|
|
82
|
+
appId: string;
|
|
83
|
+
accountType: AccountType;
|
|
84
|
+
capabilities: AccountCapability[];
|
|
85
|
+
client: GmailClient | ImapSmtpClient;
|
|
86
|
+
}
|
|
30
87
|
/**
|
|
31
|
-
* Get authenticated
|
|
88
|
+
* Get authenticated client instances for all accounts (or filtered by email list).
|
|
89
|
+
* Returns GmailClient for Google accounts and ImapSmtpClient for IMAP/SMTP accounts.
|
|
32
90
|
* If no accounts are registered, throws with a helpful message.
|
|
33
91
|
*/
|
|
34
|
-
export declare function getClients(accounts?: string[]): Promise<
|
|
35
|
-
email: string;
|
|
36
|
-
appId: string;
|
|
37
|
-
client: GmailClient;
|
|
38
|
-
}>>;
|
|
92
|
+
export declare function getClients(accounts?: string[]): Promise<ClientEntry[]>;
|
|
39
93
|
/**
|
|
40
|
-
* Get a single authenticated
|
|
94
|
+
* Get a single authenticated client. Errors if multiple accounts exist
|
|
41
95
|
* and no --account filter was provided.
|
|
42
96
|
*/
|
|
43
|
-
export declare function getClient(accounts?: string[]): Promise<
|
|
97
|
+
export declare function getClient(accounts?: string[]): Promise<ClientEntry>;
|
|
98
|
+
/**
|
|
99
|
+
* Get a single authenticated GmailClient. Errors if account is not a Google account.
|
|
100
|
+
* Use this for commands that require Gmail-specific features.
|
|
101
|
+
*/
|
|
102
|
+
export declare function getGmailClient(accounts?: string[]): Promise<{
|
|
44
103
|
email: string;
|
|
45
104
|
appId: string;
|
|
46
105
|
client: GmailClient;
|
|
47
106
|
}>;
|
|
48
107
|
/**
|
|
49
|
-
* Get authenticated CalendarClient instances for
|
|
108
|
+
* Get authenticated CalendarClient instances for Google accounts only.
|
|
109
|
+
* IMAP/SMTP accounts are silently skipped (calendar requires Google OAuth).
|
|
50
110
|
*/
|
|
51
111
|
export declare function getCalendarClients(accounts?: string[]): Promise<Array<{
|
|
52
112
|
email: string;
|
|
@@ -65,6 +125,8 @@ export declare function getCalendarClient(accounts?: string[]): Promise<{
|
|
|
65
125
|
export interface AuthStatus {
|
|
66
126
|
email: string;
|
|
67
127
|
appId: string;
|
|
128
|
+
accountType: AccountType;
|
|
129
|
+
capabilities: AccountCapability[];
|
|
68
130
|
expiresAt?: Date;
|
|
69
131
|
}
|
|
70
132
|
export declare function getAuthStatuses(): Promise<AuthStatus[]>;
|
package/dist/auth.js
CHANGED
|
@@ -15,6 +15,18 @@ import { getPrisma } from './db.js';
|
|
|
15
15
|
import { GmailClient } from './gmail-client.js';
|
|
16
16
|
import { CalendarClient } from './calendar-client.js';
|
|
17
17
|
import * as errore from 'errore';
|
|
18
|
+
import { AuthError, UnsupportedError } from './api-utils.js';
|
|
19
|
+
import { ImapSmtpClient } from './imap-smtp-client.js';
|
|
20
|
+
export const IMAP_SMTP_APP_ID = 'imap_smtp';
|
|
21
|
+
export function parseCapabilities(raw) {
|
|
22
|
+
if (!raw)
|
|
23
|
+
return [];
|
|
24
|
+
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
export function hasCapability(capabilities, cap) {
|
|
27
|
+
const list = typeof capabilities === 'string' ? parseCapabilities(capabilities) : capabilities;
|
|
28
|
+
return list.includes(cap);
|
|
29
|
+
}
|
|
18
30
|
// ---------------------------------------------------------------------------
|
|
19
31
|
// Known open-source Google OAuth clients (Desktop app type).
|
|
20
32
|
// All support localhost + OOB redirects. All have Gmail, Calendar, Drive,
|
|
@@ -449,18 +461,21 @@ export async function login(appId, options) {
|
|
|
449
461
|
const email = profile.emailAddress;
|
|
450
462
|
// Upsert account in DB
|
|
451
463
|
const prisma = await getPrisma();
|
|
464
|
+
const googleCapabilities = 'gmail,calendar,smtp';
|
|
452
465
|
const upsertResult = await errore.tryAsync({
|
|
453
466
|
try: () => prisma.account.upsert({
|
|
454
467
|
where: { email_appId: { email, appId: resolved.clientId } },
|
|
455
468
|
create: {
|
|
456
469
|
email,
|
|
457
470
|
appId: resolved.clientId,
|
|
471
|
+
accountType: 'google',
|
|
472
|
+
capabilities: googleCapabilities,
|
|
458
473
|
accountStatus: 'active',
|
|
459
474
|
tokens: JSON.stringify(tokens),
|
|
460
475
|
createdAt: new Date(),
|
|
461
476
|
updatedAt: new Date(),
|
|
462
477
|
},
|
|
463
|
-
update: { tokens: JSON.stringify(tokens), updatedAt: new Date() },
|
|
478
|
+
update: { tokens: JSON.stringify(tokens), capabilities: googleCapabilities, updatedAt: new Date() },
|
|
464
479
|
}),
|
|
465
480
|
catch: (err) => new Error(`Failed to save account ${email}`, { cause: err }),
|
|
466
481
|
});
|
|
@@ -468,6 +483,100 @@ export async function login(appId, options) {
|
|
|
468
483
|
return upsertResult;
|
|
469
484
|
return { email, appId: resolved.clientId, client };
|
|
470
485
|
}
|
|
486
|
+
/**
|
|
487
|
+
* Login with IMAP/SMTP credentials. Tests connections before saving.
|
|
488
|
+
* Returns an error value if validation or connection test fails.
|
|
489
|
+
*/
|
|
490
|
+
export async function loginImap(options) {
|
|
491
|
+
const { email, imapHost, imapPort = 993, smtpHost, smtpPort = 465, password, imapUser, imapPassword, smtpUser, smtpPassword, tls = true, } = options;
|
|
492
|
+
const imapPass = imapPassword ?? password;
|
|
493
|
+
if (!imapPass) {
|
|
494
|
+
return new Error('IMAP password is required (--password or --imap-password)');
|
|
495
|
+
}
|
|
496
|
+
const credentials = {
|
|
497
|
+
imap: {
|
|
498
|
+
host: imapHost,
|
|
499
|
+
port: imapPort,
|
|
500
|
+
user: imapUser ?? email,
|
|
501
|
+
password: imapPass,
|
|
502
|
+
tls,
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
// Test IMAP connection
|
|
506
|
+
const { ImapFlow } = await import('imapflow');
|
|
507
|
+
const testClient = new ImapFlow({
|
|
508
|
+
host: imapHost,
|
|
509
|
+
port: imapPort,
|
|
510
|
+
secure: tls,
|
|
511
|
+
auth: { user: imapUser ?? email, pass: imapPass },
|
|
512
|
+
logger: false,
|
|
513
|
+
});
|
|
514
|
+
const imapTest = await errore.tryAsync({
|
|
515
|
+
try: async () => {
|
|
516
|
+
await testClient.connect();
|
|
517
|
+
await testClient.logout();
|
|
518
|
+
},
|
|
519
|
+
catch: (err) => new AuthError({ email, reason: `IMAP connection failed: ${String(err)}` }),
|
|
520
|
+
});
|
|
521
|
+
if (imapTest instanceof Error)
|
|
522
|
+
return imapTest;
|
|
523
|
+
// Configure SMTP if provided
|
|
524
|
+
const capabilities = ['imap'];
|
|
525
|
+
if (smtpHost) {
|
|
526
|
+
const smtpPass = smtpPassword ?? password;
|
|
527
|
+
if (!smtpPass) {
|
|
528
|
+
return new Error('SMTP password is required when --smtp-host is provided (--password or --smtp-password)');
|
|
529
|
+
}
|
|
530
|
+
credentials.smtp = {
|
|
531
|
+
host: smtpHost,
|
|
532
|
+
port: smtpPort,
|
|
533
|
+
user: smtpUser ?? email,
|
|
534
|
+
password: smtpPass,
|
|
535
|
+
tls: smtpPort === 465,
|
|
536
|
+
};
|
|
537
|
+
// Test SMTP connection
|
|
538
|
+
const nodemailer = await import('nodemailer');
|
|
539
|
+
const transporter = nodemailer.default.createTransport({
|
|
540
|
+
host: smtpHost,
|
|
541
|
+
port: smtpPort,
|
|
542
|
+
secure: smtpPort === 465,
|
|
543
|
+
auth: { user: smtpUser ?? email, pass: smtpPass },
|
|
544
|
+
});
|
|
545
|
+
const smtpTest = await errore.tryAsync({
|
|
546
|
+
try: () => transporter.verify(),
|
|
547
|
+
catch: (err) => new AuthError({ email, reason: `SMTP connection failed: ${String(err)}` }),
|
|
548
|
+
});
|
|
549
|
+
if (smtpTest instanceof Error)
|
|
550
|
+
return smtpTest;
|
|
551
|
+
capabilities.push('smtp');
|
|
552
|
+
}
|
|
553
|
+
// Save to DB
|
|
554
|
+
const prisma = await getPrisma();
|
|
555
|
+
const upsertResult = await errore.tryAsync({
|
|
556
|
+
try: () => prisma.account.upsert({
|
|
557
|
+
where: { email_appId: { email, appId: IMAP_SMTP_APP_ID } },
|
|
558
|
+
create: {
|
|
559
|
+
email,
|
|
560
|
+
appId: IMAP_SMTP_APP_ID,
|
|
561
|
+
accountType: 'imap_smtp',
|
|
562
|
+
capabilities: capabilities.join(','),
|
|
563
|
+
accountStatus: 'active',
|
|
564
|
+
tokens: JSON.stringify(credentials),
|
|
565
|
+
createdAt: new Date(),
|
|
566
|
+
updatedAt: new Date(),
|
|
567
|
+
},
|
|
568
|
+
update: {
|
|
569
|
+
capabilities: capabilities.join(','),
|
|
570
|
+
tokens: JSON.stringify(credentials),
|
|
571
|
+
updatedAt: new Date(),
|
|
572
|
+
},
|
|
573
|
+
}),
|
|
574
|
+
catch: (err) => new Error(`Failed to save account ${email}`, { cause: err }),
|
|
575
|
+
});
|
|
576
|
+
if (upsertResult instanceof Error)
|
|
577
|
+
return upsertResult;
|
|
578
|
+
return { email, appId: IMAP_SMTP_APP_ID };
|
|
579
|
+
}
|
|
471
580
|
// ---------------------------------------------------------------------------
|
|
472
581
|
// Logout: remove account from DB
|
|
473
582
|
// ---------------------------------------------------------------------------
|
|
@@ -486,8 +595,13 @@ export async function logout(email) {
|
|
|
486
595
|
// ---------------------------------------------------------------------------
|
|
487
596
|
export async function listAccounts() {
|
|
488
597
|
const prisma = await getPrisma();
|
|
489
|
-
const rows = await prisma.account.findMany({ select: { email: true, appId: true } });
|
|
490
|
-
return rows.map((r) => ({
|
|
598
|
+
const rows = await prisma.account.findMany({ select: { email: true, appId: true, accountType: true, capabilities: true } });
|
|
599
|
+
return rows.map((r) => ({
|
|
600
|
+
email: r.email,
|
|
601
|
+
appId: r.appId,
|
|
602
|
+
accountType: r.accountType,
|
|
603
|
+
capabilities: parseCapabilities(r.capabilities),
|
|
604
|
+
}));
|
|
491
605
|
}
|
|
492
606
|
// ---------------------------------------------------------------------------
|
|
493
607
|
// Get authenticated clients
|
|
@@ -523,7 +637,8 @@ async function authenticateAccount(account) {
|
|
|
523
637
|
return oauth2Client;
|
|
524
638
|
}
|
|
525
639
|
/**
|
|
526
|
-
* Get authenticated
|
|
640
|
+
* Get authenticated client instances for all accounts (or filtered by email list).
|
|
641
|
+
* Returns GmailClient for Google accounts and ImapSmtpClient for IMAP/SMTP accounts.
|
|
527
642
|
* If no accounts are registered, throws with a helpful message.
|
|
528
643
|
*/
|
|
529
644
|
export async function getClients(accounts) {
|
|
@@ -538,14 +653,37 @@ export async function getClients(accounts) {
|
|
|
538
653
|
const available = allAccounts.map((a) => a.email).join(', ');
|
|
539
654
|
throw new Error(`No matching accounts. Available: ${available}`);
|
|
540
655
|
}
|
|
656
|
+
const prisma = await getPrisma();
|
|
541
657
|
const results = await Promise.all(filtered.map(async (account) => {
|
|
658
|
+
if (account.accountType === 'imap_smtp') {
|
|
659
|
+
const row = await prisma.account.findUnique({
|
|
660
|
+
where: { email_appId: { email: account.email, appId: account.appId } },
|
|
661
|
+
});
|
|
662
|
+
if (!row)
|
|
663
|
+
throw new Error(`No account found for ${account.email}. Run: zele login`);
|
|
664
|
+
const credentials = JSON.parse(row.tokens);
|
|
665
|
+
return {
|
|
666
|
+
email: account.email,
|
|
667
|
+
appId: account.appId,
|
|
668
|
+
accountType: 'imap_smtp',
|
|
669
|
+
capabilities: account.capabilities,
|
|
670
|
+
client: new ImapSmtpClient({ credentials, account }),
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
// Google account
|
|
542
674
|
const auth = await authenticateAccount(account);
|
|
543
|
-
return {
|
|
675
|
+
return {
|
|
676
|
+
email: account.email,
|
|
677
|
+
appId: account.appId,
|
|
678
|
+
accountType: 'google',
|
|
679
|
+
capabilities: account.capabilities,
|
|
680
|
+
client: new GmailClient({ auth, account }),
|
|
681
|
+
};
|
|
544
682
|
}));
|
|
545
683
|
return results;
|
|
546
684
|
}
|
|
547
685
|
/**
|
|
548
|
-
* Get a single authenticated
|
|
686
|
+
* Get a single authenticated client. Errors if multiple accounts exist
|
|
549
687
|
* and no --account filter was provided.
|
|
550
688
|
*/
|
|
551
689
|
export async function getClient(accounts) {
|
|
@@ -556,11 +694,27 @@ export async function getClient(accounts) {
|
|
|
556
694
|
const emails = clients.map((c) => c.email).join('\n ');
|
|
557
695
|
throw new Error(`Multiple accounts matched. Specify --account:\n ${emails}`);
|
|
558
696
|
}
|
|
697
|
+
/**
|
|
698
|
+
* Get a single authenticated GmailClient. Errors if account is not a Google account.
|
|
699
|
+
* Use this for commands that require Gmail-specific features.
|
|
700
|
+
*/
|
|
701
|
+
export async function getGmailClient(accounts) {
|
|
702
|
+
const entry = await getClient(accounts);
|
|
703
|
+
if (entry.accountType !== 'google') {
|
|
704
|
+
throw new UnsupportedError({
|
|
705
|
+
feature: 'This command',
|
|
706
|
+
accountType: 'IMAP/SMTP',
|
|
707
|
+
hint: 'It requires a Google account.',
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
return { email: entry.email, appId: entry.appId, client: entry.client };
|
|
711
|
+
}
|
|
559
712
|
// ---------------------------------------------------------------------------
|
|
560
713
|
// Calendar client helpers
|
|
561
714
|
// ---------------------------------------------------------------------------
|
|
562
715
|
/**
|
|
563
|
-
* Get authenticated CalendarClient instances for
|
|
716
|
+
* Get authenticated CalendarClient instances for Google accounts only.
|
|
717
|
+
* IMAP/SMTP accounts are silently skipped (calendar requires Google OAuth).
|
|
564
718
|
*/
|
|
565
719
|
export async function getCalendarClients(accounts) {
|
|
566
720
|
const allAccounts = await listAccounts();
|
|
@@ -574,7 +728,16 @@ export async function getCalendarClients(accounts) {
|
|
|
574
728
|
const available = allAccounts.map((a) => a.email).join(', ');
|
|
575
729
|
throw new Error(`No matching accounts. Available: ${available}`);
|
|
576
730
|
}
|
|
577
|
-
|
|
731
|
+
// Only Google accounts support calendar
|
|
732
|
+
const googleAccounts = filtered.filter((a) => a.accountType === 'google');
|
|
733
|
+
if (googleAccounts.length === 0) {
|
|
734
|
+
throw new UnsupportedError({
|
|
735
|
+
feature: 'Calendar',
|
|
736
|
+
accountType: 'IMAP/SMTP',
|
|
737
|
+
hint: 'Calendar requires a Google account.',
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
const results = await Promise.all(googleAccounts.map(async (account) => {
|
|
578
741
|
const auth = await authenticateAccount(account);
|
|
579
742
|
const { token } = await auth.getAccessToken();
|
|
580
743
|
if (!token)
|
|
@@ -599,11 +762,24 @@ export async function getAuthStatuses() {
|
|
|
599
762
|
const prisma = await getPrisma();
|
|
600
763
|
const rows = await prisma.account.findMany();
|
|
601
764
|
return rows.map((row) => {
|
|
602
|
-
const
|
|
765
|
+
const accountType = row.accountType;
|
|
766
|
+
const capabilities = parseCapabilities(row.capabilities);
|
|
767
|
+
if (accountType === 'google') {
|
|
768
|
+
const tokens = JSON.parse(row.tokens);
|
|
769
|
+
return {
|
|
770
|
+
email: row.email,
|
|
771
|
+
appId: row.appId,
|
|
772
|
+
accountType,
|
|
773
|
+
capabilities,
|
|
774
|
+
expiresAt: tokens.expiry_date ? new Date(tokens.expiry_date) : undefined,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
// IMAP/SMTP — no expiry
|
|
603
778
|
return {
|
|
604
779
|
email: row.email,
|
|
605
780
|
appId: row.appId,
|
|
606
|
-
|
|
781
|
+
accountType,
|
|
782
|
+
capabilities,
|
|
607
783
|
};
|
|
608
784
|
});
|
|
609
785
|
}
|