feat(scripts): add cross-platform build and installation tools
This commit is contained in:
@@ -0,0 +1,78 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$root = Split-Path $PSScriptRoot -Parent
|
||||||
|
$shared = Join-Path $root 'shared'
|
||||||
|
$output = Join-Path $root 'generated'
|
||||||
|
$pluginManifest = Join-Path $root 'adapters/plugins.tsv'
|
||||||
|
if (-not (Test-Path -LiteralPath $pluginManifest)) { throw "Missing plugin manifest: $pluginManifest" }
|
||||||
|
$pluginEntries = @(Import-Csv -LiteralPath $pluginManifest -Delimiter ([char]9))
|
||||||
|
if ($pluginEntries.Count -ne 4) { throw 'Plugin manifest must define exactly four plugins.' }
|
||||||
|
foreach ($pluginEntry in $pluginEntries) {
|
||||||
|
foreach ($field in @('claude_plugin','codex_plugin')) {
|
||||||
|
if ([string]::IsNullOrWhiteSpace($pluginEntry.$field)) { throw "Missing $field for plugin $($pluginEntry.name)." }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Remove-Item $output -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
|
New-Item $output -ItemType Directory -Force | Out-Null
|
||||||
|
function Copy-Directory($source, $destination) {
|
||||||
|
New-Item $destination -ItemType Directory -Force | Out-Null
|
||||||
|
Get-ChildItem $source -File -Recurse | ForEach-Object {
|
||||||
|
$relative = $_.FullName.Substring($source.Length).TrimStart([char[]]@('\','/'))
|
||||||
|
$target = Join-Path $destination $relative
|
||||||
|
New-Item (Split-Path $target -Parent) -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item $_.FullName $target -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function Read-Field($path, $name) {
|
||||||
|
$line = Get-Content $path | Where-Object { $_ -match ('^' + [regex]::Escape($name) + ':\s*(.*)$') } | Select-Object -First 1
|
||||||
|
if (-not $line) { throw "Missing $name in $path" }
|
||||||
|
return ([regex]::Match($line, '^' + [regex]::Escape($name) + ':\s*(.*)$')).Groups[1].Value.Trim()
|
||||||
|
}
|
||||||
|
function Quote-Toml($value) {
|
||||||
|
return ('"' + $value.Replace('\','\\').Replace('"','\"').Replace("`r",'').Replace("`n",'\n') + '"')
|
||||||
|
}
|
||||||
|
& (Join-Path $root 'shared/hooks/scripts/Test-SessionConfig.ps1') -RepositoryRoot $root
|
||||||
|
foreach ($platform in @('windows','linux')) {
|
||||||
|
Copy-Directory (Join-Path $shared 'skills') (Join-Path $output "codex-$platform/skills")
|
||||||
|
Copy-Directory (Join-Path $shared 'skills') (Join-Path $output "claude-$platform/skills")
|
||||||
|
Copy-Directory (Join-Path $shared 'rules') (Join-Path $output "codex-$platform/rules")
|
||||||
|
Copy-Directory (Join-Path $shared 'rules') (Join-Path $output "claude-$platform/rules")
|
||||||
|
New-Item (Join-Path $output "codex-$platform") -ItemType Directory -Force | Out-Null
|
||||||
|
New-Item (Join-Path $output "claude-$platform") -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Directory (Join-Path $shared 'hooks') (Join-Path $output "codex-$platform/hooks")
|
||||||
|
Copy-Directory (Join-Path $shared 'hooks') (Join-Path $output "claude-$platform/hooks")
|
||||||
|
Copy-Directory (Join-Path $shared 'statusline') (Join-Path $output "claude-$platform/statusline")
|
||||||
|
Copy-Item (Join-Path $shared 'global-instructions.md') (Join-Path $output "codex-$platform/AGENTS.md") -Force
|
||||||
|
Copy-Item (Join-Path $shared 'global-instructions.md') (Join-Path $output "claude-$platform/CLAUDE.md") -Force
|
||||||
|
Copy-Item (Join-Path $root 'adapters/codex/config/config.toml') (Join-Path $output "codex-$platform/config.toml") -Force
|
||||||
|
Copy-Item (Join-Path $root 'adapters/claude/config/settings.json') (Join-Path $output "claude-$platform/settings.json") -Force
|
||||||
|
foreach ($client in @('codex','claude')) {
|
||||||
|
$agentsOutput = Join-Path $output "$client-$platform/agents"
|
||||||
|
New-Item $agentsOutput -ItemType Directory -Force | Out-Null
|
||||||
|
Get-ChildItem (Join-Path $shared 'agents') -Directory | Sort-Object Name | ForEach-Object {
|
||||||
|
$metadata = Join-Path $_.FullName 'agent.yml'
|
||||||
|
$name = Read-Field $metadata 'name'
|
||||||
|
$description = Read-Field $metadata 'description'
|
||||||
|
$instructions = Get-Content (Join-Path $_.FullName 'instructions.md') -Raw
|
||||||
|
if ($client -eq 'codex') {
|
||||||
|
Set-Content (Join-Path $agentsOutput "$name.toml") "name = $(Quote-Toml $name)`r`ndescription = $(Quote-Toml $description)`r`ndeveloper_instructions = $(Quote-Toml $instructions)" -Encoding UTF8
|
||||||
|
} else {
|
||||||
|
Set-Content (Join-Path $agentsOutput "$name.md") "---`r`nname: $name`r`ndescription: $description`r`n---`r`n`r`n$instructions" -Encoding UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($client in @('codex','claude')) {
|
||||||
|
$file = if ($client -eq 'codex') { 'config.toml' } else { 'settings.json' }
|
||||||
|
$path = Join-Path $output "$client-$platform/$file"
|
||||||
|
$command = if ($platform -eq 'windows') { 'powershell -NoProfile -ExecutionPolicy Bypass -File' } else { 'bash' }
|
||||||
|
$script = if ($platform -eq 'windows') { 'flashbang.ps1' } else { 'flashbang.sh' }
|
||||||
|
$statusLineScript = if ($platform -eq 'windows') { 'statusline.ps1' } else { 'statusline.sh' }
|
||||||
|
$content = Get-Content -LiteralPath $path -Raw
|
||||||
|
$content = $content.Replace('__HOOK_COMMAND__', $command).Replace('__WINDOWS_HOOK_COMMAND__', $command).Replace('__HOOK_SCRIPT__', $script).Replace('__WINDOWS_HOOK_SCRIPT__', $script).Replace('__STATUSLINE_COMMAND__', $command).Replace('__STATUSLINE_SCRIPT__', $statusLineScript)
|
||||||
|
Set-Content -LiteralPath $path -Value $content -Encoding UTF8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Output 'PASS build: codex-windows, claude-windows, codex-linux, claude-linux'
|
||||||
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
shared="$root/shared"
|
||||||
|
output="$root/generated"
|
||||||
|
plugin_manifest="$root/adapters/plugins.tsv"
|
||||||
|
[ -f "$plugin_manifest" ] || { printf 'Missing plugin manifest: %s\n' "$plugin_manifest" >&2; exit 1; }
|
||||||
|
[ "$(awk 'NR > 1 && NF == 5 { count++ } END { print count + 0 }' "$plugin_manifest")" -eq 4 ] || { printf 'Plugin manifest must define exactly four plugins.\n' >&2; exit 1; }
|
||||||
|
|
||||||
|
copy_directory() {
|
||||||
|
local source=$1 destination=$2
|
||||||
|
mkdir -p "$destination"
|
||||||
|
cp -R "$source"/. "$destination"/
|
||||||
|
}
|
||||||
|
|
||||||
|
read_field() {
|
||||||
|
local path=$1 name=$2 value
|
||||||
|
value=$(sed -n "s/^${name}:[[:space:]]*//p" "$path" | head -n 1)
|
||||||
|
[ -n "$value" ] || { printf 'Missing %s in %s\n' "$name" "$path" >&2; exit 1; }
|
||||||
|
printf '%s' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
quote_toml() {
|
||||||
|
local value=$1
|
||||||
|
value=${value//\\/\\\\}
|
||||||
|
value=${value//\"/\\\"}
|
||||||
|
value=${value//$'\r'/}
|
||||||
|
value=${value//$'\n'/\\n}
|
||||||
|
printf '"%s"' "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
bash "$shared/hooks/scripts/test-session-config.sh" "$root"
|
||||||
|
rm -rf -- "$output"
|
||||||
|
for platform in windows linux; do
|
||||||
|
mkdir -p "$output/codex-$platform" "$output/claude-$platform"
|
||||||
|
copy_directory "$shared/skills" "$output/codex-$platform/skills"
|
||||||
|
copy_directory "$shared/skills" "$output/claude-$platform/skills"
|
||||||
|
copy_directory "$shared/rules" "$output/codex-$platform/rules"
|
||||||
|
copy_directory "$shared/rules" "$output/claude-$platform/rules"
|
||||||
|
copy_directory "$shared/hooks" "$output/codex-$platform/hooks"
|
||||||
|
copy_directory "$shared/hooks" "$output/claude-$platform/hooks"
|
||||||
|
copy_directory "$shared/statusline" "$output/claude-$platform/statusline"
|
||||||
|
|
||||||
|
cp "$shared/global-instructions.md" "$output/codex-$platform/AGENTS.md"
|
||||||
|
cp "$shared/global-instructions.md" "$output/claude-$platform/CLAUDE.md"
|
||||||
|
cp "$root/adapters/codex/config/config.toml" "$output/codex-$platform/config.toml"
|
||||||
|
cp "$root/adapters/claude/config/settings.json" "$output/claude-$platform/settings.json"
|
||||||
|
|
||||||
|
for client in codex claude; do
|
||||||
|
agents_output="$output/$client-$platform/agents"
|
||||||
|
mkdir -p "$agents_output"
|
||||||
|
for agent in "$shared"/agents/*; do
|
||||||
|
metadata="$agent/agent.yml"
|
||||||
|
name=$(read_field "$metadata" name)
|
||||||
|
description=$(read_field "$metadata" description)
|
||||||
|
instructions=$(<"$agent/instructions.md")
|
||||||
|
if [ "$client" = codex ]; then
|
||||||
|
printf 'name = %s\ndescription = %s\ndeveloper_instructions = %s\n' "$(quote_toml "$name")" "$(quote_toml "$description")" "$(quote_toml "$instructions")" > "$agents_output/$name.toml"
|
||||||
|
else
|
||||||
|
printf '%s\nname: %s\ndescription: %s\n%s\n\n%s\n' '---' "$name" "$description" '---' "$instructions" > "$agents_output/$name.md"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
for client in codex claude; do
|
||||||
|
file=config.toml
|
||||||
|
[ "$client" != claude ] || file=settings.json
|
||||||
|
command=bash
|
||||||
|
script=flashbang.sh
|
||||||
|
statusline_script=statusline.sh
|
||||||
|
if [ "$platform" = windows ]; then
|
||||||
|
command='powershell -NoProfile -ExecutionPolicy Bypass -File'
|
||||||
|
script=flashbang.ps1
|
||||||
|
statusline_script=statusline.ps1
|
||||||
|
fi
|
||||||
|
path="$output/$client-$platform/$file"
|
||||||
|
content=$(<"$path")
|
||||||
|
content=${content//__HOOK_COMMAND__/$command}
|
||||||
|
content=${content//__WINDOWS_HOOK_COMMAND__/$command}
|
||||||
|
content=${content//__HOOK_SCRIPT__/$script}
|
||||||
|
content=${content//__WINDOWS_HOOK_SCRIPT__/$script}
|
||||||
|
content=${content//__STATUSLINE_COMMAND__/$command}
|
||||||
|
content=${content//__STATUSLINE_SCRIPT__/$statusline_script}
|
||||||
|
printf '%s\n' "$content" > "$path"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
printf 'PASS build: codex-windows, claude-windows, codex-linux, claude-linux\n'
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$root = Split-Path $PSScriptRoot -Parent
|
||||||
|
$homePath = [Environment]::GetFolderPath('UserProfile')
|
||||||
|
$fail = $false
|
||||||
|
function Result($state, $message) { Write-Output ("$state $message"); if ($state -eq 'FAIL') { $script:fail = $true } }
|
||||||
|
foreach ($tool in @('codex','claude')) { if (Get-Command $tool -ErrorAction SilentlyContinue) { $version = & $tool --version 2>&1 | Select-Object -First 1; Result 'PASS' "$tool available ($version)" } else { Result 'WARN' "$tool unavailable" } }
|
||||||
|
if ($PSVersionTable.PSVersion.Major -ge 6 -and -not $IsWindows) { if (Get-Command jq -ErrorAction SilentlyContinue) { Result 'PASS' 'jq available for Claude status line' } else { Result 'WARN' 'jq unavailable; Claude status line is disabled' } }
|
||||||
|
foreach ($platform in @('windows','linux')) {
|
||||||
|
if (Test-Path (Join-Path $root "generated/codex-$platform/AGENTS.md")) { Result 'PASS' 'generated output present' } else { Result 'FAIL' 'generated output missing; run build' }
|
||||||
|
if ((Test-Path (Join-Path $root "generated/codex-$platform/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/codex-$platform/hooks/scripts/validate-command-safety.sh")) -and (Test-Path (Join-Path $root "generated/claude-$platform/hooks/scripts/Validate-CommandSafety.ps1")) -and (Test-Path (Join-Path $root "generated/claude-$platform/hooks/scripts/validate-command-safety.sh"))) { Result 'PASS' 'generated hooks present' } else { Result 'FAIL' 'generated hooks missing; run build' }
|
||||||
|
}
|
||||||
|
foreach ($path in @('.codex/AGENTS.md','.codex/config.toml','.claude/CLAUDE.md','.claude/settings.json')) { if (Test-Path (Join-Path $homePath $path)) { Result 'PASS' "installed $path" } else { Result 'WARN' "not installed $path" } }
|
||||||
|
foreach ($path in @('shared/rules','shared/skills','shared/agents','shared/hooks/scripts')) { if (Test-Path (Join-Path $root $path)) { Result 'PASS' "source $path" } else { Result 'FAIL' "missing $path" } }
|
||||||
|
if ($fail) { exit 1 }
|
||||||
|
Write-Output 'PASS doctor'
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
home_path=${HOME:?HOME is required}
|
||||||
|
failed=false
|
||||||
|
|
||||||
|
result() {
|
||||||
|
printf '%s %s\n' "$1" "$2"
|
||||||
|
[ "$1" != FAIL ] || failed=true
|
||||||
|
}
|
||||||
|
|
||||||
|
for tool in codex claude; do
|
||||||
|
if command -v "$tool" >/dev/null; then result PASS "$tool available ($($tool --version 2>&1 | head -n 1))"; else result WARN "$tool unavailable"; fi
|
||||||
|
done
|
||||||
|
command -v jq >/dev/null && result PASS 'jq available for Claude status line' || result WARN 'jq unavailable; Claude status line is disabled'
|
||||||
|
for platform in windows linux; do
|
||||||
|
[ -f "$root/generated/codex-$platform/AGENTS.md" ] && result PASS 'generated output present' || result FAIL 'generated output missing; run build'
|
||||||
|
[ -f "$root/generated/codex-$platform/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/codex-$platform/hooks/scripts/validate-command-safety.sh" ] && [ -f "$root/generated/claude-$platform/hooks/scripts/Validate-CommandSafety.ps1" ] && [ -f "$root/generated/claude-$platform/hooks/scripts/validate-command-safety.sh" ] && result PASS 'generated hooks present' || result FAIL 'generated hooks missing; run build'
|
||||||
|
done
|
||||||
|
for path in .codex/AGENTS.md .codex/config.toml .claude/CLAUDE.md .claude/settings.json; do
|
||||||
|
[ -f "$home_path/$path" ] && result PASS "installed $path" || result WARN "not installed $path"
|
||||||
|
done
|
||||||
|
for path in shared/rules shared/skills shared/agents shared/hooks/scripts; do
|
||||||
|
[ -e "$root/$path" ] && result PASS "source $path" || result FAIL "missing $path"
|
||||||
|
done
|
||||||
|
"$failed" && exit 1
|
||||||
|
printf 'PASS doctor\n'
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
[CmdletBinding()]
|
||||||
|
param([switch]$DryRun)
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$root = Split-Path $PSScriptRoot -Parent
|
||||||
|
. (Join-Path $PSScriptRoot 'lib/plugins.ps1')
|
||||||
|
$buildScript = Join-Path $PSScriptRoot 'build.ps1'
|
||||||
|
& $buildScript
|
||||||
|
if (-not $?) { throw 'Build failed. Installation was not started.' }
|
||||||
|
$generated = Join-Path $root 'generated'
|
||||||
|
if (-not (Test-Path $generated)) { throw 'Generated output is missing after a successful build.' }
|
||||||
|
Write-Output 'Select target platform:'
|
||||||
|
Write-Output '1) Windows'
|
||||||
|
Write-Output '2) Linux'
|
||||||
|
do { $platformSelection = Read-Host 'Selection [1-2]' } while ($platformSelection -notin @('1','2'))
|
||||||
|
$platform = @{'1'='windows'; '2'='linux'}[$platformSelection]
|
||||||
|
Write-Output 'Select installation target:'
|
||||||
|
Write-Output '1) Codex'
|
||||||
|
Write-Output '2) Claude'
|
||||||
|
Write-Output '3) Both'
|
||||||
|
do { $selection = Read-Host 'Selection [1-3]' } while ($selection -notin @('1','2','3'))
|
||||||
|
$Client = @{'1'='Codex'; '2'='Claude'; '3'='Both'}[$selection]
|
||||||
|
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||||
|
$homePath = [Environment]::GetFolderPath('UserProfile')
|
||||||
|
$shellCommand = 'pwsh -NoProfile -ExecutionPolicy Bypass -File'
|
||||||
|
$windowsShellCommand = 'powershell -NoProfile -ExecutionPolicy Bypass -File'
|
||||||
|
if ([Environment]::OSVersion.Platform -eq [PlatformID]::Win32NT) { $shellCommand = $windowsShellCommand }
|
||||||
|
$targets = @()
|
||||||
|
if ($Client -in @('Codex','Both')) { $targets += @{ Source=(Join-Path $generated "codex-$platform"); Destination=(Join-Path $homePath '.codex') } }
|
||||||
|
if ($Client -in @('Codex','Both')) { $targets += @{ Source=(Join-Path $generated "codex-$platform/skills"); Destination=(Join-Path $homePath '.agents/skills') } }
|
||||||
|
if ($Client -in @('Claude','Both')) { $targets += @{ Source=(Join-Path $generated "claude-$platform"); Destination=(Join-Path $homePath '.claude') } }
|
||||||
|
foreach ($item in $targets) {
|
||||||
|
$backupRoot = Join-Path $item.Destination 'backups'
|
||||||
|
Get-ChildItem $item.Source -File -Recurse | ForEach-Object {
|
||||||
|
$relative = $_.FullName.Substring($item.Source.Length).TrimStart([char[]]@('\','/'))
|
||||||
|
$target = Join-Path $item.Destination $relative
|
||||||
|
if ($DryRun) { Write-Output "DRYRUN $($_.FullName) -> $target"; return }
|
||||||
|
if (Test-Path $target) {
|
||||||
|
$backup = Join-Path $backupRoot $relative
|
||||||
|
New-Item (Split-Path $backup -Parent) -ItemType Directory -Force | Out-Null
|
||||||
|
Copy-Item $target "$backup.$stamp" -Force
|
||||||
|
}
|
||||||
|
New-Item (Split-Path $target -Parent) -ItemType Directory -Force | Out-Null
|
||||||
|
$content = Get-Content $_.FullName -Raw
|
||||||
|
if ($content.Contains('__AI_CONFIG_ROOT__') -or $content.Contains('__HOOK_COMMAND__') -or $content.Contains('__WINDOWS_HOOK_COMMAND__')) {
|
||||||
|
$replacement = $item.Destination.Replace('\','/')
|
||||||
|
$content = $content.Replace('__AI_CONFIG_ROOT__', $replacement)
|
||||||
|
$content = $content.Replace('__HOOK_COMMAND__', $shellCommand)
|
||||||
|
$content = $content.Replace('__WINDOWS_HOOK_COMMAND__', $windowsShellCommand)
|
||||||
|
$content = $content.Replace('__HOOK_SCRIPT__', 'flashbang.ps1')
|
||||||
|
$content = $content.Replace('__WINDOWS_HOOK_SCRIPT__', 'flashbang.ps1')
|
||||||
|
[System.IO.File]::WriteAllText($target, $content, (New-Object System.Text.UTF8Encoding($false)))
|
||||||
|
}
|
||||||
|
else { Copy-Item $_.FullName $target -Force }
|
||||||
|
Write-Output "INSTALL $target"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Install-ConfiguredPlugins -RepositoryRoot $root -Client $Client -DryRun:$DryRun -Update
|
||||||
|
Write-Output ($(if ($DryRun) { 'PASS install dry-run' } else { 'PASS install' }))
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
dry_run=false
|
||||||
|
for argument in "$@"; do
|
||||||
|
[ "$argument" != "--dry-run" ] || dry_run=true
|
||||||
|
done
|
||||||
|
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
. "$root/scripts/lib/plugins.sh"
|
||||||
|
bash "$root/scripts/build.sh"
|
||||||
|
generated="$root/generated"
|
||||||
|
[ -d "$generated" ] || { printf 'Generated output is missing after a successful build.\n' >&2; exit 1; }
|
||||||
|
|
||||||
|
printf 'Select target platform:\n1) Windows\n2) Linux\n'
|
||||||
|
while :; do
|
||||||
|
read -r -p 'Selection [1-2] ' platform_selection
|
||||||
|
case "$platform_selection" in 1) platform=windows; break;; 2) platform=linux; break;; esac
|
||||||
|
done
|
||||||
|
printf 'Select installation target:\n1) Codex\n2) Claude\n3) Both\n'
|
||||||
|
while :; do
|
||||||
|
read -r -p 'Selection [1-3] ' selection
|
||||||
|
case "$selection" in 1|2|3) break;; esac
|
||||||
|
done
|
||||||
|
|
||||||
|
stamp=$(date +%Y%m%d-%H%M%S)
|
||||||
|
home_path=${HOME:?HOME is required}
|
||||||
|
targets=()
|
||||||
|
case "$selection" in
|
||||||
|
1|3) targets+=("$generated/codex-$platform|$home_path/.codex" "$generated/codex-$platform/skills|$home_path/.agents/skills");;
|
||||||
|
esac
|
||||||
|
case "$selection" in
|
||||||
|
2|3) targets+=("$generated/claude-$platform|$home_path/.claude");;
|
||||||
|
esac
|
||||||
|
|
||||||
|
for target_pair in "${targets[@]}"; do
|
||||||
|
source=${target_pair%%|*}
|
||||||
|
destination=${target_pair#*|}
|
||||||
|
while IFS= read -r -d '' source_file; do
|
||||||
|
relative=${source_file#"$source/"}
|
||||||
|
target="$destination/$relative"
|
||||||
|
if "$dry_run"; then printf 'DRYRUN %s -> %s\n' "$source_file" "$target"; continue; fi
|
||||||
|
if [ -f "$target" ]; then
|
||||||
|
backup="$destination/backups/$relative.$stamp"
|
||||||
|
mkdir -p "$(dirname -- "$backup")"
|
||||||
|
cp "$target" "$backup"
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname -- "$target")"
|
||||||
|
content=$(<"$source_file")
|
||||||
|
if [[ "$content" == *'__AI_CONFIG_ROOT__'* || "$content" == *'__HOOK_COMMAND__'* || "$content" == *'__WINDOWS_HOOK_COMMAND__'* ]]; then
|
||||||
|
replacement=$destination
|
||||||
|
if command -v cygpath >/dev/null; then replacement=$(cygpath -m "$destination"); fi
|
||||||
|
content=${content//__AI_CONFIG_ROOT__/$replacement}
|
||||||
|
content=${content//__HOOK_COMMAND__/bash}
|
||||||
|
content=${content//__WINDOWS_HOOK_COMMAND__/bash}
|
||||||
|
content=${content//__HOOK_SCRIPT__/flashbang.sh}
|
||||||
|
content=${content//__WINDOWS_HOOK_SCRIPT__/flashbang.sh}
|
||||||
|
printf '%s' "$content" > "$target"
|
||||||
|
else
|
||||||
|
cp "$source_file" "$target"
|
||||||
|
fi
|
||||||
|
printf 'INSTALL %s\n' "$target"
|
||||||
|
done < <(find "$source" -type f -print0)
|
||||||
|
done
|
||||||
|
|
||||||
|
client_selection=both
|
||||||
|
case "$selection" in
|
||||||
|
1) client_selection=codex;;
|
||||||
|
2) client_selection=claude;;
|
||||||
|
esac
|
||||||
|
install_configured_plugins "$root" "$client_selection" "$dry_run" true
|
||||||
|
|
||||||
|
if "$dry_run"; then printf 'PASS install dry-run\n'; else printf 'PASS install\n'; fi
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
function Invoke-PluginCommand {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$Command,
|
||||||
|
[Parameter(Mandatory=$true)][string[]]$Arguments,
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
$display = "$Command $($Arguments -join ' ')"
|
||||||
|
if ($DryRun) { Write-Output "DRYRUN $display"; return }
|
||||||
|
& $Command @Arguments
|
||||||
|
if ($LASTEXITCODE -ne 0) { throw "Plugin command failed: $display" }
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ConfiguredPluginEntries {
|
||||||
|
param([Parameter(Mandatory=$true)][string]$RepositoryRoot)
|
||||||
|
$manifest = Join-Path $RepositoryRoot 'adapters/plugins.tsv'
|
||||||
|
if (-not (Test-Path -LiteralPath $manifest)) { throw "Plugin manifest is missing: $manifest" }
|
||||||
|
Import-Csv -LiteralPath $manifest -Delimiter ([char]9) | ForEach-Object {
|
||||||
|
$_.claude_plugin = $_.claude_plugin.Trim()
|
||||||
|
$_.codex_plugin = $_.codex_plugin.Trim()
|
||||||
|
$_
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-InstalledPlugins {
|
||||||
|
param([ValidateSet('Claude','Codex')][string]$Client)
|
||||||
|
if ($Client -eq 'Claude') {
|
||||||
|
if (-not (Get-Command claude -ErrorAction SilentlyContinue)) { throw 'Claude Code CLI is required to manage Claude plugins.' }
|
||||||
|
$items = (claude plugin list --json | Out-String) | ConvertFrom-Json
|
||||||
|
return @($items)
|
||||||
|
}
|
||||||
|
if (-not (Get-Command codex -ErrorAction SilentlyContinue)) { throw 'Codex CLI is required to manage Codex plugins.' }
|
||||||
|
$items = (codex plugin list --json | Out-String) | ConvertFrom-Json
|
||||||
|
return @($items.installed)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Ensure-CodexMarketplace {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$Source,
|
||||||
|
[Parameter(Mandatory=$true)][string]$MarketplaceName,
|
||||||
|
[switch]$DryRun
|
||||||
|
)
|
||||||
|
if ($DryRun) {
|
||||||
|
Write-Output "DRYRUN codex plugin marketplace add $Source"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$previousErrorActionPreference = $ErrorActionPreference
|
||||||
|
try {
|
||||||
|
$ErrorActionPreference = 'Continue'
|
||||||
|
$output = @(& codex plugin marketplace add $Source 2>&1)
|
||||||
|
$exitCode = $LASTEXITCODE
|
||||||
|
} catch {
|
||||||
|
$output = @($_)
|
||||||
|
$exitCode = 1
|
||||||
|
} finally {
|
||||||
|
$ErrorActionPreference = $previousErrorActionPreference
|
||||||
|
}
|
||||||
|
$output | ForEach-Object { Write-Output $_ }
|
||||||
|
if ($exitCode -eq 0) { return }
|
||||||
|
|
||||||
|
$text = $output -join [Environment]::NewLine
|
||||||
|
if ($text -match 'marketplace .* already added from a different source') {
|
||||||
|
Write-Output "WARN Codex marketplace '$MarketplaceName' exists from another source; replacing it with: $Source"
|
||||||
|
Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','remove',$MarketplaceName)
|
||||||
|
Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','add',$Source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw "Plugin command failed: codex plugin marketplace add $Source"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-ConfiguredPlugins {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory=$true)][string]$RepositoryRoot,
|
||||||
|
[ValidateSet('Codex','Claude','Both')][Parameter(Mandatory=$true)][string]$Client,
|
||||||
|
[switch]$DryRun,
|
||||||
|
[switch]$Update
|
||||||
|
)
|
||||||
|
$entries = @(Get-ConfiguredPluginEntries -RepositoryRoot $RepositoryRoot)
|
||||||
|
$clients = if ($Client -eq 'Both') { @('Claude','Codex') } else { @($Client) }
|
||||||
|
|
||||||
|
foreach ($selectedClient in $clients) {
|
||||||
|
$installed = if ($DryRun) { @() } else { @(Get-InstalledPlugins -Client $selectedClient) }
|
||||||
|
foreach ($entry in $entries) {
|
||||||
|
$marketplace = if ($selectedClient -eq 'Claude') { $entry.claude_marketplace.Trim() } else { $entry.codex_marketplace.Trim() }
|
||||||
|
if ($marketplace -eq '-') { $marketplace = '' }
|
||||||
|
$plugin = if ($selectedClient -eq 'Claude') { $entry.claude_plugin } else { $entry.codex_plugin }
|
||||||
|
if ([string]::IsNullOrWhiteSpace($plugin)) { throw ('Plugin selector missing for {0}: {1}' -f $selectedClient, $entry.name) }
|
||||||
|
|
||||||
|
$installedItem = if ($selectedClient -eq 'Claude') { $installed | Where-Object { $_.id -eq $plugin } | Select-Object -First 1 } else { $installed | Where-Object { $_.pluginId -eq $plugin } | Select-Object -First 1 }
|
||||||
|
if ($Update -and $selectedClient -eq 'Claude' -and $null -ne $installedItem) {
|
||||||
|
Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','update',$plugin) -DryRun:$DryRun
|
||||||
|
if (-not $installedItem.enabled) {
|
||||||
|
Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (-not $Update -and $null -ne $installedItem) {
|
||||||
|
if ($selectedClient -eq 'Claude' -and -not $installedItem.enabled) {
|
||||||
|
Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
Write-Output "PASS $selectedClient plugin already installed: $plugin"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($selectedClient -eq 'Claude') {
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($marketplace)) {
|
||||||
|
Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','marketplace','add',$marketplace) -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','install',$plugin,'--scope','user') -DryRun:$DryRun
|
||||||
|
} else {
|
||||||
|
if (-not [string]::IsNullOrWhiteSpace($marketplace) -and $marketplace -ne 'openai-curated-remote') {
|
||||||
|
$marketplaceName = ($plugin -split '@')[-1]
|
||||||
|
Ensure-CodexMarketplace -Source $marketplace -MarketplaceName $marketplaceName -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','add',$plugin) -DryRun:$DryRun
|
||||||
|
}
|
||||||
|
Write-Output "PASS $selectedClient plugin ensured: $plugin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
plugin_installed() {
|
||||||
|
local client=$1 plugin=$2
|
||||||
|
if [ "$client" = claude ]; then
|
||||||
|
claude plugin list --json 2>/dev/null | grep -Fq "\"id\": \"$plugin\""
|
||||||
|
else
|
||||||
|
codex plugin list --json 2>/dev/null | grep -Fq "\"pluginId\": \"$plugin\""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
run_plugin_command() {
|
||||||
|
local dry_run=$1
|
||||||
|
shift
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
printf 'DRYRUN %s\n' "$*"
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_codex_marketplace() {
|
||||||
|
local dry_run=$1 source=$2 marketplace_name=$3 output
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
printf 'DRYRUN codex plugin marketplace add %s\n' "$source"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if output=$(codex plugin marketplace add "$source" 2>&1); then
|
||||||
|
[ -z "$output" ] || printf '%s\n' "$output"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
printf '%s\n' "$output" >&2
|
||||||
|
if printf '%s\n' "$output" | grep -Eiq 'marketplace .* already added from a different source'; then
|
||||||
|
printf "WARN Codex marketplace '%s' exists from another source; replacing it with: %s\n" "$marketplace_name" "$source"
|
||||||
|
codex plugin marketplace remove "$marketplace_name"
|
||||||
|
codex plugin marketplace add "$source"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
install_configured_plugins() {
|
||||||
|
local root=$1 client_selection=$2 dry_run=$3 update=$4
|
||||||
|
local manifest="$root/adapters/plugins.tsv"
|
||||||
|
[ -f "$manifest" ] || { printf 'Plugin manifest is missing: %s\n' "$manifest" >&2; return 1; }
|
||||||
|
|
||||||
|
local clients=()
|
||||||
|
case "$client_selection" in
|
||||||
|
codex) clients=(codex);;
|
||||||
|
claude) clients=(claude);;
|
||||||
|
both) clients=(claude codex);;
|
||||||
|
*) printf 'Unknown plugin client selection: %s\n' "$client_selection" >&2; return 1;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local client name claude_marketplace claude_plugin codex_marketplace codex_plugin marketplace plugin
|
||||||
|
while IFS=$'\t' read -r name claude_marketplace claude_plugin codex_marketplace codex_plugin; do
|
||||||
|
[ "$name" = name ] && continue
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
[ "$claude_marketplace" = - ] && claude_marketplace=
|
||||||
|
[ "$codex_marketplace" = - ] && codex_marketplace=
|
||||||
|
for client in "${clients[@]}"; do
|
||||||
|
if [ "$client" = claude ]; then marketplace=$claude_marketplace; plugin=$claude_plugin; else marketplace=$codex_marketplace; plugin=$codex_plugin; fi
|
||||||
|
[ -n "$plugin" ] || { printf 'Plugin selector missing for %s: %s\n' "$client" "$name" >&2; return 1; }
|
||||||
|
if [ "$dry_run" = false ] && plugin_installed "$client" "$plugin"; then
|
||||||
|
if [ "$update" = true ] && [ "$client" = claude ]; then
|
||||||
|
run_plugin_command "$dry_run" claude plugin update "$plugin"
|
||||||
|
run_plugin_command "$dry_run" claude plugin enable "$plugin"
|
||||||
|
elif [ "$update" = true ] && [ "$client" = codex ]; then
|
||||||
|
run_plugin_command "$dry_run" codex plugin add "$plugin"
|
||||||
|
else
|
||||||
|
printf 'PASS %s plugin already installed: %s\n' "$client" "$plugin"
|
||||||
|
fi
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ "$client" = claude ]; then
|
||||||
|
[ -z "$marketplace" ] || run_plugin_command "$dry_run" claude plugin marketplace add "$marketplace"
|
||||||
|
run_plugin_command "$dry_run" claude plugin install "$plugin" --scope user
|
||||||
|
else
|
||||||
|
if [ -n "$marketplace" ] && [ "$marketplace" != openai-curated-remote ]; then
|
||||||
|
marketplace_name=${plugin##*@}
|
||||||
|
ensure_codex_marketplace "$dry_run" "$marketplace" "$marketplace_name"
|
||||||
|
fi
|
||||||
|
run_plugin_command "$dry_run" codex plugin add "$plugin"
|
||||||
|
fi
|
||||||
|
printf 'PASS %s plugin ensured: %s\n' "$client" "$plugin"
|
||||||
|
done
|
||||||
|
done < "$manifest"
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
$root = Split-Path $PSScriptRoot -Parent
|
||||||
|
$powerShellHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.ps1') -Raw
|
||||||
|
$bashHook = Get-Content -LiteralPath (Join-Path $root 'shared/hooks/flashbang.sh') -Raw
|
||||||
|
if ($powerShellHook -notmatch '\$HoldMs\s*=\s*250' -or $powerShellHook -notmatch '\$FadeMs\s*=\s*250' -or ([regex]::Matches($bashHook, 'default=250').Count -lt 2)) { throw 'Flashbang defaults must total 500 milliseconds on Windows and Linux.' }
|
||||||
|
$statusInput = '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"' + $root.Replace('\','\\') + '","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}'
|
||||||
|
$branch = & git -C $root branch --show-current
|
||||||
|
$statusOutput = $statusInput | & (Join-Path $root 'shared/statusline/statusline.ps1')
|
||||||
|
if ($statusOutput -ne "Model: Test Model | Effort: high | Repo: TestRepo | Branch: $branch | Max Context: 200000 | Used Context: 9% | Used Tokens: 16700") { throw 'PowerShell status line output is incorrect.' }
|
||||||
|
foreach ($platform in @('windows','linux')) {
|
||||||
|
foreach ($client in @('codex','claude')) {
|
||||||
|
$package = Join-Path $root "generated/$client-$platform"
|
||||||
|
$file = if ($client -eq 'codex') { 'config.toml' } else { 'settings.json' }
|
||||||
|
$content = Get-Content -LiteralPath (Join-Path $package $file) -Raw
|
||||||
|
if ($client -eq 'claude') { $settings = $content | ConvertFrom-Json }
|
||||||
|
if (@(Get-ChildItem "$package/agents" -File).Count -ne 5) { throw "Missing agents in $package" }
|
||||||
|
if (@(Get-ChildItem "$package/skills" -Filter SKILL.md -Recurse).Count -ne 22) { throw "Missing skills in $package" }
|
||||||
|
$expected = if ($platform -eq 'windows') { 'powershell .*flashbang.ps1' } else { 'bash .*flashbang.sh' }
|
||||||
|
if ($content -notmatch $expected -or $content -match '__HOOK_|__WINDOWS_HOOK_') { throw "Incorrect platform command in $package" }
|
||||||
|
if ($platform -eq 'windows' -and $content -match 'WindowStyle\s+Hidden') { throw "Windows hook hides the terminal in $package" }
|
||||||
|
if ($client -eq 'codex') {
|
||||||
|
$events = @([regex]::Matches($content, '(?m)^\[\[hooks\.([^.\]]+)\]\]\s*$') | ForEach-Object { $_.Groups[1].Value })
|
||||||
|
if ($events.Count -ne 1 -or $events[0] -ne 'Stop' -or $content -match 'flashbang-if-input' -or $content -match '(?m)^async\s*=\s*true\s*$') { throw "Codex finish hook is incorrect in $package" }
|
||||||
|
if ($content -notmatch 'approvals_reviewer\s*=\s*"auto_review"') { throw "Codex auto review is missing in $package" }
|
||||||
|
if ($content -notmatch 'status_line\s*=\s*\["model", "reasoning", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"\]') { throw "Codex status line is incorrect in $package" }
|
||||||
|
} elseif (@($settings.hooks.PSObject.Properties.Name).Count -ne 1 -or $settings.hooks.PSObject.Properties.Name -ne 'Stop' -or $content -match 'flashbang-if-input' -or $settings.hooks.Stop[0].hooks[0].async) {
|
||||||
|
throw "Claude finish hook is incorrect in $package"
|
||||||
|
} elseif ($content -notmatch '"defaultMode"\s*:\s*"auto"') {
|
||||||
|
throw "Claude auto permission mode is missing in $package"
|
||||||
|
} elseif (-not (Test-Path -LiteralPath (Join-Path $package "statusline/statusline.$(if ($platform -eq 'windows') { 'ps1' } else { 'sh' })")) -or $settings.statusLine.command -notmatch "statusline\.$(if ($platform -eq 'windows') { 'ps1' } else { 'sh' })") {
|
||||||
|
throw "Claude status line is incorrect in $package"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($platformSelection in @('1','2')) {
|
||||||
|
foreach ($clientSelection in @('1','2','3')) {
|
||||||
|
$script:answers = [System.Collections.Generic.Queue[string]]::new()
|
||||||
|
$script:answers.Enqueue($platformSelection)
|
||||||
|
$script:answers.Enqueue($clientSelection)
|
||||||
|
function Read-Host { param($Prompt) $answers.Dequeue() }
|
||||||
|
$output = & "$PSScriptRoot/install.ps1" -DryRun
|
||||||
|
if ($output -notcontains 'PASS install dry-run') { throw 'Dry-run did not finish.' }
|
||||||
|
$platform = if ($platformSelection -eq '1') { 'windows' } else { 'linux' }
|
||||||
|
if (($output -join "`n") -notmatch "-$platform") { throw 'Incorrect selected package.' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Output 'PASS four platform packages and six PowerShell selections'
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
|
grep -Eq '\$HoldMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
|
||||||
|
grep -Eq '\$FadeMs[[:space:]]*=[[:space:]]*250' "$root/shared/hooks/flashbang.ps1"
|
||||||
|
[ "$(grep -c 'default=250' "$root/shared/hooks/flashbang.sh")" -ge 2 ]
|
||||||
|
branch=$(git -C "$root" branch --show-current)
|
||||||
|
status_output=$(printf '{"model":{"display_name":"Test Model"},"effort":{"level":"high"},"workspace":{"current_dir":"%s","repo":{"name":"TestRepo"}},"context_window":{"context_window_size":200000,"used_percentage":8.5,"total_input_tokens":15500,"total_output_tokens":1200}}' "$root" | bash "$root/shared/statusline/statusline.sh")
|
||||||
|
[ "$status_output" = "Model: Test Model | Effort: high | Repo: TestRepo | Branch: $branch | Max Context: 200000 | Used Context: 9% | Used Tokens: 16700" ]
|
||||||
|
for platform in windows linux; do
|
||||||
|
for client in codex claude; do
|
||||||
|
package="$root/generated/$client-$platform"
|
||||||
|
file=config.toml
|
||||||
|
[ "$client" != claude ] || file=settings.json
|
||||||
|
test -f "$package/$file"
|
||||||
|
test "$(find "$package/agents" -type f | wc -l)" -eq 5
|
||||||
|
test "$(find "$package/skills" -name SKILL.md | wc -l)" -eq 22
|
||||||
|
if [ "$platform" = windows ]; then
|
||||||
|
grep -q 'powershell .*flashbang.ps1' "$package/$file"
|
||||||
|
! grep -Eq 'WindowStyle[[:space:]]+Hidden' "$package/$file"
|
||||||
|
else
|
||||||
|
grep -q 'bash .*flashbang.sh' "$package/$file"
|
||||||
|
fi
|
||||||
|
if [ "$client" = codex ]; then
|
||||||
|
[ "$(sed -n 's/^\[\[hooks\.\([^].]*\)\]\]$/\1/p' "$package/$file")" = Stop ]
|
||||||
|
! grep -q 'flashbang-if-input' "$package/$file"
|
||||||
|
! grep -Eq '^async[[:space:]]*=[[:space:]]*true[[:space:]]*$' "$package/$file"
|
||||||
|
grep -q 'approvals_reviewer[[:space:]]*=[[:space:]]*"auto_review"' "$package/$file"
|
||||||
|
grep -Fq 'status_line = ["model", "reasoning", "project-name", "git-branch", "context-window-size", "context-used", "used-tokens"]' "$package/$file"
|
||||||
|
else
|
||||||
|
[ "$(awk '/^ "hooks": \{/{inside=1; next} inside && /^ \}/{inside=0} inside && /^ "[^"]+": \[$/{gsub(/^ "|": \[$/, ""); print}' "$package/$file")" = Stop ]
|
||||||
|
! grep -q 'flashbang-if-input' "$package/$file"
|
||||||
|
! grep -q '"async"[[:space:]]*:[[:space:]]*true' "$package/$file"
|
||||||
|
grep -q '"defaultMode"[[:space:]]*:[[:space:]]*"auto"' "$package/$file"
|
||||||
|
statusline_extension=sh
|
||||||
|
[ "$platform" != windows ] || statusline_extension=ps1
|
||||||
|
test -f "$package/statusline/statusline.$statusline_extension"
|
||||||
|
grep -q "statusline.$statusline_extension" "$package/$file"
|
||||||
|
fi
|
||||||
|
if grep -Eq '__HOOK_|__WINDOWS_HOOK_' "$package/$file"; then exit 1; fi
|
||||||
|
done
|
||||||
|
done
|
||||||
|
for platform_selection in 1 2; do
|
||||||
|
for client_selection in 1 2 3; do
|
||||||
|
output=$(printf '%s\n%s\n' "$platform_selection" "$client_selection" | bash "$root/scripts/install.sh" --dry-run)
|
||||||
|
[[ "$output" == *'PASS install dry-run'* ]]
|
||||||
|
platform=windows
|
||||||
|
[ "$platform_selection" != 2 ] || platform=linux
|
||||||
|
[[ "$output" == *"-$platform/"* ]]
|
||||||
|
done
|
||||||
|
done
|
||||||
|
printf 'PASS four platform packages and six Bash selections\n'
|
||||||
|
|
||||||
Reference in New Issue
Block a user