# Qimsdk Deploy

> Deploy, build, run, and retrieve QIM SDK GStreamer C apps, gst-launch pipelines, QIM SDK C++ apps, and Python qimsdk apps on Qualcomm Linux devices. Five modes: Mode A (gst-launch — push and run directly, no build), Mode B (Ubuntu on-device build — QIMSDK source tree on device), Mode C (host build — gstreamer-app-builder C sample app, build on a Linux workstation (x86_64 or aarch64, arch auto-detected) using the host SDK, deploy to QLI device), Mode P (Python qimsdk app — push main.py and run, no build), Mode D (host build — cpp-app-builder C++ SDK app, standalone build against the Yocto SDK on a Linux workstation (x86_64 or aarch64, arch auto-detected), deploy to QLI device). Returns structured result for eval scoring.

- Skill: `qualcomm/qimsdk-deploy` (Agent Skill, multi-file: 16 files)
- Install (CLI): `npx skillmds@latest add qualcomm/qimsdk-deploy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/qualcomm/qimsdk-deploy/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: qualcomm (https://skillmd.com/u/qualcomm)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/qualcomm/qimsdk-deploy

---


# QIM SDK Deploy Skill

## Operating Principle — Fail Fast, Don't Fix

When anything goes wrong — a missing password, a failed SSH connection, a path that doesn't exist, a missing config field — **stop immediately and report the exact error to the user**. Do not attempt workarounds, retries, alternative paths, or recovery steps unless explicitly documented in this skill. The user is responsible for fixing environment and configuration issues. Once fixed, they re-invoke the skill.

This keeps the skill predictable and token-efficient. Silent fallbacks hide real problems.

---

## ABSOLUTE RULES — NO EXCEPTIONS

These rules override any instinct to "be helpful by trying something else." They apply to every invocation, every mode, every step.

1. **NEVER improvise.** Run the reference scripts exactly as documented. Do not write ad-hoc commands to work around a failure.

2. **STOP on any failure.** Report the exact error message. Do not try an alternative path. The correct response to failure is: show the error, name the script/step that failed, and stop. "Let me try X instead" is NEVER the correct response.

3. **ALWAYS ask for mode and artifact path before doing anything.** Do not read `.env`, do not run preflight, do not check anything until you know: (a) Mode A / B / C / P / D, (b) the artifact path. Then read `configs/.env` for only the keys that mode needs.

4. **NEVER proceed to deploy without a passing preflight for the current mode and current `.env`.** Re-read `configs/.env` from disk immediately before every preflight and every deploy — never reuse values remembered from earlier in the conversation, and never pass device credentials as command-line flags (let the script read the file, so a mid-session edit always takes effect). If preflight has not been run: run it first. If deploying a different mode than preflight was last run for: re-run preflight for the current mode.

5. **NEVER commit anything.** Leave git completely to the user.

6. **NEVER hardcode credentials.** All credentials come from `configs/.env`.

7. **NEVER skip the config wizard.** If `configs/.env` is incomplete, the wizard runs before anything else, every time.

8. **NEVER install, fix, or modify anything on the device or linux workstation during a deploy or eval run.** When a pipeline, build, or device error occurs, show the exact error output and stop — do not attempt to fix it. Exception: preflight and SSH connection setup (host key, key copy, config wizard) — these are one-time setup steps the skill guides the user through so they can get connected.

9. **NEVER run two deploys in parallel if they compete for an exclusive hardware resource.** The device has one of each piece of hardware — two pipelines sharing the same resource will fail or corrupt each other's output. Camera pipelines (e.g. ISP camera, USB camera) are the most common case and must always run sequentially. When in doubt, run sequentially.

10. **NEVER ask the user to "press Enter" or "click" anything.** This skill runs through a chat interface — the user answers by typing. Every prompt must end with a concrete question expecting a typed reply, never a keypress or button action.

---

## Deploy flow

The skill always deploys. Preflight runs automatically before every deploy — the user never needs to ask for it explicitly. The two steps the skill runs internally are:

### Step A — preflight (automatic, before every deploy)

All deploy scripts live in the `references/` folder next to this SKILL.md file. Use whichever path this file was loaded from — do not hardcode a path. For example, if this SKILL.md is at `.claude/skills/qimsdk-deploy/SKILL.md`, the scripts are at `.claude/skills/qimsdk-deploy/references/`.

```bash
python <skill-references-dir>/preflight_check.py \
    --mode <A|B|C|P|D> \
    [--artifact-path <path>]
