Skip to content

feat: symlink to multiple vendors and shared configurable lib#74

Open
mikkezavala wants to merge 3 commits intoShpigford:mainfrom
mikkezavala:multi-vendor-symlink
Open

feat: symlink to multiple vendors and shared configurable lib#74
mikkezavala wants to merge 3 commits intoShpigford:mainfrom
mikkezavala:multi-vendor-symlink

Conversation

@mikkezavala
Copy link
Copy Markdown
Contributor

@mikkezavala mikkezavala commented Mar 29, 2026

Symlink multi-vendor support

Adds the ability to symlink a skill, agent, or rule into any installed vendor's global directory directly from the detail view. Includes a new shared library concept: a vendor-neutral directory you configure once and link from freely.

Note: Most of the vendors (Cursor, Augment, Windsurf, etc...) already mostly inherits plugins, skills, rules from ~/.claude. This gives you an extra layer if you want to disable in the that capability.

I did not used additional directories, that is meant more for "project" specific skills/rules and pinning on it will cause to deviate from it's original intent.

With the Shared lib, you can point to any directory, including iCloud Drive and just symlink. WARNING: different machines might need similar user home directory to work out of the box.

What changed

ToolSource.shared — new enum case backed by a user-configured path (sharedLibraryPath in UserDefaults). Derives skills/, agents/, and rules/ subdirectories automatically. isInstalled returns true only when the configured path exists on disk.

SymlinkTarget (SwiftData model): tracks every active symlink as a (resolvedPath, toolSource) pair. Stores ToolSource and ItemKind as raw strings for #Predicate compatibility; exposes typed toolSourceEnum / itemKind accessors. isBroken is set by reconcile when the linked path no longer resolves.

SymlinkService: @MainActor singleton. link creates the symlink and inserts the record (idempotent only when the existing symlink points to the same source; errors if it points elsewhere). unlink uses attributesOfItem to distinguish a deleted file from a non-symlink, cleaning up the record silently in the former case. targets and isLinked log fetch errors instead of swallowing them. reconcile is called on launch and after every scan.

VendorLinkingPanel: collapsible panel in the skill detail view (hidden for plugin and remote skills). Shows only installed vendors that support the skill's kind. Excludes the resolved physical origin and any symlink host unless an active record already exists (which allows unlinking). VendorLinkRow takes initiallyLinked at init time via State(initialValue:) so onChange only fires on real user interaction, not on onAppear. linkedPath is cached in @State and refreshed after each operation. PathCrumb renders a single-line ~/src → ~/dst display.

LibrarySettingsView : replaces the old SOT directory picker with a shared library path field + Browse button. Tilde expansion is normalized on both onSubmit and after Browse via a shared normalize(_:) helper. includeHiddenFiles is persisted via @AppStorage.

ToolSource.globalDirs(for:): new extension method that returns the correct global paths for a given ItemKind, eliminating the duplicated itemKind switch that previously existed in both VendorLinkingPanel and SymlinkService.

ChopsSettings: Cleaned SOT directory properties (sotDir, sotSkillsDir, etc.) that were superseded by ToolSource.shared.

This resolve most of: #13, #16, #17, #66 and maybe #68

Screenshot 2026-03-29 at 9 00 02 AM

@mikkezavala mikkezavala marked this pull request as draft March 29, 2026 16:46
@mikkezavala
Copy link
Copy Markdown
Contributor Author

I have a small fix, in non-existent dirs....

@mikkezavala mikkezavala force-pushed the multi-vendor-symlink branch from 7b10208 to 6334bed Compare March 29, 2026 21:43
@mikkezavala mikkezavala marked this pull request as ready for review March 29, 2026 23:55
@Shpigford
Copy link
Copy Markdown
Owner

Haven't reviewed yet but just saying the thing out loud: I'm super nervous about this. It's 100% needed (we all know how much of a dumpster fire skills files are). But just clarifying I want to be VERY careful as we approach this.

@mikkezavala
Copy link
Copy Markdown
Contributor Author

@Shpigford I totally understand. I really just wanted to have a head first to this, but very open for feedback and approach bounce. if feels unstructured, we can benefit from an spec, to benefit the most as possible.

@Shpigford
Copy link
Copy Markdown
Owner

Talking this out. How would this practically work with daily usage? Say a user creates a new skill from...anywhere (Claude CLI, npx skills add, Chops directly)...do we do something to immediately move that to /.chops?

Having a hard time picturing what the workflow would be.

@mikkezavala mikkezavala force-pushed the multi-vendor-symlink branch from bf21797 to a8217d6 Compare March 31, 2026 16:29
@mikkezavala
Copy link
Copy Markdown
Contributor Author

@Shpigford Not really, global is a new concept and optional one:

The way I use it is I do have it in a global directory (~/.chops as example) and I store them there and I decide where to link to.

If you create one in ~/.claude it will stay there even if you have the "shared" one but you can also link it to ~/.cursor, the shared is just an extra layer for organization in reality does not affect the core capability.

You can create in shared and symlink it to any other vendor if you want to, but you might not want to.

@mikkezavala mikkezavala force-pushed the multi-vendor-symlink branch 4 times, most recently from a3bddb2 to c1e2a94 Compare April 1, 2026 16:21
@mikkezavala mikkezavala force-pushed the multi-vendor-symlink branch from c1e2a94 to 3288329 Compare April 6, 2026 15:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants