| 132 | |
| 133 | |
| 134 | class CursorAdapter(HarnessAdapter): |
| 135 | harness_id = "cursor" |
| 136 | |
| 137 | def emit_plugin(self, plugin: PluginSource) -> EmitResult: |
| 138 | """Emit `.cursor-plugin/plugin.json` per plugin (manifest only). |
| 139 | |
| 140 | Cursor auto-discovers components from `.claude/` directories, so we don't |
| 141 | re-emit agents/skills/commands. |
| 142 | """ |
| 143 | result = EmitResult() |
| 144 | manifest = self._build_plugin_manifest(plugin) |
| 145 | result.written.append( |
| 146 | self.write( |
| 147 | Path(".cursor-plugin") / "plugins" / f"{plugin.name}.json", |
| 148 | json.dumps(manifest, indent=2) + "\n", |
| 149 | ) |
| 150 | ) |
| 151 | return result |
| 152 | |
| 153 | def emit_global(self, plugins: list[PluginSource]) -> EmitResult: |
| 154 | result = EmitResult() |
| 155 | # 1. Marketplace |
| 156 | marketplace = self._build_marketplace(plugins) |
| 157 | result.written.append( |
| 158 | self.write( |
| 159 | Path(".cursor-plugin") / "marketplace.json", |
| 160 | json.dumps(marketplace, indent=2) + "\n", |
| 161 | ) |
| 162 | ) |
| 163 | |
| 164 | # 2. Top-level plugin.json (matches Cursor convention for single-plugin repos |
| 165 | # AND advertises the marketplace bundle) |
| 166 | if marketplace["plugins"]: |
| 167 | top_manifest = { |
| 168 | "name": marketplace["name"], |
| 169 | "displayName": marketplace["name"].replace("-", " ").title(), |
| 170 | "version": marketplace.get("metadata", {}).get("version", "0.0.0"), |
| 171 | "description": marketplace.get("metadata", {}).get("description", ""), |
| 172 | "author": { |
| 173 | "name": marketplace["owner"]["name"], |
| 174 | "email": marketplace["owner"].get("email", ""), |
| 175 | }, |
| 176 | "homepage": marketplace["owner"].get("url", ""), |
| 177 | "license": "MIT", |
| 178 | } |
| 179 | result.written.append( |
| 180 | self.write( |
| 181 | Path(".cursor-plugin") / "plugin.json", |
| 182 | json.dumps(top_manifest, indent=2) + "\n", |
| 183 | ) |
| 184 | ) |
| 185 | |
| 186 | # 3. Curated rules |
| 187 | rules_emitted = 0 |
| 188 | if _CURATED_RULES_DIR.is_dir(): |
| 189 | for mdc in sorted(_CURATED_RULES_DIR.glob("*.mdc")): |
| 190 | content = read_file(mdc) |
| 191 | errors = _validate_mdc_frontmatter(content, mdc) |
no outgoing calls