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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{
"name": "synter",
"source": ".",
"logo": "https://raw.githubusercontent.com/Synter-Media-AI/plugin/main/assets/logo.png",
"logo": "assets/logo.png",
"description": "The AI Agent Operator for Ads. Connect 21 ad platforms, build audiences, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — one conversation, every platform. You direct, the agents execute. Nothing that spends money ships without your approval."
}
]
Expand Down
4 changes: 2 additions & 2 deletions .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"homepage": "https://syntermedia.ai",
"repository": "https://github.com/Synter-Media-AI/plugin",
"license": "MIT",
"logo": "https://raw.githubusercontent.com/Synter-Media-AI/plugin/main/assets/logo.png",
"logo": "assets/logo.png",
"keywords": [
"ads",
"advertising",
Expand All @@ -36,7 +36,7 @@
"SYNTER_API_KEY": {
"type": "string",
"title": "Synter API key",
"description": "Optional for onboarding. Paste your syn_... key from syntermedia.ai/developer to use connected ad accounts. Leave blank to run /synter:quickstart and the free GA4 tools."
"description": "Optional for onboarding. Paste your syn_... key from syntermedia.ai/developer to use connected ad accounts. Leave blank to run /synter:quickstart and the GA4 and onboarding tools first; paid actions need Solo ($20/mo) or credits."
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

All notable changes to the Synter plugin are documented here. This project follows [semantic versioning](https://semver.org).

## [Unreleased]

### Changed
- Cursor Marketplace submission prep: `logo` in `.cursor-plugin/plugin.json` and `.cursor-plugin/marketplace.json` is now the repo-relative `assets/logo.png` (Cursor resolves it to a commit-pinned raw.githubusercontent URL), per the submission checklist.
- Removed "free" wording from the Cursor `SYNTER_API_KEY` description and README; paid actions are framed as Solo ($20/mo) or credits.
- Root `plugin.json` is now a real Agent Plugins 1.0.0 manifest instead of a symlink to the Claude manifest (the symlink failed the Agent Plugins schema and breaks on symlink-less checkouts).
- `scripts/validate-manifests.js` guards the root manifest, relative Cursor logo paths, and "free" wording in Cursor listing copy.

## [1.1.2] — 2026-09-06

### Fixed
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Official install documentation: [Use plugins in Claude](https://support.claude.c

After install, set `SYNTER_API_KEY` in **Plugins → Configure**. The hosted MCP is `https://mcp.syntermedia.ai`.

Free GA4 and onboarding tools work with no key and no credits.
GA4 and onboarding tools work before you add a key; paid actions need Solo ($20/mo) or credits.

> Campaign write actions spend real money — every spend asks for your approval first.

Expand Down Expand Up @@ -134,6 +134,7 @@ plugin/
├── .cursor-plugin/
│ ├── plugin.json # Cursor Plugin manifest + SYNTER_API_KEY variable
│ └── marketplace.json # Cursor Team Marketplace index
├── plugin.json # Agent Plugins 1.0.0 manifest (open standard; real file, not a symlink)
├── .mcp.json # Synter remote MCP for Claude (browser OAuth)
├── mcp.json # Synter MCP for Cursor (header: ${SYNTER_API_KEY})
├── skills/ # /synter:* slash commands (SKILL.md each)
Expand Down Expand Up @@ -210,7 +211,7 @@ and store it in the client's secret configuration, never in chat.

## Safety

Your agent can create campaigns, change budgets, and pause spend. The plugin defaults to **recommend-then-execute** and asks for explicit approval before anything spends money. It confirms the org/account before any write, uses only the real IDs the platform returns, and guards against fat-finger budgets. Reads are always free to run.
Your agent can create campaigns, change budgets, and pause spend. The plugin defaults to **recommend-then-execute** and asks for explicit approval before anything spends money. It confirms the org/account before any write, uses only the real IDs the platform returns, and guards against fat-finger budgets. Reads (lists, reports, pulls) never need spend approval.

---

Expand Down
1 change: 0 additions & 1 deletion plugin.json

This file was deleted.

28 changes: 28 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "synter",
"version": "1.1.2",
"description": "The AI Agent Operator for Ads. Connect every ad platform, build audiences from signals and first-party data, generate on-brand creative, launch cross-platform campaigns, and reallocate spend by ROAS — all in one conversation. You direct, the agents execute. Nothing ships without your approval.",
"author": {
"name": "Synter",
"email": "joel@synterai.com",
"url": "https://syntermedia.ai"
},
"homepage": "https://syntermedia.ai",
"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"
]
}
52 changes: 52 additions & 0 deletions scripts/validate-manifests.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const MANIFEST_PATHS = [
'.cursor-plugin/marketplace.json',
'.mcp.json',
'mcp.json',
'plugin.json',
];

const manifests = {};
Expand Down Expand Up @@ -125,6 +126,57 @@ if (pluginJson && pluginJson.userConfig && pluginJson.userConfig.synter_api_key)
errors.push('.claude-plugin/plugin.json: Claude plugin must not collect a static Synter API key');
}

// (e) Root plugin.json is an Agent Plugins 1.0.0 manifest (Cursor detects it
// as a second plugin format). Keep it a real file — symlinks break on Windows
// checkouts and some marketplace crawlers — and keep it schema-clean.
const AGENT_PLUGIN_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json';
const AGENT_PLUGIN_KEYS = new Set([
'$schema', 'name', 'version', 'description', 'author', 'homepage',
'repository', 'license', 'keywords', 'extensions',
]);
if (fs.lstatSync(path.join(REPO_ROOT, 'plugin.json')).isSymbolicLink()) {
errors.push('plugin.json: must be a regular file, not a symlink');
}
const rootPluginJson = manifests['plugin.json'];
if (rootPluginJson) {
if (rootPluginJson.$schema !== AGENT_PLUGIN_SCHEMA) {
errors.push(`plugin.json: $schema must be ${AGENT_PLUGIN_SCHEMA}`);
}
for (const key of Object.keys(rootPluginJson)) {
if (!AGENT_PLUGIN_KEYS.has(key)) {
errors.push(`plugin.json: "${key}" is not an Agent Plugins 1.0.0 manifest field`);
}
}
if (pluginJson && rootPluginJson.name !== pluginJson.name) {
errors.push('plugin.json: name does not match .claude-plugin/plugin.json');
}
if (pluginJson && rootPluginJson.version !== pluginJson.version) {
errors.push('plugin.json: version does not match .claude-plugin/plugin.json');
}
}

// (f) Cursor Marketplace checklist: logo committed and referenced by a
// relative path that exists in the repo.
for (const [relPath, logo] of [
['.cursor-plugin/plugin.json', manifests['.cursor-plugin/plugin.json'] && manifests['.cursor-plugin/plugin.json'].logo],
['.cursor-plugin/marketplace.json', (manifests['.cursor-plugin/marketplace.json'] && manifests['.cursor-plugin/marketplace.json'].plugins || [])[0] && manifests['.cursor-plugin/marketplace.json'].plugins[0].logo],
]) {
if (!logo) continue;
if (/^[a-z]+:\/\//i.test(logo) || logo.startsWith('/') || logo.includes('..')) {
errors.push(`${relPath}: logo must be a relative repo path (got "${logo}")`);
} else if (!fs.existsSync(path.join(REPO_ROOT, logo))) {
errors.push(`${relPath}: logo "${logo}" does not exist`);
}
}

// (g) Listing copy: no "free" framing in Cursor-facing manifest text (pricing
// is Solo $20/mo or credits).
for (const relPath of ['.cursor-plugin/plugin.json', '.cursor-plugin/marketplace.json']) {
if (/\bfree\b/i.test(readFile(relPath))) {
errors.push(`${relPath}: remove "free" wording from listing copy`);
}
}

const mcpJson = readFile('mcp.json');
if (!mcpJson.includes('SYNTER_API_KEY')) {
errors.push('mcp.json: missing required literal "SYNTER_API_KEY"');
Expand Down
Loading