Skip to content

Commit 211f000

Browse files
committed
refactor: extract skills directly into target dir, no symlinks
1 parent 079e385 commit 211f000

File tree

1 file changed

+20
-61
lines changed

1 file changed

+20
-61
lines changed

cmd/setup.go

Lines changed: 20 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -109,65 +109,8 @@ func claudeMDSnippet() string {
109109
"Follow the data: curious, not confirmatory.\n"
110110
}
111111

112-
// skillsDir returns the path where felt extracts its bundled skills.
113-
func skillsDir() (string, error) {
114-
home, err := os.UserHomeDir()
115-
if err != nil {
116-
return "", err
117-
}
118-
return filepath.Join(home, ".local", "share", "felt", "skills"), nil
119-
}
120-
121-
// installSkills extracts bundled skills to ~/.local/share/felt/skills and
122-
// symlinks each skill directory into targetDir (e.g. ~/.claude/skills).
112+
// installSkills extracts bundled skills directly into targetDir (e.g. ~/.claude/skills).
123113
func installSkills(targetDir string) error {
124-
src, err := skillsDir()
125-
if err != nil {
126-
return err
127-
}
128-
129-
// Extract embedded skills
130-
if err := extractEmbeddedSkills(src); err != nil {
131-
return fmt.Errorf("extracting skills: %w", err)
132-
}
133-
134-
// Ensure target directory exists
135-
if err := os.MkdirAll(targetDir, 0755); err != nil {
136-
return fmt.Errorf("creating skills directory: %w", err)
137-
}
138-
139-
// Symlink each skill directory
140-
entries, err := os.ReadDir(src)
141-
if err != nil {
142-
return err
143-
}
144-
145-
for _, e := range entries {
146-
if !e.IsDir() {
147-
continue
148-
}
149-
linkPath := filepath.Join(targetDir, e.Name())
150-
target := filepath.Join(src, e.Name())
151-
152-
if existing, err := os.Lstat(linkPath); err == nil {
153-
if existing.Mode()&os.ModeSymlink != 0 {
154-
fmt.Printf("· %s already linked\n", e.Name())
155-
continue
156-
}
157-
fmt.Printf("· %s exists (not a symlink, skipping)\n", e.Name())
158-
continue
159-
}
160-
161-
if err := os.Symlink(target, linkPath); err != nil {
162-
return fmt.Errorf("symlinking %s: %w", e.Name(), err)
163-
}
164-
fmt.Printf("✓ Linked skill: %s\n", e.Name())
165-
}
166-
return nil
167-
}
168-
169-
// extractEmbeddedSkills writes the embedded skills/ tree to dest.
170-
func extractEmbeddedSkills(dest string) error {
171114
return fs.WalkDir(embeddedSkills, "skills", func(path string, d fs.DirEntry, err error) error {
172115
if err != nil {
173116
return err
@@ -177,12 +120,20 @@ func extractEmbeddedSkills(dest string) error {
177120
if err != nil {
178121
return err
179122
}
180-
target := filepath.Join(dest, rel)
123+
if rel == "." {
124+
return nil
125+
}
126+
target := filepath.Join(targetDir, rel)
181127

182128
if d.IsDir() {
183129
return os.MkdirAll(target, 0755)
184130
}
185131

132+
// Skip if already exists
133+
if _, err := os.Stat(target); err == nil {
134+
return nil
135+
}
136+
186137
data, err := embeddedSkills.ReadFile(path)
187138
if err != nil {
188139
return err
@@ -192,13 +143,21 @@ func extractEmbeddedSkills(dest string) error {
192143
return err
193144
}
194145

195-
// Preserve executable bit for scripts
196146
mode := fs.FileMode(0644)
197147
if strings.Contains(path, "/scripts/") {
198148
mode = 0755
199149
}
200150

201-
return os.WriteFile(target, data, mode)
151+
if err := os.WriteFile(target, data, mode); err != nil {
152+
return err
153+
}
154+
155+
// Print once per top-level skill directory
156+
parts := strings.SplitN(rel, string(filepath.Separator), 2)
157+
if len(parts) == 2 && parts[1] == "SKILL.md" {
158+
fmt.Printf("✓ Installed skill: %s\n", parts[0])
159+
}
160+
return nil
202161
})
203162
}
204163

0 commit comments

Comments
 (0)