Applications
An application is one already-built static directory with a root index.html. The packer injects the browser SDK at _screenrig/runtime.js and adds its script tag, so the app reaches window.screenrig with no build step and nothing to add to its dependencies. Nothing in the directory is ever built or executed during packing.
app pack#
Pack a static directory into a deterministic archive without uploading it.
app pack <directory> [--output FILE]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | The built static directory. It needs a root index.html. |
--output FILE | path | no | nothing is written | Also write the archive to this path. Without it the command reports the archive and keeps it in memory. |
screenrig app pack ./lobby-board --output ./lobby-board.tgzThe envelope reports sha256, compressed_bytes, expanded_bytes, file_count, the entry list, and whether the SDK runtime was injected. app upload packs the directory itself, so reach for this command only when you want to inspect the archive first. It runs locally and needs no sign-in.
A package holds at most 5000 files, 250 MiB expanded and 100 MiB compressed, each file at most 32 MiB, with paths of at most 16 levels and 255 bytes. Packing leaves out names that start with ., node_modules/, bower_components/, jspm_packages/, vendor/, venv/, __pycache__/, CVS/, *.map source maps, key and credential files (*.pem, *.key, *.p12, *.pfx, *.crt, *.cer, id_rsa, id_dsa, id_ecdsa, id_ed25519, credentials.json, secrets.json), editor swap and backup files, Thumbs.db and desktop.ini. A .screenrigignore file at the directory root adds gitignore-style patterns, one per line, and cannot bring back a built-in exclusion. Symlinks, hardlinks, sparse files, devices, duplicate paths (compared case-insensitively too), files that change while packing and a directory without a root index.html are refused with the path named: the command exits 2 and sends nothing.
Applications run under a content security policy that blocks inline code. Packing moves inline style and script blocks into _screenrig-inline-* files beside each HTML document and links them in. Inline event handlers such as onclick= and javascript: URLs cannot be moved; packing refuses them and names the file and line.
app types#
Write the TypeScript types for window.screenrig into a directory.
app types [directory]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
[directory] | path | no | the current directory | The directory to write screenrig.d.ts into. It must exist. |
screenrig app types ./srcThe envelope reports path, bytes, sha256 and written, which is false when the file already held the current types. The file matches the runtime that app pack and app upload inject, so run the command again after you update the CLI. An existing screenrig.d.ts is replaced whole; a directory or a symlink at that path is refused, and the command exits 2. It runs locally and needs no sign-in. TypeScript covers what the types declare.
app dev#
Serve a directory in a local Player harness to try an application before you upload it.
app dev <directory> [--port PORT] [--open] [--no-reload] [--kv FILE | --kv-from APP_ID]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | The built static directory. It needs a root index.html. |
--port PORT | integer | no | 18890 | The port the harness listens on. The runtime trusts the harness on 18890 alone, so any other port is refused. |
--open | boolean | no | off | Open the harness in your browser. |
--no-reload | boolean | no | off | Keep the page as it is when a file changes. Without it the page reloads on every change. |
--kv FILE | path | no | .screenrig-dev/kv.json | The JSON file that holds the key/value store of the run. |
--kv-from APP_ID | string | no | none | Read the key/value store of this uploaded application, read-only, instead of a file. |
screenrig app dev ./lobby-board --openThe command serves the harness at http://play.screenrig.localhost:18890 and runs until you stop it. It injects the runtime as app pack does, and reports what packing refuses, naming the file and line, as you edit. The harness simulates the capabilities, the viewport, the screen name, time zone and params, the page timer, pause, display and leaving, device events, and serial ports, GPIO lines, Bluetooth LE peripherals and printers. emit and log lines print in the terminal. Develop locally covers the controls.
app upload#
Pack a directory, publish it as a new application release, and wait for the publication to succeed.
app upload <directory> [--name NAME] [--no-wait] [--poll-ms MS] [--timeout MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<directory> | path | yes | none | The built static directory. |
--name NAME | string | no | a generated name | Application name, 1 to 120 characters after trimming, without control characters. |
--no-wait | boolean | no | off | Return before the publication operation finishes. Run operations wait before pinning the release into a playlist. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
--timeout MS | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |
screenrig app upload ./lobby-board --name "Lobby board"Read three fields: data.operation.state is succeeded, data.application.release_id is the rel_ id an application primitive pins, and data.application.id is the app_ id the kv commands take. A succeeded operation, not a release id on its own, is what means the release is usable.
Every app upload creates a new application and a first immutable release. To publish a later release of the same app, use app update. A playlist keeps serving the release it already pins until you edit that primitive.
A transfer to storage that is cut off is tried up to 4 times while its upload URL is valid. If it still fails, run the same command again to resume the same upload.
app update#
Publish a new release of an existing application, preserving identity, name, and application K/V.
app update <id> <directory> [--expect-rev REVISION] [--no-wait] [--poll-ms MS] [--timeout MS]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
<directory> | path | yes | none | The built static directory. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
--no-wait | boolean | no | off | Return before the publication operation finishes. Run operations wait before pinning the release into a playlist. |
--poll-ms MS | integer | no | 1000 | Interval between operation polls while waiting. |
--timeout MS | integer | no | 120000 | Deadline for the wait. It also bounds each underlying request. |
screenrig app update app_01EXAMPLEAPP00000000000 ./lobby-boardThe name stays as it is; change it with app rename. Wait for data.operation.state: "succeeded", then use playlist replace-release to review the exact page, primitive, new release, and affected screens. Apply with the returned impact token and an optional revision guard as described in the playlist workflow. Existing playlist pins remain unchanged until apply. On a revision conflict, read the app again before deciding whether to retry; do not create a replacement application just to bypass the conflict.
app list#
List the project's applications.
app listNo parameters.
screenrig app listapp rename#
Change an application's display name.
app rename <id> --name NAME [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
--name NAME | string | yes | none | The new name, 1 to 120 characters after trimming, without control characters. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig app rename app_01EXAMPLEAPP00000000000 --name "Lobby welcome"Only the display name changes. The application id, its releases, its key/value data and the playlists that pin its releases stay as they are, and no directory is uploaded. Renaming to the current name leaves the revision unchanged. Names may repeat; address an application by its id.
app show#
Read one application.
app show <id>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
screenrig app show app_01EXAMPLEAPP00000000000latest_ready_release is absent until a first publish reaches ready. Publish state lives on the operation and the release, not on the application. last_used_at is absent until a screen first plays the application.
app delete#
Delete an application and every one of its releases.
app delete <id> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<id> | string | yes | none | The app_ application id. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig app delete app_01EXAMPLEAPP00000000000There is no per-release delete. While a screen shows one of the application’s releases, the delete answers application_in_use: give those screens other content first. The delete does not check playlists that pin a release without showing it, so update or delete those playlists too.
Application key/value
Key/value storage belongs to one application and is binary-safe. Every command takes --app-id with the app_ id from app upload, never a release id. Use exactly one value mode per write.
kv list#
List the keys stored for one application.
kv list --app-id ID| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
screenrig kv list --app-id app_01EXAMPLEAPP00000000000kv get#
Read one key.
kv get --app-id ID <key>| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to read. |
screenrig kv get --app-id app_01EXAMPLEAPP00000000000 menuThe answer carries the value as data.value_base64, JSON values included: decode it, and parse JSON only when data.content_type says JSON. kv list omits values.
kv set#
Write one key from JSON, a file, or base64 bytes.
kv set --app-id ID <key> --json-value JSON [--expect-rev REVISION]
kv set --app-id ID <key> --file FILE [--content-type TYPE] [--expect-rev REVISION]
kv set --app-id ID <key> --value-base64 BASE64 [--content-type TYPE] [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to write. |
--json-value JSON | string | one mode | none | A JSON value, canonicalized before it is sent, always stored as application/json. An explicit application/json content type is also accepted. |
--file FILE | path | one mode | none | Read the value bytes from a file, at most 1 MiB, or from stdin with -. Defaults to application/octet-stream. |
--value-base64 BASE64 | string | one mode | none | Canonical padded standard base64 without whitespace, at most 1,398,104 characters; an empty value is valid. Defaults to application/octet-stream. |
--content-type TYPE | string | no | application/octet-stream for bytes | One to 127 characters. |
--expect-rev REVISION | string | no | none | Write only if the stored entry is still at this revision. |
screenrig kv set --app-id app_01EXAMPLEAPP00000000000 menu --json-value '{"soup":"lentil"}'kv delete#
Delete one key.
kv delete --app-id ID <key> [--expect-rev REVISION]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | yes | none | The app_ application id. |
<key> | string | yes | none | The key to delete. |
--expect-rev REVISION | string | no | none | Optional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry. |
screenrig kv delete --app-id app_01EXAMPLEAPP00000000000 menuKey/value webhooks
A key/value webhook is a URL that writes one key of one application, so any outside system, such as a point of sale, a spreadsheet script or a cron job, pushes data to screens with one HTTP POST. An application that subscribes with kv.onChange updates within seconds on every screen that runs it. An application has at most 20 webhooks.
The sender authenticates with the webhook's secret, srkw_…, as Authorization: Bearer <secret>, or with HTTP Basic using the webhook id as the username and the secret as the password; a secret in the URL or query string is never accepted. The raw body, at most 1 MiB, becomes the key's value and Content-Type its content type. Each post does not count toward the agent request cap, is counted toward storage like kv set, raises the revision, and answers with the key's new metadata, never its value. An optional Idempotency-Key header deduplicates a sender's retries for 24 hours. One webhook takes at most 120 posts a minute, and a missing or wrong secret or an unknown webhook answers 401. Push data from another system has curl examples.
The secret is shown only by kv webhook create and kv webhook rotate-secret. The URL is not secret. Creating, rotating and deleting a webhook append kv_webhook.created, kv_webhook.secret_rotated and kv_webhook.deleted to the project event log, and each post appends kv.updated like any write.
| Problem code | Exit | Meaning |
|---|---|---|
kv_webhook_limit_reached | 5 | The application or card already has 20 webhooks. Nothing was created. Delete one first. |
not_found | 4 | The application is not in this project, or no webhook has this id on it. |
card_not_found | 4 | The card is not in this project. |
kv webhook create#
Create a webhook that writes one key of an application, or with --card the data document of a card, and receive its URL and secret once.
kv webhook create <key> --app-id ID [--description TEXT]
kv webhook create --card CARD_ID [--description TEXT] [--map FILE|-] [--key-template TEMPLATE] [--signing]
[--verify VENDOR [--verify-secret-file FILE|-] [--verify-header NAME] [--verify-encoding hex|base64]
[--verify-timestamp-header NAME] [--verify-tolerance SECONDS]] [--handshake VENDOR]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<key> | string | with --app-id | none | The key every post writes, 1 to 256 characters: a letter or digit first, then letters, digits, ., _, :, / or -. The key need not exist yet. A card webhook takes no key. |
--app-id ID | string | one of | none | The app_ application id. Give exactly one of --app-id and --card. |
--card CARD_ID | string | one of | none | The crd_ card id: the webhook writes the card's data document. See Card webhooks. |
--description TEXT | string | no | none | What sends to it, at most 200 bytes. It must not hold a credential. |
--map FILE | path | no | none | The mapping that builds the document from the posted body: a JSON object of at most 64 KiB read from a file, or - for stdin. Without it the posted object is the document. |
--key-template TEMPLATE | string | no | none | Write keyed documents under a key filled from {screen.field.NAME} tokens, such as room-{screen.field.room}. |
--signing | boolean | no | off | Also require ScreenRig-Signature on every post and print the signing secret once. Not with --verify. |
--verify VENDOR | string | no | none | Accept posts that carry this vendor's own signature in place of the webhook's srkw_ secret, which keeps working beside it: github, stripe, shopify, slack, linear, vercel, asana or generic_hmac. |
--verify-secret-file FILE | path | with --verify | none | Read the vendor's secret from a file, or - for stdin (never an argument): the secret GitHub, Stripe, Shopify, Slack, Linear or Vercel shows, or a generic_hmac sender's. asana takes none: its handshake delivers it. Only one option reads stdin. |
--verify-header NAME | string | with generic_hmac | none | The header that carries the signature, 1 to 100 letters, digits, - or _. |
--verify-encoding ENCODING | string | no | hex | With generic_hmac: how the signature is written, hex or base64; it may begin with sha256=. |
--verify-timestamp-header NAME | string | no | none | With generic_hmac: a header that carries the Unix time in seconds, or in milliseconds as 13 digits. The signed text is then the timestamp, a dot and the body. |
--verify-tolerance SECONDS | integer | no | 300 | With --verify-timestamp-header: how far the timestamp may sit from the server's clock, 1 to 86400. |
--handshake VENDOR | string | no | none | Answer the setup challenge of microsoft_graph, monday, trello or azure_event_grid. Slack and Asana answer theirs through --verify. |
screenrig kv webhook create menu --app-id app_01EXAMPLEAPP00000000000 --description "Point of sale menu feed"
screenrig kv webhook create --card crd_01EXAMPLEBOARD0000000000 --map alert-map.json --description "CMS alerts"--map, --key-template, --signing, --verify with its options and --handshake apply to a card webhook only.
The envelope is the webhook: data.id (kvw_…), data.key or, for a card, data.card_id, data.url (https://api.screenrig.ai/api/applications/<app_id>/kv-webhooks/<id>/write, or https://api.screenrig.ai/api/cards/<card_id>/kv-webhooks/<id>/write) and data.secret (srkw_…). A signing card webhook also carries data.signing_secret (whsec_…). Enter the URL and secrets in the sending system now. No later command shows them again. After an interrupted or ambiguous create, rerun the identical command within 24 hours: it reuses the saved idempotency key, and the server returns the same webhook and secrets instead of creating another.
kv webhook list#
List the webhooks of an application or a card, oldest first.
kv webhook list (--app-id ID | --card CARD_ID)| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
--app-id ID | string | one of | none | The app_ application id. Give exactly one of --app-id and --card. |
--card CARD_ID | string | one of | none | The crd_ card id. |
screenrig kv webhook list --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook list --card crd_01EXAMPLEBOARD0000000000Each item has id, key, description, url, created_at and, once it has written, last_used_at. A card webhook's item has card_id in place of key, plus mapping (null when the posted object is the document), enabled, signing and, when set, key_template, verify and handshake. A list never carries a secret.
kv webhook delete#
Delete a webhook. Its URL stops working at once, and the key or the card's document keeps its value.
kv webhook delete <webhook_id> (--app-id ID | --card CARD_ID)| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<webhook_id> | string | yes | none | The kvw_ webhook id. |
--app-id ID | string | one of | none | The app_ application id. Give exactly one of --app-id and --card. |
--card CARD_ID | string | one of | none | The crd_ card id. |
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --card crd_01EXAMPLEBOARD0000000000kv webhook rotate-secret#
Replace a webhook's secret and receive the new one once.
kv webhook rotate-secret <webhook_id> (--app-id ID | --card CARD_ID) [--signing]| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
<webhook_id> | string | yes | none | The kvw_ webhook id. |
--app-id ID | string | one of | none | The app_ application id. Give exactly one of --app-id and --card. |
--card CARD_ID | string | one of | none | The crd_ card id. |
--signing | boolean | no | off | With --card: replace the signing secret instead of the webhook secret. |
screenrig kv webhook rotate-secret kvw_01EXAMPLEWEBHOOK00000 --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook rotate-secret kvw_01EXAMPLEWEBHOOK00000 --card crd_01EXAMPLEBOARD0000000000 --signingThe envelope carries the new secret in data.secret. The old secret stops working at once, so enter the new one in the sending system. With --signing the envelope carries the new signing secret in data.signing_secret instead, and the webhook's srkw_ secret stays. After an interrupted or ambiguous rotation, rerun the identical command: it reuses the saved idempotency key, and the server returns the rotation that already happened instead of rotating again.