Skip to content

Repository files navigation

devops-cli

CLI for interacting with Azure DevOps via REST API, without depending on the az CLI.

Requirements

Installation

Install as a .NET Global Tool from NuGet:

dotnet tool install -g azure-devops-cli

Update or uninstall:

dotnet tool update -g azure-devops-cli
dotnet tool uninstall -g azure-devops-cli

The command is invoked as devops.

Build from source

dotnet publish DevOps/DevOps.csproj -c Release -o ./publish

Add the publish directory to your PATH, or copy the executable to a directory already on your PATH.

Configuration

Pick one authentication method:

# Option A — Microsoft Entra ID (interactive browser sign-in)
devops config -o https://dev.azure.com/myorg
devops config --login
# Option B — Personal Access Token
devops config -o https://dev.azure.com/myorg -P <PAT>

Then the shared settings:

devops config -p MyProject # set default project
devops config -T "MyProject Team"# set default team (for iteration and area resolution)
devops config -e your@email.com# set email manually if auto-detection fails
devops config --show # display current configuration
devops config --logout # sign out of Entra ID (clear the cached token)
devops config --reset # remove all configuration and sign out
devops config --refresh-cache # force re-fetch of iteration and area on next create

When you sign in (--login) or provide a --pat, the CLI automatically fetches your display name and email from Azure DevOps. The email is used to resolve --assigned-to me.

OptionAliasDescription
--org-oAzure DevOps organization URL
--loginSign in interactively with Microsoft Entra ID
--logoutSign out and clear the cached Entra ID token
--tenantEntra ID tenant ID or domain to sign in against (defaults to your home tenant)
--pat-PPersonal Access Token
--project-pDefault project (used when --project is omitted from other commands)
--team-TDefault team for resolving the active iteration and area path (defaults to {Project} Team)
--email-eYour email address, used to resolve --assigned-to me. Set manually if auto-detection fails
--borderTable border style for list output: minimal (default), square, or markdown
--showDisplay the current configuration (auth mode and masked secret)
--resetRemove all local configuration and cache, and sign out
--refresh-cacheForce re-fetch of iteration and area path on next create

Work Item Commands

Note on -p / -P:-p is always --project. The uppercase -P is each command's secondary flag: --parent on list, mine and normalize; --priority on create and update; --pat on config.

get — Get work item details

Shows the core fields plus area, iteration, tags, parent, reason, the description, and a browser URL that opens the item directly. Add --comments to also fetch the discussion.

devops get -i 1234
devops get -i 1234--comments # also show the discussion comments
devops get -i 1234-p AnotherProject
devops get -i 1234-o json # flat projection for scripting
OptionAliasDescription
--id-iWork item ID (required)
--project-pProject name (uses default if configured)
--commentsAlso fetch and show the discussion comments (extra API call)
--output-oOutput format: json or csv. Defaults to a detailed view

Comments require a separate (preview) API call, so they are opt-in via --comments. The --output json/csv modes always return the flat field projection.


mine — List work items assigned to me

Shortcut for list --assigned-to me. The ASSIGNED TO column is omitted since all items belong to the current user.

devops mine
devops mine -s Active
devops mine -t Bug
devops mine -s "In Progress"-t Task
devops mine -P 1234# only children of work item 1234
OptionAliasDescription
--project-pProject name (uses default if configured)
--state-sFilter by state
--type-tFilter by work item type
--query-qAdditional WIQL WHERE clause
--parent-PFilter by parent work item ID
--top-nMaximum number of work items to fetch (default: 50)
--output-oOutput format: json or csv. Defaults to a table

list — List work items

devops list
devops list -s Active
devops list -t Bug -a me
devops list -p MyProject -s "In Progress"-t Task
devops list -P 1234# only children of work item 1234
devops list -n 200# fetch up to 200 items instead of the default 50
devops list -s Active -o json # machine-readable output for scripting
devops list -q "[System.IterationPath] UNDER 'MyProject\\Sprint 1'"

Queries fetch up to --top items (default 50). When more match than were fetched, the footer says so — for example Showing 50 of 312 work items - use --top to fetch more. Items are retrieved in batches of 200 behind the scenes, which is the maximum the Azure DevOps batch endpoint accepts.

