From 949d8466e539e161840e8d5fcc006233be1220d3 Mon Sep 17 00:00:00 2001 From: sherwinski Date: Wed, 14 Oct 2026 18:16:21 -0700 Subject: [PATCH] fix: meet the portal's listing rules for the subtitle and the support URL The 1.2.2 draft reported 2 blocking findings: the subtitle must be 30 characters or fewer, and a support URL is required. The subtitle is now "Set up push, email, and SMS" (27 characters), and `supportURL` points at the repository issues page, the same URL the Claude manifest uses. The portal also advised against marketing claims and pricing text. The long description no longer says "market leading" or "free". The packager gate now checks the 2 submission limits (`displayName` and `shortDescription` at 30 characters) and the 4 listing URLs, with a self-test case for each new finding. --- .codex-plugin/plugin.json | 5 +++-- scripts/package_directory_bundle.py | 32 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index b8206b5..c4eb41f 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -20,8 +20,8 @@ "mcpServers": "./.mcp.json", "interface": { "displayName": "OneSignal", - "shortDescription": "Set up and send push notifications", - "longDescription": "OneSignal is the market leading customer engagement solution for email, push notifications, SMS, in-app messaging, and Live Activities. This plugin brings OneSignal onboarding into Codex: point it at your codebase and it installs the SDK, sets up your push credentials, and verifies that a real notification reaches your device.\n\nOnce installed, describe what you want in plain language or invoke the plugin directly with @OneSignal.\n\nTry prompts like:\n@OneSignal send an email to promote our new product announcement.\n@OneSignal create an email template for our weekly newsletter.\n@OneSignal send an SMS to remind customers about their abandoned cart.\n@OneSignal text a discount code to my subscribers for this weekend's sale.\n@OneSignal set up push notifications in this app.\n@OneSignal upload my APNs .p8 key and Firebase service account to OneSignal.\n@OneSignal why isn't my push notification arriving on Android?\n@OneSignal send a test push to my device and confirm delivery.\n\nRequirements: A free OneSignal account. A device, simulator, or browser that can receive push notifications.", + "shortDescription": "Set up email, push, and SMS", + "longDescription": "OneSignal is a customer engagement platform for email, push notifications, SMS, in-app messaging, and Live Activities. This plugin brings OneSignal onboarding into Codex: point it at your codebase and it installs the SDK, sets up your push credentials, and verifies that a real notification reaches your device.\n\nOnce installed, describe what you want in plain language or invoke the plugin directly with @OneSignal.\n\nTry prompts like:\n@OneSignal send an email to promote our new product announcement.\n@OneSignal create an email template for our weekly newsletter.\n@OneSignal send an SMS to remind customers about their abandoned cart.\n@OneSignal text a discount code to my subscribers for this weekend's sale.\n@OneSignal set up push notifications in this app.\n@OneSignal upload my APNs .p8 key and Firebase service account to OneSignal.\n@OneSignal why isn't my push notification arriving on Android?\n@OneSignal send a test push to my device and confirm delivery.\n\nRequirements: A OneSignal account. A device, simulator, or browser that can receive push notifications.", "developerName": "OneSignal", "category": "Developer Tools", "capabilities": [ @@ -29,6 +29,7 @@ "Write" ], "websiteURL": "https://documentation.onesignal.com", + "supportURL": "https://github.com/OneSignal/onesignal-agent-plugin/issues", "privacyPolicyURL": "https://onesignal.com/privacy_policy", "termsOfServiceURL": "https://onesignal.com/tos", "defaultPrompt": [ diff --git a/scripts/package_directory_bundle.py b/scripts/package_directory_bundle.py index 3610f99..725226a 100755 --- a/scripts/package_directory_bundle.py +++ b/scripts/package_directory_bundle.py @@ -94,6 +94,10 @@ # The portal accepts this field only for a plugin with a custom UI # (`screenshot_configuration_excluded`); this plugin has none. EXCLUDED_INTERFACE_FIELDS = ("screenshots",) +# Limits the portal applies at submission, which are tighter than the ones at upload. +SUBMISSION_TEXT_LIMITS = {"displayName": 30, "shortDescription": 30} +# A plugin with an MCP connection must list all 4 URLs before submission. +LISTING_URL_FIELDS = ("websiteURL", "supportURL", "privacyPolicyURL", "termsOfServiceURL") VERSION_FILES = { "claude": ".claude-plugin/plugin.json", @@ -257,6 +261,16 @@ def check_manifest(source, findings): if not isinstance(interface, dict): findings.append("%s: interface is missing" % CODEX_MANIFEST) return + for field, limit in SUBMISSION_TEXT_LIMITS.items(): + value = interface.get(field) + if not isinstance(value, str) or not value: + findings.append("%s: interface.%s is missing" % (CODEX_MANIFEST, field)) + elif len(value) > limit: + findings.append("%s: interface.%s must be %d characters or fewer (got %d)" % (CODEX_MANIFEST, field, limit, len(value))) + for field in LISTING_URL_FIELDS: + value = interface.get(field) + if not isinstance(value, str) or not value.startswith("https://"): + findings.append("%s: interface.%s must be an HTTPS URL (got %r)" % (CODEX_MANIFEST, field, value)) for field in brand_asset_fields(interface): path = interface.get(field) if not isinstance(path, str) or not path.startswith("./%s/" % ASSETS_DIR): @@ -650,6 +664,24 @@ def self_test(source): ok = expect_failure("display name in the manifest name field", bad, os.path.join(tmp, "bad-plugin-name-work"), "name must be lowercase letters, digits, and single hyphens") and ok + bad = os.path.join(tmp, "bad-long-subtitle") + copy_source_subset(source, bad) + path = os.path.join(bad, CODEX_MANIFEST) + manifest = json.loads(read_text(path)) + manifest["interface"]["shortDescription"] = "Set up and send push, email, and SMS" + write_text(path, json.dumps(manifest, indent=2) + "\n") + ok = expect_failure("subtitle over the submission limit", bad, os.path.join(tmp, "bad-long-subtitle-work"), + "interface.shortDescription must be 30 characters or fewer") and ok + + bad = os.path.join(tmp, "bad-missing-support-url") + copy_source_subset(source, bad) + path = os.path.join(bad, CODEX_MANIFEST) + manifest = json.loads(read_text(path)) + del manifest["interface"]["supportURL"] + write_text(path, json.dumps(manifest, indent=2) + "\n") + ok = expect_failure("manifest without a support URL", bad, os.path.join(tmp, "bad-missing-support-url-work"), + "interface.supportURL must be an HTTPS URL") and ok + bad = os.path.join(tmp, "bad-missing-mcp-config") copy_source_subset(source, bad) os.remove(os.path.join(bad, MCP_CONFIG))