Skip to content

aitools: plugin-first install command (picker, scope, --path/--skills-only)#5738

Merged
simonfaltum merged 4 commits into
mainfrom
simonfaltum/aitools-install-cmd
Jun 26, 2026
Merged

aitools: plugin-first install command (picker, scope, --path/--skills-only)#5738
simonfaltum merged 4 commits into
mainfrom
simonfaltum/aitools-install-cmd

Conversation

@simonfaltum

@simonfaltum simonfaltum commented Jun 26, 2026

Copy link
Copy Markdown
Member

Stack

Part of the aitools plugin-first redesign. Merge bottom to top:

Why

This is the user-visible flip of the aitools redesign: databricks aitools install now installs the Databricks plugin through each coding agent's own CLI instead of copying raw skill files into every agent. Raw skills alongside a plugin produce duplicate, hard-to-update content; the plugin is how those agents expect third-party content to arrive.

Stacked on #5737 (plugin engine).

Changes

Before: install copied skill files into each detected agent's skills dir.

Now: install is plugin-first, with explicit escape hatches.

  • Delivery per agent: plugin agents (Claude Code, Codex, Copilot) get the databricks plugin; agents with no plugin (OpenCode, Antigravity) get skill files; Cursor (plugin, but no headless install) prints the /add-plugin databricks step and copies nothing.
  • Escape hatches: --skills-only forces raw skill files for every agent; --path <dir> is a dumb dump (no agents, no state). --skills-only + --path is rejected.
  • Scope mapping (mapAgentScope): CLI global → agent user scope; CLI project → agent project scope only where supported (Claude), otherwise the agent is skipped with a reason. No silent fallback to user scope.
  • No silent fallback: a blocked plugin install (CLI not on PATH, install failure) is reported and skipped (exit 0), never swapped for skills. It errors only when the agent was explicitly named via --agents.
  • Interactive picker lists every known agent with a detection-state label (detected ones pre-checked), then shows a plan summary and a single confirm.
  • Detection: non-interactive selection now also detects plugin agents by their CLI binary on PATH (fixing the Codex/Copilot config-dir miss); --skills-only selection stays config-dir based and PATH-independent.
  • The legacy experimental skills install alias pins --skills-only to preserve its behavior.

Test plan

  • Unit tests: buildPlan deliveries (plugin/skills/manual/skip) and --skills-only forcing skills; mapAgentScope matrix; executePlan skip-with-warning vs error-on-explicit; plugin-first default (with a controlled PATH), --agents for an undetected agent, no-agents no-op, --skills-only/--path conflict, scope flags, interactive picker + confirm.
  • Acceptance: the 3 existing experimental aitools install goldens switch to --skills-only and keep their behavior strings byte-identical (only the >>> echo gains the flag); a new path-dump test covers --path.
  • go test ./libs/aitools/... ./cmd/aitools/... ./acceptance -run TestAccept/experimental/aitools passes.
  • ./task fmt-q, ./task lint-q (0 issues), ./task checks (no dead code) clean.

This pull request and its description were written by Isaac.

@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 14c1921

Run: 28249353710

Env 🟨​KNOWN 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 1 13 235 1034 5:23
🟨​ aws windows 7 1 13 237 1032 8:57
💚​ aws-ucws linux 8 13 322 951 4:52
💚​ aws-ucws windows 8 13 324 949 4:39
💚​ azure linux 2 15 235 1033 4:25
💚​ azure windows 2 15 237 1031 4:20
💚​ azure-ucws linux 2 15 324 948 5:25
💚​ azure-ucws windows 2 15 326 946 4:56
💚​ gcp linux 2 15 234 1035 3:54
💚​ gcp windows 2 15 236 1033 4:11
21 interesting tests: 13 SKIP, 7 KNOWN, 1 RECOVERED
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/invariant/no_drift 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/replace_existing 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_endpoints/drift/recreated_same_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/vector_search_indexes/recreate/embedding_dimension 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/ssh/connection 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestFetchRepositoryInfoAPI_FromRepo 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
Top 4 slowest tests (at least 2 minutes):
duration env testname
3:19 azure-ucws windows TestAccept
3:13 aws-ucws windows TestAccept
3:11 gcp windows TestAccept
3:11 azure windows TestAccept

