# FaceNinja skill installer 1.0.0 for Windows PowerShell. It copies the skill folder (SKILL.md, scripts\faceninja.py, # reference\api.md, README.md) into the skills folder of each AI agent it finds and changes nothing else. # # irm https://faceninja.net/skill/install.ps1 | iex every agent found # & ([scriptblock]::Create((irm https://faceninja.net/skill/install.ps1))) -Claude only Claude Code # -Codex -Cursor (.\.cursor\skills) -Agents (.\.agents\skills) -Dir PATH -AgentsMd (add a line to .\AGENTS.md) param([switch]$Claude, [switch]$Codex, [switch]$Cursor, [switch]$Agents, [string]$Dir, [switch]$AgentsMd) $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' $base = if ($env:FACENINJA_API_BASE) { $env:FACENINJA_API_BASE.TrimEnd('/') } else { 'https://faceninja.net' } # SHA-256 of each file, written by tools/skill/build.mjs; a download that does not match is refused $sums = [ordered]@{ 'SKILL.md' = 'e5042de77212ea11a0e93954d4e6733fe828481c270d0570d478792f982f9fdd' 'README.md' = '817bb11ae1244a32ff55d8ac10e0f19ab5e33565b7842dec3a0b3fece27a1bbb' 'scripts/faceninja.py' = '561461d99e29f4251ac76e31cee0f1fcbe7112c829c0481d52fd8505a60e6135' 'reference/api.md' = '5ba682b2b97c4144ccf20d1ed447e96dd5abf3742c47f00642b0ed083d4caba6' } $targets = New-Object System.Collections.Generic.List[string] function Add-Target([string]$p) { if (-not $targets.Contains($p)) { $targets.Add($p) } } $claudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME '.claude' } $codexDir = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { Join-Path $HOME '.codex' } $here = (Get-Location).Path if ($Claude) { Add-Target (Join-Path $claudeDir 'skills') } if ($Codex) { Add-Target (Join-Path $codexDir 'skills') } if ($Cursor) { Add-Target (Join-Path $here '.cursor\skills') } if ($Agents) { Add-Target (Join-Path $here '.agents\skills') } if ($Dir) { Add-Target $Dir } if ($targets.Count -eq 0) { if ($env:CLAUDE_CONFIG_DIR -or (Test-Path (Join-Path $HOME '.claude'))) { Add-Target (Join-Path $claudeDir 'skills') } if ($env:CODEX_HOME) { Add-Target (Join-Path $env:CODEX_HOME 'skills') } if (Test-Path (Join-Path $HOME '.codex')) { Add-Target (Join-Path $HOME '.codex\skills') } if (Test-Path (Join-Path $here '.cursor')) { Add-Target (Join-Path $here '.cursor\skills') } if ((Test-Path (Join-Path $here '.agents')) -or $targets.Count -eq 0) { Add-Target (Join-Path $here '.agents\skills') } } $tmp = Join-Path ([IO.Path]::GetTempPath()) ('faceninja-skill-' + [guid]::NewGuid()) New-Item -ItemType Directory -Force -Path (Join-Path $tmp 'scripts'), (Join-Path $tmp 'reference') | Out-Null try { foreach ($file in $sums.Keys) { $out = Join-Path $tmp $file Invoke-WebRequest -UseBasicParsing -Uri "$base/skill/$file" -OutFile $out if ((Get-FileHash -Algorithm SHA256 $out).Hash.ToLower() -ne $sums[$file]) { throw "Checksum mismatch for $file; nothing was installed." } } Write-Host "Installed the FaceNinja skill 1.0.0 to:" foreach ($t in $targets) { $dest = Join-Path $t 'faceninja' New-Item -ItemType Directory -Force -Path (Join-Path $dest 'scripts'), (Join-Path $dest 'reference') | Out-Null foreach ($file in $sums.Keys) { Copy-Item -Force (Join-Path $tmp $file) (Join-Path $dest $file) } Write-Host " $dest" } } finally { Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue } if ($targets.Contains((Join-Path $here '.agents\skills'))) { $line = '- FaceNinja (photos to MetaHuman heads): read .agents/skills/faceninja/SKILL.md' $agentsFile = Join-Path $here 'AGENTS.md' if (-not ((Test-Path $agentsFile) -and (Select-String -Quiet -SimpleMatch '.agents/skills/faceninja/SKILL.md' $agentsFile))) { if (-not $AgentsMd -and [Environment]::UserInteractive -and -not [Console]::IsInputRedirected) { $AgentsMd = (Read-Host 'Add a line to .\AGENTS.md so agents find the skill? [y/N]') -match '^(y|yes)$' } if ($AgentsMd) { Add-Content -Path $agentsFile -Value "`n$line"; Write-Host 'Added a line to .\AGENTS.md.' } else { Write-Host "Agents that read AGENTS.md find the skill if you add this line there (or rerun with -AgentsMd):`n $line" } } } $py = Get-Command python -ErrorAction SilentlyContinue if (-not $py) { $py = Get-Command py -ErrorAction SilentlyContinue } if (-not $py) { Write-Host 'Note: the skill needs Python 3.9 or newer (python.org or `winget install Python.Python.3.12`).' } Write-Host 'Next: ask your agent to use the FaceNinja skill, or run: python \scripts\faceninja.py login'