OptionAliasDescription
--project-pProject name (uses default if configured)
--state-sFilter by state (e.g., Active, Closed, Resolved)
--type-tFilter by work item type (e.g., Task, Bug, User Story)
--assigned-to-aFilter by assignee. Use me for the current user
--query-qWIQL WHERE clause for advanced filtering
--parent-PFilter by parent work item ID
--top-nMaximum number of work items to fetch (default: 50)
--output-oOutput format: json or csv. Defaults to a table

create — Create a work item

On creation, the active iteration and the team's default area path are resolved automatically. Use --iteration or --area to override. The resolved values are cached for the rest of the month.

devops create -t "Fix login bug"
devops create -t "New auth endpoint"--type "User Story"-s Active -a me -P 2
devops create -t "Implement service"-e 6-y Development -R 1234
devops create -t "Backlog item"-I "MyProject\Backlog"
devops create -t "Task with custom field"-f"Custom.SomeField=value"-f"Custom.Another=other"
devops create -t "Análise Técnica"-R 1234--normalize # title becomes "PBI 1234 - Análise Técnica"
OptionAliasDescription
--title-tWork item title (required)
--project-pProject name (uses default if configured)
--typeWork item type (default: Task)
--state-sInitial state (e.g., New, Active)
--assigned-to-aAssignee email or display name. Use me for the current user
--description-dDescription
--priority-PPriority from 1 (highest) to 4 (lowest)
--iteration-IIteration path. Defaults to the active sprint of the configured team
--area-AArea path. Defaults to the team's default area
--estimate-eEstimated work in hours (Custom.EstimateWork)
--activity-type-yActivity type (Microsoft.VSTS.Common.Activity), e.g., Development, Testing, Design
--field-fCustom field in Key=Value format. Repeatable for multiple fields
--related-id-RID of the work item to relate to
--relation-type-rRelation type (default: parent). See table below
--normalizePrefix the title with the parent type and ID (e.g., PBI 1234 - <title>). Requires a parent relation

Relation types:

ValueDescription
parentThe new item is a child of the specified item
childThe new item is a parent of the specified item
relatedGeneric relation
blocksThe new item blocks the specified item
blocked-byThe new item is blocked by the specified item

update — Update a work item

Only fields explicitly provided are updated. No field has a default that causes unintended writes.

devops update -i 1234-s Closed
devops update -i 1234-t "New title"-P 1
devops update -i 1234-a me -e 8-y Testing
devops update -i 1234-c "Dependency resolved."
devops update -i 1234-I "MyProject\Sprint 4"
devops update -i 1234-R 5678--relation-type blocks
devops update -i 1234-f"Custom.ReviewEstimate=2"
OptionAliasDescription
--id-iWork item ID (required)
--project-pProject name (uses default if configured)
--title-tNew title
--state-sNew state
--assigned-to-aNew assignee. Use me for the current user
--description-dNew description
--priority-PNew priority (1–4)
--iteration-INew iteration path
--area-ANew area path
--estimate-eEstimated work in hours (Custom.EstimateWork)
--activity-type-yActivity type (e.g., Development, Testing, Design)
--field-fCustom field in Key=Value format. Repeatable
--comment-cAdd a comment to the work item history
--related-id-RID of the work item to relate to
--relation-type-rRelation type (default: related). See create for valid values

normalize — Normalize task titles with a parent prefix

Renames Tasks whose title follows the [Role] Description pattern (e.g., created by third parties) to <PARENT_TYPE> <PARENT_ID> - [Role] Description. The parent type is abbreviated: Product Backlog Item becomes PBI; any other type is uppercased (e.g., Bug -> BUG). Tasks already normalized or without a parent are skipped. By default only Tasks assigned to the current user are processed.

devops normalize # normalize my tasks
devops normalize --dry-run # preview without applying
devops normalize -s Active # only tasks in a given state
devops normalize -P 1234# only children of work item 1234
devops normalize -a any # all tasks, regardless of assignee
OptionAliasDescription
--project-pProject name (uses default if configured)
--state-sFilter by state
--assigned-to-aFilter by assignee. Use me (default) or any for all
--parent-PRestrict to children of a specific parent ID
--dry-runPreview changes without applying them
--top-nMaximum number of tasks to fetch (default: 200)

