From a6a83efe9a0efdb696744c39e19af89f53e69839 Mon Sep 17 00:00:00 2001 From: Daniel Vataj <153308333+sent-dm@users.noreply.github.com> Date: Sun, 9 Aug 2026 17:32:59 -0400 Subject: [PATCH] feat(metadata): complete skill and marketplace discovery --- .claude-plugin/marketplace.json | 2 +- adapter-sources/shared/marketplace.json | 17 +++ .../sent/.claude-plugin/plugin.json | 2 +- .../agents/openai.yaml | 4 + .../rcs-agent-onboarding/agents/openai.yaml | 4 + .../agents/openai.yaml | 4 + .../sent/skills/sent/agents/openai.yaml | 4 + .../sms-10dlc-registration/agents/openai.yaml | 4 + .../template-builder-ui/agents/openai.yaml | 4 + .../waba-embedded-signup/agents/openai.yaml | 4 + .../waba-template-author/agents/openai.yaml | 4 + packages/sent/plugin.json | 2 +- .../agents/openai.yaml | 4 + .../rcs-agent-onboarding/agents/openai.yaml | 4 + .../agents/openai.yaml | 4 + packages/sent/skills/sent/agents/openai.yaml | 4 + .../sms-10dlc-registration/agents/openai.yaml | 4 + .../template-builder-ui/agents/openai.yaml | 4 + .../waba-embedded-signup/agents/openai.yaml | 4 + .../waba-template-author/agents/openai.yaml | 4 + plugin.json | 2 +- plugins/sent/.codex-plugin/plugin.json | 12 +- .../agents/openai.yaml | 4 + .../rcs-agent-onboarding/agents/openai.yaml | 4 + .../agents/openai.yaml | 4 + plugins/sent/skills/sent/agents/openai.yaml | 4 + .../sms-10dlc-registration/agents/openai.yaml | 4 + .../template-builder-ui/agents/openai.yaml | 4 + .../waba-embedded-signup/agents/openai.yaml | 4 + .../waba-template-author/agents/openai.yaml | 4 + scripts/generate_adapters.py | 29 ++--- scripts/test_contracts.py | 38 ++++++ scripts/test_validation_gates.py | 42 +++++- scripts/validate.py | 120 ++++++++++++++++++ .../agents/openai.yaml | 4 + .../rcs-agent-onboarding/agents/openai.yaml | 4 + .../agents/openai.yaml | 4 + skills/sent/agents/openai.yaml | 4 + .../sms-10dlc-registration/agents/openai.yaml | 4 + skills/template-builder-ui/agents/openai.yaml | 4 + .../waba-embedded-signup/agents/openai.yaml | 4 + .../waba-template-author/agents/openai.yaml | 4 + 42 files changed, 364 insertions(+), 30 deletions(-) create mode 100644 adapter-sources/shared/marketplace.json create mode 100644 claude-plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/sender-profile-architect/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/sent/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/template-builder-ui/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/waba-embedded-signup/agents/openai.yaml create mode 100644 claude-plugins/sent/skills/waba-template-author/agents/openai.yaml create mode 100644 packages/sent/skills/messaging-performance-analyzer/agents/openai.yaml create mode 100644 packages/sent/skills/rcs-agent-onboarding/agents/openai.yaml create mode 100644 packages/sent/skills/sender-profile-architect/agents/openai.yaml create mode 100644 packages/sent/skills/sent/agents/openai.yaml create mode 100644 packages/sent/skills/sms-10dlc-registration/agents/openai.yaml create mode 100644 packages/sent/skills/template-builder-ui/agents/openai.yaml create mode 100644 packages/sent/skills/waba-embedded-signup/agents/openai.yaml create mode 100644 packages/sent/skills/waba-template-author/agents/openai.yaml create mode 100644 plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml create mode 100644 plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml create mode 100644 plugins/sent/skills/sender-profile-architect/agents/openai.yaml create mode 100644 plugins/sent/skills/sent/agents/openai.yaml create mode 100644 plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml create mode 100644 plugins/sent/skills/template-builder-ui/agents/openai.yaml create mode 100644 plugins/sent/skills/waba-embedded-signup/agents/openai.yaml create mode 100644 plugins/sent/skills/waba-template-author/agents/openai.yaml create mode 100644 skills/messaging-performance-analyzer/agents/openai.yaml create mode 100644 skills/rcs-agent-onboarding/agents/openai.yaml create mode 100644 skills/sender-profile-architect/agents/openai.yaml create mode 100644 skills/sent/agents/openai.yaml create mode 100644 skills/sms-10dlc-registration/agents/openai.yaml create mode 100644 skills/template-builder-ui/agents/openai.yaml create mode 100644 skills/waba-embedded-signup/agents/openai.yaml create mode 100644 skills/waba-template-author/agents/openai.yaml diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index c9e5fb7..9cc4636 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,7 +5,7 @@ "url": "https://sent.dm" }, "metadata": { - "description": "Official Sent skills and remote MCP integration.", + "description": "Official Sent skills and remote MCP integration for messaging operations and specialist channel workflows.", "version": "0.1.0" }, "plugins": [ diff --git a/adapter-sources/shared/marketplace.json b/adapter-sources/shared/marketplace.json new file mode 100644 index 0000000..589193e --- /dev/null +++ b/adapter-sources/shared/marketplace.json @@ -0,0 +1,17 @@ +{ + "display_name": "Sent", + "short_description": "Business messaging workflows", + "long_description": "Use Sent MCP operations for safe account, contact, template, analytics, and messaging workflows. Focused specialist skills add delivery diagnosis, Sender Profile architecture, US A2P 10DLC registration, WhatsApp Business onboarding and template authoring, RCS agent launch readiness, and cross-channel template-builder design.", + "developer_name": "Sent", + "category": "Productivity", + "capabilities": ["Interactive", "Write"], + "website_url": "https://github.com/sentdm/sent-plugin#readme", + "privacy_policy_url": "https://www.sent.dm/en/legal/privacy-policy", + "terms_of_service_url": "https://www.sent.dm/en/legal/terms-of-service", + "default_prompts": [ + "Use $sent-account-readiness to check whether my Sent account is messaging-ready.", + "Use $messaging-performance-analyzer to diagnose this delivery report by channel.", + "Use $sms-10dlc-registration to review my US A2P registration evidence." + ], + "marketplace_description": "Official Sent skills and remote MCP integration for messaging operations and specialist channel workflows." +} diff --git a/claude-plugins/sent/.claude-plugin/plugin.json b/claude-plugins/sent/.claude-plugin/plugin.json index 6054a50..aaa17df 100644 --- a/claude-plugins/sent/.claude-plugin/plugin.json +++ b/claude-plugins/sent/.claude-plugin/plugin.json @@ -6,7 +6,7 @@ "name": "Sent", "url": "https://sent.dm" }, - "homepage": "https://docs.sent.dm/start/llm-docs/mcp-server", + "homepage": "https://github.com/sentdm/sent-plugin#readme", "repository": "https://github.com/sentdm/sent-plugin", "license": "MIT", "keywords": [ diff --git a/claude-plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml b/claude-plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml new file mode 100644 index 0000000..d9662c9 --- /dev/null +++ b/claude-plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Performance Analyzer" + short_description: "Diagnose channel delivery and engagement" + default_prompt: "Use $messaging-performance-analyzer to analyze this MDR export by channel and outcome." diff --git a/claude-plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml b/claude-plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml new file mode 100644 index 0000000..3f65f01 --- /dev/null +++ b/claude-plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent RCS Onboarding" + short_description: "Review RCS agent launch readiness" + default_prompt: "Use $rcs-agent-onboarding to review my RCS agent readiness and missing evidence." diff --git a/claude-plugins/sent/skills/sender-profile-architect/agents/openai.yaml b/claude-plugins/sent/skills/sender-profile-architect/agents/openai.yaml new file mode 100644 index 0000000..702dc4b --- /dev/null +++ b/claude-plugins/sent/skills/sender-profile-architect/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Profile Architect" + short_description: "Design safe Sender Profile boundaries" + default_prompt: "Use $sender-profile-architect to design Sender Profile boundaries for this messaging system." diff --git a/claude-plugins/sent/skills/sent/agents/openai.yaml b/claude-plugins/sent/skills/sent/agents/openai.yaml new file mode 100644 index 0000000..68d4495 --- /dev/null +++ b/claude-plugins/sent/skills/sent/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Workflow Router" + short_description: "Route broad Sent messaging requests" + default_prompt: "Use $sent to identify the safest Sent workflow for my request." diff --git a/claude-plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml b/claude-plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml new file mode 100644 index 0000000..bcc3f7a --- /dev/null +++ b/claude-plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent 10DLC Registration" + short_description: "Review US A2P registration evidence" + default_prompt: "Use $sms-10dlc-registration to review my 10DLC evidence packet for gaps." diff --git a/claude-plugins/sent/skills/template-builder-ui/agents/openai.yaml b/claude-plugins/sent/skills/template-builder-ui/agents/openai.yaml new file mode 100644 index 0000000..1c86f9d --- /dev/null +++ b/claude-plugins/sent/skills/template-builder-ui/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Template Builder UI" + short_description: "Audit cross-channel template builder UX" + default_prompt: "Use $template-builder-ui to audit this template builder flow and payload mapping." diff --git a/claude-plugins/sent/skills/waba-embedded-signup/agents/openai.yaml b/claude-plugins/sent/skills/waba-embedded-signup/agents/openai.yaml new file mode 100644 index 0000000..e94294f --- /dev/null +++ b/claude-plugins/sent/skills/waba-embedded-signup/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Signup" + short_description: "Plan WhatsApp Business onboarding" + default_prompt: "Use $waba-embedded-signup to review my WABA onboarding flow and readiness." diff --git a/claude-plugins/sent/skills/waba-template-author/agents/openai.yaml b/claude-plugins/sent/skills/waba-template-author/agents/openai.yaml new file mode 100644 index 0000000..133c2f1 --- /dev/null +++ b/claude-plugins/sent/skills/waba-template-author/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Template Author" + short_description: "Draft compliant WhatsApp templates" + default_prompt: "Use $waba-template-author to draft and validate a WhatsApp template payload." diff --git a/packages/sent/plugin.json b/packages/sent/plugin.json index f5577b0..d230e14 100644 --- a/packages/sent/plugin.json +++ b/packages/sent/plugin.json @@ -7,7 +7,7 @@ "name": "Sent", "url": "https://sent.dm" }, - "homepage": "https://docs.sent.dm/start/llm-docs/mcp-server", + "homepage": "https://github.com/sentdm/sent-plugin#readme", "repository": "https://github.com/sentdm/sent-plugin", "license": "MIT", "keywords": [ diff --git a/packages/sent/skills/messaging-performance-analyzer/agents/openai.yaml b/packages/sent/skills/messaging-performance-analyzer/agents/openai.yaml new file mode 100644 index 0000000..d9662c9 --- /dev/null +++ b/packages/sent/skills/messaging-performance-analyzer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Performance Analyzer" + short_description: "Diagnose channel delivery and engagement" + default_prompt: "Use $messaging-performance-analyzer to analyze this MDR export by channel and outcome." diff --git a/packages/sent/skills/rcs-agent-onboarding/agents/openai.yaml b/packages/sent/skills/rcs-agent-onboarding/agents/openai.yaml new file mode 100644 index 0000000..3f65f01 --- /dev/null +++ b/packages/sent/skills/rcs-agent-onboarding/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent RCS Onboarding" + short_description: "Review RCS agent launch readiness" + default_prompt: "Use $rcs-agent-onboarding to review my RCS agent readiness and missing evidence." diff --git a/packages/sent/skills/sender-profile-architect/agents/openai.yaml b/packages/sent/skills/sender-profile-architect/agents/openai.yaml new file mode 100644 index 0000000..702dc4b --- /dev/null +++ b/packages/sent/skills/sender-profile-architect/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Profile Architect" + short_description: "Design safe Sender Profile boundaries" + default_prompt: "Use $sender-profile-architect to design Sender Profile boundaries for this messaging system." diff --git a/packages/sent/skills/sent/agents/openai.yaml b/packages/sent/skills/sent/agents/openai.yaml new file mode 100644 index 0000000..68d4495 --- /dev/null +++ b/packages/sent/skills/sent/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Workflow Router" + short_description: "Route broad Sent messaging requests" + default_prompt: "Use $sent to identify the safest Sent workflow for my request." diff --git a/packages/sent/skills/sms-10dlc-registration/agents/openai.yaml b/packages/sent/skills/sms-10dlc-registration/agents/openai.yaml new file mode 100644 index 0000000..bcc3f7a --- /dev/null +++ b/packages/sent/skills/sms-10dlc-registration/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent 10DLC Registration" + short_description: "Review US A2P registration evidence" + default_prompt: "Use $sms-10dlc-registration to review my 10DLC evidence packet for gaps." diff --git a/packages/sent/skills/template-builder-ui/agents/openai.yaml b/packages/sent/skills/template-builder-ui/agents/openai.yaml new file mode 100644 index 0000000..1c86f9d --- /dev/null +++ b/packages/sent/skills/template-builder-ui/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Template Builder UI" + short_description: "Audit cross-channel template builder UX" + default_prompt: "Use $template-builder-ui to audit this template builder flow and payload mapping." diff --git a/packages/sent/skills/waba-embedded-signup/agents/openai.yaml b/packages/sent/skills/waba-embedded-signup/agents/openai.yaml new file mode 100644 index 0000000..e94294f --- /dev/null +++ b/packages/sent/skills/waba-embedded-signup/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Signup" + short_description: "Plan WhatsApp Business onboarding" + default_prompt: "Use $waba-embedded-signup to review my WABA onboarding flow and readiness." diff --git a/packages/sent/skills/waba-template-author/agents/openai.yaml b/packages/sent/skills/waba-template-author/agents/openai.yaml new file mode 100644 index 0000000..133c2f1 --- /dev/null +++ b/packages/sent/skills/waba-template-author/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Template Author" + short_description: "Draft compliant WhatsApp templates" + default_prompt: "Use $waba-template-author to draft and validate a WhatsApp template payload." diff --git a/plugin.json b/plugin.json index f5577b0..d230e14 100644 --- a/plugin.json +++ b/plugin.json @@ -7,7 +7,7 @@ "name": "Sent", "url": "https://sent.dm" }, - "homepage": "https://docs.sent.dm/start/llm-docs/mcp-server", + "homepage": "https://github.com/sentdm/sent-plugin#readme", "repository": "https://github.com/sentdm/sent-plugin", "license": "MIT", "keywords": [ diff --git a/plugins/sent/.codex-plugin/plugin.json b/plugins/sent/.codex-plugin/plugin.json index 442317e..94a4fb6 100644 --- a/plugins/sent/.codex-plugin/plugin.json +++ b/plugins/sent/.codex-plugin/plugin.json @@ -6,7 +6,7 @@ "name": "Sent", "url": "https://sent.dm" }, - "homepage": "https://docs.sent.dm/start/llm-docs/mcp-server", + "homepage": "https://github.com/sentdm/sent-plugin#readme", "repository": "https://github.com/sentdm/sent-plugin", "license": "MIT", "keywords": [ @@ -34,22 +34,22 @@ "interface": { "displayName": "Sent", "shortDescription": "Business messaging workflows", - "longDescription": "Use Sent skills and its remote MCP server for safe messaging, contacts, templates, analytics, account readiness, SMS, WhatsApp, and RCS workflows.", + "longDescription": "Use Sent MCP operations for safe account, contact, template, analytics, and messaging workflows. Focused specialist skills add delivery diagnosis, Sender Profile architecture, US A2P 10DLC registration, WhatsApp Business onboarding and template authoring, RCS agent launch readiness, and cross-channel template-builder design.", "developerName": "Sent", "category": "Productivity", "capabilities": [ "Interactive", "Write" ], - "websiteURL": "https://www.sent.dm", + "websiteURL": "https://github.com/sentdm/sent-plugin#readme", "privacyPolicyURL": "https://www.sent.dm/en/legal/privacy-policy", "termsOfServiceURL": "https://www.sent.dm/en/legal/terms-of-service", "logo": "./assets/logo.svg", "composerIcon": "./assets/logo.svg", "defaultPrompt": [ - "Check whether my account is ready to send and flag anything blocking me.", - "Summarize message volume and deliverability for the last 30 days.", - "Find an approved template, preview an SMS to a contact, and ask before sending." + "Use $sent-account-readiness to check whether my Sent account is messaging-ready.", + "Use $messaging-performance-analyzer to diagnose this delivery report by channel.", + "Use $sms-10dlc-registration to review my US A2P registration evidence." ] } } diff --git a/plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml b/plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml new file mode 100644 index 0000000..d9662c9 --- /dev/null +++ b/plugins/sent/skills/messaging-performance-analyzer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Performance Analyzer" + short_description: "Diagnose channel delivery and engagement" + default_prompt: "Use $messaging-performance-analyzer to analyze this MDR export by channel and outcome." diff --git a/plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml b/plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml new file mode 100644 index 0000000..3f65f01 --- /dev/null +++ b/plugins/sent/skills/rcs-agent-onboarding/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent RCS Onboarding" + short_description: "Review RCS agent launch readiness" + default_prompt: "Use $rcs-agent-onboarding to review my RCS agent readiness and missing evidence." diff --git a/plugins/sent/skills/sender-profile-architect/agents/openai.yaml b/plugins/sent/skills/sender-profile-architect/agents/openai.yaml new file mode 100644 index 0000000..702dc4b --- /dev/null +++ b/plugins/sent/skills/sender-profile-architect/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Profile Architect" + short_description: "Design safe Sender Profile boundaries" + default_prompt: "Use $sender-profile-architect to design Sender Profile boundaries for this messaging system." diff --git a/plugins/sent/skills/sent/agents/openai.yaml b/plugins/sent/skills/sent/agents/openai.yaml new file mode 100644 index 0000000..68d4495 --- /dev/null +++ b/plugins/sent/skills/sent/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Workflow Router" + short_description: "Route broad Sent messaging requests" + default_prompt: "Use $sent to identify the safest Sent workflow for my request." diff --git a/plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml b/plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml new file mode 100644 index 0000000..bcc3f7a --- /dev/null +++ b/plugins/sent/skills/sms-10dlc-registration/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent 10DLC Registration" + short_description: "Review US A2P registration evidence" + default_prompt: "Use $sms-10dlc-registration to review my 10DLC evidence packet for gaps." diff --git a/plugins/sent/skills/template-builder-ui/agents/openai.yaml b/plugins/sent/skills/template-builder-ui/agents/openai.yaml new file mode 100644 index 0000000..1c86f9d --- /dev/null +++ b/plugins/sent/skills/template-builder-ui/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Template Builder UI" + short_description: "Audit cross-channel template builder UX" + default_prompt: "Use $template-builder-ui to audit this template builder flow and payload mapping." diff --git a/plugins/sent/skills/waba-embedded-signup/agents/openai.yaml b/plugins/sent/skills/waba-embedded-signup/agents/openai.yaml new file mode 100644 index 0000000..e94294f --- /dev/null +++ b/plugins/sent/skills/waba-embedded-signup/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Signup" + short_description: "Plan WhatsApp Business onboarding" + default_prompt: "Use $waba-embedded-signup to review my WABA onboarding flow and readiness." diff --git a/plugins/sent/skills/waba-template-author/agents/openai.yaml b/plugins/sent/skills/waba-template-author/agents/openai.yaml new file mode 100644 index 0000000..133c2f1 --- /dev/null +++ b/plugins/sent/skills/waba-template-author/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Template Author" + short_description: "Draft compliant WhatsApp templates" + default_prompt: "Use $waba-template-author to draft and validate a WhatsApp template payload." diff --git a/scripts/generate_adapters.py b/scripts/generate_adapters.py index f6edd4b..d0c4845 100644 --- a/scripts/generate_adapters.py +++ b/scripts/generate_adapters.py @@ -63,6 +63,7 @@ def build(output_root: Path, source_root: Path = ROOT) -> None: package = source_root / "packages" / "sent" commands = source_root / "adapter-sources" / "claude" / "commands" adapter_readme = source_root / "adapter-sources" / "shared" / "README.md" + marketplace = read_json(source_root / "adapter-sources" / "shared" / "marketplace.json") metadata = load_repository_metadata(source_root) portable_manifest = read_json(package / "plugin.json") portable_mcp = read_json(package / "mcp.json") @@ -112,22 +113,18 @@ def build(output_root: Path, source_root: Path = ROOT) -> None: "skills": "./skills/", "mcpServers": "./.mcp.json", "interface": { - "displayName": "Sent", - "shortDescription": "Business messaging workflows", - "longDescription": "Use Sent skills and its remote MCP server for safe messaging, contacts, templates, analytics, account readiness, SMS, WhatsApp, and RCS workflows.", - "developerName": "Sent", - "category": "Productivity", - "capabilities": ["Interactive", "Write"], - "websiteURL": "https://www.sent.dm", - "privacyPolicyURL": "https://www.sent.dm/en/legal/privacy-policy", - "termsOfServiceURL": "https://www.sent.dm/en/legal/terms-of-service", + "displayName": marketplace["display_name"], + "shortDescription": marketplace["short_description"], + "longDescription": marketplace["long_description"], + "developerName": marketplace["developer_name"], + "category": marketplace["category"], + "capabilities": marketplace["capabilities"], + "websiteURL": marketplace["website_url"], + "privacyPolicyURL": marketplace["privacy_policy_url"], + "termsOfServiceURL": marketplace["terms_of_service_url"], "logo": "./assets/logo.svg", "composerIcon": "./assets/logo.svg", - "defaultPrompt": [ - "Check whether my account is ready to send and flag anything blocking me.", - "Summarize message volume and deliverability for the last 30 days.", - "Find an approved template, preview an SMS to a contact, and ask before sending." - ], + "defaultPrompt": marketplace["default_prompts"], }, } adapter_mcp = { @@ -154,7 +151,7 @@ def build(output_root: Path, source_root: Path = ROOT) -> None: output_root / ".agents" / "plugins" / "marketplace.json", { "name": "sent", - "interface": {"displayName": "Sent"}, + "interface": {"displayName": marketplace["display_name"]}, "plugins": [ { "name": "sent", @@ -174,7 +171,7 @@ def build(output_root: Path, source_root: Path = ROOT) -> None: "name": "sent", "owner": {"name": "Sent", "url": "https://sent.dm"}, "metadata": { - "description": "Official Sent skills and remote MCP integration.", + "description": marketplace["marketplace_description"], "version": version, }, "plugins": [ diff --git a/scripts/test_contracts.py b/scripts/test_contracts.py index 97265e8..311ec22 100644 --- a/scripts/test_contracts.py +++ b/scripts/test_contracts.py @@ -14,6 +14,7 @@ import generate_adapters as GENERATOR import repository_metadata as REPOSITORY_METADATA +import yaml ROOT = Path(__file__).resolve().parents[1] @@ -84,6 +85,43 @@ def test_version_change_propagates_to_generated_manifests(self) -> None: self.assertEqual(marketplace["metadata"]["version"], "9.8.7") +class SkillUiMetadataContractTests(unittest.TestCase): + def test_every_skill_has_unique_valid_openai_metadata(self) -> None: + metadata = REPOSITORY_METADATA.load_repository_metadata(ROOT) + display_names: set[str] = set() + descriptions: set[str] = set() + for name in metadata.skills: + path = SKILLS / name / "agents" / "openai.yaml" + with self.subTest(skill=name): + self.assertTrue(path.is_file(), path) + data = yaml.safe_load(path.read_text(encoding="utf-8")) + interface = data["interface"] + display_name = interface["display_name"].strip() + description = interface["short_description"].strip() + prompt = interface["default_prompt"].strip() + self.assertTrue(display_name) + self.assertTrue(description) + self.assertNotIn(display_name.casefold(), display_names) + self.assertNotIn(description.casefold(), descriptions) + self.assertEqual(re.findall(r"\$[a-z0-9-]+", prompt), [f"${name}"]) + display_names.add(display_name.casefold()) + descriptions.add(description.casefold()) + + def test_marketplace_prompts_include_specialists(self) -> None: + metadata = REPOSITORY_METADATA.load_repository_metadata(ROOT) + path = ROOT / "adapter-sources" / "shared" / "marketplace.json" + catalog = json.loads(path.read_text(encoding="utf-8")) + prompts = catalog["default_prompts"] + invoked = { + match.group(1) + for prompt in prompts + if (match := re.search(r"\$([a-z0-9-]+)", prompt)) + } + specialists = set(metadata.skills) - set(metadata.tools_by_owner) - {"sent"} + self.assertGreaterEqual(len(invoked & specialists), 2) + self.assertEqual(catalog["website_url"], "https://github.com/sentdm/sent-plugin#readme") + + class BundledExampleTests(unittest.TestCase): def test_every_markdown_json_block_parses(self) -> None: for path in SKILLS.rglob("*.md"): diff --git a/scripts/test_validation_gates.py b/scripts/test_validation_gates.py index c483455..ef11b05 100644 --- a/scripts/test_validation_gates.py +++ b/scripts/test_validation_gates.py @@ -76,10 +76,17 @@ def missing_root_mcp(root: Path) -> None: def missing_listing_url(root: Path) -> None: - path = root / "scripts" / "generate_adapters.py" - content = path.read_text(encoding="utf-8") - path.write_text(content.replace(' "websiteURL": "https://www.sent.dm",\n', ""), encoding="utf-8") - subprocess.run([sys.executable, str(path)], cwd=root, check=True, capture_output=True, text=True) + path = root / "adapter-sources" / "shared" / "marketplace.json" + catalog = json.loads(path.read_text(encoding="utf-8")) + catalog["website_url"] = "not-an-https-url" + path.write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8") + subprocess.run( + [sys.executable, str(root / "scripts" / "generate_adapters.py")], + cwd=root, + check=True, + capture_output=True, + text=True, + ) def missing_readme_catalog_entry(root: Path) -> None: @@ -99,6 +106,29 @@ def weak_skill_discovery_description(root: Path) -> None: path.write_text("\n".join(lines) + "\n", encoding="utf-8") +def missing_skill_ui_metadata(root: Path) -> None: + (root / "packages" / "sent" / "skills" / "sent" / "agents" / "openai.yaml").unlink() + + +def wrong_skill_default_prompt(root: Path) -> None: + path = root / "packages" / "sent" / "skills" / "sent-analytics" / "agents" / "openai.yaml" + content = path.read_text(encoding="utf-8") + path.write_text(content.replace("$sent-analytics", "$sent-messaging"), encoding="utf-8") + + +def duplicate_skill_display_name(root: Path) -> None: + path = root / "packages" / "sent" / "skills" / "sent-analytics" / "agents" / "openai.yaml" + content = path.read_text(encoding="utf-8") + path.write_text(content.replace('display_name: "Sent Analytics"', 'display_name: "Sent Account Readiness"'), encoding="utf-8") + + +def unsafe_marketplace_prompt(root: Path) -> None: + path = root / "adapter-sources" / "shared" / "marketplace.json" + catalog = json.loads(path.read_text(encoding="utf-8")) + catalog["default_prompts"][0] = "Use $sent-account-readiness to send a message." + path.write_text(json.dumps(catalog, indent=2) + "\n", encoding="utf-8") + + def wrong_openai_test_count(root: Path) -> None: path = root / "chatgpt-app-submission.json" submission = json.loads(path.read_text(encoding="utf-8")) @@ -175,6 +205,10 @@ def contract_manifest_drift(root: Path) -> None: ("required listing URLs", missing_listing_url, "Codex websiteURL must be an HTTPS URL"), ("README skill catalog coverage", missing_readme_catalog_entry, "skill catalog does not link"), ("skill discovery descriptions", weak_skill_discovery_description, "discovery description must explain"), + ("skill UI metadata coverage", missing_skill_ui_metadata, "missing agents/openai.yaml"), + ("exact skill prompt invocation", wrong_skill_default_prompt, "must invoke exactly $sent-analytics"), + ("unique skill display names", duplicate_skill_display_name, "display_name must be unique"), + ("non-mutating marketplace prompt", unsafe_marketplace_prompt, "must be non-mutating"), ("OpenAI test counts", wrong_openai_test_count, "exactly three negative test cases"), ("unexpected MCP tool", unexpected_public_tool, "OpenAI submission tool set mismatch"), ("invalid MCP tool owner", invalid_tool_owner, "is not a canonical skill"), diff --git a/scripts/validate.py b/scripts/validate.py index df4cb82..94bf6f5 100644 --- a/scripts/validate.py +++ b/scripts/validate.py @@ -24,6 +24,7 @@ ROOT_SKILLS = ROOT / "skills" EVALS = ROOT / "evals" ADAPTER_README = ROOT / "adapter-sources" / "shared" / "README.md" +MARKETPLACE_CONTENT = ROOT / "adapter-sources" / "shared" / "marketplace.json" SCHEMAS = ROOT / "schemas" / "agent-plugins" / "1.0.0" OPENAI_SUBMISSION_SCHEMA = ROOT / "schemas" / "openai" / "chatgpt-app-submission.v1.json" CONTRACT_MANIFEST = ROOT / "schemas" / "sent" / "v3-contract-manifest.json" @@ -98,6 +99,8 @@ MARKDOWN_LINK = re.compile(r"\[[^\]]*\]\(([^)]+)\)") LOCAL_RESOURCE = re.compile(r"(?]*>", re.IGNORECASE) SVG_VIEWBOX = re.compile(r"\bviewBox\s*=\s*([\"'])([^\"']+)\1", re.IGNORECASE) JSON_FENCE = re.compile(r"```json\s*\n(.*?)```", re.DOTALL) @@ -195,6 +198,12 @@ def validate_manifests(validation: Validation) -> None: for field in ("homepage", "repository"): value = plugin.get(field, "") validation.check(urlparse(value).scheme == "https", f"plugin {field} must use HTTPS") + if MARKETPLACE_CONTENT.is_file(): + marketplace = load_json(MARKETPLACE_CONTENT) + validation.check( + plugin.get("homepage") == marketplace.get("website_url"), + "portable plugin homepage must match canonical marketplace website_url", + ) author_url = plugin.get("author", {}).get("url", "") validation.check(urlparse(author_url).scheme == "https", "plugin author.url must use HTTPS") validation.check(urlparse(MCP_URL).scheme == "https", "MCP URL must use HTTPS") @@ -286,6 +295,115 @@ def validate_skills(validation: Validation) -> None: validation.check(routed_skill in dispatcher, f"sent dispatcher does not route to {routed_skill}") +def validate_skill_ui_metadata(validation: Validation) -> None: + display_names: set[str] = set() + descriptions: set[str] = set() + confirmation_skills = set(MUTATION_TOOLS.values()) + prompts: dict[str, str] = {} + for name in sorted(EXPECTED_SKILLS): + path = SKILLS / name / "agents" / "openai.yaml" + validation.check(path.is_file(), f"{name}: missing agents/openai.yaml") + if not path.is_file(): + continue + try: + data = yaml.safe_load(path.read_text(encoding="utf-8")) + except yaml.YAMLError as exc: + validation.errors.append(f"{path.relative_to(ROOT)}: invalid YAML: {exc}") + continue + interface = data.get("interface") if isinstance(data, dict) else None + validation.check( + isinstance(interface, dict) + and set(interface) == {"display_name", "short_description", "default_prompt"}, + f"{path.relative_to(ROOT)}: interface requires display_name, short_description, and default_prompt", + ) + if not isinstance(interface, dict): + continue + display_name = interface.get("display_name") + description = interface.get("short_description") + prompt = interface.get("default_prompt") + validation.check( + isinstance(display_name, str) and bool(display_name.strip()), + f"{name}: display_name must be non-empty", + ) + validation.check( + isinstance(description, str) and bool(description.strip()), + f"{name}: short_description must be non-empty", + ) + if isinstance(display_name, str) and display_name.strip(): + normalized = display_name.strip().casefold() + validation.check(normalized not in display_names, f"{name}: display_name must be unique") + display_names.add(normalized) + if isinstance(description, str) and description.strip(): + normalized = description.strip().casefold() + validation.check(normalized not in descriptions, f"{name}: short_description must be unique") + descriptions.add(normalized) + invocations = SKILL_INVOCATION.findall(prompt) if isinstance(prompt, str) else [] + validation.check( + invocations == [name], + f"{name}: default_prompt must invoke exactly ${name}", + ) + if isinstance(prompt, str): + prompts[name] = prompt + validation.check( + not MUTATING_PROMPT.search(prompt) or name in confirmation_skills, + f"{name}: default_prompt must remain non-mutating unless the skill owns confirmed mutations", + ) + + for required in ("sent-account-readiness", "sent-analytics", "sent-messaging"): + validation.check(required in prompts, f"{required}: discovery prompt is required") + + +def validate_marketplace_content(validation: Validation) -> None: + validation.check(MARKETPLACE_CONTENT.is_file(), "missing canonical marketplace content source") + if not MARKETPLACE_CONTENT.is_file(): + return + catalog = load_json(MARKETPLACE_CONTENT) + required = { + "display_name", + "short_description", + "long_description", + "developer_name", + "category", + "capabilities", + "website_url", + "privacy_policy_url", + "terms_of_service_url", + "default_prompts", + "marketplace_description", + } + validation.check(set(catalog) == required, "canonical marketplace content fields drifted") + validation.check( + catalog.get("website_url") == "https://github.com/sentdm/sent-plugin#readme", + "plugin homepage must use the GitHub README until a dedicated landing page exists", + ) + long_description = str(catalog.get("long_description", "")).lower() + validation.check( + "mcp" in long_description and all(term in long_description for term in ("10dlc", "whatsapp", "rcs")), + "marketplace long description must balance MCP operations and specialist workflows", + ) + prompts = catalog.get("default_prompts", []) + validation.check(isinstance(prompts, list) and 1 <= len(prompts) <= 3, "marketplace default prompts must contain 1..3 entries") + invoked: set[str] = set() + if isinstance(prompts, list): + for index, prompt in enumerate(prompts, 1): + matches = SKILL_INVOCATION.findall(prompt) if isinstance(prompt, str) else [] + validation.check( + len(matches) == 1 and matches[0] in EXPECTED_SKILLS, + f"marketplace default prompt {index} must invoke exactly one canonical skill", + ) + if matches: + invoked.add(matches[0]) + validation.check( + isinstance(prompt, str) and not MUTATING_PROMPT.search(prompt), + f"marketplace default prompt {index} must be non-mutating", + ) + specialists = EXPECTED_SKILLS - set(MCP_SKILLS) - {"sent"} + validation.check( + len(invoked & specialists) >= 2, + "marketplace starter prompts must expose at least two specialist skills", + ) + + def validate_public_content(validation: Validation) -> None: scan_roots = (PACKAGE, EVALS, ROOT / "adapter-sources" / "claude" / "commands") text_suffixes = {".md", ".yaml", ".yml", ".json", ".py", ".txt", ".csv"} @@ -766,6 +884,8 @@ def main() -> None: validate_root_discovery(validation) validate_containment(validation) validate_skills(validation) + validate_skill_ui_metadata(validation) + validate_marketplace_content(validation) validate_public_content(validation) validate_tool_contract(validation) validate_documentation(validation) diff --git a/skills/messaging-performance-analyzer/agents/openai.yaml b/skills/messaging-performance-analyzer/agents/openai.yaml new file mode 100644 index 0000000..d9662c9 --- /dev/null +++ b/skills/messaging-performance-analyzer/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Performance Analyzer" + short_description: "Diagnose channel delivery and engagement" + default_prompt: "Use $messaging-performance-analyzer to analyze this MDR export by channel and outcome." diff --git a/skills/rcs-agent-onboarding/agents/openai.yaml b/skills/rcs-agent-onboarding/agents/openai.yaml new file mode 100644 index 0000000..3f65f01 --- /dev/null +++ b/skills/rcs-agent-onboarding/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent RCS Onboarding" + short_description: "Review RCS agent launch readiness" + default_prompt: "Use $rcs-agent-onboarding to review my RCS agent readiness and missing evidence." diff --git a/skills/sender-profile-architect/agents/openai.yaml b/skills/sender-profile-architect/agents/openai.yaml new file mode 100644 index 0000000..702dc4b --- /dev/null +++ b/skills/sender-profile-architect/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Profile Architect" + short_description: "Design safe Sender Profile boundaries" + default_prompt: "Use $sender-profile-architect to design Sender Profile boundaries for this messaging system." diff --git a/skills/sent/agents/openai.yaml b/skills/sent/agents/openai.yaml new file mode 100644 index 0000000..68d4495 --- /dev/null +++ b/skills/sent/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Workflow Router" + short_description: "Route broad Sent messaging requests" + default_prompt: "Use $sent to identify the safest Sent workflow for my request." diff --git a/skills/sms-10dlc-registration/agents/openai.yaml b/skills/sms-10dlc-registration/agents/openai.yaml new file mode 100644 index 0000000..bcc3f7a --- /dev/null +++ b/skills/sms-10dlc-registration/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent 10DLC Registration" + short_description: "Review US A2P registration evidence" + default_prompt: "Use $sms-10dlc-registration to review my 10DLC evidence packet for gaps." diff --git a/skills/template-builder-ui/agents/openai.yaml b/skills/template-builder-ui/agents/openai.yaml new file mode 100644 index 0000000..1c86f9d --- /dev/null +++ b/skills/template-builder-ui/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent Template Builder UI" + short_description: "Audit cross-channel template builder UX" + default_prompt: "Use $template-builder-ui to audit this template builder flow and payload mapping." diff --git a/skills/waba-embedded-signup/agents/openai.yaml b/skills/waba-embedded-signup/agents/openai.yaml new file mode 100644 index 0000000..e94294f --- /dev/null +++ b/skills/waba-embedded-signup/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Signup" + short_description: "Plan WhatsApp Business onboarding" + default_prompt: "Use $waba-embedded-signup to review my WABA onboarding flow and readiness." diff --git a/skills/waba-template-author/agents/openai.yaml b/skills/waba-template-author/agents/openai.yaml new file mode 100644 index 0000000..133c2f1 --- /dev/null +++ b/skills/waba-template-author/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Sent WABA Template Author" + short_description: "Draft compliant WhatsApp templates" + default_prompt: "Use $waba-template-author to draft and validate a WhatsApp template payload."