Files
claude-mode/lib/files.ps1
T
smoidoandClaude Opus 5 4091746d4e 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>
2026-09-15 02:06:45 +03:00

99 lines
4.1 KiB
PowerShell

# 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)"
}
}