From 9c7a5b3a3bf187c2b19d0b777768ecb52dd2de22 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 12 May 2026 21:06:54 +0000 Subject: [PATCH 1/4] Add AppKit support and databricks-agent-skills integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Equivalent of #356 (which targets main), adapted for the experimental branch: - Rename `databricks-app-python` → `databricks-apps-python` (plural) for the bundled skill directory, baselines, manifests, builder-app refs, install scripts, and cross-skill mentions. - `databricks-apps-python/SKILL.md` now leads with AppKit (TypeScript + React SDK) as the recommended approach for new apps, with Python frameworks (Dash, Streamlit, Gradio, Flask, FastAPI, Reflex) demoted to an explicit alternative. Frontmatter `name` and H1 title updated to match. - `install.sh` and `install.ps1` fetch and install skills from `databricks/databricks-agent-skills` (`databricks`, `databricks-apps`, `databricks-lakebase`) via a single GitHub API tree call. New `AGENT_SKILLS` variable supports `source:install-name` syntax (e.g. `databricks-core:databricks`) so the install directory can differ from the upstream skill path. - Preserves experimental's polish: keeps `6-cli-approach.md` (not MCP) and references `databricks-lakebase-autoscale` (since `databricks-lakebase-provisioned` was removed on experimental). Co-authored-by: Isaac --- .../baseline.yaml | 2 +- .test/scripts/run_app_eval.py | 6 +- .test/skills/_routing/ground_truth.yaml | 18 ++-- .../candidates.yaml | 0 .../ground_truth.yaml | 0 .../manifest.yaml | 4 +- .test/src/skill_test/scorers/routing.py | 2 +- .test/tests/test_scorers.py | 30 +++---- databricks-builder-app/README.md | 4 +- databricks-builder-app/app.yaml_backup | 2 +- .../client/src/pages/DocPage.tsx | 2 +- .../server/services/skills_manager.py | 2 +- .../server/services/system_prompt.py | 2 +- .../databricks_mcp_server/tools/apps.py | 2 +- databricks-skills/README.md | 2 +- .../1-authorization.md | 0 .../2-app-resources.md | 0 .../3-frameworks.md | 0 .../4-deployment.md | 0 .../5-lakebase.md | 0 .../6-cli-approach.md | 0 .../SKILL.md | 66 ++++++++++++-- .../examples/fm-minimal-chat.py | 0 .../examples/fm-parallel-calls.py | 0 .../examples/fm-structured-outputs.py | 0 .../examples/llm_config.py | 0 databricks-skills/databricks-bundles/SKILL.md | 2 +- .../databricks-lakebase-autoscale/SKILL.md | 2 +- databricks-skills/install_skills.sh | 6 +- install.ps1 | 89 ++++++++++++++++-- install.sh | 90 ++++++++++++++++--- 31 files changed, 263 insertions(+), 70 deletions(-) rename .test/baselines/{databricks-app-python => databricks-apps-python}/baseline.yaml (89%) rename .test/skills/{databricks-app-python => databricks-apps-python}/candidates.yaml (100%) rename .test/skills/{databricks-app-python => databricks-apps-python}/ground_truth.yaml (100%) rename .test/skills/{databricks-app-python => databricks-apps-python}/manifest.yaml (91%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/1-authorization.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/2-app-resources.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/3-frameworks.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/4-deployment.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/5-lakebase.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/6-cli-approach.md (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/SKILL.md (76%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/examples/fm-minimal-chat.py (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/examples/fm-parallel-calls.py (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/examples/fm-structured-outputs.py (100%) rename databricks-skills/{databricks-app-python => databricks-apps-python}/examples/llm_config.py (100%) diff --git a/.test/baselines/databricks-app-python/baseline.yaml b/.test/baselines/databricks-apps-python/baseline.yaml similarity index 89% rename from .test/baselines/databricks-app-python/baseline.yaml rename to .test/baselines/databricks-apps-python/baseline.yaml index 300bbcf8..3a9bb677 100644 --- a/.test/baselines/databricks-app-python/baseline.yaml +++ b/.test/baselines/databricks-apps-python/baseline.yaml @@ -10,6 +10,6 @@ results_summary: status: RUNNING success: true run_id: 1fb85925959b453090dce7d3317559cf -skill_name: databricks-app-python +skill_name: databricks-apps-python test_count: 1 timestamp: '2026-02-12T20:17:59Z' diff --git a/.test/scripts/run_app_eval.py b/.test/scripts/run_app_eval.py index 17628bfb..66b93089 100644 --- a/.test/scripts/run_app_eval.py +++ b/.test/scripts/run_app_eval.py @@ -9,8 +9,8 @@ 5. Cleans up all test apps Usage: - DATABRICKS_CONFIG_PROFILE=ffe python run_app_eval.py databricks-app-python [--test-ids id1 id2] - DATABRICKS_CONFIG_PROFILE=ffe python run_app_eval.py databricks-app-python --keep # Don't delete apps after + DATABRICKS_CONFIG_PROFILE=ffe python run_app_eval.py databricks-apps-python [--test-ids id1 id2] + DATABRICKS_CONFIG_PROFILE=ffe python run_app_eval.py databricks-apps-python --keep # Don't delete apps after """ import argparse @@ -226,7 +226,7 @@ def detect_framework_yaml(python_code: str) -> str: def main(): parser = argparse.ArgumentParser(description="Run app skill integration tests on Databricks") - parser.add_argument("skill_name", help="Name of skill to evaluate (e.g., databricks-app-python)") + parser.add_argument("skill_name", help="Name of skill to evaluate (e.g., databricks-apps-python)") parser.add_argument("--test-ids", nargs="+", help="Specific test IDs to run") parser.add_argument("--keep", action="store_true", help="Don't delete apps after testing") args = parser.parse_args() diff --git a/.test/skills/_routing/ground_truth.yaml b/.test/skills/_routing/ground_truth.yaml index d89e1fd9..1733c338 100644 --- a/.test/skills/_routing/ground_truth.yaml +++ b/.test/skills/_routing/ground_truth.yaml @@ -148,7 +148,7 @@ test_cases: inputs: prompt: "Create a Streamlit app that shows sales data" expectations: - expected_skills: ["databricks-app-python"] + expected_skills: ["databricks-apps-python"] is_multi_skill: false metadata: category: "single_skill" @@ -159,7 +159,7 @@ test_cases: inputs: prompt: "Build a Dash app with interactive charts" expectations: - expected_skills: ["databricks-app-python"] + expected_skills: ["databricks-apps-python"] is_multi_skill: false metadata: category: "single_skill" @@ -170,7 +170,7 @@ test_cases: inputs: prompt: "Create a Gradio app for testing my ML model" expectations: - expected_skills: ["databricks-app-python"] + expected_skills: ["databricks-apps-python"] is_multi_skill: false metadata: category: "single_skill" @@ -181,7 +181,7 @@ test_cases: inputs: prompt: "Build a FastAPI app that serves data from a warehouse" expectations: - expected_skills: ["databricks-app-python"] + expected_skills: ["databricks-apps-python"] is_multi_skill: false metadata: category: "single_skill" @@ -192,7 +192,7 @@ test_cases: inputs: prompt: "Create a Reflex app for managing inventory" expectations: - expected_skills: ["databricks-app-python"] + expected_skills: ["databricks-apps-python"] is_multi_skill: false metadata: category: "single_skill" @@ -205,7 +205,7 @@ test_cases: prompt: "Build a Dash app and deploy it using DABs" expectations: expected_skills: - - "databricks-app-python" + - "databricks-apps-python" - "databricks-bundles" is_multi_skill: true metadata: @@ -218,7 +218,7 @@ test_cases: prompt: "Create a Streamlit app that stores data in Lakebase" expectations: expected_skills: - - "databricks-app-python" + - "databricks-apps-python" - "databricks-lakebase-provisioned" is_multi_skill: true metadata: @@ -231,7 +231,7 @@ test_cases: prompt: "Build a Gradio app that queries a model serving endpoint" expectations: expected_skills: - - "databricks-app-python" + - "databricks-apps-python" - "model-serving" is_multi_skill: true metadata: @@ -246,7 +246,7 @@ test_cases: expectations: expected_skills: - "databricks-app-apx" - - "databricks-app-python" + - "databricks-apps-python" is_multi_skill: true metadata: category: "multi_skill" diff --git a/.test/skills/databricks-app-python/candidates.yaml b/.test/skills/databricks-apps-python/candidates.yaml similarity index 100% rename from .test/skills/databricks-app-python/candidates.yaml rename to .test/skills/databricks-apps-python/candidates.yaml diff --git a/.test/skills/databricks-app-python/ground_truth.yaml b/.test/skills/databricks-apps-python/ground_truth.yaml similarity index 100% rename from .test/skills/databricks-app-python/ground_truth.yaml rename to .test/skills/databricks-apps-python/ground_truth.yaml diff --git a/.test/skills/databricks-app-python/manifest.yaml b/.test/skills/databricks-apps-python/manifest.yaml similarity index 91% rename from .test/skills/databricks-app-python/manifest.yaml rename to .test/skills/databricks-apps-python/manifest.yaml index 9ed23b19..ff02aa39 100644 --- a/.test/skills/databricks-app-python/manifest.yaml +++ b/.test/skills/databricks-apps-python/manifest.yaml @@ -1,6 +1,6 @@ skill: - name: "databricks-app-python" - source_path: "databricks-skills/databricks-app-python" + name: "databricks-apps-python" + source_path: "databricks-skills/databricks-apps-python" description: "Python Databricks Apps: Dash, Streamlit, Gradio, Flask, FastAPI, Reflex" tool_modules: [apps, serving] diff --git a/.test/src/skill_test/scorers/routing.py b/.test/src/skill_test/scorers/routing.py index fa32c073..e16bb2ac 100644 --- a/.test/src/skill_test/scorers/routing.py +++ b/.test/src/skill_test/scorers/routing.py @@ -32,7 +32,7 @@ "fastapi react", "react frontend", ], - "databricks-app-python": [ + "databricks-apps-python": [ "python app", "streamlit", "dash", diff --git a/.test/tests/test_scorers.py b/.test/tests/test_scorers.py index 63de0125..6df1d6c4 100644 --- a/.test/tests/test_scorers.py +++ b/.test/tests/test_scorers.py @@ -70,34 +70,34 @@ def test_detect_genie(self): assert "databricks-agent-bricks" in skills def test_detect_app_python_streamlit(self): - """Test detection of databricks-app-python via Streamlit.""" + """Test detection of databricks-apps-python via Streamlit.""" prompt = "Create a Streamlit app that shows sales data" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_app_python_dash(self): - """Test detection of databricks-app-python via Dash.""" + """Test detection of databricks-apps-python via Dash.""" prompt = "Build a Dash app with interactive charts" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_app_python_gradio(self): - """Test detection of databricks-app-python via Gradio.""" + """Test detection of databricks-apps-python via Gradio.""" prompt = "Create a Gradio app for testing my ML model" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_app_python_fastapi(self): - """Test detection of databricks-app-python via FastAPI.""" + """Test detection of databricks-apps-python via FastAPI.""" prompt = "Build a FastAPI app that serves data from a warehouse" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_app_python_reflex(self): - """Test detection of databricks-app-python via Reflex.""" + """Test detection of databricks-apps-python via Reflex.""" prompt = "Create a Reflex app for managing inventory" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_app_apx(self): """Test detection of databricks-app-apx.""" @@ -109,13 +109,13 @@ def test_detect_fastapi_react_matches_both(self): """Test that 'FastAPI React' matches both APX and Python app skills. 'fastapi react' triggers APX, while bare 'fastapi' also triggers - databricks-app-python. This is intentional — the router sees both + databricks-apps-python. This is intentional — the router sees both and picks the best fit. """ prompt = "Create a FastAPI React app for my dashboard" skills = detect_skills_from_prompt(prompt) assert "databricks-app-apx" in skills - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills def test_detect_lakebase(self): """Test detection of databricks-lakebase-provisioned skill.""" @@ -140,14 +140,14 @@ def test_detect_multi_app_lakebase(self): """Test detection of app + lakebase.""" prompt = "Create a Streamlit app that stores data in Lakebase" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills assert "databricks-lakebase-provisioned" in skills def test_detect_multi_app_serving(self): """Test detection of app + model serving.""" prompt = "Build a Gradio app that queries a model serving endpoint" skills = detect_skills_from_prompt(prompt) - assert "databricks-app-python" in skills + assert "databricks-apps-python" in skills assert "databricks-model-serving" in skills def test_detect_no_match(self): @@ -171,7 +171,7 @@ def test_all_skills_have_triggers(self): expected_skills = [ "databricks-spark-declarative-pipelines", "databricks-app-apx", - "databricks-app-python", + "databricks-apps-python", "databricks-bundles", "databricks-python-sdk", "databricks-jobs", diff --git a/databricks-builder-app/README.md b/databricks-builder-app/README.md index 59780939..4d7ef413 100644 --- a/databricks-builder-app/README.md +++ b/databricks-builder-app/README.md @@ -179,7 +179,7 @@ Skills provide specialized guidance for Databricks development tasks. They are m Skills include: - **databricks-bundles**: DABs configuration - **databricks-app-apx**: Full-stack apps with APX framework (FastAPI + React) -- **databricks-app-python**: Python apps with Dash, Streamlit, Flask +- **databricks-apps-python**: Python apps with Dash, Streamlit, Flask - **databricks-python-sdk**: Python SDK patterns - **databricks-mlflow-evaluation**: MLflow evaluation and trace analysis - **databricks-spark-declarative-pipelines**: Spark Declarative Pipelines (SDP) development @@ -313,7 +313,7 @@ Skills are loaded from `../databricks-skills/` and filtered by the `ENABLED_SKIL - `databricks-spark-declarative-pipelines`: SDP/DLT pipeline development - `databricks-synthetic-data-gen`: Creating test datasets - `databricks-app-apx`: Full-stack apps with React (APX framework) -- `databricks-app-python`: Python apps with Dash, Streamlit, Flask +- `databricks-apps-python`: Python apps with Dash, Streamlit, Flask **Adding custom skills:** 1. Create a new directory in `../databricks-skills/` diff --git a/databricks-builder-app/app.yaml_backup b/databricks-builder-app/app.yaml_backup index 1349cb8c..e9c6f688 100644 --- a/databricks-builder-app/app.yaml_backup +++ b/databricks-builder-app/app.yaml_backup @@ -30,7 +30,7 @@ env: # ============================================================================= # Comma-separated list of skills to enable - name: ENABLED_SKILLS - value: "databricks-asset-bundles,databricks-agent-bricks,databricks-aibi-dashboards,databricks-app-apx,databricks-app-python,databricks-config,databricks-docs,databricks-jobs,databricks-python-sdk,databricks-unity-catalog,mlflow-evaluation,spark-declarative-pipelines,synthetic-data-generation,unstructured-pdf-generation" + value: "databricks-asset-bundles,databricks-agent-bricks,databricks-aibi-dashboards,databricks-app-apx,databricks-apps-python,databricks-config,databricks-docs,databricks-jobs,databricks-python-sdk,databricks-unity-catalog,mlflow-evaluation,spark-declarative-pipelines,synthetic-data-generation,unstructured-pdf-generation" - name: SKILLS_ONLY_MODE value: "false" diff --git a/databricks-builder-app/client/src/pages/DocPage.tsx b/databricks-builder-app/client/src/pages/DocPage.tsx index 34f706b0..ca75328e 100644 --- a/databricks-builder-app/client/src/pages/DocPage.tsx +++ b/databricks-builder-app/client/src/pages/DocPage.tsx @@ -92,7 +92,7 @@ function OverviewSection() { Skills explain how to do things and reference the tools from databricks-tools-core.

