diff --git a/client/cmd/root.go b/client/cmd/root.go index 72de67f..697a781 100644 --- a/client/cmd/root.go +++ b/client/cmd/root.go @@ -38,6 +38,9 @@ var rootCmd = &cobra.Command{ if len(args) == 0 { return cmd.Help() } + if len(args) == 1 { + return fmt.Errorf("unknown command %q\nTo add a note, use: kb \"%s ...\" or pass multiple words", args[0], args[0]) + } note := strings.Join(args, " ") tags, _ := cmd.Flags().GetString("tags") client := api.NewClient() @@ -48,8 +51,8 @@ var rootCmd = &cobra.Command{ func init() { api.SetVersionInfo(Version, MinEngineVersion) rootCmd.Version = Version - rootCmd.SetUsageTemplate(`Quick note taking: - kb "note text" [flags] + rootCmd.SetUsageTemplate(`Quick note taking (must be more than one word): + kb "note text here" [flags] Normal usage: kb [command] [flags]{{if .HasAvailableSubCommands}} diff --git a/client/cmd/root_test.go b/client/cmd/root_test.go new file mode 100644 index 0000000..0d3be51 --- /dev/null +++ b/client/cmd/root_test.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "bytes" + "strings" + "testing" +) + +func TestRootCmd_SingleWordRejected(t *testing.T) { + rootCmd.SetArgs([]string{"infow"}) + + var stderr bytes.Buffer + rootCmd.SetErr(&stderr) + + err := rootCmd.Execute() + if err == nil { + t.Fatal("expected error for single bare word, got nil") + } + + errMsg := err.Error() + if !strings.Contains(errMsg, `unknown command "infow"`) { + t.Errorf("expected error to mention unknown command, got: %s", errMsg) + } + if !strings.Contains(errMsg, "multiple words") { + t.Errorf("expected error to suggest multiple words, got: %s", errMsg) + } +} + +func TestRootCmd_MultipleWordsNotRejected(t *testing.T) { + rootCmd.SetArgs([]string{"remember", "to", "update", "dns"}) + + err := rootCmd.Execute() + // Will fail at API call (no server), but should NOT be the "unknown command" error + if err != nil && strings.Contains(err.Error(), "unknown command") { + t.Errorf("multi-word input should not be rejected as unknown command, got: %s", err.Error()) + } +} + +func TestRootCmd_NoArgs_ShowsHelp(t *testing.T) { + rootCmd.SetArgs([]string{}) + + var stdout bytes.Buffer + rootCmd.SetOut(&stdout) + + err := rootCmd.Execute() + if err != nil { + t.Fatalf("expected no error for zero args, got: %v", err) + } + + output := stdout.String() + if !strings.Contains(output, "Available Commands") { + t.Errorf("expected help output, got: %s", output) + } +} diff --git a/openspec/specs/go-client/spec.md b/openspec/specs/go-client/spec.md index ea030d0..ff9b42d 100644 --- a/openspec/specs/go-client/spec.md +++ b/openspec/specs/go-client/spec.md @@ -70,7 +70,7 @@ The client SHALL provide a `kb search ` command that sends the query to t ### Requirement: Implicit note shorthand -The client SHALL treat bare string arguments (with no subcommand) as an implicit note. `kb "my note"` SHALL behave identically to submitting a note via `POST /api/v1/jobs`. All persistent flags (`--format`, `--engine`, `--api-key`) and the root `--tags` flag SHALL work with the shorthand form. +The client SHALL treat bare string arguments (with no subcommand) as an implicit note only when **more than one argument** is provided. `kb "my note"` SHALL behave identically to submitting a note via `POST /api/v1/jobs`. All persistent flags (`--format`, `--engine`, `--api-key`) and the root `--tags` flag SHALL work with the shorthand form. A single bare word SHALL be rejected with an error message. #### Scenario: Quick note via bare argument - **WHEN** the user runs `kb "remember to update DNS"` @@ -92,6 +92,10 @@ The client SHALL treat bare string arguments (with no subcommand) as an implicit - **WHEN** the user runs `kb remember to update dns` (without quotes) - **THEN** the client SHALL join all arguments into a single note string and submit it +#### Scenario: Single bare word rejected +- **WHEN** the user runs `kb infow` (a single unrecognized word) +- **THEN** the client SHALL print to stderr: `Unknown command "infow". Run 'kb --help' for available commands.` followed by a hint about note usage, and exit with a non-zero code + #### Scenario: No interference with subcommands - **WHEN** the user runs `kb search "query"` or any other existing subcommand - **THEN** the client SHALL route to the subcommand as before — the implicit note shorthand SHALL NOT interfere