refactor(cli): reduce framework command glue paths

This commit is contained in:
thibaud-leclere 2026-04-14 16:17:22 +02:00
parent 8c88084181
commit fcee5a0a36
3 changed files with 55 additions and 115 deletions

View file

@ -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 <command> [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 <command>\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
}

View file

@ -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)
}
}

View file

@ -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 "."