MCPcopy
hub / github.com/cli/cli / installSkill

Function installSkill

internal/skills/installer/installer.go:251–305  ·  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 content, 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 relPath := strings.TrimPrefix(file.Path, skill.Path+"/")
275
276 safeDest, err := safeSkillDir.Join(relPath)
277 if err != nil {
278 var traversalErr safepaths.PathTraversalError
279 if errors.As(err, &traversalErr) {
280 return fmt.Errorf("blocked path traversal in %q", relPath)
281 }
282 return fmt.Errorf("could not resolve destination path: %w", err)
283 }
284 destPath := safeDest.String()
285
286 if dir := filepath.Dir(destPath); dir != skillDir {
287 if err := os.MkdirAll(dir, 0o755); err != nil {
288 return fmt.Errorf("could not create directory: %w", err)
289 }
290 }
291
292 if filepath.Base(relPath) == "SKILL.md" {
293 content, err = frontmatter.InjectGitHubMetadata(content, opts.Host, opts.Owner, opts.Repo, opts.Ref, skill.TreeSHA, opts.PinnedRef, skill.Path)
294 if err != nil {
295 return fmt.Errorf("could not inject metadata: %w", err)
296 }
297 }
298
299 if err := os.WriteFile(destPath, []byte(content), 0o644); err != nil {
300 return fmt.Errorf("could not write %s: %w", destPath, err)
301 }
302 }
303
304 return nil
305}
306
307// ResolveGitRoot returns the git repository root using the provided client,
308// falling back to the current working directory on error.

Callers 2

InstallFunction · 0.85
TestInstallSkillFunction · 0.85

Calls 8

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

Tested by 1

TestInstallSkillFunction · 0.68