```

Checks: Python env, PuTTY tools, credentials, TCP reachability, SSH login, device OS confirmation,
mode-specific tool/path checks (cmake, make, sudo, timeout, source tree, output dir, cam-server),
and for Mode C/D: workstation SSH, SDK validation, disk space.

**Always run preflight before the first deploy and whenever the device or environment changes.**
Credentials are read from `configs/.env` — see below.

**What preflight checks per mode — and what to do if it fails:**

Mode A requires on device:
- `timeout` command
- `gst-launch-1.0` installed
- Output directory writable
- All input files and model/label paths from `pipeline.sh` present on device

Mode B requires on device:
- `cmake` installed
- `make` installed
- `sudo` access for the device user
- `timeout` command
- Internet access (for `apt-get source` on first deploy if workspace not yet provisioned)
- `SOURCE_ROOT` in `.env` is optional — auto-discovered via glob if not set

Note: Mode B deploy auto-provisions the workspace from scratch (apt setup, cmake configure, build)
if `SOURCE_ROOT` does not exist. Preflight reports the current workspace state as informational;
it does NOT fail if cmake has not been run or the source tree is absent.

Mode C requires on linux workstation:
- SSH key auth to linux workstation
- ~8GB free disk on local (non-NFS) filesystem (for SDK + repo + build artifacts)

Note: Mode C deploy auto-provisions the workspace from scratch (SDK download+install, git clone,
cmake configure, host-build) if not already set up. The SDK zip
(`x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` for x86_64 hosts) is downloaded from codelinaro.org if absent.
If the download fails (network blocked), place the zip manually at
`{LINUX_WORKSTATION_BUILD_DIR}/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` and re-run.

Mode P requires on device:
- `python3` installed
- `qimsdk` Python package installed (`from qimsdk import Pipeline` must work)
- `timeout` command
- Wayland compositor running (for waylanksink pipelines)

Note: Mode P is **fail-fast only** — if `qimsdk` is not installed, preflight fails with a clear
message. There is no auto-install. The user must install the QIM SDK Python package on the device
before deploying. Unlike Modes B/C/D, Mode P does no workspace provisioning.

Mode D requires on the Linux/WSL workstation:
- SSH key or password auth to the workstation
- ~5GB free disk on local (non-NFS) filesystem (for the Yocto SDK)

Mode D requires on device:
- `libqimsdk-app-builder.so` runtime library present (WARN only) — a Mode D binary dynamically links it;
  if preflight WARNs it's missing, install the C++ IMSDK runtime on the device before deploying
  or the binary will fail to start.

Note: Mode D deploy auto-provisions the Yocto SDK (unzip + run the installer into
`{LINUX_WORKSTATION_BUILD_DIR}/qcom-sdk`) if not already installed, then builds each app
**standalone** (out of tree) against the installed SDK's target sysroot — no shared source tree
is cloned or mutated, unlike Mode C. Workstation arch (x86_64 or aarch64) is auto-detected,
same as Mode C. See "Mode D — C++ Standalone Host Build" below.

**Preflight failure means deploy will fail.** Every [FAIL] in preflight output must be resolved before running deploy. Do not proceed with deploy if preflight exits non-zero.

### Step B — deploy

All scripts are in `references/` next to this SKILL.md — use the same path used for preflight above.

```bash
# Mode A (gst-launch)
python <skill-references-dir>/deploy_mode_a.py \
    --artifact-path <path/to/artifact> \
    [--output-dir outputs/deploy]

# Mode B (Ubuntu on-device C app build)
python <skill-references-dir>/deploy_mode_b.py \
    --artifact-path <path/to/artifact> \
    [--output-dir outputs/deploy]

# Mode C (host build on linux workstation)
python <skill-references-dir>/deploy_mode_c.py \
    --artifact-path <path/to/artifact> \
    [--output-dir outputs/deploy]

# Mode P (Python qimsdk app — main.py, or legacy app.py)
python <skill-references-dir>/deploy_mode_p.py \
    --artifact-path <path/to/artifact> \
    [--output-dir outputs/deploy]

# Mode D (host build on Linux/WSL workstation — cpp-app-builder C++ SDK app)
python <skill-references-dir>/deploy_mode_d.py \
    --artifact-path <path/to/artifact> \
    [--output-dir outputs/deploy]
