Metadata-Version: 2.5
Name: appletv-screenshot
Version: 0.2.0
Summary: Screen-capture helper for Apple TV MCP: one PNG per run over RemoteXPC via pymobiledevice3.
Project-URL: Homepage, https://github.com/trevor-nichols/appletv-mcp
Project-URL: Repository, https://github.com/trevor-nichols/appletv-mcp
Project-URL: Documentation, https://github.com/trevor-nichols/appletv-mcp
Project-URL: Changelog, https://github.com/trevor-nichols/appletv-mcp/releases
Project-URL: Issues, https://github.com/trevor-nichols/appletv-mcp/issues
Author: Trevor Nichols
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Keywords: apple-tv,mcp,pymobiledevice3,screenshot
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Home Automation
Requires-Python: <3.15,>=3.14
Requires-Dist: platformdirs<5,>=4.3
Requires-Dist: pydantic<3,>=2.12
Requires-Dist: pymobiledevice3==11.12.4
Description-Content-Type: text/markdown

# appletv-screenshot

One-shot screen-capture helper for [Apple TV MCP](../../README.md). The MCP server
spawns it as a separate process, waits for one PNG, and maps its exit code to a
tool error. Nothing from `pymobiledevice3` is imported by the MCP package.

## Why a separate process

- `pymobiledevice3` is confined to this separately distributed helper. The main
  `appletv-mcp` package does not import or bundle it; the two processes communicate
  through a narrow command-line/file contract.
- RemoteXPC tunnels, developer pairing, and DVT sessions have their own failure
  modes. Isolating them means a screenshot failure can never take down Apple TV
  control, which runs over `pyatv` in the MCP process.

## Requirements

- Python 3.14.
- An Apple TV with tvOS 17 or later that has Developer Mode enabled and the
  developer disk image mounted. Without those the device refuses the DVT screenshot
  service and the helper exits with code 14. The pinned pymobiledevice3 troubleshooting
  guide covers the two steps (`amfi enable-developer-mode`, `mounter auto-mount`).
- `pymobiledevice3==11.12.4`. This pin was chosen from the latest stable release at the
  time of writing. It has not been exercised against Apple TV hardware in this
  repository; see Verification status below.

## Install

From the repository root:

```bash
uv tool install ./sidecars/appletv-screenshot
```

`pipx install ./sidecars/appletv-screenshot` also works. The MCP server resolves the
`appletv-screenshot` executable from its own `PATH`. If the MCP host launches the server
with a minimal environment, set `screen_capture.command` in the MCP configuration to the
absolute path printed by `uv tool dir --bin` or `which appletv-screenshot`.

## Pairing

Screenshots need a RemoteXPC developer pairing. This pairing is separate from the
`pyatv` (Companion) pairing that Apple TV MCP uses for control.

```bash
pymobiledevice3 remote pair
```

Four transports exist. Capture always requires a configured UDID.

### userspace (macOS, Linux, Windows)

The normal no-root path for a Wi-Fi Apple TV. The helper browses RemotePairing over
bonjour, then opens an in-process PyTCP tunnel. No usbmux, no root, and no `tunneld`
daemon. This is not `UserspaceRsdTunnel`. That class calls `create_using_usbmux` first
and never reaches RemotePairing when USB is absent.

```bash
appletv-screenshot configure --udid 00008110-000A1B2C3D4E5F60 --transport userspace
```

With `--transport auto` (the default) the helper tries native on macOS, then userspace,
then tunneld. On Linux and Windows it tries userspace, then tunneld. If every attempt
fails, `auto` reports the userspace outcome when userspace ran. Native pairing is a
different credential domain and does not outrank a later userspace device or tunnel
failure. Privileged tunneld remains a last operational fallback, not the normal path.

### native (macOS only)

The helper rides Apple's own `remoted` tunnel through `remotepairingd`, so no daemon and
no root are needed. It relies on the Apple developer pairing already held by
`remotepairingd` for the current user. `pymobiledevice3 remote browse` lists the devices
that pairing knows. How to establish that pairing for an Apple TV is not covered by the
pinned pymobiledevice3 corpus in this repository. If native fails, `auto` falls through
to userspace.

### tunneld (macOS, Linux, Windows)

A last resort when userspace cannot open a tunnel. Leave a privileged daemon running.

