Skip to content

Add --global flag for system-wide skill installation#76

Merged
albertodebortoli merged 6 commits intoLucaTools:mainfrom
julioyg:feature/global-skill-installation
Apr 27, 2026
Merged

Add --global flag for system-wide skill installation#76
albertodebortoli merged 6 commits intoLucaTools:mainfrom
julioyg:feature/global-skill-installation

Conversation

@julioyg
Copy link
Copy Markdown
Contributor

@julioyg julioyg commented Apr 22, 2026

Summary

Some skills are useful across all projects and shouldn't need a per-project Lucafile to install. This PR adds a --global flag to install, uninstall, and installed so users can manage skills at the system level.

New commands

# Install all skills from your global Lucafile
luca install --global

# Install a specific skill globally
luca install org/repo --skill my-skill --global

# Use a custom global Lucafile
luca install --global --spec ~/my-global-skills.yaml

# Uninstall a globally-installed skill
luca uninstall my-skill --global

# List globally-installed skills
luca installed --skills --global

Path conventions

Resource Path
Global Lucafile ~/.config/luca/Lucafile (XDG-style)
Global skill cache ~/.luca/skills/ (consistent with existing ~/.luca/tools/)
Global agent dirs Per agent — e.g. ~/.claude/skills/, ~/.config/opencode/skills/

Behaviour notes

  • Re-running luca install --global fetches the latest version of all skills — no separate update command needed
  • Project-local skills take precedence over global skills when agent tools resolve paths
  • repos: aliases in the global Lucafile work — the same spec parser is used
  • --global is skills-only; tools already install globally to ~/.luca/tools/
  • --global --only-tools is rejected with a clear validation error
  • .gitignore management and post-checkout hook installation are skipped in global mode (no project repo involved)

Introduces global skill installation support so skills useful across
all projects do not require a per-project Lucafile.

New commands:
- luca install --global (installs from ~/.config/luca/Lucafile)
- luca install <repo> --skill <name> --global (install single skill)
- luca install --global --spec <path> (custom global Lucafile)
- luca uninstall <name> --global
- luca installed --skills --global

Path conventions:
- Global Lucafile: ~/.config/luca/Lucafile
- Global skill cache: ~/.luca/skills/
- Global agent dirs: per agent (e.g. ~/.claude/skills/)

Implementation:
- Added globalSkillsCacheFolder to FileManaging and relevant sub-protocols
- Added AgentInfo.resolvedGlobalSkillsPath(homeDirectory:) for safe tilde expansion
- SkillSymLinker, SkillUninstaller, InstalledSkillsLister support isGlobal parameter
- Skips .gitignore management and git hook installation in global mode
- --global and --only-tools are mutually exclusive

Tests: 11 new unit tests covering AgentInfo expansion, global list/uninstall/symlink paths
@julioyg julioyg marked this pull request as draft April 22, 2026 14:19
fileExists(atPath:) follows symlinks and returns false once the
target is deleted, so symlinks were silently skipped.

