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

Function installSkill

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

Source from the content-addressed store, hash-verified

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

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