email-mcp/internal/cli/doctor.go
thibaud-lclr 23fff88ccb
All checks were successful
Release / release (push) Successful in 27s
refactor(doctor): drop redundant password check
Le check "password" ouvrait le store et lisait le secret indépendamment
du ConnectivityCheck, provoquant deux appels bw --version + bw status.
La connectivité échoue déjà si le mot de passe est inaccessible, donc
le check était redondant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 11:51:48 +02:00

131 lines
3.5 KiB
Go

package cli
import (
"context"
"fmt"
"os"
"time"
"email-mcp/mcpgen"
frameworkcli "forge.lclr.dev/AI/mcp-framework/cli"
frameworkconfig "forge.lclr.dev/AI/mcp-framework/config"
)
func (a *App) runDoctor(ctx context.Context, args []string) error {
profileFlag, err := parseProfileArgs("doctor", args)
if err != nil {
return err
}
if a.configStore == nil {
return fmt.Errorf("config store is not configured")
}
if a.openSecretStore == nil {
return fmt.Errorf("secret store is not configured")
}
if a.newMailService == nil {
return fmt.Errorf("mail service is not configured")
}
report := frameworkcli.RunDoctor(ctx, frameworkcli.DoctorOptions{
ConfigCheck: frameworkcli.NewConfigCheck(frameworkconfig.NewStore[ProfileConfig](mcpgen.BinaryName)),
SecretStoreCheck: frameworkcli.SecretStoreAvailabilityCheck(a.openSecretStore),
ConnectivityCheck: a.doctorConnectivityCheck(profileFlag),
ExtraChecks: []frameworkcli.DoctorCheck{
a.doctorRequiredProfileFieldsCheck(profileFlag),
},
})
if err := frameworkcli.RenderDoctorReport(a.stdout, report); err != nil {
return err
}
if report.HasFailures() {
return fmt.Errorf("doctor checks failed")
}
return nil
}
func (a *App) doctorRequiredProfileFieldsCheck(profileFlag string) frameworkcli.DoctorCheck {
var (
profileValues map[string]string
loadErr error
)
check := frameworkcli.RequiredResolvedFieldsCheck(frameworkcli.ResolveOptions{
Fields: profileFieldSpecs(a.resolveDoctorProfileName(profileFlag)),
Lookup: frameworkcli.ResolveLookup(frameworkcli.ResolveLookupOptions{
Env: frameworkcli.EnvLookup(os.LookupEnv),
Config: func(key string) (string, bool, error) {
if loadErr != nil {
return "", false, loadErr
}
if profileValues == nil {
cfg, _, err := a.configStore.LoadDefault()
if err != nil {
loadErr = err
return "", false, loadErr
}
profileName := a.resolveProfileName(profileFlag, cfg.CurrentProfile)
profile := cfg.Profiles[profileName]
profileValues = map[string]string{
"host": profile.Host,
"username": profile.Username,
}
}
return frameworkcli.MapLookup(profileValues)(key)
},
}),
})
return func(ctx context.Context) frameworkcli.DoctorResult {
result := check(ctx)
result.Name = "profile"
return result
}
}
func (a *App) doctorConnectivityCheck(profileFlag string) frameworkcli.DoctorCheck {
return func(parent context.Context) frameworkcli.DoctorResult {
ctx, cancel := context.WithTimeout(parent, 35*time.Second)
defer cancel()
cred, err := a.loadCredential(profileFlag)
if err != nil {
return frameworkcli.DoctorResult{
Name: "connectivity",
Status: frameworkcli.DoctorStatusFail,
Summary: "cannot load IMAP credentials",
Detail: err.Error(),
}
}
if _, err := a.newMailService().ListMailboxes(ctx, cred); err != nil {
return frameworkcli.DoctorResult{
Name: "connectivity",
Status: frameworkcli.DoctorStatusFail,
Summary: "IMAP server is unreachable or rejected authentication",
Detail: err.Error(),
}
}
return frameworkcli.DoctorResult{
Name: "connectivity",
Status: frameworkcli.DoctorStatusOK,
Summary: "IMAP server is reachable",
}
}
}
func (a *App) resolveDoctorProfileName(profileFlag string) string {
if a.configStore == nil {
return a.resolveProfileName(profileFlag, "")
}
cfg, _, err := a.configStore.LoadDefault()
if err != nil {
return a.resolveProfileName(profileFlag, "")
}
return a.resolveProfileName(profileFlag, cfg.CurrentProfile)
}