// Skills: carlosframework.com publishes the platform's agent skills per the // Agent Skills Discovery draft (github.com/cloudflare/agent-skills-discovery-rfc) // at a well-known index. This command is the bootstrap pointer — any agent // holding the CLI can list what is published and fetch one, verified, // without knowing the site's layout in advance. package main import ( "crypto/sha256" "encoding/hex" "encoding/json" "flag" "fmt" "io" "log" "net/http" "net/url" "strings" "time" ) // skillsIndexURL is a var, not a const, so tests can point it at an // httptest server and -index can point a self-hosted deployment elsewhere. // Production never changes it. var skillsIndexURL = "https://carlosframework.com/.well-known/agent-skills/index.json" // skillEntry is one row of the published index. The draft allows unknown // fields ("//" among them) — only what this command uses is declared here. type skillEntry struct { Name string `json:"name"` Type string `json:"type"` Description string `json:"description"` URL string `json:"url"` Digest string `json:"digest"` } type skillsIndex struct { Skills []skillEntry `json:"skills"` } // skillsHTTPTimeout bounds both the index fetch and the skill fetch — a // slow or hung site must fail loudly, not hang an agent's session. const skillsHTTPTimeout = 10 * time.Second // fetchSkillsIndex GETs and parses the index. Errors are returned rather // than fatal'd so runSkills is the only place in this file that exits. func fetchSkillsIndex(indexURL string) (*skillsIndex, error) { client := &http.Client{Timeout: skillsHTTPTimeout} res, err := client.Get(indexURL) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s: status %d", indexURL, res.StatusCode) } var idx skillsIndex if err := json.NewDecoder(res.Body).Decode(&idx); err != nil { return nil, fmt.Errorf("%s: %v", indexURL, err) } return &idx, nil } // resolveSkillURL turns a site-relative entry.url into the absolute URL to // fetch, resolved against indexURL per the draft (net/url ResolveReference, // not string concatenation — a leading "/" must land at the site root // even when the index itself lives under a path prefix). func resolveSkillURL(indexURL, ref string) (string, error) { base, err := url.Parse(indexURL) if err != nil { return "", err } rel, err := url.Parse(ref) if err != nil { return "", err } return base.ResolveReference(rel).String(), nil } // listSkills renders the index: name, then indented description, then // indented absolute url, blank line between entries. func listSkills(w io.Writer, indexURL string, idx *skillsIndex) error { for i, s := range idx.Skills { if i > 0 { fmt.Fprintln(w) } abs, err := resolveSkillURL(indexURL, s.URL) if err != nil { return err } fmt.Fprintln(w, s.Name) fmt.Fprintf(w, " %s\n", s.Description) fmt.Fprintf(w, " %s\n", abs) } return nil } // fetchSkill finds name in the index at indexURL, fetches its url, and // verifies the sha256 digest before returning the bytes. A mismatch means // corruption or tampering (the draft's own words) — the skill must not be // used, so the mismatched bytes are never returned to the caller. func fetchSkill(indexURL, name string) ([]byte, error) { idx, err := fetchSkillsIndex(indexURL) if err != nil { return nil, err } var entry *skillEntry names := make([]string, 0, len(idx.Skills)) for i := range idx.Skills { names = append(names, idx.Skills[i].Name) if idx.Skills[i].Name == name { entry = &idx.Skills[i] } } if entry == nil { return nil, fmt.Errorf("no skill named %q — available: %s", name, strings.Join(names, ", ")) } abs, err := resolveSkillURL(indexURL, entry.URL) if err != nil { return nil, err } client := &http.Client{Timeout: skillsHTTPTimeout} res, err := client.Get(abs) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, fmt.Errorf("%s: status %d", abs, res.StatusCode) } raw, err := io.ReadAll(res.Body) if err != nil { return nil, err } sum := sha256.Sum256(raw) got := "sha256:" + hex.EncodeToString(sum[:]) if got != entry.Digest { return nil, fmt.Errorf("%s: digest mismatch (index says %s, fetched bytes are %s) — corrupted or tampered, refusing to use it", name, entry.Digest, got) } return raw, nil } const skillsUsage = `usage: carlos skills [] [--index ] Lists the agent skills the platform publishes at /.well-known/agent-skills/index.json (the Agent Skills Discovery draft), or prints one SKILL.md to stdout after verifying its sha256 digest against the index — "carlos skills getting-started > SKILL.md" hands an agent the recipe.` func runSkills(args []string) { fs := flag.NewFlagSet("skills", flag.ExitOnError) index := fs.String("index", skillsIndexURL, "agent-skills index URL") fs.Usage = flagUsage(fs, skillsUsage) fs.Parse(args) if fs.NArg() == 0 { idx, err := fetchSkillsIndex(*index) if err != nil { log.Fatalf("skills: %v", err) } if err := listSkills(stdout, *index, idx); err != nil { log.Fatalf("skills: %v", err) } return } raw, err := fetchSkill(*index, fs.Arg(0)) if err != nil { log.Fatalf("skills: %v", err) } if _, err := stdout.Write(raw); err != nil { log.Fatalf("skills: %v", err) } }