- {['databricks-bundles/', 'databricks-app-apx/', 'databricks-app-python/', 'databricks-python-sdk/', 'databricks-mlflow-evaluation/', 'databricks-spark-declarative-pipelines/', 'databricks-synthetic-data-gen/'].map((skill) => ( + {['databricks-bundles/', 'databricks-app-apx/', 'databricks-apps-python/', 'databricks-python-sdk/', 'databricks-mlflow-evaluation/', 'databricks-spark-declarative-pipelines/', 'databricks-synthetic-data-gen/'].map((skill) => ( {skill} diff --git a/databricks-builder-app/server/services/skills_manager.py b/databricks-builder-app/server/services/skills_manager.py index 0732d5db..07a1f256 100644 --- a/databricks-builder-app/server/services/skills_manager.py +++ b/databricks-builder-app/server/services/skills_manager.py @@ -48,7 +48,7 @@ # APX (FastAPI+React) and Python (Dash/Streamlit/etc.) share the same # app lifecycle tools — the skill content differs, not the MCP operations. 'databricks-app-apx': ['manage_app'], - 'databricks-app-python': ['manage_app'], + 'databricks-apps-python': ['manage_app'], } diff --git a/databricks-builder-app/server/services/system_prompt.py b/databricks-builder-app/server/services/system_prompt.py index ea7c9dc5..4b34772a 100644 --- a/databricks-builder-app/server/services/system_prompt.py +++ b/databricks-builder-app/server/services/system_prompt.py @@ -12,7 +12,7 @@ ('SDK, API, Databricks client', 'databricks-python-sdk'), ('Unity Catalog, tables, volumes, schemas', 'databricks-unity-catalog'), ('Agent, chatbot, AI assistant', 'databricks-agent-bricks'), - ('App deployment, web app', 'databricks-app-python'), + ('App deployment, web app', 'databricks-apps-python'), ] diff --git a/databricks-mcp-server/databricks_mcp_server/tools/apps.py b/databricks-mcp-server/databricks_mcp_server/tools/apps.py index 34c7474d..7939b148 100644 --- a/databricks-mcp-server/databricks_mcp_server/tools/apps.py +++ b/databricks-mcp-server/databricks_mcp_server/tools/apps.py @@ -81,7 +81,7 @@ def manage_app( - delete: Delete an app. Requires name. Returns: {name, status}. - See databricks-app-python skill for app development guidance.""" + See databricks-apps-python skill for app development guidance.""" act = action.lower() if act == "create_or_update": diff --git a/databricks-skills/README.md b/databricks-skills/README.md index a5e50868..28151e74 100644 --- a/databricks-skills/README.md +++ b/databricks-skills/README.md @@ -102,7 +102,7 @@ cp -r ai-dev-kit/databricks-skills/databricks-agent-bricks .claude/skills/ ### 🚀 Development & Deployment - **databricks-bundles** - DABs for multi-environment deployments - **databricks-app-apx** - Full-stack apps (FastAPI + React) -- **databricks-app-python** - Python web apps (Dash, Streamlit, Flask) with foundation model integration +- **databricks-apps-python** - Python web apps (Dash, Streamlit, Flask) with foundation model integration - **databricks-python-sdk** - Python SDK, Connect, CLI, REST API - **databricks-config** - Profile authentication setup - **databricks-lakebase-autoscale** - Lakebase Autoscaling managed PostgreSQL with branching, scale-to-zero, reverse ETL diff --git a/databricks-skills/databricks-app-python/1-authorization.md b/databricks-skills/databricks-apps-python/1-authorization.md similarity index 100% rename from databricks-skills/databricks-app-python/1-authorization.md rename to databricks-skills/databricks-apps-python/1-authorization.md diff --git a/databricks-skills/databricks-app-python/2-app-resources.md b/databricks-skills/databricks-apps-python/2-app-resources.md similarity index 100% rename from databricks-skills/databricks-app-python/2-app-resources.md rename to databricks-skills/databricks-apps-python/2-app-resources.md diff --git a/databricks-skills/databricks-app-python/3-frameworks.md b/databricks-skills/databricks-apps-python/3-frameworks.md similarity index 100% rename from databricks-skills/databricks-app-python/3-frameworks.md rename to databricks-skills/databricks-apps-python/3-frameworks.md diff --git a/databricks-skills/databricks-app-python/4-deployment.md b/databricks-skills/databricks-apps-python/4-deployment.md similarity index 100% rename from databricks-skills/databricks-app-python/4-deployment.md rename to databricks-skills/databricks-apps-python/4-deployment.md diff --git a/databricks-skills/databricks-app-python/5-lakebase.md b/databricks-skills/databricks-apps-python/5-lakebase.md similarity index 100% rename from databricks-skills/databricks-app-python/5-lakebase.md rename to databricks-skills/databricks-apps-python/5-lakebase.md diff --git a/databricks-skills/databricks-app-python/6-cli-approach.md b/databricks-skills/databricks-apps-python/6-cli-approach.md similarity index 100% rename from databricks-skills/databricks-app-python/6-cli-approach.md rename to databricks-skills/databricks-apps-python/6-cli-approach.md diff --git a/databricks-skills/databricks-app-python/SKILL.md b/databricks-skills/databricks-apps-python/SKILL.md similarity index 76% rename from databricks-skills/databricks-app-python/SKILL.md rename to databricks-skills/databricks-apps-python/SKILL.md index c5a595a5..99850a01 100644 --- a/databricks-skills/databricks-app-python/SKILL.md +++ b/databricks-skills/databricks-apps-python/SKILL.md @@ -1,24 +1,71 @@ --- -name: databricks-app-python -description: "Builds Python-based Databricks applications using Dash, Streamlit, Gradio, Flask, FastAPI, or Reflex. Handles OAuth authorization (app and user auth), app resources, SQL warehouse and Lakebase connectivity, model serving integration, foundation model APIs, LLM integration, and deployment. Use when building Python web apps, dashboards, ML demos, or REST APIs for Databricks, or when the user mentions Streamlit, Dash, Gradio, Flask, FastAPI, Reflex, or Databricks app." +name: databricks-apps-python +description: "Builds Databricks applications. Prefers AppKit (TypeScript + React SDK) for new apps; falls back to Python frameworks (Dash, Streamlit, Gradio, Flask, FastAPI, Reflex) when Python is required. Handles OAuth authorization, app resources, SQL warehouse and Lakebase connectivity, model serving, foundation model APIs, and deployment. Use when building web apps, dashboards, ML demos, or REST APIs for Databricks, or when the user mentions AppKit, Streamlit, Dash, Gradio, Flask, FastAPI, Reflex, or Databricks app." --- -# Databricks Python Application +# Databricks Applications -Build Python-based Databricks applications. For full examples and recipes, see the **[Databricks Apps Cookbook](https://apps-cookbook.dev/)**. +Build Databricks applications. For full examples and recipes, see the **[Databricks Apps Cookbook](https://apps-cookbook.dev/)**. --- -## Critical Rules (always follow) +## AppKit (Preferred for New Apps) -- **MUST** confirm framework choice or use [Framework Selection](#framework-selection) below +**[AppKit](https://github.com/databricks/appkit)** is the recommended SDK for new Databricks apps. It is a TypeScript + React SDK with a plugin architecture, built-in caching, telemetry, and end-to-end type safety. + +### Requirements +- Node.js v22+ +- Databricks CLI v0.295.0+ + +### Scaffold a new app +```bash +databricks apps init +``` +This interactive command scaffolds the full project, installs dependencies, and optionally deploys. + +### Deploy +```bash +databricks apps deploy +``` + +### AppKit plugins +| Plugin | Purpose | +|--------|---------| +| **Analytics** | SQL queries against Databricks SQL Warehouses — file-based, typed, cached | +| **Genie** | Conversational AI/BI interface with natural language queries | +| **Files** | Browse/upload Unity Catalog Volumes | +| **Lakebase** | OLTP PostgreSQL via Lakebase with OAuth token management | + +### AI-assisted development +```bash +# Install agent skills for AI-powered scaffolding +databricks experimental aitools skills install + +# Query AppKit docs inline +npx @databricks/appkit docs "your question here" +``` + +### AppKit documentation +- **[AppKit Docs](https://databricks.github.io/appkit/docs/)** — getting started, plugins, API reference +- **[AI-assisted development](https://databricks.github.io/appkit/docs/development/ai-assisted-development)** — guidance for code assistants +- **[llms.txt](https://databricks.github.io/appkit/llms.txt)** — machine-readable docs for AI context + +--- + +## Python Apps (alternative) + +Use Python when: the team is Python-only, you need Streamlit/Dash/Gradio, or you are extending an existing Python app. + +## Critical Rules for Python apps (always follow) + +- **MUST** confirm framework choice or use [Python Framework Selection](#python-framework-selection) below - **MUST** use SDK `Config()` for authentication (never hardcode tokens) - **MUST** use `app.yaml` `valueFrom` for resources (never hardcode resource IDs) - **MUST** use `dash-bootstrap-components` for Dash app layout and styling - **MUST** use `@st.cache_resource` for Streamlit database connections - **MUST** deploy Flask with Gunicorn, FastAPI with uvicorn (not dev servers) -## Required Steps +## Required Steps for Python apps Copy this checklist and verify each item: ``` @@ -31,7 +78,7 @@ Copy this checklist and verify each item: --- -## Framework Selection +## Python Framework Selection | Framework | Best For | app.yaml Command | |-----------|----------|------------------| @@ -82,7 +129,7 @@ Copy this checklist and verify each item: 1. Determine the task type: - **New app from scratch?** → Use [Framework Selection](#framework-selection), then read [3-frameworks.md](3-frameworks.md) + **New app from scratch?** → Use [AppKit](#appkit-preferred-for-new-apps) (`databricks apps init`). Fall back to [Python Framework Selection](#python-framework-selection) only if Python is required. **Setting up authorization?** → Read [1-authorization.md](1-authorization.md) **Connecting to data/resources?** → Read [2-app-resources.md](2-app-resources.md) **Using Lakebase (PostgreSQL)?** → Read [5-lakebase.md](5-lakebase.md) @@ -195,6 +242,7 @@ class EntityIn(BaseModel): ## Official Documentation +- **[AppKit](https://databricks.github.io/appkit/docs/)** — preferred SDK for new apps (TypeScript + React) - **[Databricks Apps Overview](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/)** — main docs hub - **[Apps Cookbook](https://apps-cookbook.dev/)** — ready-to-use code snippets (Streamlit, Dash, Reflex, FastAPI) - **[Authorization](https://docs.databricks.com/aws/en/dev-tools/databricks-apps/auth)** — app auth and user auth diff --git a/databricks-skills/databricks-app-python/examples/fm-minimal-chat.py b/databricks-skills/databricks-apps-python/examples/fm-minimal-chat.py similarity index 100% rename from databricks-skills/databricks-app-python/examples/fm-minimal-chat.py rename to databricks-skills/databricks-apps-python/examples/fm-minimal-chat.py diff --git a/databricks-skills/databricks-app-python/examples/fm-parallel-calls.py b/databricks-skills/databricks-apps-python/examples/fm-parallel-calls.py similarity index 100% rename from databricks-skills/databricks-app-python/examples/fm-parallel-calls.py rename to databricks-skills/databricks-apps-python/examples/fm-parallel-calls.py diff --git a/databricks-skills/databricks-app-python/examples/fm-structured-outputs.py b/databricks-skills/databricks-apps-python/examples/fm-structured-outputs.py similarity index 100% rename from databricks-skills/databricks-app-python/examples/fm-structured-outputs.py rename to databricks-skills/databricks-apps-python/examples/fm-structured-outputs.py diff --git a/databricks-skills/databricks-app-python/examples/llm_config.py b/databricks-skills/databricks-apps-python/examples/llm_config.py similarity index 100% rename from databricks-skills/databricks-app-python/examples/llm_config.py rename to databricks-skills/databricks-apps-python/examples/llm_config.py diff --git a/databricks-skills/databricks-bundles/SKILL.md b/databricks-skills/databricks-bundles/SKILL.md index 5b010512..3cff53a1 100644 --- a/databricks-skills/databricks-bundles/SKILL.md +++ b/databricks-skills/databricks-bundles/SKILL.md @@ -311,7 +311,7 @@ databricks bundle destroy -t prod --auto-approve - **[databricks-spark-declarative-pipelines](../databricks-spark-declarative-pipelines/SKILL.md)** - pipeline definitions referenced by DABs - **[databricks-app-apx](../databricks-app-apx/SKILL.md)** - app deployment via DABs -- **[databricks-app-python](../databricks-app-python/SKILL.md)** - Python app deployment via DABs +- **[databricks-apps-python](../databricks-apps-python/SKILL.md)** - Python app deployment via DABs - **[databricks-config](../databricks-config/SKILL.md)** - profile and authentication setup for CLI/SDK - **[databricks-jobs](../databricks-jobs/SKILL.md)** - job orchestration managed through bundles diff --git a/databricks-skills/databricks-lakebase-autoscale/SKILL.md b/databricks-skills/databricks-lakebase-autoscale/SKILL.md index 82fcbcdd..56bfd915 100644 --- a/databricks-skills/databricks-lakebase-autoscale/SKILL.md +++ b/databricks-skills/databricks-lakebase-autoscale/SKILL.md @@ -227,6 +227,6 @@ Custom billing tags / serverless budget policies. ## Related Skills -- [databricks-app-apx](../databricks-app-apx/SKILL.md), [databricks-app-python](../databricks-app-python/SKILL.md) — apps using Lakebase +- [databricks-app-apx](../databricks-app-apx/SKILL.md), [databricks-apps-python](../databricks-apps-python/SKILL.md) — apps using Lakebase - [databricks-bundles](../databricks-bundles/SKILL.md) — bundle deploys with Lakebase resources - [databricks-jobs](../databricks-jobs/SKILL.md) — scheduling reverse ETL pipelines diff --git a/databricks-skills/install_skills.sh b/databricks-skills/install_skills.sh index 67866942..09381eb1 100755 --- a/databricks-skills/install_skills.sh +++ b/databricks-skills/install_skills.sh @@ -47,7 +47,7 @@ MLFLOW_REPO_RAW_URL="https://raw.githubusercontent.com/mlflow/skills" MLFLOW_REPO_REF="main" # Databricks skills (hosted in this repo) -DATABRICKS_SKILLS="databricks-agent-bricks databricks-ai-functions databricks-aibi-dashboards databricks-bundles databricks-app-python databricks-config databricks-dbsql databricks-docs databricks-genie databricks-iceberg databricks-jobs databricks-lakebase-autoscale databricks-metric-views databricks-mlflow-evaluation databricks-model-serving databricks-python-sdk databricks-execution-compute databricks-spark-declarative-pipelines databricks-spark-structured-streaming databricks-synthetic-data-gen databricks-unity-catalog databricks-unstructured-pdf-generation databricks-vector-search databricks-zerobus-ingest spark-python-data-source" +DATABRICKS_SKILLS="databricks-agent-bricks databricks-ai-functions databricks-aibi-dashboards databricks-bundles databricks-apps-python databricks-config databricks-dbsql databricks-docs databricks-genie databricks-iceberg databricks-jobs databricks-lakebase-autoscale databricks-metric-views databricks-mlflow-evaluation databricks-model-serving databricks-python-sdk databricks-execution-compute databricks-spark-declarative-pipelines databricks-spark-structured-streaming databricks-synthetic-data-gen databricks-unity-catalog databricks-unstructured-pdf-generation databricks-vector-search databricks-zerobus-ingest spark-python-data-source" # MLflow skills (fetched from mlflow/skills repo) MLFLOW_SKILLS="agent-evaluation analyze-mlflow-chat-session analyze-mlflow-trace instrumenting-with-mlflow-tracing mlflow-onboarding querying-mlflow-metrics retrieving-mlflow-traces searching-mlflow-docs" @@ -72,7 +72,7 @@ get_skill_description() { "databricks-aibi-dashboards") echo "Databricks AI/BI Dashboards - create and manage dashboards" ;; "databricks-bundles") echo "Databricks Asset Bundles - deployment and configuration" ;; "databricks-app-apx") echo "Databricks Apps with React/Next.js (APX framework)" ;; - "databricks-app-python") echo "Databricks Apps with Python (Dash, Streamlit) and foundation model integration" ;; + "databricks-apps-python") echo "Databricks Apps with Python (Dash, Streamlit) and foundation model integration" ;; "databricks-config") echo "Profile authentication setup for Databricks" ;; "databricks-dbsql") echo "Databricks SQL - SQL scripting, MVs, geospatial, AI functions, federation" ;; "databricks-docs") echo "Documentation reference via llms.txt" ;; @@ -118,7 +118,7 @@ get_skill_extra_files() { "databricks-bundles") echo "alerts_guidance.md SDP_guidance.md" ;; "databricks-iceberg") echo "1-managed-iceberg-tables.md 2-uniform-and-compatibility.md 3-iceberg-rest-catalog.md 4-snowflake-interop.md 5-external-engine-interop.md" ;; "databricks-app-apx") echo "backend-patterns.md best-practices.md frontend-patterns.md" ;; - "databricks-app-python") echo "1-authorization.md 2-app-resources.md 3-frameworks.md 4-deployment.md 5-lakebase.md 6-cli-approach.md examples/llm_config.py examples/fm-minimal-chat.py examples/fm-parallel-calls.py examples/fm-structured-outputs.py" ;; + "databricks-apps-python") echo "1-authorization.md 2-app-resources.md 3-frameworks.md 4-deployment.md 5-lakebase.md 6-cli-approach.md examples/llm_config.py examples/fm-minimal-chat.py examples/fm-parallel-calls.py examples/fm-structured-outputs.py" ;; "databricks-jobs") echo "task-types.md triggers-schedules.md notifications-monitoring.md examples.md" ;; "databricks-python-sdk") echo "doc-index.md examples/1-authentication.py examples/2-clusters-and-jobs.py examples/3-sql-and-warehouses.py examples/4-unity-catalog.py examples/5-serving-and-vector-search.py" ;; "databricks-unity-catalog") echo "5-system-tables.md" ;; diff --git a/install.ps1 b/install.ps1 index 53e75af7..395be3a1 100644 --- a/install.ps1 +++ b/install.ps1 @@ -83,7 +83,7 @@ $script:Channel = if ($env:AIDEVKIT_CHANNEL) { $env:AIDEVKIT_CHANNEL } else # Databricks skills (bundled in repo) $script:Skills = @( - "databricks-agent-bricks", "databricks-aibi-dashboards", "databricks-app-python", + "databricks-agent-bricks", "databricks-aibi-dashboards", "databricks-apps-python", "databricks-bundles", "databricks-config", "databricks-dbsql", "databricks-docs", "databricks-genie", "databricks-iceberg", "databricks-jobs", "databricks-lakebase-autoscale", "databricks-lakebase-provisioned", "databricks-metric-views", "databricks-mlflow-evaluation", "databricks-model-serving", "databricks-ai-functions", @@ -104,6 +104,11 @@ $MlflowRawUrl = "https://raw.githubusercontent.com/mlflow/skills/main" $script:ApxSkills = @("databricks-app-apx") $ApxRawUrl = "https://raw.githubusercontent.com/databricks-solutions/apx/main/skills/apx" +# Agent skills (fetched from databricks/databricks-agent-skills repo) +$script:AgentSkills = @("databricks-core:databricks", "databricks-apps", "databricks-lakebase") +$AgentSkillsRawUrl = "https://raw.githubusercontent.com/databricks/databricks-agent-skills/main/skills" +$AgentSkillsApiUrl = "https://api.github.com/repos/databricks/databricks-agent-skills/git/trees/main?recursive=1" + # ─── Skill profiles ────────────────────────────────────────── $script:CoreSkills = @("databricks-config", "databricks-docs", "databricks-python-sdk", "databricks-unity-catalog") @@ -127,15 +132,17 @@ $script:ProfileAiMlMlflow = @( "retrieving-mlflow-traces", "searching-mlflow-docs" ) $script:ProfileAppDeveloper = @( - "databricks-app-python", "databricks-app-apx", "databricks-lakebase-autoscale", + "databricks-apps-python", "databricks-app-apx", "databricks-lakebase-autoscale", "databricks-lakebase-provisioned", "databricks-model-serving", "databricks-dbsql", "databricks-jobs", "databricks-bundles" ) +$script:ProfileAppDeveloperAgent = @("databricks-core:databricks", "databricks-apps", "databricks-lakebase") # Selected skills (populated during profile selection) $script:SelectedSkills = @() $script:SelectedMlflowSkills = @() $script:SelectedApxSkills = @() +$script:SelectedAgentSkills = @() # ─── --list-skills handler ──────────────────────────────────── if ($script:ListSkills) { @@ -179,6 +186,10 @@ if ($script:ListSkills) { Write-Host "--------------------------------" foreach ($s in $script:ApxSkills) { Write-Host " $s" } Write-Host "" + Write-Host "Agent Skills (from databricks/databricks-agent-skills repo)" -ForegroundColor White + Write-Host "--------------------------------" + foreach ($s in $script:AgentSkills) { Write-Host " $($s -replace '^.*:', '')" } + Write-Host "" Write-Host "Usage: .\install.ps1 --skills-profile data-engineer,ai-ml-engineer" -ForegroundColor DarkGray Write-Host " .\install.ps1 --skills databricks-jobs,databricks-dbsql" -ForegroundColor DarkGray Write-Host "" @@ -915,12 +926,15 @@ function Resolve-Skills { $dbSkills = @() + $script:CoreSkills $mlflowSkills = @() $apxSkills = @() + $agentSkills = @() foreach ($skill in $userList) { $skill = $skill.Trim() if ($script:MlflowSkills -contains $skill) { $mlflowSkills += $skill } elseif ($script:ApxSkills -contains $skill) { $apxSkills += $skill + } elseif ($script:AgentSkills | ForEach-Object { $_ -replace '^.*:', '' } | Where-Object { $_ -eq $skill }) { + $agentSkills += ($script:AgentSkills | Where-Object { ($_ -replace '^.*:', '') -eq $skill }) } else { $dbSkills += $skill } @@ -928,6 +942,7 @@ function Resolve-Skills { $script:SelectedSkills = $dbSkills | Select-Object -Unique $script:SelectedMlflowSkills = $mlflowSkills | Select-Object -Unique $script:SelectedApxSkills = $apxSkills | Select-Object -Unique + $script:SelectedAgentSkills = $agentSkills | Select-Object -Unique return } @@ -936,6 +951,7 @@ function Resolve-Skills { $script:SelectedSkills = $script:Skills $script:SelectedMlflowSkills = $script:MlflowSkills $script:SelectedApxSkills = $script:ApxSkills + $script:SelectedAgentSkills = $script:AgentSkills return } @@ -943,6 +959,7 @@ function Resolve-Skills { $dbSkills = @() + $script:CoreSkills $mlflowSkills = @() $apxSkills = @() + $agentSkills = @() foreach ($profile in ($script:SkillsProfile -split ',')) { $profile = $profile.Trim() @@ -951,6 +968,7 @@ function Resolve-Skills { $script:SelectedSkills = $script:Skills $script:SelectedMlflowSkills = $script:MlflowSkills $script:SelectedApxSkills = $script:ApxSkills + $script:SelectedAgentSkills = $script:AgentSkills return } "data-engineer" { $dbSkills += $script:ProfileDataEngineer } @@ -962,6 +980,7 @@ function Resolve-Skills { "app-developer" { $dbSkills += $script:ProfileAppDeveloper $apxSkills += $script:ApxSkills + $agentSkills += $script:ProfileAppDeveloperAgent } default { Write-Warn "Unknown skill profile: $profile (ignored)" } } @@ -970,6 +989,7 @@ function Resolve-Skills { $script:SelectedSkills = $dbSkills | Select-Object -Unique $script:SelectedMlflowSkills = $mlflowSkills | Select-Object -Unique $script:SelectedApxSkills = $apxSkills | Select-Object -Unique + $script:SelectedAgentSkills = $agentSkills | Select-Object -Unique } function Invoke-PromptSkillsProfile { @@ -1160,7 +1180,7 @@ function Invoke-PromptCustomSkills { "data-engineer" { $preselected += $script:ProfileDataEngineer } "analyst" { $preselected += $script:ProfileAnalyst } "ai-ml-engineer" { $preselected += $script:ProfileAiMlEngineer + $script:ProfileAiMlMlflow } - "app-developer" { $preselected += $script:ProfileAppDeveloper + $script:ApxSkills } + "app-developer" { $preselected += $script:ProfileAppDeveloper + $script:ApxSkills + $script:ProfileAppDeveloperAgent } } } @@ -1189,8 +1209,11 @@ function Invoke-PromptCustomSkills { @{ Label = "Synthetic Data"; Value = "databricks-synthetic-data-gen"; State = ($preselected -contains "databricks-synthetic-data-gen"); Hint = "Generate test data" } @{ Label = "Lakebase Autoscale"; Value = "databricks-lakebase-autoscale"; State = ($preselected -contains "databricks-lakebase-autoscale"); Hint = "Managed PostgreSQL" } @{ Label = "Lakebase Provisioned"; Value = "databricks-lakebase-provisioned"; State = ($preselected -contains "databricks-lakebase-provisioned"); Hint = "Provisioned PostgreSQL" } - @{ Label = "App Python"; Value = "databricks-app-python"; State = ($preselected -contains "databricks-app-python"); Hint = "Dash, Streamlit, Flask" } + @{ Label = "App (AppKit + Python)"; Value = "databricks-apps-python"; State = ($preselected -contains "databricks-apps-python"); Hint = "AppKit, Dash, Streamlit, Flask" } @{ Label = "App APX"; Value = "databricks-app-apx"; State = ($preselected -contains "databricks-app-apx"); Hint = "FastAPI + React" } + @{ Label = "Agent: Databricks"; Value = "databricks"; State = ($preselected -contains "databricks"); Hint = "CLI auth, data exploration" } + @{ Label = "Agent: Apps"; Value = "databricks-apps"; State = ($preselected -contains "databricks-apps"); Hint = "AppKit + all frameworks" } + @{ Label = "Agent: Lakebase"; Value = "databricks-lakebase"; State = ($preselected -contains "databricks-lakebase"); Hint = "Lakebase OLTP" } @{ Label = "MLflow Onboarding"; Value = "mlflow-onboarding"; State = ($preselected -contains "mlflow-onboarding"); Hint = "Getting started" } @{ Label = "Agent Evaluation"; Value = "agent-evaluation"; State = ($preselected -contains "agent-evaluation"); Hint = "Evaluate AI agents" } @{ Label = "MLflow Tracing"; Value = "instrumenting-with-mlflow-tracing"; State = ($preselected -contains "instrumenting-with-mlflow-tracing"); Hint = "Instrument with tracing" } @@ -1259,7 +1282,8 @@ function Install-Skills { $dbCount = $script:SelectedSkills.Count $mlflowCount = $script:SelectedMlflowSkills.Count $apxCount = $script:SelectedApxSkills.Count - $totalCount = $dbCount + $mlflowCount + $apxCount + $agentCount = $script:SelectedAgentSkills.Count + $totalCount = $dbCount + $mlflowCount + $apxCount + $agentCount Write-Msg "Installing $totalCount skills" # Build set of all skills being installed now @@ -1267,6 +1291,7 @@ function Install-Skills { $allNewSkills += $script:SelectedSkills $allNewSkills += $script:SelectedMlflowSkills $allNewSkills += $script:SelectedApxSkills + $allNewSkills += $script:SelectedAgentSkills | ForEach-Object { $_ -replace '^.*:', '' } # Clean up previously installed skills that are no longer selected # Check scope-local manifest first, fall back to global for upgrades from older versions @@ -1361,6 +1386,58 @@ function Install-Skills { $ErrorActionPreference = $prevEAP2 Write-Ok "APX skills ($apxCount) -> $shortDir" } + + # Install Agent skills from databricks/databricks-agent-skills repo + if ($script:SelectedAgentSkills.Count -gt 0) { + # Fetch the full repo tree once (single API call) for all skills + $agentTree = $null + try { + $agentTree = Invoke-WebRequest -Uri $AgentSkillsApiUrl -UseBasicParsing -ErrorAction Stop | Select-Object -ExpandProperty Content + } catch { + Write-Warn "Could not fetch agent skills tree from GitHub API" + } + if ($agentTree) { + $prevEAP3 = $ErrorActionPreference; $ErrorActionPreference = "Continue" + foreach ($entry in $script:SelectedAgentSkills) { + $srcName = ($entry -split ':')[0] + $installName = ($entry -replace '^.*:', '') + $destDir = Join-Path $dir $installName + if (-not (Test-Path $destDir)) { + New-Item -ItemType Directory -Path $destDir -Force | Out-Null + } + # Extract all file paths under skills// that contain a dot (i.e. are files not dirs) + $filePaths = [regex]::Matches($agentTree, '"path":"(skills/' + [regex]::Escape($srcName) + '/[^"]*\.[^"]*)"') | + ForEach-Object { $_.Groups[1].Value } + if (-not $filePaths) { + Remove-Item $destDir -ErrorAction SilentlyContinue + Write-Warn "Could not fetch agent skill '$srcName'" + continue + } + $okFlag = $true + foreach ($filePath in $filePaths) { + $rel = $filePath.Substring("skills/$srcName/".Length) + $dest = Join-Path $destDir ($rel -replace '/', '\') + $destParent = Split-Path $dest -Parent + if (-not (Test-Path $destParent)) { + New-Item -ItemType Directory -Path $destParent -Force | Out-Null + } + try { + Invoke-WebRequest -Uri "$AgentSkillsRawUrl/$srcName/$rel" -OutFile $dest -UseBasicParsing -ErrorAction Stop + } catch { + $okFlag = $false + } + } + if ($okFlag) { + $manifestEntries += "$dir|$installName" + } else { + Remove-Item -Recurse -Force $destDir -ErrorAction SilentlyContinue + Write-Warn "Could not install agent skill '$srcName'" + } + } + $ErrorActionPreference = $prevEAP3 + } + Write-Ok "Agent skills ($agentCount) -> $shortDir" + } } # Save manifest and profile to scope-local state directory @@ -2124,7 +2201,7 @@ function Invoke-Main { Write-Host " MCP server: " -NoNewline; Write-Host $script:InstallDir -ForegroundColor Green } if ($script:InstallSkills) { - $skTotal = $script:SelectedSkills.Count + $script:SelectedMlflowSkills.Count + $script:SelectedApxSkills.Count + $skTotal = $script:SelectedSkills.Count + $script:SelectedMlflowSkills.Count + $script:SelectedApxSkills.Count + $script:SelectedAgentSkills.Count if (-not [string]::IsNullOrWhiteSpace($script:UserSkills)) { Write-Host " Skills: " -NoNewline; Write-Host "custom selection ($skTotal skills)" -ForegroundColor Green } else { diff --git a/install.sh b/install.sh index 24b7a618..ad667798 100644 --- a/install.sh +++ b/install.sh @@ -102,7 +102,7 @@ MIN_SDK_VERSION="0.85.0" G='\033[0;32m' Y='\033[1;33m' R='\033[0;31m' BL='\033[0;34m' B='\033[1m' D='\033[2m' N='\033[0m' # Databricks skills (bundled in repo) -SKILLS="databricks-agent-bricks databricks-ai-functions databricks-aibi-dashboards databricks-app-python databricks-bundles databricks-config databricks-dbsql databricks-docs databricks-genie databricks-iceberg databricks-jobs databricks-lakebase-autoscale databricks-lakebase-provisioned databricks-metric-views databricks-mlflow-evaluation databricks-model-serving databricks-python-sdk databricks-spark-declarative-pipelines databricks-spark-structured-streaming databricks-synthetic-data-gen databricks-unity-catalog databricks-unstructured-pdf-generation databricks-vector-search databricks-zerobus-ingest spark-python-data-source" +SKILLS="databricks-agent-bricks databricks-ai-functions databricks-aibi-dashboards databricks-apps-python databricks-bundles databricks-config databricks-dbsql databricks-docs databricks-genie databricks-iceberg databricks-jobs databricks-lakebase-autoscale databricks-lakebase-provisioned databricks-metric-views databricks-mlflow-evaluation databricks-model-serving databricks-python-sdk databricks-spark-declarative-pipelines databricks-spark-structured-streaming databricks-synthetic-data-gen databricks-unity-catalog databricks-unstructured-pdf-generation databricks-vector-search databricks-zerobus-ingest spark-python-data-source" # MLflow skills (fetched from mlflow/skills repo) MLFLOW_SKILLS="agent-evaluation analyze-mlflow-chat-session analyze-mlflow-trace instrumenting-with-mlflow-tracing mlflow-onboarding querying-mlflow-metrics retrieving-mlflow-traces searching-mlflow-docs" @@ -112,6 +112,11 @@ MLFLOW_RAW_URL="https://raw.githubusercontent.com/mlflow/skills/main" APX_SKILLS="databricks-app-apx" APX_RAW_URL="https://raw.githubusercontent.com/databricks-solutions/apx/main/skills/apx" +# Agent skills (fetched from databricks/databricks-agent-skills repo) +AGENT_SKILLS="databricks-core:databricks databricks-apps databricks-lakebase" +AGENT_SKILLS_RAW_URL="https://raw.githubusercontent.com/databricks/databricks-agent-skills/main/skills" +AGENT_SKILLS_API_URL="https://api.github.com/repos/databricks/databricks-agent-skills/git/trees/main?recursive=1" + # ─── Skill profiles ────────────────────────────────────────── # Core skills always installed regardless of profile selection CORE_SKILLS="databricks-config databricks-docs databricks-python-sdk databricks-unity-catalog" @@ -121,12 +126,14 @@ PROFILE_DATA_ENGINEER="databricks-spark-declarative-pipelines databricks-spark-s PROFILE_ANALYST="databricks-aibi-dashboards databricks-dbsql databricks-genie databricks-metric-views" PROFILE_AIML_ENGINEER="databricks-agent-bricks databricks-ai-functions databricks-vector-search databricks-model-serving databricks-genie databricks-unstructured-pdf-generation databricks-mlflow-evaluation databricks-synthetic-data-gen databricks-jobs" PROFILE_AIML_MLFLOW="agent-evaluation analyze-mlflow-chat-session analyze-mlflow-trace instrumenting-with-mlflow-tracing mlflow-onboarding querying-mlflow-metrics retrieving-mlflow-traces searching-mlflow-docs" -PROFILE_APP_DEVELOPER="databricks-app-python databricks-app-apx databricks-lakebase-autoscale databricks-lakebase-provisioned databricks-model-serving databricks-dbsql databricks-jobs databricks-bundles" +PROFILE_APP_DEVELOPER="databricks-apps-python databricks-app-apx databricks-lakebase-autoscale databricks-lakebase-provisioned databricks-model-serving databricks-dbsql databricks-jobs databricks-bundles" +PROFILE_APP_DEVELOPER_AGENT="databricks-core:databricks databricks-apps databricks-lakebase" # Selected skills (populated during profile selection) SELECTED_SKILLS="" SELECTED_MLFLOW_SKILLS="" SELECTED_APX_SKILLS="" +SELECTED_AGENT_SKILLS="" # Output helpers msg() { [ "$SILENT" = true ] || echo -e " $*"; } @@ -205,7 +212,7 @@ if [ "${LIST_SKILLS:-false}" = true ]; then echo -e "${B}Available Skill Profiles${N}" echo "────────────────────────────────" echo "" - echo -e " ${B}all${N} All 34 skills (default)" + echo -e " ${B}all${N} All 37 skills (default)" echo -e " ${B}data-engineer${N} Pipelines, Spark, Jobs, Streaming (14 skills)" echo -e " ${B}analyst${N} Dashboards, SQL, Genie, Metrics (8 skills)" echo -e " ${B}ai-ml-engineer${N} Agents, RAG, Vector Search, MLflow (17 skills)" @@ -257,6 +264,12 @@ if [ "${LIST_SKILLS:-false}" = true ]; then echo -e " $skill" done echo "" + echo -e "${B}Agent Skills${N} (from databricks/databricks-agent-skills repo)" + echo "────────────────────────────────" + for entry in $AGENT_SKILLS; do + echo -e " ${entry#*:}" + done + echo "" echo -e "${D}Usage: bash install.sh --skills-profile data-engineer,ai-ml-engineer${N}" echo -e "${D} bash install.sh --skills databricks-jobs,databricks-dbsql${N}" echo "" @@ -893,19 +906,21 @@ prompt_mcp_path() { # ─── Skill profile selection ────────────────────────────────── # Resolve selected skills from profile names or explicit skill list resolve_skills() { - local db_skills="" mlflow_skills="" apx_skills="" + local db_skills="" mlflow_skills="" apx_skills="" agent_skills="" # Priority 1: Explicit --skills flag (comma-separated skill names) if [ -n "$USER_SKILLS" ]; then local user_list user_list=$(echo "$USER_SKILLS" | tr ',' ' ') - # Separate into DB, MLflow, and APX buckets, always include core + # Separate into DB, MLflow, APX, and Agent buckets db_skills="" for skill in $user_list; do if echo "$MLFLOW_SKILLS" | grep -qw "$skill"; then mlflow_skills="${mlflow_skills:+$mlflow_skills }$skill" elif echo "$APX_SKILLS" | grep -qw "$skill"; then apx_skills="${apx_skills:+$apx_skills }$skill" + elif echo "$AGENT_SKILLS" | tr ' ' '\n' | sed 's/.*://' | grep -qw "$skill"; then + agent_skills="${agent_skills:+$agent_skills }$(echo "$AGENT_SKILLS" | tr ' ' '\n' | grep -w ".*:${skill}\|^${skill}$")" else db_skills="${db_skills:+$db_skills }$skill" fi @@ -914,6 +929,7 @@ resolve_skills() { SELECTED_SKILLS=$(echo "$db_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') SELECTED_MLFLOW_SKILLS=$(echo "$mlflow_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') SELECTED_APX_SKILLS=$(echo "$apx_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') + SELECTED_AGENT_SKILLS=$(echo "$agent_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') return fi @@ -922,6 +938,7 @@ resolve_skills() { SELECTED_SKILLS="$SKILLS" SELECTED_MLFLOW_SKILLS="$MLFLOW_SKILLS" SELECTED_APX_SKILLS="$APX_SKILLS" + SELECTED_AGENT_SKILLS="$AGENT_SKILLS" return fi @@ -929,6 +946,7 @@ resolve_skills() { db_skills="$CORE_SKILLS" mlflow_skills="" apx_skills="" + agent_skills="" local profiles profiles=$(echo "$SKILLS_PROFILE" | tr ',' ' ') @@ -938,6 +956,7 @@ resolve_skills() { SELECTED_SKILLS="$SKILLS" SELECTED_MLFLOW_SKILLS="$MLFLOW_SKILLS" SELECTED_APX_SKILLS="$APX_SKILLS" + SELECTED_AGENT_SKILLS="$AGENT_SKILLS" return ;; data-engineer) @@ -953,6 +972,7 @@ resolve_skills() { app-developer) db_skills="$db_skills $PROFILE_APP_DEVELOPER" apx_skills="$apx_skills $APX_SKILLS" + agent_skills="$agent_skills $PROFILE_APP_DEVELOPER_AGENT" ;; *) warn "Unknown skill profile: $profile (ignored)" @@ -964,6 +984,7 @@ resolve_skills() { SELECTED_SKILLS=$(echo "$db_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') SELECTED_MLFLOW_SKILLS=$(echo "$mlflow_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') SELECTED_APX_SKILLS=$(echo "$apx_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') + SELECTED_AGENT_SKILLS=$(echo "$agent_skills" | tr ' ' '\n' | sort -u | tr '\n' ' ') } # Interactive skill profile selection (multi-select) @@ -1118,7 +1139,7 @@ prompt_custom_skills() { data-engineer) preselected="$preselected $PROFILE_DATA_ENGINEER" ;; analyst) preselected="$preselected $PROFILE_ANALYST" ;; ai-ml-engineer) preselected="$preselected $PROFILE_AIML_ENGINEER $PROFILE_AIML_MLFLOW" ;; - app-developer) preselected="$preselected $PROFILE_APP_DEVELOPER $APX_SKILLS" ;; + app-developer) preselected="$preselected $PROFILE_APP_DEVELOPER $APX_SKILLS $PROFILE_APP_DEVELOPER_AGENT" ;; esac done @@ -1152,8 +1173,11 @@ prompt_custom_skills() { "Synthetic Data|databricks-synthetic-data-gen|$(_is_preselected databricks-synthetic-data-gen)|Generate test data" \ "Lakebase Autoscale|databricks-lakebase-autoscale|$(_is_preselected databricks-lakebase-autoscale)|Managed PostgreSQL" \ "Lakebase Provisioned|databricks-lakebase-provisioned|$(_is_preselected databricks-lakebase-provisioned)|Provisioned PostgreSQL" \ - "App Python|databricks-app-python|$(_is_preselected databricks-app-python)|Dash, Streamlit, Flask" \ + "App (AppKit + Python)|databricks-apps-python|$(_is_preselected databricks-apps-python)|AppKit, Dash, Streamlit, Flask" \ "App APX|databricks-app-apx|$(_is_preselected databricks-app-apx)|FastAPI + React" \ + "Agent: Databricks|databricks|$(_is_preselected databricks)|CLI auth, data exploration" \ + "Agent: Apps|databricks-apps|$(_is_preselected databricks-apps)|AppKit + all frameworks" \ + "Agent: Lakebase|databricks-lakebase|$(_is_preselected databricks-lakebase)|Lakebase OLTP" \ "MLflow Onboarding|mlflow-onboarding|$(_is_preselected mlflow-onboarding)|Getting started" \ "Agent Evaluation|agent-evaluation|$(_is_preselected agent-evaluation)|Evaluate AI agents" \ "MLflow Tracing|instrumenting-with-mlflow-tracing|$(_is_preselected instrumenting-with-mlflow-tracing)|Instrument with tracing" \ @@ -1364,15 +1388,18 @@ install_skills() { dirs=("${unique[@]}") # Count selected skills for display - local db_count=0 mlflow_count=0 apx_count=0 + local db_count=0 mlflow_count=0 apx_count=0 agent_count=0 for _ in $SELECTED_SKILLS; do db_count=$((db_count + 1)); done for _ in $SELECTED_MLFLOW_SKILLS; do mlflow_count=$((mlflow_count + 1)); done for _ in $SELECTED_APX_SKILLS; do apx_count=$((apx_count + 1)); done - local total_count=$((db_count + mlflow_count + apx_count)) + for _ in $SELECTED_AGENT_SKILLS; do agent_count=$((agent_count + 1)); done + local total_count=$((db_count + mlflow_count + apx_count + agent_count)) msg "Installing ${B}${total_count}${N} skills" # Build set of all skills being installed now - local all_new_skills="$SELECTED_SKILLS $SELECTED_MLFLOW_SKILLS $SELECTED_APX_SKILLS" + local agent_install_names + agent_install_names=$(echo "$SELECTED_AGENT_SKILLS" | tr ' ' '\n' | sed 's/.*://' | tr '\n' ' ') + local all_new_skills="$SELECTED_SKILLS $SELECTED_MLFLOW_SKILLS $SELECTED_APX_SKILLS $agent_install_names" # Clean up previously installed skills that are no longer selected # Check scope-local manifest first, fall back to global for upgrades from older versions @@ -1447,6 +1474,47 @@ install_skills() { done ok "APX skills ($apx_count) → ${dir#$HOME/}" fi + + # Install Agent skills from databricks/databricks-agent-skills repo + if [ -n "$SELECTED_AGENT_SKILLS" ]; then + # Fetch the full repo tree once (single API call) for all skills + local agent_tree + agent_tree=$(curl -fsSL "$AGENT_SKILLS_API_URL" 2>/dev/null) + for entry in $SELECTED_AGENT_SKILLS; do + local src_name="${entry%%:*}" + local install_name="${entry#*:}" + local dest_dir="$dir/$install_name" + mkdir -p "$dest_dir" + # Extract all file paths under skills// from the tree + local files + files=$(echo "$agent_tree" \ + | grep -o '"path":"skills/'"$src_name"'/[^"]*"' \ + | grep '\.' \ + | sed 's/"path":"//;s/"$//') + if [ -z "$files" ]; then + rmdir "$dest_dir" 2>/dev/null || true + warn "Could not fetch agent skill '$src_name'" + continue + fi + local ok_flag=1 + while IFS= read -r filepath; do + [ -z "$filepath" ] && continue + local rel="${filepath#skills/$src_name/}" + local dest="$dest_dir/$rel" + mkdir -p "$(dirname "$dest")" + if ! curl -fsSL "$AGENT_SKILLS_RAW_URL/$src_name/${rel}" -o "$dest" 2>/dev/null; then + ok_flag=0 + fi + done <<< "$files" + if [ "$ok_flag" -eq 1 ]; then + echo "$dir|$install_name" >> "$manifest.tmp" + else + rm -rf "$dest_dir" + warn "Could not install agent skill '$src_name'" + fi + done + ok "Agent skills ($agent_count) → ${dir#$HOME/}" + fi done # Save manifest of installed skills (for cleanup on profile change) @@ -2179,7 +2247,7 @@ main() { echo -e " Skills: ${G}custom selection${N}" else local sk_total=0 - for _ in $SELECTED_SKILLS $SELECTED_MLFLOW_SKILLS $SELECTED_APX_SKILLS; do sk_total=$((sk_total + 1)); done + for _ in $SELECTED_SKILLS $SELECTED_MLFLOW_SKILLS $SELECTED_APX_SKILLS $SELECTED_AGENT_SKILLS; do sk_total=$((sk_total + 1)); done echo -e " Skills: ${G}${SKILLS_PROFILE:-all} ($sk_total skills)${N}" fi fi From 5d2e6acff75617bd65eb10d573673916f1735e8f Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 12 May 2026 21:49:43 +0000 Subject: [PATCH 2/4] fix: address ACE multi-model review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GPT 5.4 xhigh and Gemini 3.1 Pro both flagged the new short agent-skill names triggering latent bugs in the install scripts. Three fixes: 1. install.sh `_is_preselected`: `grep -qw` treats `-` as a word boundary, so checking for `databricks` would falsely match `databricks-jobs`, `databricks-apps`, etc. Strip `source:` prefix from each preselected entry and use `grep -Fxq` for exact whole-line equality. Same fix applied to the deselection cleanup at the cleanup loop. 2. install.ps1: `$preselected -contains "databricks"` is exact equality on array elements, so it never matched the `"databricks-core:databricks"` entry seeded by the app-developer profile — the Agent: Databricks checkbox was never auto-preselected. Normalize `$preselected` by stripping `source:` prefixes once before the menu is built. 3. install.sh/ps1: the "Agent skills (N) -> ..." success line ran unconditionally, even when the GitHub tree fetch failed or every per-skill download warned and was rolled back. Track an `agent_success`/`$agentSuccess` counter and only print the success line when all selected agent skills installed; print a warning when only some succeeded. Co-authored-by: Isaac --- install.ps1 | 11 ++++++++++- install.sh | 18 +++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/install.ps1 b/install.ps1 index 395be3a1..53990508 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1183,6 +1183,9 @@ function Invoke-PromptCustomSkills { "app-developer" { $preselected += $script:ProfileAppDeveloper + $script:ApxSkills + $script:ProfileAppDeveloperAgent } } } + # Normalize "source:install-name" entries (e.g. "databricks-core:databricks") to install-name only, + # so `-contains` exact-equality checks below match against the same names used in the menu. + $preselected = @($preselected | ForEach-Object { $_ -replace '^[^:]+:', '' }) Write-Host "" Write-Host " Select individual skills" -ForegroundColor White @@ -1391,6 +1394,7 @@ function Install-Skills { if ($script:SelectedAgentSkills.Count -gt 0) { # Fetch the full repo tree once (single API call) for all skills $agentTree = $null + $agentSuccess = 0 try { $agentTree = Invoke-WebRequest -Uri $AgentSkillsApiUrl -UseBasicParsing -ErrorAction Stop | Select-Object -ExpandProperty Content } catch { @@ -1429,6 +1433,7 @@ function Install-Skills { } if ($okFlag) { $manifestEntries += "$dir|$installName" + $agentSuccess++ } else { Remove-Item -Recurse -Force $destDir -ErrorAction SilentlyContinue Write-Warn "Could not install agent skill '$srcName'" @@ -1436,7 +1441,11 @@ function Install-Skills { } $ErrorActionPreference = $prevEAP3 } - Write-Ok "Agent skills ($agentCount) -> $shortDir" + if ($agentSuccess -eq $agentCount) { + Write-Ok "Agent skills ($agentCount) -> $shortDir" + } elseif ($agentSuccess -gt 0) { + Write-Warn "Agent skills (only $agentSuccess of $agentCount installed) -> $shortDir" + } } } diff --git a/install.sh b/install.sh index ad667798..c95457fc 100644 --- a/install.sh +++ b/install.sh @@ -1144,7 +1144,10 @@ prompt_custom_skills() { done _is_preselected() { - echo "$preselected" | grep -qw "$1" && echo "on" || echo "off" + # Strip "source:" prefix from each entry (e.g. "databricks-core:databricks" → "databricks"), + # then exact-match against $1. Plain `grep -w` is unsafe here because `-` is a non-word + # character — `grep -w databricks` would match `databricks-jobs`, `databricks-apps`, etc. + echo "$preselected" | tr ' ' '\n' | sed 's/.*://' | grep -Fxq "$1" && echo "on" || echo "off" } echo "" @@ -1408,8 +1411,8 @@ install_skills() { if [ -f "$manifest" ]; then while IFS='|' read -r prev_dir prev_skill; do [ -z "$prev_skill" ] && continue - # Skip if this skill is still selected - if echo " $all_new_skills " | grep -qw "$prev_skill"; then + # Skip if this skill is still selected (exact match — see _is_preselected for why) + if echo "$all_new_skills" | tr ' ' '\n' | grep -Fxq "$prev_skill"; then continue fi # Only remove if the directory exists @@ -1478,7 +1481,7 @@ install_skills() { # Install Agent skills from databricks/databricks-agent-skills repo if [ -n "$SELECTED_AGENT_SKILLS" ]; then # Fetch the full repo tree once (single API call) for all skills - local agent_tree + local agent_tree agent_success=0 agent_tree=$(curl -fsSL "$AGENT_SKILLS_API_URL" 2>/dev/null) for entry in $SELECTED_AGENT_SKILLS; do local src_name="${entry%%:*}" @@ -1508,12 +1511,17 @@ install_skills() { done <<< "$files" if [ "$ok_flag" -eq 1 ]; then echo "$dir|$install_name" >> "$manifest.tmp" + agent_success=$((agent_success + 1)) else rm -rf "$dest_dir" warn "Could not install agent skill '$src_name'" fi done - ok "Agent skills ($agent_count) → ${dir#$HOME/}" + if [ "$agent_success" -eq "$agent_count" ]; then + ok "Agent skills ($agent_count) → ${dir#$HOME/}" + elif [ "$agent_success" -gt 0 ]; then + warn "Agent skills (only $agent_success of $agent_count installed) → ${dir#$HOME/}" + fi fi done From 39c349c0e5f125c45052e0055c743834dfdf90d6 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 12 May 2026 21:54:59 +0000 Subject: [PATCH 3/4] fix: agent-skills installer regex matches live GitHub API + clears stale dirs Two more ACE review findings, both real: 1. The path-extraction regex `"path":"skills/..."` (no space) does NOT match the GitHub tree API's actual response, which is pretty-printed as `"path": "skills/..."` (space after colon). Result: the installer would warn "Could not fetch agent skill" for every entry and install nothing. Fix: collapse the JSON whitespace before regex extraction, and switch from the `grep '\.'` heuristic to matching the adjacent `"type": "blob"` field so directory entries are correctly skipped. 2. Reinstalls reused the existing $dest_dir / $destDir without clearing it, so files removed upstream would persist locally across upgrades. Fix: `rm -rf` / `Remove-Item -Recurse` the destination before each skill is downloaded. Co-authored-by: Isaac --- install.ps1 | 20 ++++++++++++++------ install.sh | 20 ++++++++++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/install.ps1 b/install.ps1 index 53990508..e8a37bd1 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1392,11 +1392,14 @@ function Install-Skills { # Install Agent skills from databricks/databricks-agent-skills repo if ($script:SelectedAgentSkills.Count -gt 0) { - # Fetch the full repo tree once (single API call) for all skills + # Fetch the full repo tree once (single API call) for all skills. + # Collapse pretty-printed JSON whitespace so the path/mode/type fields + # land adjacent for the per-entry regex below. $agentTree = $null $agentSuccess = 0 try { - $agentTree = Invoke-WebRequest -Uri $AgentSkillsApiUrl -UseBasicParsing -ErrorAction Stop | Select-Object -ExpandProperty Content + $rawTree = Invoke-WebRequest -Uri $AgentSkillsApiUrl -UseBasicParsing -ErrorAction Stop | Select-Object -ExpandProperty Content + $agentTree = ($rawTree -replace '\s+', ' ') } catch { Write-Warn "Could not fetch agent skills tree from GitHub API" } @@ -1406,11 +1409,16 @@ function Install-Skills { $srcName = ($entry -split ':')[0] $installName = ($entry -replace '^.*:', '') $destDir = Join-Path $dir $installName - if (-not (Test-Path $destDir)) { - New-Item -ItemType Directory -Path $destDir -Force | Out-Null + # Wipe any prior install so upstream-deleted files don't persist + if (Test-Path $destDir) { + Remove-Item -Recurse -Force $destDir -ErrorAction SilentlyContinue } - # Extract all file paths under skills// that contain a dot (i.e. are files not dirs) - $filePaths = [regex]::Matches($agentTree, '"path":"(skills/' + [regex]::Escape($srcName) + '/[^"]*\.[^"]*)"') | + New-Item -ItemType Directory -Path $destDir -Force | Out-Null + # Extract file paths under skills// — match only entries whose + # next JSON fields are `"mode": "...", "type": "blob"`, so directory + # entries (type=tree) are skipped. agentTree has been whitespace-collapsed + # above; the GitHub tree API returns fields in order path → mode → type. + $filePaths = [regex]::Matches($agentTree, '"path": *"(skills/' + [regex]::Escape($srcName) + '/[^"]+)", *"mode": *"[^"]+", *"type": *"blob"') | ForEach-Object { $_.Groups[1].Value } if (-not $filePaths) { Remove-Item $destDir -ErrorAction SilentlyContinue diff --git a/install.sh b/install.sh index c95457fc..e99b1f4f 100644 --- a/install.sh +++ b/install.sh @@ -1480,20 +1480,28 @@ install_skills() { # Install Agent skills from databricks/databricks-agent-skills repo if [ -n "$SELECTED_AGENT_SKILLS" ]; then - # Fetch the full repo tree once (single API call) for all skills + # Fetch the full repo tree once (single API call) for all skills. + # Collapse pretty-printed JSON to a single line + squeeze whitespace so the + # path/mode/type fields land adjacent for the per-entry regex below. local agent_tree agent_success=0 - agent_tree=$(curl -fsSL "$AGENT_SKILLS_API_URL" 2>/dev/null) + agent_tree=$(curl -fsSL "$AGENT_SKILLS_API_URL" 2>/dev/null | tr -d '\n' | tr -s ' ') for entry in $SELECTED_AGENT_SKILLS; do local src_name="${entry%%:*}" local install_name="${entry#*:}" local dest_dir="$dir/$install_name" + # Wipe any prior install so upstream-deleted files don't persist + rm -rf "$dest_dir" mkdir -p "$dest_dir" - # Extract all file paths under skills// from the tree + # Extract file paths under skills// — match only entries whose + # next JSON fields are `"mode": "...", "type": "blob"`, so directory + # entries (type=tree) are skipped. Note the agent_tree has been + # whitespace-collapsed above; the GitHub tree API returns fields in + # the order path → mode → type → sha → size → url, so this pattern + # matches each blob exactly once. local files files=$(echo "$agent_tree" \ - | grep -o '"path":"skills/'"$src_name"'/[^"]*"' \ - | grep '\.' \ - | sed 's/"path":"//;s/"$//') + | grep -oE '"path": *"skills/'"$src_name"'/[^"]+", *"mode": *"[^"]+", *"type": *"blob"' \ + | sed 's/.*"path": *"\([^"]*\)".*/\1/') if [ -z "$files" ]; then rmdir "$dest_dir" 2>/dev/null || true warn "Could not fetch agent skill '$src_name'" From dd2257cd6ffe165e3fe1f06824271811a4902220 Mon Sep 17 00:00:00 2001 From: James Broadhead Date: Tue, 12 May 2026 22:20:05 +0000 Subject: [PATCH 4/4] fix: resolve_skills bucketing + 0-of-N agent install summary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ACE iteration 2 findings: - Gemini caught that the `grep -qw` word-boundary bug I fixed in `_is_preselected` still bit the resolve_skills() bucketing chain (lines 918/920/922). Passing `--skills databricks` (a valid agent install-name) would match `databricks-app-apx` via the hyphen word-boundary on the APX branch and get misclassified. Switched all three buckets to exact match via `tr ' ' '\n' | grep -Fxq`, and rewrote the source:install-name lookup on line 923 with `-E` and a clean anchored alternation. - GPT caught that when the GitHub tree fetch fails entirely (`agent_tree` empty / `$agentTree` null), the summary branch emits nothing — neither OK nor "only N of M installed" — even though zero of N agent skills were actually installed. Added an `else` branch that emits a `0 of $agent_count installed` warning. Out of scope (flagged but not addressed): - GPT raised legacy-name compatibility for `databricks-app-python` → `databricks-apps-python`. The rename is intentional per the product decision; back-compat aliasing is a separate concern. Co-authored-by: Isaac --- install.ps1 | 2 ++ install.sh | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/install.ps1 b/install.ps1 index e8a37bd1..669083ca 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1453,6 +1453,8 @@ function Install-Skills { Write-Ok "Agent skills ($agentCount) -> $shortDir" } elseif ($agentSuccess -gt 0) { Write-Warn "Agent skills (only $agentSuccess of $agentCount installed) -> $shortDir" + } else { + Write-Warn "Agent skills (0 of $agentCount installed) -> $shortDir" } } } diff --git a/install.sh b/install.sh index e99b1f4f..1ec2a3a2 100644 --- a/install.sh +++ b/install.sh @@ -915,12 +915,16 @@ resolve_skills() { # Separate into DB, MLflow, APX, and Agent buckets db_skills="" for skill in $user_list; do - if echo "$MLFLOW_SKILLS" | grep -qw "$skill"; then + # Exact-match bucketing — `grep -w` treats `-` as a word boundary, so e.g. + # `grep -w databricks` would match `databricks-app-apx` and misclassify + # an agent install-name (`databricks`) as APX. + if echo "$MLFLOW_SKILLS" | tr ' ' '\n' | grep -Fxq "$skill"; then mlflow_skills="${mlflow_skills:+$mlflow_skills }$skill" - elif echo "$APX_SKILLS" | grep -qw "$skill"; then + elif echo "$APX_SKILLS" | tr ' ' '\n' | grep -Fxq "$skill"; then apx_skills="${apx_skills:+$apx_skills }$skill" - elif echo "$AGENT_SKILLS" | tr ' ' '\n' | sed 's/.*://' | grep -qw "$skill"; then - agent_skills="${agent_skills:+$agent_skills }$(echo "$AGENT_SKILLS" | tr ' ' '\n' | grep -w ".*:${skill}\|^${skill}$")" + elif echo "$AGENT_SKILLS" | tr ' ' '\n' | sed 's/.*://' | grep -Fxq "$skill"; then + # Look up the full source:install-name entry (or bare entry if no colon) + agent_skills="${agent_skills:+$agent_skills }$(echo "$AGENT_SKILLS" | tr ' ' '\n' | grep -E "^.*:${skill}$|^${skill}$")" else db_skills="${db_skills:+$db_skills }$skill" fi @@ -1529,6 +1533,8 @@ install_skills() { ok "Agent skills ($agent_count) → ${dir#$HOME/}" elif [ "$agent_success" -gt 0 ]; then warn "Agent skills (only $agent_success of $agent_count installed) → ${dir#$HOME/}" + else + warn "Agent skills (0 of $agent_count installed) → ${dir#$HOME/}" fi fi done