@@ -3,6 +3,7 @@ package agentskill
33
44import (
55 "bufio"
6+ "encoding/json"
67 "fmt"
78 "io"
89 "net/http"
@@ -22,6 +23,9 @@ const (
2223 // GitHub raw content base URL template
2324 baseURLTemplate = "https://raw.githubusercontent.com/brevdev/brev-cli/%s/.agents/skills/brev-cli"
2425
26+ // GitHub API URL template for resolving branch to commit SHA
27+ githubAPICommitTemplate = "https://api.github.com/repos/brevdev/brev-cli/commits/%s"
28+
2529 // Default branch
2630 defaultBranch = "main"
2731
@@ -30,16 +34,61 @@ const (
3034
3135 // Skill name
3236 skillName = "brev-cli"
37+
38+ // Version file name
39+ versionFileName = ".version"
3340)
3441
3542// getBaseURL returns the base URL for downloading skill files
3643// Uses BREV_SKILL_BRANCH env var if set, otherwise defaults to main
3744func getBaseURL () string {
45+ return fmt .Sprintf (baseURLTemplate , getBranch ())
46+ }
47+
48+ // getBranch returns the branch used for downloading skill files
49+ func getBranch () string {
3850 branch := os .Getenv (branchEnvVar )
3951 if branch == "" {
4052 branch = defaultBranch
4153 }
42- return fmt .Sprintf (baseURLTemplate , branch )
54+ return branch
55+ }
56+
57+ // resolveCommitSHA uses the GitHub API to resolve a branch/ref to a commit SHA
58+ func resolveCommitSHA (client * http.Client , ref string ) (string , error ) {
59+ url := fmt .Sprintf (githubAPICommitTemplate , ref )
60+ req , err := http .NewRequest ("GET" , url , nil ) //nolint:noctx // simple API call
61+ if err != nil {
62+ return "" , breverrors .WrapAndTrace (err )
63+ }
64+ req .Header .Set ("Accept" , "application/vnd.github.v3+json" )
65+
66+ resp , err := client .Do (req ) //nolint:bodyclose // closed below
67+ if err != nil {
68+ return "" , breverrors .WrapAndTrace (err )
69+ }
70+ defer resp .Body .Close ()
71+
72+ if resp .StatusCode != http .StatusOK {
73+ return "" , breverrors .NewValidationError (fmt .Sprintf ("failed to resolve commit for %s: %s" , ref , resp .Status ))
74+ }
75+
76+ var result struct {
77+ SHA string `json:"sha"`
78+ }
79+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
80+ return "" , breverrors .WrapAndTrace (err )
81+ }
82+
83+ return result .SHA , nil
84+ }
85+
86+ // writeVersionFile writes a .version file to all skill directories
87+ func writeVersionFile (skillDirs []string , branch , commitSHA string ) {
88+ content := fmt .Sprintf ("branch=%s\n commit=%s\n " , branch , commitSHA )
89+ for _ , dir := range skillDirs {
90+ _ = os .WriteFile (filepath .Join (dir , versionFileName ), []byte (content ), 0o644 ) //nolint:gosec // not sensitive
91+ }
4392}
4493
4594// Files to download (relative to skill directory)
@@ -224,6 +273,15 @@ func InstallSkill(t *terminal.Terminal, homeDir string, quiet bool) error {
224273 }
225274 }
226275
276+ // Resolve commit SHA and write .version file
277+ branch := getBranch ()
278+ if commitSHA , err := resolveCommitSHA (client , branch ); err == nil {
279+ writeVersionFile (skillDirs , branch , commitSHA )
280+ if ! quiet {
281+ fmt .Printf (" %s .version (%s)\n " , t .Green ("✓" ), commitSHA [:12 ])
282+ }
283+ }
284+
227285 fmt .Println ()
228286
229287 if failed > 0 {
0 commit comments