@putdotio/rokit 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -28,6 +28,8 @@ Keep it platform-focused, typed, and useful for both humans and agents.
28
28
  - Treat `process.cwd()` as the consumer app root.
29
29
  - Keep `.rokit/` consumer-local; it can hold env, generated artifacts, and
30
30
  transient device state.
31
+ - Keep `skills/rokit/SKILL.md` aligned with agent-facing command and safety
32
+ guardrails.
31
33
  - Keep `examples/live-probe-channel` generic. It exists only to prove package,
32
34
  install, launch, input, SceneGraph, screenshot, and proof mechanics.
33
35
  - Wrap `roku-deploy` for package publish, screenshots, and device metadata when
@@ -53,7 +55,8 @@ Keep it platform-focused, typed, and useful for both humans and agents.
53
55
  - Avoid sleeps in generic commands. App repos can add meaningful wait/assert
54
56
  loops around `rokit` primitives.
55
57
  - Release details live in `docs/DISTRIBUTION.md`; debugging details live in
56
- `docs/DEBUGGING.md`; readiness details live in `docs/READINESS.md`.
58
+ `docs/DEBUGGING.md`; readiness details live in `docs/READINESS.md`;
59
+ consumer skill guidance lives in `skills/rokit/SKILL.md`.
57
60
 
58
61
  ## When Contracts Change
59
62
 
