email-mcp/internal/cli/app.go
2026-04-10 10:58:51 +02:00

122 lines
2.6 KiB
Go

package cli
import (
"context"
"errors"
"fmt"
"io"
"os"
"email-mcp/internal/secretstore"
"email-mcp/internal/secretstore/kwallet"
)
type MCPRunner interface {
Run(ctx context.Context) error
}
type App struct {
prompter SetupPrompter
store secretstore.Store
runner MCPRunner
stderr io.Writer
}
func NewApp() *App {
return NewAppWithDependencies(nil, nil, nil, os.Stderr)
}
func NewAppWithDependencies(prompter SetupPrompter, store secretstore.Store, runner MCPRunner, stderr io.Writer) *App {
if stderr == nil {
stderr = io.Discard
}
return &App{
prompter: prompter,
store: store,
runner: runner,
stderr: stderr,
}
}
func (a *App) Run(args []string) error {
if len(args) == 0 {
return fmt.Errorf("usage: email-mcp <setup|mcp>")
}
switch args[0] {
case "setup":
return a.runSetup(context.Background())
case "mcp":
return a.runMCP(context.Background())
default:
return fmt.Errorf("unknown command: %s", args[0])
}
}
func (a *App) runSetup(ctx context.Context) error {
if a.prompter == nil {
return fmt.Errorf("setup prompter is not configured")
}
if a.store == nil {
return fmt.Errorf("secret store is not configured")
}
cred, err := a.prompter.PromptSetup(ctx)
if err != nil {
return err
}
if err := cred.Validate(); err != nil {
return err
}
if err := a.store.Save(ctx, secretstore.DefaultAccountKey, cred); err != nil {
return mapAppError(err)
}
return nil
}
func (a *App) runMCP(ctx context.Context) error {
if a.runner == nil {
return fmt.Errorf("mcp runner is not configured")
}
return mapAppError(a.runner.Run(ctx))
}
func mapAppError(err error) error {
if err == nil {
return nil
}
switch {
case errors.Is(err, kwallet.ErrKWalletUnavailable):
return newUserFacingError("kwallet is not available; make sure KDE Wallet is installed and your session D-Bus is running", err)
case errors.Is(err, kwallet.ErrKWalletDisabled):
return newUserFacingError("kwallet is disabled in this KDE session", err)
case errors.Is(err, kwallet.ErrKWalletOpenFailed):
return newUserFacingError("kwallet could not be opened; unlock the wallet and try again", err)
case errors.Is(err, kwallet.ErrCredentialNotFound):
return newUserFacingError("credentials not configured; run `email-mcp setup`", err)
default:
return err
}
}
type userFacingError struct {
message string
err error
}
func (e *userFacingError) Error() string {
return e.message
}
func (e *userFacingError) Unwrap() error {
return e.err
}
func newUserFacingError(message string, err error) error {
return &userFacingError{
message: message,
err: err,
}
}