Files
stack/packages/mosaic/framework/tools/_scripts/mosaic-sync-skills.ps1
T
fargo 1556982dbc skills: single install path — canonical skills ship with the framework
Phase D2 of plan 2026-08-19: single package, single install, single command.

The framework installer already treats skills/** as a shipped, manifest-owned
framework subtree, so the folded skills now install into
$MOSAIC_HOME/skills with the rest of the framework — no second repository,
no separate sync step:

- mosaic-sync-skills (bash + powershell): the fetch machinery is gone (clone,
  pull, dirty-state migration, rsync from sources/agent-skills). The script
  now only links installed skills into runtime homes. --link-only is a compat
  no-op; --no-link exits having nothing to do.
- catalog.ts: the sources/agent-skills fallback is dead and removed.
- install.sh, launch.ts, defaults/README.md, README.md, skills/README.md:
  references to the second repo rewritten to describe the shipped path.

Verified: clean install into a fresh MOSAIC_HOME produces 102 skills with no
sources/ directory; the linker then links the selected skills into the four
runtime homes with no git involvement.
2026-08-19 14:39:28 -05:00

125 lines
4.4 KiB
PowerShell

# mosaic-sync-skills.ps1
# Links the INSTALLED canonical skills into agent runtime skill directories.
# Canonical skills ship inside the framework package and are installed into
# ~/.config/mosaic/skills by the framework installer — there is no second
# repository to clone. This script only maintains the runtime links.
# Uses directory junctions (no elevation required) with fallback to copies.
# PowerShell equivalent of mosaic-sync-skills (bash).
$ErrorActionPreference = "Stop"
param(
[switch]$LinkOnly,
[switch]$NoLink,
[switch]$Help
)
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$MosaicSkillsDir = Join-Path $MosaicHome "skills"
$MosaicLocalSkillsDir = Join-Path $MosaicHome "skills-local"
if ($Help) {
Write-Host @"
Usage: mosaic-sync-skills.ps1 [-LinkOnly] [-NoLink] [-Help]
Link installed skills from ~/.config/mosaic/{skills,skills-local} into runtime
skill directories using directory junctions. Canonical skills arrive with the
framework installer; this script never clones or pulls a second repository.
Options:
-LinkOnly Accepted for compatibility; linking is now the whole job
-NoLink Do nothing (kept for compatibility)
-Help Show help
"@
exit 0
}
if ($NoLink) {
Write-Host "[mosaic-skills] Nothing to do (-NoLink; canonical skills ship with the installer)"
exit 0
}
foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) {
if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
}
if (-not (Test-Path $MosaicSkillsDir)) {
Write-Host "[mosaic-skills] Canonical skills dir missing: $MosaicSkillsDir" -ForegroundColor Red
exit 1
}
if ($NoLink) {
Write-Host "[mosaic-skills] Canonical sync completed (link update skipped)"
exit 0
}
function Link-SkillIntoTarget {
param([string]$SkillPath, [string]$TargetDir)
$name = Split-Path $SkillPath -Leaf
if ($name.StartsWith(".")) { return }
$linkPath = Join-Path $TargetDir $name
# Recreate only Mosaic-owned junctions/symlinks. Foreign reparse points are
# runtime-owned and must never be clobbered by install/upgrade auto-sync.
$existing = Get-Item $linkPath -Force -ErrorAction SilentlyContinue
if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
$rawTarget = @($existing.Target)[0]
$candidate = if ([System.IO.Path]::IsPathRooted($rawTarget)) {
$rawTarget
}
else {
Join-Path (Split-Path $linkPath -Parent) $rawTarget
}
$resolvedTarget = [System.IO.Path]::GetFullPath($candidate)
$canonicalRoot = [System.IO.Path]::GetFullPath($MosaicSkillsDir).TrimEnd('\') + '\'
$localRoot = [System.IO.Path]::GetFullPath($MosaicLocalSkillsDir).TrimEnd('\') + '\'
$owned = $resolvedTarget.StartsWith($canonicalRoot, [System.StringComparison]::OrdinalIgnoreCase) -or
$resolvedTarget.StartsWith($localRoot, [System.StringComparison]::OrdinalIgnoreCase)
if (-not $owned) {
Write-Host "[mosaic-skills] Preserve foreign runtime symlink: $linkPath"
return
}
Remove-Item $linkPath -Force
}
elseif ($existing) {
Write-Host "[mosaic-skills] Preserve existing runtime-specific entry: $linkPath"
return
}
# Try junction first, fall back to copy
try {
New-Item -ItemType Junction -Path $linkPath -Target $SkillPath -ErrorAction Stop | Out-Null
}
catch {
Write-Host "[mosaic-skills] Junction failed for $name, falling back to copy"
Copy-Item $SkillPath $linkPath -Recurse -Force
}
}
$linkTargets = @(
(Join-Path $env:USERPROFILE ".claude\skills"),
(Join-Path $env:USERPROFILE ".codex\skills"),
(Join-Path $env:USERPROFILE ".config\opencode\skills")
)
foreach ($target in $linkTargets) {
if (-not (Test-Path $target)) { New-Item -ItemType Directory -Path $target -Force | Out-Null }
# Link canonical skills
Get-ChildItem $MosaicSkillsDir -Directory | ForEach-Object {
Link-SkillIntoTarget $_.FullName $target
}
# Link local skills
if (Test-Path $MosaicLocalSkillsDir) {
Get-ChildItem $MosaicLocalSkillsDir -Directory | ForEach-Object {
Link-SkillIntoTarget $_.FullName $target
}
}
Write-Host "[mosaic-skills] Linked skills into: $target"
}
Write-Host "[mosaic-skills] Complete"