package/README.md CHANGED
@@ -106,6 +106,7 @@ App-specific scenario scripts can import the generic helpers:
106
106
  import {
107
107
  assertMediaPlayerContainer,
108
108
  assertSceneGraphNode,
109
+ captureScreenshot,
109
110
  pressKey,
110
111
  querySceneGraph,
111
112
  waitForSceneGraphAssertion,
@@ -124,9 +125,14 @@ const context: RokuContext = {
124
125
  };
125
126
 
126
127
  await pressKey(context, "Info");
127
- await querySceneGraph(context, { attempts: 3, requireComplete: true });
128
+ await querySceneGraph(context, { attempts: 3, requireAppNode: true, requireComplete: true });
128
129
  await assertSceneGraphNode(context, "videoPlayerScreen", { state: "visible" });
129
130
  await assertMediaPlayerContainer(context, "mp4");
131
+ await captureScreenshot(
132
+ { ...context, password: process.env.ROKIT_PASSWORD ?? "" },
133
+ "artifacts/player.jpg",
134
+ { attempts: 3 },
135
+ );
130
136
  await waitForSceneGraphAssertion(context, "player ready", (xml) => {
131
137
  if (!xml.includes("videoPlayerScreen")) {
132
138
  throw new Error("expected player screen");
@@ -154,6 +160,7 @@ artifacts.
154
160
  - [Roku debugging](./docs/DEBUGGING.md)
155
161
  - [Distribution](./docs/DISTRIBUTION.md)
156
162
  - [Agent readiness](./docs/READINESS.md)
163
+ - [rokit skill](./skills/rokit/SKILL.md)
157
164
  - [Security](./SECURITY.md)
158
165
 
159
166
  ## Repo Internals
@@ -4,6 +4,8 @@ import { existsSync } from "node:fs";
4
4
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
5
5
  import * as rokuDeploy from "roku-deploy";
6
6
  import { createSocket } from "node:dgram";
7
+ import { access, copyFile, mkdir, mkdtemp, rm } from "node:fs/promises";
8
+ import { tmpdir } from "node:os";
7
9
  //#region src/errors.ts
8
10
  var InvalidInput = class extends Schema.TaggedErrorClass()("InvalidInput", { message: Schema.String }) {};
9
11
  var MissingTarget = class extends Schema.TaggedErrorClass()("MissingTarget", {}) {
@@ -335,22 +337,31 @@ const waitForMediaPlayerState = async (context, expectedState, timeoutMs = 1e4)
335
337
  };
336
338
  const querySceneGraph = async (context, options = {}) => {
337
339
  const attempts = options.attempts ?? 1;
340
+ const requireAppNode = options.requireAppNode ?? false;
338
341
  const requireComplete = options.requireComplete ?? false;
339
342
  const retryDelayMs = options.retryDelayMs ?? 500;
340
343
  let lastXml = "";
341
344
  let lastError;
345
+ let lastMissingAppNode = false;
342
346
  for (let attempt = 0; attempt < attempts; attempt += 1) {
343
347
  try {
344
348
  const xml = await queryEcp(context, "/query/sgnodes/all");
345
- if (!requireComplete || isCompleteSceneGraph(xml)) return xml;
349
+ const complete = !requireComplete || isCompleteSceneGraph(xml);
350
+ const hasAppNode = !requireAppNode || !xml.includes("<All_Nodes>") || xml.includes("<App ");
351
+ if (complete && hasAppNode) return xml;
346
352
  lastXml = xml;
353
+ lastMissingAppNode = complete && !hasAppNode;
347
354
  } catch (error) {
348
355
  lastError = error;
349
356
  lastXml = "";
357
+ lastMissingAppNode = false;
350
358
  }
351
359
  if (attempt < attempts - 1) await sleep(retryDelayMs);
352
360
  }
353
- if (lastXml !== "") throw new Error("SceneGraph query returned incomplete XML");
361
+ if (lastXml !== "") {
362
+ if (lastMissingAppNode) throw new Error("SceneGraph query returned complete XML without an App node");
363
+ throw new Error("SceneGraph query returned incomplete XML");
364
+ }
354
365
  throw new Error(`SceneGraph query failed: ${formatErrorMessage$1(lastError)}`);
355
366
  };
356
367
  const assertSceneGraphNode = async (context, nodeName, expectation) => {
@@ -411,6 +422,41 @@ const takeScreenshot = async (context, outputPath) => {
411
422
  password: context.password
412
423
  });
413
424
  };
425
+ const captureScreenshot = async (context, outputPath, options = {}) => {
426
+ const attempts = options.attempts ?? 3;
427
+ const retryDelayMs = options.retryDelayMs ?? 1500;
428
+ const resolvedOutput = resolve(outputPath);
429
+ await mkdir(dirname(resolvedOutput), { recursive: true });
430
+ let lastError = "unknown";
431
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
432
+ const captureDir = await mkdtemp(join(tmpdir(), `${safeTempPrefix(options.tempDirPrefix ?? "rokit-screenshot")}-`));
433
+ const capturePath = join(captureDir, basename(resolvedOutput));
434
+ try {
435
+ const tempResult = await firstExistingPath([await takeScreenshot(context, capturePath), capturePath]);
436
+ if (tempResult !== void 0) {
437
+ await copyFile(tempResult, resolvedOutput);
438
+ return resolvedOutput;
439
+ }
440
+ const directResult = await firstExistingPath([resolvedOutput, await takeScreenshot(context, resolvedOutput)]);
441
+ if (directResult === resolvedOutput) return resolvedOutput;
442
+ if (directResult !== void 0) {
443
+ await copyFile(directResult, resolvedOutput);
444
+ return resolvedOutput;
445
+ }
446
+ throw new Error("screenshot capture succeeded without writing an image file");
447
+ } catch (error) {
448
+ lastError = formatErrorMessage$1(error);
449
+ if (attempt === attempts) break;
450
+ await sleep(retryDelayMs);
451
+ } finally {
452
+ await rm(captureDir, {
453
+ force: true,
454
+ recursive: true
455
+ });
456
+ }
457
+ }
458
+ throw new Error(`failed to capture ${basename(resolvedOutput)}: ${lastError}`);
459
+ };
414
460
  const packageChannel = async (outputPath, rootDir = process.cwd()) => {
415
461
  const options = packageOptions(outputPath, rootDir);
416
462
  await rokuDeploy.createPackage(options);
@@ -444,6 +490,18 @@ const rejectUnsafeInput$1 = (value, label) => {
444
490
  return code < 32 || code === 127;
445
491
  })) throw new Error(`${label} contains control characters`);
446
492
  };
