(
config: {
host: string
cwd?: string
localVersion: string
permissionMode?: string
dangerouslySkipPermissions?: boolean
extraCliArgs: string[]
remoteBin?: string
},
callbacks?: {
onProgress?: (msg: string) => void
},
)
| 33 | } |
| 34 | |
| 35 | export async function createSSHSession( |
| 36 | config: { |
| 37 | host: string |
| 38 | cwd?: string |
| 39 | localVersion: string |
| 40 | permissionMode?: string |
| 41 | dangerouslySkipPermissions?: boolean |
| 42 | extraCliArgs: string[] |
| 43 | remoteBin?: string |
| 44 | }, |
| 45 | callbacks?: { |
| 46 | onProgress?: (msg: string) => void |
| 47 | }, |
| 48 | ): Promise<SSHSession> { |
| 49 | const { host, localVersion, extraCliArgs, remoteBin } = config |
| 50 | const onProgress = callbacks?.onProgress |
| 51 | |
| 52 | let remoteBinaryPath: string |
| 53 | let defaultCwd = '/' |
| 54 | |
| 55 | if (remoteBin) { |
| 56 | onProgress?.('Using custom remote binary, skipping probe/deploy…') |
| 57 | remoteBinaryPath = remoteBin |
| 58 | logForDebugging(`[SSH] custom remoteBin: ${remoteBin}`) |
| 59 | // Quick SSH to get remote home directory for default CWD |
| 60 | try { |
| 61 | const pwdProc = Bun.spawn( |
| 62 | ['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=5', host, 'pwd'], |
| 63 | { |
| 64 | stdin: 'ignore', |
| 65 | stdout: 'pipe', |
| 66 | stderr: 'ignore', |
| 67 | }, |
| 68 | ) |
| 69 | await pwdProc.exited |
| 70 | const pwd = (await new Response(pwdProc.stdout).text()).trim() |
| 71 | if (pwd.startsWith('/')) defaultCwd = pwd |
| 72 | } catch { |
| 73 | /* use fallback */ |
| 74 | } |
| 75 | } else { |
| 76 | // 1. Probe remote host |
| 77 | const probe = await probeRemote(host, onProgress) |
| 78 | logForDebugging(`[SSH] probe result: ${JSON.stringify(probe)}`) |
| 79 | defaultCwd = probe.defaultCwd |
| 80 | |
| 81 | // 2. Deploy if binary missing or version mismatch |
| 82 | remoteBinaryPath = probe.binaryPath ?? '~/.local/bin/claude' |
| 83 | if (!probe.hasBinary || probe.remoteVersion !== localVersion) { |
| 84 | onProgress?.( |
| 85 | probe.hasBinary |
| 86 | ? `Updating remote binary (${probe.remoteVersion} → ${localVersion})…` |
| 87 | : 'Deploying binary to remote…', |
| 88 | ) |
| 89 | remoteBinaryPath = await deployBinary({ |
| 90 | host, |
| 91 | remotePlatform: probe.remotePlatform, |
| 92 | remoteArch: probe.remoteArch, |
no test coverage detected