Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
337f1b1
docs(plans): add full API parity design for CLI and MCP
seanb4t Feb 7, 2026
6c71f03
fix(plans): address review findings in API parity design
seanb4t Feb 7, 2026
be02609
fix(plans): clarify MCP registration dedup and use CapSubmission cons…
seanb4t Feb 7, 2026
0133875
docs(plans): add full API parity implementation plan with 25 TDD tasks
seanb4t Feb 7, 2026
076802e
feat(jmap): add MailboxSetBuilder for Mailbox/set operations
seanb4t Feb 7, 2026
914d9c0
feat(fastmail): add MailboxService with List/Create/Rename/Delete
seanb4t Feb 7, 2026
2718a77
feat(cli): add mailbox list/create/rename/delete commands
seanb4t Feb 7, 2026
34e6b56
feat(mcp): add mailbox_list/create/rename/delete tools
seanb4t Feb 7, 2026
631549e
feat(fastmail): add GetFull for expanded email properties
seanb4t Feb 7, 2026
49e556f
feat(jmap): add DownloadBlob and MailService.GetRaw for raw email ret…
seanb4t Feb 7, 2026
92e1731
feat(cli): add mail show command with --raw flag
seanb4t Feb 7, 2026
263855e
feat(search): add query parser for JMAP Email/query filter conditions
seanb4t Feb 7, 2026
5e78c24
feat(fastmail): add SearchWithFilter for structured JMAP query filters
seanb4t Feb 7, 2026
66a8e12
feat(cli): add mail search command with query parser syntax
seanb4t Feb 7, 2026
975fe32
feat(cli): add mail move and delete commands
seanb4t Feb 7, 2026
27af98e
feat(mail): add SetKeywords service method, flag CLI command, and MCP…
seanb4t Feb 7, 2026
c73f8cb
feat(cli): add calendar commands with CalDAV integration
seanb4t Feb 7, 2026
0a20491
refactor(mcp): consolidate tool registration and add calendar tools
seanb4t Feb 7, 2026
c849c1d
fix(jmap): correct Identity.ReplyTo and BCC types to []EmailAddress p…
seanb4t Feb 7, 2026
1034e8a
feat(jmap): add Thread types and ThreadGetBuilder
seanb4t Feb 7, 2026
0290c81
feat(jmap): add VacationResponse types, builders, and CapVacationResp…
seanb4t Feb 7, 2026
6b06a77
feat: add identity list command, service, and MCP tool
seanb4t Feb 7, 2026
48f7f0a
feat: add vacation show/set command, service, and MCP tools
seanb4t Feb 7, 2026
b3bb8fb
feat: add thread show command, service, and MCP tool
seanb4t Feb 7, 2026
f7af9b2
feat(mcp): add contacts_update and contacts_delete tools
seanb4t Feb 7, 2026
55a1173
feat(mcp): add mailboxes, identities, vacation, and calendars resources
seanb4t Feb 7, 2026
1da5332
fix(vacation): add NotUpdated check and empty-options validation
seanb4t Feb 8, 2026
6b48b70
fix(mcp): wire resource registration and log DAV client errors
seanb4t Feb 8, 2026
f4749d3
fix: add timestamp parse warning and flag mutual exclusivity
seanb4t Feb 8, 2026
9b8ea9b
fix(cli): protect well-known mailbox roles from deletion
seanb4t Feb 8, 2026
3e0cd06
fix: address all 12 PR review findings from code review (#6)
seanb4t Feb 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
435 changes: 435 additions & 0 deletions cli/calendar.go

Large diffs are not rendered by default.

83 changes: 83 additions & 0 deletions cli/calendar_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package cli

import (
"bytes"
"strings"
"testing"
)

func TestCalendarHelp_ShowsSubcommands(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"calendar", "--help"})

err := cmd.Execute()
if err != nil {
t.Fatalf("calendar --help should not error: %v", err)
}

output := buf.String()
subcommands := []string{"list", "show", "create", "update", "delete", "calendars"}
for _, sub := range subcommands {
if !strings.Contains(output, sub) {
t.Errorf("expected %q subcommand in help, got: %q", sub, output)
}
}
}

func TestCalendarCreate_RequiresSummary(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"calendar", "create", "--start", "2026-01-01T10:00", "--end", "2026-01-01T11:00"})

err := cmd.Execute()
if err == nil {
t.Fatal("calendar create without --summary should error")
}
}

func TestCalendarShow_RequiresIDArg(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"calendar", "show"})

err := cmd.Execute()
if err == nil {
t.Fatal("calendar show without ID should error")
}
}

func TestCalendarDelete_RequiresForce(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"calendar", "delete", "event-123"})

err := cmd.Execute()
if err == nil {
t.Fatal("calendar delete without --force should error")
}
if !strings.Contains(err.Error(), "force") {
t.Errorf("expected 'force' in error, got: %v", err)
}
}

func TestCalendarUpdate_RequiresIDArg(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"calendar", "update"})

err := cmd.Execute()
if err == nil {
t.Fatal("calendar update without ID should error")
}
}
69 changes: 69 additions & 0 deletions cli/identity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package cli

import (
"context"
"encoding/json"
"fmt"

"github.com/spf13/cobra"

"github.com/seanb4t/fastmail-cli/pkg/fastmail"
)

func newIdentityCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "identity",
Short: "Identity operations",
Long: "Commands for managing sender identities.",
}

cmd.AddCommand(newIdentityListCommand())
return cmd
}

func newIdentityListCommand() *cobra.Command {
return &cobra.Command{
Use: "list",
Short: "List sender identities",
Long: "List all configured sender identities.",
RunE: func(cmd *cobra.Command, _ []string) error {
client, err := createClient()
if err != nil {
return err
}

ctx := context.Background()
if err := client.Connect(ctx); err != nil {
return fmt.Errorf("connecting: %w", err)
}

identities, err := client.Identity().List(ctx)
if err != nil {
return fmt.Errorf("listing identities: %w", err)
}

return outputIdentities(cmd, identities)
},
}
}

func outputIdentities(cmd *cobra.Command, identities []fastmail.Identity) error {
if IsQuiet() {
return nil
}

if IsJSONOutput() {
enc := json.NewEncoder(cmd.OutOrStdout())
enc.SetIndent("", " ")
return enc.Encode(identities)
}

for _, id := range identities {
deletable := ""
if !id.MayDelete {
deletable = " [locked]"
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s %s <%s>%s\n", id.ID, id.Name, id.Email, deletable)
}
return nil
}
25 changes: 25 additions & 0 deletions cli/identity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package cli

import (
"bytes"
"strings"
"testing"
)

func TestIdentityHelp_ShowsSubcommands(t *testing.T) {
cmd := NewRootCommand()
buf := new(bytes.Buffer)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.SetArgs([]string{"identity", "--help"})

err := cmd.Execute()
if err != nil {
t.Fatalf("identity --help should not error: %v", err)
}

output := buf.String()
if !strings.Contains(output, "list") {
t.Errorf("expected 'list' subcommand in help, got: %q", output)
}
}
Loading
Loading