diff --git a/internal/cli/app.go b/internal/cli/app.go index bb94956..2cf1873 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -45,11 +45,7 @@ type profileConfigStore interface { SaveDefault(frameworkconfig.FileConfig[ProfileConfig]) (string, error) } -type secretStore interface { - SetSecret(name, label, secret string) error - GetSecret(name string) (string, error) - DeleteSecret(name string) error -} +type secretStore = frameworksecretstore.Store type manifestLoader func(startDir string) (frameworkmanifest.File, string, error) type executableResolver func() (string, error) @@ -119,19 +115,7 @@ func NewAppWithDependencies( } func (a *App) Run(args []string) error { - if isDoctorHelpCommand(args) { - return a.printDoctorHelp() - } - - if len(args) > 0 && strings.TrimSpace(args[0]) == "doctor" { - return a.runDoctor(context.Background(), args[1:]) - } - - if isGlobalHelpCommand(args) { - return a.printGlobalHelp() - } - - return a.runBootstrap(context.Background(), args) + return a.runBootstrap(context.Background(), normalizeArgs(args)) } func (a *App) runBootstrap(ctx context.Context, args []string) error { @@ -156,7 +140,7 @@ func (a *App) runBootstrap(ctx context.Context, args []string) error { return a.runConfigShow(ctx, inv.Args) }, ConfigTest: func(ctx context.Context, inv frameworkbootstrap.Invocation) error { - return a.runConfigTest(ctx, inv.Args) + return a.runDoctor(ctx, inv.Args) }, ConfigDelete: func(ctx context.Context, inv frameworkbootstrap.Invocation) error { return a.runConfigDelete(ctx, inv.Args) @@ -168,84 +152,6 @@ func (a *App) runBootstrap(ctx context.Context, args []string) error { }) } -func isGlobalHelpCommand(args []string) bool { - if len(args) == 0 { - return true - } - if len(args) != 1 { - return false - } - - switch strings.TrimSpace(args[0]) { - case "help", "-h", "--help": - return true - default: - return false - } -} - -func isDoctorHelpCommand(args []string) bool { - if len(args) != 2 { - return false - } - - first := strings.TrimSpace(args[0]) - second := strings.TrimSpace(args[1]) - - if first == "help" && second == "doctor" { - return true - } - if first == "doctor" && (second == "-h" || second == "--help") { - return true - } - return false -} - -func (a *App) printGlobalHelp() error { - metadata := a.runtimeMetadata() - - if _, err := fmt.Fprintf(a.stdout, "%s\n\n", metadata.Description); err != nil { - return err - } - if _, err := fmt.Fprintf(a.stdout, "Usage:\n %s [args]\n\n", metadata.BinaryName); err != nil { - return err - } - if _, err := fmt.Fprintln(a.stdout, "Common commands:"); err != nil { - return err - } - - commands := []struct { - name string - description string - }{ - {name: "setup", description: "Initialize or update local configuration."}, - {name: "mcp", description: "Run the MCP server over stdio."}, - {name: "config", description: "Inspect or test resolved configuration."}, - {name: "doctor", description: "Run local diagnostics."}, - {name: "update", description: "Run the self-update flow."}, - {name: "version", description: "Print the binary version."}, - } - for _, command := range commands { - if _, err := fmt.Fprintf(a.stdout, " %-7s %s\n", command.name, command.description); err != nil { - return err - } - } - - _, err := fmt.Fprintf(a.stdout, "\nDetailed help: %s help \n", metadata.BinaryName) - return err -} - -func (a *App) printDoctorHelp() error { - metadata := a.runtimeMetadata() - - _, err := fmt.Fprintf( - a.stdout, - "Usage:\n %s doctor [--profile NAME]\n\nRun local diagnostics for config, wallet, manifest, and IMAP connectivity.\n", - metadata.BinaryName, - ) - return err -} - func (a *App) runConfig(ctx context.Context, command string, args []string) error { if a.prompter == nil { return fmt.Errorf("config prompter is not configured") @@ -382,10 +288,6 @@ func (a *App) runConfigShow(ctx context.Context, args []string) error { return nil } -func (a *App) runConfigTest(ctx context.Context, args []string) error { - return a.runDoctor(ctx, args) -} - func (a *App) runConfigDelete(_ context.Context, args []string) error { if a.configStore == nil { return fmt.Errorf("config store is not configured") @@ -896,3 +798,40 @@ func newUserFacingError(message string, err error) error { err: err, } } + +func normalizeArgs(args []string) []string { + if len(args) == 0 { + return []string{} + } + + normalized := append([]string(nil), args...) + for i, arg := range normalized { + normalized[i] = strings.TrimSpace(arg) + } + + switch normalized[0] { + case "doctor": + if len(normalized) == 1 { + return []string{frameworkbootstrap.CommandConfig, frameworkbootstrap.ConfigSubcommandTest} + } + + lastArg := normalized[len(normalized)-1] + if lastArg == "-h" || lastArg == "--help" { + return []string{"help", frameworkbootstrap.CommandConfig, frameworkbootstrap.ConfigSubcommandTest} + } + + return append( + []string{frameworkbootstrap.CommandConfig, frameworkbootstrap.ConfigSubcommandTest}, + normalized[1:]..., + ) + case "help": + if len(normalized) > 1 && normalized[1] == "doctor" { + return append( + []string{"help", frameworkbootstrap.CommandConfig, frameworkbootstrap.ConfigSubcommandTest}, + normalized[2:]..., + ) + } + } + + return normalized +} diff --git a/internal/cli/app_test.go b/internal/cli/app_test.go index 34edf8b..c120d54 100644 --- a/internal/cli/app_test.go +++ b/internal/cli/app_test.go @@ -158,7 +158,7 @@ func TestAppRunShowsUsageWhenNoArgsProvided(t *testing.T) { } text := output.String() - for _, snippet := range []string{"Usage:", "doctor", "version"} { + for _, snippet := range []string{"Usage:", "config", "version"} { if !strings.Contains(text, snippet) { t.Fatalf("help output missing %q: %q", snippet, text) } @@ -219,7 +219,19 @@ func TestAppRunDoctorHelp(t *testing.T) { if err := app.Run([]string{"doctor", "--help"}); err != nil { t.Fatalf("doctor help returned error: %v", err) } - if got := output.String(); !strings.Contains(got, "email-mcp doctor [--profile NAME]") { + if got := output.String(); !strings.Contains(got, "email-mcp config test [args]") { + t.Fatalf("unexpected doctor help output: %q", got) + } +} + +func TestAppRunHelpDoctorUsesConfigTestHelp(t *testing.T) { + output := &bytes.Buffer{} + app := NewAppWithDependencies(nil, nil, nil, nil, nil, nil, nil, nil, output, &bytes.Buffer{}, "dev") + + if err := app.Run([]string{"help", "doctor"}); err != nil { + t.Fatalf("help doctor returned error: %v", err) + } + if got := output.String(); !strings.Contains(got, "email-mcp config test [args]") { t.Fatalf("unexpected doctor help output: %q", got) } } diff --git a/internal/cli/doctor.go b/internal/cli/doctor.go index 73b7a6a..819bc90 100644 --- a/internal/cli/doctor.go +++ b/internal/cli/doctor.go @@ -12,7 +12,6 @@ import ( frameworkcli "gitea.lclr.dev/AI/mcp-framework/cli" frameworkconfig "gitea.lclr.dev/AI/mcp-framework/config" frameworkmanifest "gitea.lclr.dev/AI/mcp-framework/manifest" - frameworksecretstore "gitea.lclr.dev/AI/mcp-framework/secretstore" frameworkupdate "gitea.lclr.dev/AI/mcp-framework/update" ) @@ -34,7 +33,7 @@ func (a *App) runDoctor(ctx context.Context, args []string) error { metadata := a.runtimeMetadata() report := frameworkcli.RunDoctor(ctx, frameworkcli.DoctorOptions{ ConfigCheck: frameworkcli.NewConfigCheck(frameworkconfig.NewStore[ProfileConfig](binaryName)), - SecretStoreCheck: frameworkcli.SecretStoreAvailabilityCheck(a.frameworkSecretStoreFactory()), + SecretStoreCheck: frameworkcli.SecretStoreAvailabilityCheck(a.openSecretStore), ManifestDir: a.doctorManifestDir(), ManifestValidator: func(file frameworkmanifest.File, _ string) []string { return validateManifestUpdate(file, metadata.BinaryName) @@ -54,16 +53,6 @@ func (a *App) runDoctor(ctx context.Context, args []string) error { return nil } -func (a *App) frameworkSecretStoreFactory() func() (frameworksecretstore.Store, error) { - return func() (frameworksecretstore.Store, error) { - store, err := a.openSecretStore() - if err != nil { - return nil, err - } - return store, nil - } -} - func (a *App) doctorManifestDir() string { if a.resolveExecutable == nil { return "."