```

Each script reads credentials from `configs/.env` (same as preflight).

---

## Before You Start — `configs/.env`

**`configs/.env` is the single source of truth for all credentials and device connection details.** It is read fresh on EVERY invocation — no values are carried between sessions or conversation turns.

This file is gitignored and never committed. Create it once from the sample — `cp configs/.env.sample configs/.env` — then fill in your values. Both preflight and deploy scripts read it automatically.

### If `configs/.env` is missing or incomplete — run the config wizard

When `configs/.env` does not exist, or when required keys for the selected mode are missing, run the **interactive config wizard** before doing anything else:

1. **Mode is already known from Step 0** — only ask for keys that mode needs (see table below)
2. For each required key for that mode (in order), ask one at a time:
   - State what the key is for
   - Explain how to find its value
   - Show an example
   - Wait for the user's answer
   - **End every prompt with a concrete question expecting a typed answer.** Never tell the user to "press Enter", "hit return", or "click" anything — this skill is driven through a chat interface where the user replies with text; there is no keypress or button to trigger.
3. After collecting all values, write them to `configs/.env` with inline comments
4. Tell the user: "Config saved to `configs/.env`. Running preflight for Mode <X> now."
5. Proceed to preflight — do not skip it.

**Mode C wizard — ask this first before collecting any LINUX_WORKSTATION_* keys:**

> "For Mode C, I need a Linux x86_64 machine to run the host build. Two options:
> - **Remote Linux workstation** — a Linux machine on the network (e.g. `hu-gaurmeht-lv`)
> - **WSL (Windows Subsystem for Linux)** — Ubuntu running locally on this Windows machine
>
> Which are you using?"

**If they answer WSL:**
1. Ask first: "What is your WSL username? Run `whoami` inside your WSL terminal and tell me the output."

2. Once you have the username, tell them: "Before I can connect, WSL needs its SSH server running. Run these inside your WSL terminal (Ubuntu app):
   ```bash
   sudo service ssh start
   sudo service ssh status
   ```
   It should say `active (running)`. Keep the WSL window open (minimize it — don't exit).

   Also confirm passwordless sudo is configured (needed for auto-installing build tools):
   ```bash
   sudo whoami
   ```
   It should print `root` without a password prompt. If it asks for a password, run (replace `<wsl-username>` with the username you just told me):
   ```bash
   echo "<wsl-username> ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/<wsl-username>
   ```
   Let me know once SSH is running and sudo is passwordless."

3. Ask for port: "What port is WSL SSH on? Run inside WSL: `grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null || echo '22'`"

3. Verify connectivity from PowerShell — tell the user to run:
   ```powershell
   ssh -p <port> <wsl-username>@localhost "echo connected"
   ```
   If it asks for a password that's fine (key not copied yet). If connection is refused, sshd isn't running — go back to step 1.

4. Copy the SSH key to WSL. Tell the user to run in PowerShell:
   ```powershell
   Get-Content "$HOME\.ssh\id_ed25519_qimsdk.pub" | ssh -p <port> <wsl-username>@localhost "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && chmod 700 ~/.ssh"
   ```
   Enter WSL password when prompted. No output = success. Then verify key auth works:
   ```powershell
   ssh -p <port> -i "$HOME\.ssh\id_ed25519_qimsdk" <wsl-username>@localhost "echo connected"
   ```
   Should print `connected` with no password prompt.

5. Ask for build dir: "Where should build files go in WSL? Pick a path on WSL local disk with ~10GB free, e.g. `/home/<wsl-username>/qimsdk-build`"

6. Write to `configs/.env`:
   - `LINUX_WORKSTATION_HOST=localhost`
   - `LINUX_WORKSTATION_USER=<wsl-username>`
   - `LINUX_WORKSTATION_PORT=<port>`
   - `LINUX_WORKSTATION_KEY=C:/Users/<windows-username>/.ssh/id_ed25519_qimsdk`
   - `LINUX_WORKSTATION_BUILD_DIR=<chosen-path>`

**If they answer remote Linux workstation:**
- Proceed with the keys table as normal (ask `LINUX_WORKSTATION_HOST`, `LINUX_WORKSTATION_USER`, etc. one at a time).



**`configs/.env` is the right place for ALL config** including Mode C linux workstation details. The file is gitignored and never committed. It persists across sessions so you don't have to re-enter values every time.

### Keys required per mode

**Mode A — gst-launch (always required):**
| Key | What it is | How to find it | Example |
|-----|-----------|---------------|---------|
| `DEVICE_IP` | IP of the target device | `ip addr` on device, or router DHCP | `192.168.1.100` |
| `DEVICE_USER` | SSH login username | `ubuntu` (Ubuntu) or `root` (QLI) | `ubuntu` |
| `DEVICE_KEY` | SSH private key path (preferred) | Check existing keys: `Get-ChildItem $HOME\.ssh\*.pub` in PowerShell. If `id_ed25519_qimsdk.pub` exists, use `C:/Users/<you>/.ssh/id_ed25519_qimsdk`. Otherwise generate: `ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk"` | `C:/Users/<you>/.ssh/id_ed25519_qimsdk` |
| `DEVICE_PASSWORD` | SSH password (fallback if no key) | Device administrator | `<password>` |
| `HOST_KEY` | SSH host key fingerprint | **Preferred: leave blank and run preflight — it prints the fingerprint automatically.** If you need it before preflight: `ssh <DEVICE_USER>@<DEVICE_IP> "for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf \$f 2>/dev/null; done"` — pick ed25519 if present, else ecdsa. | `SHA256:...` |
| `DEPLOY_OUTPUT_DIR` | Local folder for logs and output | Default is fine | `outputs/deploy` |

> Set either `DEVICE_KEY` or `DEVICE_PASSWORD` (or both — key is tried first). If neither is set, preflight fails with instructions to see `ssh-setup.md`.

**Mode B — adds:**
| Key | What it is | How to find it | Example |
|-----|-----------|---------------|---------|
| `SOURCE_ROOT` | QIMSDK source tree path on device | SSH in, `ls ~ \| grep gst-plugins` | `/home/ubuntu/Downloads/qimsdk_samples/gst-plugins-qti-oss-<version>` |

**Mode C — adds (instead of SOURCE_ROOT):**
| Key | What it is | How to find it | Example |
|-----|-----------|---------------|---------|
| `LINUX_WORKSTATION_HOST` | Linux host-build workstation hostname/IP (x86_64 or aarch64 — arch auto-detected) | Remote machine: `hostname` on the machine. WSL: `localhost` | `my-linux-workstation` or `localhost` (WSL) |
| `LINUX_WORKSTATION_USER` | SSH username on linux workstation | `whoami` on the machine | `myusername` |
| `LINUX_WORKSTATION_PASSWORD` | SSH password (used when key not set) | Your login password | `mypassword` |
| `LINUX_WORKSTATION_KEY` | SSH private key path for linux workstation (optional if LINUX_WORKSTATION_PASSWORD set) | Check existing keys: `Get-ChildItem $HOME\.ssh\*.pub` in PowerShell. If `id_ed25519_qimsdk.pub` exists, use `C:/Users/<you>/.ssh/id_ed25519_qimsdk`. Otherwise generate: `ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk"` | `C:/Users/<you>/.ssh/id_ed25519_qimsdk` |
| `LINUX_WORKSTATION_BUILD_DIR` | Working directory on linux workstation (local disk, not NFS) | Pick a path on local disk with ~10GB free | `/home/myusername/qimsdk-build` |
| `LINUX_WORKSTATION_PORT` | SSH port on the linux workstation | Standard Linux: use `22`. If you're not sure or using WSL, run on the machine: `grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null \|\| echo "22"` | `22` |