```bash
sudo pymobiledevice3 remote tunneld
appletv-screenshot configure --transport tunneld --tunneld-port 49151
```

It listens on `127.0.0.1:49151` by default. Pass `--tunneld-host` and `--tunneld-port` if
you moved it. The helper reuses the daemon's tunnel and never needs root itself.

## Configure

```bash
appletv-screenshot configure --udid 00008110-000A1B2C3D4E5F60
appletv-screenshot configure --transport userspace
appletv-screenshot configure --transport tunneld --tunneld-port 49151
appletv-screenshot configure --timeout 15 --discovery-timeout 3
appletv-screenshot identify
```

Configuration lives in the `appletv-screenshot` platform config directory
(`platformdirs`). `APPLETV_SCREENSHOT_CONFIG_DIR` overrides it. The file holds only the
target UDID, transport, tunneld address, and timeouts. Pairing records stay where
`pymobiledevice3` or `remotepairingd` keep them.

`identify` prints that file as JSON so `appletv-mcp doctor` can report the capture target
next to the pyatv control identifier. The two identities are not the same value and are
not compared.

Capture requires `--udid`. The helper never captures the first device of any type. A
configured UDID that is not an Apple TV exits with code 10. `--clear-udid` forgets the
target. The next capture then fails until you set a UDID again.

## Capture

```bash
appletv-screenshot capture --output /absolute/path/screen.png
```

This is the only invocation Apple TV MCP makes. The helper:

1. Loads its configuration.
2. Opens the device over the first transport that succeeds.
3. Takes one screenshot over DVT.
4. Writes the PNG atomically to `--output` (sibling temp file, then rename).
5. Exits with `0`. Stdout stays empty. Progress goes to stderr only with `--verbose`.

The whole capture runs under one deadline (`timeout_seconds`, default 15 s). The MCP
server's own default deadline is 20 s, so a slow device produces exit code 16 from the
helper rather than a kill from the parent.

## Exit codes

Exit codes are the contract between the helper and the MCP server. The MCP repository
compares this table with its own copy in a test.

| Code | Name                  | Meaning                                                     |
| ---: | --------------------- | ----------------------------------------------------------- |
|    0 | `SUCCESS`             | PNG written to `--output`.                                  |
|    2 | `USAGE`               | Bad arguments (argparse) or invalid `configure` values.      |
|   10 | `DEVICE_NOT_FOUND`    | Configured device not reachable, or none visible.           |
|   11 | `AMBIGUOUS_DEVICE`    | Capture has no configured UDID, or several Apple TVs match. |
|   12 | `PAIRING_REQUIRED`    | The device rejected the pairing or is not paired.           |
|   13 | `TUNNEL_UNAVAILABLE`  | No usable transport (userspace, native, and tunneld failed). |
|   14 | `CAPTURE_FAILED`      | Device reached, but no PNG came back from DVT.              |
|   15 | `OUTPUT_WRITE_FAILED` | PNG captured but `--output` could not be written.           |
|   16 | `CAPTURE_TIMEOUT`     | The capture exceeded `timeout_seconds`.                     |
|   17 | `CONFIG_INVALID`      | The helper's own config file is unreadable or invalid.      |

## Verification status

Every module has unit tests that run without hardware, using fake transports and fake
DVT sessions. The transport and capture code was written against the installed
pymobiledevice3 11.12.4 sources and its type hints. No Apple TV was captured in this
repository yet, so treat the pin and the transport behaviour as implemented and
fake-tested, not live-verified.

## Development

```bash
cd sidecars/appletv-screenshot
uv sync --locked
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run pytest
```

This project is intentionally not a member of the root `uv` workspace, so
`uv sync` at the repository root never installs `pymobiledevice3`.

## License

`appletv-screenshot` is licensed under the GNU General Public License,
version 3 or later (`GPL-3.0-or-later`).

```text
Apple TV Screenshot
Copyright (C) 2026 Trevor Nichols

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
```

It depends on `pymobiledevice3`, which is also distributed under
GPL-3.0-or-later.

This license applies to the screenshot helper under this directory.
The main `appletv-mcp` package is a separate MIT-licensed project and
communicates with this helper through a subprocess interface.

See [`LICENSE`](LICENSE) for the full GPLv3 text.
