@putdotio/rokit
v2.4.7
Published
A tiny CLI companion for Roku device harness work.
Readme
Install
Requires Node >=24.19.0; install it in a consumer repo with:
pnpm add -D @putdotio/rokitQuick Start
Set the target Roku in the app repo that consumes rokit:
export ROKIT_TARGET=<roku-ip>
export ROKIT_PASSWORD=<developer-mode-password>Then run the generic device checks and actions you need:
pnpm exec rokit check
pnpm exec rokit package artifacts/live/channel.zip
pnpm exec rokit install artifacts/live/channel.zip
pnpm exec rokit launch dev
pnpm exec rokit press Down Select
pnpm exec rokit console artifacts/live/console.log --duration-ms 30000
pnpm exec rokit proof artifacts/live/proof --screenshotROKIT_PASSWORD is required for developer-installer operations such as
install and screenshot. ROKU_DEV_TARGET and ROKU_DEV_PASSWORD are
accepted as optional aliases when the ROKIT_* names are unset.
Automation
Prefer JSON when rokit feeds another tool:
pnpm exec rokit describe
pnpm exec rokit describe proof
pnpm exec rokit --json active-app
pnpm exec rokit --dry-run launch dev
pnpm exec rokit --fields status,data.state media-player
pnpm exec rokit --input-json '{"command":"press","keys":["Down","Select"]}'
pnpm exec rokit --input-json @artifacts/rokit-payload.json
printf '{"command":"press","keys":["Back"]}' | pnpm exec rokit --input-json -describe prints the machine-readable command surface, including command
names, parameters, JSON payload fields, global options, and automation feature
flags. Pass a command name, such as rokit describe proof, to return a
one-command schema when you only need a small payload. When stdout is not a TTY,
command output defaults to JSON unless --output text is explicit.
Use inline --input-json for small payloads, --input-json @file for larger
payloads, and --input-json - to read the payload from stdin.
Command Surface
Common commands:
| Command | Purpose |
| -------------------------------------------------- | -------------------------------------------------- |
| describe [command] | Print command schemas for all commands or one |
| check | Confirm ECP and developer-installer reachability |
| discover | Find Roku ECP devices with SSDP |
| device-info | Read Roku device metadata |
| active-app | Read the foreground app |
| console <output-path> | Capture BrightScript console output from 8085 |
| debug-command <command> [args...] | Run an allowlisted Roku debug command |
| media-player | Read parsed /query/media-player state |
| snapshot | Read a compact state snapshot |
| proof <output-dir> | Write reviewable local proof artifacts |
| package <zip-path> | Create a sideload ZIP from the current app root |
| install <zip-path> | Publish an existing ZIP to the Roku developer slot |
| launch <app-id> | Launch an app by id with optional params |
| press <key...> | Send Roku remote keys |
| query <ecp-path> | Print a raw ECP response |
| sgnodes | Print the raw SceneGraph tree |
| assert-node / wait-node | Check generic SceneGraph node state |
| wait-active / wait-media-player / wait-ready | Poll generic runtime readiness |
| screenshot <output-path> | Save a timestamped developer screenshot |
Node waits use positional node conditions, for example
wait-ready dev videoPlayerScreen visible and wait-node title text "Ready".
Wait timeouts cover the entire polling operation, including in-flight requests
and sleeps. Expiry interrupts that work and reports the last completed observation;
late success is not accepted. A zero or negative library wait budget times out
without making an initial request.
Remote navigation keeps the keys as positional arguments and uses options for
the bounded loop: press Down --until-node videoPlayerScreen --until-state visible --max 8.
Use --input-json for literal values that look like flags.
Mutating commands support --dry-run where the platform can validate without
changing device or filesystem state. ECP paths reject query strings, fragments,
traversal, backslashes, control characters, and percent-encoded path segments.
Generated output paths must stay within the current working directory.
Screenshots append a timestamp to the requested filename and report the actual
path written, so repeated captures do not reuse cache-prone filenames.
Captures reject non-JPEG/PNG responses and detected framing or decoding errors.
JPEG scan completion and restart order are checked before saving.
Captures accept up to 64 MiB of compressed data and 8,847,360 pixels (including
3840×2160 UHD and 4096×2160 DCI 4K); JPEG decoding has a 256 MiB memory limit.
PNG width and height are each limited to 8192 to bound decoder row allocations;
decompression is bounded by the declared pixel dimensions. Original image
bytes are preserved. Detected image errors use the normal capture retry path.
Library Use
App-specific scenario scripts can import the generic helpers:
import {
assertMediaPlayerContainer,
assertSceneGraphNode,
captureScreenshot,
createPackageZip,
deleteInstalledChannel,
pressKey,
querySceneGraph,
waitForSceneGraphAssertion,
type RokuContext,
} from "@putdotio/rokit";
const target = process.env.ROKIT_TARGET;
if (!target) {
throw new Error("ROKIT_TARGET is not set");
}
const context: RokuContext = {
target,
timeoutMs: 10_000,
username: "rokudev",
};
await pressKey(context, "Info");
await querySceneGraph(context, { attempts: 3, requireAppNode: true, requireComplete: true });
await assertSceneGraphNode(context, "videoPlayerScreen", { state: "visible" });
await assertMediaPlayerContainer(context, "mp4");
await captureScreenshot(
{ ...context, password: process.env.ROKIT_PASSWORD ?? "" },
"artifacts/player.jpg",
{ attempts: 3 },
);
await waitForSceneGraphAssertion(context, "player ready", (xml) => {
if (!xml.includes("videoPlayerScreen")) {
throw new Error("expected player screen");
}
});
await createPackageZip({
rootDir: process.cwd(),
outFile: "artifacts/channel.zip",
exclude: (path) => path.startsWith("components/lab/"),
overrides: [{ path: "manifest", contents: "title=Example Channel\n" }],
});
await deleteInstalledChannel({ ...context, password: process.env.ROKIT_PASSWORD ?? "" });Boundaries
rokit owns generic Roku mechanics:
- package, install, launch, deeplink params, and remote keypresses
- raw ECP queries and parsed media-player state
- BrightScript console capture and allowlisted debug-server commands
- SceneGraph state queries and named-node assertions
- timestamped screenshots, snapshots, and proof artifacts
Consumer app repos own product behavior: opening specific routes, asserting playback for real content, checking app-specific UI nodes, and generating review artifacts.
