@@ -2,6 +2,7 @@ package main
22
33import (
44 "encoding/json"
5+ "errors"
56 "flag"
67 "fmt"
78 "os"
@@ -19,23 +20,28 @@ type globalFlags struct {
1920 format string
2021}
2122
23+ var errHelpShown = errors .New ("help shown" )
24+
2225const usageText = `usage: msx [--profile name] [--format text|json] <command> [flags]
2326
2427commands:
25- login Start device-code login and save tokens locally
26- import-op Import an existing Microsoft account from 1Password
27- profiles List configured profiles
28- whoami Show the current Graph account
29- mail List mail with optional filters
30- agenda List calendar events in a time range
31- files List or search OneDrive files
32- contacts List or search contacts
33- sites Search SharePoint / org sites
34- help Show this message
28+ login Start device-code login and save tokens locally
29+ import-op Import an existing Microsoft account from 1Password
30+ profiles List configured profiles
31+ whoami Show the current Graph account
32+ mail List mail with optional filters
33+ agenda List calendar events in a time range
34+ files List or search OneDrive files
35+ contacts List or search contacts
36+ sites Search SharePoint / org sites
37+ help Show this message
3538`
3639
3740func main () {
3841 if err := run (os .Args [1 :]); err != nil {
42+ if errors .Is (err , errHelpShown ) {
43+ os .Exit (0 )
44+ }
3945 fmt .Fprintln (os .Stderr , "error:" , err )
4046 os .Exit (1 )
4147 }
@@ -47,10 +53,10 @@ func run(args []string) error {
4753 return err
4854 }
4955 if len (rest ) == 0 {
50- return usage ( )
56+ return showUsage ( true , "" )
5157 }
5258 if rest [0 ] == "help" || rest [0 ] == "--help" || rest [0 ] == "-h" {
53- return usage ( )
59+ return showUsage ( false , "" )
5460 }
5561
5662 s , err := store .Open ("" )
@@ -81,7 +87,7 @@ func run(args []string) error {
8187 case "sites" :
8288 return cmdSites (s , g , rest )
8389 default :
84- return usage ( )
90+ return showUsage ( true , fmt . Sprintf ( "unknown command %q" , cmd ) )
8591 }
8692}
8793
@@ -117,8 +123,19 @@ func parseGlobals(args []string) (globalFlags, []string, error) {
117123 return g , out , nil
118124}
119125
120- func usage () error {
121- return fmt .Errorf (usageText )
126+ func showUsage (asError bool , prefix string ) error {
127+ if asError {
128+ if prefix != "" {
129+ return fmt .Errorf ("%s\n \n %s" , prefix , usageText )
130+ }
131+ return fmt .Errorf (usageText )
132+ }
133+ if prefix != "" {
134+ fmt .Fprintln (os .Stdout , prefix )
135+ fmt .Fprintln (os .Stdout )
136+ }
137+ _ , _ = fmt .Fprint (os .Stdout , usageText )
138+ return errHelpShown
122139}
123140
124141func cmdLogin (s * store.Store , g globalFlags , args []string ) error {
@@ -200,6 +217,7 @@ func cmdMail(s *store.Store, g globalFlags, args []string) error {
200217 top := fs .Int ("top" , 10 , "maximum number of messages" )
201218 sender := fs .String ("sender" , "" , "exact sender email filter" )
202219 query := fs .String ("query" , "" , "Graph search expression text" )
220+ subject := fs .String ("subject" , "" , "case-insensitive subject substring filter applied client-side" )
203221 since := fs .String ("since" , "" , "received since RFC3339 timestamp" )
204222 folder := fs .String ("folder" , "inbox" , "well-known mail folder or folder id" )
205223 unread := fs .Bool ("unread" , false , "only include unread messages" )
@@ -236,6 +254,9 @@ func cmdMail(s *store.Store, g globalFlags, args []string) error {
236254 if err != nil {
237255 return err
238256 }
257+ if * subject != "" {
258+ data ["value" ] = filterMailBySubject (data ["value" ], * subject )
259+ }
239260 return emit (g , data )
240261}
241262
@@ -283,12 +304,16 @@ func cmdFiles(s *store.Store, g globalFlags, args []string) error {
283304 top := fs .Int ("top" , 25 , "maximum number of items" )
284305 path := fs .String ("path" , "" , "folder path to list" )
285306 query := fs .String ("query" , "" , "search query" )
307+ kind := fs .String ("kind" , "all" , "all|files|folders" )
286308 if err := fs .Parse (args ); err != nil {
287309 return err
288310 }
289311 if err := requirePositive ("--top" , * top ); err != nil {
290312 return err
291313 }
314+ if * kind != "all" && * kind != "files" && * kind != "folders" {
315+ return fmt .Errorf ("--kind must be all, files, or folders" )
316+ }
292317 endpoint := "/me/drive/root/children"
293318 params := map [string ]string {"$top" : fmt .Sprint (* top ), "$select" : "id,name,webUrl,file,folder,size,lastModifiedDateTime,parentReference" }
294319 if * query != "" {
@@ -300,6 +325,9 @@ func cmdFiles(s *store.Store, g globalFlags, args []string) error {
300325 if err != nil {
301326 return err
302327 }
328+ if * kind != "all" {
329+ data ["value" ] = filterDriveItems (data ["value" ], * kind )
330+ }
303331 return emit (g , data )
304332}
305333
@@ -375,20 +403,48 @@ func escapePathSegment(v string) string {
375403 return strings .ReplaceAll (v , "'" , "''" )
376404}
377405
406+ func filterMailBySubject (v any , q string ) []map [string ]any {
407+ return filterRows (v , func (row map [string ]any ) bool {
408+ subject , _ := row ["subject" ].(string )
409+ return strings .Contains (strings .ToLower (subject ), strings .ToLower (q ))
410+ })
411+ }
412+
378413func filterEvents (v any , q string ) []map [string ]any {
414+ q = strings .ToLower (q )
415+ return filterRows (v , func (row map [string ]any ) bool {
416+ blob , _ := json .Marshal (row )
417+ return strings .Contains (strings .ToLower (string (blob )), q )
418+ })
419+ }
420+
421+ func filterDriveItems (v any , kind string ) []map [string ]any {
422+ return filterRows (v , func (row map [string ]any ) bool {
423+ _ , hasFile := row ["file" ]
424+ _ , hasFolder := row ["folder" ]
425+ switch kind {
426+ case "files" :
427+ return hasFile && ! hasFolder
428+ case "folders" :
429+ return hasFolder
430+ default :
431+ return true
432+ }
433+ })
434+ }
435+
436+ func filterRows (v any , keep func (map [string ]any ) bool ) []map [string ]any {
379437 items , ok := v .([]any )
380438 if ! ok {
381439 return nil
382440 }
383- q = strings .ToLower (q )
384441 out := []map [string ]any {}
385442 for _ , item := range items {
386443 row , ok := item .(map [string ]any )
387444 if ! ok {
388445 continue
389446 }
390- blob , _ := json .Marshal (row )
391- if strings .Contains (strings .ToLower (string (blob )), q ) {
447+ if keep (row ) {
392448 out = append (out , row )
393449 }
394450 }
0 commit comments