Split the Windows script into modules; drop the Arkylx Index pieces
claude-mode.ps1 was 2,407 lines. It is now 153: the help block, parameters, paths, the managed-key list, a loader, and the dispatch. The rest moved, verbatim, into eleven files under lib/ - providers, output, files, core, vault, switch, guards, health, catalogue, commands, menu - dot-sourced into the script's scope in their original order, with the same check as the bash split that every original line landed in exactly one file. Inside a module $PSScriptRoot is lib\, so the one path beside the main script (providers.json) now goes through $script:Here. install.ps1 ships lib\, clearing old modules first. The Windows suite parses every module and install.ps1 (36 checks, all green on Windows PowerShell 5.1); install.ps1 is parsed but never run, since it edits the real profile and User PATH. linux/bootstrap.sh and scripts/build-package.ps1 existed only to build and serve packages for the Arkylx Index. Both installers fetch the repository's own archive, so a push to master is the release; the two scripts, the dist/ ignore and their mentions in the docs are gone. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# lib/files.ps1 - JSON read/write for PowerShell 5.1, and restricting a file to its owner.
|
||||
#
|
||||
# Part of claude-mode.ps1, which dot-sources it into its own script scope after
|
||||
# the settings at its top. Not meant to run on its own. ASCII only: Windows
|
||||
# PowerShell 5.1 reads a .ps1 without a BOM as ANSI. $PSScriptRoot here would be
|
||||
# lib\, so paths beside the main script go through $script:Here.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# JSON helpers (PS 5.1 has no ConvertFrom-Json -AsHashtable)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function ConvertTo-DeepHashtable {
|
||||
param($InputObject)
|
||||
|
||||
if ($null -eq $InputObject) { return $null }
|
||||
|
||||
if ($InputObject -is [System.Collections.IDictionary]) {
|
||||
$h = [ordered]@{}
|
||||
foreach ($k in $InputObject.Keys) { $h[[string]$k] = ConvertTo-DeepHashtable $InputObject[$k] }
|
||||
return $h
|
||||
}
|
||||
|
||||
if ($InputObject -is [System.Management.Automation.PSCustomObject]) {
|
||||
$h = [ordered]@{}
|
||||
foreach ($p in $InputObject.PSObject.Properties) { $h[$p.Name] = ConvertTo-DeepHashtable $p.Value }
|
||||
return $h
|
||||
}
|
||||
|
||||
if ($InputObject -is [string]) { return $InputObject }
|
||||
|
||||
if ($InputObject -is [System.Collections.IEnumerable]) {
|
||||
$list = New-Object System.Collections.ArrayList
|
||||
foreach ($item in $InputObject) { [void]$list.Add((ConvertTo-DeepHashtable $item)) }
|
||||
return , $list.ToArray()
|
||||
}
|
||||
|
||||
return $InputObject
|
||||
}
|
||||
|
||||
function Read-JsonFile {
|
||||
param([string] $Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) { return $null }
|
||||
$raw = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
||||
if ([string]::IsNullOrWhiteSpace($raw)) { return $null }
|
||||
return ConvertTo-DeepHashtable (ConvertFrom-Json $raw)
|
||||
}
|
||||
|
||||
function Write-JsonFile {
|
||||
param([string] $Path, $Data)
|
||||
$dir = Split-Path -Parent $Path
|
||||
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
$json = Format-JsonPretty ($Data | ConvertTo-Json -Depth 100)
|
||||
# UTF-8 without BOM; some JSON readers choke on a BOM.
|
||||
[System.IO.File]::WriteAllText($Path, $json, (New-Object System.Text.UTF8Encoding($false)))
|
||||
}
|
||||
|
||||
# PowerShell 5.1's ConvertTo-Json indents by aligning values into a column,
|
||||
# which is valid but painful to hand-edit. Re-indent with node when available.
|
||||
function Format-JsonPretty {
|
||||
param([string] $Json)
|
||||
if ($null -eq $script:NodeExe) {
|
||||
$c = Get-Command node -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($c) { $script:NodeExe = $c.Source } else { $script:NodeExe = '' }
|
||||
}
|
||||
if (-not $script:NodeExe) { return $Json }
|
||||
|
||||
$tmp = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
[System.IO.File]::WriteAllText($tmp, $Json, (New-Object System.Text.UTF8Encoding($false)))
|
||||
$out = & $script:NodeExe -e "const fs=require('fs');process.stdout.write(JSON.stringify(JSON.parse(fs.readFileSync(process.argv[1],'utf8')),null,2)+'\n')" $tmp
|
||||
if ($out) { return (($out -join "`n") + "`n") }
|
||||
return $Json
|
||||
} catch {
|
||||
return $Json
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $tmp -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ACL hardening - restrict a file to the current user only
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Protect-FileAcl {
|
||||
param([string] $Path)
|
||||
try {
|
||||
$acl = Get-Acl -LiteralPath $Path
|
||||
if ($acl.AreAccessRulesProtected) { return }
|
||||
$acl.SetAccessRuleProtection($true, $false)
|
||||
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRule($rule) }
|
||||
$me = New-Object System.Security.Principal.NTAccount($env:USERDOMAIN, $env:USERNAME)
|
||||
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule(
|
||||
$me, 'FullControl', 'None', 'None', 'Allow')))
|
||||
Set-Acl -LiteralPath $Path -AclObject $acl
|
||||
} catch {
|
||||
Write-Warn2 "could not harden ACL on $Path : $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user