llmtxt powershell一键部署

发布于:2025-05-01 ⋅ 阅读:(26) ⋅ 点赞:(0)


# --- Configuration - Paths provided by user ---
$ScriptInstallDir = "D:\scripts"
$ShimDir = Join-Path $ScriptInstallDir "shims"
$ScriptName = "llmtxt.ps1"
$ShimName = "llmtxt.bat"
$ScriptPath = Join-Path $ScriptInstallDir $ScriptName
$ShimPath = Join-Path $ShimDir $ShimName

# --- Create Directories ---
try {
    if (-not (Test-Path $ScriptInstallDir)) {
        Write-Host "Creating script directory: $ScriptInstallDir"
        New-Item -ItemType Directory -Path $ScriptInstallDir -Force -ErrorAction Stop | Out-Null
    } else {
        Write-Host "Script directory already exists: $ScriptInstallDir" -ForegroundColor Cyan
    }
    if (-not (Test-Path $ShimDir)) {
        Write-Host "Creating shim directory: $ShimDir"
        New-Item -ItemType Directory -Path $ShimDir -Force -ErrorAction Stop | Out-Null
    } else {
         Write-Host "Shim directory already exists: $ShimDir" -ForegroundColor Cyan
    }
} catch {
     Write-Error "Failed to create directories. Check permissions for 'D:\scripts'. Error: $($_.Exception.Message)"
     exit 1
}


# --- PowerShell Script Content (llmtxt.ps1) ---
# Final Fix Attempt: Uses Where-Object to filter *.pyc files after Get-ChildItem.
$PowerShellScriptContent = @'
<#
.SYNOPSIS
Generates a directory tree structure and concatenates the content of all text files
within a specified directory (recursively), then copies the combined text to the clipboard. Excludes *.pyc files reliably using Where-Object.

.DESCRIPTION
This script first builds a text representation of the directory tree under the target path.
Then, it gets all files recursively and filters out *.pyc files using Where-Object.
It then reads the content of the remaining files.
Finally, it combines the tree structure and all file contents into a single string
and copies it to the Windows clipboard using Set-Clipboard.

.PARAMETER Path
The directory path to process. If not provided, the current working directory is used.

.EXAMPLE
.\llmtxt.ps1 C:\MyProject
Processes the C:\MyProject directory.

.EXAMPLE
.\llmtxt.ps1
Processes the current directory.

.NOTES
Author: AI Assistant
Requires PowerShell 5.1 or later.
Handles basic permission errors.
File content is read using UTF-8 encoding.
Excludes *.pyc files by filtering with Where-Object { $_.Extension -ne '.pyc' }.
Large amounts of text might cause performance issues or clipboard limitations.
#>
param(
    [Parameter(Mandatory=$false, Position=0)]
    [ValidateScript({
        if (-not (Test-Path $_)) { throw "Path does not exist: $_" }
        if (-not (Test-Path $_ -PathType Container)) { throw "Path is not a directory: $_" }
        return $true
    })]
    [string]$Path = $PWD.Path # Default to current directory
)

# --- Configuration ---
$BranchChars = @{
    Continue = '├── '
    End      = '└── '
    Vertical = ''
    Blank    = '    '
}
$TreeHeader = "====== DIRECTORY TREE ======"
$ContentHeader = "====== FILE CONTENTS ======"
$FileSeparatorTemplate = "--- File: {0} ---"
$FileEndSeparatorTemplate = "--- End File: {0} ---`n"
# Define the extension to exclude (used in Where-Object filter)
$ExcludeExtensionString = '.pyc' # Note: This is the literal extension string now

# --- Script Body ---

try {
    $targetPath = Resolve-Path -LiteralPath $Path -ErrorAction Stop
} catch {
    Write-Error "Error resolving path '$Path': $($_.Exception.Message)"
    exit 1
}
$basePathLength = $targetPath.Path.Length

$outputBuilder = [System.Text.StringBuilder]::new()

