183 lines
7.6 KiB
PowerShell
183 lines
7.6 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[string]$OutputMarkdown,
|
|
[string]$OutputJson
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($OutputMarkdown)) {
|
|
$OutputMarkdown = Join-Path $PSScriptRoot '..\tool-index.md'
|
|
}
|
|
if ([string]::IsNullOrWhiteSpace($OutputJson)) {
|
|
$OutputJson = Join-Path $PSScriptRoot '..\tool-index.json'
|
|
}
|
|
|
|
. (Join-Path $PSScriptRoot 'lib\ToolDiscovery.ps1')
|
|
|
|
$scriptRefs = @{
|
|
'jadx' = @('apk-reverse/scripts/decode.ps1')
|
|
'apktool' = @('apk-reverse/scripts/decode.ps1', 'apk-reverse/scripts/rebuild-sign-install.ps1')
|
|
'adb' = @('apk-reverse/scripts/rebuild-sign-install.ps1')
|
|
'java' = @('apk-reverse/scripts/decode.ps1')
|
|
'apksigner' = @('apk-reverse/scripts/rebuild-sign-install.ps1')
|
|
'zipalign' = @('apk-reverse/scripts/rebuild-sign-install.ps1')
|
|
'frida' = @('apk-reverse/scripts/frida-run.ps1')
|
|
'frida-ps' = @('apk-reverse/scripts/frida-run.ps1')
|
|
'r2' = @('radare2/scripts/recon.ps1')
|
|
'rabin2' = @('radare2/scripts/recon.ps1')
|
|
'rasm2' = @('radare2/SKILL.md')
|
|
'radiff2' = @('radare2/SKILL.md')
|
|
'rahash2' = @('radare2/SKILL.md')
|
|
'rax2' = @('radare2/SKILL.md')
|
|
'r2pm' = @('radare2/SKILL.md')
|
|
'r2xsql' = @('radare2/SKILL.md')
|
|
'r2xsql-full' = @('radare2/SKILL.md')
|
|
'r2mcp' = @('radare2/SKILL.md')
|
|
'radius2' = @('radare2/SKILL.md')
|
|
'python' = @('apk-reverse/scripts/frida-run.ps1', 'case-review/scripts/review_case.py')
|
|
'pip' = @()
|
|
'node' = @('js-reverse/SKILL.md')
|
|
'npx' = @('js-reverse/SKILL.md')
|
|
'jshookmcp' = @('js-reverse/SKILL.md')
|
|
'reqable-mcp' = @('pentest-tools/SKILL.md')
|
|
'jeb-pro' = @('apk-reverse/SKILL.md')
|
|
'seclists' = @('pentest-tools/SKILL.md')
|
|
'pentestswarm' = @('pentest-tools/SKILL.md')
|
|
'agent-browser' = @('browser-automation/SKILL.md')
|
|
'playwright' = @('browser-automation/SKILL.md', 'browser-automation/scripts/setup.ps1')
|
|
'analyzeHeadless' = @('reverse-engineering/SKILL.md')
|
|
'proxycat' = @('pentest-tools/SKILL.md')
|
|
'nmap' = @('pentest-tools/SKILL.md')
|
|
'binwalk' = @('firmware-pentest/SKILL.md')
|
|
'yara' = @('malware-analysis/SKILL.md')
|
|
'pwntools' = @('reverse-engineering/SKILL.md', 'reverse-engineering/patterns-ctf*.md')
|
|
'bkcrack' = @('reverse-engineering/crypto-decode-tools.md', '../CTF-Sandbox-Orchestrator/competition-zip-archive/SKILL.md')
|
|
}
|
|
|
|
$skillsRoot = Split-Path -Parent $PSScriptRoot
|
|
|
|
$reports = @(Get-ReverseToolReport | Where-Object {
|
|
$skill = [string]$_.Skill
|
|
$keep = $true
|
|
if ($keep -and -not [string]::IsNullOrWhiteSpace($skill)) {
|
|
$skillMd = Join-Path $skillsRoot (($skill -replace '/', [IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar + 'SKILL.md')
|
|
if (-not (Test-Path -LiteralPath $skillMd)) { $keep = $false }
|
|
}
|
|
$keep
|
|
})
|
|
$generatedAt = Get-Date -Format 'yyyy-MM-dd HH:mm:ss K'
|
|
|
|
$markdownLines = @(
|
|
'# 逆向工具索引',
|
|
'',
|
|
"- 扫描时间: $generatedAt",
|
|
'- 路由入口: `SKILL.md` → `routing.md` → 对应子 skill',
|
|
'- 说明: 本表由 `skills/scripts/refresh-tool-index.ps1` 自动生成,用于各 Agent 客户端的路由和工具路径确认。',
|
|
'- 注意: MCP-only 能力的工具可用性与运行时分开计算;`npx` 只代表 npm MCP 的运行条件,不能单独让 jshookmcp / reqable-mcp 变成可用或 Ready。',
|
|
'',
|
|
'| 工具 | 归属 skill | 作用 | 可用 | 路径 | 版本 | 来源 | 脚本引用 |',
|
|
'|---|---|---|---|---|---|---|---|'
|
|
)
|
|
|
|
foreach ($report in $reports) {
|
|
$pathText = if ($report.ResolvedPath) { $report.ResolvedPath } else { '—' }
|
|
$versionText = if ($report.Version) { $report.Version } else { '—' }
|
|
$refs = $scriptRefs[$report.Name]
|
|
$refsText = if ($refs -and $refs.Count -gt 0) { ($refs -join '<br>') } else { '—' }
|
|
$availableText = if ($report.Available) { 'yes' } else { 'no' }
|
|
$escapedPath = $pathText.Replace('|', '\|')
|
|
$escapedVersion = $versionText.Replace('|', '\|')
|
|
$escapedRefs = $refsText.Replace('|', '\|')
|
|
$markdownLines += "| $($report.Name) | $($report.Skill) | $($report.Purpose) | $availableText | $escapedPath | $escapedVersion | $($report.Source) | $escapedRefs |"
|
|
}
|
|
|
|
$markdownContent = ($markdownLines -join [Environment]::NewLine) + [Environment]::NewLine
|
|
$markdownContent | Set-Content -LiteralPath $OutputMarkdown -Encoding utf8
|
|
|
|
# --- Capability status view ---
|
|
$capabilityNames = @('jadx', 'apktool', 'jeb-pro', 'frida', 'frida-ps', 'idalib-mcp', 'jshookmcp', 'reqable-mcp', 'anything-analyzer', 'idapro', 'r2', 'rabin2', 'adb', 'agent-browser', 'ghidra-mcp', 'seclists', 'proxycat', 'burpsuite-mcp', 'pentestswarm', 'nmap', 'binwalk', 'yara', 'pwntools', 'bkcrack')
|
|
$capabilityRows = @()
|
|
foreach ($capName in $capabilityNames) {
|
|
$state = Get-ReverseCapabilityState -Name $capName
|
|
if ($null -eq $state) { continue }
|
|
$toolAvailable = $false
|
|
try {
|
|
$toolSpec = Resolve-ReverseToolSpec -Name $capName
|
|
$toolAvailable = $toolSpec.Available
|
|
}
|
|
catch {
|
|
# Capability exists in bootstrap manifest but not in tool catalog (e.g. MCP-only capabilities)
|
|
$toolAvailable = $false
|
|
}
|
|
$capabilityRows += [pscustomobject]@{
|
|
name = $capName
|
|
tool_available = $toolAvailable
|
|
ready = $state.Ready
|
|
mcp_registered = $state.Registered
|
|
service_online = $state.ServiceOnline
|
|
mcp_http_verified = $state.McpHttpVerified
|
|
can_auto_install = $state.CanAutoInstall
|
|
bootstrap_kind = $state.BootstrapKind
|
|
}
|
|
}
|
|
|
|
# Append capability view to markdown
|
|
$markdownCapLines = @(
|
|
'',
|
|
'---',
|
|
'',
|
|
'## 能力状态视图 (Capability Status)',
|
|
'',
|
|
'| 能力 | 工具可用 | Ready | MCP 已注册 | 服务在线 | MCP HTTP | 可自动安装 | 安装方式 |',
|
|
'|------|---------|-------|-----------|---------|-----------|---------|'
|
|
)
|
|
foreach ($cap in $capabilityRows) {
|
|
$toolText = if ($cap.tool_available) { '✓' } else { '✗' }
|
|
$readyText = if ($cap.ready) { '✓' } else { '✗' }
|
|
$mcpText = if ($cap.mcp_registered) { '✓' } else { '—' }
|
|
$svcText = if ($cap.service_online) { '✓' } else { '—' }
|
|
$mcpHttpText = if ($cap.mcp_http_verified) { '✓' } else { '—' }
|
|
$autoText = if ($cap.can_auto_install) { '✓' } else { '✗' }
|
|
$kindText = if ($cap.bootstrap_kind) { $cap.bootstrap_kind } else { '—' }
|
|
$markdownCapLines += "| $($cap.name) | $toolText | $readyText | $mcpText | $svcText | $mcpHttpText | $autoText | $kindText |"
|
|
}
|
|
$markdownCapLines += ''
|
|
$markdownCapLines += '> ✓ = 是 | ✗ = 否 | — = 不适用或未检测'
|
|
$markdownCapLines += ''
|
|
|
|
$capContent = ($markdownCapLines -join [Environment]::NewLine)
|
|
Add-Content -LiteralPath $OutputMarkdown -Value $capContent -Encoding utf8
|
|
|
|
$jsonRows = foreach ($report in $reports) {
|
|
[pscustomobject]@{
|
|
name = $report.Name
|
|
skill = $report.Skill
|
|
purpose = $report.Purpose
|
|
available = $report.Available
|
|
is_executable = $report.IsExecutable
|
|
is_directory = $report.IsDirectory
|
|
resolved_path = $report.ResolvedPath
|
|
version = $report.Version
|
|
source = $report.Source
|
|
script_refs = @($scriptRefs[$report.Name])
|
|
}
|
|
}
|
|
|
|
$jsonPayload = [pscustomobject]@{
|
|
generated_at = $generatedAt
|
|
routing_entry = @('SKILL.md', 'routing.md')
|
|
tools = $jsonRows
|
|
capabilities = $capabilityRows
|
|
}
|
|
|
|
$jsonPayload | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $OutputJson -Encoding utf8
|
|
|
|
"markdown=$OutputMarkdown"
|
|
"json=$OutputJson"
|
|
"tools=$($reports.Count)"
|