> SDK install and repo clone are auto-provisioned on first deploy if not already present.
> If internet is blocked, place the SDK zip at `{LINUX_WORKSTATION_BUILD_DIR}/sdk.zip` (simplest) or use the arch-specific name: `x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` (x86_64) or `arm-qli-2.0-qimsdk-2.0.0-standardsdk.zip` (aarch64/WSL on ARM). The script checks for all three.

All Mode C keys above are required. Preflight exits immediately with `[FAIL]` if any are missing.

**Mode C — optional, bring-your-own SDK/repo:**
| Key | What it is | Example |
|-----|-----------|---------|
| `LINUX_WORKSTATION_SDK_PATH` | Absolute path **on the workstation** to an SDK installer — a `.zip` (unzipped automatically to find the installer inside) or a `.sh` (already extracted, run directly). Only consulted when the SDK is not yet installed; ignored otherwise. If not set, falls back to the existing zip-lookup + codelinaro.org download. | `/local/mnt/sdk/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` or `/home/user/sdk-installer.sh` |
| `LINUX_WORKSTATION_IMSDK_PATH` | Absolute path **on the workstation** to an already-cloned `qimsdk` repo. When set, this path is used directly and the git clone step is skipped entirely. If the path is set but `CMakeLists.txt` is not found there, deploy fails with a clear message — it never silently falls back to cloning a fresh copy. If not set, falls back to cloning from GitHub into `{LINUX_WORKSTATION_BUILD_DIR}/qimsdk`. | `/local/mnt/repos/qimsdk` |

**Ask the user during the Mode C wizard, right after `LINUX_WORKSTATION_BUILD_DIR`:**
1. "Do you have the SDK installer already on the workstation (as a `.zip` or `.sh`)?"
   — if yes: "What is the path on the workstation?" → save as `LINUX_WORKSTATION_SDK_PATH`
   — if no: nothing to do; SDK downloads automatically on first deploy
2. "Do you have `qimsdk` already cloned on the workstation?"
   — if yes: "What is the path on the workstation?" → save as `LINUX_WORKSTATION_IMSDK_PATH`
   — if no: nothing to do; repo clones automatically on first deploy

**Mode D — adds (same LINUX_WORKSTATION_* keys as Mode C, no new required keys):**

Mode D reuses every `LINUX_WORKSTATION_HOST/USER/KEY/PASSWORD/PORT/BUILD_DIR` key above — it targets
the same class of Linux workstation as Mode C (x86_64 or aarch64 — arch is auto-detected), just
with a different SDK (the Yocto standard SDK, not qimsdk). Optional additions:

| Key | What it is | How to find it | Example |
|-----|-----------|---------------|---------|
| `LINUX_WORKSTATION_SDK_URL` | Source for the Yocto SDK zip (installer .sh inside) | A `file://` path to a network share, or an `http(s)://` URL | `file:///mnt/share/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` |
| `LINUX_WORKSTATION_SDK_PATH` | Absolute path **on the workstation** to a Yocto SDK installer already present — a `.zip` (unzipped automatically to find the `.sh` installer inside) or a `.sh` (already extracted, run directly). Takes precedence over the build-dir zip lookup and `LINUX_WORKSTATION_SDK_URL`. Only consulted when the SDK is not yet installed; ignored otherwise. **Same key as Mode C** — set once, works for both modes. | `/local/mnt/sdk/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` or `/local/mnt/sdk/installer.sh` |

> If a `qcom-yocto-sdk*.zip`, `sdk.zip`, or the arch-specific zip name (see below) is already
> present in `LINUX_WORKSTATION_BUILD_DIR`, neither `LINUX_WORKSTATION_SDK_PATH` nor
> `LINUX_WORKSTATION_SDK_URL` is needed — deploy uses the local zip. The SDK is installed once into
> `{LINUX_WORKSTATION_BUILD_DIR}/qcom-sdk` and reused for every app. SDK source precedence:
> `LINUX_WORKSTATION_SDK_PATH` → build-dir zip → `LINUX_WORKSTATION_SDK_URL` → default Artifactory
> zip for the detected workstation arch (via `uname -m`, same as Mode C):
> `https://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/meta-qcom/qcom-distro/qcom-armv8a/x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip`
> (x86_64) or
> `https://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/meta-qcom/qcom-distro/qcom-armv8a/arm-qli-2.0-qimsdk-2.0.0-standardsdk.zip`
> (aarch64).

