package main import ( "context" "errors" "fmt" "os" "os/signal" "runtime/debug" "strings" "syscall" "github.com/spf13/cobra" ) // version is set at build time; `go install` builds fall back to the module // version. var version = "dev" func init() { if bi, ok := debug.ReadBuildInfo(); ok && version == "dev" && bi.Main.Version != "" && bi.Main.Version != "(devel)" { version = bi.Main.Version } } const docsURL = "https://git.simenandre.no/skills/about/" type usageError struct{ error } type options struct { manifest string quiet bool } func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) err := newRoot().ExecuteContext(ctx) interrupted := ctx.Err() != nil stop() if err == nil { return } fmt.Fprintln(os.Stderr, "skills:", err) var ue usageError switch { case interrupted: os.Exit(130) case errors.As(err, &ue), strings.HasPrefix(err.Error(), "unknown command"): os.Exit(2) } os.Exit(1) } func newRoot() *cobra.Command { opts := &options{} root := &cobra.Command{ Use: "skills", Short: "Install and update agent skills from git repositories", Long: `skills installs agent skills (directories holding a SKILL.md) from git repositories into Claude Code, Codex and OpenCode, and keeps them updated. What you add is recorded in a manifest, and the exact commit of each source in a lock file next to it. Commit both to reproduce the same skills on another machine with ` + "`skills install`" + `.`, Example: ` # Add every skill in a repository skills add anthropics/skills # Add one skill, from a private repository, at a tag skills add git@example.com:team/skills.git#v1.2.0 --skill deploy # See what is installed, then move everything to the newest commits skills list skills update`, Version: version, SilenceUsage: true, SilenceErrors: true, } root.SetFlagErrorFunc(func(_ *cobra.Command, err error) error { return usageError{err} }) root.PersistentFlags().StringVar(&opts.manifest, "manifest", "", "manifest to use (default $SKILLS_MANIFEST, else $XDG_CONFIG_HOME/skills/skills.json)") root.PersistentFlags().BoolVarP(&opts.quiet, "quiet", "q", false, "print only errors") root.SetHelpTemplate(root.HelpTemplate() + "\nDocumentation and source: " + docsURL + "\n") root.AddCommand(addCmd(opts), removeCmd(opts), updateCmd(opts), installCmd(opts), listCmd(opts)) return root } func (o *options) workspace() (*workspace, error) { path, err := manifestPath(o.manifest) if err != nil { return nil, err } return openWorkspace(path) } func (o *options) printf(format string, a ...any) { if !o.quiet { fmt.Printf(format, a...) } } // progressf reports slow work on stderr, only to a person watching. func (o *options) progressf(format string, a ...any) { if o.quiet { return } if fi, err := os.Stderr.Stat(); err == nil && fi.Mode()&os.ModeCharDevice != 0 { fmt.Fprintf(os.Stderr, format, a...) } } func exactArgs(n int) cobra.PositionalArgs { return func(cmd *cobra.Command, args []string) error { if len(args) != n { return usageError{fmt.Errorf("%s takes %d argument(s), got %d (see `%s --help`)", cmd.Name(), n, len(args), cmd.CommandPath())} } return nil } } func minArgs(n int) cobra.PositionalArgs { return func(cmd *cobra.Command, args []string) error { if len(args) < n { return usageError{fmt.Errorf("%s needs at least %d argument(s) (see `%s --help`)", cmd.Name(), n, cmd.CommandPath())} } return nil } }