diff --git a/.surface b/.surface index 5f50f694..2735e90e 100644 --- a/.surface +++ b/.surface @@ -146,6 +146,7 @@ hey habit edit --name hey habit uncomplete hey habit uncomplete --date hey ignore +hey ignore --kind hey journal hey journal list hey journal list --all @@ -178,6 +179,7 @@ hey login --no-browser hey login --token hey logout hey move +hey move --kind hey move --to hey recordings hey recordings --all @@ -275,6 +277,7 @@ hey todo list --all hey todo list --limit hey todo uncomplete hey trash +hey trash --kind hey tui hey tui --instance hey tui --remote diff --git a/README.md b/README.md index 29ad22c5..31c1bc48 100644 --- a/README.md +++ b/README.md @@ -400,11 +400,11 @@ hey compose --to alice@example.com --cc bob@example.com --bcc carol@example.org hey drafts # list drafts hey seen 12345 # mark a thread as seen hey unseen 12345 67890 # mark threads as unseen -hey move 12345 --to feed # move a thread to another box -hey move 12345 67890 --to "paper trail" # move multiple threads -hey trash 12345 # move a thread to Trash +hey move 12345 --to feed --kind topic # move an email thread to another box +hey move 12345 67890 --to "paper trail" --kind topic # move multiple email threads +hey trash 12345 --kind topic # move an email thread to Trash hey spam 12345 # mark a thread as spam -hey ignore 12345 # ignore future activity on a thread +hey ignore 12345 --kind topic # ignore future activity on an email thread hey stop-ignoring 12345 # resume attention for a thread ``` @@ -424,7 +424,7 @@ The Screener is where first-time senders wait. `hey screener list` returns clear `--attach` is repeatable on `hey compose`, `hey reply`, and `hey bulk-reply send`, and attachment-only messages are supported. The CLI validates and uploads every file before sending the email. `hey attachments ` returns stable message-and-position IDs such as `456:1`; pass an ID to `hey attachments save`. Saving uses the original filename by default, accepts `--output` for a file or directory, and preserves existing files unless `--force` is set. -Organization actions take the `id` values returned by `hey box view --json`, `hey label view --json`, or `hey search --json`. Reading, replying to, and forwarding a thread take its `topic_id` instead, which `hey box view --json`, `hey label view --json`, `hey collection view --json` and `hey search --json` all carry alongside `id`. `hey box view` also returns `next_page` and accepts `--page ` to continue a box listing; it keeps `next_history_url` for the sync clients that read it, and `--page` accepts that URL as readily as the cursor inside it. Label IDs come from `hey label list`; `hey label view` returns `next_page` and `total_count`, accepts `--page ` for continuation, and supports `--all` for complete traversal. HEY creates a label while adding it to at least one thread, so `hey label create` requires thread item IDs. +Organization actions take the `id` values returned by `hey box view --json`, `hey label view --json`, or `hey search --json`. For box and label results, select a record whose `kind` is exactly `topic`; search returns only email threads and does not include a `kind` field. `move`, `trash`, and `ignore` only manage email threads and require the exact flag `--kind topic`; missing and non-email kinds are rejected before setup or any request. The CLI does not manage HEY World posts. Reading, replying to, and forwarding a thread take its `topic_id` instead, which `hey box view --json`, `hey label view --json`, `hey collection view --json` and `hey search --json` all carry alongside `id`. `hey box view` also returns `next_page` and accepts `--page ` to continue a box listing; it keeps `next_history_url` for the sync clients that read it, and `--page` accepts that URL as readily as the cursor inside it. Label IDs come from `hey label list`; `hey label view` returns `next_page` and `total_count`, accepts `--page ` for continuation, and supports `--all` for complete traversal. HEY creates a label while adding it to at least one thread, so `hey label create` requires thread item IDs. Collection IDs come from `hey collection list`. `hey collection view` returns both each posting `id` and its `topic_id`, plus `next_page` and `total_count`. Collection membership commands take `topic_id`; posting organization commands continue to take `id`. Creating a collection returns a confirmed mutation, and `hey collection list` provides its ID for subsequent commands. Collection updates accept a non-empty name, summary, or both. diff --git a/internal/cmd/box.go b/internal/cmd/box.go index 3e3eb2fd..0fe3d2e6 100644 --- a/internal/cmd/box.go +++ b/internal/cmd/box.go @@ -40,7 +40,7 @@ var boxListing = postingsListing{ }, breadcrumbs: []output.Breadcrumb{ {Action: "read", Command: "hey threads ", Description: "Read an email thread"}, - {Action: "move", Command: "hey move --to ", Description: "Move an email thread to another box"}, + {Action: "move", Command: "hey move --to --kind topic", Description: "Move an email thread to another box"}, {Action: "compose", Command: "hey compose --to --subject ", Description: "Compose a new message"}, }, } diff --git a/internal/cmd/box_test.go b/internal/cmd/box_test.go index 76b9302c..4fe554ec 100644 --- a/internal/cmd/box_test.go +++ b/internal/cmd/box_test.go @@ -59,6 +59,18 @@ func TestValidateBoxArgs(t *testing.T) { } } +func TestBoxListingMoveBreadcrumbIncludesEmailKind(t *testing.T) { + for _, breadcrumb := range boxListing.breadcrumbs { + if breadcrumb.Action == "move" { + if breadcrumb.Command != "hey move --to --kind topic" { + t.Fatalf("move breadcrumb = %q", breadcrumb.Command) + } + return + } + } + t.Fatal("move breadcrumb not found") +} + func TestBoxCommandNamedRoutes(t *testing.T) { tests := []struct { name string diff --git a/internal/cmd/help_test.go b/internal/cmd/help_test.go index 942916be..90ff0c96 100644 --- a/internal/cmd/help_test.go +++ b/internal/cmd/help_test.go @@ -55,6 +55,35 @@ func TestEmailCommandHelpKeepsPostingAsAnInternalTerm(t *testing.T) { } } +func TestEmailActionAgentNotesUseBoxItemIDs(t *testing.T) { + root := newRootCmd() + for _, name := range []string{"move", "trash", "ignore"} { + t.Run(name, func(t *testing.T) { + command, _, err := root.Find([]string{name}) + if err != nil { + t.Fatal(err) + } + + notes := command.Annotations["agent_notes"] + for _, want := range []string{ + "box item IDs (id)", + "hey box view --json", + "hey label view