+1
-7
AGENT.md
+1
-7
AGENT.md
···
69
69
duplicating error handling code.
70
70
18. DONE Add support for JMAP email submission to the library, and create a fastmail-send that accepts
71
71
a list of to: on the CLI as arguments and a subject on the CLI and reads in the message body
72
-
19. Potential future work:
73
-
- Add helper functions for more complex filtering of emails
74
-
- Implement support for Email/copy and Email/import methods
75
-
- Add functions for managing identities and vacation responses
76
-
- Add proper testing framework with mocked JMAP server responses
77
-
- Create more examples of different JMAP operations (creating/updating mailboxes and emails)
78
-
- Add support for other JMAP extensions like Contacts and Calendars
72
+
19. DONE Port fastmail-list to use Cmdliner instead of Arg with nice manual page.
+1
-1
bin/dune
+1
-1
bin/dune
+70
-43
bin/fastmail_list.ml
+70
-43
bin/fastmail_list.ml
···
9
9
* JMAP_API_TOKEN=your_api_token ./fastmail_list [options]
10
10
*
11
11
* Options:
12
-
* -unread List only unread messages
13
-
* -labels Show labels/keywords associated with messages
14
-
* -debug LEVEL Set debug level (0-4, where 4 is most verbose)
12
+
* --unread List only unread messages
13
+
* --labels Show labels/keywords associated with messages
14
+
* --debug=LEVEL Set debug level (0-4, where 4 is most verbose)
15
+
* --from=PATTERN Filter messages by sender email address
16
+
* --demo-refs Demonstrate result references feature
15
17
*)
16
18
17
19
open Lwt.Syntax
18
20
open Jmap
19
21
open Jmap_mail
22
+
open Cmdliner
20
23
module Mail = Jmap_mail.Types
21
24
22
25
(** Prints the email details *)
···
112
115
113
116
Lwt.return_unit
114
117
115
-
(** Main function *)
116
-
let main () =
117
-
(* Parse command-line arguments *)
118
-
let unread_only = ref false in
119
-
let show_labels = ref false in
120
-
let debug_level = ref 0 in
121
-
let demo_refs = ref false in
122
-
let sender_filter = ref "" in
123
-
124
-
let args = [
125
-
("-unread", Arg.Set unread_only, "List only unread messages");
126
-
("-labels", Arg.Set show_labels, "Show labels/keywords associated with messages");
127
-
("-debug", Arg.Int (fun level -> debug_level := level), "Set debug level (0-4, where 4 is most verbose)");
128
-
("-demo-refs", Arg.Set demo_refs, "Demonstrate result references");
129
-
("-from", Arg.Set_string sender_filter, "Filter messages by sender email address (supports wildcards: * and ?)");
130
-
] in
131
-
132
-
let usage_msg = "Usage: JMAP_API_TOKEN=your_token fastmail_list [options]" in
133
-
Arg.parse args (fun _ -> ()) usage_msg;
134
-
118
+
(** Main function for listing emails *)
119
+
let list_emails unread_only show_labels debug_level demo_refs sender_filter =
135
120
(* Configure logging *)
136
-
init_logging ~level:!debug_level ~enable_logs:(!debug_level > 0) ~redact_sensitive:true ();
121
+
init_logging ~level:debug_level ~enable_logs:(debug_level > 0) ~redact_sensitive:true ();
137
122
138
123
match Sys.getenv_opt "JMAP_API_TOKEN" with
139
124
| None ->
140
125
Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n";
141
-
Printf.eprintf "Usage: JMAP_API_TOKEN=your_token ./fastmail_list [options]\n";
142
-
Printf.eprintf "Options:\n";
143
-
Printf.eprintf " -unread List only unread messages\n";
144
-
Printf.eprintf " -labels Show labels/keywords associated with messages\n";
145
-
Printf.eprintf " -debug LEVEL Set debug level (0-4, where 4 is most verbose)\n";
146
-
Printf.eprintf " -demo-refs Demonstrate result references\n";
147
-
Printf.eprintf " -from PATTERN Filter messages by sender email address (supports wildcards: * and ?)\n";
126
+
Printf.eprintf "Usage: JMAP_API_TOKEN=your_token fastmail-list [options]\n";
148
127
exit 1
149
128
| Some token ->
150
129
(* Only print token info at Info level or higher *)
···
166
145
Printf.printf "1. Get a token from: https://app.fastmail.com/settings/tokens\n";
167
146
Printf.printf "2. Create a new token with Mail scope (read/write)\n";
168
147
Printf.printf "3. Copy the full token (example: 3de40-5fg1h2-a1b2c3...)\n";
169
-
Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" opam exec -- dune exec bin/fastmail_list.exe [options]\n\n";
148
+
Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" fastmail-list [options]\n\n";
170
149
Printf.printf "Note: This example is working correctly but needs a valid Fastmail token.\n\n";
171
150
end;
172
151
let* result = login_with_token
···
193
172
194
173
(* Run result references demo if requested *)
195
174
let* () =
196
-
if !demo_refs then
175
+
if demo_refs then
197
176
demo_result_references conn primary_account_id
198
177
else
199
178
Lwt.return_unit
···
230
209
| Ok emails ->
231
210
(* Apply filters based on command line arguments *)
232
211
let filtered_by_unread =
233
-
if !unread_only then
212
+
if unread_only then
234
213
List.filter is_unread emails
235
214
else
236
215
emails
···
238
217
239
218
(* Apply sender filter if specified *)
240
219
let filtered_emails =
241
-
if !sender_filter <> "" then begin
242
-
Printf.printf "Filtering by sender: %s\n" !sender_filter;
220
+
if sender_filter <> "" then begin
221
+
Printf.printf "Filtering by sender: %s\n" sender_filter;
243
222
List.filter (fun email ->
244
-
Jmap_mail.email_matches_sender email !sender_filter
223
+
Jmap_mail.email_matches_sender email sender_filter
245
224
) filtered_by_unread
246
225
end else
247
226
filtered_by_unread
···
250
229
(* Create description of applied filters *)
251
230
let filter_description =
252
231
let parts = [] in
253
-
let parts = if !unread_only then "unread" :: parts else parts in
254
-
let parts = if !sender_filter <> "" then ("from \"" ^ !sender_filter ^ "\"") :: parts else parts in
232
+
let parts = if unread_only then "unread" :: parts else parts in
233
+
let parts = if sender_filter <> "" then ("from \"" ^ sender_filter ^ "\"") :: parts else parts in
255
234
match parts with
256
235
| [] -> "the most recent"
257
236
| [p] -> p
···
262
241
filter_description
263
242
(List.length filtered_emails);
264
243
Printf.printf "--------------------------------------------\n";
265
-
List.iter (print_email ~show_labels:!show_labels) filtered_emails;
244
+
List.iter (print_email ~show_labels) filtered_emails;
266
245
Lwt.return 0
267
246
247
+
(** Command line interface *)
248
+
let unread_only =
249
+
let doc = "List only unread messages" in
250
+
Arg.(value & flag & info ["unread"] ~doc)
251
+
252
+
let show_labels =
253
+
let doc = "Show labels/keywords associated with messages" in
254
+
Arg.(value & flag & info ["labels"] ~doc)
255
+
256
+
let debug_level =
257
+
let doc = "Set debug level (0-4, where 4 is most verbose)" in
258
+
Arg.(value & opt int 0 & info ["debug"] ~docv:"LEVEL" ~doc)
259
+
260
+
let demo_refs =
261
+
let doc = "Demonstrate result references feature" in
262
+
Arg.(value & flag & info ["demo-refs"] ~doc)
263
+
264
+
let sender_filter =
265
+
let doc = "Filter messages by sender email address (supports wildcards: * and ?)" in
266
+
Arg.(value & opt string "" & info ["from"] ~docv:"PATTERN" ~doc)
267
+
268
+
let cmd =
269
+
let doc = "List emails from a Fastmail account using JMAP API" in
270
+
let man = [
271
+
`S Manpage.s_description;
272
+
`P "This program connects to the Fastmail JMAP API using an authentication token
273
+
from the JMAP_API_TOKEN environment variable and lists the most recent emails
274
+
with their subjects, sender details, and labels.";
275
+
`P "You must obtain a Fastmail API token from https://app.fastmail.com/settings/tokens
276
+
and set it in the JMAP_API_TOKEN environment variable.";
277
+
`S Manpage.s_environment;
278
+
`P "$(b,JMAP_API_TOKEN) The Fastmail API authentication token (required)";
279
+
`S Manpage.s_examples;
280
+
`P "List all emails:";
281
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token)";
282
+
`P "List only unread emails:";
283
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread";
284
+
`P "List emails from a specific sender:";
285
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --from=user@example.com";
286
+
`P "List unread emails with labels:";
287
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread --labels";
288
+
] in
289
+
let info = Cmd.info "fastmail-list" ~doc ~man in
290
+
Cmd.v info Term.(const (fun u l d r s ->
291
+
Lwt_main.run (list_emails u l d r s)
292
+
) $ unread_only $ show_labels $ debug_level $ demo_refs $ sender_filter)
293
+
268
294
(** Program entry point *)
269
-
let () =
270
-
let exit_code = Lwt_main.run (main ()) in
271
-
exit exit_code
295
+
let () = exit (Cmd.eval_value cmd |> function
296
+
| Ok (`Ok exit_code) -> exit_code
297
+
| Ok (`Version | `Help) -> 0
298
+
| Error _ -> 1)