feat(skills): add azd ai skill command group#8224
Open
huimiu wants to merge 7 commits into
Open
Conversation
Introduces a new standalone `azure.ai.skills` extension that exposes `azd ai skill create | update | show | list | download | delete` for managing Foundry Skills from any directory. Implements the design in PR #8204 / issue #8142: - New extension under `cli/azd/extensions/azure.ai.skills` (namespace `ai.skill`, id `azure.ai.skills`, version `0.0.1-preview`). - Typed Foundry Skills data-plane client in `internal/pkg/skill_api` with SKILL.md YAML front matter parser and two-phase safe gzip-tar extractor (zip-slip guard, no symlinks / hard links, 10,000-entry / 512 MB caps, staging + atomic copy, `--force` for collisions). - Three mutually exclusive `create` modes: inline (`--description` + `--instructions`), `--file SKILL.md` (parsed locally), and `--file *.tar.gz` / `*.tgz` (streamed as `application/gzip` to `POST /skills:import`). `--force` does delete-then-create. - `update` accepts inline flags or `--file *.md` only; gzip is rejected with a structured suggestion to use `create --force`. - `download` extracts by default into `./.agents/skills/<name>/` and supports `--raw` to write the unmodified archive. - `delete` confirms by default; `--force` skips, and `--no-prompt` without `--force` errors. Interactive `n` returns exit 0. - Endpoint resolution shares the 5-level cascade with `azure.ai.agents` via a read-only fallback to `extensions.ai-agents.project.context.endpoint` when the new `extensions.ai-skills.project.context.endpoint` key is unset. - Bearer scope `https://ai.azure.com/.default`, `Foundry-Features: Skills=V1Preview` header, API version `2025-11-15-preview`. Debug log file `azd-ai-skills-<date>.log`. `IncludeBody` opt-out on the HTTP pipeline until a sanitizer for `description` / `instructions` lands. - Skill name regex aligned with `agent_yaml.ValidateAgentName`: `^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\$`. Closes #8142. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
azd ai skill command group (preview)azd ai skill command group
huimiu
added a commit
that referenced
this pull request
May 18, 2026
77c69a9 to
28ca3b2
Compare
…SafeExtract P1 - Endpoint validation (Finding 1): - Add validateEndpoint() to endpoint.go that rejects non-https URLs and empty hosts before sending Azure bearer tokens to any resolved endpoint. - Called at all five resolution levels (flag, azd env, global config x2, host env var) so misconfigured endpoints fail with a clear message rather than an opaque SDK error. - Host-suffix validation intentionally deferred to HTTP layer per design. - Add TestResolveProjectEndpoint_InvalidScheme covering http/ftp/no-scheme/empty-host cases. P1 - Symlink escape in SafeExtract (Finding 2): - archive.go copy phase was vulnerable: if OutputDir contained a pre-existing symlink to a directory outside OutputDir, MkdirAll + copyFile would follow it silently, writing extracted files outside the intended destination. - Fix: resolve OutputDir with EvalSymlinks once; before each copyFile resolve the destination directory and assert it is under the real output dir. - Add isUnder() helper for path containment check. - Add TestSafeExtract_RejectsSymlinkParentEscape (skipped on Windows).
28ca3b2 to
4757f72
Compare
The Skills data-plane lives under api-version=v1; the preview opt-in is communicated via the Foundry-Features: Skills=V1Preview header. Using 2025-11-15-preview as the api-version returns 400 UnsupportedApiVersion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skills created from inline JSON or SKILL.md have no downloadable package; the server returns an opaque 404 'does not have an associated package'. Pre-flight Get the skill so the download command can return a structured CodeSkillNoPackage validation error with an actionable suggestion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
256aec8 to
c957348
Compare
The live Foundry Skills service implements POST /skills:import and
GET /skills/{name}:download with application/zip, not application/gzip
as the upstream TypeSpec declares. Verified via 415 Unsupported Media
Type on gzip uploads. Public docs confirm:
https://learn.microsoft.com/azure/foundry/agents/how-to/tools/skills
Changes:
- skill_api: replace archive/tar+compress/gzip with archive/zip
- skill_api: Download now returns []byte (archive/zip needs io.ReaderAt)
- skill_api: rename ContentTypeGzip -> ContentTypeZip, ErrInvalidGzip ->
ErrInvalidZip
- cmd: accept '.zip' for --file; reject '.tar.gz'/'.tgz'
- cmd: writeRaw now writes '<name>.zip'
- tests: rewrite archive_test.go and archive_peek_test.go for ZIP
- docs (AGENTS.md, README.md, CHANGELOG.md): s/gzip,tar.gz/zip/g
The design spec (PR #8204) will need a follow-up to match.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Foundry Skills surface is asymmetric on archive format:
- POST /skills:import requires application/zip (gzip yields 415)
- GET /skills/{name}:download returns application/gzip
Make SafeExtract sniff magic bytes (PK or 1f 8b) and dispatch to either
the zip or the gzip+tar handler. Download() now accepts both
Content-Type values. The raw download filename uses .zip or .tar.gz
based on the detected format.
Also:
- Add DetectArchiveFormat() public helper for callers that need the
format (e.g. the --raw filename picker).
- Add gitignore for local test artifacts (zips, tar.gz, debug logs).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
Remove restating-the-code comments, narrative section headers, and verbose struct-field docs. Keep design-rationale notes (TypeSpec/docs mismatch on archive format, --force destructive sequence, defensive path validation) and important safety annotations. Net: -396 lines of comments, no behavior change.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new first-party azure.ai.skills azd extension that exposes the standalone azd ai skill command group for managing Foundry Skills from the terminal, including endpoint resolution, typed REST client calls, SKILL.md parsing, and safe package download/extraction.
Changes:
- Introduces the
azd ai skillCobra command tree (create|update|show|list|download|delete) with structured errors and output formatting. - Adds a typed Skills data-plane REST client plus SKILL.md parsing and archive handling helpers (including “safe extract”).
- Adds extension packaging/build assets (extension.yaml, versioning, build scripts, lint config, and docs).
Reviewed changes
Copilot reviewed 42 out of 43 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| cli/azd/extensions/azure.ai.skills/version.txt | Initial extension version. |
| cli/azd/extensions/azure.ai.skills/README.md | Extension overview, command surface, endpoint resolution, dev workflow. |
| cli/azd/extensions/azure.ai.skills/main.go | Extension binary entry point. |
| cli/azd/extensions/azure.ai.skills/internal/version/version.go | Build-time version variables. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/skill_md.go | SKILL.md YAML-front-matter + body parser. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/skill_md_test.go | Unit tests for SKILL.md parsing. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/models.go | Public/CLI JSON models + wire conversions. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/client.go | Typed REST client for Skills (create/update/get/list/download/delete). |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/client_test.go | Client request/response behavior tests via httptest server. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/archive.go | Archive format detection + safe extraction implementation. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/archive_test.go | Tests for safe extraction and validation limits. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/archive_peek.go | ZIP SKILL.md name peek helper for --force safety checks. |
| cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/archive_peek_test.go | Tests for archive name peek behavior. |
| cli/azd/extensions/azure.ai.skills/internal/exterrors/errors.go | Structured error helpers and Azure SDK error conversion. |
| cli/azd/extensions/azure.ai.skills/internal/exterrors/codes.go | Extension-specific error codes and operation names. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/version.go | version subcommand. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_validate.go | Skill name validation. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_validate_test.go | Tests for name validation + flag-mode validation helpers. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_update.go | skill update implementation (GET-merge-POST). |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_show.go | skill show implementation. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_list.go | skill list implementation (paged flattening). |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_download.go | skill download implementation (raw vs extracted). |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_delete.go | skill delete implementation with confirmation behavior. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_create.go | skill create implementation (inline, SKILL.md, ZIP package). |
| cli/azd/extensions/azure.ai.skills/internal/cmd/skill_context.go | Endpoint + credential + client construction. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/root.go | Root command wiring, persistent flags, and host commands. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/output.go | JSON/table output helpers for skills. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/output_test.go | Tests for table output rendering. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/endpoint.go | Endpoint resolution cascade + URL validation. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/endpoint_test.go | Tests for endpoint resolution and validation. |
| cli/azd/extensions/azure.ai.skills/internal/cmd/debug.go | Debug logging setup for stdlib log + Azure SDK logger. |
| cli/azd/extensions/azure.ai.skills/go.sum | Extension module dependency lockfile. |
| cli/azd/extensions/azure.ai.skills/go.mod | Extension module definition and dependencies. |
| cli/azd/extensions/azure.ai.skills/extension.yaml | Extension metadata and examples for azd. |
| cli/azd/extensions/azure.ai.skills/cspell.yaml | Spellcheck configuration for extension-specific terms. |
| cli/azd/extensions/azure.ai.skills/ci-test.ps1 | CI test runner for the extension. |
| cli/azd/extensions/azure.ai.skills/ci-build.ps1 | CI build script for the extension binary. |
| cli/azd/extensions/azure.ai.skills/CHANGELOG.md | Initial release notes for the extension. |
| cli/azd/extensions/azure.ai.skills/build.sh | Local multi-platform build helper (bash). |
| cli/azd/extensions/azure.ai.skills/build.ps1 | Local multi-platform build helper (PowerShell). |
| cli/azd/extensions/azure.ai.skills/AGENTS.md | Extension-specific contributor/agent guidance. |
| cli/azd/extensions/azure.ai.skills/.golangci.yaml | Extension-local golangci-lint configuration. |
| cli/azd/extensions/azure.ai.skills/.gitignore | Ignores local build/test artifacts for the extension. |
Comments suppressed due to low confidence (3)
cli/azd/extensions/azure.ai.skills/internal/pkg/skill_api/archive.go:389
copyFilewrites todstviaos.OpenFile(..., O_TRUNC, ...), which will follow a symlink if one exists atdst. For a “safe extraction” helper, this should defensively refuse to write through symlinks (and other non-regular file types), including in--forcemode.
cli/azd/extensions/azure.ai.skills/internal/cmd/skill_download.go:287- The command help text says
downloadretrieves a “ZIP package” and--rawwrites the “ZIP archive as-is”, but the implementation explicitly supports ZIP or gzip-tar downloads (and even chooses the extension based on magic bytes). Update the Long/flag help strings to refer to a generic “archive/package” to avoid contradicting actual behavior.
cli/azd/extensions/azure.ai.skills/internal/cmd/endpoint.go:133 validateEndpointreturns plainfmt.Errorferrors for invalid user input. Since endpoint validation errors are user-fixable and should produce stable telemetry, consider returning a structuredexterrors.Validation(e.g.,CodeInvalidParameter) instead of an untyped error so the azd host categorizes and renders it consistently.
Comment on lines
+118
to
+124
| lineOffset += step | ||
| cur = cur[step:] | ||
| if step == 0 { | ||
| return 0, 0, fmt.Errorf("SKILL.md must begin with a YAML front matter block delimited by '---'") | ||
| } | ||
| continue | ||
| } |
Comment on lines
+299
to
+307
| dstDir := filepath.Dir(filepath.Join(opts.OutputDir, filepath.FromSlash(rel))) | ||
| if mkErr := os.MkdirAll(dstDir, 0700); mkErr != nil { | ||
| return fmt.Errorf("create output dir for %q: %w", rel, mkErr) | ||
| } | ||
| realDstDir, evalErr := filepath.EvalSymlinks(dstDir) | ||
| if evalErr != nil { | ||
| return fmt.Errorf("resolve destination path for %q: %w", rel, evalErr) | ||
| } | ||
| if !isUnder(realDstDir, realOutDir) { |
Comment on lines
+228
to
+235
| } | ||
|
|
||
| type createMode int | ||
|
|
||
| const ( | ||
| modeNone createMode = iota | ||
| modeInline | ||
| modeFileMd |
Comment on lines
+9
to
+13
| ```bash | ||
| azd ai skill create <name> [--description "..." --instructions "..."] | ||
| azd ai skill create <name> --file ./SKILL.md | ||
| azd ai skill create <name> --file ./skill.zip | ||
|
|
Comment on lines
+4
to
+7
| // Package skill_api provides a typed REST client for the Foundry Skills | ||
| // data-plane surface, plus helpers for parsing SKILL.md files and safely | ||
| // extracting downloaded skill packages. | ||
| package skill_api |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #8142. Implements the design from #8204.
Summary
New standalone
azure.ai.skillsextension exposingazd ai skill create | update | show | list | download | delete.create— three mutually exclusive modes: inline (--description+--instructions), SKILL.md (--file *.md), or gzip package (--file *.tar.gz).--forcedoes delete-then-create.update— inline flags or--file *.mdonly; gzip rejected with suggestion to usecreate --force.download— default extracts into./.agents/skills/<name>/with two-phase safe extraction (zip-slip guard, no symlinks, 10K-entry / 512 MB caps).--rawkeeps the gzip archive.delete— confirms by default;--forceskips,--no-promptrequires--force.azure.ai.agentsvia read-only fallback toextensions.ai-agents.project.context.endpoint.Test
Install ai.skill extension

Create skill

Create skill (create --description X --file Y)

Create skill (zip)

Show skill

List skills

Download skill (no-blob)

Download skill

Update skill

Delete skill (interactive)

Delete skill (--no-prompt, without --force)

Delete skill
