aboutsummaryrefslogtreecommitdiffstats
path: root/main.go
diff options
context:
space:
mode:
authorSimen A. W. Olsen <hello@simenandre.no>2026-10-25 12:41:28 +0100
committerSimen A. W. Olsen <hello@simenandre.no>2026-10-25 12:41:28 +0100
commitc596038adc7cdbd64988e61fee856d533b58c25d (patch)
treea02fde25835416d143ff56b807e640f6a7e9cf82 /main.go
downloadskills-main.tar.gz
skills-main.zip
feat: skills, a package manager for agent skillsHEADv0.1.0main
Diffstat (limited to 'main.go')
-rw-r--r--main.go128
1 files changed, 128 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..b1674c6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,128 @@
+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
+ }
+}