Skip to content

Commit a6da289

Browse files
Polish plugin manifest + add self-marketplace; rename Claude plugin to 'databricks' (#77)
## Summary Polish for submitting this repo to the **official Anthropic Claude Code marketplace** via [claude.ai/settings/plugins/submit](https://claude.ai/settings/plugins/submit): - Refresh `.claude-plugin/plugin.json` description to match the 8 skills shipped today (drop the lingering "Unity Catalog" mention; add Lakebase, Jobs, Pipelines, serverless migration explicitly) - Add `homepage`, explicit `license: LicenseRef-Databricks`, `author.url` - Bump version 0.1.0 → 0.2.0 - **Rename the Claude Code plugin** `databricks-skills` → `databricks` to match the github/gitlab/terraform/linear single-brand-word convention. Claude side only — never published, no breakage. - Add `.claude-plugin/marketplace.json` (with `source: "./"`, verified against the official `claude plugin validate .` — `source: "."` was caught by `/ace-review` and fixed) - Wire `.claude-plugin/**` into `scripts/skills.py validate` so CI fails when (a) a new skill is added without a matching `plugin_keyword` in `plugin.json` keywords, or (b) plugin/marketplace descriptions drift apart. Caught and fixed a real drift on this PR ("DABs" vs "Declarative Automation Bundles (DABs)") **Cursor side intentionally NOT renamed.** `.cursor-plugin/plugin.json` `name` stays as `databricks-skills` and the README `/add-plugin` command matches. The Cursor marketplace URL slug `databricks` is display-only — actual install command is `/add-plugin databricks-skills`, and renaming would orphan existing user installs from auto-updates. Documented in new `.cursor-plugin/NOTES.md`. Long-term rename tracked as [#78](#78). ## Why [Slack thread](https://databricks.slack.com/archives/C0AN1BVC2LF/p1779094289341709) — fresh ask to release a Databricks Claude plugin in the official Anthropic marketplace. This repo already meets the Anthropic plugin manifest spec; this PR polishes the metadata so a reviewer doesn't bounce it for stale claims (UC) or missing fields (homepage/license), and the new CI check keeps it from drifting again as more skills land. ## Review trail Reviewed with `/ace-review` (two parallel GPT models). Both flagged `source: "."` as invalid; `claude plugin validate .` confirmed → fixed to `"./"` in commit `8841de4`. Other findings (type-checking in-tree JSON, alleged install-path breakage from rename) were false positives. The Cursor-side rename was applied and then reverted after manually verifying that the Cursor marketplace install ID is `databricks-skills`, not `databricks` (commit `54ec175`). ## Test plan - [ ] `python3 scripts/skills.py validate` passes (verified locally — clean tree, dropped-keyword negative, description-drift negative all behave correctly) - [ ] `claude plugin validate .` passes (verified locally) - [ ] `jq empty .claude-plugin/plugin.json && jq empty .claude-plugin/marketplace.json && jq empty .cursor-plugin/plugin.json` This pull request and its description were written by Isaac. --------- Signed-off-by: James Broadhead <james.broadhead@databricks.com>
1 parent 701b7c6 commit a6da289

5 files changed

Lines changed: 140 additions & 12 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
{
2-
"name": "databricks-skills",
3-
"description": "Official Databricks agent skills marketplace",
4-
"version": "0.1.0",
2+
"$schema": "https://json.schemastore.org/claude-code-plugin-marketplace.json",
3+
"name": "databricks-agent-skills",
4+
"description": "Databricks agent skills for AI coding assistants.",
55
"owner": {
66
"name": "Databricks"
77
},
88
"plugins": [
99
{
10-
"name": "databricks-skills",
10+
"name": "databricks",
1111
"source": "./",
12-
"description": "Databricks skills for CLI, Apps, Unity Catalog, Model Serving, Declarative Automation Bundles (DABs), and more. Stable skills only — for experimental skills use `databricks aitools install --experimental`.",
13-
"version": "0.1.0"
12+
"category": "development",
13+
"description": "Databricks skills for the CLI, Apps, Lakebase, Model Serving, Lakeflow Jobs, Spark Declarative Pipelines, Declarative Automation Bundles (DABs), and classic-to-serverless migration."
1414
}
1515
]
1616
}

.claude-plugin/plugin.json

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
{
2-
"name": "databricks-skills",
3-
"description": "Databricks skills for CLI, Apps, Unity Catalog, Model Serving, Declarative Automation Bundles (DABs), and more.",
4-
"version": "0.1.0",
2+
"name": "databricks",
3+
"description": "Databricks skills for the CLI, Apps, Lakebase, Model Serving, Lakeflow Jobs, Spark Declarative Pipelines, Declarative Automation Bundles (DABs), and classic-to-serverless migration.",
4+
"version": "0.2.0",
55
"author": {
6-
"name": "Databricks"
6+
"name": "Databricks",
7+
"url": "https://databricks.com"
78
},
9+
"homepage": "https://github.com/databricks/databricks-agent-skills",
810
"repository": "https://github.com/databricks/databricks-agent-skills",
9-
"keywords": ["databricks", "cli", "apps", "etl", "data-engineering", "spark", "data-pipelines", "unity-catalog", "model-serving", "dabs"],
11+
"license": "LicenseRef-Databricks",
12+
"keywords": [
13+
"databricks",
14+
"cli",
15+
"apps",
16+
"lakebase",
17+
"model-serving",
18+
"jobs",
19+
"pipelines",
20+
"dabs",
21+
"serverless",
22+
"data-engineering"
23+
],
1024
"skills": "./skills/"
1125
}

.cursor-plugin/NOTES.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Why `name` is `databricks-skills` here (and `databricks` next door)
2+
3+
The `name` field in this `plugin.json` is intentionally **`databricks-skills`**, not `databricks`, even though the sibling [`../.claude-plugin/plugin.json`](../.claude-plugin/plugin.json) uses `databricks`. This is **not** a typo or stale value — please do not "fix" it.
4+
5+
## Background
6+
7+
This plugin has been published on the Cursor marketplace since at least 2026-02-12 (see [cursor.com/marketplace/databricks](https://cursor.com/marketplace/databricks)). Cursor's marketplace assigned two URL slugs that *look* like they should drive the install identifier:
8+
9+
- `cursor.com/marketplace/databricks` — the plugin landing page
10+
- `cursor.com/marketplace/databricks-apps` — the primary skill's page
11+
12+
…but the **actual install command shown on those pages is `/add-plugin databricks-skills`**. Cursor uses the `name` field from this file as the canonical install identifier — the URL slugs are display-only.
13+
14+
## Why we can't just rename it to `databricks`
15+
16+
Renaming `name` here would change the install identifier Cursor uses to track existing installations under `~/.cursor/plugins/databricks-skills/`. Cursor's update mechanism is undocumented but appears to key on this identifier, so an in-place rename would likely orphan every existing user from auto-updates until they uninstall and reinstall under the new name. Multiple Databricks teams (Field Engineering, Auth Platform, Apps DevEx) have been pointing internal and external customers at the Cursor marketplace listing for months, so the user base is non-trivial.
17+
18+
The Claude Code side has no such constraint — the plugin has never been published to the Anthropic marketplace under any name, so PR #77 freely set the Claude `name` to `databricks` to match the github/gitlab/terraform/linear single-brand-word convention.
19+
20+
## No rename planned
21+
22+
A rename to align the Cursor side with the Claude side (`databricks`) was explicitly closed as not planned ([issue #78](https://github.com/databricks/databricks-agent-skills/issues/78)). The breakage cost to existing installs outweighs the consistency win. The asymmetry is permanent; leave it alone.
23+
24+
## TL;DR for future editors
25+
26+
| File | `name` value | Why |
27+
|------|--------------|-----|
28+
| `.claude-plugin/plugin.json` | `databricks` | Fresh submission, no install-base constraint |
29+
| `.cursor-plugin/plugin.json` | `databricks-skills` | Matches the install ID Cursor users have been using since Feb 2026 |

.github/workflows/validate-manifest.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
- 'assets/**'
99
- 'scripts/skills.py'
1010
- 'manifest.json'
11+
- '.claude-plugin/**'
1112
push:
1213
branches:
1314
- main

scripts/skills.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,35 @@
2323
SKILL_METADATA = {
2424
"databricks-core": {
2525
"description": "Core Databricks skill for CLI, auth, and data exploration",
26+
"plugin_keyword": "cli",
2627
},
2728
"databricks-apps": {
2829
"description": "Databricks Apps development and deployment (evaluates analytics vs synced tables data access)",
30+
"plugin_keyword": "apps",
2931
},
3032
"databricks-jobs": {
3133
"description": "Develop and deploy Lakeflow Jobs on Databricks via DABs, Python SDK, or the CLI — covers all task types, triggers, notifications, and worked examples",
34+
"plugin_keyword": "jobs",
3235
},
3336
"databricks-lakebase": {
3437
"description": "Databricks Lakebase Postgres: projects, scaling, connectivity, synced tables, and Data API",
38+
"plugin_keyword": "lakebase",
3539
},
3640
"databricks-dabs": {
3741
"description": "Declarative Automation Bundles (DABs) for deploying and managing Databricks resources",
42+
"plugin_keyword": "dabs",
3843
},
3944
"databricks-model-serving": {
4045
"description": "Databricks Model Serving endpoint management",
46+
"plugin_keyword": "model-serving",
4147
},
4248
"databricks-pipelines": {
4349
"description": "Databricks Spark Declarative Pipelines (SDP) for ETL and streaming",
50+
"plugin_keyword": "pipelines",
4451
},
4552
"databricks-serverless-migration": {
4653
"description": "Migrate Databricks workloads from classic compute to serverless compute, including compatibility checks and concrete fixes",
54+
"plugin_keyword": "serverless",
4755
},
4856
}
4957

@@ -422,6 +430,69 @@ def validate_manifest(repo_root: Path) -> bool:
422430
return True
423431

424432

433+
def validate_plugin_manifests(repo_root: Path) -> list[str]:
434+
"""Validate .claude-plugin/plugin.json and .claude-plugin/marketplace.json
435+
stay in sync with the set of skills shipped in this repo.
436+
437+
The two manifests power Claude Code marketplace discovery; if a new skill
438+
lands without a corresponding plugin keyword bump, the marketplace listing
439+
silently goes stale. This check forces SKILL_METADATA, plugin.json, and
440+
marketplace.json to stay aligned.
441+
442+
Returns a list of error strings (empty means all good).
443+
"""
444+
errors: list[str] = []
445+
446+
plugin_path = repo_root / ".claude-plugin" / "plugin.json"
447+
marketplace_path = repo_root / ".claude-plugin" / "marketplace.json"
448+
449+
if not plugin_path.exists():
450+
errors.append(f"Missing {plugin_path.relative_to(repo_root)}")
451+
if not marketplace_path.exists():
452+
errors.append(f"Missing {marketplace_path.relative_to(repo_root)}")
453+
if errors:
454+
return errors
455+
456+
plugin = json.loads(plugin_path.read_text())
457+
marketplace = json.loads(marketplace_path.read_text())
458+
459+
keywords = {k.lower() for k in plugin.get("keywords", [])}
460+
461+
for skill_dir in iter_skill_dirs(repo_root):
462+
meta = SKILL_METADATA.get(skill_dir.name)
463+
if meta is None:
464+
continue # generate_manifest will flag the missing metadata
465+
expected_keyword = meta.get("plugin_keyword")
466+
if not expected_keyword:
467+
errors.append(
468+
f"SKILL_METADATA['{skill_dir.name}'] is missing 'plugin_keyword'. "
469+
"Add one so .claude-plugin/plugin.json keywords coverage can be validated."
470+
)
471+
continue
472+
if expected_keyword.lower() not in keywords:
473+
errors.append(
474+
f"Skill '{skill_dir.name}' has no corresponding entry in "
475+
f".claude-plugin/plugin.json 'keywords' (looking for '{expected_keyword}'). "
476+
"Add it so the Claude marketplace listing stays in sync."
477+
)
478+
479+
plugin_name = plugin.get("name")
480+
market_plugins = marketplace.get("plugins", [])
481+
market_entry = next((p for p in market_plugins if p.get("name") == plugin_name), None)
482+
if market_entry is None:
483+
errors.append(
484+
f".claude-plugin/marketplace.json has no entry for plugin '{plugin_name}'."
485+
)
486+
else:
487+
if market_entry.get("description") != plugin.get("description"):
488+
errors.append(
489+
".claude-plugin/marketplace.json plugin description must match "
490+
".claude-plugin/plugin.json description (they drift independently otherwise)."
491+
)
492+
493+
return errors
494+
495+
425496
# ---------------------------------------------------------------------------
426497
# CLI
427498
# ---------------------------------------------------------------------------
@@ -479,9 +550,22 @@ def main() -> None:
479550
if not validate_manifest(repo_root):
480551
ok = False
481552

553+
plugin_errors = validate_plugin_manifests(repo_root)
554+
if plugin_errors:
555+
print(
556+
"ERROR: .claude-plugin manifests are out of sync with skills:",
557+
file=sys.stderr,
558+
)
559+
for err in plugin_errors:
560+
print(f" - {err}", file=sys.stderr)
561+
ok = False
562+
482563
if not ok:
483564
print(
484-
"\nRun `python3 scripts/skills.py generate` to fix.",
565+
"\nRun `python3 scripts/skills.py generate` to fix the "
566+
"manifest, and update .claude-plugin/plugin.json + "
567+
"marketplace.json by hand for any plugin-keyword/description "
568+
"mismatches.",
485569
file=sys.stderr,
486570
)
487571
sys.exit(1)

0 commit comments

Comments
 (0)