@simonfaltum simonfaltum force-pushed the simonfaltum/aitools-plugin-engine branch from a022299 to 72c9a8f Compare June 26, 2026 12:44
@simonfaltum simonfaltum force-pushed the simonfaltum/aitools-install-cmd branch from dc07368 to b793136 Compare June 26, 2026 12:45
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 26, 2026
…atabricks#5734)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Merge bottom to top:

- **databricks#5734 Detection & registry (plugin metadata + capability detection)
← this PR**
- databricks#5736 State schema v2 (plugin + file provenance records)
- databricks#5737 Plugin engine, checksums, `--path` dump (library)
- databricks#5738 Plugin-first install command
- databricks#5739 Update + prune of vanished skills
- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
<!-- /aitools-stack -->

## Why

`databricks aitools` is being redesigned from a raw-skills installer
into a plugin-first installer. The first thing that redesign needs is
better agent detection: today an agent is "detected" only if its config
dir exists on disk, which misses Codex and Copilot (often used through
an IDE, or installed-but-not-yet-run), and the registry has no notion of
which agents have a plugin or what their CLI binary is.

This PR lays that foundation. It is deliberately behavior-neutral: no
command output changes, and `DetectInstalled` still uses the same
config-dir signal it does today. The picker and install rewrites that
consume this come in later PRs in the stack.

## Changes

Before: the agent registry only knew each agent's config dir; detection
was a single `os.Stat` on that dir.

Now: the registry also describes each agent's plugin and CLI binary, and
there's a capability detector that can tell the difference between
"binary on PATH" and "config dir exists."

- `Agent.Binary` — the CLI binary name on PATH (`claude`, `codex`,
`copilot`, `cursor-agent`, `opencode`; empty for IDE-only Antigravity).
Cursor's binary is `cursor-agent`, not `cursor` (the latter is an IDE
shim that isn't on PATH).
- `Agent.Plugin *PluginSpec` — describes the databricks plugin. `Plugin
!= nil` means the agent has a plugin (Claude, Codex, Copilot, Cursor);
`nil` means raw skills are the only delivery (OpenCode, Antigravity).
`ManualOnly` marks Cursor (plugin exists, but no headless install path).
- OpenCode config dir is now per-OS: `%APPDATA%\opencode` on Windows,
and honors `XDG_CONFIG_HOME` otherwise (defaulting to
`~/.config/opencode`). The previous hardcoded path made OpenCode
undetectable on Windows and ignored `XDG_CONFIG_HOME` on Linux.
- New `detect.go`: `HasBinary` (via `exec.LookPath`), `DisplayState` (a
5-state enum for the picker, computed from the two cheap signals without
running the agent), `Preselect`, and `ProbePlugin` (runs `<agent> plugin
--help` with a 5s timeout through `libs/process`; refuses a cwd-relative
binary via `exec.ErrDot` so a malicious `./claude` is never executed).

## Test plan