# --- Function to Build Directory Tree Recursively ---
function Get-DirectoryTreeString {
    param(
        [Parameter(Mandatory=$true)]
        [System.IO.DirectoryInfo]$Directory,
        [Parameter(Mandatory=$true)][AllowEmptyString()][string]$Prefix,
        [Parameter(Mandatory=$true)][AllowEmptyString()][string]$BaseDirName
    )

    if ($Prefix -eq '' -and $BaseDirName -ne '') {
        $outputBuilder.AppendLine($BaseDirName) | Out-Null
    }

    $items = @()
    try {
        $items = Get-ChildItem -LiteralPath $Directory.FullName -Force -ErrorAction SilentlyContinue | Sort-Object @{Expression={$_.PSIsContainer}; Descending=$true}, Name
    } catch [System.UnauthorizedAccessException] {
        $outputBuilder.AppendLine("${Prefix}$($BranchChars.Continue)[Access Denied]") | Out-Null; return
    } catch {
        $outputBuilder.AppendLine("${Prefix}$($BranchChars.Continue)[Error Listing: $($_.Exception.Message)]") | Out-Null; return
    }

    $count = $items.Count
    for ($i = 0; $i -lt $count; $i++) {
        $item = $items[$i]
        $isLast = ($i -eq $count - 1)

        if ($isLast) {
            $connector = $BranchChars.End
            $newPrefix = $Prefix + $BranchChars.Blank
        } else {
            $connector = $BranchChars.Continue
            $newPrefix = $Prefix + $BranchChars.Vertical
        }

        $outputBuilder.AppendLine("$Prefix$connector$($item.Name)") | Out-Null

        if ($item.PSIsContainer) {
            Get-DirectoryTreeString -Directory $item -Prefix $newPrefix -BaseDirName ""
        }
    }
}