**Ask the user during the Mode D wizard, right after `LINUX_WORKSTATION_BUILD_DIR`:**
1. "Do you have the Yocto SDK installer already on the workstation (as a `.zip` or `.sh`)?"
   — if yes: "What is the path on the workstation?" → save as `LINUX_WORKSTATION_SDK_PATH`
   — if no: deploy will detect the workstation arch (`uname -m`) and download the matching
     default Yocto SDK zip (`x64-qli-2.0-qimsdk-2.0.0-standardsdk.zip` for x86_64 or
     `arm-qli-2.0-qimsdk-2.0.0-standardsdk.zip` for aarch64) from the Artifactory URL on first
     deploy. If you need a different source, set `LINUX_WORKSTATION_SDK_URL` to a `file://` path
     or `http(s)://` URL.
   (Mode D has no repo-clone step, so there is no `IMSDK_PATH` question — unlike Mode C.)

**Mode P — same as Mode A (no extra keys needed):**

Mode P uses the same device credentials as Mode A. No additional keys required — just `DEVICE_IP`, `DEVICE_USER`, `DEVICE_KEY`/`DEVICE_PASSWORD`, `HOST_KEY`, `DEPLOY_OUTPUT_DIR`. Never ask for `SOURCE_ROOT` or `LINUX_WORKSTATION_*` for Mode P. Artifact entry point is `main.py` (legacy `app.py` also accepted).

### Full example — copy this and fill in your values

```bash
# configs/.env
# ─────────────────────────────────────────────────────────────────────────────
# Required for all modes
# ─────────────────────────────────────────────────────────────────────────────

DEVICE_IP=<device-ip>
# Why: The SSH target. Used by preflight and deploy to connect to the device.

DEVICE_USER=ubuntu
# Why: SSH login user. Usually 'ubuntu' on Ubuntu devices, 'root' on QLI 2.0.

DEVICE_PASSWORD=<password>
# Why: SSH password. Never put this on the command line or in a YAML config.
#      Set DEVICE_PASSWORD here; the scripts read it via os.environ.

HOST_KEY=SHA256:cKvXMnoKhO6g+fnn17WzmrpnfSSXW+MwzwFNiUu/gC4
# Why: Prevents connecting to the wrong device.
#      If you don't have it, run preflight without HOST_KEY first — the script
#      will show you the actual fingerprint and tell you to save it here.

DEPLOY_OUTPUT_DIR=outputs/deploy
# Why: Where deploy scripts save per-artifact logs and pulled output files.

# ─────────────────────────────────────────────────────────────────────────────
# Mode B only (Ubuntu on-device build)
# ─────────────────────────────────────────────────────────────────────────────

SOURCE_ROOT=/home/ubuntu/Downloads/qimsdk_samples/gst-plugins-qti-oss-1.0.r1.06800

# ─────────────────────────────────────────────────────────────────────────────
# Mode C (host build on linux workstation)
# ─────────────────────────────────────────────────────────────────────────────

LINUX_WORKSTATION_HOST=<linux-workstation-hostname>
LINUX_WORKSTATION_USER=<username>
LINUX_WORKSTATION_KEY=C:/Users/<you>/.ssh/id_ed25519_qimsdk
LINUX_WORKSTATION_PORT=22
# Why: SSH port on the linux workstation. Default is 22 (standard Linux).
#      WSL (Windows Subsystem for Linux) commonly uses port 2222.
#      To find your port, run on the linux workstation:
#        grep -E '^Port' /etc/ssh/sshd_config 2>/dev/null || echo "22"

# ─────────────────────────────────────────────────────────────────────────────
# Mode D (host build on Linux/WSL workstation — cpp-app-builder C++ SDK app)
# Reuses ALL LINUX_WORKSTATION_* keys above — no new required keys.
# ─────────────────────────────────────────────────────────────────────────────

LINUX_WORKSTATION_SDK_URL=file:///path/to/qcom-yocto-sdk-deploy.zip
# Why: Source for the Yocto standard SDK (installer .sh inside). Accepts a
#      file:// path (a share/mount visible on the workstation) or an
#      http(s):// URL. Optional if a qcom-yocto-sdk*.zip is already present
#      in LINUX_WORKSTATION_BUILD_DIR — deploy looks there first.
```

---

## When invoked — mandatory startup sequence

Every invocation follows this exact sequence. Do not skip steps.

### Step -1 — Verify Python is available

Before anything else, run:

```bash
python --version || python3 --version || py --version
```

If all three fail, Python is not installed. Tell the user:
> "Python 3 is required to run deploy. Install it from https://www.python.org/downloads/ and ensure it is on PATH. On Windows, tick 'Add Python to PATH' during installation. Then restart your terminal and try again."
**Stop here until Python is available.**

### Step 0 — Ask what the user wants to do

Before reading any files or checking any config, ask the user to pick exactly one mode from
this list — present each mode as its own separate, distinct option, never abbreviated or
grouped (e.g. never phrase it as "P or D?"):

1. **Mode — pick one:**
   - **A** — gst-launch pipeline (`pipeline.sh`), no build
   - **B** — Ubuntu on-device build (gstreamer-app-builder C app, QIMSDK source tree already on device)
   - **C** — Host build (gstreamer-app-builder C app), device is QLI 2.0, needs a Linux workstation
   - **D** — Host build (cpp-app-builder C++ SDK app), needs a Linux workstation
   - **P** — Python qimsdk app (`main.py`/`app.py`), no build
