Speed up adom-desktop pull_file for large binaries

Problem

pull_file currently base64-encodes the entire file in memory and embeds it inside a single JSON WebSocket message. For files > ~10 MB, the message takes 30–120 seconds to transfer over the WS relay (base64 inflates by 33%, ndjson serialization isn't pipelined, and the relay buffers the whole frame). For 75 MB files (e.g. NXP reference manuals) it routinely times out at the Docker side. Real Adom-user reports: "why is this so slow?"

Files to look at

  • src-tauri/src/file_handler.rshandle_pull_files() reads bytes, base64s, packs into a single pull_files_result WS message
  • src-tauri/src/protocol.rspull_files_result() constructs the WS frame
  • src-tauri/src/ws_client.rs — sends the frame
  • CLI side: cli/src/commands.rspull_file() command, currently uses desktop_command(...) with default timeout (30 s)

Desired behavior

  1. Stream large files in fixed-size chunks (1 MB binary frames) over a dedicated WS message type pull_files_chunk instead of one giant base64 JSON payload.
  2. Use binary WebSocket frames (no base64) — tokio-tungstenite supports Message::Binary(Vec<u8>) natively; on the Docker CLI side, tungstenite does too. Saves the 33% inflation + JSON parse overhead.
  3. Send progress events so Docker can show "12/74 MB" instead of hanging silent.
  4. Bump CLI default timeout to a function of file size: 30 + (size_mb * 0.5) seconds, capped at 600.

Wire format proposal

// File header (text JSON)
{"type":"pull_file_start","requestId":"...","name":"foo.pdf","size":78912580,"chunks":76}

// N binary chunks, each with this 12-byte prefix:
//   [u32 BE: requestId-hash][u32 BE: chunk_index][u32 BE: chunk_size]
// followed by the raw bytes (no base64, no JSON)

// Completion
{"type":"pull_file_done","requestId":"...","sha256":"..."}

CLI streams chunks to disk as they arrive; final SHA256 verified once done is received.

Backwards compat

Keep the old pull_files text-frame path working for files < 5 MB (the common case; small CAD bundles, datasheets). Add a new pull_files_streamed command for the large-file path; CLI auto-picks based on stat-reported size.

Acceptance test

# Pull a 75 MB PDF — should complete in < 15 s, not 60+
time adom-desktop pull_file '{"filePaths":["C:\\Users\\john\\Downloads\\MCXNP184M150F70RM.pdf"],"saveTo":"/tmp"}'

Target: 75 MB in 15 s (5 MB/s effective throughput minimum). Current measured: timeout at 30 s with no completion.

Why this matters

pull_file is the canonical desktop→container transfer used by chip-fetcher (NXP/TI/ST/etc. CAD bundles), datasheet harvesting, log forwarding, and screenshot pulls. Any tool that needs the user's local Downloads folder → container hits this. Sub-10MB users don't notice; anyone grabbing reference manuals (commonly 50–100 MB) does. This is on the chip-fetcher skill's "must fix" list because the skill recommends harvesting all design-help PDFs per chip — which means hitting RM/SRM > 50 MB regularly.

Bonus: HTTP fallback path

Even simpler fix if streaming WS frames is too invasive: have adom-desktop spawn a one-shot localhost HTTP server (random port, single-use signed token), respond to the pull_file request with {"http_url": "http://desktop-ip:port/<token>/<file>"}, and let Docker curl it directly over the user's LAN. WebSockets stay for control messages; bulk data goes over HTTP/1.1 chunked transfer where it belongs.