Add azd tool install azure-skills support#8704
Conversation
Reword comments to satisfy cspell: CLI's->CLI, prereq->prerequisite, recognise->recognize, and avoid the 'dotall' term. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR extends azd tool to support installing “skill” plugins via an existing agentic CLI host on the user’s PATH, adding azure-skills (microsoft/azure-skills) as the first built-in skill entry. It introduces a new tool category and adds host-specific install/update/list command metadata, plus detection and installer flows that run the host CLI interactively.
Changes:
- Added
ToolCategorySkill+ per-hostSkillHostdefinitions and registered theazure-skillsbuilt-in tool. - Implemented skill detection (
plugin list/extensions listparsing) and skill install/upgrade flows (marketplace add handling, host selection, verification retries). - Updated Copilot agent required plugin source to the new
microsoft/azure-skillsplugin manifest path and added unit tests covering the skill installer behavior.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
cli/azd/pkg/tool/manifest.go |
Adds the new “skill” tool category, SkillHost model, registers azure-skills, and updates the Azure MCP Server website field. |
cli/azd/pkg/tool/manifest_test.go |
Updates built-in tool expectations and adds skill-specific registry assertions. |
cli/azd/pkg/tool/installer.go |
Adds a skill-specific install/upgrade execution path with host selection, marketplace add, and verification retries. |
cli/azd/pkg/tool/installer_test.go |
Adds table-driven tests for host selection, prereqs, marketplace idempotency, and codex upgrade behavior. |
cli/azd/pkg/tool/detector.go |
Adds skill detection logic using per-host list commands and regex-based version extraction. |
cli/azd/internal/agent/copilot_agent_factory.go |
Updates required Copilot plugin source to the new azure-skills repo path. |
…na/azd-tool-skill-plugins
…hemarina/azure-dev into hemarina/azd-tool-skill-plugins
| // SkillHosts describes the agent CLI hosts that can install this tool when | ||
| // Category == ToolCategorySkill. Hosts are evaluated in order; the first | ||
| // one whose binary is on PATH is used. Platform-agnostic because the host | ||
| // CLI's plugin command syntax does not vary between operating systems. | ||
| // Ignored for other categories. | ||
| SkillHosts []SkillHost |
There was a problem hiding this comment.
If a user has multiple CLI hosts installed (e.g. copilot, claude, and codex), the current evaluation logic means that we only install the skill for copilot.
I'm wondering if we could give users more control in selecting which hosts to install the skill for. They could be using codex as their main CLI but they won't get the skill because they happen to have copilot or gemini installed.
There was a problem hiding this comment.
Good point. If users have apm, we will use apm to install skills for all agents. We should add commands for:
azd tool install --all
azd tool install --host
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
|
|
||
| // For codex, need to install after marketplace is updated | ||
| if upgrade && host.Host == "codex" { | ||
| runArgs = exec.NewRunArgs(host.Host, host.PluginInstallCommand...) |
There was a problem hiding this comment.
[nit] The codex install-after-update command is run without .WithInteractive(true), unlike the primary command on line 554 and the fresh-install path. If codex plugin add azure@azure-skills ever prompts, the upgrade flow would hang or fail where a fresh install would not. Add .WithInteractive(true) here for consistency. (azd-code-reviewer)
| } | ||
|
|
||
| // 1. HARD prerequisite: at least one supported host must be on PATH. | ||
| host, hostErr := i.pickSkillHost(tool) |
There was a problem hiding this comment.
[suggestion] pickSkillHost returns the first host on PATH for both install and upgrade, regardless of which host actually has the skill installed. Consider a user with both apm and copilot on PATH who installed via copilot: azd tool upgrade azure-skills will pick apm (listed first) and run apm update microsoft/azure-skills -g against a package apm never installed — which will likely error or silently install a second copy. For the upgrade path, it'd be more robust to prefer the host that the detector reports as already having the skill. (azd-code-reviewer)
| if host.Host == "gemini" { | ||
| output = result.Stdout + "\n" + result.Stderr | ||
| } | ||
| if host.PluginName != "" && |
There was a problem hiding this comment.
[nit] The host.PluginName != "" guard here is dead — the loop already continues when host.PluginName == "" at the top. The two-stage gate comment above is the valuable part; the redundant check can be dropped to if !strings.Contains(output, host.PluginName). (azd-code-reviewer)
| // exponential backoff to match the package-manager install path. | ||
| const maxAttempts = 4 // 1 initial + 3 retries | ||
| var status *ToolStatus | ||
| backoff := 1 * time.Second |
There was a problem hiding this comment.
[suggestion] The verification backoff is hardcoded (1s, doubling). TestRunSkill_VerificationFails_AfterRetries therefore sleeps ~7s of real wall time (1+2+4) on every run. Consider making the initial backoff injectable (struct field defaulting to 1s) so tests can set it to ~1ms and keep the suite fast. (azd-code-reviewer)
| // requiredPlugins lists plugins that must be installed before starting a Copilot session. | ||
| var requiredPlugins = []pluginSpec{ | ||
| {Source: "microsoft/GitHub-Copilot-for-Azure:plugin", Name: "azure"}, | ||
| {Source: "microsoft/azure-skills:.github/plugins/azure-skills", Name: "azure"}, |
There was a problem hiding this comment.
[suggestion] This changes the auto-install source for the existing Copilot agent session from microsoft/GitHub-Copilot-for-Azure:plugin to microsoft/azure-skills:.github/plugins/azure-skills, which goes through copilot plugin install <source> (see copilot_agent.go → InstallPlugin). Meanwhile the new azd tool install azure-skills path installs the same plugin into copilot via plugin marketplace add + plugin install azure@azure-skills. Two different mechanisms installing the "same" azure plugin into the same host is a footgun — worth confirming the raw microsoft/azure-skills:.github/plugins/azure-skills ref is valid for copilot plugin install and that both paths land an identically-named plugin (the agent dedupes on Name: "azure"). I don't see a test covering this requiredPlugins value. (azd-code-reviewer)
Add
azd tool install azure-skillssupportFixes #8596.
Adds
azure-skillsas a newToolCategorySkillentry in the built-in toolregistry, so users can run:
…to install / upgrade / inspect the Microsoft Azure Skills plugin (from
microsoft/azure-skills) throughwhichever agentic CLI host the user already has on PATH.
Supported hosts
Detection is platform-agnostic; the first host whose binary is on
PATHwins, in this order:
copilot)plugin marketplace add microsoft/azure-skills→plugin install azure@azure-skillsplugin update azure@azure-skillsplugin listclaude)plugin marketplace add https://github.com/microsoft/azure-skills→plugin install azureplugin update azure@azure-skillsplugin list azure@azure-skillsgemini)extensions install https://github.com/microsoft/azure-skillsextensions update azureextensions listcodex)plugin marketplace add microsoft/azure-skills→plugin add azure@azure-skillsplugin marketplace upgrade azure-skills→plugin add azure@azure-skills(two-step)plugin listPrerequisite rules
PATH, install fails with anerrorhandler.ErrorWithSuggestionthatpoints the user at the single easiest fix:
nodeis missing, a stdout warning isemitted (Azure MCP server requires Node 18+ via
npx) and the installproceeds. The skill files install successfully even without Node.
surfaces its own diagnostic when git is missing.
UX choices
WithInteractive(true)so anyprompts surfaced by the host CLI (e.g. gemini's two
[Y/n]prompts forthe git-clone fallback and MCP/skills consent) are answered by the user
directly.
azdnever pipes canned answers.disk" (copilot/claude/codex), that case is recognised as idempotent
success; real errors (network, 404, etc.) are propagated.
the package-manager install path uses (4 attempts, exponential backoff)
because plugin-list output can lag the install action.
Detection
detectSkillruns each host'sPluginListCommandand uses a two-stagegate to avoid false positives:
PluginNamesubstring pre-filter, thenper-host
VersionRegex(e.g. claude'sVersion:line, gemini'sRelease tag:line). The regex match is the authoritative existencesignal — required because claude's filtered query echoes the queried
name even when the plugin is not installed.
The same
VersionRegexextractsInstalledVersion, which feeds theexisting
tool.upgrade.from_version/to_versiontelemetry withoutadditional code.
Agent integration
internal/agent/copilot_agent_factory.go—requiredPluginssourceupdated from the old
microsoft/GitHub-Copilot-for-Azure:pluginreference(repo + path no longer exist) to
microsoft/azure-skills:.github/plugins/azure-skills, which is thecanonical directory containing the Copilot CLI plugin manifest
(
.plugin/plugin.jsonwithname: "azure").Files changed
cli/azd/pkg/tool/manifest.goToolCategorySkill,SkillHoststruct (with per-host commands +VersionRegex),azureSkills()registrationcli/azd/pkg/tool/detector.godetectSkillcli/azd/pkg/tool/installer.gorunSkill,pickSkillHost,runSkillHostCommand,runMarketplaceAdd,isMarketplaceAlreadyAddedcli/azd/pkg/tool/installer_test.goTestRunSkill_*tests covering host selection, prereq matrix, marketplace-add tolerance, codex two-step upgrade, install-error surfacingcli/azd/internal/agent/copilot_agent_factory.gorequiredPlugins[0].Sourceupdated to the new repo pathVerification
go build ./...✓go test ./pkg/tool/...✓ (12 new skill tests + existing tests green)gofmt -l ./pkg/tool/cleanUPDATE_SNAPSHOTS=true go test ./cmd -run 'TestFigSpec|TestUsage'— nosnapshot changes (no user-facing help text touched)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>