2. **Artifact path:** path to the artifact folder

Do not proceed until you have both. Mode determines which config keys are required. The subcommand is always deploy — preflight runs automatically before every deploy.

**Mode selection guide (show this if the user is unsure) — each mode below is a separate,
distinct choice, not to be merged or abbreviated when relaying to the user:**

- **Mode A** — artifact is a `pipeline.sh` (gst-launch command). No build needed.
- **Mode B** — artifact has `main.c` + `set(GST_EXAMPLE_BIN ...)`, device is Ubuntu with QIMSDK source tree on-device.
- **Mode C** — artifact has `main.c` + `set(GST_EXAMPLE_BIN ...)`, device is QLI 2.0 / host build (no build tools). Needs a Linux workstation (x86_64 or aarch64 — arch is auto-detected; WSL on either Windows x86_64 or Windows ARM works).
- **Mode D** — artifact has `main.cc` + `set(TEST_TARGET ...)` using `qti::Pipeline` / `<qti/qimsdk.h>` (cpp-app-builder). Needs a Linux workstation (x86_64 or aarch64 — arch is auto-detected; WSL on either Windows x86_64 or Windows ARM works).
- **Mode P** — artifact has `main.py` (or legacy `app.py`) using `qimsdk.Pipeline`. No build.

Mode C and Mode D both host-build on a workstation and push a binary to the device, but they target
different builder contracts (gstreamer-app-builder's C sample apps vs cpp-app-builder's standalone
C++ SDK apps) and use different toolchains (qimsdk source tree vs the Yocto standard SDK)
— check `main.c`+`GST_EXAMPLE_BIN` (Mode C) vs `main.cc`+`TEST_TARGET` (Mode D) in the artifact.

### Step 1 — Read `configs/.env` for the selected mode

Read `configs/.env` from the repo root. Check only the keys required for the selected mode:

| Mode | Required keys |
|------|--------------|
| A | `DEVICE_IP`, `DEVICE_USER`, `DEVICE_KEY` or `DEVICE_PASSWORD` (at least one), `DEPLOY_OUTPUT_DIR` |
| B | All Mode A keys + `SOURCE_ROOT` |
| C | All Mode A keys + `LINUX_WORKSTATION_HOST`, `LINUX_WORKSTATION_USER`, `LINUX_WORKSTATION_BUILD_DIR` |
| P | All Mode A keys (no extra) |
| D | All Mode A keys + `LINUX_WORKSTATION_HOST`, `LINUX_WORKSTATION_USER`, `LINUX_WORKSTATION_BUILD_DIR` (same as Mode C) + optional `LINUX_WORKSTATION_SDK_URL` |

- If the file does not exist: run the config wizard for the selected mode (see below)
- If the file exists but is missing required keys for the selected mode: run the wizard for only the missing keys
- If all required keys are present: proceed to Step 1a (Mode C/D) or Step 2 (all other modes)

### Step 1a — Mode C / Mode D only: ask about SDK and repo (REQUIRED, never skip)

After collecting `LINUX_WORKSTATION_BUILD_DIR`, always ask these questions before running preflight.
**Do not skip to preflight without completing this step.**

**Mode D — SDK question (ask exactly this):**
> "Do you have the Yocto SDK installer already on the workstation — as a `.zip` or a `.sh` file?"
- **Yes** → "What is the full path to it on the workstation?" → set `LINUX_WORKSTATION_SDK_PATH`
- **No** → leave unset; deploy will download the default Yocto SDK zip from Artifactory on first run

**Mode C — SDK question (ask exactly this):**
> "Do you have the SDK installer already on the workstation — as a `.zip` or a `.sh` file?"
- **Yes** → "What is the full path to it on the workstation?" → set `LINUX_WORKSTATION_SDK_PATH`
- **No** → leave unset; SDK downloads automatically from codelinaro.org on first deploy

**Mode C — repo question (ask exactly this):**
> "Do you have `qimsdk` already cloned on the workstation?"
- **Yes** → "What is the full path to it on the workstation?" → set `LINUX_WORKSTATION_IMSDK_PATH`
- **No** → leave unset; repo clones automatically from GitHub on first deploy

### Step 2 — Run preflight for the selected mode

Always run preflight before deploying — it takes ~5s and verifies the device is reachable and ready:

```bash
python <skill-references-dir>/preflight_check.py --mode <A|B|C|P|D>
```

**If preflight fails with a connection or auth error, work through the recovery flow below before asking the user to do anything manually.** Never proceed to Step 3 until preflight exits 0.

---

#### Preflight Recovery Flow

**Case 1 — Host key mismatch or `FATAL ERROR: Host key not in manually configured list`**

The device was likely re-imaged. Fetch the new fingerprint — run this in PowerShell (enter password when prompted):

```powershell
ssh <DEVICE_USER>@<DEVICE_IP> "for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf \$f 2>/dev/null; done"
```

This prints one `SHA256:...` line per key type the device has. Pick the `ed25519` line if present, otherwise use `ecdsa`. Update `HOST_KEY` in `configs/.env` with that value, then continue to Case 2 to re-copy the SSH key (a re-imaged device has lost all authorized_keys).

**Case 2 — `SSH key authentication failed`**

