diff --git a/cli/cmd/auth.go b/cli/cmd/auth.go index cfb65f4..6f42ae3 100644 --- a/cli/cmd/auth.go +++ b/cli/cmd/auth.go @@ -16,25 +16,36 @@ var credentialsPath string func addAuthCmd(root *cobra.Command) { authCmd := &cobra.Command{ Use: "auth", - Short: "Manage authentication", + Short: "Manage authentication with the registry", } loginCmd := &cobra.Command{ Use: "login", - Short: "Authenticate with the registry", - RunE: runAuthLogin, + Short: "Authenticate with the registry via OIDC device flow", + Long: `Start an OIDC device authorization flow against the registry's configured +identity provider. The CLI prints a URL and a user code; open the URL in any +browser, enter the code, and approve. The resulting tokens are cached at +~/.config/skillsctl/credentials.json (or the path passed to --credentials-path) +and refreshed automatically on subsequent commands. + +If the server is running without OIDC configured, this command prints a notice +and exits successfully without writing credentials.`, + Example: ` skillsctl auth login`, + RunE: runAuthLogin, } statusCmd := &cobra.Command{ - Use: "status", - Short: "Show authentication status", - RunE: runAuthStatus, + Use: "status", + Short: "Show the active identity and token expiry", + Example: ` skillsctl auth status`, + RunE: runAuthStatus, } logoutCmd := &cobra.Command{ - Use: "logout", - Short: "Remove cached credentials", - RunE: runAuthLogout, + Use: "logout", + Short: "Remove cached credentials", + Example: ` skillsctl auth logout`, + RunE: runAuthLogout, } authCmd.AddCommand(loginCmd, statusCmd, logoutCmd) diff --git a/cli/cmd/config.go b/cli/cmd/config.go index 88a6085..f8e5d47 100644 --- a/cli/cmd/config.go +++ b/cli/cmd/config.go @@ -21,34 +21,41 @@ var validConfigKeys = []string{"api_url", "skills_dir"} func addConfigCmd(root *cobra.Command) { configCmd := &cobra.Command{ Use: "config", - Short: "Manage skillsctl configuration", + Short: "Manage the local skillsctl configuration file", + Long: `Read and write ~/.config/skillsctl/config.yaml. Recognized keys are +api_url (the registry URL the CLI talks to) and skills_dir (where 'install' +writes downloaded skills).`, } initCmd := &cobra.Command{ - Use: "init", - Short: "Interactive first-time setup", - RunE: runConfigInit, + Use: "init", + Short: "Interactive first-time setup; prompts for api_url and skills_dir", + Example: ` skillsctl config init`, + RunE: runConfigInit, } - initCmd.Flags().Bool("force", false, "Overwrite existing config") + initCmd.Flags().Bool("force", false, "Overwrite an existing config file") setCmd := &cobra.Command{ - Use: "set ", - Short: "Set a config value", - Args: cobra.ExactArgs(2), - RunE: runConfigSet, + Use: "set ", + Short: "Set a config value (api_url or skills_dir)", + Example: ` skillsctl config set api_url https://skillsctl.example.com`, + Args: cobra.ExactArgs(2), + RunE: runConfigSet, } getCmd := &cobra.Command{ - Use: "get ", - Short: "Get a config value", - Args: cobra.ExactArgs(1), - RunE: runConfigGet, + Use: "get ", + Short: "Print a single config value", + Example: ` skillsctl config get api_url`, + Args: cobra.ExactArgs(1), + RunE: runConfigGet, } listCmd := &cobra.Command{ - Use: "list", - Short: "List all config values", - RunE: runConfigList, + Use: "list", + Short: "Print every recognized config value", + Example: ` skillsctl config list`, + RunE: runConfigList, } configCmd.PersistentFlags().StringVar(&configPath, "config-path", "", "Config file path (for testing)") diff --git a/cli/cmd/explore.go b/cli/cmd/explore.go index f84769b..6a73148 100644 --- a/cli/cmd/explore.go +++ b/cli/cmd/explore.go @@ -20,6 +20,14 @@ func addExploreCmd(root *cobra.Command) { exploreCmd := &cobra.Command{ Use: "explore", Short: "Browse available skills", + Example: ` # List every skill in the registry + skillsctl explore + + # Filter by tag (repeatable; matches any) + skillsctl explore --tag go --tag testing + + # Only show skills published directly to this registry + skillsctl explore --source internal`, RunE: func(cmd *cobra.Command, _ []string) error { client := getClientCtx(cmd.Context()) @@ -41,13 +49,18 @@ func addExploreCmd(root *cobra.Command) { }, } - exploreCmd.Flags().StringSliceVar(&tags, "tag", nil, "Filter by tag (repeatable)") - exploreCmd.Flags().StringVar(&source, "source", "all", "Filter: internal, external, all") + exploreCmd.Flags().StringSliceVar(&tags, "tag", nil, "Filter by tag; repeat to match any of several tags") + exploreCmd.Flags().StringVar(&source, "source", "all", "Filter by source: internal, external, or all") showCmd := &cobra.Command{ Use: "show ", - Short: "Show skill details", - Args: cobra.ExactArgs(1), + Short: "Show skill details and version history", + Example: ` # Metadata and version history + skillsctl explore show git-commit + + # Include the rendered SKILL.md content + skillsctl explore show git-commit --verbose`, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { client := getClientCtx(cmd.Context()) skill, versions, err := client.GetSkill(context.Background(), args[0]) @@ -78,7 +91,7 @@ func addExploreCmd(root *cobra.Command) { return nil }, } - showCmd.Flags().BoolP("verbose", "v", false, "include skill content in output") + showCmd.Flags().BoolP("verbose", "v", false, "Include the SKILL.md content (multi-file skills are extracted first)") exploreCmd.AddCommand(showCmd) root.AddCommand(exploreCmd) diff --git a/cli/cmd/install.go b/cli/cmd/install.go index 29c6011..ffe18be 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -38,7 +38,24 @@ func addInstallCmd(root *cobra.Command) { installCmd := &cobra.Command{ Use: "install ", Short: "Install a skill from the registry", - Args: cobra.ExactArgs(1), + Long: `Download a skill from the registry and write it to the local skills directory. + +By default skills install to the directory configured as skills_dir +(typically ~/.claude/skills). Use --project to install into a specific +project's .claude/skills/ instead, or --skills-dir to override the +target for a single invocation.`, + Example: ` # Install the latest version + skillsctl install git-commit + + # Pin a version and verify the digest + skillsctl install git-commit@1.2.0 --digest sha256:abc123... + + # Install into the current project (writes to ./.claude/skills/) + skillsctl install git-commit --project + + # Install into a specific project directory + skillsctl install git-commit --project /path/to/repo`, + Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { name, version := parseNameVersion(args[0]) if err := validateSkillName(name); err != nil { @@ -90,8 +107,8 @@ func addInstallCmd(root *cobra.Command) { }, } - installCmd.Flags().StringVar(&digest, "digest", "", "Expected content digest for verification") - installCmd.Flags().StringVar(&skillsDir, "skills-dir", "", "Override skills directory") + installCmd.Flags().StringVar(&digest, "digest", "", "Expected content digest in sha256:... form; install aborts if the downloaded content does not match") + installCmd.Flags().StringVar(&skillsDir, "skills-dir", "", "Override the configured skills directory for this install") installCmd.Flags().StringVar(&projectDir, "project", "", "Install to /.claude/skills; when used without a value, installs into the current directory") // Passing --project without a value resolves to the current directory. installCmd.Flags().Lookup("project").NoOptDefVal = "." diff --git a/cli/cmd/publish.go b/cli/cmd/publish.go index 8221b17..f78e714 100644 --- a/cli/cmd/publish.go +++ b/cli/cmd/publish.go @@ -23,6 +23,27 @@ func addPublishCmd(root *cobra.Command) { publishCmd := &cobra.Command{ Use: "publish", Short: "Publish a skill to the registry", + Long: `Package a skill directory and upload it to the registry. + +The directory passed via --dir must contain SKILL.md at its root; +subdirectories (scripts/, references/, assets/) are packaged and +preserved on install. Versions are immutable once published, so each +publish must use a new --version.`, + Example: ` # Publish the first version of a skill + skillsctl publish \ + --name git-conventional \ + --version 1.0.0 \ + --description "Enforce conventional commit messages" \ + --dir ./git-conventional + + # Publish with tags and a changelog + skillsctl publish \ + --name git-conventional \ + --version 1.1.0 \ + --description "Enforce conventional commit messages" \ + --dir ./git-conventional \ + --tag git --tag commits \ + --changelog "Add breaking-change detection"`, RunE: func(cmd *cobra.Command, _ []string) error { content, err := skillpkg.Pack(dirPath) if err != nil { @@ -53,12 +74,12 @@ func addPublishCmd(root *cobra.Command) { }, } - publishCmd.Flags().StringVar(&name, "name", "", "Skill name") - publishCmd.Flags().StringVar(&version, "version", "", "Skill version (semver)") - publishCmd.Flags().StringVar(&description, "description", "", "Skill description") - publishCmd.Flags().StringVar(&dirPath, "dir", "", "Path to skill directory containing SKILL.md") - publishCmd.Flags().StringSliceVar(&tags, "tag", nil, "Tags (repeatable)") - publishCmd.Flags().StringVar(&changelog, "changelog", "", "Version changelog") + publishCmd.Flags().StringVar(&name, "name", "", "Skill name (lowercase, 2-64 chars, alphanumeric and hyphens)") + publishCmd.Flags().StringVar(&version, "version", "", "Semantic version, e.g. 1.0.0; must be unique for this skill") + publishCmd.Flags().StringVar(&description, "description", "", "Short description shown in 'skillsctl explore'") + publishCmd.Flags().StringVar(&dirPath, "dir", "", "Path to the skill directory; must contain SKILL.md at its root") + publishCmd.Flags().StringSliceVar(&tags, "tag", nil, "Tag for filtering in 'explore' (repeatable)") + publishCmd.Flags().StringVar(&changelog, "changelog", "", "Release notes for this version") _ = publishCmd.MarkFlagRequired("name") _ = publishCmd.MarkFlagRequired("version") diff --git a/cli/cmd/root.go b/cli/cmd/root.go index f1a7dbc..4ad20fa 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -21,10 +21,14 @@ func NewRootCmd() *cobra.Command { rootCmd := &cobra.Command{ Use: "skillsctl", Short: "Discover, install, and publish Claude Code skills", + Long: `skillsctl is the CLI for the SkillsCtl skill registry. Use it to browse +and install skills published by your team or organization, and to publish +your own. Skills install into ~/.claude/skills/ by default and are picked +up automatically by Claude Code at session start.`, } rootCmd.Version = version - rootCmd.PersistentFlags().StringVar(&apiURL, "api-url", "", "Backend API URL") + rootCmd.PersistentFlags().StringVar(&apiURL, "api-url", "", "Registry URL; overrides the api_url setting from config and SKILLCTL_API_URL") rootCmd.PersistentFlags().StringVar(&credentialsPath, "credentials-path", "", "Credentials file path (for testing)") cobra.OnInitialize(func() {