MCPcopy Create free account
hub / github.com/cli/cli / installSkill

Function installSkill

internal/skills/installer/installer.go:251–309  ·  view source on GitHub ↗
(opts *Options, skill discovery.Skill, baseDir string)

Source from the content-addressed store, hash-verified

249}
250
251func installSkill(opts *Options, skill discovery.Skill, baseDir string) error {
252 // Use skill.Name (not InstallName) for a flat directory layout.
253 skillDir := filepath.Join(baseDir, skill.Name)
254 if err := os.MkdirAll(skillDir, 0o755); err != nil {
255 return fmt.Errorf("could not create directory %s: %w", skillDir, err)
256 }
257
258 files, err := discovery.DiscoverSkillFiles(opts.Client, opts.Host, opts.Owner, opts.Repo, skill.TreeSHA, skill.Path)
259 if err != nil {
260 return fmt.Errorf("could not list skill files: %w", err)
261 }
262
263 safeSkillDir, err := safepaths.ParseAbsolute(skillDir)
264 if err != nil {
265 return fmt.Errorf("could not resolve skill directory path: %w", err)
266 }
267
268 for _, file := range files {
269 fetchedContent, err := discovery.FetchBlob(opts.Client, opts.Host, opts.Owner, opts.Repo, file.SHA)
270 if err != nil {
271 return fmt.Errorf("could not fetch %s: %w", file.Path, err)
272 }
273
274 // Install path: the blob is written to disk verbatim, so the raw bytes
275 // must be preserved.
276 content := fetchedContent.Raw()
277
278 relPath := strings.TrimPrefix(file.Path, skill.Path+"/")
279
280 safeDest, err := safeSkillDir.Join(relPath)
281 if err != nil {
282 var traversalErr safepaths.PathTraversalError
283 if errors.As(err, &traversalErr) {
284 return fmt.Errorf("blocked path traversal in %q", relPath)
285 }
286 return fmt.Errorf("could not resolve destination path: %w", err)
287 }
288 destPath := safeDest.String()
289
290 if dir := filepath.Dir(destPath); dir != skillDir {
291 if err := os.MkdirAll(dir, 0o755); err != nil {
292 return fmt.Errorf("could not create directory: %w", err)
293 }
294 }
295
296 if filepath.Base(relPath) == "SKILL.md" {
297 content, err = frontmatter.InjectGitHubMetadata(content, opts.Host, opts.Owner, opts.Repo, opts.Ref, skill.TreeSHA, opts.PinnedRef, skill.Path)
298 if err != nil {
299 return fmt.Errorf("could not inject metadata: %w", err)
300 }
301 }
302
303 if err := os.WriteFile(destPath, []byte(content), 0o644); err != nil {
304 return fmt.Errorf("could not write %s: %w", destPath, err)
305 }
306 }
307
308 return nil

Callers 2

InstallFunction · 0.85
TestInstallSkillFunction · 0.85

Calls 9

DiscoverSkillFilesFunction · 0.92
ParseAbsoluteFunction · 0.92
FetchBlobFunction · 0.92
InjectGitHubMetadataFunction · 0.92
JoinMethod · 0.80
RawMethod · 0.80
BaseMethod · 0.80
ErrorfMethod · 0.65
StringMethod · 0.65

Tested by 1

TestInstallSkillFunction · 0.68