diff --git a/internal/cmd/add.go b/internal/cmd/add.go index 099e879..7618bc5 100644 --- a/internal/cmd/add.go +++ b/internal/cmd/add.go @@ -28,7 +28,13 @@ Required: + --email. Everything else is optional. Run with no flags (just the name) and you'll get an interactive prompt for each field.`, - Args: cobra.ExactArgs(1), + Args: exactArgsHelp(1, + "gitswitch add requires an identity name", + "gitswitch add work --email you@company.com", + "gitswitch add personal (interactive prompts for the rest)", + ), + Example: " gitswitch add work --email you@company.com --gh you-work\n" + + " gitswitch add personal # interactive: prompts for email, key, gh, etc.", RunE: func(cmd *cobra.Command, args []string) error { return runAdd(cmd, args[0]) }, diff --git a/internal/cmd/argshelp.go b/internal/cmd/argshelp.go new file mode 100644 index 0000000..c7009c1 --- /dev/null +++ b/internal/cmd/argshelp.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "errors" + "strings" + + "github.com/spf13/cobra" +) + +// argError formats a friendly "you used this wrong" error for argument +// validation failures. The default cobra message ("accepts 1 arg(s), +// received 0") is technically correct but tells the user nothing +// useful — they have to leave the error and run --help to recover. +// +// Usage: pass a one-line description of what's wrong, then a list of +// worked examples. The output is: +// +// +// +// examples: +// +// +// +// run " --help" for the full reference. +func argError(cmd *cobra.Command, usage string, examples ...string) error { + var sb strings.Builder + sb.WriteString(usage) + if len(examples) > 0 { + sb.WriteString("\n\nexamples:") + for _, ex := range examples { + sb.WriteString("\n ") + sb.WriteString(ex) + } + } + if cmd != nil { + sb.WriteString("\n\nrun \"") + sb.WriteString(cmd.CommandPath()) + sb.WriteString(" --help\" for the full reference.") + } + return errors.New(sb.String()) +} + +// exactArgsHelp is cobra.ExactArgs(n) with a useful error message on +// miscount. Pass the same usage + example shape as argError. +func exactArgsHelp(n int, usage string, examples ...string) cobra.PositionalArgs { + return func(cmd *cobra.Command, args []string) error { + if len(args) == n { + return nil + } + return argError(cmd, usage, examples...) + } +} + +// rangeArgsHelp is cobra.RangeArgs(min, max) with a useful error message. +func rangeArgsHelp(minArgs, maxArgs int, usage string, examples ...string) cobra.PositionalArgs { + return func(cmd *cobra.Command, args []string) error { + if len(args) >= minArgs && len(args) <= maxArgs { + return nil + } + return argError(cmd, usage, examples...) + } +} diff --git a/internal/cmd/delete.go b/internal/cmd/delete.go index 071008b..a80ce13 100644 --- a/internal/cmd/delete.go +++ b/internal/cmd/delete.go @@ -28,7 +28,14 @@ Does NOT touch your SSH keys, GPG keys, or gh CLI authentication — those aren't gitswitch's to delete. Use this when you've added a duplicate by accident or want to retire an identity from gitswitch without nuking the underlying credentials.`, - Args: cobra.ExactArgs(1), + Args: exactArgsHelp(1, + "gitswitch delete requires an identity name", + "gitswitch delete work (prompts before removing)", + "gitswitch delete work -y (skips the confirmation)", + ), + Example: " gitswitch delete work\n" + + " gitswitch rm personal -y\n" + + " gitswitch list # see what's configured first", RunE: func(cmd *cobra.Command, args []string) error { yes, _ := cmd.Flags().GetBool("yes") return runDelete(args[0], yes) diff --git a/internal/cmd/doctor.go b/internal/cmd/doctor.go index d921a61..555ae42 100644 --- a/internal/cmd/doctor.go +++ b/internal/cmd/doctor.go @@ -22,7 +22,8 @@ green line if everything agrees, or a red diff if they disagree. This is the command you should run any time you suspect "wait, did my last commit go out as the right person?"`, - RunE: runDoctor, + Example: " gitswitch doctor", + RunE: runDoctor, } } diff --git a/internal/cmd/init.go b/internal/cmd/init.go index 9c24a44..015e181 100644 --- a/internal/cmd/init.go +++ b/internal/cmd/init.go @@ -24,6 +24,8 @@ one. Writes the gitswitch config — but does not yet apply anything to This command is read-only with respect to the rest of your system. The only file it writes is ~/.config/gitswitch/config.json.`, + Example: " gitswitch init # interactive — prompts to name each identity\n" + + " gitswitch init -y # accept proposed names, no prompts", RunE: func(_ *cobra.Command, _ []string) error { return runInit(assumeYes) }, diff --git a/internal/cmd/list.go b/internal/cmd/list.go index 00d23c1..3d6cceb 100644 --- a/internal/cmd/list.go +++ b/internal/cmd/list.go @@ -19,6 +19,8 @@ binding currently active. Reads ~/.config/gitswitch/config.json. This is what you should run before "gitswitch use" if you've forgotten the names of your identities.`, + Example: " gitswitch list\n" + + " gitswitch ls", RunE: func(_ *cobra.Command, _ []string) error { return runList() }, diff --git a/internal/cmd/rename.go b/internal/cmd/rename.go index 19e5ed6..33c536f 100644 --- a/internal/cmd/rename.go +++ b/internal/cmd/rename.go @@ -27,7 +27,13 @@ func newRenameCommand() *cobra.Command { Useful when init auto-named an identity something awkward (e.g. a lowercased gh login) and you want a more human name. Underlying SSH keys and gh accounts are untouched.`, - Args: cobra.ExactArgs(2), + Args: exactArgsHelp(2, + "gitswitch rename takes ", + "gitswitch rename ofirhaim1 personal", + "gitswitch mv work-old work # alias", + ), + Example: " gitswitch rename ofirhaim1 personal\n" + + " gitswitch mv work-old work-new", RunE: func(_ *cobra.Command, args []string) error { return runRename(args[0], args[1]) }, diff --git a/internal/cmd/use.go b/internal/cmd/use.go index bbf59d6..7557f4e 100644 --- a/internal/cmd/use.go +++ b/internal/cmd/use.go @@ -36,7 +36,15 @@ Concretely, "use" does three things: Re-running "use" with the same identity + directory is a no-op (the sentinel-marked block is replaced in place). Run with --unbind to remove the binding.`, - Args: cobra.RangeArgs(1, 2), + Args: rangeArgsHelp(1, 2, + "gitswitch use takes []", + "gitswitch use work ~/work # bind work identity to ~/work", + "gitswitch use personal ~/code", + "gitswitch use work # refresh per-identity config (no binding)", + ), + Example: " gitswitch use work ~/work\n" + + " gitswitch use personal ~/code\n" + + " gitswitch use work ~/work --unbind # reverse the binding", RunE: func(cmd *cobra.Command, args []string) error { unbind, _ := cmd.Flags().GetBool("unbind") return runUse(args, unbind) diff --git a/internal/cmd/why.go b/internal/cmd/why.go index b09dadc..a7d0f87 100644 --- a/internal/cmd/why.go +++ b/internal/cmd/why.go @@ -21,6 +21,8 @@ included, what user.email resolves to, and whether the layers agree. The honest counterweight to any "automatic" tool — magic you can't inspect is just a bug waiting to happen.`, + Example: " cd ~/work && gitswitch why\n" + + " cd ~/personal && gitswitch why", RunE: func(_ *cobra.Command, _ []string) error { return runWhy() },