- [x] Unit tests (`detect_test.go`): `HasBinary`, `DisplayState` across
all five states, `Preselect` rules, `ProbePlugin` (supported / CLI
reports unsupported / binary not on PATH / refuses dot-relative binary
with zero executions), and `openCodeConfigDir` (XDG honored /
`~/.config` default; Windows APPDATA branch on Windows hosts).
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` passes (no behavior
change to existing commands).
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) all clean.

This pull request and its description were written by Isaac.

@mihaimitrea-db mihaimitrea-db left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good

@simonfaltum simonfaltum force-pushed the simonfaltum/aitools-plugin-engine branch from 72c9a8f to 1f5f724 Compare June 26, 2026 15:10
@simonfaltum simonfaltum force-pushed the simonfaltum/aitools-install-cmd branch from b793136 to 4ad9646 Compare June 26, 2026 15:10
Base automatically changed from simonfaltum/aitools-plugin-engine to main June 26, 2026 15:18
… --skills-only)

Wires the install command to the plugin engine. The default now installs the
databricks plugin per agent instead of copying raw skill files.

- Plugin-first delivery: plugin agents (Claude/Codex/Copilot) get the plugin;
  no-plugin agents (OpenCode/Antigravity) get skills; Cursor prints the
  /add-plugin tip and copies nothing. --skills-only forces raw skills for every
  agent; --path <dir> is a dumb dump (no agents, no state). --skills-only and
  --path together is an error.
- Per-agent scope mapping (mapAgentScope): CLI global -> agent user scope; CLI
  project -> agent project scope only where supported (Claude), else skip with a
  reason. No silent fallback to user scope.
- A blocked plugin install is reported and skipped (exit 0), never silently
  swapped for skills; it errors only when the agent was named via --agents.
- Interactive picker now lists all known agents with detection-state labels
  (detected ones pre-checked), followed by a plan summary and a single confirm.
- Non-interactive selection: plugin agents detected by binary-on-PATH (fixes the
  Codex/Copilot config-dir miss); --skills-only selection stays config-dir based.
- The legacy `experimental skills install` alias pins --skills-only to keep its
  behavior; the 3 existing acceptance goldens switch to --skills-only and stay
  byte-identical in their behavior strings.

Co-authored-by: Isaac
The aitools acceptance tests share a process-global HOME (via t.Setenv), so
running several in parallel let one test's install race another's on the shared
~/.databricks skills dir. sethome exports a per-test HOME inside each script's
own shell, making them hermetic and deterministic. Output is unchanged.

Co-authored-by: Isaac
…guard, picker errors)

Address cursor review of the install command:

- Fix a panic: RecordPluginInstalls created a plugin-only state with nil Skills/
  RepoDirs/Files maps, so a later --skills-only install or update assigned into a
  nil map. Initialize all maps when creating plugin-only state, and add a
  defensive Skills nil-guard in InstallSkillsForAgents. Regression test covers
  plugin install followed by a raw-skills install.
- Reject --skills unless --skills-only or --path is set; the plugin is installed
  in full, so cherry-picking individual skills was silently ignored before.
- Propagate interactive picker errors (selectAgents now returns an error) instead
  of swallowing them into a "no agents found" exit 0.

Co-authored-by: Isaac
@simonfaltum simonfaltum force-pushed the simonfaltum/aitools-install-cmd branch from 4ad9646 to 14c1921 Compare June 26, 2026 15:53
@simonfaltum simonfaltum added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit e41c0d1 Jun 26, 2026
24 checks passed
@simonfaltum simonfaltum deleted the simonfaltum/aitools-install-cmd branch June 26, 2026 16:51
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 27, 2026
…ks#5736)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Merge bottom to top:

- databricks#5734 Detection & registry (plugin metadata + capability detection)
- **databricks#5736 State schema v2 (plugin + file provenance records) ← this PR**
- databricks#5737 Plugin engine, checksums, `--path` dump (library)
- databricks#5738 Plugin-first install command
- databricks#5739 Update + prune of vanished skills
- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
<!-- /aitools-stack -->

## Why

The plugin-first `aitools` redesign needs `.state.json` to record two
new things: which plugins we installed through each agent's own CLI (so
`update`/`uninstall`/`list` act on exactly where we installed), and
provenance for the skill files we wrote (so a later `update` can prune a
skill that vanished from the release only when the user hasn't modified
it). This PR adds that schema, additively, ahead of the
install/update/uninstall changes that consume it.

Stacked on databricks#5734 (detection foundation).

## Changes

Before: `InstallState` tracked only `skills`/`repo_dirs` (schema v1).

Now: schema v2 adds two optional maps, and existing state migrates
forward transparently.

- `PluginRecord` (in `InstallState.Plugins`, keyed by agent name):
marketplace, plugin id, agent-native scope, last-seen version, and
`installed_marketplace` (whether this CLI registered the marketplace, so
uninstall knows if it may de-register it).
- `FileRecord` (in `InstallState.Files`, keyed by canonical-relative
path like `databricks/SKILL.md`): per-file `sha256` + `origin` ref.
- Both maps are `json:",omitempty"`, so a files-only install serializes
byte-identically to today.
- `LoadState` runs `migrateState`: forward-only and idempotent. v1 → v2
needs no data transformation (the maps are additive and optional), so it
only stamps the version; writers lazily initialize the maps the same way
`RepoDirs` is handled.
- The fresh-install writer now stamps v2 so the on-disk and in-memory
versions agree.

## Test plan

- [x] Unit tests: v1→v2 migration is additive (existing skills
untouched, new maps nil), migration is idempotent, and a state with
populated `Plugins`/`Files` round-trips through save/load. Existing
round-trip fixtures updated to v2.
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` passes.
- [x] `go test ./acceptance -run TestAccept/experimental/aitools` passes
(`.state.json` is in `Ignore`, so the version bump doesn't touch
goldens).
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 27, 2026
…tabricks#5737)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Merge bottom to top:

