// Command brief implements the Brief project's // canonical command-line interaction with the Brief system. // // See the documentation of "pkg.jfrech.com/brief" for an overview. package main import ( "compress/gzip" "fmt" "io" "log" "os" "path" "path/filepath" "strings" "time" "pkg.jfrech.com/brief/cmd/brief/internal/cmd" "pkg.jfrech.com/brief/cmd/brief/internal/manpages" "pkg.jfrech.com/brief/config" "pkg.jfrech.com/brief/config/vault" "pkg.jfrech.com/brief/homographhardened" "pkg.jfrech.com/brief/internal/crumbs" "pkg.jfrech.com/brief/internal/markdownish" "pkg.jfrech.com/brief/internal/vfs" "pkg.jfrech.com/brief/internal/worlds" "pkg.jfrech.com/brief/messageid" "pkg.jfrech.com/brief/sem" "pkg.jfrech.com/brief/uuids" ) // main is the Brief project's brief command's main entry point. // // Ideas and lost sub-commands: // // brief build-cache // brief header-query // brief claimed-display-names // TODO(jfrech): 2024-05-21: For when one wants to know all display names a given e-mail address (was) declared (itself) as. // brief fs-suck // NOTE(jfrech): 2024-05-21: Became "import" (I think). // brief smtp // brief fs-fixup func main() { invoked := time.Now() osworld := func(f func(world *worlds.World) error) error { world, err := worlds.OS(invoked) if err != nil { return err } return f(world) } // argmux := new(ArgMux) help := make(map[string]string) defer argmux.Main(SetDollar0("brief", os.Args)) // // for the time being feature-complete if true { // BUG(jfrech): 2024-05-20: "brief init doesnt/exist/" should error (it currently silently creates the directory). if true { help["brief-init"] = "Initalises a new Brief box." action := func(dir string) error { if len(dir) <= 0 { dir = "." } dir = path.Clean(dir) // path-y version of [path/filepath.Abs] if !path.IsAbs(dir) { wd, err := os.Getwd() if err != nil { return err } else if !path.IsAbs(wd) { return fmt.Errorf("working directory not absolute: %q", wd) } dir = path.Join(filepath.ToSlash(wd), dir) } if !path.IsAbs(dir) { panic("unreachable") } if true { // BUG(jfrech): 2024-05-20: What to do about symlinks? briefroot, relativecwd, err := worlds.FindBriefRoot(vfs.DirFS("/", true), dir) if err == nil { log.Fatalf("already in a Brief box: %q (/%q)", briefroot, relativecwd) } } // briefroot := worlds.BriefRoot(dir) err := os.MkdirAll(filepath.FromSlash(path.Join(string(briefroot), sem.DotBrief)), 0755) if err != nil { return err } cleanup := func() { _ = os.Remove(filepath.FromSlash(path.Join(string(briefroot), sem.DotBrief))) } world, err := worlds.OSAt(invoked, briefroot, ".") if err != nil { cleanup() // don't leave an empty ".brief" hanging return err } err = cmd.Init(world) if err != nil { cleanup() // don't leave an empty ".brief" hanging return err } return nil } argmux.Add("brief init", func(*Vars) error { return action(".") }) argmux.Add("brief init $dir", func(vars *Vars) error { return action(filepath.ToSlash(vars.String("dir"))) }) } if true { help["brief-version"] = "Prints this Brief's version to stdout." argmux.Add("brief version", func(*Vars) error { return cmd.Version(&worlds.World{Stdout: os.Stdout}) }) } if true { // Requests the old master password and twice the new master password. Password // manager trust is set to current vault.DefaultTrust and all secrets are // decrypted, thus checked for validity, and encrypted with the new scheme. help["brief-vault"] = "" argmux.Add("brief vault path", func(*Vars) error { cwd, err := os.Getwd() if err != nil { return err } briefroot, _, err := worlds.FindBriefRoot(vfs.DirFS("/", true), cwd) if err != nil { return err } // BUG(jfrech): 2024-05-20: This should be relative to os.Getwd() (==cwd). fmt.Println(homographhardened.Word(filepath.FromSlash(path.Join(string(briefroot), config.Name())))) return nil }) argmux.Add("brief vault recrypt", func(*Vars) error { return osworld(func(world *worlds.World) error { oldcnf, err := world.Config() if err != nil { return err } oldpwd, err := vault.ReadPassword(world.PasswordSource, "old master password") if err != nil { return err } newpwd, err := vault.ReadPassword(world.PasswordSource, "new master password") if err != nil { return err } newv, recrypt, err := oldcnf.Vault.Recrypt(oldpwd, vault.DefaultTrust, newpwd) if err != nil { return err } _, err = newv.Crypter_(world.PasswordSource) if err != nil { return err } newcnf, err := oldcnf.Recrypt(newv, recrypt) if err != nil { return err } return config.Write(world.FS, newcnf) }) }) } } // still under design considerations, but usable currently if true { if true { help["brief-1l"] = "Sends a one-line e-mail (\"Subject: 1L: \")." argmux.Add("brief 1l $from $to $message", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.OneL(world, vars.String("from"), vars.String("to"), vars.String("message")) }) }) } if true { help["brief-compose"] = "" argmux.Add("brief compose", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.Compose(world) }) }) argmux.Add("brief compose done", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.ComposeDone(world) }) }) argmux.Add("brief compose attach $filenames...", func(vars *Vars) error { filenames := vars.Strings("filenames") seen := make(map[string]bool) names := make([]string, 0, len(filenames)) for _, filename := range filenames { name := path.Clean(filepath.ToSlash(filename)) if seen[name] { return fmt.Errorf("duplicate filename: %q", filename) } seen[name] = true names = append(names, name) } return osworld(func(world *worlds.World) error { // BUG(jfrech): 2024-05-22: This has os calls in it! return cmd.ComposeAttach(world, names...) }) }) } if true { // BUG(jfrech): 2024-05-21: One is sometimes prompted the master password twice (esp. when one mistyped for POP3, SMTP asks again). // // BUG(jfrech): 2024-05-22: This has os or net calls in it! help["brief-sync"] = "Synchronizes with the outer world. Performs network requests." argmux.Add("brief sync", func(*Vars) error { return osworld(cmd.Sync) }) } if true { help["brief-cull"] = "" argmux.Add("brief cull", func(*Vars) error { return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.Cull(world, "INBOX") }) }) argmux.Add("brief cull $mailbox", func(vars *Vars) error { return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.Cull(world, vars.String("mailbox")) }) }) } if true { // TODO(jfrech): 2024-01-16: E.g. "brief whatshot -3" to only show three entries. help["brief-whatshot"] = "Lists e-mails which need attention in a one-line format." argmux.Add("brief whatshot", func(*Vars) error { return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.Whatshot(world) }) }) } if true { help["brief-fix"] = "resend: Set a mail's mail-transfer-status to \"outgoing\", scheduling it to be sent by \"brief sync\"." argmux.Add("brief fix resend $messageid...", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.FixResend(world, vars.Strings("messageid")...) }) }) } if true { help["brief-query"] = "Atomically (except for racy docket edits) queries the Brief sack using BQL (the Brief Query Language)." argmux.Add("brief query $query...", func(vars *Vars) error { return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.Query(world, vars.Strings("query")) }) }) } if true { help["brief-view"] = "" action := func(msgidorfilename, format string) error { if isprobablynotafilename(msgidorfilename) { mid, err := crumbs.ParseString[messageid.MessageId](msgidorfilename) if err != nil { return err } return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.ViewMessageId(world, *mid, format) }) } // filename := filepath.ToSlash(msgidorfilename) owf, err := osOtherworldlyFile(filename) if err != nil { return err } return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.ViewFileName(world, owf, format) }) } argmux.Add("brief view $msgidorfilename", func(vars *Vars) error { return action(vars.String("msgidorfilename"), "text") }) argmux.Add("brief view $msgidorfilename $format", func(vars *Vars) error { return action(vars.String("msgidorfilename"), vars.String("format")) }) } // NOTE(jfrech): 2024-05-21: Whereas "brief view" can take display messages identified by filename outside of briefroot, "brief docket" cannot and thus does not require a "worlds.Disk". if true { help["brief-docket"] = "" argmux.Add("brief docket $msgidorfilename", func(vars *Vars) error { msgidorfilename := vars.String("msgidorfilename") if isprobablynotafilename(msgidorfilename) { mid, err := crumbs.ParseString[messageid.MessageId](msgidorfilename) if err != nil { return err } return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.DocketMessageId(world, *mid) }) } // filename := filepath.ToSlash(msgidorfilename) return osworld(func(world *worlds.World) error { world.StdoutUnderstandsANSI = true // BUG(jfrech): 2024-01-16: This is *ugly*! return cmd.DocketFilename(world, filename) }) }) } } // will probably get expunged (or heavily modified) if true { if true { help["brief-help"] = "" argmux.Add("brief help", func(*Vars) error { return cmd.Help(&worlds.World{Stdout: os.Stdout}) }) } if true { help["brief-install-manpage"] = "" action := func(location string) error { log.Printf("installing brief manpage to: %q", location) err := os.MkdirAll(filepath.Dir(filepath.FromSlash(location)), 0755) if err != nil { return err } f, err := os.OpenFile(filepath.FromSlash(location), os.O_EXCL|os.O_CREATE|os.O_WRONLY, 0444) if err != nil { return err } defer f.Close() // may double-close switch path.Ext(location) { default: _, err := f.Write(manpages.Brief()) if err != nil { return err } case ".gz": gzw, err := gzip.NewWriterLevel(f, gzip.BestCompression) if err != nil { return err } // Don't defer anything, as gzw.Close may write a trailer even on repeated invocation. _, err = gzw.Write(manpages.Brief()) if err != nil { gzw.Close() return err } err = gzw.Close() if err != nil { return err } } err = f.Sync() if err != nil { return err } return f.Close() } argmux.Add("brief install-manpage", func(*Vars) error { return action("/usr/local/man/man1/brief.1.gz") }) argmux.Add("brief install-manpage $location", func(vars *Vars) error { return action(filepath.ToSlash(vars.String("location"))) }) } if true { help["brief-status"] = "" argmux.Add("brief status", func(*Vars) error { return osworld(cmd.Status) }) } if true { help["brief-fsck"] = "" argmux.Add("brief fsck", func(*Vars) error { return osworld(cmd.Fsck) }) argmux.Add("brief fsck sort-by-mailbox", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.FsckSortByMailbox(world, false) }) }) argmux.Add("brief fsck sort-by-mailbox noop", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.FsckSortByMailbox(world, true) }) }) argmux.Add("brief fsck sort-by-filepath", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.FsckSortByFilepath(world, false) }) }) argmux.Add("brief fsck sort-by-filepath noop", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.FsckSortByFilepath(world, true) }) }) } if true { help["brief-toolbox"] = "" action := func(*Vars) error { cnf := new(markdownish.ManConfig) cnf.Environment = make(map[string]string) for _, k := range []string{"VERSION", "VERSION_DATE"} { if v, ok := os.LookupEnv(k); ok { cnf.Environment[k] = v } } text, err := io.ReadAll(os.Stdin) if err != nil { return err } md := new(markdownish.Md) err = md.UnmarshalText(text) if err != nil { return err } _, err = os.Stdout.Write([]byte(markdownish.Md2Man(cnf, md))) return err } argmux.Add("brief toolbox markdown2mandoc", action) argmux.Add("brief toolbox md2man", action) argmux.Add("brief toolbox generate-uuid", func(*Vars) error { uuid := uuids.V4() _, err := fmt.Fprintln(os.Stdout, uuid.String()) return err }) } if true { help["brief-address"] = "Modifies and formats .config/." argmux.Add("brief address", func(*Vars) error { return osworld(func(world *worlds.World) error { return cmd.Address(world) }) }) argmux.Add("brief address $addressish", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressAddress(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish create", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressCreate(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish display-name", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressDisplayName(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish display-name $displayname...", func(vars *Vars) error { return osworld(func(world *worlds.World) error { displayname := strings.Join(vars.Strings("displayname"), " ") return cmd.AddressDisplayNameSet(world, vars.String("addressish"), displayname) }) }) argmux.Add("brief address $addressish alias", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressAlias(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish alias $alias", func(vars *Vars) error { return osworld(func(world *worlds.World) error { alias, err := config.ParseAlias(vars.String("alias")) if err != nil { return err } return cmd.AddressAliasSet(world, vars.String("addressish"), alias) }) }) argmux.Add("brief address $addressish note", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressNote(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish note $note...", func(vars *Vars) error { return osworld(func(world *worlds.World) error { note := strings.Join(vars.Strings("note"), " ") return cmd.AddressNoteSet(world, vars.String("addressish"), note) }) }) argmux.Add("brief address $addressish pop3", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressPOP3(world, vars.String("addressish")) }) }) // NOTE(jfrech): 2024-05-21: Once: "brief address $addressish pop3 password --show-in-plaintext" argmux.Add("brief address $addressish pop3 show-password-in-plaintext", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressPOP3ShowPasswordInPlaintext(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish pop3 password", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressPOP3Password(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish smtp", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressSMTP(world, vars.String("addressish")) }) }) // NOTE(jfrech): 2024-05-21: Once: "brief address $addressish smtp password --show-in-plaintext" argmux.Add("brief address $addressish smtp show-password-in-plaintext", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressSMTPShowPasswordInPlaintext(world, vars.String("addressish")) }) }) argmux.Add("brief address $addressish smtp password", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.AddressSMTPPassword(world, vars.String("addressish")) }) }) } } // low priority, worked well if true { if true { // NOTE(jfrech): 2024-05-21: For a long time, "-" had the special meaning of "read from stdin". Yet this discriminates against the file named "-". I see using an operating system which provides /dev/stdin (or [pkg.jfrech.com/brief/internal/vfs#WithDevStdin]) as better options. help["brief-import"] = "Imports .tar balls full of or singular .eml files.\n\nShould you have trouble with non-CRLF-terminated messages, consider: `$ go install pkg.jfrech.com/glu/crlf@latest && cat /tmp/lf.eml | \"${GOBIN:-${GOPATH:-$HOME/go}/bin}/crlf\" >/tmp/crlf.eml && brief import eml /tmp/crlf.eml`\n" argmux.Add("brief import eml $filenames...", func(vars *Vars) error { otherworldlyFiles, err := osOtherworldlyFiles(vars.Strings("filenames")) if err != nil { return err } return osworld(func(world *worlds.World) error { return cmd.ImportEml(world, otherworldlyFiles) }) }) argmux.Add("brief import tar $filenames...", func(vars *Vars) error { otherworldlyFiles, err := osOtherworldlyFiles(vars.Strings("filenames")) if err != nil { return err } return osworld(func(world *worlds.World) error { return cmd.ImportEml(world, otherworldlyFiles) }) }) } if true { help["pop3-shell"] = "pop3-shell opens a live POP3 connection and enters the user into a shell." argmux.Add("pop3-shell $addressish", func(vars *Vars) error { return osworld(func(world *worlds.World) error { return cmd.Pop3Shell(world, vars.String("addressish")) }) }) } } // low priority, not needed a lot if true { if true { help["brief-fixup"] = "" argmux.Add("brief fixup docket.order", func(vars *Vars) error { return osworld(cmd.FixupDocketOrder) }) argmux.Add("brief fixup docket.mail-transfer-status", func(vars *Vars) error { return osworld(cmd.FixupDocketMailTransferStatus) }) } } }