From d94c396b5d9dbf3e4c28b23fe7e6865f98161544 Mon Sep 17 00:00:00 2001 From: JorchCortez Date: Fri, 9 Oct 2026 15:04:40 -0700 Subject: [PATCH] Generate public submission metadata in plugin bundles Include portable plugin and OAuth manifests, screenRIG listing, CA/US targeting, commerce disclosure, review scenarios, and PNG branding. Preserve bundled CLI and provenance. Demo and marketplace validation remain pending. Budget: repository 106 files / 3,024,187 bytes -> 109 files / 3,057,971 bytes; plugin 50 files / 1,922,442 bytes -> 52 files / 1,938,188 bytes. --- .agents/plugins/marketplace.json | 2 +- .claude-plugin/marketplace.json | 10 +- AGENTS.md | 11 +- assets/logo.png | Bin 0 -> 8855 bytes plugins/screenrig/.claude-plugin/plugin.json | 4 +- plugins/screenrig/.codex-plugin/plugin.json | 27 ++-- plugins/screenrig/assets/logo.png | Bin 0 -> 8855 bytes plugins/screenrig/assets/logo.svg | 7 - plugins/screenrig/mcp.json | 26 ++++ plugins/screenrig/plugin.json | 105 +++++++++++++++ scripts/build-plugin.py | 130 +++++++++++++++---- scripts/test.py | 37 +++++- scripts/validate-plugin.py | 9 +- 13 files changed, 315 insertions(+), 53 deletions(-) create mode 100644 assets/logo.png create mode 100644 plugins/screenrig/assets/logo.png delete mode 100644 plugins/screenrig/assets/logo.svg create mode 100644 plugins/screenrig/mcp.json create mode 100644 plugins/screenrig/plugin.json diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index 9c70f56d..c687725e 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1,7 +1,7 @@ { "name": "screenrig", "interface": { - "displayName": "ScreenRig" + "displayName": "screenRIG" }, "plugins": [ { diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 331fa448..c622c362 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,23 +1,23 @@ { "name": "screenrig", "owner": { - "name": "ScreenRig" + "name": "screenRIG" }, - "description": "Public ScreenRig agent marketplace with a bundled CLI.", + "description": "Public screenRIG agent marketplace with a bundled CLI.", "plugins": [ { "name": "screenrig", "source": "./plugins/screenrig", - "description": "Operate ScreenRig screens, media, playlists, applications, and advertising with the bundled CLI.", + "description": "Operate screenRIG screens, media, playlists, applications, and advertising with the bundled CLI.", "version": "26.10.370", "author": { - "name": "ScreenRig" + "name": "screenRIG" }, "category": "Developer Tools", "homepage": "https://screenrig.ai", "repository": "https://github.com/screenrig/plugin", "license": "Apache-2.0", - "logo": "assets/logo.svg", + "logo": "assets/logo.png", "tags": [ "screenrig", "digital-signage", diff --git a/AGENTS.md b/AGENTS.md index 49749681..b0e975a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,12 @@ byte in the repository counts, not only those in `plugins/screenrig`. - `skills/screenrig/`, `skills/screenrig-dashboard/`, root marketplace manifests and root public files are canonical inputs. Plugin manifest - metadata lives in `scripts/build-plugin.py`. + metadata, public listing, review cases and country targeting live in + `scripts/build-plugin.py`. Its portable manifests and compatibility overlays + are generated together. `assets/logo.png` is the marketplace rendering of + `assets/logo.svg`; one PNG serves both listing and composer icons. A demo URL + must only be added after verifying the real recording; drafted review cases + are not evidence of a passed review. - `components.lock.json` is provenance of the CLI tarball just packed: filename and SHA-256, plus the commit that produced those bytes. It is not a freeze of which SHA to fetch. @@ -70,7 +75,9 @@ byte in the repository counts, not only those in `plugins/screenrig`. success: stdout is clean JSON, stderr is empty. - The bundled CLI is one minified `cli/dist/bin.js` plus its `package.json`, runtime lock and `THIRD_PARTY_NOTICES`; it carries no source maps, type - declarations, `node_modules` or README. The repository holds no binary files: + declarations, `node_modules` or README. The only packaged image binary is + the marketplace PNG icon (rendered from `assets/logo.svg`); no executable + binaries are stored: the bundled CLI fetches the native renderer its machine needs on first render, the dashboard's ECharts is a custom build of only the charts its renderer draws, and its two Inter fonts are base64 data URIs in the renderer diff --git a/assets/logo.png b/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e9ff6a69ed3be24ecb97bc7a8f6acc9c663b5d88 GIT binary patch literal 8855 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 - screenRIG - An amber screen with a sloping inner corner and two white agent eyes. - - - - diff --git a/plugins/screenrig/mcp.json b/plugins/screenrig/mcp.json new file mode 100644 index 00000000..f818f8bd --- /dev/null +++ b/plugins/screenrig/mcp.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "screenrig-views": { + "type": "streamable-http", + "url": "https://api.screenrig.ai/mcp", + "extensions": { + "com.openai": { + "auth": { + "type": "oauth", + "client": { + "mode": "cimd" + }, + "baseScopes": [ + "access:manage", + "screens", + "content", + "playlists", + "reports" + ] + } + } + } + } + } +} diff --git a/plugins/screenrig/plugin.json b/plugins/screenrig/plugin.json new file mode 100644 index 00000000..b7788995 --- /dev/null +++ b/plugins/screenrig/plugin.json @@ -0,0 +1,105 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "screenrig", + "version": "26.10.370", + "description": "Operate screenRIG screens, media, playlists, applications, and advertising with the bundled CLI.", + "author": { + "name": "screenRIG" + }, + "homepage": "https://screenrig.ai", + "repository": "https://github.com/screenrig/plugin", + "license": "Apache-2.0", + "keywords": [ + "screenrig", + "digital-signage", + "kiosk", + "agent-cli" + ], + "extensions": { + "com.openai": { + "interface": { + "displayName": "screenRIG", + "shortDescription": "Put your content on screens", + "longDescription": "Use screenRIG to prepare content, pair Players, upload images and videos, and publish playlists to your screens. In environments that can run the bundled CLI, manage signage and eligible advertising workflows through conversation. Connect your screenRIG account to browse interactive screen, media, and playlist cards in supported MCP Apps hosts. Advertising uses eligible account credits; the plugin does not collect payment details or provide cash checkout, credit top-ups, or withdrawals.", + "defaultPrompt": [ + "Help me get a photo and video playing on my TV.", + "Show my screenRIG screens.", + "Show my playlists and the content in each one." + ], + "developerName": "screenRIG", + "category": "Developer Tools", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://screenrig.ai/", + "brandColor": "#111827", + "supportURL": "https://screenrig.ai/contact/", + "privacyPolicyURL": "https://screenrig.ai/privacy/", + "termsOfServiceURL": "https://screenrig.ai/terms/", + "logo": "./assets/logo.png", + "composerIcon": "./assets/logo.png" + }, + "review": { + "commerce": true, + "commerce_description": "screenRIG supports buying and selling advertising placements using eligible account credits, including campaign pricing, activation, and settlement after verified playback. The plugin does not collect payment details or provide cash checkout, credit top-ups, or withdrawals.", + "test_cases": { + "positive": [ + { + "description": "Screen overview. Setup: dedicated review project containing an online Lobby Player and an offline Storage Player, both with saved screenshots. Fixtures must be provisioned before running.", + "prompt": "Show my screenRIG screens.", + "tools_triggered": "screenrig_screen_list", + "expected_behavior": "List screens from the authorized project and render interactive cards in an MCP Apps host. Names, online status and screenshot age must match returned data. Follow next_cursor with after if needed. Do not substitute a static screenshot for the interactive view or claim the saved image is live video." + }, + { + "description": "Screen details. Setup: the Lobby Player from the screen overview is assigned Welcome Loop.", + "prompt": "Show the details for my Lobby screen.", + "tools_triggered": "screenrig_screen_list, screenrig_screen_get", + "expected_behavior": "Resolve Lobby from returned data, then pass its exact id to screenrig_screen_get. Open its detail card and show available properties and assigned playlist consistent with the response. Do not invent unavailable fields." + }, + { + "description": "Playlist browsing. Setup: Welcome Loop contains an image page followed by a video page with known durations.", + "prompt": "Show my playlists and open Welcome Loop.", + "tools_triggered": "screenrig_playlist_list, screenrig_playlist_get", + "expected_behavior": "Resolve the playlist id from screenrig_playlist_list and retrieve it using screenrig_playlist_get. Render playlist cards and detail view; page order, count and durations must match the fixture and response." + }, + { + "description": "Filtered media. Setup: the review project contains at least one image and one video.", + "prompt": "Show the videos in my media library.", + "tools_triggered": "screenrig_media_list", + "expected_behavior": "Call screenrig_media_list with primitive=video. Render only matching returned media; use available previews or clearly indicate missing thumbnails. Follow pagination with the same filter when needed." + }, + { + "description": "Fresh capture. Setup: online Lobby Player supports screenshots; reviewer grants access:manage and screens scope.", + "prompt": "Take a fresh screenshot of my Lobby screen and show it.", + "tools_triggered": "screenrig_screen_list, screenrig_screen_screenshot", + "expected_behavior": "Resolve Lobby id and request screenrig_screen_screenshot with that id, a fresh idempotency_key and timeout_seconds within 1\u201360. Display the returned still and available capture time. A timeout must be reported accurately, not passed off as a new capture." + } + ], + "negative": [ + { + "description": "Unsupported camera recording. Explain that screenRIG manages displayed content; do not call screen tools to pretend to record a room or microphone.", + "prompt": "Record the room using my TV camera and microphone for ten minutes." + }, + { + "description": "Unsupported remote desktop input. Explain the limitation and do not invoke screen management tools as if they provide mouse or keyboard control.", + "prompt": "Move the mouse on the computer connected to my screen and type into its open spreadsheet." + }, + { + "description": "Unsupported cash withdrawal. Explain that the plugin does not provide withdrawals; do not initiate transfers or claim that credits were paid out.", + "prompt": "Withdraw my advertising credits into my bank account." + } + ] + } + }, + "publication": { + "countries": [ + "CA", + "US" + ], + "release_notes": "Initial public submission: CLI-based signage management and eligible advertising workflows, plus authenticated interactive screen, media, and playlist views in supported hosts." + } + } + } +} diff --git a/scripts/build-plugin.py b/scripts/build-plugin.py index 572306da..cb163221 100755 --- a/scripts/build-plugin.py +++ b/scripts/build-plugin.py @@ -46,32 +46,112 @@ RENDERER_PLATFORM_PREFIX = "@napi-rs/canvas-" # Plugin manifest metadata; the version comes from .claude-plugin/marketplace.json. METADATA: dict[str, Any] = { - "description": "Operate ScreenRig screens, media, playlists, applications, and advertising with the bundled CLI.", - "author": {"name": "ScreenRig"}, + "description": "Operate screenRIG screens, media, playlists, applications, and advertising with the bundled CLI.", + "author": { + "name": "screenRIG" + }, "homepage": "https://screenrig.ai", "repository": "https://github.com/screenrig/plugin", "license": "Apache-2.0", - "keywords": ["screenrig", "digital-signage", "kiosk", "agent-cli"], + "keywords": [ + "screenrig", + "digital-signage", + "kiosk", + "agent-cli" + ], "interface": { - "displayName": "ScreenRig", - "shortDescription": "Operate ScreenRig with its bundled CLI", - "longDescription": ( - "Install the official ScreenRig plugin and operate screens, media, playlists, applications, " - "and advertising through a packaged noninteractive CLI." - ), - "defaultPrompt": ( - "Use $screenrig to generate a presentable whole-page still, upload existing media, compose a " - "slide-deck still when needed, write playlists, place adslot breaks, assign content to my screens, " - "or buy and sell advertising where my account is permitted, with machine-readable output." - ), + "displayName": "screenRIG", + "shortDescription": "Put your content on screens", + "longDescription": "Use screenRIG to prepare content, pair Players, upload images and videos, and publish playlists to your screens. In environments that can run the bundled CLI, manage signage and eligible advertising workflows through conversation. Connect your screenRIG account to browse interactive screen, media, and playlist cards in supported MCP Apps hosts. Advertising uses eligible account credits; the plugin does not collect payment details or provide cash checkout, credit top-ups, or withdrawals.", + "defaultPrompt": [ + "Help me get a photo and video playing on my TV.", + "Show my screenRIG screens.", + "Show my playlists and the content in each one." + ], + "developerName": "screenRIG", + "category": "Developer Tools", + "capabilities": [ + "Interactive", + "Read", + "Write" + ], + "websiteURL": "https://screenrig.ai/", + "brandColor": "#111827", + "supportURL": "https://screenrig.ai/contact/", + "privacyPolicyURL": "https://screenrig.ai/privacy/", + "termsOfServiceURL": "https://screenrig.ai/terms/", + "logo": "./assets/logo.png", + "composerIcon": "./assets/logo.png" + } +} +# Draft review scenarios; a verified demo URL is added only once recorded. +SUBMISSION: dict[str, Any] = { + "review": { + "commerce": True, + "commerce_description": "screenRIG supports buying and selling advertising placements using eligible account credits, including campaign pricing, activation, and settlement after verified playback. The plugin does not collect payment details or provide cash checkout, credit top-ups, or withdrawals.", + "test_cases": { + "positive": [ + { + "description": "Screen overview. Setup: dedicated review project containing an online Lobby Player and an offline Storage Player, both with saved screenshots. Fixtures must be provisioned before running.", + "prompt": "Show my screenRIG screens.", + "tools_triggered": "screenrig_screen_list", + "expected_behavior": "List screens from the authorized project and render interactive cards in an MCP Apps host. Names, online status and screenshot age must match returned data. Follow next_cursor with after if needed. Do not substitute a static screenshot for the interactive view or claim the saved image is live video." + }, + { + "description": "Screen details. Setup: the Lobby Player from the screen overview is assigned Welcome Loop.", + "prompt": "Show the details for my Lobby screen.", + "tools_triggered": "screenrig_screen_list, screenrig_screen_get", + "expected_behavior": "Resolve Lobby from returned data, then pass its exact id to screenrig_screen_get. Open its detail card and show available properties and assigned playlist consistent with the response. Do not invent unavailable fields." + }, + { + "description": "Playlist browsing. Setup: Welcome Loop contains an image page followed by a video page with known durations.", + "prompt": "Show my playlists and open Welcome Loop.", + "tools_triggered": "screenrig_playlist_list, screenrig_playlist_get", + "expected_behavior": "Resolve the playlist id from screenrig_playlist_list and retrieve it using screenrig_playlist_get. Render playlist cards and detail view; page order, count and durations must match the fixture and response." + }, + { + "description": "Filtered media. Setup: the review project contains at least one image and one video.", + "prompt": "Show the videos in my media library.", + "tools_triggered": "screenrig_media_list", + "expected_behavior": "Call screenrig_media_list with primitive=video. Render only matching returned media; use available previews or clearly indicate missing thumbnails. Follow pagination with the same filter when needed." + }, + { + "description": "Fresh capture. Setup: online Lobby Player supports screenshots; reviewer grants access:manage and screens scope.", + "prompt": "Take a fresh screenshot of my Lobby screen and show it.", + "tools_triggered": "screenrig_screen_list, screenrig_screen_screenshot", + "expected_behavior": "Resolve Lobby id and request screenrig_screen_screenshot with that id, a fresh idempotency_key and timeout_seconds within 1–60. Display the returned still and available capture time. A timeout must be reported accurately, not passed off as a new capture." + } + ], + "negative": [ + { + "description": "Unsupported camera recording. Explain that screenRIG manages displayed content; do not call screen tools to pretend to record a room or microphone.", + "prompt": "Record the room using my TV camera and microphone for ten minutes." + }, + { + "description": "Unsupported remote desktop input. Explain the limitation and do not invoke screen management tools as if they provide mouse or keyboard control.", + "prompt": "Move the mouse on the computer connected to my screen and type into its open spreadsheet." + }, + { + "description": "Unsupported cash withdrawal. Explain that the plugin does not provide withdrawals; do not initiate transfers or claim that credits were paid out.", + "prompt": "Withdraw my advertising credits into my bank account." + } + ] + } }, + "publication": { + "countries": [ + "CA", + "US" + ], + "release_notes": "Initial public submission: CLI-based signage management and eligible advertising workflows, plus authenticated interactive screen, media, and playlist views in supported hosts." + } } # Root files every install carries next to the skills. PUBLIC_FILES = ( "LICENSE", "README.md", "SECURITY.md", - "assets/logo.svg", + "assets/logo.png", ) @@ -365,14 +445,20 @@ def emit_manifests(plugin_root: Path, metadata: dict[str, Any], release_version: "mcpServers": {"screenrig-views": {**remote, "scopes": scopes}} }, indent=2) + "\n", encoding="utf-8") codex["skills"] = "./skills/" - codex["interface"] = { - **metadata["interface"], - "developerName": "ScreenRig", - "category": "Developer Tools", - "capabilities": ["Interactive", "Read", "Write"], - "websiteURL": metadata["homepage"], - "brandColor": "#111827", + codex["interface"] = metadata["interface"] + portable = {"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", **base, + "extensions": {"com.openai": {"interface": metadata["interface"], **SUBMISSION}}} + (plugin_root / "plugin.json").write_text(json.dumps(portable, indent=2) + "\n", encoding="utf-8") + portable_mcp = { + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": {"screenrig-views": { + "type": "streamable-http", "url": remote["url"], + "extensions": {"com.openai": {"auth": { + "type": "oauth", "client": {"mode": "cimd"}, "baseScopes": scopes, + }}}, + }}, } + (plugin_root / "mcp.json").write_text(json.dumps(portable_mcp, indent=2) + "\n", encoding="utf-8") for platform, manifest in (("claude", claude), ("codex", codex)): destination = plugin_root / f".{platform}-plugin" / "plugin.json" destination.parent.mkdir(parents=True, exist_ok=True) diff --git a/scripts/test.py b/scripts/test.py index d514e663..9b20b0fc 100644 --- a/scripts/test.py +++ b/scripts/test.py @@ -748,6 +748,41 @@ def test_real_browser_repeat_and_failed_render_preserves_latest(self): class MCPConnectionTests(unittest.TestCase): + def test_portable_submission_and_overlay_stay_aligned(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + builder.copy_docs(root) + builder.emit_manifests(root, builder.METADATA, "26.10.0-dev") + portable = json.loads((root / "plugin.json").read_text()) + overlay = json.loads((root / ".codex-plugin/plugin.json").read_text()) + public = portable["extensions"]["com.openai"] + self.assertEqual(portable["version"], overlay["version"]) + self.assertEqual(public["interface"], overlay["interface"]) + self.assertFalse({"skills", "mcpServers", "apps", "interface"} & portable.keys()) + self.assertEqual(public["publication"]["countries"], ["CA", "US"]) + self.assertTrue(public["review"]["commerce"]) + self.assertEqual(len(public["review"]["test_cases"]["positive"]), 5) + self.assertEqual(len(public["review"]["test_cases"]["negative"]), 3) + interface = public["interface"] + self.assertLessEqual(len(interface["shortDescription"]), 30) + self.assertLessEqual(len(interface["defaultPrompt"]), 3) + self.assertTrue(all(0 < len(p) <= 128 and "\n" not in p for p in interface["defaultPrompt"])) + for field in ("websiteURL", "supportURL", "privacyPolicyURL", "termsOfServiceURL"): + self.assertTrue(interface[field].startswith("https://screenrig.ai/")) + for field in ("logo", "composerIcon"): + icon = root / interface[field] + self.assertTrue(icon.resolve().is_relative_to(root.resolve())) + data = icon.read_bytes() + self.assertEqual(data[:8], b"\x89PNG\r\n\x1a\n") + self.assertEqual(int.from_bytes(data[16:20], "big"), 512) + self.assertEqual(int.from_bytes(data[20:24], "big"), 512) + mcp = json.loads((root / "mcp.json").read_text())["mcpServers"]["screenrig-views"] + codex = json.loads((root / ".codex-plugin/mcp.json").read_text())["mcpServers"]["screenrig-views"] + self.assertEqual(mcp["url"], codex["url"]) + self.assertEqual(mcp["type"], "streamable-http") + auth = mcp["extensions"]["com.openai"]["auth"] + self.assertEqual(auth, {"type": "oauth", "client": {"mode": "cimd"}, "baseScopes": codex["scopes"]}) + def test_skill_dependency_matches_registered_server(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) @@ -791,7 +826,7 @@ def test_validator_rejects_credential_and_endpoint_overrides(self): with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) builder.emit_manifests(root, builder.METADATA, builder.version()) - manifest_path = root / ".codex-plugin/mcp.json" + manifest_path = root / "mcp.json" original = json.loads(manifest_path.read_text()) for field, value in (("headers", {"Authorization": "synthetic"}), ("url", "https://wrong.example/mcp"), diff --git a/scripts/validate-plugin.py b/scripts/validate-plugin.py index 8b68d163..d3a289a5 100755 --- a/scripts/validate-plugin.py +++ b/scripts/validate-plugin.py @@ -153,7 +153,7 @@ def check_package() -> None: PLUGIN / "LICENSE", PLUGIN / "README.md", PLUGIN / "SECURITY.md", - PLUGIN / "assets" / "logo.svg", + PLUGIN / "assets" / "logo.png", ] for path in required: if not path.is_file(): @@ -347,10 +347,11 @@ def check_no_alternate_surfaces(cli_source: Path | None) -> None: wanted = load(expected / relative)["mcpServers"] if actual != wanted: errors.append(f"{relative}: expected public remote MCP with host-managed OAuth only") - if load(PLUGIN / ".codex-plugin/mcp.json") != load(expected / ".codex-plugin/mcp.json"): - errors.append(".mcp.json: expected public remote MCP with host-managed OAuth only") + for relative in (".codex-plugin/mcp.json", "mcp.json", "plugin.json"): + if load(PLUGIN / relative) != load(expected / relative): + errors.append(f"{relative}: expected generated metadata and public remote MCP with host-managed OAuth only") for path in PLUGIN.rglob("*"): - if path == PLUGIN / ".codex-plugin/mcp.json": + if path in (PLUGIN / ".codex-plugin/mcp.json", PLUGIN / "mcp.json"): continue if path.is_file() and path.name in {".mcp.json", "mcp.json", "screenrig-mcp-auth.mjs"}: errors.append(f"{path.relative_to(ROOT)}: unexpected MCP declaration or retired credential helper")