- databricks#5734 Detection & registry (plugin metadata + capability detection)
- databricks#5736 State schema v2 (plugin + file provenance records)
- **databricks#5737 Plugin engine, checksums, `--path` dump (library)  ← this PR**
- databricks#5738 Plugin-first install command
- databricks#5739 Update + prune of vanished skills
- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
<!-- /aitools-stack -->

## Why

The plugin-first redesign installs the databricks plugin by driving each
agent's own plugin CLI (`claude plugin install`, etc.), and on `update`
it needs to be able to prune a skill that vanished from a release
without clobbering files the user edited. This PR adds the library layer
for both, with no command wiring yet, so the engine can be reviewed and
unit-tested on its own. The install/update/uninstall commands call into
it in later PRs.

Stacked on databricks#5736 (state schema v2).

## Changes

- `plugin.go` — the plugin engine. It drives each agent's CLI through
`libs/process` (so tests mock it with `process.WithStub`):
- `InstallPluginForAgent` registers the marketplace and installs the
plugin, recording whether *we* added the marketplace.
- `UpdatePluginForAgent` runs the per-agent update (Codex's two-step
`marketplace upgrade` then `plugin add` is encoded).
- `UninstallPluginForAgent` removes the plugin and de-registers the
marketplace **only** when this CLI registered it and
`--keep-marketplace` wasn't set — never a marketplace another plugin may
share.
- Argv is built per agent (Codex uses `plugin add`; Claude is the only
`--scope` agent) and run by absolute path; a cwd-relative binary
(`exec.ErrDot`) is refused, never executed.
- A blocked operation returns a typed `*BlockedError` (`CLINotOnPath` /
`InstallFailed` / `ManualOnly`), so the command layer can report it and
decide skip-vs-fail — it never silently falls back to skills. The
agent's own stderr is surfaced via `errors.AsType`, never
string-matched.
- `installer.go` — `installSkillToDir` now records a sha256 `FileRecord`
per file it writes (origin = ref); `InstallSkillsForAgents` and
`UpdateSkills` persist these to `state.Files` for the prune safeguard.
The symlink-vs-copy rules are unchanged.
- `dump.go` — `DumpSkillsToPath`, a dumb `--path` dump (no agents, no
`.state.json`, no lifecycle) that reuses the existing
manifest/resolve/fetch path.

## Test plan

- [x] `plugin_test.go` via `process.WithStub` + injected `lookPath`:
Claude success (marketplace add + `--scope user` install), Codex uses
`plugin add` with no `--scope`, manual-only (Cursor) → `ManualOnly`,
CLI-not-on-path → `CLINotOnPath` with zero executions, install failure →
`InstallFailed` surfacing stderr verbatim, marketplace-already-present
leaves `InstalledMarketplace=false`, Codex two-step update, marketplace
de-register only when we installed it and `--keep-marketplace` honored.
- [x] `dump_test.go`: writes files, writes no state, honors `--skills`
cherry-pick.
- [x] `installer_test.go`: install captures file checksums into
`state.Files`.
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` and `go test
./acceptance -run TestAccept/experimental/aitools` pass.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code — engine funcs are reachable via tests until the command layer
lands) clean.

This pull request and its description were written by Isaac.
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 27, 2026
)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Merge bottom to top:

- databricks#5734 Detection & registry (plugin metadata + capability detection)
- databricks#5736 State schema v2 (plugin + file provenance records)
- databricks#5737 Plugin engine, checksums, `--path` dump (library)
- databricks#5738 Plugin-first install command
- **databricks#5739 Update + prune of vanished skills  ← this PR**
- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
<!-- /aitools-stack -->

## Why

With install now plugin-first, `update` has to know about both worlds:
update the plugin for plugin agents, and reconcile raw skills for the
rest. This PR also makes the one intended behavior change from the
redesign: a skill that disappears from a release is now removed, not
warned-about-and-kept, so users don't accumulate dead skills forever.

Stacked on databricks#5738 (install command).

## Changes

Before: `update` only reconciled skill files, and a skill that vanished
from the manifest was kept with a warning.

Now:

- **Plugin agents:** every agent recorded in `state.Plugins` is updated
through its own plugin CLI (`UpdateInstalledPlugins`). The plugin's own
update handles content the release dropped, so plugin agents have no
per-skill prune.
- **Prune vanished skills:** a skill that disappeared from the manifest
is pruned, but **only** when the CLI installed it and the on-disk files
still match the recorded sha256. A user-modified skill, or one with no
recorded provenance (legacy v1 state), is kept with a warning.
`--no-prune` keeps vanished skills; `--check` previews the removals
without deleting.
- **No duplicate skills:** the file-skills reconcile excludes agents
managed as plugins in this scope (so a plugin agent never gets duplicate
raw skill files), and is skipped entirely for a pure-plugin install.
- `FormatUpdateResult` gains "removed" lines and a "Removed N skills."
summary; the existing updated/added lines and summary are
byte-identical.

## Test plan

- [x] Unit tests: prune of a vanished unmodified skill (state +
canonical dir cleaned), `--no-prune` keeps it, a user-modified vanished
skill is kept, `--check` shows the prune without deleting,
`UpdateInstalledPlugins` runs the plugin update and bumps the recorded
version, and the `--no-prune` flag wiring.
- [x] Acceptance: new `update-prune` test installs two skills then
updates against a release missing one — alpha updates, beta is pruned
("Removed 1 skill.").
- [x] `go test ./libs/aitools/... ./cmd/aitools/... ./acceptance -run
TestAccept/experimental/aitools` passes.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
github-merge-queue Bot pushed a commit that referenced this pull request Jun 29, 2026
<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Open PRs, merge bottom to
top:

- #5740 Uninstall teardown + legacy skills cleanup
- **#5741 list + version plugin state  ← this PR**
- #5745 Bug bash (wording, latest-by-default skills, project-scope plan,
version reporting)
- #5746 Bug bash round 2 (uninstall confirm, picker, Cursor, official
marketplace, version scope)

Merged: #5734, #5736, #5737, #5738, #5739.
<!-- /aitools-stack -->

## Why

`list` and `version` only knew about raw skills. Now that the CLI
installs plugins, they should report the real per-agent plugin state so
users (and the plugin's SessionStart nudge) can see what's installed and
whether it's current.

Stacked on #5740 (uninstall).

## Changes

- **`list --output json`** gains an additive `agents[]` array: each
plugin agent with a recorded install (its `version` and an `up_to_date`
/ `update_available` status, `managed: true`), plus Cursor as
`manual_add_plugin` (`managed: false`) when it's present. The existing
`release` / `skills` / `summary` keys and shapes are byte-identical
(additive `omitempty` field). The text view adds an `AGENT / STATUS`
block only when there are agent entries, leaving the skills table and
summary line unchanged.
- **`version`** prints a `Plugin (<Agent>): vX` line per recorded
plugin, and only prints the skills line when skills are actually
installed, so a plugin-only install no longer reports "0 skills".
Existing skills output is unchanged.

## Test plan

- [x] Unit tests: `list` JSON includes `agents[]` with the right
statuses/managed flags while keeping `release`/`skills`/`summary`
intact; `buildAgentEntries` maps recorded plugins to
up_to_date/update_available and surfaces Cursor as manual; `version`
prints the plugin line for a plugin-only install.
- [x] Existing `list`/`version` tests pass unchanged (JSON round-trip,
both-scopes skills output).
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` passes.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 29, 2026
…all (databricks#5740)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Open PRs, merge bottom to
top:

- **databricks#5740 Uninstall teardown + legacy skills cleanup  ← this PR**
- databricks#5741 list + version plugin state
- databricks#5745 Bug bash (wording, latest-by-default skills, project-scope plan,
version reporting)
- databricks#5746 Bug bash round 2 (uninstall confirm, picker, Cursor, official
marketplace, version scope)

Merged: databricks#5734, databricks#5736, databricks#5737, databricks#5738, databricks#5739.
<!-- /aitools-stack -->

## Why

To finish the lifecycle: `uninstall` has to remove the plugin we
installed (not just skills), and `install` has to clean up any raw
skills a user had from the old skills-everywhere model, so a plugin
agent doesn't end up with the plugin *and* leftover loose skill files
showing the same skills twice.

Stacked on databricks#5739 (update).

## Changes

- **Uninstall tears down plugins.** On a full uninstall (no `--skills`
filter), each agent recorded in `state.Plugins` has its plugin removed
through the agent's own CLI, and the marketplace is de-registered, but
only when this CLI registered it and `--keep-marketplace` wasn't passed,
so we never remove a marketplace another plugin may share. Skills
teardown is unchanged for file agents; the state file is removed only
when no skills and no plugins remain in the scope.
- **Install cleans up legacy raw skills.** After installing the plugin
for an agent, `RemoveLegacyRawSkills` removes skill dirs the CLI
previously dropped there: a symlink pointing into our canonical dir, or
a copy whose files all match the recorded checksums. User-modified dirs,
third-party dirs, and copies with no recorded provenance are left
untouched.
- New `--keep-marketplace` flag on `uninstall`.

