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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "iterable-sdk",
"version": "26.10.10-beta",
"version": "26.10.11-beta",
"description": "Takes an Android or iOS app — native or React Native — from nothing to a push notification proven on a device: Firebase or Apple (APNs) provisioning, version-pinned SDK integration, silent-failure pitfalls, and device-side proof that a push actually arrived. Android proof runs on a device or emulator; iOS proof runs on the iOS Simulator.",
"author": { "name": "Iterable" },
"repository": "https://github.com/Iterable/iterable-sdk-skill",
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"owner": { "name": "Iterable" },
"metadata": {
"description": "Iterable's official skills for AI coding assistants — mobile SDK integration guidance.",
"version": "26.10.10-beta"
"version": "26.10.11-beta"
},
"plugins": [
{
Expand Down
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iterable-sdk",
"version": "26.10.10-beta",
"version": "26.10.11-beta",
"description": "Takes an Android or iOS app — native or React Native — from nothing to a push notification proven on a device: Firebase or Apple (APNs) provisioning, version-pinned SDK integration, silent-failure pitfalls, and device-side proof that a push actually arrived. Android proof runs on a device or emulator; iOS proof runs on the iOS Simulator.",
"author": { "name": "Iterable" },
"repository": "https://github.com/Iterable/iterable-sdk-skill",
Expand Down
8 changes: 0 additions & 8 deletions .mcp.json

This file was deleted.

86 changes: 69 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,62 @@ select **iterable**, and select **Enable auto-update**.

### Cursor

Cursor requires version 3.9 or later. Install the skills as a plugin:
Cursor installs plugins from a marketplace, so you first add this repository
as a marketplace and then install the plugin from it. Before you start, sign
in to Cursor and update it to the latest version. <!-- verify in UI -->

1. Open **Customize** in the sidebar.
1. Open **Customize** in the sidebar, then open **Plugins**.
2. Select **+ Add**, then **From GitHub Repository**.
3. Enter `https://github.com/Iterable/iterable-sdk-skill`.
4. Find **iterable-sdk**, select **Install**, and choose the project or user
scope.
3. In **Import Marketplace**, enter
`https://github.com/Iterable/iterable-sdk-skill` under **Repository**. Leave
**Scope** set to **User**, then select **Import**.
4. Cursor switches to the marketplace browser. Select **Personal** or
**Iterable**, whichever the marketplace is listed under, to see its plugins.
Then open **iterable-sdk**.
5. On the plugin's page, select **Install**. If Cursor asks for a scope, choose
the project or user scope.

The plugin installs the skills, the scripts they run, and the Context7 MCP
server. Team admins can also add the repository as a team marketplace.
The plugin installs the skills and the scripts they run. To confirm the
install, open **Customize** and check that:

- **Plugins** lists **iterable-sdk**.
- **Skills** lists the six `iterable-*` skills.

Then start a new Agent chat.

If **From GitHub Repository** is missing, open the marketplace from
**Customize**, select **Add Marketplace**, then **Import from GitHub**. <!-- verify in UI -->

Who can import depends on your plan:

- **Free and Pro:** follow the steps above.
- **Teams:** the steps above work, because community plugin import is on by
default.
- **Enterprise:** community plugin import is off by default. Ask an admin to
allow it, or to add the repository as a team marketplace. <!-- verify in UI -->

If you installed the plugin in Claude Code, Cursor also loads that copy while
**Include Third-Party Plugins, Skills, and Other Configs** is on, in
**Cursor Settings → Agents → Third-Party Imports**. That copy updates through
Claude Code. <!-- verify in UI -->

#### Team marketplace

On Teams and Enterprise, an admin can share the plugin with the whole team:

1. Go to **Dashboard → Plugins & MCPs**.
2. In **Team Marketplaces**, click **Add Marketplace**, choose
**Import from Repo**, and paste the repository URL.
3. Add **iterable-sdk** with **Add to Marketplace**.
4. Under **Marketplace Settings**, set **Marketplace Access**, then save.
5. Choose how the plugin is distributed: **Default Off**, **Default On**, or
**Required**.

Teammates then find the plugin in **Customize**. The Teams plan allows one
imported team marketplace. Auto Refresh needs the Cursor GitHub App on the
repository, so select **Refresh** to pick up a new version.

#### Local checkout

To test a local checkout, clone it into Cursor's local plugin folder:

Expand All @@ -153,13 +199,15 @@ git clone https://github.com/Iterable/iterable-sdk-skill.git ~/.cursor/plugins/l
```

Run **Developer: Reload Window**, then confirm the skills in **Customize**.
Cursor skips a symlink in that folder that points to a clone elsewhere. On
Teams and Enterprise plans, an admin must allow local plugin imports.
Cursor skips a symlink in that folder that points to a clone elsewhere. If
**iterable-sdk** is also installed from a marketplace, that install takes
precedence over the local copy. On Enterprise, an admin must turn on
**Allow Local Plugin Imports**.

#### Fallback: link the skill directories

If you can't install plugins, clone the repository and link the skill
directories. This installs the skills without the Context7 MCP server:
directories:

```bash
git clone --depth 1 https://github.com/Iterable/iterable-sdk-skill.git ~/iterable-skills
Expand Down Expand Up @@ -239,8 +287,7 @@ notifications afterwards is optional.
- Push proof creates or updates one proof template in your Iterable project and
sends it only to the test user. The test identity becomes a real user profile
in that project.
- The plugin also adds the Context7 MCP server (no API key) for documentation
lookups and, in Claude Code, five helper subagents.
- In Claude Code, the plugin also adds five helper subagents.
- You authenticate in your own browser or `gcloud` session. The scripts do not
collect account passwords or attempt to bypass sign-in, CAPTCHA, or
terms-of-service screens.
Expand Down Expand Up @@ -295,9 +342,12 @@ codex plugin marketplace upgrade iterable

Then start a new Codex session.

For Cursor, manage the installed plugin from **Customize**. For a local
plugin checkout, run `git pull` in `~/.cursor/plugins/local/iterable-sdk` and
reload the window. For the clone-and-link fallback:
For Cursor, the plugin is indexed when you import the repository. To get a
newer version, refresh the marketplace from **Customize**, or remove it and
import it again. <!-- verify in UI --> For a team marketplace, an admin
selects **Refresh** in **Dashboard → Plugins & MCPs**. For a local plugin
checkout, run `git pull` in `~/.cursor/plugins/local/iterable-sdk` and reload
the window. For the clone-and-link fallback:

```bash
cd ~/iterable-skills
Expand All @@ -319,8 +369,10 @@ codex plugin remove iterable-sdk@iterable
codex plugin marketplace remove iterable
```

For Cursor, uninstall the plugin from **Customize**, or delete
`~/.cursor/plugins/local/iterable-sdk` for a local checkout. For the
For Cursor, open **iterable-sdk** in **Customize** and select **Uninstall**.
You can then remove the marketplace. That removes it from your Cursor account
and leaves the repository unchanged. <!-- verify in UI --> For a local
checkout, delete `~/.cursor/plugins/local/iterable-sdk`. For the
clone-and-link fallback, delete the links in `~/.cursor/skills/` and the
`~/iterable-skills` clone.

Expand Down
8 changes: 0 additions & 8 deletions mcp.json

This file was deleted.

40 changes: 3 additions & 37 deletions pipeline/src/validate-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Validates Cursor/Claude plugin manifests and MCP config files.
* Validates Cursor/Claude plugin manifests.
*
* Checks:
* - `.cursor-plugin/plugin.json` and `.cursor-plugin/marketplace.json` parse
* and have required fields.
* - `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json` parse.
* - `mcp.json` and `.mcp.json` exist and have identical `mcpServers` content.
* - No manifest declares MCP servers; the skills read the docs they ship.
* - Declared skill paths exist on disk.
* - Every manifest declares the same, valid version.
*
Expand Down Expand Up @@ -93,7 +93,7 @@ function validateCursorPlugin(): void {
if ("mcpServers" in manifest) {
fail(
file,
"omit `mcpServers` — Cursor auto-discovers `mcp.json` at the plugin root",
"omit `mcpServers` — the plugin bundles no MCP server",
);
}
}
Expand Down Expand Up @@ -151,39 +151,6 @@ function validateClaudeMarketplace(): void {
}
}

function validateMcpConfigs(): void {
const cursorMcp = readJson("mcp.json");
const claudeMcp = readJson(".mcp.json");
if (!isRecord(cursorMcp) || !isRecord(claudeMcp)) return;

const cursorServers = cursorMcp.mcpServers;
const claudeServers = claudeMcp.mcpServers;

if (!isRecord(cursorServers)) {
fail("mcp.json", "`mcpServers` must be an object");
return;
}
if (!isRecord(claudeServers)) {
fail(".mcp.json", "`mcpServers` must be an object");
return;
}

const cursorJson = JSON.stringify(cursorServers);
const claudeJson = JSON.stringify(claudeServers);
if (cursorJson !== claudeJson) {
fail("mcp.json", "`mcpServers` must match `.mcp.json` exactly");
}

const context7 = cursorServers.context7;
if (!isRecord(context7)) {
fail("mcp.json", "`mcpServers.context7` is required");
return;
}
if (context7.url !== "https://mcp.context7.com/mcp") {
fail("mcp.json", "`mcpServers.context7.url` must point at Context7");
}
}

/**
* Hosts decide whether to update by comparing this string to what is already
* installed, so a disagreement means some hosts update and others silently do
Expand Down Expand Up @@ -240,7 +207,6 @@ function main(): void {
validateCursorMarketplace();
validateClaudePlugin();
validateClaudeMarketplace();
validateMcpConfigs();
validateVersions();

if (issues.length === 0) {
Expand Down
11 changes: 0 additions & 11 deletions tests/plugin-manifests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -139,17 +139,6 @@ want="$(tr ' ' '\n' <<< "$SHIPPED" | sort | tr '\n' ' ')"
&& ok "$(printf '%-48s %s' "only client-facing agents are shipped" "$(wc -w <<< "$want" | tr -d ' ') of them")" \
|| bad "agents/ has changed" "shipped='${have% }' expected='${want% }' — build roles belong in tools/agents/"

# ------------------------------------------------------------------- the MCP files
# Two names for one file because the hosts look in different places. They drifted
# once; a client on one host would then get doc fetching the other host's client does not.
if [[ -f mcp.json && -f .mcp.json ]]; then
cmp -s mcp.json .mcp.json \
&& ok "mcp.json and .mcp.json are identical" \
|| bad "the two MCP files have drifted" "one host would get different servers"
else
bad "an MCP file is missing" "mcp.json and .mcp.json must both exist"
fi

# ---------------------------------------------------------------- the host's paths
# Everything below is read verbatim by a client's agent. An absolute path from this
# machine in it is an instruction to use a directory the client does not have — and it
Expand Down
4 changes: 1 addition & 3 deletions tools/install-local
Original file line number Diff line number Diff line change
Expand Up @@ -249,15 +249,13 @@ for s in iterable-provision iterable-android iterable-android-verify iterable-io
check "$s/SKILL.md"
done
check .claude-plugin/plugin.json
check mcp.json
check .mcp.json
check agents/ios-provisioner.md
for c in onboard wizard gates agent provision teardown discover iterable-keys proof-push handoff config.sh ios-provision ios-prepare-proof ios-install ios-launch ios-observe ios-send ios-deliver ios-verify; do
[[ -x "$DEST/bin/$c" || "$c" == config.sh ]] || MISSING="$MISSING bin/$c(not executable)"
check "bin/$c"
done
[[ -z "$MISSING" ]] || die "the build is missing:$MISSING"
say "six skills, the iOS specialist, both MCP files, and every entry point present and executable"
say "six skills, the iOS specialist, and every entry point present and executable"

INSTALLED_NAME="$(node -e 'process.stdout.write(require(process.argv[1]).name)' "$MKT_DIR/.claude-plugin/marketplace.json")"
[[ "$INSTALLED_NAME" == "$MKT" ]] || die "the marketplace still calls itself '$INSTALLED_NAME'"
Expand Down
Loading