493
+ const safeTempPrefix = (value) => value.replace(/[^a-zA-Z0-9_-]/g, "-");
494
+ const firstExistingPath = async (paths) => {
495
+ for (const path of paths) if (await fileExists(path)) return path;
496
+ };
497
+ const fileExists = async (path) => {
498
+ try {
499
+ await access(path);
500
+ return true;
501
+ } catch {
502
+ return false;
503
+ }
504
+ };
447
505
  const validateRemoteKey = (key) => {
448
506
  if (key.startsWith("Lit_")) return;
449
507
  if (!remoteKeySet.has(key)) throw new Error(`unsupported remote key: ${key}`);
@@ -747,4 +805,4 @@ const validateNonNegativeInteger = (value, label) => {
747
805
  if (!Number.isInteger(parsed) || parsed < 0) fail(`Invalid ${label}: ${value}`);
748
806
  };
749
807
  //#endregion
750
- export { readNamedNodeBounds as $, readMediaPlayerInfo as A, waitForSceneGraphNode as B, queryEcp as C, queryMediaPlayerXmlSafe as D, queryMediaPlayerXml as E, validateEcpPath as F, assertNamedNodeTranslation as G, assertNamedNodeSize as H, validateRemoteKey as I, isCompleteSceneGraph as J, assertSceneGraphNumberNear as K, waitForActiveApp as L, readMediaPlayerState as M, resolvePackageOutputPath as N, querySceneGraph as O, takeScreenshot as P, readNamedNodeAttributes as Q, waitForMediaPlayerState as R, queryActiveApp as S, queryMediaPlayerSafe as T, assertNamedNodeState as U, assertNamedNode as V, assertNamedNodeText as W, parseSceneGraphNumberList as X, isNamedNodeVisible as Y, readNamedNodeAttribute as Z, installPackage as _, loadEnv as a, readActiveApp as at, packageChannel as b, requirePassword as c, normalizeError as ct, resolveOutputPath as d, readNamedNodeNumber as et, assertMediaPlayerContainer as f, getDeviceInfo as g, discoverRokuDevices as h, fail as i, sceneGraphContainsText as it, readMediaPlayerPositionMs as j, readMediaPlayerContainer as k, requireTarget as l, renderError as lt, checkDevice as m, captureDebugConsole as n, readSceneGraphFailure as nt, loadLocalEnv as o, readXmlAttribute as ot, assertSceneGraphNode as p, escapeXmlAttribute as q, runDebugCommand as r, readSceneGraphStatus as rt, rejectUnsafeEcpPath as s, readXmlTag as st, buildDebugCommand as t, readNamedNodeTranslation as tt, resolveFileOutputPath as u, isActiveMediaPlayerState as v, queryMediaPlayer as w, pressKey as x, launchApp as y, waitForSceneGraphAssertion as z };
808
+ export { readNamedNodeAttributes as $, readMediaPlayerContainer as A, waitForSceneGraphAssertion as B, queryActiveApp as C, queryMediaPlayerXml as D, queryMediaPlayerSafe as E, takeScreenshot as F, assertNamedNodeText as G, assertNamedNode as H, validateEcpPath as I, escapeXmlAttribute as J, assertNamedNodeTranslation as K, validateRemoteKey as L, readMediaPlayerPositionMs as M, readMediaPlayerState as N, queryMediaPlayerXmlSafe as O, resolvePackageOutputPath as P, readNamedNodeAttribute as Q, waitForActiveApp as R, pressKey as S, queryMediaPlayer as T, assertNamedNodeSize as U, waitForSceneGraphNode as V, assertNamedNodeState as W, isNamedNodeVisible as X, isCompleteSceneGraph as Y, parseSceneGraphNumberList as Z, getDeviceInfo as _, loadEnv as a, sceneGraphContainsText as at, launchApp as b, requirePassword as c, readXmlTag as ct, resolveOutputPath as d, readNamedNodeBounds as et, assertMediaPlayerContainer as f, discoverRokuDevices as g, checkDevice as h, fail as i, readSceneGraphStatus as it, readMediaPlayerInfo as j, querySceneGraph as k, requireTarget as l, normalizeError as lt, captureScreenshot as m, captureDebugConsole as n, readNamedNodeTranslation as nt, loadLocalEnv as o, readActiveApp as ot, assertSceneGraphNode as p, assertSceneGraphNumberNear as q, runDebugCommand as r, readSceneGraphFailure as rt, rejectUnsafeEcpPath as s, readXmlAttribute as st, buildDebugCommand as t, readNamedNodeNumber as tt, resolveFileOutputPath as u, renderError as ut, installPackage as v, queryEcp as w, packageChannel as x, isActiveMediaPlayerState as y, waitForMediaPlayerState as z };
package/dist/index.d.mts CHANGED
@@ -88,6 +88,7 @@ type MediaPlayerInfo = {
88
88
  };
89
89
  type RetryOptions = {
90
90
  readonly attempts?: number;
91
+ readonly requireAppNode?: boolean;
91
92
  readonly requireComplete?: boolean;
92
93
  readonly retryDelayMs?: number;
93
94
  };
@@ -99,6 +100,11 @@ type WaitForSceneGraphAssertionOptions = {
99
100
  type PackageResult = {
100
101
  readonly path: string;
101
102
  };
103
+ type ScreenshotCaptureOptions = {
104
+ readonly attempts?: number;
105
+ readonly retryDelayMs?: number;
106
+ readonly tempDirPrefix?: string;
107
+ };
102
108
  declare const checkDevice: (context: RokuContext) => Promise<DeviceSummary>;
103
109
  declare const getDeviceInfo: (context: RokuContext) => Promise<rokuDeploy.DeviceInfo>;
104
110
  declare const queryActiveApp: (context: RokuContext) => Promise<ActiveApp>;
@@ -128,6 +134,9 @@ declare const installPackage: (context: RokuContext & {
128
134
  declare const takeScreenshot: (context: RokuContext & {
129
135
  readonly password: string;
130
136
  }, outputPath: string) => Promise<string>;
137
+ declare const captureScreenshot: (context: RokuContext & {
138
+ readonly password: string;
139
+ }, outputPath: string, options?: ScreenshotCaptureOptions) => Promise<string>;
131
140
  declare const packageChannel: (outputPath: string, rootDir?: string) => Promise<PackageResult>;
132
141
  declare const validateEcpPath: (path: string) => string;
133
142
  declare const validateRemoteKey: (key: string) => void;
@@ -159,4 +168,4 @@ declare const buildDebugCommand: (command: string, args: readonly string[]) => R
159
168
  declare const runDebugCommand: (context: RokuContext, command: RokuDebugCommand, durationMs: number, idleTimeoutMs: number) => Promise<DebugCommandResult>;
160
169
  declare const captureDebugConsole: (context: RokuContext, durationMs: number) => Promise<DebugConsoleCapture>;
161
170
  //#endregion
162
- export { type ActiveApp, type DebugCommandResult, type DebugConsoleCapture, type DeviceSummary, type DiscoveredRokuDevice, type MediaPlayerInfo, type MediaPlayerState, type NodeExpectation, type NodeState, type PackageResult, type RemoteKey, type RetryOptions, type RokuContext, type RokuDebugCommand, type RokuDebugPort, type SceneGraphAssertion, type SceneGraphBounds, type SceneGraphPoint, type SceneGraphStatus, type WaitForSceneGraphAssertionOptions, assertMediaPlayerContainer, assertNamedNode, assertNamedNodeSize, assertNamedNodeState, assertNamedNodeText, assertNamedNodeTranslation, assertSceneGraphNode, assertSceneGraphNumberNear, buildDebugCommand, captureDebugConsole, checkDevice, discoverRokuDevices, escapeXmlAttribute, getDeviceInfo, installPackage, isActiveMediaPlayerState, isCompleteSceneGraph, isNamedNodeVisible, launchApp, packageChannel, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, queryMediaPlayer, queryMediaPlayerSafe, queryMediaPlayerXml, queryMediaPlayerXmlSafe, querySceneGraph, readActiveApp, readMediaPlayerContainer, readMediaPlayerInfo, readMediaPlayerPositionMs, readMediaPlayerState, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readSceneGraphFailure, readSceneGraphStatus, readXmlAttribute, readXmlTag, runDebugCommand, sceneGraphContainsText, takeScreenshot, validateEcpPath, validateRemoteKey, waitForActiveApp, waitForMediaPlayerState, waitForSceneGraphAssertion, waitForSceneGraphNode };
171
+ export { type ActiveApp, type DebugCommandResult, type DebugConsoleCapture, type DeviceSummary, type DiscoveredRokuDevice, type MediaPlayerInfo, type MediaPlayerState, type NodeExpectation, type NodeState, type PackageResult, type RemoteKey, type RetryOptions, type RokuContext, type RokuDebugCommand, type RokuDebugPort, type SceneGraphAssertion, type SceneGraphBounds, type SceneGraphPoint, type SceneGraphStatus, type ScreenshotCaptureOptions, type WaitForSceneGraphAssertionOptions, assertMediaPlayerContainer, assertNamedNode, assertNamedNodeSize, assertNamedNodeState, assertNamedNodeText, assertNamedNodeTranslation, assertSceneGraphNode, assertSceneGraphNumberNear, buildDebugCommand, captureDebugConsole, captureScreenshot, checkDevice, discoverRokuDevices, escapeXmlAttribute, getDeviceInfo, installPackage, isActiveMediaPlayerState, isCompleteSceneGraph, isNamedNodeVisible, launchApp, packageChannel, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, queryMediaPlayer, queryMediaPlayerSafe, queryMediaPlayerXml, queryMediaPlayerXmlSafe, querySceneGraph, readActiveApp, readMediaPlayerContainer, readMediaPlayerInfo, readMediaPlayerPositionMs, readMediaPlayerState, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readSceneGraphFailure, readSceneGraphStatus, readXmlAttribute, readXmlTag, runDebugCommand, sceneGraphContainsText, takeScreenshot, validateEcpPath, validateRemoteKey, waitForActiveApp, waitForMediaPlayerState, waitForSceneGraphAssertion, waitForSceneGraphNode };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as readNamedNodeBounds, A as readMediaPlayerInfo, B as waitForSceneGraphNode, C as queryEcp, D as queryMediaPlayerXmlSafe, E as queryMediaPlayerXml, F as validateEcpPath, G as assertNamedNodeTranslation, H as assertNamedNodeSize, I as validateRemoteKey, J as isCompleteSceneGraph, K as assertSceneGraphNumberNear, L as waitForActiveApp, M as readMediaPlayerState, O as querySceneGraph, P as takeScreenshot, Q as readNamedNodeAttributes, R as waitForMediaPlayerState, S as queryActiveApp, T as queryMediaPlayerSafe, U as assertNamedNodeState, V as assertNamedNode, W as assertNamedNodeText, X as parseSceneGraphNumberList, Y as isNamedNodeVisible, Z as readNamedNodeAttribute, _ as installPackage, at as readActiveApp, b as packageChannel, et as readNamedNodeNumber, f as assertMediaPlayerContainer, g as getDeviceInfo, h as discoverRokuDevices, it as sceneGraphContainsText, j as readMediaPlayerPositionMs, k as readMediaPlayerContainer, m as checkDevice, n as captureDebugConsole, nt as readSceneGraphFailure, ot as readXmlAttribute, p as assertSceneGraphNode, q as escapeXmlAttribute, r as runDebugCommand, rt as readSceneGraphStatus, st as readXmlTag, t as buildDebugCommand, tt as readNamedNodeTranslation, v as isActiveMediaPlayerState, w as queryMediaPlayer, x as pressKey, y as launchApp, z as waitForSceneGraphAssertion } from "./debug-af6vvS9T.mjs";
2
- export { assertMediaPlayerContainer, assertNamedNode, assertNamedNodeSize, assertNamedNodeState, assertNamedNodeText, assertNamedNodeTranslation, assertSceneGraphNode, assertSceneGraphNumberNear, buildDebugCommand, captureDebugConsole, checkDevice, discoverRokuDevices, escapeXmlAttribute, getDeviceInfo, installPackage, isActiveMediaPlayerState, isCompleteSceneGraph, isNamedNodeVisible, launchApp, packageChannel, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, queryMediaPlayer, queryMediaPlayerSafe, queryMediaPlayerXml, queryMediaPlayerXmlSafe, querySceneGraph, readActiveApp, readMediaPlayerContainer, readMediaPlayerInfo, readMediaPlayerPositionMs, readMediaPlayerState, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readSceneGraphFailure, readSceneGraphStatus, readXmlAttribute, readXmlTag, runDebugCommand, sceneGraphContainsText, takeScreenshot, validateEcpPath, validateRemoteKey, waitForActiveApp, waitForMediaPlayerState, waitForSceneGraphAssertion, waitForSceneGraphNode };
1
+ import { $ as readNamedNodeAttributes, A as readMediaPlayerContainer, B as waitForSceneGraphAssertion, C as queryActiveApp, D as queryMediaPlayerXml, E as queryMediaPlayerSafe, F as takeScreenshot, G as assertNamedNodeText, H as assertNamedNode, I as validateEcpPath, J as escapeXmlAttribute, K as assertNamedNodeTranslation, L as validateRemoteKey, M as readMediaPlayerPositionMs, N as readMediaPlayerState, O as queryMediaPlayerXmlSafe, Q as readNamedNodeAttribute, R as waitForActiveApp, S as pressKey, T as queryMediaPlayer, U as assertNamedNodeSize, V as waitForSceneGraphNode, W as assertNamedNodeState, X as isNamedNodeVisible, Y as isCompleteSceneGraph, Z as parseSceneGraphNumberList, _ as getDeviceInfo, at as sceneGraphContainsText, b as launchApp, ct as readXmlTag, et as readNamedNodeBounds, f as assertMediaPlayerContainer, g as discoverRokuDevices, h as checkDevice, it as readSceneGraphStatus, j as readMediaPlayerInfo, k as querySceneGraph, m as captureScreenshot, n as captureDebugConsole, nt as readNamedNodeTranslation, ot as readActiveApp, p as assertSceneGraphNode, q as assertSceneGraphNumberNear, r as runDebugCommand, rt as readSceneGraphFailure, st as readXmlAttribute, t as buildDebugCommand, tt as readNamedNodeNumber, v as installPackage, w as queryEcp, x as packageChannel, y as isActiveMediaPlayerState, z as waitForMediaPlayerState } from "./debug-BbJdwNkW.mjs";
2
+ export { assertMediaPlayerContainer, assertNamedNode, assertNamedNodeSize, assertNamedNodeState, assertNamedNodeText, assertNamedNodeTranslation, assertSceneGraphNode, assertSceneGraphNumberNear, buildDebugCommand, captureDebugConsole, captureScreenshot, checkDevice, discoverRokuDevices, escapeXmlAttribute, getDeviceInfo, installPackage, isActiveMediaPlayerState, isCompleteSceneGraph, isNamedNodeVisible, launchApp, packageChannel, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, queryMediaPlayer, queryMediaPlayerSafe, queryMediaPlayerXml, queryMediaPlayerXmlSafe, querySceneGraph, readActiveApp, readMediaPlayerContainer, readMediaPlayerInfo, readMediaPlayerPositionMs, readMediaPlayerState, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readSceneGraphFailure, readSceneGraphStatus, readXmlAttribute, readXmlTag, runDebugCommand, sceneGraphContainsText, takeScreenshot, validateEcpPath, validateRemoteKey, waitForActiveApp, waitForMediaPlayerState, waitForSceneGraphAssertion, waitForSceneGraphNode };
package/dist/rokit.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { B as waitForSceneGraphNode, C as queryEcp, I as validateRemoteKey, L as waitForActiveApp, N as resolvePackageOutputPath, O as querySceneGraph, P as takeScreenshot, R as waitForMediaPlayerState, S as queryActiveApp, _ as installPackage, a as loadEnv, b as packageChannel, c as requirePassword, ct as normalizeError, d as resolveOutputPath, g as getDeviceInfo, h as discoverRokuDevices, i as fail, l as requireTarget, lt as renderError, m as checkDevice, n as captureDebugConsole, nt as readSceneGraphFailure, o as loadLocalEnv, p as assertSceneGraphNode, r as runDebugCommand, rt as readSceneGraphStatus, s as rejectUnsafeEcpPath, t as buildDebugCommand, u as resolveFileOutputPath, w as queryMediaPlayer, x as pressKey, y as launchApp } from "./debug-af6vvS9T.mjs";
2
+ import { C as queryActiveApp, L as validateRemoteKey, P as resolvePackageOutputPath, R as waitForActiveApp, S as pressKey, T as queryMediaPlayer, V as waitForSceneGraphNode, _ as getDeviceInfo, a as loadEnv, b as launchApp, c as requirePassword, d as resolveOutputPath, g as discoverRokuDevices, h as checkDevice, i as fail, it as readSceneGraphStatus, k as querySceneGraph, l as requireTarget, lt as normalizeError, m as captureScreenshot, n as captureDebugConsole, o as loadLocalEnv, p as assertSceneGraphNode, r as runDebugCommand, rt as readSceneGraphFailure, s as rejectUnsafeEcpPath, t as buildDebugCommand, u as resolveFileOutputPath, ut as renderError, v as installPackage, w as queryEcp, x as packageChannel, z as waitForMediaPlayerState } from "./debug-BbJdwNkW.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { Effect } from "effect";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -255,8 +255,7 @@ const runCommand = async (context, command, dryRun) => {
255
255
  const path = timestampOutputPath(resolveFileOutputPath(command.outputPath, "screenshot output path"));
256
256
  if (dryRun) return dryRunResult(command.name, { path });
257
257
  const password = requirePassword(deviceContext);
258
- mkdirSync(dirname(path), { recursive: true });
259
- const screenshotPath = await takeScreenshot({
258
+ const screenshotPath = await captureScreenshot({
260
259
  ...deviceContext,
261
260
  password
262
261
  }, path);
@@ -405,7 +404,7 @@ const writeProof = async (context, outputDir, includeScreenshot) => {
405
404
  writeJson("media-player", await observe(async () => await queryMediaPlayer(context)));
406
405
  if (includeScreenshot) {
407
406
  const password = requirePassword(context);
408
- const path = await takeScreenshot({
407
+ const path = await captureScreenshot({
409
408
  ...context,
410
409
  password
411
410
  }, timestampOutputPath(`${outputDir}/screenshot.png`));
@@ -38,11 +38,11 @@ Release writes use the `putio-release-bot` installation token. The default `GITH
38
38
 
39
39
  ## Package Contents
40
40
 
41
- The npm package includes `dist`, `README.md`, `docs`, `examples`, `AGENTS.md`,
42
- `CONTRIBUTING.md`, and `SECURITY.md`. The docs and generic live probe are
43
- included so agents consuming the package can inspect readiness, distribution,
44
- security, and generic Roku proof mechanics without cloning extra private
45
- context.
41
+ The npm package includes `dist`, `README.md`, `docs`, `examples`, `skills`,
42
+ `AGENTS.md`, `CONTRIBUTING.md`, and `SECURITY.md`. The docs, consumer skill, and
43
+ generic live probe are included so agents consuming the package can inspect
44
+ readiness, distribution, security, and generic Roku proof mechanics without
45
+ cloning extra private context.
46
46
 
47
47
  ## Release Smoke
48
48
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/rokit",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "A tiny CLI companion for Roku device harness work.",
5
5
  "keywords": [
6
6
  "cli",
@@ -28,7 +28,8 @@
28
28
  "docs",
29
29
  "examples",
30
30
  "README.md",
31
- "SECURITY.md"
31
+ "SECURITY.md",
32
+ "skills"
32
33
  ],
33
34
  "type": "module",
34
35
  "sideEffects": false,
@@ -57,19 +58,19 @@
57
58
  "verify": "vp check . && tsc --noEmit && vp run test && npm pack --dry-run"
58
59
  },
59
60
  "dependencies": {
60
- "@effect/platform-node": "4.0.0-beta.66",
61
- "effect": "4.0.0-beta.66",
62
- "roku-deploy": "3.17.3"
61
+ "@effect/platform-node": "4.0.0-beta.69",
62
+ "effect": "4.0.0-beta.69",
63
+ "roku-deploy": "3.17.4"
63
64
  },
64
65
  "devDependencies": {
65
- "@effect/vitest": "4.0.0-beta.66",
66
+ "@effect/vitest": "4.0.0-beta.69",
66
67
  "@types/node": "^25.8.0",
67
68
  "typescript": "^6.0.2",
68
- "vite-plus": "^0.1.20",
69
+ "vite-plus": "^0.1.22",
69
70
  "vitest": "^4.1.6"
70
71
  },
71
72
  "engines": {
72
73
  "node": ">=24.14.0"
73
74
  },
74
- "packageManager": "pnpm@11.0.0"
75
+ "packageManager": "pnpm@11.2.2"
75
76
  }
@@ -1,9 +1,9 @@
1
1
  ---
2
- name: rokit-harness
3
- description: Use rokit as a generic Roku harness adapter for packaging, installing, launching, key input, ECP/SceneGraph/media-player observation, readiness waits, debug console capture, screenshots, and proof bundles.
2
+ name: rokit
3
+ description: Use when testing, sideloading, deploying, or debugging Roku apps with rokit, including package/install/launch flows, remote key input, BrightScript console capture, ECP/SceneGraph/media-player observation, screenshots, readiness waits, and proof bundles.
4
4
  ---
5
5
 
6
- # rokit Harness
6
+ # rokit
7
7
 
8
8
  Use this skill when a repo consumes `@putdotio/rokit` for Roku device harness
9
9
  work. Keep `rokit` generic and put app journeys, product selectors, fixture
@@ -33,6 +33,18 @@ repo.
33
33
  9. Use `rokit press --until-node ...` for bounded navigation loops instead of
34
34
  arbitrary sleeps.
35
35
 
36
+ ## Common Commands
37
+
38
+ ```bash
39
+ rokit describe
40
+ rokit --json snapshot --fields command,status,data.activeApp,data.mediaPlayer
41
+ rokit --dry-run package --out artifacts/live/channel.zip
42
+ rokit --dry-run launch dev
43
+ rokit --json proof artifacts/live/proof --screenshot
44
+ rokit --input-json '{"command":"press","keys":["Down","Select"]}'
45
+ rokit console artifacts/live/console.log --duration-ms 30000
46
+ ```
47
+
36
48
  ## Boundaries
37
49
 
38
50
  - Device IPs, developer passwords, screenshots, packages, and proof bundles stay