# --- Function to Get File Contents ---
function Get-FileContentsString {
    param(
        [Parameter(Mandatory=$true)]
        [string]$RootPath
    )
    $global:outputBuilder
    $global:ExcludeExtensionString # Ensure we can read the global variable

    $files = @()
    try {
        # --- FIX IS HERE: Using Where-Object for reliable filtering ---
        $files = Get-ChildItem -LiteralPath $RootPath -Recurse -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Extension -ne $ExcludeExtensionString } | Sort-Object FullName
        # Note: Removed -Exclude parameter from Get-ChildItem
    } catch [System.UnauthorizedAccessException] {
         $outputBuilder.AppendLine("`n[Access Denied while searching for files in '$RootPath']") | Out-Null; return
    } catch {
        $outputBuilder.AppendLine("`n[Error searching for files in '$RootPath': $($_.Exception.Message)]") | Out-Null; return
    }

    if ($files.Count -gt 0) {
        $outputBuilder.AppendLine("`n$ContentHeader") | Out-Null

        # This loop should now ONLY process files that passed the Where-Object filter
        foreach ($file in $files) {
            $normRootPath = $RootPath
            if (-not ($normRootPath.EndsWith('\') -or $normRootPath.EndsWith('/'))) { $normRootPath += '\' }
            $relativePath = if ($normRootPath.Length -ge $file.FullName.Length) { $file.Name } else { $file.FullName.Substring($normRootPath.Length) }

            # Print header ONLY for files that will be read
            $outputBuilder.AppendLine("`n$($FileSeparatorTemplate -f $relativePath)") | Out-Null
            try {
                $content = Get-Content -LiteralPath $file.FullName -Raw -Encoding UTF8 -ErrorAction Stop
                $outputBuilder.AppendLine($content) | Out-Null
            } catch [System.UnauthorizedAccessException] {
                $outputBuilder.AppendLine("[Access Denied reading file]") | Out-Null
            } catch [System.IO.IOException] {
                 $outputBuilder.AppendLine("[IO Error reading file: $($_.Exception.Message)]") | Out-Null
            } catch {
                $outputBuilder.AppendLine("[Error reading file (maybe not UTF-8?): $($_.Exception.Message)]") | Out-Null
            } finally {
                 $outputBuilder.AppendLine($FileEndSeparatorTemplate -f $relativePath) | Out-Null
            }
        }
    } else {
         $outputBuilder.AppendLine("`nNo suitable files found (excluding extension '$ExcludeExtensionString') in '$RootPath' or its subdirectories.") | Out-Null
    }
}

# --- Main Execution ---

Write-Host "Processing directory: $($targetPath.Path)" -ForegroundColor Cyan
Write-Host "Building output for clipboard (excluding extension '$ExcludeExtensionString')..." -ForegroundColor Cyan

# 1. Build Directory Tree
$outputBuilder.AppendLine($TreeHeader) | Out-Null
$rootDirInfo = Get-Item -LiteralPath $targetPath.Path
Get-DirectoryTreeString -Directory $rootDirInfo -Prefix "" -BaseDirName $rootDirInfo.Name

# 2. Build File Contents (will now reliably skip excluded files)
Get-FileContentsString -RootPath $targetPath.Path

# 3. Copy to Clipboard
try {
    $finalOutput = $outputBuilder.ToString().Trim()
    if ($finalOutput.Length -gt 0) {
      Set-Clipboard -Value $finalOutput -ErrorAction Stop
      Write-Host "Directory tree and file contents copied to clipboard." -ForegroundColor Green
      Write-Host "Total characters copied: $($finalOutput.Length)" -ForegroundColor Green
    } else {
       Write-Warning "No content was generated to copy to clipboard."
       Set-Clipboard -Value ""
    }

} catch {
    Write-Error "Failed to copy to clipboard: $($_.Exception.Message)"
    if ($_.Exception.Message -like '*Insufficient memory*') {
         Write-Warning "The generated text might be too large for the clipboard."
    }
    exit 1
}

exit 0 # Success
'@

# --- Save the PowerShell Script ---
try {
    $PowerShellScriptContent | Out-File -FilePath $ScriptPath -Encoding utf8 -Force -ErrorAction Stop
    Write-Host "PowerShell script saved to: $ScriptPath"
} catch {
    Write-Error "Failed to save PowerShell script '$ScriptPath'. Check permissions. Error: $($_.Exception.Message)"
    exit 1
}


# --- Create the Batch Shim Content ---
$BatchShimContent = @"
@echo off
rem This batch file executes the llmtxt.ps1 PowerShell script.
rem It passes all command line arguments (%%*) to the PowerShell script.
powershell.exe -ExecutionPolicy Bypass -NoProfile -File "$ScriptPath" %*
"@

# --- Save the Batch Shim ---
try {
    $BatchShimContent | Out-File -FilePath $ShimPath -Encoding ascii -Force -ErrorAction Stop
    Write-Host "Batch shim saved to: $ShimPath"
} catch {
    Write-Error "Failed to save Batch shim '$ShimPath'. Check permissions. Error: $($_.Exception.Message)"
}


# --- Add Shim Directory to User PATH Environment Variable ---
Write-Host "Adding '$ShimDir' to User PATH environment variable..."
try {
    $CurrentUserPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
    $PathItems = $CurrentUserPath -split ';' | Where-Object { $_ -ne '' } | Select-Object -Unique

    if ($PathItems -notcontains $ShimDir) {
        $NewPathItems = $PathItems + $ShimDir
        $NewUserPath = $NewPathItems -join ';'
        [System.Environment]::SetEnvironmentVariable('Path', $NewUserPath, 'User')
        Write-Host "'$ShimDir' added to User PATH." -ForegroundColor Green
        Write-Host "IMPORTANT: You MUST RESTART your terminal (cmd/PowerShell) for the PATH change to take effect." -ForegroundColor Yellow
    } else {
        Write-Host "'$ShimDir' is already in the User PATH." -ForegroundColor Cyan
    }
} catch {
     Write-Error "Failed to update User PATH environment variable. Error: $($_.Exception.Message)"
     Write-Warning "You might need to manually add '$ShimDir' to your User PATH environment variable."
}

Write-Host "`nSetup complete!" -ForegroundColor Green
Write-Host "Open a NEW terminal window and try typing: llmtxt"
Write-Host "Or specify a path: llmtxt D:\SomeFolder"

网站公告

今日签到

点亮在社区的每一天
去签到