# Pointframe CLI `Pointframe.Cli.exe` is a self-contained Windows command-line tool for discovering monitors, capturing a whole monitor as PNG, running Windows OCR against a monitor capture, and recording a whole monitor to MP4. It uses `Pointframe.Engine` directly and does not start the Pointframe tray application or any WPF window. ## Requirements - Windows x64 - An interactive, unlocked Windows desktop session - No .NET runtime or .NET SDK when using the published ZIP - `ffmpeg.exe` on `PATH`, set via `POINTFRAME_FFMPEG_PATH`, or bundled next to `Pointframe.Cli.exe` — required only for the `record` command The CLI cannot capture a user's desktop from a Windows service or session 0. Run it as the same interactive user who owns the desktop being inspected. ## Install Download `Pointframe.Cli--win-x64.zip` from the [latest Pointframe release](https://github.com/dimitar-radenkov/Pointframe/releases/latest) and extract it to a directory. The ZIP is self-contained and includes the single-file executable and its native dependencies. For source builds, use: ```powershell pwsh .\packaging\build-cli-package.ps1 -Version 1.0.0 -FfmpegPath 'C:\path\to\ffmpeg.exe' ``` `-FfmpegPath` is optional; omit it to build a package without a bundled `ffmpeg.exe` (the `record` command then relies on `PATH` or `POINTFRAME_FFMPEG_PATH` on the target machine). The script writes the ZIP and SHA-256 file under `packaging\output\Pointframe.Cli--win-x64`. ## Commands Every long option below also accepts a short alias: `-m` for `--monitor`, `-w` for `--window-id`, `-g` for `--region`, `-s` for `--seconds`, `-f` for `--fps`, `-r` for `--redact`, and `-o` for `--output`. Every long option also accepts an inline value, e.g. `--monitor=\\.\DISPLAY1` instead of `--monitor \\.\DISPLAY1`. ### Choosing where the artifact is written Every command that produces a file — `capture`, `ocr`, `capture-window`, `ocr-window`, and `record` — accepts `--output ` (`-o`) to name the exact file to write, instead of letting Pointframe generate a timestamped name: ```powershell .\Pointframe.Cli.exe capture --monitor '\\.\DISPLAY1' --output .\shot.png .\Pointframe.Cli.exe record --monitor '\\.\DISPLAY1' --seconds 5 -o .\clips\take1.mp4 ``` The value is a file path, not a directory: missing parent directories are created, an existing file is overwritten, and passing the path of an existing directory is rejected as a runtime error. The metadata sidecar is written next to the file. When `--output` is omitted, artifacts keep their previous behavior and land under `%LOCALAPPDATA%\Pointframe` with a generated name. ### Discover monitors ```powershell .\Pointframe.Cli.exe displays ``` Use the exact `monitorName` returned by this command. A typical name is `\\.\DISPLAY1`, but the available names depend on the current Windows session. The response is JSON containing monitor identifiers, physical pixel bounds, and DPI scale information. ### List windows ```powershell .\Pointframe.Cli.exe windows ``` Lists visible top-level windows as JSON. Each entry includes `Hwnd` (the window handle), `Title`, `ProcessName`, `ProcessId`, `BoundsPixels` in absolute physical screen pixels, `MonitorName` (the containing monitor's device name, if the window fits on one monitor), and `IsMinimized`. Pointframe's own process windows are excluded. Window handles are session-local and temporary. Always call `windows` to get current handles before calling `capture-window` or `ocr-window`. ### Capture a window ```powershell .\Pointframe.Cli.exe capture-window --window-id 12345678 ``` Captures the visible screen rectangle of the specified window and saves it as a PNG. The `--window-id` (`-w`) value is the `Hwnd` returned by the `windows` command and must be a positive integer. This is a screen-rectangle capture: if the target window is partially covered by another window, the occluding content will appear in the capture. Minimized, zero-size, off-screen, and multi-monitor-spanning windows are rejected with a runtime error (exit code `1`). ### Capture a window and run OCR ```powershell .\Pointframe.Cli.exe ocr-window --window-id 12345678 ``` Same as `capture-window`, but also runs Windows OCR against the captured image. The JSON response includes `RecognizedText` (`null` when no text is found or no OCR language pack is installed). ### Capture a monitor ```powershell .\Pointframe.Cli.exe capture --monitor '\\.\DISPLAY1' ``` The command writes a JSON response to standard output and saves a PNG plus metadata sidecar beneath: ```text %LOCALAPPDATA%\Pointframe\Screenshots ``` The metadata identifies the artifact path, byte length, SHA-256, timestamp, monitor, DPI, and physical capture bounds. Add `--region ` (`-g`) to capture only a sub-rectangle of the monitor instead of the whole thing. The coordinates are physical pixels relative to the monitor's own top-left corner (not the virtual desktop), and width/height must be positive integers: ```powershell .\Pointframe.Cli.exe capture --monitor '\\.\DISPLAY1' --region 100,100,800,600 ``` A region that falls outside the monitor's bounds is rejected with a runtime error (exit code `1`) rather than being clipped. The response metadata reports both `MonitorBoundsPixels` (the full monitor) and `CaptureBoundsPixels` (what was actually captured), so a region capture is distinguishable from a whole-monitor one. ### Capture and run OCR ```powershell .\Pointframe.Cli.exe ocr --monitor '\\.\DISPLAY1' ``` OCR uses the same monitor capture as `capture`, then calls Windows OCR for the current user's installed language profiles. The PNG and metadata sidecar are still produced. The JSON adds `RecognizedText`; it is `null` when no text is recognized or no suitable OCR language pack is installed. `ocr` accepts the same optional `--region ` (`-g`) flag as `capture`, so OCR can be scoped to a sub-region of the monitor. ### Record a monitor ```powershell .\Pointframe.Cli.exe record --monitor '\\.\DISPLAY1' --seconds 10 ``` `record` starts a direct MP4 recording of the whole monitor, waits for the requested duration (or an earlier Ctrl+C, which stops the recording gracefully instead of killing the process), stops the recording, and writes a single combined JSON response containing both the started `Session` and the finished `Artifact`. The MP4 and its `.events.jsonl` sidecar are saved beneath: ```text %LOCALAPPDATA%\Pointframe\Recordings ``` Optional flags: | Flag | Meaning | Default | |---|---|---| | `--fps <1-60>` (`-f`) | Capture frame rate | `20` | | `--redact ` (`-r`) | Pixelate a capture-local physical-pixel region; repeatable | none | Example with a 30 fps capture and two redacted regions: ```powershell .\Pointframe.Cli.exe record --monitor '\\.\DISPLAY1' --seconds 30 --fps 30 --redact 100,100,200,80 --redact 400,300,150,150 ``` `record` is a single blocking command: there is no separate `stop-recording` command because each CLI invocation is a standalone process with no session state that could persist across two separate invocations. If a script needs to start recording and stop it later from a different process, use the MCP server's `start_recording`/`stop_recording` tools instead — see the [MCP server README](../mcp-desktop-testing/README.md). If the recording cannot be started (for example, an unknown monitor name or a missing `ffmpeg.exe`), the command writes a JSON response with `"Success": false` and an `Error` object to standard output and exits with code `1`. ## Exit codes and errors | Exit code | Meaning | |---:|---| | `0` | Command completed successfully | | `1` | Runtime or capture/OCR/recording failure | | `2` | Invalid or incomplete command-line arguments | Every command other than `--help`/`--version` writes a single-line JSON response to standard output, on both the success and the failure path, so a script can parse standard output the same way regardless of outcome. A runtime failure writes a `"Success": false` response whose `Error` object carries a stable, machine-readable `Code` alongside the human-readable `Message`: | `Error.Code` | Raised when | |---|---| | `target_not_found` | The named monitor or window handle does not exist | | `target_not_capturable` | The window is minimized, zero-size, off-screen, or spans monitors | | `invalid_region` | A `--region`/`--redact` rectangle is non-positive or outside the monitor | | `invalid_output_path` | The `--output` value names a directory rather than a file | | `canceled` | The operation was canceled before it completed | | `capture_failed` | Any other unexpected runtime failure | `record` failures the engine reports as a structured error use the recording response's own codes (such as `monitor_not_found`) in the same `Error` shape. The human-readable `Pointframe CLI failed: ...` line is still written to standard error as well, so interactive use is unchanged; only invalid command-line arguments (exit code `2`) write usage text to standard error *instead of* JSON. The parser accepts only these forms: ```text Pointframe.Cli.exe displays Pointframe.Cli.exe windows Pointframe.Cli.exe capture --monitor [--region ] Pointframe.Cli.exe ocr --monitor [--region ] Pointframe.Cli.exe capture-window --window-id Pointframe.Cli.exe ocr-window --window-id Pointframe.Cli.exe record --monitor --seconds [--fps <1-60>] [--redact ]... Pointframe.Cli.exe --help Pointframe.Cli.exe --version ``` Friendly monitor labels, display indexes, or omitted `--monitor`/`--window-id` values are not accepted. ## Help and version ```powershell .\Pointframe.Cli.exe --help # or -h .\Pointframe.Cli.exe --version # or -v ``` Both accept the flag form (`--help`/`--version`), the short form (`-h`/`-v`), or a bare `help`/`version` command. Unlike every other command, these write plain text (not JSON) to standard output and always exit with code `0`. `--help`/`-h` and `--version`/`-v` take priority over any other arguments on the command line, so they can be appended to an otherwise invalid or incomplete command to see usage instead of an error, e.g. `Pointframe.Cli.exe record --monitor '\\.\DISPLAY1' --help`. ## Artifact verification For every successful capture or OCR operation: 1. Read the JSON response from standard output. 2. Locate the PNG and `.metadata.json` sidecar in the reported artifact area. 3. Compare the file length and SHA-256 in the sidecar with the actual PNG. 4. Preserve both files together when attaching evidence to a report. For every successful `record` operation: 1. Read the JSON response from standard output. 2. Locate the MP4 at `artifact.path` and the `.events.jsonl` sidecar at `artifact.eventSidecarPath`. 3. Compare the file length and SHA-256 in `Artifact` with the actual MP4. 4. Preserve both files together when attaching evidence to a report. The CLI writes through the shared direct capture and recording services, so the metadata is produced alongside the artifact rather than inferred by the caller. ## Development and testing Build the project: ```powershell dotnet build Pointframe.Cli\Pointframe.Cli.csproj ``` Run the CLI from source: ```powershell dotnet run --project Pointframe.Cli\Pointframe.Cli.csproj -- displays ``` Run focused tests: ```powershell dotnet test Pointframe.Tests\Pointframe.Tests.csproj ` --filter "FullyQualifiedName~CliApplication|FullyQualifiedName~CliCommand" ``` The CLI tests cover command parsing, output and error streams, exit codes, and the direct-capture and direct-recording service contracts. A successful unit test does not prove that the current machine has an unlocked interactive desktop or a working `ffmpeg.exe`; use a real `displays`, `capture`, or `record` invocation for that check. ## Troubleshooting ### No displays or capture errors Run the executable in the logged-in interactive session, not as a scheduled task or Windows service. Confirm that the desktop is unlocked and that the process is running in the same Windows session as the monitors. ### Monitor name rejected Run `displays` again and copy the exact `monitorName`. Do not replace it with a friendly name or an assumed display number. ### OCR returns `null` The capture may contain no readable text, or Windows may not have an OCR language pack matching the current user's language profile. The PNG remains valid and can be inspected independently. ### `record` fails immediately `record` needs `ffmpeg.exe`. Set `POINTFRAME_FFMPEG_PATH` to its full path, place `ffmpeg.exe` next to `Pointframe.Cli.exe`, or add it to `PATH`. Rebuild the package with `-FfmpegPath` to bundle it automatically. ### ZIP or checksum problems Rebuild with `packaging\build-cli-package.ps1`, ensure the archive and `.sha256` file come from the same build, and verify the SHA-256 before distribution. ## Related documentation - [Pointframe product README](../../README.md) - [MCP server README](../mcp-desktop-testing/README.md) - [CLI implementation](../../Pointframe.Cli/) - [CLI packaging script](../../packaging/build-cli-package.ps1)