Skip to content

feat(cli): add cli subcommands - #1680

Closed
Robin-Everaars wants to merge 5 commits into
floatpane:masterfrom
Robin-Everaars:feat/non-interactive-cli
Closed

feat(cli): add cli subcommands#1680
Robin-Everaars wants to merge 5 commits into
floatpane:masterfrom
Robin-Everaars:feat/non-interactive-cli

Conversation

@Robin-Everaars

@Robin-Everaars Robin-Everaars commented Jul 13, 2026

Copy link
Copy Markdown

What?

Adds a non-interactive CLI to Matcha so mail can be handled from scripts and one-off commands without entering the TUI. New subcommands:

  • list <folder>: list messages in a folder (tabular, or --json)
  • read <uid>: print a message body; --json includes attachment metadata
  • search <query>: search using the same query DSL the TUI uses
  • folders: list available folders
  • mail management: archive, delete, mark-read, mark-unread, move

Supporting changes:

  • daemon: new Search RPC (service method + handler). NewCLIClient(cfg, autoStart) lets a one-shot invocation use a running daemon when present and otherwise fall back to a direct connection, rather than forking a daemon.
  • backend: Attachment gains explicit snake_case JSON tags and excludes the raw Data bytes, so scripted --json consumers never receive megabytes of base64.
  • fix: the mark-read handler previously logged per-UID failures but reported success over RPC; it now joins and returns them, matching the other mutating handlers and direct-mode semantics.
  • refactor: commands write through hookable Out/ErrOut writers; the existing contacts-export, config, and upgrade commands were moved onto the same writers for consistency and testability. Shared plumbing (account resolution by --from, tabular/JSON rendering, folder-name normalization, interspersed flag parsing) lives in cli/util.go.

Every new command ships with unit tests.

Why?

Matcha is TUI-only today, which makes it awkward to use from scripts, cron jobs, or other tooling. The CLI docs already promise these commands "work without launching the TUI and are ideal for scripts, cron jobs, and AI agent integration," but on the mail side only matcha send was actually exposed. This closes that gap: the core mailbox operations (list / read / search / folders, plus manage) become non-interactive subcommands, making Matcha scriptable and composable with the rest of a shell workflow, while reusing the existing daemon and query DSL so behaviour stays consistent with the TUI.

Closes #1642.

Robin Everaars added 4 commits July 13, 2026 09:30
Attachment is about to be exposed through the CLI's --json output.
Give it explicit snake_case field names and exclude the raw Data
bytes so scripted consumers never receive megabytes of base64.
Expose provider search through the service layer: a Search method on
daemonclient.Service (daemon and direct implementations), a Search RPC
method with handler, and NewCLIClient(cfg, autoStart) so one-shot CLI
invocations can fall back to direct mode instead of forking a daemon.

The mark-read handler previously logged per-UID failures but reported
success over RPC; it now joins and returns them, matching the other
mutating handlers and direct-mode semantics.
Round out the non-interactive CLI beyond send: list a folder, read a
message body (with --json attachment metadata), search with the same
query DSL the TUI uses, list folders, and manage mail (archive,
delete, mark-read, mark-unread, move).

Shared plumbing lives in cli/util.go: account resolution by --from
(case-insensitive on Email with a FetchEmail fallback), tabular and
JSON email rendering, folder-name normalization, and interspersed
flag parsing so flags may follow positionals. Commands write through
hookable Out/ErrOut writers, which the existing contacts-export,
config, and upgrade commands now also use for package consistency.

All commands connect with autoStart disabled: a one-shot invocation
uses a running daemon when present and otherwise falls back to a
direct connection rather than spawning a daemon.
Route folders, list, read, search, archive, delete, mark-read,
mark-unread and move through the cli package; errors print to stderr
and exit 1.
@Robin-Everaars
Robin-Everaars requested a review from a team as a code owner July 13, 2026 07:34
@floatpanebot floatpanebot added bug Something isn't working chore Maintenance, refactor, cleanup ci CI / build pipeline documentation Documentation changes enhancement New feature or request area/cli CLI flags / commands area/daemon Daemon / RPC size/XL Diff: 800+ lines labels Jul 13, 2026
@floatpanebot

Copy link
Copy Markdown
Member

This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size.

@floatpanebot floatpanebot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Robin-Everaars! Please fix the following issues with your PR:

  • Title: Is too long (76 characters). The PR title must be strictly under 40 characters.

@floatpanebot

floatpanebot commented Jul 13, 2026

Copy link
Copy Markdown
Member

Benchmark report — no significant change

Metrics worse: 0 · better: 0 (threshold: ±3%).

benchstat output
goos: linux
goarch: amd64
pkg: github.com/floatpane/matcha/backend
cpu: AMD EPYC 9V74 80-Core Processor                
                           │   old.txt    │               new.txt               │
                           │    sec/op    │    sec/op      vs base              │
ParseSearchQuery_Simple-4    2.559µ ± 43%   2.683µ ±  22%       ~ (p=0.818 n=6)
ParseSearchQuery_Complex-4   7.174µ ± 29%   6.151µ ±  42%       ~ (p=0.818 n=6)
TokenizeSearchQuery-4        3.996µ ± 67%   4.638µ ± 141%       ~ (p=0.065 n=6)
geomean                      4.186µ         4.246µ         +1.43%

                           │  old.txt   │              new.txt               │
                           │    B/op    │    B/op     vs base                │
