Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -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."
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 9 additions & 5 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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.",
Comment thread
sherwinski marked this conversation as resolved.
"developerName": "OneSignal",
"category": "Developer Tools",
"capabilities": [
Expand All @@ -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"
}
}
4 changes: 3 additions & 1 deletion AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
]
}
Expand Down
3 changes: 3 additions & 0 deletions assets/logomark-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 35 additions & 2 deletions scripts/package_directory_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand All @@ -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))


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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


Expand Down
2 changes: 1 addition & 1 deletion skills/credentials/SKILL.md
Original file line number Diff line number Diff line change
@@ -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=<APP_ID>]"
---

Expand Down
2 changes: 1 addition & 1 deletion skills/setup/SKILL.md
Original file line number Diff line number Diff line change
@@ -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=<APP_ID>". 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=<APP_ID>".
argument-hint: app=<APP_ID>
---

Expand Down
2 changes: 1 addition & 1 deletion skills/verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -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=<APP_ID>]"
---

Expand Down
Loading