abwi
A CLI for reading and writing Azure Boards work items with first-class
Markdown support — what az boards makes hard, abwi makes the default.
Why
- Azure Boards supports native Markdown in large text fields, but
az devopscannot write it: setting a field's format requires an extramultilineFieldsFormatentry in the JSON Patch document, which the Azure CLI never sends.abwiadds it automatically, so Description, Repro Steps, Acceptance Criteria, and friends land as real Markdown — not HTML-escaped text. - Every multiline field is addressed generically with
-f <refname>=<value>, with curl-style input:@filereads a file,@-reads stdin, and a leading\@escapes a literal@. No per-field flags to memorize.
Install
via npm
npm install -g @abwi/cli
abwi --help
via PyPI
pip install abwi
abwi --help
via GitHub Releases
Download the binary for your platform from Releases.
via Go
go install github.com/lohn/abwi/cmd/abwi@latest
Authentication
Entra ID (default). Sign in once with the Azure CLI; abwi picks up the
Azure CLI credential automatically:
az login
PAT (explicit opt-in, not recommended). Set auth = "pat" in the
global config file (or pass --auth pat) and export the token as
ABWI_PAT (falling back to AZURE_DEVOPS_EXT_PAT, the variable the Azure
CLI uses):
export ABWI_PAT=... # or AZURE_DEVOPS_EXT_PAT
abwi --auth pat show 123
PAT values are read only from environment variables, never from config files, so a token can't end up committed by accident.
The auth key is honored only in the global config file — a checked-out
repository must not be able to switch your authentication mode, so auth in
a repo-local .abwi.toml is ignored with a warning.
Configuration
abwi merges configuration from two TOML files:
- Local:
.abwi.toml, found by walking up from the current directory (put it at your repository root) - Global: the per-user config directory —
~/.config/abwi/config.tomlon Linux (Go'sos.UserConfigDir, so~/Library/Application Support/abwi/config.tomlon macOS and%AppData%\abwi\config.tomlon Windows)
Precedence, highest first: flags > environment (ABWI_ORG,
ABWI_PROJECT) > local file > global file.
# .abwi.toml — committed at the repository root
org = "https://dev.azure.com/myorg"
project = "MyProject"
default-type = "Product Backlog Item"
# Shorthand names for -f, expanded to full field reference names
[aliases]
ac = "Microsoft.VSTS.Common.AcceptanceCriteria"
repro = "Microsoft.VSTS.TCM.ReproSteps"
Available keys:
| Key | Description | Default |
|---|---|---|
org |
Organization URL (https://dev.azure.com/<org>) |
— |
project |
Project name | — |
format |
Large text format: markdown / html |
markdown |
auth |
Authentication: entra / pat — global config only |
entra |
default-type |
Work item type used when create --type is omitted |
— |
[aliases] |
Table of shorthand names for -f, expanded to full reference names |
— |
Run abwi config to see the resolved values and where each one came from —
see Usage below.
Usage
Create a work item (--type falls back to default-type from the config;
-d/-f values support @file and @-):
abwi create -T Bug -t "Crash when saving a draft" \
-d @description.md \
-f Microsoft.VSTS.TCM.ReproSteps=@repro.md
# The same, using the [aliases] shorthand from the config
abwi create -T Bug -t "Crash when saving a draft" -d @description.md -f repro=@repro.md
Update fields of an existing work item (Markdown from stdin via @-; use
\@ when a value must start with a literal @):
generate-criteria | abwi update 123 -s Active -f ac=@- \
-f System.Description='\@mentions start with an escaped at-sign'
Show a work item (--json for the raw response):
abwi show 123
List work items — yours by default, most recently changed first; filter with flags or take over with a full WIQL query:
abwi list -T Bug -s Active --limit 20
abwi list --all # everyone's, not just yours
abwi list --assignee "someone@example.com" # someone else's
abwi list --wiql @query.wiql # full control
Comments (posted as Markdown by default):
abwi comment add 123 "Reproduced on \`main\`; see #456."
abwi comment add 123 @- # comment body from stdin
abwi comment list 123
Link and unlink (--type: parent, child, related — the default — or a
full System.LinkTypes.* reference name):
abwi link 123 456 --type parent # make #456 the parent of #123
abwi unlink 123 456 # --type disambiguates multiple links
Show the resolved configuration, each value annotated with its origin
(flag, env, local, global, or default):
$ abwi config
# global: /home/you/.config/abwi/config.toml
# local: /home/you/src/myrepo/.abwi.toml
org = "https://dev.azure.com/myorg" # local
project = "MyProject" # env
format = "markdown" # default
auth = "entra" # default
default-type = "Product Backlog Item" # local
[aliases]
ac = "Microsoft.VSTS.Common.AcceptanceCriteria"
repro = "Microsoft.VSTS.TCM.ReproSteps"
abwi config <key> prints a single value; --json prints the whole thing as
JSON.
The --format html fallback
If your organization does not support Markdown work items yet (or you want to
avoid the irreversible switch), pass --format html or set
format = "html" in the config. You still write Markdown — abwi converts
it to HTML via goldmark before sending,
and the fields stay in HTML format:
abwi create -T Bug -t "Crash when saving a draft" --format html -d @description.md
License
MIT © lohn
Release files for abwi 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| abwi-0.1.0-py3-none-win_arm64.whl | Python 3 | none | Windows ARM64 | Details |
| abwi-0.1.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| abwi-0.1.0-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| abwi-0.1.0-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| abwi-0.1.0-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
| abwi-0.1.0-py3-none-macosx_10_9_x86_64.whl | Python 3 | none | macOS 10.9+ x86-64 | Details |
Total release size: 24.2 MB
Release files / abwi-0.1.0-py3-none-win_arm64.whl
| Download URL | abwi-0.1.0-py3-none-win_arm64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | Python 3 Windows ARM64 |
|
SHA-256 checksum How to use checksums |
85a47c6026f988c5fb80a9833e0061e034b6931ac233a84874178d9a47c49b99
|
|
BLAKE2b-256 checksum How to use checksums |
84f33f596b5ff5dac4382444e1acecec99d2e5af0892f8eba428f3ec00bcc6a7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / abwi-0.1.0-py3-none-win_amd64.whl
| Download URL | abwi-0.1.0-py3-none-win_amd64.whl |
|---|---|
| Size | 4.3 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
b3ad6a1f8e5ba2bcb77b9b2d80176387105c47a1cc502cb19f1319c5a1ab0006
|
|
BLAKE2b-256 checksum How to use checksums |
e0a0cc8a4bbe2d3c35ccef623844485c32a46875f2b982a6f01800caab681a84
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / abwi-0.1.0-py3-none-manylinux_2_17_x86_64.whl
| Download URL | abwi-0.1.0-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
96e2ae9fc995503a8e355881ac970fae855c2c49c69ad90057d55c42cda27a40
|
|
BLAKE2b-256 checksum How to use checksums |
37fc17c0e0a4b1f1a0681fae4c4c6ec1f112c2442255f6b23d047e2ce0513e5d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / abwi-0.1.0-py3-none-manylinux_2_17_aarch64.whl
| Download URL | abwi-0.1.0-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 3.8 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
8420905beec401e1f53becc54902280ef5925a3028cfd43360883bb2f64ef516
|
|
BLAKE2b-256 checksum How to use checksums |
67aa8ee819ee5591e0175c534f7ba6d1e88aad0486f169e4c5e087432cdb6f7d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / abwi-0.1.0-py3-none-macosx_11_0_arm64.whl
| Download URL | abwi-0.1.0-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 3.9 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e61e6d17d65ba75bb2f57070540dfe8970c9a92c9b7db4b922d12992daacf9c3
|
|
BLAKE2b-256 checksum How to use checksums |
53df5a57364cc2ade18a0f28d568e54010871a227e883011023111263fecf99a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency logRelease files / abwi-0.1.0-py3-none-macosx_10_9_x86_64.whl
| Download URL | abwi-0.1.0-py3-none-macosx_10_9_x86_64.whl |
|---|---|
| Size | 4.2 MB |
| Tags | Python 3 macOS 10.9+ x86-64 |
|
SHA-256 checksum How to use checksums |
06d7639f42fef89a9bae63006fcdb9c1153cc610df90203e09426653843ddd40
|
|
BLAKE2b-256 checksum How to use checksums |
d6aa23408faa3b829e801ac3232253c8fbfdd2945314a2f04cf1663c9a82e388
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 12, 2026.
Transparency log