comment — Add a comment to a work item

devops comment -i 1234"Dependency resolved, ready for review."
devops comment -i 1234"Blocked by infra team."-p AnotherProject
OptionAliasDescription
--id-iWork item ID (required)
messageComment text (positional, required)
--project-pProject name (uses default if configured)

state — Change the state of one or more work items

Shortcut for update --state. Fetches the current state first and shows the full transition in the output. Multiple IDs are processed in parallel.

devops state -i 1234-s "In Progress"
devops state -i 123456789012-s "Closed"
devops state -i 1234-s "Done"-p AnotherProject

Output: Work item #1234: To Do -> In Progress

OptionAliasDescription
--id-iWork item ID (required). Multiple IDs space-separated: -i 1 2 3
--state-sTarget state (required, e.g. In Progress, Closed, Done)
--project-pProject name (uses default if configured)

delete — Delete one or more work items

Moves the work items to the project recycle bin (recoverable, not a permanent delete). Prompts for confirmation unless --force. Multiple IDs are processed in parallel.

devops delete -i 1234
devops delete -i 123456789012
devops delete -i 1234--force
OptionAliasDescription
--id-iWork item ID(s) (required). Space-separated: -i 1 2 3
--project-pProject name (uses default if configured)
--forceSkip the confirmation prompt

open — Open a work item in the browser

devops open -i 1234
devops open -i 1234-p AnotherProject
OptionAliasDescription
--id-iWork item ID (required)
--project-pProject name (uses default if configured)

Pipeline Commands

pipelines — List available pipelines

devops pipelines
devops pipelines -n "deploy"
devops pipelines -p AnotherProject
OptionAliasDescription
--project-pProject name (uses default if configured)
--name-nFilter by pipeline name (partial match)

runs — List recent runs of a pipeline

Shows the most recent runs of a pipeline, newest first. Use the pipeline ID from pipelines.

devops runs -i 42
devops runs -i 42-n 25
devops runs -i 42-p AnotherProject

Output columns: ID, NAME, STATE (e.g. inProgress, completed), RESULT (e.g. succeeded, failed; - while still running), CREATED.

OptionAliasDescription
--id-iPipeline (definition) ID (required). See pipelines
--project-pProject name (uses default if configured)
--top-nNumber of most recent runs to show (default: 10)

run — Queue a new pipeline run

Triggers a new run of a pipeline. Without --branch, it runs the pipeline's default branch.

devops run -i 42
devops run -i 42-b main
devops run -i 42-b refs/heads/release/1.0-p AnotherProject

On success it prints the new run ID, its state, and a link to follow it in the browser.

OptionAliasDescription
--id-iPipeline (definition) ID (required). See pipelines
--project-pProject name (uses default if configured)
--branch-bBranch to run (main or refs/heads/main). Defaults to the pipeline's default branch

Pull Request Commands

Pull requests belong to a repository, specified with --repo (required for pr-create, optional filter for pr-list). pr-get, pr-open, pr-vote, pr-comment, pr-abandon and pr-complete work by PR ID and resolve the repository automatically, so they need neither project nor repo.

pr-list --mine and pr-vote need your user ID, which is captured during config --login / config --pat. If they report a missing user ID, re-run config to refresh it.

pr-list — List pull requests

devops pr-list
devops pr-list -r MyRepo -s active
devops pr-list -r MyRepo -t main
devops pr-list -s all -n 50
devops pr-list --mine
OptionAliasDescription
--project-pProject name (uses default if configured)
--repo-rRepository name. If omitted, lists across all repos in the project
--status-sactive (default), completed, abandoned, or all
--target-tFilter by target branch (e.g., main)
--top-nMaximum number of PRs to show (default: 25)
--mineOnly pull requests you created

pr-get — Show pull request details

devops pr-get -i 123

Shows status, source/target branches, author, reviewers with their votes, the web URL, and the description.

OptionAliasDescription
--id-iPull request ID (required)

pr-create — Create a pull request

devops pr-create -r MyRepo -s feature/login -t main --title "Add login"
devops pr-create -r MyRepo -t main --title "Add login"# source = current git branch
devops pr-create -r MyRepo -t main --title "WIP"-d "Details..."--draft
devops pr-create -r MyRepo -t main --title "Add login"--reviewers me,jane@contoso.com
devops pr-create -r MyRepo -t main --title "Add login"-w 1234,1235

