azwi
azwi fetches Azure DevOps work items and turns them into clean context for coding agents. It returns descriptions, acceptance criteria, comments, attachments, and linked pull requests as deterministic JSON or readable Markdown.
The CLI is designed for both people and agents. Successful output stays on stdout. Logs, progress, and maintenance notices go to stderr.
Prerequisite
azwi is designed to be used with uv. Install uv before continuing. The documented workflows and managed agent skill use uvx to run the tool without requiring a global installation.
Quick start with an agent
- Install
uvusing the link above. - Create an Azure DevOps personal access token for your organization with Work Items: Read and Code: Read.
- Run setup using the PowerShell or Bash commands below and enter the PAT at the hidden terminal prompt. Replace the example URL with a work item you can access.
- Use
$azure-workitemin your agent.
To create the PAT, open Azure DevOps User settings > Personal access tokens. See Microsoft's PAT instructions.
Choose an expiration that fits your organization's policy. A longer lifetime reduces renewal interruptions. Use a shorter lifetime when the information or environment calls for it.
PowerShell:
uvx azwi setup "https://dev.azure.com/my-org/Payments/_workitems/edit/2195"
Bash or zsh:
uvx azwi setup "https://dev.azure.com/my-org/Payments/_workitems/edit/2195"
Setup extracts and saves your organization, verifies the supplied work item through a normal fetch, and installs the managed agent skill. Setup saves the PAT in ~/.azwi/credentials.toml, separately from non-secret settings in ~/.azwi/config.toml. If AZWI_PAT is already set, setup uses it without saving its value. No separate test fetch is needed.
Bare uvx azwi setup asks for a work item URL or organization name. You can also use uvx azwi setup --org my-org. An organization-only setup configures local defaults but does not verify a work item. No project default is required.
Then use $azure-workitem in Codex, Claude Code, or another agent harness that supports skills:
Use $azure-workitem to inspect 2195. Summarize the requested change, acceptance criteria, and any relevant pull request discussion.
The skill accepts numeric IDs and supported Azure DevOps Cloud URLs. It requests PR comments and downloads only when needed. Start a new agent session if the installed skill is not yet available.
Credentials and environment overrides
Saved credentials use one PAT per organization:
[orgs."my-org"]
pat = "<your-pat>"
Setup creates this file for you. It stores the PAT as plain text with normal inherited filesystem permissions. It does not apply special restrictions or change ACLs. Keep it out of repositories and shared config exports. config show, setup reports, and diagnostics never display saved PATs.
A non-empty AZWI_PAT overrides the saved token. This also works if the credentials file is unavailable or malformed. If file storage is unavailable, set the variable in the environment where azwi runs:
PowerShell:
$env:AZWI_PAT = "<your-pat>"
uvx azwi setup "https://dev.azure.com/my-org/Payments/_workitems/edit/2195"
Bash or zsh:
export AZWI_PAT="<your-pat>"
uvx azwi setup "https://dev.azure.com/my-org/Payments/_workitems/edit/2195"
These assignments affect the current shell and its child processes. An already-running agent application does not receive the change. Configure the environment where the agent actually executes uvx, including remote or sandboxed environments. Setup cannot change its parent shell's environment. Do not paste the PAT into an agent conversation.
On Windows, you can optionally persist the current value for your own account without administrator rights:
[Environment]::SetEnvironmentVariable("AZWI_PAT", $env:AZWI_PAT, "User")
Future applications must inherit the updated environment. On Linux and macOS, persistent shell variables usually belong in the appropriate user shell startup file. Bash login shells and interactive shells read different files. Shell configuration does not automatically configure every desktop application. Persisting an environment variable this way stores the PAT as an ordinary setting. Environment variables are not encrypted secret storage.
Check or repair setup
uvx azwi config check
uvx azwi config check "https://dev.azure.com/my-org/Payments/_workitems/edit/2195"
config check is read-only and reports the effective organization and its source, the credential source (environment, file, missing, or unavailable) and credentials file path, skill status, and exact next steps. With a URL it also runs a default fetch. Without a URL it only checks local readiness. Run it through the agent when diagnosing differences between terminal and agent access. A verified work item without returned linked PRs does not establish Code access.
If a saved PAT expires or is rejected, run uvx azwi setup "<work-item-url>" --replace-pat in your terminal. Setup verifies the replacement before saving it. If you use AZWI_PAT, update its value in the execution environment instead. Unset it before using --replace-pat. Make sure the token applies to the selected organization and has both required scopes.
Setup and checks return JSON by default. Add --format plain for a compact text report. Setup installs the skill by default and respects existing managed-skill protections. Use --skills-dir DIR with either command for a custom skill root. setup --non-interactive never prompts. It uses AZWI_PAT or the saved PAT for the selected organization. If both are missing, setup saves the organization and installs the skill, then returns exit code 4 with ready: false and repair instructions. Run interactive setup or set the variable and rerun setup to verify the work item. A failure to save an entered PAT also returns incomplete readiness and environment-variable instructions. Sandboxed and remote agents need access to the same credentials file or their own AZWI_PAT. Run config check through the agent to verify its access.
What it returns
The default JSON output is stable and designed for agent parsing. Markdown output is designed for reading or adding directly to a prompt.
A Markdown result looks like this:
# 2195 Login bug
# Metadata
- Type: Bug
- State: Active
- Assigned To: Alice
- Changed Date: 2026-03-10T10:00:00Z
# Description:
Main **issue**
## Repro Steps
1. Open app
2. Click sign in
# Acceptance Criteria:
Should be fixed
JSON always contains top-level work_item metadata and a sections object. Text fields contain both rendered Markdown and the Azure DevOps field reference name. Raw HTML is not included.
| Format | Best for |
|---|---|
json |
Agent tools, scripts, and automation |
markdown |
Reading, saved context, and direct prompt input |
Use the CLI directly
Fetch a work item without installing the package globally:
uvx azwi 2195 --org my-org
Save the organization as a default so later calls need only the work item ID:
uvx azwi config set-defaults --org my-org
uvx azwi 2195
Request Markdown instead of the default JSON:
uvx azwi 2195 --format markdown
Write the result to a file:
uvx azwi 2195 --format markdown --output work-item-2195.md
Existing output files are preserved unless --force is supplied.
To install the command as a persistent tool:
uv tool install azwi
azwi 2195
The examples below continue to use uvx azwi so they work without a global installation.
How fetching works
The fetch model has four core rules:
- A work item ID is looked up within an Azure DevOps organization.
- The organization comes from
--org, user config, orAZWI_ORG. - The fetched work item's
System.TeamProjectfield determines the project used for field mappings and follow-up requests. - Requested sections are returned in a fixed order and failures stop the command instead of producing partial output.
The main interface is:
azwi <work_item_id> [options]
There is no fetch subcommand and no --project option for direct work item lookup. Project selection remains available for project-scoped commands such as fields.
Choose the context you need
Without --section, azwi returns all standard sections. Repeat --section to request a smaller result.
| Goal | Command |
|---|---|
| Fetch all default context | uvx azwi 2195 |
| Fetch acceptance criteria only | uvx azwi 2195 --section acceptance |
| Fetch metadata and comments | uvx azwi 2195 --section metadata --section comments |
| Increase the comment limit | uvx azwi 2195 --section comments --comment-limit 20 |
| Include all linked PR states | uvx azwi 2195 --section prs --pr-status all |
| Add a custom field once | uvx azwi 2195 --extra-field Custom.DevNotes |
| Save prompt-ready Markdown | uvx azwi 2195 --format markdown --output work-item-2195.md |
Available sections:
| Section | Content |
|---|---|
metadata |
Type, state, assignee, and changed date |
description |
Description plus bug repro steps and system information |
acceptance |
Acceptance criteria |
comments |
Work item discussion, newest first |
attachments |
Attachment names, URLs, comments, sizes, and local paths when downloaded |
prs |
Linked pull requests and optional review discussion |
Section output order is fixed by the tool, not by the order of --section arguments.
The comment limit defaults to 10 and accepts values from 1 through 50. Linked pull request metadata defaults to active PRs. Requested section keys remain present in JSON even when their content is empty.
Configure defaults and fields
azwi stores non-secret defaults and field mappings in ~/.azwi/config.toml. Use the CLI to manage the common settings:
uvx azwi config show
uvx azwi config set-defaults --org my-org --project Payments
uvx azwi config set-field --global --acceptance Microsoft.VSTS.Common.AcceptanceCriteria
uvx azwi config set-field --project Payments --description Custom.DevDescription
uvx azwi config add-extra-field --project Payments Custom.ReleaseNotes
config show displays the effective resolved configuration. Config updates create the file when needed and never write AZWI_PAT into it. config check does not create or update files.
Settings are resolved in this order:
- Explicit CLI flags
- Matching project-specific config
- Matching organization-specific config
- Top-level config defaults
- Environment variables
- Built-in defaults
The common single-organization configuration looks like this:
[defaults]
org = "my-org"
project = "Payments"
[defaults.fields]
description = "System.Description"
acceptance = "Microsoft.VSTS.Common.AcceptanceCriteria"
repro_steps = "Microsoft.VSTS.TCM.ReproSteps"
system_info = "Microsoft.VSTS.TCM.SystemInfo"
[projects."Payments".fields]
extra_fields = ["Custom.DevNotes"]
Organization-specific profiles are also supported:
[orgs."other-org".defaults]
project = "ProjectX"
[orgs."other-org".defaults.fields]
acceptance = "Custom.Acceptance"
[orgs."other-org".projects."ProjectY".fields]
extra_fields = ["Custom.ReleaseNotes"]
Discover and override fields
List the available field reference names for a work item type:
uvx azwi fields --type Bug --project Payments
uvx azwi fields --type "User Story" --project Payments
Override logical fields for one invocation:
uvx azwi 2195 --field-description Custom.DevDescription
uvx azwi 2195 --field-acceptance Custom.Acceptance
uvx azwi 2195 --field-repro-steps Custom.ReproSteps
uvx azwi 2195 --field-system-info Custom.SystemInfo
Use repeatable --extra-field REFNAME options to add fields without replacing the standard sections. Markdown labels extra fields by reference name. JSON returns them in the extra_fields object.
Download attachments and images
The attachments section lists attachment metadata without downloading files. Downloads are always explicit:
uvx azwi 2195 --download-attachments work-item-2195-files
--download-attachments DIR automatically includes the attachments section. Without selectors, it downloads every work item attachment.
Use repeatable exact-match selectors to list or download specific attachments:
uvx azwi 2195 --section attachments --attachment-name notes.txt
uvx azwi 2195 --download-attachments files --attachment-url https://dev.azure.com/...
The attachment output contains exact name and url values for follow-up calls. If any selector does not match, the command fails instead of silently returning a partial result.
Relative download directories resolve from the current working directory. With --output, rendered attachment paths are relative to the output file location. Without --output, they are relative to the current working directory when possible.
Use --download-images DIR with --output to download remote images found in rendered Markdown and rewrite their links to local relative paths:
uvx azwi 2195 --format markdown --output work-item-2195.md --download-images work-item-2195-images
Relative image directories also resolve from the current working directory. Image downloading without --output is a usage error.
Include pull request discussion
The prs section lists linked pull requests. PR thread comments are high-volume context and remain opt-in:
uvx azwi 2195 --include-pr-comments
This option automatically includes the prs section. Active threads are included by default. Include active and resolved threads with:
uvx azwi 2195 --include-pr-comments --pr-comment-status all
Azure DevOps system comments are excluded unless --include-pr-system-comments is supplied.
Manage the agent skill
The standard skill location is ~/.agents/skills/azure-workitem/SKILL.md.
uvx azwi skill install
uvx azwi skill status
uvx azwi skill status --format plain
uvx azwi skill remove
Skill commands return JSON by default. All three commands accept --skills-dir DIR for a custom skills root. The older install-skill, skill-status, and remove-skill command aliases remain available.
Normal invocations of an installed release automatically update an older managed skill when its installed content is unchanged. Synchronization is local. It does not query PyPI, refresh uv's cache, or update the CLI. Missing skills, unmanaged skills, modified managed skills, equal versions, and newer versions are left alone.
Inspect version, integrity, and update eligibility before replacing modified managed content:
uvx azwi skill status
uvx azwi skill install --force
Install-time --force can replace altered content only when the skill is managed by azwi. It never overwrites an unmanaged skill or downgrades a newer version.
Automatic synchronization checks only the standard location. Local checkouts, direct source installs, editable builds, and custom locations require explicit skill commands. Updates affect future agent sessions and may not change instructions already loaded by a running agent.
skill remove removes the managed SKILL.md and its directory when empty. It preserves unrelated files. Removing unmanaged content requires --force. Installation and removal refuse linked paths and unexpected file types.
Reference
Useful discovery and metadata commands:
uvx azwi --help
uvx azwi 2195 --help
uvx azwi fields --help
uvx azwi config --help
uvx azwi setup --help
uvx azwi config check --help
uvx azwi skill --help
uvx azwi --about
uvx azwi version
Environment variables:
| Variable | Purpose |
|---|---|
AZWI_PAT |
Azure DevOps PAT override. Otherwise use the selected organization's saved PAT |
AZWI_ORG |
Default organization for fetch and fields |
AZWI_PROJECT |
Default project for project-scoped commands such as fields |
Exit codes:
| Code | Meaning |
|---|---|
0 |
Success |
2 |
Usage or input error |
3 |
Configuration error |
4 |
Authentication error |
5 |
Work item or resource not found |
6 |
Azure DevOps API error |
7 |
Throttling retries exhausted |
uvx azwi --about prints the command name and version, a short summary, the project URL, and the MIT license. The project source is available on GitHub.
Development and release
The repository supports both execution paths required by the project:
uv run ./azwi.py ...through PEP 723 metadata in the root wrapperuvx azwi ...through the package defined inpyproject.toml
Run the CLI from a checkout:
uv run ./azwi.py --help
uv run ./azwi.py 2195 --org my-org
Run the tests and build the distribution:
uv run python -m unittest discover -s tests -v
uv build --no-sources
uv run python tests/wheel_smoke.py dist/azwi-1.3.0-py3-none-any.whl
The wheel smoke check uses temporary environments and a temporary home directory. It validates wheel packaging, index-style metadata, local and editable installs, uvx, and the PEP 723 wrapper. It requires access to build and runtime dependencies.
To release a version:
- Tag a release such as
v1.3.0. - Let GitHub Actions build the package.
- Publish to PyPI using Trusted Publishing.
License
MIT
Metadata
Release files for azwi 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| azwi-1.3.0.tar.gz | 56.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| azwi-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 96.6 kB
Release files / azwi-1.3.0.tar.gz
| Download URL | azwi-1.3.0.tar.gz |
|---|---|
| Size | 56.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4e55dab8dca52b29546e9fbada775da2fcce2b786fc68db90e13037958ea78f4
|
|
BLAKE2b-256 checksum How to use checksums |
d36846b7e4c13735db11bbf9079bc6f0baaede761f7ed06ba4a490295da535dd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / azwi-1.3.0-py3-none-any.whl
| Download URL | azwi-1.3.0-py3-none-any.whl |
|---|---|
| Size | 40.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9929c2ee3e2c02f3fd2dbf5cdc595d8c7332d4f54154943bc962007d3e030c24
|
|
BLAKE2b-256 checksum How to use checksums |
3641074999e0cde2daa7700e9cce06d5b6bd759e2c3593e8d7e2c5afcbc48d9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.13 {"installer":{"name":"uv","version":"0.12.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|