From 3b73ec4c15f0b2156ecb967ab2ccc5f430a16646 Mon Sep 17 00:00:00 2001 From: Michael Finson Date: Sun, 2 Aug 2026 16:28:30 +0300 Subject: [PATCH 1/3] fix: handle version as a local command --- main.go | 12 ++++++++++++ main_test.go | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/main.go b/main.go index e88644e..fc3ad90 100644 --- a/main.go +++ b/main.go @@ -297,6 +297,7 @@ func run() (exitCode int) { registerAuthCommands(configDir) registerCustomerContextCommands(configDir) registerUpgradeCommand(configDir) + registerVersionCommand() registerSkillCommands() // Unhide the customer-context command for DoiT employees so it appears in help. if cachedTokenIsDoer() { @@ -1118,6 +1119,17 @@ func registerStatusCommands(configDir string) { }) } +func registerVersionCommand() { + cli.Root.AddCommand(&cobra.Command{ + Use: "version", + Short: "Print the DCI CLI version", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintln(os.Stdout, version) + }, + }) +} + // applyAPIKeyAuth injects DCI_API_KEY into restish's auth cache as a Bearer // token. Restish's OAuth TokenHandler checks the cache before triggering a // browser flow, so pre-populating it bypasses interactive login. We use diff --git a/main_test.go b/main_test.go index 0c1c388..6910ccd 100644 --- a/main_test.go +++ b/main_test.go @@ -107,6 +107,22 @@ func TestNormalizeArgs(t *testing.T) { } } +func TestRegisterVersionCommand(t *testing.T) { + setupTestRoot(t) + registerVersionCommand() + + command, _, err := cli.Root.Find([]string{"version"}) + if err != nil { + t.Fatal(err) + } + if command.Name() != "version" { + t.Fatalf("command = %q, want version", command.Name()) + } + if command.Short != "Print the DCI CLI version" { + t.Fatalf("short description = %q", command.Short) + } +} + func TestRejectProfileFlags(t *testing.T) { setupTestRoot(t) From 225b213f86cf36c823308cd1f4ac3e3c8b45adc9 Mon Sep 17 00:00:00 2001 From: Michael Finson Date: Sun, 2 Aug 2026 16:55:53 +0300 Subject: [PATCH 2/3] fix: make unknown command help concise --- main.go | 70 +++++++++++------------ main_test.go | 37 ++++++++++++ unknown_command.go | 124 ++++++++++++++++++++++++++++++++++++++++ unknown_command_test.go | 45 +++++++++++++++ 4 files changed, 241 insertions(+), 35 deletions(-) create mode 100644 unknown_command.go create mode 100644 unknown_command_test.go diff --git a/main.go b/main.go index fc3ad90..ac47bdf 100644 --- a/main.go +++ b/main.go @@ -314,9 +314,13 @@ func run() (exitCode int) { applyCustomerContext(configDir) lockToDCI() setupCompletion() + installUnknownCommandHandler() os.Args = normalizeArgs(os.Args) if err := cli.Run(); err != nil { + if code, handled := handleUnknownCommandExecutionError(err); handled { + return code + } fmt.Fprintf(os.Stderr, "%v\n", err) maybeHintDoerContext(1, cli.GetLastStatus(), configDir) return 1 @@ -746,6 +750,34 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} const dciLongDescription = "Command-line interface for the DoiT Cloud Intelligence API." +const compactRootHelpHeader = `Command-line interface for the DoiT Cloud Intelligence API. + +Usage: + dci [flags] + dci --help + +Common commands: + login Sign in to the DoiT Console + status Show CLI configuration and active context + list-budgets List budgets + list-reports List Cloud Analytics reports + list-alerts List alerts + query Run a Cloud Analytics query + skill Manage AI agent skills + version Print the CLI version + +Discovery: + dci --help Show detailed help for one command +` + +func compactRootHelp() string { + help := compactRootHelpHeader + if isRootCommand("commands") { + help += " dci commands Show the complete command catalog\n dci commands --json Show the machine-readable catalog\n" + } + return help +} + var rootExamples = []string{ " dci status", " dci list-budgets", @@ -831,8 +863,7 @@ func lockToDCI() { } } -// setupCompletion configures shell completion and root help so that API -// commands appear at root level (alongside status, login, etc.). +// setupCompletion configures shell completion and compact root help. // // The "dci" API subcommand is hidden since users access its commands directly // via normalizeArgs. Its ValidArgsFunction (which returns URL paths from @@ -903,44 +934,13 @@ func setupCompletion() { return completions, cobra.ShellCompDirectiveNoFileComp } - // Override root help to include API commands. Load the API, move its - // commands to root so the standard usage template renders them, then - // show help normally. defaultHelp := cli.Root.HelpFunc() cli.Root.SetHelpFunc(func(cmd *cobra.Command, args []string) { - hasAPICommands := false if cmd == cli.Root { - loadAPI() - hasAPICommands = len(dciCmd.Commands()) > 0 - // Copy command groups from the API subcommand to root so the - // usage template can render grouped commands. - for _, g := range dciCmd.Groups() { - if !cli.Root.ContainsGroup(g.ID) { - cli.Root.AddGroup(g) - } - } - // Collect first — iterating Commands() while removing mutates the slice. - subs := make([]*cobra.Command, len(dciCmd.Commands())) - copy(subs, dciCmd.Commands()) - for _, sub := range subs { - dciCmd.RemoveCommand(sub) - cli.Root.AddCommand(sub) - } + fmt.Fprint(cmd.OutOrStdout(), compactRootHelp()) + return } defaultHelp(cmd, args) - if cmd == cli.Root && !hasAPICommands { - hint := "\n! To get started, authenticate with: dci login (or set DCI_API_KEY)\n\n" - // In agent mode the hint is chatter — route it to stderr (plain, no - // color) so stdout stays parseable. - if agentMode { - fmt.Fprint(os.Stderr, hint) - } else { - if term.IsTerminal(int(os.Stdout.Fd())) { - hint = "\n\033[1;33m!\033[0m To get started, authenticate with: \033[1mdci login\033[0m (or set \033[1mDCI_API_KEY\033[0m)\n\n" - } - fmt.Fprint(os.Stdout, hint) - } - } }) } diff --git a/main_test.go b/main_test.go index 6910ccd..fea4742 100644 --- a/main_test.go +++ b/main_test.go @@ -588,6 +588,43 @@ func TestCLIIntegrationBehavior(t *testing.T) { assertNoOAuthOrPanic(t, res.output) }) + t.Run("unknown command is compact and suggests a match", func(t *testing.T) { + home := t.TempDir() + res := runCLIWithEnv(t, bin, home, []string{"DCI_AGENT_MODE=0", "DCI_API_KEY=test-key"}, "list-bugets") + if res.timedOut { + t.Fatalf("command timed out; output:\n%s", res.output) + } + if res.exitCode != 2 { + t.Fatalf("exit code = %d, want 2; output:\n%s", res.exitCode, res.output) + } + for _, expected := range []string{"unknown command \"list-bugets\"", "list-budgets", "dci --help"} { + if !strings.Contains(res.output, expected) { + t.Fatalf("output missing %q:\n%s", expected, res.output) + } + } + if strings.Contains(res.output, "Alerts Commands:") { + t.Fatalf("unknown command printed the full catalog:\n%s", res.output) + } + }) + + t.Run("agent unknown command is structured", func(t *testing.T) { + home := t.TempDir() + res := runCLIWithEnv(t, bin, home, []string{"DCI_AGENT_MODE=1", "DCI_API_KEY=test-key"}, "list-bugets") + if res.timedOut { + t.Fatalf("command timed out; output:\n%s", res.output) + } + if res.exitCode != 2 { + t.Fatalf("exit code = %d, want 2; output:\n%s", res.exitCode, res.output) + } + var envelope unknownCommandEnvelope + if err := json.Unmarshal([]byte(res.output), &envelope); err != nil { + t.Fatalf("invalid structured error %q: %v", res.output, err) + } + if envelope.Error.Code != "UNKNOWN_COMMAND" || !strings.Contains(envelope.Error.Hint, "list-budgets") { + t.Fatalf("unexpected structured error: %+v", envelope.Error) + } + }) + t.Run("status works", func(t *testing.T) { home := t.TempDir() res := runCLIWithHome(t, bin, home, "status") diff --git a/unknown_command.go b/unknown_command.go new file mode 100644 index 0000000..48550d2 --- /dev/null +++ b/unknown_command.go @@ -0,0 +1,124 @@ +package main + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "os" + "strings" + + "github.com/rest-sh/restish/cli" + "github.com/spf13/cobra" +) + +type unknownCommandError struct { + Command string + Suggestions []string + HasCommandCatalog bool +} + +var suppressedUnknownCommandStderr io.Writer + +func (e unknownCommandError) Error() string { + var message strings.Builder + fmt.Fprintf(&message, "unknown command %q", e.Command) + if len(e.Suggestions) == 1 { + fmt.Fprintf(&message, "\n\nDid you mean this?\n %s", e.Suggestions[0]) + } else if len(e.Suggestions) > 1 { + message.WriteString("\n\nDid you mean one of these?") + for _, suggestion := range e.Suggestions { + fmt.Fprintf(&message, "\n %s", suggestion) + } + } + message.WriteString("\n\nRun \"dci --help\" for common commands.") + if e.HasCommandCatalog { + message.WriteString("\nRun \"dci commands\" for the complete command catalog.") + } + return message.String() +} + +func (e unknownCommandError) ExitCode() int { + return 2 +} + +func (e unknownCommandError) AgentErrorCode() string { + return "UNKNOWN_COMMAND" +} + +func (e unknownCommandError) AgentErrorHint() string { + hint := "Run: dci --help" + if e.HasCommandCatalog { + hint += ". Full catalog: dci commands --json" + } + if len(e.Suggestions) > 0 { + hint = fmt.Sprintf("Did you mean %q? %s", e.Suggestions[0], hint) + } + return hint +} + +func (e unknownCommandError) AgentErrorRetryable() bool { + return false +} + +type unknownCommandEnvelope struct { + Error unknownCommandEnvelopeDetail `json:"error"` +} + +type unknownCommandEnvelopeDetail struct { + Code string `json:"code"` + Message string `json:"message"` + Hint string `json:"hint"` + Retryable bool `json:"retryable"` +} + +func handleUnknownCommandExecutionError(err error) (int, bool) { + var unknown unknownCommandError + if !errors.As(err, &unknown) { + return 0, false + } + writer := suppressedUnknownCommandStderr + if writer == nil { + writer = os.Stderr + } + cli.Stderr = writer + if agentMode { + _ = json.NewEncoder(writer).Encode(unknownCommandEnvelope{Error: unknownCommandEnvelopeDetail{ + Code: unknown.AgentErrorCode(), + Message: fmt.Sprintf("unknown command %q", unknown.Command), + Hint: unknown.AgentErrorHint(), + Retryable: unknown.AgentErrorRetryable(), + }}) + } else { + fmt.Fprintln(writer, unknown.Error()) + } + return unknown.ExitCode(), true +} + +func installUnknownCommandHandler() { + apiCommand := findDCICommand() + if apiCommand == nil { + return + } + previousArgs := apiCommand.Args + apiCommand.Args = func(command *cobra.Command, args []string) error { + if len(args) == 0 { + if previousArgs != nil { + return previousArgs(command, args) + } + return nil + } + command.Root().SilenceErrors = true + command.Root().SilenceUsage = true + suppressedUnknownCommandStderr = cli.Stderr + cli.Stderr = io.Discard + if command.SuggestionsMinimumDistance <= 0 { + command.SuggestionsMinimumDistance = 2 + } + suggestions := command.SuggestionsFor(args[0]) + if len(suggestions) > 3 { + suggestions = suggestions[:3] + } + return unknownCommandError{Command: args[0], Suggestions: suggestions, HasCommandCatalog: isRootCommand("commands")} + } +} diff --git a/unknown_command_test.go b/unknown_command_test.go new file mode 100644 index 0000000..e8e4e00 --- /dev/null +++ b/unknown_command_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "strings" + "testing" + + "github.com/rest-sh/restish/cli" + "github.com/spf13/cobra" +) + +func TestUnknownCommandError(t *testing.T) { + errorDetail := unknownCommandError{Command: "list-bugets", Suggestions: []string{"list-budgets"}} + if errorDetail.ExitCode() != 2 { + t.Fatalf("exit code = %d, want 2", errorDetail.ExitCode()) + } + if !strings.Contains(errorDetail.Error(), "Did you mean this?\n list-budgets") { + t.Fatalf("human error omitted suggestion: %s", errorDetail.Error()) + } + if errorDetail.AgentErrorCode() != "UNKNOWN_COMMAND" || !strings.Contains(errorDetail.AgentErrorHint(), "list-budgets") { + t.Fatalf("agent error = %+v", errorDetail) + } +} + +func TestInstallUnknownCommandHandler(t *testing.T) { + oldRoot := cli.Root + root := &cobra.Command{Use: "dci"} + apiCommand := &cobra.Command{Use: "dci"} + apiCommand.AddCommand(&cobra.Command{Use: "list-budgets", Run: func(*cobra.Command, []string) {}}) + root.AddCommand(apiCommand) + cli.Root = root + t.Cleanup(func() { cli.Root = oldRoot }) + + installUnknownCommandHandler() + err := apiCommand.Args(apiCommand, []string{"list-bugets"}) + unknown, ok := err.(unknownCommandError) + if !ok { + t.Fatalf("error = %T, want unknownCommandError", err) + } + if len(unknown.Suggestions) != 1 || unknown.Suggestions[0] != "list-budgets" { + t.Fatalf("suggestions = %v", unknown.Suggestions) + } + if !root.SilenceErrors || !root.SilenceUsage { + t.Fatal("framework error and usage output remain enabled") + } +} From 0a141f8fadbdd1eb0cd50eb03f9bc86dc294ae43 Mon Sep 17 00:00:00 2001 From: Michael Finson Date: Mon, 3 Aug 2026 15:40:19 +0300 Subject: [PATCH 3/3] chore: remove unrelated help rewrite --- main.go | 70 +++++++++++------------ main_test.go | 37 ------------ unknown_command.go | 124 ---------------------------------------- unknown_command_test.go | 45 --------------- 4 files changed, 35 insertions(+), 241 deletions(-) delete mode 100644 unknown_command.go delete mode 100644 unknown_command_test.go diff --git a/main.go b/main.go index ac47bdf..fc3ad90 100644 --- a/main.go +++ b/main.go @@ -314,13 +314,9 @@ func run() (exitCode int) { applyCustomerContext(configDir) lockToDCI() setupCompletion() - installUnknownCommandHandler() os.Args = normalizeArgs(os.Args) if err := cli.Run(); err != nil { - if code, handled := handleUnknownCommandExecutionError(err); handled { - return code - } fmt.Fprintf(os.Stderr, "%v\n", err) maybeHintDoerContext(1, cli.GetLastStatus(), configDir) return 1 @@ -750,34 +746,6 @@ Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} const dciLongDescription = "Command-line interface for the DoiT Cloud Intelligence API." -const compactRootHelpHeader = `Command-line interface for the DoiT Cloud Intelligence API. - -Usage: - dci [flags] - dci --help - -Common commands: - login Sign in to the DoiT Console - status Show CLI configuration and active context - list-budgets List budgets - list-reports List Cloud Analytics reports - list-alerts List alerts - query Run a Cloud Analytics query - skill Manage AI agent skills - version Print the CLI version - -Discovery: - dci --help Show detailed help for one command -` - -func compactRootHelp() string { - help := compactRootHelpHeader - if isRootCommand("commands") { - help += " dci commands Show the complete command catalog\n dci commands --json Show the machine-readable catalog\n" - } - return help -} - var rootExamples = []string{ " dci status", " dci list-budgets", @@ -863,7 +831,8 @@ func lockToDCI() { } } -// setupCompletion configures shell completion and compact root help. +// setupCompletion configures shell completion and root help so that API +// commands appear at root level (alongside status, login, etc.). // // The "dci" API subcommand is hidden since users access its commands directly // via normalizeArgs. Its ValidArgsFunction (which returns URL paths from @@ -934,13 +903,44 @@ func setupCompletion() { return completions, cobra.ShellCompDirectiveNoFileComp } + // Override root help to include API commands. Load the API, move its + // commands to root so the standard usage template renders them, then + // show help normally. defaultHelp := cli.Root.HelpFunc() cli.Root.SetHelpFunc(func(cmd *cobra.Command, args []string) { + hasAPICommands := false if cmd == cli.Root { - fmt.Fprint(cmd.OutOrStdout(), compactRootHelp()) - return + loadAPI() + hasAPICommands = len(dciCmd.Commands()) > 0 + // Copy command groups from the API subcommand to root so the + // usage template can render grouped commands. + for _, g := range dciCmd.Groups() { + if !cli.Root.ContainsGroup(g.ID) { + cli.Root.AddGroup(g) + } + } + // Collect first — iterating Commands() while removing mutates the slice. + subs := make([]*cobra.Command, len(dciCmd.Commands())) + copy(subs, dciCmd.Commands()) + for _, sub := range subs { + dciCmd.RemoveCommand(sub) + cli.Root.AddCommand(sub) + } } defaultHelp(cmd, args) + if cmd == cli.Root && !hasAPICommands { + hint := "\n! To get started, authenticate with: dci login (or set DCI_API_KEY)\n\n" + // In agent mode the hint is chatter — route it to stderr (plain, no + // color) so stdout stays parseable. + if agentMode { + fmt.Fprint(os.Stderr, hint) + } else { + if term.IsTerminal(int(os.Stdout.Fd())) { + hint = "\n\033[1;33m!\033[0m To get started, authenticate with: \033[1mdci login\033[0m (or set \033[1mDCI_API_KEY\033[0m)\n\n" + } + fmt.Fprint(os.Stdout, hint) + } + } }) } diff --git a/main_test.go b/main_test.go index fea4742..6910ccd 100644 --- a/main_test.go +++ b/main_test.go @@ -588,43 +588,6 @@ func TestCLIIntegrationBehavior(t *testing.T) { assertNoOAuthOrPanic(t, res.output) }) - t.Run("unknown command is compact and suggests a match", func(t *testing.T) { - home := t.TempDir() - res := runCLIWithEnv(t, bin, home, []string{"DCI_AGENT_MODE=0", "DCI_API_KEY=test-key"}, "list-bugets") - if res.timedOut { - t.Fatalf("command timed out; output:\n%s", res.output) - } - if res.exitCode != 2 { - t.Fatalf("exit code = %d, want 2; output:\n%s", res.exitCode, res.output) - } - for _, expected := range []string{"unknown command \"list-bugets\"", "list-budgets", "dci --help"} { - if !strings.Contains(res.output, expected) { - t.Fatalf("output missing %q:\n%s", expected, res.output) - } - } - if strings.Contains(res.output, "Alerts Commands:") { - t.Fatalf("unknown command printed the full catalog:\n%s", res.output) - } - }) - - t.Run("agent unknown command is structured", func(t *testing.T) { - home := t.TempDir() - res := runCLIWithEnv(t, bin, home, []string{"DCI_AGENT_MODE=1", "DCI_API_KEY=test-key"}, "list-bugets") - if res.timedOut { - t.Fatalf("command timed out; output:\n%s", res.output) - } - if res.exitCode != 2 { - t.Fatalf("exit code = %d, want 2; output:\n%s", res.exitCode, res.output) - } - var envelope unknownCommandEnvelope - if err := json.Unmarshal([]byte(res.output), &envelope); err != nil { - t.Fatalf("invalid structured error %q: %v", res.output, err) - } - if envelope.Error.Code != "UNKNOWN_COMMAND" || !strings.Contains(envelope.Error.Hint, "list-budgets") { - t.Fatalf("unexpected structured error: %+v", envelope.Error) - } - }) - t.Run("status works", func(t *testing.T) { home := t.TempDir() res := runCLIWithHome(t, bin, home, "status") diff --git a/unknown_command.go b/unknown_command.go deleted file mode 100644 index 48550d2..0000000 --- a/unknown_command.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "os" - "strings" - - "github.com/rest-sh/restish/cli" - "github.com/spf13/cobra" -) - -type unknownCommandError struct { - Command string - Suggestions []string - HasCommandCatalog bool -} - -var suppressedUnknownCommandStderr io.Writer - -func (e unknownCommandError) Error() string { - var message strings.Builder - fmt.Fprintf(&message, "unknown command %q", e.Command) - if len(e.Suggestions) == 1 { - fmt.Fprintf(&message, "\n\nDid you mean this?\n %s", e.Suggestions[0]) - } else if len(e.Suggestions) > 1 { - message.WriteString("\n\nDid you mean one of these?") - for _, suggestion := range e.Suggestions { - fmt.Fprintf(&message, "\n %s", suggestion) - } - } - message.WriteString("\n\nRun \"dci --help\" for common commands.") - if e.HasCommandCatalog { - message.WriteString("\nRun \"dci commands\" for the complete command catalog.") - } - return message.String() -} - -func (e unknownCommandError) ExitCode() int { - return 2 -} - -func (e unknownCommandError) AgentErrorCode() string { - return "UNKNOWN_COMMAND" -} - -func (e unknownCommandError) AgentErrorHint() string { - hint := "Run: dci --help" - if e.HasCommandCatalog { - hint += ". Full catalog: dci commands --json" - } - if len(e.Suggestions) > 0 { - hint = fmt.Sprintf("Did you mean %q? %s", e.Suggestions[0], hint) - } - return hint -} - -func (e unknownCommandError) AgentErrorRetryable() bool { - return false -} - -type unknownCommandEnvelope struct { - Error unknownCommandEnvelopeDetail `json:"error"` -} - -type unknownCommandEnvelopeDetail struct { - Code string `json:"code"` - Message string `json:"message"` - Hint string `json:"hint"` - Retryable bool `json:"retryable"` -} - -func handleUnknownCommandExecutionError(err error) (int, bool) { - var unknown unknownCommandError - if !errors.As(err, &unknown) { - return 0, false - } - writer := suppressedUnknownCommandStderr - if writer == nil { - writer = os.Stderr - } - cli.Stderr = writer - if agentMode { - _ = json.NewEncoder(writer).Encode(unknownCommandEnvelope{Error: unknownCommandEnvelopeDetail{ - Code: unknown.AgentErrorCode(), - Message: fmt.Sprintf("unknown command %q", unknown.Command), - Hint: unknown.AgentErrorHint(), - Retryable: unknown.AgentErrorRetryable(), - }}) - } else { - fmt.Fprintln(writer, unknown.Error()) - } - return unknown.ExitCode(), true -} - -func installUnknownCommandHandler() { - apiCommand := findDCICommand() - if apiCommand == nil { - return - } - previousArgs := apiCommand.Args - apiCommand.Args = func(command *cobra.Command, args []string) error { - if len(args) == 0 { - if previousArgs != nil { - return previousArgs(command, args) - } - return nil - } - command.Root().SilenceErrors = true - command.Root().SilenceUsage = true - suppressedUnknownCommandStderr = cli.Stderr - cli.Stderr = io.Discard - if command.SuggestionsMinimumDistance <= 0 { - command.SuggestionsMinimumDistance = 2 - } - suggestions := command.SuggestionsFor(args[0]) - if len(suggestions) > 3 { - suggestions = suggestions[:3] - } - return unknownCommandError{Command: args[0], Suggestions: suggestions, HasCommandCatalog: isRootCommand("commands")} - } -} diff --git a/unknown_command_test.go b/unknown_command_test.go deleted file mode 100644 index e8e4e00..0000000 --- a/unknown_command_test.go +++ /dev/null @@ -1,45 +0,0 @@ -package main - -import ( - "strings" - "testing" - - "github.com/rest-sh/restish/cli" - "github.com/spf13/cobra" -) - -func TestUnknownCommandError(t *testing.T) { - errorDetail := unknownCommandError{Command: "list-bugets", Suggestions: []string{"list-budgets"}} - if errorDetail.ExitCode() != 2 { - t.Fatalf("exit code = %d, want 2", errorDetail.ExitCode()) - } - if !strings.Contains(errorDetail.Error(), "Did you mean this?\n list-budgets") { - t.Fatalf("human error omitted suggestion: %s", errorDetail.Error()) - } - if errorDetail.AgentErrorCode() != "UNKNOWN_COMMAND" || !strings.Contains(errorDetail.AgentErrorHint(), "list-budgets") { - t.Fatalf("agent error = %+v", errorDetail) - } -} - -func TestInstallUnknownCommandHandler(t *testing.T) { - oldRoot := cli.Root - root := &cobra.Command{Use: "dci"} - apiCommand := &cobra.Command{Use: "dci"} - apiCommand.AddCommand(&cobra.Command{Use: "list-budgets", Run: func(*cobra.Command, []string) {}}) - root.AddCommand(apiCommand) - cli.Root = root - t.Cleanup(func() { cli.Root = oldRoot }) - - installUnknownCommandHandler() - err := apiCommand.Args(apiCommand, []string{"list-bugets"}) - unknown, ok := err.(unknownCommandError) - if !ok { - t.Fatalf("error = %T, want unknownCommandError", err) - } - if len(unknown.Suggestions) != 1 || unknown.Suggestions[0] != "list-budgets" { - t.Fatalf("suggestions = %v", unknown.Suggestions) - } - if !root.SilenceErrors || !root.SilenceUsage { - t.Fatal("framework error and usage output remain enabled") - } -}