# mtp-rs CLI
`mtp-rs` includes a universal MTP file-transfer CLI. It is intentionally
device-neutral: Garmin watches, Android phones, Kindles, media players, and
cameras that expose MTP storage all use the same file commands.
## Installation
Install the binary from crates.io:
```sh
cargo install mtp-rs-cli
```
The installed binary is called `mtp-rs`.
If you are working from a checkout:
```sh
cargo run -p mtp-rs-cli -- devices
```
## Quick Start
```sh
# Show visible devices.
mtp-rs devices
# Inspect a specific device and its storages.
mtp-rs info --device SERIAL
# List files.
mtp-rs ls /
mtp-rs ls /Music --recursive
# Upload and download files.
mtp-rs put ./song.mp3 /Music/song.mp3 --replace
mtp-rs get /Music/song.mp3 ./song.mp3
# Create and remove remote objects.
mtp-rs mkdir /Upload
mtp-rs rm /Upload/old.bin --yes
# Rename, move, and copy remote objects.
mtp-rs rename /Upload/old.bin new.bin
mtp-rs mv /Upload/new.bin /Archive/new.bin
mtp-rs cp /Archive/new.bin /Archive/new-copy.bin
# Diagnose common MTP access problems.
mtp-rs doctor
```
## Global Options
| `--device SERIAL` | Select a physical device by serial number. |
| `--location HEX_OR_DECIMAL` | Select the USB location shown by `devices`. |
| `--storage INDEX_OR_ID` | Select a storage by zero-based index or storage ID. |
| `--known VID:PID` | Include a non-standard MTP device in discovery. Can be repeated. |
| `--timeout SECONDS` | Set the USB transfer timeout. Defaults to 30 seconds. |
| `--json` | Emit machine-readable JSON for every command. Transfer progress still goes to stderr. |
| `--verbose` | Include lower-level protocol or transport error detail. |
`--device` and `--location` are mutually exclusive. If neither is passed,
commands open the only visible MTP device. If multiple devices are visible,
pass one of them explicitly.
Mutating commands select the first storage only when the device exposes a
single storage. If a device exposes multiple storages, pass `--storage` for
`put`, `mkdir`, `rm`, `rename`, `mv`, and `cp`.
## Remote Paths
Remote paths are POSIX-like and absolute:
```text
/
/Music/song.mp3
/Download/report.pdf
/GARMIN/APPS/MyApp.prg
```
Path matching is case-sensitive in v1 because MTP devices disagree about
case behavior. Components containing `/`, `\`, null bytes, `.`, or `..` are
rejected before sending an MTP request.
For destination paths:
- `DEST/` means “use the source or local filename inside `DEST`”.
- If `DEST` exists and is a folder, the command writes inside that folder.
- If a destination file exists, pass `--replace` where supported.
## Commands
### `devices`
List visible MTP devices.
```sh
mtp-rs devices
mtp-rs devices --json
mtp-rs devices --known 091e:0003
```
The output includes VID/PID, manufacturer, product, serial number, USB
location, negotiated link speed when the OS reports it, and `match_reason`.
`match_reason` explains why the device was accepted as an MTP candidate:
`standard_class`, `interface_string`, `known_vid_pid`, or
`opened_descriptor_scan`.
### `info`
Open a device and show device metadata plus storages.
```sh
mtp-rs info
mtp-rs info --device SERIAL
mtp-rs info --location 0xffff000000000000 --json
```
Use this before mutating commands when the device has multiple storages.
### `ls`
List a remote folder.
```sh
mtp-rs ls /
mtp-rs ls /Music
mtp-rs ls /Music --recursive
mtp-rs ls / --storage 0 --json
```
`ls` requires the path to resolve to a folder.
### `put`
Upload a local file to a remote path.
```sh
mtp-rs put ./song.mp3 /Music/song.mp3
mtp-rs put ./song.mp3 /Music/ --replace
mtp-rs put ./MyApp.prg /GARMIN/APPS/MyApp.prg --replace --verify
```
Behavior:
- If `REMOTE_PATH` ends with `/`, the local filename is used.
- If `REMOTE_PATH` resolves to an existing folder, the file is uploaded inside it.
- If the destination file exists, `--replace` deletes it before upload.
- `--verify` downloads the uploaded object back and compares bytes. This is
intentionally explicit because it doubles transfer traffic.
Uploads are streamed; large files are not buffered in memory.
### `get`
Download a remote file to a local path.
```sh
mtp-rs get /Music/song.mp3 ./song.mp3
mtp-rs get /Music/song.mp3 ./song.mp3 --replace
```
`get --replace` writes to a temporary file first and replaces the destination
only after the download succeeds, so an interrupted transfer does not destroy
the existing local file.
### `mkdir`
Create one remote folder under an existing parent.
```sh
mtp-rs mkdir /Upload
mtp-rs mkdir /GARMIN/APPS
```
This does not create missing intermediate folders.
### `rm`
Delete a remote object.
```sh
mtp-rs rm /Upload/old.bin --yes
```
`--yes` is required. Folder deletion depends on what the device supports; many
devices reject non-empty folder deletes.
### `rename`
Rename a remote object in place.
```sh
mtp-rs rename /Upload/old.bin new.bin
```
`new.bin` must be a filename, not a path. The device must advertise rename
support.
### `mv`
Move a remote object to another folder or path.
```sh
mtp-rs mv /Upload/file.bin /Archive/file.bin
mtp-rs mv /Upload/file.bin /Archive/
mtp-rs mv /Upload/file.bin /Archive/new-name.bin
```
If the destination changes the filename, the device must support rename.
If the destination file exists, pass `--replace`.
### `cp`
Copy a remote object to another folder or path.
```sh
mtp-rs cp /Upload/file.bin /Archive/file.bin
mtp-rs cp /Upload/file.bin /Archive/
mtp-rs cp /Upload/file.bin /Archive/file-copy.bin
```
If the destination changes the filename, the device must support rename.
If the destination file exists, pass `--replace`.
### `doctor`
Diagnose common discovery and access problems.
```sh
mtp-rs doctor
mtp-rs doctor --device SERIAL --json
```
`doctor` checks visible devices, open behavior, storages, root listing, and
common writable-folder hints such as `Download`, `Documents`, `Music`, and
`GARMIN`. In JSON mode it keeps the visible device rows, including
`match_reason`, even when opening the selected device fails.
## JSON Output
Pass `--json` to any command for machine-readable output:
```sh
mtp-rs devices --json
mtp-rs info --json
mtp-rs put ./song.mp3 /Music/song.mp3 --json
```
JSON is written to stdout. Transfer progress is written to stderr, so stdout
remains parseable JSON during `put`, `get`, and `put --verify`.
For mutating commands, JSON includes an `operation` field and relevant paths,
handles, filenames, byte counts, and booleans such as `verified`, `renamed`,
or `replaced`. `replaced` means an existing visible destination object was
actually replaced.
## Exit Codes
| `0` | Success. |
| `1` | Other error. |
| `2` | No device found. |
| `3` | Ambiguous device or storage selection. |
| `4` | Access denied, read-only storage, or exclusive access. |
| `5` | Remote path error. |
| `6` | Transfer failed. |
| `7` | Verification failed. |
Use `--verbose` when an automation needs lower-level protocol context in
stderr.
## Device Workflows
### Android
Android devices usually reject uploads to the storage root. Upload into an
existing folder such as `Download`:
```sh
mtp-rs put ./report.pdf /Download/report.pdf --replace
mtp-rs get /Download/report.pdf ./report.pdf
```
Make sure the phone is unlocked and USB mode is set to file transfer.
### Kindle
Copy documents into the documents folder:
```sh
mtp-rs put ./book.epub /documents/book.epub --replace
mtp-rs ls /documents
```
### Garmin
Connect IQ sideloading is a normal file upload:
```sh
mtp-rs put ./MyApp.prg /GARMIN/APPS/MyApp.prg --replace --verify
```
Garmin remains just one workflow; the CLI does not include Garmin-specific
commands.
### Generic Backup
Download files by path after listing folders:
```sh
mtp-rs ls /DCIM --recursive
mtp-rs get /DCIM/Camera/IMG_0001.jpg ./IMG_0001.jpg
```
## Troubleshooting
### No Device Found
Check that the device is connected, unlocked, and in MTP/file-transfer mode.
Some devices expose different USB modes for charging, PTP, tethering, and MTP.
### Multiple Devices
Run:
```sh
mtp-rs devices
```
Then repeat the command with `--device SERIAL` or `--location LOCATION`.
### Multiple Storages
Run:
```sh
mtp-rs info
```
Then pass `--storage INDEX_OR_ID` to mutating commands.
### macOS Exclusive Access
macOS may let Photos, `ptpcamerad`, Android File Transfer, Garmin Express, or
another file manager claim the USB device. Close those applications and retry.
The CLI detects common exclusive-access errors and prints a practical hint.
### macOS USB Authorization
On recent macOS versions, IOKit can also deny USB user-client access before the
device is opened. This is different from another app owning the interface: the
device is visible in `mtp-rs devices`, but `info`, `ls`, or `doctor` fail while
creating the IOKit PlugInInterface. Run the CLI from Terminal or iTerm with the
Mac unlocked and the accessory allowed. GUI helpers launched as normal `.app`
bundles can have different USB authorization than background agents or IDE task
runners.
### Linux Permissions
You may need udev rules to access USB devices without root. See the platform
notes in the main [README](../README.md#linux).
### Timeout Or Disconnect
Use a better cable or port, unlock the device, avoid sleep/lock during transfer,
and increase timeout for large files:
```sh
mtp-rs --timeout 120 put ./large-video.mp4 /Movies/large-video.mp4
```