Documentation

Applications CLI commands

Pack, upload, update and delete web applications, and read or write their key/value store and its webhooks.

Every command here runs as described in the CLI reference: global flags, JSON output, exit codes, revisions and retries.

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]
ParameterTypeRequiredDefaultNotes
<directory>pathyesnoneThe built static directory. It needs a root index.html.
--output FILEpathnonothing is writtenAlso 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.tgz

The 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]
ParameterTypeRequiredDefaultNotes
[directory]pathnothe current directoryThe directory to write screenrig.d.ts into. It must exist.
screenrig app types ./src

The 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]
ParameterTypeRequiredDefaultNotes
<directory>pathyesnoneThe built static directory. It needs a root index.html.
--port PORTintegerno18890The port the harness listens on. The runtime trusts the harness on 18890 alone, so any other port is refused.
--openbooleannooffOpen the harness in your browser.
--no-reloadbooleannooffKeep the page as it is when a file changes. Without it the page reloads on every change.
--kv FILEpathno.screenrig-dev/kv.jsonThe JSON file that holds the key/value store of the run.
--kv-from APP_IDstringnononeRead the key/value store of this uploaded application, read-only, instead of a file.
screenrig app dev ./lobby-board --open

The 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]
ParameterTypeRequiredDefaultNotes
<directory>pathyesnoneThe built static directory.
--name NAMEstringnoa generated nameApplication name, 1 to 120 characters after trimming, without control characters.
--no-waitbooleannooffReturn before the publication operation finishes. Run operations wait before pinning the release into a playlist.
--poll-ms MSintegerno1000Interval between operation polls while waiting.
--timeout MSintegerno120000Deadline 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]
ParameterTypeRequiredDefaultNotes
<id>stringyesnoneThe app_ application id.
<directory>pathyesnoneThe built static directory.
--expect-rev REVISIONstringnononeOptional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry.
--no-waitbooleannooffReturn before the publication operation finishes. Run operations wait before pinning the release into a playlist.
--poll-ms MSintegerno1000Interval between operation polls while waiting.
--timeout MSintegerno120000Deadline for the wait. It also bounds each underlying request.
screenrig app update app_01EXAMPLEAPP00000000000 ./lobby-board

The 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 list

No parameters.

screenrig app list

app rename#

Change an application's display name.

app rename <id> --name NAME [--expect-rev REVISION]
ParameterTypeRequiredDefaultNotes
<id>stringyesnoneThe app_ application id.
--name NAMEstringyesnoneThe new name, 1 to 120 characters after trimming, without control characters.
--expect-rev REVISIONstringnononeOptional 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>
ParameterTypeRequiredDefaultNotes
<id>stringyesnoneThe app_ application id.
screenrig app show app_01EXAMPLEAPP00000000000

latest_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]
ParameterTypeRequiredDefaultNotes
<id>stringyesnoneThe app_ application id.
--expect-rev REVISIONstringnononeOptional revision precondition. Omit to write the current resource. A supplied stale value is revision_conflict: refetch, reapply, retry.
screenrig app delete app_01EXAMPLEAPP00000000000

There 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
ParameterTypeRequiredDefaultNotes
--app-id IDstringyesnoneThe app_ application id.
screenrig kv list --app-id app_01EXAMPLEAPP00000000000

kv get#

Read one key.

kv get --app-id ID <key>
ParameterTypeRequiredDefaultNotes
--app-id IDstringyesnoneThe app_ application id.
<key>stringyesnoneThe key to read.
screenrig kv get --app-id app_01EXAMPLEAPP00000000000 menu

The 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]
ParameterTypeRequiredDefaultNotes
--app-id IDstringyesnoneThe app_ application id.
<key>stringyesnoneThe key to write.
--json-value JSONstringone modenoneA JSON value, canonicalized before it is sent, always stored as application/json. An explicit application/json content type is also accepted.
--file FILEpathone modenoneRead the value bytes from a file, at most 1 MiB, or from stdin with -. Defaults to application/octet-stream.
--value-base64 BASE64stringone modenoneCanonical padded standard base64 without whitespace, at most 1,398,104 characters; an empty value is valid. Defaults to application/octet-stream.
--content-type TYPEstringnoapplication/octet-stream for bytesOne to 127 characters.
--expect-rev REVISIONstringnononeWrite 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]
ParameterTypeRequiredDefaultNotes
--app-id IDstringyesnoneThe app_ application id.
<key>stringyesnoneThe key to delete.
--expect-rev REVISIONstringnononeOptional 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 menu

Key/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 codeExitMeaning
kv_webhook_limit_reached5The application or card already has 20 webhooks. Nothing was created. Delete one first.
not_found4The application is not in this project, or no webhook has this id on it.
card_not_found4The 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]
ParameterTypeRequiredDefaultNotes
<key>stringwith --app-idnoneThe 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 IDstringone ofnoneThe app_ application id. Give exactly one of --app-id and --card.
--card CARD_IDstringone ofnoneThe crd_ card id: the webhook writes the card's data document. See Card webhooks.
--description TEXTstringnononeWhat sends to it, at most 200 bytes. It must not hold a credential.
--map FILEpathnononeThe 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 TEMPLATEstringnononeWrite keyed documents under a key filled from {screen.field.NAME} tokens, such as room-{screen.field.room}.
--signingbooleannooffAlso require ScreenRig-Signature on every post and print the signing secret once. Not with --verify.
--verify VENDORstringnononeAccept 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 FILEpathwith --verifynoneRead 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 NAMEstringwith generic_hmacnoneThe header that carries the signature, 1 to 100 letters, digits, - or _.
--verify-encoding ENCODINGstringnohexWith generic_hmac: how the signature is written, hex or base64; it may begin with sha256=.
--verify-timestamp-header NAMEstringnononeWith 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 SECONDSintegerno300With --verify-timestamp-header: how far the timestamp may sit from the server's clock, 1 to 86400.
--handshake VENDORstringnononeAnswer 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)
ParameterTypeRequiredDefaultNotes
--app-id IDstringone ofnoneThe app_ application id. Give exactly one of --app-id and --card.
--card CARD_IDstringone ofnoneThe crd_ card id.
screenrig kv webhook list --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook list --card crd_01EXAMPLEBOARD0000000000

Each 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)
ParameterTypeRequiredDefaultNotes
<webhook_id>stringyesnoneThe kvw_ webhook id.
--app-id IDstringone ofnoneThe app_ application id. Give exactly one of --app-id and --card.
--card CARD_IDstringone ofnoneThe crd_ card id.
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --app-id app_01EXAMPLEAPP00000000000
screenrig kv webhook delete kvw_01EXAMPLEWEBHOOK00000 --card crd_01EXAMPLEBOARD0000000000

kv 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]
ParameterTypeRequiredDefaultNotes
<webhook_id>stringyesnoneThe kvw_ webhook id.
--app-id IDstringone ofnoneThe app_ application id. Give exactly one of --app-id and --card.
--card CARD_IDstringone ofnoneThe crd_ card id.
--signingbooleannooffWith --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 --signing

The 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.

Other command groups