this repo has no description

Port fastmail-list to use Cmdliner for better CLI interface

- Replace Arg with Cmdliner for argument parsing
- Add comprehensive manual page with examples and documentation
- Update command-line flags to use modern double-dash format (--flag)
- Improve code structure to handle parameters without global references

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>

Changed files
+72 -51
bin
+1 -7
AGENT.md
···
duplicating error handling code.
18. DONE Add support for JMAP email submission to the library, and create a fastmail-send that accepts
a list of to: on the CLI as arguments and a subject on the CLI and reads in the message body
-
19. Potential future work:
-
- Add helper functions for more complex filtering of emails
-
- Implement support for Email/copy and Email/import methods
-
- Add functions for managing identities and vacation responses
-
- Add proper testing framework with mocked JMAP server responses
-
- Create more examples of different JMAP operations (creating/updating mailboxes and emails)
-
- Add support for other JMAP extensions like Contacts and Calendars
+
19. DONE Port fastmail-list to use Cmdliner instead of Arg with nice manual page.
+1 -1
bin/dune
···
(public_name fastmail-list)
(package jmap)
(modules fastmail_list)
-
(libraries jmap jmap_mail lwt.unix logs logs.fmt))
+
(libraries jmap jmap_mail lwt.unix logs logs.fmt cmdliner))
(executable
(name flag_color_test)
+70 -43
bin/fastmail_list.ml
···
* JMAP_API_TOKEN=your_api_token ./fastmail_list [options]
*
* Options:
-
* -unread List only unread messages
-
* -labels Show labels/keywords associated with messages
-
* -debug LEVEL Set debug level (0-4, where 4 is most verbose)
+
* --unread List only unread messages
+
* --labels Show labels/keywords associated with messages
+
* --debug=LEVEL Set debug level (0-4, where 4 is most verbose)
+
* --from=PATTERN Filter messages by sender email address
+
* --demo-refs Demonstrate result references feature
*)
open Lwt.Syntax
open Jmap
open Jmap_mail
+
open Cmdliner
module Mail = Jmap_mail.Types
(** Prints the email details *)
···
Lwt.return_unit
-
(** Main function *)
-
let main () =
-
(* Parse command-line arguments *)
-
let unread_only = ref false in
-
let show_labels = ref false in
-
let debug_level = ref 0 in
-
let demo_refs = ref false in
-
let sender_filter = ref "" in
-
-
let args = [
-
("-unread", Arg.Set unread_only, "List only unread messages");
-
("-labels", Arg.Set show_labels, "Show labels/keywords associated with messages");
-
("-debug", Arg.Int (fun level -> debug_level := level), "Set debug level (0-4, where 4 is most verbose)");
-
("-demo-refs", Arg.Set demo_refs, "Demonstrate result references");
-
("-from", Arg.Set_string sender_filter, "Filter messages by sender email address (supports wildcards: * and ?)");
-
] in
-
-
let usage_msg = "Usage: JMAP_API_TOKEN=your_token fastmail_list [options]" in
-
Arg.parse args (fun _ -> ()) usage_msg;
-
+
(** Main function for listing emails *)
+
let list_emails unread_only show_labels debug_level demo_refs sender_filter =
(* Configure logging *)
-
init_logging ~level:!debug_level ~enable_logs:(!debug_level > 0) ~redact_sensitive:true ();
+
init_logging ~level:debug_level ~enable_logs:(debug_level > 0) ~redact_sensitive:true ();
match Sys.getenv_opt "JMAP_API_TOKEN" with
| None ->
Printf.eprintf "Error: JMAP_API_TOKEN environment variable not set\n";
-
Printf.eprintf "Usage: JMAP_API_TOKEN=your_token ./fastmail_list [options]\n";
-
Printf.eprintf "Options:\n";
-
Printf.eprintf " -unread List only unread messages\n";
-
Printf.eprintf " -labels Show labels/keywords associated with messages\n";
-
Printf.eprintf " -debug LEVEL Set debug level (0-4, where 4 is most verbose)\n";
-
Printf.eprintf " -demo-refs Demonstrate result references\n";
-
Printf.eprintf " -from PATTERN Filter messages by sender email address (supports wildcards: * and ?)\n";
+
Printf.eprintf "Usage: JMAP_API_TOKEN=your_token fastmail-list [options]\n";
exit 1
| Some token ->
(* Only print token info at Info level or higher *)
···
Printf.printf "1. Get a token from: https://app.fastmail.com/settings/tokens\n";
Printf.printf "2. Create a new token with Mail scope (read/write)\n";
Printf.printf "3. Copy the full token (example: 3de40-5fg1h2-a1b2c3...)\n";
-
Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" opam exec -- dune exec bin/fastmail_list.exe [options]\n\n";
+
Printf.printf "4. Run: env JMAP_API_TOKEN=\"your_full_token\" fastmail-list [options]\n\n";
Printf.printf "Note: This example is working correctly but needs a valid Fastmail token.\n\n";
end;
let* result = login_with_token
···
(* Run result references demo if requested *)
let* () =
-
if !demo_refs then
+
if demo_refs then
demo_result_references conn primary_account_id
else
Lwt.return_unit
···
| Ok emails ->
(* Apply filters based on command line arguments *)
let filtered_by_unread =
-
if !unread_only then
+
if unread_only then
List.filter is_unread emails
else
emails
···
(* Apply sender filter if specified *)
let filtered_emails =
-
if !sender_filter <> "" then begin
-
Printf.printf "Filtering by sender: %s\n" !sender_filter;
+
if sender_filter <> "" then begin
+
Printf.printf "Filtering by sender: %s\n" sender_filter;
List.filter (fun email ->
-
Jmap_mail.email_matches_sender email !sender_filter
+
Jmap_mail.email_matches_sender email sender_filter
) filtered_by_unread
end else
filtered_by_unread
···
(* Create description of applied filters *)
let filter_description =
let parts = [] in
-
let parts = if !unread_only then "unread" :: parts else parts in
-
let parts = if !sender_filter <> "" then ("from \"" ^ !sender_filter ^ "\"") :: parts else parts in
+
let parts = if unread_only then "unread" :: parts else parts in
+
let parts = if sender_filter <> "" then ("from \"" ^ sender_filter ^ "\"") :: parts else parts in
match parts with
| [] -> "the most recent"
| [p] -> p
···
filter_description
(List.length filtered_emails);
Printf.printf "--------------------------------------------\n";
-
List.iter (print_email ~show_labels:!show_labels) filtered_emails;
+
List.iter (print_email ~show_labels) filtered_emails;
Lwt.return 0
+
(** Command line interface *)
+
let unread_only =
+
let doc = "List only unread messages" in
+
Arg.(value & flag & info ["unread"] ~doc)
+
+
let show_labels =
+
let doc = "Show labels/keywords associated with messages" in
+
Arg.(value & flag & info ["labels"] ~doc)
+
+
let debug_level =
+
let doc = "Set debug level (0-4, where 4 is most verbose)" in
+
Arg.(value & opt int 0 & info ["debug"] ~docv:"LEVEL" ~doc)
+
+
let demo_refs =
+
let doc = "Demonstrate result references feature" in
+
Arg.(value & flag & info ["demo-refs"] ~doc)
+
+
let sender_filter =
+
let doc = "Filter messages by sender email address (supports wildcards: * and ?)" in
+
Arg.(value & opt string "" & info ["from"] ~docv:"PATTERN" ~doc)
+
+
let cmd =
+
let doc = "List emails from a Fastmail account using JMAP API" in
+
let man = [
+
`S Manpage.s_description;
+
`P "This program connects to the Fastmail JMAP API using an authentication token
+
from the JMAP_API_TOKEN environment variable and lists the most recent emails
+
with their subjects, sender details, and labels.";
+
`P "You must obtain a Fastmail API token from https://app.fastmail.com/settings/tokens
+
and set it in the JMAP_API_TOKEN environment variable.";
+
`S Manpage.s_environment;
+
`P "$(b,JMAP_API_TOKEN) The Fastmail API authentication token (required)";
+
`S Manpage.s_examples;
+
`P "List all emails:";
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token)";
+
`P "List only unread emails:";
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread";
+
`P "List emails from a specific sender:";
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --from=user@example.com";
+
`P "List unread emails with labels:";
+
`P " $(mname) $(i,JMAP_API_TOKEN=your_token) --unread --labels";
+
] in
+
let info = Cmd.info "fastmail-list" ~doc ~man in
+
Cmd.v info Term.(const (fun u l d r s ->
+
Lwt_main.run (list_emails u l d r s)
+
) $ unread_only $ show_labels $ debug_level $ demo_refs $ sender_filter)
+
(** Program entry point *)
-
let () =
-
let exit_code = Lwt_main.run (main ()) in
-
exit exit_code
+
let () = exit (Cmd.eval_value cmd |> function
+
| Ok (`Ok exit_code) -> exit_code
+
| Ok (`Version | `Help) -> 0
+
| Error _ -> 1)