@putdotio/rokit 1.7.0 → 2.0.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 +90 -0
- package/CONTRIBUTING.md +52 -0
- package/README.md +80 -112
- package/SECURITY.md +38 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.mjs +2 -2
- package/dist/rokit.mjs +798 -66
- package/dist/{roku-B61mpmWt.mjs → roku-BxnS6Axs.mjs} +102 -5
- package/docs/DISTRIBUTION.md +60 -0
- package/docs/READINESS.md +80 -0
- package/docs/skills/rokit-harness/SKILL.md +40 -0
- package/examples/live-probe-channel/components/MainScene.xml +26 -0
- package/examples/live-probe-channel/manifest +5 -0
- package/examples/live-probe-channel/source/main.brs +14 -0
- package/package.json +15 -5
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as rokuDeploy from "roku-deploy";
|
|
2
|
-
import {
|
|
2
|
+
import { createSocket } from "node:dgram";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, resolve } from "node:path";
|
|
3
4
|
//#region src/xml.ts
|
|
4
5
|
const readXmlTag = (xml, tag) => {
|
|
5
6
|
return new RegExp(`<${tag}>([^<]*)</${tag}>`).exec(xml)?.[1]?.trim();
|
|
@@ -70,7 +71,7 @@ const isNamedNodeVisible = (xml, nodeName) => {
|
|
|
70
71
|
const attributes = readNamedNodeAttributes(xml, nodeName);
|
|
71
72
|
return attributes !== void 0 && !attributes.includes("visible=\"false\"");
|
|
72
73
|
};
|
|
73
|
-
const isCompleteSceneGraph = (xml) => !xml.includes("<All_Nodes>") || xml.includes("
|
|
74
|
+
const isCompleteSceneGraph = (xml) => !xml.includes("<All_Nodes>") || xml.includes("</sgnodes>") && (readSceneGraphStatus(xml).status !== void 0 || readSceneGraphFailure(xml) !== void 0);
|
|
74
75
|
const escapeXmlAttribute = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll("\"", """);
|
|
75
76
|
const sceneGraphContainsText = (xml, text) => xml.includes(escapeXmlAttribute(text));
|
|
76
77
|
const assertSceneGraphNumberNear = (actual, expected, label, tolerance = 1) => {
|
|
@@ -161,6 +162,50 @@ const getDeviceInfo = async (context) => await rokuDeploy.getDeviceInfo({
|
|
|
161
162
|
timeout: context.timeoutMs
|
|
162
163
|
});
|
|
163
164
|
const queryActiveApp = async (context) => readActiveApp(await fetchText(context, "/query/active-app"));
|
|
165
|
+
const discoverRokuDevices = async (timeoutMs = 3e3) => await new Promise((resolveDevices, reject) => {
|
|
166
|
+
const socket = createSocket("udp4");
|
|
167
|
+
const devices = /* @__PURE__ */ new Map();
|
|
168
|
+
const request = [
|
|
169
|
+
"M-SEARCH * HTTP/1.1",
|
|
170
|
+
"HOST: 239.255.255.250:1900",
|
|
171
|
+
"MAN: \"ssdp:discover\"",
|
|
172
|
+
"MX: 1",
|
|
173
|
+
"ST: roku:ecp",
|
|
174
|
+
"",
|
|
175
|
+
""
|
|
176
|
+
].join("\r\n");
|
|
177
|
+
const finish = () => {
|
|
178
|
+
socket.close();
|
|
179
|
+
resolveDevices([...devices.values()]);
|
|
180
|
+
};
|
|
181
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
182
|
+
socket.on("message", (message) => {
|
|
183
|
+
const headers = readSsdpHeaders(message.toString("utf8"));
|
|
184
|
+
const location = headers.get("location");
|
|
185
|
+
if (!location) return;
|
|
186
|
+
devices.set(location, {
|
|
187
|
+
location,
|
|
188
|
+
server: headers.get("server"),
|
|
189
|
+
target: readLocationTarget(location),
|
|
190
|
+
usn: headers.get("usn")
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
socket.on("error", (error) => {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
socket.close();
|
|
196
|
+
reject(error);
|
|
197
|
+
});
|
|
198
|
+
socket.bind(() => {
|
|
199
|
+
socket.setBroadcast(true);
|
|
200
|
+
socket.send(Buffer.from(request), 1900, "239.255.255.250", (error) => {
|
|
201
|
+
if (error) {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
socket.close();
|
|
204
|
+
reject(error);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
164
209
|
const waitForActiveApp = async (context, appId, timeoutMs = 1e4) => {
|
|
165
210
|
const start = Date.now();
|
|
166
211
|
let lastApp;
|
|
@@ -191,7 +236,10 @@ const pressKey = async (context, key) => {
|
|
|
191
236
|
validateRemoteKey(key);
|
|
192
237
|
await postOk(context, ecpUrl(context, `/keypress/${encodeURIComponent(key)}`));
|
|
193
238
|
};
|
|
194
|
-
const queryEcp = async (context, path) =>
|
|
239
|
+
const queryEcp = async (context, path) => {
|
|
240
|
+
const safePath = validateEcpPath(path);
|
|
241
|
+
return await fetchText(context, safePath.startsWith("/") ? safePath : `/${safePath}`);
|
|
242
|
+
};
|
|
195
243
|
const queryMediaPlayerXml = async (context) => await queryEcp(context, "/query/media-player");
|
|
196
244
|
const queryMediaPlayer = async (context) => readMediaPlayerInfo(await queryMediaPlayerXml(context));
|
|
197
245
|
const queryMediaPlayerXmlSafe = async (context) => {
|
|
@@ -271,7 +319,7 @@ const querySceneGraph = async (context, options = {}) => {
|
|
|
271
319
|
}
|
|
272
320
|
if (attempt < attempts - 1) await sleep(retryDelayMs);
|
|
273
321
|
}
|
|
274
|
-
if (lastXml !== "")
|
|
322
|
+
if (lastXml !== "") throw new Error("SceneGraph query returned incomplete XML");
|
|
275
323
|
throw new Error(`SceneGraph query failed: ${formatErrorMessage(lastError)}`);
|
|
276
324
|
};
|
|
277
325
|
const assertSceneGraphNode = async (context, nodeName, expectation) => {
|
|
@@ -332,6 +380,39 @@ const takeScreenshot = async (context, outputPath) => {
|
|
|
332
380
|
password: context.password
|
|
333
381
|
});
|
|
334
382
|
};
|
|
383
|
+
const packageChannel = async (outputPath, rootDir = process.cwd()) => {
|
|
384
|
+
const options = packageOptions(outputPath, rootDir);
|
|
385
|
+
await rokuDeploy.createPackage(options);
|
|
386
|
+
return { path: rokuDeploy.getOutputZipFilePath(options) };
|
|
387
|
+
};
|
|
388
|
+
const resolvePackageOutputPath = (outputPath, rootDir = process.cwd()) => {
|
|
389
|
+
const options = packageOptions(outputPath, rootDir);
|
|
390
|
+
return rokuDeploy.getOutputZipFilePath(options);
|
|
391
|
+
};
|
|
392
|
+
const packageOptions = (outputPath, rootDir = process.cwd()) => {
|
|
393
|
+
const resolvedRoot = resolve(rootDir);
|
|
394
|
+
const resolvedOutput = isAbsolute(outputPath) ? resolve(outputPath) : resolve(resolvedRoot, outputPath);
|
|
395
|
+
return {
|
|
396
|
+
outDir: dirname(resolvedOutput),
|
|
397
|
+
outFile: basename(resolvedOutput),
|
|
398
|
+
rootDir: resolvedRoot
|
|
399
|
+
};
|
|
400
|
+
};
|
|
401
|
+
const validateEcpPath = (path) => {
|
|
402
|
+
rejectUnsafeInput(path, "ECP path");
|
|
403
|
+
if (path.includes("\\")) throw new Error("ECP path must not include backslashes");
|
|
404
|
+
if (path.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(path)) throw new Error("ECP path must be device-relative");
|
|
405
|
+
if (path.includes("?") || path.includes("#")) throw new Error("ECP path must not include query strings or fragments");
|
|
406
|
+
if (/(^|[/\\])\.\.($|[/\\])/.test(path)) throw new Error("ECP path must not include path traversal");
|
|
407
|
+
if (/%(?:2e|2f|5c)/i.test(path)) throw new Error("ECP path must not include percent-encoded path segments");
|
|
408
|
+
return path;
|
|
409
|
+
};
|
|
410
|
+
const rejectUnsafeInput = (value, label) => {
|
|
411
|
+
if ([...value].some((character) => {
|
|
412
|
+
const code = character.charCodeAt(0);
|
|
413
|
+
return code < 32 || code === 127;
|
|
414
|
+
})) throw new Error(`${label} contains control characters`);
|
|
415
|
+
};
|
|
335
416
|
const validateRemoteKey = (key) => {
|
|
336
417
|
if (key.startsWith("Lit_")) return;
|
|
337
418
|
if (!remoteKeySet.has(key)) throw new Error(`unsupported remote key: ${key}`);
|
|
@@ -386,5 +467,21 @@ const sleep = (ms) => new Promise((resolve) => {
|
|
|
386
467
|
setTimeout(resolve, ms);
|
|
387
468
|
});
|
|
388
469
|
const formatErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
470
|
+
const readSsdpHeaders = (text) => {
|
|
471
|
+
const headers = /* @__PURE__ */ new Map();
|
|
472
|
+
for (const line of text.split(/\r?\n/)) {
|
|
473
|
+
const separator = line.indexOf(":");
|
|
474
|
+
if (separator <= 0) continue;
|
|
475
|
+
headers.set(line.slice(0, separator).trim().toLowerCase(), line.slice(separator + 1).trim());
|
|
476
|
+
}
|
|
477
|
+
return headers;
|
|
478
|
+
};
|
|
479
|
+
const readLocationTarget = (location) => {
|
|
480
|
+
try {
|
|
481
|
+
return new URL(location).hostname;
|
|
482
|
+
} catch {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
389
486
|
//#endregion
|
|
390
|
-
export {
|
|
487
|
+
export { assertNamedNode as A, readNamedNodeAttribute as B, takeScreenshot as C, waitForMediaPlayerState as D, waitForActiveApp as E, assertSceneGraphNumberNear as F, readSceneGraphFailure as G, readNamedNodeBounds as H, escapeXmlAttribute as I, readActiveApp as J, readSceneGraphStatus as K, isCompleteSceneGraph as L, assertNamedNodeState as M, assertNamedNodeText as N, waitForSceneGraphAssertion as O, assertNamedNodeTranslation as P, isNamedNodeVisible as R, resolvePackageOutputPath as S, validateRemoteKey as T, readNamedNodeNumber as U, readNamedNodeAttributes as V, readNamedNodeTranslation as W, readXmlTag as X, readXmlAttribute as Y, querySceneGraph as _, getDeviceInfo as a, readMediaPlayerPositionMs as b, launchApp as c, queryActiveApp as d, queryEcp as f, queryMediaPlayerXmlSafe as g, queryMediaPlayerXml as h, discoverRokuDevices as i, assertNamedNodeSize as j, waitForSceneGraphNode as k, packageChannel as l, queryMediaPlayerSafe as m, assertSceneGraphNode as n, installPackage as o, queryMediaPlayer as p, sceneGraphContainsText as q, checkDevice as r, isActiveMediaPlayerState as s, assertMediaPlayerContainer as t, pressKey as u, readMediaPlayerContainer as v, validateEcpPath as w, readMediaPlayerState as x, readMediaPlayerInfo as y, parseSceneGraphNumberList as z };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Distribution
|
|
2
|
+
|
|
3
|
+
`rokit` is a public npm package published as `@putdotio/rokit`.
|
|
4
|
+
|
|
5
|
+
## Local Contract
|
|
6
|
+
|
|
7
|
+
The release path starts with the repo-local verification command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm verify
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`verify` runs formatting/lint checks, TypeScript, package bundling, tests, and an npm pack dry run. GitHub Actions calls this same command before release.
|
|
14
|
+
|
|
15
|
+
## Continuous Release
|
|
16
|
+
|
|
17
|
+
Merges to `main` are considered publishable. The CI workflow runs:
|
|
18
|
+
|
|
19
|
+
1. `verify` on pull requests and `main` pushes.
|
|
20
|
+
2. semantic-release on `main` after `verify` passes.
|
|
21
|
+
|
|
22
|
+
semantic-release analyzes conventional commits, publishes to npm, creates GitHub Releases, and writes release metadata when needed.
|
|
23
|
+
|
|
24
|
+
## Release Credentials
|
|
25
|
+
|
|
26
|
+
The release job uses the `release` GitHub Environment with `deployment: false`.
|
|
27
|
+
|
|
28
|
+
Required protected inputs:
|
|
29
|
+
|
|
30
|
+
- `PUTIO_RELEASE_BOT_CLIENT_ID` as a repository or Environment variable
|
|
31
|
+
- `PUTIO_RELEASE_BOT_PRIVATE_KEY` as an Environment secret
|
|
32
|
+
|
|
33
|
+
The npm package uses Trusted Publishing from GitHub Actions. On npm, configure owner `putdotio`, repository `rokit`, workflow `ci.yml`, and Environment named `release` for the package.
|
|
34
|
+
|
|
35
|
+
During the `@semantic-release/npm` publish step, npm detects the GitHub OIDC identity, mints short-lived publish credentials, and publishes provenance for the release job.
|
|
36
|
+
|
|
37
|
+
Release writes use the `putio-release-bot` installation token. The default `GITHUB_TOKEN` remains read-only, and the release-bot remote is configured only after dependencies are installed.
|
|
38
|
+
|
|
39
|
+
## Package Contents
|
|
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.
|
|
46
|
+
|
|
47
|
+
## Release Smoke
|
|
48
|
+
|
|
49
|
+
After a release, confirm the tag and package are visible:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gh release list --repo putdotio/rokit --limit 5
|
|
53
|
+
npm view @putdotio/rokit version
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Live Roku behavior is not required for npm release. Real-device checks are local/manual because they require a developer-enabled Roku:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
ROKIT_TARGET=<roku-ip> pnpm live:smoke
|
|
60
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Agent Readiness
|
|
2
|
+
|
|
3
|
+
Status: current as of 2026-05-17
|
|
4
|
+
|
|
5
|
+
`rokit` is a CLI/package repo. There is no long-running app to boot; readiness is based on deterministic package verification plus optional live Roku proof.
|
|
6
|
+
|
|
7
|
+
## Grade
|
|
8
|
+
|
|
9
|
+
Overall: B+
|
|
10
|
+
|
|
11
|
+
| Dimension | Status | Evidence | Gap |
|
|
12
|
+
| ---------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- |
|
|
13
|
+
| bootable | pass | `pnpm smoke` builds the CLI and checks `--version` plus `--help` | no server boot surface, by design |
|
|
14
|
+
| testable | pass | `pnpm verify` runs TypeScript, bundle, unit tests, and npm pack dry run | live Roku checks require local hardware |
|
|
15
|
+
| observable | pass | CLI commands print active app, device info, raw ECP state, SceneGraph XML, snapshots, proof bundles, screenshots, compact assertion failures, and JSON output by default in non-TTY runs | no CI hardware lane |
|
|
16
|
+
| verifiable | pass | CI runs `pnpm verify`; `pnpm live:smoke` proves a configured Roku responds; `pnpm live:probe` packages, installs, launches, drives, asserts, screenshots, and proofs a generic channel | no CI hardware lane |
|
|
17
|
+
|
|
18
|
+
## Layers
|
|
19
|
+
|
|
20
|
+
- Boot: `pnpm smoke`, `rokit describe`
|
|
21
|
+
- Smoke: `pnpm smoke`, `rokit check`
|
|
22
|
+
- Interact: `rokit press`, `rokit launch`, `rokit query`, `rokit sgnodes`
|
|
23
|
+
- E2e: `pnpm live:probe` for a configured developer-enabled Roku
|
|
24
|
+
- Enforce: GitHub Actions `verify`, optional `.git-hooks/pre-push`
|
|
25
|
+
- Observe: ECP responses, SceneGraph XML, `rokit snapshot`, `rokit proof`, screenshots, concise command output
|
|
26
|
+
- Isolate: `.rokit/` and `.env` stay local per worktree
|
|
27
|
+
|
|
28
|
+
## Agent-Facing CLI Contract
|
|
29
|
+
|
|
30
|
+
- The CLI entrypoint runs through Effect's Node runtime, and expected CLI
|
|
31
|
+
failures are schema-backed errors rendered without stack traces.
|
|
32
|
+
- `rokit describe` exposes the machine-readable command surface, parameters,
|
|
33
|
+
JSON payload fields, and global options without a Roku target.
|
|
34
|
+
- Non-TTY command output defaults to JSON. Use `--output text` when a human
|
|
35
|
+
transcript is required.
|
|
36
|
+
- `--input-json` lets agents provide typed command payloads without translating
|
|
37
|
+
everything into flags.
|
|
38
|
+
- `--fields` trims JSON output for context-window control.
|
|
39
|
+
- `--dry-run` validates mutating commands without touching a device or writing
|
|
40
|
+
files.
|
|
41
|
+
- ECP paths and generated output paths are hardened against common agent
|
|
42
|
+
mistakes: query strings, fragments, traversal, backslashes, control
|
|
43
|
+
characters, encoded path segments, and writes outside the current app root.
|
|
44
|
+
|
|
45
|
+
## Setup For Agents
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm install
|
|
49
|
+
pnpm verify
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`pnpm install` runs the local Effect source setup outside CI only. When `CI` is
|
|
53
|
+
set to any non-empty value, `scripts/prepare-effect.sh` exits without cloning
|
|
54
|
+
`.repos/effect` so install, pack, publish, and CI/CD workspaces stay predictable.
|
|
55
|
+
|
|
56
|
+
Optional local hook:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pnpm hooks:install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Optional live smoke:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
ROKIT_TARGET=<roku-ip> pnpm live:smoke
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Full local live probe:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
cp .env.example .env
|
|
72
|
+
# Fill ROKIT_TARGET and ROKIT_PASSWORD.
|
|
73
|
+
pnpm live:probe
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`pnpm live:probe` reads `.env` and `.env.local`, accepts `ROKU_DEV_TARGET` and
|
|
77
|
+
`ROKU_DEV_PASSWORD` as fallbacks, copies `examples/live-probe-channel` into
|
|
78
|
+
`.rokit/live-probe/app`, packages it, installs it into the Roku developer slot,
|
|
79
|
+
launches `dev`, checks generic SceneGraph labels, sends `Info Back`, and writes
|
|
80
|
+
proof artifacts to `.rokit/live-probe/artifacts`.
|
|
@@ -0,0 +1,40 @@
|
|
|
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, screenshots, and proof bundles.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# rokit Harness
|
|
7
|
+
|
|
8
|
+
Use this skill when a repo consumes `@putdotio/rokit` for Roku device harness
|
|
9
|
+
work. Keep `rokit` generic and put app journeys, product selectors, fixture
|
|
10
|
+
names, content IDs, account state, and product assertions in the consumer app
|
|
11
|
+
repo.
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
1. Run `rokit describe` to inspect the command surface instead of guessing
|
|
16
|
+
flags or `--input-json` payload fields.
|
|
17
|
+
2. Use `--dry-run` before mutating commands such as `package`, `install`,
|
|
18
|
+
`launch`, `press`, `screenshot`, or `proof`.
|
|
19
|
+
3. Prefer structured output. In automation, rely on the non-TTY JSON default or
|
|
20
|
+
pass `--json` explicitly.
|
|
21
|
+
4. Use `--fields` to keep observations small when only a few values are needed.
|
|
22
|
+
5. For live proof, use `rokit snapshot` for a quick state read,
|
|
23
|
+
`rokit proof <output-dir>` for review artifacts, or `pnpm live:probe` in the
|
|
24
|
+
rokit repo for the full generic package/install/launch/input/proof probe.
|
|
25
|
+
6. Use `rokit wait-ready <app-id>` after launch when the app can race ECP or
|
|
26
|
+
SceneGraph readiness.
|
|
27
|
+
7. Use `rokit press --until-node ...` for bounded navigation loops instead of
|
|
28
|
+
arbitrary sleeps.
|
|
29
|
+
|
|
30
|
+
## Boundaries
|
|
31
|
+
|
|
32
|
+
- Device IPs, developer passwords, screenshots, packages, and proof bundles stay
|
|
33
|
+
local or ignored.
|
|
34
|
+
- `rokit` can assert generic Roku and SceneGraph state. Consumer repos own
|
|
35
|
+
product routes, screen names when product-specific, playback/content
|
|
36
|
+
expectations, and review narratives.
|
|
37
|
+
- ECP query paths must be plain paths. Do not include query strings, fragments,
|
|
38
|
+
traversal, backslashes, or encoded path segments.
|
|
39
|
+
- The local Effect source setup is skipped whenever `CI` is set. Do not make CI,
|
|
40
|
+
install, pack, or publish flows depend on cloning `.repos/effect`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8" ?>
|
|
2
|
+
<component name="MainScene" extends="Scene">
|
|
3
|
+
<children>
|
|
4
|
+
<Rectangle
|
|
5
|
+
id="background"
|
|
6
|
+
width="1280"
|
|
7
|
+
height="720"
|
|
8
|
+
color="0x101820FF" />
|
|
9
|
+
<Label
|
|
10
|
+
id="title"
|
|
11
|
+
text="Rokit Live Probe"
|
|
12
|
+
translation="[120, 140]"
|
|
13
|
+
width="1040"
|
|
14
|
+
height="80"
|
|
15
|
+
horizAlign="center"
|
|
16
|
+
vertAlign="center" />
|
|
17
|
+
<Label
|
|
18
|
+
id="status"
|
|
19
|
+
text="Install, launch, input, and proof OK"
|
|
20
|
+
translation="[120, 250]"
|
|
21
|
+
width="1040"
|
|
22
|
+
height="60"
|
|
23
|
+
horizAlign="center"
|
|
24
|
+
vertAlign="center" />
|
|
25
|
+
</children>
|
|
26
|
+
</component>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
sub Main()
|
|
2
|
+
screen = CreateObject("roSGScreen")
|
|
3
|
+
port = CreateObject("roMessagePort")
|
|
4
|
+
screen.SetMessagePort(port)
|
|
5
|
+
screen.CreateScene("MainScene")
|
|
6
|
+
screen.Show()
|
|
7
|
+
|
|
8
|
+
while true
|
|
9
|
+
msg = Wait(0, port)
|
|
10
|
+
if Type(msg) = "roSGScreenEvent" and msg.IsScreenClosed() then
|
|
11
|
+
return
|
|
12
|
+
end if
|
|
13
|
+
end while
|
|
14
|
+
end sub
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@putdotio/rokit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A tiny CLI companion for Roku device harness work.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -22,8 +22,13 @@
|
|
|
22
22
|
"rokit": "dist/rokit.mjs"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
+
"AGENTS.md",
|
|
26
|
+
"CONTRIBUTING.md",
|
|
25
27
|
"dist",
|
|
26
|
-
"
|
|
28
|
+
"docs",
|
|
29
|
+
"examples",
|
|
30
|
+
"README.md",
|
|
31
|
+
"SECURITY.md"
|
|
27
32
|
],
|
|
28
33
|
"type": "module",
|
|
29
34
|
"sideEffects": false,
|
|
@@ -43,17 +48,22 @@
|
|
|
43
48
|
"clean": "rm -rf .turbo coverage dist",
|
|
44
49
|
"hooks:install": "git config core.hooksPath .git-hooks",
|
|
45
50
|
"live:smoke": "vp pack src/index.ts src/rokit.ts --dts && node dist/rokit.mjs check",
|
|
51
|
+
"live:probe": "scripts/live-probe.sh",
|
|
52
|
+
"prepare": "./scripts/prepare-effect.sh",
|
|
46
53
|
"prepack": "vp pack src/index.ts src/rokit.ts --dts",
|
|
47
54
|
"smoke": "vp pack src/index.ts src/rokit.ts --dts && node dist/rokit.mjs --version && node dist/rokit.mjs --help >/dev/null",
|
|
48
|
-
"test": "vp
|
|
55
|
+
"test": "vp pack src/index.ts src/rokit.ts --dts && vitest run --passWithNoTests",
|
|
49
56
|
"typecheck": "tsc --noEmit",
|
|
50
|
-
"verify": "vp check . && tsc --noEmit && vp
|
|
57
|
+
"verify": "vp check . && tsc --noEmit && vp run test && npm pack --dry-run"
|
|
51
58
|
},
|
|
52
59
|
"dependencies": {
|
|
60
|
+
"@effect/platform-node": "4.0.0-beta.66",
|
|
61
|
+
"effect": "4.0.0-beta.66",
|
|
53
62
|
"roku-deploy": "3.17.3"
|
|
54
63
|
},
|
|
55
64
|
"devDependencies": {
|
|
56
|
-
"@
|
|
65
|
+
"@effect/vitest": "4.0.0-beta.66",
|
|
66
|
+
"@types/node": "^24.12.4",
|
|
57
67
|
"typescript": "^6.0.2",
|
|
58
68
|
"vite-plus": "^0.1.20",
|
|
59
69
|
"vitest": "^4.1.6"
|