The SSH public key is not installed on the device (new or re-imaged device). Copy it now.

**NEVER ask the user for their password through the chat interface — not even "just this once".** Always give them the plink command to run themselves. Credentials stay out of the conversation.

First confirm the HOST_KEY in `configs/.env` is current (run Case 1 if unsure). Then copy the public key to the device — run these three commands one at a time (enter password at each prompt):

```powershell
ssh <DEVICE_USER>@<DEVICE_IP> "mkdir -p ~/.ssh && chmod 700 ~/.ssh"
Get-Content "$HOME\.ssh\id_ed25519_qimsdk.pub" | ssh <DEVICE_USER>@<DEVICE_IP> "cat >> ~/.ssh/authorized_keys"
ssh <DEVICE_USER>@<DEVICE_IP> "chmod 600 ~/.ssh/authorized_keys"
```

Replace `<DEVICE_USER>` and `<DEVICE_IP>` from `configs/.env`. Do not chain these with `&&` — run each separately. You may be prompted for the password 2-3 times per command (PAM stacking — re-enter the same password each time). No output means success.

After running, re-run preflight. If it still fails with key auth, check that `DEVICE_KEY` in `configs/.env` points to the correct key file.

**Case 3 — `DEVICE_KEY` file not found**

The key file path in `configs/.env` is wrong or the file doesn't exist. Check:
```bash
ls -la <DEVICE_KEY>
```
If missing, generate a new key pair:
```powershell
ssh-keygen -t ed25519 -C "qimsdk-deploy" -f "$HOME\.ssh\id_ed25519_qimsdk"
```
Then update `DEVICE_KEY` in `configs/.env` and repeat Case 2.

**Case 4 — TCP reachable but all auth fails**

Try password auth as a fallback: uncomment `DEVICE_PASSWORD` in `configs/.env` and re-run preflight. Once preflight passes with password auth, run Case 2 to install the key, then switch back to key auth.

---

### Step 3 — State what you're about to do, then run

Tell the user: mode, artifact path, script that will run. Then run it. Do not improvise.

---

## Output review categories — sync vs async

Artifacts fall into two review categories based on how output is delivered:

| Category | Condition | Review timing |
|---|---|---|
| **Sync** | `waylandsink` present with no `filesink` (display-only), OR `qtirtspbin` as sink (RTSP-out) | **Must be reviewed live** — output cannot be recovered after the run. Pause before AND after every sync run. |
| **Async** | `filesink` present (output written to file) | File can be reviewed any time after the run. |

**For sync artifacts, when acting as orchestrator (not called from harness):**
- **Before running:** Tell the user the artifact name, what pipeline it is, what they should see on screen or stream, and ask them to confirm they are watching before you start.
- **After running:** Ask explicitly — (1) did you see the expected output? (2) did the inference results look correct? (bounding boxes on right objects, correct labels, etc.). Record their y/n answer as the G3 human score.
- **Never skip this for display or RTSP-out pipelines.** Running a display pipeline without the user watching is a wasted run with no scorable output.

## Non-interactive callers (`--no-confirm`)

When called from the eval skill or another automated context with `--no-confirm`:
- All config values come from `configs/.env`. If any required key is missing, stop and report which keys.
- Never ask interactive questions.
- If the artifact has a `waylandsink`, emit: "⚠️ Non-interactive call: pipeline writes to display — ensure Wayland compositor is running on device." Then proceed.

---

## Hard Rules

- **NEVER ask the user for a password through the chat interface** — not to install a key, not "just this once", not for any reason. If a password is needed to copy an SSH key to the device, give the user the plink command to run themselves. Credentials must not appear in the conversation.
- All SSH/SCP in deploy scripts uses **paramiko** (Python) — not plink/pscp. PuTTY is not required by the deploy scripts (it is optional for interactive manual use only).
- Always pass `HOST_KEY` fingerprint to every paramiko connection — never use AutoAddPolicy in the deploy scripts.
- Always use `timeout --signal=SIGINT --kill-after=15 30` for all gst-launch (Mode A) pipelines — both file-source and camera. 30 seconds allows for QNN/SNPE/TFLite model loading on first invocation (which can take 15-25s) plus time for PLAYING and initial inference. SIGINT (not SIGTERM) lets GStreamer flush the MP4 moov atom cleanly.
- **For Mode B/C/D C/C++ app file-source pipelines**: run to natural EOS — no timeout (5 min SSH cap). The binary exits on its own when the file ends. Using timeout before EOS produces corrupt MP4.
- **Camera pipeline retry logic (Mode A/B)**: the deploy script runs the camera pipeline first without restarting cam-server (the common case is fine). If attempt 1 fails (never reached PLAYING, or crashed, or no moov atom), it restarts cam-server, waits 3 seconds, and retries once. A second failure is a real problem — not retried again. Do not add unconditional pre-run restarts.
- **Pull output immediately after each camera run before starting the next** — camera pipelines (02, 03, c-app equivalents) share the same output filenames (`two_stream_obj_detect_out.mp4`, `three_stream_*`). The next run overwrites the previous output. Always: run → pull → run next.
- **Camera pipeline run duration: 30s for all modes** (Mode A/B/C/D). Use `timeout --signal=SIGINT --kill-after=15 30`. File-source runs to natural EOS.
- Always use `echo <PASSWORD> | sudo -S <cmd>` for any write to root-owned paths
- Never commit anything — leave git to the user
- Device config is always passed in as parameters — never hardcoded
- Never skip the upfront config stage when called standalone
- **Check port availability with `/proc/net/tcp` not `ss`** — `ss` is not available on QLI 2.0 devices. Use `cat /proc/net/tcp | grep <HEX_PORT>` where port 8900 = 22C4, port 8901 = 22C5 (4-digit hex uppercase).
- **RTSP input pipeline timing:** When you control both the RTSP source and the RTSP consumer pipeline, start source first, then start consumer exactly **3 seconds** later (not more). Longer gaps allow the source to start looping or stall before the consumer connects. Verify source port is open before starting consumer.
- **Wayland env is always set before every pipeline run** (all modes, all sink types) — `qtivoverlay` probes for a display even in file-output pipelines; setting it is harmless when no display is present. The socket must be searched across all of `/run`, not just `$(id -u)` runtime dir, because the compositor may run as a different user (e.g. `weston` uid 1000 at `/run/user/1000/wayland-1` while the pipeline runs as `root`). The deploy scripts handle this automatically; use this snippet if running manually:
  ```bash
  WS=$(find /run -maxdepth 3 -name "wayland-*" ! -name "*.lock" 2>/dev/null | head -1)
  export XDG_RUNTIME_DIR=$(dirname "$WS")
  export WAYLAND_DISPLAY=$(basename "$WS")
  ```