Branches accept either the short name (main) or the full ref (refs/heads/main). When --source is omitted, the current git branch is detected automatically from .git/HEAD.

--reviewers accepts me (you), a reviewer GUID, or an email / display name (resolved through the Identities API). --work-item links existing work items to the new PR.

OptionAliasDescription
--repo-rRepository name (required)
--source-sSource branch (defaults to the current git branch)
--target-tTarget branch (required)
--titlePull request title (required)
--description-dPull request description
--draftCreate as a draft
--reviewersReviewers to add: me, a GUID, or email/display name (comma-separated)
--work-item-wWork item IDs to link to the pull request (comma-separated)
--project-pProject name (uses default if configured)

pr-open — Open a pull request in the browser

devops pr-open -i 123
OptionAliasDescription
--id-iPull request ID (required)

pr-vote — Vote on a pull request

Casts your vote (self-adding as a reviewer if needed). Works by PR ID; the repository is resolved automatically.

devops pr-vote -i 123-v approve
devops pr-vote -i 123-v reject
devops pr-vote -i 123-v reset
OptionAliasDescription
--id-iPull request ID (required)
--vote-vapprove, approve-suggestions, reject, wait, or reset (required)

pr-comment — Add a comment to a pull request

Posts a top-level comment thread. Works by PR ID; the repository is resolved automatically.

devops pr-comment -i 123-m "Looks good, one nit on the naming."
OptionAliasDescription
--id-iPull request ID (required)
--message-mComment text (required)

pr-abandon — Abandon a pull request

devops pr-abandon -i 123
OptionAliasDescription
--id-iPull request ID (required)

pr-complete — Complete (merge) a pull request

Merges the PR using its last merge source commit. Fails if the PR has no merge commit (e.g. a draft or with conflicts).

devops pr-complete -i 123
devops pr-complete -i 123--delete-source
OptionAliasDescription
--id-iPull request ID (required)
--delete-sourceDelete the source branch after completing

Authentication

The CLI supports two authentication methods. Your choice is stored in config.json as the active auth mode and switching is just a matter of re-running config.

Microsoft Entra ID (recommended)

devops config -o https://dev.azure.com/myorg
devops config --login

Signs in through an interactive browser flow (MSAL), using the well-known public client of the Azure CLI — no app registration is required. The token is scoped to Azure DevOps (499b84ac-1321-427f-aa17-267ca6975798/.default) and cached securely (DPAPI on Windows, the keychain on macOS, an encrypted file on Linux), then refreshed silently. Your effective permissions are those your account already has in the organization.

The Azure CLI (az) does not need to be installed. The sign-in is performed by MSAL inside the CLI; only the Azure CLI's public client ID is reused as an identifier. (This does require that the "Microsoft Azure CLI" application itself is allowed in your Entra tenant.)

If your account is a guest in another tenant or your organization enforces Conditional Access, pass the tenant explicitly:

devops config --login --tenant contoso.onmicrosoft.com

Entra sign-in requires the organization policy "Allow access via Microsoft Entra authentication" to be enabled (on by default for Entra-backed organizations).

Session lifetime. Access tokens are refreshed silently, so you normally sign in once and stay authenticated for weeks — the token cache survives terminal restarts and reboots. A new interactive sign-in is only needed after long inactivity, a credential change, or when your organization's Conditional Access policy requires it. In those cases a regular command stops with a clear message asking you to run devops config --login again; the CLI never opens a browser unexpectedly during other commands (keeping it safe for scripts and CI).

Personal Access Token

devops config -o https://dev.azure.com/myorg -P <PAT>

Go to User Settings → Personal Access Tokens → New Token and grant the following scopes:

ScopePermissionUsed by
Work ItemsRead & WriteAll work item commands
CodeRead & WritePull request commands. Read covers pr-list/pr-get/pr-open; pr-create needs Read & Write
BuildRead & Executepipelines and runs need Read; queueing with run needs Execute

The PAT is stored encrypted (DPAPI) on Windows and in an owner-only file (600) on Linux/macOS.

About

A .NET CLI for Azure DevOps work items talking directly to the REST API - no az CLI required.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages