platform/windows-pc/SKILL.md

name: windows-pc description: >- Drive James's Windows PC (supedupsilly) from this Mac: shell work over SSH (specs, winget installs, git, Unreal Windows packaging, files, services) and desktop work through the C:\desk agent (screenshots, mouse, keyboard, window focus) once it is installed. Use for any task that has to run or be seen on the PC. Not for the PC's own Claude Code session's work; coordinate with it on the Linear issue instead of both driving the same desktop.

windows-pc

The PC is supedupsilly: Windows 11 Pro, i9-14900KF (24C/32T), 32 GB, RTX 4080 SUPER, 2560×1440, ~675 GB free on C:. User volpe, logged in on the console session. Reach it as ssh volpe@supedupsilly (Tailscale 100.108.214.60; LAN 192.168.4.123). Key auth from this Mac's ~/.ssh/id_ed25519 is installed in C:\Users\volpe\.ssh\authorized_keys. Tracker home: VUH-1194.

Shell work: pc.sh

~/.claude/skills/windows-pc/pc.sh 'Get-PSDrive C | Select-Object Free,Used'
~/.claude/skills/windows-pc/pc.sh -f setup.ps1
PC_TIMEOUT=1800 ~/.claude/skills/windows-pc/pc.sh 'winget install --id Git.Git --silent --accept-package-agreements --accept-source-agreements'

pc.sh ships the PowerShell text as -EncodedCommand, so write ordinary PowerShell: $vars, pipes, quotes and backslashes all survive. Plain ssh volpe@supedupsilly '<cmd>' lands in cmd.exe, not PowerShell; anything beyond whoami/ver/dir wants the wrapper. Output has \r stripped and the CLIXML progress noise filtered.

Probe the toolchain before assuming its state; installs change it:

& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -property displayName,installationVersion
Get-ChildItem 'C:\Program Files\Epic Games'          # engines appear as UE_5.x
Get-Command git,python -ErrorAction SilentlyContinue | Select-Object Source
query session                                        # console must show volpe Active for desktop work

Desktop work: desk.sh

SSH sessions have no screen. The bridge is a scheduled task, desk-agent, registered with an Interactive logon for volpe so it runs inside the console session; it polls C:\desk\jobs\*.ps1, runs each with C:\desk\lib.ps1 loaded, and writes C:\desk\done\<job>.out. desk.sh drops a job and waits.

~/.claude/skills/windows-pc/desk.sh install        # deploy lib.ps1 + agent.ps1, register and start the task
~/.claude/skills/windows-pc/desk.sh 'Windows'      # every window: process, x,y, w×h, title
~/.claude/skills/windows-pc/desk.sh shot out.png   # full-screen PNG pulled to this Mac; Read it
~/.claude/skills/windows-pc/desk.sh 'Focus "Epic Games"; Click 1200 700; Type "5.8"; Key "{ENTER}"'

lib.ps1 must define: Screenshot [path], MouseMove x y, Click x y [right] [times], Scroll n, Type text and Key keys (SendKeys syntax: {ENTER}, ^c, %{F4}), Windows, Focus titlePart, Launch path [args]. Implementation notes for whoever writes it: call SetProcessDPIAware first or screenshots and clicks land at 2/3 scale; System.Drawing CopyFromScreen for shots, user32 SetCursorPos/mouse_event for the mouse, SendKeys.SendWait for typing. The task needs ExecutionTimeLimit zero and MultipleInstances IgnoreNew, RunLevel Limited (no UAC), trigger AtLogOn so it survives reboots.

Always shot before and after a click sequence and Read both; coordinates are screen pixels at 2560×1440, and a wrong click on a live desktop is not undone by the next one. If the console shows Disc or a lock screen, the agent has no input to inject: ask James to unlock rather than retrying.

Long jobs

The SSH session dies with this Mac's command and takes its children with it. For anything over a few minutes (VS install, engine build, cook, package):

Start-Process -FilePath powershell -ArgumentList '-NoProfile','-File','C:\jobs\build.ps1' -RedirectStandardOutput 'C:\jobs\build.log' -RedirectStandardError 'C:\jobs\build.err' -WindowStyle Hidden

then poll Get-Content C:\jobs\build.log -Tail 20 on later calls. Write the job's exit code into its log yourself; a detached process has no other channel back. Keep PC_TIMEOUT on the poll, not on the job.

That Start-Process line has been observed starting nothing: the job script was written, both redirect files appeared at 0 bytes and never grew, and the work never happened — silently, because a detached process that dies has no channel to report on. Check the job's own side effect (its output directory, not just its log) before believing it is running. The reliable fallback is to keep the session open from this Mac rather than detaching:

nohup env PC_TIMEOUT=3600 pc.sh -f job.ps1 > local.log 2>&1 &

It lives as long as the Mac-side background task and streams progress into local.log. A 4 GB LFS clone finished that way in 4.5 minutes after the detached form silently did nothing.

Gotchas that cost real attempts

  • Stop it sleeping before anything else, every session. A sleeping PC is not reachable and cannot be woken remotely as configured (see WOL below), so a lost afternoon starts here. First command on contact: powercfg -change -standby-timeout-ac 0 and powercfg -change -hibernate-timeout-ac 0, then read the active scheme back rather than trusting the exit code — powercfg /query SCHEME_CURRENT SUB_SLEEP, where Current AC Power Setting Index: 0x00000000 is never. Both read 0 as of 2026-09-10. It is per-scheme, so it reverts if the active plan changes.
  • Wake-on-LAN will not actually wake it as configured. WakeOnMagicPacket is Enabled on both adapters, but HiberbootEnabled=1 — Windows fast startup — turns a full shutdown into a hibernate that WOL does not fire from. The live adapter is also Wi-Fi (Intel AX211, C8-58-B3-2F-B9-D0); the Realtek 2.5GbE Ethernet reads Disconnected, so a magic packet aimed at 88-AE-DD-71-FE-07 reaches nothing. Until fast startup is off, waking it still means asking James.
  • Try the LAN address before Tailscale; they fail independently. On 2026-09-10 192.168.4.123 answered while 100.108.214.60 did not. PC_HOST overrides the default: PC_HOST=volpe@192.168.4.123 pc.sh '...'.
  • A new address means a new host key, and BatchMode turns that into Host key verification failed. Do not reach for StrictHostKeyChecking=no. Compare what the address offers against the key already trusted for the name and only then add it: ssh-keyscan -T 6 -t ed25519,rsa <addr> against ssh-keygen -F supedupsilly. Identical base64 on every shared key type is the same machine, and appending the scan to known_hosts is then safe.
  • Key rejected with Permission denied (publickey,...) although the key is installed. Windows sshd reads administrators_authorized_keys only while sshd_config's Match Group administrators block is active; on this PC it is commented out, so the per-user file is the one that counts. Check the block before deciding where a key goes. Any echo ... >> or Out-File in Windows PowerShell writes UTF-16, which sshd cannot parse; write keys with [IO.File]::WriteAllText($f, $k + "n", [Text.Encoding]::ASCII)`.
  • Finding the PC when the name does not resolve: arp -a is empty inside this Mac's sandbox, so sweep nc -z -G 1 192.168.4.N 22 across the /24 and read banners with nc; Windows answers SSH-2.0-OpenSSH_for_Windows_*.
  • The auto-mode classifier refuses desktop actions unless an allow rule covers them: desk.sh shot, chmod +x on these scripts and SSH loops that try several usernames all got blocked on 2026-09-09 even with James's spoken authorization. The rule that clears it, in the project's .claude/settings.local.json permissions.allow: "Bash(zsh ~/.claude/skills/windows-pc/desk.sh:*)" and "Bash(zsh ~/.claude/skills/windows-pc/pc.sh:*)". Invoke the scripts as zsh <path> so the rule matches; desk.sh install deploys desktop/lib.ps1 and desktop/agent.ps1 from this skill and (re)registers the task.
  • The build machine is not the play machine. Package on the PC, ship the ZIP; James and friends run it on their own PCs, like the Mac ZIPs.
  • Unreal has no Windows-from-Mac path (Epic's cross-compile is Windows→Linux only, and Remote Windows Deployment needs a Windows host), which is the whole reason this PC exists.

Evidence

Retain the Windows package receipt the same way as the Mac one: tested revision, four phase exits, startup log, one inspected first frame, ZIP size and sha256, on the issue with launch steps. Load grapple-native-proof for the repo's evidence rules before claiming a Windows result.