## Test plan

- [x] Unit tests: uninstall tears down the plugin and de-registers the
marketplace, removing the state file when nothing remains;
`--keep-marketplace` skips the de-register; `RemoveLegacyRawSkills`
removes our symlink and a checksum-matched copy while keeping a
user-modified copy and a third-party dir.
- [x] Existing skills-uninstall tests still pass byte-for-byte
("Uninstalled N skills.", selective removal, orphan cleanup, "no skills
installed").
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` passes.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 29, 2026
…lan, version reporting) (databricks#5745)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Open PRs, merge bottom to
top:

- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
- **databricks#5745 Bug bash (wording, latest-by-default skills, project-scope
plan, version reporting) ← this PR**
- databricks#5746 Bug bash round 2 (uninstall confirm, picker, Cursor, official
marketplace, version scope)

Merged: databricks#5734, databricks#5736, databricks#5737, databricks#5738, databricks#5739.
<!-- /aitools-stack -->

## Why

Testing the built CLI surfaced several issues in `databricks aitools`:

1. Inconsistent, plugin-blind copy ("AI Tools" / "AI skills") even
though most agents now get a plugin.
2. Skills pinned to a cli-compat version (e.g. `0.2.5`) while plugin
agents installed latest (e.g. `0.2.7`). Two different versions for the
same content, and list/version misreported the plugin version.
3. Project-scope install offered files-only agents (OpenCode,
Antigravity) that can't do project scope, then failed after the fact
without installing anything.

## Changes

**Wording.** Everything under `databricks aitools` now reads "skills and
plugins" (help, version output, install log); empty-state errors say "no
skills or plugins installed". The legacy skills alias and `experimental
aitools` are untouched.

**Skills track latest by default.** Plugin agents' CLIs have no version
flag, so they only ever install the marketplace's latest. Skills now
match that:

- `GetSkillsRef` precedence: `DATABRICKS_SKILLS_REF` (exact ref, for
evals), then cli-compat.json, then default latest (`main`).
- cli-compat.json is the remote safety valve: its `skills` field is now
`"latest"` (track latest), but it can be edited to a concrete version to
pin older CLIs after a breaking change, with no CLI release needed.
AppKit version resolution is unchanged (cli-compat still pins it via its
own field).
- Plugins (and unpinned skills) report `latest` instead of a stale
concrete version, so list/version are honest.
- `update` reconciles against latest when unpinned instead of falsely
reporting "already up to date".

**Project-scope plan is honest.** `buildPlan` skips files-only agents
that don't support project scope (the way plugin agents already were),
and the picker is scope-aware, so incompatible agents are labeled and
not pre-checked. No more "offer then fail with nothing installed".

## Test plan

- [x] Unit: `GetSkillsRef` (default latest / env pin / cli-compat pin),
`DisplaySkillsVersion`, `buildPlan` project-scope skip for files-only
agents; clicompat allows the `latest` sentinel.
- [x] Acceptance `experimental/aitools` unchanged (tests pin the ref via
env).
- [x] `go test ./libs/aitools/... ./cmd/aitools/...
./libs/clicompat/...` passes.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
bradleyjamrozik-origindigital pushed a commit to bradleyjamrozik-origindigital/databricks-cli that referenced this pull request Jun 29, 2026
… marketplace) (databricks#5746)

<!-- aitools-stack -->
### Stack

Part of the `aitools` plugin-first redesign. Open PRs, merge bottom to
top:

- databricks#5740 Uninstall teardown + legacy skills cleanup
- databricks#5741 list + version plugin state
- databricks#5745 Bug bash (wording, latest-by-default skills, project-scope plan,
version reporting)
- **databricks#5746 Bug bash round 2 (uninstall confirm, picker, Cursor, official
marketplace, version scope) ← this PR**

Merged: databricks#5734, databricks#5736, databricks#5737, databricks#5738, databricks#5739.
<!-- /aitools-stack -->

## Why

A second round of testing the plugin-first `databricks aitools` CLI.
Findings are
tracked in `documents/proposals/aitools-bugbash-findings.md`.

## Changes

- **uninstall confirms in interactive mode.** It removed skills and
de-registered
plugins immediately; now (on a TTY) it summarizes what will be removed
and asks
  "Proceed?". Non-interactive runs are unaffected.
- **version labels the scope.** Skills and plugins are now always
labeled
  global/project (e.g. `Plugin (Claude Code, global): latest`).
- **Claude installs from the official marketplace.** The databricks
plugin is in
Claude's built-in `claude-plugins-official` marketplace, so Claude
installs
  `databricks@claude-plugins-official`. A built-in marketplace (empty
PluginSpec.Source) is never added or de-registered. Codex/Copilot keep
our own
  marketplace.
- **Picker only offers actionable agents.** Every detected agent still
shows in the
detection list with its state/reason, but only agents that will do
something
(plugin or skills) are selectable; agents skipped in the scope are no
longer
  checkboxes.
- **Cursor is a plain skills agent.** Cursor was a confusing "manual,
run
/add-plugin" option that did nothing. Since the Cursor plugin can't be
installed
or interacted with headlessly, all references to it are removed: Cursor
now
installs raw skills like OpenCode/Antigravity, labeled simply "skills".
This
removed the whole manual-only concept
(PluginSpec.ManualOnly/ManualInstructions,
StateManualOnly, ReasonManualOnly, the manual_add_plugin list status,
and the
  deliveryManualCursor path).
- **Help links the source repo.** `databricks aitools` help points at
https://github.com/databricks/databricks-agent-skills so users know
where skills
  and plugins come from.

## Test plan

- [x] Unit: uninstall confirm; version scope labels; built-in
marketplace install
(no `marketplace add`) and uninstall (no de-register); picker offers
only
  actionable agents; Cursor plan = skills; no-plugin install guard.
- [x] `go test ./libs/aitools/... ./cmd/aitools/...` and acceptance
`experimental/aitools` pass.
- [x] `./task fmt-q`, `./task lint-q` (0 issues), `./task checks` (no
dead code) clean.

This pull request and its description were written by Isaac.
deco-sdk-tagging Bot added a commit that referenced this pull request Jul 2, 2026
## Release v1.6.0

### CLI

 * `ssh connect` now accepts a `--base-environment` flag to run a serverless session on a custom base environment. It takes an `env.yaml` path, a `workspace-base-environments/...` resource ID, or a base environment display name, and is rejected together with `--environment-version` or `--cluster` ([#5706](#5706)).
 * `databricks aitools install` is now plugin-first: it installs the Databricks plugin through each agent's own CLI (Claude Code, Codex, GitHub Copilot) instead of copying raw skill files. Agents without a plugin (OpenCode, Antigravity) still get skill files, and Cursor prints the `/add-plugin databricks` step. Use `--skills-only` to force raw skill files for every agent, or `--path <dir>` to write skills to a directory ([#5738](#5738)).
 * `databricks labs list` now only shows projects that can be installed ([#5560](#5560)).

### Bundles

 * direct: Fixed persistent drift on `model_serving_endpoints` caused by the `traffic_config` field ([#5708](#5708)).
 * direct: Fix spurious update when `apply_policy_default_values: true` is set on job task, for-each-task, or job cluster new_cluster ([#5731](#5731)). Also fix spurious updates for for-each-task clusters due to missing backend defaults for `data_security_mode`, `node_type_id`, `driver_node_type_id`, `driver_instance_pool_id`, `enable_elastic_disk`, and `enable_local_disk_encryption`.
 * direct: Cluster resize now falls back to regular update if resize fails due to `INVALID_STATE` ([#5716](#5716)).
 * `bundle generate dashboard` now honors the `--key` flag when naming the generated resource, and rejects combining `--existing-path`, `--existing-id`, and `--resource` instead of silently ignoring all but one ([#5492](#5492)).
 * Fixed `bundle deployment migrate` failing on `model_serving_endpoints`/`database_instances` with permissions (regression since v1.5.0) ([#5775](#5775)).
 * After a terraform deploy, the CLI now dry-runs a migration to the direct engine (writing nothing locally or remotely) and reports the outcome via telemetry, warning if the migration could not be completed ([#5797](#5797)).

### Dependency updates
 * Bump `github.com/databricks/databricks-sdk-go` from v0.147.0 to v0.152.0 ([#5773](#5773)).
 * Bump Terraform provider from v1.118.0 to v1.120.0 ([#5792](#5792)).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants