# Builds dist\cursor\ from skills\: flat skill copies, mcp snippets, one plugin per skill. # Usage: scripts\build-cursor.ps1 [-Name ] param([string]$Name) . (Join-Path $PSScriptRoot 'lib.ps1') . (Join-Path $PSScriptRoot 'cursor-deeplink.ps1') $CursorDist = Join-Path $DistDir 'cursor' $ServerKeys = @{ 'wordcraft-mcp' = 'wordcraft' 'gridcraft-mcp' = 'gridcraft' 'pdfcraft-mcp' = 'pdfcraft' 'figcraft-mcp' = 'figcraft' 'vectorcraft-mcp' = 'vectorcraft' } function Get-McpServerKey([string]$SkillName) { if ($ServerKeys.ContainsKey($SkillName)) { return $ServerKeys[$SkillName] } # fallback: strip trailing -mcp if ($SkillName -match '^(.+)-mcp$') { return $Matches[1] } return $SkillName } function Convert-ClaudeArgsToCursor([object[]]$CliArgs) { $out = New-Object System.Collections.Generic.List[string] foreach ($a in $CliArgs) { $s = [string]$a if ($s -eq '${user_config.author}') { $out.Add('${AUTHOR}'); continue } if ($s -eq '${user_config.read_root}') { $out.Add('${READ_ROOT}'); continue } if ($s -eq '${user_config.write_root}') { $out.Add('${WRITE_ROOT}'); continue } $out.Add($s) } return , $out.ToArray() } function Convert-ArgsToUserHomeDefaults([object[]]$CliArgs) { $out = New-Object System.Collections.Generic.List[string] foreach ($a in $CliArgs) { $s = [string]$a if ($s -eq '${user_config.author}' -or $s -eq '${AUTHOR}') { $out.Add('Claude'); continue } if ($s -eq '${user_config.read_root}' -or $s -eq '${READ_ROOT}') { $out.Add('${userHome}/Documents'); continue } if ($s -eq '${user_config.write_root}' -or $s -eq '${WRITE_ROOT}') { $out.Add('${userHome}/Documents/Claude'); continue } $out.Add($s) } return , $out.ToArray() } function Build-OneSkill([string]$N) { $dir = Join-Path $SkillsDir $N if (-not (Test-Path $dir -PathType Container)) { Write-Host "skills/$N fehlt." return $false } $ver = Get-SkillVersion $dir if (-not $ver) { Write-Host "CHANGELOG von $N hat keine Version." return $false } $skillPath = Join-Path $dir 'SKILL.md' $fm = Get-Frontmatter $skillPath if ($null -eq $fm) { Write-Host "${N}: kein Frontmatter." return $false } $fmName = Get-FmValue $fm 'name' $desc = Get-FmValue $fm 'description' if ($fmName -cne $N) { Write-Host "${N}: Frontmatter name '$fmName' != Ordner." return $false } if (-not $desc -or $desc.Length -gt 1024) { Write-Host "${N}: description fehlt oder > 1024." return $false } # Flat skill copy $flatDir = Join-Path (Join-Path $CursorDist 'skills') $N New-Item -ItemType Directory -Force -Path $flatDir | Out-Null Copy-Item -LiteralPath $skillPath -Destination (Join-Path $flatDir 'SKILL.md') -Force $extManPath = Join-Path $dir 'extension\manifest.json' $hasExt = Test-Path $extManPath $pluginRoot = Join-Path (Join-Path $CursorDist 'plugins') $N if (Test-Path $pluginRoot) { Remove-Item $pluginRoot -Recurse -Force } New-Item -ItemType Directory -Force -Path (Join-Path $pluginRoot '.cursor-plugin') | Out-Null $pluginSkillDir = Join-Path (Join-Path $pluginRoot 'skills') $N New-Item -ItemType Directory -Force -Path $pluginSkillDir | Out-Null Copy-Item -LiteralPath $skillPath -Destination (Join-Path $pluginSkillDir 'SKILL.md') -Force $displayDesc = $desc $authorName = 'Dominic Gundel' $pluginVars = $null $mcpForPlugin = $null $mcpStandalone = $null $serverKey = Get-McpServerKey $N if ($hasExt) { $man = (Read-Text $extManPath) | ConvertFrom-Json if ($man.version -cne $ver) { Write-Host "${N}: manifest version '$($man.version)' != CHANGELOG '$ver'." return $false } $dn = [string]$man.display_name if ($dn -match ' for Claude$') { $displayDesc = ($dn -replace ' for Claude$', ' for Cursor') + '. ' + $desc } elseif ($man.description) { $displayDesc = [string]$man.description } if ($man.author -and $man.author.name) { $authorName = [string]$man.author.name } $cmd = [string]$man.server.mcp_config.command $argsRaw = @($man.server.mcp_config.args) $argsPlugin = Convert-ClaudeArgsToCursor $argsRaw $argsHome = Convert-ArgsToUserHomeDefaults $argsRaw $transportPlugin = @{ command = $cmd; args = @($argsPlugin) } $transportHome = @{ command = $cmd; args = @($argsHome) } $mcpForPlugin = @{ mcpServers = @{ $serverKey = $transportPlugin } } $mcpStandalone = @{ mcpServers = @{ $serverKey = $transportHome } } if ($man.user_config) { $props = @{} $required = @() if ($man.user_config.read_root) { $props['READ_ROOT'] = @{ type = 'string' title = 'Lese-Ordner' description = 'Weit fassen (Standard: Dokumente).' default = '${userHome}/Documents' } $required += 'READ_ROOT' } if ($man.user_config.write_root) { $props['WRITE_ROOT'] = @{ type = 'string' title = 'Schreib-Ordner' description = 'Eng halten (Standard: Dokumente/Claude).' default = '${userHome}/Documents/Claude' } $required += 'WRITE_ROOT' } if ($man.user_config.author) { $props['AUTHOR'] = @{ type = 'string' title = 'Autorname' description = 'Name fuer nachverfolgte Aenderungen.' default = 'Claude' } } if ($props.Count -gt 0) { $pluginVars = @{ type = 'object' properties = $props } if ($required.Count -gt 0) { $pluginVars['required'] = $required } } } $mcpDir = Join-Path $CursorDist 'mcp' New-Item -ItemType Directory -Force -Path $mcpDir | Out-Null $snippetPath = Join-Path $mcpDir "$N.json" Write-Text $snippetPath (($mcpStandalone | ConvertTo-Json -Depth 20) + "`n") } $pluginJson = @{ name = $N version = $ver description = $displayDesc author = @{ name = $authorName } license = 'MIT' homepage = 'https://git.gundel.dev/dg/claude-skills' repository = 'https://git.gundel.dev/dg/claude-skills.git' } if ($pluginVars) { $pluginJson['variables'] = $pluginVars } Write-Text (Join-Path $pluginRoot '.cursor-plugin\plugin.json') (($pluginJson | ConvertTo-Json -Depth 20) + "`n") if ($mcpForPlugin) { Write-Text (Join-Path $pluginRoot 'mcp.json') (($mcpForPlugin | ConvertTo-Json -Depth 20) + "`n") } # Validate JSON round-trip $null = (Read-Text (Join-Path $pluginRoot '.cursor-plugin\plugin.json')) | ConvertFrom-Json if ($mcpForPlugin) { $null = (Read-Text (Join-Path $pluginRoot 'mcp.json')) | ConvertFrom-Json } Write-Host "OK cursor plugin $N $ver" return $true } if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) } New-Item -ItemType Directory -Force -Path $CursorDist | Out-Null $ok = $true foreach ($n in $names) { if (-not (Build-OneSkill $n)) { $ok = $false } } if (-not $ok) { exit 1 } exit 0