feat(bootstrap): auto-hide commands with no hook configured
Commands are now hidden from help and return ErrUnknownCommand when invoked if their hook is nil (and for version, if Version string is also empty). No explicit DisabledCommands needed for MCPs that don't use login/setup/config/etc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4a7248cfa9
commit
9a52b5dce1
2 changed files with 60 additions and 7 deletions
|
|
@ -205,6 +205,11 @@ func normalize(opts Options) Options {
|
|||
}
|
||||
opts.Aliases = normalizeAliases(opts.Aliases, opts.EnableDoctorAlias)
|
||||
opts.AliasDescriptions = normalizeAliasDescriptions(opts.AliasDescriptions, opts.Aliases, opts.EnableDoctorAlias)
|
||||
for _, cmd := range autoDisabledCommands(opts) {
|
||||
if !isCommandDisabled(cmd, opts.DisabledCommands) {
|
||||
opts.DisabledCommands = append(opts.DisabledCommands, cmd)
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
|
|
@ -461,14 +466,14 @@ func printHelp(opts Options, command string, args ...[]string) error {
|
|||
return printGlobalHelp(opts)
|
||||
}
|
||||
|
||||
if command == CommandConfig {
|
||||
return printConfigHelp(opts, commandArgs)
|
||||
}
|
||||
|
||||
if isCommandDisabled(command, opts.DisabledCommands) {
|
||||
return fmt.Errorf("%w: %s", ErrUnknownCommand, command)
|
||||
}
|
||||
|
||||
if command == CommandConfig {
|
||||
return printConfigHelp(opts, commandArgs)
|
||||
}
|
||||
|
||||
for _, def := range commands {
|
||||
if def.Name != command {
|
||||
continue
|
||||
|
|
@ -583,6 +588,30 @@ func printGlobalHelp(opts Options) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func autoDisabledCommands(opts Options) []string {
|
||||
h := opts.Hooks
|
||||
var disabled []string
|
||||
if h.Setup == nil {
|
||||
disabled = append(disabled, CommandSetup)
|
||||
}
|
||||
if h.Login == nil {
|
||||
disabled = append(disabled, CommandLogin)
|
||||
}
|
||||
if h.MCP == nil {
|
||||
disabled = append(disabled, CommandMCP)
|
||||
}
|
||||
if h.Config == nil && h.ConfigShow == nil && h.ConfigTest == nil && h.ConfigDelete == nil {
|
||||
disabled = append(disabled, CommandConfig)
|
||||
}
|
||||
if h.Update == nil {
|
||||
disabled = append(disabled, CommandUpdate)
|
||||
}
|
||||
if h.Version == nil && strings.TrimSpace(opts.Version) == "" {
|
||||
disabled = append(disabled, CommandVersion)
|
||||
}
|
||||
return disabled
|
||||
}
|
||||
|
||||
func isCommandDisabled(command string, disabled []string) bool {
|
||||
for _, d := range disabled {
|
||||
if d == command {
|
||||
|
|
|
|||
|
|
@ -91,11 +91,13 @@ func TestRunReturnsCommandNotConfigured(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"config"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigShow: noop},
|
||||
})
|
||||
if !errors.Is(err, ErrSubcommandRequired) {
|
||||
t.Fatalf("Run error = %v, want ErrSubcommandRequired", err)
|
||||
|
|
@ -148,7 +150,7 @@ func TestRunVersionHookOverridesDefault(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunRequiresVersionWithoutVersionHook(t *testing.T) {
|
||||
func TestRunVersionAutoHiddenWithoutHookOrVersionString(t *testing.T) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
|
|
@ -158,8 +160,8 @@ func TestRunRequiresVersionWithoutVersionHook(t *testing.T) {
|
|||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
if !errors.Is(err, ErrVersionRequired) {
|
||||
t.Fatalf("Run error = %v, want ErrVersionRequired", err)
|
||||
if !errors.Is(err, ErrUnknownCommand) {
|
||||
t.Fatalf("Run error = %v, want ErrUnknownCommand", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,12 +169,15 @@ func TestRunPrintsGlobalHelp(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Description: "Binaire MCP de test.",
|
||||
Version: "v1.2.3",
|
||||
Args: []string{"help"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -197,12 +202,15 @@ func TestRunPrintsGlobalHelpWhenNoArgs(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Description: "Binaire MCP de test.",
|
||||
Version: "v1.2.3",
|
||||
Args: []string{},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{Setup: noop, MCP: noop, ConfigShow: noop, Update: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -227,11 +235,13 @@ func TestRunPrintsCommandHelp(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"help", "update"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{Update: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -250,11 +260,13 @@ func TestRunPrintsLoginCommandHelp(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"help", "login"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{Login: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -273,11 +285,13 @@ func TestRunPrintsConfigHelp(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"help", "config"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigShow: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -299,11 +313,13 @@ func TestRunPrintsConfigSubcommandHelp(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"help", "config", "show"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigShow: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -349,11 +365,13 @@ func TestRunConfigShowReturnsCommandNotConfigured(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"config", "show"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigTest: noop},
|
||||
})
|
||||
if !errors.Is(err, ErrCommandNotConfigured) {
|
||||
t.Fatalf("Run error = %v, want ErrCommandNotConfigured", err)
|
||||
|
|
@ -364,11 +382,13 @@ func TestRunConfigReturnsUnknownSubcommand(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Args: []string{"config", "sync"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigShow: noop},
|
||||
})
|
||||
if !errors.Is(err, ErrUnknownSubcommand) {
|
||||
t.Fatalf("Run error = %v, want ErrUnknownSubcommand", err)
|
||||
|
|
@ -412,6 +432,7 @@ func TestRunPrintsAliasHelpFromHelpCommand(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Aliases: map[string][]string{
|
||||
|
|
@ -420,6 +441,7 @@ func TestRunPrintsAliasHelpFromHelpCommand(t *testing.T) {
|
|||
Args: []string{"help", "doctor"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigTest: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
@ -435,6 +457,7 @@ func TestRunPrintsAliasHelpFromTrailingHelpFlag(t *testing.T) {
|
|||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
|
||||
noop := func(_ context.Context, _ Invocation) error { return nil }
|
||||
err := Run(context.Background(), Options{
|
||||
BinaryName: "my-mcp",
|
||||
Aliases: map[string][]string{
|
||||
|
|
@ -443,6 +466,7 @@ func TestRunPrintsAliasHelpFromTrailingHelpFlag(t *testing.T) {
|
|||
Args: []string{"doctor", "--help"},
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
Hooks: Hooks{ConfigTest: noop},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Run error = %v", err)
|
||||
|
|
|
|||
Loading…
Reference in a new issue