diff --git a/README.md b/README.md index 78dd6d5..75b9fa9 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ The `shared` binary talks to the server over HTTP (`--server`, default ```sh shared init [dir] # scaffold index.html + a shared-sites agent skill +shared skill install [--force] # install the shared-sites skill into ~/.claude/skills shared deploy [dir] --name NAME # pack a directory and deploy it (--force skips # the overwrite check) shared list # deployed sites with size, views, last deployer @@ -171,7 +172,10 @@ shared backup [file] # download a gzipped tarball of all server dat `shared init` writes a minimal `index.html` and `.claude/skills/shared-sites/SKILL.md` (an agent skill documenting the client -API), refusing to overwrite existing files. `shared backup` defaults to +API) into a site directory, refusing to overwrite existing files. `shared skill +install` writes that same skill to `~/.claude/skills/shared-sites/SKILL.md` so +it is available to agents in every project, not just a scaffolded one; it skips +an existing file unless `--force` is given. `shared backup` defaults to `shared-backup-.tar.gz` in the current directory. ## Subdomain routing diff --git a/cmd/shared/main.go b/cmd/shared/main.go index c91d501..535b7ac 100644 --- a/cmd/shared/main.go +++ b/cmd/shared/main.go @@ -37,6 +37,7 @@ commands: versions NAME [--server URL] list a site's saved versions backup [file] [--server URL] download a tarball of all server data init [dir] scaffold a new site directory + skill install [--force] install the shared-sites skill into ~/.claude/skills ` func main() { @@ -61,6 +62,8 @@ func main() { cmdBackup(os.Args[2:]) case "init": cmdInit(os.Args[2:]) + case "skill": + cmdSkill(os.Args[2:]) case "-h", "--help", "help": fmt.Print(usage) default: @@ -643,6 +646,35 @@ func cmdInit(args []string) { } } +func cmdSkill(args []string) { + if len(args) < 1 || args[0] != "install" { + fmt.Fprintln(os.Stderr, "usage: shared skill install [--force]") + os.Exit(2) + } + fs := flag.NewFlagSet("skill install", flag.ExitOnError) + force := fs.Bool("force", false, "overwrite an existing skill file") + fs.Parse(args[1:]) + + home, err := os.UserHomeDir() + if err != nil { + fatal("%v", err) + } + dest := filepath.Join(home, ".claude", "skills", "shared-sites", "SKILL.md") + if _, err := os.Stat(dest); err == nil && !*force { + fmt.Printf("skip %s (exists; --force to overwrite)\n", dest) + return + } else if err != nil && !os.IsNotExist(err) { + fatal("%v", err) + } + if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { + fatal("%v", err) + } + if err := os.WriteFile(dest, web.InitSkillMD, 0o644); err != nil { + fatal("writing %s: %v", dest, err) + } + fmt.Printf("wrote %s\n", dest) +} + func humanSize(n int64) string { const unit = 1024 if n < unit {