ParseSearchQuery_Simple-4    26.00 ± 0%   26.00 ± 0%       ~ (p=1.000 n=6) ¹
ParseSearchQuery_Complex-4   762.0 ± 0%   762.0 ± 0%       ~ (p=1.000 n=6) ¹
TokenizeSearchQuery-4        176.0 ± 0%   176.0 ± 0%       ~ (p=1.000 n=6) ¹
geomean                      151.6        151.6       +0.00%
¹ all samples are equal

                           │  old.txt   │              new.txt               │
                           │ allocs/op  │ allocs/op   vs base                │
ParseSearchQuery_Simple-4    2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=6) ¹
ParseSearchQuery_Complex-4   23.00 ± 0%   23.00 ± 0%       ~ (p=1.000 n=6) ¹
TokenizeSearchQuery-4        9.000 ± 0%   9.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                      7.453        7.453       +0.00%
¹ all samples are equal

pkg: github.com/floatpane/matcha/tui
                    │   old.txt    │               new.txt               │
                    │    sec/op    │    sec/op     vs base               │
LogPanelView-4        179.8µ ± 14%   163.7µ ± 10%        ~ (p=0.093 n=6)
SearchOverlayView-4   188.2µ ± 10%   168.9µ ± 10%  -10.24% (p=0.026 n=6)
InboxConstruction-4   997.5µ ± 14%   986.3µ ± 24%        ~ (p=0.240 n=6)
geomean               323.2µ         301.0µ         -6.86%

                    │    old.txt    │               new.txt               │
                    │     B/op      │     B/op       vs base              │
LogPanelView-4        44.67Ki ± 51%   44.67Ki ± 51%       ~ (p=1.000 n=6)
SearchOverlayView-4   56.14Ki ± 41%   56.14Ki ± 41%       ~ (p=1.000 n=6)
InboxConstruction-4   874.3Ki ±  0%   874.2Ki ±  0%       ~ (p=0.416 n=6)
geomean               129.9Ki         129.9Ki        -0.00%

                    │   old.txt   │              new.txt              │
                    │  allocs/op  │  allocs/op   vs base              │
LogPanelView-4         714.0 ± 0%    714.0 ± 0%       ~ (p=1.000 n=6)
SearchOverlayView-4    926.0 ± 0%    926.0 ± 0%       ~ (p=1.000 n=6)
InboxConstruction-4   3.478k ± 0%   3.477k ± 0%       ~ (p=0.448 n=6)
geomean               1.320k        1.320k       -0.01%

auto-generated by benchmarks.yml

@Robin-Everaars Robin-Everaars changed the title feat(cli): non-interactive subcommands (list, read, search, folders, manage) feat(cli): add non-interactive subcommands Jul 13, 2026

@floatpanebot floatpanebot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Robin-Everaars! Please fix the following issues with your PR:

  • Title: Is too long (42 characters). The PR title must be strictly under 40 characters.

@Robin-Everaars Robin-Everaars changed the title feat(cli): add non-interactive subcommands feat(cli): add cli subcommands Jul 13, 2026
@floatpanebot
floatpanebot dismissed stale reviews from themself July 13, 2026 07:38

Formatting issues have been resolved. Thank you!

@Robin-Everaars

Copy link
Copy Markdown
Author

I've seen the linting issues. I'll work on them later today (thought I could quickly open the PR in between other things).

Address the golangci-lint findings on the new CLI files:

- errcheck: route CLI stdout/stderr writes through fprintln/fprintf/
  fprint helpers that discard the unactionable write error, and check
  the previously-ignored fs.Parse and svc.Close returns.
- goconst: hoist the repeated "INBOX" and management subcommand names
  into constants.
- gocritic (unlambda): assign daemonclient.NewCLIClient directly
  instead of wrapping it in a pass-through closure.
- revive (blank-imports): justify the i18n/languages blank import.

No behaviour change; build and tests remain green.
@floatpanebot

Copy link
Copy Markdown
Member

This PR exceeds the recommended size of 1000 lines. Please make sure you are NOT addressing multiple issues with one PR. Note this PR might be rejected due to its size.

@andrinoff

Copy link
Copy Markdown
Member

@Robin-Everaars this PR is way too big and will not backport to v1 automatically, requiring the team to remake this change on release/v1 branch.

Please, remake this PR on release/v1 branch

@Robin-Everaars

Copy link
Copy Markdown
Author

@andrinoff understood, I'll have a go at it sometime soon. Otherwise happy with the general direction?

@Robin-Everaars

Copy link
Copy Markdown
Author

And I'll close this PR to create several smaller, separate new PRs later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/cli CLI flags / commands area/daemon Daemon / RPC bug Something isn't working chore Maintenance, refactor, cleanup ci CI / build pipeline documentation Documentation changes enhancement New feature or request size/XL Diff: 800+ lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEAT: Round out the non-interactive CLI (read + manage mail, not just send)

3 participants