How a batch is built
Collect files, stage them under a batch id, stream them over ssh into a partial remote dir, rename into place, and copy the prompt to the clipboard.
vmup’s job is a single remote directory the agent can open. The original filenames on your laptop are not that directory. This page is the pipeline, not a recipe. Commands: Upload files.
Collect
Four inputs, one staging session:
- Paths you passed, including one level of directory children.
- Picker (no args).
- Clipboard loop (
--clip). - Folder watch (
watch/--watch).
--clip and --watch cannot run together because both hold stdin.
SSH preflight starts as soon as the session exists, in parallel with collect. A long picker does not block the first ssh call. The result is awaited after collect, before upload.
Stage
A batch id is minted:
agents-YYYYMMDD-HHMMSS-<uuid>Files are copied to ~/.cache/vmup/<batchId>/ and renamed image-NN, video-NN, file-NN. Counters are per kind, so two PDFs become file-01.pdf and file-02.pdf even if an image was staged between them.
The rename is deliberate. Agents get a stable listing: images together, then other files, instead of Screenshot 2026-09-11 at 2.01.28 PM.png mixed with notes (1).pdf. You lose the original basename on the VM. If you need the name, keep local staging (--keep-local) or send a manifest yourself.
Hidden junk is dropped at collect for directories and watch. Validate then applies size, count, and type rules.
Preflight
ssh runs mkdir -p <remote_dir> && test -d <remote_dir> && echo OK with a 20s timeout. Direct mode also checks the private key exists and is not group/world-readable.
Failure here keeps local staging and skips the upload. Exit 3.
Upload
Remote layout:
rm -rfthe final dir and a sibling*.partialmkdirthe partial dir- Stream each staged file over
ssh(cat >the remote path, local file on stdin) into<remote>/<batchId>.partial/ mvpartial → final
Agents never see a half-written folder. A failed upload removes the partial dir. Local staging stays so you can look at what would have been sent.
Progress on a TTY:
⠋ Uploading 3 files 1.2 MB / 4.5 MB 4sThe right-hand size is the sum of local stats. The left-hand size is bytes ssh has accepted on stdin, including the current file, so a single large file ticks up during the transfer. JSON mode still animates stderr when it is a TTY.
After success
Local staging is deleted unless --keep-local.
vmup then:
- Client delete: a detached
sleep <ttlMinutes * 60>(minimum 60s) thenssh rm -rfthe batch. This only runs while this process’s machine stays up. - Sweeper probe:
test -x ~/vmup/.cleanup.sh. If missing, human mode warns. - Prompt:
prompt_templatewith{{remote_path}}set to the final dir plus/. - Clipboard:
clipboard_copy(promptby default, orpath, ornone) viapbcopy/wl-copy/xclip.--jsonskips this.
To delete batches from this laptop without waiting: vmup prune (expired) or vmup prune --all (every agents-* dir). Recipes: Clean up batches.
The agent does not run vmup. You paste. The default sentence is Please inspect all files in <path>.
Ctrl+C during collect deletes staging and exits 130. After upload has started, interrupt handling is the default Node behavior plus whatever OpenSSH does; the partial-dir cleanup is on upload failure, not on SIGINT mid-transfer.
Error strings
Messages the CLI prints, which code path emits them, and what to do next when SSH, staging, or an empty batch fails.
Why batches expire
TTL is in minutes. The host cron deletes expired batches. vmup prune --all deletes every remote batch now. The laptop sleep job is a backup if the VM was unreachable.