Fix: remove agent symlinks before deleting the cache folder,
while the symlink targets still exist.
@julioyg julioyg marked this pull request as ready for review April 22, 2026 14:36
Comment thread Package.swift Outdated
let package = Package(
name: "Luca",
platforms: [.macOS(.v13)],
platforms: [.macOS(.v14)],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no problem with this but it is necessary for these changes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you're right

Comment thread Package.swift Outdated
.package(url: "https://github.com/tuist/Noora", exact: "0.56.0"),
.package(url: "https://github.com/jpsim/Yams.git", exact: "6.1.0"),
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0")
.package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed

Comment thread Package.resolved Outdated
@@ -1,5 +1,5 @@
{
"originHash" : "a31213e24adf1d343da5ebac02f62e47ad7592b105c37b1affd30cd2c4f9c737",
"originHash" : "4a3ad8fae5a41167e7c6a01e509b954827de48429c49753a85921d47a45c4914",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unclear why this changed since nothing else changed in the file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

think it changed because of the update on package.swift file, (both reverted)

@albertodebortoli
Copy link
Copy Markdown
Member

From Claude Code:

---
Code Review Summary

Scope: branch feature/global-skill-installation vs main (2 commits)
Files Changed: 23
Lines Added/Removed: +451 / -47
Verdict: Needs fixes (3 issues before merging, 1 optional)

---
Positive Notes

- The uninstall ordering fix (b9f47a9) is correct and well-commented — symlinks are now cleaned up before the cache folder is deleted, resolving the
dangling-symlink bug.
- resolvedGlobalSkillsPath handles ~/…, bare ~, and absolute paths cleanly.
- .gitignore and git-hook logic is correctly skipped for the global path.
- InstalledSkillsLister, SkillSymLinker, and SkillUninstaller all have solid unit tests for the new global paths.
- Protocol additions (globalSkillsCacheFolder, homeDirectoryForCurrentUser) are minimal and targeted.

---
Issue Rating Table

┌─────┬─────────────────────────────────────────────────────────────┬─────────┬────────┬──────────────┬───────────┬─────────┬─────────────────────┐
│  #  │                           Finding                           │ Urgency │ Risk:  │ Risk: No Fix │    ROI    │ Blast   │     Fix Effort      │
│     │                                                             │         │  Fix   │              │           │ Radius  │                     │
├─────┼─────────────────────────────────────────────────────────────┼─────────┼────────┼──────────────┼───────────┼─────────┼─────────────────────┤
│     │ Installer.swift:244,261,263,287 — Four user-facing messages │         │        │ 🟡 High      │ 🟠        │ ⚪ 1    │                     │
│ 1   │  say "for the current project" even when isGlobal: true     │ 🟡 High │ ⚪ Low │ (confusing   │ Excellent │ file    │ Trivial             │
│     │                                                             │         │        │ UX)          │           │         │                     │
├─────┼─────────────────────────────────────────────────────────────┼─────────┼────────┼──────────────┼───────────┼─────────┼─────────────────────┤
│     │ InstallCommand.swift:98 — Help text reads "Install skills   │         │        │              │ 🟠        │ ⚪ 1    │                     │
│ 2   │ globally to ~/.config/luca/Lucafile" — that's the source    │ 🟡 High │ ⚪ Low │ 🟢 Medium    │ Excellent │ file    │ Trivial             │
│     │ spec file, not a destination                                │         │        │              │           │         │                     │
├─────┼─────────────────────────────────────────────────────────────┼─────────┼────────┼──────────────┼───────────┼─────────┼─────────────────────┤
│     │ InstallCommandTests.swift — Entire file is stub TODOs;      │ 🟢      │        │              │           │ ⚪ 1    │ Trivial (delete     │
│ 3   │ ships with zero active tests                                │ Medium  │ ⚪ Low │ 🟢 Medium    │ 🟢 Good   │ file    │ file or move TODO   │
│     │                                                             │         │        │              │           │         │ to PR description)  │
├─────┼─────────────────────────────────────────────────────────────┼─────────┼────────┼──────────────┼───────────┼─────────┼─────────────────────┤
│     │ Installer.swift — No tests for the isGlobal: true install   │ 🟢      │ 🟢     │              │           │ ⚪ 1    │                     │
│ 4   │ path (SkillSet routing, cache folder selection). Individual │ Medium  │ Medium │ 🟢 Medium    │ 🟢 Good   │ file    │ Medium              │
│     │  components are tested, but the orchestration layer is not. │         │        │              │           │         │                     │
└─────┴─────────────────────────────────────────────────────────────┴─────────┴────────┴──────────────┴───────────┴─────────┴─────────────────────┘

---
Detail on Each Issue

Issue #1 — Wrong success messages when global (Installer.swift)

Four strings need an isGlobal branch:

// line 244
"\(.info("🧠 Installing skills for the current project."))"
// → when isGlobal: "🧠 Installing skills globally."

// line 261
"\(.success("🚀 Skills have been installed for the current project."))"
// → when isGlobal: "🚀 Skills have been installed globally."

// line 263
"\(.muted("🫥 No skills have been installed for the current project."))"
// → when isGlobal: "🫥 No skills found to install globally."

// line 287 (in install(_:agents:useNpx:isGlobal:resolvedAgents:))
"\(.primary("🙌 Skills from \(skillSet.repository) installed for the current project."))"
// → when isGlobal: "globally." instead of "for the current project."

Issue #2 — Misleading --global help text (InstallCommand.swift:98–107)

Current: "Install skills globally to ~/.config/luca/Lucafile and ~/.luca/skills/."

~/.config/luca/Lucafile is read as the source spec, not written to. Suggested:

"Install skills globally. Uses ~/.config/luca/Lucafile as the spec (unless --spec is provided) and caches skills in ~/.luca/skills/."

Issue #3 — Empty test file

Tests/Core/InstallCommandTests.swift has no @Test functions — only TODO comment blocks. The architectural constraint is real and well-explained, but a
file with only comments adds noise and zero coverage. The TODO context is better placed in the PR description. Recommend deleting the file unless you
want to leave it as a future reminder.

Issue #4 — Missing Installer global path tests

InstallerTests.swift has no test asserting that when isGlobal: true the skill folder is globalSkillsCacheFolder rather than skillsCacheFolder, or that
setSymLink is called with isGlobal: true. The SkillSymLinkerMock already records isGlobal implicitly (via the protocol signature), so a test could
assert skillSymLinkerMock.lastIsGlobal == true. Worth adding before merging.

---
Verdict: Address issues #1 and #2 before merging (wrong messages and misleading help text). Issue #3 is a clean-up call (recommend deleting the stub
file). Issue #4 is a coverage gap worth closing but not blocking.

julioyg and others added 4 commits April 27, 2026 12:33
…l mode, and cleanup

- Fix @Flag help text: clarify ~/.config/luca/Lucafile is the source spec, not a destination
- Fix confirmation messages in Installer to say "globally" when isGlobal is true
- Auto-promote installMode to .skillsOnly when --global is set, preventing unintended tool installation
- Promote try? to try when creating ~/.config/luca/ so permission errors surface immediately
- Revert Package.swift platform from .v14 back to .v13 (no macOS 14 API is used)
- Remove trailing comma from Package.swift dependency list
- Delete InstallCommandTests.swift (stub-only file with zero active tests)
- Add lastIsGlobal tracking to SkillSymLinkerMock
- Add InstallerTests coverage verifying global installs route to globalSkillsCacheFolder

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….v13

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@albertodebortoli albertodebortoli merged commit 2409f4a into LucaTools:main Apr 27, 2026
3 checks passed
@albertodebortoli albertodebortoli added the feature New feature or enhancement label Apr 27, 2026
@albertodebortoli albertodebortoli added this to the 0.18.0 milestone Apr 27, 2026
@julioyg julioyg deleted the feature/global-skill-installation branch April 28, 2026 07:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants