@@ -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).
123113func 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