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.
125 lines
4.4 KiB
PowerShell
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"
|