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
··· 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
··· 3 3 (public_name fastmail-list) 4 4 (package jmap) 5 5 (modules fastmail_list) 6 - (libraries jmap jmap_mail lwt.unix logs logs.fmt)) 6 + (libraries jmap jmap_mail lwt.unix logs logs.fmt cmdliner)) 7 7 8 8 (executable 9 9 (name flag_color_test)
+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)