diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 13a2f21..dabf454 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -1,12 +1,12 @@
{
"name": "onesignal",
- "description": "OneSignal onboarding skills for coding agents: install the OneSignal SDK, provision push credentials, and send a test notification to your device.",
+ "description": "OneSignal onboarding for coding agents: install the OneSignal SDK, set up your push credentials, and verify that a real notification reaches your device.",
"owner": { "name": "OneSignal", "url": "https://onesignal.com" },
"plugins": [
{
"name": "onesignal",
"source": "./",
- "description": "OneSignal onboarding & activation skills"
+ "description": "OneSignal onboarding for coding agents: install the OneSignal SDK, set up your push credentials, and verify that a real notification reaches your device."
}
]
}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index 2261134..527f37f 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "onesignal",
"displayName": "OneSignal",
- "description": "OneSignal onboarding & activation skills: install and initialize the SDK, provision your app and push credentials, and verify real delivery.",
+ "description": "OneSignal onboarding for coding agents: install the OneSignal SDK, set up your push credentials, and verify that a real notification reaches your device.",
"version": "1.1.1",
"author": {
"name": "OneSignal",
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
index 40a15dd..ab1766f 100644
--- a/.codex-plugin/plugin.json
+++ b/.codex-plugin/plugin.json
@@ -1,7 +1,7 @@
{
"name": "onesignal",
"version": "1.1.1",
- "description": "OneSignal onboarding & activation skills: install and initialize the SDK, provision your app and push credentials, and verify real delivery.",
+ "description": "OneSignal onboarding for coding agents: install the OneSignal SDK, set up your push credentials, and verify that a real notification reaches your device.",
"author": {
"name": "OneSignal",
"url": "https://onesignal.com"
@@ -20,8 +20,8 @@
"mcpServers": "./.mcp.json",
"interface": {
"displayName": "OneSignal",
- "shortDescription": "Set up and verify OneSignal",
- "longDescription": "Install and initialize the OneSignal SDK, configure push credentials, and verify delivery. The bundled helper scripts need Python 3.7 or newer on PATH; the setup skill checks for it first and offers by-hand fallbacks when it is missing.",
+ "shortDescription": "Set up and send push notifications",
+ "longDescription": "OneSignal is the market leading customer engagement solution for push notifications, email, 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 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.",
"developerName": "OneSignal",
"category": "Developer Tools",
"capabilities": [
@@ -33,10 +33,14 @@
"termsOfServiceURL": "https://onesignal.com/tos",
"defaultPrompt": [
"Set up OneSignal in this app.",
- "Verify that OneSignal push delivery works."
+ "Upload my APNs and FCM credentials to OneSignal.",
+ "Send a test push to my device and confirm delivery."
],
"brandColor": "#051B2C",
+ "brandColorDark": "#4E50D1",
"composerIcon": "./assets/logomark.png",
- "logo": "./assets/logomark.png"
+ "composerIconDark": "./assets/logomark-white.svg",
+ "logo": "./assets/logomark.png",
+ "logoDark": "./assets/logomark-white.svg"
}
}
diff --git a/AGENT.md b/AGENT.md
index 95b9808..469ea2d 100644
--- a/AGENT.md
+++ b/AGENT.md
@@ -136,7 +136,9 @@ builds that artifact from this tree at release time:
- It writes `.codex-plugin/plugin.json` from the repository manifest without `mcpServers`,
because a Skills-only upload rejects MCP configuration (the listing's MCP server is attached
- in the portal). It copies `assets/` so `interface.logo` and `interface.composerIcon` resolve.
+ in the portal). It copies `assets/` so `interface.logo`, `interface.composerIcon`, and
+ their `Dark` variants resolve. The gate rejects `interface.screenshots`, which a
+ Skills-only upload refuses.
- It copies each skill folder under `skills/` (`skills/setup/`, `skills/credentials/`,
`skills/verify/`), and gives each one its own `references/`, `endpoint.conf`, and the
runtime scripts that skill calls (a table in the script names them).
diff --git a/README.md b/README.md
index 21dcae2..207eba5 100644
--- a/README.md
+++ b/README.md
@@ -113,7 +113,7 @@ plugin, then add + install it.
{
"name": "onesignal",
"source": "./onesignal-agent-plugin",
- "description": "OneSignal onboarding & activation skills"
+ "description": "OneSignal onboarding for coding agents: install the OneSignal SDK, set up your push credentials, and verify that a real notification reaches your device."
}
]
}
diff --git a/assets/logomark-white.svg b/assets/logomark-white.svg
new file mode 100644
index 0000000..e9c6b6b
--- /dev/null
+++ b/assets/logomark-white.svg
@@ -0,0 +1,3 @@
+
diff --git a/scripts/package_directory_bundle.py b/scripts/package_directory_bundle.py
index 14d4db8..792ca77 100755
--- a/scripts/package_directory_bundle.py
+++ b/scripts/package_directory_bundle.py
@@ -80,6 +80,10 @@
# single hyphens are what the submission rules accept.
PLUGIN_NAME_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
BRAND_ASSET_FIELDS = ("logo", "composerIcon")
+# Dark-theme variants. The directory falls back to the light file when one is absent.
+OPTIONAL_BRAND_ASSET_FIELDS = ("logoDark", "composerIconDark")
+# A Skills-only upload rejects this field (`screenshot_configuration_excluded`).
+EXCLUDED_INTERFACE_FIELDS = ("screenshots",)
VERSION_FILES = {
"claude": ".claude-plugin/plugin.json",
@@ -218,6 +222,11 @@ def bundle_manifest(manifest):
return bundled
+def brand_asset_fields(interface):
+ """The required asset fields, plus every optional one the manifest sets."""
+ return BRAND_ASSET_FIELDS + tuple(f for f in OPTIONAL_BRAND_ASSET_FIELDS if f in interface)
+
+
def check_manifest(source, findings):
"""Check the fields the directory reads from the manifest and the files they point to."""
manifest = read_manifest(source)
@@ -237,12 +246,15 @@ def check_manifest(source, findings):
if not isinstance(interface, dict):
findings.append("%s: interface is missing" % CODEX_MANIFEST)
return
- for field in BRAND_ASSET_FIELDS:
+ for field in brand_asset_fields(interface):
path = interface.get(field)
if not isinstance(path, str) or not path.startswith("./%s/" % ASSETS_DIR):
findings.append("%s: interface.%s must point into ./%s/ (got %r)" % (CODEX_MANIFEST, field, ASSETS_DIR, path))
elif not os.path.isfile(os.path.join(source, path[2:])):
findings.append("%s: interface.%s points to %s, which does not exist" % (CODEX_MANIFEST, field, path))
+ for field in EXCLUDED_INTERFACE_FIELDS:
+ if field in interface:
+ findings.append("%s: interface.%s is rejected by a Skills-only upload; remove it" % (CODEX_MANIFEST, field))
# ---------------------------------------------------------------------------
@@ -377,10 +389,13 @@ def check_bundle_manifest(bundle_dir, findings):
if os.path.exists(os.path.join(bundle_dir, ".mcp.json")):
findings.append(".mcp.json must not ship in a Skills-only bundle")
interface = manifest.get("interface") or {}
- for field in BRAND_ASSET_FIELDS:
+ for field in brand_asset_fields(interface):
path = interface.get(field)
if not isinstance(path, str) or not os.path.isfile(os.path.join(bundle_dir, path)):
findings.append("%s: interface.%s (%r) does not resolve inside the bundle" % (CODEX_MANIFEST, field, path))
+ for field in EXCLUDED_INTERFACE_FIELDS:
+ if field in interface:
+ findings.append("%s: interface.%s must not ship in a Skills-only bundle" % (CODEX_MANIFEST, field))
def check_bundle(bundle_dir):
@@ -613,6 +628,24 @@ def self_test(source):
os.remove(os.path.join(bad, ASSETS_DIR, "logomark.png"))
ok = expect_failure("manifest logo that points to a missing file", bad, os.path.join(tmp, "bad-brand-asset-work"),
"interface.logo points to ./assets/logomark.png, which does not exist") and ok
+
+ bad = os.path.join(tmp, "bad-dark-brand-asset")
+ copy_source_subset(source, bad)
+ path = os.path.join(bad, CODEX_MANIFEST)
+ manifest = json.loads(read_text(path))
+ manifest["interface"]["logoDark"] = "./assets/does-not-exist.svg"
+ write_text(path, json.dumps(manifest, indent=2) + "\n")
+ ok = expect_failure("manifest logoDark that points to a missing file", bad, os.path.join(tmp, "bad-dark-brand-asset-work"),
+ "interface.logoDark points to ./assets/does-not-exist.svg, which does not exist") and ok
+
+ bad = os.path.join(tmp, "bad-screenshots")
+ copy_source_subset(source, bad)
+ path = os.path.join(bad, CODEX_MANIFEST)
+ manifest = json.loads(read_text(path))
+ manifest["interface"]["screenshots"] = ["./assets/logomark.png"]
+ write_text(path, json.dumps(manifest, indent=2) + "\n")
+ ok = expect_failure("interface.screenshots in the manifest", bad, os.path.join(tmp, "bad-screenshots-work"),
+ "interface.screenshots is rejected by a Skills-only upload") and ok
return ok
diff --git a/skills/credentials/SKILL.md b/skills/credentials/SKILL.md
index ad5b9ec..f21ff37 100644
--- a/skills/credentials/SKILL.md
+++ b/skills/credentials/SKILL.md
@@ -1,6 +1,6 @@
---
name: credentials
-description: Guides a OneSignal customer through procuring and configuring the platform push/messaging credentials that only a human can start in an external console — Apple APNs .p8 keys, Firebase FCM v1 service-account JSON, web Site URL / Safari certs, email SPF/DKIM/DMARC DNS records, and SMS sender registration. Use when the user says push isn't delivering after SDK install, asks to "set up APNs", "add my .p8", "connect Firebase / FCM", "upload push credentials", "why is Android/iOS push failing", "configure email domain / DNS", "set up SMS/texting", or when another OneSignal skill reports a platform is missing credentials. The agent walks the human through the portal steps, then finishes the API-uploadable ones (Apple .p8, Firebase JSON) itself via the OneSignal apps API, validating the response and keeping every secret file out of the repo.
+description: Upload your APNs .p8 key, Firebase FCM service-account JSON, or web Site URL to OneSignal, and get guided setup for Safari certs, email SPF/DKIM/DMARC DNS records, and SMS sender registration. Use when the user asks to "set up APNs", "add my .p8", "connect Firebase / FCM", "upload push credentials", "configure email domain / DNS", or "set up SMS", asks why Android or iOS push is failing, says push is not delivering after the SDK install, or when another OneSignal skill reports that a platform is missing credentials.
argument-hint: "[platform=ios|android|web|email|sms] [app=]"
---
diff --git a/skills/setup/SKILL.md b/skills/setup/SKILL.md
index 63e298b..21c8885 100644
--- a/skills/setup/SKILL.md
+++ b/skills/setup/SKILL.md
@@ -1,6 +1,6 @@
---
name: setup
-description: Entry-point OneSignal onboarding skill. Use when a developer wants to add, install, integrate, initialize, or "set up" the OneSignal SDK in their own codebase (web, iOS, Android, React Native, Expo, Flutter, Cordova/Ionic/Capacitor, Unity) — triggers on "set up OneSignal", "add push notifications", "install the OneSignal SDK", "integrate OneSignal", "onboard onto OneSignal", or a fresh project with no OneSignal present. Supports one-command invocation with arguments, e.g. "/onesignal:setup app=". Detects the platform/framework from project manifests, gates on push credentials FIRST (uploading them via the provisioning endpoint before any SDK code is written), installs and initializes the SDK, adds a debug-only verification helper, and hands off to the verify skill.
+description: Set up OneSignal in your codebase by asking "set up OneSignal". Detects the platform/framework from your project, guides you through uploading push credentials, installs and sets up the SDK, then hands off to the verify skill. Use when a developer wants to add, install, integrate, or initialize the OneSignal SDK in a web, iOS, Android, React Native, Expo, Flutter, Cordova/Ionic/Capacitor, or Unity project, says "add push notifications", "install the OneSignal SDK", "integrate OneSignal", or "onboard onto OneSignal", or opens a fresh project with no OneSignal present. Supports one-command invocation, for example "/onesignal:setup app=".
argument-hint: app=
---
diff --git a/skills/verify/SKILL.md b/skills/verify/SKILL.md
index bc3b861..b9023d4 100644
--- a/skills/verify/SKILL.md
+++ b/skills/verify/SKILL.md
@@ -1,6 +1,6 @@
---
name: verify
-description: Closed-loop verification that a OneSignal integration actually works end to end — builds/runs the app, waits for the first real device subscription to register, checks identity, sends a real test push, and confirms server-side delivery. Use after the SDK-install or credentials skills have run, or whenever the user asks to "verify OneSignal works", "test push delivery", "prove the integration works", "why isn't my notification arriving", "confirm the device registered", "send myself a test notification", or is debugging an install that appears complete but delivers nothing. This is the "prove it works" step — it does NOT install the SDK or upload credentials; if those are missing it routes to the appropriate skill.
+description: Verify that your OneSignal integration works end to end — builds/runs your app, waits for the first real device subscription to register, sends a real test push, and confirms server-side delivery. Use after the setup or credentials skills have run, or when the user asks to "verify OneSignal works", "test push delivery", "prove the integration works", "confirm the device registered", or "send myself a test notification", asks why a notification is not arriving, or is debugging an install that looks complete but delivers nothing. Does not install the SDK or upload credentials; routes to those skills when they are missing.
argument-hint: "[app=]"
---