// // Copyright 2026 The InfiniFlow Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package source import ( "fmt" "os" "path/filepath" "strings" "gopkg.in/yaml.v3" ) // LocalSource handles local filesystem skills type LocalSource struct{} // NewLocalSource creates a new local source adapter func NewLocalSource() *LocalSource { return &LocalSource{} } // SourceID returns the source identifier func (s *LocalSource) SourceID() string { return "local" } // TrustLevel returns the trust level for local sources func (s *LocalSource) TrustLevel(identifier string) string { return "community" // Local skills default to community trust level } // Fetch retrieves a skill from the local filesystem func (s *LocalSource) Fetch(identifier string) (*SkillBundle, error) { // Validate path exists info, err := os.Stat(identifier) if err != nil { return nil, fmt.Errorf("cannot access path %s: %w", identifier, err) } if !info.IsDir() { return nil, fmt.Errorf("%s is not a directory", identifier) } // Read SKILL.md skillMdPath := filepath.Join(identifier, "SKILL.md") content, err := os.ReadFile(skillMdPath) if err != nil { return nil, fmt.Errorf("SKILL.md not found in %s: %w", identifier, err) } // Parse frontmatter meta, err := parseSkillFrontmatter(string(content)) if err != nil { return nil, fmt.Errorf("invalid SKILL.md frontmatter in %s: %w", identifier, err) } skillName := meta.Name if skillName == "" { skillName = filepath.Base(identifier) } // Collect all files files := make(map[string][]byte) ignorePatterns := []string{ ".git/", ".svn/", ".hg/", "node_modules/", "__MACOSX/", ".DS_Store", "._*", "*.log", "*.tmp", "*.temp", "*.swp", "*.swo", "*~", ".env", ".env.*", ".vscode/", ".idea/", "Thumbs.db", "desktop.ini", } err = filepath.Walk(identifier, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } // Skip non-regular files (symlinks, devices, pipes, etc.) if !info.Mode().IsRegular() { return nil } relPath, err := filepath.Rel(identifier, path) if err != nil { return err } // Check ignore patterns for _, pattern := range ignorePatterns { if matched, _ := filepath.Match(pattern, relPath); matched { return nil } if strings.Contains(relPath, pattern) { return nil } } // Only include text files based on extension if !isTextFile(path) { return nil } data, err := os.ReadFile(path) if err != nil { return err } files[relPath] = data return nil }) if err != nil { return nil, err } return &SkillBundle{ Name: skillName, Files: files, Source: "local", Identifier: identifier, TrustLevel: s.TrustLevel(identifier), Metadata: meta, }, nil } // Inspect retrieves metadata without reading all files func (s *LocalSource) Inspect(identifier string) (*SkillMetadata, error) { info, err := os.Stat(identifier) if err != nil { return nil, err } if !info.IsDir() { return nil, fmt.Errorf("not a directory") } skillMdPath := filepath.Join(identifier, "SKILL.md") content, err := os.ReadFile(skillMdPath) if err != nil { return nil, err } meta, err := parseSkillFrontmatter(string(content)) if err != nil { return nil, fmt.Errorf("invalid SKILL.md frontmatter in %s: %w", identifier, err) } if meta.Name == "" { meta.Name = filepath.Base(identifier) } return meta, nil } // parseSkillFrontmatter extracts YAML frontmatter from SKILL.md content // Returns an error if frontmatter delimiters are missing or YAML is invalid func parseSkillFrontmatter(content string) (*SkillMetadata, error) { meta := &SkillMetadata{} // Look for YAML frontmatter content = strings.TrimSpace(content) if !strings.HasPrefix(content, "---") { return nil, fmt.Errorf("missing opening frontmatter delimiter '---'") } // Find end of frontmatter endIdx := strings.Index(content[3:], "---") if endIdx == -1 { return nil, fmt.Errorf("missing closing frontmatter delimiter '---'") } frontmatter := content[3 : endIdx+3] if err := yaml.Unmarshal([]byte(frontmatter), meta); err != nil { return nil, fmt.Errorf("invalid YAML frontmatter: %w", err) } return meta, nil } // isTextFile checks if a file is a text file based on extension func isTextFile(filename string) bool { ext := strings.ToLower(filepath.Ext(filename)) if ext != "" && ext[0] == '.' { ext = ext[1:] } textExts := map[string]bool{ "md": true, "mdx": true, "txt": true, "json": true, "json5": true, "yaml": true, "yml": true, "toml": true, "js": true, "cjs": true, "mjs": true, "ts": true, "tsx": true, "jsx": true, "py": true, "sh": true, "rb": true, "go": true, "rs": true, "swift": true, "kt": true, "java": true, "cs": true, "cpp": true, "c": true, "h": true, "hpp": true, "sql": true, "csv": true, "ini": true, "cfg": true, "env": true, "xml": true, "html": true, "css": true, "scss": true, "sass": true, "svg": true, } return textExts[ext] }