---

## Structured Result

The deploy script always writes `result.json` to `<DEPLOY_OUTPUT_DIR>/<artifact-name>/result.json`,
even on failure. The schema and AI result card format are defined in:

**[`references/result-format.md`](references/result-format.md)**

Read that file before rendering any deploy result. Always show the result card first.

---

## Output Collection

```
<DEPLOY_OUTPUT_DIR>/<artifact-name>/
├── <artifact-name>_device.log
├── <artifact-name>_build.log     (Modes B/C/D only)
└── <artifact-name>_output.<ext>
```

When called standalone, pull to `DEPLOY_OUTPUT_DIR` (from `configs/.env`) and notify user.

---

## SSH/SCP Command Templates

### Windows host (paramiko — all deploy scripts)
The deploy scripts use paramiko internally. For interactive use from a terminal, use OpenSSH (included in Windows 10/11):
```bash
# SSH
ssh -i <KEY_FILE> <USER>@<IP> "<command>"

# SCP local → remote
scp -i <KEY_FILE> <local_file> <USER>@<IP>:<remote_path>

# SCP remote → local
scp -i <KEY_FILE> <USER>@<IP>:<remote_path> <local_path>
```

### Linux host
```bash
ssh -i <KEY_FILE> <USER>@<HOST> "<command>"
scp -i <KEY_FILE> <local_file> <USER>@<HOST>:<remote_path>
scp -i <KEY_FILE> <USER>@<HOST>:<remote_path> <local_path>
```

> **Deploy scripts always use paramiko** — see `deploy_mode_b.py`, `deploy_mode_a.py`, `deploy_mode_c.py` in `references/`. PuTTY (plink/pscp) is never needed.

---

## Mode A — gst-launch (No Build)

Use when: artifact is a `pipeline.sh` (generated by `qimsdk-gstreamer-dev`). No compilation needed. Works on any device with GStreamer and QIMSDK plugins installed.

**Script:** `references/deploy_mode_a.py`

---

### What the script does (step by step)

**A0 — Parse `pipeline.sh` and read `README.md` locally** before connecting to device:
- Read `README.md` **in full** if present — surface any prerequisites listed in "How to Run" (e.g. `wpctl set-default` for audio/AV pipelines) to the user before proceeding so they can complete them on the device
- Detect source type: `file-source` (filesrc), `camera` (qtiqmmfsrc/qticamsrc), or `rtsp` (rtspsrc)
- Extract output file path from `filesink location=<path>` (if present — display-only pipelines have none)
- Extract all input file paths from `filesrc location=<path>` (may contain `$HOME` shell variables)
- Detect if `waylandsink` is present (sets Wayland env before run regardless of sink type)
- Detect if `qtirtspbin` is present (RTSP serving **sink**, `rtsp_out=True`) — this is orthogonal to source type and does **not** make the pipeline an `rtsp` source
- **RTSP input pipelines** (`rtspsrc` — `source_type=rtsp`): cannot be run automatically — stop, print exact manual instructions, and ask the user to run and report back. Instructions to show:
  1. Push `pipeline.sh` to device: `pscp pipeline.sh ubuntu@<ip>:~/pipeline.sh`
  2. SSH in, set Wayland env, run: `bash ~/pipeline.sh`
  3. Verify: check for `Setting pipeline to PLAYING` in output; `ls -lh /home/ubuntu/Downloads/qimsdk_samples/media/output/` for any output file; pull it with `pscp`
  4. Ask user to paste the output and report what they observed
- **RTSP output pipelines** (`qtirtspbin` serving sink fed by camera/file): run automatically like any live sink — handled by the normal run path (same as `waylandsink`). Treated as a **sync** artifact (see review-timing table) — must be reviewed live.

**A1 — Verify input files exist on device** (file-source pipelines only, before pushing anything):

…(truncated)
