168 lines
5.4 KiB
PowerShell
168 lines
5.4 KiB
PowerShell
param(
|
|
[string]$RepoSlug = $(if ($env:SECURITYAUDIT_REPO) { $env:SECURITYAUDIT_REPO } else { "andydixon/securityaudit-skill" }),
|
|
[string]$Ref = $(if ($env:SECURITYAUDIT_REF) { $env:SECURITYAUDIT_REF } else { "main" }),
|
|
[Alias("SkillPath")]
|
|
[string]$Skills = $(if ($env:SECURITYAUDIT_SKILLS) { $env:SECURITYAUDIT_SKILLS } elseif ($env:SECURITYAUDIT_SKILL_PATH) { $env:SECURITYAUDIT_SKILL_PATH } else { "securityaudit aitm debullshit summarise" })
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Say {
|
|
param([string]$Message)
|
|
Write-Host $Message
|
|
}
|
|
|
|
function Fail {
|
|
param([string]$Message)
|
|
Write-Error $Message
|
|
exit 1
|
|
}
|
|
|
|
function Get-SkillList {
|
|
param([string]$SkillText)
|
|
return @($SkillText -split "[,\s]+" | Where-Object { $_ })
|
|
}
|
|
|
|
function Assert-ValidSkillName {
|
|
param([string]$SkillName)
|
|
if ($SkillName -notmatch "^[A-Za-z0-9_-]+$") {
|
|
Fail "Invalid skill name: $SkillName"
|
|
}
|
|
}
|
|
|
|
function Show-ClaudeUsage {
|
|
param([string[]]$SkillNames)
|
|
|
|
Say "Restart Claude Code if it was already open, then use:"
|
|
foreach ($skill in $SkillNames) {
|
|
if ($skill -eq "securityaudit") {
|
|
Say " /securityaudit"
|
|
Say " /securityaudit --fix"
|
|
}
|
|
else {
|
|
Say " /$skill"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Copy-ClaudeSkill {
|
|
param(
|
|
[string]$SourcePath,
|
|
[string]$SkillName
|
|
)
|
|
|
|
Assert-ValidSkillName -SkillName $SkillName
|
|
|
|
$skillFile = Join-Path $SourcePath "SKILL.md"
|
|
if (-not (Test-Path -LiteralPath $skillFile)) {
|
|
Fail "Could not find SKILL.md at $SourcePath"
|
|
}
|
|
|
|
$skillText = Get-Content -LiteralPath $skillFile -Raw
|
|
if ($skillText -notmatch "(?m)^name:\s*$([regex]::Escape($SkillName))\s*$") {
|
|
Fail "SKILL.md at $SourcePath does not look like the $SkillName skill"
|
|
}
|
|
|
|
$claudeHome = if ($env:CLAUDE_HOME) { $env:CLAUDE_HOME } else { Join-Path $HOME ".claude" }
|
|
$destParent = Join-Path $claudeHome "skills"
|
|
$dest = Join-Path $destParent $SkillName
|
|
|
|
New-Item -ItemType Directory -Force -Path $destParent | Out-Null
|
|
|
|
$stage = Join-Path $destParent (".$SkillName-claude-install-" + [Guid]::NewGuid().ToString("N"))
|
|
$stageSkill = Join-Path $stage $SkillName
|
|
New-Item -ItemType Directory -Force -Path $stageSkill | Out-Null
|
|
|
|
Copy-Item -LiteralPath $skillFile -Destination (Join-Path $stageSkill "SKILL.md") -Force
|
|
foreach ($dir in @("scripts", "references", "assets")) {
|
|
$sourceDir = Join-Path $SourcePath $dir
|
|
if (Test-Path -LiteralPath $sourceDir) {
|
|
Copy-Item -LiteralPath $sourceDir -Destination (Join-Path $stageSkill $dir) -Recurse -Force
|
|
}
|
|
}
|
|
|
|
if (-not (Test-Path -LiteralPath (Join-Path $stageSkill "SKILL.md"))) {
|
|
Fail "Install staging failed for $SkillName"
|
|
}
|
|
|
|
if (Test-Path -LiteralPath $dest) {
|
|
$backup = "$dest.backup.$(Get-Date -Format 'yyyyMMdd-HHmmss')"
|
|
Say "Existing Claude $SkillName skill found. Backing it up to:"
|
|
Say $backup
|
|
Move-Item -LiteralPath $dest -Destination $backup
|
|
}
|
|
|
|
Move-Item -LiteralPath $stageSkill -Destination $dest
|
|
Remove-Item -LiteralPath $stage -Force -ErrorAction SilentlyContinue
|
|
Say "Installed Claude Code $SkillName skill to $dest"
|
|
}
|
|
|
|
$skillNames = Get-SkillList -SkillText $Skills
|
|
if ($skillNames.Count -eq 0) {
|
|
Fail "No skills requested"
|
|
}
|
|
|
|
if ($PSScriptRoot -and (Test-Path -LiteralPath (Join-Path (Join-Path $PSScriptRoot "securityaudit") "SKILL.md"))) {
|
|
Say "Installing Claude Code skills from local checkout..."
|
|
foreach ($skill in $skillNames) {
|
|
$sourcePath = Join-Path $PSScriptRoot $skill
|
|
if (-not (Test-Path -LiteralPath (Join-Path $sourcePath "SKILL.md"))) {
|
|
Fail "Could not find $skill/SKILL.md in local checkout"
|
|
}
|
|
Copy-ClaudeSkill -SourcePath $sourcePath -SkillName $skill
|
|
}
|
|
Say ""
|
|
Say "Installed Claude Code skills:"
|
|
foreach ($skill in $skillNames) {
|
|
$claudeHome = if ($env:CLAUDE_HOME) { $env:CLAUDE_HOME } else { Join-Path $HOME ".claude" }
|
|
Say " $skill -> $(Join-Path (Join-Path $claudeHome "skills") $skill)"
|
|
}
|
|
Say ""
|
|
Show-ClaudeUsage -SkillNames $skillNames
|
|
exit 0
|
|
}
|
|
|
|
if (-not (Get-Command Expand-Archive -ErrorAction SilentlyContinue)) {
|
|
Fail "PowerShell Expand-Archive is required. Please use PowerShell 5 or newer."
|
|
}
|
|
|
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
|
|
$temp = Join-Path ([IO.Path]::GetTempPath()) ("securityaudit-claude-install-" + [Guid]::NewGuid().ToString("N"))
|
|
New-Item -ItemType Directory -Force -Path $temp | Out-Null
|
|
|
|
try {
|
|
$archive = Join-Path $temp "repo.zip"
|
|
$url = "https://github.com/$RepoSlug/archive/refs/heads/$Ref.zip"
|
|
|
|
Say "Downloading Claude Code skills from $RepoSlug ($Ref)..."
|
|
$client = New-Object Net.WebClient
|
|
$client.DownloadFile($url, $archive)
|
|
|
|
Expand-Archive -LiteralPath $archive -DestinationPath $temp -Force
|
|
|
|
foreach ($skill in $skillNames) {
|
|
$skillFile = Get-ChildItem -Path $temp -Filter "SKILL.md" -Recurse |
|
|
Where-Object { Split-Path -Leaf (Split-Path -Parent $_.FullName) -eq $skill } |
|
|
Select-Object -First 1
|
|
|
|
if (-not $skillFile) {
|
|
Fail "Could not find $skill/SKILL.md in the downloaded repo"
|
|
}
|
|
|
|
Copy-ClaudeSkill -SourcePath (Split-Path -Parent $skillFile.FullName) -SkillName $skill
|
|
}
|
|
|
|
Say ""
|
|
Say "Installed Claude Code skills:"
|
|
foreach ($skill in $skillNames) {
|
|
$claudeHome = if ($env:CLAUDE_HOME) { $env:CLAUDE_HOME } else { Join-Path $HOME ".claude" }
|
|
Say " $skill -> $(Join-Path (Join-Path $claudeHome "skills") $skill)"
|
|
}
|
|
Say ""
|
|
Show-ClaudeUsage -SkillNames $skillNames
|
|
}
|
|
finally {
|
|
Remove-Item -LiteralPath $temp -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|