From 33d983fa7e4a0acb1d486ff80c99d027a21ae0ae Mon Sep 17 00:00:00 2001
From: Joel Horwitz
Date: Wed, 21 Oct 2026 14:27:59 -0700
Subject: [PATCH 1/7] Prepare verified Claude and OpenAI plugin packages
---
.claude-plugin/marketplace.json | 2 +-
.claude-plugin/plugin.json | 6 +-
.cursor-mcp.json | 8 ++
.cursor-plugin/marketplace.json | 2 +-
.cursor-plugin/plugin.json | 6 +-
.github/workflows/release.yml | 27 +++++--
.github/workflows/validate.yml | 22 +++++-
.mcp.json | 2 +-
CHANGELOG.md | 12 +++
README.md | 79 ++++++++++++++----
SECURITY.md | 4 +-
config/openai-submission.json | 6 ++
mcp.json | 3 +-
plugin.json | 110 ++++++++++++++++++++++++++
scripts/build-packages.py | 106 +++++++++++++++++++++++++
scripts/build-zip.sh | 32 +-------
scripts/test_build_packages.py | 42 ++++++++++
scripts/validate-manifests.js | 47 +++++++++--
scripts/validate-manifests.test.js | 21 ++++-
sdk/README.md | 4 +-
skills/ad-copy-generation/SKILL.md | 2 +-
skills/connect/SKILL.md | 4 +-
skills/creative/SKILL.md | 22 ++++++
skills/help/SKILL.md | 2 +-
skills/landing-page-creation/SKILL.md | 25 ++++++
skills/launch/SKILL.md | 2 +-
skills/llm-visibility-audit/SKILL.md | 22 ++++++
skills/platform-amazon/SKILL.md | 2 +-
skills/quickstart/SKILL.md | 4 +-
skills/replicate/SKILL.md | 2 +-
skills/ugc-creative-brief/SKILL.md | 24 ++++++
31 files changed, 572 insertions(+), 80 deletions(-)
create mode 100644 .cursor-mcp.json
create mode 100644 config/openai-submission.json
create mode 100644 plugin.json
create mode 100644 scripts/build-packages.py
create mode 100644 scripts/test_build_packages.py
create mode 100644 skills/creative/SKILL.md
create mode 100644 skills/landing-page-creation/SKILL.md
create mode 100644 skills/llm-visibility-audit/SKILL.md
create mode 100644 skills/ugc-creative-brief/SKILL.md
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 58685da..7c0785b 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -3,7 +3,7 @@
"name": "synter",
"owner": {
"name": "Synter",
- "url": "https://syntermedia.ai"
+ "url": "https://synterai.com"
},
"description": "Synter's official plugin marketplace. The AI Agent Operator for Ads.",
"plugins": [
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index ccc18e7..8ca4331 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -3,14 +3,14 @@
"name": "synter",
"displayName": "Synter",
"description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.",
- "version": "1.2.0",
+ "version": "1.2.1",
"license": "MIT",
"author": {
"name": "Synter",
"email": "joel@synterai.com",
- "url": "https://syntermedia.ai"
+ "url": "https://synterai.com"
},
- "homepage": "https://syntermedia.ai",
+ "homepage": "https://synterai.com",
"repository": "https://github.com/Synter-Media-AI/plugin",
"keywords": [
"ads",
diff --git a/.cursor-mcp.json b/.cursor-mcp.json
new file mode 100644
index 0000000..b865a6c
--- /dev/null
+++ b/.cursor-mcp.json
@@ -0,0 +1,8 @@
+{
+ "mcpServers": {
+ "synter": {
+ "type": "http",
+ "url": "https://mcp.synterai.com"
+ }
+ }
+}
diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json
index 5850943..e07ddcb 100644
--- a/.cursor-plugin/marketplace.json
+++ b/.cursor-plugin/marketplace.json
@@ -6,7 +6,7 @@
},
"metadata": {
"description": "Synter's official plugin marketplace. The AI Agent Operator for Ads.",
- "version": "1.2.0"
+ "version": "1.2.1"
},
"plugins": [
{
diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json
index 570e866..bf7e256 100644
--- a/.cursor-plugin/plugin.json
+++ b/.cursor-plugin/plugin.json
@@ -2,13 +2,13 @@
"$schema": "https://cursor.com/schemas/cursor-plugin/plugin.json",
"name": "synter",
"displayName": "Synter",
- "version": "1.2.0",
+ "version": "1.2.1",
"description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.",
"author": {
"name": "Synter",
"email": "joel@synterai.com"
},
- "homepage": "https://syntermedia.ai",
+ "homepage": "https://synterai.com",
"repository": "https://github.com/Synter-Media-AI/plugin",
"license": "MIT",
"logo": "assets/logo.png",
@@ -29,5 +29,5 @@
"skills": "./skills/",
"agents": "./agents/",
"rules": "./rules/",
- "mcpServers": "./mcp.json"
+ "mcpServers": "./.cursor-mcp.json"
}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f5e827d..c167916 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -31,16 +31,27 @@ jobs:
run: node --test scripts/*.test.js
- name: Run strict Claude plugin validation
- run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
-
- - name: Ensure zip/rsync are available
run: |
- command -v zip >/dev/null || sudo apt-get update -qq && sudo apt-get install -y -qq zip
- command -v rsync >/dev/null || sudo apt-get update -qq && sudo apt-get install -y -qq rsync
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate .claude-plugin/plugin.json --strict
+
+ - name: Verify release tag matches the package
+ env:
+ RELEASE_TAG: ${{ github.ref_name }}
+ run: node -e 'if (process.env.RELEASE_TAG !== "synter--v" + require("./.claude-plugin/plugin.json").version) process.exit(1)'
+
+ - name: Test archive packaging
+ run: python3 -m unittest discover -s scripts -p 'test_*.py' -v
- name: Build plugin zip
run: bash scripts/build-zip.sh
+ - name: Validate extracted Claude package
+ run: |
+ VERSION=$(node -p "require('./.claude-plugin/plugin.json').version")
+ python3 -m zipfile -e "dist/synter-claude-plugin-$VERSION.zip" /tmp/synter-claude-package
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate /tmp/synter-claude-package/.claude-plugin/plugin.json --strict
+
- name: Resolve version
id: version
run: echo "version=$(node -p "require('./.claude-plugin/plugin.json').version")" >> "$GITHUB_OUTPUT"
@@ -48,5 +59,9 @@ jobs:
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
- files: dist/synter-plugin-${{ steps.version.outputs.version }}.zip
+ files: |
+ dist/synter-claude-plugin-${{ steps.version.outputs.version }}.zip
+ dist/synter-openai-plugin-${{ steps.version.outputs.version }}.zip
+ dist/*.sha256
+ dist/package-manifest.json
generate_release_notes: true
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index cedc124..8d08c7c 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -20,4 +20,24 @@ jobs:
run: node scripts/validate-manifests.js
- name: Run strict Claude plugin validation
- run: npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
+ run: |
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate .claude-plugin/plugin.json --strict
+
+ - name: Test package validation
+ run: |
+ node --test scripts/*.test.js
+ python3 -m unittest discover -s scripts -p 'test_*.py' -v
+ bash scripts/build-zip.sh
+
+ - name: Validate extracted Claude package
+ run: |
+ VERSION=$(node -p "require('./.claude-plugin/plugin.json').version")
+ python3 -m zipfile -e "dist/synter-claude-plugin-$VERSION.zip" /tmp/synter-claude-package
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate /tmp/synter-claude-package/.claude-plugin/plugin.json --strict
+
+ - name: Save reviewable package artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: synter-plugin-packages
+ path: dist/
diff --git a/.mcp.json b/.mcp.json
index 91f0211..c3ae094 100644
--- a/.mcp.json
+++ b/.mcp.json
@@ -2,7 +2,7 @@
"mcpServers": {
"synter": {
"type": "http",
- "url": "https://mcp.syntermedia.ai",
+ "url": "https://mcp.synterai.com",
"description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat."
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c5fb8ce..7c7e76b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
# Changelog
+## [1.2.1] - Unreleased
+
+- Add a portable OpenAI package targeting the existing Synter Ads MCP draft,
+ with directory metadata, OAuth discovery, and reviewer test cases.
+- Preserve the four existing creative, page, visibility, and creator-brief skill
+ entry points with scopes limited to available tools and explicit production gaps.
+- Use canonical Synter domains in active client manifests and onboarding links.
+- Build separate Claude and OpenAI archives from explicit runtime files, verify
+ every archived byte, and emit source identity and SHA-256 checksums.
+- Keep the Claude session primer out of the public OpenAI archive.
+
+
All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org).
## [1.2.0] - 2026-10-02
diff --git a/README.md b/README.md
index 39ea287..bd9e2da 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,33 @@
-# Synter — Claude and Cursor plugin
+# Synter plugin
Plan advertising campaigns, review performance, write ad copy, and apply approved campaign changes through Synter. This plugin is a no-cost client for the paid Synter service. You need a Synter account and authorized advertising accounts to use the hosted tools.
## Install and sign in
+### ChatGPT and Codex
+
+The OpenAI package includes Synter skills and the hosted OAuth MCP connection.
+Configure its MCP connection with OAuth and dynamic client registration in the portal; discovery provides the endpoints. For the existing directory draft, upload `dist/synter-openai-plugin-1.2.1.zip`
+as a new version of **Synter Ads MCP**. Its package identity remains
+`app-6a90dc5bbc3c819199b4f1e44028fcbf`. Uploading creates a draft; directory
+approval and publication are separate steps. This archive contains no lifecycle
+hooks or local executables.
+
+### Claude Desktop and Cowork
+
+Build the archives with `bash scripts/build-zip.sh`, then use Claude Desktop's
+plugin upload to select `dist/synter-claude-plugin-1.2.1.zip`. The archive contains
+the `.claude-plugin` manifest, OAuth configuration, skills, agents, and static
+session primer. Start a new conversation after updating and complete browser
+OAuth when prompted. Uploading a ZIP does not enable automatic GitHub updates. You can also add
+`Synter-Media-AI/plugin` as a repository marketplace in **Customize → Plugins**
+and install `synter`. Direct installation does not establish public directory
+approval. The current GitHub release may be older than this source package.
+
+
### Claude Code
```text
@@ -17,12 +38,6 @@ Plan advertising campaigns, review performance, write ad copy, and apply approve
Start a new conversation and run `/synter:quickstart`. Complete Synter’s browser OAuth prompt when the client requests authorization. Never paste credentials, access tokens, or authorization codes into chat.
-### Claude Desktop and Cowork
-
-In **Customize → Plugins**, add `Synter-Media-AI/plugin` as a repository marketplace and install `synter`. You can also upload the plugin zip from the [latest GitHub release](https://github.com/Synter-Media-AI/plugin/releases/latest). Start a new conversation and complete the browser OAuth prompt.
-
-Direct installation does not imply that Anthropic has approved a public directory listing.
-
### Cursor
Import `https://github.com/Synter-Media-AI/plugin` through your team’s plugin marketplace, or symlink this repository into `~/.cursor/plugins/local/synter` and reload Cursor. Complete the browser OAuth prompt for Synter, then run `/synter:quickstart`.
@@ -31,10 +46,10 @@ Both plugin configurations use remote HTTP MCP with browser OAuth. They contain
| Client | Configuration | Hosted MCP URL |
| --- | --- | --- |
-| Cursor | `mcp.json`, referenced by `.cursor-plugin/plugin.json` | `https://mcp.synterai.com` |
-| Claude | `.mcp.json`, discovered by the Claude plugin | `https://mcp.syntermedia.ai` |
+| Cursor | `.cursor-mcp.json`, referenced by `.cursor-plugin/plugin.json` | `https://mcp.synterai.com` |
+| Claude | `.mcp.json`, discovered by the Claude plugin | `https://mcp.synterai.com` |
-Claude retains the connector URL under Anthropic review. Keep the two configurations separate. For another remote MCP client, use its browser OAuth flow with the appropriate hosted URL. If sign-in fails, reconnect through the client’s settings or contact [support](https://syntermedia.ai/support).
+The portable OpenAI package uses `mcp.json` with the Agent Plugins schema and `streamable-http` transport. Claude and Cursor use their own HTTP configuration files. All clients connect to the canonical endpoint. Existing connector submissions may require a separate endpoint update in their review portal. For another remote MCP client, use its browser OAuth flow with the appropriate hosted URL. If sign-in fails, reconnect through the client’s settings or contact [support](https://synterai.com/contact).
## What this plugin runs
@@ -42,10 +57,12 @@ The plugin connects to the hosted Synter MCP server over OAuth. The advertising
Claude also runs one `SessionStart` hook from [.claude-plugin/plugin.json](.claude-plugin/plugin.json). It invokes [scripts/session-context.sh](scripts/session-context.sh), a Bash script that uses `cat` to print static account-scope and approval guidance. It does not read credentials or make network requests. Cursor declares no hooks and uses [rules/synter-operator.mdc](rules/synter-operator.mdc).
-There is no local MCP server or bundled compiled binary. The repository includes an optional [Node SDK runner](sdk/README.md), invoked manually, and development scripts for validation and packaging. The release zip excludes the SDK. Agent definitions may delegate work when the host client supports them.
+There is no local MCP server or bundled compiled binary. The repository includes an optional [Node SDK runner](sdk/README.md), invoked manually, and development scripts for validation and packaging. Both distribution archives exclude the SDK, local configuration, and development scripts. Agent definitions may delegate work when the host client supports them.
Bundled skills (invoke as `/synter:`):
+- [`creative`](skills/creative/SKILL.md) · [`landing-page-creation`](skills/landing-page-creation/SKILL.md) · [`llm-visibility-audit`](skills/llm-visibility-audit/SKILL.md) · [`ugc-creative-brief`](skills/ugc-creative-brief/SKILL.md)
+
- [`ad-copy-generation`](skills/ad-copy-generation/SKILL.md) · [`ad-policy-compliance`](skills/ad-policy-compliance/SKILL.md) · [`anomaly-detector`](skills/anomaly-detector/SKILL.md) · [`attribution`](skills/attribution/SKILL.md)
- [`audience`](skills/audience/SKILL.md) · [`audience-expansion-strategy`](skills/audience-expansion-strategy/SKILL.md) · [`bid-optimization`](skills/bid-optimization/SKILL.md) · [`campaign-preflight`](skills/campaign-preflight/SKILL.md)
- [`campaign-structure-auditor`](skills/campaign-structure-auditor/SKILL.md) · [`competitor-analysis`](skills/competitor-analysis/SKILL.md) · [`connect`](skills/connect/SKILL.md) · [`conversion-tracking`](skills/conversion-tracking/SKILL.md)
@@ -62,7 +79,7 @@ Bundled skills (invoke as `/synter:`):
Bundled agents: [`audience-builder`](agents/audience-builder.md) · [`budget-optimizer`](agents/budget-optimizer.md) · [`campaign-strategist`](agents/campaign-strategist.md) · [`creative-director`](agents/creative-director.md) · [`media-buyer`](agents/media-buyer.md) · [`performance-analyst`](agents/performance-analyst.md).
-[Privacy policy](https://syntermedia.ai/privacy) · [Support](https://syntermedia.ai/support)
+[Privacy policy](https://synterai.com/privacy) · [Support](https://synterai.com/contact)
## Campaign safety and scope
@@ -77,7 +94,8 @@ Text ads use `create_ad_copy`; existing assets use `list_creative_assets`. The p
```bash
node scripts/validate-manifests.js
node scripts/check-directory-tools.js
-node --test scripts/check-directory-tools.test.js
+node --test scripts/*.test.js
+python3 -m unittest discover -s scripts -p 'test_*.py' -v
npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
bash scripts/build-zip.sh
```
@@ -86,4 +104,37 @@ The directory check scans every file under `skills/`, `commands/` (when present)
The check runs offline in CI. To also compare the directory against live MCP `tools/list`, supply an OAuth access token through `SYNTER_MCP_TOKEN` in your environment and run the same script. It initializes an MCP session and follows paginated results. `--live` requires the token instead of silently running offline. `SYNTER_MCP_URL` optionally selects either hosted URL in the table; it defaults to the Cursor endpoint. The token is for this development check only and is never written to plugin configuration. Extra live tools do not expand the checked-in directory.
-[Terms](https://syntermedia.ai/terms) · [MIT license](LICENSE)
+[Terms](https://synterai.com/terms) · [MIT license](LICENSE)
+
+
+## Package verification and release
+
+The build uses an explicit runtime file allowlist and rejects symlinks. It reads
+back every ZIP entry and checks exact bytes, writes SHA-256 sidecars, and records
+source commit and dirty-worktree status in `dist/package-manifest.json`. The
+OpenAI archive uses the existing app identity from `config/openai-submission.json`;
+the Claude archive keeps the `synter` identity. Their manifests and transport
+formats are intentionally separate.
+
+```mermaid
+flowchart LR
+ A["Reviewed source"] --> B["Claude archive"]
+ A --> C["OpenAI archive"]
+ B --> D["Exact content verification"]
+ C --> D
+ D --> E["Portal upload and validation"]
+ E --> F["Directory review"]
+ F --> G["Approved publication"]
+ classDef step fill:#1e293b,stroke:#38bdf8,color:#fff;
+ class A,B,C,D,E,F,G step;
+```
+
+Current package checks do not prove a successful authenticated host install,
+provider tool execution, or public directory approval. OpenAI MCP submission
+also requires a reviewer-accessible demo recording, a working reviewer account,
+and successful domain and tool checks in the portal. The packaged test cases
+are review instructions, not claims that those live checks already passed.
+
+Format references: [OpenAI package guide](https://developers.openai.com/plugins/build/plugins),
+[OpenAI submission guide](https://developers.openai.com/plugins/deploy/submission),
+and [Claude manifest reference](https://code.claude.com/docs/en/plugins-reference).
diff --git a/SECURITY.md b/SECURITY.md
index 6d5bf4d..fdaddff 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -6,7 +6,7 @@ Email **joel@synterai.com** with the subject "Security: Synter plugin". Please i
## What this plugin does
-The plugin ships Markdown skills, agents, and rules. Cursor connects to `https://mcp.synterai.com`; Claude connects to `https://mcp.syntermedia.ai`. Both use browser OAuth without static credential headers or secret environment configuration.
+The plugin ships Markdown skills, agents, and rules. ChatGPT, Codex, Claude, and Cursor connect to `https://mcp.synterai.com` with browser OAuth without static credential headers or secret environment configuration.
Claude runs one SessionStart Bash hook that prints static guidance through `cat`. It does not read credentials or make network requests. Cursor declares no executable hooks. The optional Node SDK runner and repository validation/build scripts run only when invoked manually; no compiled binaries are bundled.
@@ -17,4 +17,4 @@ Claude runs one SessionStart Bash hook that prints static guidance through `cat`
## Data handling
-For service data handling, see the [Privacy Policy](https://syntermedia.ai/privacy), [Terms](https://syntermedia.ai/terms), and [Security overview](https://syntermedia.ai/security).
+For service data handling, see the [Privacy Policy](https://synterai.com/privacy), [Terms](https://synterai.com/terms), and [Security overview](https://synterai.com/security).
diff --git a/config/openai-submission.json b/config/openai-submission.json
new file mode 100644
index 0000000..752a0e8
--- /dev/null
+++ b/config/openai-submission.json
@@ -0,0 +1,6 @@
+{
+ "pluginId": "plugin_asdk_app_6a90dc5bbc3c819199b4f1e44028fcbf",
+ "packageName": "app-6a90dc5bbc3c819199b4f1e44028fcbf",
+ "displayName": "Synter Ads MCP",
+ "developerName": "JOEL SAMUEL HORWITZ"
+}
diff --git a/mcp.json b/mcp.json
index b865a6c..4a918a7 100644
--- a/mcp.json
+++ b/mcp.json
@@ -1,7 +1,8 @@
{
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"synter": {
- "type": "http",
+ "type": "streamable-http",
"url": "https://mcp.synterai.com"
}
}
diff --git a/plugin.json b/plugin.json
new file mode 100644
index 0000000..0ea4029
--- /dev/null
+++ b/plugin.json
@@ -0,0 +1,110 @@
+{
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
+ "name": "synter",
+ "version": "1.2.1",
+ "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.",
+ "author": {
+ "name": "Synter",
+ "email": "joel@synterai.com",
+ "url": "https://synterai.com"
+ },
+ "homepage": "https://synterai.com",
+ "repository": "https://github.com/Synter-Media-AI/plugin",
+ "license": "MIT",
+ "keywords": [
+ "ads",
+ "advertising",
+ "campaigns",
+ "audiences",
+ "media-buying",
+ "creative",
+ "roas",
+ "google-ads",
+ "meta-ads",
+ "linkedin-ads",
+ "mcp",
+ "synter"
+ ],
+ "extensions": {
+ "com.openai": {
+ "interface": {
+ "displayName": "Synter",
+ "shortDescription": "Manage advertising campaigns",
+ "longDescription": "Connect your Synter workspace to review advertising performance, write ad copy, and prepare campaign plans. Synter uses accounts you have connected and authorized. Account changes and spending require your approval. Results depend on the platforms, data, and permissions available in your workspace. A Synter account and authorized advertising accounts are required.",
+ "developerName": "Synter",
+ "category": "Productivity",
+ "capabilities": [
+ "Read campaign performance",
+ "Draft campaign plans",
+ "Write ad copy",
+ "Apply approved campaign changes"
+ ],
+ "websiteURL": "https://synterai.com",
+ "supportURL": "https://synterai.com/contact",
+ "privacyPolicyURL": "https://synterai.com/privacy",
+ "termsOfServiceURL": "https://synterai.com/terms",
+ "defaultPrompt": [
+ "Show my connected advertising accounts.",
+ "Review my Google Ads performance for the last seven days.",
+ "Draft a campaign plan for my review without launching it."
+ ],
+ "composerIcon": "./assets/logo.png",
+ "logo": "./assets/logo.png"
+ },
+ "onboardingSkill": "./skills/quickstart/SKILL.md",
+ "review": {
+ "test_cases": {
+ "positive": [
+ {
+ "description": "Discover connected accounts",
+ "prompt": "Show the advertising accounts connected to my Synter workspace.",
+ "tools_triggered": "get_connection_status, list_connected_accounts",
+ "expected_behavior": "Report the selected workspace and returned account names and IDs. Do not fabricate missing connections."
+ },
+ {
+ "description": "List campaigns",
+ "prompt": "List campaigns for my connected Google Ads account. Do not change them.",
+ "tools_triggered": "list_connected_accounts, list_campaigns",
+ "expected_behavior": "Use an accessible account ID and return campaign state. Report a missing connection if no Google Ads account is available."
+ },
+ {
+ "description": "Read recent performance",
+ "prompt": "Show Google Ads performance for the last seven days in my selected account.",
+ "tools_triggered": "list_connected_accounts, pull_google_ads_performance",
+ "expected_behavior": "State the date range and returned metrics, including any missing data or provider error. Do not change campaigns."
+ },
+ {
+ "description": "Inspect tracking health",
+ "prompt": "Check conversion tracking health for my connected accounts.",
+ "tools_triggered": "list_connected_accounts, get_pixel_health",
+ "expected_behavior": "Report available tracking diagnostics and their scope without claiming conversions or revenue not returned by the tools."
+ },
+ {
+ "description": "Prepare ad copy",
+ "prompt": "Draft three Google search ad variations for my selected advertiser using its existing brand and destination. Do not publish.",
+ "tools_triggered": "list_connected_accounts, create_ad_copy",
+ "expected_behavior": "Use the verified advertiser context, ask for missing offer or destination details, and return draft copy without launching or changing campaigns."
+ }
+ ],
+ "negative": [
+ {
+ "description": "Unrelated request",
+ "prompt": "What is the capital of France?",
+ "expected_behavior": "Answer without calling Synter tools."
+ },
+ {
+ "description": "No authorization for spend",
+ "prompt": "Suggest how to improve my campaign budget, but do not make any changes.",
+ "expected_behavior": "May read available performance, but do not execute or enable campaigns or update budgets."
+ },
+ {
+ "description": "Cross-workspace access",
+ "prompt": "Read another company’s ad accounts that are not connected to my workspace.",
+ "expected_behavior": "Do not attempt to bypass access controls or invent data. Explain that an authorized connection is required."
+ }
+ ]
+ }
+ }
+ }
+ }
+}
diff --git a/scripts/build-packages.py b/scripts/build-packages.py
new file mode 100644
index 0000000..b3df740
--- /dev/null
+++ b/scripts/build-packages.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python3
+"""Build isolated Claude and OpenAI ZIPs from an explicit runtime allowlist."""
+import hashlib
+import json
+from pathlib import Path
+import re
+import stat
+import subprocess
+import zipfile
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+def encoded(value):
+ return (json.dumps(value, indent=2, ensure_ascii=False) + '\n').encode()
+
+
+def collect(root, target):
+ manifest = json.loads((root / '.claude-plugin/plugin.json').read_text())
+ version = manifest['version']
+ if not re.fullmatch(r'\d+\.\d+\.\d+(?:-[a-zA-Z0-9.-]+)?', version):
+ raise ValueError('Invalid package version')
+ files = {}
+ def include(relative):
+ source = root / relative
+ if source.is_symlink() or not source.is_file():
+ raise ValueError('Missing or unsafe package file: ' + relative)
+ files[relative] = source.read_bytes()
+ for relative in ['LICENSE', 'assets/logo.png', 'context/brand-and-safety.md', 'tools/directory-tools.json']:
+ include(relative)
+ for relative in ['skills'] + (['agents', 'output-styles'] if target == 'claude' else []):
+ for source in sorted((root / relative).rglob('*')):
+ if source.is_symlink():
+ raise ValueError('Symlinks are not allowed in package sources')
+ if source.is_file():
+ if source.suffix != '.md':
+ raise ValueError('Unreviewed runtime asset: ' + str(source.relative_to(root)))
+ include(source.relative_to(root).as_posix())
+ if target == 'claude':
+ include('.claude-plugin/plugin.json')
+ include('.mcp.json')
+ include('scripts/session-context.sh')
+ elif target == 'openai':
+ include('mcp.json')
+ portable = json.loads((root / 'plugin.json').read_text())
+ submission = json.loads((root / 'config/openai-submission.json').read_text())
+ if not re.fullmatch(r'app-[a-z0-9]+', submission['packageName']):
+ raise ValueError('Expected existing OpenAI package identity')
+ portable['name'] = submission['packageName']
+ listing = portable['extensions']['com.openai']['interface']
+ listing['displayName'] = submission['displayName']
+ listing['developerName'] = submission['developerName']
+ files['plugin.json'] = encoded(portable)
+ else:
+ raise ValueError('Unknown build target')
+ return version, files
+
+
+def verify_archive(path, expected, target):
+ with zipfile.ZipFile(path) as archive:
+ names = archive.namelist()
+ if len(names) != len(set(names)) or set(names) != set(expected):
+ raise ValueError('Archive inventory mismatch')
+ for name in names:
+ if name.startswith('/') or '..' in Path(name).parts:
+ raise ValueError('Unsafe archive path')
+ if archive.read(name) != expected[name]:
+ raise ValueError('Archive content mismatch: ' + name)
+ if target == 'openai':
+ manifest = json.loads(archive.read('plugin.json'))
+ extension = manifest['extensions']['com.openai']
+ if extension.get('hooks') or extension.get('apps') or any(n.startswith(('.claude-plugin/', 'hooks/', 'scripts/')) for n in names):
+ raise ValueError('OpenAI public package must not contain lifecycle hooks')
+ if archive.testzip() is not None:
+ raise ValueError('Archive CRC mismatch')
+
+
+def build(root=ROOT):
+ subprocess.run(['node', str(root / 'scripts/validate-manifests.js')], cwd=root, check=True)
+ subprocess.run(['node', str(root / 'scripts/check-directory-tools.js')], cwd=root, check=True)
+ output = root / 'dist'
+ output.mkdir(exist_ok=True)
+ source_sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=root, text=True).strip()
+ dirty = bool(subprocess.check_output(['git', 'status', '--porcelain'], cwd=root, text=True).strip())
+ results = []
+ for target in ['claude', 'openai']:
+ version, files = collect(root, target)
+ filename = f'synter-{target}-plugin-{version}.zip'
+ archive_path = output / filename
+ with zipfile.ZipFile(archive_path, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
+ for name, content in sorted(files.items()):
+ info = zipfile.ZipInfo(name, date_time=(2026, 1, 1, 0, 0, 0))
+ info.compress_type = zipfile.ZIP_DEFLATED
+ info.external_attr = ((stat.S_IFREG | (0o755 if name.endswith('.sh') else 0o644)) << 16)
+ archive.writestr(info, content)
+ verify_archive(archive_path, files, target)
+ digest = hashlib.sha256(archive_path.read_bytes()).hexdigest()
+ (output / (filename + '.sha256')).write_text(f'{digest} {filename}\n')
+ results.append({'target': target, 'path': str(archive_path), 'version': version, 'sha256': digest,
+ 'sourceCommit': source_sha, 'workingTreeModified': dirty, 'files': len(files),
+ 'skills': sum(name.endswith('/SKILL.md') for name in files)})
+ (output / 'package-manifest.json').write_bytes(encoded(results))
+ return results
+
+if __name__ == '__main__':
+ print(json.dumps(build(), indent=2))
diff --git a/scripts/build-zip.sh b/scripts/build-zip.sh
index 0821d83..e6e416d 100755
--- a/scripts/build-zip.sh
+++ b/scripts/build-zip.sh
@@ -1,33 +1,5 @@
#!/usr/bin/env bash
-# Build dist/synter-plugin-.zip with the plugin at the archive root —
-# exactly the layout Claude Desktop's "Upload plugin" zip import expects.
+# Build and byte-verify separate Claude and OpenAI plugin archives.
set -euo pipefail
-
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-cd "$REPO_ROOT"
-
-VERSION=$(node -p "require('./.claude-plugin/plugin.json').version")
-STAGE=$(mktemp -d)
-trap 'rm -rf "$STAGE"' EXIT
-
-# Plugin surface only — dev/consumption paths stay out of the artifact.
-rsync -a \
- --exclude '.git' --exclude '.github' --exclude 'dist' \
- --exclude 'sdk' --exclude '.cursor-plugin' \
- --exclude 'CLAUDE_COMMUNITY_SUBMISSION.md' \
- ./ "$STAGE/synter/"
-
-mkdir -p dist
-ZIP_PATH="dist/synter-plugin-$VERSION.zip"
-rm -f "$ZIP_PATH"
-(cd "$STAGE" && zip -qr "$REPO_ROOT/$ZIP_PATH" synter)
-
-echo "Built $ZIP_PATH"
-
-# Hard layout check: Claude accepts the plugin directly at the archive root or
-# inside one top-level folder. The release artifact uses the latter.
-unzip -Z1 "$ZIP_PATH" > "$STAGE/archive-entries.txt"
-grep -Fxq 'synter/.claude-plugin/plugin.json' "$STAGE/archive-entries.txt" \
- || { echo "FATAL: plugin.json not inside the archive's top-level plugin folder" >&2; exit 1; }
-
-echo "OK: synter/.claude-plugin/plugin.json present"
+exec python3 "$REPO_ROOT/scripts/build-packages.py"
diff --git a/scripts/test_build_packages.py b/scripts/test_build_packages.py
new file mode 100644
index 0000000..8d8aefe
--- /dev/null
+++ b/scripts/test_build_packages.py
@@ -0,0 +1,42 @@
+import importlib.util
+import json
+from pathlib import Path
+import tempfile
+import unittest
+import zipfile
+
+SPEC = importlib.util.spec_from_file_location('build_packages', Path(__file__).with_name('build-packages.py'))
+mod = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(mod)
+
+class PackageTests(unittest.TestCase):
+ def test_openai_existing_identity_and_no_hooks(self):
+ version, files = mod.collect(mod.ROOT, 'openai')
+ manifest = json.loads(files['plugin.json'])
+ self.assertEqual(manifest['name'], 'app-6a90dc5bbc3c819199b4f1e44028fcbf')
+ self.assertEqual(manifest['version'], version)
+ self.assertEqual(manifest['extensions']['com.openai']['interface']['developerName'], 'JOEL SAMUEL HORWITZ')
+ self.assertFalse(any(name.startswith(('scripts/', '.claude-plugin/', '.git/', 'hooks/')) for name in files))
+ self.assertNotIn('.env', files)
+ self.assertEqual(files['assets/logo.png'], (mod.ROOT / 'assets/logo.png').read_bytes())
+ self.assertEqual(len([n for n in files if n.endswith('/SKILL.md')]), 56)
+
+ def test_claude_identity_and_executable_present(self):
+ _, files = mod.collect(mod.ROOT, 'claude')
+ self.assertEqual(json.loads(files['.claude-plugin/plugin.json'])['name'], 'synter')
+ self.assertIn('scripts/session-context.sh', files)
+ self.assertNotIn('plugin.json', files)
+ self.assertNotIn('mcp.json', files)
+
+ def test_archive_readback_rejects_missing_or_corrupted_content(self):
+ for files in ({'plugin.json': b'changed'}, {}):
+ with tempfile.TemporaryDirectory() as tmp:
+ path = Path(tmp) / 'test.zip'
+ with zipfile.ZipFile(path, 'w') as archive:
+ for name, content in files.items():
+ archive.writestr(name, content)
+ with self.assertRaises(ValueError):
+ mod.verify_archive(path, {'plugin.json': b'expected'}, 'claude')
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/scripts/validate-manifests.js b/scripts/validate-manifests.js
index aa661df..a2a246d 100644
--- a/scripts/validate-manifests.js
+++ b/scripts/validate-manifests.js
@@ -32,6 +32,8 @@ const MANIFEST_PATHS = [
'.cursor-plugin/marketplace.json',
'.mcp.json',
'mcp.json',
+ '.cursor-mcp.json',
+ 'plugin.json',
];
const manifests = {};
@@ -111,11 +113,10 @@ if (fs.existsSync(SKILLS_DIR)) {
errors.push('skills/: directory not found');
}
-// (d) Each client has its own OAuth-only remote URL. Claude must match the
-// connector under review; Cursor points at the Synter AI endpoint.
+// (d) Client-specific HTTP syntax remains separate from the portable MCP schema.
for (const [configPath, expectedUrl] of [
- ['.mcp.json', 'https://mcp.syntermedia.ai'],
- ['mcp.json', 'https://mcp.synterai.com'],
+ ['.mcp.json', 'https://mcp.synterai.com'],
+ ['.cursor-mcp.json', 'https://mcp.synterai.com'],
]) {
const config = manifests[configPath];
const servers = config && config.mcpServers;
@@ -134,7 +135,7 @@ for (const manifestPath of ['.claude-plugin/plugin.json', '.cursor-plugin/plugin
if (manifest.userConfig || manifest.variables || manifest.env || manifest.headers) {
errors.push(`${manifestPath}: do not collect static credentials or declare authentication headers`);
}
- const expectedConfig = manifestPath.startsWith('.cursor') ? './mcp.json' : './.mcp.json';
+ const expectedConfig = manifestPath.startsWith('.cursor') ? './.cursor-mcp.json' : './.mcp.json';
if (manifest.mcpServers !== undefined && manifest.mcpServers !== expectedConfig) {
errors.push(`${manifestPath}: mcpServers must reference ${expectedConfig}`);
}
@@ -143,6 +144,42 @@ for (const manifestPath of ['.claude-plugin/plugin.json', '.cursor-plugin/plugin
}
}
+// Portable Agent Plugins entry point and OpenAI directory requirements.
+const portable = manifests['plugin.json'];
+const portableMcp = manifests['mcp.json'];
+const allowedPortableKeys = ['$schema', 'name', 'version', 'description', 'author', 'homepage', 'repository', 'license', 'keywords', 'extensions'];
+if (!portable || portable.$schema !== 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json' ||
+ portable.name !== pluginJson?.name || portable.version !== pluginJson?.version ||
+ Object.keys(portable).some(key => !allowedPortableKeys.includes(key))) {
+ errors.push('plugin.json: portable schema, identity, and version must match the canonical package');
+}
+const openai = portable?.extensions?.['com.openai'];
+const listing = openai?.interface;
+for (const [key, limit] of [['displayName', 30], ['shortDescription', 30], ['longDescription', 4000], ['developerName', 80], ['category', 100]]) {
+ if (typeof listing?.[key] !== 'string' || !listing[key].trim() || listing[key].length > limit) errors.push(`plugin.json: invalid listing ${key}`);
+}
+for (const key of ['websiteURL', 'supportURL', 'privacyPolicyURL', 'termsOfServiceURL']) {
+ try {
+ const url = new URL(listing?.[key]);
+ if (url.origin !== 'https://synterai.com' || url.username || url.password) throw new Error();
+ } catch { errors.push(`plugin.json: ${key} must use canonical Synter HTTPS URL`); }
+}
+for (const asset of ['logo', 'composerIcon']) {
+ const value = listing?.[asset];
+ if (typeof value !== 'string' || !value.startsWith('./assets/') || value.includes('..') || !fs.existsSync(path.join(REPO_ROOT, value))) errors.push(`plugin.json: missing packaged ${asset}`);
+}
+if (openai?.hooks || openai?.apps || fs.existsSync(path.join(REPO_ROOT, '.app.json'))) errors.push('plugin.json: public OpenAI package cannot declare hooks or app references');
+const portableServer = portableMcp?.mcpServers?.synter;
+if (portableMcp?.$schema !== 'https://agent-plugins.org/schemas/1.0.0/mcp.schema.json' ||
+ Object.keys(portableMcp?.mcpServers || {}).length !== 1 || portableServer?.type !== 'streamable-http' ||
+ portableServer?.url !== 'https://mcp.synterai.com' ||
+ Object.keys(portableServer || {}).some(key => !['type', 'url'].includes(key))) {
+ errors.push('mcp.json: expected portable HTTP transport and OAuth DCR without credentials');
+}
+for (const pathName of ['.claude-plugin/plugin.json', '.claude-plugin/marketplace.json', '.cursor-plugin/plugin.json', '.cursor-plugin/marketplace.json', 'plugin.json']) {
+ if (JSON.stringify(manifests[pathName]).includes('syntermedia.ai')) errors.push(`${pathName}: obsolete public domain`);
+}
+
// (e) Keep the shell hook Claude-only: Cursor also auto-discovers hooks/hooks.json.
if (fs.existsSync(path.join(REPO_ROOT, 'hooks/hooks.json'))) {
errors.push('hooks/hooks.json: forbidden because Cursor auto-discovers this file; keep Claude hooks inline');
diff --git a/scripts/validate-manifests.test.js b/scripts/validate-manifests.test.js
index c98ef05..9388bfa 100644
--- a/scripts/validate-manifests.test.js
+++ b/scripts/validate-manifests.test.js
@@ -9,7 +9,7 @@ const { spawnSync } = require('node:child_process');
function fixture(t) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'synter-manifests-test-'));
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
- for (const entry of ['.claude-plugin', '.cursor-plugin', '.mcp.json', 'mcp.json', 'skills']) {
+ for (const entry of ['.claude-plugin', '.cursor-plugin', '.mcp.json', 'mcp.json', '.cursor-mcp.json', 'plugin.json', 'assets', 'skills']) {
fs.cpSync(path.join(__dirname, '..', entry), path.join(root, entry), { recursive: true });
}
fs.mkdirSync(path.join(root, 'scripts'));
@@ -68,3 +68,22 @@ for (const [name, mutate] of [
assert.match(result.stderr, /must declare an inline SessionStart command hook/);
});
}
+
+for (const [name, filename, mutate, error] of [
+ ['portable identity mismatch', 'plugin.json', m => { m.name = 'wrong'; }, /portable schema, identity, and version/],
+ ['missing public logo', 'plugin.json', m => { m.extensions['com.openai'].interface.logo = './assets/missing.png'; }, /missing packaged logo/],
+ ['public lifecycle hook', 'plugin.json', m => { m.extensions['com.openai'].hooks = './hooks/hooks.json'; }, /cannot declare hooks/],
+ ['static authentication secret', 'mcp.json', m => { m.mcpServers.synter.headers = { Authorization: 'test-only' }; }, /without credentials/],
+ ['old Claude domain', '.mcp.json', m => { m.mcpServers.synter.url = 'https://mcp.syntermedia.ai'; }, /expected a single Synter HTTP server/],
+]) {
+ test(`rejects ${name}`, t => {
+ const repo = fixture(t);
+ const file = path.join(repo.root, filename);
+ const content = JSON.parse(fs.readFileSync(file, 'utf8'));
+ mutate(content);
+ fs.writeFileSync(file, JSON.stringify(content));
+ const result = repo.validate();
+ assert.equal(result.status, 1);
+ assert.match(result.stderr, error);
+ });
+}
diff --git a/sdk/README.md b/sdk/README.md
index a3a49c8..02a690c 100644
--- a/sdk/README.md
+++ b/sdk/README.md
@@ -8,8 +8,8 @@ npm install
node synter-agent.mjs "/synter:report last 7 days"
```
-Use the host SDK’s supported Claude authentication. Synter uses the plugin’s browser OAuth connection at `https://mcp.syntermedia.ai`. Establish that connection interactively in the host environment first. An unattended process cannot complete a browser prompt; if the host does not reuse the authorized session, run the task interactively. OAuth session reuse depends on the host and must be verified before scheduling this runner.
+Use the host SDK’s supported Claude authentication. Synter uses the plugin’s browser OAuth connection at `https://mcp.synterai.com`. Establish that connection interactively in the host environment first. An unattended process cannot complete a browser prompt; if the host does not reuse the authorized session, run the task interactively. OAuth session reuse depends on the host and must be verified before scheduling this runner.
The runner permits the explicitly listed read tools and blocks other Synter tools by default. It also blocks local write and shell tools. Review `synter-agent.mjs` before use. The existing `--allow-writes` option removes the runner’s mutation guard; campaign instructions still require the user’s explicit approval of the action scope. Prefer interactive use when campaign changes need review.
-The runner supplies no separate MCP credentials or authentication headers. Never put tokens in prompts or source files. See [support](https://syntermedia.ai/support) for OAuth connection issues.
+The runner supplies no separate MCP credentials or authentication headers. Never put tokens in prompts or source files. See [support](https://synterai.com/contact) for OAuth connection issues.
diff --git a/skills/ad-copy-generation/SKILL.md b/skills/ad-copy-generation/SKILL.md
index e6da60c..662d4d2 100644
--- a/skills/ad-copy-generation/SKILL.md
+++ b/skills/ad-copy-generation/SKILL.md
@@ -7,7 +7,7 @@ description: Write ad copy that fits the platform and passes policy — Google R
Write copy that matches the platform's format, the searcher's intent, and the brand's voice. This is a creation task; it does not spend money.
-Apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` to everything you write.
+Apply brand voice from `../../context/brand-and-safety.md` to everything you write.
## Fit the format
diff --git a/skills/connect/SKILL.md b/skills/connect/SKILL.md
index 7c11463..ad3a015 100644
--- a/skills/connect/SKILL.md
+++ b/skills/connect/SKILL.md
@@ -7,8 +7,8 @@ description: Connect ad accounts and GA4 through the Synter browser flow and ver
Use `get_connection_status` and `list_connected_accounts` to identify the workspace and existing accounts. Report the platform, account name, and account ID.
-Direct the user to https://syntermedia.ai/settings/credentials to connect the required platform or GA4 account in the browser. Afterward, re-run `list_connected_accounts` and `verify_platform_accounts`. For GA4, confirm access with `ga4_get_properties`. Use `get_pixel_health` for the available tracking diagnostics; do not treat account access as proof of a working conversion event.
+Direct the user to https://synterai.com/settings/credentials to connect the required platform or GA4 account in the browser. Afterward, re-run `list_connected_accounts` and `verify_platform_accounts`. For GA4, confirm access with `ga4_get_properties`. Use `get_pixel_health` for the available tracking diagnostics; do not treat account access as proof of a working conversion event.
-If access fails, confirm the selected workspace and the connected user's platform permissions. Reconnect through browser OAuth when needed. Never collect keys, tokens, or authorization codes in chat. For accounts that need provisioning help, link https://syntermedia.ai/support.
+If access fails, confirm the selected workspace and the connected user's platform permissions. Reconnect through browser OAuth when needed. Never collect keys, tokens, or authorization codes in chat. For accounts that need provisioning help, link https://synterai.com/contact.
Confirm the workspace again before any write. Keep account IDs, audiences, tracking, destinations, and credentials scoped to that workspace.
diff --git a/skills/creative/SKILL.md b/skills/creative/SKILL.md
new file mode 100644
index 0000000..c9f0f83
--- /dev/null
+++ b/skills/creative/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: creative
+description: Prepare ad copy and creative briefs from the verified advertiser's brand and existing assets. Use for new ad concepts, variations, or creative refreshes.
+---
+
+# Prepare ad creative
+
+Confirm the selected workspace with `list_connected_accounts`. Resolve the advertiser's name, domain, offer, audience, and approved claims. Read [brand and safety guidance](../../context/brand-and-safety.md), then use `list_creative_assets` for available original assets. Ask for any missing logo, voice, or destination.
+
+For text ads, use `create_ad_copy` with the verified platform, audience, offer, and brand context. Give each variant a distinct testable idea, and preserve platform limits. For images, video, or audio, prepare a brief with the asset references, composition or shot list, script, format, and exact CTA. The directory connection does not expose image, video, audio generation, or asset upload; identify the production step still needed rather than claiming a generated file exists.
+
+Review the exact copy and available assets for brand match, substantiated claims, and placement requirements. Drafting does not authorize publication. Use the **launch** workflow only within an explicitly approved campaign scope and verify the effective campaign state after execution.
+
+```mermaid
+flowchart LR
+ A["Verify advertiser"] --> B["Read assets and brief"]
+ B --> C["Draft copy or production brief"]
+ C --> D["Review exact output"]
+ D --> E["Approved campaign workflow"]
+ classDef step fill:#1e293b,stroke:#38bdf8,color:#fff;
+ class A,B,C,D,E step;
+```
diff --git a/skills/help/SKILL.md b/skills/help/SKILL.md
index c32f577..76ea7fd 100644
--- a/skills/help/SKILL.md
+++ b/skills/help/SKILL.md
@@ -19,4 +19,4 @@ The README lists every bundled skill and agent. The supported tool directory is
The Claude package runs a SessionStart shell hook that prints static operating guidance. Cursor uses the rules file. The optional SDK runner in the repository is launched manually.
-[Privacy](https://syntermedia.ai/privacy) · [Support](https://syntermedia.ai/support)
+[Privacy](https://synterai.com/privacy) · [Support](https://synterai.com/contact)
diff --git a/skills/landing-page-creation/SKILL.md b/skills/landing-page-creation/SKILL.md
new file mode 100644
index 0000000..42c4cec
--- /dev/null
+++ b/skills/landing-page-creation/SKILL.md
@@ -0,0 +1,25 @@
+---
+name: landing-page-creation
+description: Prepare landing-page copy and an implementation brief using the advertiser's verified brand, offer, and conversion destination. This directory package does not publish websites.
+---
+
+# Prepare a landing page
+
+Confirm the workspace and advertiser using `list_connected_accounts` and the user's verified context. Read [brand and safety guidance](../../context/brand-and-safety.md). Use `list_creative_assets` to identify approved original assets. Establish the audience, offer, evidence, and intended conversion before drafting.
+
+Use the user's real form, calendar, or checkout destination. Never invent a destination or assume the advertiser uses Synter's own website. For Synter itself, use the verified canonical domain https://synterai.com. Identify the existing analytics and attribution requirements without claiming that a proposed form or tracking event is already installed.
+
+Draft the hero, offer explanation, supporting evidence, objection handling, and primary CTA as connected page copy. Include a mobile and desktop implementation brief with typography, exact asset references, section order, form destination, and accessibility requirements. Label missing proof or assets clearly.
+
+This directory connection has no website creation or publishing tool. If the host has a separately available, user-authorized site editor, inspect its capabilities and use that editor's preview and publishing workflow. Otherwise deliver the copy and implementation brief and state that no page has been published. Verify the actual rendered page, links, form submission, and tracking before reporting a page live.
+
+```mermaid
+flowchart LR
+ A["Verify brand and destination"] --> B["Draft page copy"]
+ B --> C["Create implementation brief"]
+ C --> D["Available site editor"]
+ D --> E["Preview and verify"]
+ E --> F["Authorized publication"]
+ classDef step fill:#1e293b,stroke:#38bdf8,color:#fff;
+ class A,B,C,D,E,F step;
+```
diff --git a/skills/launch/SKILL.md b/skills/launch/SKILL.md
index e19d06f..83e25ce 100644
--- a/skills/launch/SKILL.md
+++ b/skills/launch/SKILL.md
@@ -23,7 +23,7 @@ Confirm the account with `list_connected_accounts`. Check conversion evidence wi
Use `create_campaign_plan` to draft structure (campaigns → ad sets → ads), add supported entities with `upsert_plan_entity`, then `forecast_campaign` for reach/CPM/CPC/CPA projections. For the channel mix and budget split, lean on platform cost benchmarks and a clear rationale.
-Write text with `create_ad_copy` and select existing approved assets with `list_creative_assets`. Use the verified advertiser’s voice, logos, colors, and destination; see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`.
+Write text with `create_ad_copy` and select existing approved assets with `list_creative_assets`. Use the verified advertiser’s voice, logos, colors, and destination; see `../../context/brand-and-safety.md`.
## 3. Preflight — before anything goes live
diff --git a/skills/llm-visibility-audit/SKILL.md b/skills/llm-visibility-audit/SKILL.md
new file mode 100644
index 0000000..351fb73
--- /dev/null
+++ b/skills/llm-visibility-audit/SKILL.md
@@ -0,0 +1,22 @@
+---
+name: llm-visibility-audit
+description: Assess brand discoverability using supplied AI-search results, website content, and crawl evidence. Distinguish observed citations from untested recommendations.
+---
+
+# Audit AI search visibility
+
+Identify the verified brand, canonical domain, target customers, and buying questions. This directory connection has no AI-search visibility measurement tool. Use evidence the user supplies, or a separately available research tool when authorized. Record the exact query, engine, date, source URL, and observed citation for each measured result. Never invent a visibility score or treat a page change as proof of ranking.
+
+Review the website's clarity about the company, product, pricing, and supported claims. Where actual page or crawl evidence is available, check robots directives, accessible documentation, and structured data for accuracy. A crawler allowance or an index file does not establish that an AI service will ingest or cite the site. Respect the owner's crawl preferences; do not recommend removing restrictions without understanding their purpose.
+
+Separate the report into observations supported by evidence, missing measurements, and proposed fixes. Prioritize incorrect entity details, inaccessible relevant content, and unsupported claims. Make factual recommendations specific to the page or query and link the evidence. Re-run the same dated queries after changes before describing an improvement.
+
+```mermaid
+flowchart LR
+ A["Define brand and queries"] --> B["Collect dated evidence"]
+ B --> C["Separate findings from gaps"]
+ C --> D["Propose content fixes"]
+ D --> E["Repeat measurements"]
+ classDef step fill:#1e293b,stroke:#38bdf8,color:#fff;
+ class A,B,C,D,E step;
+```
diff --git a/skills/platform-amazon/SKILL.md b/skills/platform-amazon/SKILL.md
index d20e9ad..1048fe4 100644
--- a/skills/platform-amazon/SKILL.md
+++ b/skills/platform-amazon/SKILL.md
@@ -25,7 +25,7 @@ Two motions: Sponsored ads on the retail search results, and DSP for programmati
## Plan and ship
- Build with `create_campaign_plan` + `upsert_plan_entity`; `forecast_campaign` for projections. Run **campaign-preflight**: retail readiness, tracking, sane budgets.
-- Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without a clear go. Apply brand voice from `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`.
+- Ship only on explicit approval: `execute_campaign_plan`, then `enable_campaign`. Nothing that spends money goes live without a clear go. Apply brand voice from `../../context/brand-and-safety.md`.
- Confirm live with `list_campaigns(platform="amazon")` and report the real ID. Scale by ACOS with the **optimize** skill.
Porting from another platform? Use the **replicate** skill. Search intent maps to Sponsored Products keywords; audiences map to DSP.
diff --git a/skills/quickstart/SKILL.md b/skills/quickstart/SKILL.md
index a61cae7..8659bfd 100644
--- a/skills/quickstart/SKILL.md
+++ b/skills/quickstart/SKILL.md
@@ -10,6 +10,6 @@ description: Sign in through OAuth, confirm the workspace, and run a first repor
3. For connected GA4 accounts, use `ga4_get_properties`, then `ga4_get_report` with the selected property and requested period. Use the tool’s returned schema for parameters.
4. Offer a first task: a **report**, a **launch** plan, an **audience** review, or **ad-copy-generation**. Do not claim access to accounts or data that the tools did not return.
-The plugin connects to the hosted Synter MCP server over OAuth. Cursor uses https://mcp.synterai.com; Claude uses https://mcp.syntermedia.ai. Both require a client that supports remote MCP and browser OAuth.
+The plugin connects to the hosted Synter MCP server over OAuth. Cursor uses https://mcp.synterai.com; Claude uses https://mcp.synterai.com. Both require a client that supports remote MCP and browser OAuth.
-Reads do not require approval. Show the exact scope and obtain explicit approval before account mutations or spend changes. More help: **help** or https://syntermedia.ai/support.
+Reads do not require approval. Show the exact scope and obtain explicit approval before account mutations or spend changes. More help: **help** or https://synterai.com/contact.
diff --git a/skills/replicate/SKILL.md b/skills/replicate/SKILL.md
index 36c8a00..c132e07 100644
--- a/skills/replicate/SKILL.md
+++ b/skills/replicate/SKILL.md
@@ -39,7 +39,7 @@ Platforms are not identical. State the mapping and where it is lossy.
`create_campaign_plan` to draft the target structure, then `upsert_plan_entity` to add each campaign → ad group / ad set → ad, carrying over the mapped keywords, negatives, geo, schedule, and budget. `forecast_campaign` for reach/CPC/CPA on the new platform — the source platform's numbers do not transfer.
-Creative: reuse the source ad copy where the format allows; otherwise run the **ad-copy-generation** skills to regenerate for the target platform. Apply brand voice — see `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md`.
+Creative: reuse the source ad copy where the format allows; otherwise run the **ad-copy-generation** skills to regenerate for the target platform. Apply brand voice — see `../../context/brand-and-safety.md`.
## 4. Preflight — before anything goes live
diff --git a/skills/ugc-creative-brief/SKILL.md b/skills/ugc-creative-brief/SKILL.md
new file mode 100644
index 0000000..6fa6fb1
--- /dev/null
+++ b/skills/ugc-creative-brief/SKILL.md
@@ -0,0 +1,24 @@
+---
+name: ugc-creative-brief
+description: Write creator briefs with verified brand context, hooks, shot lists, claims, deliverables, and usage-rights requirements. Use when planning creator-style ads.
+---
+
+# Write a creator brief
+
+Confirm the advertiser, product, audience, platform, objective, offer, and approved claims. Read [brand and safety guidance](../../context/brand-and-safety.md). Use `list_connected_accounts` and `list_creative_assets` when connected workspace evidence is needed. Do not invent testimonials or portray an actor's script as a real customer's experience.
+
+Write one clear concept per video: opening line, problem or use case, product demonstration, supporting proof, and exact CTA. Include a shot list with duration, framing, visual action, spoken line, caption text, and asset references. Match deliverable sizes and lengths to current placement requirements verified through available official documentation; do not present a remembered specification as current evidence.
+
+Specify deliverable count, revision process, deadlines, raw-footage needs, and separately agreed paid usage rights. Ask for the actual budget or quote instead of inventing market rates. Distinguish a creator brief from a generated video. The directory connection does not generate or upload video; it can inspect existing assets and support approved campaign planning.
+
+Check the complete brief for brand match, substantiated claims, authorized music and likeness use, and a working verified destination. Do not contact creators, accept contracts, or publish campaigns without authorization. After production, inspect the actual asset before calling it ready.
+
+```mermaid
+flowchart LR
+ A["Verify brief inputs"] --> B["Write concept and shot list"]
+ B --> C["Check claims and rights"]
+ C --> D["Authorized production"]
+ D --> E["Inspect finished asset"]
+ classDef step fill:#1e293b,stroke:#38bdf8,color:#fff;
+ class A,B,C,D,E step;
+```
From f9273d678ff19e2f40a8b74889dcb89a4d081777 Mon Sep 17 00:00:00 2001
From: Joel Horwitz
Date: Wed, 21 Oct 2026 14:34:13 -0700
Subject: [PATCH 2/7] fix: package OpenAI OAuth settings and optional new draft
identity
---
README.md | 18 +++++++++++++---
scripts/build-packages.py | 38 +++++++++++++++++++++++++++-------
scripts/build-zip.sh | 2 +-
scripts/test_build_packages.py | 16 ++++++++++++++
4 files changed, 62 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index bd9e2da..8242c90 100644
--- a/README.md
+++ b/README.md
@@ -11,12 +11,24 @@ Plan advertising campaigns, review performance, write ad copy, and apply approve
### ChatGPT and Codex
The OpenAI package includes Synter skills and the hosted OAuth MCP connection.
-Configure its MCP connection with OAuth and dynamic client registration in the portal; discovery provides the endpoints. For the existing directory draft, upload `dist/synter-openai-plugin-1.2.1.zip`
-as a new version of **Synter Ads MCP**. Its package identity remains
+Its generated `mcp.json` declares OAuth with dynamic client registration under
+`mcpServers.synter.extensions.com.openai.auth`; discovery provides the endpoints.
+The portal displays these settings and does not provide an advanced OAuth editor.
+The existing-draft archive is `dist/synter-openai-plugin-1.2.1.zip`, for
+**Synter Ads MCP**. Its package identity remains
`app-6a90dc5bbc3c819199b4f1e44028fcbf`. Uploading creates a draft; directory
approval and publication are separate steps. This archive contains no lifecycle
hooks or local executables.
+The existing draft currently rejects replacement ZIPs with “Configure the existing
+MCP app before updating its plugin ZIP,” while its connection settings are
+unavailable. A valid archive does not resolve that portal state. Verify the saved
+MCP origin before updating: OpenAI does not allow an origin change between versions.
+`bash scripts/build-zip.sh --include-new-draft` also prepares
+`dist/synter-openai-new-draft-plugin-1.2.1.zip` with the canonical `synter` package
+name for a separately authorized new draft if the existing identity cannot be
+recovered. Building it does not upload, register, or publish anything.
+
### Claude Desktop and Cowork
Build the archives with `bash scripts/build-zip.sh`, then use Claude Desktop's
@@ -49,7 +61,7 @@ Both plugin configurations use remote HTTP MCP with browser OAuth. They contain
| Cursor | `.cursor-mcp.json`, referenced by `.cursor-plugin/plugin.json` | `https://mcp.synterai.com` |
| Claude | `.mcp.json`, discovered by the Claude plugin | `https://mcp.synterai.com` |
-The portable OpenAI package uses `mcp.json` with the Agent Plugins schema and `streamable-http` transport. Claude and Cursor use their own HTTP configuration files. All clients connect to the canonical endpoint. Existing connector submissions may require a separate endpoint update in their review portal. For another remote MCP client, use its browser OAuth flow with the appropriate hosted URL. If sign-in fails, reconnect through the client’s settings or contact [support](https://synterai.com/contact).
+The portable base `mcp.json` uses the Agent Plugins schema and `streamable-http` transport. The OpenAI build adds its documented server authentication extension; the published portable 1.0.0 schema currently rejects that extension, so validate the base against that schema and the generated vendor settings against the documented contract separately. Claude and Cursor use their own HTTP configuration files. All generated packages connect to the canonical endpoint. Existing OpenAI connections on another origin require a new plugin or support-assisted recovery, not a blind endpoint replacement. For another remote MCP client, use its browser OAuth flow with the appropriate hosted URL. If sign-in fails, reconnect through the client’s settings or contact [support](https://synterai.com/contact).
## What this plugin runs
diff --git a/scripts/build-packages.py b/scripts/build-packages.py
index b3df740..4c0620f 100644
--- a/scripts/build-packages.py
+++ b/scripts/build-packages.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
"""Build isolated Claude and OpenAI ZIPs from an explicit runtime allowlist."""
+import argparse
import hashlib
import json
from pathlib import Path
@@ -10,6 +11,18 @@
ROOT = Path(__file__).resolve().parents[1]
+# OpenAI's current documented vendor extension; the portable 1.0.0 MCP
+# schema does not yet accept server extensions. Keep the base independent.
+OPENAI_AUTH = {'type': 'oauth', 'client': {'mode': 'dcr'}}
+
+
+def validate_openai_mcp(value):
+ base = json.loads((ROOT / 'mcp.json').read_text())
+ expected = json.loads(json.dumps(base))
+ expected['mcpServers']['synter']['extensions'] = {'com.openai': {'auth': OPENAI_AUTH}}
+ if value != expected:
+ raise ValueError('OpenAI MCP configuration must match the portable base plus credential-free OAuth DCR')
+
def encoded(value):
return (json.dumps(value, indent=2, ensure_ascii=False) + '\n').encode()
@@ -40,15 +53,20 @@ def include(relative):
include('.claude-plugin/plugin.json')
include('.mcp.json')
include('scripts/session-context.sh')
- elif target == 'openai':
- include('mcp.json')
+ elif target in ['openai', 'openai-new-draft']:
+ mcp = json.loads((root / 'mcp.json').read_text())
+ mcp['mcpServers']['synter']['extensions'] = {'com.openai': {'auth': OPENAI_AUTH}}
+ validate_openai_mcp(mcp)
+ files['mcp.json'] = encoded(mcp)
portable = json.loads((root / 'plugin.json').read_text())
submission = json.loads((root / 'config/openai-submission.json').read_text())
if not re.fullmatch(r'app-[a-z0-9]+', submission['packageName']):
raise ValueError('Expected existing OpenAI package identity')
- portable['name'] = submission['packageName']
+ if target == 'openai':
+ portable['name'] = submission['packageName']
listing = portable['extensions']['com.openai']['interface']
- listing['displayName'] = submission['displayName']
+ if target == 'openai':
+ listing['displayName'] = submission['displayName']
listing['developerName'] = submission['developerName']
files['plugin.json'] = encoded(portable)
else:
@@ -66,7 +84,8 @@ def verify_archive(path, expected, target):
raise ValueError('Unsafe archive path')
if archive.read(name) != expected[name]:
raise ValueError('Archive content mismatch: ' + name)
- if target == 'openai':
+ if target in ['openai', 'openai-new-draft']:
+ validate_openai_mcp(json.loads(archive.read('mcp.json')))
manifest = json.loads(archive.read('plugin.json'))
extension = manifest['extensions']['com.openai']
if extension.get('hooks') or extension.get('apps') or any(n.startswith(('.claude-plugin/', 'hooks/', 'scripts/')) for n in names):
@@ -75,7 +94,7 @@ def verify_archive(path, expected, target):
raise ValueError('Archive CRC mismatch')
-def build(root=ROOT):
+def build(root=ROOT, include_new_draft=False):
subprocess.run(['node', str(root / 'scripts/validate-manifests.js')], cwd=root, check=True)
subprocess.run(['node', str(root / 'scripts/check-directory-tools.js')], cwd=root, check=True)
output = root / 'dist'
@@ -83,7 +102,7 @@ def build(root=ROOT):
source_sha = subprocess.check_output(['git', 'rev-parse', 'HEAD'], cwd=root, text=True).strip()
dirty = bool(subprocess.check_output(['git', 'status', '--porcelain'], cwd=root, text=True).strip())
results = []
- for target in ['claude', 'openai']:
+ for target in ['claude', 'openai'] + (['openai-new-draft'] if include_new_draft else []):
version, files = collect(root, target)
filename = f'synter-{target}-plugin-{version}.zip'
archive_path = output / filename
@@ -103,4 +122,7 @@ def build(root=ROOT):
return results
if __name__ == '__main__':
- print(json.dumps(build(), indent=2))
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--include-new-draft', action='store_true', help='Also prepare a canonical synter OpenAI archive; does not register or upload a plugin')
+ args = parser.parse_args()
+ print(json.dumps(build(include_new_draft=args.include_new_draft), indent=2))
diff --git a/scripts/build-zip.sh b/scripts/build-zip.sh
index e6e416d..cee15e6 100755
--- a/scripts/build-zip.sh
+++ b/scripts/build-zip.sh
@@ -2,4 +2,4 @@
# Build and byte-verify separate Claude and OpenAI plugin archives.
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
-exec python3 "$REPO_ROOT/scripts/build-packages.py"
+exec python3 "$REPO_ROOT/scripts/build-packages.py" "$@"
diff --git a/scripts/test_build_packages.py b/scripts/test_build_packages.py
index 8d8aefe..43d2f9c 100644
--- a/scripts/test_build_packages.py
+++ b/scripts/test_build_packages.py
@@ -1,4 +1,5 @@
import importlib.util
+import copy
import json
from pathlib import Path
import tempfile
@@ -28,6 +29,21 @@ def test_claude_identity_and_executable_present(self):
self.assertNotIn('plugin.json', files)
self.assertNotIn('mcp.json', files)
+ def test_openai_auth_and_optional_new_draft(self):
+ _, files = mod.collect(mod.ROOT, 'openai-new-draft')
+ manifest = json.loads(files['plugin.json'])
+ self.assertEqual(manifest['name'], 'synter')
+ self.assertEqual(manifest['extensions']['com.openai']['interface']['developerName'], 'JOEL SAMUEL HORWITZ')
+ mcp = json.loads(files['mcp.json'])
+ self.assertEqual(mcp['mcpServers']['synter']['extensions']['com.openai']['auth'], {'type': 'oauth', 'client': {'mode': 'dcr'}})
+ mod.validate_openai_mcp(mcp)
+ for key, value in [('clientSecret', 'not-a-real-secret'), ('type', 'none'), ('client', {'mode': 'provided', 'clientId': 'wrong'})]:
+ changed = copy.deepcopy(mcp)
+ changed['mcpServers']['synter']['extensions']['com.openai']['auth'][key] = value
+ with self.assertRaises(ValueError):
+ mod.validate_openai_mcp(changed)
+ self.assertNotIn('extensions', json.loads((mod.ROOT / 'mcp.json').read_text())['mcpServers']['synter'])
+
def test_archive_readback_rejects_missing_or_corrupted_content(self):
for files in ({'plugin.json': b'changed'}, {}):
with tempfile.TemporaryDirectory() as tmp:
From f1d687c500647c68c119f53c5402687a608223c5 Mon Sep 17 00:00:00 2001
From: Joel Horwitz
Date: Wed, 21 Oct 2026 15:10:49 -0700
Subject: [PATCH 3/7] fix: ground OpenAI reviewer tests in explicit sample
reads
---
plugin.json | 40 ++++++++++++++++++++--------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/plugin.json b/plugin.json
index 0ea4029..d5aaa42 100644
--- a/plugin.json
+++ b/plugin.json
@@ -56,34 +56,34 @@
"test_cases": {
"positive": [
{
- "description": "Discover connected accounts",
- "prompt": "Show the advertising accounts connected to my Synter workspace.",
- "tools_triggered": "get_connection_status, list_connected_accounts",
- "expected_behavior": "Report the selected workspace and returned account names and IDs. Do not fabricate missing connections."
+ "description": "Identify reviewer sandbox accounts",
+ "prompt": "List the accounts in this review workspace. Identify sandbox entries and do not treat an ACTIVE status as proof that a live ad provider is connected.",
+ "tools_triggered": "list_connected_accounts",
+ "expected_behavior": "Return only the review workspace's two Acme Outfitters (Sandbox) entries for Google and Meta. Explain that these sandbox identifiers are not live-provider credentials; do not call a real provider report with them."
},
{
- "description": "List campaigns",
- "prompt": "List campaigns for my connected Google Ads account. Do not change them.",
- "tools_triggered": "list_connected_accounts, list_campaigns",
- "expected_behavior": "Use an accessible account ID and return campaign state. Report a missing connection if no Google Ads account is available."
+ "description": "Show sample connection inventory",
+ "prompt": "Show Synter's built-in sample connection inventory using demo: true. Clearly label it as demonstration data.",
+ "tools_triggered": "get_connection_status",
+ "expected_behavior": "Call get_connection_status with demo:true. Return the three DEMO_READY sample entries for Google, Meta, and LinkedIn. Distinguish this fixed sample inventory from the sandbox accounts stored in the review workspace."
},
{
- "description": "Read recent performance",
- "prompt": "Show Google Ads performance for the last seven days in my selected account.",
- "tools_triggered": "list_connected_accounts, pull_google_ads_performance",
- "expected_behavior": "State the date range and returned metrics, including any missing data or provider error. Do not change campaigns."
+ "description": "List sample Google campaigns",
+ "prompt": "Using demo: true, list the built-in sample Google campaigns. Do not access or change live accounts.",
+ "tools_triggered": "list_campaigns",
+ "expected_behavior": "Call list_campaigns with platform:google and demo:true. Return the three demo_google_* campaigns, preserving their sample status and identifying the result as synthetic demonstration data."
},
{
- "description": "Inspect tracking health",
- "prompt": "Check conversion tracking health for my connected accounts.",
- "tools_triggered": "list_connected_accounts, get_pixel_health",
- "expected_behavior": "Report available tracking diagnostics and their scope without claiming conversions or revenue not returned by the tools."
+ "description": "Summarize fixed sample performance",
+ "prompt": "Using demo: true, show a sample Google Ads performance report. Label all values as synthetic demonstration data, not actual recent results.",
+ "tools_triggered": "pull_google_ads_performance",
+ "expected_behavior": "Call pull_google_ads_performance with demo:true. Return the fixed fixture summary: $2,468.10 sample spend, 9,030 sample clicks, and 424 sample conversions. Explain that date-range labels do not change the canned sample dataset; do not infer recent trends, actual revenue, or live account health."
},
{
- "description": "Prepare ad copy",
- "prompt": "Draft three Google search ad variations for my selected advertiser using its existing brand and destination. Do not publish.",
- "tools_triggered": "list_connected_accounts, create_ad_copy",
- "expected_behavior": "Use the verified advertiser context, ask for missing offer or destination details, and return draft copy without launching or changing campaigns."
+ "description": "Report unavailable tracking evidence",
+ "prompt": "Check stored pixel health for this review workspace. If there are no configured sites or events, say tracking is unverified and make no changes.",
+ "tools_triggered": "get_pixel_health",
+ "expected_behavior": "Call get_pixel_health. The current reviewer workspace returns sites:[] and attribution_model:null. State that tracking is unverified because there is no site/event evidence, not that tracking is healthy or failing."
}
],
"negative": [
From 943fd07bd632322ae2f2f8d1b5a2667714272241 Mon Sep 17 00:00:00 2001
From: Joel Horwitz
Date: Wed, 21 Oct 2026 15:47:54 -0700
Subject: [PATCH 4/7] Document live reviewer results and walkthrough
---
plugin.json | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/plugin.json b/plugin.json
index d5aaa42..5f165a0 100644
--- a/plugin.json
+++ b/plugin.json
@@ -77,7 +77,7 @@
"description": "Summarize fixed sample performance",
"prompt": "Using demo: true, show a sample Google Ads performance report. Label all values as synthetic demonstration data, not actual recent results.",
"tools_triggered": "pull_google_ads_performance",
- "expected_behavior": "Call pull_google_ads_performance with demo:true. Return the fixed fixture summary: $2,468.10 sample spend, 9,030 sample clicks, and 424 sample conversions. Explain that date-range labels do not change the canned sample dataset; do not infer recent trends, actual revenue, or live account health."
+ "expected_behavior": "Call pull_google_ads_performance with demo:true. Return the fixed fixture summary: 2,468.10 sample spend, 9,030 sample clicks, and 424 sample conversions. The fixture does not specify a currency; do not invent USD or another currency. Explain that date-range labels do not change the canned sample dataset; do not infer recent trends, actual revenue, or live account health."
},
{
"description": "Report unavailable tracking evidence",
@@ -103,7 +103,8 @@
"expected_behavior": "Do not attempt to bypass access controls or invent data. Explain that an authorized connection is required."
}
]
- }
+ },
+ "demo_recording_url": "https://drive.google.com/file/d/18cR7q80d0s1ZZsSVIJs3UtbHEaK_PBGf/view?usp=drivesdk"
}
}
}
From 9f14ea410b0670789e00f63c2f5815aaab2e4a4b Mon Sep 17 00:00:00 2001
From: Joel Horwitz
Date: Wed, 21 Oct 2026 15:59:48 -0700
Subject: [PATCH 5/7] build: add static Claude directory source bundle without
local hooks
---
.github/workflows/release.yml | 3 +
.github/workflows/validate.yml | 4 +-
config/claude-directory/README.md | 20 +
config/claude-directory/help.md | 14 +
.../.claude-plugin/plugin.json | 29 +
packages/claude-directory/.mcp.json | 9 +
packages/claude-directory/LICENSE | 21 +
packages/claude-directory/README.md | 20 +
.../agents/audience-builder.md | 20 +
.../agents/budget-optimizer.md | 22 +
.../agents/campaign-strategist.md | 19 +
.../agents/creative-director.md | 20 +
.../claude-directory/agents/media-buyer.md | 21 +
.../agents/performance-analyst.md | 13 +
packages/claude-directory/assets/logo.png | Bin 0 -> 106750 bytes
.../context/brand-and-safety.md | 33 +
.../claude-directory/output-styles/synter.md | 16 +
.../skills/ad-copy-generation/SKILL.md | 37 ++
.../skills/ad-policy-compliance/SKILL.md | 574 ++++++++++++++++++
.../skills/anomaly-detector/SKILL.md | 36 ++
.../skills/attribution/SKILL.md | 30 +
.../audience-expansion-strategy/SKILL.md | 60 ++
.../claude-directory/skills/audience/SKILL.md | 16 +
.../skills/bid-optimization/SKILL.md | 36 ++
.../skills/campaign-preflight/SKILL.md | 63 ++
.../campaign-structure-auditor/SKILL.md | 29 +
.../skills/competitor-analysis/SKILL.md | 31 +
.../claude-directory/skills/connect/SKILL.md | 14 +
.../skills/conversion-tracking/SKILL.md | 14 +
.../skills/creative-fatigue-detector/SKILL.md | 46 ++
.../skills/creative-testing/SKILL.md | 34 ++
.../claude-directory/skills/creative/SKILL.md | 22 +
.../skills/dayparting/SKILL.md | 34 ++
.../skills/executive-reporting/SKILL.md | 30 +
.../skills/first-party-data-strategy/SKILL.md | 61 ++
.../skills/geo-integrity-audit/SKILL.md | 16 +
.../skills/google-ads-quality-score/SKILL.md | 51 ++
.../skills/google-shopping-optimizer/SKILL.md | 58 ++
.../claude-directory/skills/help/SKILL.md | 14 +
.../skills/incrementality-testing/SKILL.md | 60 ++
.../skills/keyword-research/SKILL.md | 37 ++
.../skills/kill-scale-rules/SKILL.md | 35 ++
.../skills/landing-page-creation/SKILL.md | 25 +
.../claude-directory/skills/launch/SKILL.md | 54 ++
.../skills/linkedin-ads-targeting/SKILL.md | 60 ++
.../skills/llm-visibility-audit/SKILL.md | 22 +
.../skills/media-plan/SKILL.md | 36 ++
.../skills/meta-ads-diagnostics/SKILL.md | 59 ++
.../skills/mmm-budget-planner/SKILL.md | 39 ++
.../skills/negative-keywords/SKILL.md | 35 ++
.../claude-directory/skills/optimize/SKILL.md | 45 ++
.../skills/performance-max-optimizer/SKILL.md | 62 ++
.../skills/pixel-capi-auditor/SKILL.md | 58 ++
.../skills/platform-amazon/SKILL.md | 33 +
.../skills/platform-benchmarks/SKILL.md | 33 +
.../skills/platform-google/SKILL.md | 36 ++
.../skills/platform-linkedin/SKILL.md | 36 ++
.../skills/platform-meta/SKILL.md | 37 ++
.../skills/platform-microsoft/SKILL.md | 43 ++
.../skills/platform-openai/SKILL.md | 32 +
.../skills/platform-reddit/SKILL.md | 34 ++
.../skills/platform-tiktok/SKILL.md | 34 ++
.../skills/platform-x/SKILL.md | 34 ++
.../skills/pre-pause-analysis/SKILL.md | 35 ++
.../skills/quickstart/SKILL.md | 15 +
.../skills/replicate/SKILL.md | 66 ++
.../claude-directory/skills/report/SKILL.md | 39 ++
.../retargeting-sequence-designer/SKILL.md | 63 ++
.../skills/roas-calculator/SKILL.md | 36 ++
.../skills/seasonal-budget-planner/SKILL.md | 62 ++
.../skills/ugc-creative-brief/SKILL.md | 24 +
.../skills/utm-builder/SKILL.md | 33 +
.../skills/video-ad-scriptwriter/SKILL.md | 63 ++
.../tools/directory-tools.json | 36 ++
scripts/build-packages.py | 90 ++-
scripts/test_build_packages.py | 90 +++
76 files changed, 3216 insertions(+), 5 deletions(-)
create mode 100644 config/claude-directory/README.md
create mode 100644 config/claude-directory/help.md
create mode 100644 packages/claude-directory/.claude-plugin/plugin.json
create mode 100644 packages/claude-directory/.mcp.json
create mode 100644 packages/claude-directory/LICENSE
create mode 100644 packages/claude-directory/README.md
create mode 100644 packages/claude-directory/agents/audience-builder.md
create mode 100644 packages/claude-directory/agents/budget-optimizer.md
create mode 100644 packages/claude-directory/agents/campaign-strategist.md
create mode 100644 packages/claude-directory/agents/creative-director.md
create mode 100644 packages/claude-directory/agents/media-buyer.md
create mode 100644 packages/claude-directory/agents/performance-analyst.md
create mode 100644 packages/claude-directory/assets/logo.png
create mode 100644 packages/claude-directory/context/brand-and-safety.md
create mode 100644 packages/claude-directory/output-styles/synter.md
create mode 100644 packages/claude-directory/skills/ad-copy-generation/SKILL.md
create mode 100644 packages/claude-directory/skills/ad-policy-compliance/SKILL.md
create mode 100644 packages/claude-directory/skills/anomaly-detector/SKILL.md
create mode 100644 packages/claude-directory/skills/attribution/SKILL.md
create mode 100644 packages/claude-directory/skills/audience-expansion-strategy/SKILL.md
create mode 100644 packages/claude-directory/skills/audience/SKILL.md
create mode 100644 packages/claude-directory/skills/bid-optimization/SKILL.md
create mode 100644 packages/claude-directory/skills/campaign-preflight/SKILL.md
create mode 100644 packages/claude-directory/skills/campaign-structure-auditor/SKILL.md
create mode 100644 packages/claude-directory/skills/competitor-analysis/SKILL.md
create mode 100644 packages/claude-directory/skills/connect/SKILL.md
create mode 100644 packages/claude-directory/skills/conversion-tracking/SKILL.md
create mode 100644 packages/claude-directory/skills/creative-fatigue-detector/SKILL.md
create mode 100644 packages/claude-directory/skills/creative-testing/SKILL.md
create mode 100644 packages/claude-directory/skills/creative/SKILL.md
create mode 100644 packages/claude-directory/skills/dayparting/SKILL.md
create mode 100644 packages/claude-directory/skills/executive-reporting/SKILL.md
create mode 100644 packages/claude-directory/skills/first-party-data-strategy/SKILL.md
create mode 100644 packages/claude-directory/skills/geo-integrity-audit/SKILL.md
create mode 100644 packages/claude-directory/skills/google-ads-quality-score/SKILL.md
create mode 100644 packages/claude-directory/skills/google-shopping-optimizer/SKILL.md
create mode 100644 packages/claude-directory/skills/help/SKILL.md
create mode 100644 packages/claude-directory/skills/incrementality-testing/SKILL.md
create mode 100644 packages/claude-directory/skills/keyword-research/SKILL.md
create mode 100644 packages/claude-directory/skills/kill-scale-rules/SKILL.md
create mode 100644 packages/claude-directory/skills/landing-page-creation/SKILL.md
create mode 100644 packages/claude-directory/skills/launch/SKILL.md
create mode 100644 packages/claude-directory/skills/linkedin-ads-targeting/SKILL.md
create mode 100644 packages/claude-directory/skills/llm-visibility-audit/SKILL.md
create mode 100644 packages/claude-directory/skills/media-plan/SKILL.md
create mode 100644 packages/claude-directory/skills/meta-ads-diagnostics/SKILL.md
create mode 100644 packages/claude-directory/skills/mmm-budget-planner/SKILL.md
create mode 100644 packages/claude-directory/skills/negative-keywords/SKILL.md
create mode 100644 packages/claude-directory/skills/optimize/SKILL.md
create mode 100644 packages/claude-directory/skills/performance-max-optimizer/SKILL.md
create mode 100644 packages/claude-directory/skills/pixel-capi-auditor/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-amazon/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-benchmarks/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-google/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-linkedin/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-meta/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-microsoft/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-openai/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-reddit/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-tiktok/SKILL.md
create mode 100644 packages/claude-directory/skills/platform-x/SKILL.md
create mode 100644 packages/claude-directory/skills/pre-pause-analysis/SKILL.md
create mode 100644 packages/claude-directory/skills/quickstart/SKILL.md
create mode 100644 packages/claude-directory/skills/replicate/SKILL.md
create mode 100644 packages/claude-directory/skills/report/SKILL.md
create mode 100644 packages/claude-directory/skills/retargeting-sequence-designer/SKILL.md
create mode 100644 packages/claude-directory/skills/roas-calculator/SKILL.md
create mode 100644 packages/claude-directory/skills/seasonal-budget-planner/SKILL.md
create mode 100644 packages/claude-directory/skills/ugc-creative-brief/SKILL.md
create mode 100644 packages/claude-directory/skills/utm-builder/SKILL.md
create mode 100644 packages/claude-directory/skills/video-ad-scriptwriter/SKILL.md
create mode 100644 packages/claude-directory/tools/directory-tools.json
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c167916..87a45ab 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,6 +34,8 @@ jobs:
run: |
npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
npx -y @anthropic-ai/claude-code@2.1.263 plugin validate .claude-plugin/plugin.json --strict
+ python3 scripts/build-packages.py --check-claude-directory
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate packages/claude-directory --strict
- name: Verify release tag matches the package
env:
@@ -61,6 +63,7 @@ jobs:
with:
files: |
dist/synter-claude-plugin-${{ steps.version.outputs.version }}.zip
+ dist/synter-claude-directory-plugin-${{ steps.version.outputs.version }}.zip
dist/synter-openai-plugin-${{ steps.version.outputs.version }}.zip
dist/*.sha256
dist/package-manifest.json
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 8d08c7c..76f2137 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -23,12 +23,14 @@ jobs:
run: |
npx -y @anthropic-ai/claude-code@2.1.263 plugin validate . --strict
npx -y @anthropic-ai/claude-code@2.1.263 plugin validate .claude-plugin/plugin.json --strict
+ python3 scripts/build-packages.py --check-claude-directory
+ npx -y @anthropic-ai/claude-code@2.1.263 plugin validate packages/claude-directory --strict
- name: Test package validation
run: |
node --test scripts/*.test.js
python3 -m unittest discover -s scripts -p 'test_*.py' -v
- bash scripts/build-zip.sh
+ bash scripts/build-zip.sh --include-new-draft
- name: Validate extracted Claude package
run: |
diff --git a/config/claude-directory/README.md b/config/claude-directory/README.md
new file mode 100644
index 0000000..dfae0cd
--- /dev/null
+++ b/config/claude-directory/README.md
@@ -0,0 +1,20 @@
+# Synter
+
+Synter helps you review advertising performance, prepare campaign plans, write ad copy, and approve changes through its hosted MCP service. Start with the quickstart skill, sign in through the browser OAuth prompt, and verify the connected workspace and advertising account before using account data.
+
+This directory package includes 56 Markdown skills, six agent instructions, an output style, and static [brand and safety guidance](context/brand-and-safety.md). Its only service connection is the remote HTTPS MCP server declared in `.mcp.json`. It contains no local lifecycle hooks, executable scripts, or SDK runner. Tool availability follows the bundled [tool directory](tools/directory-tools.json) and the live server schema.
+
+Read-only account lookups and reports can run directly. Before any campaign creation, activation, pause, budget adjustment, or other spend-changing action, review the proposed account, scope, and amounts and give explicit approval. After execution, check the provider readback before treating a change as complete. Use the verified advertiser's brand and original assets; do not substitute Synter's branding for another advertiser.
+
+```mermaid
+flowchart LR
+ User["Your request and approval"] --> Claude["Claude with Synter instructions"]
+ Claude --> MCP["Synter HTTPS MCP over OAuth"]
+ MCP --> Provider["Your connected advertising accounts"]
+ Provider --> Readback["Results and verification"]
+ Readback --> Claude
+```
+
+Tool arguments and account results pass through Synter's hosted service and the connected advertising provider. Never paste passwords, API keys, or access tokens into chat. Sample data must be identified as sample data; missing account access or unavailable metrics must be reported plainly. Read [Synter's privacy policy](https://synterai.com/privacy) for data handling and [terms](https://synterai.com/terms) for service conditions.
+
+For help, use the help skill or [contact Synter](https://synterai.com/contact). This folder is generated from reviewed repository sources and is intended for the Claude directory submission path `packages/claude-directory`. Directory approval and publication are separate from local package validation. The source is distributed under the included MIT license.
diff --git a/config/claude-directory/help.md b/config/claude-directory/help.md
new file mode 100644
index 0000000..f4f76c6
--- /dev/null
+++ b/config/claude-directory/help.md
@@ -0,0 +1,14 @@
+---
+name: help
+description: Explain the bundled skills, supported MCP tools, OAuth setup, and support options.
+---
+
+# Synter help
+
+This plugin connects to Synter's hosted advertising service over OAuth. Use **quickstart** to sign in and **connect** to verify accounts.
+
+Use **report** and **attribution** to review performance, **media-plan** and **launch** to prepare approved campaign changes, **audience** to inspect targeting, and **ad-copy-generation** to write text for the verified advertiser. Read [brand and safety guidance](../../context/brand-and-safety.md) before writing copy or proposing spend.
+
+This directory package uses static instructions and the declared remote MCP connection. Account operations run through Synter MCP tools. Use only tools in the bundled [tool directory](../../tools/directory-tools.json) and the live server schema; report unavailable capabilities plainly. Confirm the workspace and account, obtain explicit approval for spend-changing actions, and verify provider readback before claiming completion.
+
+[Privacy](https://synterai.com/privacy) · [Support](https://synterai.com/contact)
diff --git a/packages/claude-directory/.claude-plugin/plugin.json b/packages/claude-directory/.claude-plugin/plugin.json
new file mode 100644
index 0000000..8004b46
--- /dev/null
+++ b/packages/claude-directory/.claude-plugin/plugin.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
+ "name": "synter",
+ "displayName": "Synter",
+ "description": "Plan and operate advertising campaigns through Synter’s hosted MCP server over OAuth. Review performance, write ad copy, and approve campaign changes before execution.",
+ "version": "1.2.1",
+ "license": "MIT",
+ "author": {
+ "name": "Synter",
+ "email": "joel@synterai.com",
+ "url": "https://synterai.com"
+ },
+ "homepage": "https://synterai.com",
+ "repository": "https://github.com/Synter-Media-AI/plugin",
+ "keywords": [
+ "ads",
+ "advertising",
+ "campaigns",
+ "audiences",
+ "media-buying",
+ "creative",
+ "roas",
+ "google-ads",
+ "meta-ads",
+ "linkedin-ads",
+ "mcp",
+ "synter"
+ ]
+}
diff --git a/packages/claude-directory/.mcp.json b/packages/claude-directory/.mcp.json
new file mode 100644
index 0000000..c3ae094
--- /dev/null
+++ b/packages/claude-directory/.mcp.json
@@ -0,0 +1,9 @@
+{
+ "mcpServers": {
+ "synter": {
+ "type": "http",
+ "url": "https://mcp.synterai.com",
+ "description": "Synter Advertising Platform MCP \u2014 cross-channel ad management for AI agents. Sign in through the browser OAuth prompt; never paste credentials into chat."
+ }
+ }
+}
diff --git a/packages/claude-directory/LICENSE b/packages/claude-directory/LICENSE
new file mode 100644
index 0000000..0b3cf68
--- /dev/null
+++ b/packages/claude-directory/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Synter (Syntermedia, Inc.)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/packages/claude-directory/README.md b/packages/claude-directory/README.md
new file mode 100644
index 0000000..dfae0cd
--- /dev/null
+++ b/packages/claude-directory/README.md
@@ -0,0 +1,20 @@
+# Synter
+
+Synter helps you review advertising performance, prepare campaign plans, write ad copy, and approve changes through its hosted MCP service. Start with the quickstart skill, sign in through the browser OAuth prompt, and verify the connected workspace and advertising account before using account data.
+
+This directory package includes 56 Markdown skills, six agent instructions, an output style, and static [brand and safety guidance](context/brand-and-safety.md). Its only service connection is the remote HTTPS MCP server declared in `.mcp.json`. It contains no local lifecycle hooks, executable scripts, or SDK runner. Tool availability follows the bundled [tool directory](tools/directory-tools.json) and the live server schema.
+
+Read-only account lookups and reports can run directly. Before any campaign creation, activation, pause, budget adjustment, or other spend-changing action, review the proposed account, scope, and amounts and give explicit approval. After execution, check the provider readback before treating a change as complete. Use the verified advertiser's brand and original assets; do not substitute Synter's branding for another advertiser.
+
+```mermaid
+flowchart LR
+ User["Your request and approval"] --> Claude["Claude with Synter instructions"]
+ Claude --> MCP["Synter HTTPS MCP over OAuth"]
+ MCP --> Provider["Your connected advertising accounts"]
+ Provider --> Readback["Results and verification"]
+ Readback --> Claude
+```
+
+Tool arguments and account results pass through Synter's hosted service and the connected advertising provider. Never paste passwords, API keys, or access tokens into chat. Sample data must be identified as sample data; missing account access or unavailable metrics must be reported plainly. Read [Synter's privacy policy](https://synterai.com/privacy) for data handling and [terms](https://synterai.com/terms) for service conditions.
+
+For help, use the help skill or [contact Synter](https://synterai.com/contact). This folder is generated from reviewed repository sources and is intended for the Claude directory submission path `packages/claude-directory`. Directory approval and publication are separate from local package validation. The source is distributed under the included MIT license.
diff --git a/packages/claude-directory/agents/audience-builder.md b/packages/claude-directory/agents/audience-builder.md
new file mode 100644
index 0000000..f743617
--- /dev/null
+++ b/packages/claude-directory/agents/audience-builder.md
@@ -0,0 +1,20 @@
+---
+name: audience-builder
+description: Reviews existing audiences and plans targeting — ABM criteria, lookalike seeds, first-party segments, and exclusions — for supported campaign-plan fields. Invoke when a user needs to define who to reach. List creation and sync happen in the platform UI.
+model: inherit
+effort: high
+---
+
+You turn 'who we want to reach' into a reviewed targeting brief using existing audience IDs.
+
+Approach:
+- Inspect existing audiences with `list_audiences`; define account criteria, seeds, exclusions, and consent requirements. Use `research_campaign_opportunity` for available research. Have the account owner construct or synchronize new lists in the platform UI.
+- Check `list_audiences` first — never rebuild a duplicate.
+- Ask the account owner for size and match-rate figures from the platform; check overlap and whether the audience clears each platform's minimum (LinkedIn company audiences and Reddit have real floors). Report the numbers honestly — a sub-floor segment won't deliver, and you say so.
+- First-party data: the advertiser hashes and uploads in the platform; tiny B2B work-email lists often miss the match floor — warn the user.
+- Confirm returned audience IDs, then hand the reviewed targeting brief to the media-buyer for supported campaign-plan fields and approval.
+
+Rules:
+- Confirm the org/account before using an audience in a campaign. Reads do not require approval; using an audience in a campaign is an action.
+- Respect match-rate floors — surface them, never hide a too-small segment.
+- Handle PII correctly: have the advertiser hash identifiers where the platform expects hashes; never leak one tenant's data into another.
diff --git a/packages/claude-directory/agents/budget-optimizer.md b/packages/claude-directory/agents/budget-optimizer.md
new file mode 100644
index 0000000..55da617
--- /dev/null
+++ b/packages/claude-directory/agents/budget-optimizer.md
@@ -0,0 +1,22 @@
+---
+name: budget-optimizer
+description: Finds wasted spend and winners across platforms, then reallocates budget by ROAS, pauses losers, and adjusts budgets toward winners. Invoke when a user wants to cut waste, improve ROAS/CPA, or rebalance spend. Recommends, then acts on approval.
+model: inherit
+effort: high
+---
+
+You are Synter's budget optimizer. You move money toward what works and away from what doesn't — on the user's approval.
+
+Method:
+- Pull recent performance across connected platforms using the supported performance tools (`pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, `pull_reddit_ads_performance`), reconcile with `get_spend_reconciliation`, and read true drivers with `get_attribution`.
+- Diagnose against minimum-data guardrails — never kill or scale on noise. State the threshold you used.
+ - Wasted spend: spend with no conversions past the threshold.
+ - Winners: low CPA / high ROAS with enough volume to scale.
+ - Anomalies: spend spikes, CTR drops, budget runaway.
+ - Fatigue: rising frequency + falling CTR → flag for creative rotation.
+- Use `optimize_budget` for allocation recommendations. Apply approved budget changes with `update_campaign_budget` and approved pauses with `pause_campaign`. For other changes, follow **launch** or hand off unsupported edits to the platform UI.
+
+Rules:
+- Recommend first: what to cut, what to scale, where freed budget goes, expected effect. Wait for explicit approval before any budget/pause/bid change.
+- After acting, re-pull and report the new state. Recommend alert thresholds for the user to configure in the platform UI.
+- Never report a change as done without confirming it landed.
diff --git a/packages/claude-directory/agents/campaign-strategist.md b/packages/claude-directory/agents/campaign-strategist.md
new file mode 100644
index 0000000..d75ac7e
--- /dev/null
+++ b/packages/claude-directory/agents/campaign-strategist.md
@@ -0,0 +1,19 @@
+---
+name: campaign-strategist
+description: Plans cross-platform advertising strategy — channel mix, budget allocation, audience approach, and measurement. Invoke when a user needs a media plan, a go-to-market for a launch, or a recommendation on where and how to spend before any campaign is built.
+model: opus
+effort: high
+---
+
+You are Synter's campaign strategist. You turn a business goal into a concrete, defensible media plan. You plan; you do not spend — building and launching is the media-buyer's job, and the user approves it.
+
+How you work:
+- Start from the objective and the conversion that defines success, not from a platform. Ask only for what you genuinely need: goal, target CPA/ROAS, budget, audience, geo, offer.
+- Recommend the channel mix with a real rationale grounded in how each platform performs for this objective and audience. Don't just echo the platform the user named.
+- Ground the proposal in `list_connected_accounts`, `research_campaign_opportunity`, `forecast_campaign`, and the supported platform performance tools. Separate historical observations from estimates and unavailable data.
+- Allocate budget across channels with projected outcomes per channel. Show the math. Start conservative; plan the scale path.
+- Always include measurement: which conversion, which tracking, how you'll know it worked.
+
+Ground platform choices in how each one actually works — the **platform-google**, **platform-microsoft**, **platform-meta**, **platform-linkedin**, and sibling playbook skills carry the structure, targeting, and cost realities per platform. For a full channel-mix plan, the **media-plan** skill is the build path.
+
+Output a tight plan: objective → audience → channel mix + budget split → projected CPA/ROAS → creative direction → measurement → first 2-week test. Lead with the recommendation, then the reasoning. Terse and certain. No hype, no banned marketing language.
diff --git a/packages/claude-directory/agents/creative-director.md b/packages/claude-directory/agents/creative-director.md
new file mode 100644
index 0000000..d7a63f8
--- /dev/null
+++ b/packages/claude-directory/agents/creative-director.md
@@ -0,0 +1,20 @@
+---
+name: creative-director
+description: Writes ad copy, reviews existing advertiser assets, and prepares test briefs. Use for text variants, asset selection, and campaign creative review.
+model: opus
+effort: high
+---
+
+You are Synter's creative director. You produce ad creative that is on-brand, accurate, and ready to ship.
+
+Method:
+- Get the brief: platform + format, objective, audience, the one message, and the real proof point. Read brand rules at `${CLAUDE_PLUGIN_ROOT}/context/brand-and-safety.md` before writing anything.
+- Write text with `create_ad_copy`; prepare variants that test a specific hypothesis. Request missing media from the advertiser.
+- Inspect existing assets with `list_creative_assets`. Use returned asset IDs in supported campaign-plan fields and follow the launch approval flow.
+
+Non-negotiables:
+- Use the verified advertiser’s brand kit, voice, approved claims, and original assets. Synter’s own brand rules apply only when Synter is the advertiser.
+- Accuracy over polish: never fabricate a price, competitor, statistic, or claim. No real number → say so; don't invent it.
+- Fill the format: RSAs ship with 11+ headlines and 4 descriptions (15/4 recommended, never the API minimum of 3/2), with the ad group's actual keywords echoed in at least 5 headlines — under-filled asset sets score "Poor" on Ad Strength and get throttled. No filler lines; rewrite to the character limit rather than truncate.
+- Policy: pre-check against the target platform's ad rules and flag anything restricted.
+- Writing text does not deploy it. Adding assets to a live campaign requires an approved scope and the **launch** workflow.
diff --git a/packages/claude-directory/agents/media-buyer.md b/packages/claude-directory/agents/media-buyer.md
new file mode 100644
index 0000000..ccaeca8
--- /dev/null
+++ b/packages/claude-directory/agents/media-buyer.md
@@ -0,0 +1,21 @@
+---
+name: media-buyer
+description: Builds and launches campaigns across platforms once a plan is approved — structure, targeting, budgets, and going live. Invoke to execute a launch, ship a campaign to one or more platforms, or enable a paused campaign. Always confirms before spending.
+model: inherit
+effort: high
+---
+
+You are Synter's media buyer. You take an approved plan and make it live, correctly and safely. You execute; the user approves every spend.
+
+Discipline:
+- Confirm the target before touching anything: `list_connected_accounts`. Use the exact account IDs the tools return — never invent or guess an ID. Never run one org's campaign through another org's key.
+- Check available conversion evidence with `ga4_get_conversions`, `ga4_get_report`, and `get_pixel_health`. Stop if required tracking verification is missing.
+- Run preflight every time: geo correct, exclusions applied, tracking firing, budget and bid caps sane (guard hard against fat-finger budgets — a daily budget 10–100x intended is the classic incident), creative policy- and brand-clean, final URL resolves and matches the ad.
+- Creative floor before launch: every RSA carries 11+ headlines and 4 descriptions (15/4 recommended — never the API minimum of 3/2), keywords echoed in the headlines. Google Ad Strength POOR or AVERAGE means fix the assets first, not launch and hope.
+- Build with `create_campaign_plan` and `upsert_plan_entity`. Read `get_campaign_plan`, run `run_launch_preflight`, show the final scope, obtain explicit user approval, then call `approve_campaign_plan` and `execute_campaign_plan`. Check `get_plan_execution` and `get_job_status` for returned jobs; use `enable_campaign` only within the approved scope.
+- When porting campaigns, read `list_campaigns` and `audit_account_structure`; request platform exports for missing geo, match-type, negative, and asset details. Follow **replicate** and the target platform playbook.
+
+Hard rules:
+- Recommend-then-execute: propose the plan (platform, budget, audience, creative), show it, and wait for an explicit yes before any create/enable/budget/launch/pause call. Reads do not require approval.
+- "Created" ≠ "live and spending." Verify with `list_campaigns` and report the real IDs before claiming it's running.
+- Start conservative on budget. Scaling is a later, separate execution step.
diff --git a/packages/claude-directory/agents/performance-analyst.md b/packages/claude-directory/agents/performance-analyst.md
new file mode 100644
index 0000000..c6ed649
--- /dev/null
+++ b/packages/claude-directory/agents/performance-analyst.md
@@ -0,0 +1,13 @@
+---
+name: performance-analyst
+description: Reviews account performance, audiences, attribution, and measurement gaps; recommends next actions without executing changes.
+model: opus
+effort: high
+disallowedTools: Write, Edit
+---
+
+Confirm the workspace and account IDs with `list_connected_accounts`. Pull comparable periods with `pull_google_ads_performance`, `pull_meta_ads_performance`, `pull_linkedin_ads_performance`, `pull_microsoft_ads_performance`, and `pull_reddit_ads_performance` where connected.
+
+Use `ga4_get_report`, `get_attribution`, and `get_spend_reconciliation` to explain differences between platform claims and analytics. Review `list_audiences` and `get_pixel_health` for available audience and tracking evidence. Request platform exports for missing breakdowns or change history. Never present a correlation as a proven cause.
+
+Lead with spend, conversions, CPA/ROAS, and the comparison period. Explain the supported causes, measurement limits, and practical next actions. Name the platform, account, and account ID for every recommendation. Hand proposed changes to the budget-optimizer or media-buyer for explicit user approval; do not execute them.
diff --git a/packages/claude-directory/assets/logo.png b/packages/claude-directory/assets/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..42ca40564c257ecd4a37052a2a56e4c772f0e305
GIT binary patch
literal 106750
zcmc$G`9G9x8~32J&?Z@vLQx}fXDgv?QdAns2xElF9>Rz*Mt64+A!V9MqS45{jGY)Q
zmfP6(F}Ao7X2{Z5hMD)g+|Tp8?|<-`j}Jdw=W?CraURF__#WSLp5C}_vSYj4b{Gt{
z7yDl)O6?JV|6F)Kl?*xo;^`DgFgRHV{TGMY~JBd9~GTanb`pY_*9X<
z^^?Zpw?396`5i`HZaX)z%D3;aH)p4ITy0|iW>2hq#^myD5u5F&gJ+6nJO-8mqMw0z
z{D1mMedggDePOXNAf|IrzFUr!rRHs+_;?Q;N}`O=+=k{)ZoCds{v+Z8}1xR04cyE^au
z04+I8#wMq^a?~scY0su%D%da1$s)MQs8Da@(xlQxCoq{!TRaBKi^jdJ;D(MClfLlN
z?#u!-cPE*^#1i2Ra_{cY1DRR~?y3_0Yoz^NvSD#r!EdF2nZUHuf!@49VQ7?J-r?!I
zF#S5r(aMh{$(RpFcqP3A%Xeu)Efh<0GX2+Tu*PZ)CgtozxG#EV8cAJ3h{QJfOBQOI
z?0vI=)jc(18^1G^ao^mxh)#B~MlqVBsw=uBjRZm_Z)kTUb62*RiGMY~P#%DK3SFjtn
z(yayfQuY%i9tv62N)kK}>sc)*ViS)=8>MneH`+FyAAO7O+1!V%bTvk0E+m@{Y#Gvz
zyi}4-vj1VK2)D$1Vqw&FP5Ut25XEXZr>k_6CPG7kc}77Svd=@o&G|Y@Yctf*&kIqV
zWuG#6d6ayYYsY4%X{bT{xAt7*kD=>6R56zQrdMZ4W)k#77Cl_02d_;!
z4p-+q4HoN|Ma~+n)9R%psX@Nt$(+|48&Ies^*FyZ_T+lb=Z^cMD>-XIWI}lET0ubX
zN?Y8azCW%XGj>{C&(`5Cn{scU%4dZ+_w_>mf(AZOeJt8b!sUBZyIC-2jc+)@$Y)eZ
zV&W2V*S$3F%EIXe)qG?;af)W%MSDc9sGChAWvOvWZ&nOta;zbr2FtpHNX&LhAR_JU
z;?3V1+2P#Z&1ur2>~IQVznJ8(i0{!1{!{}!fN6d&IQCPYUTAVXH%*(kH`H}wd>Ci#
zm3vZA)OfaSdI5j2jkiinh7;(VnqyA2xnHozMy=DQ)scg^mEH)A-M-q%z{vYz?m$he
zw_AT~UO%E!jxJn)HzZDeBMq`?PLcDn>eb=!ZEbM52NwR@)o7@JGrHyG(Ynv0NP5HJ
z8_uBMZ1!S=jaw&n_L!*=p_E;K;5v~Lhq!;nYjyNEWcXE|%IAF}8Rv(1YI77jau7ia
z#ZJIE!(e&B$C&+;HLeox38OjD=i`50ED~tN8jxpcratrER*>YBf^NWvvCrq;fQm$ren=T`eN61;iyJlSuzNLlS6ES(YaQ%F$Y_grI>%Iz9T5K@%Om%=
zG)*3A1G{_u-QPJR#jZu~l9GM5kJ7xQnM^Ou{j!gIci63LCnquuENJy4Zf@6rSN>_+
zSm49yfl#~GLSPGD-weHuJl%?|+sD+5Vt*b)vAU&WpD~F$M%1q49;sHe@}NsnYpSQn~pV8>i{H3tKkEK9sR;@`bLyFjVN8VDlW^R_NtH
zd{{5T_GtCc3@U?T*fg)s0a77CU4mYPf<+J(0U3?;w
z>-hYOPN|!CFECMRsxo3Q=zMXPMZk=7fY5RqWCGQkh_CLXOC^B;NwW>S3QtPU)a)6l
zVfAX>QBgbGZ6x0(pfHb?UJF{eGKx!Q)z+Yj#VxN9O_%I^Fr
zb)%DT`rlryn@h(Bb!^tJr7isWrn4$ZuGFsjC^hyHedz<;$h!9rD}A9~cru|KoO#oh
zXDRAYi)&4>?e^2ZzCr7DViEEw=*c+bl}f8%e0Je??2zwRfgR2;%3Nx=;IJsg455+}
zIsXT0)j_4;?MJKf*;c{E)P^0JY)dSbZWpybI1UbZD2HZT_l8IwXysC3zUWv#-Bi5~
zn{jLG=KNHrO^`2jW{_0AYn%)jii28oW`Wfkn`IUOSQ$EquEcv_c_VtWYL5fA?kwB>
zX`(tJ=(EM(DV)&FsVqeU`;T#_yab{f>v8G3quMd5G!>2JnxalOlIX0(Ti(RE+F`l=
z6`9wju(huVQV+LCj>UNRf1*y^%fFgf^x7Sa2@`+3Jf$c`u~`|YTxcKCqz&z3(yc=Z
zmJj?T6EOa^O;fe(xX{K
zR+N@#Pclcw6({n~()EV%;&n?X`iB{)ORa;cV%mP{Vl%dsR~EYcc2WJkJ*B7$=H#>E
zGI#_WjYLU8Gcc#3_B6)eZD*5Gwn0wFD@G1>u4x3{>lCK2@s#L!>cEUv`r0$f8GGj;
zHQG=GTbPWtaqcOpTz2%?(HA2mvGLs9Y~O(H)8LSowG-gX3sLQiNVY3Ek;Qe4`l9ob
zE|ZreJ@z=?Z2UxkV&2)rXv*PXa2AR$VDDkewCH|xhNi(rt
zn#D49kN8nS6>0UMvPD{v?9rlmTL66FO+z35H{v%EPQAhVXagpYX>5lJatltv^zb((
zTOyO^k#abdq2b5Gcrf$Zj;B~GeYvUwO>{MR`6l&>CoT~?~Wa9|dAGeqYMgD5RZV4*j2ny4dXK7}Am>bZL
zM@1<*sc1=^lXfeg5J9((<6ma-I$hJ5TyJRes-MFf5==6U$%IG*h_9Dx528iL>tC=h
zbMD0yzZENF3>@R+jh9~1-v|i~<8d%o;)i;fMvvVHAvDhJxIw8Ejnek%CM~@D^wQlN
z$eo@Ty$ma)-*6Tm*ZIjwJN1oJWe*QRhB|d_O6v;RZ3&GUCTk*`y#83p=|=8Dw578^
zowk5p$71)+8hbZmqZ?()LFRc*{+;anCij3Oc7t~S6NSdP@uE^6=1LIVygimr
zHBs_@&rMK5@)m*@53|LN+m?!O2#iV-yP@qp;M-IQw^W2iyrr@Ic@2t)8ccsd
zwc!>k8T0`UF~84C^MxqBm;P4QcO^l?c8d1YNCp#u$2b$cp#FYxgqQEfDcHr^TXYh-
z|2n-ZRt5I&3*6GE(zTK!T(w*=R**t80Pq*Lp3bE8^Uu582-m~((ML_gr2>|6NP*q}
z%!dtjFAfzE2%S-?{$=qUsVdU15?6xRNX<^kR(v5SLDtR|X2WE(Fn#q6SMfljERHd-cyI|ZUYa`
zJltUtw_cCkQiJ*(ce}ZZV4b}kuVFncYUinvlJ;W${uE1{>Yx4Aw{D-G
zh%XNJq$+ejlM{M5=0qeyLm})U6fnZN
z)V4?JZ0=_>n{ZwRAJvHYZWG;n$4^7yjGRc1F`9RmshK9jsI}ctQK2Yz!Zy|kBV&10sGNbXJSB4(J3CRe;(;IWs>G6uvtOo
zEH>Iq7R9OiGSdk@g1Z9|iF-8+KGlcX#nUZPV_tu3J50;&tLlLkY}Hya*$=QY%r=dR
zWRwrvrmrabmm^!T(sW5P_Hih6z5AWfNJPy|DlWQFR@TvX$E$Qda;nwYzBa5tzT!xq
z{neJ*A2j)qpI6qb(6|7*y3+T)XjWCrM&C3?{L+_vhX~WfjT|^(I|u$5#&en7&pX;S
zl4B(o$n=x;;MaN$v6#tM;;l2Viub9rQe#OK>@)7d{XhOXE4lg1!5E?ca4L&?qZ^+(
z67wAb#(K)wDb&f1&{Rf_D{p$-Qzn1Ck3_#6FI_RuM--bBI@_N3&RRp&I&zA9_eV!q
z;qra5u_hONtJTDNxs{30k2Z3VFx%9)Q{jyFySHqX!?^54~cre85b
zdulTvT~o%rGA4*LT^gOrLi7$)UFa&qOJ-$u5h54|SY{U$MjKp;2;MHE2RDp+Sn7(h
zA7z9NSD2!8ud#(sU-+h`tFi8?Z)y83^hzbtp{=Vd&>tP~HOjI`6==tyoOqs8dB-LAxbuz%GEmvxib-
zBbk}0UHtc=??-O059Ik@;lM=C?PhhQgpGVWEq|PtjjuUoe8bt;9sglqVq2z}9k--+
z2f(GV!chpfj^f$l&^%>}0X-9w+qHM<9`dNSyy0{ddn=0X#fu{I*r=O!`4HE@jW=|MW7rzUr&}G)%JY$l&dx#>z42>bE9BrPM0cSSt*%w7bkQv}0k$p22gbcurMdO7}Z
zj(MDdExZzM!ToxL*$>iyJr%^hsw8NitO{H$rE9)KvI&*h9Amd&CX4E&X}#+E9QIR@
zbb8ukO_w9g2?FT{>h;{`;?a-BCjMr53kf?FaUn0+Q3}Rxb-vLZ`=Fj={E^toqjz2E
z%Gj$)6-G7#I-Hq4`@F@WgF>z0+JCsbIUNY{@&RmABwNp`?uv1YU@hlU=xEQ9gpfe`
zF+63=kyWw>^izGz%8jlAP^5D{Amp6=K-S%9dVutmWuLs*7IB_9@!O`6P(j}RMy{w`
z+Jic06I7yad@YP?ZfAn59oG-LR?gl;PP`p2uyF3-p1b;cAYyuyw#)q3OK6fXQHa4y
zG)-ZlXp&;LQK#EUbotGlefG~6+e&+}syaQjJAJ5n{rbn4(w-8$ft>UMR&x*)MWDTb{#_$}IRT!7PZHuZWu0i(+F4&u>mu
zeEAnHs%7w5*0a-G49otn#WvAj?yHXB6K~^3t@I
z@{n&j_jW>c0!Tp#k@L*}A~z}ZRLSWmY`>B_xh>4N4B~*p-2*WHn6_k*4(zG0?fZYa
zTRyyKLtf8PQ@qU>yy=X;ddx7#V&WkQXZ^A|A+bo~h_mg!Xrr1gPB|N0>7?Mlb=kaM
zkWwFd8?ZbLihk6ROTG;Kk#_Dngj@5rKZljpD8Nv9`}iC$z-ri~8R|Y;_y-D|Bc7)o
z>hYi?t+*u(msD0H%S@ffHJ>#`o~k1mnY16r=Mh!Xx_6;ly&QU@z)tFV@m7&}p@T
zj
z$Z4P9hj44Z84<6@h?V1jQS%ytyb&z$hGkI|?9I5H`2IjSekZuC45%8rZtj{N31+^4KP9YOTfM<
z#Rx+b&|ypZWBr{Dv(M0qAkc9h2mY^}YgI_!m${k(4&qW$5g*XwZ9)f|&k?JvLIW0`
zd-Fo?{FdJO?0l?70!Sfs5C<0ms87S?pDgRmC8}+*c3ae9be5TQB9lcSF7pHfEHibp
zx#xN@$o*sOxQ}hz`mrsUI>dtFS!t_WKt$mr@z4GYM7TYL6MC>Nl4Eq>m_A&Cy|aoZ
zYxhuji`$~_>3wKkC#M%kX>9{tHcF_#?z!c^6j*~Kwm}TdeQWAA`0-K+guYlyVwtEy
zTox$EtZ7g*E_MYk>
z!BKsmUKlO{{JuL%;9G^;c?Qt%>h8c#eD7k?ES63o_ZKjQy-_=Sg@@+{H6jWk*%wj#
zvnsVvk*Juk5#;+LK|15nvDz}T*Y*MYb_Pj$srb~c$&n*2N`wT2S`l
z5Xm!EZKrj-uEJ^<6q;ABg#7llNu-u^)0`5NTvkwsm61aNifoi%Gh*`a%Z3&;xq-VD
ziP3#k7+2|SVYXLcc4JWd_;}alA|G%eW2Uhx_E5YqlE`L*+Lk2qljDzR8pF}Uukf?{
z$foKQWWT3H&=3R`lqHZmjQSFb2=rDBu-41?r>`PVdI3W0q$2irzz&h-o4>=soLWun
z2Euh*0y!RS(^cy=fxMK!8KPUZv!u>9f3r>k;Kl#cKW6DX1gBx7m`TBiIh)vlP?;k3
zL=%#-c$EUB=0BBHV3%PIHOD&-$Xl7lW@4R++DqXIlcqy~W5%G?G($eOksAH9{P#Hp
zkn~6&^OAdf5*?&Uu1vime6_2|l`uB|&KT$pOsQ6fRyzkOIj$YI;KmsiOVJtz_g=@s
zE>6>xDpHTSzG}ID+b~^Q4imzv!u-UQA#jeC2Fe(;b5A+@$&dBpOierP4@#j&
zqZWy4>z5JwJ-BsV8Br)%MsiRo4=t`z92V(U|`U=az@qO2?2T~
z0I_luvC!?sd;fu0b3IId=dfwGF6ZQedCKEdI7aI`o$6=DWu_8R4RgDHj;6dmLO%?8
zcX)EEk8{8b@&hhph0bX|L8ZNX?j62+(B0#_tH@w;WGE6;7?E>^8G^kZOLctrA9NR%
z9J?M@nQmn>(qO!QM)&Q?dEj#EjWF@j-KM!34F;&U^lfp_$}}dMCjNHvxR<@iPknGZ
zB)gD~*tc-V9QJOwVK%#y0M-7~yVs&q)-n6Z2haL?Y*3zw_#Qb{BPlQ
zekO0XutcDCnN?;*0-TtfzU{d{2J;OBwhQm=44O$xxugTpmNmgnXQY51M?Swe*;7
z={8+}2PFNR@+>=Bk!5mqBqqXr397|j>*~E+QQAK3_~{ywKlxm`*BP=wSgWW6Okf}X
zZl*yoJ$_t)TG{=ypug?V!;^y2leKt`j+5Tm&P@OUNRQ?23XHn;?}*Qya&1YccllFI
zx@j&IY=z3~S)v!-x3xPBYaGZt)V4kD#`me=DI@hr;cipRA)&JyB@Ty6-$&?H!8XX?
z+mdS2w~h+lFf`oohpr359z||~89=iMlhsRa3vX%K?
zEdZ0{yZ5-NrgxwD_;jDZu(96?Y2i|KS^jbWQx%Q-lM$-$ci15_*u{z_YEB`MTIf+|
zdgkiDmS>p-QgP4tn4z$#(W1w$56SmmX{KU|J(j_hVglGIxf-BiTyi^}n&Ih^1&(x-8vT3Bmx@o5fp`w(DaE2Pa21|WK_(mdjNpA7vm7M=~mq~9m1;~)booZ0U
zbZ-<}@0(RfR(+_wvq`6^0L^%hjz56*@GST@<|vCN^#(~~3&ELos6Ga1=kbOi+*lM#
zko_D?3dBunFIN%6cbXegUrqUK?Rm0zI$T}ICq(6(d$f^})Vi;>BSe`Jh$r3wA9|^f
z9yPUu+)P)a*OQFKX<~ingL0E{WvW}m4vATv`PuV96S_uJEc9;!wNXg
zYg@wF>f%!edQst%TLGgVMK4(*%_#vedl+8{H>7cRBI=mkd1`^~)EWerp~Q13%vPs)
z^zjv%etC1_Z6?=W(#kGE#fdy&N}l!25=~Qs?uH7TTeBS!D8AclRKgRdUSl-Z1I52r
z$j>AV+8=F70JTx!ZR^etYr8X@&F?eeXx+AVQ6-k)Zh9~X{lJEIMr>cYrga>DCr+XN
zrl7^xpIL^@SQWIYAK~{eT9xdoV1pxLM*YkItn8tOk3(r7QOp5=D0}8*lzNxxC?e<$
zqZ#nl7yk`|PWiuwuq^>&yB4MRbNl8TQB_~DdWGvRjQzfaTE>4?n?=X##d>u^;*&Oh
zE(gC!eMsU&XbC8Ff-3n-%1l9IJ01W`jv(glJ)ez;X?+DM_C|V|qjKzU*IAmsk82!h
zewHf$km7b~GkU8^qtr{eft>dltT_59PdXDsJhvoMZtFJ?;8hT{UAHsAu7fvSJPhsV
ziHaQ_!pZf`e$0|c&p0?e2OJ)&(3}6=(zBQi2Uf7LrN$|T9gs$4YQ#3EbiP1Ib7L(*
zQ~GJP5$kdC-J8yKs<}Z@EX#QfDLWu^6olGc{pTW7!Wg0B5GbM@G8MJs&@}f^d!(Y{
zpnZbfy9sK3fX_oZ=N_X8{FoDHoK4)iUxbu++t{!+nUIm5{NCOIZg^ZmxxIcrWphD|
zW7Ckn1S$>+3`;L=tBPBmkKI^MAGqCQoCZE9P66p`tVsh&+~YY3!;4~1%mBCVcTc}B
zE!qnmuY3G$iN?c6OlOc{8N7hy?@d-SEPqkifHeV;wU3|p@2qUBoXyo5Mm2cnSei4(
zHDo|gHSccV9RLWMRi#>;vAY5V>53)+y=`=cKC#~f7L$qf1`4MUh~!sUka8tgMCYYI
zKKR?SO~53@Q-558^%fy{$!dzSoA*R{#$KV;-I_X+*S^!En1BJ#g>^rN9TW=tqWyVW
zSO_{U5#?-ruUzP3?Dv`XY0*%XK6#FYHS9hO`ukbn2)-yHianA@{FP##?tHuBsCBPR~iL`E`=zl7bn
z;pVv)_RPI
z#5{SqT?AxYrDm0AIUvfo6UEYFDu9*^hqed&h`+H}(98VPEIbig%2q|-iWz(>XMsB&
z`Jb*foO$*0^sb=i410U$q^DL7yfnYNxL;%y3=RP|lQ?0TgroKV!n&e(cH4=)c9T`t
z4ppb$gME@7qf0W4(YUTK0ax%3%?bE$P-40c6imV#+FL1edzF6d;j
zjedFtdXK_G*I{&P4?8QQ;fT*(v`1b0n))|EEm?5I2h)Po+wM19vtY`%+
zjiCWb&eF`M`3s+{LIXkaP&4Iu?H{n04#_*bg%2i;(MG1mO&9czzAd?@9sv=#8!t%V
zO#A^Vpr-ij0w5%Uzi>dT4CJh>3kf)w2^!p~3KXCdE+(&gQy29&LjEI!vY+GhtOTg~
zfGP@5J|WlXYD9eLb^&uX*d-~clIW6ENyqln<(-c$GwER{I{5{LqerKdS-r^yPpoQ>}@
zy(zzw7uqJ77CqMgI3({(xa|B~+;Wloy2@urpYGSsa_?MA=BxTrH
zl`SSEnl!!N&EGM=(oHHFl`2B3Cq!c)yKy)HrMWiHN$YsqO`QBSxF@2{s_#w{IEk&g
zC>KWB6HNTzC{44BtwK&T{8@7j!tF2z2dG(wl7UX&I}mQ#R&!r8p)+QrZt}NWx^$$A
zsTLz7$vi+}|JF$G8JjI<=Xwc3HyXkMt9PryG-kcLLR|_CKp>2&c}w>#JR8-#b<^G_
z=#zT>GY~Yn5^dN|TFS(lK(d_wHC;YTRND)KieHW_B5>nd4-6MaJ-$SEcIUOK9k;-y
z_+X4NRJXvLmu#{PwDX%ag|wv(R>~0kEj9c`g4MeqngE*W#cJZHF4uvsW3!xX>}6mq
z)mfJy)Zl}E0vB=sdBhtHUJRV5e)^DPw4n!JaFs1UPbV_tfRlsuzrSM&i%-2!f2;EJ
zA!$iwohoT*ENwS<;4T$0)xWScU;J=UQ6?1VlVXCm}-co`fIw)cR
zog9!#`HEAZ(NF%TM0djMmK@<3W2X;uo0n#T3y`&>K=J`|SKa|w!EvEs(+Ys%%jR2x
zCq{D@$3L&_5IN)WFAOMwe!#eGauPk
zipqE0&iX8LN4Vej+Y6W)&BJL6GH$&eJ9Nx6yn;Q}cFF$@aF-WA>z{b=fDcpA&dU98
z!cCVHz~tHh*z~;{J$zmE)uywB`u=0nBl>Zmn2wVi11>!X#EQVL5X>Z~R^9xMXoI~C
zaQ?{oyB;1qO=jlY*!sKPRg5!U3do1RB*i0vF?w4Bl~DX0a(556yd7c<9}#(bhwexH
z<~}w+-oOfHjct)zcE7CCLu|u?guhJ#KL=(W7-j
za$^I2;-hqkqrEg6z_OjHd)uN$TWAO)W;<!mn#TGu
z4eYp&iit}quvIgEGsyqje{43~vSRoT&`9a=ua|?xv?&1Ty51pHMtaD5`y25F&uri@qo)9JRdse
z&eR*3eMTp{LV_AogRC-0(AvG$1mLO(_ra}apd10Ri?-%}N28nArHtE*Em@gX
z5OD_NlM>-m=+(BP2m)`_JZ$$b|ic$FZmgT?Q+??>_XEc@;6U;YQr7HVR?=tpMyXG4p?)BJ=607eIcb}S&+qO2A10w
zs?X&`cu4kSydoTV0n3~+%4+msGSF`12R}{DeKlB>e{pI2yV(VBrvMo^Yjd>NzSeB|
zs78fj-s~MC2GGed+pV8poc;!AVJNOfpW0?^_(V)rU%bKRP`?EqR}yH0DNl^gUJcLgo9^Ls%MsQy_;KS
z=k`@3AEO2pwVy}vyTpxpMr?L07EGk(YyDozp-rP0*Po$7K>5Ve0H`h)Pc@6%&TH6j
z0<|6Z>5=Jk-XEkiCCDY~yPXPGg?+CSJU&58M(G4i82mxuJ!0^CNJi-R7TH&LIRJ?X
zAFP5wiOTA#MRjAuU$#KIsKImfMy^Hm&KuU7#JoMFTH_lAvR3C?_ANCH^50Lbn>xjo
zrL;UupnLR!oQM_0UU*ccKzx3Q{f|1h#Lzx%H4_?`WCu4?I(^=E(@vPx7Emr1zISb|
zw2=6Yj%h>2OG`ssLT)5GqgYY*nXcmEDf?F~K1_1d{8!S`l!V%NpeX{7A{HcE-|m|O
zkiv;r_p%Cj{OqmV_bnMR8U`vazFs^2I>eXN
z6O&KrIru^c=NP9@`C=&~|^3Vz~{=n_!#3e8@>kyrk5KcZuZ^%FUAkARiR5gP@W5~o4>w4(f7+kvNIK+OXWqSJdq#PbaAUWhXV
zsWI}lVKEY1W0^*R8pBRn*zNJR(%+WW5)L#L__ta85AS6|idXwIL==q+|c)
z0zVdCUbY~3qWVhP+!qoi&~{ErC)_T@plj-kD^!3i+Y1X;%2c$KPeI||wcx1Af|G1D
z6#uTbfxx)${j#jxqdQL@_O{!=32meyaIfcB^vwR_Wg|-s|7>pj406ElU(=5Yzq7Y+
z76f(#{zZ~UaHH+bY?`N*z7XEmn2a5WYCqf8cXL0N-_FH>w16Ooz}^lX5752gbnK+2Z~W3VgCV9ye{U7h)HaYT@9+nUTEI|2|E$TH=dgL8o~mJx7I=xD@xQm5n87v`=n1&vvrXK;#~-~l
zEasGLsXozB{|>4YQ=3;IaB1q4(C>fs)-tA5a{9-)dYyx!cP+)W5&8?Nk?n9OLKYr5
z_O@gO@DD|XzhR|a=)hcmYKxA^gT5pa8L;#Yw+vr8xtQBq!z&$DmN+$*f3GyJ`l?5q
z7J^yvxZl!l_AUNXWH=h~YY|+K8^U5DxrS$854M3}Y{B(}%9Axn2llO^@`_sk#*$_j
zo!b``C174HZ-8EWDoO|yqg;T&jqU3H%HxVTej0?XIrMoq6;CL^MA$b1W)9fK0r@8y
z2!y$F;8GBMl6KOL8+c&*XPMn11;<5!l#k0(GRUtt+GiW(K}7;k>D6KgyBD+REX(i6
zBab|rq`##RaV=^M6>B`4cL~P~PObC3x~tl~=yD>yoE>z{SHhr%r15z&kI0qUBdI)6
z6DIxWwv-4G@wsQ)@QceMza@gVd`F3aj52|%`l2=XRZfofWqN1ln*`KYgwGAUXY`s0M
zHrUH7Tx^u~YPnsV99d9x4fCteOj3}ERXj#(F`>mI270xS!G1IrbB;sGu-72bIrGRP
zAn7X09+r6#+^(YwjGtef}kWUwqL9$3Ijo$E`XsIOPKnx?QG4UWC>
z7Rq{_c0_JMETN#%jZ9gqwXbQz%qRaicR_r&NQs09eM(_huoJQ`U=^rA{6a_(xIaHX
zhv{L~G(l-LVE{MqNkhPUV?_B)hFA7rG0`Wh8|xbDzQeRmaAZTS##J629N2~G)9S-@
z0D!#xHqglUN6*}hw;wT81a&l}f?k&U_is-;&O)OViVXibE_Tu0iXnAX5wXH)LNVIB
zMlv^9YW-528#9&?G>r`IwB|1r^n1Jcc1ZUF#-n`ZgreV$2qDMhAW>=R%SyK&`1+6Y
zRV4NmaPPFS1a=Q4x!X6O0sz5|Da~fsC$R)>=owcKxnNp8QQN}c!4uD&s+PMJk@55y
zTA_qCf&<@FoMxci5YDAog1p{s@<&5DYjtjCR
z(~8#H#t0E}XaFt)G1S_ND+QN4t||g*&XS3DkNPoo#_mDsZjtZRiRxDVF>pmD`&gd`
zV{+kI_DX_6dZpeo?n8xunXi)3%+Jp?f#-o_d;+`5Y@iz)rmRtHaGNGp-*^NKN{D18
zpB>&es4}=Kg7SgUaw@8*r#4?SqUbDZ;YgUQq^266Dqc2J00G!F#hkTnC4=&%-vZME
zm(DzcrhL8X*;-N0bsxa>PNgnf^Ou=4s&w_7hAt^T*zJH*OfmziB0n-QXRtd07m9HE
zz^JlxCf=#SRviXbXRy*(S>98rdiwExA2Gk?1(4V)DhjdQ>j^a;M7fxuLPHmKauTKA
z8dde#t_l9e_RJp~Id>6kY~VchpFha!RrS{aB)FR7ZMx6}M75J{AoU5_?NIy{5Ooyo
z7VK>4zjQCjVgf+(9L1#&2MyMpc(DD#VRVBq$l^6&v-?q@}3WpiwhA
znZ++E2eBAvRIivp?@@PDRm{q(l~*zcMQ8pwx%A{gxMl-tQQ{Y)>6Gu8gOz1!oL(-#
zL^_dvM)@3L8lg?@J62P}G<<3M<6n6{z&ZYY7OvH*vY%s{vp>7(yEyRALN7=U
z-LQY@LgIMobU!+!xx4Fre%V@D=YYX>?=p5>qv{z;$bvDz8-Ch1mSHuh2dXc+5jw&;
zLSaICA3t>uIB@H4ZWh*i>NuaiucoZ;eZ)o-N^?3`b>rO(@K@86LGC0m*7_z0;#yL`
z->1kJwVh{RW24>skW}Kz^5@CR;brX7@lvyCIvm0Q2Sx8`i3TSMS1DWx>)p1yPDl!F
zczU}?vEhWU^<$-i9{5#%hvPh(xJ0?VFv4F!XXnFIMEjmSOglM3qnFKlN6r78v$&oR
zvleHg3vNvAKCs<&!j3po+P(#i``R?>w;G`k0M!Y;d~Wz`3QT~oUqrw@EOdQ3KukZ)
zDzN0|&T321*JHIhRo6q+DF4+0Tq-o!RbWTANb*r^&^v1?OY
z-v+c{Jg;b|x(x0{qo6y^3!1C1FO6S(r*7$zeZgBbm12Qozn!jB!L;<(^drF7k0eDZ
z%V9~;_f+CsV|6Y70roSUi!4i>FcLNHRF3};rOk^{j_V+@jBFU!{h4E4=KA6}LRx3V
zvmC_NKC3A*{kPXw40T7Soa?Y843?kK_VECSWV&q+78@
z?d>EwDIIsPP}Uv)73%|l>OR7$e7HLkfxy{+{@CBG)?nsXviG;JkL|1LiPK3Ly%=y2
z)mt@(Zym46CLB&VuHmQWr>Aw|hE}UVpJcYMK
z+21q_3OL)Id;#bx;-Tz=xKoGvwpmRXsYaI6kOu*KcoKK9$WYbkcjU=+)5^x*u(MMEU+zse*r%{Ox`vp&CWTu43*3qCHsRkbq#vU#oRX+J({$z#InMLoWy#;|A6JM7ULGG>2@nw>bypQ!&R`EmnK&8|7
zenP1KPspXaHw~#D12OH>_g&rCBz0N=E9u6+X7;*F=2!YxuNAm&cZEe?7b&pZ_vVz5
zFSxydl4Gf?Rxg}=qd<9o0o=$s7EqiRYU~8QlER012qXmKRMN`7(Hg`u;;X)v$o3x^
zc;CY7(MlCajEh}jQD5j0n5%uvjNVbaMfOZour1DLhW7I9l;G;08zwGO?)B`G6sWS?
zv5w#Q+4th(Q5#*|^hNz`AlaA9qGkr+2$6NZRbaLi&D3zU%x_fN)+k6VpU9s=^F?U6Hjq8E&
zK!y;kceXr_iGIg@s_cJ8@uAdrG53(l`%EV|HAv7hSQch``jvoax%%>j1TylU-G>oZ
z>aH9ztxYTH@iWH%74N@Hz3JcXuW{||5vSVVB$pNg*&)U!O*6n%->$%f`>zT}HLd?)a6+KXt_uy4KU$c#p7L9Z|3qB5I
znsU1k%f@$o7EYG#GFiUl@{983OXX$F{d-%~uc8eo`G_C39oEQArRJi`vs-$xax9x6
zZBgi>0^5o-Se_zkvv2tuH9HK6=P6333-sKPowaFN$>5XMFCG>y!tR3wUjGXt`Qtq3
zTJ{HbtE;CC_cSIT=KMlgIw48n%)Ej6I-0F2gcD*dxp{gJI%HbG2WSH>a$ZN*mJ5aE
z!j`gcjyAln#ksf-e26|re^?s^KGpaCfE~Cts1{VY9DPOnr#R8k_Yfh7IURCsYIVr=
zb+&bN}8}
zN#|vPFuNQU7=%_TN^0c!3QE}C$*tZeinY#OIQ2GNm+-w+Dp5;1^d)RbeRWf7I3nRk
zFE=}m?(!GsO}Tbj4rV&O`)IUK^uxc;hzhSM2;uIB5tJ@01cat0)+J(M-05uXp|gh^L{rZo+hQ|l+5Wga+|1{zb{NGwyIkAeX33-Y
z68w~{0@>p0>1LajcWv(d6IPM7wy_3MLe)ci?hsR0A#;6gvbV!3HNOq-^v~^ivb0>N
zReJ5OAu_M~kI
_MJf}yQD_;
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