--- name: device-pod-cli description: Use the HWLAB v0.2 internal device-pod CLI from a HWLAB code agent runner to operate authorized Device Pods through cloud-api REST, leases, hwlab-device-pod, gateway, and device-host-cli. version: 0.2.0-rest --- # Device Pod CLI Skill(cli-spec) Scope: this skill is only for HWLAB-internal code agent runners inside the HWLAB runtime, such as `/workspace/hwlab` sessions running from the G14 DEV, PROD, or v0.2 images. It does not apply to external UniDesk developer workspaces or third-party machines. Use this skill when the task mentions `device-pod-cli`, `device-pod`, `device-host-cli`, Keil build/download through a Device Pod, debug probe control, workspace operations, or I/O probe reads such as UART boot logs. Canonical skill location: `/app/skills/device-pod-cli/SKILL.md`. Do not create duplicate skill copies under `/root/.agents/skills`, `/home/ubuntu/.agents/skills`, or `/workspace/hwlab/skills`; those locations cause stale instructions. ## Runtime Contract - The stable runner entrypoint is `node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs`. - The canonical implementation is `/app/tools/device-pod-cli.ts`; `/app/tools/device-pod-cli.mjs` and the skill script are compatibility wrappers that launch the Bun+TypeScript CLI. - All commands emit JSON. A command with no output is a failure. - Formal v0.2 mode is cloud-api authority: the CLI calls `/v1/device-pods...` REST APIs and must not read `.device-pod/*.json` as the source of truth for gateway session, workspace root, probe UID, UART port, or host CLI. - Server-side profile, grant, lease, job authority, and profile hash are owned by `hwlab-cloud-api`; `hwlab-device-pod` and `device-host-cli` execute only after cloud-api authorization. ## Configuration Pass the API base URL with one of these sources: ```sh --api-base-url http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667 HWLAB_DEVICE_POD_API_URL=http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667 HWLAB_CLOUD_API_URL=http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667 HWLAB_CLI_ENDPOINT=http://74.48.78.17:19667 ``` Pass authentication with one of these sources: ```sh --session-token --cookie 'hwlab_session=' --bearer-token HWLAB_DEVICE_POD_SESSION_TOKEN= HWLAB_CLOUD_API_SESSION_TOKEN= HWLAB_SESSION_TOKEN= HWLAB_SESSION_COOKIE='hwlab_session=' ``` Use `login` only when a username/password credential is explicitly available: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs login --api-base-url --username --password ``` The `setCookie` value in the JSON output can be passed back as `--cookie` or `HWLAB_SESSION_COOKIE`. ## Primary Commands List visible Device Pods through cloud-api authority: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs profile list --api-base-url --session-token ``` Show one visible Device Pod status and redacted server profile summary: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs profile show --pod-id device-pod-71-freq --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs health --pod-id device-pod-71-freq --api-base-url --session-token ``` Diagnose auth, grant, lease, and gateway blockers without touching local profiles: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs doctor --pod-id device-pod-71-freq --api-base-url --session-token ``` Acquire and inspect a lease for mutating jobs: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease acquire --pod-id device-pod-71-freq --reason "build smoke" --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease current --pod-id device-pod-71-freq --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs lease release --pod-id device-pod-71-freq --lease-token --api-base-url --session-token ``` Run read-only workspace, debug, and I/O jobs: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/ ls --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/src rg main --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe chip-id --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:io-probe:/uart/1 read --duration-ms 5000 --api-base-url --session-token ``` Run mutating jobs only with reason and lease token when required by cloud-api: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ apply-patch \ --reason "DEV edit" --lease-token \ --api-base-url --session-token < patch.diff node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ put User/new.c \ --reason "DEV edit" --lease-token \ --api-base-url --session-token < file node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ rm User/old.c --missing-ok \ --reason "remove obsolete source" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ rmdir User/empty \ --reason "remove empty folder" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ keil add-source User/new.c --group User \ --reason "sync Keil project" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ keil remove-source User/old.c \ --reason "sync Keil project" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:workspace:/ build start \ --reason "build smoke" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:debug-probe download start \ --reason "DEV smoke" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:debug-probe reset \ --reason "reset smoke" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:io-probe:/uart/1 write "AT" \ --reason "UART write smoke" --lease-token \ --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs \ device-pod-71-freq:io-probe:/uart/1 jsonrpc gpio.read \ --params-json '{"pin":"PB5"}' --expect-result-field value \ --reason "UART JSON-RPC smoke" --lease-token \ --api-base-url --session-token ``` Inspect or cancel jobs through cloud-api: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job status --pod-id device-pod-71-freq --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job output --pod-id device-pod-71-freq --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs job cancel --pod-id device-pod-71-freq --api-base-url --session-token ``` Selector aliases also support job status/output/cancel after build/download: ```sh node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:workspace:/ build status --api-base-url --session-token node /app/skills/device-pod-cli/scripts/device-pod-cli.mjs device-pod-71-freq:debug-probe download output --api-base-url --session-token ``` ## Removed Legacy Path `profile create` is removed from formal v0.2 mode. If a profile is missing or wrong, an admin must update cloud-api with the admin Device Pod APIs and then grant users. The CLI returns `legacy_profile_create_removed` instead of creating `.device-pod/.json`. Do not treat local files such as `.device-pod/device-pod-71-freq.json` or `DEVICE_POD_PROFILE_DIR` as authority. They are ignored by the formal CLI and may only exist as obsolete caches or hardware-host bootstrap artifacts. ## Operation Rules - Use `device-pod-cli` first for workspace, build, download, reset, UART, and debug-probe tasks. - Prefer `workspace apply-patch` for source edits. Use `workspace put` only for intentional whole-file writes or new files, because it bypasses hunk-level conflict detection. - If apply-patch fails, read the returned `patchHint`, re-read the current file with `workspace cat` or `workspace rg`, and retry a smaller exact-context hunk before using whole-file write. - Do not start by calling `/app/tools/hwlab-gateway-tran.mjs`, Windows-side Keil skills, serial-monitor skills, `keil-cli.py`, or generic shell when the Device Pod REST path is available. - If the CLI returns an auth, grant, lease, executor, or gateway blocker, fix that named blocker in the cloud-api/device-pod path; do not bypass the profile with generic gateway shell. - The server-side profile already carries `hostWorkspaceRoot`, `projectWorkspace`, `debugInterface.uv4Path`, probe UID, UART port, and gateway route. Let `cloud-api -> hwlab-device-pod -> device-host-cli` apply those values. - Keep I/O selectors as strict path tokens, for example `device-pod-71-freq:io-probe:/uart/1 read`; do not split `/uart/1` across argv. - Use `--dry-run` to inspect the REST job request without dispatching it. ## Host Asset Bootstrap The packaged Windows host CLI asset remains `/app/skills/device-pod-cli/assets/device-host-cli.mjs`. Upload it only when cloud-api/gateway dispatch reaches a Windows host and reports that `tools\device-host-cli.mjs` is missing or broken. Do not upload or recreate runner-side `.device-pod/*.json` as the formal profile source. The profile must come from cloud-api. Host-side profile snapshots, when needed, are written by `hwlab-device-pod` from the cloud-api-provided server profile before invoking `device-host-cli`. ## Safety - Mutating operations require a human/business reason and may require an active lease. Cloud-api is the enforcement point; the CLI only forwards `reason` and `leaseToken`. - `hwlab-gateway` is transport only. Do not add a generic `cmd` operation to `device-pod-cli`; add a named Device Pod operation instead. - `SOURCE`, `LOCAL`, `DRY-RUN`, fixture data, and local profile files cannot be reported as `DEV-LIVE` hardware evidence. ## Manual Tests ## T1 阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:在未设置 API URL 时运行 `profile list`,确认输出 JSON 且错误码为 `api_base_url_required`。 ## T2 阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:设置 `HWLAB_DEVICE_POD_API_URL` 和有效 session 后运行 `profile list`、`profile show --pod-id `、`doctor --pod-id `,确认输出来自 cloud-api authority,并且没有泄露 `gatewaySessionId`、`hostWorkspaceRoot`、probe UID 或 UART port。 ## T3 阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:创建本地 `.device-pod/.json`,写入假的 `gatewaySessionId`,再运行 `profile list` 和 selector `--dry-run`;确认 JSON 输出标记 `localProfileAuthority=false` 且没有读取本地 fake route。 ## T4 阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `profile create --pod-id `,确认返回 `legacy_profile_create_removed`,并提示使用 cloud-api admin API 管理服务端 profile/grant。 ## T5 阅读 `/app/skills/device-pod-cli/SKILL.md`,然后用 cli 手动测试以下内容:运行 `workspace put/rm/rmdir/keil` 和 `io-probe jsonrpc` 的 `--dry-run`,确认 JSON plan 中 intent 分别为 `workspace.put`、`workspace.rm`、`workspace.rmdir`、`workspace.keil`、`io.uart.jsonrpc`,且 `contentB64`、`text`、`patch` 不以原文泄露。