Last active
February 1, 2026 03:30
-
-
Save cowwoc/be4893de4bed8f3d819f3bc91a889a7a to your computer and use it in GitHub Desktop.
Claude Code clipboard sync installer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Installs Claude Code clipboard sync for Windows Terminal + WSL2 | |
| .DESCRIPTION | |
| This script: | |
| 1. Creates the clipboard watcher PowerShell script | |
| 2. Creates a VBS wrapper for hidden execution | |
| 3. Creates a startup shortcut | |
| 4. Configures Windows Terminal keybindings | |
| .NOTES | |
| Run as: powershell -ExecutionPolicy Bypass -File install-claude-clipboard-sync.ps1 | |
| #> | |
| param( | |
| [string]$WslDistro = "Ubuntu", | |
| [string]$WslUser = $env:USERNAME.ToLower(), | |
| [string]$ClaudeClipboardPath = "/home/node/clipboard/screenshot.png", | |
| [switch]$Uninstall | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| # Paths | |
| $scriptPath = "$env:USERPROFILE\Documents\claude-sync-clipboard.ps1" | |
| $vbsPath = "$env:USERPROFILE\Documents\claude-sync-clipboard.vbs" | |
| $startupLink = "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\claude-sync-clipboard.lnk" | |
| $wtSettingsPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json" | |
| $wtPreviewSettingsPath = "$env:LOCALAPPDATA\Packages\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\LocalState\settings.json" | |
| # Determine WSL destination path | |
| # This writes to ~/.claude/clipboard/ on WSL host, which Docker maps to /home/node/.claude/clipboard/ inside container | |
| $wslDestPath = "\\wsl$\$WslDistro\home\$WslUser\.claude\clipboard\screenshot.png" | |
| function Write-Status { | |
| param([string]$Message, [string]$Type = "Info") | |
| $color = switch ($Type) { | |
| "Success" { "Green" } | |
| "Warning" { "Yellow" } | |
| "Error" { "Red" } | |
| default { "Cyan" } | |
| } | |
| Write-Host "[$Type] " -ForegroundColor $color -NoNewline | |
| Write-Host $Message | |
| } | |
| function Stop-ClipboardWatcher { | |
| $processes = Get-Process -Name powershell, pwsh -ErrorAction SilentlyContinue | | |
| Where-Object { $_.MainWindowTitle -eq "" } | | |
| ForEach-Object { | |
| try { | |
| $cmdLine = (Get-CimInstance Win32_Process -Filter "ProcessId = $($_.Id)").CommandLine | |
| if ($cmdLine -match "claude-sync-clipboard") { $_ } | |
| } catch { } | |
| } | |
| foreach ($proc in $processes) { | |
| Write-Status "Stopping existing clipboard watcher (PID: $($proc.Id))..." "Warning" | |
| Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue | |
| } | |
| } | |
| function Install-ClipboardWatcher { | |
| Write-Status "Creating clipboard watcher script..." | |
| $clipboardWatcherScript = @" | |
| Add-Type -WarningAction SilentlyContinue -TypeDefinition @`" | |
| using System; | |
| using System.Windows.Forms; | |
| using System.Runtime.InteropServices; | |
| using System.Drawing; | |
| using System.Drawing.Imaging; | |
| using System.IO; | |
| using System.Security.Cryptography; | |
| public class ClipboardWatcher : Form { | |
| [DllImport("user32.dll", SetLastError = true)] | |
| private static extern bool AddClipboardFormatListener(IntPtr hwnd); | |
| [DllImport("user32.dll", SetLastError = true)] | |
| private static extern bool RemoveClipboardFormatListener(IntPtr hwnd); | |
| private const int WM_CLIPBOARDUPDATE = 0x031D; | |
| private string destPath; | |
| private string lastHash = ""; | |
| public ClipboardWatcher(string dest) { | |
| destPath = dest; | |
| this.ShowInTaskbar = false; | |
| this.WindowState = FormWindowState.Minimized; | |
| this.FormBorderStyle = FormBorderStyle.None; | |
| this.Load += (s, e) => { this.Visible = false; }; | |
| AddClipboardFormatListener(this.Handle); | |
| } | |
| protected override void WndProc(ref Message m) { | |
| if (m.Msg == WM_CLIPBOARDUPDATE) { | |
| ProcessClipboard(); | |
| } | |
| base.WndProc(ref m); | |
| } | |
| private void ProcessClipboard() { | |
| try { | |
| if (Clipboard.ContainsImage()) { | |
| Image img = Clipboard.GetImage(); | |
| if (img != null) { | |
| using (MemoryStream ms = new MemoryStream()) { | |
| img.Save(ms, ImageFormat.Png); | |
| byte[] bytes = ms.ToArray(); | |
| string hash = BitConverter.ToString(MD5.Create().ComputeHash(bytes)); | |
| if (hash != lastHash) { | |
| string dir = Path.GetDirectoryName(destPath); | |
| if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); | |
| File.WriteAllBytes(destPath, bytes); | |
| lastHash = hash; | |
| } | |
| } | |
| img.Dispose(); | |
| } | |
| } else { | |
| if (File.Exists(destPath)) File.Delete(destPath); | |
| lastHash = ""; | |
| } | |
| } catch { } | |
| } | |
| protected override void OnFormClosing(FormClosingEventArgs e) { | |
| RemoveClipboardFormatListener(this.Handle); | |
| base.OnFormClosing(e); | |
| } | |
| } | |
| `"@ -ReferencedAssemblies System.Windows.Forms, System.Drawing | |
| `$dest = "$wslDestPath" | |
| `$watcher = New-Object ClipboardWatcher(`$dest) | |
| [System.Windows.Forms.Application]::Run(`$watcher) | |
| "@ | |
| $clipboardWatcherScript | Set-Content $scriptPath -Encoding UTF8 | |
| Write-Status "Created: $scriptPath" "Success" | |
| } | |
| function Install-VbsWrapper { | |
| Write-Status "Creating VBS wrapper for hidden execution..." | |
| $vbsContent = @" | |
| CreateObject("WScript.Shell").Run "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File ""$scriptPath""", 0, False | |
| "@ | |
| $vbsContent | Set-Content $vbsPath -Encoding ASCII | |
| Write-Status "Created: $vbsPath" "Success" | |
| } | |
| function Install-StartupShortcut { | |
| Write-Status "Creating startup shortcut..." | |
| $ws = New-Object -ComObject WScript.Shell | |
| $shortcut = $ws.CreateShortcut($startupLink) | |
| $shortcut.TargetPath = "wscript.exe" | |
| $shortcut.Arguments = "`"$vbsPath`"" | |
| $shortcut.Save() | |
| Write-Status "Created: $startupLink" "Success" | |
| } | |
| function Update-WindowsTerminalSettings { | |
| param([string]$settingsPath) | |
| if (-not (Test-Path $settingsPath)) { | |
| Write-Status "Windows Terminal settings not found at: $settingsPath" "Warning" | |
| return $false | |
| } | |
| Write-Status "Updating Windows Terminal settings: $settingsPath" | |
| # Backup | |
| $backupPath = "$settingsPath.backup.$(Get-Date -Format 'yyyyMMdd-HHmmss')" | |
| Copy-Item $settingsPath $backupPath | |
| Write-Status "Backup created: $backupPath" | |
| # Read and parse JSON | |
| $content = Get-Content $settingsPath -Raw | |
| # Remove comments for parsing (Windows Terminal allows comments) | |
| $jsonContent = $content -replace '(?m)^\s*//.*$', '' -replace '/\*[\s\S]*?\*/', '' | |
| try { | |
| $settings = $jsonContent | ConvertFrom-Json | |
| } catch { | |
| Write-Status "Failed to parse settings.json: $_" "Error" | |
| return $false | |
| } | |
| # Define our action | |
| $actionId = "User.SendClaudeClipboardPath" | |
| $esc = [char]0x1b # Actual ESC character - ConvertTo-Json will escape it as \u001b | |
| $newAction = @{ | |
| command = @{ | |
| action = "sendInput" | |
| input = "${esc}[200~$ClaudeClipboardPath${esc}[201~" | |
| } | |
| id = $actionId | |
| } | |
| $newKeybinding = @{ | |
| id = $actionId | |
| keys = "alt+v" | |
| } | |
| # Handle actions array | |
| if (-not $settings.PSObject.Properties['actions']) { | |
| $settings | Add-Member -NotePropertyName 'actions' -NotePropertyValue @() | |
| } | |
| # Remove existing action with same id | |
| $settings.actions = @($settings.actions | Where-Object { $_.id -ne $actionId }) | |
| $settings.actions += $newAction | |
| # Handle keybindings array | |
| if (-not $settings.PSObject.Properties['keybindings']) { | |
| $settings | Add-Member -NotePropertyName 'keybindings' -NotePropertyValue @() | |
| } | |
| # Remove existing keybinding with same id | |
| $settings.keybindings = @($settings.keybindings | Where-Object { $_.id -ne $actionId }) | |
| $settings.keybindings += $newKeybinding | |
| # Write back | |
| $settings | ConvertTo-Json -Depth 100 | Set-Content $settingsPath -Encoding UTF8 | |
| Write-Status "Windows Terminal settings updated" "Success" | |
| return $true | |
| } | |
| function Uninstall-All { | |
| Write-Status "Uninstalling Claude clipboard sync..." "Warning" | |
| Stop-ClipboardWatcher | |
| if (Test-Path $scriptPath) { Remove-Item $scriptPath -Force; Write-Status "Removed: $scriptPath" } | |
| if (Test-Path $vbsPath) { Remove-Item $vbsPath -Force; Write-Status "Removed: $vbsPath" } | |
| if (Test-Path $startupLink) { Remove-Item $startupLink -Force; Write-Status "Removed: $startupLink" } | |
| Write-Status "Uninstall complete. Windows Terminal settings were not modified." "Success" | |
| Write-Status "Manually remove the 'User.SendClaudeClipboardPath' action and keybinding if desired." "Info" | |
| } | |
| function Show-Summary { | |
| Write-Host "" | |
| Write-Host "========================================" -ForegroundColor Cyan | |
| Write-Host " Installation Complete!" -ForegroundColor Green | |
| Write-Host "========================================" -ForegroundColor Cyan | |
| Write-Host "" | |
| Write-Host "Configuration:" -ForegroundColor Yellow | |
| Write-Host " WSL Distro: $WslDistro" | |
| Write-Host " WSL User: $WslUser" | |
| Write-Host " WSL host path: \\wsl`$\$WslDistro\home\$WslUser\.claude\clipboard\screenshot.png" | |
| Write-Host " Docker path: $ClaudeClipboardPath (via volume mount)" | |
| Write-Host "" | |
| Write-Host "Usage:" -ForegroundColor Yellow | |
| Write-Host " 1. Copy a screenshot to clipboard (Win+Shift+S, then select area)" | |
| Write-Host " 2. In Windows Terminal with Claude Code, press Alt+V" | |
| Write-Host " 3. The path to the screenshot will be pasted" | |
| Write-Host "" | |
| Write-Host "To start now without reboot:" -ForegroundColor Yellow | |
| Write-Host " wscript.exe `"$vbsPath`"" | |
| Write-Host "" | |
| Write-Host "To uninstall:" -ForegroundColor Yellow | |
| Write-Host " powershell -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`" -Uninstall" | |
| Write-Host "" | |
| } | |
| # Main | |
| Write-Host "" | |
| Write-Host "Claude Code Clipboard Sync Installer" -ForegroundColor Cyan | |
| Write-Host "=====================================" -ForegroundColor Cyan | |
| Write-Host "" | |
| if ($Uninstall) { | |
| Uninstall-All | |
| exit 0 | |
| } | |
| # Stop any existing watcher | |
| Stop-ClipboardWatcher | |
| # Install components | |
| Install-ClipboardWatcher | |
| Install-VbsWrapper | |
| Install-StartupShortcut | |
| # Update Windows Terminal settings | |
| $wtUpdated = $false | |
| if (Test-Path $wtSettingsPath) { | |
| $wtUpdated = Update-WindowsTerminalSettings -settingsPath $wtSettingsPath | |
| } | |
| if (Test-Path $wtPreviewSettingsPath) { | |
| $wtUpdated = (Update-WindowsTerminalSettings -settingsPath $wtPreviewSettingsPath) -or $wtUpdated | |
| } | |
| if (-not $wtUpdated) { | |
| Write-Status "Could not find Windows Terminal settings. Manual configuration required." "Warning" | |
| Write-Host "" | |
| Write-Host "Add to your Windows Terminal settings.json:" -ForegroundColor Yellow | |
| Write-Host @" | |
| "actions": [ | |
| { | |
| "command": { | |
| "action": "sendInput", | |
| "input": "\u001b[200~$ClaudeClipboardPath\u001b[201~" | |
| }, | |
| "id": "User.SendClaudeClipboardPath" | |
| } | |
| ], | |
| "keybindings": [ | |
| { | |
| "id": "User.SendClaudeClipboardPath", | |
| "keys": "alt+v" | |
| } | |
| ] | |
| "@ | |
| } | |
| # Start the watcher | |
| Write-Status "Starting clipboard watcher..." | |
| Start-Process wscript.exe -ArgumentList "`"$vbsPath`"" | |
| Write-Status "Clipboard watcher started" "Success" | |
| Show-Summary |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment